From affda5eae9cf5328b098cf53d6ad48ded759b22e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=89milie=20Feral?= Date: Mon, 9 Jan 2017 14:57:13 +0100 Subject: [PATCH] [apps] Modular reimplementation of application models (stores and ranges) and of curve views/curve view controllers. Change-Id: If4ca9bf1bec024917ef540a3fc7baefa8700f7ba --- apps/Makefile | 13 +- apps/curve_view.cpp | 160 +++++--- apps/curve_view.h | 33 +- apps/curve_view_cursor.cpp | 21 ++ apps/curve_view_cursor.h | 16 + ...e_view_window.cpp => curve_view_range.cpp} | 6 +- ...curve_view_window.h => curve_view_range.h} | 8 +- apps/curve_view_window_with_cursor.cpp | 197 ---------- apps/curve_view_with_banner.cpp | 47 --- apps/curve_view_with_banner.h | 25 -- apps/curve_view_with_banner_and_cursor.cpp | 55 --- apps/curve_view_with_banner_and_cursor.h | 27 -- ...view_with_banner_and_cursor_controller.cpp | 153 -------- ...e_view_with_banner_and_cursor_controller.h | 41 --- apps/float_pair_store.cpp | 2 +- apps/float_pair_store.h | 2 +- apps/graph/Makefile | 1 - apps/graph/function_store.cpp | 8 + apps/graph/function_store.h | 2 + .../graph/curve_parameter_controller.cpp | 4 +- apps/graph/graph/curve_parameter_controller.h | 4 +- .../graph/graph/goto_parameter_controller.cpp | 13 +- apps/graph/graph/goto_parameter_controller.h | 7 +- apps/graph/graph/graph_controller.cpp | 140 +++++-- apps/graph/graph/graph_controller.h | 19 +- apps/graph/graph/graph_view.cpp | 4 +- apps/graph/graph/graph_view.h | 8 +- apps/graph/graph/graph_window.cpp | 137 ------- apps/graph/graph/graph_window.h | 35 -- .../initialisation_parameter_controller.cpp | 12 +- .../initialisation_parameter_controller.h | 6 +- apps/interactive_curve_view_controller.cpp | 162 ++++++++ apps/interactive_curve_view_controller.h | 58 +++ apps/interactive_curve_view_range.cpp | 218 +++++++++++ ...ursor.h => interactive_curve_view_range.h} | 47 ++- apps/interactive_curve_view_range_delegate.h | 11 + apps/probability/law/law.h | 4 +- apps/probability/law_curve_view.cpp | 13 +- apps/probability/law_curve_view.h | 1 - ...ler.cpp => range_parameter_controller.cpp} | 60 +-- ...troller.h => range_parameter_controller.h} | 14 +- apps/regression/calculation_controller.h | 2 +- .../regression/go_to_parameter_controller.cpp | 17 +- apps/regression/go_to_parameter_controller.h | 4 +- apps/regression/graph_controller.cpp | 104 +++++- apps/regression/graph_controller.h | 18 +- apps/regression/graph_view.cpp | 4 +- apps/regression/graph_view.h | 7 +- .../prediction_parameter_controller.cpp | 4 +- .../prediction_parameter_controller.h | 3 +- apps/regression/store.cpp | 348 +++++++----------- apps/regression/store.h | 34 +- apps/statistics/Makefile | 2 +- apps/statistics/box_controller.cpp | 6 - apps/statistics/box_controller.h | 1 - .../{box_window.cpp => box_range.cpp} | 14 +- apps/statistics/{box_window.h => box_range.h} | 10 +- apps/statistics/box_view.cpp | 8 +- apps/statistics/box_view.h | 10 +- apps/statistics/calculation_controller.h | 2 +- apps/statistics/histogram_controller.cpp | 104 +++++- apps/statistics/histogram_controller.h | 10 + .../histogram_parameter_controller.cpp | 4 +- apps/statistics/histogram_view.cpp | 37 +- apps/statistics/histogram_view.h | 9 +- apps/statistics/store.cpp | 168 +++------ apps/statistics/store.h | 41 +-- apps/store_parameter_controller.cpp | 1 - apps/zoom_parameter_controller.cpp | 42 +-- apps/zoom_parameter_controller.h | 14 +- 70 files changed, 1406 insertions(+), 1416 deletions(-) create mode 100644 apps/curve_view_cursor.cpp create mode 100644 apps/curve_view_cursor.h rename apps/{curve_view_window.cpp => curve_view_range.cpp} (82%) rename apps/{curve_view_window.h => curve_view_range.h} (88%) delete mode 100644 apps/curve_view_window_with_cursor.cpp delete mode 100644 apps/curve_view_with_banner.cpp delete mode 100644 apps/curve_view_with_banner.h delete mode 100644 apps/curve_view_with_banner_and_cursor.cpp delete mode 100644 apps/curve_view_with_banner_and_cursor.h delete mode 100644 apps/curve_view_with_banner_and_cursor_controller.cpp delete mode 100644 apps/curve_view_with_banner_and_cursor_controller.h delete mode 100644 apps/graph/graph/graph_window.cpp delete mode 100644 apps/graph/graph/graph_window.h create mode 100644 apps/interactive_curve_view_controller.cpp create mode 100644 apps/interactive_curve_view_controller.h create mode 100644 apps/interactive_curve_view_range.cpp rename apps/{curve_view_window_with_cursor.h => interactive_curve_view_range.h} (53%) create mode 100644 apps/interactive_curve_view_range_delegate.h rename apps/{window_parameter_controller.cpp => range_parameter_controller.cpp} (54%) rename apps/{window_parameter_controller.h => range_parameter_controller.h} (67%) rename apps/statistics/{box_window.cpp => box_range.cpp} (60%) rename apps/statistics/{box_window.h => box_range.h} (64%) diff --git a/apps/Makefile b/apps/Makefile index 0f5c14609..65bcee208 100644 --- a/apps/Makefile +++ b/apps/Makefile @@ -8,23 +8,23 @@ include apps/statistics/Makefile app_objs += $(addprefix apps/,\ apps_container.o\ - constant.o\ banner_view.o\ + constant.o\ cursor_view.o\ curve_view.o\ - curve_view_with_banner.o\ - curve_view_with_banner_and_cursor.o\ - curve_view_with_banner_and_cursor_controller.o\ - curve_view_window.o\ - curve_view_window_with_cursor.o\ + curve_view_cursor.o\ + curve_view_range.o\ editable_cell_table_view_controller.o\ expression_text_field_delegate.o\ float_pair_store.o\ float_parameter_controller.o\ + interactive_curve_view_controller.o\ + interactive_curve_view_range.o\ main.o\ node.o\ node_list_view_controller.o\ node_navigation_controller.o\ + range_parameter_controller.o\ store_controller.o\ store_parameter_controller.o\ text_field_delegate_app.o\ @@ -33,7 +33,6 @@ app_objs += $(addprefix apps/,\ toolbox_node.o\ variable_box_controller.o\ variable_box_leaf_cell.o\ - window_parameter_controller.o\ zoom_parameter_controller.o\ ) diff --git a/apps/curve_view.cpp b/apps/curve_view.cpp index 008a106f4..de465d5f0 100644 --- a/apps/curve_view.cpp +++ b/apps/curve_view.cpp @@ -6,11 +6,16 @@ #include constexpr KDColor CurveView::k_axisColor; +constexpr KDColor CurveView::k_gridColor; -CurveView::CurveView(CurveViewWindow * curveViewWindow, float topMarginFactor, - float rightMarginFactor, float bottomMarginFactor, float leftMarginFactor) : +CurveView::CurveView(CurveViewRange * curveViewRange, CurveViewCursor * curveViewCursor, View * bannerView, + View * cursorView, float topMarginFactor, float rightMarginFactor, float bottomMarginFactor, + float leftMarginFactor) : View(), - m_curveViewWindow(curveViewWindow), + m_curveViewRange(curveViewRange), + m_curveViewCursor(curveViewCursor), + m_bannerView(bannerView), + m_cursorView(cursorView), m_topMarginFactor(topMarginFactor), m_bottomMarginFactor(bottomMarginFactor), m_leftMarginFactor(leftMarginFactor), @@ -18,32 +23,61 @@ CurveView::CurveView(CurveViewWindow * curveViewWindow, float topMarginFactor, { } -void CurveView::setCurveViewWindow(CurveViewWindow * curveViewWindow) { - m_curveViewWindow = curveViewWindow; -} - void CurveView::reload() { markRectAsDirty(bounds()); + if (label(Axis::Horizontal, 0) != nullptr) { + computeLabels(Axis::Horizontal); + } + if (label(Axis::Vertical, 0) != nullptr) { + computeLabels(Axis::Vertical); + } + layoutSubviews(); +} + +void CurveView::reloadSelection() { + if (m_curveViewCursor != nullptr) { + float pixelXSelection = roundf(floatToPixel(Axis::Horizontal, m_curveViewCursor->x())); + float pixelYSelection = roundf(floatToPixel(Axis::Vertical, m_curveViewCursor->y())); + KDRect dirtyZone(KDRect(pixelXSelection - k_cursorSize/2, pixelYSelection - k_cursorSize/2, k_cursorSize, k_cursorSize)); + markRectAsDirty(dirtyZone); + layoutSubviews(); + } +} + +bool CurveView::isMainViewSelected() const { + return m_mainViewSelected; +} + +void CurveView::selectMainView(bool mainViewSelected) { + if (m_mainViewSelected != mainViewSelected) { + m_mainViewSelected = mainViewSelected; + reloadSelection(); + layoutSubviews(); + } +} + +void CurveView::setCurveViewRange(CurveViewRange * curveViewRange) { + m_curveViewRange = curveViewRange; } float CurveView::min(Axis axis) const { assert(axis == Axis::Horizontal || axis == Axis::Vertical); - float range = axis == Axis::Horizontal ? m_curveViewWindow->xMax() - m_curveViewWindow->xMin() : m_curveViewWindow->yMax() - m_curveViewWindow->yMin(); - float absoluteMin = axis == Axis::Horizontal ? m_curveViewWindow->xMin(): m_curveViewWindow->yMin(); + float range = axis == Axis::Horizontal ? m_curveViewRange->xMax() - m_curveViewRange->xMin() : m_curveViewRange->yMax() - m_curveViewRange->yMin(); + float absoluteMin = axis == Axis::Horizontal ? m_curveViewRange->xMin(): m_curveViewRange->yMin(); float marginFactor = axis == Axis::Horizontal ? m_leftMarginFactor : m_bottomMarginFactor; return absoluteMin - marginFactor*range; } float CurveView::max(Axis axis) const { assert(axis == Axis::Horizontal || axis == Axis::Vertical); - float range = axis == Axis::Horizontal ? m_curveViewWindow->xMax() - m_curveViewWindow->xMin() : m_curveViewWindow->yMax() - m_curveViewWindow->yMin(); - float absoluteMax = (axis == Axis::Horizontal ? m_curveViewWindow->xMax() : m_curveViewWindow->yMax()); + float range = axis == Axis::Horizontal ? m_curveViewRange->xMax() - m_curveViewRange->xMin() : m_curveViewRange->yMax() - m_curveViewRange->yMin(); + float absoluteMax = (axis == Axis::Horizontal ? m_curveViewRange->xMax() : m_curveViewRange->yMax()); float marginFactor = axis == Axis::Horizontal ? m_rightMarginFactor : m_topMarginFactor; return absoluteMax + marginFactor*range; } float CurveView::gridUnit(Axis axis) const { - return (axis == Axis::Horizontal ? m_curveViewWindow->xGridUnit() : m_curveViewWindow->yGridUnit()); + return (axis == Axis::Horizontal ? m_curveViewRange->xGridUnit() : m_curveViewRange->yGridUnit()); } KDCoordinate CurveView::pixelLength(Axis axis) const { @@ -62,14 +96,6 @@ float CurveView::floatToPixel(Axis axis, float f) const { return pixelLength(axis)*fraction; } -int CurveView::numberOfLabels(Axis axis) const { - Axis otherAxis = axis == Axis::Horizontal ? Axis::Vertical : Axis::Horizontal; - if (min(otherAxis) > 0.0f || max(otherAxis) < 0.0f) { - return 0; - } - return ceilf((max(axis) - min(axis))/(2*gridUnit(axis))); -} - void CurveView::computeLabels(Axis axis) { char buffer[Constant::FloatBufferSizeInDecimalMode]; float step = gridUnit(axis); @@ -173,6 +199,11 @@ void CurveView::drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float ste } } +void CurveView::drawGrid(KDContext * ctx, KDRect rect) const { + drawGridLines(ctx, rect, Axis::Horizontal, m_curveViewRange->xGridUnit(), k_gridColor); + drawGridLines(ctx, rect, Axis::Vertical, m_curveViewRange->yGridUnit(), k_gridColor); +} + void CurveView::drawAxes(KDContext * ctx, KDRect rect, Axis axis) const { drawLine(ctx, rect, axis, 0.0f, k_axisColor, 2); } @@ -271,31 +302,12 @@ void CurveView::drawHistogram(KDContext * ctx, KDRect rect, Model * model, float } } -void CurveView::stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float pyf, KDColor color) const { - // We avoid drawing when no part of the stamp is visible - if (pyf < -stampSize || pyf > pixelLength(Axis::Vertical)+stampSize) { - return; +int CurveView::numberOfLabels(Axis axis) const { + Axis otherAxis = axis == Axis::Horizontal ? Axis::Vertical : Axis::Horizontal; + if (min(otherAxis) > 0.0f || max(otherAxis) < 0.0f) { + return 0; } - KDCoordinate px = pxf; - KDCoordinate py = pyf; - KDRect stampRect(px-circleDiameter/2, py-circleDiameter/2, stampSize, stampSize); - if (!rect.intersects(stampRect)) { - return; - } - uint8_t shiftedMask[stampSize][stampSize]; - KDColor workingBuffer[stampSize*stampSize]; - float dx = pxf - floorf(pxf); - float dy = pyf - floorf(pyf); - /* TODO: this could be optimized by precomputing 10 or 100 shifted masks. The - * dx and dy would be rounded to one tenth or one hundredth to choose the - * right shifted mask. */ - for (int i=0; iblendRectWithMask(stampRect, color, (const uint8_t *)shiftedMask, workingBuffer); + return ceilf((max(axis) - min(axis))/(2*gridUnit(axis))); } float CurveView::evaluateModelWithParameter(Model * curve, float t) const { @@ -350,3 +362,63 @@ void CurveView::straightJoinDots(KDContext * ctx, KDRect rect, float pxf, float } straightJoinDots(ctx, rect, puf, pvf, pxf, pyf, color); } + +void CurveView::stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float pyf, KDColor color) const { + // We avoid drawing when no part of the stamp is visible + if (pyf < -stampSize || pyf > pixelLength(Axis::Vertical)+stampSize) { + return; + } + KDCoordinate px = pxf; + KDCoordinate py = pyf; + KDRect stampRect(px-circleDiameter/2, py-circleDiameter/2, stampSize, stampSize); + if (!rect.intersects(stampRect)) { + return; + } + uint8_t shiftedMask[stampSize][stampSize]; + KDColor workingBuffer[stampSize*stampSize]; + float dx = pxf - floorf(pxf); + float dy = pyf - floorf(pyf); + /* TODO: this could be optimized by precomputing 10 or 100 shifted masks. The + * dx and dy would be rounded to one tenth or one hundredth to choose the + * right shifted mask. */ + for (int i=0; iblendRectWithMask(stampRect, color, (const uint8_t *)shiftedMask, workingBuffer); +} + +void CurveView::layoutSubviews() { + if (m_curveViewCursor != nullptr && m_cursorView != nullptr) { + KDCoordinate xCursorPixelPosition = roundf(floatToPixel(Axis::Horizontal, m_curveViewCursor->x())); + KDCoordinate yCursorPixelPosition = roundf(floatToPixel(Axis::Vertical, m_curveViewCursor->y())); + KDRect cursorFrame(xCursorPixelPosition - k_cursorSize/2, yCursorPixelPosition - k_cursorSize/2, k_cursorSize, k_cursorSize); + if (!m_mainViewSelected) { + cursorFrame = KDRectZero; + } + m_cursorView->setFrame(cursorFrame); + } + if (m_bannerView != nullptr) { + m_bannerView->setFrame(bounds()); + KDCoordinate bannerHeight = m_bannerView->minimalSizeForOptimalDisplay().height(); + KDRect bannerFrame(KDRect(0, bounds().height()- bannerHeight, bounds().width(), bannerHeight)); + if (!m_mainViewSelected) { + bannerFrame = KDRectZero; + } + m_bannerView->setFrame(bannerFrame); + } +} + +int CurveView::numberOfSubviews() const { + return (m_bannerView != nullptr) + (m_cursorView != nullptr); +}; + +View * CurveView::subviewAtIndex(int index) { + assert(index >= 0 && index < 2); + if (index == 0) { + return m_bannerView; + } + return m_cursorView; +} diff --git a/apps/curve_view.h b/apps/curve_view.h index 590225bf0..c34cd7809 100644 --- a/apps/curve_view.h +++ b/apps/curve_view.h @@ -3,7 +3,8 @@ #include #include -#include "curve_view_window.h" +#include "curve_view_range.h" +#include "curve_view_cursor.h" #include class CurveView : public View { @@ -13,15 +14,26 @@ public: Horizontal = 0, Vertical = 1 }; - CurveView(CurveViewWindow * curveViewWindow = nullptr, float topMarginFactor = 0.0f, + CurveView(CurveViewRange * curveViewRange = nullptr, CurveViewCursor * curveViewCursor = nullptr, + View * bannerView = nullptr, View * cursorView = nullptr, float topMarginFactor = 0.0f, float rightMarginFactor = 0.0f, float bottomMarginFactor = 0.0f, float leftMarginFactor = 0.0f); - virtual void reload(); + // Reload methods + void reload(); + virtual void reloadSelection(); + // When the main view is selected, the banner view is visible + bool isMainViewSelected() const; + void selectMainView(bool mainViewSelected); + protected: + void setCurveViewRange(CurveViewRange * curveViewRange); + + // Drawing methods constexpr static KDColor k_axisColor = KDColor::RGB24(0x000000); + constexpr static KDColor k_gridColor = KDColor::RGB24(0xEEEEEE); constexpr static KDCoordinate k_labelMargin = 4; constexpr static int k_maxNumberOfXLabels = 18; constexpr static int k_maxNumberOfYLabels = 13; - void setCurveViewWindow(CurveViewWindow * curveViewWindow); + constexpr static KDCoordinate k_cursorSize = 9; /* The window bounds are deduced from the model bounds but also take into account a margin (computed with k_marginFactor) */ float min(Axis axis) const; @@ -38,12 +50,14 @@ protected: KDColor color, KDCoordinate thickness = 1) const; void drawDot(KDContext * ctx, KDRect rect, float x, float y, KDColor color, KDSize size) const; void drawGridLines(KDContext * ctx, KDRect rect, Axis axis, float step, KDColor color) const; + void drawGrid(KDContext * ctx, KDRect rect) const; void drawAxes(KDContext * ctx, KDRect rect, Axis axis) const; void drawCurve(KDContext * ctx, KDRect rect, Model * curve, KDColor color, bool colorUnderCurve = false, float colorLowerBound = 0.0f, float colorUpperBound = 0.0f, bool continuously = false) const; void drawHistogram(KDContext * ctx, KDRect rect, Model * model, float firstBarAbscissa, float barWidth, bool fillBar, KDColor defaultColor, KDColor highlightColor, float highlightLowerBound = INFINITY, float highlightUpperBound = -INFINITY) const; void computeLabels(Axis axis); void drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOrigin) const; + private: constexpr static int k_externRectMargin = 1; int numberOfLabels(Axis axis) const; @@ -57,7 +71,16 @@ private: * function shifts the stamp (by blending adjacent pixel colors) to draw with * anti alising. */ void stampAtLocation(KDContext * ctx, KDRect rect, float pxf, float pyf, KDColor color) const; - CurveViewWindow * m_curveViewWindow; + void layoutSubviews() override; + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + /* m_curveViewRange has to be non null but the cursor model, the banner and + * cursor views may be nullptr if not needed. */ + CurveViewRange * m_curveViewRange; + CurveViewCursor * m_curveViewCursor; + View * m_bannerView; + View * m_cursorView; + bool m_mainViewSelected; float m_topMarginFactor; float m_bottomMarginFactor; float m_leftMarginFactor; diff --git a/apps/curve_view_cursor.cpp b/apps/curve_view_cursor.cpp new file mode 100644 index 000000000..d4a794dc3 --- /dev/null +++ b/apps/curve_view_cursor.cpp @@ -0,0 +1,21 @@ +#include "curve_view_cursor.h" +#include + +CurveViewCursor::CurveViewCursor() : + m_x(NAN), + m_y(NAN) +{ +} + +float CurveViewCursor::x() { + return m_x; +} + +float CurveViewCursor::y() { + return m_y; +} + +void CurveViewCursor::moveTo(float x, float y) { + m_x = x; + m_y = y; +} diff --git a/apps/curve_view_cursor.h b/apps/curve_view_cursor.h new file mode 100644 index 000000000..d66139d30 --- /dev/null +++ b/apps/curve_view_cursor.h @@ -0,0 +1,16 @@ +#ifndef APPS_CURVE_VIEW_CURSOR_H +#define APPS_CURVE_VIEW_CURSOR_H + + +class CurveViewCursor { +public: + CurveViewCursor(); + float x(); + float y(); + void moveTo(float x, float y); +protected: + float m_x; + float m_y; +}; + +#endif diff --git a/apps/curve_view_window.cpp b/apps/curve_view_range.cpp similarity index 82% rename from apps/curve_view_window.cpp rename to apps/curve_view_range.cpp index 7ad21fe36..f85bde9cd 100644 --- a/apps/curve_view_window.cpp +++ b/apps/curve_view_range.cpp @@ -1,12 +1,12 @@ -#include "curve_view_window.h" +#include "curve_view_range.h" #include #include -float CurveViewWindow::yGridUnit() { +float CurveViewRange::yGridUnit() { return 0.0f; } -float CurveViewWindow::computeGridUnit(Axis axis, float min, float max) { +float CurveViewRange::computeGridUnit(Axis axis, float min, float max) { int a = 0; int b = 0; float d = max - min; diff --git a/apps/curve_view_window.h b/apps/curve_view_range.h similarity index 88% rename from apps/curve_view_window.h rename to apps/curve_view_range.h index 62ca096fe..f2b8804d4 100644 --- a/apps/curve_view_window.h +++ b/apps/curve_view_range.h @@ -1,7 +1,7 @@ -#ifndef APPS_CURVE_VIEW_WINDOW_H -#define APPS_CURVE_VIEW_WINDOW_H +#ifndef APPS_CURVE_VIEW_RANGE_H +#define APPS_CURVE_VIEW_RANGE_H -class CurveViewWindow { +class CurveViewRange { public: enum class Axis { X, @@ -13,6 +13,7 @@ public: virtual float yMax() = 0; virtual float xGridUnit() = 0; virtual float yGridUnit(); + float computeGridUnit(Axis axis, float min, float max); protected: constexpr static float k_minNumberOfXGridUnits = 7.0f; constexpr static float k_maxNumberOfXGridUnits = 18.0f; @@ -21,7 +22,6 @@ protected: constexpr static float k_oneUnit = 1.0f; constexpr static float k_twoUnit = 2.0f; constexpr static float k_fiveUnit = 5.0f; - float computeGridUnit(Axis axis, float min, float max); }; #endif diff --git a/apps/curve_view_window_with_cursor.cpp b/apps/curve_view_window_with_cursor.cpp deleted file mode 100644 index f56831237..000000000 --- a/apps/curve_view_window_with_cursor.cpp +++ /dev/null @@ -1,197 +0,0 @@ -#include "curve_view_window_with_cursor.h" -#include - -CurveViewWindowWithCursor::CurveViewWindowWithCursor() : - m_xCursorPosition(NAN), - m_yCursorPosition(NAN), - m_xMin(-10.0f), - m_xMax(10.0f), - m_yMin(-10.0f), - m_yMax(10.0f), - m_yAuto(true), - m_xGridUnit(2.0f), - m_yGridUnit(2.0f) -{ -} - -float CurveViewWindowWithCursor::xCursorPosition() { - return m_xCursorPosition; -} - -float CurveViewWindowWithCursor::yCursorPosition() { - return m_yCursorPosition; -} - -float CurveViewWindowWithCursor::xMin() { - return m_xMin; -} - -float CurveViewWindowWithCursor::xMax() { - return m_xMax; -} - -float CurveViewWindowWithCursor::yMin() { - return m_yMin; -} - -float CurveViewWindowWithCursor::yMax() { - return m_yMax; -} - -bool CurveViewWindowWithCursor::yAuto() { - return m_yAuto; -} - -float CurveViewWindowWithCursor::xGridUnit() { - return m_xGridUnit; -} - -float CurveViewWindowWithCursor::yGridUnit() { - return m_yGridUnit; -} - -void CurveViewWindowWithCursor::setXMin(float xMin) { - m_xMin = xMin; - computeYaxis(); - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - initCursorPosition(); -} - -void CurveViewWindowWithCursor::setXMax(float xMax) { - m_xMax = xMax; - computeYaxis(); - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - initCursorPosition(); -} - -void CurveViewWindowWithCursor::setYMin(float yMin) { - m_yMin = yMin; - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - initCursorPosition(); -} - -void CurveViewWindowWithCursor::setYMax(float yMax) { - m_yMax = yMax; - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - initCursorPosition(); -} - -void CurveViewWindowWithCursor::setYAuto(bool yAuto) { - m_yAuto = yAuto; - computeYaxis(); -} - -void CurveViewWindowWithCursor::zoom(float ratio) { - float xMin = m_xMin; - float xMax = m_xMax; - float yMin = m_yMin; - float yMax = m_yMax; - m_xMin = (xMax+xMin)/2.0f - ratio*fabsf(xMax-xMin); - m_xMax = (xMax+xMin)/2.0f + ratio*fabsf(xMax-xMin); - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - m_yAuto = false; - m_yMin = (yMax+yMin)/2.0f - ratio*fabsf(yMax-yMin); - m_yMax = (yMax+yMin)/2.0f + ratio*fabsf(yMax-yMin); - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - initCursorPosition(); -} - -void CurveViewWindowWithCursor::translateWindow(Direction direction) { - m_yAuto = false; - if (direction == Direction::Up) { - m_yMin = m_yMin + m_yGridUnit; - m_yMax = m_yMax + m_yGridUnit; - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - } - if (direction == Direction::Down) { - m_yMin = m_yMin - m_yGridUnit; - m_yMax = m_yMax - m_yGridUnit; - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - } - if (direction == Direction::Left) { - m_xMin = m_xMin - m_xGridUnit; - m_xMax = m_xMax - m_xGridUnit; - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - } - if (direction == Direction::Right) { - m_xMin = m_xMin + m_xGridUnit; - m_xMax = m_xMax + m_xGridUnit; - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - } - initCursorPosition(); -} - -void CurveViewWindowWithCursor::roundAbscissa() { - float xMin = m_xMin; - float xMax = m_xMax; - m_xMin = roundf((xMin+xMax)/2) - 160.0f; - m_xMax = roundf((xMin+xMax)/2) + 159.0f; - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - computeYaxis(); - initCursorPosition(); -} - -void CurveViewWindowWithCursor::normalize() { - float xMin = m_xMin; - float xMax = m_xMax; - float yMin = m_yMin; - float yMax = m_yMax; - m_xMin = (xMin+xMax)/2 - 5.3f; - m_xMax = (xMin+xMax)/2 + 5.3f; - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - m_yAuto = false; - m_yMin = (yMin+yMax)/2 - 3.1f; - m_yMax = (yMin+yMax)/2 + 3.1f; - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - initCursorPosition(); -} - -bool CurveViewWindowWithCursor::panToMakePointVisible(float x, float y, float xMargin, float yMargin) { - bool windowMoved = false; - float xRange = m_xMax - m_xMin; - float yRange = m_yMax - m_yMin; - if (x < m_xMin + xMargin) { - m_xMin = x - xMargin; - m_xMax = m_xMin + xRange; - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - m_yAuto = false; - windowMoved = true; - } - if (x > m_xMax - xMargin) { - m_xMax = x + xMargin; - m_xMin = m_xMax - xRange; - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - m_yAuto = false; - windowMoved = true; - } - if (y < m_yMin + yMargin) { - m_yMin = y - yMargin; - m_yMax = m_yMin + yRange; - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - m_yAuto = false; - windowMoved = true; - } - if (y > m_yMax - yMargin) { - m_yMax = y + yMargin; - m_yMin = m_yMax - yRange; - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - m_yAuto = false; - windowMoved = true; - } - return windowMoved; -} - -void CurveViewWindowWithCursor::centerAxisAround(Axis axis, float position) { - if (axis == Axis::X) { - float range = m_xMax - m_xMin; - m_xMin = position - range/2.0f; - m_xMax = position + range/2.0f; - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - } else { - m_yAuto = false; - float range = m_yMax - m_yMin; - m_yMin = position - range/2.0f; - m_yMax = position + range/2.0f; - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - } -} diff --git a/apps/curve_view_with_banner.cpp b/apps/curve_view_with_banner.cpp deleted file mode 100644 index 04a499eac..000000000 --- a/apps/curve_view_with_banner.cpp +++ /dev/null @@ -1,47 +0,0 @@ -#include "curve_view_with_banner.h" -#include -#include - -CurveViewWithBanner::CurveViewWithBanner(CurveViewWindow * curveViewWindow, BannerView * bannerView, - float topMarginFactor, float rightMarginFactor, float bottomMarginFactor, float leftMarginFactor) : - CurveView(curveViewWindow, topMarginFactor, rightMarginFactor, bottomMarginFactor, leftMarginFactor), - m_mainViewSelected(true), - m_bannerView(bannerView) -{ -} - -void CurveViewWithBanner::reload() { - markRectAsDirty(bounds()); - computeLabels(Axis::Horizontal); -} - -bool CurveViewWithBanner::isMainViewSelected() { - return m_mainViewSelected; -} - -void CurveViewWithBanner::selectMainView(bool mainViewSelected) { - if (m_mainViewSelected != mainViewSelected) { - m_mainViewSelected = mainViewSelected; - reloadSelection(); - layoutSubviews(); - } -} - -int CurveViewWithBanner::numberOfSubviews() const { - return 1; -}; - -View * CurveViewWithBanner::subviewAtIndex(int index) { - assert(index == 0); - return m_bannerView; -} - -void CurveViewWithBanner::layoutSubviews() { - m_bannerView->setFrame(bounds()); - KDCoordinate bannerHeight = m_bannerView->minimalSizeForOptimalDisplay().height(); - KDRect bannerFrame(KDRect(0, bounds().height()- bannerHeight, bounds().width(), bannerHeight)); - if (!m_mainViewSelected) { - bannerFrame = KDRectZero; - } - m_bannerView->setFrame(bannerFrame); -} diff --git a/apps/curve_view_with_banner.h b/apps/curve_view_with_banner.h deleted file mode 100644 index 0418ca05b..000000000 --- a/apps/curve_view_with_banner.h +++ /dev/null @@ -1,25 +0,0 @@ -#ifndef APPS_CURVE_VIEW_WITH_BANNER_H -#define APPS_CURVE_VIEW_WITH_BANNER_H - -#include -#include "curve_view.h" -#include "banner_view.h" - -class CurveViewWithBanner : public CurveView { -public: - CurveViewWithBanner(CurveViewWindow * curveViewWindow = nullptr, BannerView * bannerView = nullptr, - float topMarginFactor = 0.0f, float rightMarginFactor = 0.0f, float bottomMarginFactor = 0.0f, float leftMarginFactor = 0.0f); - virtual void reloadSelection() = 0; - void reload() override; - bool isMainViewSelected(); - void selectMainView(bool mainViewSelected); -protected: - void layoutSubviews() override; - bool m_mainViewSelected; - BannerView * m_bannerView; -private: - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; -}; - -#endif diff --git a/apps/curve_view_with_banner_and_cursor.cpp b/apps/curve_view_with_banner_and_cursor.cpp deleted file mode 100644 index 33783b4c5..000000000 --- a/apps/curve_view_with_banner_and_cursor.cpp +++ /dev/null @@ -1,55 +0,0 @@ -#include "curve_view_with_banner_and_cursor.h" -#include - -constexpr KDColor CurveViewWithBannerAndCursor::k_gridColor; - -CurveViewWithBannerAndCursor::CurveViewWithBannerAndCursor(CurveViewWindowWithCursor * curveViewWindowWithCursor, - BannerView * bannerView, CursorView * cursorView, float topMarginFactor, float rightMarginFactor, float bottomMarginFactor, float leftMarginFactor) : - CurveViewWithBanner(curveViewWindowWithCursor, bannerView, topMarginFactor, rightMarginFactor, bottomMarginFactor, leftMarginFactor), - m_cursorView(cursorView), - m_curveViewWindowWithCursor(curveViewWindowWithCursor) -{ -} - -void CurveViewWithBannerAndCursor::reload() { - markRectAsDirty(bounds()); - computeLabels(Axis::Horizontal); - computeLabels(Axis::Vertical); - layoutSubviews(); -} - -void CurveViewWithBannerAndCursor::reloadSelection() { - float pixelXSelection = roundf(floatToPixel(Axis::Horizontal, m_curveViewWindowWithCursor->xCursorPosition())); - float pixelYSelection = roundf(floatToPixel(Axis::Vertical, m_curveViewWindowWithCursor->yCursorPosition())); - KDRect dirtyZone(KDRect(pixelXSelection - k_cursorSize/2, pixelYSelection - k_cursorSize/2, k_cursorSize, k_cursorSize)); - markRectAsDirty(dirtyZone); - layoutSubviews(); -} - -void CurveViewWithBannerAndCursor::drawGrid(KDContext * ctx, KDRect rect) const { - drawGridLines(ctx, rect, Axis::Horizontal, m_curveViewWindowWithCursor->xGridUnit(), k_gridColor); - drawGridLines(ctx, rect, Axis::Vertical, m_curveViewWindowWithCursor->yGridUnit(), k_gridColor); -} - -void CurveViewWithBannerAndCursor::layoutSubviews() { - KDCoordinate xCursorPixelPosition = roundf(floatToPixel(Axis::Horizontal, m_curveViewWindowWithCursor->xCursorPosition())); - KDCoordinate yCursorPixelPosition = roundf(floatToPixel(Axis::Vertical, m_curveViewWindowWithCursor->yCursorPosition())); - KDRect cursorFrame(xCursorPixelPosition - k_cursorSize/2, yCursorPixelPosition - k_cursorSize/2, k_cursorSize, k_cursorSize); - if (!m_mainViewSelected) { - cursorFrame = KDRectZero; - } - m_cursorView->setFrame(cursorFrame); - CurveViewWithBanner::layoutSubviews(); -} - -int CurveViewWithBannerAndCursor::numberOfSubviews() const { - return 2; -}; - -View * CurveViewWithBannerAndCursor::subviewAtIndex(int index) { - assert(index >= 0 && index < 2); - if (index == 0) { - return m_cursorView; - } - return m_bannerView; -} diff --git a/apps/curve_view_with_banner_and_cursor.h b/apps/curve_view_with_banner_and_cursor.h deleted file mode 100644 index 802538295..000000000 --- a/apps/curve_view_with_banner_and_cursor.h +++ /dev/null @@ -1,27 +0,0 @@ -#ifndef APPS_CURVE_VIEW_WITH_BANNER_AND_CURSOR_H -#define APPS_CURVE_VIEW_WITH_BANNER_AND_CURSOR_H - -#include -#include "curve_view_window_with_cursor.h" -#include "curve_view_with_banner.h" -#include "cursor_view.h" - -class CurveViewWithBannerAndCursor : public CurveViewWithBanner { -public: - CurveViewWithBannerAndCursor(CurveViewWindowWithCursor * curveViewWindowWithCursor = nullptr, BannerView * bannerView = nullptr, - CursorView * cursorView = nullptr, float topMarginFactor = 0.0f, float rightMarginFactor = 0.0f, float bottomMarginFactor = 0.0f, float leftMarginFactor = 0.0f); - void reload() override; - void reloadSelection() override; - void drawGrid(KDContext * ctx, KDRect rect) const; -protected: - constexpr static KDCoordinate k_cursorSize = 9; - constexpr static KDColor k_gridColor = KDColor::RGB24(0xEEEEEE); - void layoutSubviews() override; - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; - - CursorView * m_cursorView; - CurveViewWindowWithCursor * m_curveViewWindowWithCursor; -}; - -#endif diff --git a/apps/curve_view_with_banner_and_cursor_controller.cpp b/apps/curve_view_with_banner_and_cursor_controller.cpp deleted file mode 100644 index 8527b51c8..000000000 --- a/apps/curve_view_with_banner_and_cursor_controller.cpp +++ /dev/null @@ -1,153 +0,0 @@ -#include "curve_view_with_banner_and_cursor_controller.h" -#include -#include - -CurveViewWindowWithBannerAndCursorController::CurveViewWindowWithBannerAndCursorController(Responder * parentResponder, - HeaderViewController * header, CurveViewWindowWithCursor * graphWindow, CurveViewWithBannerAndCursor * graphView) : - ViewController(parentResponder), - HeaderViewDelegate(header), - m_graphWindow(graphWindow), - m_graphView(graphView), - m_windowParameterController(WindowParameterController(this, m_graphWindow)), - m_zoomParameterController(ZoomParameterController(this, m_graphWindow, m_graphView)), - m_windowButton(this, "Axes", Invocation([](void * context, void * sender) { - CurveViewWindowWithBannerAndCursorController * graphController = (CurveViewWindowWithBannerAndCursorController *) context; - StackViewController * stack = graphController->stackController(); - stack->push(graphController->windowParameterController()); - }, this)), - m_zoomButton(this, "Zoom", Invocation([](void * context, void * sender) { - CurveViewWindowWithBannerAndCursorController * graphController = (CurveViewWindowWithBannerAndCursorController *) context; - StackViewController * stack = graphController->stackController(); - stack->push(graphController->zoomParameterController()); - }, this)), - m_defaultInitialisationButton(this, "Initialisation", Invocation([](void * context, void * sender) { - CurveViewWindowWithBannerAndCursorController * graphController = (CurveViewWindowWithBannerAndCursorController *) context; - StackViewController * stack = graphController->stackController(); - stack->push(graphController->initialisationParameterController()); - }, this)), - m_cursorView(CursorView()) -{ -} - -const char * CurveViewWindowWithBannerAndCursorController::title() const { - return "Graphique"; -} - -View * CurveViewWindowWithBannerAndCursorController::view() { - return m_graphView; -} - -bool CurveViewWindowWithBannerAndCursorController::handleEvent(Ion::Events::Event event) { - if (!m_graphView->isMainViewSelected()) { - if (event == Ion::Events::Down) { - headerViewController()->setSelectedButton(-1); - m_graphView->selectMainView(true); - m_graphView->reloadSelection(); - reloadBannerView(); - return true; - } - if (event == Ion::Events::Up) { - headerViewController()->setSelectedButton(-1); - app()->setFirstResponder(tabController()); - return true; - } - return false; - } - if (event == Ion::Events::Plus) { - m_graphWindow->zoom(1.0f/3.0f); - m_graphView->reload(); - reloadBannerView(); - return true; - } - if (event == Ion::Events::Minus) { - m_graphWindow->zoom(3.0f/4.0f); - m_graphView->reload(); - reloadBannerView(); - return true; - } - if (event == Ion::Events::Left || event == Ion::Events::Right) { - int direction = event == Ion::Events::Left ? -1 : 1; - m_graphView->reloadSelection(); - int didMoveCursor = m_graphWindow->moveCursorHorizontally(direction); - if (didMoveCursor == 0) { - m_graphView->reloadSelection(); - } else { - m_graphView->reload(); - } - reloadBannerView(); - return (didMoveCursor >= 0); - } - if (event == Ion::Events::Down || event == Ion::Events::Up) { - int direction = event == Ion::Events::Down ? -1 : 1; - m_graphView->reloadSelection(); - int didMoveCursor = m_graphWindow->moveCursorVertically(direction); - if (didMoveCursor < 0) { - if (event == Ion::Events::Down) { - return false; - } - m_graphView->selectMainView(false); - headerViewController()->setSelectedButton(0); - return true; - } - if (didMoveCursor == 0) { - m_graphView->reloadSelection(); - } - if (didMoveCursor == 1) { - m_graphView->reload(); - } - reloadBannerView(); - return true; - } - if (event == Ion::Events::OK) { - return handleEnter(); - } - return false; -} - -void CurveViewWindowWithBannerAndCursorController::didBecomeFirstResponder() { - headerViewController()->setSelectedButton(-1); - m_graphView->selectMainView(true); - // Layout view whe the graph view that might have been modified by the zoom page - headerViewController()->layoutView(); - // Reload graph view - m_graphView->reload(); - reloadBannerView(); -} - -ViewController * CurveViewWindowWithBannerAndCursorController::windowParameterController() { - return &m_windowParameterController; -} - -ViewController * CurveViewWindowWithBannerAndCursorController::zoomParameterController() { - return &m_zoomParameterController; -} - -int CurveViewWindowWithBannerAndCursorController::numberOfButtons() const { - return 3; -} - -Button * CurveViewWindowWithBannerAndCursorController::buttonAtIndex(int index) { - switch (index) { - case 0: - return &m_windowButton; - case 1: - return &m_zoomButton; - case 2: - return &m_defaultInitialisationButton; - default: - assert(false); - } - return nullptr; -} - -Responder * CurveViewWindowWithBannerAndCursorController::defaultController() { - return tabController(); -} - -Responder * CurveViewWindowWithBannerAndCursorController::tabController() const{ - return (parentResponder()->parentResponder()->parentResponder()->parentResponder()); -} - -StackViewController * CurveViewWindowWithBannerAndCursorController::stackController() const{ - return (StackViewController *)(parentResponder()->parentResponder()->parentResponder()); -} diff --git a/apps/curve_view_with_banner_and_cursor_controller.h b/apps/curve_view_with_banner_and_cursor_controller.h deleted file mode 100644 index 8dbace239..000000000 --- a/apps/curve_view_with_banner_and_cursor_controller.h +++ /dev/null @@ -1,41 +0,0 @@ -#ifndef APPS_CURVE_VIEW_WINDOW_WITH_BANNER_AND_CURSOR_CONTROLLER_H -#define APPS_CURVE_VIEW_WINDOW_WITH_BANNER_AND_CURSOR_CONTROLLER_H - -#include -#include "curve_view_window_with_cursor.h" -#include "curve_view_with_banner_and_cursor.h" -#include "window_parameter_controller.h" -#include "zoom_parameter_controller.h" - -class CurveViewWindowWithBannerAndCursorController : public ViewController, public HeaderViewDelegate, public AlternateEmptyViewDelegate { -public: - CurveViewWindowWithBannerAndCursorController(Responder * parentResponder, HeaderViewController * header, CurveViewWindowWithCursor * graphWindow, CurveViewWithBannerAndCursor * graphView); - View * view() override; - const char * title() const override; - bool handleEvent(Ion::Events::Event event) override; - void didBecomeFirstResponder() override; - - ViewController * windowParameterController(); - ViewController * zoomParameterController(); - virtual ViewController * initialisationParameterController() = 0; - - int numberOfButtons() const override; - Button * buttonAtIndex(int index) override; - - Responder * defaultController() override; -protected: - virtual bool handleEnter() = 0; - Responder * tabController() const; - StackViewController * stackController() const; - virtual void reloadBannerView() = 0; - CurveViewWindowWithCursor * m_graphWindow; - CurveViewWithBannerAndCursor * m_graphView; - WindowParameterController m_windowParameterController; - ZoomParameterController m_zoomParameterController; - Button m_windowButton; - Button m_zoomButton; - Button m_defaultInitialisationButton; - CursorView m_cursorView; -}; - -#endif diff --git a/apps/float_pair_store.cpp b/apps/float_pair_store.cpp index c097ed96b..845d91a85 100644 --- a/apps/float_pair_store.cpp +++ b/apps/float_pair_store.cpp @@ -55,7 +55,7 @@ float FloatPairStore::sumOfColumn(int i) { return result; } -uint32_t FloatPairStore::checksum() { +uint32_t FloatPairStore::storeChecksum() { size_t dataLengthInBytes = m_numberOfPairs*2*sizeof(float); assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 //return Ion::crc32((uint32_t *)m_data, dataLengthInBytes>>2); diff --git a/apps/float_pair_store.h b/apps/float_pair_store.h index 011489c17..12394a250 100644 --- a/apps/float_pair_store.h +++ b/apps/float_pair_store.h @@ -15,7 +15,7 @@ public: void deleteAllPairs(); void resetColumn(int i); float sumOfColumn(int i); - uint32_t checksum(); + uint32_t storeChecksum(); constexpr static int k_maxNumberOfPairs = 500; protected: virtual float defaultValue(int ii); diff --git a/apps/graph/Makefile b/apps/graph/Makefile index bff3915e2..7fa082c3a 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -4,7 +4,6 @@ app_objs += $(addprefix apps/graph/,\ function_store.o\ function_title_cell.o\ graph/banner_view.o\ - graph/graph_window.o\ graph/curve_parameter_controller.o\ graph/goto_parameter_controller.o\ graph/graph_controller.o\ diff --git a/apps/graph/function_store.cpp b/apps/graph/function_store.cpp index d17b91dc5..42c4d7971 100644 --- a/apps/graph/function_store.cpp +++ b/apps/graph/function_store.cpp @@ -2,6 +2,7 @@ extern "C" { #include } +#include namespace Graph { @@ -15,6 +16,13 @@ FunctionStore::FunctionStore() : addEmptyFunction(); } +uint32_t FunctionStore::storeChecksum() { + size_t dataLengthInBytes = m_numberOfFunctions*sizeof(Function); + assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 + //return Ion::crc32((uint32_t *)m_functions, dataLengthInBytes>>2); + return m_numberOfFunctions; +} + Function * FunctionStore::functionAtIndex(int i) { assert(i>=0 && i namespace Graph { /* FunctionStore is a dumb class. @@ -9,6 +10,7 @@ namespace Graph { class FunctionStore { public: FunctionStore(); + uint32_t storeChecksum(); Function * functionAtIndex(int i); Function * activeFunctionAtIndex(int i); Function * definedFunctionAtIndex(int i); diff --git a/apps/graph/graph/curve_parameter_controller.cpp b/apps/graph/graph/curve_parameter_controller.cpp index bee2e0c4f..692e69aae 100644 --- a/apps/graph/graph/curve_parameter_controller.cpp +++ b/apps/graph/graph/curve_parameter_controller.cpp @@ -3,7 +3,7 @@ namespace Graph { -CurveParameterController::CurveParameterController(GraphWindow * graphWindow, BannerView * bannerView) : +CurveParameterController::CurveParameterController(InteractiveCurveViewRange * graphRange, BannerView * bannerView, CurveViewCursor * cursor) : ViewController(nullptr), m_bannerView(bannerView), m_function(nullptr), @@ -12,7 +12,7 @@ CurveParameterController::CurveParameterController(GraphWindow * graphWindow, Ba m_derivativeCell(SwitchMenuListCell((char*)"Nombre derivee")), m_selectableTableView(SelectableTableView(this, this, Metric::TopMargin, Metric::RightMargin, Metric::BottomMargin, Metric::LeftMargin)), - m_goToParameterController(GoToParameterController(this, graphWindow)) + m_goToParameterController(GoToParameterController(this, graphRange, cursor)) { } diff --git a/apps/graph/graph/curve_parameter_controller.h b/apps/graph/graph/curve_parameter_controller.h index b18c51d42..0fad7a742 100644 --- a/apps/graph/graph/curve_parameter_controller.h +++ b/apps/graph/graph/curve_parameter_controller.h @@ -5,12 +5,14 @@ #include "goto_parameter_controller.h" #include "banner_view.h" #include "../function.h" +#include "../../curve_view_cursor.h" +#include "../../interactive_curve_view_range.h" namespace Graph { class CurveParameterController : public ViewController, public SimpleListViewDataSource { public: - CurveParameterController(GraphWindow * graphWindow, BannerView * bannerView); + CurveParameterController(InteractiveCurveViewRange * graphRange, BannerView * bannerView, CurveViewCursor * cursor); View * view() override; const char * title() const override; diff --git a/apps/graph/graph/goto_parameter_controller.cpp b/apps/graph/graph/goto_parameter_controller.cpp index 468686ec6..87732df36 100644 --- a/apps/graph/graph/goto_parameter_controller.cpp +++ b/apps/graph/graph/goto_parameter_controller.cpp @@ -4,10 +4,11 @@ namespace Graph { -GoToParameterController::GoToParameterController(Responder * parentResponder, GraphWindow * graphWindow) : +GoToParameterController::GoToParameterController(Responder * parentResponder, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor) : FloatParameterController(parentResponder), m_abscisseCell(EditableTextMenuListCell(&m_selectableTableView, this, m_draftTextBuffer, (char*)"x")), - m_graphWindow(graphWindow), + m_graphRange(graphRange), + m_cursor(cursor), m_function(nullptr) { } @@ -18,12 +19,16 @@ const char * GoToParameterController::title() const { float GoToParameterController::parameterAtIndex(int index) { assert(index == 0); - return m_graphWindow->xCursorPosition(); + return m_cursor->x(); } void GoToParameterController::setParameterAtIndex(int parameterIndex, float f) { assert(parameterIndex == 0); - m_graphWindow->setCursorPositionAtAbscissaWithFunction(f, m_function); + App * graphApp = (Graph::App *)app(); + float y = m_function->evaluateAtAbscissa(f, graphApp->localContext()); + m_graphRange->centerAxisAround(CurveViewRange::Axis::X, f); + m_graphRange->centerAxisAround(CurveViewRange::Axis::Y, y); + m_graphRange->moveCursorTo(f, y); } int GoToParameterController::numberOfRows() { diff --git a/apps/graph/graph/goto_parameter_controller.h b/apps/graph/graph/goto_parameter_controller.h index 57107c310..8a2b75a72 100644 --- a/apps/graph/graph/goto_parameter_controller.h +++ b/apps/graph/graph/goto_parameter_controller.h @@ -4,11 +4,13 @@ #include #include "graph_view.h" #include "../../float_parameter_controller.h" +#include "../../curve_view_cursor.h" +#include "../../interactive_curve_view_range.h" namespace Graph { class GoToParameterController : public FloatParameterController { public: - GoToParameterController(Responder * parentResponder, GraphWindow * graphWindow); + GoToParameterController(Responder * parentResponder, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor); const char * title() const override; int numberOfRows() override; TableViewCell * reusableCell(int index) override; @@ -19,7 +21,8 @@ private: void setParameterAtIndex(int parameterIndex, float f) override; char m_draftTextBuffer[EditableTextMenuListCell::k_bufferLength]; EditableTextMenuListCell m_abscisseCell; - GraphWindow * m_graphWindow; + InteractiveCurveViewRange * m_graphRange; + CurveViewCursor * m_cursor; Function * m_function; }; diff --git a/apps/graph/graph/graph_controller.cpp b/apps/graph/graph/graph_controller.cpp index 24ab0701a..b3f70541f 100644 --- a/apps/graph/graph/graph_controller.cpp +++ b/apps/graph/graph/graph_controller.cpp @@ -2,24 +2,23 @@ #include "../app.h" #include #include +#include namespace Graph { GraphController::GraphController(Responder * parentResponder, FunctionStore * functionStore, HeaderViewController * header) : - CurveViewWindowWithBannerAndCursorController(parentResponder, header, &m_graphWindow, &m_view), + InteractiveCurveViewController(parentResponder, header, &m_graphRange, &m_view), m_bannerView(BannerView()), - m_view(GraphView(functionStore, &m_graphWindow, &m_bannerView, &m_cursorView)), - m_graphWindow(functionStore), - m_initialisationParameterController(InitialisationParameterController(this, &m_graphWindow)), - m_curveParameterController(CurveParameterController(&m_graphWindow, &m_bannerView)), - m_functionStore(functionStore) + m_view(GraphView(functionStore, &m_graphRange, &m_cursor, &m_bannerView, &m_cursorView)), + m_graphRange(InteractiveCurveViewRange(&m_cursor, this)), + m_initialisationParameterController(InitialisationParameterController(this, &m_graphRange)), + m_curveParameterController(CurveParameterController(&m_graphRange, &m_bannerView, &m_cursor)), + m_functionStore(functionStore), + m_indexFunctionSelectedByCursor(0) { } View * GraphController::view() { - if (isnan(m_graphWindow.xCursorPosition()) && !isEmpty()) { - m_graphWindow.initCursorPosition(); - } return &m_view; } @@ -46,19 +45,50 @@ void GraphController::didBecomeFirstResponder() { App * graphApp = (Graph::App *)app(); m_view.setContext(graphApp->localContext()); } - if (m_graphWindow.context() == nullptr) { - App * graphApp = (Graph::App *)app(); - m_graphWindow.setContext(graphApp->localContext()); + InteractiveCurveViewController::didBecomeFirstResponder(); +} + +bool GraphController::didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange) { + if (!m_graphRange.yAuto()) { + return false; } - // if new functions were added to the store, the window parameters need to be refresh - if (m_graphWindow.computeYaxis()) { - m_graphWindow.initCursorPosition(); + App * graphApp = (Graph::App *)app(); + if (m_functionStore->numberOfActiveFunctions() > 0) { + float min = FLT_MAX; + float max = -FLT_MAX; + float xMin = m_graphRange.xMin(); + float xMax = m_graphRange.xMax(); + float step = (xMax - xMin)/Ion::Display::Width; + for (int i=0; inumberOfActiveFunctions(); i++) { + Function * f = m_functionStore->activeFunctionAtIndex(i); + float y = 0.0f; + for (float x = xMin; x <= xMax; x += step) { + y = f->evaluateAtAbscissa(x, graphApp->localContext()); + if (!isnan(y) && !isinf(y)) { + min = min < y ? min : y; + max = max > y ? max : y; + } + } + } + if (m_graphRange.yMin() == min && m_graphRange.yMax() == max) { + return false; + } + if (min == max) { + min = min - 1; + max = max + 1; + } + m_graphRange.setYMin(min); + m_graphRange.setYMax(max); } - CurveViewWindowWithBannerAndCursorController::didBecomeFirstResponder(); + return true; +} + +BannerView * GraphController::bannerView() { + return &m_bannerView; } bool GraphController::handleEnter() { - Function * f = m_graphWindow.functionSelectedByCursor(); + Function * f = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor); m_curveParameterController.setFunction(f); StackViewController * stack = stackController(); stack->push(&m_curveParameterController); @@ -67,21 +97,87 @@ bool GraphController::handleEnter() { void GraphController::reloadBannerView() { char buffer[k_maxNumberOfCharacters+Constant::FloatBufferSizeInScientificMode] = {'x', ' ', '=', ' ',0}; - Float(m_graphWindow.xCursorPosition()).convertFloatToText(buffer+4, Constant::FloatBufferSizeInScientificMode, Constant::NumberOfDigitsInMantissaInScientificMode); + Float(m_cursor.x()).convertFloatToText(buffer+4, Constant::FloatBufferSizeInScientificMode, Constant::NumberOfDigitsInMantissaInScientificMode); m_bannerView.setLegendAtIndex(buffer, 0); strlcpy(buffer, "00(x) = ", k_maxNumberOfCharacters+1); - buffer[1] = m_graphWindow.functionSelectedByCursor()->name()[0]; - Float(m_graphWindow.yCursorPosition()).convertFloatToText(buffer+8, Constant::FloatBufferSizeInScientificMode, Constant::NumberOfDigitsInMantissaInScientificMode); + Function * f = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor); + buffer[1] = f->name()[0]; + Float(m_cursor.y()).convertFloatToText(buffer+8, Constant::FloatBufferSizeInScientificMode, Constant::NumberOfDigitsInMantissaInScientificMode); m_bannerView.setLegendAtIndex(buffer+1, 1); if (m_bannerView.displayDerivative()) { - buffer[0] = m_graphWindow.functionSelectedByCursor()->name()[0]; + buffer[0] = f->name()[0]; buffer[1] = '\''; - Float(m_graphWindow.derivativeAtCursorPosition()).convertFloatToText(buffer+8, Constant::FloatBufferSizeInScientificMode, Constant::NumberOfDigitsInMantissaForDerivativeNumberInScientificMode); + App * graphApp = (Graph::App *)app(); + float y = f->approximateDerivative(m_cursor.x(), graphApp->localContext()); + Float(y).convertFloatToText(buffer+8, Constant::FloatBufferSizeInScientificMode, Constant::NumberOfDigitsInMantissaForDerivativeNumberInScientificMode); m_bannerView.setLegendAtIndex(buffer, 2); } m_bannerView.layoutSubviews(); } +void GraphController::initRangeParameters() { + if (didChangeRange(&m_graphRange)) { + initCursorParameters(); + } +} + +void GraphController::initCursorParameters() { + float x = (m_graphRange.xMin()+m_graphRange.xMax())/2.0f; + m_indexFunctionSelectedByCursor = 0; + Function * firstFunction = m_functionStore->activeFunctionAtIndex(0); + App * graphApp = (Graph::App *)app(); + float y = firstFunction->evaluateAtAbscissa(x, graphApp->localContext()); + m_graphRange.moveCursorTo(x, y); +} + +int GraphController::moveCursorHorizontally(int direction) { + float xCursorPosition = m_cursor.x(); + float x = direction > 0 ? xCursorPosition + m_graphRange.xGridUnit()/InteractiveCurveViewRange::k_numberOfCursorStepsInGradUnit : + xCursorPosition - m_graphRange.xGridUnit()/InteractiveCurveViewRange::k_numberOfCursorStepsInGradUnit; + Function * f = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor); + App * graphApp = (Graph::App *)app(); + float y = f->evaluateAtAbscissa(x, graphApp->localContext()); + return m_graphRange.moveCursorTo(x, y);; +} + +int GraphController::moveCursorVertically(int direction) { + Function * actualFunction = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor); + App * graphApp = (Graph::App *)app(); + float y = actualFunction->evaluateAtAbscissa(m_cursor.x(), graphApp->localContext()); + Function * nextFunction = actualFunction; + float nextY = direction > 0 ? FLT_MAX : -FLT_MAX; + for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) { + Function * f = m_functionStore->activeFunctionAtIndex(i); + float newY = f->evaluateAtAbscissa(m_cursor.x(), graphApp->localContext()); + bool isNextFunction = direction > 0 ? (newY > y && newY < nextY) : (newY < y && newY > nextY); + if (isNextFunction) { + m_indexFunctionSelectedByCursor = i; + nextY = newY; + nextFunction = f; + } + } + if (nextFunction == actualFunction) { + return -1; + } + return m_graphRange.moveCursorTo(m_cursor.x(), nextY); +} + +uint32_t GraphController::modelVersion() { + return m_functionStore->storeChecksum(); +} + +uint32_t GraphController::rangeVersion() { + return m_graphRange.rangeChecksum(); +} + +InteractiveCurveViewRange * GraphController::interactiveCurveViewRange() { + return &m_graphRange; +} + +CurveView * GraphController::curveView() { + return &m_view; +} + } diff --git a/apps/graph/graph/graph_controller.h b/apps/graph/graph/graph_controller.h index d92c6f045..7efd7d7c7 100644 --- a/apps/graph/graph/graph_controller.h +++ b/apps/graph/graph/graph_controller.h @@ -3,15 +3,14 @@ #include #include "graph_view.h" -#include "graph_window.h" #include "banner_view.h" #include "curve_parameter_controller.h" #include "initialisation_parameter_controller.h" -#include "../../curve_view_with_banner_and_cursor_controller.h" +#include "../../interactive_curve_view_controller.h" #include "../function_store.h" namespace Graph { -class GraphController : public CurveViewWindowWithBannerAndCursorController { +class GraphController : public InteractiveCurveViewController, public InteractiveCurveViewRangeDelegate { public: GraphController(Responder * parentResponder, FunctionStore * functionStore, HeaderViewController * header); View * view() override; @@ -21,16 +20,28 @@ public: bool isEmpty() override; const char * emptyMessage() override; + bool didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange) override; + private: constexpr static int k_maxNumberOfCharacters = 8; + BannerView * bannerView() override; bool handleEnter() override; void reloadBannerView() override; + void initRangeParameters() override; + void initCursorParameters() override; + int moveCursorHorizontally(int direction) override; + int moveCursorVertically(int direction) override; + uint32_t modelVersion() override; + uint32_t rangeVersion() override; + InteractiveCurveViewRange * interactiveCurveViewRange() override; + CurveView * curveView() override; BannerView m_bannerView; GraphView m_view; - GraphWindow m_graphWindow; + InteractiveCurveViewRange m_graphRange; InitialisationParameterController m_initialisationParameterController; CurveParameterController m_curveParameterController; FunctionStore * m_functionStore; + int m_indexFunctionSelectedByCursor; }; } diff --git a/apps/graph/graph/graph_view.cpp b/apps/graph/graph/graph_view.cpp index 6813b1257..22aab8543 100644 --- a/apps/graph/graph/graph_view.cpp +++ b/apps/graph/graph/graph_view.cpp @@ -5,8 +5,8 @@ namespace Graph { -GraphView::GraphView(FunctionStore * functionStore, GraphWindow * graphWindow, BannerView * bannerView, CursorView * cursorView) : - CurveViewWithBannerAndCursor(graphWindow, bannerView, cursorView, 0.0f, 0.0f, 0.2f, 0.0f), +GraphView::GraphView(FunctionStore * functionStore, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor, View * bannerView, View * cursorView) : + CurveView(graphRange, cursor, bannerView, cursorView, 0.0f, 0.0f, 0.2f, 0.0f), m_functionStore(functionStore), m_context(nullptr) { diff --git a/apps/graph/graph/graph_view.h b/apps/graph/graph/graph_view.h index 03b96050c..b6de4efd6 100644 --- a/apps/graph/graph/graph_view.h +++ b/apps/graph/graph/graph_view.h @@ -2,17 +2,17 @@ #define GRAPH_GRAPH_VIEW_H #include -#include "graph_window.h" -#include "../../curve_view_with_banner_and_cursor.h" +#include "../../curve_view.h" #include "../../constant.h" #include "../function_store.h" #include "../local_context.h" +#include "../../interactive_curve_view_range.h" namespace Graph { -class GraphView : public CurveViewWithBannerAndCursor { +class GraphView : public CurveView { public: - GraphView(FunctionStore * functionStore, GraphWindow * graphWindow, BannerView * bannerView, CursorView * cursorView); + GraphView(FunctionStore * functionStore, InteractiveCurveViewRange * graphRange, CurveViewCursor * cursor, View * bannerView, View * cursorView); void drawRect(KDContext * ctx, KDRect rect) const override; void setContext(Context * context); Context * context() const; diff --git a/apps/graph/graph/graph_window.cpp b/apps/graph/graph/graph_window.cpp deleted file mode 100644 index f1b14b469..000000000 --- a/apps/graph/graph/graph_window.cpp +++ /dev/null @@ -1,137 +0,0 @@ -#include "graph_window.h" -#include "../../constant.h" -#include -#include -#include -#include - -namespace Graph { - -GraphWindow::GraphWindow(FunctionStore * functionStore) : - CurveViewWindowWithCursor(), - m_indexFunctionSelectedByCursor(0), - m_functionStore(functionStore), - m_context(nullptr) -{ -} - -bool GraphWindow::computeYaxis() { - if (!m_yAuto) { - return false; - } - if (m_functionStore->numberOfActiveFunctions() > 0) { - float min = FLT_MAX; - float max = -FLT_MAX; - float step = (m_xMax - m_xMin)/Ion::Display::Width; - for (int i=0; inumberOfActiveFunctions(); i++) { - Function * f = m_functionStore->activeFunctionAtIndex(i); - float y = 0.0f; - for (float x = m_xMin; x <= m_xMax; x += step) { - y = f->evaluateAtAbscissa(x, m_context); - if (!isnan(y) && !isinf(y)) { - min = min < y ? min : y; - max = max > y ? max : y; - } - } - } - if (m_yMin == min && m_yMax == max) { - return false; - } - m_yMin = min; - m_yMax = max; - if (m_yMin == m_yMax) { - m_yMin = min - 1; - m_yMax = max + 1; - } - } - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - return true; -} - -Context * GraphWindow::context() { - return m_context; -} - -void GraphWindow::setContext(Context * context) { - m_context = context; -} - -void GraphWindow::setTrigonometric() { - m_xMin = -10.5f; - m_xMax = 10.5f; - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - m_yAuto = false; - m_yMin = -1.6f; - m_yMax = 1.6f; - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - initCursorPosition(); -} - -void GraphWindow::setDefault() { - m_xMin = -10.0f; - m_xMax = 10.0f; - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - setYAuto(true); - initCursorPosition(); -} - -float GraphWindow::derivativeAtCursorPosition() { - Function * f = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor); - return f->approximateDerivative(m_xCursorPosition, m_context); -} - -Function * GraphWindow::functionSelectedByCursor() { - return m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor); -} - -void GraphWindow::setCursorPositionAtAbscissaWithFunction(float abscissa, Function * function) { - m_xCursorPosition = abscissa; - centerAxisAround(GraphWindow::Axis::X, m_xCursorPosition); - m_yCursorPosition = function->evaluateAtAbscissa(m_xCursorPosition, m_context); - centerAxisAround(GraphWindow::Axis::Y, m_yCursorPosition); -} - -void GraphWindow::initCursorPosition() { - m_xCursorPosition = (m_xMin+m_xMax)/2.0f; - m_indexFunctionSelectedByCursor = 0; - Function * firstFunction = m_functionStore->activeFunctionAtIndex(0); - m_yCursorPosition = firstFunction->evaluateAtAbscissa(m_xCursorPosition, m_context); -} - -int GraphWindow::moveCursorHorizontally(int direction) { - m_xCursorPosition = direction > 0 ? m_xCursorPosition + m_xGridUnit/CurveViewWindowWithCursor::k_numberOfCursorStepsInGradUnit : - m_xCursorPosition - m_xGridUnit/CurveViewWindowWithCursor::k_numberOfCursorStepsInGradUnit; - Function * f = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor); - m_yCursorPosition = f->evaluateAtAbscissa(m_xCursorPosition, m_context); - float xMargin = CurveViewWindowWithCursor::k_cursorMarginFactorToBorder * (m_xMax - m_xMin); - float yMargin = CurveViewWindowWithCursor::k_cursorMarginFactorToBorder * (m_yMax - m_yMin); - bool windowHasMoved = panToMakePointVisible(m_xCursorPosition, m_yCursorPosition, xMargin, yMargin); - return windowHasMoved; -} - -int GraphWindow::moveCursorVertically(int direction) { - Function * actualFunction = m_functionStore->activeFunctionAtIndex(m_indexFunctionSelectedByCursor); - float y = actualFunction->evaluateAtAbscissa(m_xCursorPosition, m_context); - Function * nextFunction = actualFunction; - float nextY = direction > 0 ? FLT_MAX : -FLT_MAX; - for (int i = 0; i < m_functionStore->numberOfActiveFunctions(); i++) { - Function * f = m_functionStore->activeFunctionAtIndex(i); - float newY = f->evaluateAtAbscissa(m_xCursorPosition, m_context); - bool isNextFunction = direction > 0 ? (newY > y && newY < nextY) : (newY < y && newY > nextY); - if (isNextFunction) { - m_indexFunctionSelectedByCursor = i; - nextY = newY; - nextFunction = f; - } - } - if (nextFunction == actualFunction) { - return -1; - } - float xMargin = CurveViewWindowWithCursor::k_cursorMarginFactorToBorder * (m_xMax - m_xMin); - float yMargin = CurveViewWindowWithCursor::k_cursorMarginFactorToBorder * (m_yMax - m_yMin); - m_yCursorPosition = nextY; - bool windowHasMoved = panToMakePointVisible(m_xCursorPosition, m_yCursorPosition, xMargin, yMargin); - return windowHasMoved; -} - -} diff --git a/apps/graph/graph/graph_window.h b/apps/graph/graph/graph_window.h deleted file mode 100644 index bed5f669a..000000000 --- a/apps/graph/graph/graph_window.h +++ /dev/null @@ -1,35 +0,0 @@ -#ifndef GRAPH_GRAPH_AXIS_INTERVAL_H -#define GRAPH_GRAPH_AXIS_INTERVAL_H - -#include "../function_store.h" -#include "../../curve_view_window_with_cursor.h" - -namespace Graph { - -class GraphWindow : public CurveViewWindowWithCursor { -public: - GraphWindow(FunctionStore * functionStore); - /* computeYAxis needs to be public to be called when a function is added to - * the function store. */ - bool computeYaxis() override; - Context * context(); - void setContext(Context * context); - void setTrigonometric(); - void setDefault(); - float derivativeAtCursorPosition(); - Function * functionSelectedByCursor(); - void setCursorPositionAtAbscissaWithFunction(float abscissa, Function * function); - void initCursorPosition() override; - int moveCursorHorizontally(int direction) override; - // the result of moveCursorVertically means: - // -1 -> no next function 0-> the window has not changed 1->the window changed - int moveCursorVertically(int direction) override; -private: - int m_indexFunctionSelectedByCursor; - FunctionStore * m_functionStore; - Context * m_context; -}; - -} - -#endif diff --git a/apps/graph/graph/initialisation_parameter_controller.cpp b/apps/graph/graph/initialisation_parameter_controller.cpp index 88cb84ee8..598428156 100644 --- a/apps/graph/graph/initialisation_parameter_controller.cpp +++ b/apps/graph/graph/initialisation_parameter_controller.cpp @@ -4,11 +4,11 @@ namespace Graph { -InitialisationParameterController::InitialisationParameterController(Responder * parentResponder, GraphWindow * graphWindow) : +InitialisationParameterController::InitialisationParameterController(Responder * parentResponder, InteractiveCurveViewRange * graphRange) : ViewController(parentResponder), m_selectableTableView(SelectableTableView(this, this, Metric::TopMargin, Metric::RightMargin, Metric::BottomMargin, Metric::LeftMargin)), - m_graphWindow(graphWindow) + m_graphRange(graphRange) { } @@ -30,20 +30,20 @@ bool InitialisationParameterController::handleEvent(Ion::Events::Event event) { switch (m_selectableTableView.selectedRow()) { case 0: // TODO: if mode == degree, xmin = -600, xmax = 600 - m_graphWindow->setTrigonometric(); + m_graphRange->setTrigonometric(); break; case 1: { - m_graphWindow->roundAbscissa(); + m_graphRange->roundAbscissa(); break; } case 2: { - m_graphWindow->normalize(); + m_graphRange->normalize(); break; } case 3: - m_graphWindow->setDefault(); + m_graphRange->setDefault(); break; default: return false; diff --git a/apps/graph/graph/initialisation_parameter_controller.h b/apps/graph/graph/initialisation_parameter_controller.h index 9c2b1f488..5b0b2bfbc 100644 --- a/apps/graph/graph/initialisation_parameter_controller.h +++ b/apps/graph/graph/initialisation_parameter_controller.h @@ -2,13 +2,13 @@ #define GRAPH_GRAPH_INITIALISATION_PARAMETER_CONTROLLER_H #include -#include "graph_window.h" +#include "../../interactive_curve_view_range.h" namespace Graph { class InitialisationParameterController : public ViewController, public SimpleListViewDataSource { public: - InitialisationParameterController(Responder * parentResponder, GraphWindow * graphWindow); + InitialisationParameterController(Responder * parentResponder, InteractiveCurveViewRange * graphRange); View * view() override; const char * title() const override; bool handleEvent(Ion::Events::Event event) override; @@ -22,7 +22,7 @@ private: constexpr static int k_totalNumberOfCells = 4; MenuListCell m_cells[k_totalNumberOfCells]; SelectableTableView m_selectableTableView; - GraphWindow * m_graphWindow; + InteractiveCurveViewRange * m_graphRange; }; } diff --git a/apps/interactive_curve_view_controller.cpp b/apps/interactive_curve_view_controller.cpp new file mode 100644 index 000000000..0c5cbcf0c --- /dev/null +++ b/apps/interactive_curve_view_controller.cpp @@ -0,0 +1,162 @@ +#include "interactive_curve_view_controller.h" +#include +#include + +InteractiveCurveViewController::InteractiveCurveViewController(Responder * parentResponder, HeaderViewController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView) : + ViewController(parentResponder), + HeaderViewDelegate(header), + m_cursor(), + m_cursorView(CursorView()), + m_rangeParameterController(RangeParameterController(this, interactiveRange)), + m_zoomParameterController(ZoomParameterController(this, interactiveRange, curveView)), + m_rangeButton(this, "Axes", Invocation([](void * context, void * sender) { + InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; + StackViewController * stack = graphController->stackController(); + stack->push(graphController->rangeParameterController()); + }, this)), + m_zoomButton(this, "Zoom", Invocation([](void * context, void * sender) { + InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; + StackViewController * stack = graphController->stackController(); + stack->push(graphController->zoomParameterController()); + }, this)), + m_defaultInitialisationButton(this, "Initialisation", Invocation([](void * context, void * sender) { + InteractiveCurveViewController * graphController = (InteractiveCurveViewController *) context; + StackViewController * stack = graphController->stackController(); + stack->push(graphController->initialisationParameterController()); + }, this)) +{ +} + +const char * InteractiveCurveViewController::title() const { + return "Graphique"; +} + +View * InteractiveCurveViewController::view() { + return curveView(); +} + +bool InteractiveCurveViewController::handleEvent(Ion::Events::Event event) { + if (!curveView()->isMainViewSelected()) { + if (event == Ion::Events::Down) { + headerViewController()->setSelectedButton(-1); + curveView()->selectMainView(true); + curveView()->reloadSelection(); + reloadBannerView(); + return true; + } + if (event == Ion::Events::Up) { + headerViewController()->setSelectedButton(-1); + app()->setFirstResponder(tabController()); + return true; + } + return false; + } + if (event == Ion::Events::Plus) { + interactiveCurveViewRange()->zoom(1.0f/3.0f); + curveView()->reload(); + reloadBannerView(); + return true; + } + if (event == Ion::Events::Minus) { + interactiveCurveViewRange()->zoom(3.0f/4.0f); + curveView()->reload(); + reloadBannerView(); + return true; + } + if (event == Ion::Events::Left || event == Ion::Events::Right) { + int direction = event == Ion::Events::Left ? -1 : 1; + curveView()->reloadSelection(); + int didMoveCursor = moveCursorHorizontally(direction); + if (didMoveCursor == 0) { + curveView()->reloadSelection(); + } + if (didMoveCursor == 1) { + curveView()->reload(); + } + reloadBannerView(); + return (didMoveCursor >= 0); + } + if (event == Ion::Events::Down || event == Ion::Events::Up) { + int direction = event == Ion::Events::Down ? -1 : 1; + curveView()->reloadSelection(); + int didMoveCursor = moveCursorVertically(direction); + if (didMoveCursor < 0) { + if (event == Ion::Events::Down) { + return false; + } + curveView()->selectMainView(false); + headerViewController()->setSelectedButton(0); + return true; + } + if (didMoveCursor == 0) { + curveView()->reloadSelection(); + } + if (didMoveCursor == 1) { + curveView()->reload(); + } + reloadBannerView(); + return true; + } + if (event == Ion::Events::OK) { + return handleEnter(); + } + return false; +} + +void InteractiveCurveViewController::didBecomeFirstResponder() { + uint32_t newModelVersion = modelVersion(); + if (m_modelVersion != newModelVersion) { + m_modelVersion = newModelVersion; + initRangeParameters(); + } + uint32_t newRangeVersion = rangeVersion(); + if (m_rangeVersion != newRangeVersion) { + m_rangeVersion = newRangeVersion; + initCursorParameters(); + } + headerViewController()->setSelectedButton(-1); + curveView()->selectMainView(true); + // Layout view whe the graph view that might have been modified by the zoom page + headerViewController()->layoutView(); + // Reload graph view + curveView()->reload(); + reloadBannerView(); +} + +ViewController * InteractiveCurveViewController::rangeParameterController() { + return &m_rangeParameterController; +} + +ViewController * InteractiveCurveViewController::zoomParameterController() { + return &m_zoomParameterController; +} + +int InteractiveCurveViewController::numberOfButtons() const { + return 3; +} + +Button * InteractiveCurveViewController::buttonAtIndex(int index) { + switch (index) { + case 0: + return &m_rangeButton; + case 1: + return &m_zoomButton; + case 2: + return &m_defaultInitialisationButton; + default: + assert(false); + } + return nullptr; +} + +Responder * InteractiveCurveViewController::defaultController() { + return tabController(); +} + +Responder * InteractiveCurveViewController::tabController() const{ + return (parentResponder()->parentResponder()->parentResponder()->parentResponder()); +} + +StackViewController * InteractiveCurveViewController::stackController() const{ + return (StackViewController *)(parentResponder()->parentResponder()->parentResponder()); +} diff --git a/apps/interactive_curve_view_controller.h b/apps/interactive_curve_view_controller.h new file mode 100644 index 000000000..676439119 --- /dev/null +++ b/apps/interactive_curve_view_controller.h @@ -0,0 +1,58 @@ +#ifndef APPS_INTERACTIVE_CURVE_VIEW_CONTROLLER_H +#define APPS_INTERACTIVE_CURVE_VIEW_CONTROLLER_H + +#include +#include "interactive_curve_view_range.h" +#include "curve_view.h" +#include "cursor_view.h" +#include "banner_view.h" +#include "range_parameter_controller.h" +#include "zoom_parameter_controller.h" + +class InteractiveCurveViewController : public ViewController, public HeaderViewDelegate, public AlternateEmptyViewDelegate { +public: + InteractiveCurveViewController(Responder * parentResponder, HeaderViewController * header, InteractiveCurveViewRange * interactiveRange, CurveView * curveView); + View * view() override; + const char * title() const override; + bool handleEvent(Ion::Events::Event event) override; + void didBecomeFirstResponder() override; + + ViewController * rangeParameterController(); + ViewController * zoomParameterController(); + virtual ViewController * initialisationParameterController() = 0; + + int numberOfButtons() const override; + Button * buttonAtIndex(int index) override; + + Responder * defaultController() override; +protected: + virtual BannerView * bannerView() = 0; + virtual bool handleEnter() = 0; + Responder * tabController() const; + StackViewController * stackController() const; + virtual void reloadBannerView() = 0; + virtual void initRangeParameters() = 0; + virtual void initCursorParameters() = 0; + /* the result of moveCursorVertically/Horizontally means: + * -1 -> the cursor cannot move in this direction + * 0 -> the cursor moved but the window has not changed + * 1-> the cursor moved and the window changed */ + virtual int moveCursorHorizontally(int direction) = 0; + virtual int moveCursorVertically(int direction) = 0; + virtual uint32_t modelVersion() = 0; + virtual uint32_t rangeVersion() = 0; + virtual InteractiveCurveViewRange * interactiveCurveViewRange() = 0; + virtual CurveView * curveView() = 0; + CurveViewCursor m_cursor; + CursorView m_cursorView; +private: + uint32_t m_modelVersion; + uint32_t m_rangeVersion; + RangeParameterController m_rangeParameterController; + ZoomParameterController m_zoomParameterController; + Button m_rangeButton; + Button m_zoomButton; + Button m_defaultInitialisationButton; +}; + +#endif diff --git a/apps/interactive_curve_view_range.cpp b/apps/interactive_curve_view_range.cpp new file mode 100644 index 000000000..9403cd215 --- /dev/null +++ b/apps/interactive_curve_view_range.cpp @@ -0,0 +1,218 @@ +#include "interactive_curve_view_range.h" +#include +#include +#include +#include + +InteractiveCurveViewRange::InteractiveCurveViewRange(CurveViewCursor * cursor, InteractiveCurveViewRangeDelegate * delegate) : + m_xMin(-10.0f), + m_xMax(10.0f), + m_yMin(-10.0f), + m_yMax(10.0f), + m_yAuto(true), + m_xGridUnit(2.0f), + m_yGridUnit(2.0f), + m_cursor(cursor), + m_delegate(delegate) +{ +} + +void InteractiveCurveViewRange::setCursor(CurveViewCursor * cursor) { + m_cursor = cursor; +} + +uint32_t InteractiveCurveViewRange::rangeChecksum() { + float data[5] = {m_xMin, m_xMax, m_yMin, m_yMax, m_yAuto ? 1.0f : 0.0f}; + size_t dataLengthInBytes = 5*sizeof(float); + assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 + //return Ion::crc32((uint32_t *)data, dataLengthInBytes>>2); + return m_xMin+ m_xMax+ m_yMin+ m_yMax+ m_yAuto; +} + +float InteractiveCurveViewRange::xMin() { + return m_xMin; +} + +float InteractiveCurveViewRange::xMax() { + return m_xMax; +} + +float InteractiveCurveViewRange::yMin() { + return m_yMin; +} + +float InteractiveCurveViewRange::yMax() { + return m_yMax; +} + +bool InteractiveCurveViewRange::yAuto() { + return m_yAuto; +} + +float InteractiveCurveViewRange::xGridUnit() { + return m_xGridUnit; +} + +float InteractiveCurveViewRange::yGridUnit() { + return m_yGridUnit; +} + +void InteractiveCurveViewRange::setXMin(float xMin) { + m_xMin = xMin; + if (m_delegate) { + m_delegate->didChangeRange(this); + } + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); +} + +void InteractiveCurveViewRange::setXMax(float xMax) { + m_xMax = xMax; + if (m_delegate) { + m_delegate->didChangeRange(this); + } + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); +} + +void InteractiveCurveViewRange::setYMin(float yMin) { + m_yMin = yMin; + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); +} + +void InteractiveCurveViewRange::setYMax(float yMax) { + m_yMax = yMax; + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); +} + +void InteractiveCurveViewRange::setYAuto(bool yAuto) { + m_yAuto = yAuto; + if (m_delegate) { + m_delegate->didChangeRange(this); + } +} + +void InteractiveCurveViewRange::zoom(float ratio) { + float xMin = m_xMin; + float xMax = m_xMax; + float yMin = m_yMin; + float yMax = m_yMax; + m_xMin = (xMax+xMin)/2.0f - ratio*fabsf(xMax-xMin); + m_xMax = (xMax+xMin)/2.0f + ratio*fabsf(xMax-xMin); + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); + m_yAuto = false; + m_yMin = (yMax+yMin)/2.0f - ratio*fabsf(yMax-yMin); + m_yMax = (yMax+yMin)/2.0f + ratio*fabsf(yMax-yMin); + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); +} + +void InteractiveCurveViewRange::panWithVector(float x, float y) { + m_yAuto = false; + m_xMin = m_xMin + x; + m_xMax = m_xMax + x; + m_yMin = m_yMin + y; + m_yMax = m_yMax + y; + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); +} + +void InteractiveCurveViewRange::roundAbscissa() { + float xMin = m_xMin; + float xMax = m_xMax; + m_xMin = roundf((xMin+xMax)/2) - (float)Ion::Display::Width/2.0f; + m_xMax = roundf((xMin+xMax)/2) + (float)Ion::Display::Width/2.0f-1.0f; + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); + if (m_delegate) { + m_delegate->didChangeRange(this); + } +} + +void InteractiveCurveViewRange::normalize() { + float xMin = m_xMin; + float xMax = m_xMax; + float yMin = m_yMin; + float yMax = m_yMax; + m_xMin = (xMin+xMax)/2 - 5.3f; + m_xMax = (xMin+xMax)/2 + 5.3f; + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); + m_yAuto = false; + m_yMin = (yMin+yMax)/2 - 3.1f; + m_yMax = (yMin+yMax)/2 + 3.1f; + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); +} + +void InteractiveCurveViewRange::setTrigonometric() { + m_xMin = -10.5f; + m_xMax = 10.5f; + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); + m_yAuto = false; + m_yMin = -1.6f; + m_yMax = 1.6f; + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); +} + +void InteractiveCurveViewRange::setDefault() { + m_xMin = -10.0f; + m_xMax = 10.0f; + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); + setYAuto(true); +} + +void InteractiveCurveViewRange::centerAxisAround(Axis axis, float position) { + if (axis == Axis::X) { + float range = m_xMax - m_xMin; + m_xMin = position - range/2.0f; + m_xMax = position + range/2.0f; + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); + } else { + m_yAuto = false; + float range = m_yMax - m_yMin; + m_yMin = position - range/2.0f; + m_yMax = position + range/2.0f; + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); + } +} + +bool InteractiveCurveViewRange::moveCursorTo(float x, float y) { + if (m_cursor) { + m_cursor->moveTo(x, y); + float xMargin = InteractiveCurveViewRange::k_cursorMarginFactorToBorder * (m_xMax - m_xMin); + float yMargin = InteractiveCurveViewRange::k_cursorMarginFactorToBorder * (m_yMax - m_yMin); + bool windowHasMoved = panToMakePointVisible(x, y, xMargin, yMargin); + return windowHasMoved; + } + return false; +} + +bool InteractiveCurveViewRange::panToMakePointVisible(float x, float y, float xMargin, float yMargin) { + bool windowMoved = false; + float xRange = m_xMax - m_xMin; + float yRange = m_yMax - m_yMin; + if (x < m_xMin + xMargin) { + m_xMin = x - xMargin; + m_xMax = m_xMin + xRange; + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); + m_yAuto = false; + windowMoved = true; + } + if (x > m_xMax - xMargin) { + m_xMax = x + xMargin; + m_xMin = m_xMax - xRange; + m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); + m_yAuto = false; + windowMoved = true; + } + if (y < m_yMin + yMargin) { + m_yMin = y - yMargin; + m_yMax = m_yMin + yRange; + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); + m_yAuto = false; + windowMoved = true; + } + if (y > m_yMax - yMargin) { + m_yMax = y + yMargin; + m_yMin = m_yMax - yRange; + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); + m_yAuto = false; + windowMoved = true; + } + return windowMoved; +} diff --git a/apps/curve_view_window_with_cursor.h b/apps/interactive_curve_view_range.h similarity index 53% rename from apps/curve_view_window_with_cursor.h rename to apps/interactive_curve_view_range.h index 236d560a6..146b741b3 100644 --- a/apps/curve_view_window_with_cursor.h +++ b/apps/interactive_curve_view_range.h @@ -1,22 +1,16 @@ -#ifndef APPS_CURVE_VIEW_WINDOW_WITH_CURSOR_H -#define APPS_CURVE_VIEW_WINDOW_WITH_CURSOR_H +#ifndef APPS_INTERACTIVE_CURVE_VIEW_RANGE_H +#define APPS_INTERACTIVE_CURVE_VIEW_RANGE_H -#include "curve_view_window.h" +#include +#include "curve_view_range.h" +#include "curve_view_cursor.h" +#include "interactive_curve_view_range_delegate.h" -class CurveViewWindowWithCursor : public CurveViewWindow { +class InteractiveCurveViewRange : public CurveViewRange { public: - enum class Direction { - Up, - Left, - Down, - Right - }; - CurveViewWindowWithCursor(); - // Cursor - float xCursorPosition(); - float yCursorPosition(); - virtual int moveCursorHorizontally(int direction) = 0; - virtual int moveCursorVertically(int direction) = 0; + InteractiveCurveViewRange(CurveViewCursor * cursor, InteractiveCurveViewRangeDelegate * delegate); + void setCursor(CurveViewCursor * cursor); + uint32_t rangeChecksum(); //CurveViewWindow float xMin() override; @@ -34,21 +28,20 @@ public: // Window void zoom(float ratio); - void translateWindow(Direction direction); + void panWithVector(float x, float y); void roundAbscissa(); void normalize(); + void setTrigonometric(); + virtual void setDefault(); + void centerAxisAround(Axis axis, float position); + + //Cursor + // moveCursorTo returns true if the window has moved + bool moveCursorTo(float x, float y); + constexpr static float k_numberOfCursorStepsInGradUnit = 5.0f; protected: constexpr static float k_cursorMarginFactorToBorder = 0.025f; - constexpr static float k_numberOfCursorStepsInGradUnit = 5.0f; bool panToMakePointVisible(float x, float y, float xMargin, float yMargin); - void centerAxisAround(Axis axis, float position); - virtual void initCursorPosition() = 0; - virtual bool computeYaxis() = 0; - - // Cursor - float m_xCursorPosition; - float m_yCursorPosition; - // Window bounds of the data float m_xMin; float m_xMax; @@ -57,6 +50,8 @@ protected: bool m_yAuto; float m_xGridUnit; float m_yGridUnit; + CurveViewCursor * m_cursor; + InteractiveCurveViewRangeDelegate * m_delegate; }; #endif diff --git a/apps/interactive_curve_view_range_delegate.h b/apps/interactive_curve_view_range_delegate.h new file mode 100644 index 000000000..87645359b --- /dev/null +++ b/apps/interactive_curve_view_range_delegate.h @@ -0,0 +1,11 @@ +#ifndef APPS_INTERACTIVE_CURVE_VIEW_DELEGATE_H +#define APPS_INTERACTIVE_CURVE_VIEW_DELEGATE_H + +class InteractiveCurveViewRange; + +class InteractiveCurveViewRangeDelegate { +public: + virtual bool didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange) = 0; +}; + +#endif diff --git a/apps/probability/law/law.h b/apps/probability/law/law.h index 662ee83d5..3449537c3 100644 --- a/apps/probability/law/law.h +++ b/apps/probability/law/law.h @@ -2,11 +2,11 @@ #define PROBABILITE_LAW_H #include -#include "../../curve_view_window.h" +#include "../../curve_view_range.h" namespace Probability { -class Law : public CurveViewWindow { +class Law : public CurveViewRange { public: enum class Type : uint8_t{ Binomial, diff --git a/apps/probability/law_curve_view.cpp b/apps/probability/law_curve_view.cpp index 7161f31cb..10e0d5d6a 100644 --- a/apps/probability/law_curve_view.cpp +++ b/apps/probability/law_curve_view.cpp @@ -5,14 +5,14 @@ namespace Probability { LawCurveView::LawCurveView() : - CurveView(nullptr, 0.2f, 0.1f, 0.2f, 0.1f), + CurveView(nullptr, nullptr, nullptr, nullptr, 0.2f, 0.1f, 0.2f, 0.1f), m_law(nullptr), m_calculation(nullptr) { } void LawCurveView::setLaw(Law * law) { - setCurveViewWindow(law); + setCurveViewRange(law); m_law = law; } @@ -20,11 +20,6 @@ void LawCurveView::setCalculation(Calculation * calculation) { m_calculation = calculation; } -void LawCurveView::reload() { - markRectAsDirty(bounds()); - computeLabels(Axis::Horizontal); -} - void LawCurveView::drawRect(KDContext * ctx, KDRect rect) const { float lowerBound = m_calculation->lowerBound(); float upperBound = m_calculation->upperBound(); @@ -39,7 +34,9 @@ void LawCurveView::drawRect(KDContext * ctx, KDRect rect) const { } char * LawCurveView::label(Axis axis, int index) const { - assert(axis == Axis::Horizontal); + if (axis == Axis::Vertical) { + return nullptr; + } return (char *)m_labels[index]; } diff --git a/apps/probability/law_curve_view.h b/apps/probability/law_curve_view.h index 3bee58179..2bc4bddde 100644 --- a/apps/probability/law_curve_view.h +++ b/apps/probability/law_curve_view.h @@ -15,7 +15,6 @@ public: LawCurveView(); void setLaw(Law * law); void setCalculation(Calculation * calculation); - void reload() override; void drawRect(KDContext * ctx, KDRect rect) const override; protected: char * label(Axis axis, int index) const override; diff --git a/apps/window_parameter_controller.cpp b/apps/range_parameter_controller.cpp similarity index 54% rename from apps/window_parameter_controller.cpp rename to apps/range_parameter_controller.cpp index 414927192..a4e8a4e31 100644 --- a/apps/window_parameter_controller.cpp +++ b/apps/range_parameter_controller.cpp @@ -1,37 +1,37 @@ -#include "window_parameter_controller.h" +#include "range_parameter_controller.h" #include "apps_container.h" #include -WindowParameterController::WindowParameterController(Responder * parentResponder, CurveViewWindowWithCursor * graphWindow) : +RangeParameterController::RangeParameterController(Responder * parentResponder, InteractiveCurveViewRange * interactiveRange) : FloatParameterController(parentResponder), - m_graphWindow(graphWindow), - m_windowCells{EditableTextMenuListCell(&m_selectableTableView, this, m_draftTextBuffer, (char *)"Xmin"), EditableTextMenuListCell(&m_selectableTableView, this, m_draftTextBuffer, (char *)"Xmax"), + m_interactiveRange(interactiveRange), + m_rangeCells{EditableTextMenuListCell(&m_selectableTableView, this, m_draftTextBuffer, (char *)"Xmin"), EditableTextMenuListCell(&m_selectableTableView, this, m_draftTextBuffer, (char *)"Xmax"), EditableTextMenuListCell(&m_selectableTableView, this, m_draftTextBuffer, (char *)"Ymin"), EditableTextMenuListCell(&m_selectableTableView, this, m_draftTextBuffer, (char *)"Ymax")}, m_yAutoCell(SwitchMenuListCell((char*)"Y auto")) { } -const char * WindowParameterController::title() const { +const char * RangeParameterController::title() const { return "Axes"; } -void WindowParameterController::willDisplayCellForIndex(TableViewCell * cell, int index) { +void RangeParameterController::willDisplayCellForIndex(TableViewCell * cell, int index) { if (index == 2) { SwitchView * switchView = (SwitchView *)m_yAutoCell.accessoryView(); - switchView->setState(m_graphWindow->yAuto()); + switchView->setState(m_interactiveRange->yAuto()); return; } if (index == 3 || index == 4) { - if (m_graphWindow->yAuto()) { - m_windowCells[index-1].setTextColor(Palette::DesactiveTextColor); + if (m_interactiveRange->yAuto()) { + m_rangeCells[index-1].setTextColor(Palette::DesactiveTextColor); } else { - m_windowCells[index-1].setTextColor(KDColorBlack); + m_rangeCells[index-1].setTextColor(KDColorBlack); } } FloatParameterController::willDisplayCellForIndex(cell, index); } -bool WindowParameterController::textFieldDidFinishEditing(TextField * textField, const char * text) { +bool RangeParameterController::textFieldDidFinishEditing(TextField * textField, const char * text) { AppsContainer * appsContainer = (AppsContainer *)app()->container(); Context * globalContext = appsContainer->globalContext(); float floatBody = Expression::parse(text)->approximate(*globalContext); @@ -42,7 +42,7 @@ bool WindowParameterController::textFieldDidFinishEditing(TextField * textField, return true; } -void WindowParameterController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { +void RangeParameterController::tableViewDidChangeSelection(SelectableTableView * t, int previousSelectedCellX, int previousSelectedCellY) { if (previousSelectedCellX == 0 && previousSelectedCellY >= 0 && previousSelectedCellY !=2) { EditableTextMenuListCell * myCell = (EditableTextMenuListCell *)t->cellAtLocation(previousSelectedCellX, previousSelectedCellY); myCell->setEditing(false); @@ -50,76 +50,76 @@ void WindowParameterController::tableViewDidChangeSelection(SelectableTableView } if (t->selectedColumn() == 0 && t->selectedRow() >= 0 && t->selectedRow() !=2) { EditableTextMenuListCell * myNewCell = (EditableTextMenuListCell *)t->cellAtLocation(t->selectedColumn(), t->selectedRow()); - if ((t->selectedRow() == 0 || t->selectedRow() == 1) || !m_graphWindow->yAuto()) { + if ((t->selectedRow() == 0 || t->selectedRow() == 1) || !m_interactiveRange->yAuto()) { app()->setFirstResponder(myNewCell); } } } -bool WindowParameterController::handleEvent(Ion::Events::Event event) { +bool RangeParameterController::handleEvent(Ion::Events::Event event) { if (activeCell() == 2) { if (event == Ion::Events::OK) { - m_graphWindow->setYAuto(!m_graphWindow->yAuto()); + m_interactiveRange->setYAuto(!m_interactiveRange->yAuto()); m_selectableTableView.reloadData(); return true; } return false; } - if (m_graphWindow->yAuto() && (activeCell() == 3 || activeCell() == 4)) { + if (m_interactiveRange->yAuto() && (activeCell() == 3 || activeCell() == 4)) { return false; } return false; } -float WindowParameterController::parameterAtIndex(int index) { +float RangeParameterController::parameterAtIndex(int index) { switch (index) { case 0: - return m_graphWindow->xMin(); + return m_interactiveRange->xMin(); case 1: - return m_graphWindow->xMax(); + return m_interactiveRange->xMax(); case 3: - return m_graphWindow->yMin(); + return m_interactiveRange->yMin(); case 4: - return m_graphWindow->yMax(); + return m_interactiveRange->yMax(); default: assert(false); return 0.0f; } } -void WindowParameterController::setParameterAtIndex(int parameterIndex, float f) { +void RangeParameterController::setParameterAtIndex(int parameterIndex, float f) { switch(parameterIndex) { case 0: - m_graphWindow->setXMin(f); + m_interactiveRange->setXMin(f); break; case 1: - m_graphWindow->setXMax(f); + m_interactiveRange->setXMax(f); break; case 3: - m_graphWindow->setYMin(f); + m_interactiveRange->setYMin(f); break; case 4: - m_graphWindow->setYMax(f); + m_interactiveRange->setYMax(f); break; default: assert(false); } } -int WindowParameterController::numberOfRows() { +int RangeParameterController::numberOfRows() { return k_numberOfTextCell+1; }; -TableViewCell * WindowParameterController::reusableCell(int index) { +TableViewCell * RangeParameterController::reusableCell(int index) { if (index == 2) { return &m_yAutoCell; } int cellIndex = index > 2 ? index - 1 : index; assert(cellIndex >= 0); assert(cellIndex < k_numberOfTextCell); - return &m_windowCells[cellIndex]; + return &m_rangeCells[cellIndex]; } -int WindowParameterController::reusableCellCount() { +int RangeParameterController::reusableCellCount() { return k_numberOfTextCell+1; } diff --git a/apps/window_parameter_controller.h b/apps/range_parameter_controller.h similarity index 67% rename from apps/window_parameter_controller.h rename to apps/range_parameter_controller.h index e59b63ee4..1d395a033 100644 --- a/apps/window_parameter_controller.h +++ b/apps/range_parameter_controller.h @@ -1,13 +1,13 @@ -#ifndef APPS_WINDOW_PARAMETER_CONTROLLER_H -#define APPS_WINDOW_PARAMETER_CONTROLLER_H +#ifndef APPS_RANGE_PARAMETER_CONTROLLER_H +#define APPS_RANGE_PARAMETER_CONTROLLER_H #include -#include "curve_view_window_with_cursor.h" +#include "interactive_curve_view_range.h" #include "float_parameter_controller.h" -class WindowParameterController : public FloatParameterController { +class RangeParameterController : public FloatParameterController { public: - WindowParameterController(Responder * parentResponder, CurveViewWindowWithCursor * graphWindow); + RangeParameterController(Responder * parentResponder, InteractiveCurveViewRange * interactiveCurveViewRange); const char * title() const override; int numberOfRows() override; TableViewCell * reusableCell(int index) override; @@ -20,9 +20,9 @@ private: float parameterAtIndex(int index) override; void setParameterAtIndex(int parameterIndex, float f) override; constexpr static int k_numberOfTextCell = 4; - CurveViewWindowWithCursor * m_graphWindow; + InteractiveCurveViewRange * m_interactiveRange; char m_draftTextBuffer[EditableTextMenuListCell::k_bufferLength]; - EditableTextMenuListCell m_windowCells[k_numberOfTextCell]; + EditableTextMenuListCell m_rangeCells[k_numberOfTextCell]; SwitchMenuListCell m_yAutoCell; }; diff --git a/apps/regression/calculation_controller.h b/apps/regression/calculation_controller.h index addf3a3ea..5d11803ae 100644 --- a/apps/regression/calculation_controller.h +++ b/apps/regression/calculation_controller.h @@ -39,7 +39,7 @@ private: constexpr static int k_totalNumberOfColumns = 2; constexpr static int k_maxNumberOfDisplayableRows = 10; static constexpr KDCoordinate k_cellHeight = 25; - static constexpr KDCoordinate k_cellWidth = 320/2 - Metric::RightMargin/2 - Metric::LeftMargin/2; + static constexpr KDCoordinate k_cellWidth = Ion::Display::Width/2 - Metric::RightMargin/2 - Metric::LeftMargin/2; EvenOddPointerTextCell m_titleCells[k_maxNumberOfDisplayableRows]; EvenOddDoubleBufferTextCell m_columnTitleCell; EvenOddDoubleBufferTextCell m_doubleCalculationCells[k_maxNumberOfDisplayableRows/2]; diff --git a/apps/regression/go_to_parameter_controller.cpp b/apps/regression/go_to_parameter_controller.cpp index c478919e5..f606fa70c 100644 --- a/apps/regression/go_to_parameter_controller.cpp +++ b/apps/regression/go_to_parameter_controller.cpp @@ -3,10 +3,11 @@ namespace Regression { -GoToParameterController::GoToParameterController(Responder * parentResponder, Store * store) : +GoToParameterController::GoToParameterController(Responder * parentResponder, Store * store, CurveViewCursor * cursor) : FloatParameterController(parentResponder), m_abscisseCell(EditableTextMenuListCell(&m_selectableTableView, this, m_draftTextBuffer)), m_store(store), + m_cursor(cursor), m_xPrediction(true) { } @@ -25,17 +26,23 @@ const char * GoToParameterController::title() const { float GoToParameterController::parameterAtIndex(int index) { assert(index == 0); if (m_xPrediction) { - return m_store->xCursorPosition(); + return m_cursor->x(); } - return m_store->yCursorPosition(); + return m_cursor->y(); } void GoToParameterController::setParameterAtIndex(int parameterIndex, float f) { assert(parameterIndex == 0); if (m_xPrediction) { - m_store->setCursorPositionAtAbscissa(f); + float y = m_store->yValueForXValue(f); + m_store->centerAxisAround(CurveViewRange::Axis::X, f); + m_store->centerAxisAround(CurveViewRange::Axis::Y, y); + m_store->moveCursorTo(f, y); } else { - m_store->setCursorPositionAtOrdinate(f); + float x = m_store->xValueForYValue(f); + m_store->centerAxisAround(CurveViewRange::Axis::X, x); + m_store->centerAxisAround(CurveViewRange::Axis::Y, f); + m_store->moveCursorTo(x, f); } } diff --git a/apps/regression/go_to_parameter_controller.h b/apps/regression/go_to_parameter_controller.h index 2d81c2ad5..8a34f9622 100644 --- a/apps/regression/go_to_parameter_controller.h +++ b/apps/regression/go_to_parameter_controller.h @@ -3,13 +3,14 @@ #include #include "../float_parameter_controller.h" +#include "../curve_view_cursor.h" #include "store.h" namespace Regression { class GoToParameterController : public FloatParameterController { public: - GoToParameterController(Responder * parentResponder, Store * store); + GoToParameterController(Responder * parentResponder, Store * store, CurveViewCursor * cursor); void setXPrediction(bool xPrediction); const char * title() const override; int numberOfRows() override; @@ -22,6 +23,7 @@ private: char m_draftTextBuffer[EditableTextMenuListCell::k_bufferLength]; EditableTextMenuListCell m_abscisseCell; Store * m_store; + CurveViewCursor * m_cursor; bool m_xPrediction; }; diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index 06ee3f6ab..641b39423 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -3,29 +3,21 @@ namespace Regression { GraphController::GraphController(Responder * parentResponder, HeaderViewController * headerViewController, Store * store) : - CurveViewWindowWithBannerAndCursorController(parentResponder, headerViewController, store, &m_view), - m_view(GraphView(store, &m_bannerView, &m_cursorView)), + InteractiveCurveViewController(parentResponder, headerViewController, store, &m_view), + m_bannerView(BannerView()), + m_view(GraphView(store, &m_cursor, &m_bannerView, &m_cursorView)), m_store(store), m_initialisationParameterController(InitialisationParameterController(this, m_store)), - m_predictionParameterController(PredictionParameterController(this, m_store)) + m_predictionParameterController(PredictionParameterController(this, m_store, &m_cursor)), + m_selectedDotIndex(-1) { + m_store->setCursor(&m_cursor); } ViewController * GraphController::initialisationParameterController() { return &m_initialisationParameterController; } -void GraphController::didBecomeFirstResponder() { - uint32_t storeChecksum = m_store->checksum(); - if (m_storeVersion != storeChecksum) { - m_storeVersion = storeChecksum; - m_store->initWindowParameters(); - m_store->initCursorPosition(); - } - CurveViewWindowWithBannerAndCursorController::didBecomeFirstResponder(); -} - - bool GraphController::isEmpty() { if (m_store->numberOfPairs() < 2 || isinf(m_store->slope()) || isnan(m_store->slope())) { return true; @@ -40,8 +32,20 @@ const char * GraphController::emptyMessage() { return "Pas assez de donnees pour un regression"; } +BannerView * GraphController::bannerView() { + return &m_bannerView; +} + +CurveView * GraphController::curveView() { + return &m_view; +} + +InteractiveCurveViewRange * GraphController::interactiveCurveViewRange() { + return m_store; +} + bool GraphController::handleEnter() { - if (m_store->selectedDotIndex() == -1) { + if (m_selectedDotIndex == -1) { stackController()->push(&m_predictionParameterController); return true; } @@ -66,9 +70,9 @@ void GraphController::reloadBannerView() { m_bannerView.setLegendAtIndex(buffer, 2); legend = "x = "; - float x = m_store->xCursorPosition(); + float x = m_cursor.x(); // Display a specific legend if the mean dot is selected - if (m_store->selectedDotIndex() == m_store->numberOfPairs()) { + if (m_selectedDotIndex == m_store->numberOfPairs()) { legend = "x^ = "; x = m_store->meanOfColumn(0); } @@ -78,8 +82,8 @@ void GraphController::reloadBannerView() { m_bannerView.setLegendAtIndex(buffer, 3); legend = "y = "; - float y = m_store->yCursorPosition(); - if (m_store->selectedDotIndex() == m_store->numberOfPairs()) { + float y = m_cursor.y(); + if (m_selectedDotIndex == m_store->numberOfPairs()) { legend = "y^ = "; y = m_store->meanOfColumn(1); } @@ -91,4 +95,66 @@ void GraphController::reloadBannerView() { m_bannerView.layoutSubviews(); } +void GraphController::initRangeParameters() { + m_store->setDefault(); +} + +void GraphController::initCursorParameters() { + float x = (m_store->xMin() + m_store->xMax())/2.0f; + float y = m_store->yValueForXValue(x); + m_store->moveCursorTo(x, y); + m_selectedDotIndex = -1; +} + +int GraphController::moveCursorHorizontally(int direction) { + if (m_selectedDotIndex >= 0) { + int dotSelected = m_store->nextDot(direction, m_selectedDotIndex); + if (dotSelected >= 0 && dotSelected < m_store->numberOfPairs()) { + m_selectedDotIndex = dotSelected; + return m_store->moveCursorTo(m_store->get(0, m_selectedDotIndex), m_store->get(1, m_selectedDotIndex)); + } + if (dotSelected == m_store->numberOfPairs()) { + m_selectedDotIndex = dotSelected; + return m_store->moveCursorTo(m_store->meanOfColumn(0), m_store->meanOfColumn(1)); + } + return -1; + } else { + float x = direction > 0 ? m_cursor.x() + m_store->xGridUnit()/InteractiveCurveViewRange::k_numberOfCursorStepsInGradUnit : + m_cursor.x() - m_store->xGridUnit()/InteractiveCurveViewRange::k_numberOfCursorStepsInGradUnit; + float y = m_store->yValueForXValue(x); + return m_store->moveCursorTo(x, y); + } +} + +int GraphController::moveCursorVertically(int direction) { + float yRegressionCurve = m_store->yValueForXValue(m_cursor.x()); + if (m_selectedDotIndex >= 0) { + if ((yRegressionCurve - m_cursor.y() > 0) == (direction > 0)) { + m_selectedDotIndex = -1; + return m_store->moveCursorTo(m_cursor.x(), yRegressionCurve); + } else { + return -1; + } + } else { + int dotSelected = m_store->closestVerticalDot(direction, m_cursor.x()); + if (dotSelected >= 0 && dotSelected < m_store->numberOfPairs()) { + m_selectedDotIndex = dotSelected; + return m_store->moveCursorTo(m_store->get(0, m_selectedDotIndex), m_store->get(1, m_selectedDotIndex)); + } + if (dotSelected == m_store->numberOfPairs()) { + m_selectedDotIndex = dotSelected; + return m_store->moveCursorTo(m_store->meanOfColumn(0), m_store->meanOfColumn(1)); + } + return -1; + } +} + +uint32_t GraphController::modelVersion() { + return m_store->storeChecksum(); +} + +uint32_t GraphController::rangeVersion() { + return m_store->rangeChecksum(); +} + } diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index 773c9b942..6b6594af0 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -7,28 +7,38 @@ #include "graph_view.h" #include "initialisation_parameter_controller.h" #include "prediction_parameter_controller.h" -#include "../curve_view_with_banner_and_cursor_controller.h" +#include "../interactive_curve_view_controller.h" namespace Regression { -class GraphController : public CurveViewWindowWithBannerAndCursorController { +class GraphController : public InteractiveCurveViewController { public: GraphController(Responder * parentResponder, HeaderViewController * headerViewController, Store * store); ViewController * initialisationParameterController() override; - void didBecomeFirstResponder() override; bool isEmpty() override; const char * emptyMessage() override; private: constexpr static int k_maxNumberOfCharacters = 8; + BannerView * bannerView() override; + CurveView * curveView() override; + InteractiveCurveViewRange * interactiveCurveViewRange() override; bool handleEnter() override; void reloadBannerView() override; + void initRangeParameters() override; + void initCursorParameters() override; + int moveCursorHorizontally(int direction) override; + int moveCursorVertically(int direction) override; + uint32_t modelVersion() override; + uint32_t rangeVersion() override; BannerView m_bannerView; GraphView m_view; Store * m_store; - uint32_t m_storeVersion; InitialisationParameterController m_initialisationParameterController; PredictionParameterController m_predictionParameterController; + /* The selectedDotIndex is -1 when no dot is selected, m_numberOfPairs when + * the mean dot is selected and the dot index otherwise */ + int m_selectedDotIndex; }; } diff --git a/apps/regression/graph_view.cpp b/apps/regression/graph_view.cpp index f01fdf091..310fe76d0 100644 --- a/apps/regression/graph_view.cpp +++ b/apps/regression/graph_view.cpp @@ -4,8 +4,8 @@ namespace Regression { -GraphView::GraphView(Store * store, BannerView * bannerView, CursorView * cursorView) : - CurveViewWithBannerAndCursor(store, bannerView, cursorView, 0.05f, 0.05f, 0.25f, 0.05f), +GraphView::GraphView(Store * store, CurveViewCursor * cursor, View * bannerView, View * cursorView) : + CurveView(store, cursor, bannerView, cursorView, 0.05f, 0.05f, 0.25f, 0.05f), m_store(store) { } diff --git a/apps/regression/graph_view.h b/apps/regression/graph_view.h index c723bc994..09b8962c6 100644 --- a/apps/regression/graph_view.h +++ b/apps/regression/graph_view.h @@ -4,14 +4,13 @@ #include #include "store.h" #include "../constant.h" -#include "../cursor_view.h" -#include "../curve_view_with_banner_and_cursor.h" +#include "../curve_view.h" namespace Regression { -class GraphView : public CurveViewWithBannerAndCursor { +class GraphView : public CurveView { public: - GraphView(Store * store, ::BannerView * bannerView, CursorView * cursorView); + GraphView(Store * store, CurveViewCursor * cursor, View * bannerView, View * cursorView); void drawRect(KDContext * ctx, KDRect rect) const override; private: constexpr static KDCoordinate k_dotSize = 5; diff --git a/apps/regression/prediction_parameter_controller.cpp b/apps/regression/prediction_parameter_controller.cpp index 72cf39ace..8b9301c51 100644 --- a/apps/regression/prediction_parameter_controller.cpp +++ b/apps/regression/prediction_parameter_controller.cpp @@ -3,11 +3,11 @@ namespace Regression { -PredictionParameterController::PredictionParameterController(Responder * parentResponder, Store * store) : +PredictionParameterController::PredictionParameterController(Responder * parentResponder, Store * store, CurveViewCursor * cursor) : ViewController(parentResponder), m_selectableTableView(SelectableTableView(this, this, Metric::TopMargin, Metric::RightMargin, Metric::BottomMargin, Metric::LeftMargin)), - m_goToParameterController(GoToParameterController(this, store)) + m_goToParameterController(GoToParameterController(this, store, cursor)) { } diff --git a/apps/regression/prediction_parameter_controller.h b/apps/regression/prediction_parameter_controller.h index aeb829f2a..2565330a4 100644 --- a/apps/regression/prediction_parameter_controller.h +++ b/apps/regression/prediction_parameter_controller.h @@ -3,13 +3,14 @@ #include #include "store.h" +#include "../curve_view_cursor.h" #include "go_to_parameter_controller.h" namespace Regression { class PredictionParameterController : public ViewController, public SimpleListViewDataSource { public: - PredictionParameterController(Responder * parentResponder, Store * store); + PredictionParameterController(Responder * parentResponder, Store * store, CurveViewCursor * cursor); View * view() override; const char * title() const override; bool handleEvent(Ion::Events::Event event) override; diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 559c882f2..5b44331b5 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -7,79 +7,139 @@ namespace Regression { Store::Store() : - CurveViewWindowWithCursor(), - FloatPairStore(), - m_selectedDotIndex(-1) + InteractiveCurveViewRange(nullptr, this), + FloatPairStore() { } -/* Cursor */ - -void Store::initCursorPosition() { - m_xCursorPosition = (m_xMin+m_xMax)/2.0f; - m_yCursorPosition = yValueForXValue(m_xCursorPosition); - m_selectedDotIndex = -1; -} - -int Store::moveCursorVertically(int direction) { - float yRegressionCurve = yValueForXValue(m_xCursorPosition); - if (m_selectedDotIndex >= 0) { - if ((yRegressionCurve - m_yCursorPosition > 0) == (direction > 0)) { - m_selectedDotIndex = -1; - m_yCursorPosition = yRegressionCurve; - } else { - return -1; - } - } else { - int dotSelected = selectClosestDotRelativelyToCurve(direction); - if (dotSelected >= 0) { - m_selectedDotIndex = dotSelected; - } else { - return -1; +bool Store::didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange) { + if (!m_yAuto) { + return false; + } + float min = m_yMin; + float max = m_yMax; + for (int k = 0; k < m_numberOfPairs; k++) { + if (m_xMin <= m_data[0][k] && m_data[0][k] <= m_xMax) { + if (m_data[1][k] < min) { + min = m_data[1][k]; + } + if (m_data[1][k] > max) { + max = m_data[1][k]; + } } } - bool windowHasMoved = panToMakePointVisible(m_xCursorPosition, m_yCursorPosition, 0.0f, 0.0f); - return windowHasMoved; + if (min == m_yMin && max == m_yMax) { + return false; + } + m_yMin = min; + m_yMax = max; + m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); + return true; } -int Store::moveCursorHorizontally(int direction) { - if (m_selectedDotIndex >= 0) { - int dotSelected = selectNextDot(direction); - if (dotSelected >= 0) { - m_selectedDotIndex = dotSelected; - } else { - return -1; +/* Dots */ + +int Store::closestVerticalDot(int direction, float x) { + float nextX = INFINITY; + float nextY = INFINITY; + int selectedDot = -1; + /* The conditions to test on all dots are in this order: + * - the next dot should be within the window abscissa bounds + * - the next dot is the closest one in abscissa to x + * - the next dot is above the regression curve if direction == 1 and below + * otherwise */ + for (int index = 0; index < m_numberOfPairs; index++) { + if ((m_xMin <= m_data[0][index] && m_data[0][index] <= m_xMax) && + (fabsf(m_data[0][index] - x) < fabsf(nextX - x)) && + ((m_data[1][index] - yValueForXValue(m_data[0][index]) >= 0) == (direction > 0))) { + // Handle edge case: if 2 dots have the same abscissa but different ordinates + if (nextX != m_data[0][index] || ((nextY - m_data[1][index] >= 0) == (direction > 0))) { + nextX = m_data[0][index]; + nextY = m_data[1][index]; + selectedDot = index; + } + } + } + // Compare with the mean dot + if (m_xMin <= meanOfColumn(0) && meanOfColumn(0) <= m_xMax && + (fabsf(meanOfColumn(0) - x) < fabsf(nextX - x)) && + ((meanOfColumn(1) - yValueForXValue(meanOfColumn(0)) >= 0) == (direction > 0))) { + if (nextX != meanOfColumn(0) || ((nextY - meanOfColumn(1) >= 0) == (direction > 0))) { + nextX = meanOfColumn(0); + nextY = meanOfColumn(1); + selectedDot = m_numberOfPairs; + } + } + return selectedDot; +} + +int Store::nextDot(int direction, int dot) { + float nextX = INFINITY; + float nextY = INFINITY; + int selectedDot = -1; + float x = meanOfColumn(0); + if (dot >= 0 && dot < m_numberOfPairs) { + x = get(0, dot); + } + /* We have to scan the Store in opposite ways for the 2 directions to ensure to + * select all dots (even with equal abscissa) */ + if (direction > 0) { + for (int index = 0; index < m_numberOfPairs; index++) { + /* The conditions to test are in this order: + * - the next dot is the closest one in abscissa to x + * - the next dot is not the same as the selected one + * - the next dot is at the right of the selected one */ + if (fabsf(m_data[0][index] - x) < fabsf(nextX - x) && + (index != dot) && + (m_data[0][index] >= x)) { + // Handle edge case: 2 dots have same abscissa + if (m_data[0][index] != x || (index > dot)) { + nextX = m_data[0][index]; + nextY = m_data[1][index]; + selectedDot = index; + } + } + } + // Compare with the mean dot + if (fabsf(meanOfColumn(0) - x) < fabsf(nextX - x) && + (m_numberOfPairs != dot) && + (meanOfColumn(0) >= x)) { + if (meanOfColumn(0) != x || (x > dot)) { + nextX = meanOfColumn(0); + nextY = meanOfColumn(1); + selectedDot = m_numberOfPairs; + } } } else { - m_xCursorPosition = direction > 0 ? m_xCursorPosition + m_xGridUnit/CurveViewWindowWithCursor::k_numberOfCursorStepsInGradUnit : - m_xCursorPosition - m_xGridUnit/CurveViewWindowWithCursor::k_numberOfCursorStepsInGradUnit; - m_yCursorPosition = yValueForXValue(m_xCursorPosition); + // Compare with the mean dot + if (fabsf(meanOfColumn(0) - x) < fabsf(nextX - x) && + (m_numberOfPairs != dot) && + (meanOfColumn(0) <= x)) { + if (meanOfColumn(0) != x || (m_numberOfPairs < dot)) { + nextX = meanOfColumn(0); + nextY = meanOfColumn(1); + selectedDot = m_numberOfPairs; + } + } + for (int index = m_numberOfPairs-1; index >= 0; index--) { + if (fabsf(m_data[0][index] - x) < fabsf(nextX - x) && + (index != dot) && + (m_data[0][index] <= x)) { + // Handle edge case: 2 dots have same abscissa + if (m_data[0][index] != x || (index < dot)) { + nextX = m_data[0][index]; + nextY = m_data[1][index]; + selectedDot = index; + } + } + } } - bool windowHasMoved = panToMakePointVisible(m_xCursorPosition, m_yCursorPosition, 0.0f, 0.0f); - return windowHasMoved; -} - -int Store::selectedDotIndex() { - return m_selectedDotIndex; -} - -void Store::setCursorPositionAtAbscissa(float abscissa) { - m_xCursorPosition = abscissa; - centerAxisAround(CurveViewWindow::Axis::X, m_xCursorPosition); - m_yCursorPosition = yValueForXValue(m_xCursorPosition); - centerAxisAround(CurveViewWindow::Axis::Y, m_yCursorPosition); -} - -void Store::setCursorPositionAtOrdinate(float ordinate) { - m_yCursorPosition = ordinate; - centerAxisAround(CurveViewWindow::Axis::Y, m_yCursorPosition); - m_xCursorPosition = xValueForYValue(m_yCursorPosition); - centerAxisAround(CurveViewWindow::Axis::X, m_xCursorPosition); + return selectedDot; } /* Window */ -void Store::initWindowParameters() { +void Store::setDefault() { m_xMin = minValueOfColumn(0); m_xMax = maxValueOfColumn(0); m_yMin = minValueOfColumn(1); @@ -96,11 +156,27 @@ void Store::initWindowParameters() { m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); } -void Store::setDefault() { - initWindowParameters(); +/* Calculations */ + +float Store::maxValueOfColumn(int i) { + float max = -FLT_MAX; + for (int k = 0; k < m_numberOfPairs; k++) { + if (m_data[i][k] > max) { + max = m_data[i][k]; + } + } + return max; } -/* Calculations */ +float Store::minValueOfColumn(int i) { + float min = FLT_MAX; + for (int k = 0; k < m_numberOfPairs; k++) { + if (m_data[i][k] < min) { + min = m_data[i][k]; + } + } + return min; +} float Store::squaredValueSumOfColumn(int i) { float result = 0; @@ -160,152 +236,4 @@ float Store::squaredCorrelationCoefficient() { return cov*cov/(varianceOfColumn(0)*varianceOfColumn(1)); } -/* private methods */ - -float Store::maxValueOfColumn(int i) { - float max = -FLT_MAX; - for (int k = 0; k < m_numberOfPairs; k++) { - if (m_data[i][k] > max) { - max = m_data[i][k]; - } - } - return max; -} - -float Store::minValueOfColumn(int i) { - float min = FLT_MAX; - for (int k = 0; k < m_numberOfPairs; k++) { - if (m_data[i][k] < min) { - min = m_data[i][k]; - } - } - return min; -} - -bool Store::computeYaxis() { - float min = m_yMin; - float max = m_yMax; - for (int k = 0; k < m_numberOfPairs; k++) { - if (m_xMin <= m_data[0][k] && m_data[0][k] <= m_xMax) { - if (m_data[1][k] < min) { - min = m_data[1][k]; - } - if (m_data[1][k] > max) { - max = m_data[1][k]; - } - } - } - if (min == m_yMin && max == m_yMax) { - return false; - } - m_yMin = min; - m_yMax = max; - m_yGridUnit = computeGridUnit(Axis::Y, m_yMin, m_yMax); - return true; -} - -int Store::selectClosestDotRelativelyToCurve(int direction) { - float nextX = INFINITY; - float nextY = INFINITY; - int selectedDot = -1; - /* The conditions to test on all dots are in this order: - * - the next dot should be within the window abscissa bounds - * - the next dot is the closest one in abscissa - * - the next dot is above the selected one if direction == 1 and below - * otherwise */ - for (int index = 0; index < m_numberOfPairs; index++) { - if ((m_xMin <= m_data[0][index] && m_data[0][index] <= m_xMax) && - (fabsf(m_data[0][index] - m_xCursorPosition) < fabsf(nextX - m_xCursorPosition)) && - ((m_data[1][index] - yValueForXValue(m_data[0][index]) >= 0) == (direction > 0))) { - // Handle edge case: if 2 dots have the same abscissa but different ordinates - if (nextX != m_data[0][index] || ((nextY - m_data[1][index] >= 0) == (direction > 0))) { - nextX = m_data[0][index]; - nextY = m_data[1][index]; - selectedDot = index; - } - } - } - // Compare with the mean dot - if (m_xMin <= meanOfColumn(0) && meanOfColumn(0) <= m_xMax && - (fabsf(meanOfColumn(0) - m_xCursorPosition) < fabsf(nextX - m_xCursorPosition)) && - ((meanOfColumn(1) - yValueForXValue(meanOfColumn(0)) >= 0) == (direction > 0))) { - if (nextX != meanOfColumn(0) || ((nextY - meanOfColumn(1) >= 0) == (direction > 0))) { - nextX = meanOfColumn(0); - nextY = meanOfColumn(1); - selectedDot = m_numberOfPairs; - } - } - if (!isinf(nextX) && !isinf(nextY)) { - m_xCursorPosition = nextX; - m_yCursorPosition = nextY; - return selectedDot; - } - return selectedDot; -} - -int Store::selectNextDot(int direction) { - float nextX = INFINITY; - float nextY = INFINITY; - int selectedDot = -1; - /* We have to scan the Store in opposite ways for the 2 directions to ensure to - * select all dots (even with equal abscissa) */ - if (direction > 0) { - for (int index = 0; index < m_numberOfPairs; index++) { - /* The conditions to test are in this order: - * - the next dot is the closest one in abscissa - * - the next dot is not the same as the selected one - * - the next dot is at the right of the selected one */ - if (fabsf(m_data[0][index] - m_xCursorPosition) < fabsf(nextX - m_xCursorPosition) && - (index != m_selectedDotIndex) && - (m_data[0][index] >= m_xCursorPosition)) { - // Handle edge case: 2 dots have same abscissa - if (m_data[0][index] != m_xCursorPosition || (index > m_selectedDotIndex)) { - nextX = m_data[0][index]; - nextY = m_data[1][index]; - selectedDot = index; - } - } - } - // Compare with the mean dot - if (fabsf(meanOfColumn(0) - m_xCursorPosition) < fabsf(nextX - m_xCursorPosition) && - (m_numberOfPairs != m_selectedDotIndex) && - (meanOfColumn(0) >= m_xCursorPosition)) { - if (meanOfColumn(0) != m_xCursorPosition || (m_numberOfPairs > m_selectedDotIndex)) { - nextX = meanOfColumn(0); - nextY = meanOfColumn(1); - selectedDot = m_numberOfPairs; - } - } - } else { - // Compare with the mean dot - if (fabsf(meanOfColumn(0) - m_xCursorPosition) < fabsf(nextX - m_xCursorPosition) && - (m_numberOfPairs != m_selectedDotIndex) && - (meanOfColumn(0) <= m_xCursorPosition)) { - if (meanOfColumn(0) != m_xCursorPosition || (m_numberOfPairs < m_selectedDotIndex)) { - nextX = meanOfColumn(0); - nextY = meanOfColumn(1); - selectedDot = m_numberOfPairs; - } - } - for (int index = m_numberOfPairs-1; index >= 0; index--) { - if (fabsf(m_data[0][index] - m_xCursorPosition) < fabsf(nextX - m_xCursorPosition) && - (index != m_selectedDotIndex) && - (m_data[0][index] <= m_xCursorPosition)) { - // Handle edge case: 2 dots have same abscissa - if (m_data[0][index] != m_xCursorPosition || (index < m_selectedDotIndex)) { - nextX = m_data[0][index]; - nextY = m_data[1][index]; - selectedDot = index; - } - } - } - } - if (!isinf(nextX) && !isinf(nextY)) { - m_xCursorPosition = nextX; - m_yCursorPosition = nextY; - return selectedDot; - } - return selectedDot; -} - } diff --git a/apps/regression/store.h b/apps/regression/store.h index ef7dd3819..8f2556807 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -1,29 +1,26 @@ #ifndef REGRESSION_STORE_H #define REGRESSION_STORE_H -#include "../curve_view_window_with_cursor.h" +#include "../interactive_curve_view_range.h" #include "../float_pair_store.h" namespace Regression { -class Store : public CurveViewWindowWithCursor, public FloatPairStore { +class Store : public InteractiveCurveViewRange, public FloatPairStore, public InteractiveCurveViewRangeDelegate { public: Store(); - // Cursor - void initCursorPosition() override; - // the result of moveCursorVertically means: - // 0-> the window has not changed 1->the window changed - int moveCursorVertically(int direction) override; - int moveCursorHorizontally(int direction) override; - /* The selectedDotIndex is -1 when no dot is selected, m_numberOfPairs when - * the mean dot is selected and the dot index otherwise */ - int selectedDotIndex(); - void setCursorPositionAtAbscissa(float abscissa); - void setCursorPositionAtOrdinate(float ordinate); + bool didChangeRange(InteractiveCurveViewRange * interactiveCurveViewRange) override; + + // Dots + /* Return the closest dot to x above the regression curve if direction > 0, + * below otherwise*/ + int closestVerticalDot(int direction, float x); + /* Return the closest dot to dot given on the right if direction > 0, + * on the left otherwise*/ + int nextDot(int direction, int dot); // Window - void initWindowParameters(); - void setDefault(); + void setDefault() override; // Calculation float squaredValueSumOfColumn(int i); @@ -41,13 +38,6 @@ public: private: float maxValueOfColumn(int i); float minValueOfColumn(int i); - - bool computeYaxis() override; - int selectClosestDotRelativelyToCurve(int direction); - int selectNextDot(int direction); - - // Cursor - int m_selectedDotIndex; }; } diff --git a/apps/statistics/Makefile b/apps/statistics/Makefile index 40dbc257f..a41297390 100644 --- a/apps/statistics/Makefile +++ b/apps/statistics/Makefile @@ -2,8 +2,8 @@ app_objs += $(addprefix apps/statistics/,\ app.o\ box_banner_view.o\ box_controller.o\ + box_range.o\ box_view.o\ - box_window.o\ calculation_controller.o\ histogram_banner_view.o\ histogram_controller.o\ diff --git a/apps/statistics/box_controller.cpp b/apps/statistics/box_controller.cpp index 5ce49bf19..21e176b96 100644 --- a/apps/statistics/box_controller.cpp +++ b/apps/statistics/box_controller.cpp @@ -37,12 +37,6 @@ bool BoxController::handleEvent(Ion::Events::Event event) { } void BoxController::didBecomeFirstResponder() { - uint32_t storeChecksum = m_store->checksum(); - if (m_storeVersion != storeChecksum) { - m_storeVersion = storeChecksum; - m_store->initWindowParameters(); - m_store->initBarParameters(); - } m_view.selectMainView(true); m_view.reload(); reloadBannerView(); diff --git a/apps/statistics/box_controller.h b/apps/statistics/box_controller.h index a22b1204c..8f53b5b15 100644 --- a/apps/statistics/box_controller.h +++ b/apps/statistics/box_controller.h @@ -25,7 +25,6 @@ private: BoxBannerView m_boxBannerView; BoxView m_view; Store * m_store; - uint32_t m_storeVersion; }; } diff --git a/apps/statistics/box_window.cpp b/apps/statistics/box_range.cpp similarity index 60% rename from apps/statistics/box_window.cpp rename to apps/statistics/box_range.cpp index 9b879c31e..f0f9e33cf 100644 --- a/apps/statistics/box_window.cpp +++ b/apps/statistics/box_range.cpp @@ -1,32 +1,32 @@ -#include "box_window.h" +#include "box_range.h" namespace Statistics { -BoxWindow::BoxWindow(Store * store) : +BoxRange::BoxRange(Store * store) : m_store(store) { } -float BoxWindow::xMin() { +float BoxRange::xMin() { return m_store->minValue(); } -float BoxWindow::xMax() { +float BoxRange::xMax() { if (m_store->minValue() >= m_store->maxValue()) { return m_store->minValue() + 1.0f; } return m_store->maxValue(); } -float BoxWindow::yMin() { +float BoxRange::yMin() { return 0.0f; } -float BoxWindow::yMax() { +float BoxRange::yMax() { return 1.0f; } -float BoxWindow::xGridUnit() { +float BoxRange::xGridUnit() { return computeGridUnit(Axis::X, xMin(), xMax()); } diff --git a/apps/statistics/box_window.h b/apps/statistics/box_range.h similarity index 64% rename from apps/statistics/box_window.h rename to apps/statistics/box_range.h index 2c2861530..e4db02bed 100644 --- a/apps/statistics/box_window.h +++ b/apps/statistics/box_range.h @@ -1,14 +1,14 @@ -#ifndef STATISTICS_BOX_WINDOW_H -#define STATISTICS_BOX_WINDOW_H +#ifndef STATISTICS_BOX_RANGE_H +#define STATISTICS_BOX_RANGE_H -#include "../curve_view_window.h" +#include "../curve_view_range.h" #include "store.h" namespace Statistics { -class BoxWindow : public CurveViewWindow { +class BoxRange : public CurveViewRange { public: - BoxWindow(Store * store); + BoxRange(Store * store); float xMin() override; // if the range of value is to wide compared to the bar width, value max is capped float xMax() override; diff --git a/apps/statistics/box_view.cpp b/apps/statistics/box_view.cpp index 4f3d54d80..e74780e3f 100644 --- a/apps/statistics/box_view.cpp +++ b/apps/statistics/box_view.cpp @@ -4,10 +4,10 @@ namespace Statistics { -BoxView::BoxView(Store * store, BannerView * bannerView) : - CurveViewWithBanner(&m_boxWindow, bannerView, 0.0f, 0.2f, 0.4f, 0.2f), +BoxView::BoxView(Store * store, View * bannerView) : + CurveView(&m_boxRange, nullptr, bannerView, nullptr, 0.0f, 0.2f, 0.4f, 0.2f), m_store(store), - m_boxWindow(BoxWindow(store)), + m_boxRange(BoxRange(store)), m_selectedQuantile(0) { } @@ -47,7 +47,7 @@ void BoxView::drawRect(KDContext * ctx, KDRect rect) const { for (int k = 0; k < 5; k++) { drawSegment(ctx, rect, Axis::Vertical, calculations[k], lowBounds[k], upBounds[k], KDColorBlack); } - if (m_mainViewSelected) { + if (isMainViewSelected()) { drawSegment(ctx, rect, Axis::Vertical, calculations[m_selectedQuantile], lowBounds[m_selectedQuantile], upBounds[m_selectedQuantile], KDColorRed); } drawSegment(ctx, rect, Axis::Horizontal, 0.5f, m_store->minValue(), m_store->firstQuartile(), KDColorBlack); diff --git a/apps/statistics/box_view.h b/apps/statistics/box_view.h index 348855ea7..471747b37 100644 --- a/apps/statistics/box_view.h +++ b/apps/statistics/box_view.h @@ -3,15 +3,15 @@ #include #include "store.h" -#include "box_window.h" +#include "box_range.h" #include "../constant.h" -#include "../curve_view_with_banner.h" +#include "../curve_view.h" namespace Statistics { -class BoxView : public CurveViewWithBanner { +class BoxView : public CurveView { public: - BoxView(Store * store, BannerView * bannerView); + BoxView(Store * store, View * bannerView); void reloadSelection() override; int selectedQuantile(); bool selectQuantile(int selectedQuantile); @@ -19,7 +19,7 @@ public: private: char * label(Axis axis, int index) const override; Store * m_store; - BoxWindow m_boxWindow; + BoxRange m_boxRange; char m_labels[k_maxNumberOfXLabels][Constant::FloatBufferSizeInScientificMode]; // -1->Unselect 0->min 1->first quartile 2->median 3->third quartile 4->max int m_selectedQuantile; diff --git a/apps/statistics/calculation_controller.h b/apps/statistics/calculation_controller.h index 2d90530f8..044641d2e 100644 --- a/apps/statistics/calculation_controller.h +++ b/apps/statistics/calculation_controller.h @@ -36,7 +36,7 @@ private: constexpr static int k_totalNumberOfRows = 13; constexpr static int k_maxNumberOfDisplayableRows = 10; static constexpr KDCoordinate k_cellHeight = 25; - static constexpr KDCoordinate k_cellWidth = 320/2 - Metric::RightMargin/2 - Metric::LeftMargin/2; + static constexpr KDCoordinate k_cellWidth = Ion::Display::Width/2 - Metric::RightMargin/2 - Metric::LeftMargin/2; EvenOddPointerTextCell m_titleCells[k_maxNumberOfDisplayableRows]; EvenOddBufferTextCell m_calculationCells[k_maxNumberOfDisplayableRows]; SelectableTableView m_selectableTableView; diff --git a/apps/statistics/histogram_controller.cpp b/apps/statistics/histogram_controller.cpp index c95970a21..20a2efac1 100644 --- a/apps/statistics/histogram_controller.cpp +++ b/apps/statistics/histogram_controller.cpp @@ -1,6 +1,7 @@ #include "histogram_controller.h" #include #include +#include namespace Statistics { @@ -15,6 +16,7 @@ HistogramController::HistogramController(Responder * parentResponder, HeaderView stack->push(histogramController->histogramParameterController()); }, this))), m_store(store), + m_cursor(CurveViewCursor()), m_histogramParameterController(nullptr, store) { } @@ -59,11 +61,8 @@ bool HistogramController::handleEvent(Ion::Events::Event event) { } if (m_view.isMainViewSelected() && (event == Ion::Events::Left || event == Ion::Events::Right)) { int direction = event == Ion::Events::Left ? -1 : 1; - m_view.reloadSelection(); - if (m_store->selectNextBarToward(direction)) { + if (moveSelection(direction)) { m_view.reload(); - } else { - m_view.reloadSelection(); } reloadBannerView(); return true; @@ -72,12 +71,22 @@ bool HistogramController::handleEvent(Ion::Events::Event event) { } void HistogramController::didBecomeFirstResponder() { - uint32_t storeChecksum = m_store->checksum(); + uint32_t storeChecksum = m_store->storeChecksum(); if (m_storeVersion != storeChecksum) { m_storeVersion = storeChecksum; - m_store->initBarParameters(); - m_store->initWindowParameters(); + initBarParameters(); } + uint32_t barChecksum = m_store->barChecksum(); + if (m_barVersion != barChecksum) { + m_barVersion = barChecksum; + initRangeParameters(); + } + uint32_t rangeChecksum = m_store->rangeChecksum(); + if (m_rangeVersion != rangeChecksum) { + m_rangeVersion = rangeChecksum; + initBarSelection(); + } + m_view.setHighlight(m_store->startOfBarAtIndex(m_selectedBarIndex), m_store->endOfBarAtIndex(m_selectedBarIndex)); headerViewController()->setSelectedButton(-1); m_view.selectMainView(true); m_view.reload(); @@ -115,10 +124,10 @@ void HistogramController::reloadBannerView() { const char * legend = "Interval ["; int legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); - float lowerBound = m_store->selectedBar() - m_store->barWidth()/2; + float lowerBound = m_store->startOfBarAtIndex(m_selectedBarIndex); int lowerBoundNumberOfChar = Float(lowerBound).convertFloatToText(buffer+legendLength, Constant::FloatBufferSizeInScientificMode, Constant::NumberOfDigitsInMantissaInScientificMode); buffer[legendLength+lowerBoundNumberOfChar] = ';'; - float upperBound = m_store->selectedBar() + m_store->barWidth()/2; + float upperBound = m_store->endOfBarAtIndex(m_selectedBarIndex); int upperBoundNumberOfChar = Float(upperBound).convertFloatToText(buffer+legendLength+lowerBoundNumberOfChar+1, Constant::FloatBufferSizeInScientificMode, Constant::NumberOfDigitsInMantissaInScientificMode); buffer[legendLength+lowerBoundNumberOfChar+upperBoundNumberOfChar+1] = '['; buffer[legendLength+lowerBoundNumberOfChar+upperBoundNumberOfChar+2] = 0; @@ -127,7 +136,7 @@ void HistogramController::reloadBannerView() { legend = "Effectif: "; legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); - float size = m_store->heightForBarAtValue(m_store->selectedBar()); + float size = m_store->heightOfBarAtIndex(m_selectedBarIndex); Float(size).convertFloatToText(buffer+legendLength, Constant::FloatBufferSizeInScientificMode, Constant::NumberOfDigitsInMantissaInScientificMode); m_bannerView.setLegendAtIndex(buffer, 1); @@ -140,4 +149,79 @@ void HistogramController::reloadBannerView() { m_bannerView.layoutSubviews(); } +bool HistogramController::moveSelection(int deltaIndex) { + int newSelectedBarIndex = m_selectedBarIndex; + if (deltaIndex > 0) { + do { + newSelectedBarIndex++; + } while (m_store->heightOfBarAtIndex(newSelectedBarIndex) == 0 && newSelectedBarIndex < m_store->numberOfBars()); + } else { + do { + newSelectedBarIndex--; + } while (m_store->heightOfBarAtIndex(newSelectedBarIndex) == 0 && newSelectedBarIndex >= 0); + } + if (newSelectedBarIndex >= 0 && newSelectedBarIndex < m_store->numberOfBars()) { + m_selectedBarIndex = newSelectedBarIndex; + m_view.setHighlight(m_store->startOfBarAtIndex(m_selectedBarIndex), m_store->endOfBarAtIndex(m_selectedBarIndex)); + return m_store->scrollToSelectedBarIndex(m_selectedBarIndex); + } + return false; +} + +void HistogramController::initRangeParameters() { + float min = m_store->firstDrawnBarAbscissa(); + float max = m_store->maxValue(); + float barWidth = m_store->barWidth(); + float xMin = min; + float xMax = max + barWidth; + /* if a bar is represented by less than one pixel, we cap xMax */ + if ((xMax - xMin)/barWidth > k_maxNumberOfBarsPerWindow) { + xMax = xMin + k_maxNumberOfBarsPerWindow*barWidth; + } + /* Edge case */ + if (xMin >= xMax) { + xMax = xMin + 10.0f*barWidth; + } + m_store->setXMin(xMin); + m_store->setXMax(xMax); + m_store->setYMin(0.0f); + float yMax = -FLT_MAX; + for (int index = 0; index < m_store->numberOfBars(); index++) { + float size = m_store->heightOfBarAtIndex(index); + if (size > yMax) { + yMax = size; + } + } + yMax = yMax/m_store->sumOfColumn(1); + m_store->setYMax(yMax); +} + +void HistogramController::initBarParameters() { + float min = m_store->minValue(); + float max = m_store->maxValue(); + m_store->setFirstDrawnBarAbscissa(min); + float barWidth = m_store->computeGridUnit(CurveViewRange::Axis::X, min, max); + if (barWidth <= 0.0f) { + barWidth = 1.0f; + } + m_store->setBarWidth(barWidth); +} + +void HistogramController::initBarSelection() { + m_selectedBarIndex = 0; + while ((m_store->heightOfBarAtIndex(m_selectedBarIndex) == 0 && + m_selectedBarIndex < m_store->numberOfBars()) || + m_store->startOfBarAtIndex(m_selectedBarIndex) < m_store->firstDrawnBarAbscissa()) { + m_selectedBarIndex++; + } + if (m_selectedBarIndex >= m_store->numberOfBars()) { + /* No bar is after m_firstDrawnBarAbscissa, so we select the first bar */ + m_selectedBarIndex = 0; + while (m_store->heightOfBarAtIndex(m_selectedBarIndex) == 0 && m_selectedBarIndex < m_store->numberOfBars()) { + m_selectedBarIndex++; + } + } + m_store->scrollToSelectedBarIndex(m_selectedBarIndex); +} + } diff --git a/apps/statistics/histogram_controller.h b/apps/statistics/histogram_controller.h index 0ec5e1568..1f0e6b572 100644 --- a/apps/statistics/histogram_controller.h +++ b/apps/statistics/histogram_controller.h @@ -28,14 +28,24 @@ public: const char * emptyMessage() override; Responder * defaultController() override; private: + constexpr static int k_maxNumberOfBarsPerWindow = 280; constexpr static int k_maxNumberOfCharacters = 12; Responder * tabController() const; void reloadBannerView(); + void initRangeParameters(); + void initBarParameters(); + void initBarSelection(); + // return true if the window has scrolled + bool moveSelection(int deltaIndex); HistogramBannerView m_bannerView; HistogramView m_view; Button m_settingButton; Store * m_store; + CurveViewCursor m_cursor; uint32_t m_storeVersion; + uint32_t m_barVersion; + uint32_t m_rangeVersion; + int m_selectedBarIndex; HistogramParameterController m_histogramParameterController; }; diff --git a/apps/statistics/histogram_parameter_controller.cpp b/apps/statistics/histogram_parameter_controller.cpp index f811dfef2..1ba28639b 100644 --- a/apps/statistics/histogram_parameter_controller.cpp +++ b/apps/statistics/histogram_parameter_controller.cpp @@ -21,7 +21,7 @@ float HistogramParameterController::parameterAtIndex(int index) { if (index == 0) { return m_store->barWidth(); } - return m_store->firsBarAbscissa(); + return m_store->firstDrawnBarAbscissa(); } void HistogramParameterController::setParameterAtIndex(int parameterIndex, float f) { @@ -33,7 +33,7 @@ void HistogramParameterController::setParameterAtIndex(int parameterIndex, float } m_store->setBarWidth(f); } else { - m_store->setFirsBarAbscissa(f); + m_store->setFirstDrawnBarAbscissa(f); } } diff --git a/apps/statistics/histogram_view.cpp b/apps/statistics/histogram_view.cpp index 8ee0e5807..966005d6c 100644 --- a/apps/statistics/histogram_view.cpp +++ b/apps/statistics/histogram_view.cpp @@ -4,19 +4,19 @@ namespace Statistics { -HistogramView::HistogramView(Store * store, BannerView * bannerView) : - CurveViewWithBanner(store, bannerView, 0.2f, 0.1f, 0.4f, 0.1f), - m_store(store) +HistogramView::HistogramView(Store * store, View * bannerView) : + CurveView(store, nullptr, bannerView, nullptr, 0.2f, 0.1f, 0.4f, 0.1f), + m_store(store), + m_highlightedBarStart(NAN), + m_highlightedBarEnd(NAN) { } void HistogramView::reloadSelection() { - float pixelLowerBound = floatToPixel(Axis::Horizontal, m_store->selectedBar() - m_store->barWidth())-1; - float pixelUpperBound = floatToPixel(Axis::Horizontal, m_store->selectedBar() + m_store->barWidth())+1; - float selectedValueInPixels = floatToPixel(Axis::Vertical, (float)m_store->heightForBarAtValue(m_store->selectedBar())/m_store->sumOfColumn(1))-1; - float horizontalAxisInPixels = floatToPixel(Axis::Vertical, 0.0f)+1; - KDRect dirtyZone(KDRect(pixelLowerBound, selectedValueInPixels, pixelUpperBound-pixelLowerBound, - horizontalAxisInPixels - selectedValueInPixels)); + float pixelLowerBound = floatToPixel(Axis::Horizontal, m_highlightedBarStart)-2; + float pixelUpperBound = floatToPixel(Axis::Horizontal, m_highlightedBarEnd)+2; + KDRect dirtyZone(KDRect(pixelLowerBound, 0, pixelUpperBound-pixelLowerBound, + bounds().height())); markRectAsDirty(dirtyZone); } @@ -24,11 +24,20 @@ void HistogramView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(rect, KDColorWhite); drawAxes(ctx, rect, Axis::Horizontal); drawLabels(ctx, rect, Axis::Horizontal, false); - if (m_mainViewSelected) { - drawHistogram(ctx, rect, nullptr, m_store->firsBarAbscissa(), m_store->barWidth(), true, KDColorBlack, KDColorRed, - m_store->selectedBar() - m_store->barWidth()/2, m_store->selectedBar() + m_store->barWidth()/2); + if (isMainViewSelected()) { + drawHistogram(ctx, rect, nullptr, m_store->firstDrawnBarAbscissa(), m_store->barWidth(), true, KDColorBlack, KDColorRed, + m_highlightedBarStart, m_highlightedBarEnd); } else { - drawHistogram(ctx, rect, nullptr, m_store->firsBarAbscissa(), m_store->barWidth(), true, KDColorBlack, KDColorRed); + drawHistogram(ctx, rect, nullptr, m_store->firstDrawnBarAbscissa(), m_store->barWidth(), true, KDColorBlack, KDColorRed); + } +} + +void HistogramView::setHighlight(float start, float end) { + if (m_highlightedBarStart != start || m_highlightedBarEnd != end) { + reloadSelection(); + m_highlightedBarStart = start; + m_highlightedBarEnd = end; + reloadSelection(); } } @@ -40,7 +49,7 @@ char * HistogramView::label(Axis axis, int index) const { } float HistogramView::evaluateModelWithParameter(Model * curve, float t) const { - return (float)m_store->heightForBarAtValue(t)/m_store->sumOfColumn(1); + return m_store->heightOfBarAtValue(t)/m_store->sumOfColumn(1); } } diff --git a/apps/statistics/histogram_view.h b/apps/statistics/histogram_view.h index 80f6e5b4b..3ddaf828e 100644 --- a/apps/statistics/histogram_view.h +++ b/apps/statistics/histogram_view.h @@ -4,20 +4,23 @@ #include #include "store.h" #include "../constant.h" -#include "../curve_view_with_banner.h" +#include "../curve_view.h" namespace Statistics { -class HistogramView : public CurveViewWithBanner { +class HistogramView : public CurveView { public: - HistogramView(Store * store, BannerView * bannerView); + HistogramView(Store * store, View * bannerView); void reloadSelection() override; void drawRect(KDContext * ctx, KDRect rect) const override; + void setHighlight(float start, float end); private: char * label(Axis axis, int index) const override; float evaluateModelWithParameter(Model * curve, float t) const override; Store * m_store; char m_labels[k_maxNumberOfXLabels][Constant::FloatBufferSizeInScientificMode]; + float m_highlightedBarStart; + float m_highlightedBarEnd; }; } diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index 7bad50c5c..36d78b90b 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -7,29 +7,23 @@ namespace Statistics { Store::Store() : + InteractiveCurveViewRange(nullptr, nullptr), FloatPairStore(), m_barWidth(1.0f), - m_selectedBar(0.0f), - m_firstBarAbscissa(0.0f), - m_xMin(0.0f), - m_xMax(10.0f), - m_yMax(1.0f), - m_xGridUnit(1.0f) + m_firstDrawnBarAbscissa(0.0f) { } -/* Histogram bars */ - -void Store::initBarParameters() { - float min = minValue(); - float max = maxValue(); - m_firstBarAbscissa = min; - m_barWidth = computeGridUnit(Axis::X, min, max); - if (m_barWidth <= 0.0f) { - m_barWidth = 1.0f; - } +uint32_t Store::barChecksum() { + float data[2] = {m_barWidth, m_firstDrawnBarAbscissa}; + size_t dataLengthInBytes = 2*sizeof(float); + assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 + //return Ion::crc32((uint32_t *)data, dataLengthInBytes>>2); + return m_barWidth + m_firstDrawnBarAbscissa; } +/* Histogram bars */ + float Store::barWidth() { return m_barWidth; } @@ -41,108 +35,57 @@ void Store::setBarWidth(float barWidth) { m_barWidth = barWidth; } -float Store::firsBarAbscissa() { - return m_firstBarAbscissa; +float Store::firstDrawnBarAbscissa() { + return m_firstDrawnBarAbscissa; } -void Store::setFirsBarAbscissa(float firsBarAbscissa) { - m_firstBarAbscissa = firsBarAbscissa; +void Store::setFirstDrawnBarAbscissa(float firstBarAbscissa) { + m_firstDrawnBarAbscissa = firstBarAbscissa; } -int Store::heightForBarAtValue(float value) { +float Store::heightOfBarAtIndex(int index) { + return sumOfValuesBetween(startOfBarAtIndex(index), endOfBarAtIndex(index)); +} + +float Store::heightOfBarAtValue(float value) { float width = barWidth(); - int barNumber = floorf((value - m_firstBarAbscissa)/width); - float lowerBound = m_firstBarAbscissa + barNumber*width; - float upperBound = m_firstBarAbscissa + (barNumber+1)*width; + int barNumber = floorf((value - m_firstDrawnBarAbscissa)/width); + float lowerBound = m_firstDrawnBarAbscissa + barNumber*width; + float upperBound = m_firstDrawnBarAbscissa + (barNumber+1)*width; return sumOfValuesBetween(lowerBound, upperBound); } -float Store::selectedBar() { - return m_selectedBar; +float Store::startOfBarAtIndex(int index) { + float firstBarAbscissa = m_firstDrawnBarAbscissa + m_barWidth*floorf((minValue()- m_firstDrawnBarAbscissa)/m_barWidth); + return firstBarAbscissa + index * m_barWidth; } -bool Store::selectNextBarToward(int direction) { - float newSelectedBar = m_selectedBar; - float max = maxValue(); - float min = minValue(); - if (direction > 0.0f) { - do { - newSelectedBar += m_barWidth; - newSelectedBar = closestMiddleBarTo(newSelectedBar); - } while (heightForBarAtValue(newSelectedBar) == 0 && newSelectedBar < max + m_barWidth); +float Store::endOfBarAtIndex(int index) { + return startOfBarAtIndex(index) + m_barWidth; +} + +int Store::numberOfBars() { + float firstBarAbscissa = m_firstDrawnBarAbscissa + m_barWidth*floorf((minValue()- m_firstDrawnBarAbscissa)/m_barWidth); + return ceilf((maxValue() - firstBarAbscissa)/m_barWidth)+1; +} + +bool Store::scrollToSelectedBarIndex(int index) { + float startSelectedBar = startOfBarAtIndex(index); + float range = m_xMax - m_xMin; + if (m_xMin > startSelectedBar) { + m_xMin = startSelectedBar; + m_xMax = m_xMin + range; + return true; } - if (direction < 0.0f) { - do { - newSelectedBar -= m_barWidth; - newSelectedBar = closestMiddleBarTo(newSelectedBar); - } while (heightForBarAtValue(newSelectedBar) == 0 && newSelectedBar > min - m_barWidth); - } - if (newSelectedBar > min - m_barWidth && newSelectedBar < max + m_barWidth) { - m_selectedBar = newSelectedBar; - return scrollToSelectedBar(); + float endSelectedBar = endOfBarAtIndex(index); + if (endSelectedBar > m_xMax) { + m_xMax = endSelectedBar; + m_xMin = m_xMax - range; + return true; } return false; } -/* CurveViewWindow */ - -void Store::initWindowParameters() { - float min = minValue(); - float max = maxValue(); - m_xMin = m_firstBarAbscissa; - m_xMax = max + m_barWidth; - if ((m_xMax - m_xMin)/m_barWidth > k_maxNumberOfBarsPerWindow) { - m_xMax = m_xMin + k_maxNumberOfBarsPerWindow*m_barWidth; - } - if (m_xMin >= m_xMax) { - m_xMax = m_xMin + 10.0f*m_barWidth; - } - - m_yMax = -FLT_MAX; - for (float x = min+m_barWidth/2.0f; x < max+m_barWidth; x += m_barWidth) { - float size = heightForBarAtValue(x); - if (size > m_yMax) { - m_yMax = size; - } - } - m_yMax = m_yMax/sumOfColumn(1); - m_xGridUnit = computeGridUnit(Axis::X, m_xMin, m_xMax); - - m_selectedBar = m_firstBarAbscissa + m_barWidth/2.0f; - while (heightForBarAtValue(m_selectedBar) == 0 && m_selectedBar < max + m_barWidth) { - m_selectedBar += m_barWidth; - m_selectedBar = closestMiddleBarTo(m_selectedBar); - } - if (m_selectedBar > max + m_barWidth) { - /* No bar is after m_firstBarAbscissa */ - m_selectedBar = m_firstBarAbscissa + m_barWidth/2.0f; - while (heightForBarAtValue(m_selectedBar) == 0 && m_selectedBar > min - m_barWidth) { - m_selectedBar -= m_barWidth; - m_selectedBar = closestMiddleBarTo(m_selectedBar); - } - } -} - -float Store::xMin() { - return m_xMin; -} - -float Store::xMax() { - return m_xMax; -} - -float Store::yMin() { - return 0.0f; -} - -float Store::yMax() { - return m_yMax; -} - -float Store::xGridUnit() { - return m_xGridUnit; -} - /* Calculation */ float Store::maxValue() { @@ -244,21 +187,6 @@ float Store::sumOfValuesBetween(float x1, float x2) { return result; } -bool Store::scrollToSelectedBar() { - float range = m_xMax - m_xMin; - if (m_xMin > m_selectedBar) { - m_xMin = m_selectedBar - m_barWidth/2; - m_xMax = m_xMin + range; - return true; - } - if (m_selectedBar > m_xMax) { - m_xMax = m_selectedBar + m_barWidth/2; - m_xMin = m_xMax - range; - return true; - } - return false; -} - float Store::sortedElementNumber(int k) { // TODO: use an other algorithm (ex quickselect) to avoid quadratic complexity float bufferValues[m_numberOfPairs]; @@ -283,8 +211,4 @@ int Store::minIndex(float * bufferValues, int bufferLength) { return index; } -float Store::closestMiddleBarTo(float f) { - return m_firstBarAbscissa + roundf((f-m_firstBarAbscissa - m_barWidth/2.0f) / m_barWidth) * m_barWidth + m_barWidth/2.0f; -} - } diff --git a/apps/statistics/store.h b/apps/statistics/store.h index cd3685882..dd48b93c9 100644 --- a/apps/statistics/store.h +++ b/apps/statistics/store.h @@ -1,33 +1,27 @@ #ifndef STATISTICS_STORE_H #define STATISTICS_STORE_H -#include "../curve_view_window.h" +#include "../interactive_curve_view_range.h" #include "../float_pair_store.h" namespace Statistics { -class Store : public CurveViewWindow, public FloatPairStore { +class Store : public InteractiveCurveViewRange, public FloatPairStore { public: Store(); - + uint32_t barChecksum(); // Histogram bars - void initBarParameters(); float barWidth(); void setBarWidth(float barWidth); - float firsBarAbscissa(); - void setFirsBarAbscissa(float firsBarAbscissa); - int heightForBarAtValue(float value); - float selectedBar(); - bool selectNextBarToward(int direction); - - //CurveViewWindow - void initWindowParameters(); - float xMin() override; - // if the range of value is to wide compared to the bar width, value max is capped - float xMax() override; - float yMin() override; - float yMax() override; - float xGridUnit() override; + float firstDrawnBarAbscissa(); + void setFirstDrawnBarAbscissa(float firstDrawnBarAbscissa); + float heightOfBarAtIndex(int index); + float heightOfBarAtValue(float value); + float startOfBarAtIndex(int index); + float endOfBarAtIndex(int index); + int numberOfBars(); + // return true if the window has scrolled + bool scrollToSelectedBarIndex(int index); // Calculation float maxValue(); @@ -43,22 +37,13 @@ public: float sum(); float squaredValueSum(); private: - constexpr static int k_maxNumberOfBarsPerWindow = 300; float defaultValue(int i) override; float sumOfValuesBetween(float x1, float x2); - bool scrollToSelectedBar(); float sortedElementNumber(int k); int minIndex(float * bufferValues, int bufferLength); - float closestMiddleBarTo(float f); // Histogram bars float m_barWidth; - float m_selectedBar; - float m_firstBarAbscissa; - // Window bounds of the data - float m_xMin; - float m_xMax; - float m_yMax; - float m_xGridUnit; + float m_firstDrawnBarAbscissa; }; } diff --git a/apps/store_parameter_controller.cpp b/apps/store_parameter_controller.cpp index 0e43be907..dd8e5ed6f 100644 --- a/apps/store_parameter_controller.cpp +++ b/apps/store_parameter_controller.cpp @@ -17,7 +17,6 @@ void StoreParameterController::selectXColumn(bool xColumnSelected) { m_xColumnSelected = xColumnSelected; } - const char * StoreParameterController::title() const { return "Option de la colonne"; } diff --git a/apps/zoom_parameter_controller.cpp b/apps/zoom_parameter_controller.cpp index 8a46ff5f2..06613f953 100644 --- a/apps/zoom_parameter_controller.cpp +++ b/apps/zoom_parameter_controller.cpp @@ -6,10 +6,10 @@ constexpr KDColor ZoomParameterController::ContentView::LegendView::k_legendBack /* Zoom Parameter Controller */ -ZoomParameterController::ZoomParameterController(Responder * parentResponder, CurveViewWindowWithCursor * graphWindow, CurveViewWithBannerAndCursor * graphView) : +ZoomParameterController::ZoomParameterController(Responder * parentResponder, InteractiveCurveViewRange * interactiveRange, CurveView * curveView) : ViewController(parentResponder), - m_contentView(ContentView(graphView)), - m_graphWindow(graphWindow) + m_contentView(ContentView(curveView)), + m_interactiveRange(interactiveRange) { } @@ -23,33 +23,33 @@ View * ZoomParameterController::view() { bool ZoomParameterController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::Plus) { - m_graphWindow->zoom(1.0f/3.0f); - m_contentView.graphView()->reload(); + m_interactiveRange->zoom(1.0f/3.0f); + m_contentView.curveView()->reload(); return true; } if (event == Ion::Events::Minus) { - m_graphWindow->zoom(3.0f/4.0f); - m_contentView.graphView()->reload(); + m_interactiveRange->zoom(3.0f/4.0f); + m_contentView.curveView()->reload(); return true; } if (event == Ion::Events::Up) { - m_graphWindow->translateWindow(CurveViewWindowWithCursor::Direction::Up); - m_contentView.graphView()->reload(); + m_interactiveRange->panWithVector(0.0f, m_interactiveRange->yGridUnit()); + m_contentView.curveView()->reload(); return true; } if (event == Ion::Events::Down) { - m_graphWindow->translateWindow(CurveViewWindowWithCursor::Direction::Down); - m_contentView.graphView()->reload(); + m_interactiveRange->panWithVector(0.0f, -m_interactiveRange->yGridUnit()); + m_contentView.curveView()->reload(); return true; } if (event == Ion::Events::Left) { - m_graphWindow->translateWindow(CurveViewWindowWithCursor::Direction::Left); - m_contentView.graphView()->reload(); + m_interactiveRange->panWithVector(-m_interactiveRange->xGridUnit(), 0.0f); + m_contentView.curveView()->reload(); return true; } if (event == Ion::Events::Right) { - m_graphWindow->translateWindow(CurveViewWindowWithCursor::Direction::Right); - m_contentView.graphView()->reload(); + m_interactiveRange->panWithVector(m_interactiveRange->xGridUnit(), 0.0f); + m_contentView.curveView()->reload(); return true; } @@ -62,8 +62,8 @@ void ZoomParameterController::didBecomeFirstResponder() { /* Content View */ -ZoomParameterController::ContentView::ContentView(CurveViewWithBannerAndCursor * graphView) : - m_graphView(graphView) +ZoomParameterController::ContentView::ContentView(CurveView * curveView) : + m_curveView(curveView) { } @@ -74,18 +74,18 @@ int ZoomParameterController::ContentView::numberOfSubviews() const { View * ZoomParameterController::ContentView::subviewAtIndex(int index) { assert(index >= 0 && index < 2); if (index == 0) { - return m_graphView; + return m_curveView; } return &m_legendView; } void ZoomParameterController::ContentView::layoutSubviews() { - m_graphView->setFrame(bounds()); + m_curveView->setFrame(bounds()); m_legendView.setFrame(KDRect(0, bounds().height() - k_legendHeight, bounds().width(), k_legendHeight)); } -CurveViewWithBannerAndCursor * ZoomParameterController::ContentView::graphView() { - return m_graphView; +CurveView * ZoomParameterController::ContentView::curveView() { + return m_curveView; } /* Legend View */ diff --git a/apps/zoom_parameter_controller.h b/apps/zoom_parameter_controller.h index 47b7c6303..5b157e6e7 100644 --- a/apps/zoom_parameter_controller.h +++ b/apps/zoom_parameter_controller.h @@ -2,12 +2,12 @@ #define APPS_ZOOM_PARAMETER_CONTROLLER_H #include -#include "curve_view_window_with_cursor.h" -#include "curve_view_with_banner_and_cursor.h" +#include "interactive_curve_view_range.h" +#include "curve_view.h" class ZoomParameterController : public ViewController { public: - ZoomParameterController(Responder * parentResponder, CurveViewWindowWithCursor * graphWindow, CurveViewWithBannerAndCursor * graphView); + ZoomParameterController(Responder * parentResponder, InteractiveCurveViewRange * interactiveCurveViewRange, CurveView * curveView); const char * title() const override; View * view() override; bool handleEvent(Ion::Events::Event event) override; @@ -15,9 +15,9 @@ public: private: class ContentView : public View { public: - ContentView(CurveViewWithBannerAndCursor * graphView); + ContentView(CurveView * curveView); void layoutSubviews() override; - CurveViewWithBannerAndCursor * graphView(); + CurveView * curveView(); private: class LegendView : public View { public: @@ -34,12 +34,12 @@ private: }; int numberOfSubviews() const override; View * subviewAtIndex(int index) override; - CurveViewWithBannerAndCursor * m_graphView; + CurveView * m_curveView; LegendView m_legendView; constexpr static KDCoordinate k_legendHeight = 50; }; ContentView m_contentView; - CurveViewWindowWithCursor * m_graphWindow; + InteractiveCurveViewRange * m_interactiveRange; }; #endif