diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index 7ce45e16b..7cae98b09 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -256,43 +256,37 @@ bool GraphController::moveCursorHorizontally(int direction) { } double x = direction > 0 ? m_cursor->x() + m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit : m_cursor->x() - m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; - double y = m_store->yValueForXValue(*m_selectedSeriesIndex, x, globalContext()); + double y = yValue(*m_selectedSeriesIndex, x, globalContext()); m_cursor->moveTo(x, y); m_store->panToMakePointVisible(x, y, cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); return true; } bool GraphController::moveCursorVertically(int direction) { - int closestRegressionSeries = -1; + Poincare::Context * context = globalContext(); + double x = m_cursor->x(); + double y = m_cursor->y(); + + // Find the closest regression + int selectedRegressionIndex = *m_selectedDotIndex == -1 ? *m_selectedSeriesIndex : -1; + int closestRegressionSeries = InteractiveCurveViewController::closestCurveIndexVertically(direction > 0, selectedRegressionIndex, context); + + // Find the closest dot int closestDotSeries = -1; - int dotSelected = -1; - Poincare::Context * globContext = globalContext(); - - if (*m_selectedDotIndex == -1) { - // The current cursor is on a regression - // Check the closest regression - closestRegressionSeries = m_store->closestVerticalRegression(direction, m_cursor->x(), m_cursor->y(), *m_selectedSeriesIndex, globContext); - // Check the closest dot - dotSelected = m_store->closestVerticalDot(direction, m_cursor->x(), direction > 0 ? -FLT_MAX : FLT_MAX, *m_selectedSeriesIndex, *m_selectedDotIndex, &closestDotSeries, globContext); - } else { - // The current cursor is on a dot - // Check the closest regression - closestRegressionSeries = m_store->closestVerticalRegression(direction, m_cursor->x(), m_cursor->y(), -1, globContext); - // Check the closest dot - dotSelected = m_store->closestVerticalDot(direction, m_cursor->x(), m_cursor->y(), *m_selectedSeriesIndex, *m_selectedDotIndex, &closestDotSeries, globContext); - } + double selectedDotY = *m_selectedDotIndex == -1 ? (direction > 0 ? -DBL_MAX : DBL_MAX) : y; + int dotSelected = m_store->closestVerticalDot(direction, x, selectedDotY, *m_selectedSeriesIndex, *m_selectedDotIndex, &closestDotSeries, context); + // Choose between selecting the regression or the dot bool validRegression = closestRegressionSeries > -1; bool validDot = dotSelected >= 0 && dotSelected <= m_store->numberOfPairsOfSeries(closestDotSeries); - if (validRegression && validDot) { /* Compare the abscissa distances to select either the dot or the * regression. If they are equal, compare the ordinate distances. */ double dotDistanceX = -1; if (dotSelected == m_store->numberOfPairsOfSeries(closestDotSeries)) { - dotDistanceX = std::fabs(m_store->meanOfColumn(closestDotSeries, 0) - m_cursor->x()); + dotDistanceX = std::fabs(m_store->meanOfColumn(closestDotSeries, 0) - x); } else { - dotDistanceX = std::fabs(m_store->get(closestDotSeries, 0, dotSelected) - m_cursor->x()); + dotDistanceX = std::fabs(m_store->get(closestDotSeries, 0, dotSelected) - x); } if (dotDistanceX != 0) { /* The regression X distance to the point is 0, so it is closer than the @@ -300,12 +294,12 @@ bool GraphController::moveCursorVertically(int direction) { validDot = false; } else { // Compare the y distances - double regressionDistanceY = std::fabs(m_store->yValueForXValue(closestRegressionSeries, m_cursor->x(), globContext) - m_cursor->y()); + double regressionDistanceY = std::fabs(yValue(closestRegressionSeries, x, context) - y); double dotDistanceY = -1; if (dotSelected == m_store->numberOfPairsOfSeries(closestDotSeries)) { - dotDistanceY = std::fabs(m_store->meanOfColumn(closestDotSeries, 1) - m_cursor->y()); + dotDistanceY = std::fabs(m_store->meanOfColumn(closestDotSeries, 1) - y); } else { - dotDistanceY = std::fabs(m_store->get(closestDotSeries, 1, dotSelected) - m_cursor->y()); + dotDistanceY = std::fabs(m_store->get(closestDotSeries, 1, dotSelected) - y); } if (regressionDistanceY <= dotDistanceY) { validDot = false; @@ -314,28 +308,36 @@ bool GraphController::moveCursorVertically(int direction) { } } } - if (!validDot && validRegression) { + + assert(!validDot || !validRegression); + + if (validRegression) { // Select the regression *m_selectedSeriesIndex = closestRegressionSeries; selectRegressionCurve(); - m_cursor->moveTo(m_cursor->x(), m_store->yValueForXValue(*m_selectedSeriesIndex, m_cursor->x(), globContext)); + m_cursor->moveTo(x, yValue(*m_selectedSeriesIndex, x, context)); m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); return true; } - if (validDot && !validRegression) { + if (validDot) { + // Select the dot m_view.setCursorView(&m_crossCursorView); *m_selectedSeriesIndex = closestDotSeries; *m_selectedDotIndex = dotSelected; if (dotSelected == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { + // Select the mean dot m_cursor->moveTo(m_store->meanOfColumn(*m_selectedSeriesIndex, 0), m_store->meanOfColumn(*m_selectedSeriesIndex, 1)); m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); - return true; + } else { + // Select a data point dot + m_cursor->moveTo(m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex), m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex)); + m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); } - m_cursor->moveTo(m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex), m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex)); - m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); return true; } + + // There was no suitable selection return false; } @@ -351,6 +353,22 @@ bool GraphController::isCursorVisible() { return interactiveCurveViewRange()->isCursorVisible(cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); } +bool GraphController::closestCurveIndexIsSuitable(int newIndex, int currentIndex) const { + return newIndex != currentIndex && !m_store->seriesIsEmpty(newIndex); +} + +double GraphController::yValue(int curveIndex, double x, Poincare::Context * context) const { + return m_store->yValueForXValue(curveIndex, x, context); +} + +bool GraphController::suitableYValue(double y) const { + return m_store->yMin() <= y && y <= m_store->yMax(); +} + +int GraphController::numberOfCurves() const { + return Store::k_numberOfSeries; +} + float GraphController::cursorBottomMarginRatio() { float f = (m_view.cursorView()->minimalSizeForOptimalDisplay().height()/2 + 2 + estimatedBannerHeight())/k_viewHeight; return f; diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index 1d02d8015..43a3505b7 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -28,29 +28,34 @@ private: constexpr static int k_maxLegendLength = 16; constexpr static int k_maxNumberOfCharacters = 50; constexpr static float k_viewHeight = 174.0f; + Poincare::Context * globalContext(); - Shared::CurveView * curveView() override; - Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override; - bool handleEnter() override; - void reloadBannerView() override; - void initRangeParameters() override; - void initCursorParameters() override; - bool moveCursorHorizontally(int direction) override; - bool moveCursorVertically(int direction) override; - uint32_t modelVersion() override; - uint32_t rangeVersion() override; - bool isCursorVisible() override; - - // InteractiveCurveViewController - float displayTopMarginRatio() override; - float displayBottomMarginRatio() override; - float cursorTopMarginRatio() { return 0.07f; } // (cursorHeight/2) / graphViewHeight float cursorBottomMarginRatio(); float estimatedBannerHeight() const; + // SimpleInteractiveCurveViewController + void reloadBannerView() override; + bool moveCursorHorizontally(int direction) override; + Shared::InteractiveCurveViewRange * interactiveCurveViewRange() override; + Shared::CurveView * curveView() override; + bool handleEnter() override; + + // InteractiveCurveViewController + void initRangeParameters() override; + void initCursorParameters() override; + bool moveCursorVertically(int direction) override; + uint32_t modelVersion() override; + uint32_t rangeVersion() override; + bool isCursorVisible() override; + bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const override; + double yValue(int curveIndex, double x, Poincare::Context * context) const override; + bool suitableYValue(double y) const override; + int numberOfCurves() const override; + // InteractiveCurveViewRangeDelegate - Shared::InteractiveCurveViewRangeDelegate::Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override; + float displayTopMarginRatio() override; + float displayBottomMarginRatio() override; Shared::InteractiveCurveViewRangeDelegate::Range computeYRange(Shared::InteractiveCurveViewRange * interactiveCurveViewRange) override; Shared::CursorView m_crossCursorView; Shared::RoundCursorView m_roundCursorView; diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index fcbee5b78..072eca522 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -43,56 +43,6 @@ void Store::setSeriesRegressionType(int series, Model::Type type) { } } -int Store::closestVerticalRegression(int direction, double x, double y, int currentRegressionSeries, Poincare::Context * globalContext) { - /* The conditions to test on all the regressions are in this order: - * - the current regression is not the current regression - * - the next regression point should be within the window abscissa bounds - * - it is the closest one in abscissa to x - * - it is above y if direction > 0 and below otherwise */ - - int nextRegressionSeries = -1; - float nextY = direction > 0 ? FLT_MAX : -FLT_MAX; - - for (int i = 0; i < k_numberOfSeries; i ++) { - if (i == currentRegressionSeries || seriesIsEmpty(i)) { - continue; - } - double newY = yValueForXValue(i, x, globalContext); - if (newY < m_yMin || newY > m_yMax) { - continue; - } - bool isNextRegression = false; - /* Choosing the next regression to select quite complex because we need to - * take care of regressions that have the same values at the current x. When - * moving up, if several regressions have the same value, we select the - * regression of higher index. When going down, we select the regression of - * lower index. */ - if (direction > 0) { - if (newY > y && newY < nextY) { - isNextRegression = true; - } else if (newY == nextY) { - assert(i > nextRegressionSeries); - isNextRegression = true; - } else if (newY == y && i < currentRegressionSeries) { - isNextRegression = true; - } - } else { - if (newY < y && newY > nextY) { - isNextRegression = true; - } else if (newY == nextY) { - assert(i > nextRegressionSeries); - } else if (newY == y && i > currentRegressionSeries) { - isNextRegression = true; - } - } - if (isNextRegression) { - nextY = newY; - nextRegressionSeries = i; - } - } - return nextRegressionSeries; -} - /* Dots */ int Store::closestVerticalDot(int direction, double x, double y, int currentSeries, int currentDot, int * nextSeries, Poincare::Context * globalContext) { diff --git a/apps/regression/store.h b/apps/regression/store.h index 81e5350bc..a5acc58b6 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -35,9 +35,7 @@ public: assert((int)m_regressionTypes[series] >= 0 && (int)m_regressionTypes[series] < Model::k_numberOfModels); return regressionModel((int)m_regressionTypes[series]); } - /* Return the series index of the closest regression at abscissa x, above - * ordinate y if direction > 0, below otherwise */ - int closestVerticalRegression(int direction, double x, double y, int currentRegressionSeries, Poincare::Context * globalContext); + // Dots /* Return the closest dot to abscissa x above the regression curve if * direction > 0, below otherwise */ diff --git a/apps/shared/interactive_curve_view_controller.cpp b/apps/shared/interactive_curve_view_controller.cpp index d5a2e8083..d46d8f1c8 100644 --- a/apps/shared/interactive_curve_view_controller.cpp +++ b/apps/shared/interactive_curve_view_controller.cpp @@ -1,6 +1,7 @@ #include "interactive_curve_view_controller.h" #include "text_field_delegate_app.h" #include +#include #include using namespace Poincare; @@ -161,4 +162,49 @@ StackViewController * InteractiveCurveViewController::stackController() const{ return (StackViewController *)(parentResponder()->parentResponder()->parentResponder()); } +int InteractiveCurveViewController::closestCurveIndexVertically(bool goingUp, int currentCurveIndex, Poincare::Context * context) const { + double x = m_cursor->x(); + double y = m_cursor->y(); + double nextY = goingUp ? DBL_MAX : -DBL_MAX; + int nextCurveIndex = -1; + int curvesCount = numberOfCurves(); + for (int i = 0; i < curvesCount; i++) { + if (!closestCurveIndexIsSuitable(i, currentCurveIndex)) { + continue; + } + double newY = yValue(i, x, context); + if (!suitableYValue(newY)) { + continue; + } + bool isNextCurve = false; + /* Choosing the closest vertical curve is quite complex because we need to + * take care of curves that have the same values at the current x. When + * moving up, if several curves have the same value, we choose the curve + * of higher index. When going down, we select the curve of lower index. */ + if (goingUp) { + if (newY > y && newY < nextY) { + isNextCurve = true; + } else if (newY == nextY) { + assert(i > nextCurveIndex); + isNextCurve = true; + } else if (newY == y && i < currentCurveIndex) { + isNextCurve = true; + } + } else { + if (newY < y && newY > nextY) { + isNextCurve = true; + } else if (newY == nextY) { + assert(i > nextCurveIndex); + } else if (newY == y && i > currentCurveIndex) { + isNextCurve = true; + } + } + if (isNextCurve) { + nextY = newY; + nextCurveIndex = i; + } + } + return nextCurveIndex; +} + } diff --git a/apps/shared/interactive_curve_view_controller.h b/apps/shared/interactive_curve_view_controller.h index f992a50d0..27079c950 100644 --- a/apps/shared/interactive_curve_view_controller.h +++ b/apps/shared/interactive_curve_view_controller.h @@ -44,6 +44,14 @@ protected: virtual uint32_t modelVersion() = 0; virtual uint32_t rangeVersion() = 0; virtual bool isCursorVisible() = 0; + + // Closest vertical curve helper + int closestCurveIndexVertically(bool goingUp, int currentSelectedCurve, Poincare::Context * context) const; + virtual bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const { assert(false); return false; } + virtual double yValue(int curveIndex, double x, Poincare::Context * context) const { assert(false); return 0; } + virtual bool suitableYValue(double y) const { return true; } + virtual int numberOfCurves() const { assert(false); return 0; } + OkView m_okView; private: uint32_t * m_modelVersion; diff --git a/apps/shared/storage_function_graph_controller.cpp b/apps/shared/storage_function_graph_controller.cpp index 4c37b733e..9d8c7efac 100644 --- a/apps/shared/storage_function_graph_controller.cpp +++ b/apps/shared/storage_function_graph_controller.cpp @@ -119,6 +119,11 @@ double StorageFunctionGraphController::defaultCursorAbscissa() { return (interactiveCurveViewRange()->xMin()+interactiveCurveViewRange()->xMax())/2.0f; } +StorageFunctionStore * StorageFunctionGraphController::functionStore() const { + StorageFunctionApp * myApp = static_cast(app()); + return myApp->functionStore(); +} + void StorageFunctionGraphController::initCursorParameters() { double x = defaultCursorAbscissa(); StorageFunctionApp * myApp = static_cast(app()); @@ -136,50 +141,14 @@ void StorageFunctionGraphController::initCursorParameters() { bool StorageFunctionGraphController::moveCursorVertically(int direction) { int currentActiveFunctionIndex = indexFunctionSelectedByCursor(); - StorageFunctionApp * myApp = static_cast(app()); - double y = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(currentActiveFunctionIndex))->evaluateAtAbscissa(m_cursor->x(), myApp->localContext()); - int nextActiveFunctionIndex = -1; - double nextY = direction > 0 ? DBL_MAX : -DBL_MAX; - int activeFunctionsCount = functionStore()->numberOfActiveFunctions(); - for (int i = 0; i < activeFunctionsCount; i++) { - if (i == currentActiveFunctionIndex) { - continue; - } - double newY = functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(i))->evaluateAtAbscissa(m_cursor->x(), myApp->localContext()); - bool isNextFunction = false; - /* Choosing the next function to select quite complex because we need to - * take care of functions that have the same values at the current x. When - * moving up, if several functions have the same value, we select the - * function of higher index. When going down, we select the function of - * lower index. */ - if (direction > 0) { - if (newY > y && newY < nextY) { - isNextFunction = true; - } else if (newY == nextY) { - assert(i > nextActiveFunctionIndex); - isNextFunction = true; - } else if (newY == y && i < currentActiveFunctionIndex) { - isNextFunction = true; - } - } else { - if (newY < y && newY > nextY) { - isNextFunction = true; - } else if (newY == nextY) { - assert(i > nextActiveFunctionIndex); - } else if (newY == y && i > currentActiveFunctionIndex) { - isNextFunction = true; - } - } - if (isNextFunction) { - nextY = newY; - nextActiveFunctionIndex = i; - } - } + Poincare::Context * context = static_cast(app())->localContext(); + + int nextActiveFunctionIndex = InteractiveCurveViewController::closestCurveIndexVertically(direction > 0, currentActiveFunctionIndex, context); if (nextActiveFunctionIndex < 0) { return false; } selectFunctionWithCursor(nextActiveFunctionIndex); - m_cursor->moveTo(m_cursor->x(), nextY); + m_cursor->moveTo(m_cursor->x(), yValue(nextActiveFunctionIndex, m_cursor->x(), context)); interactiveCurveViewRange()->panToMakePointVisible(m_cursor->x(), m_cursor->y(), cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); return true; } @@ -200,9 +169,16 @@ bool StorageFunctionGraphController::isCursorVisible() { return interactiveCurveViewRange()->isCursorVisible(cursorTopMarginRatio(), k_cursorRightMarginRatio, cursorBottomMarginRatio(), k_cursorLeftMarginRatio); } -StorageFunctionStore * StorageFunctionGraphController::functionStore() const { - StorageFunctionApp * myApp = static_cast(app()); - return myApp->functionStore(); +bool StorageFunctionGraphController::closestCurveIndexIsSuitable(int newIndex, int currentIndex) const { + return newIndex != currentIndex; +} + +double StorageFunctionGraphController::yValue(int curveIndex, double x, Poincare::Context * context) const { + return functionStore()->modelForRecord(functionStore()->activeRecordAtIndex(curveIndex))->evaluateAtAbscissa(x, context); +} + +int StorageFunctionGraphController::numberOfCurves() const { + return functionStore()->numberOfActiveFunctions(); } } diff --git a/apps/shared/storage_function_graph_controller.h b/apps/shared/storage_function_graph_controller.h index 000a98f27..6266f55f1 100644 --- a/apps/shared/storage_function_graph_controller.h +++ b/apps/shared/storage_function_graph_controller.h @@ -19,31 +19,34 @@ public: void viewWillAppear() override; protected: - float cursorTopMarginRatio() { - return 0.068f; // (cursorHeight/2)/graphViewHeight; - } + float cursorTopMarginRatio() { return 0.068f; } // (cursorHeight/2)/graphViewHeight float cursorBottomMarginRatio(); void reloadBannerView() override; bool handleEnter() override; - int indexFunctionSelectedByCursor() const { - return *m_indexFunctionSelectedByCursor; - } + int indexFunctionSelectedByCursor() const { return *m_indexFunctionSelectedByCursor; } virtual void selectFunctionWithCursor(int functionIndex); virtual double defaultCursorAbscissa(); virtual StorageFunctionStore * functionStore() const; + private: constexpr static float k_viewHeight = 174.0f; // TODO Taken from Regresssion/graph_controller. Maybe we should compute and/or put in common ? + + virtual StorageFunctionGraphView * functionGraphView() = 0; + virtual View * cursorView() = 0; + virtual StorageFunctionCurveParameterController * curveParameterController() = 0; + // InteractiveCurveViewController /* When y auto is ticked, we use a display margin to be ensure that the user * can move the cursor along the curve without panning the window */ - float displayTopMarginRatio() override { - return 0.09f; // cursorHeight/graphViewHeight - } + float displayTopMarginRatio() override { return 0.09f; } // cursorHeight/graphViewHeight float displayBottomMarginRatio() override; + // InteractiveCurveViewRangeDelegate InteractiveCurveViewRangeDelegate::Range computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) override; float estimatedBannerHeight() const; virtual int estimatedBannerNumberOfLines() const { return 1; } + + // InteractiveCurveViewController void initRangeParameters() override; void initCursorParameters() override; bool moveCursorVertically(int direction) override; @@ -51,9 +54,10 @@ private: uint32_t modelVersion() override; uint32_t rangeVersion() override; bool isCursorVisible() override; - virtual StorageFunctionGraphView * functionGraphView() = 0; - virtual View * cursorView() = 0; - virtual StorageFunctionCurveParameterController * curveParameterController() = 0; + bool closestCurveIndexIsSuitable(int newIndex, int currentIndex) const override; + double yValue(int curveIndex, double x, Poincare::Context * context) const override; + int numberOfCurves() const override; + InitialisationParameterController m_initialisationParameterController; Poincare::Preferences::AngleUnit * m_angleUnitVersion; int * m_indexFunctionSelectedByCursor;