[apps/shared] Change Zoom API

Moved some code around to decrease redundancy and put more of the logic
into Poincare::Zoom

Change-Id: I4804cf39493ac7f2f0b3c4eb554e5c15c3cef1c9
This commit is contained in:
Gabriel Ozouf
2020-10-13 12:19:26 +02:00
committed by Émilie Feral
parent 8572f4953c
commit 6be5e7d62c
11 changed files with 93 additions and 128 deletions

View File

@@ -139,31 +139,27 @@ int Store::nextDot(int series, int direction, int dot) {
/* Window */
void Store::setDefault() {
float minX = FLT_MAX;
float maxX = -FLT_MAX;
float min, max;
float mins[k_numberOfSeries], maxs[k_numberOfSeries];
for (int series = 0; series < k_numberOfSeries; series++) {
if (!seriesIsEmpty(series)) {
minX = std::min(minX, minValueOfColumn(series, 0));
maxX = std::max(maxX, maxValueOfColumn(series, 0));
}
bool empty = seriesIsEmpty(series);
mins[series] = empty ? NAN : minValueOfColumn(series, 0);
maxs[series] = empty ? NAN : maxValueOfColumn(series, 0);
}
float range = maxX - minX;
setXMin(minX - k_displayHorizontalMarginRatio * range);
setXMax(maxX + k_displayHorizontalMarginRatio * range);
Poincare::Zoom::CombineRanges(k_numberOfSeries, mins, maxs, &min, &max);
float range = max - min;
setXMin(min - k_displayHorizontalMarginRatio * range);
setXMax(max + k_displayHorizontalMarginRatio * range);
float minY = FLT_MAX;
float maxY = -FLT_MAX;
for (int series = 0; series < k_numberOfSeries; series++) {
for (int k = 0; k < numberOfPairsOfSeries(series); k++) {
if (xMin() <= get(series, 0, k) && get(series, 0, k) <= xMax()) {
minY = std::min<float>(minY, get(series, 1, k));
maxY = std::max<float>(maxY, get(series, 1, k));
}
}
bool empty = seriesIsEmpty(series);
mins[series] = empty ? NAN : minValueOfColumn(series, 1);
maxs[series] = empty ? NAN : maxValueOfColumn(series, 1);
}
range = maxY - minY;
setYMin(m_delegate->addMargin(minY, range, true, true));
setYMax(m_delegate->addMargin(maxY, range, true, false));
Poincare::Zoom::CombineRanges(k_numberOfSeries, mins, maxs, &min, &max);
range = max - min;
setYMin(m_delegate->addMargin(min, range, true, true));
setYMax(m_delegate->addMargin(max, range, true, false));
}
/* Series */

View File

@@ -46,35 +46,6 @@ float GraphController::interestingXMin() const {
return nmin;
}
void GraphController::interestingRanges(InteractiveCurveViewRange * range) const {
int nmin = INT_MAX;
int nmax = 0;
int nbOfActiveModels = functionStore()->numberOfActiveFunctions();
for (int i = 0; i < nbOfActiveModels; i++) {
Shared::Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i));
int firstInterestingIndex = s->initialRank();
nmin = std::min(nmin, firstInterestingIndex);
nmax = std::max(nmax, firstInterestingIndex + static_cast<int>(k_defaultXHalfRange));
}
assert(nmax - nmin >= k_defaultXHalfRange);
range->setXMin(nmin);
range->setXMax(nmax);
Context * context = textFieldDelegateApp()->localContext();
float yMin = FLT_MAX, yMax = -FLT_MAX;
for (int i = 0; i < nbOfActiveModels; i++) {
Shared::Sequence * s = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i));
Zoom::ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) {
return static_cast<const Shared::Sequence *>(auxiliary)->evaluateXYAtParameter(x, context).x2();
};
Zoom::RefinedYRangeForDisplay(evaluation, nmin, nmax, &yMin, &yMax, context, s);
}
range->setYMin(yMin);
range->setYMax(yMax);
}
bool GraphController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) {
Shared::TextFieldDelegateApp * myApp = textFieldDelegateApp();
double floatBody;

View File

@@ -20,7 +20,6 @@ public:
TermSumController * termSumController() { return &m_termSumController; }
// InteractiveCurveViewRangeDelegate
float interestingXMin() const override;
void interestingRanges(Shared::InteractiveCurveViewRange * range) const override;
bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override;
private:
Shared::XYBannerView * bannerView() override { return &m_bannerView; }

View File

@@ -262,38 +262,35 @@ void ContinuousFunction::setTMax(float tMax) {
}
void ContinuousFunction::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const {
if (plotType() == PlotType::Cartesian) {
protectedRangeForDisplay(xMin, xMax, yMin, yMax, context, true);
if (plotType() != PlotType::Cartesian) {
assert(std::isfinite(tMin()) && std::isfinite(tMax()) && std::isfinite(rangeStep()) && rangeStep() > 0);
protectedFullRangeForDisplay(tMin(), tMax(), rangeStep(), xMin, xMax, context, true);
protectedFullRangeForDisplay(tMin(), tMax(), rangeStep(), yMin, yMax, context, false);
return;
}
Zoom::ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) {
/* When evaluating sin(x)/x close to zero using the standard sine function,
* one can detect small variations, while the cardinal sine is supposed to be
* locally monotonous. To smooth our such variations, we round the result of
* the evaluations. As we are not interested in precise results but only in
* ordering, this approximation is sufficient. */
constexpr float precision = 1e-5;
return precision * std::round(static_cast<const Function *>(auxiliary)->evaluateXYAtParameter(x, context).x2() / precision);
};
bool fullyComputed = Zoom::InterestingRangesForDisplay(evaluation, xMin, xMax, yMin, yMax, tMin(), tMax(), context, this);
evaluation = [](float x, Context * context, const void * auxiliary) {
return static_cast<const Function *>(auxiliary)->evaluateXYAtParameter(x, context).x2();
};
if (fullyComputed) {
Zoom::RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, this);
} else {
fullXYRange(xMin, xMax, yMin, yMax, context);
Zoom::RangeWithRatioForDisplay(evaluation, InteractiveCurveViewRange::NormalYXRatio(), xMin, xMax, yMin, yMax, context, this);
}
}
void ContinuousFunction::fullXYRange(float * xMin, float * xMax, float * yMin, float * yMax, Context * context) const {
assert(yMin && yMax);
assert(!(std::isinf(tMin()) || std::isinf(tMax()) || std::isnan(rangeStep())));
float resultXMin = FLT_MAX, resultXMax = - FLT_MAX, resultYMin = FLT_MAX, resultYMax = - FLT_MAX;
for (float t = tMin(); t <= tMax(); t += rangeStep()) {
Coordinate2D<float> xy = privateEvaluateXYAtParameter(t, context);
if (!std::isfinite(xy.x1()) || !std::isfinite(xy.x2())) {
continue;
}
resultXMin = std::min(xy.x1(), resultXMin);
resultXMax = std::max(xy.x1(), resultXMax);
resultYMin = std::min(xy.x2(), resultYMin);
resultYMax = std::max(xy.x2(), resultYMax);
}
if (xMin) {
*xMin = resultXMin;
}
if (xMax) {
*xMax = resultXMax;
}
*yMin = resultYMin;
*yMax = resultYMax;
}
void * ContinuousFunction::Model::expressionAddress(const Ion::Storage::Record * record) const {
return (char *)record->value().buffer+sizeof(RecordDataBuffer);
}

