mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-18 16:27:34 +01:00
287 lines
12 KiB
C++
287 lines
12 KiB
C++
#include "histogram_controller.h"
|
|
#include "../shared/poincare_helpers.h"
|
|
#include "../shared/text_helpers.h"
|
|
#include "app.h"
|
|
#include <poincare/ieee754.h>
|
|
#include <poincare/preferences.h>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <assert.h>
|
|
#include <float.h>
|
|
|
|
using namespace Poincare;
|
|
using namespace Shared;
|
|
|
|
namespace Statistics {
|
|
|
|
HistogramController::HistogramController(Responder * parentResponder, InputEventHandlerDelegate * inputEventHandlerDelegate, 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_view(this, store),
|
|
m_storeVersion(storeVersion),
|
|
m_barVersion(barVersion),
|
|
m_rangeVersion(rangeVersion),
|
|
m_histogramParameterController(nullptr, inputEventHandlerDelegate, store)
|
|
{
|
|
}
|
|
|
|
void HistogramController::setCurrentDrawnSeries(int series) {
|
|
initYRangeParameters(series);
|
|
/* The histogram's CurveView range has been updated along the Vertical axis.
|
|
* To call drawLabelsAndGraduations (in HistogramView::drawRect()), the
|
|
* CurveView must be reloaded so that labels and their values match the new
|
|
* range.
|
|
* In this situation, we update CurveView's Vertical axis, and draw horizontal
|
|
* labels, which are independent. To avoid having to call CurveView::reload(),
|
|
* axis could be taken into account when checking if labels are up to date,
|
|
* instead of using rangeChecksum(), which mixes all axis. */
|
|
m_view.dataViewAtIndex(series)->CurveView::reload();
|
|
}
|
|
|
|
StackViewController * HistogramController::stackController() {
|
|
StackViewController * stack = (StackViewController *)(parentResponder()->parentResponder()->parentResponder());
|
|
return stack;
|
|
}
|
|
|
|
const char * HistogramController::title() {
|
|
return I18n::translate(I18n::Message::HistogramTab);
|
|
}
|
|
|
|
bool HistogramController::handleEvent(Ion::Events::Event event) {
|
|
assert(selectedSeriesIndex() >= 0);
|
|
if (event == Ion::Events::OK || event == Ion::Events::EXE) {
|
|
stackController()->push(histogramParameterController());
|
|
return true;
|
|
}
|
|
return MultipleDataViewController::handleEvent(event);
|
|
}
|
|
|
|
void HistogramController::viewWillAppear() {
|
|
MultipleDataViewController::viewWillAppear();
|
|
uint32_t storeChecksum = m_store->storeChecksum();
|
|
bool initedRangeParameters = false;
|
|
if (*m_storeVersion != storeChecksum) {
|
|
*m_storeVersion = storeChecksum;
|
|
initBarParameters();
|
|
initRangeParameters();
|
|
initedRangeParameters = true;
|
|
}
|
|
uint32_t barChecksum = m_store->barChecksum();
|
|
if (*m_barVersion != barChecksum) {
|
|
*m_barVersion = barChecksum;
|
|
if (!initedRangeParameters) {
|
|
initRangeParameters();
|
|
}
|
|
}
|
|
uint32_t rangeChecksum = m_store->rangeChecksum();
|
|
if (*m_rangeVersion != rangeChecksum) {
|
|
*m_rangeVersion = rangeChecksum;
|
|
initBarSelection();
|
|
reloadBannerView();
|
|
}
|
|
}
|
|
|
|
void HistogramController::willExitResponderChain(Responder * nextFirstResponder) {
|
|
if (nextFirstResponder == tabController()) {
|
|
assert(tabController() != nullptr);
|
|
if (selectedSeriesIndex() >= 0) {
|
|
m_view.dataViewAtIndex(selectedSeriesIndex())->setForceOkDisplay(false);
|
|
}
|
|
}
|
|
MultipleDataViewController::willExitResponderChain(nextFirstResponder);
|
|
}
|
|
|
|
void HistogramController::highlightSelection() {
|
|
HistogramView * selectedHistogramView = static_cast<HistogramView *>(m_view.dataViewAtIndex(selectedSeriesIndex()));
|
|
selectedHistogramView->setHighlight(m_store->startOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex), m_store->endOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex));
|
|
// if the selectedBar was outside of range, we need to scroll
|
|
if (m_store->scrollToSelectedBarIndex(selectedSeriesIndex(), *m_selectedBarIndex)) {
|
|
multipleDataView()->reload();
|
|
}
|
|
}
|
|
|
|
Responder * HistogramController::tabController() const {
|
|
return (parentResponder()->parentResponder()->parentResponder()->parentResponder());
|
|
}
|
|
|
|
void HistogramController::reloadBannerView() {
|
|
if (selectedSeriesIndex() < 0) {
|
|
return;
|
|
}
|
|
constexpr int precision = Preferences::LargeNumberOfSignificantDigits;
|
|
constexpr size_t bufferSize = k_maxNumberOfCharacters + 2 * PrintFloat::charSizeForFloatsWithPrecision(precision);
|
|
char buffer[bufferSize] = "";
|
|
int numberOfChar = 0;
|
|
|
|
// Add Interval Data
|
|
const char * legend = " [";
|
|
int legendLength = strlen(legend);
|
|
strlcpy(buffer, legend, bufferSize);
|
|
numberOfChar += legendLength;
|
|
|
|
// Add lower bound
|
|
if (selectedSeriesIndex() >= 0) {
|
|
double lowerBound = m_store->startOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex);
|
|
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(lowerBound, buffer+numberOfChar, bufferSize-numberOfChar, precision);
|
|
}
|
|
|
|
numberOfChar+= UTF8Decoder::CodePointToChars(';', buffer + numberOfChar, bufferSize - numberOfChar);
|
|
|
|
// Add upper bound
|
|
if (selectedSeriesIndex() >= 0) {
|
|
double upperBound = m_store->endOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex);
|
|
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(upperBound, buffer+numberOfChar, bufferSize-numberOfChar, precision);
|
|
}
|
|
numberOfChar+= UTF8Decoder::CodePointToChars('[', buffer + numberOfChar, bufferSize - numberOfChar);
|
|
|
|
// Padding
|
|
Shared::TextHelpers::PadWithSpaces(buffer, bufferSize, &numberOfChar, k_maxIntervalLegendLength);
|
|
m_view.bannerView()->intervalView()->setText(buffer);
|
|
|
|
// Add Size Data
|
|
numberOfChar = 0;
|
|
double size = 0;
|
|
if (selectedSeriesIndex() >= 0) {
|
|
size = m_store->heightOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex);
|
|
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(size, buffer+numberOfChar, bufferSize-numberOfChar, precision);
|
|
}
|
|
// Padding
|
|
Shared::TextHelpers::PadWithSpaces(buffer, bufferSize, &numberOfChar, k_maxLegendLength);
|
|
m_view.bannerView()->sizeView()->setText(buffer);
|
|
|
|
// Add Frequency Data
|
|
numberOfChar = 0;
|
|
if (selectedSeriesIndex() >= 0) {
|
|
double frequency = size/m_store->sumOfOccurrences(selectedSeriesIndex());
|
|
numberOfChar += PoincareHelpers::ConvertFloatToText<double>(frequency, buffer+numberOfChar, bufferSize - numberOfChar, precision);
|
|
}
|
|
// Padding
|
|
Shared::TextHelpers::PadWithSpaces(buffer, bufferSize, &numberOfChar, k_maxLegendLength);
|
|
m_view.bannerView()->frequencyView()->setText(buffer);
|
|
|
|
m_view.bannerView()->reload();
|
|
}
|
|
|
|
bool HistogramController::moveSelectionHorizontally(int deltaIndex) {
|
|
int newSelectedBarIndex = *m_selectedBarIndex;
|
|
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.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::preinitXRangeParameters(double * xMin) {
|
|
/* Compute m_store's min and max values, hold them temporarily in the
|
|
* CurveViewRange, for later use by initRangeParameters and
|
|
* initBarParameters. Indeed, initRangeParameters will anyway alter the
|
|
* CurveViewRange. The CurveViewRange setter methods take care of the case
|
|
* where minValue >= maxValue. Moreover they compute the xGridUnit, which is
|
|
* used by initBarParameters. */
|
|
double minValue = DBL_MAX;
|
|
double maxValue = -DBL_MAX;
|
|
for (int i = 0; i < Store::k_numberOfSeries; i ++) {
|
|
if (!m_store->seriesIsEmpty(i)) {
|
|
minValue = std::min<double>(minValue, m_store->minValue(i));
|
|
maxValue = std::max<double>(maxValue, m_store->maxValue(i));
|
|
}
|
|
}
|
|
assert(xMin != nullptr);
|
|
*xMin = minValue;
|
|
m_store->setXMin(minValue);
|
|
m_store->setXMax(maxValue);
|
|
}
|
|
|
|
void HistogramController::initRangeParameters() {
|
|
assert(selectedSeriesIndex() >= 0 && m_store->sumOfOccurrences(selectedSeriesIndex()) > 0);
|
|
double barWidth = m_store->barWidth();
|
|
double xMin;
|
|
preinitXRangeParameters(&xMin);
|
|
double xMax = m_store->xMax() + 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;
|
|
}
|
|
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(series); index++) {
|
|
float size = m_store->heightOfBarAtIndex(series, index);
|
|
if (size > yMax) {
|
|
yMax = size;
|
|
}
|
|
}
|
|
yMax = yMax/m_store->sumOfOccurrences(series);
|
|
yMax = yMax < 0 ? 1 : 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() {
|
|
assert(selectedSeriesIndex() >= 0 && m_store->sumOfOccurrences(selectedSeriesIndex()) > 0);
|
|
double xMin;
|
|
preinitXRangeParameters(&xMin);
|
|
m_store->setFirstDrawnBarAbscissa(xMin);
|
|
double barWidth = m_store->xGridUnit();
|
|
if (barWidth <= 0.0) {
|
|
barWidth = 1.0;
|
|
} else {
|
|
// Truncate the bar width, as we convert from float to double
|
|
const double precision = 7; // TODO factorize? This is an experimental value, the same as in Expression;;Epsilon<float>()
|
|
const double logBarWidth = IEEE754<double>::exponentBase10(barWidth);
|
|
barWidth = ((int)(barWidth * std::pow(10.0, precision - logBarWidth))) * std::pow(10.0, -precision + logBarWidth);
|
|
}
|
|
m_store->setBarWidth(barWidth);
|
|
}
|
|
|
|
void HistogramController::initBarSelection() {
|
|
assert(selectedSeriesIndex() >= 0 && m_store->sumOfOccurrences(selectedSeriesIndex()) > 0);
|
|
*m_selectedBarIndex = 0;
|
|
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(selectedSeriesIndex())) {
|
|
/* No bar is after m_firstDrawnBarAbscissa, so we select the first bar */
|
|
*m_selectedBarIndex = 0;
|
|
while (m_store->heightOfBarAtIndex(selectedSeriesIndex(), *m_selectedBarIndex) == 0 && *m_selectedBarIndex < m_store->numberOfBars(selectedSeriesIndex())) {
|
|
*m_selectedBarIndex = *m_selectedBarIndex+1;
|
|
}
|
|
}
|
|
m_store->scrollToSelectedBarIndex(selectedSeriesIndex(), *m_selectedBarIndex);
|
|
}
|
|
|
|
}
|