mirror of
https://github.com/UpsilonNumworks/Upsilon.git
synced 2026-01-19 00:37:25 +01:00
[apps] Fix graph X axis labels that read the same
Scenario: just zoom in in a graph Fix: - Do not truncate labels. - Label size is computed depending on available size and number of labels. - If labels cannot be displayed properly, just display the minimal and maximal labels!
This commit is contained in:
@@ -18,7 +18,7 @@ public:
|
||||
protected:
|
||||
char * label(Axis axis, int index) const override;
|
||||
private:
|
||||
char m_labels[k_maxNumberOfXLabels][k_horizontalLabelBufferSize];
|
||||
char m_labels[k_maxNumberOfXLabels][k_labelBufferMaxSize];
|
||||
static float EvaluateAtAbscissa(float abscissa, void * model, void * context);
|
||||
Law * m_law;
|
||||
Calculation * m_calculation;
|
||||
|
||||
@@ -15,8 +15,8 @@ public:
|
||||
private:
|
||||
char * label(Axis axis, int index) const override;
|
||||
Store * m_store;
|
||||
char m_xLabels[k_maxNumberOfXLabels][k_horizontalLabelBufferSize];
|
||||
char m_yLabels[k_maxNumberOfYLabels][k_verticalLabelBufferSize];
|
||||
char m_xLabels[k_maxNumberOfXLabels][k_labelBufferMaxSize];
|
||||
char m_yLabels[k_maxNumberOfYLabels][k_labelBufferMaxSize];
|
||||
Responder * m_controller;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
#include "curve_view.h"
|
||||
#include "../constant.h"
|
||||
#include <poincare/print_float.h>
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include <cmath>
|
||||
#include <float.h>
|
||||
|
||||
#include <poincare/print_float.h>
|
||||
|
||||
using namespace Poincare;
|
||||
|
||||
namespace Shared {
|
||||
|
||||
static inline int minInt(int x, int y) { return (x<y ? x : y); }
|
||||
|
||||
CurveView::CurveView(CurveViewRange * curveViewRange, CurveViewCursor * curveViewCursor, BannerView * bannerView,
|
||||
View * cursorView, View * okView, bool displayBanner) :
|
||||
View(),
|
||||
@@ -110,6 +111,22 @@ KDCoordinate CurveView::pixelLength(Axis axis) const {
|
||||
return (axis == Axis::Horizontal ? m_frame.width() : m_frame.height());
|
||||
}
|
||||
|
||||
int CurveView::numberOfLabels(Axis axis) const {
|
||||
Axis otherAxis = axis == Axis::Horizontal ? Axis::Vertical : Axis::Horizontal;
|
||||
if (min(otherAxis) > 0.0f || max(otherAxis) < 0.0f) {
|
||||
return 0;
|
||||
}
|
||||
float minA = min(axis);
|
||||
float maxA = max(axis);
|
||||
float margin = labelsDisplayMarginRatio(axis) * (maxA - minA);
|
||||
float minVisibleInAxis = minA + margin;
|
||||
float maxVisibleInAxis = maxA - margin;
|
||||
float labelStep = 2.0f * gridUnit(axis);
|
||||
float minLabel = std::ceil(minVisibleInAxis/labelStep);
|
||||
float maxLabel = std::floor(maxVisibleInAxis/labelStep);
|
||||
return maxLabel - minLabel + 1;
|
||||
}
|
||||
|
||||
float CurveView::pixelToFloat(Axis axis, KDCoordinate p) const {
|
||||
KDCoordinate pixels = axis == Axis::Horizontal ? p : pixelLength(axis)-p;
|
||||
return min(axis) + pixels*((float)(max(axis)-min(axis)))/((float)pixelLength(axis));
|
||||
@@ -132,31 +149,55 @@ float CurveView::floatToPixel(Axis axis, float f) const {
|
||||
|
||||
void CurveView::computeLabels(Axis axis) {
|
||||
float step = gridUnit(axis);
|
||||
int labelsCount = numberOfLabels(axis);
|
||||
for (int index = 0; index < labelsCount; index++) {
|
||||
float labelValue = 2.0f*step*(std::ceil(min(axis)/(2.0f*step)))+index*2.0f*step;
|
||||
int axisLabelsCount = numberOfLabels(axis);
|
||||
for (int i = 0; i < axisLabelsCount; i++) {
|
||||
float labelValue = labelValueAtIndex(axis, i);
|
||||
/* Label cannot hold more than k_labelBufferMaxSize characters to prevent
|
||||
* them from overprinting one another.*/
|
||||
int labelMaxSize = k_labelBufferMaxSize;
|
||||
if (axis == Axis::Horizontal) {
|
||||
float pixelsPerLabel = 320.0f/axisLabelsCount - 10; // 10 is a margin between the labels
|
||||
labelMaxSize = minInt(k_labelBufferMaxSize, pixelsPerLabel/k_font->glyphSize().width());
|
||||
}
|
||||
|
||||
if (labelValue < step && labelValue > -step) {
|
||||
// Make sure the 0 value is really written 0
|
||||
labelValue = 0.0f;
|
||||
}
|
||||
|
||||
/* Label cannot hold more than k_labelBufferSize characters to prevent them
|
||||
* from overprinting one another.*/
|
||||
|
||||
char * labelBuffer = label(axis, i);
|
||||
PrintFloat::convertFloatToText<float>(
|
||||
labelValue,
|
||||
label(axis, index),
|
||||
axis == Axis::Vertical ? k_verticalLabelBufferSize : k_horizontalLabelBufferSize,
|
||||
axis == Axis::Vertical ? 6 : Constant::ShortNumberOfSignificantDigits,
|
||||
Preferences::PrintFloatMode::Decimal);
|
||||
labelBuffer,
|
||||
labelMaxSize,
|
||||
k_numberSignificantDigits,
|
||||
Preferences::PrintFloatMode::Decimal,
|
||||
axis == Axis::Vertical);
|
||||
|
||||
if (labelBuffer[0] == 0 && axis == Axis::Horizontal) {
|
||||
/* Some labels are too big and may overlap their neighbours. We write the
|
||||
* extrema labels only. */
|
||||
computeHorizontalExtremaLabels();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOrigin, bool graduationOnly, bool fixCoordinate, KDCoordinate fixedCoordinate) const {
|
||||
int labelsCount = numberOfLabels(axis);
|
||||
if (labelsCount < 1) {
|
||||
return;
|
||||
}
|
||||
float step = gridUnit(axis);
|
||||
float start = 2.0f*step*(std::ceil(min(axis)/(2.0f*step)));
|
||||
float end = max(axis);
|
||||
float start = labelValueAtIndex(axis, 0);
|
||||
float end = labelValueAtIndex(axis, labelsCount - 1);
|
||||
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) {
|
||||
for (float x = start; x <= end + step; x += 2.0f*step) {
|
||||
/* When |start| >> step, start + step = start. In that case, quit the
|
||||
* infinite loop. */
|
||||
if (x == x-step || x == x+step) {
|
||||
@@ -167,7 +208,7 @@ void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOr
|
||||
graduation = KDRect(horizontalCoordinate-(k_labelGraduationLength-2)/2, std::round(floatToPixel(Axis::Vertical, x)), k_labelGraduationLength, 1);
|
||||
}
|
||||
if (!graduationOnly) {
|
||||
KDSize textSize = KDFont::SmallFont->stringSize(label(axis, i));
|
||||
KDSize textSize = k_font->stringSize(label(axis, i));
|
||||
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);
|
||||
@@ -175,8 +216,8 @@ void CurveView::drawLabels(KDContext * ctx, KDRect rect, Axis axis, bool shiftOr
|
||||
if (-step < x && x < step && shiftOrigin) {
|
||||
origin = KDPoint(horizontalCoordinate + k_labelMargin, verticalCoordinate + k_labelMargin);
|
||||
}
|
||||
if (rect.intersects(KDRect(origin, KDFont::SmallFont->stringSize(label(axis, i))))) {
|
||||
ctx->drawString(label(axis, i), origin, KDFont::SmallFont, KDColorBlack);
|
||||
if (rect.intersects(KDRect(origin, k_font->stringSize(label(axis, i))))) {
|
||||
ctx->drawString(label(axis, i), origin, k_font, KDColorBlack);
|
||||
}
|
||||
}
|
||||
ctx->fillRect(graduation, KDColorBlack);
|
||||
@@ -432,14 +473,6 @@ void CurveView::drawHistogram(KDContext * ctx, KDRect rect, EvaluateModelWithPar
|
||||
}
|
||||
}
|
||||
|
||||
int CurveView::numberOfLabels(Axis axis) const {
|
||||
Axis otherAxis = axis == Axis::Horizontal ? Axis::Vertical : Axis::Horizontal;
|
||||
if (min(otherAxis) > 0.0f || max(otherAxis) < 0.0f) {
|
||||
return 0;
|
||||
}
|
||||
return std::ceil((max(axis) - min(axis))/(2*gridUnit(axis)));
|
||||
}
|
||||
|
||||
void CurveView::jointDots(KDContext * ctx, KDRect rect, EvaluateModelWithParameter evaluation, void * model, void * context, float x, float y, float u, float v, KDColor color, int maxNumberOfRecursion) const {
|
||||
float pyf = floatToPixel(Axis::Vertical, y);
|
||||
float pvf = floatToPixel(Axis::Vertical, v);
|
||||
@@ -599,4 +632,35 @@ View * CurveView::subviewAtIndex(int index) {
|
||||
return m_bannerView;
|
||||
}
|
||||
|
||||
void CurveView::computeHorizontalExtremaLabels() {
|
||||
Axis axis = Axis::Horizontal;
|
||||
int axisLabelsCount = numberOfLabels(axis);
|
||||
|
||||
// All labels but the extrema are empty
|
||||
for (int i = 1; i < axisLabelsCount - 1 ; i++) {
|
||||
label(axis, i)[0] = 0;
|
||||
}
|
||||
|
||||
int minMax[] = {0, axisLabelsCount-1};
|
||||
for (int i : minMax) {
|
||||
// Compute the minimal and maximal label
|
||||
PrintFloat::convertFloatToText<float>(
|
||||
labelValueAtIndex(axis, i),
|
||||
label(axis, i),
|
||||
k_labelBufferMaxSize,
|
||||
k_numberSignificantDigits,
|
||||
Preferences::PrintFloatMode::Decimal,
|
||||
false);
|
||||
}
|
||||
}
|
||||
|
||||
float CurveView::labelValueAtIndex(Axis axis, int i) const {
|
||||
assert(i >= 0 && i < numberOfLabels(axis));
|
||||
float minA = min(axis);
|
||||
float maxA = max(axis);
|
||||
float labelStep = 2.0f * gridUnit(axis);
|
||||
float minVisibleInAxis = minA + labelsDisplayMarginRatio(axis) * (maxA - minA);
|
||||
return labelStep*(std::ceil(minVisibleInAxis/labelStep)+i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,9 @@ namespace Shared {
|
||||
|
||||
class CurveView : public View {
|
||||
public:
|
||||
/* We want a 3 characters margin before the first label tick, so that most
|
||||
* labels appear completely. This gives 3*charWidth/320 = 3*7/320= 0.066 */
|
||||
static constexpr float k_labelsHorizontalMarginRatio = 0.066f;
|
||||
typedef float (*EvaluateModelWithParameter)(float t, void * model, void * context);
|
||||
enum class Axis {
|
||||
Horizontal = 0,
|
||||
@@ -41,17 +44,8 @@ protected:
|
||||
constexpr static KDCoordinate k_okVerticalMargin = 23;
|
||||
constexpr static KDCoordinate k_okHorizontalMargin = 10;
|
||||
constexpr static KDCoordinate k_labelGraduationLength = 6;
|
||||
/* The labels are bounds by ±1E-4 and ±1E-8 which in worst case can be written
|
||||
* using 6 characters (including the null-terminating char).
|
||||
* To avoid overlapping horizontal labels, k_horizontalLabelBufferSize should
|
||||
* verify:
|
||||
* k_horizontalLabelBufferSize = Ion::Display::Width /
|
||||
* ((CurveViewRange::k_maxNumberOfXGridUnits/2)*KDFont::SmallFont->glyphWidth)
|
||||
* = 320/((18/2)*7) ~ 5.
|
||||
* We take 6, which creates a small overlap in worst cases but prevents from
|
||||
* truncating labels (ie, "-1E-"). */
|
||||
constexpr static int k_horizontalLabelBufferSize = 6;
|
||||
constexpr static int k_verticalLabelBufferSize = 14; // '-' + 6 significant digits + '.' + "E-" + 3 digits + null-terminating char
|
||||
constexpr static int k_numberSignificantDigits = 6;
|
||||
constexpr static int k_labelBufferMaxSize = 1 + k_numberSignificantDigits + 3 + 3 + 1; // '-' + 6 significant digits + '.' + "E-" + 3 digits + null-terminating char
|
||||
constexpr static int k_maxNumberOfXLabels = CurveViewRange::k_maxNumberOfXGridUnits;
|
||||
constexpr static int k_maxNumberOfYLabels = CurveViewRange::k_maxNumberOfYGridUnits;
|
||||
constexpr static int k_externRectMargin = 2;
|
||||
@@ -74,6 +68,7 @@ protected:
|
||||
View * m_bannerView;
|
||||
CurveViewCursor * m_curveViewCursor;
|
||||
private:
|
||||
static constexpr const KDFont * k_font = KDFont::SmallFont;
|
||||
/* The window bounds are deduced from the model bounds but also take into
|
||||
account a margin (computed with k_marginFactor) */
|
||||
float min(Axis axis) const;
|
||||
@@ -99,6 +94,9 @@ private:
|
||||
View * subviewAtIndex(int index) override;
|
||||
/* m_curveViewRange has to be non null but the cursor model, the banner and
|
||||
* cursor views may be nullptr if not needed. */
|
||||
void computeHorizontalExtremaLabels();
|
||||
float labelValueAtIndex(Axis axis, int i) const;
|
||||
float labelsDisplayMarginRatio(Axis axis) const { return axis == Axis::Horizontal ? k_labelsHorizontalMarginRatio : 0.0f; }
|
||||
CurveViewRange * m_curveViewRange;
|
||||
View * m_cursorView;
|
||||
View * m_okView;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include "curve_view_range.h"
|
||||
#include "curve_view.h"
|
||||
#include <cmath>
|
||||
#include <ion.h>
|
||||
#include <assert.h>
|
||||
@@ -18,7 +19,9 @@ float CurveViewRange::yGridUnit() {
|
||||
return 0.0f;
|
||||
}
|
||||
|
||||
float CurveViewRange::computeGridUnit(Axis axis, float range) {
|
||||
float CurveViewRange::computeGridUnit(Axis axis, float range1) {
|
||||
float range = axis == Axis::Y ? range1 : ((1.0f-2.0f*CurveView::k_labelsHorizontalMarginRatio)*range1);
|
||||
// TODO share the axis enum of curve_view and then call a static method CurveView::marginRatio(Axis)
|
||||
int a = 0;
|
||||
int b = 0;
|
||||
float maxNumberOfUnits = (axis == Axis::X) ? k_maxNumberOfXGridUnits : k_maxNumberOfYGridUnits;
|
||||
|
||||
@@ -27,8 +27,8 @@ protected:
|
||||
bool m_shouldColorHighlighted;
|
||||
private:
|
||||
char * label(Axis axis, int index) const override;
|
||||
char m_xLabels[k_maxNumberOfXLabels][k_horizontalLabelBufferSize];
|
||||
char m_yLabels[k_maxNumberOfYLabels][k_verticalLabelBufferSize];
|
||||
char m_xLabels[k_maxNumberOfXLabels][k_labelBufferMaxSize];
|
||||
char m_yLabels[k_maxNumberOfYLabels][k_labelBufferMaxSize];
|
||||
Poincare::Context * m_context;
|
||||
};
|
||||
|
||||
|
||||
@@ -27,8 +27,8 @@ protected:
|
||||
bool m_shouldColorHighlighted;
|
||||
private:
|
||||
char * label(Axis axis, int index) const override;
|
||||
char m_xLabels[k_maxNumberOfXLabels][k_horizontalLabelBufferSize];
|
||||
char m_yLabels[k_maxNumberOfYLabels][k_verticalLabelBufferSize];
|
||||
char m_xLabels[k_maxNumberOfXLabels][k_labelBufferMaxSize];
|
||||
char m_yLabels[k_maxNumberOfYLabels][k_labelBufferMaxSize];
|
||||
Poincare::Context * m_context;
|
||||
};
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ public:
|
||||
private:
|
||||
constexpr static KDCoordinate k_axisMargin = 3;
|
||||
char * label(Axis axis, int index) const override;
|
||||
char m_labels[k_maxNumberOfXLabels][k_horizontalLabelBufferSize];
|
||||
char m_labels[k_maxNumberOfXLabels][k_labelBufferMaxSize];
|
||||
BoxRange m_boxRange;
|
||||
};
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ private:
|
||||
char * label(Axis axis, int index) const override;
|
||||
HistogramController * m_controller;
|
||||
Store * m_store;
|
||||
char m_labels[k_maxNumberOfXLabels][k_horizontalLabelBufferSize];
|
||||
char m_labels[k_maxNumberOfXLabels][k_labelBufferMaxSize];
|
||||
static float EvaluateHistogramAtAbscissa(float abscissa, void * model, void * context);
|
||||
float m_highlightedBarStart;
|
||||
float m_highlightedBarEnd;
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace PrintFloat {
|
||||
* ConvertFloatToText return the number of characters that have been written
|
||||
* in buffer (excluding the last \O character) */
|
||||
template <class T>
|
||||
int convertFloatToText(T d, char * buffer, int bufferSize, int numberOfSignificantDigits, Preferences::PrintFloatMode mode);
|
||||
int convertFloatToText(T d, char * buffer, int bufferSize, int numberOfSignificantDigits, Preferences::PrintFloatMode mode, bool allowRounding = true);
|
||||
template <class T>
|
||||
static int convertFloatToTextPrivate(T f, char * buffer, int numberOfSignificantDigits, Preferences::PrintFloatMode mode, int * numberOfRemovedZeros);
|
||||
}
|
||||
|
||||
@@ -41,20 +41,28 @@ void PrintFloat::printBase10IntegerWithDecimalMarker(char * buffer, int bufferLe
|
||||
|
||||
template <class T>
|
||||
int PrintFloat::convertFloatToText(T f, char * buffer, int bufferSize,
|
||||
int numberOfSignificantDigits, Preferences::PrintFloatMode mode) {
|
||||
int numberOfSignificantDigits, Preferences::PrintFloatMode mode, bool allowRounding)
|
||||
{
|
||||
assert(numberOfSignificantDigits > 0);
|
||||
assert(bufferSize > 0);
|
||||
|
||||
char tempBuffer[PrintFloat::k_maxFloatBufferLength];
|
||||
int numberOfZerosRemoved = 0;
|
||||
int requiredLength = convertFloatToTextPrivate(f, tempBuffer, numberOfSignificantDigits, mode, &numberOfZerosRemoved);
|
||||
/* if the required buffer size overflows the buffer size, we first force the
|
||||
/* If the required buffer size overflows the buffer size, we first force the
|
||||
* display mode to scientific and decrease the number of significant digits to
|
||||
* fit the buffer size. If the buffer size is still to small, we only write
|
||||
* the beginning of the float and truncate it (which can result in a non sense
|
||||
* text) */
|
||||
* fit the buffer size. */
|
||||
if (mode == Preferences::PrintFloatMode::Decimal && requiredLength >= bufferSize) {
|
||||
requiredLength = convertFloatToTextPrivate(f, tempBuffer, numberOfSignificantDigits, Preferences::PrintFloatMode::Scientific, &numberOfZerosRemoved);
|
||||
}
|
||||
if (requiredLength >= bufferSize) {
|
||||
/* If the buffer size is still too small and rounding is allowed, we only
|
||||
* write the beginning of the float and truncate it (which can result in a
|
||||
* non sense text). If no rounding is allowed, we set the text to null. */
|
||||
if (!allowRounding) {
|
||||
buffer[0] = 0;
|
||||
return requiredLength;
|
||||
}
|
||||
int adjustedNumberOfSignificantDigits = numberOfSignificantDigits - numberOfZerosRemoved - requiredLength + bufferSize - 1;
|
||||
adjustedNumberOfSignificantDigits = adjustedNumberOfSignificantDigits < 1 ? 1 : adjustedNumberOfSignificantDigits;
|
||||
requiredLength = convertFloatToTextPrivate(f, tempBuffer, adjustedNumberOfSignificantDigits, Preferences::PrintFloatMode::Scientific, &numberOfZerosRemoved);
|
||||
@@ -194,7 +202,7 @@ int PrintFloat::convertFloatToTextPrivate(T f, char * buffer, int numberOfSignif
|
||||
return (numberOfCharsForMantissaWithSign+1+numberOfCharExponent);
|
||||
}
|
||||
|
||||
template int PrintFloat::convertFloatToText<float>(float, char*, int, int, Preferences::Preferences::PrintFloatMode);
|
||||
template int PrintFloat::convertFloatToText<double>(double, char*, int, int, Preferences::Preferences::PrintFloatMode);
|
||||
template int PrintFloat::convertFloatToText<float>(float, char*, int, int, Preferences::Preferences::PrintFloatMode, bool);
|
||||
template int PrintFloat::convertFloatToText<double>(double, char*, int, int, Preferences::Preferences::PrintFloatMode, bool);
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user