diff --git a/apps/graph/test/ranges.cpp b/apps/graph/test/ranges.cpp index a2f5b8edf..4d13c1546 100644 --- a/apps/graph/test/ranges.cpp +++ b/apps/graph/test/ranges.cpp @@ -97,6 +97,7 @@ QUIZ_CASE(graph_ranges_single_function) { assert_best_cartesian_range_is("ℯ^x", -10, 10, -1.71249962, 8.91249943); assert_best_cartesian_range_is("ℯ^x+4", -10, 10, 2.28750038, 12.9124994); assert_best_cartesian_range_is("ℯ^(-x)", -10, 10, -1.71249962, 8.91249943); + assert_best_cartesian_range_is("(1-x)ℯ^(1/(1-x))", -1.8, 2.9, -3, 5.1); assert_best_cartesian_range_is("ln(x)", -2.85294199, 8.25294113, -3.5, 2.4000001); assert_best_cartesian_range_is("log(x)", -0.900000036, 3.20000005, -1.23906231, 0.939062357); diff --git a/poincare/include/poincare/zoom.h b/poincare/include/poincare/zoom.h index ed61d072b..5339d5c50 100644 --- a/poincare/include/poincare/zoom.h +++ b/poincare/include/poincare/zoom.h @@ -68,6 +68,10 @@ private: * an asymptote, by recursively computing the slopes. In case of an extremum, * the slope should taper off toward the center. */ static bool IsConvexAroundExtremum(ValueAtAbscissa evaluation, float x1, float x2, float x3, float y1, float y2, float y3, Context * context, const void * auxiliary, int iterations = 3); + /* If the function is discontinuous between its points of interest, there + * might be a lot of empty space in the middle of the screen. In that case, + * we want to zoom out to see more of the graph. */ + static void ExpandSparseWindow(float * sample, int length, float * xMin, float * xMax, float * yMin, float * yMax); }; } diff --git a/poincare/src/zoom.cpp b/poincare/src/zoom.cpp index 36bec8977..a786e658f 100644 --- a/poincare/src/zoom.cpp +++ b/poincare/src/zoom.cpp @@ -201,15 +201,19 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float * xMin, flo * yMax that must be inside the Y range.*/ assert(yMin && yMax); + float sample[k_sampleSize]; float sampleYMin = FLT_MAX, sampleYMax = -FLT_MAX; const float step = (*xMax - *xMin) / (k_sampleSize - 1); float x, y; float sum = 0.f; int pop = 0; + sample[0] = evaluation(*xMin, context, auxiliary); + sample[k_sampleSize - 1] = evaluation(*xMax, context, auxiliary); for (int i = 1; i < k_sampleSize - 1; i++) { x = *xMin + i * step; y = evaluation(x, context, auxiliary); + sample[i] = y; if (!std::isfinite(y)) { continue; } @@ -244,6 +248,8 @@ void Zoom::RefinedYRangeForDisplay(ValueAtAbscissa evaluation, float * xMin, flo } else if (*yMax < 0.f && *yMax / *yMin < k_forceXAxisThreshold) { *yMax = 0.f; } + + ExpandSparseWindow(sample, k_sampleSize, xMin, xMax, yMin, yMax); } void Zoom::RangeWithRatioForDisplay(ValueAtAbscissa evaluation, float yxRatio, float * xMin, float * xMax, float * yMin, float * yMax, Context * context, const void * auxiliary) { @@ -446,4 +452,31 @@ bool Zoom::IsConvexAroundExtremum(ValueAtAbscissa evaluation, float x1, float x2 return true; } +void Zoom::ExpandSparseWindow(float * sample, int length, float * xMin, float * xMax, float * yMin, float * yMax) { + /* We compute the "empty center" of the window, i.e. the largest rectangle + * (with same center and shape as the window) that does not contain any + * point. If that rectangle is deemed too large, we consider that not enough + * of the curve shows up on screen and we zoom out. */ + constexpr float emptyCenterMaxSize = 0.5f; + constexpr float ratioCorrection = 4.f/3.f; + + float xCenter = (*xMax + *xMin) / 2.f; + float yCenter = (*yMax + *yMin) / 2.f; + float xRange = *xMax - *xMin; + float yRange = *yMax - *yMin; + + float emptyCenter = FLT_MAX; + float step = xRange / (length - 1); + for (int i = 0; i < length; i++) { + float x = *xMin + i * step; + float y = sample[i]; + float r = 2 * std::max(std::fabs(x - xCenter) / xRange, std::fabs(y - yCenter) / yRange); + emptyCenter = std::min(emptyCenter, r); + } + + if (emptyCenter > emptyCenterMaxSize) { + SetZoom(ratioCorrection + emptyCenter, xCenter, yCenter, xMin, xMax, yMin ,yMax); + } +} + } diff --git a/poincare/test/zoom.cpp b/poincare/test/zoom.cpp index 49b53cb38..2e1da2a86 100644 --- a/poincare/test/zoom.cpp +++ b/poincare/test/zoom.cpp @@ -128,6 +128,7 @@ QUIZ_CASE(poincare_zoom_refined_range) { assert_refined_range_is("x×sin(x)", -14.4815292, 14.4815292, -7.37234354, 7.37234354); assert_refined_range_is("x×ln(x)", -0.314885706, 1.36450469, -0.367870897, 0.396377981); assert_refined_range_is("x!", -10, 10, NAN, NAN); + assert_refined_range_is("xℯ^(1/x)", -1.3, 2.4, -0.564221799, 5.58451653); } void assert_orthonormal_range_is(const char * definition, float targetXMin, float targetXMax, float targetYMin, float targetYMax, Preferences::AngleUnit angleUnit = Radian, const char * symbol = "x") {