summaryrefslogtreecommitdiffstats
path: root/stats
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2021-01-11 12:59:17 +0100
committerGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2021-01-11 12:59:17 +0100
commitbdecd98ef500a8193b3abc5c51bf89d52271ea41 (patch)
tree6f76c1996fea522af1868e3a6296b27f5f091743 /stats
parent0ff1145fd822fec21d301c221744073733f012ed (diff)
downloadsubsurface-bdecd98ef500a8193b3abc5c51bf89d52271ea41.tar.gz
statistics: consider overhang of horizontal axes
The old code didn't consider that labels can peak out of horizontal axes if labels are under ticks. This commit takes this into account. However, it must be noted that this is only heuristics: Before setting the size of the axes, the actual minimum and maximum label are not known, because we round to "nice" numbers. But the size of the axis can only be set after knowing the overhang, leading to a circular dependency. Therefore, the code currently simply uses the minimum and maximum value of the data, hoping that the "nice" values will not format to something significantly larger. We could do a multi-pass scheme, but let's not for now. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Diffstat (limited to 'stats')
-rw-r--r--stats/statsaxis.cpp56
-rw-r--r--stats/statsaxis.h6
-rw-r--r--stats/statsview.cpp12
3 files changed, 63 insertions, 11 deletions
diff --git a/stats/statsaxis.cpp b/stats/statsaxis.cpp
index 8b8694964..d31b5827b 100644
--- a/stats/statsaxis.cpp
+++ b/stats/statsaxis.cpp
@@ -57,6 +57,17 @@ std::pair<double, double> StatsAxis::minMaxScreen() const
: std::make_pair(zeroOnScreen, zeroOnScreen - size);
}
+std::pair<double, double> StatsAxis::horizontalOverhang() const
+{
+ // If the labels are between ticks, they cannot peak out
+ if (!horizontal || labelsBetweenTicks)
+ return { 0.0, 0.0 };
+ QFontMetrics fm(labelFont);
+ auto [firstLabel, lastLabel] = getFirstLastLabel();
+ return { fm.size(Qt::TextSingleLine, firstLabel).width() / 2.0,
+ fm.size(Qt::TextSingleLine, lastLabel).width() / 2.0 };
+}
+
void StatsAxis::setRange(double minIn, double maxIn)
{
min = minIn;
@@ -223,6 +234,19 @@ ValueAxis::ValueAxis(const QString &title, double min, double max, int decimals,
StatsAxis(title, horizontal, false),
min(min), max(max), decimals(decimals)
{
+ // Avoid degenerate cases
+ if (max - min < 0.0001) {
+ max += 0.5;
+ min -= 0.5;
+ }
+}
+
+// Attention: this is only heuristics. Before setting the actual size, we
+// don't know the actual numbers of the minimum and maximum value.
+std::pair<QString, QString> ValueAxis::getFirstLastLabel() const
+{
+ QLocale loc;
+ return { loc.toString(min, 'f', decimals), loc.toString(max, 'f', decimals) };
}
void ValueAxis::updateLabels()
@@ -230,15 +254,8 @@ void ValueAxis::updateLabels()
labels.clear();
ticks.clear();
- // Avoid degenerate cases
- if (max - min < 0.0001) {
- max += 0.5;
- min -= 0.5;
- }
-
QLocale loc;
- QString minString = loc.toString(min, 'f', decimals);
- QString maxString = loc.toString(max, 'f', decimals);
+ auto [minString, maxString] = getFirstLastLabel();
int numTicks = guessNumTicks({ minString, maxString});
// Use full decimal increments
@@ -279,6 +296,14 @@ CountAxis::CountAxis(const QString &title, int count, bool horizontal) :
{
}
+// Attention: this is only heuristics. Before setting the actual size, we
+// don't know the actual numbers of the minimum and maximum value.
+std::pair<QString, QString> CountAxis::getFirstLastLabel() const
+{
+ QLocale loc;
+ return { QString("0"), loc.toString(count) };
+}
+
void CountAxis::updateLabels()
{
labels.clear();
@@ -333,6 +358,13 @@ CategoryAxis::CategoryAxis(const QString &title, const std::vector<QString> &lab
setRange(-0.5, static_cast<double>(labels.size()) + 0.5);
}
+// No implementation because the labels are inside ticks and this
+// is only used to calculate the "overhang" of labels under ticks.
+std::pair<QString, QString> CategoryAxis::getFirstLastLabel() const
+{
+ return { QString(), QString() };
+}
+
void CategoryAxis::updateLabels()
{
// TODO: paint ellipses if space too small
@@ -370,6 +402,14 @@ HistogramAxis::HistogramAxis(const QString &title, std::vector<HistogramAxisEntr
setRange(bin_values.front().value, bin_values.back().value);
}
+std::pair<QString, QString> HistogramAxis::getFirstLastLabel() const
+{
+ if (bin_values.empty())
+ return { QString(), QString() };
+ else
+ return { bin_values.front().name, bin_values.back().name };
+}
+
// Initialize a histogram axis with the given labels. Labels are specified as (name, value, recommended) triplets.
// If labels are skipped, try to skip it in such a way that a recommended label is shown.
// The one example where this is relevant is the quarterly bins, which are formated as (2019, q1, q2, q3, 2020, ...).
diff --git a/stats/statsaxis.h b/stats/statsaxis.h
index 72a191963..9d46f753a 100644
--- a/stats/statsaxis.h
+++ b/stats/statsaxis.h
@@ -16,6 +16,7 @@ public:
// Returns minimum and maximum of shown range, not of data points.
std::pair<double, double> minMax() const;
std::pair<double, double> minMaxScreen() const; // minimum and maximum in screen coordinates
+ std::pair<double, double> horizontalOverhang() const; // space that labels peak out in horizontal axes
double width() const; // Only supported by vertical axes. Only valid after setSize().
double height() const; // Only supported for horizontal axes. Always valid.
@@ -39,6 +40,7 @@ protected:
std::vector<Label> labels;
void addLabel(const QString &label, double pos);
virtual void updateLabels() = 0;
+ virtual std::pair<QString, QString> getFirstLastLabel() const = 0;
struct Tick {
std::unique_ptr<QGraphicsLineItem> item;
@@ -69,6 +71,7 @@ private:
double min, max;
int decimals;
void updateLabels() override;
+ std::pair<QString, QString> getFirstLastLabel() const override;
};
class CountAxis : public ValueAxis {
@@ -77,6 +80,7 @@ public:
private:
int count;
void updateLabels() override;
+ std::pair<QString, QString> getFirstLastLabel() const override;
};
class CategoryAxis : public StatsAxis {
@@ -85,6 +89,7 @@ public:
private:
std::vector<QString> labelsText;
void updateLabels();
+ std::pair<QString, QString> getFirstLastLabel() const override;
};
struct HistogramAxisEntry {
@@ -98,6 +103,7 @@ public:
HistogramAxis(const QString &title, std::vector<HistogramAxisEntry> bin_values, bool horizontal);
private:
void updateLabels() override;
+ std::pair<QString, QString> getFirstLastLabel() const override;
std::vector<HistogramAxisEntry> bin_values;
int preferred_step;
};
diff --git a/stats/statsview.cpp b/stats/statsview.cpp
index 1533583ba..5583643d7 100644
--- a/stats/statsview.cpp
+++ b/stats/statsview.cpp
@@ -97,15 +97,21 @@ void StatsView::plotAreaChanged(const QSizeF &s)
if (title)
top += title->boundingRect().height() + titleBorder;
// Currently, we only have either none, or an x- and a y-axis
- if (xAxis)
+ std::pair<double,double> horizontalSpace{ 0.0, 0.0 };
+ if (xAxis) {
bottom -= xAxis->height();
+ horizontalSpace = xAxis->horizontalOverhang();
+ }
if (bottom - top < minSize)
return;
if (yAxis) {
yAxis->setSize(bottom - top);
- left += yAxis->width();
- yAxis->setPos(QPointF(left, bottom));
+ horizontalSpace.first = std::max(horizontalSpace.first, yAxis->width());
}
+ left += horizontalSpace.first;
+ right -= horizontalSpace.second;
+ if (yAxis)
+ yAxis->setPos(QPointF(left, bottom));
if (right - left < minSize)
return;
if (xAxis) {