View File

@@ -80,26 +80,19 @@ Function::RecordDataBuffer * Function::recordData() const {
return reinterpret_cast<RecordDataBuffer *>(const_cast<void *>(d.buffer));
}
void Function::protectedRangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool boundByMagnitude) const {
Zoom::ValueAtAbscissa evaluation = [](float x, Context * context, const void * auxiliary) {
/* When evaluating sin(x)/x close to zero using the standard sine function,
* one can detect small variations, while the cardinal sine is supposed to be
* locally monotonous. To smooth our such variations, we round the result of
* the evaluations. As we are not interested in precise results but only in
* ordering, this approximation is sufficient. */
constexpr float precision = 1e-5;
return precision * std::round(static_cast<const Function *>(auxiliary)->evaluateXYAtParameter(x, context).x2() / precision);
};
bool fullyComputed = Zoom::InterestingRangesForDisplay(evaluation, xMin, xMax, yMin, yMax, tMin(), tMax(), context, this);
evaluation = [](float x, Context * context, const void * auxiliary) {
return static_cast<const Function *>(auxiliary)->evaluateXYAtParameter(x, context).x2();
};
if (fullyComputed) {
Zoom::RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, this, boundByMagnitude);
void Function::protectedFullRangeForDisplay(float tMin, float tMax, float tStep, float * min, float * max, Poincare::Context * context, bool xRange) const {
Poincare::Zoom::ValueAtAbscissa evaluation;
if (xRange) {
evaluation = [](float x, Poincare::Context * context, const void * auxiliary) {
return static_cast<const Function *>(auxiliary)->evaluateXYAtParameter(x, context).x1();
};
} else {
Zoom::RangeWithRatioForDisplay(evaluation, InteractiveCurveViewRange::NormalYXRatio(), xMin, xMax, yMin, yMax, context, this);
evaluation = [](float x, Poincare::Context * context, const void * auxiliary) {
return static_cast<const Function *>(auxiliary)->evaluateXYAtParameter(x, context).x2();
};
}
Poincare::Zoom::FullRange(evaluation, tMin, tMax, tStep, min, max, context, this);
}
}

