Files
Upsilon/apps/shared/interactive_curve_view_range.cpp
Gabriel Ozouf 8104caea50 [apps/shared] New automatic zoom on curves
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
2020-11-04 15:30:53 +01:00

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);
}
}
}