[apps] Factorize closestCurveIndexVertically from Regression and Graph

This commit is contained in:
Léa Saviot
2018-11-16 11:12:29 +01:00
committed by Émilie Feral
parent acdb4ca357
commit e3935fd9c0
8 changed files with 160 additions and 155 deletions

View File

@@ -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;

View File

@@ -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;

View File

@@ -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) {

View File

@@ -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 */

View File

@@ -1,6 +1,7 @@
#include "interactive_curve_view_controller.h"
#include "text_field_delegate_app.h"
#include <cmath>
#include <float.h>
#include <assert.h>
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;
}
}

View File

@@ -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;

View File

@@ -119,6 +119,11 @@ double StorageFunctionGraphController::defaultCursorAbscissa() {
return (interactiveCurveViewRange()->xMin()+interactiveCurveViewRange()->xMax())/2.0f;
}
StorageFunctionStore * StorageFunctionGraphController::functionStore() const {
StorageFunctionApp * myApp = static_cast<StorageFunctionApp *>(app());
return myApp->functionStore();
}
void StorageFunctionGraphController::initCursorParameters() {
double x = defaultCursorAbscissa();
StorageFunctionApp * myApp = static_cast<StorageFunctionApp *>(app());
@@ -136,50 +141,14 @@ void StorageFunctionGraphController::initCursorParameters() {
bool StorageFunctionGraphController::moveCursorVertically(int direction) {
int currentActiveFunctionIndex = indexFunctionSelectedByCursor();
StorageFunctionApp * myApp = static_cast<StorageFunctionApp *>(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<StorageFunctionApp *>(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<StorageFunctionApp *>(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();
}
}

View File

@@ -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;