View File

@@ -93,7 +93,7 @@ protected:
bool m_active;
};
void protectedRangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context, bool boundByMagnitude) const;
void protectedFullRangeForDisplay(float tMin, float tMax, float tStep, float * min, float * max, Poincare::Context * context, bool xRange) const;
private:
RecordDataBuffer * recordData() const;

View File

@@ -138,22 +138,23 @@ int FunctionGraphController::numberOfCurves() const {
void FunctionGraphController::interestingRanges(InteractiveCurveViewRange * range) const {
Poincare::Context * context = textFieldDelegateApp()->localContext();
float resultXMin = FLT_MAX;
float resultXMax = -FLT_MAX;
float resultYMin = FLT_MAX;
float resultYMax = -FLT_MAX;
assert(functionStore()->numberOfActiveFunctions() > 0);
int functionsCount = functionStore()->numberOfActiveFunctions();
for (int i = 0; i < functionsCount; i++) {
constexpr int maxLength = 10;
float xMins[maxLength], xMaxs[maxLength], yMins[maxLength], yMaxs[maxLength];
int length = functionStore()->numberOfActiveFunctions();
for (int i = 0; i < length; i++) {
ExpiringPointer<Function> f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i));
f->rangeForDisplay(&resultXMin, &resultXMax, &resultYMin, &resultYMax, context);
f->rangeForDisplay(xMins + i, xMaxs + i, yMins + i, yMaxs + i, context);
}
range->setXMin(resultXMin);
range->setXMax(resultXMax);
range->setYMin(resultYMin);
range->setYMax(resultYMax);
/* We can only call this method once the X range has been fully computed. */
float xMin, xMax, yMin, yMax;
Poincare::Zoom::CombineRanges(length, xMins, xMaxs, &xMin, &xMax);
Poincare::Zoom::CombineRanges(length, yMins, yMaxs, &yMin, &yMax);
range->setXMin(xMin);
range->setXMax(xMax);
range->setYMin(yMin);
range->setYMax(yMax);
yRangeForCursorFirstMove(range);
}

View File

