diff --git a/apps/graph/Makefile b/apps/graph/Makefile index 04edd7eb8..342bcfecd 100644 --- a/apps/graph/Makefile +++ b/apps/graph/Makefile @@ -5,7 +5,6 @@ app_objs += $(addprefix apps/graph/,\ app.o\ cartesian_function.o\ cartesian_function_store.o\ - function_title_cell.o\ graph/banner_view.o\ graph/calculation_graph_controller.o\ graph/calculation_parameter_controller.o\ diff --git a/apps/graph/cartesian_function_store.cpp b/apps/graph/cartesian_function_store.cpp index 70b84f62b..4b2215624 100644 --- a/apps/graph/cartesian_function_store.cpp +++ b/apps/graph/cartesian_function_store.cpp @@ -8,7 +8,6 @@ extern "C" { namespace Graph { constexpr int CartesianFunctionStore::k_maxNumberOfFunctions; -constexpr KDColor CartesianFunctionStore::k_defaultColors[k_maxNumberOfFunctions]; constexpr const char * CartesianFunctionStore::k_functionNames[k_maxNumberOfFunctions]; CartesianFunctionStore::CartesianFunctionStore() : diff --git a/apps/graph/cartesian_function_store.h b/apps/graph/cartesian_function_store.h index 3e60c41b3..f71851f9b 100644 --- a/apps/graph/cartesian_function_store.h +++ b/apps/graph/cartesian_function_store.h @@ -22,9 +22,6 @@ public: void removeAll() override; static constexpr int k_maxNumberOfFunctions = 4; private: - static constexpr KDColor k_defaultColors[k_maxNumberOfFunctions] = { - Palette::Red, Palette::Blue, Palette::Green, Palette::YellowDark, - }; static constexpr const char * k_functionNames[k_maxNumberOfFunctions] = { "f", "g", "h", "p", }; @@ -34,9 +31,6 @@ private: const char * firstAvailableName() override { return firstAvailableAttribute(k_functionNames, FunctionStore::name); } - const KDColor firstAvailableColor() override { - return firstAvailableAttribute(k_defaultColors, FunctionStore::color); - } CartesianFunction m_functions[k_maxNumberOfFunctions]; }; diff --git a/apps/graph/function_title_cell.cpp b/apps/graph/function_title_cell.cpp deleted file mode 100644 index ed336ee18..000000000 --- a/apps/graph/function_title_cell.cpp +++ /dev/null @@ -1,50 +0,0 @@ -#include "function_title_cell.h" -#include - -using namespace Shared; - -namespace Graph { - -FunctionTitleCell::FunctionTitleCell(Orientation orientation, KDText::FontSize size) : - Shared::FunctionTitleCell(orientation), - m_bufferTextView(size, 0.5f, 0.5f) -{ -} - -void FunctionTitleCell::setHighlighted(bool highlight) { - EvenOddCell::setHighlighted(highlight); - m_bufferTextView.setHighlighted(highlight); -} - -void FunctionTitleCell::setEven(bool even) { - EvenOddCell::setEven(even); - m_bufferTextView.setEven(even); -} - -void FunctionTitleCell::setColor(KDColor color) { - Shared::FunctionTitleCell::setColor(color); - m_bufferTextView.setTextColor(color); -} - -void FunctionTitleCell::setText(const char * title) { - m_bufferTextView.setText(title); -} - -int FunctionTitleCell::numberOfSubviews() const { - return 1; -} - -View * FunctionTitleCell::subviewAtIndex(int index) { - assert(index == 0); - return &m_bufferTextView; -} - -void FunctionTitleCell::layoutSubviews() { - KDRect textFrame(0, k_colorIndicatorThickness, bounds().width(), bounds().height() - k_colorIndicatorThickness); - if (m_orientation == Orientation::VerticalIndicator){ - textFrame = KDRect(k_colorIndicatorThickness, 0, bounds().width() - k_colorIndicatorThickness-k_separatorThickness, bounds().height()-k_separatorThickness); - } - m_bufferTextView.setFrame(textFrame); -} - -} diff --git a/apps/graph/list/list_controller.cpp b/apps/graph/list/list_controller.cpp index 97f0df353..4d4cd4449 100644 --- a/apps/graph/list/list_controller.cpp +++ b/apps/graph/list/list_controller.cpp @@ -40,7 +40,7 @@ HighlightCell * ListController::expressionCells(int index) { void ListController::willDisplayTitleCellAtIndex(HighlightCell * cell, int j) { - FunctionTitleCell * myFunctionCell = (FunctionTitleCell *)cell; + Shared::BufferFunctionTitleCell * myFunctionCell = (Shared::BufferFunctionTitleCell *)cell; CartesianFunction * function = ((CartesianFunctionStore *)m_functionStore)->modelAtIndex(j); char bufferName[5] = {*function->name(),'(', m_functionStore->symbol(),')', 0}; myFunctionCell->setText(bufferName); @@ -66,7 +66,7 @@ bool ListController::removeModelRow(ExpressionModel * model) { View * ListController::loadView() { for (int i = 0; i < k_maxNumberOfRows; i++) { - m_functionTitleCells[i] = new FunctionTitleCell(FunctionTitleCell::Orientation::VerticalIndicator); + m_functionTitleCells[i] = new Shared::BufferFunctionTitleCell(FunctionTitleCell::Orientation::VerticalIndicator); m_expressionCells[i] = new FunctionExpressionCell(); m_expressionCells[i]->setMargin(k_expressionMargin); } diff --git a/apps/graph/list/list_controller.h b/apps/graph/list/list_controller.h index 2d4460dc0..fd817ae63 100644 --- a/apps/graph/list/list_controller.h +++ b/apps/graph/list/list_controller.h @@ -2,10 +2,10 @@ #define GRAPH_LIST_CONTROLLER_H #include -#include "../function_title_cell.h" -#include "../cartesian_function_store.h" -#include "../../shared/function_expression_cell.h" #include "../../shared/function_list_controller.h" +#include "../cartesian_function_store.h" +#include "../../shared/buffer_function_title_cell.h" +#include "../../shared/function_expression_cell.h" #include "../../shared/list_parameter_controller.h" namespace Graph { @@ -25,7 +25,7 @@ private: View * loadView() override; void unloadView(View * view) override; constexpr static int k_maxNumberOfRows = 5; - FunctionTitleCell * m_functionTitleCells[k_maxNumberOfRows]; + Shared::BufferFunctionTitleCell * m_functionTitleCells[k_maxNumberOfRows]; Shared::FunctionExpressionCell * m_expressionCells[k_maxNumberOfRows]; Shared::ListParameterController m_parameterController; }; diff --git a/apps/graph/values/values_controller.cpp b/apps/graph/values/values_controller.cpp index 5fdc157ab..f9364f4eb 100644 --- a/apps/graph/values/values_controller.cpp +++ b/apps/graph/values/values_controller.cpp @@ -37,7 +37,7 @@ void ValuesController::willDisplayCellAtLocation(HighlightCell * cell, int i, in } // The cell is a function title cell: if (j == 0 && i > 0) { - FunctionTitleCell * myFunctionCell = (FunctionTitleCell *)cell; + Shared::BufferFunctionTitleCell * myFunctionCell = (Shared::BufferFunctionTitleCell *)cell; CartesianFunction * function = functionAtColumn(i); char bufferName[6] = {0, 0, '(', 'x', ')', 0}; const char * name = nullptr; @@ -122,7 +122,7 @@ int ValuesController::maxNumberOfFunctions() { return k_maxNumberOfFunctions; } -FunctionTitleCell * ValuesController::functionTitleCells(int j) { +Shared::BufferFunctionTitleCell * ValuesController::functionTitleCells(int j) { assert(j >= 0 && j < k_maxNumberOfFunctions); return m_functionTitleCells[j]; } @@ -151,7 +151,7 @@ double ValuesController::evaluationOfAbscissaAtColumn(double abscissa, int colum View * ValuesController::loadView() { for (int i = 0; i < k_maxNumberOfFunctions; i++) { - m_functionTitleCells[i] = new FunctionTitleCell(FunctionTitleCell::Orientation::HorizontalIndicator, KDText::FontSize::Small); + m_functionTitleCells[i] = new Shared::BufferFunctionTitleCell(FunctionTitleCell::Orientation::HorizontalIndicator, KDText::FontSize::Small); } for (int i = 0; i < k_maxNumberOfCells; i++) { m_floatCells[i] = new EvenOddBufferTextCell(); diff --git a/apps/graph/values/values_controller.h b/apps/graph/values/values_controller.h index 2297e5afc..b12507ec6 100644 --- a/apps/graph/values/values_controller.h +++ b/apps/graph/values/values_controller.h @@ -2,7 +2,7 @@ #define GRAPH_VALUES_CONTROLLER_H #include "../cartesian_function_store.h" -#include "../function_title_cell.h" +#include "../../shared/buffer_function_title_cell.h" #include "../../shared/values_controller.h" #include "../../shared/interval_parameter_controller.h" #include "derivative_parameter_controller.h" @@ -27,8 +27,8 @@ private: double evaluationOfAbscissaAtColumn(double abscissa, int columnIndex) override; constexpr static int k_maxNumberOfCells = 50; constexpr static int k_maxNumberOfFunctions = 5; - FunctionTitleCell * m_functionTitleCells[k_maxNumberOfFunctions]; - FunctionTitleCell * functionTitleCells(int j) override; + Shared::BufferFunctionTitleCell * m_functionTitleCells[k_maxNumberOfFunctions]; + Shared::BufferFunctionTitleCell * functionTitleCells(int j) override; EvenOddBufferTextCell * m_floatCells[k_maxNumberOfCells]; EvenOddBufferTextCell * floatCells(int j) override; CartesianFunctionStore * m_functionStore; diff --git a/apps/regression/Makefile b/apps/regression/Makefile index c35c184a3..ade5e836f 100644 --- a/apps/regression/Makefile +++ b/apps/regression/Makefile @@ -5,7 +5,10 @@ app_objs += $(addprefix apps/regression/,\ app.o\ banner_view.o\ calculation_controller.o\ - even_odd_double_buffer_text_cell.o\ + column_title_cell.o\ + even_odd_buffer_text_cell_with_margin.o\ + even_odd_double_buffer_text_cell_with_separator.o\ + even_odd_expression_cell_with_margin.o\ go_to_parameter_controller.o\ graph_controller.o\ graph_view.o\ @@ -13,6 +16,7 @@ app_objs += $(addprefix apps/regression/,\ prediction_parameter_controller.o\ store.o\ store_controller.o\ + regression_context.o\ ) i18n_files += $(addprefix apps/regression/,\ diff --git a/apps/regression/app.cpp b/apps/regression/app.cpp index ecd8180a3..a5bca541e 100644 --- a/apps/regression/app.cpp +++ b/apps/regression/app.cpp @@ -23,7 +23,8 @@ App::Snapshot::Snapshot() : m_cursor(), m_graphSelectedDotIndex(-1), m_modelVersion(0), - m_rangeVersion(0) + m_rangeVersion(0), + m_selectedSeriesIndex(-1) { } @@ -44,32 +45,12 @@ App::Descriptor * App::Snapshot::descriptor() { return &descriptor; } -Store * App::Snapshot::store() { - return &m_store; -} - -CurveViewCursor * App::Snapshot::cursor() { - return &m_cursor; -} - -int * App::Snapshot::graphSelectedDotIndex() { - return &m_graphSelectedDotIndex; -} - -uint32_t * App::Snapshot::modelVersion() { - return &m_modelVersion; -} - -uint32_t * App::Snapshot::rangeVersion() { - return &m_rangeVersion; -} - App::App(Container * container, Snapshot * snapshot) : TextFieldDelegateApp(container, snapshot, &m_tabViewController), m_calculationController(&m_calculationAlternateEmptyViewController, &m_calculationHeader, snapshot->store()), m_calculationAlternateEmptyViewController(&m_calculationHeader, &m_calculationController, &m_calculationController), m_calculationHeader(&m_tabViewController, &m_calculationAlternateEmptyViewController, &m_calculationController), - m_graphController(&m_graphAlternateEmptyViewController, &m_graphHeader, snapshot->store(), snapshot->cursor(), snapshot->modelVersion(), snapshot->rangeVersion(), snapshot->graphSelectedDotIndex()), + m_graphController(&m_graphAlternateEmptyViewController, &m_graphHeader, snapshot->store(), snapshot->cursor(), snapshot->modelVersion(), snapshot->rangeVersion(), snapshot->graphSelectedDotIndex(), snapshot->selectedSeriesIndex()), m_graphAlternateEmptyViewController(&m_graphHeader, &m_graphController, &m_graphController), m_graphHeader(&m_graphStackViewController, &m_graphAlternateEmptyViewController, &m_graphController), m_graphStackViewController(&m_tabViewController, &m_graphHeader), diff --git a/apps/regression/app.h b/apps/regression/app.h index c07eea624..aa976f617 100644 --- a/apps/regression/app.h +++ b/apps/regression/app.h @@ -24,17 +24,19 @@ public: App * unpack(Container * container) override; void reset() override; Descriptor * descriptor() override; - Store * store(); - Shared::CurveViewCursor * cursor(); - int * graphSelectedDotIndex(); - uint32_t * modelVersion(); - uint32_t * rangeVersion(); + Store * store() { return &m_store; } + Shared::CurveViewCursor * cursor() { return &m_cursor; } + int * graphSelectedDotIndex() { return &m_graphSelectedDotIndex; } + int * selectedSeriesIndex() { return &m_selectedSeriesIndex; } + uint32_t * modelVersion() { return &m_modelVersion; } + uint32_t * rangeVersion() { return &m_rangeVersion; } private: Store m_store; Shared::CurveViewCursor m_cursor; int m_graphSelectedDotIndex; uint32_t m_modelVersion; uint32_t m_rangeVersion; + int m_selectedSeriesIndex; }; private: App(Container * container, Snapshot * snapshot); diff --git a/apps/regression/calculation_controller.cpp b/apps/regression/calculation_controller.cpp index d8b774acd..e3186db81 100644 --- a/apps/regression/calculation_controller.cpp +++ b/apps/regression/calculation_controller.cpp @@ -17,9 +17,10 @@ CalculationController::CalculationController(Responder * parentResponder, Button ButtonRowDelegate(header, nullptr), m_titleCells{}, m_r2TitleCell(nullptr), - m_columnTitleCell(nullptr), + m_columnTitleCells{}, m_doubleCalculationCells{}, m_calculationCells{}, + m_hideableCell(nullptr), m_store(store) { m_r2Layout = new HorizontalLayout(new CharLayout('r', KDText::FontSize::Small), new VerticalOffsetLayout(new CharLayout('2', KDText::FontSize::Small), VerticalOffsetLayout::Type::Superscript, false), false); @@ -66,14 +67,14 @@ void CalculationController::tableViewDidChangeSelection(SelectableTableView * t, selectableTableView()->deselectTable(); app()->setFirstResponder(tabController()); } else { - t->selectCellAtLocation(previousSelectedCellX, previousSelectedCellY); + t->selectCellAtLocation(0, 1); } } - if (t->selectedColumn() == 1 && t->selectedRow() >= 0 && t->selectedRow() <= k_totalNumberOfDoubleBufferRows) { - EvenOddDoubleBufferTextCell * myCell = (EvenOddDoubleBufferTextCell *)t->selectedCell(); + if (t->selectedColumn() > 0 && t->selectedRow() >= 0 && t->selectedRow() <= k_totalNumberOfDoubleBufferRows) { + EvenOddDoubleBufferTextCellWithSeparator * myCell = (EvenOddDoubleBufferTextCellWithSeparator *)t->selectedCell(); bool firstSubCellSelected = true; - if (previousSelectedCellX == 1 && previousSelectedCellY >= 0 && previousSelectedCellY <= k_totalNumberOfDoubleBufferRows) { - EvenOddDoubleBufferTextCell * myPreviousCell = (EvenOddDoubleBufferTextCell *)t->cellAtLocation(previousSelectedCellX, previousSelectedCellY); + if (previousSelectedCellX > 0 && previousSelectedCellY >= 0 && previousSelectedCellY <= k_totalNumberOfDoubleBufferRows) { + EvenOddDoubleBufferTextCellWithSeparator * myPreviousCell = (EvenOddDoubleBufferTextCellWithSeparator *)t->cellAtLocation(previousSelectedCellX, previousSelectedCellY); firstSubCellSelected = myPreviousCell->firstTextSelected(); } myCell->selectFirstText(firstSubCellSelected); @@ -100,41 +101,51 @@ int CalculationController::numberOfRows() { } int CalculationController::numberOfColumns() { - return k_totalNumberOfColumns; + return 1 + m_store->numberOfNonEmptySeries(); } void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { + if (i == 0 && j == 0) { + return; + } EvenOddCell * myCell = (EvenOddCell *)cell; myCell->setEven(j%2 == 0); myCell->setHighlighted(i == selectedColumn() && j == selectedRow()); - if (j == 0 && i > 0) { - EvenOddDoubleBufferTextCell * myCell = (EvenOddDoubleBufferTextCell *)cell; - myCell->setFirstText("x"); - myCell->setSecondText("y"); - return; - } + + // Calculation title if (i == 0) { if (j == numberOfRows()-1) { - EvenOddExpressionCell * myCell = (EvenOddExpressionCell *)cell; + EvenOddExpressionCellWithMargin * myCell = (EvenOddExpressionCellWithMargin *)cell; myCell->setExpressionLayout(m_r2Layout); return; } - EvenOddMessageTextCell * myCell = (EvenOddMessageTextCell *)cell; - if (j == 0) { - myCell->setMessage(I18n::Message::Default); - return; - } + MarginEvenOddMessageTextCell * myCell = (MarginEvenOddMessageTextCell *)cell; myCell->setAlignment(1.0f, 0.5f); I18n::Message titles[k_totalNumberOfRows-1] = {I18n::Message::Mean, I18n::Message::Sum, I18n::Message::SquareSum, I18n::Message::StandardDeviation, I18n::Message::Deviation, I18n::Message::NumberOfDots, I18n::Message::Covariance, I18n::Message::Sxy, I18n::Message::Regression, I18n::Message::A, I18n::Message::B, I18n::Message::R, I18n::Message::Default}; myCell->setMessage(titles[j-1]); return; } - if (i == 1 && j > 0 && j <= k_totalNumberOfDoubleBufferRows) { - ArgCalculPointer calculationMethods[k_totalNumberOfDoubleBufferRows] = {&Store::meanOfColumn, &Store::sumOfColumn, - &Store::squaredValueSumOfColumn, &Store::standardDeviationOfColumn, &Store::varianceOfColumn}; - double calculation1 = (m_store->*calculationMethods[j-1])(0); - double calculation2 = (m_store->*calculationMethods[j-1])(1); - EvenOddDoubleBufferTextCell * myCell = (EvenOddDoubleBufferTextCell *)cell; + + int seriesNumber = m_store->indexOfKthNonEmptySeries(i - 1); + assert(i >= 0 && seriesNumber < DoublePairStore::k_numberOfSeries); + + // Coordinate and series title + if (j == 0 && i > 0) { + ColumnTitleCell * myCell = (ColumnTitleCell *)cell; + char buffer[] = {'X', static_cast('1' + seriesNumber), 0}; + myCell->setFirstText(buffer); + buffer[0] = 'Y'; + myCell->setSecondText(buffer); + myCell->setColor(Palette::DataColor[seriesNumber]); + return; + } + + // Calculation cell + if (i > 0 && j > 0 && j <= k_totalNumberOfDoubleBufferRows) { + ArgCalculPointer calculationMethods[k_totalNumberOfDoubleBufferRows] = {&Store::meanOfColumn, &Store::sumOfColumn, &Store::squaredValueSumOfColumn, &Store::standardDeviationOfColumn, &Store::varianceOfColumn}; + double calculation1 = (m_store->*calculationMethods[j-1])(seriesNumber, 0); + double calculation2 = (m_store->*calculationMethods[j-1])(seriesNumber, 1); + EvenOddDoubleBufferTextCellWithSeparator * myCell = (EvenOddDoubleBufferTextCellWithSeparator *)cell; char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)]; PrintFloat::convertFloatToText(calculation1, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); myCell->setFirstText(buffer); @@ -142,17 +153,16 @@ void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int myCell->setSecondText(buffer); return; } - if (i == 1 && j == 9) { - EvenOddBufferTextCell * myCell = (EvenOddBufferTextCell *)cell; + if (i > 0 && j == 9) { + SeparatorEvenOddBufferTextCell * myCell = (SeparatorEvenOddBufferTextCell *)cell; myCell->setText("ax+b"); return; } - if (i == 1 && j > k_totalNumberOfDoubleBufferRows) { + if (i > 0 && j > k_totalNumberOfDoubleBufferRows) { assert(j != 9); - CalculPointer calculationMethods[k_totalNumberOfRows-k_totalNumberOfDoubleBufferRows] = {&Store::numberOfPairs, &Store::covariance, - &Store::columnProductSum, nullptr, &Store::slope, &Store::yIntercept, &Store::correlationCoefficient, &Store::squaredCorrelationCoefficient}; - double calculation = (m_store->*calculationMethods[j-k_totalNumberOfDoubleBufferRows-1])(); - EvenOddBufferTextCell * myCell = (EvenOddBufferTextCell *)cell; + CalculPointer calculationMethods[k_totalNumberOfRows-k_totalNumberOfDoubleBufferRows] = {&Store::doubleCastedNumberOfPairsOfSeries, &Store::covariance, &Store::columnProductSum, nullptr, &Store::slope, &Store::yIntercept, &Store::correlationCoefficient, &Store::squaredCorrelationCoefficient}; + double calculation = (m_store->*calculationMethods[j-k_totalNumberOfDoubleBufferRows-1])(seriesNumber); + SeparatorEvenOddBufferTextCell * myCell = (SeparatorEvenOddBufferTextCell *)cell; char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)]; PrintFloat::convertFloatToText(calculation, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); myCell->setText(buffer); @@ -169,59 +179,69 @@ KDCoordinate CalculationController::rowHeight(int j) { } HighlightCell * CalculationController::reusableCell(int index, int type) { - if (type == 0) { - assert(index < k_maxNumberOfDisplayableRows); + if (type == k_standardCalculationTitleCellType) { + assert(index >= 0 && index < k_maxNumberOfDisplayableRows); assert(m_titleCells[index] != nullptr); return m_titleCells[index]; } - if (type == 1) { + if (type == k_r2CellType) { assert(index == 0); return m_r2TitleCell; } - if (type == 2) { - assert(index == 0); - return m_columnTitleCell; + if (type == k_columnTitleCellType) { + assert(index >= 0 && index < Store::k_numberOfSeries); + return m_columnTitleCells[index]; } - if (type == 3) { - assert(index < k_totalNumberOfDoubleBufferRows); + if (type == k_doubleBufferCalculationCellType) { + assert(index >= 0 && index < k_numberOfDoubleCalculationCells); assert(m_doubleCalculationCells[index] != nullptr); return m_doubleCalculationCells[index]; } - assert(index < k_totalNumberOfRows-k_totalNumberOfDoubleBufferRows); - assert(m_calculationCells[index] != nullptr); + if (type == k_hideableCellType) { + return m_hideableCell; + } + assert(index >= 0 && index < k_numberOfCalculationCells); + assert(m_calculationCells[index] != nullptr); return m_calculationCells[index]; } int CalculationController::reusableCellCount(int type) { - if (type == 0) { + if (type == k_standardCalculationTitleCellType) { return k_maxNumberOfDisplayableRows; } - if (type == 1) { + if (type == k_r2CellType) { return 1; } - if (type == 2) { + if (type == k_columnTitleCellType) { + return Store::k_numberOfSeries; + } + if (type == k_doubleBufferCalculationCellType) { + return k_numberOfDoubleCalculationCells; + } + if (type == k_hideableCellType) { return 1; } - if (type == 3) { - return k_totalNumberOfDoubleBufferRows; - } - return k_totalNumberOfRows-k_totalNumberOfDoubleBufferRows; + assert(type == k_standardCalculationCellType); + return k_numberOfCalculationCells; } int CalculationController::typeAtLocation(int i, int j) { + if (i == 0 && j == 0) { + return k_hideableCellType; + } if (i == 0 && j == k_totalNumberOfRows-1) { - return 1; + return k_r2CellType; } if (i == 0) { - return 0; + return k_standardCalculationTitleCellType; } if (j == 0) { - return 2; + return k_columnTitleCellType; } if (j > 0 && j <= k_totalNumberOfDoubleBufferRows) { - return 3; + return k_doubleBufferCalculationCellType; } - return 4; + return k_standardCalculationCellType; } Responder * CalculationController::tabController() const { @@ -232,34 +252,40 @@ View * CalculationController::loadView() { SelectableTableView * tableView = new SelectableTableView(this, this, this, this); tableView->setVerticalCellOverlap(0); tableView->setBackgroundColor(Palette::WallScreenDark); -; - m_r2TitleCell = new EvenOddExpressionCell(1.0f, 0.5f); - m_columnTitleCell = new EvenOddDoubleBufferTextCell(tableView); - for (int i = 0; i < k_maxNumberOfDisplayableRows; i++) { - m_titleCells[i] = new EvenOddMessageTextCell(KDText::FontSize::Small); + tableView->setMargins(k_margin, k_scrollBarMargin, k_scrollBarMargin, k_margin); + m_r2TitleCell = new EvenOddExpressionCellWithMargin(1.0f, 0.5f); + for (int i = 0; i < Store::k_numberOfSeries; i++) { + m_columnTitleCells[i] = new ColumnTitleCell(tableView); } - for (int i = 0; i < k_totalNumberOfDoubleBufferRows; i++) { - m_doubleCalculationCells[i] = new EvenOddDoubleBufferTextCell(); + for (int i = 0; i < k_maxNumberOfDisplayableRows; i++) { + m_titleCells[i] = new MarginEvenOddMessageTextCell(KDText::FontSize::Small); + } + for (int i = 0; i < k_numberOfDoubleCalculationCells; i++) { + m_doubleCalculationCells[i] = new EvenOddDoubleBufferTextCellWithSeparator(); m_doubleCalculationCells[i]->setTextColor(Palette::GreyDark); m_doubleCalculationCells[i]->setParentResponder(tableView); } - for (int i = 0; i < k_totalNumberOfRows-k_totalNumberOfDoubleBufferRows;i++) { - m_calculationCells[i] = new EvenOddBufferTextCell(KDText::FontSize::Small); + for (int i = 0; i < k_numberOfCalculationCells;i++) { + m_calculationCells[i] = new SeparatorEvenOddBufferTextCell(KDText::FontSize::Small); m_calculationCells[i]->setTextColor(Palette::GreyDark); } + m_hideableCell = new HideableEvenOddCell(); + m_hideableCell->setHide(true); return tableView; } void CalculationController::unloadView(View * view) { delete m_r2TitleCell; m_r2TitleCell = nullptr; - delete m_columnTitleCell; - m_columnTitleCell = nullptr; - for (int i = 0; i < k_totalNumberOfDoubleBufferRows; i++) { + for (int i = 0; i < Store::k_numberOfSeries; i++) { + delete m_columnTitleCells[i]; + m_columnTitleCells[i] = nullptr; + } + for (int i = 0; i < k_numberOfDoubleCalculationCells; i++) { delete m_doubleCalculationCells[i]; m_doubleCalculationCells[i] = nullptr; } - for (int i = 0; i < k_totalNumberOfRows-k_totalNumberOfDoubleBufferRows;i++) { + for (int i = 0; i < k_numberOfCalculationCells;i++) { delete m_calculationCells[i]; m_calculationCells[i] = nullptr; } @@ -267,6 +293,8 @@ void CalculationController::unloadView(View * view) { delete m_titleCells[i]; m_titleCells[i] = nullptr; } + delete m_hideableCell; + m_hideableCell = nullptr; TabTableController::unloadView(view); } diff --git a/apps/regression/calculation_controller.h b/apps/regression/calculation_controller.h index 2de5133d3..3b581be69 100644 --- a/apps/regression/calculation_controller.h +++ b/apps/regression/calculation_controller.h @@ -3,9 +3,15 @@ #include #include "store.h" -#include "even_odd_double_buffer_text_cell.h" +#include "column_title_cell.h" +#include "even_odd_double_buffer_text_cell_with_separator.h" +#include "even_odd_expression_cell_with_margin.h" +#include "../shared/hideable_even_odd_cell.h" +#include "../shared/margin_even_odd_message_text_cell.h" #include "../shared/tab_table_controller.h" #include "../shared/regular_table_view_data_source.h" +#include "../shared/separator_even_odd_buffer_text_cell.h" +#include "../shared/store_cell.h" namespace Regression { @@ -36,21 +42,34 @@ public: int reusableCellCount(int type) override; int typeAtLocation(int i, int j) override; private: + constexpr static int k_totalNumberOfRows = 14; + constexpr static int k_maxNumberOfDisplayableRows = 11; + constexpr static int k_totalNumberOfDoubleBufferRows = 5; + constexpr static int k_numberOfDoubleCalculationCells = Store::k_numberOfSeries * k_totalNumberOfDoubleBufferRows; + constexpr static int k_numberOfCalculationCells = Store::k_numberOfSeries * k_totalNumberOfRows - k_numberOfDoubleCalculationCells; + + constexpr static int k_standardCalculationTitleCellType = 0; + constexpr static int k_r2CellType = 1; + constexpr static int k_columnTitleCellType = 2; + constexpr static int k_doubleBufferCalculationCellType = 3; + constexpr static int k_standardCalculationCellType = 4; + static constexpr int k_hideableCellType = 5; + + static constexpr KDCoordinate k_cellHeight = 25; + static constexpr KDCoordinate k_cellWidth = Ion::Display::Width/2 - Metric::CommonRightMargin/2 - Metric::CommonLeftMargin/2; + static constexpr KDCoordinate k_margin = 8; + static constexpr KDCoordinate k_scrollBarMargin = Metric::CommonRightMargin; + Responder * tabController() const override; View * loadView() override; void unloadView(View * view) override; - constexpr static int k_totalNumberOfRows = 14; - constexpr static int k_totalNumberOfColumns = 2; - constexpr static int k_maxNumberOfDisplayableRows = 11; - constexpr static int k_totalNumberOfDoubleBufferRows = 5; - static constexpr KDCoordinate k_cellHeight = 25; - static constexpr KDCoordinate k_cellWidth = Ion::Display::Width/2 - Metric::CommonRightMargin/2 - Metric::CommonLeftMargin/2; - EvenOddMessageTextCell * m_titleCells[k_maxNumberOfDisplayableRows]; - EvenOddExpressionCell * m_r2TitleCell; + Shared::MarginEvenOddMessageTextCell * m_titleCells[k_maxNumberOfDisplayableRows]; + EvenOddExpressionCellWithMargin * m_r2TitleCell; Poincare::ExpressionLayout * m_r2Layout; - EvenOddDoubleBufferTextCell * m_columnTitleCell; - EvenOddDoubleBufferTextCell * m_doubleCalculationCells[k_totalNumberOfDoubleBufferRows]; - EvenOddBufferTextCell * m_calculationCells[k_totalNumberOfRows-k_totalNumberOfDoubleBufferRows]; + ColumnTitleCell * m_columnTitleCells[Store::k_numberOfSeries]; + EvenOddDoubleBufferTextCellWithSeparator * m_doubleCalculationCells[k_numberOfDoubleCalculationCells]; + Shared::SeparatorEvenOddBufferTextCell * m_calculationCells[k_numberOfCalculationCells]; + Shared::HideableEvenOddCell * m_hideableCell; Store * m_store; }; diff --git a/apps/regression/column_title_cell.cpp b/apps/regression/column_title_cell.cpp new file mode 100644 index 000000000..12eeaf985 --- /dev/null +++ b/apps/regression/column_title_cell.cpp @@ -0,0 +1,24 @@ +#include "column_title_cell.h" + +namespace Regression { + +void ColumnTitleCell::setColor(KDColor color) { + m_functionColor = color; + m_firstBufferTextView.setTextColor(color); + m_secondBufferTextView.setTextColor(color); + reloadCell(); +} + +void ColumnTitleCell::drawRect(KDContext * ctx, KDRect rect) const { + EvenOddDoubleBufferTextCellWithSeparator::drawRect(ctx, rect); + ctx->fillRect(KDRect(Metric::TableSeparatorThickness, 0, bounds().width(), k_colorIndicatorThickness), m_functionColor); +} + +void ColumnTitleCell::layoutSubviews() { + KDCoordinate width = bounds().width() - Metric::TableSeparatorThickness; + KDCoordinate height = bounds().height(); + m_firstBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness, k_colorIndicatorThickness, width/2, height - k_colorIndicatorThickness)); + m_secondBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness + width/2, k_colorIndicatorThickness, width/2, height - k_colorIndicatorThickness)); +} + +} diff --git a/apps/regression/column_title_cell.h b/apps/regression/column_title_cell.h new file mode 100644 index 000000000..fa85817a1 --- /dev/null +++ b/apps/regression/column_title_cell.h @@ -0,0 +1,25 @@ +#ifndef REGRESSION_COLUMN_TITLE_CELL_H +#define REGRESSION_COLUMN_TITLE_CELL_H + +#include "even_odd_double_buffer_text_cell_with_separator.h" + +namespace Regression { + +class ColumnTitleCell : public EvenOddDoubleBufferTextCellWithSeparator { +public: + ColumnTitleCell(Responder * parentResponder = nullptr) : + EvenOddDoubleBufferTextCellWithSeparator(parentResponder, 0.5f, 0.5f), + m_functionColor(Palette::Red) + { + } + virtual void setColor(KDColor color); + void drawRect(KDContext * ctx, KDRect rect) const override; + void layoutSubviews() override; +private: + constexpr static KDCoordinate k_colorIndicatorThickness = 2; + KDColor m_functionColor; +}; + +} + +#endif diff --git a/apps/regression/even_odd_buffer_text_cell_with_margin.cpp b/apps/regression/even_odd_buffer_text_cell_with_margin.cpp new file mode 100644 index 000000000..63602fe64 --- /dev/null +++ b/apps/regression/even_odd_buffer_text_cell_with_margin.cpp @@ -0,0 +1,12 @@ +#include "even_odd_buffer_text_cell_with_margin.h" +#include + +namespace Regression { + +void EvenOddBufferTextCellWithMargin::layoutSubviews() { + KDRect boundsThis = bounds(); + KDRect boundsBuffer = KDRect(boundsThis.left() + k_horizontalMargin, boundsThis.top(), boundsThis.width() - 2*k_horizontalMargin, boundsThis.height()); + m_bufferTextView.setFrame(boundsBuffer); +} + +} diff --git a/apps/regression/even_odd_buffer_text_cell_with_margin.h b/apps/regression/even_odd_buffer_text_cell_with_margin.h new file mode 100644 index 000000000..414f9cfd5 --- /dev/null +++ b/apps/regression/even_odd_buffer_text_cell_with_margin.h @@ -0,0 +1,17 @@ +#ifndef REGRESSION_EVEN_ODD_BUFFER_TEXT_CELL_WITH_MARGIN_H +#define REGRESSION_EVEN_ODD_BUFFER_TEXT_CELL_WITH_MARGIN_H + +#include + +namespace Regression { + +class EvenOddBufferTextCellWithMargin : public EvenOddBufferTextCell { +public: + using EvenOddBufferTextCell::EvenOddBufferTextCell; + void layoutSubviews() override; +private: + static constexpr KDCoordinate k_horizontalMargin = 2; +}; + +} +#endif diff --git a/apps/regression/even_odd_double_buffer_text_cell.cpp b/apps/regression/even_odd_double_buffer_text_cell.cpp deleted file mode 100644 index dd31169b0..000000000 --- a/apps/regression/even_odd_double_buffer_text_cell.cpp +++ /dev/null @@ -1,108 +0,0 @@ -#include "even_odd_double_buffer_text_cell.h" -#include - -EvenOddDoubleBufferTextCell::EvenOddDoubleBufferTextCell(Responder * parentResponder) : - EvenOddCell(), - Responder(parentResponder), - m_firstTextSelected(true), - m_firstBufferTextView(KDText::FontSize::Small), - m_secondBufferTextView(KDText::FontSize::Small) -{ -} - -const char * EvenOddDoubleBufferTextCell::firstText() { - return m_firstBufferTextView.text(); -} - -const char * EvenOddDoubleBufferTextCell::secondText() { - return m_secondBufferTextView.text(); -} - -bool EvenOddDoubleBufferTextCell::firstTextSelected() { - return m_firstTextSelected; -} - -void EvenOddDoubleBufferTextCell::selectFirstText(bool selectFirstText) { - m_firstTextSelected = selectFirstText; - m_firstBufferTextView.setHighlighted(selectFirstText); - m_secondBufferTextView.setHighlighted(!selectFirstText); - reloadCell(); -} - -void EvenOddDoubleBufferTextCell::reloadCell() { - m_firstBufferTextView.reloadCell(); - m_secondBufferTextView.reloadCell(); -} - -void EvenOddDoubleBufferTextCell::setHighlighted(bool highlight) { - m_firstBufferTextView.setHighlighted(false); - m_secondBufferTextView.setHighlighted(false); - HighlightCell::setHighlighted(highlight); - if (isHighlighted()) { - if (m_firstTextSelected) { - m_firstBufferTextView.setHighlighted(true); - } else { - m_secondBufferTextView.setHighlighted(false); - } - } - reloadCell(); -} - -const char * EvenOddDoubleBufferTextCell::text() const { - if (m_firstTextSelected) { - return m_firstBufferTextView.text(); - } else { - return m_secondBufferTextView.text(); - } -} - -void EvenOddDoubleBufferTextCell::setEven(bool even) { - m_firstBufferTextView.setEven(even); - m_secondBufferTextView.setEven(even); - reloadCell(); -} - -void EvenOddDoubleBufferTextCell::setFirstText(const char * textContent) { - m_firstBufferTextView.setText(textContent); -} - -void EvenOddDoubleBufferTextCell::setSecondText(const char * textContent) { - m_secondBufferTextView.setText(textContent); -} - -void EvenOddDoubleBufferTextCell::setTextColor(KDColor textColor) { - m_firstBufferTextView.setTextColor(textColor); - m_secondBufferTextView.setTextColor(textColor); -} - -int EvenOddDoubleBufferTextCell::numberOfSubviews() const { - return 2; -} - -View * EvenOddDoubleBufferTextCell::subviewAtIndex(int index) { - assert(index == 0 || index == 1); - if (index == 0) { - return &m_firstBufferTextView; - } - return &m_secondBufferTextView; -} - -void EvenOddDoubleBufferTextCell::layoutSubviews() { - KDCoordinate width = bounds().width(); - KDCoordinate height = bounds().height(); - m_firstBufferTextView.setFrame(KDRect(0, 0, width/2, height)); - m_secondBufferTextView.setFrame(KDRect(width/2, 0, width/2, height)); -} - -bool EvenOddDoubleBufferTextCell::handleEvent(Ion::Events::Event event) { - if (m_firstTextSelected && event == Ion::Events::Right) { - selectFirstText(false); - return true; - } - if (!m_firstTextSelected && event == Ion::Events::Left) { - selectFirstText(true); - return true; - } - return false; -} - diff --git a/apps/regression/even_odd_double_buffer_text_cell.h b/apps/regression/even_odd_double_buffer_text_cell.h deleted file mode 100644 index 8e612e05d..000000000 --- a/apps/regression/even_odd_double_buffer_text_cell.h +++ /dev/null @@ -1,33 +0,0 @@ -#ifndef REGRESSION_EVEN_ODD_DOUBLE_BUFFER_TEXT_CELL_H -#define REGRESSION_EVEN_ODD_DOUBLE_BUFFER_TEXT_CELL_H - -#include - -class EvenOddDoubleBufferTextCell : public EvenOddCell, public Responder{ -public: - EvenOddDoubleBufferTextCell(Responder * parentResponder = nullptr); - const char * firstText(); - const char * secondText(); - void reloadCell() override; - void setHighlighted(bool highlight) override; - Responder * responder() override { - return this; - } - const char * text() const override; - void setEven(bool even) override; - bool firstTextSelected(); - void selectFirstText(bool selectFirstText); - void setFirstText(const char * textContent); - void setSecondText(const char * textContent); - void setTextColor(KDColor textColor); - int numberOfSubviews() const override; - View * subviewAtIndex(int index) override; - void layoutSubviews() override; - bool handleEvent(Ion::Events::Event event) override; -protected: - bool m_firstTextSelected; - EvenOddBufferTextCell m_firstBufferTextView; - EvenOddBufferTextCell m_secondBufferTextView; -}; - -#endif diff --git a/apps/regression/even_odd_double_buffer_text_cell_with_separator.cpp b/apps/regression/even_odd_double_buffer_text_cell_with_separator.cpp new file mode 100644 index 000000000..d397bf80e --- /dev/null +++ b/apps/regression/even_odd_double_buffer_text_cell_with_separator.cpp @@ -0,0 +1,120 @@ +#include "even_odd_double_buffer_text_cell_with_separator.h" +#include "../shared/hideable_even_odd_editable_text_cell.h" +#include "escher/metric.h" +#include + +namespace Regression { + +EvenOddDoubleBufferTextCellWithSeparator::EvenOddDoubleBufferTextCellWithSeparator(Responder * parentResponder, float horizontalAlignment, float verticalAlignment) : + EvenOddCell(), + Responder(parentResponder), + m_firstTextSelected(true), + m_firstBufferTextView(KDText::FontSize::Small, horizontalAlignment, verticalAlignment), + m_secondBufferTextView(KDText::FontSize::Small, horizontalAlignment, verticalAlignment) +{ +} + +const char * EvenOddDoubleBufferTextCellWithSeparator::text() const { + if (m_firstTextSelected) { + return firstText(); + } else { + return secondText(); + } +} + +const char * EvenOddDoubleBufferTextCellWithSeparator::firstText() const { + return m_firstBufferTextView.text(); +} + +const char * EvenOddDoubleBufferTextCellWithSeparator::secondText() const { + return m_secondBufferTextView.text(); +} + +bool EvenOddDoubleBufferTextCellWithSeparator::firstTextSelected() { + return m_firstTextSelected; +} + +void EvenOddDoubleBufferTextCellWithSeparator::selectFirstText(bool selectFirstText) { + m_firstTextSelected = selectFirstText; + m_firstBufferTextView.setHighlighted(selectFirstText); + m_secondBufferTextView.setHighlighted(!selectFirstText); + reloadCell(); +} + +void EvenOddDoubleBufferTextCellWithSeparator::reloadCell() { + m_firstBufferTextView.reloadCell(); + m_secondBufferTextView.reloadCell(); +} + +void EvenOddDoubleBufferTextCellWithSeparator::setHighlighted(bool highlight) { + m_firstBufferTextView.setHighlighted(false); + m_secondBufferTextView.setHighlighted(false); + HighlightCell::setHighlighted(highlight); + if (isHighlighted()) { + if (m_firstTextSelected) { + m_firstBufferTextView.setHighlighted(true); + } else { + m_secondBufferTextView.setHighlighted(false); + } + } + reloadCell(); +} + +void EvenOddDoubleBufferTextCellWithSeparator::setEven(bool even) { + m_firstBufferTextView.setEven(even); + m_secondBufferTextView.setEven(even); + reloadCell(); +} + +void EvenOddDoubleBufferTextCellWithSeparator::setFirstText(const char * textContent) { + m_firstBufferTextView.setText(textContent); +} + +void EvenOddDoubleBufferTextCellWithSeparator::setSecondText(const char * textContent) { + m_secondBufferTextView.setText(textContent); +} + +void EvenOddDoubleBufferTextCellWithSeparator::setTextColor(KDColor textColor) { + m_firstBufferTextView.setTextColor(textColor); + m_secondBufferTextView.setTextColor(textColor); +} + +void EvenOddDoubleBufferTextCellWithSeparator::drawRect(KDContext * ctx, KDRect rect) const { + EvenOddCell::drawRect(ctx, rect); + // Draw the separator + KDRect separatorRect(0, 0, Metric::TableSeparatorThickness, bounds().height()); + ctx->fillRect(separatorRect, Shared::HideableEvenOddEditableTextCell::hideColor()); +} + +int EvenOddDoubleBufferTextCellWithSeparator::numberOfSubviews() const { + return 2; +} + +View * EvenOddDoubleBufferTextCellWithSeparator::subviewAtIndex(int index) { + assert(index == 0 || index == 1); + if (index == 0) { + return &m_firstBufferTextView; + } + return &m_secondBufferTextView; +} + +void EvenOddDoubleBufferTextCellWithSeparator::layoutSubviews() { + KDCoordinate width = bounds().width() - Metric::TableSeparatorThickness; + KDCoordinate height = bounds().height(); + m_firstBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness, 0, width/2, height)); + m_secondBufferTextView.setFrame(KDRect(Metric::TableSeparatorThickness + width/2, 0, width/2, height)); +} + +bool EvenOddDoubleBufferTextCellWithSeparator::handleEvent(Ion::Events::Event event) { + if (m_firstTextSelected && event == Ion::Events::Right) { + selectFirstText(false); + return true; + } + if (!m_firstTextSelected && event == Ion::Events::Left) { + selectFirstText(true); + return true; + } + return false; +} + +} diff --git a/apps/regression/even_odd_double_buffer_text_cell_with_separator.h b/apps/regression/even_odd_double_buffer_text_cell_with_separator.h new file mode 100644 index 000000000..6c1140649 --- /dev/null +++ b/apps/regression/even_odd_double_buffer_text_cell_with_separator.h @@ -0,0 +1,39 @@ +#ifndef REGRESSION_EVEN_ODD_DOUBLE_BUFFER_TEXT_CELL_WITH_SEPARATOR_H +#define REGRESSION_EVEN_ODD_DOUBLE_BUFFER_TEXT_CELL_WITH_SEPARATOR_H + +#include +#include "even_odd_buffer_text_cell_with_margin.h" + +namespace Regression { + +class EvenOddDoubleBufferTextCellWithSeparator : public EvenOddCell, public Responder{ +public: + EvenOddDoubleBufferTextCellWithSeparator(Responder * parentResponder = nullptr, float horizontalAlignment = 1.0f, float verticalAlignment = 0.5f); + const char * text() const override; + const char * firstText() const; + const char * secondText() const; + void reloadCell() override; + void setHighlighted(bool highlight) override; + Responder * responder() override { + return this; + } + void setEven(bool even) override; + bool firstTextSelected(); + void selectFirstText(bool selectFirstText); + void setFirstText(const char * textContent); + void setSecondText(const char * textContent); + void setTextColor(KDColor textColor); + void drawRect(KDContext * ctx, KDRect rect) const override; + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + void layoutSubviews() override; + bool handleEvent(Ion::Events::Event event) override; +protected: + bool m_firstTextSelected; + EvenOddBufferTextCellWithMargin m_firstBufferTextView; + EvenOddBufferTextCellWithMargin m_secondBufferTextView; +}; + +} + +#endif diff --git a/apps/regression/even_odd_expression_cell_with_margin.cpp b/apps/regression/even_odd_expression_cell_with_margin.cpp new file mode 100644 index 000000000..7dba966c1 --- /dev/null +++ b/apps/regression/even_odd_expression_cell_with_margin.cpp @@ -0,0 +1,10 @@ +#include "even_odd_expression_cell_with_margin.h" + +namespace Regression { + +void EvenOddExpressionCellWithMargin::layoutSubviews() { + KDRect boundsThis = bounds(); + m_expressionView.setFrame(KDRect(boundsThis.topLeft(), boundsThis.width() - k_rightMargin, boundsThis.height())); +} + +} diff --git a/apps/regression/even_odd_expression_cell_with_margin.h b/apps/regression/even_odd_expression_cell_with_margin.h new file mode 100644 index 000000000..cf74ac1da --- /dev/null +++ b/apps/regression/even_odd_expression_cell_with_margin.h @@ -0,0 +1,18 @@ +#ifndef REGRESSION_EVEN_ODD_EXPRESSION_CELL_WITH_MARGIN_H +#define REGRESSION_EVEN_ODD_EXPRESSION_CELL_WITH_MARGIN_H + +#include + +namespace Regression { + +class EvenOddExpressionCellWithMargin : public EvenOddExpressionCell { +public: + using EvenOddExpressionCell::EvenOddExpressionCell; + void layoutSubviews() override; +private: + static constexpr KDCoordinate k_rightMargin = 2; +}; + +} + +#endif diff --git a/apps/regression/go_to_parameter_controller.cpp b/apps/regression/go_to_parameter_controller.cpp index 0d521b97f..e1170931a 100644 --- a/apps/regression/go_to_parameter_controller.cpp +++ b/apps/regression/go_to_parameter_controller.cpp @@ -43,16 +43,16 @@ bool GoToParameterController::setParameterAtIndex(int parameterIndex, double f) app()->displayWarning(I18n::Message::ForbiddenValue); return false; } - double x = m_store->xValueForYValue(f); + double x = m_store->xValueForYValue(m_graphController->selectedSeriesIndex(), f); if (m_xPrediction) { - x = m_store->yValueForXValue(f); + x = m_store->yValueForXValue(m_graphController->selectedSeriesIndex(), f); } if (std::fabs(x) > k_maxDisplayableFloat) { app()->displayWarning(I18n::Message::ForbiddenValue); return false; } if (std::isnan(x)) { - if (m_store->slope() < DBL_EPSILON && f == m_store->yIntercept()) { + if (m_store->slope(m_graphController->selectedSeriesIndex()) < DBL_EPSILON && f == m_store->yIntercept(m_graphController->selectedSeriesIndex())) { m_graphController->selectRegressionCurve(); m_cursor->moveTo(m_cursor->x(), f); return true; diff --git a/apps/regression/graph_controller.cpp b/apps/regression/graph_controller.cpp index b664a02b2..9023283fb 100644 --- a/apps/regression/graph_controller.cpp +++ b/apps/regression/graph_controller.cpp @@ -7,7 +7,7 @@ using namespace Shared; namespace Regression { -GraphController::GraphController(Responder * parentResponder, ButtonRowController * header, Store * store, CurveViewCursor * cursor, uint32_t * modelVersion, uint32_t * rangeVersion, int * selectedDotIndex) : +GraphController::GraphController(Responder * parentResponder, ButtonRowController * header, Store * store, CurveViewCursor * cursor, uint32_t * modelVersion, uint32_t * rangeVersion, int * selectedDotIndex, int * selectedSeriesIndex) : InteractiveCurveViewController(parentResponder, header, store, &m_view, cursor, modelVersion, rangeVersion), m_crossCursorView(), m_roundCursorView(Palette::YellowDark), @@ -16,7 +16,8 @@ GraphController::GraphController(Responder * parentResponder, ButtonRowControlle m_store(store), m_initialisationParameterController(this, m_store), m_predictionParameterController(this, m_store, m_cursor, this), - m_selectedDotIndex(selectedDotIndex) + m_selectedDotIndex(selectedDotIndex), + m_selectedSeriesIndex(selectedSeriesIndex) { m_store->setCursor(m_cursor); } @@ -26,10 +27,15 @@ ViewController * GraphController::initialisationParameterController() { } bool GraphController::isEmpty() const { - if (m_store->numberOfPairs() < 2 || std::isinf(m_store->slope()) || std::isnan(m_store->slope())) { + if (m_store->isEmpty()) { return true; } - return false; + for (int series = 0; series < DoublePairStore::k_numberOfSeries; series++) { + if (!m_store->seriesIsEmpty(series) && !std::isinf(m_store->slope(series)) && !std::isnan(m_store->slope(series))) { + return false; + } + } + return true; } I18n::Message GraphController::emptyMessage() { @@ -63,6 +69,10 @@ bool GraphController::handleEnter() { } void GraphController::reloadBannerView() { + if (*m_selectedSeriesIndex < 0) { + return; + } + m_bannerView.setMessageAtIndex(I18n::Message::RegressionFormula, 3); char buffer[k_maxNumberOfCharacters + PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)]; @@ -71,7 +81,7 @@ void GraphController::reloadBannerView() { int legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); numberOfChar += legendLength; - if (*m_selectedDotIndex == m_store->numberOfPairs()) { + if (*m_selectedDotIndex == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { legend = I18n::translate(I18n::Message::MeanDot); legendLength = strlen(legend); strlcpy(buffer+numberOfChar, legend, legendLength+1); @@ -94,10 +104,10 @@ void GraphController::reloadBannerView() { legend = "x="; double x = m_cursor->x(); // Display a specific legend if the mean dot is selected - if (*m_selectedDotIndex == m_store->numberOfPairs()) { + if (*m_selectedDotIndex == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { constexpr static char legX[] = {Ion::Charset::XBar, '=', 0}; legend = legX; - x = m_store->meanOfColumn(0); + x = m_store->meanOfColumn(*m_selectedSeriesIndex, 0); } legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); @@ -112,10 +122,10 @@ void GraphController::reloadBannerView() { numberOfChar = 0; legend = "y="; double y = m_cursor->y(); - if (*m_selectedDotIndex == m_store->numberOfPairs()) { + if (*m_selectedDotIndex == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { constexpr static char legY[] = {Ion::Charset::YBar, '=', 0}; legend = legY; - y = m_store->meanOfColumn(1); + y = m_store->meanOfColumn(*m_selectedSeriesIndex, 1); } legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); @@ -129,7 +139,7 @@ void GraphController::reloadBannerView() { numberOfChar = 0; legend = " a="; - double slope = m_store->slope(); + double slope = m_store->slope(*m_selectedSeriesIndex); legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); numberOfChar += legendLength; @@ -142,7 +152,7 @@ void GraphController::reloadBannerView() { numberOfChar = 0; legend = " b="; - double yIntercept = m_store->yIntercept(); + double yIntercept = m_store->yIntercept(*m_selectedSeriesIndex); legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); numberOfChar += legendLength; @@ -155,7 +165,7 @@ void GraphController::reloadBannerView() { numberOfChar = 0; legend = " r="; - double r = m_store->correlationCoefficient(); + double r = m_store->correlationCoefficient(*m_selectedSeriesIndex); legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); numberOfChar += legendLength; @@ -168,7 +178,7 @@ void GraphController::reloadBannerView() { numberOfChar = 0; legend = " r2="; - double r2 = m_store->squaredCorrelationCoefficient(); + double r2 = m_store->squaredCorrelationCoefficient(*m_selectedSeriesIndex); legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); numberOfChar += legendLength; @@ -185,25 +195,26 @@ void GraphController::initRangeParameters() { } void GraphController::initCursorParameters() { - double x = m_store->meanOfColumn(0); - double y = m_store->meanOfColumn(1); + *m_selectedSeriesIndex = m_store->indexOfKthNonEmptySeries(0); + double x = m_store->meanOfColumn(*m_selectedSeriesIndex, 0); + double y = m_store->meanOfColumn(*m_selectedSeriesIndex, 1); m_cursor->moveTo(x, y); m_store->panToMakePointVisible(x, y, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); - *m_selectedDotIndex = m_store->numberOfPairs(); + *m_selectedDotIndex = m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex); } bool GraphController::moveCursorHorizontally(int direction) { if (*m_selectedDotIndex >= 0) { - int dotSelected = m_store->nextDot(direction, *m_selectedDotIndex); - if (dotSelected >= 0 && dotSelected < m_store->numberOfPairs()) { + int dotSelected = m_store->nextDot(*m_selectedSeriesIndex, direction, *m_selectedDotIndex); + if (dotSelected >= 0 && dotSelected < m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { *m_selectedDotIndex = dotSelected; - m_cursor->moveTo(m_store->get(0, *m_selectedDotIndex), m_store->get(1, *m_selectedDotIndex)); + m_cursor->moveTo(m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex), m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex)); m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); return true; } - if (dotSelected == m_store->numberOfPairs()) { + if (dotSelected == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { *m_selectedDotIndex = dotSelected; - m_cursor->moveTo(m_store->meanOfColumn(0), m_store->meanOfColumn(1)); + m_cursor->moveTo(m_store->meanOfColumn(*m_selectedSeriesIndex, 0), m_store->meanOfColumn(*m_selectedSeriesIndex, 1)); m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); return true; } @@ -211,40 +222,86 @@ bool GraphController::moveCursorHorizontally(int direction) { } double x = direction > 0 ? m_cursor->x() + m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit : m_cursor->x() - m_store->xGridUnit()/k_numberOfCursorStepsInGradUnit; - double y = m_store->yValueForXValue(x); + double y = m_store->yValueForXValue(*m_selectedSeriesIndex, x); m_cursor->moveTo(x, y); m_store->panToMakePointVisible(x, y, k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); return true; } bool GraphController::moveCursorVertically(int direction) { - double yRegressionCurve = m_store->yValueForXValue(m_cursor->x()); - if (*m_selectedDotIndex >= 0) { - if ((yRegressionCurve - m_cursor->y() > 0) == (direction > 0)) { - selectRegressionCurve(); - m_cursor->moveTo(m_cursor->x(), yRegressionCurve); - m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); - return true; - } else { - return false; - } + volatile int closestRegressionSeries = -1; + int closestDotSeries = -1; + volatile int dotSelected = -1; + + if (*m_selectedDotIndex == -1) { + // The current cursor is on a regression + // Check the closest regression + closestRegressionSeries = m_store->closestVerticalRegression(direction, m_cursor->x(), m_cursor->y(), *m_selectedSeriesIndex); + // Check the closest dot + dotSelected = m_store->closestVerticalDot(direction, m_cursor->x(), direction > 0 ? -FLT_MAX : FLT_MAX, *m_selectedSeriesIndex, *m_selectedDotIndex, &closestDotSeries); } else { - int dotSelected = m_store->closestVerticalDot(direction, m_cursor->x()); - if (dotSelected >= 0 && dotSelected <= m_store->numberOfPairs()) { - m_view.setCursorView(&m_crossCursorView); - if (dotSelected == m_store->numberOfPairs()) { - *m_selectedDotIndex = dotSelected; - m_cursor->moveTo(m_store->meanOfColumn(0), m_store->meanOfColumn(1)); - m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); - return true; + // The current cursor is on a dot + // Check the closest regression + closestRegressionSeries = m_store->closestVerticalRegression(direction, m_cursor->x(), m_cursor->y(), -1); + // Check the closest dot + dotSelected = m_store->closestVerticalDot(direction, m_cursor->x(), m_cursor->y(), *m_selectedSeriesIndex, *m_selectedDotIndex, &closestDotSeries); + } + + bool validRegression = closestRegressionSeries > -1; + bool validDot = dotSelected >= 0 && dotSelected <= m_store->numberOfPairsOfSeries(closestDotSeries); + + if (validRegression && validDot) { + /* Compare the abscissa distances to select either the dot or the + * regression. If they are equal, compare the ordinate distances. */ + double dotDistanceX = -1; + if (dotSelected == m_store->numberOfPairsOfSeries(closestDotSeries)) { + dotDistanceX = std::fabs(m_store->meanOfColumn(closestDotSeries, 0) - m_cursor->x()); + } else { + dotDistanceX = std::fabs(m_store->get(closestDotSeries, 0, dotSelected) - m_cursor->x()); + } + if (dotDistanceX != 0) { + /* The regression X distance to the point is 0, so it is closer than the + * dot. */ + validDot = false; + } else { + // Compare the y distances + double regressionDistanceY = std::fabs(m_store->yValueForXValue(closestRegressionSeries, m_cursor->x()) - m_cursor->y()); + double dotDistanceY = -1; + if (dotSelected == m_store->numberOfPairsOfSeries(closestDotSeries)) { + dotDistanceY = std::fabs(m_store->meanOfColumn(closestDotSeries, 1) - m_cursor->y()); + } else { + dotDistanceY = std::fabs(m_store->get(closestDotSeries, 1, dotSelected) - m_cursor->y()); } - *m_selectedDotIndex = dotSelected; - m_cursor->moveTo(m_store->get(0, *m_selectedDotIndex), m_store->get(1, *m_selectedDotIndex)); + if (regressionDistanceY <= dotDistanceY) { + validDot = false; + } else { + validRegression = false; + } + } + } + if (!validDot && validRegression) { + // Select the regression + *m_selectedSeriesIndex = closestRegressionSeries; + selectRegressionCurve(); + m_cursor->moveTo(m_cursor->x(), m_store->yValueForXValue(*m_selectedSeriesIndex, m_cursor->x())); + m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; + } + + if (validDot && !validRegression) { + m_view.setCursorView(&m_crossCursorView); + *m_selectedSeriesIndex = closestDotSeries; + *m_selectedDotIndex = dotSelected; + if (dotSelected == m_store->numberOfPairsOfSeries(*m_selectedSeriesIndex)) { + m_cursor->moveTo(m_store->meanOfColumn(*m_selectedSeriesIndex, 0), m_store->meanOfColumn(*m_selectedSeriesIndex, 1)); m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); return true; } - return false; + m_cursor->moveTo(m_store->get(*m_selectedSeriesIndex, 0, *m_selectedDotIndex), m_store->get(*m_selectedSeriesIndex, 1, *m_selectedDotIndex)); + m_store->panToMakePointVisible(m_cursor->x(), m_cursor->y(), k_cursorTopMarginRatio, k_cursorRightMarginRatio, k_cursorBottomMarginRatio, k_cursorLeftMarginRatio); + return true; } + return false; } uint32_t GraphController::modelVersion() { diff --git a/apps/regression/graph_controller.h b/apps/regression/graph_controller.h index 234e5dd19..d18e8e139 100644 --- a/apps/regression/graph_controller.h +++ b/apps/regression/graph_controller.h @@ -17,12 +17,13 @@ namespace Regression { class GraphController : public Shared::InteractiveCurveViewController { public: - GraphController(Responder * parentResponder, ButtonRowController * header, Store * store, Shared::CurveViewCursor * cursor, uint32_t * modelVersion, uint32_t * rangeVersion, int * selectedDotIndex); + GraphController(Responder * parentResponder, ButtonRowController * header, Store * store, Shared::CurveViewCursor * cursor, uint32_t * modelVersion, uint32_t * rangeVersion, int * selectedDotIndex, int * selectedSeriesIndex); ViewController * initialisationParameterController() override; bool isEmpty() const override; I18n::Message emptyMessage() override; void viewWillAppear() override; void selectRegressionCurve(); + int selectedSeriesIndex() const { return *m_selectedSeriesIndex; } private: constexpr static float k_cursorTopMarginRatio = 0.07f; // (cursorHeight/2)/graphViewHeight constexpr static float k_cursorBottomMarginRatio = 0.3f; // (cursorHeight/2+bannerHeigh)/graphViewHeight @@ -49,6 +50,7 @@ private: /* 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; + int * m_selectedSeriesIndex; }; } diff --git a/apps/regression/graph_view.cpp b/apps/regression/graph_view.cpp index af696bbee..6088ab40e 100644 --- a/apps/regression/graph_view.cpp +++ b/apps/regression/graph_view.cpp @@ -20,17 +20,22 @@ void GraphView::drawRect(KDContext * ctx, KDRect rect) const { drawAxes(ctx, rect, Axis::Vertical); drawLabels(ctx, rect, Axis::Horizontal, true); drawLabels(ctx, rect, Axis::Vertical, true); - float regressionParameters[2] = {(float)m_store->slope(), (float)m_store->yIntercept()}; - drawCurve(ctx, rect, [](float abscissa, void * model, void * context) { - float * params = (float *)model; - return params[0]*abscissa+params[1]; - }, - regressionParameters, nullptr, Palette::YellowDark); - for (int index = 0; index < m_store->numberOfPairs(); index++) { - drawDot(ctx, rect, m_store->get(0,index), m_store->get(1,index), Palette::Red); + for (int series = 0; series < Store::k_numberOfSeries; series++) { + if (!m_store->seriesIsEmpty(series)) { + KDColor color = Palette::DataColor[series]; + float regressionParameters[2] = {(float)m_store->slope(series), (float)m_store->yIntercept(series)}; + drawCurve(ctx, rect, [](float abscissa, void * model, void * context) { + float * params = (float *)model; + return params[0]*abscissa+params[1]; + }, + regressionParameters, nullptr, color); + for (int index = 0; index < m_store->numberOfPairsOfSeries(series); index++) { + drawDot(ctx, rect, m_store->get(series, 0, index), m_store->get(series, 1, index), color); + } + drawDot(ctx, rect, m_store->meanOfColumn(series, 0), m_store->meanOfColumn(series, 1), color, true); + drawDot(ctx, rect, m_store->meanOfColumn(series, 0), m_store->meanOfColumn(series, 1), KDColorWhite); + } } - drawDot(ctx, rect, m_store->meanOfColumn(0), m_store->meanOfColumn(1), Palette::Palette::YellowDark, true); - drawDot(ctx, rect, m_store->meanOfColumn(0), m_store->meanOfColumn(1), KDColorWhite); } char * GraphView::label(Axis axis, int index) const { diff --git a/apps/regression/regression_context.cpp b/apps/regression/regression_context.cpp new file mode 100644 index 000000000..6499e64fc --- /dev/null +++ b/apps/regression/regression_context.cpp @@ -0,0 +1,33 @@ +#include "regression_context.h" +#include +#include +#include + +using namespace Poincare; +using namespace Shared; + +namespace Regression { + +const Expression * RegressionContext::expressionForSymbol(const Symbol * symbol) { + if (Symbol::isRegressionSymbol(symbol->name())) { + const char * seriesName = Symbol::textForSpecialSymbols(symbol->name()); + assert(strlen(seriesName) == 2); + + int series = (int)(seriesName[1] - '0') - 1; + assert(series >= 0 && series < DoublePairStore::k_numberOfSeries); + + assert((seriesName[0] == 'X') || (seriesName[0] == 'Y')); + int storeI = seriesName[0] == 'X' ? 0 : 1; + + assert(m_seriesPairIndex >= 0); + assert(m_seriesPairIndex < m_store->numberOfPairsOfSeries(series)); + + Expression * result = new Decimal(m_store->get(series, storeI, m_seriesPairIndex)); + assert(result != nullptr); + return result; + } else { + return m_parentContext->expressionForSymbol(symbol); + } +} + +} diff --git a/apps/regression/regression_context.h b/apps/regression/regression_context.h new file mode 100644 index 000000000..1d9543103 --- /dev/null +++ b/apps/regression/regression_context.h @@ -0,0 +1,17 @@ +#ifndef REGRESSION_REGRESSION_CONTEXT_H +#define REGRESSION_REGRESSION_CONTEXT_H + +#include +#include "../shared/store_context.h" + +namespace Regression { + +class RegressionContext : public Shared::StoreContext { +public: + using Shared::StoreContext::StoreContext; + const Poincare::Expression * expressionForSymbol(const Poincare::Symbol * symbol) override; +}; + +} + +#endif diff --git a/apps/regression/store.cpp b/apps/regression/store.cpp index 93937fb25..eb0a7be38 100644 --- a/apps/regression/store.cpp +++ b/apps/regression/store.cpp @@ -8,96 +8,144 @@ using namespace Shared; namespace Regression { +static inline float max(float x, float y) { return (x>y ? x : y); } +static inline float min(float x, float y) { return (x 0 and below otherwise */ + for (int series = 0; series < k_numberOfSeries; series ++) { + if (!seriesIsEmpty(series) && series != currentRegressionSeries) { + double regressionY = yValueForXValue(series, x); + if ((m_yMin <= regressionY && regressionY <= m_yMax) + && (std::fabs(regressionY - y) < closestDistance) + && (regressionY - y > 0) == (direction > 0)) { + closestDistance = std::fabs(regressionY - y); + regressionSeries = series; + } + } + } + return regressionSeries; +} + /* Dots */ -int Store::closestVerticalDot(int direction, float x) { +int Store::closestVerticalDot(int direction, float x, float y, int currentSeries, int currentDot, int * nextSeries) { 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) && - (std::fabs(m_data[0][index] - x) < std::fabs(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; + * - if the currentDot is valid, the next series should not be the current series + * - the next dot should not be the current dot + * - 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 + * - the next dot is above/under y + * - if the current dot is valid, do not select a dot of the same series */ + for (int series = 0; series < k_numberOfSeries; series ++) { + if (!seriesIsEmpty(series) && (currentDot < 0 || currentSeries != series)) { + for (int index = 0; index < numberOfPairsOfSeries(series); index++) { + if ((currentSeries != series) || (index != currentDot)) { + double currentDataX = m_data[series][0][index]; + double currentDataY = m_data[series][1][index]; + if ((m_xMin <= currentDataX && currentDataX <= m_xMax) && + (std::fabs(currentDataX - x) <= std::fabs(nextX - x)) && + ((currentDataY - yValueForXValue(currentSeries, currentDataX) >= 0) == (direction > 0)) && + ((currentDataY > y) == (direction > 0))) { + // Handle edge case: if 2 dots have the same abscissa but different ordinates + if (nextX != currentDataX || ((nextY - currentDataY >= 0) == (direction > 0))) { + nextX = currentDataX; + nextY = currentDataY; + selectedDot = index; + *nextSeries = series; + } + } + } + } + // Compare with the mean dot + if ((currentSeries != series) || (numberOfPairsOfSeries(series) != currentDot)) { + double meanX = meanOfColumn(series, 0); + double meanY = meanOfColumn(series, 1); + if (m_xMin <= meanX && meanX <= m_xMax && + (std::fabs(meanX - x) <= std::fabs(nextX - x)) && + ((meanY - yValueForXValue(currentSeries, meanX) >= 0) == (direction > 0)) && + ((meanY > y) == (direction > 0))) { + if (nextX != meanX || ((nextY - meanY >= 0) == (direction > 0))) { + selectedDot = numberOfPairsOfSeries(series); + *nextSeries = series; + } + } } - } - } - // Compare with the mean dot - if (m_xMin <= meanOfColumn(0) && meanOfColumn(0) <= m_xMax && - (std::fabs(meanOfColumn(0) - x) < std::fabs(nextX - x)) && - ((meanOfColumn(1) - yValueForXValue(meanOfColumn(0)) >= 0) == (direction > 0))) { - if (nextX != meanOfColumn(0) || ((nextY - meanOfColumn(1) >= 0) == (direction > 0))) { - selectedDot = m_numberOfPairs; } } return selectedDot; } -int Store::nextDot(int direction, int dot) { +int Store::nextDot(int series, int direction, int dot) { float nextX = INFINITY; int selectedDot = -1; - float x = meanOfColumn(0); - if (dot >= 0 && dot < m_numberOfPairs) { - x = get(0, dot); + double meanX = meanOfColumn(series, 0); + float x = meanX; + if (dot >= 0 && dot < numberOfPairsOfSeries(series)) { + x = get(series, 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++) { + for (int index = 0; index < numberOfPairsOfSeries(series); 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 (std::fabs(m_data[0][index] - x) < std::fabs(nextX - x) && + if (std::fabs(m_data[series][0][index] - x) < std::fabs(nextX - x) && (index != dot) && - (m_data[0][index] >= x)) { + (m_data[series][0][index] >= x)) { // Handle edge case: 2 dots have same abscissa - if (m_data[0][index] != x || (index > dot)) { - nextX = m_data[0][index]; + if (m_data[series][0][index] != x || (index > dot)) { + nextX = m_data[series][0][index]; selectedDot = index; } } } // Compare with the mean dot - if (std::fabs(meanOfColumn(0) - x) < std::fabs(nextX - x) && - (m_numberOfPairs != dot) && - (meanOfColumn(0) >= x)) { - if (meanOfColumn(0) != x || (x > dot)) { - selectedDot = m_numberOfPairs; + if (std::fabs(meanX - x) < std::fabs(nextX - x) && + (numberOfPairsOfSeries(series) != dot) && + (meanX >= x)) { + if (meanX != x || (numberOfPairsOfSeries(series) > dot)) { + selectedDot = numberOfPairsOfSeries(series); } } } else { // Compare with the mean dot - if (std::fabs(meanOfColumn(0) - x) < std::fabs(nextX - x) && - (m_numberOfPairs != dot) && - (meanOfColumn(0) <= x)) { - if (meanOfColumn(0) != x || (m_numberOfPairs < dot)) { - nextX = meanOfColumn(0); - selectedDot = m_numberOfPairs; + if (std::fabs(meanX - x) < std::fabs(nextX - x) && + (numberOfPairsOfSeries(series) != dot) && + (meanX <= x)) { + if ((meanX != x) || (numberOfPairsOfSeries(series) < dot)) { + nextX = meanX; + selectedDot = numberOfPairsOfSeries(series); } } - for (int index = m_numberOfPairs-1; index >= 0; index--) { - if (std::fabs(m_data[0][index] - x) < std::fabs(nextX - x) && + for (int index = numberOfPairsOfSeries(series)-1; index >= 0; index--) { + if (std::fabs(m_data[series][0][index] - x) < std::fabs(nextX - x) && (index != dot) && - (m_data[0][index] <= x)) { + (m_data[series][0][index] <= x)) { // Handle edge case: 2 dots have same abscissa - if (m_data[0][index] != x || (index < dot)) { - nextX = m_data[0][index]; + if (m_data[series][0][index] != x || (index < dot)) { + nextX = m_data[series][0][index]; selectedDot = index; } } @@ -109,130 +157,158 @@ int Store::nextDot(int direction, int dot) { /* Window */ void Store::setDefault() { - float min = minValueOfColumn(0); - float max = maxValueOfColumn(0); - float range = max - min; - setXMin(min - k_displayLeftMarginRatio*range); - setXMax(max + k_displayRightMarginRatio*range); + float minX = FLT_MAX; + float maxX = -FLT_MAX; + for (int series = 0; series < k_numberOfSeries; series ++) { + if (!seriesIsEmpty(series)) { + minX = min(minX, minValueOfColumn(series, 0)); + maxX = max(maxX, maxValueOfColumn(series, 0)); + } + } + float range = maxX - minX; + setXMin(minX - k_displayLeftMarginRatio*range); + setXMax(maxX + k_displayRightMarginRatio*range); setYAuto(true); } +bool Store::isEmpty() const { + for (int i = 0; i < k_numberOfSeries; i++) { + if (!seriesIsEmpty(i)) { + return false; + } + } + return true; +} + +int Store::numberOfNonEmptySeries() const { + // TODO Share with stats in FLoatPairStore + int nonEmptySeriesCount = 0; + for (int i = 0; i< k_numberOfSeries; i++) { + if (!seriesIsEmpty(i)) { + nonEmptySeriesCount++; + } + } + return nonEmptySeriesCount; +} + +bool Store::seriesIsEmpty(int series) const { + return numberOfPairsOfSeries(series) < 2; +} + +int Store::indexOfKthNonEmptySeries(int k) const { + // TODO put in DoublePairStore (it is also in stats/store) + assert(k >= 0 && k < numberOfNonEmptySeries()); + int nonEmptySeriesCount = 0; + for (int i = 0; i < k_numberOfSeries; i++) { + if (!seriesIsEmpty(i)) { + if (nonEmptySeriesCount == k) { + return i; + } + nonEmptySeriesCount++; + } + } + assert(false); + return 0; +} + /* Calculations */ -double Store::numberOfPairs() { - return m_numberOfPairs; +double Store::doubleCastedNumberOfPairsOfSeries(int series) const { + return DoublePairStore::numberOfPairsOfSeries(series); } -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]; - } +float Store::maxValueOfColumn(int series, int i) const { + float maxColumn = -FLT_MAX; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + maxColumn = max(maxColumn, m_data[series][i][k]); } - return max; + return maxColumn; } -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]; - } +float Store::minValueOfColumn(int series, int i) const { + float minColumn = FLT_MAX; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + minColumn = min(minColumn, m_data[series][i][k]); } - return min; + return minColumn; } -double Store::squaredValueSumOfColumn(int i) { +double Store::squaredValueSumOfColumn(int series, int i) const { double result = 0; - for (int k = 0; k < m_numberOfPairs; k++) { - result += m_data[i][k]*m_data[i][k]; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + result += m_data[series][i][k]*m_data[series][i][k]; } return result; } -double Store::columnProductSum() { +double Store::columnProductSum(int series) const { double result = 0; - for (int k = 0; k < m_numberOfPairs; k++) { - result += m_data[0][k]*m_data[1][k]; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + result += m_data[series][0][k]*m_data[series][1][k]; } return result; } -double Store::meanOfColumn(int i) { - if (m_numberOfPairs == 0) { - return 0; - } - return sumOfColumn(i)/m_numberOfPairs; +double Store::meanOfColumn(int series, int i) const { + return numberOfPairsOfSeries(series) == 0 ? 0 : sumOfColumn(series, i)/numberOfPairsOfSeries(series); } -double Store::varianceOfColumn(int i) { - double mean = meanOfColumn(i); - return squaredValueSumOfColumn(i)/m_numberOfPairs - mean*mean; +double Store::varianceOfColumn(int series, int i) const { + double mean = meanOfColumn(series, i); + return squaredValueSumOfColumn(series, i)/numberOfPairsOfSeries(series) - mean*mean; } -double Store::standardDeviationOfColumn(int i) { - return std::sqrt(varianceOfColumn(i)); +double Store::standardDeviationOfColumn(int series, int i) const { + return std::sqrt(varianceOfColumn(series, i)); } -double Store::covariance() { - return columnProductSum()/m_numberOfPairs - meanOfColumn(0)*meanOfColumn(1); +double Store::covariance(int series) const { + return columnProductSum(series)/numberOfPairsOfSeries(series) - meanOfColumn(series, 0)*meanOfColumn(series, 1); } -double Store::slope() { - return covariance()/varianceOfColumn(0); +double Store::slope(int series) const { + return covariance(series)/varianceOfColumn(series, 0); } -double Store::yIntercept() { - return meanOfColumn(1) - slope()*meanOfColumn(0); +double Store::yIntercept(int series) const { + return meanOfColumn(series, 1) - slope(series)*meanOfColumn(series, 0); } -double Store::yValueForXValue(double x) { - return slope()*x+yIntercept(); +double Store::yValueForXValue(int series, double x) const { + return slope(series)*x+yIntercept(series); } -double Store::xValueForYValue(double y) { - if (std::fabs(slope()) < DBL_EPSILON) { - return NAN; - } - return (y - yIntercept())/slope(); +double Store::xValueForYValue(int series, double y) const { + return std::fabs(slope(series)) < DBL_EPSILON ? NAN : (y - yIntercept(series))/slope(series); } -double Store::correlationCoefficient() { - double sd0 = standardDeviationOfColumn(0); - double sd1 = standardDeviationOfColumn(1); - if (sd0 == 0.0 || sd1 == 0.0) { - return 1.0; - } - return covariance()/(sd0*sd1); +double Store::correlationCoefficient(int series) const { + double sd0 = standardDeviationOfColumn(series, 0); + double sd1 = standardDeviationOfColumn(series, 1); + return (sd0 == 0.0 || sd1 == 0.0) ? 1.0 : covariance(series)/(sd0*sd1); } -double Store::squaredCorrelationCoefficient() { - double cov = covariance(); - double v0 = varianceOfColumn(0); - double v1 = varianceOfColumn(1); - if (v0 == 0.0 || v1 == 0.0) { - return 1.0; - } - return cov*cov/(v0*v1); +double Store::squaredCorrelationCoefficient(int series) const { + double cov = covariance(series); + double v0 = varianceOfColumn(series, 0); + double v1 = varianceOfColumn(series, 1); + return (v0 == 0.0 || v1 == 0.0) ? 1.0 : cov*cov/(v0*v1); } InteractiveCurveViewRangeDelegate::Range Store::computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) { - float min = FLT_MAX; - float max = -FLT_MAX; - 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]; + float minY = FLT_MAX; + float maxY = -FLT_MAX; + for (int series = 0; series < k_numberOfSeries; series++) { + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + if (m_xMin <= m_data[series][0][k] && m_data[series][0][k] <= m_xMax) { + minY = min(minY, m_data[series][1][k]); + maxY = max(maxY, m_data[series][1][k]); } } } InteractiveCurveViewRangeDelegate::Range range; - range.min = min; - range.max = max; + range.min = minY; + range.max = maxY; return range; } diff --git a/apps/regression/store.h b/apps/regression/store.h index c9ed07c73..1e34e8d77 100644 --- a/apps/regression/store.h +++ b/apps/regression/store.h @@ -2,38 +2,52 @@ #define REGRESSION_STORE_H #include "../shared/interactive_curve_view_range.h" -#include "../shared/float_pair_store.h" +#include "../shared/double_pair_store.h" +extern "C" { +#include +} namespace Regression { -class Store : public Shared::InteractiveCurveViewRange, public Shared::FloatPairStore, public Shared::InteractiveCurveViewRangeDelegate { +class Store : public Shared::InteractiveCurveViewRange, public Shared::DoublePairStore, public Shared::InteractiveCurveViewRangeDelegate { public: Store(); + + // Regression + /* Return the series index of the closest regression at abscissa x, above + * ordinate y if direction > 0, below otherwise */ + int closestVerticalRegression(int direction, float x, float y, int currentRegressionSeries); // 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); + /* Return the closest dot to abscissa x above the regression curve if + * direction > 0, below otherwise */ + int closestVerticalDot(int direction, float x, float y, int currentSeries, int currentDot, int * nextSeries); + /* Return the closest dot to given dot, on the right if direction > 0, + * on the left otherwise */ + int nextDot(int series, int direction, int dot); // Window void setDefault() override; + // Series + bool isEmpty() const; + int numberOfNonEmptySeries() const; + bool seriesIsEmpty(int series) const; + int indexOfKthNonEmptySeries(int k) const; + // Calculation - double numberOfPairs(); - double squaredValueSumOfColumn(int i); - double columnProductSum(); - double meanOfColumn(int i); - double varianceOfColumn(int i); - double standardDeviationOfColumn(int i); - double covariance(); - double slope(); - double yIntercept(); - double yValueForXValue(double x); - double xValueForYValue(double y); - double correlationCoefficient(); - double squaredCorrelationCoefficient(); + double doubleCastedNumberOfPairsOfSeries(int series) const; + double squaredValueSumOfColumn(int series, int i) const; + double columnProductSum(int series) const; + double meanOfColumn(int series, int i) const; + double varianceOfColumn(int series, int i) const; + double standardDeviationOfColumn(int series, int i) const; + double covariance(int series) const; + double slope(int series) const; + double yIntercept(int series) const; + double yValueForXValue(int series, double x) const; + double xValueForYValue(int series, double y) const; + double correlationCoefficient(int series) const; + double squaredCorrelationCoefficient(int series) const; private: constexpr static float k_displayTopMarginRatio = 0.12f; constexpr static float k_displayRightMarginRatio = 0.05f; @@ -41,12 +55,12 @@ private: constexpr static float k_displayLeftMarginRatio = 0.05f; InteractiveCurveViewRangeDelegate::Range computeYRange(InteractiveCurveViewRange * interactiveCurveViewRange) override; float addMargin(float x, float range, bool isMin) override; - float maxValueOfColumn(int i); - float minValueOfColumn(int i); + float maxValueOfColumn(int series, int i) const; + float minValueOfColumn(int series, int i) const; }; -typedef double (Store::*ArgCalculPointer)(int); -typedef double (Store::*CalculPointer)(); +typedef double (Store::*ArgCalculPointer)(int, int) const; +typedef double (Store::*CalculPointer)(int) const; typedef void (Store::*RangeMethodPointer)(); } diff --git a/apps/regression/store_controller.cpp b/apps/regression/store_controller.cpp index e08bb358d..22e8bb92a 100644 --- a/apps/regression/store_controller.cpp +++ b/apps/regression/store_controller.cpp @@ -1,5 +1,6 @@ #include "store_controller.h" #include "app.h" +#include "regression_context.h" #include "../apps_container.h" #include "../constant.h" #include "../../poincare/src/layout/char_layout.h" @@ -14,19 +15,25 @@ namespace Regression { StoreController::StoreController(Responder * parentResponder, Store * store, ButtonRowController * header) : Shared::StoreController(parentResponder, store, header), - m_titleCells{} + m_titleCells{}, + m_regressionContext(store) { - m_titleLayout[0] = new HorizontalLayout(new CharLayout('X', KDText::FontSize::Small), new VerticalOffsetLayout(new CharLayout('i', KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), false); - m_titleLayout[1] = new HorizontalLayout(new CharLayout('Y', KDText::FontSize::Small), new VerticalOffsetLayout(new CharLayout('i', KDText::FontSize::Small), VerticalOffsetLayout::Type::Subscript, false), false); } -StoreController::~StoreController() { - for (int i = 0; i < 2; i++) { - if (m_titleLayout[i]) { - delete m_titleLayout[i]; - m_titleLayout[i] = nullptr; - } - } +StoreContext * StoreController::storeContext() { + m_regressionContext.setParentContext(const_cast(static_cast(app()->container()))->globalContext()); + return &m_regressionContext; +} + +void StoreController::setFormulaLabel() { + int series = selectedColumn() / Store::k_numberOfColumnsPerSeries; + int isXColumn = selectedColumn() % Store::k_numberOfColumnsPerSeries == 0; + char text[] = {isXColumn ? 'X' : 'Y', static_cast('1' + series), '=', 0}; + static_cast(view())->formulaInputView()->setBufferText(text); +} + +bool StoreController::fillColumnWithFormula(Expression * formula) { + return privateFillColumnWithFormula(formula, Symbol::isRegressionSymbol); } void StoreController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { @@ -34,8 +41,13 @@ void StoreController::willDisplayCellAtLocation(HighlightCell * cell, int i, int if (cellAtLocationIsEditable(i, j)) { return; } - EvenOddExpressionCell * mytitleCell = (EvenOddExpressionCell *)cell; - mytitleCell->setExpressionLayout(m_titleLayout[i]); + Shared::StoreTitleCell * mytitleCell = static_cast(cell); + bool isValuesColumn = i%Store::k_numberOfColumnsPerSeries == 0; + mytitleCell->setSeparatorLeft(isValuesColumn); + int seriesIndex = i/Store::k_numberOfColumnsPerSeries; + mytitleCell->setColor(m_store->numberOfPairsOfSeries(seriesIndex) == 0 ? Palette::GreyDark : Store::colorOfSeriesAtIndex(seriesIndex)); // TODO Share GreyDark with graph/list_controller and statistics/store_controller + char name[] = {isValuesColumn ? 'X' : 'Y', static_cast('1' + seriesIndex), 0}; + mytitleCell->setText(name); } HighlightCell * StoreController::titleCells(int index) { @@ -45,7 +57,7 @@ HighlightCell * StoreController::titleCells(int index) { View * StoreController::loadView() { for (int i = 0; i < k_numberOfTitleCells; i++) { - m_titleCells[i] = new EvenOddExpressionCell(0.5f, 0.5f); + m_titleCells[i] = new Shared::StoreTitleCell(); } return Shared::StoreController::loadView(); } diff --git a/apps/regression/store_controller.h b/apps/regression/store_controller.h index ecbb76244..d7a37834c 100644 --- a/apps/regression/store_controller.h +++ b/apps/regression/store_controller.h @@ -3,25 +3,25 @@ #include #include "store.h" +#include "regression_context.h" #include "../shared/store_controller.h" +#include "../shared/store_title_cell.h" namespace Regression { class StoreController : public Shared::StoreController { public: StoreController(Responder * parentResponder, Store * store, ButtonRowController * header); - ~StoreController(); - StoreController(const StoreController& other) = delete; - StoreController(StoreController&& other) = delete; - StoreController& operator=(const StoreController& other) = delete; - StoreController& operator=(StoreController&& other) = delete; + Shared::StoreContext * storeContext() override; + void setFormulaLabel() override; + bool fillColumnWithFormula(Poincare::Expression * formula) override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; private: HighlightCell * titleCells(int index) override; View * loadView() override; void unloadView(View * view) override; - EvenOddExpressionCell * m_titleCells[k_numberOfTitleCells]; - Poincare::ExpressionLayout * m_titleLayout[2]; + Shared::StoreTitleCell * m_titleCells[k_numberOfTitleCells]; + RegressionContext m_regressionContext; }; } diff --git a/apps/sequence/sequence_store.cpp b/apps/sequence/sequence_store.cpp index dd2781701..ffcd0d0f0 100644 --- a/apps/sequence/sequence_store.cpp +++ b/apps/sequence/sequence_store.cpp @@ -7,7 +7,6 @@ extern "C" { namespace Sequence { -constexpr KDColor SequenceStore::k_defaultColors[MaxNumberOfSequences]; constexpr const char * SequenceStore::k_sequenceNames[MaxNumberOfSequences]; uint32_t SequenceStore::storeChecksum() { diff --git a/apps/sequence/sequence_store.h b/apps/sequence/sequence_store.h index 5ee9f8bc3..97a5b6d0c 100644 --- a/apps/sequence/sequence_store.h +++ b/apps/sequence/sequence_store.h @@ -33,12 +33,6 @@ private: Sequence * emptyModel() override; Sequence * nullModel() override; void setModelAtIndex(Shared::ExpressionModel * f, int i) override; - static constexpr KDColor k_defaultColors[MaxNumberOfSequences] = { - Palette::Red, Palette::Blue//, Palette::YellowDark - }; - const KDColor firstAvailableColor() override { - return firstAvailableAttribute(k_defaultColors, FunctionStore::color); - } Sequence m_sequences[MaxNumberOfSequences]; }; diff --git a/apps/shared.de.i18n b/apps/shared.de.i18n index 0dfa15f0e..7882b63b2 100644 --- a/apps/shared.de.i18n +++ b/apps/shared.de.i18n @@ -7,13 +7,16 @@ Cancel = "Abbrechen" ClearColumn = "Spalte loschen" ColumnOptions = "Optionen Spalte" CopyColumnInList = "Die Spalte in einer Liste kopieren" +DataNotSuitable = "Daten nicht geeignet" DataTab = "Daten" DefaultSetting = "Grundeinstellungen" Deg = "gra" +Deviation = "Varianz" DisplayValues = "Werte anzeigen" Empty = "Leer" ExitExamMode1 = "Wollen Sie den Testmodus " ExitExamMode2 = "verlassen?" +FillWithFormula = "Mit einer Formel füllen" ForbiddenValue = "Verbotener Wert" FunctionColumn = "0(0) Spalte" FunctionOptions = "Optionen Funktion" @@ -27,10 +30,12 @@ Initialization = "Initialisierung" IntervalSet = "Tabelleneinstell" Language = "Sprache" LowBattery = "Batterie leer" -MathError = "Mathematischen Fehler" +Mean = "Mittelwert" Move = " Verschieben: " Next = "Nachste" +NoDataToPlot = "Keine Daten zu zeichnen" NoFunctionToDelete = "Keine Funktion zu loschen" +NoValueToCompute = "Keine Grosse zu berechnen" Ok = "Bestatigen" Or = " oder " Orthonormal = "Orthonormal" @@ -38,7 +43,10 @@ Plot = "Graphen zeichnen" Rad = "rad" RoundAbscissa = "Ganzzahl" Sci = "sci/" +StatTab = "Stats" +StandardDeviation = "Standardabweichung" Step = "Schrittwert" +SquareSum = "Quadratsumme" SyntaxError = "Syntaxfehler" ToZoom = "Zoom: " Trigonometric = "Trigonometrisch" diff --git a/apps/shared.en.i18n b/apps/shared.en.i18n index adff13752..33cfdbd9f 100644 --- a/apps/shared.en.i18n +++ b/apps/shared.en.i18n @@ -7,13 +7,16 @@ Cancel = "Cancel" ClearColumn = "Clear column" ColumnOptions = "Column options" CopyColumnInList = "Export the column to a list" +DataNotSuitable = "Data not suitable" DataTab = "Data" DefaultSetting = "Basic settings" Deg = "deg" +Deviation = "Variance" DisplayValues = "Display values" Empty = "Empty" ExitExamMode1 = "Exit the exam " ExitExamMode2 = "mode?" +FillWithFormula = "Fill with a formula" ForbiddenValue = "Forbidden value" FunctionColumn = "0(0) column" FunctionOptions = "Function options" @@ -27,10 +30,12 @@ Initialization = "Preadjustment" IntervalSet = "Set the interval" Language = "Language" LowBattery = "Low battery" -MathError = "Math error" +Mean = "Mean" Move = " Move: " Next = "Next" +NoDataToPlot = "No data to draw" NoFunctionToDelete = "No function to delete" +NoValueToCompute = "No values to calculate" Ok = "Confirm" Or = " or " Orthonormal = "Orthonormal" @@ -38,7 +43,10 @@ Plot = "Plot graph" Rad = "rad" RoundAbscissa = "Integer" Sci = "sci/" +StandardDeviation = "Standard deviation" +StatTab = "Stats" Step = "Step" +SquareSum = "Sum of squares" SyntaxError = "Syntax error" ToZoom = "Zoom: " Trigonometric = "Trigonometrical" diff --git a/apps/shared.es.i18n b/apps/shared.es.i18n index bf7bc4eac..5339f9be0 100644 --- a/apps/shared.es.i18n +++ b/apps/shared.es.i18n @@ -7,13 +7,16 @@ Cancel = "Cancelar" ClearColumn = "Borrar la columna" ColumnOptions = "Opciones de la columna" CopyColumnInList = "Copiar la columna en una lista" +DataNotSuitable = "Datos no adecuados" DataTab = "Datos" DefaultSetting = "Ajustes basicos" Deg = "gra" +Deviation = "Varianza" DisplayValues = "Visualizar los valores" Empty = "Vacio" ExitExamMode1 = "Salir del modo " ExitExamMode2 = "examen ?" +FillWithFormula = "Rellenar con una fórmula" ForbiddenValue = "Valor prohibido" FunctionColumn = "Columna 0(0)" FunctionOptions = "Opciones de la funcion" @@ -27,10 +30,12 @@ Initialization = "Inicializacion" IntervalSet = "Ajustar el intervalo" Language = "Idioma" LowBattery = "Bateria baja" -MathError = "Error matematico" +Mean = "Media" Move = " Mover : " Next = "Siguiente" +NoDataToPlot = "Ningunos datos que dibujar" NoFunctionToDelete = "Ninguna funcion que eliminar" +NoValueToCompute = "Ninguna medida que calcular" Ok = "Confirmar" Or = " o " Orthonormal = "Ortonormal" @@ -38,6 +43,9 @@ Plot = "Dibujar el grafico" Rad = "rad" RoundAbscissa = "Abscisas enteras" Sci = "sci/" +SquareSum = "Suma cuadrados" +StandardDeviation = "Desviacion tipica" +StatTab = "Medidas" Step = "Incremento" SyntaxError = "Error sintactico" ToZoom = "Zoom : " diff --git a/apps/shared.fr.i18n b/apps/shared.fr.i18n index 96a2b3ce2..17e9da1f5 100644 --- a/apps/shared.fr.i18n +++ b/apps/shared.fr.i18n @@ -7,13 +7,16 @@ Cancel = "Annuler" ClearColumn = "Effacer la colonne" ColumnOptions = "Options de la colonne" CopyColumnInList = "Copier la colonne dans une liste" +DataNotSuitable = "Les données ne conviennent pas" DataTab = "Donnees" DefaultSetting = "Reglages de base" Deg = "deg" +Deviation = "Variance" DisplayValues = "Afficher les valeurs" Empty = "Vide" ExitExamMode1 = "Voulez-vous sortir " ExitExamMode2 = "du mode examen ?" +FillWithFormula = "Remplir avec une formule" ForbiddenValue = "Valeur interdite" FunctionColumn = "Colonne 0(0)" FunctionOptions = "Options de la fonction" @@ -27,10 +30,12 @@ Initialization = "Initialisation" IntervalSet = "Regler l'intervalle" Language = "Langue" LowBattery = "Batterie faible" -MathError = "Erreur mathematique" +Mean = "Moyenne" Move = " Deplacer : " Next = "Suivant" +NoDataToPlot = "Aucune donnee a tracer" NoFunctionToDelete = "Pas de fonction a supprimer" +NoValueToCompute = "Aucune grandeur a calculer" Ok = "Valider" Or = " ou " Orthonormal = "Orthonorme" @@ -38,6 +43,9 @@ Plot = "Tracer le graphique" Rad = "rad" RoundAbscissa = "Abscisses entieres" Sci = "sci/" +SquareSum = "Somme des carres" +StandardDeviation = "Ecart type" +StatTab = "Stats" Step = "Pas" SyntaxError = "Attention a la syntaxe" ToZoom = "Zoomer : " diff --git a/apps/shared.pt.i18n b/apps/shared.pt.i18n index b336ddeee..a23d9c19a 100644 --- a/apps/shared.pt.i18n +++ b/apps/shared.pt.i18n @@ -7,13 +7,16 @@ Cancel = "Cancelar" ClearColumn = "Excluir coluna" ColumnOptions = "Opcoes de coluna" CopyColumnInList = "Copie a coluna em uma lista" +DataNotSuitable = "Dados não adequados" DataTab = "Dados" DefaultSetting = "Configuracoes basicas" Deg = "gra" +Deviation = "Variancia" DisplayValues = "Exibir os valores" Empty = "Vacuo" ExitExamMode1 = "Voce quer sair do modo de " ExitExamMode2 = "exame ?" +FillWithFormula = "Preencher com uma fórmula" ForbiddenValue = "Valor proibida" FunctionColumn = "Coluna 0(0)" FunctionOptions = "Opcoes de funcao" @@ -27,10 +30,12 @@ Initialization = "Inicializacao" IntervalSet = "Ajustar o intervalo" Language = "Idioma" LowBattery = "Bateria fraca" -MathError = "Erro matematico" +Mean = "Media" Move = " Mover : " Next = "Seguinte" +NoDataToPlot = "Nao ha dados para desenhar" NoFunctionToDelete = "Sem funcao para eliminar" +NoValueToCompute = "Nenhuma quantidade para calcular" Ok = "Confirmar" Or = " ou " Orthonormal = "Ortonormado" @@ -38,6 +43,9 @@ Plot = "Tracar o grafico" Rad = "rad" RoundAbscissa = "Inteiro" Sci = "sci/" +SquareSum = "Soma dos quadrados" +StandardDeviation = "Desvio padrao" +StatTab = "Estat" Step = "Passo" SyntaxError = "Erro de sintaxe" ToZoom = "Zoom : " diff --git a/apps/shared/Makefile b/apps/shared/Makefile index b0bb7ed93..dcf909c56 100644 --- a/apps/shared/Makefile +++ b/apps/shared/Makefile @@ -1,16 +1,19 @@ app_objs += $(addprefix apps/shared/,\ banner_view.o\ + buffer_function_title_cell.o\ + buffer_text_view_with_text_field.o\ button_with_separator.o\ cursor_view.o\ curve_view.o\ curve_view_cursor.o\ curve_view_range.o\ + double_pair_store.o\ editable_cell_table_view_controller.o\ expression_field_delegate_app.o\ + expression_layout_field_delegate.o\ expression_model.o\ expression_model_list_controller.o\ expression_model_store.o\ - float_pair_store.o\ float_parameter_controller.o\ function.o\ function_app.o\ @@ -24,6 +27,8 @@ app_objs += $(addprefix apps/shared/,\ function_store.o\ function_title_cell.o\ go_to_parameter_controller.o\ + hideable_even_odd_cell.o\ + hideable_even_odd_editable_text_cell.o\ initialisation_parameter_controller.o\ interactive_curve_view_controller.o\ interactive_curve_view_range.o\ @@ -32,6 +37,7 @@ app_objs += $(addprefix apps/shared/,\ interval_parameter_controller.o\ language_controller.o\ list_parameter_controller.o\ + margin_even_odd_message_text_cell.o\ memoized_curve_view_range.o\ message_view.o\ ok_view.o\ @@ -41,10 +47,14 @@ app_objs += $(addprefix apps/shared/,\ round_cursor_view.o\ scrollable_exact_approximate_expressions_cell.o\ scrollable_exact_approximate_expressions_view.o\ + separator_even_odd_buffer_text_cell.o\ simple_interactive_curve_view_controller.o\ - expression_layout_field_delegate.o\ + store_cell.o\ + store_context.o\ store_controller.o\ store_parameter_controller.o\ + store_selectable_table_view.o\ + store_title_cell.o\ sum_graph_controller.o\ tab_table_controller.o\ text_field_delegate.o\ diff --git a/apps/shared/buffer_function_title_cell.cpp b/apps/shared/buffer_function_title_cell.cpp new file mode 100644 index 000000000..b6a92b7a2 --- /dev/null +++ b/apps/shared/buffer_function_title_cell.cpp @@ -0,0 +1,52 @@ +#include "buffer_function_title_cell.h" +#include + +namespace Shared { + +BufferFunctionTitleCell::BufferFunctionTitleCell(Orientation orientation, KDText::FontSize size) : + FunctionTitleCell(orientation), + m_bufferTextView(size, 0.5f, 0.5f) +{ +} + +void BufferFunctionTitleCell::setHighlighted(bool highlight) { + EvenOddCell::setHighlighted(highlight); + m_bufferTextView.setHighlighted(highlight); +} + +void BufferFunctionTitleCell::setEven(bool even) { + EvenOddCell::setEven(even); + m_bufferTextView.setEven(even); +} + +void BufferFunctionTitleCell::setColor(KDColor color) { + FunctionTitleCell::setColor(color); + m_bufferTextView.setTextColor(color); +} + +void BufferFunctionTitleCell::setText(const char * title) { + m_bufferTextView.setText(title); +} + +int BufferFunctionTitleCell::numberOfSubviews() const { + return 1; +} + +View * BufferFunctionTitleCell::subviewAtIndex(int index) { + assert(index == 0); + return &m_bufferTextView; +} + +void BufferFunctionTitleCell::layoutSubviews() { + m_bufferTextView.setFrame(bufferTextViewFrame()); +} + +KDRect BufferFunctionTitleCell::bufferTextViewFrame() const { + KDRect textFrame(0, k_colorIndicatorThickness, bounds().width(), bounds().height() - k_colorIndicatorThickness); + if (m_orientation == Orientation::VerticalIndicator){ + textFrame = KDRect(k_colorIndicatorThickness, 0, bounds().width() - k_colorIndicatorThickness-k_separatorThickness, bounds().height()-k_separatorThickness); + } + return textFrame; +} + +} diff --git a/apps/graph/function_title_cell.h b/apps/shared/buffer_function_title_cell.h similarity index 51% rename from apps/graph/function_title_cell.h rename to apps/shared/buffer_function_title_cell.h index bbc9142a9..57728ebd6 100644 --- a/apps/graph/function_title_cell.h +++ b/apps/shared/buffer_function_title_cell.h @@ -1,13 +1,13 @@ -#ifndef GRAPH_FUNCTION_TITLE_CELL_H -#define GRAPH_FUNCTION_TITLE_CELL_H +#ifndef SHARED_BUFFER_FUNCTION_TITLE_CELL_H +#define SHARED_BUFFER_FUNCTION_TITLE_CELL_H -#include "../shared/function_title_cell.h" +#include "function_title_cell.h" -namespace Graph { +namespace Shared { -class FunctionTitleCell : public Shared::FunctionTitleCell { +class BufferFunctionTitleCell : public FunctionTitleCell { public: - FunctionTitleCell(Orientation orientation, KDText::FontSize size = KDText::FontSize::Large); + BufferFunctionTitleCell(Orientation orientation, KDText::FontSize size = KDText::FontSize::Large); void setEven(bool even) override; void setHighlighted(bool highlight) override; void setColor(KDColor color) override; @@ -15,10 +15,13 @@ public: const char * text() const override { return m_bufferTextView.text(); } -private: int numberOfSubviews() const override; View * subviewAtIndex(int index) override; void layoutSubviews() override; +protected: + KDRect bufferTextViewFrame() const; + EvenOddBufferTextCell * bufferTextView() { return &m_bufferTextView; } +private: EvenOddBufferTextCell m_bufferTextView; }; diff --git a/apps/shared/buffer_text_view_with_text_field.cpp b/apps/shared/buffer_text_view_with_text_field.cpp new file mode 100644 index 000000000..4e97fec39 --- /dev/null +++ b/apps/shared/buffer_text_view_with_text_field.cpp @@ -0,0 +1,63 @@ +#include "buffer_text_view_with_text_field.h" +#include + +namespace Shared { + +BufferTextViewWithTextField::BufferTextViewWithTextField(Responder * parentResponder, TextFieldDelegate * delegate, KDText::FontSize size) : + View(), + Responder(parentResponder), + m_bufferTextView(size, 0.0f, 0.5f), + m_textField(this, m_textFieldBuffer, m_textFieldBuffer, k_textFieldBufferSize, delegate, false, size, 0.0f, 0.5f), + m_textFieldBuffer{} +{ +} + +KDSize BufferTextViewWithTextField::minimalSizeForOptimalDisplay() const { + return m_bufferTextView.minimalSizeForOptimalDisplay(); +} + +void BufferTextViewWithTextField::setBufferText(const char * text) { + m_bufferTextView.setText(text); +} + +void BufferTextViewWithTextField::drawRect(KDContext * ctx, KDRect rect) const { + KDRect textFieldRect = textFieldFrame(); + + // Fill margins with white + // Left margin + ctx->fillRect(KDRect(0, 0, Metric::TitleBarExternHorizontalMargin, bounds().height()), KDColorWhite); + ctx->fillRect(KDRect(bounds().width() - Metric::TitleBarExternHorizontalMargin, 0, Metric::TitleBarExternHorizontalMargin, bounds().height()), KDColorWhite); + // Right margin + ctx->fillRect(KDRect(bounds().width() - Metric::TitleBarExternHorizontalMargin, 0, Metric::TitleBarExternHorizontalMargin, bounds().height()), KDColorWhite); + // Above the text field + ctx->fillRect(KDRect(textFieldRect.x() - k_borderWidth, 0, textFieldRect.width() + 2*k_borderWidth, bounds().height()), KDColorWhite); + // Under the text field + ctx->fillRect(KDRect(textFieldRect.x() - k_borderWidth, textFieldRect.bottom() + k_borderWidth, textFieldRect.width() + 2*k_borderWidth, bounds().height()), KDColorWhite); + + // Draw the text field border + KDRect borderRect = KDRect(textFieldRect.x()-k_borderWidth, textFieldRect.y()-k_borderWidth, textFieldRect.width()+2*k_borderWidth, textFieldRect.height()+2*k_borderWidth); + ctx->strokeRect(borderRect, Palette::GreyMiddle); +} + +void BufferTextViewWithTextField::didBecomeFirstResponder() { + app()->setFirstResponder(&m_textField); + m_textField.setEditing(true, false); + markRectAsDirty(bounds()); +} + +View * BufferTextViewWithTextField::subviewAtIndex(int index) { + assert(index >= 0 && index < numberOfSubviews()); + View * views[] = {&m_bufferTextView, &m_textField}; + return views[index]; +} + +void BufferTextViewWithTextField::layoutSubviews() { + m_bufferTextView.setFrame(KDRect(Metric::TitleBarExternHorizontalMargin, 0, k_bufferTextWidth, bounds().height())); + m_textField.setFrame(textFieldFrame()); +} + +KDRect BufferTextViewWithTextField::textFieldFrame() const { + return KDRect(Metric::TitleBarExternHorizontalMargin + k_bufferTextWidth + k_borderWidth, k_textFieldVerticalMargin + k_borderWidth, bounds().width() - 2 * Metric::TitleBarExternHorizontalMargin - k_bufferTextWidth - 2 * k_borderWidth, bounds().height() - 2 * k_textFieldVerticalMargin - 2 * k_borderWidth); +} + +} diff --git a/apps/shared/buffer_text_view_with_text_field.h b/apps/shared/buffer_text_view_with_text_field.h new file mode 100644 index 000000000..755efd693 --- /dev/null +++ b/apps/shared/buffer_text_view_with_text_field.h @@ -0,0 +1,33 @@ +#ifndef SHARED_BUFFER_TEXT_VIEW_WITH_TEXT_FIELD_H +#define SHARED_BUFFER_TEXT_VIEW_WITH_TEXT_FIELD_H +#include + +namespace Shared { + +class BufferTextViewWithTextField : public View, public Responder { +public: + BufferTextViewWithTextField(Responder * parentResponder, TextFieldDelegate * delegate = nullptr, KDText::FontSize size = KDText::FontSize::Large); + KDSize minimalSizeForOptimalDisplay() const override; + TextField * textField() { return &m_textField; } + void setBufferText(const char * text); + void drawRect(KDContext * ctx, KDRect rect) const override; + + // Responder + void didBecomeFirstResponder() override; +private: + constexpr static int k_textFieldBufferSize = TextField::maxBufferSize(); + constexpr static KDCoordinate k_bufferTextWidth = 35; + constexpr static KDCoordinate k_textFieldVerticalMargin = 3; + constexpr static KDCoordinate k_borderWidth = 1; + int numberOfSubviews() const override { return 2; } + View * subviewAtIndex(int index) override; + void layoutSubviews() override; + KDRect textFieldFrame() const; + BufferTextView m_bufferTextView; + TextField m_textField; + char m_textFieldBuffer[k_textFieldBufferSize]; +}; + +} + +#endif diff --git a/apps/shared/curve_view.cpp b/apps/shared/curve_view.cpp index c1d2945b4..62e69b4f0 100644 --- a/apps/shared/curve_view.cpp +++ b/apps/shared/curve_view.cpp @@ -10,15 +10,17 @@ using namespace Poincare; namespace Shared { CurveView::CurveView(CurveViewRange * curveViewRange, CurveViewCursor * curveViewCursor, BannerView * bannerView, - View * cursorView, View * okView) : + View * cursorView, View * okView, bool displayBanner) : View(), m_bannerView(bannerView), m_curveViewCursor(curveViewCursor), m_curveViewRange(curveViewRange), m_cursorView(cursorView), m_okView(okView), + m_forceOkDisplay(false), m_mainViewSelected(false), - m_drawnRangeVersion(0) + m_drawnRangeVersion(0), + m_displayBanner(displayBanner) { } @@ -27,7 +29,7 @@ void CurveView::reload() { if (m_drawnRangeVersion != rangeVersion) { // FIXME: This should also be called if the *curve* changed m_drawnRangeVersion = rangeVersion; - KDCoordinate bannerHeight = m_bannerView != nullptr ? m_bannerView->bounds().height() : 0; + KDCoordinate bannerHeight = (m_bannerView != nullptr && m_displayBanner) ? m_bannerView->bounds().height() : 0; markRectAsDirty(KDRect(0, 0, bounds().width(), bounds().height() - bannerHeight)); if (label(Axis::Horizontal, 0) != nullptr) { computeLabels(Axis::Horizontal); @@ -143,10 +145,12 @@ void CurveView::computeLabels(Axis axis) { } } -void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOrigin) const { +void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOrigin, bool graduationOnly, bool fixCoordinate, KDCoordinate fixedCoordinate) const { float step = gridUnit(axis); float start = 2.0f*step*(std::ceil(min(axis)/(2.0f*step))); float end = max(axis); + float verticalCoordinate = fixCoordinate ? fixedCoordinate : std::round(floatToPixel(Axis::Vertical, 0.0f)); + float horizontalCoordinate = fixCoordinate ? fixedCoordinate : std::round(floatToPixel(Axis::Horizontal, 0.0f)); int i = 0; for (float x = start; x < end; x += 2.0f*step) { /* When |start| >> step, start + step = start. In that case, quit the @@ -154,18 +158,22 @@ void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOr if (x == x-step || x == x+step) { return; } - KDSize textSize = KDText::stringSize(label(axis, i), KDText::FontSize::Small); - KDPoint origin(std::round(floatToPixel(Axis::Horizontal, x)) - textSize.width()/2, std::round(floatToPixel(Axis::Vertical, 0.0f)) + k_labelMargin); - KDRect graduation(std::round(floatToPixel(Axis::Horizontal, x)), std::round(floatToPixel(Axis::Vertical, 0.0f)) -(k_labelGraduationLength-2)/2, 1, k_labelGraduationLength); + KDRect graduation(std::round(floatToPixel(Axis::Horizontal, x)), verticalCoordinate -(k_labelGraduationLength-2)/2, 1, k_labelGraduationLength); if (axis == Axis::Vertical) { - origin = KDPoint(std::round(floatToPixel(Axis::Horizontal, 0.0f)) + k_labelMargin, std::round(floatToPixel(Axis::Vertical, x)) - textSize.height()/2); - graduation = KDRect(std::round(floatToPixel(Axis::Horizontal, 0.0f))-(k_labelGraduationLength-2)/2, std::round(floatToPixel(Axis::Vertical, x)), k_labelGraduationLength, 1); + graduation = KDRect(horizontalCoordinate-(k_labelGraduationLength-2)/2, std::round(floatToPixel(Axis::Vertical, x)), k_labelGraduationLength, 1); } - if (-step < x && x < step && shiftOrigin) { - origin = KDPoint(std::round(floatToPixel(Axis::Horizontal, 0.0f)) + k_labelMargin, std::round(floatToPixel(Axis::Vertical, 0.0f)) + k_labelMargin); - } - if (rect.intersects(KDRect(origin, KDText::stringSize(label(axis, i), KDText::FontSize::Small)))) { - ctx->blendString(label(axis, i), origin, KDText::FontSize::Small, KDColorBlack); + if (!graduationOnly) { + KDSize textSize = KDText::stringSize(label(axis, i), KDText::FontSize::Small); + KDPoint origin(std::round(floatToPixel(Axis::Horizontal, x)) - textSize.width()/2, verticalCoordinate + k_labelMargin); + if (axis == Axis::Vertical) { + origin = KDPoint(horizontalCoordinate + k_labelMargin, std::round(floatToPixel(Axis::Vertical, x)) - textSize.height()/2); + } + if (-step < x && x < step && shiftOrigin) { + origin = KDPoint(horizontalCoordinate + k_labelMargin, verticalCoordinate + k_labelMargin); + } + if (rect.intersects(KDRect(origin, KDText::stringSize(label(axis, i), KDText::FontSize::Small)))) { + ctx->blendString(label(axis, i), origin, KDText::FontSize::Small, KDColorBlack); + } } ctx->fillRect(graduation, KDColorBlack); i++; @@ -514,7 +522,7 @@ void CurveView::layoutSubviews() { if (m_curveViewCursor != nullptr && m_cursorView != nullptr) { m_cursorView->setFrame(cursorFrame()); } - if (m_bannerView != nullptr) { + if (m_bannerView != nullptr && m_displayBanner) { m_bannerView->setFrame(bannerFrame()); } if (m_okView != nullptr) { @@ -530,7 +538,7 @@ KDRect CurveView::cursorFrame() { KDCoordinate yCursorPixelPosition = std::round(floatToPixel(Axis::Vertical, m_curveViewCursor->y())); cursorFrame = KDRect(xCursorPixelPosition - (cursorSize.width()-1)/2, yCursorPixelPosition - (cursorSize.height()-1)/2, cursorSize.width(), cursorSize.height()); if (cursorSize.height() == 0) { - KDCoordinate bannerHeight = m_bannerView != nullptr ? m_bannerView->minimalSizeForOptimalDisplay().height() : 0; + KDCoordinate bannerHeight = (m_bannerView != nullptr && m_displayBanner) ? m_bannerView->minimalSizeForOptimalDisplay().height() : 0; cursorFrame = KDRect(xCursorPixelPosition - (cursorSize.width()-1)/2, 0, cursorSize.width(),bounds().height()-bannerHeight); } } @@ -539,7 +547,7 @@ KDRect CurveView::cursorFrame() { KDRect CurveView::bannerFrame() { KDRect bannerFrame = KDRectZero; - if (m_bannerView && m_mainViewSelected) { + if (m_bannerView && m_displayBanner && m_mainViewSelected) { KDCoordinate bannerHeight = m_bannerView->minimalSizeForOptimalDisplay().height(); bannerFrame = KDRect(0, bounds().height()- bannerHeight, bounds().width(), bannerHeight); } @@ -548,19 +556,19 @@ KDRect CurveView::bannerFrame() { KDRect CurveView::okFrame() { KDRect okFrame = KDRectZero; - if (m_okView && m_mainViewSelected) { + if (m_okView && (m_mainViewSelected || m_forceOkDisplay)) { KDCoordinate bannerHeight = 0; - if (m_bannerView != nullptr) { + if (m_bannerView != nullptr && m_displayBanner) { bannerHeight = m_bannerView->minimalSizeForOptimalDisplay().height(); } KDSize okSize = m_okView->minimalSizeForOptimalDisplay(); - okFrame = KDRect(bounds().width()- okSize.width()-k_okMargin, bounds().height()- bannerHeight-okSize.height()-k_okMargin, okSize); + okFrame = KDRect(bounds().width()- okSize.width()-k_okHorizontalMargin, bounds().height()- bannerHeight-okSize.height()-k_okVerticalMargin, okSize); } return okFrame; } int CurveView::numberOfSubviews() const { - return (m_bannerView != nullptr) + (m_cursorView != nullptr) + (m_okView != nullptr); + return (m_bannerView != nullptr && m_displayBanner) + (m_cursorView != nullptr) + (m_okView != nullptr); }; View * CurveView::subviewAtIndex(int index) { @@ -572,12 +580,12 @@ View * CurveView::subviewAtIndex(int index) { if (m_okView != nullptr) { return m_okView; } else { - if (m_bannerView != nullptr) { + if (m_bannerView != nullptr && m_displayBanner) { return m_bannerView; } } } - if (index == 1 && m_bannerView != nullptr && m_okView != nullptr) { + if (index == 1 && m_bannerView != nullptr && m_displayBanner && m_okView != nullptr) { return m_bannerView; } return m_cursorView; diff --git a/apps/shared/curve_view.h b/apps/shared/curve_view.h index 7552b2e85..a4a79cc9f 100644 --- a/apps/shared/curve_view.h +++ b/apps/shared/curve_view.h @@ -17,25 +17,33 @@ public: Horizontal = 0, Vertical = 1 }; - CurveView(CurveViewRange * curveViewRange = nullptr, CurveViewCursor * curveViewCursor = nullptr, - BannerView * bannerView = nullptr, View * cursorView = nullptr, View * okView = nullptr); + CurveView(CurveViewRange * curveViewRange = nullptr, + CurveViewCursor * curveViewCursor = nullptr, + BannerView * bannerView = nullptr, + View * cursorView = nullptr, + View * okView = nullptr, + bool displayBanner = true); virtual void reload(); // When the main view is selected, the banner view is visible bool isMainViewSelected() const; void selectMainView(bool mainViewSelected); void setCursorView(View * cursorView); void setBannerView(View * bannerView); + void setDisplayBannerView(bool display) { m_displayBanner = display; } + bool displayBannerView() const { return m_displayBanner; } void setOkView(View * okView); + void setForceOkDisplay(bool force) { m_forceOkDisplay = force; } float resolution() const; protected: void setCurveViewRange(CurveViewRange * curveViewRange); // Drawing methods virtual float samplingRatio() const; - constexpr static KDCoordinate k_labelMargin = 4; - constexpr static KDCoordinate k_okMargin = 10; - constexpr static KDCoordinate k_labelGraduationLength = 6; + constexpr static KDCoordinate k_labelMargin = 4; + constexpr static KDCoordinate k_okVerticalMargin = 23; + constexpr static KDCoordinate k_okHorizontalMargin = 10; + constexpr static KDCoordinate k_labelGraduationLength = 6; constexpr static int k_maxNumberOfXLabels = CurveViewRange::k_maxNumberOfXGridUnits; - constexpr static int k_maxNumberOfYLabels = CurveViewRange::k_maxNumberOfYGridUnits; + constexpr static int k_maxNumberOfYLabels = CurveViewRange::k_maxNumberOfYGridUnits; constexpr static int k_externRectMargin = 2; float pixelToFloat(Axis axis, KDCoordinate p) const; float floatToPixel(Axis axis, float f) const; @@ -52,7 +60,7 @@ protected: void drawHistogram(KDContext * ctx, KDRect rect, EvaluateModelWithParameter evaluation, void * model, void * context, 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; + void drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOrigin, bool graduationOnly = false, bool fixCoordinate = false, KDCoordinate fixedCoordinate = 0) const; View * m_bannerView; CurveViewCursor * m_curveViewCursor; private: @@ -84,8 +92,10 @@ private: CurveViewRange * m_curveViewRange; View * m_cursorView; View * m_okView; + bool m_forceOkDisplay; bool m_mainViewSelected; uint32_t m_drawnRangeVersion; + bool m_displayBanner; }; } diff --git a/apps/shared/double_pair_store.cpp b/apps/shared/double_pair_store.cpp new file mode 100644 index 000000000..32e84c558 --- /dev/null +++ b/apps/shared/double_pair_store.cpp @@ -0,0 +1,95 @@ +#include "double_pair_store.h" +#include +#include +#include +#include + +namespace Shared { + +void DoublePairStore::set(double f, int series, int i, int j) { + assert(series >= 0 && series < k_numberOfSeries); + if (j >= k_maxNumberOfPairs) { + return; + } + m_data[series][i][j] = f; + if (j >= m_numberOfPairs[series]) { + int otherI = i == 0 ? 1 : 0; + m_data[series][otherI][j] = defaultValue(series, otherI, j); + m_numberOfPairs[series]++; + } +} + +int DoublePairStore::numberOfPairs() const { + int result = 0; + for (int i = 0; i < k_numberOfSeries; i++) { + result += m_numberOfPairs[i]; + } + return result; +} + +void DoublePairStore::deletePairOfSeriesAtIndex(int series, int j) { + m_numberOfPairs[series]--; + for (int k = j; k < m_numberOfPairs[series]; k++) { + m_data[series][0][k] = m_data[series][0][k+1]; + m_data[series][1][k] = m_data[series][1][k+1]; + } + /* We reset the values of the empty row to ensure the correctness of the + * checksum. */ + m_data[series][0][m_numberOfPairs[series]] = 0; + m_data[series][1][m_numberOfPairs[series]] = 0; +} + +void DoublePairStore::deleteAllPairsOfSeries(int series) { + assert(series >= 0 && series < k_numberOfSeries); + /* We reset all values to 0 to ensure the correctness of the checksum.*/ + for (int k = 0; k < m_numberOfPairs[series]; k++) { + m_data[series][0][k] = 0; + m_data[series][1][k] = 0; + } + m_numberOfPairs[series] = 0; +} + +void DoublePairStore::deleteAllPairs() { + for (int i = 0; i < k_numberOfSeries; i ++) { + deleteAllPairsOfSeries(i); + } +} + +void DoublePairStore::resetColumn(int series, int i) { + assert(series >= 0 && series < k_numberOfSeries); + assert(i == 0 || i == 1); + for (int k = 0; k < m_numberOfPairs[series]; k++) { + m_data[series][i][k] = defaultValue(series, i, k); + } +} + +double DoublePairStore::sumOfColumn(int series, int i) const { + assert(series >= 0 && series < k_numberOfSeries); + assert(i == 0 || i == 1); + double result = 0; + for (int k = 0; k < m_numberOfPairs[series]; k++) { + result += m_data[series][i][k]; + } + return result; +} + +uint32_t DoublePairStore::storeChecksum() const { + /* Ideally, we would only compute the checksum of the first m_numberOfPairs + * pairs. However, the two values of a pair are not stored consecutively. We + * thus compute the checksum on all pairs and ensure to set the pair at 0 + * when removing them. */ + size_t dataLengthInBytes = k_numberOfSeries*k_maxNumberOfPairs*k_numberOfColumnsPerSeries*sizeof(double); + assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 + return Ion::crc32((uint32_t *)m_data, dataLengthInBytes/sizeof(uint32_t)); +} + +double DoublePairStore::defaultValue(int series, int i, int j) const { + assert(series >= 0 && series < k_numberOfSeries); + if(i == 0 && j > 1) { + return 2*m_data[series][i][j-1]-m_data[series][i][j-2]; + } else { + return 0.0; + } +} + +} diff --git a/apps/shared/double_pair_store.h b/apps/shared/double_pair_store.h new file mode 100644 index 000000000..f172e3e11 --- /dev/null +++ b/apps/shared/double_pair_store.h @@ -0,0 +1,56 @@ +#ifndef SHARED_DOUBLE_PAIR_STORE_H +#define SHARED_DOUBLE_PAIR_STORE_H + +#include +#include +#include +#include + +namespace Shared { + +class DoublePairStore { +public: + constexpr static int k_numberOfSeries = 3; + constexpr static int k_numberOfColumnsPerSeries = 2; + constexpr static int k_maxNumberOfPairs = 100; + DoublePairStore() : + m_data{}, + m_numberOfPairs{} + {} + // Delete the implicit copy constructor: the object is heavy + DoublePairStore(const DoublePairStore&) = delete; + double get(int series, int i, int j) const { + assert(j < m_numberOfPairs[series]); + return m_data[series][i][j]; + } + virtual void set(double f, int series, int i, int j); + int numberOfPairs() const; + int numberOfPairsOfSeries(int series) const { + assert(series >= 0 && series < k_numberOfSeries); + return m_numberOfPairs[series]; + } + virtual void deletePairOfSeriesAtIndex(int series, int j); + virtual void deleteAllPairsOfSeries(int series); + void deleteAllPairs(); + void resetColumn(int series, int i); + double sumOfColumn(int series, int i) const; + uint32_t storeChecksum() const; + + static KDColor colorOfSeriesAtIndex(int i) { + assert(i >= 0 && i < k_numberOfSeries); + return Palette::DataColor[i]; + } + static KDColor colorLightOfSeriesAtIndex(int i) { + assert(i >= 0 && i < k_numberOfSeries); + return Palette::DataColorLight[i]; + } +protected: + virtual double defaultValue(int series, int i, int j) const; + double m_data[k_numberOfSeries][k_numberOfColumnsPerSeries][k_maxNumberOfPairs]; +private: + int m_numberOfPairs[k_numberOfSeries]; +}; + +} + +#endif diff --git a/apps/shared/editable_cell_table_view_controller.h b/apps/shared/editable_cell_table_view_controller.h index 3171ce411..80c6660a9 100644 --- a/apps/shared/editable_cell_table_view_controller.h +++ b/apps/shared/editable_cell_table_view_controller.h @@ -9,7 +9,7 @@ namespace Shared { -class EditableCellTableViewController : public TabTableController , public RegularTableViewDataSource , public TextFieldDelegate { +class EditableCellTableViewController : public TabTableController , public RegularTableViewDataSource, public TextFieldDelegate { public: EditableCellTableViewController(Responder * parentResponder); bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; diff --git a/apps/shared/float_pair_store.cpp b/apps/shared/float_pair_store.cpp deleted file mode 100644 index c03e64e67..000000000 --- a/apps/shared/float_pair_store.cpp +++ /dev/null @@ -1,89 +0,0 @@ -#include "float_pair_store.h" -#include -#include -#include -#include - -namespace Shared { - -FloatPairStore::FloatPairStore() : - m_numberOfPairs(0), - m_data{} -{ -} - -double FloatPairStore::get(int i, int j) { - assert(j < m_numberOfPairs); - return m_data[i][j]; -} - -void FloatPairStore::set(double f, int i, int j) { - if (j >= k_maxNumberOfPairs) { - return; - } - m_data[i][j] = f; - if (j >= m_numberOfPairs) { - int otherI = i == 0 ? 1 : 0; - m_data[otherI][j] = defaultValue(otherI, j); - m_numberOfPairs++; - } -} - -int FloatPairStore::numberOfPairs() { - return m_numberOfPairs; -} - -void FloatPairStore::deletePairAtIndex(int i) { - m_numberOfPairs--; - for (int k = i; k < m_numberOfPairs; k++) { - m_data[0][k] = m_data[0][k+1]; - m_data[1][k] = m_data[1][k+1]; - } - /* We reset the values of the empty row to ensure the correctness of the - * checksum. */ - m_data[0][m_numberOfPairs] = 0; - m_data[1][m_numberOfPairs] = 0; -} - -void FloatPairStore::deleteAllPairs() { - /* We reset all values to 0 to ensure the correctness of the checksum.*/ - for (int k = 0; k < m_numberOfPairs; k++) { - m_data[0][k] = 0; - m_data[1][k] = 0; - } - m_numberOfPairs = 0; -} - -void FloatPairStore::resetColumn(int i) { - for (int k = 0; k < m_numberOfPairs; k++) { - m_data[i][k] = defaultValue(i, k); - } -} - -double FloatPairStore::sumOfColumn(int i) { - double result = 0; - for (int k = 0; k < m_numberOfPairs; k++) { - result += m_data[i][k]; - } - return result; -} - -uint32_t FloatPairStore::storeChecksum() { - /* Ideally, we would only compute the checksum of the first m_numberOfPairs - * pairs. However, the two values of a pair are not stored consecutively. We - * thus compute the checksum on all pairs and ensure to set the pair at 0 - * when removing them. */ - size_t dataLengthInBytes = k_maxNumberOfPairs*2*sizeof(double); - assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 - return Ion::crc32((uint32_t *)m_data, dataLengthInBytes/sizeof(uint32_t)); -} - -double FloatPairStore::defaultValue(int i, int j) { - if(i == 0 && j > 1) { - return 2*m_data[i][j-1]-m_data[i][j-2]; - } else { - return 0.0; - } -} - -} diff --git a/apps/shared/float_pair_store.h b/apps/shared/float_pair_store.h deleted file mode 100644 index 6efdfe840..000000000 --- a/apps/shared/float_pair_store.h +++ /dev/null @@ -1,30 +0,0 @@ -#ifndef SHARED_FLOAT_PAIR_STORE_H -#define SHARED_FLOAT_PAIR_STORE_H - -#include - -namespace Shared { - -class FloatPairStore { -public: - FloatPairStore(); - // Delete the implicit copy constructor: the object is heavy - FloatPairStore(const FloatPairStore&) = delete; - double get(int i, int j); - void set(double f, int i, int j); - int numberOfPairs(); - void deletePairAtIndex(int j); - void deleteAllPairs(); - void resetColumn(int i); - double sumOfColumn(int i); - uint32_t storeChecksum(); - constexpr static int k_maxNumberOfPairs = 100; -protected: - virtual double defaultValue(int i, int j); - int m_numberOfPairs; - double m_data[2][k_maxNumberOfPairs]; -}; - -} - -#endif diff --git a/apps/shared/function_store.h b/apps/shared/function_store.h index 1637804a5..1628332ee 100644 --- a/apps/shared/function_store.h +++ b/apps/shared/function_store.h @@ -25,9 +25,11 @@ protected: static KDColor const color(Shared::Function * f) { return f->color(); } template using AttributeGetter = T (*)(Function * f); template T firstAvailableAttribute(T attributes[], AttributeGetter attribute); + const KDColor firstAvailableColor() { + return firstAvailableAttribute(Palette::DataColor, FunctionStore::color); + } private: virtual const char * firstAvailableName() = 0; - virtual const KDColor firstAvailableColor() = 0; }; } diff --git a/apps/shared/hideable.h b/apps/shared/hideable.h new file mode 100644 index 000000000..bf71cc740 --- /dev/null +++ b/apps/shared/hideable.h @@ -0,0 +1,22 @@ +#ifndef APPS_SHARED_HIDEABLE_H +#define APPS_SHARED_HIDEABLE_H + +#include + +namespace Shared { + +class Hideable { +public: + Hideable() : + m_hide(false) + {} + static KDColor hideColor() { return Palette::WallScreenDark; } + bool hidden() const { return m_hide; } + virtual void setHide(bool hide) { m_hide = hide; } +private: + bool m_hide; +}; + +} + +#endif diff --git a/apps/shared/hideable_even_odd_cell.cpp b/apps/shared/hideable_even_odd_cell.cpp new file mode 100644 index 000000000..7561bd4bf --- /dev/null +++ b/apps/shared/hideable_even_odd_cell.cpp @@ -0,0 +1,19 @@ +#include "hideable_even_odd_cell.h" + +namespace Shared { + +KDColor HideableEvenOddCell::backgroundColor() const { + if (hidden()) { + return hideColor(); + } + return EvenOddCell::backgroundColor(); +} + +void HideableEvenOddCell::setHide(bool hide) { + if (hidden() != hide) { + Hideable::setHide(hide); + reloadCell(); + } +} + +} diff --git a/apps/shared/hideable_even_odd_cell.h b/apps/shared/hideable_even_odd_cell.h new file mode 100644 index 000000000..ccb6b6e33 --- /dev/null +++ b/apps/shared/hideable_even_odd_cell.h @@ -0,0 +1,21 @@ +#ifndef APPS_SHARED_HIDEABLE_EVEN_ODD_CELL_H +#define APPS_SHARED_HIDEABLE_EVEN_ODD_CELL_H + +#include +#include "hideable.h" + +namespace Shared { + +class HideableEvenOddCell : public EvenOddCell, public Hideable { +public: + HideableEvenOddCell() : + EvenOddCell(), + Hideable() + {} + KDColor backgroundColor() const override; + void setHide(bool hide) override; +}; + +} + +#endif diff --git a/apps/shared/hideable_even_odd_editable_text_cell.cpp b/apps/shared/hideable_even_odd_editable_text_cell.cpp new file mode 100644 index 000000000..26cf50323 --- /dev/null +++ b/apps/shared/hideable_even_odd_editable_text_cell.cpp @@ -0,0 +1,20 @@ +#include "hideable_even_odd_editable_text_cell.h" + +namespace Shared { + +KDColor HideableEvenOddEditableTextCell::backgroundColor() const { + if (hidden()) { + return hideColor(); + } + return EvenOddEditableTextCell::backgroundColor(); +} + +void HideableEvenOddEditableTextCell::setHide(bool hide) { + if (hidden() != hide) { + Hideable::setHide(hide); + editableTextCell()->textField()->setBackgroundColor(backgroundColor()); + reloadCell(); + } +} + +} diff --git a/apps/shared/hideable_even_odd_editable_text_cell.h b/apps/shared/hideable_even_odd_editable_text_cell.h new file mode 100644 index 000000000..c9385d89a --- /dev/null +++ b/apps/shared/hideable_even_odd_editable_text_cell.h @@ -0,0 +1,22 @@ +#ifndef APPS_SHARED_HIDEABLE_EVEN_ODD_EDITABLE_TEXT_CELL_H +#define APPS_SHARED_HIDEABLE_EVEN_ODD_EDITABLE_TEXT_CELL_H + +#include +#include +#include "hideable.h" + +namespace Shared { + +class HideableEvenOddEditableTextCell : public EvenOddEditableTextCell, public Hideable { +public: + HideableEvenOddEditableTextCell(Responder * parentResponder = nullptr, TextFieldDelegate * delegate = nullptr, char * draftTextBuffer = nullptr) : + EvenOddEditableTextCell(parentResponder, delegate, draftTextBuffer), + Hideable() + {} + KDColor backgroundColor() const override; + void setHide(bool hide) override; +}; + +} + +#endif diff --git a/apps/shared/margin_even_odd_message_text_cell.cpp b/apps/shared/margin_even_odd_message_text_cell.cpp new file mode 100644 index 000000000..7ed79a3b3 --- /dev/null +++ b/apps/shared/margin_even_odd_message_text_cell.cpp @@ -0,0 +1,10 @@ +#include "margin_even_odd_message_text_cell.h" + +namespace Shared { + +void MarginEvenOddMessageTextCell::layoutSubviews() { + m_messageTextView.setFrame(KDRect(bounds().topLeft(), bounds().width() - k_rightMargin, bounds().height())); +} + +} + diff --git a/apps/shared/margin_even_odd_message_text_cell.h b/apps/shared/margin_even_odd_message_text_cell.h new file mode 100644 index 000000000..051b988ab --- /dev/null +++ b/apps/shared/margin_even_odd_message_text_cell.h @@ -0,0 +1,20 @@ +#ifndef APPS_SHARED_MARGIN_EVEN_ODD_MESSAGE_TEXT_CELL_H +#define APPS_SHARED_MARGIN_EVEN_ODD_MESSAGE_TEXT_CELL_H + +#include + +namespace Shared { + +class MarginEvenOddMessageTextCell : public EvenOddMessageTextCell { +public: + MarginEvenOddMessageTextCell(KDText::FontSize size = KDText::FontSize::Small) : + EvenOddMessageTextCell(size) + {} + void layoutSubviews() override; +private: + constexpr static KDCoordinate k_rightMargin = 2; +}; + +} + +#endif diff --git a/apps/shared/separator_even_odd_buffer_text_cell.cpp b/apps/shared/separator_even_odd_buffer_text_cell.cpp new file mode 100644 index 000000000..c87c45f91 --- /dev/null +++ b/apps/shared/separator_even_odd_buffer_text_cell.cpp @@ -0,0 +1,20 @@ +#include "separator_even_odd_buffer_text_cell.h" +#include "hideable_even_odd_editable_text_cell.h" +#include + +namespace Shared { + +void SeparatorEvenOddBufferTextCell::drawRect(KDContext * ctx, KDRect rect) const { + EvenOddBufferTextCell::drawRect(ctx, rect); + // Draw the separator + KDRect separatorRect(0, 0, Metric::TableSeparatorThickness, bounds().height()); + ctx->fillRect(separatorRect, Shared::HideableEvenOddEditableTextCell::hideColor()); +} + +void SeparatorEvenOddBufferTextCell::layoutSubviews() { + KDRect boundsThis = bounds(); + m_bufferTextView.setFrame(KDRect(boundsThis.left() + Metric::TableSeparatorThickness, boundsThis.top(), boundsThis.width() - Metric::TableSeparatorThickness - k_rightMargin, boundsThis.height())); +} + +} + diff --git a/apps/shared/separator_even_odd_buffer_text_cell.h b/apps/shared/separator_even_odd_buffer_text_cell.h new file mode 100644 index 000000000..1fd885ed5 --- /dev/null +++ b/apps/shared/separator_even_odd_buffer_text_cell.h @@ -0,0 +1,19 @@ +#ifndef APPS_SHARED_SEPARATOR_EVEN_ODD_CELL_H +#define APPS_SHARED_SEPARATOR_EVEN_ODD_CELL_H + +#include + +namespace Shared { + +class SeparatorEvenOddBufferTextCell : public EvenOddBufferTextCell { +public: + using EvenOddBufferTextCell::EvenOddBufferTextCell; + void drawRect(KDContext * ctx, KDRect rect) const override; + void layoutSubviews() override; +private: + constexpr static KDCoordinate k_rightMargin = 2; +}; + +} + +#endif diff --git a/apps/shared/store_cell.cpp b/apps/shared/store_cell.cpp new file mode 100644 index 000000000..b7179763e --- /dev/null +++ b/apps/shared/store_cell.cpp @@ -0,0 +1,28 @@ +#include "store_cell.h" +#include + +namespace Shared { + +void StoreCell::setSeparatorLeft(bool separator) { + if (m_separatorLeft != separator) { + m_separatorLeft = separator; + reloadCell(); + } +} + +void StoreCell::drawRect(KDContext * ctx, KDRect rect) const { + HideableEvenOddEditableTextCell::drawRect(ctx, rect); + // Draw the separator + KDRect separatorRect(0, 0, Metric::TableSeparatorThickness, bounds().height()); + if (m_separatorLeft) { + ctx->fillRect(separatorRect, HideableEvenOddEditableTextCell::hideColor()); + } +} + +void StoreCell::layoutSubviews() { + KDRect boundsThis = bounds(); + editableTextCell()->setFrame(KDRect(boundsThis.left() + Metric::TableSeparatorThickness, boundsThis.top(), boundsThis.width() - Metric::TableSeparatorThickness - k_rightMargin, boundsThis.height())); +} + +} + diff --git a/apps/shared/store_cell.h b/apps/shared/store_cell.h new file mode 100644 index 000000000..9e0f70064 --- /dev/null +++ b/apps/shared/store_cell.h @@ -0,0 +1,24 @@ +#ifndef APPS_SHARED_STORE_CELL_H +#define APPS_SHARED_STORE_CELL_H + +#include "hideable_even_odd_editable_text_cell.h" + +namespace Shared { + +class StoreCell : public HideableEvenOddEditableTextCell { +public: + StoreCell(Responder * parentResponder = nullptr, TextFieldDelegate * delegate = nullptr, char * draftTextBuffer = nullptr) : + HideableEvenOddEditableTextCell(parentResponder, delegate, draftTextBuffer), + m_separatorLeft(false) + {} + void setSeparatorLeft(bool separator); + void drawRect(KDContext * ctx, KDRect rect) const override; + void layoutSubviews() override; +private: + static constexpr KDCoordinate k_rightMargin = 2; + bool m_separatorLeft; +}; + +} + +#endif diff --git a/apps/shared/store_context.cpp b/apps/shared/store_context.cpp new file mode 100644 index 000000000..db4dd28bf --- /dev/null +++ b/apps/shared/store_context.cpp @@ -0,0 +1,14 @@ +#include "store_context.h" +#include +#include +#include + +using namespace Poincare; + +namespace Shared { + +void StoreContext::setExpressionForSymbolName(const Expression * expression, const Symbol * symbol, Context & context) { + m_parentContext->setExpressionForSymbolName(expression, symbol, context); +} + +} diff --git a/apps/shared/store_context.h b/apps/shared/store_context.h new file mode 100644 index 000000000..7b0513fb7 --- /dev/null +++ b/apps/shared/store_context.h @@ -0,0 +1,28 @@ +#ifndef SHARED_STORE_CONTEXT_H +#define SHARED_STORE_CONTEXT_H + +#include +#include "double_pair_store.h" + +namespace Shared { + +class StoreContext : public Poincare::Context { +public: + StoreContext(Shared::DoublePairStore * store) : + Poincare::Context(), + m_store(store), + m_seriesPairIndex(-1), + m_parentContext(nullptr) + {} + void setParentContext(Poincare::Context * parentContext) { m_parentContext = parentContext; } + void setSeriesPairIndex(int j) { m_seriesPairIndex = j; } + void setExpressionForSymbolName(const Poincare::Expression * expression, const Poincare::Symbol * symbol, Poincare::Context & context) override; +protected: + Shared::DoublePairStore * m_store; + int m_seriesPairIndex; + Poincare::Context * m_parentContext; +}; + +} + +#endif diff --git a/apps/shared/store_controller.cpp b/apps/shared/store_controller.cpp index e3aeb7633..dc8c1c2f5 100644 --- a/apps/shared/store_controller.cpp +++ b/apps/shared/store_controller.cpp @@ -1,28 +1,123 @@ #include "store_controller.h" #include "../apps_container.h" #include "../constant.h" +#include #include using namespace Poincare; namespace Shared { -StoreController::StoreController(Responder * parentResponder, FloatPairStore * store, ButtonRowController * header) : +StoreController::ContentView::ContentView(DoublePairStore * store, Responder * parentResponder, TableViewDataSource * dataSource, SelectableTableViewDataSource * selectionDataSource, TextFieldDelegate * textFieldDelegate) : + View(), + Responder(parentResponder), + m_dataView(store, this, dataSource, selectionDataSource), + m_formulaInputView(this, textFieldDelegate), + m_displayFormulaInputView(false) +{ + m_dataView.setBackgroundColor(Palette::WallScreenDark); + m_dataView.setVerticalCellOverlap(0); + m_dataView.setMargins(k_margin, k_scrollBarMargin, k_scrollBarMargin, k_margin); +} + +void StoreController::ContentView::displayFormulaInput(bool display) { + if (m_displayFormulaInputView != display) { + m_displayFormulaInputView = display; + layoutSubviews(); + markRectAsDirty(bounds()); + } +} + +void StoreController::ContentView::didBecomeFirstResponder() { + app()->setFirstResponder(m_displayFormulaInputView ? static_cast(&m_formulaInputView) : static_cast(&m_dataView)); +} + +View * StoreController::ContentView::subviewAtIndex(int index) { + assert(index >= 0 && index < numberOfSubviews()); + View * views[] = {&m_dataView, &m_formulaInputView}; + return views[index]; +} + +void StoreController::ContentView::layoutSubviews() { + KDRect dataViewFrame(0, 0, bounds().width(), bounds().height() - (m_displayFormulaInputView ? k_formulaInputHeight : 0)); + m_dataView.setFrame(dataViewFrame); + m_formulaInputView.setFrame(formulaFrame()); +} + +KDRect StoreController::ContentView::formulaFrame() const { + return KDRect(0, bounds().height() - k_formulaInputHeight, bounds().width(), m_displayFormulaInputView ? k_formulaInputHeight : 0); +} + +StoreController::StoreController(Responder * parentResponder, DoublePairStore * store, ButtonRowController * header) : EditableCellTableViewController(parentResponder), ButtonRowDelegate(header, nullptr), m_editableCells{}, m_store(store), - m_storeParameterController(this, store) + m_storeParameterController(this, store, this) { } -const char * StoreController::title() { - return I18n::translate(I18n::Message::DataTab); +void StoreController::displayFormulaInput() { + setFormulaLabel(); + contentView()->displayFormulaInput(true); } +bool StoreController::textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) { + if (textField == contentView()->formulaInputView()->textField()) { + return event == Ion::Events::OK || event == Ion::Events::EXE; + } + return EditableCellTableViewController::textFieldShouldFinishEditing(textField, event); +} + +bool StoreController::textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) { + if (textField == contentView()->formulaInputView()->textField()) { + // Handle formula input + Expression * expression = Expression::parse(textField->text()); + if (expression == nullptr) { + app()->displayWarning(I18n::Message::SyntaxError); + return false; + } + contentView()->displayFormulaInput(false); + if (fillColumnWithFormula(expression)) { + app()->setFirstResponder(contentView()); + } + delete expression; + return true; + } + AppsContainer * appsContainer = ((TextFieldDelegateApp *)app())->container(); + Context * globalContext = appsContainer->globalContext(); + double floatBody = Expression::approximateToScalar(text, *globalContext); + if (std::isnan(floatBody) || std::isinf(floatBody)) { + app()->displayWarning(I18n::Message::UndefinedValue); + return false; + } + if (!setDataAtLocation(floatBody, selectedColumn(), selectedRow())) { + app()->displayWarning(I18n::Message::ForbiddenValue); + return false; + } + // FIXME Find out if redrawing errors can be suppressed without always reloading all the data + selectableTableView()->reloadData(); + if (event == Ion::Events::EXE || event == Ion::Events::OK) { + selectableTableView()->selectCellAtLocation(selectedColumn(), selectedRow()+1); + } else { + selectableTableView()->handleEvent(event); + } + return true; +} + +bool StoreController::textFieldDidAbortEditing(TextField * textField) { + if (textField == contentView()->formulaInputView()->textField()) { + contentView()->displayFormulaInput(false); + app()->setFirstResponder(contentView()); + return true; + } + return EditableCellTableViewController::textFieldDidAbortEditing(textField); +} + + int StoreController::numberOfColumns() { - return 2; -}; + return DoublePairStore::k_numberOfColumnsPerSeries * DoublePairStore::k_numberOfSeries; +} KDCoordinate StoreController::columnWidth(int i) { return k_cellWidth; @@ -39,10 +134,10 @@ int StoreController::indexFromCumulatedWidth(KDCoordinate offsetX) { HighlightCell * StoreController::reusableCell(int index, int type) { assert(index >= 0); switch (type) { - case 0: + case k_titleCellType: assert(index < k_numberOfTitleCells); return titleCells(index); - case 1: + case k_editableCellType: assert(index < k_maxNumberOfEditableCells); return m_editableCells[index]; default: @@ -52,55 +147,84 @@ HighlightCell * StoreController::reusableCell(int index, int type) { } int StoreController::reusableCellCount(int type) { - if (type == 0) { - return k_numberOfTitleCells; - } - return k_maxNumberOfEditableCells; + return type == k_titleCellType ? k_numberOfTitleCells : k_maxNumberOfEditableCells; } int StoreController::typeAtLocation(int i, int j) { - return j!=0; + return j == 0 ? k_titleCellType : k_editableCellType; } void StoreController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { + // Handle the separator + if (cellAtLocationIsEditable(i, j)) { + bool shouldHaveLeftSeparator = i % DoublePairStore::k_numberOfColumnsPerSeries == 0; + static_cast(cell)->setSeparatorLeft(shouldHaveLeftSeparator); + } + // Handle empty cells + if (j > 0 && j > m_store->numberOfPairsOfSeries(seriesAtColumn(i)) && j < numberOfRows()) { + StoreCell * myCell = static_cast(cell); + myCell->editableTextCell()->textField()->setText(""); + if (cellShouldBeTransparent(i,j)) { + myCell->setHide(true); + } else { + myCell->setEven(j%2 == 0); + myCell->setHide(false); + } + return; + } + if (cellAtLocationIsEditable(i, j)) { + static_cast(cell)->setHide(false); + } willDisplayCellAtLocationWithDisplayMode(cell, i, j, PrintFloat::Mode::Decimal); } -void StoreController::didBecomeFirstResponder() { - if (selectedRow() < 0) { - selectCellAtLocation(0, 0); - } - EditableCellTableViewController::didBecomeFirstResponder(); +const char * StoreController::title() { + return I18n::translate(I18n::Message::DataTab); } bool StoreController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::Up) { + if (event == Ion::Events::Up && !static_cast(view())->formulaInputView()->textField()->isEditing()) { selectableTableView()->deselectTable(); assert(selectedRow() == -1); app()->setFirstResponder(tabController()); return true; } + assert(selectedColumn() >= 0 && selectedColumn() < numberOfColumns()); + int series = seriesAtColumn(selectedColumn()); if ((event == Ion::Events::OK || event == Ion::Events::EXE) && selectedRow() == 0) { - m_storeParameterController.selectXColumn(selectedColumn() == 0); + m_storeParameterController.selectXColumn(selectedColumn()%DoublePairStore::k_numberOfColumnsPerSeries == 0); + m_storeParameterController.selectSeries(series); StackViewController * stack = ((StackViewController *)parentResponder()->parentResponder()); stack->push(&m_storeParameterController); return true; } if (event == Ion::Events::Backspace) { - if (selectedRow() == 0 || selectedRow() == numberOfRows()-1) { + if (selectedRow() == 0 || selectedRow() > m_store->numberOfPairsOfSeries(selectedColumn()/DoublePairStore::k_numberOfColumnsPerSeries)) { return false; } - m_store->deletePairAtIndex(selectedRow()-1); + m_store->deletePairOfSeriesAtIndex(series, selectedRow()-1); selectableTableView()->reloadData(); return true; } return false; } +void StoreController::didBecomeFirstResponder() { + if (selectedRow() < 0 || selectedColumn() < 0) { + selectCellAtLocation(0, 0); + } + EditableCellTableViewController::didBecomeFirstResponder(); + app()->setFirstResponder(contentView()); +} + Responder * StoreController::tabController() const { return (parentResponder()->parentResponder()->parentResponder()); } +SelectableTableView * StoreController::selectableTableView() { + return contentView()->dataView(); +} + bool StoreController::cellAtLocationIsEditable(int columnIndex, int rowIndex) { if (rowIndex > 0) { return true; @@ -109,28 +233,32 @@ bool StoreController::cellAtLocationIsEditable(int columnIndex, int rowIndex) { } bool StoreController::setDataAtLocation(double floatBody, int columnIndex, int rowIndex) { - m_store->set(floatBody, columnIndex, rowIndex-1); + m_store->set(floatBody, seriesAtColumn(columnIndex), columnIndex%DoublePairStore::k_numberOfColumnsPerSeries, rowIndex-1); return true; } double StoreController::dataAtLocation(int columnIndex, int rowIndex) { - return m_store->get(columnIndex, rowIndex-1); + return m_store->get(seriesAtColumn(columnIndex), columnIndex%DoublePairStore::k_numberOfColumnsPerSeries, rowIndex-1); } int StoreController::numberOfElements() { - return m_store->numberOfPairs(); + int result = 0; + for (int i = 0; i < DoublePairStore::k_numberOfSeries; i++) { + result = max(result, m_store->numberOfPairsOfSeries(i)); + } + return result; } int StoreController::maxNumberOfElements() const { - return FloatPairStore::k_maxNumberOfPairs; + return DoublePairStore::k_maxNumberOfPairs; } View * StoreController::loadView() { - SelectableTableView * tableView = (SelectableTableView*)EditableCellTableViewController::loadView(); + ContentView * contentView = new ContentView(m_store, this, this, this, this); for (int i = 0; i < k_maxNumberOfEditableCells; i++) { - m_editableCells[i] = new EvenOddEditableTextCell(tableView, this, m_draftTextBuffer); + m_editableCells[i] = new StoreCell(contentView->dataView(), this, m_draftTextBuffer); } - return tableView; + return contentView; } void StoreController::unloadView(View * view) { @@ -138,7 +266,59 @@ void StoreController::unloadView(View * view) { delete m_editableCells[i]; m_editableCells[i] = nullptr; } - EditableCellTableViewController::unloadView(view); + delete view; +} + +bool StoreController::privateFillColumnWithFormula(Expression * formula, Expression::isVariableTest isVariable) { + int currentColumn = selectedColumn(); + // Fetch the series used in the formula to compute the size of the filled in series + char variables[7] = {0, 0, 0, 0, 0, 0, 0}; + formula->getVariables(isVariable, variables); + int numberOfValuesToCompute = -1; + int index = 0; + while (variables[index] != 0) { + const char * seriesName = Symbol::textForSpecialSymbols(variables[index]); + assert(strlen(seriesName) == 2); + int series = (int)(seriesName[1] - '0') - 1; + assert(series >= 0 && series < DoublePairStore::k_numberOfSeries); + if (numberOfValuesToCompute == -1) { + numberOfValuesToCompute = m_store->numberOfPairsOfSeries(series); + } else { + numberOfValuesToCompute = min(numberOfValuesToCompute, m_store->numberOfPairsOfSeries(series)); + } + index++; + } + if (numberOfValuesToCompute == -1) { + numberOfValuesToCompute = m_store->numberOfPairsOfSeries(selectedColumn()/DoublePairStore::k_numberOfColumnsPerSeries); + } + + StoreContext * store = storeContext(); + + // Make sure no value is undef, else display an error + for (int j = 0; j < numberOfValuesToCompute; j++) { + // Set the context + store->setSeriesPairIndex(j); + // Compute the new value using the formula + double evaluation = formula->approximateToScalar(*store); + if (std::isnan(evaluation) || std::isinf(evaluation)) { + app()->displayWarning(I18n::Message::DataNotSuitable); + return false; + } + } + + // Fill in the table with the formula values + for (int j = 0; j < numberOfValuesToCompute; j++) { + store->setSeriesPairIndex(j); + double evaluation = formula->approximateToScalar(*store); + setDataAtLocation(evaluation, currentColumn, j + 1); + } + selectableTableView()->reloadData(); + return true; +} + +bool StoreController::cellShouldBeTransparent(int i, int j) { + int seriesIndex = i/DoublePairStore::k_numberOfColumnsPerSeries; + return j > 1 + m_store->numberOfPairsOfSeries(seriesIndex); } } diff --git a/apps/shared/store_controller.h b/apps/shared/store_controller.h index 915dad585..368abd7a2 100644 --- a/apps/shared/store_controller.h +++ b/apps/shared/store_controller.h @@ -2,16 +2,31 @@ #define SHARED_STORE_CONTROLLER_H #include -#include "float_pair_store.h" -#include "store_parameter_controller.h" +#include "buffer_text_view_with_text_field.h" #include "editable_cell_table_view_controller.h" +#include "double_pair_store.h" +#include "store_cell.h" +#include "store_context.h" +#include "store_parameter_controller.h" +#include "store_selectable_table_view.h" namespace Shared { class StoreController : public EditableCellTableViewController, public ButtonRowDelegate { public: - StoreController(Responder * parentResponder, FloatPairStore * store, ButtonRowController * header); - const char * title() override; + StoreController(Responder * parentResponder, DoublePairStore * store, ButtonRowController * header); + + virtual StoreContext * storeContext() = 0; + void displayFormulaInput(); + virtual void setFormulaLabel() = 0; + virtual bool fillColumnWithFormula(Poincare::Expression * formula) = 0; + + // TextFieldDelegate + bool textFieldShouldFinishEditing(TextField * textField, Ion::Events::Event event) override; + bool textFieldDidFinishEditing(TextField * textField, const char * text, Ion::Events::Event event) override; + bool textFieldDidAbortEditing(TextField * textField) override; + + // TableViewDataSource int numberOfColumns() override; KDCoordinate columnWidth(int i) override; KDCoordinate cumulatedWidthFromIndex(int i) override; @@ -20,13 +35,46 @@ public: int reusableCellCount(int type) override; int typeAtLocation(int i, int j) override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; + + // ViewController + const char * title() override; + + // Responder bool handleEvent(Ion::Events::Event event) override; void didBecomeFirstResponder() override; + protected: - static constexpr KDCoordinate k_cellWidth = Ion::Display::Width/2 - Metric::CommonRightMargin/2 - Metric::CommonLeftMargin/2; - constexpr static int k_maxNumberOfEditableCells = 22; - constexpr static int k_numberOfTitleCells = 2; + static constexpr KDCoordinate k_cellWidth = 116; + static constexpr KDCoordinate k_margin = 8; + static constexpr KDCoordinate k_scrollBarMargin = Metric::CommonRightMargin; + constexpr static int k_maxNumberOfEditableCells = 22 * DoublePairStore::k_numberOfSeries; + constexpr static int k_numberOfTitleCells = 4; + static constexpr int k_titleCellType = 0; + static constexpr int k_editableCellType = 1; + + class ContentView : public View , public Responder { + public: + ContentView(DoublePairStore * store, Responder * parentResponder, TableViewDataSource * dataSource, SelectableTableViewDataSource * selectionDataSource, TextFieldDelegate * textFieldDelegate); + StoreSelectableTableView * dataView() { return &m_dataView; } + BufferTextViewWithTextField * formulaInputView() { return &m_formulaInputView; } + void displayFormulaInput(bool display); + // Responder + void didBecomeFirstResponder() override; + private: + static constexpr KDCoordinate k_margin = 8; + static constexpr KDCoordinate k_scrollBarMargin = Metric::CommonRightMargin; + static constexpr KDCoordinate k_formulaInputHeight = 31; + int numberOfSubviews() const override { return 1 + m_displayFormulaInputView; } + View * subviewAtIndex(int index) override; + void layoutSubviews() override; + KDRect formulaFrame() const; + StoreSelectableTableView m_dataView; + BufferTextViewWithTextField m_formulaInputView; + bool m_displayFormulaInputView; + }; + Responder * tabController() const override; + SelectableTableView * selectableTableView() override; View * loadView() override; void unloadView(View * view) override; bool cellAtLocationIsEditable(int columnIndex, int rowIndex) override; @@ -36,9 +84,14 @@ protected: int maxNumberOfElements() const override; virtual HighlightCell * titleCells(int index) = 0; char m_draftTextBuffer[TextField::maxBufferSize()]; - EvenOddEditableTextCell * m_editableCells[k_maxNumberOfEditableCells]; - FloatPairStore * m_store; + int seriesAtColumn(int column) const { return column / DoublePairStore::k_numberOfColumnsPerSeries; } + bool privateFillColumnWithFormula(Poincare::Expression * formula, Poincare::Expression::isVariableTest isVariable); + StoreCell * m_editableCells[k_maxNumberOfEditableCells]; + DoublePairStore * m_store; StoreParameterController m_storeParameterController; +private: + bool cellShouldBeTransparent(int i, int j); + ContentView * contentView() { return static_cast(view()); } }; } diff --git a/apps/shared/store_parameter_controller.cpp b/apps/shared/store_parameter_controller.cpp index dec09aa1b..49ffb86ce 100644 --- a/apps/shared/store_parameter_controller.cpp +++ b/apps/shared/store_parameter_controller.cpp @@ -1,33 +1,29 @@ #include "store_parameter_controller.h" +#include "store_controller.h" #include namespace Shared { -StoreParameterController::StoreParameterController(Responder * parentResponder, FloatPairStore * store) : +StoreParameterController::StoreParameterController(Responder * parentResponder, DoublePairStore * store, StoreController * storeController) : ViewController(parentResponder), m_deleteColumn(I18n::Message::ClearColumn), + m_fillWithFormula(I18n::Message::FillWithFormula), #if COPY_IMPORT_LIST m_copyColumn(I18n::Message::CopyColumnInList), m_importList(I18n::Message::ImportList), #endif m_selectableTableView(this, this, this), m_store(store), - m_xColumnSelected(true) + m_storeController(storeController), + m_xColumnSelected(true), + m_series(0) { } -void StoreParameterController::selectXColumn(bool xColumnSelected) { - m_xColumnSelected = xColumnSelected; -} - const char * StoreParameterController::title() { return I18n::translate(I18n::Message::ColumnOptions); } -View * StoreParameterController::view() { - return &m_selectableTableView; -} - void StoreParameterController::didBecomeFirstResponder() { selectCellAtLocation(0, 0); app()->setFirstResponder(&m_selectableTableView); @@ -39,20 +35,28 @@ bool StoreParameterController::handleEvent(Ion::Events::Event event) { case 0: { if (m_xColumnSelected) { - m_store->deleteAllPairs(); + m_store->deleteAllPairsOfSeries(m_series); } else { - m_store->resetColumn(!m_xColumnSelected); + m_store->resetColumn(m_series, !m_xColumnSelected); } StackViewController * stack = ((StackViewController *)parentResponder()); stack->pop(); return true; } + case 1: + { + m_storeController->displayFormulaInput(); + StackViewController * stack = ((StackViewController *)parentResponder()); + stack->pop(); + return true; + } + #if COPY_IMPORT_LIST /* TODO: implement copy column and import list */ - case 1: - return true; case 2: return true; + case 3: + return true; #endif default: assert(false); @@ -62,23 +66,11 @@ bool StoreParameterController::handleEvent(Ion::Events::Event event) { return false; } -int StoreParameterController::numberOfRows() { - return k_totalNumberOfCell; -}; - HighlightCell * StoreParameterController::reusableCell(int index) { assert(index >= 0); assert(index < k_totalNumberOfCell); - HighlightCell * cells[] = {&m_deleteColumn};// {&m_deleteColumn, &m_copyColumn, &m_importList}; + HighlightCell * cells[] = {&m_deleteColumn, &m_fillWithFormula};// {&m_deleteColumn, &m_fillWithFormula, &m_copyColumn, &m_importList}; return cells[index]; } -int StoreParameterController::reusableCellCount() { - return k_totalNumberOfCell; -} - -KDCoordinate StoreParameterController::cellHeight() { - return Metric::ParameterCellHeight; -} - } diff --git a/apps/shared/store_parameter_controller.h b/apps/shared/store_parameter_controller.h index e8e620b63..be713c2ef 100644 --- a/apps/shared/store_parameter_controller.h +++ b/apps/shared/store_parameter_controller.h @@ -2,35 +2,41 @@ #define SHARED_STORE_PARAM_CONTROLLER_H #include -#include "float_pair_store.h" +#include "double_pair_store.h" #include "../i18n.h" namespace Shared { +class StoreController; + class StoreParameterController : public ViewController, public SimpleListViewDataSource, public SelectableTableViewDataSource { public: - StoreParameterController(Responder * parentResponder, FloatPairStore * store); - void selectXColumn(bool xColumnSelected); - View * view() override; + StoreParameterController(Responder * parentResponder, DoublePairStore * store, StoreController * storeController); + void selectXColumn(bool xColumnSelected) { m_xColumnSelected = xColumnSelected; } + void selectSeries(int series) { m_series = series; } + View * view() override { return &m_selectableTableView; } const char * title() override; bool handleEvent(Ion::Events::Event event) override; void didBecomeFirstResponder() override; - int numberOfRows() override; - KDCoordinate cellHeight() override; + int numberOfRows() override { return k_totalNumberOfCell; } + KDCoordinate cellHeight() override { return Metric::ParameterCellHeight; } HighlightCell * reusableCell(int index) override; - int reusableCellCount() override; + int reusableCellCount() override { return k_totalNumberOfCell; } private: #if COPY_IMPORT_LIST - constexpr static int k_totalNumberOfCell = 3; + constexpr static int k_totalNumberOfCell = 4; MessageTableCellWithChevron m_copyColumn; MessageTableCellWithChevron m_importList; #else - constexpr static int k_totalNumberOfCell = 1; + constexpr static int k_totalNumberOfCell = 2; #endif MessageTableCell m_deleteColumn; + MessageTableCell m_fillWithFormula; SelectableTableView m_selectableTableView; - FloatPairStore * m_store; + DoublePairStore * m_store; + StoreController * m_storeController; bool m_xColumnSelected; + int m_series; }; } diff --git a/apps/shared/store_selectable_table_view.cpp b/apps/shared/store_selectable_table_view.cpp new file mode 100644 index 000000000..d7826f164 --- /dev/null +++ b/apps/shared/store_selectable_table_view.cpp @@ -0,0 +1,42 @@ +#include "store_selectable_table_view.h" + +namespace Shared { + +StoreSelectableTableView::StoreSelectableTableView(DoublePairStore * store, Responder * parentResponder, TableViewDataSource * dataSource, SelectableTableViewDataSource * selectionDataSource, SelectableTableViewDelegate * delegate) : + SelectableTableView(parentResponder, dataSource, selectionDataSource, delegate), + m_store(store) +{ +} + +bool StoreSelectableTableView::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Down) { + return selecNonHiddenCellAtLocation(selectedColumn(), selectedRow()+1); + } + if (event == Ion::Events::Up) { + return selecNonHiddenCellAtLocation(selectedColumn(), selectedRow()-1); + } + if (event == Ion::Events::Left) { + return selecNonHiddenCellAtLocation(selectedColumn()-1, selectedRow()); + } + if (event == Ion::Events::Right) { + return selecNonHiddenCellAtLocation(selectedColumn()+1, selectedRow()); + } + return false; +} + +bool StoreSelectableTableView::selecNonHiddenCellAtLocation(int i, int j) { + if (i < 0 || i >= dataSource()->numberOfColumns()) { + return false; + } + if (j < 0 || j >= dataSource()->numberOfRows()) { + return false; + } + int seriesIndex = i/DoublePairStore::k_numberOfColumnsPerSeries; + int numberOfPairsOfCurrentSeries = m_store->numberOfPairsOfSeries(seriesIndex); + if (j > 1 + numberOfPairsOfCurrentSeries) { + return selectCellAtLocation(i, 1 + numberOfPairsOfCurrentSeries); + } + return selectCellAtLocation(i, j); +} + +} diff --git a/apps/shared/store_selectable_table_view.h b/apps/shared/store_selectable_table_view.h new file mode 100644 index 000000000..33a4d3042 --- /dev/null +++ b/apps/shared/store_selectable_table_view.h @@ -0,0 +1,20 @@ +#ifndef APPS_SHARED_STORE_SELECTABLE_TABLE_VIEW_H +#define APPS_SHARED_STORE_SELECTABLE_TABLE_VIEW_H + +#include +#include "double_pair_store.h" + +namespace Shared { + +class StoreSelectableTableView : public SelectableTableView { +public: + StoreSelectableTableView(DoublePairStore * store, Responder * parentResponder, TableViewDataSource * dataSource, SelectableTableViewDataSource * selectionDataSource = nullptr, SelectableTableViewDelegate * delegate = nullptr); + bool handleEvent(Ion::Events::Event event) override; +private: + bool selecNonHiddenCellAtLocation(int i, int j); + DoublePairStore * m_store; +}; + +} + +#endif diff --git a/apps/shared/store_title_cell.cpp b/apps/shared/store_title_cell.cpp new file mode 100644 index 000000000..8c341cfb4 --- /dev/null +++ b/apps/shared/store_title_cell.cpp @@ -0,0 +1,30 @@ +#include "store_title_cell.h" +#include "hideable_even_odd_editable_text_cell.h" +#include "escher/metric.h" + +namespace Shared { + +void StoreTitleCell::setSeparatorLeft(bool separator) { + if (m_separatorLeft != separator) { + m_separatorLeft = separator; + reloadCell(); + } +} + +void StoreTitleCell::drawRect(KDContext * ctx, KDRect rect) const { + BufferFunctionTitleCell::drawRect(ctx, rect); + // Draw the separator + KDRect separatorRect(0, m_separatorLeft ? 0 : k_colorIndicatorThickness, Metric::TableSeparatorThickness, bounds().height() - (m_separatorLeft ? 0 : k_colorIndicatorThickness)); + if (m_separatorLeft) { + ctx->fillRect(separatorRect, HideableEvenOddEditableTextCell::hideColor()); + } else { + ctx->fillRect(separatorRect, backgroundColor()); + } +} + +void StoreTitleCell::layoutSubviews() { + KDRect textFrame = bufferTextViewFrame(); + bufferTextView()->setFrame(KDRect(textFrame.left() + Metric::TableSeparatorThickness, textFrame.top(), textFrame.width() - Metric::TableSeparatorThickness, textFrame.height())); +} + +} diff --git a/apps/shared/store_title_cell.h b/apps/shared/store_title_cell.h new file mode 100644 index 000000000..51e3952bf --- /dev/null +++ b/apps/shared/store_title_cell.h @@ -0,0 +1,23 @@ +#ifndef SHARED_STORE_TITLE_CELL_H +#define SHARED_STORE_TITLE_CELL_H + +#include "buffer_function_title_cell.h" + +namespace Shared { + +class StoreTitleCell : public BufferFunctionTitleCell { +public: + StoreTitleCell() : + BufferFunctionTitleCell(Orientation::HorizontalIndicator, KDText::FontSize::Small), + m_separatorLeft(false) + {} + void setSeparatorLeft(bool separator); + void drawRect(KDContext * ctx, KDRect rect) const override; + void layoutSubviews() override; +private: + bool m_separatorLeft; +}; + +} + +#endif diff --git a/apps/shared/tab_table_controller.cpp b/apps/shared/tab_table_controller.cpp index 4f598f430..824018371 100644 --- a/apps/shared/tab_table_controller.cpp +++ b/apps/shared/tab_table_controller.cpp @@ -25,7 +25,7 @@ void TabTableController::willExitResponderChain(Responder * nextFirstResponder) } SelectableTableView * TabTableController::selectableTableView() { - return (SelectableTableView *)view(); + return static_cast(view()); } View * TabTableController::loadView() { diff --git a/apps/shared/tab_table_controller.h b/apps/shared/tab_table_controller.h index 935dc0679..f00dcd22a 100644 --- a/apps/shared/tab_table_controller.h +++ b/apps/shared/tab_table_controller.h @@ -15,7 +15,7 @@ public: void viewWillAppear() override; void willExitResponderChain(Responder * nextFirstResponder) override; protected: - SelectableTableView * selectableTableView(); + virtual SelectableTableView * selectableTableView(); virtual View * loadView() override; void unloadView(View * view) override; virtual Responder * tabController() const = 0; diff --git a/apps/solver/equation_store.cpp b/apps/solver/equation_store.cpp index 5b54d68c6..1b31e9af3 100644 --- a/apps/solver/equation_store.cpp +++ b/apps/solver/equation_store.cpp @@ -97,7 +97,7 @@ EquationStore::Error EquationStore::exactSolve(Poincare::Context * context) { if (definedModelAtIndex(i)->standardForm(context) == nullptr) { return Error::EquationUndefined; } - numberOfVariables = definedModelAtIndex(i)->standardForm(context)->getVariables(m_variables); + numberOfVariables = definedModelAtIndex(i)->standardForm(context)->getVariables(Symbol::isVariableSymbol, m_variables); if (numberOfVariables < 0) { return Error::TooManyVariables; } diff --git a/apps/statistics/Makefile b/apps/statistics/Makefile index 7372c2ef0..0caf06634 100644 --- a/apps/statistics/Makefile +++ b/apps/statistics/Makefile @@ -3,15 +3,22 @@ snapshot_headers += apps/statistics/app.h app_objs += $(addprefix apps/statistics/,\ app.o\ + box_axis_view.o\ box_banner_view.o\ box_controller.o\ box_range.o\ box_view.o\ calculation_controller.o\ + calculation_selectable_table_view.o\ histogram_banner_view.o\ histogram_controller.o\ histogram_parameter_controller.o\ histogram_view.o\ + multiple_boxes_view.o\ + multiple_data_view.o\ + multiple_data_view_controller.o\ + multiple_histograms_view.o\ + statistics_context.o\ store.o\ store_controller.o\ ) diff --git a/apps/statistics/app.cpp b/apps/statistics/app.cpp index e437ae87b..fd396fdaa 100644 --- a/apps/statistics/app.cpp +++ b/apps/statistics/app.cpp @@ -23,7 +23,9 @@ App::Snapshot::Snapshot() : m_storeVersion(0), m_barVersion(0), m_rangeVersion(0), + m_selectedHistogramSeriesIndex(-1), m_selectedHistogramBarIndex(0), + m_selectedBoxSeriesIndex(-1), m_selectedBoxQuantile(BoxView::Quantile::Min) { } @@ -47,39 +49,15 @@ App::Descriptor * App::Snapshot::descriptor() { return &descriptor; } -Store * App::Snapshot::store() { - return &m_store; -} - -uint32_t * App::Snapshot::storeVersion() { - return &m_storeVersion; -} - -uint32_t * App::Snapshot::barVersion() { - return &m_barVersion; -} - -uint32_t * App::Snapshot::rangeVersion() { - return &m_rangeVersion; -} - -int * App::Snapshot::selectedHistogramBarIndex() { - return &m_selectedHistogramBarIndex; -} - -BoxView::Quantile * App::Snapshot::selectedBoxQuantile() { - return &m_selectedBoxQuantile; -} - App::App(Container * container, Snapshot * snapshot) : TextFieldDelegateApp(container, snapshot, &m_tabViewController), m_calculationController(&m_calculationAlternateEmptyViewController, &m_calculationHeader, snapshot->store()), m_calculationAlternateEmptyViewController(&m_calculationHeader, &m_calculationController, &m_calculationController), m_calculationHeader(&m_tabViewController, &m_calculationAlternateEmptyViewController, &m_calculationController), - m_boxController(&m_boxAlternateEmptyViewController, &m_boxHeader, snapshot->store(), snapshot->selectedBoxQuantile()), + m_boxController(&m_boxAlternateEmptyViewController, &m_boxHeader, snapshot->store(), snapshot->selectedBoxQuantile(), snapshot->selectedBoxSeriesIndex()), m_boxAlternateEmptyViewController(&m_boxHeader, &m_boxController, &m_boxController), m_boxHeader(&m_tabViewController, &m_boxAlternateEmptyViewController, &m_boxController), - m_histogramController(&m_histogramAlternateEmptyViewController, &m_histogramHeader, snapshot->store(), snapshot->storeVersion(), snapshot->barVersion(), snapshot->rangeVersion(), snapshot->selectedHistogramBarIndex()), + m_histogramController(&m_histogramAlternateEmptyViewController, &m_histogramHeader, snapshot->store(), snapshot->storeVersion(), snapshot->barVersion(), snapshot->rangeVersion(), snapshot->selectedHistogramBarIndex(), snapshot->selectedHistogramSeriesIndex()), m_histogramAlternateEmptyViewController(&m_histogramHeader, &m_histogramController, &m_histogramController), m_histogramHeader(&m_histogramStackViewController, &m_histogramAlternateEmptyViewController, &m_histogramController), m_histogramStackViewController(&m_tabViewController, &m_histogramHeader), diff --git a/apps/statistics/app.h b/apps/statistics/app.h index ee6e46e02..06871fae4 100644 --- a/apps/statistics/app.h +++ b/apps/statistics/app.h @@ -4,9 +4,9 @@ #include #include "box_controller.h" #include "calculation_controller.h" +#include "histogram_controller.h" #include "store.h" #include "store_controller.h" -#include "histogram_controller.h" #include "../shared/text_field_delegate_app.h" namespace Statistics { @@ -25,18 +25,22 @@ public: App * unpack(Container * container) override; void reset() override; Descriptor * descriptor() override; - Store * store(); - uint32_t * storeVersion(); - uint32_t * barVersion(); - uint32_t * rangeVersion(); - int * selectedHistogramBarIndex(); - BoxView::Quantile * selectedBoxQuantile(); + Store * store() { return &m_store; } + uint32_t * storeVersion() { return &m_storeVersion; } + uint32_t * barVersion() { return &m_barVersion; } + uint32_t * rangeVersion() { return &m_rangeVersion; } + int * selectedHistogramSeriesIndex() { return &m_selectedHistogramSeriesIndex; } + int * selectedHistogramBarIndex() { return &m_selectedHistogramBarIndex; } + int * selectedBoxSeriesIndex() { return &m_selectedBoxSeriesIndex; } + BoxView::Quantile * selectedBoxQuantile() { return &m_selectedBoxQuantile; } private: Store m_store; uint32_t m_storeVersion; uint32_t m_barVersion; uint32_t m_rangeVersion; + int m_selectedHistogramSeriesIndex; int m_selectedHistogramBarIndex; + int m_selectedBoxSeriesIndex; BoxView::Quantile m_selectedBoxQuantile; }; private: diff --git a/apps/statistics/base.de.i18n b/apps/statistics/base.de.i18n index a91532061..0731168e3 100644 --- a/apps/statistics/base.de.i18n +++ b/apps/statistics/base.de.i18n @@ -2,11 +2,13 @@ StatsApp = "Statistiken" StatsAppCapital = "STATISTIKEN" HistogramTab = "Histogramm" BoxTab = "Boxplot" -StatTab = "Stats" -Values = "Werte" -Sizes = "Haufigkeiten" +Values1 = "Werte V1" +Values2 = "Werte V2" +Values3 = "Werte V3" +Sizes1 = "Haufigkeiten N1" +Sizes2 = "Haufigkeiten N2" +Sizes3 = "Haufigkeiten N3" ImportList = "Laden eine Liste" -NoDataToPlot = "Keine Daten zu zeichnen" Interval = " Intervall" Size = " Haufigkeit" Frequency = "Relative" @@ -16,13 +18,8 @@ BarStart = "Beginn der Serie" FirstQuartile = "Unteres Quartil" Median = "Median" ThirdQuartile = "Oberes Quartil" -NoValueToCompute = "Keine Grosse zu berechnen" TotalSize = "Anzahl der Elemente" Range = "Spannweite" -Mean = "Mittelwert" -StandardDeviation = "Standardabweichung" StandardDeviationSigma = "Standardabweichung σ" SampleStandardDeviationS = "Standardabweichung s" -Deviation = "Varianz" InterquartileRange = "Interquartilsabstand" -SquareSum = "Quadratsumme" diff --git a/apps/statistics/base.en.i18n b/apps/statistics/base.en.i18n index 63e7f7496..61091e835 100644 --- a/apps/statistics/base.en.i18n +++ b/apps/statistics/base.en.i18n @@ -2,11 +2,13 @@ StatsApp = "Statistics" StatsAppCapital = "STATISTICS" HistogramTab = "Histogram" BoxTab = "Box" -StatTab = "Stats" -Values = "Values" -Sizes = "Sizes" +Values1 = "Values V1" +Values2 = "Values V2" +Values3 = "Values V3" +Sizes1 = "Sizes N1" +Sizes2 = "Sizes N2" +Sizes3 = "Sizes N3" ImportList = "Import from a list" -NoDataToPlot = "No data to draw" Interval = " Interval " Size = " Size" Frequency = "Frequency" @@ -16,13 +18,8 @@ BarStart = "X start" FirstQuartile = "First quartile" Median = "Median" ThirdQuartile = "Third quartile" -NoValueToCompute = "No values to calculate" TotalSize = "Total size" Range = "Range" -Mean = "Mean" -StandardDeviation = "Standard deviation" StandardDeviationSigma = "Standard deviation σ" SampleStandardDeviationS = "Sample std deviation s" -Deviation = "Variance" InterquartileRange = "Interquartile range" -SquareSum = "Sum of squares" diff --git a/apps/statistics/base.es.i18n b/apps/statistics/base.es.i18n index 62d33c1e7..7a31fe73b 100644 --- a/apps/statistics/base.es.i18n +++ b/apps/statistics/base.es.i18n @@ -2,11 +2,13 @@ StatsApp = "Estadistica" StatsAppCapital = "ESTADISTICA" HistogramTab = "Histograma" BoxTab = "Caja" -StatTab = "Medidas" -Values = "Valores" -Sizes = "Frecuencias" +Values1 = "Valores V1" +Values2 = "Valores V2" +Values3 = "Valores V3" +Sizes1 = "Frecuencias N1" +Sizes2 = "Frecuencias N2" +Sizes3 = "Frecuencias N3" ImportList = "Importar una lista" -NoDataToPlot = "Ningunos datos que dibujar" Interval = " Intervalo" Size = " Frecuencia" Frequency = "Relativa" @@ -16,13 +18,8 @@ BarStart = "Principio de la serie" FirstQuartile = "Primer cuartil" Median = "Mediana" ThirdQuartile = "Tercer cuartil" -NoValueToCompute = "Ninguna medida que calcular" TotalSize = "Poblacion" Range = "Rango" -Mean = "Media" -StandardDeviation = "Desviacion tipica" StandardDeviationSigma = "Desviacion tipica σ" -SampleStandardDeviationS = "Desviacion tipica muestral s" -Deviation = "Varianza" +SampleStandardDeviationS = "Desviacion tipica s" InterquartileRange = "Rango intercuartilo" -SquareSum = "Suma cuadrados" diff --git a/apps/statistics/base.fr.i18n b/apps/statistics/base.fr.i18n index 3a03235ea..1f4a6ee5d 100644 --- a/apps/statistics/base.fr.i18n +++ b/apps/statistics/base.fr.i18n @@ -2,11 +2,13 @@ StatsApp = "Statistiques" StatsAppCapital = "STATISTIQUES" HistogramTab = "Histogramme" BoxTab = "Boite" -StatTab = "Stats" -Values = "Valeurs" -Sizes = "Effectifs" +Values1 = "Valeurs V1" +Values2 = "Valeurs V2" +Values3 = "Valeurs V3" +Sizes1 = "Effectifs N1" +Sizes2 = "Effectifs N2" +Sizes3 = "Effectifs N3" ImportList = "Importer une liste" -NoDataToPlot = "Aucune donnee a tracer" Interval = " Intervalle " Size = " Effectif" Frequency = "Frequence" @@ -16,13 +18,8 @@ BarStart = "Debut de la serie" FirstQuartile = "Premier quartile" Median = "Mediane" ThirdQuartile = "Troisieme quartile" -NoValueToCompute = "Aucune grandeur a calculer" TotalSize = "Effectif total" Range = "Etendue" -Mean = "Moyenne" -StandardDeviation = "Ecart type" StandardDeviationSigma = "Ecart type" SampleStandardDeviationS = "Ecart type echantillon" -Deviation = "Variance" InterquartileRange = "Ecart interquartile" -SquareSum = "Somme des carres" diff --git a/apps/statistics/base.pt.i18n b/apps/statistics/base.pt.i18n index 7e3dd4ea1..c8b93fa37 100644 --- a/apps/statistics/base.pt.i18n +++ b/apps/statistics/base.pt.i18n @@ -2,11 +2,13 @@ StatsApp = "Estatistica" StatsAppCapital = "ESTATISTICA" HistogramTab = "Histograma" BoxTab = "Caixa" -StatTab = "Estat" -Values = "Valores" -Sizes = "Frequencias" +Values1 = "Valores V1" +Values2 = "Valores V2" +Values3 = "Valores V3" +Sizes1 = "Frequencias N1" +Sizes2 = "Frequencias N2" +Sizes3 = "Frequencias N3" ImportList = "Importar de uma lista" -NoDataToPlot = "Nao ha dados para desenhar" Interval = " Intervalo" Size = " Frequencia" Frequency = "Relativa" @@ -16,13 +18,8 @@ BarStart = "Inicio da serie" FirstQuartile = "Quartil inferior" Median = "Mediana" ThirdQuartile = "Quartil superior" -NoValueToCompute = "Nenhuma quantidade para calcular" TotalSize = "Numero de itens" Range = "Amplitude" -Mean = "Media" -StandardDeviation = "Desvio padrao" StandardDeviationSigma = "Desvio padrao σ" SampleStandardDeviationS = "Desvio padrao amostral s" -Deviation = "Variancia" InterquartileRange = "Interquartil" -SquareSum = "Soma dos quadrados" diff --git a/apps/statistics/box_axis_view.cpp b/apps/statistics/box_axis_view.cpp new file mode 100644 index 000000000..1510fc5da --- /dev/null +++ b/apps/statistics/box_axis_view.cpp @@ -0,0 +1,19 @@ +#include "box_axis_view.h" +#include + +using namespace Shared; + +namespace Statistics { + +void BoxAxisView::drawRect(KDContext * ctx, KDRect rect) const { + ctx->fillRect(rect, KDColorWhite); + KDRect lineRect = KDRect(0, k_axisMargin, bounds().width(), 1); + ctx->fillRect(lineRect, KDColorBlack); + drawLabels(ctx, rect, Axis::Horizontal, false, false, true, k_axisMargin); +} + +char * BoxAxisView::label(Axis axis, int index) const { + return axis == Axis::Vertical ? nullptr : (char *)m_labels[index]; +} + +} diff --git a/apps/statistics/box_axis_view.h b/apps/statistics/box_axis_view.h new file mode 100644 index 000000000..c913e7107 --- /dev/null +++ b/apps/statistics/box_axis_view.h @@ -0,0 +1,29 @@ +#ifndef STATISTICS_BOX_AXIS_VIEW_H +#define STATISTICS_BOX_AXIS_VIEW_H + +#include "box_range.h" +#include "store.h" +#include "../shared/curve_view.h" +#include "../constant.h" + +namespace Statistics { + +class BoxAxisView : public Shared::CurveView { +public: + BoxAxisView(Store * store) : + CurveView(&m_boxRange), + m_labels{}, + m_boxRange(BoxRange(store)) + {} + void drawRect(KDContext * ctx, KDRect rect) const override; +private: + constexpr static KDCoordinate k_axisMargin = 3; + char * label(Axis axis, int index) const override; + char m_labels[k_maxNumberOfXLabels][Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(Constant::ShortNumberOfSignificantDigits)]; + BoxRange m_boxRange; +}; + +} + + +#endif diff --git a/apps/statistics/box_banner_view.cpp b/apps/statistics/box_banner_view.cpp index 67d426f40..6d3b87741 100644 --- a/apps/statistics/box_banner_view.cpp +++ b/apps/statistics/box_banner_view.cpp @@ -3,25 +3,19 @@ namespace Statistics { BoxBannerView::BoxBannerView() : + m_seriesName(KDText::FontSize::Small, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), m_calculationName(KDText::FontSize::Small, I18n::Message::Minimum, 0.0f, 0.5f, KDColorBlack, Palette::GreyMiddle), m_calculationValue(KDText::FontSize::Small, 1.0f, 0.5f, KDColorBlack, Palette::GreyMiddle) { } -int BoxBannerView::numberOfSubviews() const { - return 2; -} - TextView * BoxBannerView::textViewAtIndex(int index) const { - const TextView * textViews[2] = {&m_calculationName, &m_calculationValue}; + const TextView * textViews[3] = {&m_seriesName, &m_calculationName, &m_calculationValue}; return (TextView *)textViews[index]; } MessageTextView * BoxBannerView::messageTextViewAtIndex(int index) const { - if (index == 0) { - return (MessageTextView *)&m_calculationName; - } - return nullptr; + return index == 1 ? (MessageTextView *)&m_calculationName : nullptr; } } diff --git a/apps/statistics/box_banner_view.h b/apps/statistics/box_banner_view.h index c73dbddcf..9192e8275 100644 --- a/apps/statistics/box_banner_view.h +++ b/apps/statistics/box_banner_view.h @@ -11,9 +11,10 @@ class BoxBannerView : public Shared::BannerView { public: BoxBannerView(); private: - int numberOfSubviews() const override; + int numberOfSubviews() const override { return 3; } TextView * textViewAtIndex(int i) const override; MessageTextView * messageTextViewAtIndex(int i) const override; + BufferTextView m_seriesName; MessageTextView m_calculationName; BufferTextView m_calculationValue; }; diff --git a/apps/statistics/box_controller.cpp b/apps/statistics/box_controller.cpp index 77dd73868..80129022f 100644 --- a/apps/statistics/box_controller.cpp +++ b/apps/statistics/box_controller.cpp @@ -6,86 +6,57 @@ using namespace Poincare; namespace Statistics { -BoxController::BoxController(Responder * parentResponder, ButtonRowController * header, Store * store, BoxView::Quantile * selectedQuantile) : - ViewController(parentResponder), +BoxController::BoxController(Responder * parentResponder, ButtonRowController * header, Store * store, BoxView::Quantile * selectedQuantile, int * selectedSeriesIndex) : + MultipleDataViewController(parentResponder, store, (int *)(selectedQuantile), selectedSeriesIndex), ButtonRowDelegate(header, nullptr), - m_boxBannerView(), - m_view(store, &m_boxBannerView, selectedQuantile), - m_store(store) + m_view(this, store, selectedQuantile) { } +bool BoxController::moveSelectionHorizontally(int deltaIndex) { + int selectedQuantile = (int)m_view.dataViewAtIndex(selectedSeriesIndex())->selectedQuantile(); + int nextSelectedQuantile = selectedQuantile + deltaIndex; + if (m_view.dataViewAtIndex(selectedSeriesIndex())->selectQuantile(nextSelectedQuantile)) { + reloadBannerView(); + return true; + } + return false; +} + const char * BoxController::title() { return I18n::translate(I18n::Message::BoxTab); } -View * BoxController::view() { - return &m_view; -} - -bool BoxController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::Up) { - m_view.selectMainView(false); - app()->setFirstResponder(tabController()); - return true; - } - if (event == Ion::Events::Left || event == Ion::Events::Right) { - int nextSelectedQuantile = event == Ion::Events::Left ? (int)m_view.selectedQuantile()-1 : (int)m_view.selectedQuantile()+1; - if (m_view.selectQuantile(nextSelectedQuantile)) { - reloadBannerView(); - return true; - } - return false; - } - return false; -} - -void BoxController::didBecomeFirstResponder() { - m_view.selectMainView(true); - m_view.reload(); -} - -bool BoxController::isEmpty() const { - if (m_store->sumOfColumn(1) == 0) { - return true; - } - return false; -} - -I18n::Message BoxController::emptyMessage() { - return I18n::Message::NoDataToPlot; -} - -Responder * BoxController::defaultController() { - return tabController(); -} - Responder * BoxController::tabController() const { return (parentResponder()->parentResponder()->parentResponder()); } void BoxController::reloadBannerView() { + if (selectedSeriesIndex() < 0) { + return; + } + + int selectedQuantile = (int)m_view.dataViewAtIndex(selectedSeriesIndex())->selectedQuantile(); + + // Set series name + char seriesChar = '0' + selectedSeriesIndex() + 1; + char bufferName[] = {' ', 'V', seriesChar, '/', 'N', seriesChar, 0}; + m_view.editableBannerView()->setLegendAtIndex(bufferName, 0); + + + // Set calculation name I18n::Message calculationName[5] = {I18n::Message::Minimum, I18n::Message::FirstQuartile, I18n::Message::Median, I18n::Message::ThirdQuartile, I18n::Message::Maximum}; - m_boxBannerView.setMessageAtIndex(calculationName[(int)m_view.selectedQuantile()], 0); - char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)]; + m_view.editableBannerView()->setMessageAtIndex(calculationName[selectedQuantile], 1); + + // Set calculation result + char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits) + 1]; CalculPointer calculationMethods[5] = {&Store::minValue, &Store::firstQuartile, &Store::median, &Store::thirdQuartile, &Store::maxValue}; - double calculation = (m_store->*calculationMethods[(int)m_view.selectedQuantile()])(); - PrintFloat::convertFloatToText(calculation, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); - m_boxBannerView.setLegendAtIndex(buffer, 1); -} - -void BoxController::viewWillAppear() { - m_view.selectMainView(true); - reloadBannerView(); - m_view.reload(); -} - -void BoxController::willExitResponderChain(Responder * nextFirstResponder) { - if (nextFirstResponder == tabController()) { - m_view.selectMainView(false); - m_view.reload(); - } + double calculation = (m_store->*calculationMethods[selectedQuantile])(selectedSeriesIndex()); + int numberOfChar = PrintFloat::convertFloatToText(calculation, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + buffer[numberOfChar++] = ' '; + buffer[numberOfChar] = 0; + m_view.editableBannerView()->setLegendAtIndex(buffer, 2); } } diff --git a/apps/statistics/box_controller.h b/apps/statistics/box_controller.h index 810c8a693..1a9b30084 100644 --- a/apps/statistics/box_controller.h +++ b/apps/statistics/box_controller.h @@ -3,29 +3,24 @@ #include #include "store.h" -#include "box_view.h" -#include "box_banner_view.h" +#include "multiple_boxes_view.h" +#include "multiple_data_view_controller.h" namespace Statistics { -class BoxController : public ViewController, public ButtonRowDelegate, public AlternateEmptyViewDelegate { +class BoxController : public MultipleDataViewController, public ButtonRowDelegate { public: - BoxController(Responder * parentResponder, ButtonRowController * header, Store * store, BoxView::Quantile * selectedQuantile); + BoxController(Responder * parentResponder, ButtonRowController * header, Store * store, BoxView::Quantile * selectedQuantile, int * selectedSeriesIndex); + + MultipleDataView * multipleDataView() override { return &m_view; } + bool moveSelectionHorizontally(int deltaIndex) override; + + // ViewController const char * title() override; - View * view() override; - bool handleEvent(Ion::Events::Event event) override; - void didBecomeFirstResponder() override; - bool isEmpty() const override; - I18n::Message emptyMessage() override; - Responder * defaultController() override; - void viewWillAppear() override; - void willExitResponderChain(Responder * nextFirstResponder) override; private: - Responder * tabController() const; - void reloadBannerView(); - BoxBannerView m_boxBannerView; - BoxView m_view; - Store * m_store; + Responder * tabController() const override; + void reloadBannerView() override; + MultipleBoxesView m_view; }; } diff --git a/apps/statistics/box_range.cpp b/apps/statistics/box_range.cpp index be087e243..f8a9c674d 100644 --- a/apps/statistics/box_range.cpp +++ b/apps/statistics/box_range.cpp @@ -8,27 +8,19 @@ BoxRange::BoxRange(Store * store) : } float BoxRange::xMin() { - float min = m_store->minValue(); - float max = m_store->maxValue(); + float min = m_store->minValueForAllSeries(); + float max = m_store->maxValueForAllSeries(); max = min >= max ? min + 1 : max; return min - k_displayLeftMarginRatio*(max-min); } float BoxRange::xMax() { - float min = m_store->minValue(); - float max = m_store->maxValue(); + float min = m_store->minValueForAllSeries(); + float max = m_store->maxValueForAllSeries(); max = min >= max ? min + 1 : max; return max + k_displayRightMarginRatio*(max - min); } -float BoxRange::yMin() { - return -k_displayBottomMarginRatio; -} - -float BoxRange::yMax() { - return 1.0f+k_displayTopMarginRatio; -} - float BoxRange::xGridUnit() { return computeGridUnit(Axis::X, xMin(), xMax()); } diff --git a/apps/statistics/box_range.h b/apps/statistics/box_range.h index 28116e66e..22ddcc636 100644 --- a/apps/statistics/box_range.h +++ b/apps/statistics/box_range.h @@ -11,8 +11,8 @@ public: BoxRange(Store * store); float xMin() override; float xMax() override; - float yMin() override; - float yMax() override; + float yMin() override { return -k_displayBottomMarginRatio; } + float yMax() override { return 1.0f+k_displayTopMarginRatio; } float xGridUnit() override; private: constexpr static float k_displayTopMarginRatio = 0.05f; diff --git a/apps/statistics/box_view.cpp b/apps/statistics/box_view.cpp index c1b3ab61c..0ff506527 100644 --- a/apps/statistics/box_view.cpp +++ b/apps/statistics/box_view.cpp @@ -1,4 +1,5 @@ #include "box_view.h" +#include "box_controller.h" #include #include @@ -6,79 +7,98 @@ using namespace Shared; namespace Statistics { -BoxView::BoxView(Store * store, BannerView * bannerView, Quantile * selectedQuantile) : +BoxView::BoxView(BoxController * controller, Store * store, int series, Shared::BannerView * bannerView, Quantile * selectedQuantile, KDColor color, KDColor lightColor) : CurveView(&m_boxRange, nullptr, bannerView, nullptr), m_store(store), + m_boxController(controller), m_boxRange(BoxRange(store)), - m_labels{}, - m_selectedQuantile(selectedQuantile) + m_series(series), + m_selectedQuantile(selectedQuantile), + m_selectedHistogramColor(color), + m_selectedHistogramLightColor(lightColor) { } -void BoxView::reload() { - CurveView::reload(); - CalculPointer calculationMethods[5] = {&Store::minValue, &Store::firstQuartile, &Store::median, &Store::thirdQuartile, - &Store::maxValue}; - float calculation = (m_store->*calculationMethods[(int)*m_selectedQuantile])(); - float pixelUpperBound = floatToPixel(Axis::Vertical, 0.2f)+1; - float pixelLowerBound = floatToPixel(Axis::Vertical, 0.8)-1; - float selectedValueInPixels = floatToPixel(Axis::Horizontal, calculation)-1; - KDRect dirtyZone(KDRect(selectedValueInPixels, pixelLowerBound, 4, pixelUpperBound - pixelLowerBound)); - markRectAsDirty(dirtyZone); -} - -BoxView::Quantile BoxView::selectedQuantile() { - return *m_selectedQuantile; -} - bool BoxView::selectQuantile(int selectedQuantile) { if (selectedQuantile < 0 || selectedQuantile > 4) { return false; } if ((int)*m_selectedQuantile != selectedQuantile) { - reload(); + reloadQuantile(); *m_selectedQuantile = (Quantile)selectedQuantile; - reload(); + reloadQuantile(); } return true; } +void BoxView::reloadQuantile() { + CurveView::reload(); + KDCoordinate minY = boxLowerBoundPixel(); + KDCoordinate maxY = boxUpperBoundPixel(); + CalculPointer calculationMethods[5] = {&Store::minValue, &Store::firstQuartile, &Store::median, &Store::thirdQuartile, &Store::maxValue}; + double calculation = (m_store->*calculationMethods[(int)*m_selectedQuantile])(m_series); + KDCoordinate minX = std::round(floatToPixel(Axis::Horizontal, calculation)); + KDRect dirtyRect = KDRect(minX, minY, k_quantileBarWidth, maxY - minY); + markRectAsDirty(dirtyRect); +} + +void BoxView::reload() { + CurveView::reload(); + KDCoordinate minY = boxLowerBoundPixel(); + KDCoordinate maxY = boxUpperBoundPixel(); + KDCoordinate minX = std::round(floatToPixel(Axis::Horizontal, m_store->minValue(m_series))); + KDCoordinate maxX = std::round(floatToPixel(Axis::Horizontal, m_store->maxValue(m_series))); + KDRect dirtyRect = KDRect(minX, minY, maxX - minX + k_quantileBarWidth, maxY - minY); + markRectAsDirty(dirtyRect); +} + void BoxView::drawRect(KDContext * ctx, KDRect rect) const { ctx->fillRect(rect, KDColorWhite); - drawAxes(ctx, rect, Axis::Horizontal); - drawLabels(ctx, rect, Axis::Horizontal, false); - float lowBound = 0.35f; - float upBound = 0.65f; - // Draw the main box - KDCoordinate firstQuartilePixels = std::round(floatToPixel(Axis::Horizontal, m_store->firstQuartile())); - KDCoordinate thirdQuartilePixels = std::round(floatToPixel(Axis::Horizontal, m_store->thirdQuartile())); - KDCoordinate lowBoundPixel = std::round(floatToPixel(Axis::Vertical, upBound)); - KDCoordinate upBoundPixel = std::round(floatToPixel(Axis::Vertical, lowBound)); - ctx->fillRect(KDRect(firstQuartilePixels, lowBoundPixel, thirdQuartilePixels - firstQuartilePixels+2, - upBoundPixel-lowBoundPixel), Palette::GreyWhite); - // Draw the horizontal lines linking the box to the extreme bounds - drawSegment(ctx, rect, Axis::Horizontal, 0.5f, m_store->minValue(), m_store->firstQuartile(), Palette::GreyDark); - drawSegment(ctx, rect, Axis::Horizontal, 0.5f, m_store->thirdQuartile(), m_store->maxValue(), Palette::GreyDark); - double calculations[5] = {m_store->minValue(), m_store->firstQuartile(), m_store->median(), m_store->thirdQuartile(), m_store->maxValue()}; + KDCoordinate lowBoundPixel = boxLowerBoundPixel(); + KDCoordinate upBoundPixel = boxUpperBoundPixel(); + float lowBound = pixelToFloat(Axis::Vertical, upBoundPixel); + float upBound = pixelToFloat(Axis::Vertical, lowBoundPixel); + double minVal = m_store->minValue(m_series); + double firstQuart = m_store->firstQuartile(m_series); + double thirdQuart = m_store->thirdQuartile(m_series); + double maxVal = m_store->maxValue(m_series); + + bool isSelected = m_boxController->selectedSeriesIndex() == m_series; + KDColor boxColor = isSelected ? m_selectedHistogramLightColor : Palette::GreyWhite; + // Draw the main box + KDCoordinate firstQuartilePixels = std::round(floatToPixel(Axis::Horizontal, firstQuart)); + KDCoordinate thirdQuartilePixels = std::round(floatToPixel(Axis::Horizontal, thirdQuart)); + ctx->fillRect(KDRect(firstQuartilePixels, lowBoundPixel, thirdQuartilePixels - firstQuartilePixels+2, + upBoundPixel-lowBoundPixel), boxColor); + + // Draw the horizontal lines linking the box to the extreme bounds + KDColor horizontalColor = isSelected ? m_selectedHistogramColor : Palette::GreyDark; + float segmentOrd = (lowBound + upBound)/ 2.0f; + drawSegment(ctx, rect, Axis::Horizontal, segmentOrd, minVal, firstQuart, horizontalColor); + drawSegment(ctx, rect, Axis::Horizontal, segmentOrd, thirdQuart, maxVal, horizontalColor); + + double calculations[5] = {minVal, firstQuart, m_store->median(m_series), thirdQuart, maxVal}; /* We then draw all the vertical lines of the box and then recolor the * the selected quantile (if there is one). As two quantiles can have the same * value, we cannot choose line colors and then color only once the vertical - * lines. This solution could hide the highlighed line by coloring the next + * lines. This solution could hide the highlighted line by coloring the next * quantile if it has the same value. */ for (int k = 0; k < 5; k++) { - drawSegment(ctx, rect, Axis::Vertical, calculations[k], lowBound, upBound, Palette::GreyMiddle, 2); + drawSegment(ctx, rect, Axis::Vertical, calculations[k], lowBound, upBound, Palette::GreyMiddle, k_quantileBarWidth); } if (isMainViewSelected()) { - drawSegment(ctx, rect, Axis::Vertical, calculations[(int)*m_selectedQuantile], lowBound, upBound, Palette::YellowDark, 2); + drawSegment(ctx, rect, Axis::Vertical, calculations[(int)*m_selectedQuantile], lowBound, upBound, Palette::YellowDark, k_quantileBarWidth); } } -char * BoxView::label(Axis axis, int index) const { - if (axis == Axis::Vertical) { - return nullptr; - } - return (char *)m_labels[index]; +KDCoordinate BoxView::boxLowerBoundPixel() const { + return bounds().height() / 2 - k_boxHeight / 2; } +KDCoordinate BoxView::boxUpperBoundPixel() const { + return bounds().height() / 2 + k_boxHeight / 2; +} + + } diff --git a/apps/statistics/box_view.h b/apps/statistics/box_view.h index a156297e8..ab4b87b34 100644 --- a/apps/statistics/box_view.h +++ b/apps/statistics/box_view.h @@ -9,6 +9,8 @@ namespace Statistics { +class BoxController; + class BoxView : public Shared::CurveView { public: enum class Quantile : int { @@ -19,17 +21,30 @@ public: ThirdQuartile = 3, Max = 4 }; - BoxView(Store * store, Shared::BannerView * bannerView, Quantile * selectedQuantile); - void reload() override; - Quantile selectedQuantile(); + BoxView(BoxController * controller, Store * store, int series, Shared::BannerView * bannerView, Quantile * selectedQuantile, KDColor color, KDColor lightColor); + Quantile selectedQuantile() const { return *m_selectedQuantile; } bool selectQuantile(int selectedQuantile); + int series() const { return m_series; } + void reloadQuantile(); + + // CurveView + void reload() override; + + // View void drawRect(KDContext * ctx, KDRect rect) const override; private: - char * label(Axis axis, int index) const override; + static constexpr KDCoordinate k_boxHeight = 40; + static constexpr KDCoordinate k_quantileBarWidth = 2; + KDCoordinate boxLowerBoundPixel() const; + KDCoordinate boxUpperBoundPixel() const; + char * label(Axis axis, int index) const override { return nullptr; } Store * m_store; + BoxController * m_boxController; BoxRange m_boxRange; - char m_labels[k_maxNumberOfXLabels][Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(Constant::ShortNumberOfSignificantDigits)]; + int m_series; Quantile * m_selectedQuantile; + KDColor m_selectedHistogramColor; + KDColor m_selectedHistogramLightColor; }; } diff --git a/apps/statistics/calculation_controller.cpp b/apps/statistics/calculation_controller.cpp index bb0bf6454..eb51799d6 100644 --- a/apps/statistics/calculation_controller.cpp +++ b/apps/statistics/calculation_controller.cpp @@ -1,7 +1,8 @@ #include "calculation_controller.h" +#include "app.h" +#include "calculation_selectable_table_view.h" #include "../constant.h" #include "../apps_container.h" -#include "app.h" #include #include @@ -13,16 +14,145 @@ namespace Statistics { CalculationController::CalculationController(Responder * parentResponder, ButtonRowController * header, Store * store) : TabTableController(parentResponder, this), ButtonRowDelegate(header, nullptr), - m_titleCells{}, + m_seriesTitleCells{}, + m_calculationTitleCells{}, m_calculationCells{}, + m_hideableCell(nullptr), m_store(store) { } +// AlternateEmptyViewDelegate + +bool CalculationController::isEmpty() const { + return m_store->isEmpty(); +} + +I18n::Message CalculationController::emptyMessage() { + return I18n::Message::NoValueToCompute; +} + +Responder * CalculationController::defaultController() { + return tabController(); +} + +// TableViewDataSource + +int CalculationController::numberOfColumns() { + return 1 + m_store->numberOfNonEmptySeries(); +} + +void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { + EvenOddCell * evenOddCell = (EvenOddCell *)cell; + evenOddCell->setEven(j%2 == 0); + evenOddCell->setHighlighted(i == selectedColumn() && j == selectedRow()); + if (i == 0 && j == 0) { + return; + } + if (j == 0) { + // Display a series title cell + int seriesNumber = m_store->indexOfKthNonEmptySeries(i-1); + char titleBuffer[] = {'V', static_cast('1'+seriesNumber), '/', 'N', static_cast('1'+seriesNumber), 0}; + StoreTitleCell * storeTitleCell = static_cast(cell); + storeTitleCell->setText(titleBuffer); + storeTitleCell->setColor(DoublePairStore::colorOfSeriesAtIndex(seriesNumber)); + return; + } + if (i == 0) { + // Display a calculation title cell + I18n::Message titles[k_totalNumberOfRows] = { + I18n::Message::TotalSize, + I18n::Message::Minimum, + I18n::Message::Maximum, + I18n::Message::Range, + I18n::Message::Mean, + I18n::Message::StandardDeviationSigma, + I18n::Message::Deviation, + I18n::Message::FirstQuartile, + I18n::Message::ThirdQuartile, + I18n::Message::Median, + I18n::Message::InterquartileRange, + I18n::Message::Sum, + I18n::Message::SquareSum, + I18n::Message::SampleStandardDeviationS}; + MarginEvenOddMessageTextCell * calcTitleCell = static_cast(cell); + calcTitleCell->setMessage(titles[j-1]); + return; + } + // Display a calculation cell + CalculPointer calculationMethods[k_totalNumberOfRows] = {&Store::sumOfOccurrences, &Store::minValue, + &Store::maxValue, &Store::range, &Store::mean, &Store::standardDeviation, &Store::variance, &Store::firstQuartile, + &Store::thirdQuartile, &Store::median, &Store::quartileRange, &Store::sum, &Store::squaredValueSum, &Store::sampleStandardDeviation}; + int seriesIndex = m_store->indexOfKthNonEmptySeries(i-1); + double calculation = (m_store->*calculationMethods[j-1])(seriesIndex); + EvenOddBufferTextCell * calculationCell = static_cast(cell); + char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)]; + PrintFloat::convertFloatToText(calculation, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + calculationCell->setText(buffer); +} + +KDCoordinate CalculationController::columnWidth(int i) { + return i == 0 ? k_calculationTitleCellWidth : k_calculationCellWidth; +} + +KDCoordinate CalculationController::cumulatedHeightFromIndex(int j) { + return j*rowHeight(0); +} + +int CalculationController::indexFromCumulatedHeight(KDCoordinate offsetY) { + return (offsetY-1) / rowHeight(0); +} + +HighlightCell * CalculationController::reusableCell(int index, int type) { + assert(index >= 0 && index < reusableCellCount(type)); + if (type == k_hideableCellType) { + return m_hideableCell; + } + if (type == k_calculationTitleCellType) { + return static_cast(m_calculationTitleCells[index]); + } + if (type == k_seriesTitleCellType) { + return static_cast(m_seriesTitleCells[index]); + } + assert(type == k_calculationCellType); + return static_cast(m_calculationCells[index]); +} + +int CalculationController::reusableCellCount(int type) { + if (type == k_hideableCellType) { + return 1; + } + if (type == k_calculationTitleCellType) { + return k_numberOfCalculationTitleCells; + } + if (type == k_seriesTitleCellType) { + return k_numberOfSeriesTitleCells; + } + assert(type == k_calculationCellType); + return k_numberOfCalculationCells; +} + +int CalculationController::typeAtLocation(int i, int j) { + assert(i >= 0 && i < numberOfColumns()); + assert(j >= 0 && j < numberOfRows()); + if (i == 0 && j == 0) { + return k_hideableCellType; + } + if (i == 0) { + return k_calculationTitleCellType; + } + if (j == 0) { + return k_seriesTitleCellType; + } + return k_calculationCellType; +} + +// ViewController const char * CalculationController::title() { return I18n::translate(I18n::Message::StatTab); } +// Responder bool CalculationController::handleEvent(Ion::Events::Event event) { if (event == Ion::Events::Up) { selectableTableView()->deselectTable(); @@ -34,114 +164,58 @@ bool CalculationController::handleEvent(Ion::Events::Event event) { void CalculationController::didBecomeFirstResponder() { if (selectedRow() == -1) { - selectCellAtLocation(0, 0); + selectCellAtLocation(0, 1); } else { selectCellAtLocation(selectedColumn(), selectedRow()); } TabTableController::didBecomeFirstResponder(); } -bool CalculationController::isEmpty() const { - if (m_store->sumOfColumn(1) == 0) { - return true; - } - return false; -} - -I18n::Message CalculationController::emptyMessage() { - return I18n::Message::NoValueToCompute; -} - -Responder * CalculationController::defaultController() { - return tabController(); -} - -int CalculationController::numberOfRows() { - return k_totalNumberOfRows; -} - -int CalculationController::numberOfColumns() { - return 2; -} - -void CalculationController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { - EvenOddCell * myCell = (EvenOddCell *)cell; - myCell->setEven(j%2 == 0); - myCell->setHighlighted(i == selectedColumn() && j == selectedRow()); - if (i == 0) { - I18n::Message titles[k_totalNumberOfRows] = {I18n::Message::TotalSize, I18n::Message::Minimum, I18n::Message::Maximum, I18n::Message::Range, I18n::Message::Mean, I18n::Message::StandardDeviationSigma, I18n::Message::Deviation, I18n::Message::FirstQuartile, I18n::Message::ThirdQuartile, I18n::Message::Median, I18n::Message::InterquartileRange, I18n::Message::Sum, I18n::Message::SquareSum, I18n::Message::SampleStandardDeviationS}; - EvenOddMessageTextCell * myCell = (EvenOddMessageTextCell *)cell; - myCell->setMessage(titles[j]); - } else { - CalculPointer calculationMethods[k_totalNumberOfRows] = {&Store::sumOfOccurrences, &Store::minValue, - &Store::maxValue, &Store::range, &Store::mean, &Store::standardDeviation, &Store::variance, &Store::firstQuartile, - &Store::thirdQuartile, &Store::median, &Store::quartileRange, &Store::sum, &Store::squaredValueSum, &Store::sampleStandardDeviation}; - double calculation = (m_store->*calculationMethods[j])(); - EvenOddBufferTextCell * myCell = (EvenOddBufferTextCell *)cell; - char buffer[PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)]; - PrintFloat::convertFloatToText(calculation, buffer, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); - myCell->setText(buffer); - } -} - -KDCoordinate CalculationController::columnWidth(int i) { - if (i == 0) { - return k_titleCellWidth; - } - return Ion::Display::Width - Metric::CommonRightMargin - Metric::CommonLeftMargin-k_titleCellWidth; -} - -KDCoordinate CalculationController::rowHeight(int j) { - return k_cellHeight; -} - - -KDCoordinate CalculationController::cumulatedHeightFromIndex(int j) { - return j*rowHeight(0); -} - -int CalculationController::indexFromCumulatedHeight(KDCoordinate offsetY) { - return (offsetY-1) / rowHeight(0); -} - -HighlightCell * CalculationController::reusableCell(int index, int type) { - assert(index < k_totalNumberOfRows); - if (type == 0) { - return m_titleCells[index]; - } - return m_calculationCells[index]; -} - -int CalculationController::reusableCellCount(int type) { - return k_maxNumberOfDisplayableRows; -} - -int CalculationController::typeAtLocation(int i, int j) { - return i; -} +// Private Responder * CalculationController::tabController() const { return (parentResponder()->parentResponder()->parentResponder()); } View * CalculationController::loadView() { - for (int i = 0; i < k_maxNumberOfDisplayableRows; i++) { - m_titleCells[i] = new EvenOddMessageTextCell(KDText::FontSize::Small); - m_titleCells[i]->setAlignment(1.0f, 0.5f); - m_calculationCells[i] = new EvenOddBufferTextCell(KDText::FontSize::Small); + for (int i = 0; i < k_numberOfSeriesTitleCells; i++) { + m_seriesTitleCells[i] = new StoreTitleCell(); + m_seriesTitleCells[i]->setSeparatorLeft(true); + } + for (int i = 0; i < k_numberOfCalculationTitleCells; i++) { + m_calculationTitleCells[i] = new MarginEvenOddMessageTextCell(KDText::FontSize::Small); + m_calculationTitleCells[i]->setAlignment(1.0f, 0.5f); + } + for (int i = 0; i < k_numberOfCalculationCells; i++) { + m_calculationCells[i] = new SeparatorEvenOddBufferTextCell(KDText::FontSize::Small); m_calculationCells[i]->setTextColor(Palette::GreyDark); } - return TabTableController::loadView(); + m_hideableCell = new HideableEvenOddCell(); + m_hideableCell->setHide(true); + + CalculationSelectableTableView * selectableTableView = new CalculationSelectableTableView(this, this, this); + selectableTableView->setBackgroundColor(Palette::WallScreenDark); + selectableTableView->setVerticalCellOverlap(0); + selectableTableView->setMargins(k_margin, k_scrollBarMargin, k_scrollBarMargin, k_margin); + return selectableTableView; } void CalculationController::unloadView(View * view) { - for (int i = 0; i < k_maxNumberOfDisplayableRows; i++) { - delete m_titleCells[i]; - m_titleCells[i] = nullptr; + for (int i = 0; i < k_numberOfSeriesTitleCells; i++) { + delete m_seriesTitleCells[i]; + m_seriesTitleCells[i] = nullptr; + } + for (int i = 0; i < k_numberOfCalculationTitleCells; i++) { + delete m_calculationTitleCells[i]; + m_calculationTitleCells[i] = nullptr; + } + for (int i = 0; i < k_numberOfCalculationCells; i++) { delete m_calculationCells[i]; m_calculationCells[i] = nullptr; } + delete m_hideableCell; + m_hideableCell = nullptr; TabTableController::unloadView(view); } diff --git a/apps/statistics/calculation_controller.h b/apps/statistics/calculation_controller.h index 2c0b6e23c..5210be2b6 100644 --- a/apps/statistics/calculation_controller.h +++ b/apps/statistics/calculation_controller.h @@ -3,6 +3,10 @@ #include #include "store.h" +#include "../shared/hideable_even_odd_cell.h" +#include "../shared/margin_even_odd_message_text_cell.h" +#include "../shared/separator_even_odd_buffer_text_cell.h" +#include "../shared/store_title_cell.h" #include "../shared/tab_table_controller.h" namespace Statistics { @@ -11,34 +15,55 @@ class CalculationController : public Shared::TabTableController, public ButtonRo public: CalculationController(Responder * parentResponder, ButtonRowController * header, Store * store); - const char * title() override; - bool handleEvent(Ion::Events::Event event) override; - void didBecomeFirstResponder() override; + // AlternateEmptyViewDelegate bool isEmpty() const override; I18n::Message emptyMessage() override; Responder * defaultController() override; - int numberOfRows() override; + // TableViewDataSource + int numberOfRows() override { return k_totalNumberOfRows; } int numberOfColumns() override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; KDCoordinate columnWidth(int i) override; - KDCoordinate rowHeight(int j) override; + KDCoordinate rowHeight(int j) override { return k_cellHeight; } KDCoordinate cumulatedHeightFromIndex(int j) override; int indexFromCumulatedHeight(KDCoordinate offsetY) override; HighlightCell * reusableCell(int index, int type) override; int reusableCellCount(int type) override; int typeAtLocation(int i, int j) override; + + // ViewController + const char * title() override; + + // Responder + bool handleEvent(Ion::Events::Event event) override; + void didBecomeFirstResponder() override; private: + static constexpr int k_totalNumberOfRows = 15; + static constexpr int k_maxNumberOfDisplayableRows = 11; + static constexpr int k_numberOfCalculationCells = 3 * k_maxNumberOfDisplayableRows; + static constexpr int k_numberOfSeriesTitleCells = 3; + static constexpr int k_numberOfCalculationTitleCells = k_maxNumberOfDisplayableRows; + + static constexpr int k_calculationTitleCellType = 0; + static constexpr int k_calculationCellType = 1; + static constexpr int k_seriesTitleCellType = 2; + static constexpr int k_hideableCellType = 3; + static constexpr KDCoordinate k_cellHeight = 20; + static constexpr KDCoordinate k_calculationTitleCellWidth = 175; + static constexpr KDCoordinate k_calculationCellWidth = 84; + static constexpr KDCoordinate k_margin = 8; + static constexpr KDCoordinate k_scrollBarMargin = Metric::CommonRightMargin; + Responder * tabController() const override; View * loadView() override; void unloadView(View * view) override; - constexpr static int k_totalNumberOfRows = 14; - constexpr static int k_maxNumberOfDisplayableRows = 11; - static constexpr KDCoordinate k_cellHeight = 20; - static constexpr KDCoordinate k_titleCellWidth = 175; - EvenOddMessageTextCell * m_titleCells[k_maxNumberOfDisplayableRows]; - EvenOddBufferTextCell * m_calculationCells[k_maxNumberOfDisplayableRows]; + + Shared::StoreTitleCell * m_seriesTitleCells[k_numberOfSeriesTitleCells]; + Shared::MarginEvenOddMessageTextCell * m_calculationTitleCells[k_numberOfCalculationTitleCells]; + Shared::SeparatorEvenOddBufferTextCell * m_calculationCells[k_numberOfCalculationCells]; + Shared::HideableEvenOddCell * m_hideableCell; Store * m_store; }; diff --git a/apps/statistics/calculation_selectable_table_view.cpp b/apps/statistics/calculation_selectable_table_view.cpp new file mode 100644 index 000000000..e9bd9a300 --- /dev/null +++ b/apps/statistics/calculation_selectable_table_view.cpp @@ -0,0 +1,15 @@ +#include "calculation_selectable_table_view.h" + +namespace Statistics { + +bool CalculationSelectableTableView::handleEvent(Ion::Events::Event event) { + if (event == Ion::Events::Up && selectedColumn() == 0 && selectedRow() == 1) { + return false; + } + if (event == Ion::Events::Left && selectedColumn() == 1 && selectedRow() == 0) { + return selectCellAtLocation(0, 1); + } + return SelectableTableView::handleEvent(event); +} + +} diff --git a/apps/statistics/calculation_selectable_table_view.h b/apps/statistics/calculation_selectable_table_view.h new file mode 100644 index 000000000..5de8b15f6 --- /dev/null +++ b/apps/statistics/calculation_selectable_table_view.h @@ -0,0 +1,16 @@ +#ifndef APPS_STATISTICS_CALCULATION_SELECTABLE_TABLE_VIEW_H +#define APPS_STATISTICS_CALCULATION_SELECTABLE_TABLE_VIEW_H + +#include + +namespace Statistics { + +class CalculationSelectableTableView : public SelectableTableView { +public: + using SelectableTableView::SelectableTableView; + bool handleEvent(Ion::Events::Event event) override; +}; + +} + +#endif diff --git a/apps/statistics/histogram_banner_view.cpp b/apps/statistics/histogram_banner_view.cpp index 8e8d83879..95c36180b 100644 --- a/apps/statistics/histogram_banner_view.cpp +++ b/apps/statistics/histogram_banner_view.cpp @@ -15,17 +15,13 @@ HistogramBannerView::HistogramBannerView() : { } -int HistogramBannerView::numberOfSubviews() const { - return 6; -} - TextView * HistogramBannerView::textViewAtIndex(int i) const { - const TextView * textViews[6] = {&m_intervalLegendView, &m_intervalView, &m_sizeLegendView, &m_sizeView, &m_frequencyLegendView, &m_frequencyView}; + const TextView * textViews[k_numberOfSubviews] = {&m_intervalLegendView, &m_intervalView, &m_sizeLegendView, &m_sizeView, &m_frequencyLegendView, &m_frequencyView}; return (TextView *)textViews[i]; } MessageTextView * HistogramBannerView::messageTextViewAtIndex(int index) const { - const MessageTextView * textViews[6] = {&m_intervalLegendView, nullptr, &m_sizeLegendView, nullptr, &m_frequencyLegendView, nullptr}; + const MessageTextView * textViews[k_numberOfSubviews] = {&m_intervalLegendView, nullptr, &m_sizeLegendView, nullptr, &m_frequencyLegendView, nullptr}; return (MessageTextView *)textViews[index]; } diff --git a/apps/statistics/histogram_banner_view.h b/apps/statistics/histogram_banner_view.h index 6386141f8..dcb3df8f3 100644 --- a/apps/statistics/histogram_banner_view.h +++ b/apps/statistics/histogram_banner_view.h @@ -11,7 +11,8 @@ class HistogramBannerView : public Shared::BannerView { public: HistogramBannerView(); private: - int numberOfSubviews() const override; + static constexpr int k_numberOfSubviews = 6; + int numberOfSubviews() const override { return k_numberOfSubviews; } TextView * textViewAtIndex(int i) const override; MessageTextView * messageTextViewAtIndex(int index) const override; MessageTextView m_intervalLegendView; diff --git a/apps/statistics/histogram_controller.cpp b/apps/statistics/histogram_controller.cpp index e61893ab0..d0a818f33 100644 --- a/apps/statistics/histogram_controller.cpp +++ b/apps/statistics/histogram_controller.cpp @@ -10,31 +10,22 @@ using namespace Shared; namespace Statistics { -HistogramController::HistogramController(Responder * parentResponder, ButtonRowController * header, Store * store, uint32_t * storeVersion, uint32_t * barVersion, uint32_t * rangeVersion, int * selectedBarIndex) : - ViewController(parentResponder), +static inline float min(float x, float y) { return (xy ? x : y); } + +HistogramController::HistogramController(Responder * parentResponder, ButtonRowController * header, Store * store, uint32_t * storeVersion, uint32_t * barVersion, uint32_t * rangeVersion, int * selectedBarIndex, int * selectedSeriesIndex) : + MultipleDataViewController(parentResponder, store, selectedBarIndex, selectedSeriesIndex), ButtonRowDelegate(header, nullptr), - m_bannerView(), - m_view(store, &m_bannerView), - m_settingButton(this, I18n::Message::HistogramSet, Invocation([](void * context, void * sender) { - HistogramController * histogramController = (HistogramController *) context; - StackViewController * stack = ((StackViewController *)histogramController->stackController()); - stack->push(histogramController->histogramParameterController()); - }, this)), - m_store(store), + m_view(this, store), m_storeVersion(storeVersion), m_barVersion(barVersion), m_rangeVersion(rangeVersion), - m_selectedBarIndex(selectedBarIndex), m_histogramParameterController(nullptr, store) { } -const char * HistogramController::title() { - return I18n::translate(I18n::Message::HistogramTab); -} - -View * HistogramController::view() { - return &m_view; +void HistogramController::setCurrentDrawnSeries(int series) { + initYRangeParameters(series); } StackViewController * HistogramController::stackController() { @@ -42,44 +33,22 @@ StackViewController * HistogramController::stackController() { return stack; } -HistogramParameterController * HistogramController::histogramParameterController() { - return &m_histogramParameterController; +const char * HistogramController::title() { + return I18n::translate(I18n::Message::HistogramTab); } bool HistogramController::handleEvent(Ion::Events::Event event) { - if (event == Ion::Events::Down) { - if (!m_view.isMainViewSelected()) { - header()->setSelectedButton(-1); - m_view.selectMainView(true); - reloadBannerView(); - m_view.reload(); - app()->setFirstResponder(this); - return true; - } - return false; - } - if (event == Ion::Events::Up) { - if (!m_view.isMainViewSelected()) { - header()->setSelectedButton(-1); - app()->setFirstResponder(tabController()); - return true; - } - m_view.selectMainView(false); - header()->setSelectedButton(0); + assert(selectedSeriesIndex() >= 0); + if (event == Ion::Events::OK) { + stackController()->push(histogramParameterController()); return true; } - if (m_view.isMainViewSelected() && (event == Ion::Events::Left || event == Ion::Events::Right)) { - int direction = event == Ion::Events::Left ? -1 : 1; - if (moveSelection(direction)) { - reloadBannerView(); - m_view.reload(); - } - return true; - } - return false; + return MultipleDataViewController::handleEvent(event); } void HistogramController::didBecomeFirstResponder() { + MultipleDataViewController::didBecomeFirstResponder(); + uint32_t storeChecksum = m_store->storeChecksum(); if (*m_storeVersion != storeChecksum) { *m_storeVersion = storeChecksum; @@ -97,53 +66,17 @@ void HistogramController::didBecomeFirstResponder() { initBarSelection(); reloadBannerView(); } - if (!m_view.isMainViewSelected()) { - header()->setSelectedButton(0); - } else { - m_view.setHighlight(m_store->startOfBarAtIndex(*m_selectedBarIndex), m_store->endOfBarAtIndex(*m_selectedBarIndex)); - } -} - -int HistogramController::numberOfButtons(ButtonRowController::Position) const { - if (isEmpty()) { - return 0; - } - return 1; -} -Button * HistogramController::buttonAtIndex(int index, ButtonRowController::Position) const { - return (Button *)&m_settingButton; -} - -bool HistogramController::isEmpty() const { - if (m_store->sumOfColumn(1) == 0) { - return true; - } - return false; -} - -I18n::Message HistogramController::emptyMessage() { - return I18n::Message::NoDataToPlot; -} - -Responder * HistogramController::defaultController() { - return tabController(); -} - -void HistogramController::viewWillAppear() { - if (!m_view.isMainViewSelected()) { - m_view.selectMainView(true); - header()->setSelectedButton(-1); - } - reloadBannerView(); - m_view.reload(); + HistogramView * selectedHistogramView = static_cast(m_view.dataViewAtIndex(selectedSeriesIndex())); + selectedHistogramView->setHighlight(m_store->startOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex), m_store->endOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex)); } void HistogramController::willExitResponderChain(Responder * nextFirstResponder) { if (nextFirstResponder == nullptr || nextFirstResponder == tabController()) { - m_view.selectMainView(false); - header()->setSelectedButton(-1); - m_view.reload(); + if (selectedSeriesIndex() >= 0) { + m_view.dataViewAtIndex(selectedSeriesIndex())->setForceOkDisplay(false); + } } + MultipleDataViewController::willExitResponderChain(nextFirstResponder); } Responder * HistogramController::tabController() const { @@ -151,6 +84,9 @@ Responder * HistogramController::tabController() const { } void HistogramController::reloadBannerView() { + if (selectedSeriesIndex() < 0) { + return; + } char buffer[k_maxNumberOfCharacters+ PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits)*2]; int numberOfChar = 0; @@ -161,14 +97,18 @@ void HistogramController::reloadBannerView() { numberOfChar += legendLength; // Add lower bound - double lowerBound = m_store->startOfBarAtIndex(*m_selectedBarIndex); - numberOfChar += PrintFloat::convertFloatToText(lowerBound, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + if (selectedSeriesIndex() >= 0) { + double lowerBound = m_store->startOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex); + numberOfChar += PrintFloat::convertFloatToText(lowerBound, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + } buffer[numberOfChar++] = ';'; // Add upper bound - double upperBound = m_store->endOfBarAtIndex(*m_selectedBarIndex); - numberOfChar += PrintFloat::convertFloatToText(upperBound, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + if (selectedSeriesIndex() >= 0) { + double upperBound = m_store->endOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex); + numberOfChar += PrintFloat::convertFloatToText(upperBound, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + } buffer[numberOfChar++] = '['; @@ -177,7 +117,7 @@ void HistogramController::reloadBannerView() { buffer[numberOfChar++] = ' '; } buffer[k_maxIntervalLegendLength] = 0; - m_bannerView.setLegendAtIndex(buffer, 1); + m_view.editableBannerView()->setLegendAtIndex(buffer, 1); // Add Size Data numberOfChar = 0; @@ -185,14 +125,17 @@ void HistogramController::reloadBannerView() { legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); numberOfChar += legendLength; - double size = m_store->heightOfBarAtIndex(*m_selectedBarIndex); - numberOfChar += PrintFloat::convertFloatToText(size, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + double size = 0; + if (selectedSeriesIndex() >= 0) { + size = m_store->heightOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex); + numberOfChar += PrintFloat::convertFloatToText(size, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + } // Padding for (int i = numberOfChar; i < k_maxLegendLength; i++) { buffer[numberOfChar++] = ' '; } buffer[k_maxLegendLength] = 0; - m_bannerView.setLegendAtIndex(buffer, 3); + m_view.editableBannerView()->setLegendAtIndex(buffer, 3); // Add Frequency Data numberOfChar = 0; @@ -200,42 +143,53 @@ void HistogramController::reloadBannerView() { legendLength = strlen(legend); strlcpy(buffer, legend, legendLength+1); numberOfChar += legendLength; - double frequency = size/m_store->sumOfColumn(1); - numberOfChar += PrintFloat::convertFloatToText(frequency, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + if (selectedSeriesIndex() >= 0) { + double frequency = size/m_store->sumOfOccurrences(selectedSeriesIndex()); + numberOfChar += PrintFloat::convertFloatToText(frequency, buffer+numberOfChar, PrintFloat::bufferSizeForFloatsWithPrecision(Constant::LargeNumberOfSignificantDigits), Constant::LargeNumberOfSignificantDigits); + } // Padding for (int i = numberOfChar; i < k_maxLegendLength; i++) { buffer[numberOfChar++] = ' '; } buffer[k_maxLegendLength] = 0; - m_bannerView.setLegendAtIndex(buffer, 5); + m_view.editableBannerView()->setLegendAtIndex(buffer, 5); } -bool HistogramController::moveSelection(int deltaIndex) { +bool HistogramController::moveSelectionHorizontally(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) { + do { + newSelectedBarIndex+=deltaIndex; + } while (m_store->heightOfBarAtIndex(selectedSeriesIndex(), newSelectedBarIndex) == 0 + && newSelectedBarIndex >= 0 + && newSelectedBarIndex < m_store->numberOfBars(selectedSeriesIndex())); + + if (newSelectedBarIndex >= 0 + && newSelectedBarIndex < m_store->numberOfBars(selectedSeriesIndex()) + && *m_selectedBarIndex != newSelectedBarIndex) + { *m_selectedBarIndex = newSelectedBarIndex; - m_view.setHighlight(m_store->startOfBarAtIndex(*m_selectedBarIndex), m_store->endOfBarAtIndex(*m_selectedBarIndex)); - m_store->scrollToSelectedBarIndex(*m_selectedBarIndex); + m_view.dataViewAtIndex(selectedSeriesIndex())->setHighlight(m_store->startOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex), m_store->endOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex)); + if (m_store->scrollToSelectedBarIndex(selectedSeriesIndex(), *m_selectedBarIndex)) { + multipleDataView()->reload(); + } + reloadBannerView(); return true; } return false; } void HistogramController::initRangeParameters() { - float min = m_store->firstDrawnBarAbscissa(); - float max = m_store->maxValue(); + assert(selectedSeriesIndex() >= 0 && m_store->sumOfOccurrences(selectedSeriesIndex()) > 0); + float minValue = m_store->firstDrawnBarAbscissa(); + float maxValue = -FLT_MAX; + for (int i = 0; i < Store::k_numberOfSeries; i ++) { + if (!m_store->seriesIsEmpty(i)) { + maxValue = max(maxValue, m_store->maxValue(i)); + } + } float barWidth = m_store->barWidth(); - float xMin = min; - float xMax = max + barWidth; + float xMin = minValue; + float xMax = maxValue + 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; @@ -246,25 +200,50 @@ void HistogramController::initRangeParameters() { } m_store->setXMin(xMin - Store::k_displayLeftMarginRatio*(xMax-xMin)); m_store->setXMax(xMax + Store::k_displayRightMarginRatio*(xMax-xMin)); + + initYRangeParameters(selectedSeriesIndex()); +} + +void HistogramController::initYRangeParameters(int series) { + assert(series >= 0 && m_store->sumOfOccurrences(series) > 0); float yMax = -FLT_MAX; - for (int index = 0; index < m_store->numberOfBars(); index++) { - float size = m_store->heightOfBarAtIndex(index); + for (int index = 0; index < m_store->numberOfBars(series); index++) { + float size = m_store->heightOfBarAtIndex(series, index); if (size > yMax) { yMax = size; } } - yMax = yMax/m_store->sumOfColumn(1); + yMax = yMax/m_store->sumOfOccurrences(series); yMax = yMax < 0 ? 1 : yMax; - m_store->setYMin(-Store::k_displayBottomMarginRatio*yMax); m_store->setYMax(yMax*(1.0f+Store::k_displayTopMarginRatio)); + + /* Compute YMin: + * ratioFloatPixel*(0-yMin) = k_bottomMargin + * ratioFloatPixel*(yMax-yMin) = viewHeight + * + * -ratioFloatPixel*yMin = k_bottomMargin + * ratioFloatPixel*yMax-ratioFloatPixel*yMin = viewHeight + * + * ratioFloatPixel = (viewHeight - k_bottomMargin)/yMax + * yMin = -k_bottomMargin/ratioFloatPixel = yMax*k_bottomMargin/(k_bottomMargin - viewHeight) + * */ + + m_store->setYMin(m_store->yMax()*(float)Store::k_bottomMargin/((float)Store::k_bottomMargin - m_view.dataViewAtIndex(series)->bounds().height())); } void HistogramController::initBarParameters() { - float min = m_store->minValue(); - float max = m_store->maxValue(); - max = min >= max ? min + std::pow(10.0f, std::floor(std::log10(std::fabs(min)))-1.0f) : max; - m_store->setFirstDrawnBarAbscissa(min); - float barWidth = m_store->computeGridUnit(CurveViewRange::Axis::X, min, max); + assert(selectedSeriesIndex() >= 0 && m_store->sumOfOccurrences(selectedSeriesIndex()) > 0); + float minValue = FLT_MAX; + float maxValue = -FLT_MAX; + for (int i = 0; i < Store::k_numberOfSeries; i ++) { + if (!m_store->seriesIsEmpty(i)) { + minValue = min(minValue, m_store->minValue(i)); + maxValue = max(maxValue, m_store->maxValue(i)); + } + } + maxValue = minValue >= maxValue ? minValue + std::pow(10.0f, std::floor(std::log10(std::fabs(minValue)))-1.0f) : maxValue; + m_store->setFirstDrawnBarAbscissa(minValue); + float barWidth = m_store->computeGridUnit(CurveViewRange::Axis::X, minValue, maxValue); if (barWidth <= 0.0f) { barWidth = 1.0f; } @@ -272,19 +251,20 @@ void HistogramController::initBarParameters() { } void HistogramController::initBarSelection() { + assert(selectedSeriesIndex() >= 0 && m_store->sumOfOccurrences(selectedSeriesIndex()) > 0); *m_selectedBarIndex = 0; - while ((m_store->heightOfBarAtIndex(*m_selectedBarIndex) == 0 || - m_store->startOfBarAtIndex(*m_selectedBarIndex) < m_store->firstDrawnBarAbscissa()) && *m_selectedBarIndex < m_store->numberOfBars()) { + while ((m_store->heightOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex) == 0 || + m_store->startOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex) < m_store->firstDrawnBarAbscissa()) && *m_selectedBarIndex < m_store->numberOfBars(selectedSeriesIndex())) { *m_selectedBarIndex = *m_selectedBarIndex+1; } - if (*m_selectedBarIndex >= m_store->numberOfBars()) { + if (*m_selectedBarIndex >= m_store->numberOfBars(selectedSeriesIndex())) { /* 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()) { + while (m_store->heightOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex) == 0 && *m_selectedBarIndex < m_store->numberOfBars(selectedSeriesIndex())) { *m_selectedBarIndex = *m_selectedBarIndex+1; } } - m_store->scrollToSelectedBarIndex(*m_selectedBarIndex); + m_store->scrollToSelectedBarIndex(selectedSeriesIndex(), *m_selectedBarIndex); } } diff --git a/apps/statistics/histogram_controller.h b/apps/statistics/histogram_controller.h index 84d13a447..1790723b6 100644 --- a/apps/statistics/histogram_controller.h +++ b/apps/statistics/histogram_controller.h @@ -3,52 +3,45 @@ #include #include "store.h" -#include "histogram_view.h" -#include "histogram_banner_view.h" -#include "histogram_parameter_controller.h" -#include "../shared/curve_view.h" +#include "multiple_data_view_controller.h" +#include "multiple_histograms_view.h" namespace Statistics { -class HistogramController : public ViewController, public ButtonRowDelegate, public AlternateEmptyViewDelegate { +class HistogramController : public MultipleDataViewController, public ButtonRowDelegate { public: - HistogramController(Responder * parentResponder, ButtonRowController * header, Store * store, uint32_t * m_storeVersion, uint32_t * m_barVersion, uint32_t * m_rangeVersion, int * m_selectedBarIndex); - const char * title() override; - View * view() override; + HistogramController(Responder * parentResponder, ButtonRowController * header, Store * store, uint32_t * m_storeVersion, uint32_t * m_barVersion, uint32_t * m_rangeVersion, int * m_selectedBarIndex, int * selectedSeriesIndex); + + HistogramParameterController * histogramParameterController() { return &m_histogramParameterController; } + void setCurrentDrawnSeries(int series); StackViewController * stackController(); - HistogramParameterController * histogramParameterController(); + + // ViewController + const char * title() override; + MultipleDataView * multipleDataView() override { return &m_view; } + + // Responder bool handleEvent(Ion::Events::Event event) override; void didBecomeFirstResponder() override; - - int numberOfButtons(ButtonRowController::Position) const override; - Button * buttonAtIndex(int index, ButtonRowController::Position position) const override; - - bool isEmpty() const override; - I18n::Message emptyMessage() override; - Responder * defaultController() override; - void viewWillAppear() override; void willExitResponderChain(Responder * nextFirstResponder) override; private: constexpr static int k_maxNumberOfBarsPerWindow = 100; constexpr static int k_maxIntervalLegendLength = 33; constexpr static int k_maxLegendLength = 13; constexpr static int k_maxNumberOfCharacters = 30; - Responder * tabController() const; - void reloadBannerView(); + Responder * tabController() const override; + void reloadBannerView() override; void initRangeParameters(); + void initYRangeParameters(int series); 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; + bool moveSelectionHorizontally(int deltaIndex) override; + MultipleHistogramsView m_view; 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 387633b3a..e2d187afd 100644 --- a/apps/statistics/histogram_parameter_controller.cpp +++ b/apps/statistics/histogram_parameter_controller.cpp @@ -18,10 +18,6 @@ const char * HistogramParameterController::title() { return I18n::translate(I18n::Message::HistogramSet); } -int HistogramParameterController::numberOfRows() { - return 1+k_numberOfCells; -} - void HistogramParameterController::willDisplayCellForIndex(HighlightCell * cell, int index) { if (index == numberOfRows()-1) { return; @@ -34,27 +30,68 @@ void HistogramParameterController::willDisplayCellForIndex(HighlightCell * cell, double HistogramParameterController::parameterAtIndex(int index) { assert(index >= 0 && index < k_numberOfCells); - if (index == 0) { - return m_store->barWidth(); - } - return m_store->firstDrawnBarAbscissa(); + return index == 0 ? m_store->barWidth() : m_store->firstDrawnBarAbscissa(); } bool HistogramParameterController::setParameterAtIndex(int parameterIndex, double f) { assert(parameterIndex >= 0 && parameterIndex < k_numberOfCells); if (parameterIndex == 0) { - double newNumberOfBars = std::ceil((m_store->maxValue() - m_store->minValue())/f); - if (f <= 0.0f || newNumberOfBars > Store::k_maxNumberOfBars || m_store->firstDrawnBarAbscissa() > m_store->maxValue()+f) { + // The bar width cannot be negative + if (f <= 0.0f) { app()->displayWarning(I18n::Message::ForbiddenValue); return false; } + + // There should be at least one value in the drawn bin + for (int i = 0; i < DoublePairStore::k_numberOfSeries; i++) { + if (m_store->firstDrawnBarAbscissa() <= m_store->maxValue(i)+f) { + break; + } else if (i == DoublePairStore::k_numberOfSeries - 1) { + app()->displayWarning(I18n::Message::ForbiddenValue); + return false; + } + } + + // The number of bars cannot be above the max + assert(DoublePairStore::k_numberOfSeries > 0); + double maxNewNumberOfBars = std::ceil((m_store->maxValue(0) - m_store->minValue(0))/f); + for (int i = 1; i < DoublePairStore::k_numberOfSeries; i++) { + double numberOfBars = std::ceil((m_store->maxValue(i) - m_store->minValue(i))/f); + if (maxNewNumberOfBars < numberOfBars) { + maxNewNumberOfBars = numberOfBars; + } + } + if (maxNewNumberOfBars > Store::k_maxNumberOfBars) { + app()->displayWarning(I18n::Message::ForbiddenValue); + return false; + } + + // Set the bar width m_store->setBarWidth(f); } else { - double newNumberOfBars = ceilf((m_store->maxValue() - f)/m_store->barWidth()); - if (newNumberOfBars > Store::k_maxNumberOfBars || f > m_store->maxValue()+m_store->barWidth()) { + // The number of bars cannot be above the max + assert(DoublePairStore::k_numberOfSeries > 0); + double maxNewNumberOfBars = ceilf((m_store->maxValue(0) - f)/m_store->barWidth()); + for (int i = 1; i < DoublePairStore::k_numberOfSeries; i++) { + double numberOfBars = ceilf((m_store->maxValue(i) - f)/m_store->barWidth()); + if (maxNewNumberOfBars < numberOfBars) { + maxNewNumberOfBars = numberOfBars; + } + } + if (maxNewNumberOfBars > Store::k_maxNumberOfBars) { app()->displayWarning(I18n::Message::ForbiddenValue); return false; } + // There should be at least one value in the drawn bin + for (int i = 0; i < DoublePairStore::k_numberOfSeries; i++) { + if (f <= m_store->maxValue(i)+m_store->barWidth()) { + break; + } else if (i == DoublePairStore::k_numberOfSeries - 1) { + app()->displayWarning(I18n::Message::ForbiddenValue); + return false; + } + } + // Set the first drawn bar abscissa m_store->setFirstDrawnBarAbscissa(f); } return true; @@ -65,10 +102,6 @@ HighlightCell * HistogramParameterController::reusableParameterCell(int index, i return m_cells[index]; } -int HistogramParameterController::reusableParameterCellCount(int type) { - return k_numberOfCells; -} - View * HistogramParameterController::loadView() { SelectableTableView * tableView = (SelectableTableView *)FloatParameterController::loadView(); for (int i = 0; i < k_numberOfCells; i++) { diff --git a/apps/statistics/histogram_parameter_controller.h b/apps/statistics/histogram_parameter_controller.h index 3a9a35de2..ab20dd019 100644 --- a/apps/statistics/histogram_parameter_controller.h +++ b/apps/statistics/histogram_parameter_controller.h @@ -11,17 +11,17 @@ class HistogramParameterController : public Shared::FloatParameterController { public: HistogramParameterController(Responder * parentResponder, Store * store); const char * title() override; - int numberOfRows() override; + int numberOfRows() override { return 1+k_numberOfCells; } void willDisplayCellForIndex(HighlightCell * cell, int index) override; private: + constexpr static int k_numberOfCells = 2; HighlightCell * reusableParameterCell(int index, int type) override; - int reusableParameterCellCount(int type) override; + int reusableParameterCellCount(int type) override { return k_numberOfCells; } double parameterAtIndex(int index) override; bool setParameterAtIndex(int parameterIndex, double f) override; View * loadView() override; void unloadView(View * view) override; char m_draftTextBuffer[MessageTableCellWithEditableText::k_bufferLength]; - constexpr static int k_numberOfCells = 2; MessageTableCellWithEditableText * m_cells[k_numberOfCells]; Store * m_store; }; diff --git a/apps/statistics/histogram_view.cpp b/apps/statistics/histogram_view.cpp index a1af3b496..b48faf249 100644 --- a/apps/statistics/histogram_view.cpp +++ b/apps/statistics/histogram_view.cpp @@ -1,4 +1,5 @@ #include "histogram_view.h" +#include "histogram_controller.h" #include #include @@ -6,60 +7,74 @@ using namespace Shared; namespace Statistics { -HistogramView::HistogramView(Store * store, BannerView * bannerView) : +HistogramView::HistogramView(HistogramController * controller, Store * store, int series, Shared::BannerView * bannerView, KDColor selectedHistogramColor, KDColor notSelectedHistogramColor, KDColor selectedBarColor) : CurveView(store, nullptr, bannerView, nullptr), + m_controller(controller), m_store(store), m_labels{}, m_highlightedBarStart(NAN), - m_highlightedBarEnd(NAN) + m_highlightedBarEnd(NAN), + m_series(series), + m_selectedHistogramColor(selectedHistogramColor), + m_notSelectedHistogramColor(notSelectedHistogramColor), + m_selectedBarColor(selectedBarColor), + m_displayLabels(true) { } void HistogramView::reload() { + CurveView::reload(); + /* We deliberately do not mark as dirty the frame of the banner view to avoid + *unpleasant blinking of the drawing of the banner view. */ + KDRect dirtyZone(KDRect(0, 0, bounds().width(), bounds().height() - (displayBannerView() ? m_bannerView->bounds().height() : 0))); + markRectAsDirty(dirtyZone); +} + +void HistogramView::reloadSelectedBar() { CurveView::reload(); float pixelLowerBound = floatToPixel(Axis::Horizontal, m_highlightedBarStart)-2; float pixelUpperBound = floatToPixel(Axis::Horizontal, m_highlightedBarEnd)+2; /* We deliberately do not mark as dirty the frame of the banner view to avoid *unpleasant blinking of the drawing of the banner view. */ KDRect dirtyZone(KDRect(pixelLowerBound, 0, pixelUpperBound-pixelLowerBound, - bounds().height()-m_bannerView->bounds().height())); + bounds().height() - (displayBannerView() ? m_bannerView->bounds().height() : 0))); markRectAsDirty(dirtyZone); } void HistogramView::drawRect(KDContext * ctx, KDRect rect) const { + m_controller->setCurrentDrawnSeries(m_series); ctx->fillRect(rect, KDColorWhite); drawAxes(ctx, rect, Axis::Horizontal); - drawLabels(ctx, rect, Axis::Horizontal, false); + drawLabels(ctx, rect, Axis::Horizontal, false, !m_displayLabels); /* We memoize the total size to avoid recomputing it in double precision at * every call to EvaluateHistogramAtAbscissa() */ - float totalSize = m_store->sumOfColumn(1); + float totalSize = m_store->sumOfOccurrences(m_series); + float context[] = {totalSize, static_cast(m_series)}; if (isMainViewSelected()) { - drawHistogram(ctx, rect, EvaluateHistogramAtAbscissa, m_store, &totalSize, m_store->firstDrawnBarAbscissa(), m_store->barWidth(), true, Palette::Select, Palette::YellowDark, m_highlightedBarStart, m_highlightedBarEnd); + drawHistogram(ctx, rect, EvaluateHistogramAtAbscissa, m_store, context, m_store->firstDrawnBarAbscissa(), m_store->barWidth(), true, m_selectedHistogramColor, m_selectedBarColor, m_highlightedBarStart, m_highlightedBarEnd); } else { - drawHistogram(ctx, rect, EvaluateHistogramAtAbscissa, m_store, &totalSize, m_store->firstDrawnBarAbscissa(), m_store->barWidth(), true, Palette::GreyMiddle, Palette::YellowDark); + drawHistogram(ctx, rect, EvaluateHistogramAtAbscissa, m_store, context, m_store->firstDrawnBarAbscissa(), m_store->barWidth(), true, m_notSelectedHistogramColor, m_selectedBarColor); } } void HistogramView::setHighlight(float start, float end) { if (m_highlightedBarStart != start || m_highlightedBarEnd != end) { - reload(); + reloadSelectedBar(); m_highlightedBarStart = start; m_highlightedBarEnd = end; - reload(); + reloadSelectedBar(); } } char * HistogramView::label(Axis axis, int index) const { - if (axis == Axis::Vertical) { - return nullptr; - } - return (char *)m_labels[index]; + return axis == Axis::Vertical ? nullptr : (char *)m_labels[index]; } float HistogramView::EvaluateHistogramAtAbscissa(float abscissa, void * model, void * context) { Store * store = (Store *)model; - float * totalSize = (float *)context; - return store->heightOfBarAtValue(abscissa)/(*totalSize); + float totalSize = ((float *)context)[0]; + int series = ((float *)context)[1]; + return store->heightOfBarAtValue(series, abscissa)/(totalSize); } } diff --git a/apps/statistics/histogram_view.h b/apps/statistics/histogram_view.h index fc97095e3..f871135db 100644 --- a/apps/statistics/histogram_view.h +++ b/apps/statistics/histogram_view.h @@ -8,19 +8,30 @@ namespace Statistics { +class HistogramController; + class HistogramView : public Shared::CurveView { public: - HistogramView(Store * store, Shared::BannerView * bannerView); + HistogramView(HistogramController * controller, Store * store, int series, Shared::BannerView * bannerView, KDColor selectedHistogramColor = Palette::Select, KDColor notSelectedHistogramColor = Palette::GreyMiddle, KDColor selectedBarColor = Palette::YellowDark); + int series() const { return m_series; } void reload() override; + void reloadSelectedBar(); void drawRect(KDContext * ctx, KDRect rect) const override; void setHighlight(float start, float end); + void setDisplayLabels(bool display) { m_displayLabels = display; } private: char * label(Axis axis, int index) const override; + HistogramController * m_controller; Store * m_store; char m_labels[k_maxNumberOfXLabels][Poincare::PrintFloat::bufferSizeForFloatsWithPrecision(Constant::ShortNumberOfSignificantDigits)]; static float EvaluateHistogramAtAbscissa(float abscissa, void * model, void * context); float m_highlightedBarStart; float m_highlightedBarEnd; + int m_series; + KDColor m_selectedHistogramColor; + KDColor m_notSelectedHistogramColor; + KDColor m_selectedBarColor; + bool m_displayLabels; }; } diff --git a/apps/statistics/multiple_boxes_view.cpp b/apps/statistics/multiple_boxes_view.cpp new file mode 100644 index 000000000..dc4a8c24f --- /dev/null +++ b/apps/statistics/multiple_boxes_view.cpp @@ -0,0 +1,66 @@ +#include "multiple_boxes_view.h" +#include + +using namespace Shared; + +namespace Statistics { + +MultipleBoxesView::MultipleBoxesView(BoxController * controller, Store * store, BoxView::Quantile * selectedQuantile) : + MultipleDataView(store), + m_boxView1(controller, store, 0, nullptr, selectedQuantile, DoublePairStore::colorOfSeriesAtIndex(0), DoublePairStore::colorLightOfSeriesAtIndex(0)), + m_boxView2(controller, store, 1, nullptr, selectedQuantile, DoublePairStore::colorOfSeriesAtIndex(1), DoublePairStore::colorLightOfSeriesAtIndex(1)), + m_boxView3(controller, store, 2, nullptr, selectedQuantile, DoublePairStore::colorOfSeriesAtIndex(2), DoublePairStore::colorLightOfSeriesAtIndex(2)), + m_axisView(store), + m_bannerView() +{ + for (int i = 0; i < Store::k_numberOfSeries; i++) { + BoxView * boxView = dataViewAtIndex(i); + boxView->setDisplayBannerView(false); + } +} + +BoxView * MultipleBoxesView::dataViewAtIndex(int index) { + assert(index >= 0 && index < 3); + BoxView * views[] = {&m_boxView1, &m_boxView2, &m_boxView3}; + return views[index]; +} + +int MultipleBoxesView::seriesOfSubviewAtIndex(int index) { + assert(index >= 0 && index < numberOfSubviews() - 1); + return static_cast(subviewAtIndex(index))->series(); +} + +void MultipleBoxesView::layoutDataSubviews() { + int numberOfDataSubviews = m_store->numberOfNonEmptySeries(); + assert(numberOfDataSubviews > 0); + KDCoordinate bannerHeight = bannerFrame().height(); + KDCoordinate subviewHeight = (bounds().height() - bannerHeight - k_axisViewHeight)/numberOfDataSubviews; + int displayedSubviewIndex = 0; + for (int i = 0; i < Store::k_numberOfSeries; i++) { + if (!m_store->seriesIsEmpty(i)) { + KDRect frame = KDRect(0, displayedSubviewIndex*subviewHeight, bounds().width(), subviewHeight); + dataViewAtIndex(i)->setFrame(frame); + displayedSubviewIndex++; + } + } + m_axisView.setFrame(KDRect(0, displayedSubviewIndex*subviewHeight, bounds().width(), bounds().height() - bannerHeight - displayedSubviewIndex*subviewHeight)); +} + +void MultipleBoxesView::reload() { + MultipleDataView::reload(); + m_axisView.reload(); +} + +int MultipleBoxesView::numberOfSubviews() const { + return MultipleDataView::numberOfSubviews() + 1; // +1 for the axis view +} + +View * MultipleBoxesView::subviewAtIndex(int index) { + if (index == numberOfSubviews() - 1) { + return &m_axisView; + } + return MultipleDataView::subviewAtIndex(index); +} + + +} diff --git a/apps/statistics/multiple_boxes_view.h b/apps/statistics/multiple_boxes_view.h new file mode 100644 index 000000000..86f7bf986 --- /dev/null +++ b/apps/statistics/multiple_boxes_view.h @@ -0,0 +1,40 @@ +#ifndef STATISTICS_MULTIPLE_BOXES_VIEW_H +#define STATISTICS_MULTIPLE_BOXES_VIEW_H + +#include +#include "store.h" +#include "box_axis_view.h" +#include "box_banner_view.h" +#include "box_view.h" +#include "multiple_data_view.h" + +namespace Statistics { + +class BoxController; + +class MultipleBoxesView : public MultipleDataView { +public: + MultipleBoxesView(BoxController * controller, Store * store, BoxView::Quantile * selectedQuantile); + // MultipleDataView + int seriesOfSubviewAtIndex(int index) override; + const BoxBannerView * bannerView() const override { return &m_bannerView; } + BoxView * dataViewAtIndex(int index) override; + void layoutDataSubviews() override; + void reload() override; + + // View + int numberOfSubviews() const override; + View * subviewAtIndex(int index) override; + +private: + static constexpr KDCoordinate k_axisViewHeight = 21; + BoxView m_boxView1; + BoxView m_boxView2; + BoxView m_boxView3; + BoxAxisView m_axisView; + BoxBannerView m_bannerView; +}; + +} + +#endif diff --git a/apps/statistics/multiple_data_view.cpp b/apps/statistics/multiple_data_view.cpp new file mode 100644 index 000000000..2f0203ca9 --- /dev/null +++ b/apps/statistics/multiple_data_view.cpp @@ -0,0 +1,114 @@ +#include "multiple_data_view.h" +#include + +using namespace Shared; + +namespace Statistics { + +void MultipleDataView::reload() { + layoutSubviews(); + for (int i = 0; i < Store::k_numberOfSeries; i++) { + dataViewAtIndex(i)->reload(); + } +} + +int MultipleDataView::indexOfSubviewAtSeries(int series) { + int displayedSubviewIndex = 0; + for (int i = 0; i < Store::k_numberOfSeries; i++) { + if (!m_store->seriesIsEmpty(i)) { + if (i == series) { + return displayedSubviewIndex; + } + displayedSubviewIndex++; + } else if (i == series) { + return -1; + } + } + assert(false); + return -1; +} + +void MultipleDataView::selectDataView(int index) { + changeDataViewSelection(index, true); +} + +void MultipleDataView::deselectDataView(int index) { + changeDataViewSelection(index, false); +} + +int MultipleDataView::numberOfSubviews() const { + int result = m_store->numberOfNonEmptySeries(); + assert(result <= Store::k_numberOfSeries); + return result + 1; // +1 for the banner view +} + +View * MultipleDataView::subviewAtIndex(int index) { + if (index == MultipleDataView::numberOfSubviews() -1) { + return editableBannerView(); + } + int seriesIndex = 0; + int nonEmptySeriesIndex = -1; + while (seriesIndex < Store::k_numberOfSeries) { + if (!m_store->seriesIsEmpty(seriesIndex)) { + nonEmptySeriesIndex++; + if (nonEmptySeriesIndex == index) { + return dataViewAtIndex(seriesIndex); + } + } + seriesIndex++; + } + assert(false); + return nullptr; +} + +void MultipleDataView::layoutSubviews() { + // We need to set the banner width first, so its height can be computed + editableBannerView()->setFrame(KDRect(0, 0, bounds().width(), 0)); + layoutDataSubviews(); + layoutBanner(); +} + +void MultipleDataView::layoutDataSubviews() { + int numberDataSubviews = m_store->numberOfNonEmptySeries(); + assert(numberDataSubviews > 0); + KDCoordinate bannerHeight = bannerFrame().height(); + KDCoordinate subviewHeight = (bounds().height() - bannerHeight)/numberDataSubviews; + int displayedSubviewIndex = 0; + for (int i = 0; i < Store::k_numberOfSeries; i++) { + if (!m_store->seriesIsEmpty(i)) { + CurveView * dataView = dataViewAtIndex(i); + KDRect frame = KDRect(0, displayedSubviewIndex*subviewHeight, bounds().width(), subviewHeight); + dataView->setFrame(frame); + displayedSubviewIndex++; + } + } +} + +void MultipleDataView::changeDataViewSelection(int index, bool select) { + dataViewAtIndex(index)->selectMainView(select); + dataViewAtIndex(index)->reload(); +} + +KDRect MultipleDataView::bannerFrame() const { + KDCoordinate bannerHeight = bannerView()->minimalSizeForOptimalDisplay().height(); + KDRect frame = KDRect(0, bounds().height() - bannerHeight, bounds().width(), bannerHeight); + return frame; +} + +void MultipleDataView::layoutBanner() { + KDCoordinate bannerHeight = bannerFrame().height(); + if (m_displayBanner) { + editableBannerView()->setFrame(bannerFrame()); + } else { + KDRect frame = KDRect(0, bounds().height() - bannerHeight, bounds().width(), 0); + editableBannerView()->setFrame(frame); + } +} + +void MultipleDataView::drawRect(KDContext * ctx, KDRect rect) const { + if (!m_displayBanner) { + ctx->fillRect(bannerFrame(), KDColorWhite); + } +} + +} diff --git a/apps/statistics/multiple_data_view.h b/apps/statistics/multiple_data_view.h new file mode 100644 index 000000000..ec2c2c4e5 --- /dev/null +++ b/apps/statistics/multiple_data_view.h @@ -0,0 +1,50 @@ +#ifndef STATISTICS_MULTIPLE_DATA_VIEW_H +#define STATISTICS_MULTIPLE_DATA_VIEW_H + +#include +#include "store.h" +#include "../shared/banner_view.h" +#include "../shared/curve_view.h" + +namespace Statistics { + +class MultipleDataView : public View { +public: + static constexpr int k_defaultSelectedBar = 0; + MultipleDataView(Store * store) : + m_store(store), + m_displayBanner(false) + {} + // Data views + void selectDataView(int index); + void deselectDataView(int index); + virtual Shared::CurveView * dataViewAtIndex(int index) = 0; + Shared::BannerView * editableBannerView() { return const_cast(bannerView()); } + + // Index/series + int indexOfSubviewAtSeries(int series); + virtual int seriesOfSubviewAtIndex(int index) = 0; + + // Display + void setDisplayBanner(bool display) { m_displayBanner = display; } + virtual void reload(); + + // View + int numberOfSubviews() const override; +protected: + virtual const Shared::BannerView * bannerView() const = 0; + void layoutSubviews() override; + virtual void layoutDataSubviews(); + View * subviewAtIndex(int index) override; + virtual void changeDataViewSelection(int index, bool select); + KDRect bannerFrame() const; + Store * m_store; +private: + void layoutBanner(); + void drawRect(KDContext * ctx, KDRect rect) const override; + bool m_displayBanner; +}; + +} + +#endif diff --git a/apps/statistics/multiple_data_view_controller.cpp b/apps/statistics/multiple_data_view_controller.cpp new file mode 100644 index 000000000..876164e51 --- /dev/null +++ b/apps/statistics/multiple_data_view_controller.cpp @@ -0,0 +1,98 @@ +#include "multiple_data_view_controller.h" +#include "../i18n.h" +#include + +using namespace Shared; + +namespace Statistics { + +MultipleDataViewController::MultipleDataViewController(Responder * parentResponder, Store * store, int * selectedBarIndex, int * selectedSeriesIndex) : + ViewController(parentResponder), + m_store(store), + m_selectedSeriesIndex(selectedSeriesIndex), + m_selectedBarIndex(selectedBarIndex) +{ +} + +bool MultipleDataViewController::isEmpty() const { + return m_store->isEmpty(); +} + +I18n::Message MultipleDataViewController::emptyMessage() { + return I18n::Message::NoDataToPlot; +} + +Responder * MultipleDataViewController::defaultController() { + return tabController(); +} + +void MultipleDataViewController::viewWillAppear() { + multipleDataView()->setDisplayBanner(true); + if (*m_selectedSeriesIndex < 0) { + *m_selectedSeriesIndex = multipleDataView()->seriesOfSubviewAtIndex(0); + multipleDataView()->selectDataView(*m_selectedSeriesIndex); + } + reloadBannerView(); + multipleDataView()->reload(); +} + +bool MultipleDataViewController::handleEvent(Ion::Events::Event event) { + assert(*m_selectedSeriesIndex >= 0); + if (event == Ion::Events::Down) { + int currentSelectedSubview = multipleDataView()->indexOfSubviewAtSeries(*m_selectedSeriesIndex); + if (currentSelectedSubview < m_store->numberOfNonEmptySeries() - 1) { + multipleDataView()->deselectDataView(*m_selectedSeriesIndex); + *m_selectedSeriesIndex = multipleDataView()->seriesOfSubviewAtIndex(currentSelectedSubview+1); + *m_selectedBarIndex = MultipleDataView::k_defaultSelectedBar; + multipleDataView()->selectDataView(*m_selectedSeriesIndex); + reloadBannerView(); + app()->setFirstResponder(this); + return true; + } + return false; + } + if (event == Ion::Events::Up) { + int currentSelectedSubview = multipleDataView()->indexOfSubviewAtSeries(*m_selectedSeriesIndex); + if (currentSelectedSubview > 0) { + multipleDataView()->deselectDataView(*m_selectedSeriesIndex); + *m_selectedSeriesIndex = multipleDataView()->seriesOfSubviewAtIndex(currentSelectedSubview-1); + *m_selectedBarIndex = MultipleDataView::k_defaultSelectedBar; + multipleDataView()->selectDataView(*m_selectedSeriesIndex); + app()->setFirstResponder(this); + } else { + app()->setFirstResponder(tabController()); + } + reloadBannerView(); + return true; + } + if (*m_selectedSeriesIndex >= 0 && (event == Ion::Events::Left || event == Ion::Events::Right)) { + int direction = event == Ion::Events::Left ? -1 : 1; + moveSelectionHorizontally(direction); + return true; + } + return false; +} + +void MultipleDataViewController::didBecomeFirstResponder() { + multipleDataView()->setDisplayBanner(true); + if (*m_selectedSeriesIndex < 0 || m_store->sumOfOccurrences(*m_selectedSeriesIndex) == 0) { + if (*m_selectedSeriesIndex >= 0) { + multipleDataView()->deselectDataView(*m_selectedSeriesIndex); + } + *m_selectedSeriesIndex = multipleDataView()->seriesOfSubviewAtIndex(0); + multipleDataView()->selectDataView(*m_selectedSeriesIndex); + multipleDataView()->reload(); + } +} + +void MultipleDataViewController::willExitResponderChain(Responder * nextFirstResponder) { + if (nextFirstResponder == nullptr || nextFirstResponder == tabController()) { + if (*m_selectedSeriesIndex >= 0) { + multipleDataView()->dataViewAtIndex(*m_selectedSeriesIndex)->selectMainView(false); + *m_selectedSeriesIndex = -1; + multipleDataView()->setDisplayBanner(false); + } + } +} + +} diff --git a/apps/statistics/multiple_data_view_controller.h b/apps/statistics/multiple_data_view_controller.h new file mode 100644 index 000000000..e3dfa92ed --- /dev/null +++ b/apps/statistics/multiple_data_view_controller.h @@ -0,0 +1,41 @@ +#ifndef STATISTICS_MULTIPLE_DATA_VIEW_CONTROLLER_H +#define STATISTICS_MULTIPLE_DATA_VIEW_CONTROLLER_H + +#include +#include "store.h" +#include "multiple_data_view.h" + +namespace Statistics { + +class MultipleDataViewController : public ViewController, public AlternateEmptyViewDelegate { + +public: + MultipleDataViewController(Responder * parentResponder, Store * store, int * m_selectedBarIndex, int * selectedSeriesIndex); + virtual MultipleDataView * multipleDataView() = 0; + int selectedSeriesIndex() const { return *m_selectedSeriesIndex; } + // AlternateEmptyViewDelegate + bool isEmpty() const override; + I18n::Message emptyMessage() override; + Responder * defaultController() override; + + // ViewController + View * view() override { return multipleDataView(); } + void viewWillAppear() override; + + // Responder + bool handleEvent(Ion::Events::Event event) override; + void didBecomeFirstResponder() override; + void willExitResponderChain(Responder * nextFirstResponder) override; +protected: + virtual Responder * tabController() const = 0; + virtual void reloadBannerView() = 0; + virtual bool moveSelectionHorizontally(int deltaIndex) = 0; + Store * m_store; + int * m_selectedSeriesIndex; + int * m_selectedBarIndex; +}; + +} + + +#endif diff --git a/apps/statistics/multiple_histograms_view.cpp b/apps/statistics/multiple_histograms_view.cpp new file mode 100644 index 000000000..2ac29e4b3 --- /dev/null +++ b/apps/statistics/multiple_histograms_view.cpp @@ -0,0 +1,57 @@ +#include "multiple_histograms_view.h" +#include + +using namespace Shared; + +namespace Statistics { + +MultipleHistogramsView::MultipleHistogramsView(HistogramController * controller, Store * store) : + MultipleDataView(store), + m_histogramView1(controller, store, 0, nullptr, DoublePairStore::colorOfSeriesAtIndex(0)), + m_histogramView2(controller, store, 1, nullptr, DoublePairStore::colorOfSeriesAtIndex(1)), + m_histogramView3(controller, store, 2, nullptr, DoublePairStore::colorOfSeriesAtIndex(2)), + m_bannerView(), + m_okView() +{ + for (int i = 0; i < Store::k_numberOfSeries; i++) { + HistogramView * histView = dataViewAtIndex(i); + histView->setDisplayBannerView(false); + histView->setDisplayLabels(false); + histView->setForceOkDisplay(true); + } +} + +HistogramView * MultipleHistogramsView::dataViewAtIndex(int index) { + assert(index >= 0 && index < 3); + HistogramView * views[] = {&m_histogramView1, &m_histogramView2, &m_histogramView3}; + return views[index]; +} + +int MultipleHistogramsView::seriesOfSubviewAtIndex(int index) { + assert(index >= 0 && index < numberOfSubviews() - 1); + return static_cast(subviewAtIndex(index))->series(); +} + +void MultipleHistogramsView::layoutSubviews() { + MultipleDataView::layoutSubviews(); + int numberHistogramSubviews = m_store->numberOfNonEmptySeries(); + assert(numberHistogramSubviews > 0); + int displayedSubviewIndex = 0; + for (int i = 0; i < Store::k_numberOfSeries; i++) { + if (!m_store->seriesIsEmpty(i)) { + dataViewAtIndex(i)->setOkView(displayedSubviewIndex == numberHistogramSubviews - 1 ? &m_okView : nullptr); + displayedSubviewIndex++; + } + } +} + +void MultipleHistogramsView::changeDataViewSelection(int index, bool select) { + MultipleDataView::changeDataViewSelection(index, select); + dataViewAtIndex(index)->setDisplayLabels(select); + if (select == false) { + // Set the hightlight to default selected bar to prevent blinking + dataViewAtIndex(index)->setHighlight(m_store->startOfBarAtIndex(index, k_defaultSelectedBar), m_store->endOfBarAtIndex(index, k_defaultSelectedBar)); + } +} + +} diff --git a/apps/statistics/multiple_histograms_view.h b/apps/statistics/multiple_histograms_view.h new file mode 100644 index 000000000..34d02e041 --- /dev/null +++ b/apps/statistics/multiple_histograms_view.h @@ -0,0 +1,33 @@ +#ifndef STATISTICS_MULTIPLE_HISTOGRAMS_VIEW_H +#define STATISTICS_MULTIPLE_HISTOGRAMS_VIEW_H + +#include +#include "store.h" +#include "histogram_view.h" +#include "histogram_banner_view.h" +#include "histogram_parameter_controller.h" +#include "multiple_data_view.h" +#include "../shared/ok_view.h" + +namespace Statistics { + +class MultipleHistogramsView : public MultipleDataView { +public: + MultipleHistogramsView(HistogramController * controller, Store * store); + // MultipleDataView + int seriesOfSubviewAtIndex(int index) override; + const HistogramBannerView * bannerView() const override { return &m_bannerView; } + HistogramView * dataViewAtIndex(int index) override; +private: + void layoutSubviews() override; + void changeDataViewSelection(int index, bool select) override; + HistogramView m_histogramView1; + HistogramView m_histogramView2; + HistogramView m_histogramView3; + HistogramBannerView m_bannerView; + Shared::OkView m_okView; +}; + +} + +#endif diff --git a/apps/statistics/statistics_context.cpp b/apps/statistics/statistics_context.cpp new file mode 100644 index 000000000..301345d7c --- /dev/null +++ b/apps/statistics/statistics_context.cpp @@ -0,0 +1,33 @@ +#include "statistics_context.h" +#include +#include +#include + +using namespace Poincare; +using namespace Shared; + +namespace Statistics { + +const Expression * StatisticsContext::expressionForSymbol(const Symbol * symbol) { + if (Symbol::isSeriesSymbol(symbol->name())) { + const char * seriesName = Symbol::textForSpecialSymbols(symbol->name()); + assert(strlen(seriesName) == 2); + + int series = (int)(seriesName[1] - '0') - 1; + assert(series >= 0 && series < DoublePairStore::k_numberOfSeries); + + assert((seriesName[0] == 'V') || (seriesName[0] == 'N')); + int storeI = seriesName[0] == 'V' ? 0 : 1; + + assert(m_seriesPairIndex >= 0); + assert(m_seriesPairIndex < m_store->numberOfPairsOfSeries(series)); + + Expression * result = new Decimal(m_store->get(series, storeI, m_seriesPairIndex)); + assert(result != nullptr); + return result; + } else { + return m_parentContext->expressionForSymbol(symbol); + } +} + +} diff --git a/apps/statistics/statistics_context.h b/apps/statistics/statistics_context.h new file mode 100644 index 000000000..6263bf4fd --- /dev/null +++ b/apps/statistics/statistics_context.h @@ -0,0 +1,16 @@ +#ifndef STATISTICS_STATISTICS_CONTEXT_H +#define STATISTICS_STATISTICS_CONTEXT_H + +#include "../shared/store_context.h" + +namespace Statistics { + +class StatisticsContext : public Shared::StoreContext { +public: + using Shared::StoreContext::StoreContext; + const Poincare::Expression * expressionForSymbol(const Poincare::Symbol * symbol) override; +}; + +} + +#endif diff --git a/apps/statistics/store.cpp b/apps/statistics/store.cpp index 122ed2c25..f60bd3a3c 100644 --- a/apps/statistics/store.cpp +++ b/apps/statistics/store.cpp @@ -9,15 +9,19 @@ using namespace Shared; namespace Statistics { +static_assert(Store::k_numberOfSeries == 3, "The constructor of Statistics::Store should be changed"); + Store::Store() : MemoizedCurveViewRange(), - FloatPairStore(), + DoublePairStore(), m_barWidth(1.0), - m_firstDrawnBarAbscissa(0.0) + m_firstDrawnBarAbscissa(0.0), + m_seriesEmpty{true, true, true}, + m_numberOfNonEmptySeries(0) { } -uint32_t Store::barChecksum() { +uint32_t Store::barChecksum() const { double data[2] = {m_barWidth, m_firstDrawnBarAbscissa}; size_t dataLengthInBytes = 2*sizeof(double); assert((dataLengthInBytes & 0x3) == 0); // Assert that dataLengthInBytes is a multiple of 4 @@ -26,53 +30,40 @@ uint32_t Store::barChecksum() { /* Histogram bars */ -double Store::barWidth() { - return m_barWidth; -} - void Store::setBarWidth(double barWidth) { - if (barWidth <= 0.0) { - return; + if (barWidth > 0.0) { + m_barWidth = barWidth; } - m_barWidth = barWidth; } -double Store::firstDrawnBarAbscissa() { - return m_firstDrawnBarAbscissa; +double Store::heightOfBarAtIndex(int series, int index) const { + return sumOfValuesBetween(series, startOfBarAtIndex(series, index), endOfBarAtIndex(series, index)); } -void Store::setFirstDrawnBarAbscissa(double firstBarAbscissa) { - m_firstDrawnBarAbscissa = firstBarAbscissa; -} - -double Store::heightOfBarAtIndex(int index) { - return sumOfValuesBetween(startOfBarAtIndex(index), endOfBarAtIndex(index)); -} - -double Store::heightOfBarAtValue(double value) { +double Store::heightOfBarAtValue(int series, double value) const { double width = barWidth(); int barNumber = std::floor((value - m_firstDrawnBarAbscissa)/width); double lowerBound = m_firstDrawnBarAbscissa + barNumber*width; double upperBound = m_firstDrawnBarAbscissa + (barNumber+1)*width; - return sumOfValuesBetween(lowerBound, upperBound); + return sumOfValuesBetween(series, lowerBound, upperBound); } -double Store::startOfBarAtIndex(int index) { - double firstBarAbscissa = m_firstDrawnBarAbscissa + m_barWidth*std::floor((minValue()- m_firstDrawnBarAbscissa)/m_barWidth); +double Store::startOfBarAtIndex(int series, int index) const { + double firstBarAbscissa = m_firstDrawnBarAbscissa + m_barWidth*std::floor((minValue(series)- m_firstDrawnBarAbscissa)/m_barWidth); return firstBarAbscissa + index * m_barWidth; } -double Store::endOfBarAtIndex(int index) { - return startOfBarAtIndex(index+1); +double Store::endOfBarAtIndex(int series, int index) const { + return startOfBarAtIndex(series, index+1); } -double Store::numberOfBars() { - double firstBarAbscissa = m_firstDrawnBarAbscissa + m_barWidth*std::floor((minValue()- m_firstDrawnBarAbscissa)/m_barWidth); - return std::ceil((maxValue() - firstBarAbscissa)/m_barWidth)+1; +double Store::numberOfBars(int series) const { + double firstBarAbscissa = m_firstDrawnBarAbscissa + m_barWidth*std::floor((minValue(series)- m_firstDrawnBarAbscissa)/m_barWidth); + return std::ceil((maxValue(series) - firstBarAbscissa)/m_barWidth)+1; } -bool Store::scrollToSelectedBarIndex(int index) { - float startSelectedBar = startOfBarAtIndex(index); +bool Store::scrollToSelectedBarIndex(int series, int index) { + float startSelectedBar = startOfBarAtIndex(series, index); float windowRange = m_xMax - m_xMin; float range = windowRange/(1+k_displayLeftMarginRatio+k_displayRightMarginRatio); if (m_xMin + k_displayLeftMarginRatio*range > startSelectedBar) { @@ -80,7 +71,7 @@ bool Store::scrollToSelectedBarIndex(int index) { m_xMax = m_xMin + windowRange; return true; } - float endSelectedBar = endOfBarAtIndex(index); + float endSelectedBar = endOfBarAtIndex(series, index); if (endSelectedBar > m_xMax - k_displayRightMarginRatio*range) { m_xMax = endSelectedBar + k_displayRightMarginRatio*range; m_xMin = m_xMax - windowRange; @@ -89,133 +80,229 @@ bool Store::scrollToSelectedBarIndex(int index) { return false; } -/* Calculation */ - -double Store::sumOfOccurrences() { - return sumOfColumn(1); +bool Store::isEmpty() const { + for (int i = 0; i < k_numberOfSeries; i ++) { + if (!seriesIsEmpty(i)) { + return false; + } + } + return true; } -double Store::maxValue() { +int Store::numberOfNonEmptySeries() const { + return m_numberOfNonEmptySeries; +} + +bool Store::seriesIsEmpty(int i) const { + return m_seriesEmpty[i]; +} + +int Store::indexOfKthNonEmptySeries(int k) const { + assert(k >= 0 && k < numberOfNonEmptySeries()); + int nonEmptySeriesCount = 0; + for (int i = 0; i < k_numberOfSeries; i++) { + if (!seriesIsEmpty(i)) { + if (nonEmptySeriesCount == k) { + return i; + } + nonEmptySeriesCount++; + } + } + assert(false); + return 0; +} + +/* Calculation */ + +double Store::sumOfOccurrences(int series) const { + return sumOfColumn(series, 1); +} + +double Store::maxValueForAllSeries() const { + assert(DoublePairStore::k_numberOfSeries > 0); + double result = maxValue(0); + for (int i = 1; i < DoublePairStore::k_numberOfSeries; i++) { + double maxCurrentSeries = maxValue(i); + if (result < maxCurrentSeries) { + result = maxCurrentSeries; + } + } + return result; +} + +double Store::minValueForAllSeries() const { + assert(DoublePairStore::k_numberOfSeries > 0); + double result = minValue(0); + for (int i = 1; i < DoublePairStore::k_numberOfSeries; i++) { + double minCurrentSeries = minValue(i); + if (result > minCurrentSeries) { + result = minCurrentSeries; + } + } + return result; +} + +double Store::maxValue(int series) const { double max = -DBL_MAX; - for (int k = 0; k < m_numberOfPairs; k++) { - if (m_data[0][k] > max && m_data[1][k] > 0) { - max = m_data[0][k]; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + if (m_data[series][0][k] > max && m_data[series][1][k] > 0) { + max = m_data[series][0][k]; } } return max; } -double Store::minValue() { +double Store::minValue(int series) const { double min = DBL_MAX; - for (int k = 0; k < m_numberOfPairs; k++) { - if (m_data[0][k] < min && m_data[1][k] > 0) { - min = m_data[0][k]; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + if (m_data[series][0][k] < min && m_data[series][1][k] > 0) { + min = m_data[series][0][k]; } } return min; } -double Store::range() { - return maxValue()-minValue(); +double Store::range(int series) const { + return maxValue(series)-minValue(series); } -double Store::mean() { - return sum()/sumOfColumn(1); +double Store::mean(int series) const { + return sum(series)/sumOfOccurrences(series); } -double Store::variance() { - double m = mean(); - return squaredValueSum()/sumOfColumn(1) - m*m; +double Store::variance(int series) const { + double m = mean(series); + return squaredValueSum(series)/sumOfOccurrences(series) - m*m; } -double Store::standardDeviation() { - return std::sqrt(variance()); +double Store::standardDeviation(int series) const { + return std::sqrt(variance(series)); } -double Store::sampleStandardDeviation() { - double n = sumOfColumn(1); +double Store::sampleStandardDeviation(int series) const { + double n = sumOfOccurrences(series); double s = std::sqrt(n/(n-1.0)); - return s*standardDeviation(); + return s*standardDeviation(series); } -double Store::firstQuartile() { - int firstQuartileIndex = std::ceil(sumOfColumn(1)/4); - return sortedElementNumber(firstQuartileIndex); +double Store::firstQuartile(int series) const { + return sortedElementAtCumulatedFrequency(series, 1.0/4.0); } -double Store::thirdQuartile() { - int thirdQuartileIndex = std::ceil(3*sumOfColumn(1)/4); - return sortedElementNumber(thirdQuartileIndex); +double Store::thirdQuartile(int series) const { + return sortedElementAtCumulatedFrequency(series, 3.0/4.0); } -double Store::quartileRange() { - return thirdQuartile()-firstQuartile(); +double Store::quartileRange(int series) const { + return thirdQuartile(series)-firstQuartile(series); } -double Store::median() { - int total = sumOfColumn(1); - int halfTotal = total/2; - int totalMod2 = total - 2*halfTotal; - if (totalMod2 == 0) { - double minusMedian = sortedElementNumber(halfTotal); - double maxMedian = sortedElementNumber(halfTotal+1); - return (minusMedian+maxMedian)/2.0; +double Store::median(int series) const { + bool exactElement = true; + double minMedian = sortedElementAtCumulatedFrequency(series, 1.0/2.0, &exactElement); + if (!exactElement) { + double maxMedian = sortedElementAfter(series, minMedian); + if (maxMedian == DBL_MAX) { + return minMedian; + } + return (minMedian + maxMedian)/2.0; } else { - return sortedElementNumber(halfTotal+1); + return minMedian; } } -double Store::sum() { +double Store::sum(int series) const { double result = 0; - for (int k = 0; k < m_numberOfPairs; k++) { - result += m_data[0][k]*m_data[1][k]; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + result += m_data[series][0][k]*m_data[series][1][k]; } return result; } -double Store::squaredValueSum() { +double Store::squaredValueSum(int series) const { double result = 0; - for (int k = 0; k < m_numberOfPairs; k++) { - result += m_data[0][k]*m_data[0][k]*m_data[1][k]; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + result += m_data[series][0][k]*m_data[series][0][k]*m_data[series][1][k]; } return result; } -/* private methods */ - -double Store::defaultValue(int i, int j) { - if (i == 0) { - return FloatPairStore::defaultValue(i, j); - } else { - return 1.0; - } +void Store::set(double f, int series, int i, int j) { + DoublePairStore::set(f, series, i, j); + m_seriesEmpty[series] = sumOfOccurrences(series) == 0; + updateNonEmptySeriesCount(); } -double Store::sumOfValuesBetween(double x1, double x2) { - int result = 0; - for (int k = 0; k < m_numberOfPairs; k++) { - if (m_data[0][k] < x2 && x1 <= m_data[0][k]) { - result += m_data[1][k]; +void Store::deletePairOfSeriesAtIndex(int series, int j) { + DoublePairStore::deletePairOfSeriesAtIndex(series, j); + m_seriesEmpty[series] = sumOfOccurrences(series) == 0; + updateNonEmptySeriesCount(); +} + +void Store::deleteAllPairsOfSeries(int series) { + DoublePairStore::deleteAllPairsOfSeries(series); + m_seriesEmpty[series] = true; + updateNonEmptySeriesCount(); +} + +void Store::updateNonEmptySeriesCount() { + int nonEmptySeriesCount = 0; + for (int i = 0; i< k_numberOfSeries; i++) { + if (!m_seriesEmpty[i]) { + nonEmptySeriesCount++; + } + } + m_numberOfNonEmptySeries = nonEmptySeriesCount; +} + +/* Private methods */ + +double Store::defaultValue(int series, int i, int j) const { + return i == 0 ? DoublePairStore::defaultValue(series, i, j) : 1.0; +} + +double Store::sumOfValuesBetween(int series, double x1, double x2) const { + double result = 0; + for (int k = 0; k < numberOfPairsOfSeries(series); k++) { + if (m_data[series][0][k] < x2 && x1 <= m_data[series][0][k]) { + result += m_data[series][1][k]; } } return result; } -double Store::sortedElementNumber(int k) { +double Store::sortedElementAtCumulatedFrequency(int series, double k, bool * exactElement) const { // TODO: use an other algorithm (ex quickselect) to avoid quadratic complexity - double bufferValues[m_numberOfPairs]; - memcpy(bufferValues, m_data[0], m_numberOfPairs*sizeof(double)); + assert(k >= 0.0 && k <= 1.0); + double totalNumberOfElements = sumOfOccurrences(series); + double bufferValues[numberOfPairsOfSeries(series)]; + memcpy(bufferValues, m_data[series][0], numberOfPairsOfSeries(series)*sizeof(double)); int sortedElementIndex = 0; - double cumulatedSize = 0.0; - while (cumulatedSize < k) { - sortedElementIndex = minIndex(bufferValues, m_numberOfPairs); + double cumulatedFrequency = 0.0; + while (cumulatedFrequency < k) { + sortedElementIndex = minIndex(bufferValues, numberOfPairsOfSeries(series)); bufferValues[sortedElementIndex] = DBL_MAX; - cumulatedSize += m_data[1][sortedElementIndex]; + cumulatedFrequency += m_data[series][1][sortedElementIndex] / totalNumberOfElements; + if (exactElement != nullptr && cumulatedFrequency == k) { + *exactElement = false; + } } - return m_data[0][sortedElementIndex]; + return m_data[series][0][sortedElementIndex]; } -int Store::minIndex(double * bufferValues, int bufferLength) { +double Store::sortedElementAfter(int series, double k) const { + assert(numberOfPairsOfSeries(series) > 0); + double result = DBL_MAX; + for (int i = 0; i < numberOfPairsOfSeries(series); i++) { + double currentElement = m_data[series][0][i]; + if (currentElement > k && currentElement < result) { + result = currentElement; + } + } + return result; +} + +int Store::minIndex(double * bufferValues, int bufferLength) const { int index = 0; for (int i = 1; i < bufferLength; i++) { if (bufferValues[index] > bufferValues[i]) { diff --git a/apps/statistics/store.h b/apps/statistics/store.h index c627b4c50..fa4839c83 100644 --- a/apps/statistics/store.h +++ b/apps/statistics/store.h @@ -2,58 +2,75 @@ #define STATISTICS_STORE_H #include "../shared/memoized_curve_view_range.h" -#include "../shared/float_pair_store.h" +#include "../shared/double_pair_store.h" namespace Statistics { -class Store : public Shared::MemoizedCurveViewRange, public Shared::FloatPairStore { +class Store : public Shared::MemoizedCurveViewRange, public Shared::DoublePairStore { public: Store(); - uint32_t barChecksum(); + uint32_t barChecksum() const; // Histogram bars - double barWidth(); + double barWidth() const { return m_barWidth; } void setBarWidth(double barWidth); - double firstDrawnBarAbscissa(); - void setFirstDrawnBarAbscissa(double firstDrawnBarAbscissa); - double heightOfBarAtIndex(int index); - double heightOfBarAtValue(double value); - double startOfBarAtIndex(int index); - double endOfBarAtIndex(int index); - double numberOfBars(); + double firstDrawnBarAbscissa() const { return m_firstDrawnBarAbscissa; } + void setFirstDrawnBarAbscissa(double firstDrawnBarAbscissa) { m_firstDrawnBarAbscissa = firstDrawnBarAbscissa;} + double heightOfBarAtIndex(int series, int index) const; + double heightOfBarAtValue(int series, double value) const; + double startOfBarAtIndex(int series, int index) const; + double endOfBarAtIndex(int series, int index) const; + double numberOfBars(int series) const; // return true if the window has scrolled - bool scrollToSelectedBarIndex(int index); + bool scrollToSelectedBarIndex(int series, int index); + bool isEmpty() const; + int numberOfNonEmptySeries() const; + bool seriesIsEmpty(int i) const; + int indexOfKthNonEmptySeries(int k) const; // Calculation - double sumOfOccurrences(); - double maxValue(); - double minValue(); - double range(); - double mean(); - double variance(); - double standardDeviation(); - double sampleStandardDeviation(); - double firstQuartile(); - double thirdQuartile(); - double quartileRange(); - double median(); - double sum(); - double squaredValueSum(); + double sumOfOccurrences(int series) const; + double maxValueForAllSeries() const; + double minValueForAllSeries() const; + double maxValue(int series) const; + double minValue(int series) const; + double range(int series) const; + double mean(int series) const; + double variance(int series) const; + double standardDeviation(int series) const; + double sampleStandardDeviation(int series) const; + double firstQuartile(int series) const; + double thirdQuartile(int series) const; + double quartileRange(int series) const; + double median(int series) const; + double sum(int series) const; + double squaredValueSum(int series) const; constexpr static double k_maxNumberOfBars = 10000.0; constexpr static float k_displayTopMarginRatio = 0.1f; constexpr static float k_displayRightMarginRatio = 0.04f; - constexpr static float k_displayBottomMarginRatio = 0.4f; + constexpr static int k_bottomMargin = 20; constexpr static float k_displayLeftMarginRatio = 0.04f; + + // DoublePairStore + void set(double f, int series, int i, int j) override; + void deletePairOfSeriesAtIndex(int series, int j) override; + void deleteAllPairsOfSeries(int series) override; + + void updateNonEmptySeriesCount(); + private: - double defaultValue(int i, int j) override; - double sumOfValuesBetween(double x1, double x2); - double sortedElementNumber(int k); - int minIndex(double * bufferValues, int bufferLength); + double defaultValue(int series, int i, int j) const override; + double sumOfValuesBetween(int series, double x1, double x2) const; + double sortedElementAtCumulatedFrequency(int series, double k, bool * exactElement = nullptr) const; + double sortedElementAfter(int series, double k) const; + int minIndex(double * bufferValues, int bufferLength) const; // Histogram bars double m_barWidth; double m_firstDrawnBarAbscissa; + bool m_seriesEmpty[k_numberOfSeries]; + int m_numberOfNonEmptySeries; }; -typedef double (Store::*CalculPointer)(); +typedef double (Store::*CalculPointer)(int) const; } diff --git a/apps/statistics/store_controller.cpp b/apps/statistics/store_controller.cpp index 0b9a8714a..a7a437be3 100644 --- a/apps/statistics/store_controller.cpp +++ b/apps/statistics/store_controller.cpp @@ -1,4 +1,5 @@ #include "store_controller.h" +#include "statistics_context.h" #include "app.h" #include "../apps_container.h" #include "../constant.h" @@ -6,27 +7,53 @@ #include #include +using namespace Poincare; using namespace Shared; namespace Statistics { StoreController::StoreController(Responder * parentResponder, Store * store, ButtonRowController * header) : Shared::StoreController(parentResponder, store, header), - m_titleCells{} + m_titleCells{}, + m_store(store), + m_statisticsContext(m_store) { } +StoreContext * StoreController::storeContext() { + m_statisticsContext.setParentContext(const_cast(static_cast(app()->container()))->globalContext()); + return &m_statisticsContext; +} + +void StoreController::setFormulaLabel() { + int series = selectedColumn() / Store::k_numberOfColumnsPerSeries; + int isValueColumn = selectedColumn() % Store::k_numberOfColumnsPerSeries == 0; + char text[] = {isValueColumn ? 'V' : 'N', static_cast('1' + series), '=', 0}; + static_cast(view())->formulaInputView()->setBufferText(text); +} + +bool StoreController::fillColumnWithFormula(Expression * formula) { + return privateFillColumnWithFormula(formula, Symbol::isSeriesSymbol); +} + void StoreController::willDisplayCellAtLocation(HighlightCell * cell, int i, int j) { - ::StoreController::willDisplayCellAtLocation(cell, i, j); + Shared::StoreController::willDisplayCellAtLocation(cell, i, j); if (cellAtLocationIsEditable(i, j)) { return; } - EvenOddMessageTextCell * mytitleCell = (EvenOddMessageTextCell *)cell; - if (i == 0) { - mytitleCell->setMessage(I18n::Message::Values); - return; + Shared::StoreTitleCell * mytitleCell = static_cast(cell); + bool isValuesColumn = i%Store::k_numberOfColumnsPerSeries == 0; + mytitleCell->setSeparatorLeft(isValuesColumn); + int seriesIndex = i/Store::k_numberOfColumnsPerSeries; + assert(seriesIndex >= 0 && seriesIndex < DoublePairStore::k_numberOfSeries); + if (isValuesColumn) { + I18n::Message valuesMessages[] = {I18n::Message::Values1, I18n::Message::Values2, I18n::Message::Values3}; + mytitleCell->setText(I18n::translate(valuesMessages[seriesIndex])); + } else { + I18n::Message sizesMessages[] = {I18n::Message::Sizes1, I18n::Message::Sizes2, I18n::Message::Sizes3}; + mytitleCell->setText(I18n::translate(sizesMessages[seriesIndex])); } - mytitleCell->setMessage(I18n::Message::Sizes); + mytitleCell->setColor(m_store->numberOfPairsOfSeries(seriesIndex) == 0 ? Palette::GreyDark : Store::colorOfSeriesAtIndex(seriesIndex)); // TODO Share GreyDark with graph/list_controller } HighlightCell * StoreController::titleCells(int index) { @@ -38,19 +65,17 @@ bool StoreController::setDataAtLocation(double floatBody, int columnIndex, int r if (std::fabs(floatBody) > FLT_MAX) { return false; } - if (columnIndex == 1) { + if (columnIndex % Store::k_numberOfColumnsPerSeries == 1) { if (floatBody < 0) { return false; } - m_store->set(std::round(floatBody), columnIndex, rowIndex-1); - return true; } return Shared::StoreController::setDataAtLocation(floatBody, columnIndex, rowIndex); } View * StoreController::loadView() { for (int i = 0; i < k_numberOfTitleCells; i++) { - m_titleCells[i] = new EvenOddMessageTextCell(KDText::FontSize::Small); + m_titleCells[i] = new Shared::StoreTitleCell(); } return Shared::StoreController::loadView(); } diff --git a/apps/statistics/store_controller.h b/apps/statistics/store_controller.h index e3ded8a4f..bd95da265 100644 --- a/apps/statistics/store_controller.h +++ b/apps/statistics/store_controller.h @@ -3,20 +3,27 @@ #include #include "store.h" +#include "statistics_context.h" #include "../shared/store_controller.h" +#include "../shared/store_title_cell.h" namespace Statistics { class StoreController : public Shared::StoreController { public: StoreController(Responder * parentResponder, Store * store, ButtonRowController * header); + Shared::StoreContext * storeContext() override; + void setFormulaLabel() override; + bool fillColumnWithFormula(Poincare::Expression * formula) override; void willDisplayCellAtLocation(HighlightCell * cell, int i, int j) override; private: bool setDataAtLocation(double floatBody, int columnIndex, int rowIndex) override; HighlightCell * titleCells(int index) override; View * loadView() override; void unloadView(View * view) override; - EvenOddMessageTextCell * m_titleCells[k_numberOfTitleCells]; + Shared::StoreTitleCell * m_titleCells[k_numberOfTitleCells]; + Store * m_store; + StatisticsContext m_statisticsContext; }; } diff --git a/escher/include/escher/metric.h b/escher/include/escher/metric.h index a2a7a1021..42e3211c0 100644 --- a/escher/include/escher/metric.h +++ b/escher/include/escher/metric.h @@ -29,6 +29,7 @@ public: constexpr static KDCoordinate FractionAndConjugateHorizontalOverflow = 2; constexpr static KDCoordinate FractionAndConjugateHorizontalMargin = 2; constexpr static KDCoordinate MinimalBracketAndParenthesisHeight = 18; + constexpr static KDCoordinate TableSeparatorThickness = 2; }; #endif diff --git a/escher/include/escher/palette.h b/escher/include/escher/palette.h index ad47ce19d..c78c6aea9 100644 --- a/escher/include/escher/palette.h +++ b/escher/include/escher/palette.h @@ -21,12 +21,17 @@ public: constexpr static KDColor SubTab = KDColor::RGB24(0xb8bbc5); constexpr static KDColor LowBattery = KDColor::RGB24(0xf30211); constexpr static KDColor Red = KDColor::RGB24(0xff000c); + constexpr static KDColor RedLight = KDColor::RGB24(0xfe6363); constexpr static KDColor Magenta = KDColor::RGB24(0xff0588); constexpr static KDColor Turquoise = KDColor::RGB24(0x60c1ec); constexpr static KDColor Pink = KDColor::RGB24(0xffabb6); constexpr static KDColor Blue = KDColor::RGB24(0x5075f2); + constexpr static KDColor BlueLight = KDColor::RGB24(0x718fee); constexpr static KDColor Orange = KDColor::RGB24(0xfe871f); - constexpr static KDColor Green = KDColor::RGB24(0x76d435); + constexpr static KDColor Green = KDColor::RGB24(0x50c102); + constexpr static KDColor GreenLight = KDColor::RGB24(0x52db8f); + constexpr static KDColor DataColor[] = {Red, Blue, Green, YellowDark}; + constexpr static KDColor DataColorLight[] = {RedLight, BlueLight, GreenLight, YellowLight}; }; #endif diff --git a/escher/include/escher/text_view.h b/escher/include/escher/text_view.h index 6baef8b84..e2309a661 100644 --- a/escher/include/escher/text_view.h +++ b/escher/include/escher/text_view.h @@ -24,7 +24,6 @@ protected: const char * className() const override; #endif KDText::FontSize m_fontSize; -private: float m_horizontalAlignment; float m_verticalAlignment; KDColor m_textColor; diff --git a/escher/src/even_odd_cell.cpp b/escher/src/even_odd_cell.cpp index 3b6671f8a..4e6b0bb9f 100644 --- a/escher/src/even_odd_cell.cpp +++ b/escher/src/even_odd_cell.cpp @@ -9,8 +9,8 @@ EvenOddCell::EvenOddCell() : void EvenOddCell::setEven(bool even) { if (even != m_even) { - m_even = even; - reloadCell(); + m_even = even; + reloadCell(); } } diff --git a/escher/src/palette.cpp b/escher/src/palette.cpp index 59da5fbb7..ba4b72151 100644 --- a/escher/src/palette.cpp +++ b/escher/src/palette.cpp @@ -16,9 +16,14 @@ constexpr KDColor Palette::WallScreenDark; constexpr KDColor Palette::SubTab; constexpr KDColor Palette::LowBattery; constexpr KDColor Palette::Red; +constexpr KDColor Palette::RedLight; constexpr KDColor Palette::Magenta; constexpr KDColor Palette::Turquoise; constexpr KDColor Palette::Pink; constexpr KDColor Palette::Blue; +constexpr KDColor Palette::BlueLight; constexpr KDColor Palette::Orange; constexpr KDColor Palette::Green; +constexpr KDColor Palette::GreenLight; +constexpr KDColor Palette::DataColor[]; +constexpr KDColor Palette::DataColorLight[]; diff --git a/escher/src/table_view.cpp b/escher/src/table_view.cpp index 333eb0f6e..69c7a231d 100644 --- a/escher/src/table_view.cpp +++ b/escher/src/table_view.cpp @@ -160,7 +160,9 @@ View * TableView::ContentView::subviewAtIndex(int index) { } void TableView::ContentView::layoutSubviews() { - for (int index=0; indexgetVariables(variables); - if (n < 0) { - return -1; - } - numberOfVariables = n > numberOfVariables ? n : numberOfVariables; - } - return numberOfVariables; +int Expression::getVariables(isVariableTest isVariable, char * variables) const { + int numberOfVariables = 0; + for (int i = 0; i < numberOfOperands(); i++) { + int n = operand(i)->getVariables(isVariable, variables); + if (n < 0) { + return -1; + } + numberOfVariables = n > numberOfVariables ? n : numberOfVariables; + } + return numberOfVariables; } bool dependsOnVariables(const Expression * e, Context & context) { - return e->type() == Expression::Type::Symbol && static_cast(e)->isVariableSymbol(); + return e->type() == Expression::Type::Symbol && Symbol::isVariableSymbol(static_cast(e)->name()); } bool Expression::getLinearCoefficients(char * variables, Expression * coefficients[], Expression ** constant, Context & context) const { diff --git a/poincare/src/expression_lexer.l b/poincare/src/expression_lexer.l index 0dd8fc4d2..a5e2c4836 100644 --- a/poincare/src/expression_lexer.l +++ b/poincare/src/expression_lexer.l @@ -93,6 +93,18 @@ u\_\{n\} { poincare_expression_yylval.character = Symbol::SpecialSymbols::un; re u\_\{n\+1\} { poincare_expression_yylval.character = Symbol::SpecialSymbols::un1; return SYMBOL; } v\_\{n\} { poincare_expression_yylval.character = Symbol::SpecialSymbols::vn; return SYMBOL; } v\_\{n\+1\} { poincare_expression_yylval.character = Symbol::SpecialSymbols::vn1; return SYMBOL; } +V1 { poincare_expression_yylval.character = Symbol::SpecialSymbols::V1; return SYMBOL; } +N1 { poincare_expression_yylval.character = Symbol::SpecialSymbols::N1; return SYMBOL; } +V2 { poincare_expression_yylval.character = Symbol::SpecialSymbols::V2; return SYMBOL; } +N2 { poincare_expression_yylval.character = Symbol::SpecialSymbols::N2; return SYMBOL; } +V3 { poincare_expression_yylval.character = Symbol::SpecialSymbols::V3; return SYMBOL; } +N3 { poincare_expression_yylval.character = Symbol::SpecialSymbols::N3; return SYMBOL; } +X1 { poincare_expression_yylval.character = Symbol::SpecialSymbols::X1; return SYMBOL; } +Y1 { poincare_expression_yylval.character = Symbol::SpecialSymbols::Y1; return SYMBOL; } +X2 { poincare_expression_yylval.character = Symbol::SpecialSymbols::X2; return SYMBOL; } +Y2 { poincare_expression_yylval.character = Symbol::SpecialSymbols::Y2; return SYMBOL; } +X3 { poincare_expression_yylval.character = Symbol::SpecialSymbols::X3; return SYMBOL; } +Y3 { poincare_expression_yylval.character = Symbol::SpecialSymbols::Y3; return SYMBOL; } acos { poincare_expression_yylval.expression = new ArcCosine(); return FUNCTION; } acosh { poincare_expression_yylval.expression = new HyperbolicArcCosine(); return FUNCTION; } abs { poincare_expression_yylval.expression = new AbsoluteValue(); return FUNCTION; } diff --git a/poincare/src/symbol.cpp b/poincare/src/symbol.cpp index f8c95e2ff..21a349b6a 100644 --- a/poincare/src/symbol.cpp +++ b/poincare/src/symbol.cpp @@ -21,7 +21,7 @@ extern "C" { namespace Poincare { -const char * Symbol::textForSpecialSymbols(char name) const { +const char * Symbol::textForSpecialSymbols(char name) { switch (name) { case SpecialSymbols::Ans: return "ans"; @@ -53,12 +53,56 @@ const char * Symbol::textForSpecialSymbols(char name) const { return "M8"; case SpecialSymbols::M9: return "M9"; + case SpecialSymbols::V1: + return "V1"; + case SpecialSymbols::N1: + return "N1"; + case SpecialSymbols::V2: + return "V2"; + case SpecialSymbols::N2: + return "N2"; + case SpecialSymbols::V3: + return "V3"; + case SpecialSymbols::N3: + return "N3"; + case SpecialSymbols::X1: + return "X1"; + case SpecialSymbols::Y1: + return "Y1"; + case SpecialSymbols::X2: + return "X2"; + case SpecialSymbols::Y2: + return "Y2"; + case SpecialSymbols::X3: + return "X3"; + case SpecialSymbols::Y3: + return "Y3"; default: assert(false); return nullptr; } } +int Symbol::getVariables(isVariableTest isVariable, char * variables) const { + size_t variablesLength = strlen(variables); + if (isVariable(m_name)) { + char * currentChar = variables; + while (*currentChar != 0) { + if (*currentChar == m_name) { + return variablesLength; + } + currentChar++; + } + if (variablesLength < k_maxNumberOfVariables) { + variables[variablesLength] = m_name; + variables[variablesLength+1] = 0; + return variablesLength+1; + } + return -1; + } + return variablesLength; +} + Symbol::SpecialSymbols Symbol::matrixSymbol(char index) { switch (index - '0') { case 0: @@ -113,26 +157,6 @@ int Symbol::polynomialDegree(char symbol) const { return 0; } -int Symbol::getVariables(char * variables) const { - size_t variablesLength = strlen(variables); - if (isVariableSymbol()) { - char * currentChar = variables; - while (*currentChar != 0) { - if (*currentChar == m_name) { - return variablesLength; - } - currentChar++; - } - if (variablesLength < k_maxNumberOfVariables) { - variables[variablesLength] = m_name; - variables[variablesLength+1] = 0; - return variablesLength+1; - } - return -1; - } - return variablesLength; -} - int Symbol::privateGetPolynomialCoefficients(char symbolName, Expression * coefficients[]) const { if (m_name == symbolName) { coefficients[0] = new Rational(0); @@ -262,9 +286,8 @@ ExpressionLayout * Symbol::privateCreateLayout(PrintFloat::Mode floatDisplayMode false), false); } - if (isMatrixSymbol()) { - const char mi[] = { 'M', (char)(m_name-(char)SpecialSymbols::M0+'0') }; - return LayoutEngine::createStringLayout(mi, sizeof(mi)); + if (isMatrixSymbol() || isSeriesSymbol(m_name) || isRegressionSymbol(m_name)) { + return LayoutEngine::createStringLayout(textForSpecialSymbols(m_name), 2); } return LayoutEngine::createStringLayout(&m_name, 1); } @@ -300,8 +323,22 @@ bool Symbol::isScalarSymbol() const { return false; } -bool Symbol::isVariableSymbol() const { - if (m_name >= 'a' && m_name <= 'z') { +bool Symbol::isVariableSymbol(char c) { + if (c >= 'a' && c <= 'z') { + return true; + } + return false; +} + +bool Symbol::isSeriesSymbol(char c) { + if (c >= (char)SpecialSymbols::V1 && c <= (char)SpecialSymbols::N3) { + return true; + } + return false; +} + +bool Symbol::isRegressionSymbol(char c) { + if (c >= (char)SpecialSymbols::X1 && c <= (char)SpecialSymbols::Y3) { return true; } return false;