diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index 462520cd5..f9d57c416 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -1,10 +1,12 @@ #include "graph_controller.h" #include "../app.h" +using namespace Poincare; using namespace Shared; namespace Graph { +static inline float minFloat(float x, float y) { return x < y ? x : y; } static inline float maxFloat(float x, float y) { return x > y ? x : y; } GraphController::GraphController(Responder * parentResponder, ::InputEventHandlerDelegate * inputEventHandlerDelegate, CartesianFunctionStore * functionStore, Shared::InteractiveCurveViewRange * curveViewRange, CurveViewCursor * cursor, int * indexFunctionSelectedByCursor, uint32_t * modelVersion, uint32_t * rangeVersion, Poincare::Preferences::AngleUnit * angleUnitVersion, ButtonRowController * header) : @@ -33,6 +35,72 @@ void GraphController::viewWillAppear() { selectFunctionWithCursor(indexFunctionSelectedByCursor()); // update the color of the cursor } +void GraphController::interestingFunctionRange(ExpiringPointer f, float tMin, float tMax, float step, float * xm, float * xM, float * ym, float * yM) const { + Poincare::Context * context = textFieldDelegateApp()->localContext(); + const int balancedBound = std::floor((tMax-tMin)/2/step); + for (int j = -balancedBound; j <= balancedBound ; j++) { + float t = (tMin+tMax)/2 + step * j; + Coordinate2D xy = f->evaluateXYAtParameter(t, context); + float x = xy.x1(); + float y = xy.x2(); + if (!std::isnan(x) && !std::isinf(x) && !std::isnan(y) && !std::isinf(y)) { + *xm = minFloat(*xm, x); + *xM = maxFloat(*xM, x); + *ym = minFloat(*ym, y); + *yM = maxFloat(*yM, y); + } + } +} + +void GraphController::interestingRanges(float * xm, float * xM, float * ym, float * yM) const { + float resultxMin = FLT_MAX; + float resultxMax = -FLT_MAX; + float resultyMin = FLT_MAX; + float resultyMax = -FLT_MAX; + float xMin = const_cast(this)->interactiveCurveViewRange()->xMin(); + float xMax = const_cast(this)->interactiveCurveViewRange()->xMax(); + /* In practice, a step smaller than a pixel's width is needed for sampling + * the values of a function. Otherwise some relevant extremal values may be + * missed. */ + assert(functionStore()->numberOfActiveFunctions() > 0); + if (displaysNonCartesianFunctions()) { + for (int i = 0; i < functionStore()->numberOfActiveFunctions(); i++) { + ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); + if (f->plotType() == CartesianFunction::PlotType::Cartesian) { + continue; + } + /* Scan x-range from the middle to the extrema in order to get balanced + * y-range for even functions (y = 1/x). */ + double tMin = f->tMin(); + double tMax = f->tMax(); + assert(!std::isnan(tMin)); + assert(!std::isnan(tMax)); + interestingFunctionRange(f, tMin, tMax, (tMax - tMin)/30, &resultxMin, &resultxMax, &resultyMin, &resultyMax); + } + } else { + resultxMin = xMin; + resultxMax = xMax; + } + const float step = const_cast(this)->curveView()->pixelWidth() / 2; + for (int i = 0; i < functionStore()->numberOfActiveFunctions(); i++) { + ExpiringPointer f = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i)); + if (f->plotType() != CartesianFunction::PlotType::Cartesian) { + continue; + } + /* Scan x-range from the middle to the extrema in order to get balanced + * y-range for even functions (y = 1/x). */ + assert(!std::isnan(f->tMin())); + assert(!std::isnan(f->tMax())); + double tMin = maxFloat(f->tMin(), xMin); + double tMax = minFloat(f->tMax(), xMax); + interestingFunctionRange(f, tMin, tMax, step, &resultxMin, &resultxMax, &resultyMin, &resultyMax); + } + *xm = resultxMin; + *xM = resultxMax; + *ym = resultyMin; + *yM = resultyMax; +} + float GraphController::interestingXHalfRange() const { if (displaysNonCartesianFunctions()) { diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index b7d689136..21fe08b0d 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -21,7 +21,7 @@ public: bool displayDerivativeInBanner() const { return m_displayDerivativeInBanner; } void setDisplayDerivativeInBanner(bool displayDerivative) { m_displayDerivativeInBanner = displayDerivative; } float interestingXHalfRange() const override; - + void interestingRanges(float * xm, float * xM, float * ym, float * yM) const override; private: int estimatedBannerNumberOfLines() const override { return 1 + m_displayDerivativeInBanner; } void selectFunctionWithCursor(int functionIndex) override; @@ -36,6 +36,7 @@ private: CartesianFunctionStore * functionStore() const override { return static_cast(Shared::FunctionGraphController::functionStore()); } bool displaysNonCartesianFunctions() const; bool defautRangeIsNormalized() const override { return displaysNonCartesianFunctions(); } + void interestingFunctionRange(Shared::ExpiringPointer f, float tMin, float tMax, float step, float * xm, float * xM, float * ym, float * yM) const; Shared::RoundCursorView m_cursorView; BannerView m_bannerView; diff --git a/apps/shared/function_graph_controller.h b/apps/shared/function_graph_controller.h index 0d9144ed5..882990ed4 100644 --- a/apps/shared/function_graph_controller.h +++ b/apps/shared/function_graph_controller.h @@ -34,6 +34,7 @@ protected: Poincare::Coordinate2D xyValues(int curveIndex, double t, Poincare::Context * context) const override; int numberOfCurves() const override; void initCursorParameters() override; + CurveView * curveView() override; private: virtual FunctionGraphView * functionGraphView() = 0; @@ -44,7 +45,6 @@ private: // InteractiveCurveViewController bool moveCursorVertically(int direction) override; - CurveView * curveView() override; uint32_t modelVersion() override; uint32_t rangeVersion() override; diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index 856c74c9e..f8ff1f800 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -35,7 +35,7 @@ InteractiveCurveViewController::InteractiveCurveViewController(Responder * paren { } -float InteractiveCurveViewController::addMargin(float x, float range, bool isMin) { +float InteractiveCurveViewController::addMargin(float x, float range, bool isVertical, bool isMin) { /* We are adding margins. Let's name: * - The current range: rangeBefore * - The next range: rangeAfter @@ -60,8 +60,8 @@ float InteractiveCurveViewController::addMargin(float x, float range, bool isMin * topRatioBefore = topRatioAfter, we would create too small margins and the * controller might need to pan right after a Y auto calibration. */ - float topMarginRatio = cursorTopMarginRatio(); - float bottomMarginRatio = cursorBottomMarginRatio(); + float topMarginRatio = isVertical ? cursorTopMarginRatio() : k_cursorRightMarginRatio; + float bottomMarginRatio = isVertical ? cursorBottomMarginRatio() : k_cursorLeftMarginRatio; assert(topMarginRatio + bottomMarginRatio < 1); // Assertion so that the formula is correct float ratioDenominator = 1 - bottomMarginRatio - topMarginRatio; float ratio = isMin ? -bottomMarginRatio : topMarginRatio; diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index be38f5cab..71350b257 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -62,7 +62,7 @@ private: virtual int estimatedBannerNumberOfLines() const { return 1; } // InteractiveCurveViewRangeDelegate - float addMargin(float x, float range, bool isMin) override; + float addMargin(float x, float range, bool isVertical, bool isMin) override; uint32_t * m_modelVersion; uint32_t * m_rangeVersion; diff --git a/apps/shared/interactive_curve_view_range.cpp b/apps/shared/interactive_curve_view_range.cpp index 4a7dcba18..4c31d1e3f 100644 --- a/apps/shared/interactive_curve_view_range.cpp +++ b/apps/shared/interactive_curve_view_range.cpp @@ -146,16 +146,53 @@ void InteractiveCurveViewRange::setDefault() { if (m_delegate == nullptr) { return; } - m_xMax = m_delegate->interestingXHalfRange(); - setXMin(-m_xMax); if (!m_delegate->defautRangeIsNormalized()) { + m_xMax = m_delegate->interestingXHalfRange(); + setXMin(-m_xMax); m_yAuto = true; return; } +#if 0 m_yAuto = false; - m_yMax = 3.0f; - setYMin(-m_yMax); + m_xMax = NormalizedXHalfRange(); + setXMin(-NormalizedXHalfRange()); + m_yMax = NormalizedYHalfRange(); + setYMin(-NormalizedYHalfRange()); normalize(); +#else + m_yAuto = false; + + float a,b,c,d; + m_delegate->interestingRanges(&a, &b, &c, &d); + MemoizedCurveViewRange::setXMin(a); + MemoizedCurveViewRange::setXMax(b); + MemoizedCurveViewRange::setYMin(c); + MemoizedCurveViewRange::setYMax(d); + + float xRange = m_xMax - m_xMin; + float yRange = m_yMax - m_yMin; + m_xMin = m_delegate->addMargin(m_xMin, xRange, false, true); + MemoizedCurveViewRange::setXMax(m_delegate->addMargin(m_xMax, xRange, false, false)); + m_yMin = m_delegate->addMargin(m_yMin, yRange, true, true); + MemoizedCurveViewRange::setYMax(m_delegate->addMargin(m_yMax, yRange, true, false)); + xRange = m_xMax - m_xMin; + yRange = m_yMax - m_yMin; + float xyRatio = xRange/yRange; + float normalizedXYRatio = NormalizedXHalfRange()/NormalizedYHalfRange(); + if (xyRatio < normalizedXYRatio) { + float newXRange = normalizedXYRatio * yRange; + assert(newXRange > xRange); + float delta = (newXRange - xRange) / 2.0f; + m_xMin -= delta; + MemoizedCurveViewRange::setXMax(m_xMax+delta); + } else if (xyRatio > normalizedXYRatio) { + float newYRange = NormalizedYHalfRange()/NormalizedXHalfRange() * xRange; + assert(newYRange > yRange); + float delta = (newYRange - yRange) / 2.0f; + m_yMin -= delta; + MemoizedCurveViewRange::setYMax(m_yMax+delta); + } +#endif } void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { diff --git a/apps/shared/interactive_curve_view_range_delegate.cpp b/apps/shared/interactive_curve_view_range_delegate.cpp index 1f61f1e5d..f1f3a26c5 100644 --- a/apps/shared/interactive_curve_view_range_delegate.cpp +++ b/apps/shared/interactive_curve_view_range_delegate.cpp @@ -19,7 +19,7 @@ bool InteractiveCurveViewRangeDelegate::didChangeRange(InteractiveCurveViewRange if (max < min) { range = 0.0f; } - if (interactiveCurveViewRange->yMin() == addMargin(min, range, true) && interactiveCurveViewRange->yMax() == addMargin(max, range, false)) { + if (interactiveCurveViewRange->yMin() == addMargin(min, range, true, true) && interactiveCurveViewRange->yMax() == addMargin(max, range, true, false)) { return false; } if (min == max) { @@ -33,8 +33,8 @@ bool InteractiveCurveViewRangeDelegate::didChangeRange(InteractiveCurveViewRange max = 1.0f; } range = max - min; - interactiveCurveViewRange->setYMin(addMargin(min, range, true)); - interactiveCurveViewRange->setYMax(addMargin(max, range, false)); + interactiveCurveViewRange->setYMin(addMargin(min, range, true, true)); + interactiveCurveViewRange->setYMax(addMargin(max, range, true, false)); if (std::isinf(interactiveCurveViewRange->xMin())) { interactiveCurveViewRange->setYMin(-FLT_MAX); } diff --git a/apps/shared/interactive_curve_view_range_delegate.h b/apps/shared/interactive_curve_view_range_delegate.h index ee753c1c7..b9a98b0a3 100644 --- a/apps/shared/interactive_curve_view_range_delegate.h +++ b/apps/shared/interactive_curve_view_range_delegate.h @@ -1,6 +1,8 @@ #ifndef SHARED_INTERACTIVE_CURVE_VIEW_DELEGATE_H #define SHARED_INTERACTIVE_CURVE_VIEW_DELEGATE_H +#include + namespace Shared { class InteractiveCurveViewRange; @@ -11,6 +13,8 @@ public: virtual float interestingXMin() const { return -interestingXHalfRange(); } virtual float interestingXHalfRange() const { return 10.0f; } virtual bool defautRangeIsNormalized() const { return false; } + virtual void interestingRanges(float * xm, float * xM, float * ym, float * yM) const { assert(false); } + virtual float addMargin(float x, float range, bool isVertical, bool isMin) = 0; protected: struct Range { float min; @@ -18,7 +22,6 @@ protected: }; private: virtual Range computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) = 0; - virtual float addMargin(float x, float range, bool isMin) = 0; }; }