@@ -9,6 +9,7 @@
#include <poincare/integer.h>
#include <poincare/rational.h>
#include <poincare/addition.h>
#include <poincare/zoom.h>
#include "../shared/poincare_helpers.h"
#include <string.h>
#include <apps/i18n.h>
@@ -310,6 +311,15 @@ Expression Sequence::sumBetweenBounds(double start, double end, Poincare::Contex
return Float<double>::Builder(result);
}
void Sequence::rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const {
Poincare::Zoom::ValueAtAbscissa evaluation = [](float x, Poincare::Context * context, const void * auxiliary) {
return static_cast<float>(static_cast<const Shared::Sequence *>(auxiliary)->initialRank());
};
Poincare::Zoom::FullRange(evaluation, 0, 1, 1, xMin, xMax, context, this);
*xMax += Poincare::Zoom::k_defaultHalfRange;
protectedFullRangeForDisplay(*xMin, *xMax, 1.f, yMin, yMax, context, false);
}
Sequence::RecordDataBuffer * Sequence::recordData() const {
assert(!isNull());
Ion::Storage::Record::Data d = value();

View File

@@ -76,7 +76,7 @@ public:
constexpr static int k_initialRankNumberOfDigits = 3; // m_initialRank is capped by 999
//Range
void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const override { protectedRangeForDisplay(xMin, xMax, yMin, yMax, context, false); };
void rangeForDisplay(float * xMin, float * xMax, float * yMin, float * yMax, Poincare::Context * context) const override;
private:
constexpr static const KDFont * k_layoutFont = KDFont::LargeFont;

View File

@@ -19,7 +19,7 @@ public:
/* Return false if the X range was given a default value because there were
* no points of interest. */
static bool InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin, float * xMax, float * yMin, float * yMax, float tMin, float tMax, Context * context, const void * auxiliary);
static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, bool boundByMagnitude = false);
static void RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary);
static void RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary);
static void FullRange(ValueAtAbscissa evaluation, float tMin, float tMax, float tStep, float * fMin, float * fMax, Context * context, const void * auxiliary);

View File

@@ -147,16 +147,15 @@ bool Zoom::InterestingRangesForDisplay(ValueAtAbscissa evaluation, float * xMin,
resultX[0] -= k_breathingRoom * xRange;
resultX[1] += k_breathingRoom * xRange;
}
*xMin = std::min(resultX[0], *xMin);
*xMax = std::max(resultX[1], *xMax);
*yMin = std::min(resultYMin, *yMin);
*yMax = std::max(resultYMax, *yMax);
*xMin = resultX[0];
*xMax = resultX[1];
*yMin = resultYMin;
*yMax = resultYMax;
return true;
}
void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary, bool boundByMagnitude) {
void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) {
/* This methods computes the Y range that will be displayed for cartesian
* functions and sequences, given an X range (xMin, xMax) and bounds yMin and
* yMax that must be inside the Y range.*/
@@ -184,13 +183,12 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float xMin, float
/* sum/pop is the log mean value of the function, which can be interpreted as
* its average order of magnitude. Then, bound is the value for the next
* order of magnitude and is used to cut the Y range. */
if (boundByMagnitude) {
float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX;
sampleYMin = std::max(sampleYMin, - bound);
sampleYMax = std::min(sampleYMax, bound);
}
*yMin = std::min(*yMin, sampleYMin);
*yMax = std::max(*yMax, sampleYMax);
float bound = (pop > 0) ? std::exp(sum / pop + 1.f) : FLT_MAX;
sampleYMin = std::max(sampleYMin, - bound);
sampleYMax = std::min(sampleYMax, bound);
*yMin = sampleYMin;
*yMax = sampleYMax;
if (*yMin == *yMax) {
RangeFromSingleValue(*yMin, yMin, yMax);
}
@@ -257,7 +255,7 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f
while (xMagnitude < k_maximalDistance) {
for(const float unit : units) {
const float xRange = unit * xMagnitude;
RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary, true);
RefinedYRangeForDisplay(evaluation, -xRange, xRange, &yMinRange, &yMaxRange, context, auxiliary);
float currentRatio = (yMaxRange - yMinRange) / (2 * xRange);
float grade = std::fabs(std::log(currentRatio / yxRatio)) + std::fabs(std::log(xRange / 10.f)) * rangeMagnitudeWeight;
if (std::fabs(std::log(currentRatio / yxRatio)) < maxMagnitudeDifference && grade < bestGrade) {
@@ -274,7 +272,7 @@ void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, f
if (bestGrade == FLT_MAX) {
*xMin = -k_defaultHalfRange;
*xMax = k_defaultHalfRange;
RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, auxiliary, true);
RefinedYRangeForDisplay(evaluation, *xMin, *xMax, yMin, yMax, context, auxiliary);
return;
}