mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 08:47:28 +01:00
Initial zoom for displaying curves is computed according to the
following rules :
- For polar and parametric curves, the algorithm has not changed.
Vertical and horizontal ranges are chosen in order to display the
full curve with orthonormal graduations.
- For cartesian curves, the horizontal range is chosen in order to
display all points of interest (roots, extrema, asymptotes...).
The vertical range is fitted to be able to display those points, but
also cuts out points outside of the function's order of magnitude.
Change-Id: Idf8233fc2e6586b85d34c4da152c83e75513d85c
229 lines
9.1 KiB
C++
229 lines
9.1 KiB
C++
#include "interactive_curve_view_range.h"
|
|
#include <ion.h>
|
|
#include <cmath>
|
|
#include <stddef.h>
|
|
#include <assert.h>
|
|
#include <poincare/preferences.h>
|
|
#include <algorithm>
|
|
|
|
using namespace Poincare;
|
|
|
|
namespace Shared {
|
|
|
|
uint32_t InteractiveCurveViewRange::rangeChecksum() {
|
|
float data[5] = {xMin(), xMax(), yMin(), yMax(), m_yAuto ? 1.0f : 0.0f};
|
|
size_t dataLengthInBytes = 5*sizeof(float);
|
|
assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4
|
|
return Ion::crc32Word((uint32_t *)data, dataLengthInBytes/sizeof(uint32_t));
|
|
}
|
|
|
|
void InteractiveCurveViewRange::setYAuto(bool yAuto) {
|
|
m_yAuto = yAuto;
|
|
notifyRangeChange();
|
|
}
|
|
|
|
void InteractiveCurveViewRange::setXMin(float xMin) {
|
|
MemoizedCurveViewRange::protectedSetXMin(xMin, k_lowerMaxFloat, k_upperMaxFloat);
|
|
notifyRangeChange();
|
|
}
|
|
|
|
void InteractiveCurveViewRange::setXMax(float xMax) {
|
|
MemoizedCurveViewRange::protectedSetXMax(xMax, k_lowerMaxFloat, k_upperMaxFloat);
|
|
notifyRangeChange();
|
|
}
|
|
|
|
void InteractiveCurveViewRange::setYMin(float yMin) {
|
|
MemoizedCurveViewRange::protectedSetYMin(yMin, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
|
|
void InteractiveCurveViewRange::setYMax(float yMax) {
|
|
MemoizedCurveViewRange::protectedSetYMax(yMax, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
|
|
void InteractiveCurveViewRange::zoom(float ratio, float x, float y) {
|
|
float xMi = xMin();
|
|
float xMa = xMax();
|
|
float yMi = yMin();
|
|
float yMa = yMax();
|
|
if (ratio*std::fabs(xMa-xMi) < Range1D::k_minFloat || ratio*std::fabs(yMa-yMi) < Range1D::k_minFloat) {
|
|
return;
|
|
}
|
|
float centerX = std::isnan(x) || std::isinf(x) ? xCenter() : x;
|
|
float centerY = std::isnan(y) || std::isinf(y) ? yCenter() : y;
|
|
float newXMin = centerX*(1.0f-ratio)+ratio*xMi;
|
|
float newXMax = centerX*(1.0f-ratio)+ratio*xMa;
|
|
m_yAuto = false;
|
|
if (!std::isnan(newXMin) && !std::isnan(newXMax)) {
|
|
m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
float newYMin = centerY*(1.0f-ratio)+ratio*yMi;
|
|
float newYMax = centerY*(1.0f-ratio)+ratio*yMa;
|
|
if (!std::isnan(newYMin) && !std::isnan(newYMax)) {
|
|
m_yRange.setMax(newYMax, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
}
|
|
|
|
void InteractiveCurveViewRange::panWithVector(float x, float y) {
|
|
m_yAuto = false;
|
|
if (clipped(xMin() + x, false) != xMin() + x || clipped(xMax() + x, true) != xMax() + x || clipped(yMin() + y, false) != yMin() + y || clipped(yMax() + y, true) != yMax() + y || std::isnan(clipped(xMin() + x, false)) || std::isnan(clipped(xMax() + x, true)) || std::isnan(clipped(yMin() + y, false)) || std::isnan(clipped(yMax() + y, true))) {
|
|
return;
|
|
}
|
|
m_xRange.setMax(xMax()+x, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetXMin(xMin() + x, k_lowerMaxFloat, k_upperMaxFloat);
|
|
m_yRange.setMax(yMax()+y, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetYMin(yMin() + y, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
|
|
void InteractiveCurveViewRange::roundAbscissa() {
|
|
// Set x range
|
|
float newXMin = std::round(xCenter()) - (float)Ion::Display::Width/2.0f;
|
|
float newXMax = std::round(xCenter()) + (float)Ion::Display::Width/2.0f-1.0f;
|
|
if (std::isnan(newXMin) || std::isnan(newXMax)) {
|
|
return;
|
|
}
|
|
m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat);
|
|
setXMin(newXMin);
|
|
}
|
|
|
|
void InteractiveCurveViewRange::normalize() {
|
|
/* We center the ranges on the current range center, and put each axis so that
|
|
* 1cm = 2 current units. */
|
|
|
|
m_yAuto = false;
|
|
|
|
float xRange = xMax() - xMin();
|
|
float yRange = yMax() - yMin();
|
|
float xyRatio = xRange/yRange;
|
|
|
|
const float unit = std::max(xGridUnit(), yGridUnit());
|
|
const float newXHalfRange = NormalizedXHalfRange(unit);
|
|
const float newYHalfRange = NormalizedYHalfRange(unit);
|
|
float normalizedXYRatio = newXHalfRange/newYHalfRange;
|
|
if (xyRatio < normalizedXYRatio) {
|
|
float newXRange = normalizedXYRatio * yRange;
|
|
assert(newXRange > xRange);
|
|
float delta = (newXRange - xRange) / 2.0f;
|
|
m_xRange.setMin(xMin() - delta, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetXMax(xMax()+delta, k_lowerMaxFloat, k_upperMaxFloat);
|
|
} else if (xyRatio > normalizedXYRatio) {
|
|
float newYRange = newYHalfRange/newXHalfRange * xRange;
|
|
assert(newYRange > yRange);
|
|
float delta = (newYRange - yRange) / 2.0f;
|
|
m_yRange.setMin(yMin() - delta, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetYMax(yMax()+delta, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
}
|
|
|
|
void InteractiveCurveViewRange::setTrigonometric() {
|
|
m_yAuto = false;
|
|
// Set x range
|
|
float x = (Preferences::sharedPreferences()->angleUnit() == Preferences::AngleUnit::Degree) ? 600.0f : 10.5f;
|
|
m_xRange.setMax(x, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetXMin(-x, k_lowerMaxFloat, k_upperMaxFloat);
|
|
// Set y range
|
|
float y = 1.6f;
|
|
m_yRange.setMax(y, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetYMin(-y, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
|
|
void InteractiveCurveViewRange::setDefault() {
|
|
if (m_delegate == nullptr) {
|
|
return;
|
|
}
|
|
|
|
// Compute the interesting range
|
|
m_yAuto = false;
|
|
float xm, xM, ym, yM;
|
|
m_delegate->interestingRanges(&xm, &xM, &ym, &yM);
|
|
m_xRange.setMin(xm, k_lowerMaxFloat, k_upperMaxFloat);
|
|
m_xRange.setMax(xM, k_lowerMaxFloat, k_upperMaxFloat);
|
|
m_yRange.setMin(ym, k_lowerMaxFloat, k_upperMaxFloat);
|
|
m_yRange.setMax(yM, k_lowerMaxFloat, k_upperMaxFloat);
|
|
|
|
// Add margins
|
|
float xRange = xMax() - xMin();
|
|
float yRange = yMax() - yMin();
|
|
m_xRange.setMin(m_delegate->addMargin(xMin(), xRange, false, true), k_lowerMaxFloat, k_upperMaxFloat);
|
|
// Use MemoizedCurveViewRange::protectedSetXMax to update xGridUnit
|
|
MemoizedCurveViewRange::protectedSetXMax(m_delegate->addMargin(xMax(), xRange, false, false), k_lowerMaxFloat, k_upperMaxFloat);
|
|
m_yRange.setMin(m_delegate->addMargin(yMin(), yRange, true, true), k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetYMax(m_delegate->addMargin(yMax(), yRange, true, false), k_lowerMaxFloat, k_upperMaxFloat);
|
|
|
|
if (!m_delegate->defaultRangeIsNormalized()) {
|
|
return;
|
|
}
|
|
|
|
// Normalize the axes, so that a polar circle is displayed as a circle
|
|
normalize();
|
|
}
|
|
|
|
void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) {
|
|
if (std::isnan(position)) {
|
|
return;
|
|
}
|
|
if (axis == Axis::X) {
|
|
float range = xMax() - xMin();
|
|
if (std::fabs(position/range) > k_maxRatioPositionRange) {
|
|
range = Range1D::defaultRangeLengthFor(position);
|
|
}
|
|
m_xRange.setMax(position + range/2.0f, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetXMin(position - range/2.0f, k_lowerMaxFloat, k_upperMaxFloat);
|
|
} else {
|
|
m_yAuto = false;
|
|
float range = yMax() - yMin();
|
|
if (std::fabs(position/range) > k_maxRatioPositionRange) {
|
|
range = Range1D::defaultRangeLengthFor(position);
|
|
}
|
|
m_yRange.setMax(position + range/2.0f, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetYMin(position - range/2.0f, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
}
|
|
|
|
void InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float topMarginRatio, float rightMarginRatio, float bottomMarginRatio, float leftMarginRatio, float pixelWidth) {
|
|
if (!std::isinf(x) && !std::isnan(x)) {
|
|
const float xRange = xMax() - xMin();
|
|
const float leftMargin = leftMarginRatio * xRange;
|
|
if (x < xMin() + leftMargin) {
|
|
m_yAuto = false;
|
|
/* The panning increment is a whole number of pixels so that the caching
|
|
* for cartesian functions is not invalidated. */
|
|
const float newXMin = std::floor((x - leftMargin - xMin()) / pixelWidth) * pixelWidth + xMin();
|
|
m_xRange.setMax(newXMin + xRange, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetXMin(newXMin, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
const float rightMargin = rightMarginRatio * xRange;
|
|
if (x > xMax() - rightMargin) {
|
|
m_yAuto = false;
|
|
const float newXMax = std::ceil((x + rightMargin - xMax()) / pixelWidth) * pixelWidth + xMax();
|
|
m_xRange.setMax(newXMax, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetXMin(xMax() - xRange, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
}
|
|
if (!std::isinf(y) && !std::isnan(y)) {
|
|
const float yRange = yMax() - yMin();
|
|
const float bottomMargin = bottomMarginRatio * yRange;
|
|
if (y < yMin() + bottomMargin) {
|
|
m_yAuto = false;
|
|
const float newYMin = y - bottomMargin;
|
|
m_yRange.setMax(newYMin + yRange, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetYMin(newYMin, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
const float topMargin = topMarginRatio * yRange;
|
|
if (y > yMax() - topMargin) {
|
|
m_yAuto = false;
|
|
m_yRange.setMax(y + topMargin, k_lowerMaxFloat, k_upperMaxFloat);
|
|
MemoizedCurveViewRange::protectedSetYMin(yMax() - yRange, k_lowerMaxFloat, k_upperMaxFloat);
|
|
}
|
|
}
|
|
}
|
|
|
|
void InteractiveCurveViewRange::notifyRangeChange() {
|
|
if (m_delegate) {
|
|
m_delegate->didChangeRange(this);
|
|
}
|
|
}
|
|
|
|
}
|