diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 06f486095..0dc813ea7 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -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(minY, get(series, 1, k)); - maxY = std::max(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 */ diff --git a/apps/sequence/graph/graph_controller.cpp b/apps/sequence/graph/graph_controller.cpp index dda21f17b..4bf283382 100644 --- a/apps/sequence/graph/graph_controller.cpp +++ b/apps/sequence/graph/graph_controller.cpp @@ -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(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(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; diff --git a/apps/sequence/graph/graph_controller.h b/apps/sequence/graph/graph_controller.h index cbb17d948..7bc985003 100644 --- a/apps/sequence/graph/graph_controller.h +++ b/apps/sequence/graph/graph_controller.h @@ -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; } diff --git a/apps/shared/continuous_function.cpp b/apps/shared/continuous_function.cpp index 7fd9b47c3..616866239 100644 --- a/apps/shared/continuous_function.cpp +++ b/apps/shared/continuous_function.cpp @@ -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(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(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 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); } diff --git a/apps/shared/function.cpp b/apps/shared/function.cpp index 0ff2ec9b2..2347e1199 100644 --- a/apps/shared/function.cpp +++ b/apps/shared/function.cpp @@ -80,26 +80,19 @@ Function::RecordDataBuffer * Function::recordData() const { return reinterpret_cast(const_cast(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(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(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(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(auxiliary)->evaluateXYAtParameter(x, context).x2(); + }; } + + Poincare::Zoom::FullRange(evaluation, tMin, tMax, tStep, min, max, context, this); } + } diff --git a/apps/shared/function.h b/apps/shared/function.h index 83e65f273..60a214be3 100644 --- a/apps/shared/function.h +++ b/apps/shared/function.h @@ -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; diff --git a/apps/shared/function_graph_controller.cpp b/apps/shared/function_graph_controller.cpp index df603d777..bb50d07d5 100644 --- a/apps/shared/function_graph_controller.cpp +++ b/apps/shared/function_graph_controller.cpp @@ -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 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); } diff --git a/apps/shared/sequence.cpp b/apps/shared/sequence.cpp index d9ffbb591..683aa7288 100644 --- a/apps/shared/sequence.cpp +++ b/apps/shared/sequence.cpp @@ -9,6 +9,7 @@ #include #include #include +#include #include "../shared/poincare_helpers.h" #include #include @@ -310,6 +311,15 @@ Expression Sequence::sumBetweenBounds(double start, double end, Poincare::Contex return Float::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(static_cast(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(); diff --git a/apps/shared/sequence.h b/apps/shared/sequence.h index 2918f7041..cd9421c6b 100644 --- a/apps/shared/sequence.h +++ b/apps/shared/sequence.h @@ -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; diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index 2c0b7b891..fa19e6ef4 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -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); diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index b7004baa4..baa87f4ff 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -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; }