summaryrefslogtreecommitdiffstats
path: root/stats
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2021-01-05 12:11:46 +0100
committerGravatar Dirk Hohndel <dirk@hohndel.org>2021-01-06 12:31:22 -0800
commit4ab9f1c6b06204285267e79b5ed993514e0213e2 (patch)
tree5e8e792ec76639b1493937d6401bb92d05dabb1c /stats
parent598058e21ebc93834b71cc1b66e881451a596ce6 (diff)
downloadsubsurface-4ab9f1c6b06204285267e79b5ed993514e0213e2.tar.gz
statistics: replace QtCharts' axes
Replace by custom implementation, with the ultimate goal to remove the QtCharts module. This doesn't yet display axis titles or a grid. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at> Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Diffstat (limited to 'stats')
-rw-r--r--stats/barseries.cpp32
-rw-r--r--stats/barseries.h7
-rw-r--r--stats/boxseries.cpp22
-rw-r--r--stats/boxseries.h2
-rw-r--r--stats/pieseries.cpp5
-rw-r--r--stats/scatterseries.cpp15
-rw-r--r--stats/scatterseries.h6
-rw-r--r--stats/statsaxis.cpp260
-rw-r--r--stats/statsaxis.h66
-rw-r--r--stats/statscolors.h1
-rw-r--r--stats/statsseries.cpp15
-rw-r--r--stats/statsseries.h2
-rw-r--r--stats/statsview.cpp132
-rw-r--r--stats/statsview.h15
-rw-r--r--stats/zvalues.h12
15 files changed, 372 insertions, 220 deletions
diff --git a/stats/barseries.cpp b/stats/barseries.cpp
index 2f7f9cca9..5727745a0 100644
--- a/stats/barseries.cpp
+++ b/stats/barseries.cpp
@@ -114,8 +114,7 @@ void BarSeries::BarLabel::highlight(bool highlight, int bin_nr, int binCount)
item->setBrush(brush);
}
-void BarSeries::BarLabel::updatePosition(QtCharts::QChart *chart, QtCharts::QAbstractSeries *series,
- bool horizontal, bool center, const QRectF &rect,
+void BarSeries::BarLabel::updatePosition(bool horizontal, bool center, const QRectF &rect,
int bin_nr, int binCount)
{
if (!horizontal) {
@@ -191,7 +190,7 @@ BarSeries::Item::Item(QtCharts::QChart *chart, BarSeries *series, double lowerBo
item.item->setZValue(ZValues::series);
item.highlight(false, binCount);
}
- updatePosition(chart, series, horizontal, stacked, binCount);
+ updatePosition(series, horizontal, stacked, binCount);
}
void BarSeries::Item::highlight(int subitem, bool highlight, int binCount)
@@ -214,7 +213,7 @@ void BarSeries::SubItem::highlight(bool highlight, int binCount)
label->highlight(highlight, bin_nr, binCount);
}
-void BarSeries::Item::updatePosition(QtCharts::QChart *chart, BarSeries *series, bool horizontal, bool stacked, int binCount)
+void BarSeries::Item::updatePosition(BarSeries *series, bool horizontal, bool stacked, int binCount)
{
if (subitems.empty())
return;
@@ -233,28 +232,28 @@ void BarSeries::Item::updatePosition(QtCharts::QChart *chart, BarSeries *series,
for (SubItem &item: subitems) {
int idx = stacked ? 0 : item.bin_nr;
double center = (idx + 0.5) * fullSubWidth + from;
- item.updatePosition(chart, series, horizontal, stacked, center - subWidth / 2.0, center + subWidth / 2.0, binCount);
+ item.updatePosition(series, horizontal, stacked, center - subWidth / 2.0, center + subWidth / 2.0, binCount);
}
rect = subitems[0].item->rect();
for (auto it = std::next(subitems.begin()); it != subitems.end(); ++it)
rect = rect.united(it->item->rect());
}
-void BarSeries::SubItem::updatePosition(QtCharts::QChart *chart, BarSeries *series, bool horizontal, bool stacked,
+void BarSeries::SubItem::updatePosition(BarSeries *series, bool horizontal, bool stacked,
double from, double to, int binCount)
{
QPointF topLeft, bottomRight;
if (horizontal) {
- topLeft = chart->mapToPosition(QPointF(value_from, to), series);
- bottomRight = chart->mapToPosition(QPointF(value_to, from), series);
+ topLeft = series->toScreen(QPointF(value_from, to));
+ bottomRight = series->toScreen(QPointF(value_to, from));
} else {
- topLeft = chart->mapToPosition(QPointF(from, value_to), series);
- bottomRight = chart->mapToPosition(QPointF(to, value_from), series);
+ topLeft = series->toScreen(QPointF(from, value_to));
+ bottomRight = series->toScreen(QPointF(to, value_from));
}
QRectF rect(topLeft, bottomRight);
item->setRect(rect);
if (label)
- label->updatePosition(chart, series, horizontal, stacked, rect, bin_nr, binCount);
+ label->updatePosition(horizontal, stacked, rect, bin_nr, binCount);
}
std::vector<BarSeries::SubItem> BarSeries::makeSubItems(const std::vector<std::pair<double, std::vector<QString>>> &values) const
@@ -265,9 +264,9 @@ std::vector<BarSeries::SubItem> BarSeries::makeSubItems(const std::vector<std::p
int bin_nr = 0;
for (auto &[v, label]: values) {
if (v > 0.0) {
- res.push_back({ std::make_unique<QGraphicsRectItem>(chart()), {}, from, from + v, bin_nr });
+ res.push_back({ std::make_unique<QGraphicsRectItem>(chart), {}, from, from + v, bin_nr });
if (!label.empty())
- res.back().label = std::make_unique<BarLabel>(chart(), label, bin_nr, binCount());
+ res.back().label = std::make_unique<BarLabel>(chart, label, bin_nr, binCount());
}
if (stacked)
from += v;
@@ -293,15 +292,14 @@ void BarSeries::add_item(double lowerBound, double upperBound, std::vector<SubIt
// Don't add empty items, as that messes with the "find item under mouse" routine.
if (subitems.empty())
return;
- items.emplace_back(chart(), this, lowerBound, upperBound, std::move(subitems), binName, res,
+ items.emplace_back(chart, this, lowerBound, upperBound, std::move(subitems), binName, res,
total, horizontal, stacked, binCount());
}
void BarSeries::updatePositions()
{
- QtCharts::QChart *c = chart();
for (Item &item: items)
- item.updatePosition(c, this, horizontal, stacked, binCount());
+ item.updatePosition(this, horizontal, stacked, binCount());
}
// Attention: this supposes that items are sorted by position and no bar is inside another bar!
@@ -405,7 +403,7 @@ bool BarSeries::hover(QPointF pos)
Item &item = items[highlighted.bar];
item.highlight(index.subitem, true, binCount());
if (!information)
- information.reset(new InformationBox(chart()));
+ information.reset(new InformationBox(chart));
information->setText(makeInfo(item, highlighted.subitem), pos);
} else {
information.reset();
diff --git a/stats/barseries.h b/stats/barseries.h
index 09d008094..114df540a 100644
--- a/stats/barseries.h
+++ b/stats/barseries.h
@@ -87,8 +87,7 @@ private:
bool isOutside; // Is shown outside of bar
BarLabel(QtCharts::QChart *chart, const std::vector<QString> &labels, int bin_nr, int binCount);
void setVisible(bool visible);
- void updatePosition(QtCharts::QChart *chart, QtCharts::QAbstractSeries *series,
- bool horizontal, bool center, const QRectF &rect, int bin_nr, int binCount);
+ void updatePosition(bool horizontal, bool center, const QRectF &rect, int bin_nr, int binCount);
void highlight(bool highlight, int bin_nr, int binCount);
};
@@ -98,7 +97,7 @@ private:
double value_from;
double value_to;
int bin_nr;
- void updatePosition(QtCharts::QChart *chart, BarSeries *series, bool horizontal, bool stacked,
+ void updatePosition(BarSeries *series, bool horizontal, bool stacked,
double from, double to, int binCount);
void highlight(bool highlight, int binCount);
};
@@ -114,7 +113,7 @@ private:
std::vector<SubItem> subitems,
const QString &binName, const StatsOperationResults &res, int total, bool horizontal,
bool stacked, int binCount);
- void updatePosition(QtCharts::QChart *chart, BarSeries *series, bool horizontal, bool stacked, int binCount);
+ void updatePosition(BarSeries *series, bool horizontal, bool stacked, int binCount);
void highlight(int subitem, bool highlight, int binCount);
int getSubItemUnderMouse(const QPointF &f, bool horizontal, bool stacked) const;
};
diff --git a/stats/boxseries.cpp b/stats/boxseries.cpp
index 99651345b..b5e4422ea 100644
--- a/stats/boxseries.cpp
+++ b/stats/boxseries.cpp
@@ -39,7 +39,7 @@ BoxSeries::Item::Item(QtCharts::QChart *chart, BoxSeries *series, double lowerBo
bottomBar.setZValue(ZValues::series);
center.setZValue(ZValues::series);
highlight(false);
- updatePosition(chart, series);
+ updatePosition(series);
}
BoxSeries::Item::~Item()
@@ -59,7 +59,7 @@ void BoxSeries::Item::highlight(bool highlight)
center.setPen(pen);
}
-void BoxSeries::Item::updatePosition(QtCharts::QChart *chart, BoxSeries *series)
+void BoxSeries::Item::updatePosition(BoxSeries *series)
{
double delta = (upperBound - lowerBound) * boxWidth;
double from = (lowerBound + upperBound - delta) / 2.0;
@@ -68,17 +68,17 @@ void BoxSeries::Item::updatePosition(QtCharts::QChart *chart, BoxSeries *series)
QPointF topLeft, bottomRight;
QMarginsF margins(boxBorderWidth / 2.0, boxBorderWidth / 2.0, boxBorderWidth / 2.0, boxBorderWidth / 2.0);
- topLeft = chart->mapToPosition(QPointF(from, q.max), series);
- bottomRight = chart->mapToPosition(QPointF(to, q.min), series);
+ topLeft = series->toScreen(QPointF(from, q.max));
+ bottomRight = series->toScreen(QPointF(to, q.min));
bounding = QRectF(topLeft, bottomRight).marginsAdded(margins);
double left = topLeft.x();
double right = bottomRight.x();
double width = right - left;
double top = topLeft.y();
double bottom = bottomRight.y();
- QPointF q1 = chart->mapToPosition(QPointF(mid, q.q1), series);
- QPointF q2 = chart->mapToPosition(QPointF(mid, q.q2), series);
- QPointF q3 = chart->mapToPosition(QPointF(mid, q.q3), series);
+ QPointF q1 = series->toScreen(QPointF(mid, q.q1));
+ QPointF q2 = series->toScreen(QPointF(mid, q.q2));
+ QPointF q3 = series->toScreen(QPointF(mid, q.q3));
box.setRect(left, q3.y(), width, q1.y() - q3.y());
topWhisker.setLine(q3.x(), top, q3.x(), q3.y());
bottomWhisker.setLine(q1.x(), q1.y(), q1.x(), bottom);
@@ -89,15 +89,13 @@ void BoxSeries::Item::updatePosition(QtCharts::QChart *chart, BoxSeries *series)
void BoxSeries::append(double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName)
{
- QtCharts::QChart *c = chart();
- items.emplace_back(new Item(c, this, lowerBound, upperBound, q, binName));
+ items.emplace_back(new Item(chart, this, lowerBound, upperBound, q, binName));
}
void BoxSeries::updatePositions()
{
- QtCharts::QChart *c = chart();
for (auto &item: items)
- item->updatePosition(c, this);
+ item->updatePosition(this);
}
// Attention: this supposes that items are sorted by position and no box is inside another box!
@@ -149,7 +147,7 @@ bool BoxSeries::hover(QPointF pos)
Item &item = *items[highlighted];
item.highlight(true);
if (!information)
- information.reset(new InformationBox(chart()));
+ information.reset(new InformationBox(chart));
information->setText(formatInformation(item), pos);
} else {
information.reset();
diff --git a/stats/boxseries.h b/stats/boxseries.h
index 964e3aec1..43b177619 100644
--- a/stats/boxseries.h
+++ b/stats/boxseries.h
@@ -45,7 +45,7 @@ private:
StatsQuartiles q;
QString binName;
Item(QtCharts::QChart *chart, BoxSeries *series, double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName);
- void updatePosition(QtCharts::QChart *chart, BoxSeries *series);
+ void updatePosition(BoxSeries *series);
void highlight(bool highlight);
};
diff --git a/stats/pieseries.cpp b/stats/pieseries.cpp
index f30753294..108046b23 100644
--- a/stats/pieseries.cpp
+++ b/stats/pieseries.cpp
@@ -167,8 +167,7 @@ PieSeries::~PieSeries()
void PieSeries::updatePositions()
{
- QtCharts::QChart *c = chart();
- QRectF plotRect = c->plotArea();
+ QRectF plotRect = chart->plotArea();
center = plotRect.center();
radius = std::min(plotRect.width(), plotRect.height()) * pieSize / 2.0;
QRectF rect(center.x() - radius, center.y() - radius, 2.0 * radius, 2.0 * radius);
@@ -247,7 +246,7 @@ bool PieSeries::hover(QPointF pos)
if (highlighted >= 0 && highlighted < (int)items.size()) {
items[highlighted].highlight(highlighted, true, (int)items.size());
if (!information)
- information.reset(new InformationBox(chart()));
+ information.reset(new InformationBox(chart));
information->setText(makeInfo(highlighted), pos);
} else {
information.reset();
diff --git a/stats/scatterseries.cpp b/stats/scatterseries.cpp
index 8ef795edc..de7a8a3bd 100644
--- a/stats/scatterseries.cpp
+++ b/stats/scatterseries.cpp
@@ -65,12 +65,12 @@ ScatterSeries::Item::Item(QtCharts::QChart *chart, ScatterSeries *series, dive *
value(value)
{
item->setZValue(ZValues::series);
- updatePosition(chart, series);
+ updatePosition(series);
}
-void ScatterSeries::Item::updatePosition(QtCharts::QChart *chart, ScatterSeries *series)
+void ScatterSeries::Item::updatePosition(ScatterSeries *series)
{
- QPointF center = chart->mapToPosition(QPointF(pos, value), series);
+ QPointF center = series->toScreen(QPointF(pos, value));
item->setPos(center.x() - scatterItemDiameter / 2.0,
center.y() - scatterItemDiameter / 2.0);
}
@@ -82,14 +82,13 @@ void ScatterSeries::Item::highlight(bool highlight)
void ScatterSeries::append(dive *d, double pos, double value)
{
- items.emplace_back(chart(), this, d, pos, value);
+ items.emplace_back(chart, this, d, pos, value);
}
void ScatterSeries::updatePositions()
{
- QtCharts::QChart *c = chart();
for (Item &item: items)
- item.updatePosition(c, this);
+ item.updatePosition(this);
}
static double sq(double f)
@@ -103,7 +102,7 @@ static double squareDist(const QPointF &p1, const QPointF &p2)
return QPointF::dotProduct(diff, diff);
}
-std::vector<int> ScatterSeries::getItemsUnderMouse(const QPointF &point)
+std::vector<int> ScatterSeries::getItemsUnderMouse(const QPointF &point) const
{
std::vector<int> res;
double x = point.x();
@@ -174,7 +173,7 @@ bool ScatterSeries::hover(QPointF pos)
return false;
} else {
if (!information)
- information.reset(new InformationBox(chart()));
+ information.reset(new InformationBox(chart));
std::vector<QString> text;
text.reserve(highlighted.size() * 5);
diff --git a/stats/scatterseries.h b/stats/scatterseries.h
index 335fb828c..212a8e4ea 100644
--- a/stats/scatterseries.h
+++ b/stats/scatterseries.h
@@ -30,16 +30,14 @@ public:
private:
// Get items under mouse.
- // Super weird: this function can't be const, because QChart::mapToValue takes
- // a non-const reference!?
- std::vector<int> getItemsUnderMouse(const QPointF &f);
+ std::vector<int> getItemsUnderMouse(const QPointF &f) const;
struct Item {
std::unique_ptr<QGraphicsPixmapItem> item;
dive *d;
double pos, value;
Item(QtCharts::QChart *chart, ScatterSeries *series, dive *d, double pos, double value);
- void updatePosition(QtCharts::QChart *chart, ScatterSeries *series);
+ void updatePosition(ScatterSeries *series);
void highlight(bool highlight);
};
diff --git a/stats/statsaxis.cpp b/stats/statsaxis.cpp
index 81dd18c10..1a425cc0c 100644
--- a/stats/statsaxis.cpp
+++ b/stats/statsaxis.cpp
@@ -1,7 +1,9 @@
// SPDX-License-Identifier: GPL-2.0
#include "statsaxis.h"
+#include "statscolors.h"
#include "statstranslations.h"
#include "statsvariables.h"
+#include "zvalues.h"
#include "core/pref.h"
#include "core/subsurface-time.h"
#include <math.h> // for lrint
@@ -10,8 +12,28 @@
#include <QFontMetrics>
#include <QLocale>
-StatsAxis::StatsAxis(QtCharts::QChart *chart, bool horizontal) : chart(chart), horizontal(horizontal)
+// Define most constants for horizontal and vertical axes for more flexibility.
+// Note: *Horizontal means that this is for the horizontal axis, so a vertical space.
+static const double axisWidth = 0.5;
+static const double axisTickWidth = 0.3;
+static const double axisTickSizeHorizontal = 6.0;
+static const double axisTickSizeVertical = 6.0;
+static const double axisLabelSpaceHorizontal = 2.0; // Space between axis or ticks and labels
+static const double axisLabelSpaceVertical = 2.0; // Space between axis or ticks and labels
+static const double axisTitleSpaceHorizontal = 2.0; // Space between labels and title
+static const double axisTitleSpaceVertical = 2.0; // Space between labels and title
+
+StatsAxis::StatsAxis(QtCharts::QChart *chart, bool horizontal, bool labelsBetweenTicks) :
+ QGraphicsLineItem(chart),
+ chart(chart), horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks),
+ size(1.0), zeroOnScreen(0.0), min(0.0), max(1.0)
{
+ // use a Light version of the application fond for both labels and title
+ labelFont = QFont();
+ labelFont.setWeight(QFont::Light);
+ titleFont = labelFont;
+ setPen(QPen(axisColor, axisWidth));
+ setZValue(ZValues::axes);
}
StatsAxis::~StatsAxis()
@@ -20,7 +42,13 @@ StatsAxis::~StatsAxis()
std::pair<double, double> StatsAxis::minMax() const
{
- return { 0.0, 1.0 };
+ return { min, max };
+}
+
+void StatsAxis::setRange(double minIn, double maxIn)
+{
+ min = minIn;
+ max = maxIn;
}
// Guess the number of tick marks based on example strings.
@@ -28,14 +56,13 @@ std::pair<double, double> StatsAxis::minMax() const
// maximum-size strings especially, when using proportional fonts or for
// categorical data. Therefore, try to err on the safe side by adding enough
// margins.
-int StatsAxis::guessNumTicks(const QtCharts::QAbstractAxis *axis, const std::vector<QString> &strings) const
+int StatsAxis::guessNumTicks(const std::vector<QString> &strings) const
{
- QFont font = axis->labelsFont();
- QFontMetrics fm(font);
+ QFontMetrics fm(labelFont);
int minSize = fm.height();
for (const QString &s: strings) {
- QSize size = fm.size(Qt::TextSingleLine, s);
- int needed = horizontal ? size.width() : size.height();
+ QSize labelSize = fm.size(Qt::TextSingleLine, s);
+ int needed = horizontal ? labelSize.width() : labelSize.height();
if (needed > minSize)
minSize = needed;
}
@@ -45,31 +72,127 @@ int StatsAxis::guessNumTicks(const QtCharts::QAbstractAxis *axis, const std::vec
minSize = minSize * 3 / 2;
else
minSize *= 2;
- QRectF chartSize = chart->plotArea();
- double availableSpace = horizontal ? chartSize.width() : chartSize.height();
- int numTicks = lrint(availableSpace / minSize);
+ int numTicks = lrint(size / minSize);
return std::max(numTicks, 2);
}
-ValueAxis::ValueAxis(QtCharts::QChart *chart, double min, double max, int decimals, bool horizontal) :
- StatsAxisTemplate(chart, horizontal),
- min(min), max(max), decimals(decimals)
+double StatsAxis::width() const
+{
+ if (horizontal)
+ return 0.0; // Only supported for vertical axes
+ double labelWidth = 0.0;
+ for (const Label &label: labels) {
+ double w = label.label->boundingRect().width();
+ if (w > labelWidth)
+ labelWidth = w;
+ }
+ return labelWidth + axisLabelSpaceVertical +
+ QFontMetrics(titleFont).height() + axisTitleSpaceVertical +
+ (labelsBetweenTicks ? 0.0 : axisTickSizeVertical);
+}
+
+double StatsAxis::height() const
+{
+ if (!horizontal)
+ return 0.0; // Only supported for horizontal axes
+ return QFontMetrics(labelFont).height() + axisLabelSpaceHorizontal +
+ QFontMetrics(titleFont).height() + axisTitleSpaceHorizontal +
+ (labelsBetweenTicks ? 0.0 : axisTickSizeHorizontal);
+}
+
+StatsAxis::Label::Label(const QString &name, double pos, QtCharts::QChart *chart, const QFont &font) :
+ label(new QGraphicsSimpleTextItem(name, chart)),
+ pos(pos)
+{
+ label->setBrush(QBrush(darkLabelColor));
+ label->setFont(font);
+ label->setZValue(ZValues::axes);
+}
+
+void StatsAxis::addLabel(const QString &label, double pos)
+{
+ labels.emplace_back(label, pos, chart, labelFont);
+}
+
+StatsAxis::Tick::Tick(double pos, QtCharts::QChart *chart) :
+ item(new QGraphicsLineItem(chart)),
+ pos(pos)
+{
+ item->setPen(QPen(axisColor, axisTickWidth));
+ item->setZValue(ZValues::axes);
+}
+
+void StatsAxis::addTick(double pos)
+{
+ ticks.emplace_back(pos, chart);
+}
+
+// Map x (horizontal) or y (vertical) coordinate to or from screen coordinate
+double StatsAxis::toScreen(double pos) const
+{
+ // Vertical is bottom-up
+ return horizontal ? (pos - min) / (max - min) * size + zeroOnScreen
+ : (min - pos) / (max - min) * size + zeroOnScreen;
+}
+
+double StatsAxis::toValue(double pos) const
{
+ // Vertical is bottom-up
+ return horizontal ? (pos - zeroOnScreen) / size * (max - min) + min
+ : (zeroOnScreen - pos) / size * (max - min) + zeroOnScreen;
}
-std::pair<double, double> ValueAxis::minMax() const
+void StatsAxis::setSize(double sizeIn)
{
- return { QValueAxis::min(), QValueAxis::max() };
+ size = sizeIn;
+ updateLabels();
}
-static QString makeFormatString(int decimals)
+void StatsAxis::setPos(QPointF pos)
+{
+ if (horizontal) {
+ zeroOnScreen = pos.x();
+ double labelY = pos.y() + axisLabelSpaceHorizontal +
+ (labelsBetweenTicks ? 0.0 : axisTickSizeHorizontal);
+ double y = pos.y();
+ for (Label &label: labels) {
+ double x = toScreen(label.pos) - label.label->boundingRect().width() / 2.0;
+ label.label->setPos(QPointF(x, labelY));
+ }
+ for (Tick &tick: ticks) {
+ double x = toScreen(tick.pos);
+ tick.item->setLine(x, y, x, y + axisTickSizeHorizontal);
+ }
+ setLine(zeroOnScreen, y, zeroOnScreen + size, y);
+ } else {
+ double fontHeight = QFontMetrics(labelFont).height();
+ zeroOnScreen = pos.y();
+ double x = pos.x();
+ double labelX = x - axisLabelSpaceVertical -
+ (labelsBetweenTicks ? 0.0 : axisTickSizeVertical);
+ for (Label &label: labels) {
+ double y = toScreen(label.pos) - fontHeight / 2.0;
+ label.label->setPos(QPointF(labelX - label.label->boundingRect().width(), y));
+ }
+ for (Tick &tick: ticks) {
+ double y = toScreen(tick.pos);
+ tick.item->setLine(x, y, x - axisTickSizeVertical, y);
+ }
+ setLine(x, zeroOnScreen, x, zeroOnScreen - size);
+ }
+}
+
+ValueAxis::ValueAxis(QtCharts::QChart *chart, double min, double max, int decimals, bool horizontal) :
+ StatsAxis(chart, horizontal, false),
+ min(min), max(max), decimals(decimals)
{
- return QStringLiteral("%.%1f").arg(decimals < 0 ? 0 : decimals);
}
void ValueAxis::updateLabels()
{
using QtCharts::QValueAxis;
+ labels.clear();
+ ticks.clear();
// Avoid degenerate cases
if (max - min < 0.0001) {
@@ -80,7 +203,7 @@ void ValueAxis::updateLabels()
QLocale loc;
QString minString = loc.toString(min, 'f', decimals);
QString maxString = loc.toString(max, 'f', decimals);
- int numTicks = guessNumTicks(this, { minString, maxString});
+ int numTicks = guessNumTicks({ minString, maxString});
// Use full decimal increments
double height = max - min;
@@ -98,12 +221,20 @@ void ValueAxis::updateLabels()
if (-digits_int > decimals)
decimals = -digits_int;
- setLabelFormat(makeFormatString(decimals));
double actMin = floor(min / inc) * inc;
double actMax = ceil(max / inc) * inc;
int num = lrint((actMax - actMin) / inc);
setRange(actMin, actMax);
- setTickCount(num + 1);
+
+ double actStep = (actMax - actMin) / static_cast<double>(num);
+ double act = actMin;
+ labels.reserve(num + 1);
+ ticks.reserve(num + 1);
+ for (int i = 0; i <= num; ++i) {
+ addLabel(loc.toString(act, 'f', decimals), act);
+ addTick(act);
+ act += actStep;
+ }
}
CountAxis::CountAxis(QtCharts::QChart *chart, int count, bool horizontal) :
@@ -114,9 +245,12 @@ CountAxis::CountAxis(QtCharts::QChart *chart, int count, bool horizontal) :
void CountAxis::updateLabels()
{
+ labels.clear();
+ ticks.clear();
+
QLocale loc;
QString countString = loc.toString(count);
- int numTicks = guessNumTicks(this, { countString });
+ int numTicks = guessNumTicks({ countString });
// Get estimate of step size
if (count <= 0)
@@ -145,60 +279,43 @@ void CountAxis::updateLabels()
// Make maximum an integer number of steps, equal or greater than the needed counts
int num = (count - 1) / step + 1;
int max = num * step;
- numTicks = num + 1; // There is one more tick than steps
- setLabelFormat("%.0f");
setRange(0, max);
- setTickCount(numTicks);
-}
-CategoryAxis::CategoryAxis(QtCharts::QChart *chart, const std::vector<QString> &labels, bool horizontal) :
- StatsAxisTemplate(chart, horizontal)
-{
- for (const QString &s: labels)
- append(s);
+ labels.reserve(max + 1);
+ ticks.reserve(max + 1);
+ for (int i = 0; i <= max; i += step) {
+ addLabel(loc.toString(i), static_cast<double>(i));
+ addTick(static_cast<double>(i));
+ }
}
-void CategoryAxis::updateLabels()
+CategoryAxis::CategoryAxis(QtCharts::QChart *chart, const std::vector<QString> &labelsIn, bool horizontal) :
+ StatsAxis(chart, horizontal, true)
{
+ labels.reserve(labelsIn.size());
+ ticks.reserve(labelsIn.size() + 1);
+ double pos = 0.0;
+ addTick(-0.5);
+ for (const QString &s: labelsIn) {
+ addLabel(s, pos);
+ addTick(pos + 0.5);
+ pos += 1.0;
+ }
+ setRange(-0.5, static_cast<double>(labelsIn.size()) + 0.5);
}
-// A small helper class that makes strings unique. We need this,
-// because QCategoryAxis can only handle unique category names.
-// Disambiguate strings by adding unicode zero-width spaces.
-// Keep track of a list of strings and how many spaces have to
-// be added.
-class LabelDisambiguator {
- using Pair = std::pair<QString, int>;
- std::vector<Pair> entries;
-public:
- QString transmogrify(const QString &s);
-};
-
-QString LabelDisambiguator::transmogrify(const QString &s)
+void CategoryAxis::updateLabels()
{
- auto it = std::find_if(entries.begin(), entries.end(),
- [&s](const Pair &p) { return p.first == s; });
- if (it == entries.end()) {
- entries.emplace_back(s, 0);
- return s;
- } else {
- ++(it->second);
- return s + QString(it->second, QChar(0x200b));
- }
}
HistogramAxis::HistogramAxis(QtCharts::QChart *chart, std::vector<HistogramAxisEntry> bins, bool horizontal) :
- StatsAxisTemplate(chart, horizontal),
+ StatsAxis(chart, horizontal, false),
bin_values(std::move(bins))
{
if (bin_values.size() < 2) // Less than two makes no sense -> there must be at least one category
return;
- LabelDisambiguator labeler;
- for (HistogramAxisEntry &entry: bin_values)
- entry.name = labeler.transmogrify(entry.name);
-
// The caller can declare some bin labels as preferred, when there are
// too many labels to show all. Try to infer the preferred step size
// by finding two consecutive preferred labels. This supposes that
@@ -210,17 +327,7 @@ HistogramAxis::HistogramAxis(QtCharts::QChart *chart, std::vector<HistogramAxisE
auto it2 = std::find_if(next_it, bin_values.end(),
[](const HistogramAxisEntry &e) { return e.recommended; });
preferred_step = it2 == bin_values.end() ? 1 : it2 - it1;
- setMin(bin_values.front().value);
- setMax(bin_values.back().value);
- setStartValue(bin_values.front().value);
- setLabelsPosition(QCategoryAxis::AxisLabelsPositionOnValue);
-}
-
-std::pair<double, double> HistogramAxis::minMax() const
-{
- if (bin_values.size() < 2) // Less than two makes no sense -> there must be at least one category
- return { 0.0, 1.0 };
- return { QValueAxis::min(), QValueAxis::max() };
+ setRange(bin_values.front().value, bin_values.back().value);
}
// Initialize a histogram axis with the given labels. Labels are specified as (name, value, recommended) triplets.
@@ -229,20 +336,17 @@ std::pair<double, double> HistogramAxis::minMax() const
// There, we obviously want to show the years and not the quarters.
void HistogramAxis::updateLabels()
{
+ labels.clear();
+ ticks.clear();
+
if (bin_values.size() < 2) // Less than two makes no sense -> there must be at least one category
return;
- // There is no clear all labels function in QCategoryAxis!? You must be kidding.
- for (const QString &label: categoriesLabels())
- remove(label);
- if (count() > 0)
- qWarning("HistogramAxis::updateLabels(): labels left after clearing!?");
-
std::vector<QString> strings;
strings.reserve(bin_values.size());
for (auto &[name, value, recommended]: bin_values)
strings.push_back(name);
- int maxLabels = guessNumTicks(this, strings);
+ int maxLabels = guessNumTicks(strings);
int step = ((int)bin_values.size() - 1) / maxLabels + 1;
if (step < preferred_step) {
@@ -268,9 +372,11 @@ void HistogramAxis::updateLabels()
}
}
}
+ labels.reserve((bin_values.size() - first) / step + 1);
for (int i = first; i < (int)bin_values.size(); i += step) {
const auto &[name, value, recommended] = bin_values[i];
- append(name, value);
+ addLabel(name, value);
+ addTick(value);
}
}
diff --git a/stats/statsaxis.h b/stats/statsaxis.h
index 468dee1c2..c9619a124 100644
--- a/stats/statsaxis.h
+++ b/stats/statsaxis.h
@@ -1,50 +1,73 @@
// SPDX-License-Identifier: GPL-2.0
-// Supported chart axes
#ifndef STATS_AXIS_H
#define STATS_AXIS_H
+#include <memory>
#include <vector>
#include <QBarCategoryAxis>
#include <QCategoryAxis>
+#include <QFont>
+#include <QGraphicsSimpleTextItem>
+#include <QGraphicsLineItem>
#include <QValueAxis>
namespace QtCharts {
class QChart;
}
-class StatsAxis {
+class StatsAxis : QGraphicsLineItem {
public:
virtual ~StatsAxis();
- virtual void updateLabels() = 0;
- virtual QtCharts::QAbstractAxis *qaxis() = 0;
// Returns minimum and maximum of shown range, not of data points.
- virtual std::pair<double, double> minMax() const;
+ std::pair<double, double> minMax() const;
+
+ double width() const; // Only supported by vertical axes. Only valid after setSize().
+ double height() const; // Only supported for horizontal axes. Always valid.
+ void setSize(double size); // Width for horizontal and height for vertical.
+ void setPos(QPointF pos); // Must be called after setSize().
+ void setRange(double, double);
+
+ // Map x (horizontal) or y (vertical) coordinate to or from screen coordinate
+ double toScreen(double) const;
+ double toValue(double) const;
protected:
+ StatsAxis(QtCharts::QChart *chart, bool horizontal, bool labelsBetweenTicks);
QtCharts::QChart *chart;
- StatsAxis(QtCharts::QChart *chart, bool horizontal);
- int guessNumTicks(const QtCharts::QAbstractAxis *axis, const std::vector<QString> &strings) const;
+
+ struct Label {
+ std::unique_ptr<QGraphicsSimpleTextItem> label;
+ double pos;
+ Label(const QString &name, double pos, QtCharts::QChart *chart, const QFont &font);
+ };
+ std::vector<Label> labels;
+ void addLabel(const QString &label, double pos);
+ virtual void updateLabels() = 0;
+
+ struct Tick {
+ std::unique_ptr<QGraphicsLineItem> item;
+ double pos;
+ Tick(double pos, QtCharts::QChart *chart);
+ };
+ std::vector<Tick> ticks;
+ void addTick(double pos);
+
+ int guessNumTicks(const std::vector<QString> &strings) const;
bool horizontal;
-};
+ bool labelsBetweenTicks; // When labels are between ticks, they can be moved closer to the axis
-// Small template that derives from a QChart-axis and defines
-// the corresponding virtual axis() accessor.
-template<typename QAxis>
-class StatsAxisTemplate : public StatsAxis, public QAxis
-{
- using StatsAxis::StatsAxis;
- QtCharts::QAbstractAxis *qaxis() override final {
- return this;
- }
+ QFont labelFont, titleFont;
+ double size; // width for horizontal, height for vertical
+ double zeroOnScreen;
+ double min, max;
};
-class ValueAxis : public StatsAxisTemplate<QtCharts::QValueAxis> {
+class ValueAxis : public StatsAxis {
public:
ValueAxis(QtCharts::QChart *chart, double min, double max, int decimals, bool horizontal);
private:
double min, max;
int decimals;
void updateLabels() override;
- std::pair<double, double> minMax() const override;
};
class CountAxis : public ValueAxis {
@@ -55,7 +78,7 @@ private:
void updateLabels() override;
};
-class CategoryAxis : public StatsAxisTemplate<QtCharts::QBarCategoryAxis> {
+class CategoryAxis : public StatsAxis {
public:
CategoryAxis(QtCharts::QChart *chart, const std::vector<QString> &labels, bool horizontal);
private:
@@ -68,12 +91,11 @@ struct HistogramAxisEntry {
bool recommended;
};
-class HistogramAxis : public StatsAxisTemplate<QtCharts::QCategoryAxis> {
+class HistogramAxis : public StatsAxis {
public:
HistogramAxis(QtCharts::QChart *chart, std::vector<HistogramAxisEntry> bin_values, bool horizontal);
private:
void updateLabels() override;
- std::pair<double, double> minMax() const override;
std::vector<HistogramAxisEntry> bin_values;
int preferred_step;
};
diff --git a/stats/statscolors.h b/stats/statscolors.h
index 92ed53377..926e2e489 100644
--- a/stats/statscolors.h
+++ b/stats/statscolors.h
@@ -11,6 +11,7 @@ inline const QColor highlightedColor(Qt::yellow);
inline const QColor highlightedBorderColor(0xaa, 0xaa, 0x22);
inline const QColor darkLabelColor(Qt::black);
inline const QColor lightLabelColor(Qt::white);
+inline const QColor axisColor(Qt::black);
QColor binColor(int bin, int numBins);
QColor labelColor(int bin, size_t numBins);
diff --git a/stats/statsseries.cpp b/stats/statsseries.cpp
index 48b31336e..5996508a4 100644
--- a/stats/statsseries.cpp
+++ b/stats/statsseries.cpp
@@ -2,18 +2,17 @@
#include "statsseries.h"
#include "statsaxis.h"
-#include <QChart>
-
StatsSeries::StatsSeries(QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) :
- xAxis(xAxis), yAxis(yAxis)
+ chart(chart), xAxis(xAxis), yAxis(yAxis)
{
- chart->addSeries(this);
- if (xAxis && yAxis) {
- attachAxis(xAxis->qaxis());
- attachAxis(yAxis->qaxis());
- }
}
StatsSeries::~StatsSeries()
{
}
+
+QPointF StatsSeries::toScreen(QPointF p)
+{
+ return xAxis && yAxis ? QPointF(xAxis->toScreen(p.x()), yAxis->toScreen(p.y()))
+ : QPointF(0.0, 0.0);
+}
diff --git a/stats/statsseries.h b/stats/statsseries.h
index 9b247fb95..5c1833c8a 100644
--- a/stats/statsseries.h
+++ b/stats/statsseries.h
@@ -23,7 +23,9 @@ public:
virtual bool hover(QPointF pos) = 0; // Called on mouse movement. Return true if an item of this series is highlighted.
virtual void unhighlight() = 0; // Unhighlight any highlighted item.
protected:
+ QtCharts::QChart *chart;
StatsAxis *xAxis, *yAxis; // May be zero for charts without axes (pie charts).
+ QPointF toScreen(QPointF p);
};
#endif
diff --git a/stats/statsview.cpp b/stats/statsview.cpp
index 6709407e1..fdf6e9dc3 100644
--- a/stats/statsview.cpp
+++ b/stats/statsview.cpp
@@ -65,6 +65,8 @@ bool StatsView::EventFilter::eventFilter(QObject *o, QEvent *event)
StatsView::StatsView(QWidget *parent) : QQuickWidget(parent),
highlightedSeries(nullptr),
+ xAxis(nullptr),
+ yAxis(nullptr),
eventFilter(this)
{
setResizeMode(QQuickWidget::SizeRootObjectToView);
@@ -89,10 +91,33 @@ StatsView::~StatsView()
{
}
-void StatsView::plotAreaChanged(const QRectF &)
+void StatsView::plotAreaChanged(const QRectF &r)
{
- for (auto &axis: axes)
- axis->updateLabels();
+ double left = r.x() + sceneBorder;
+ double top = r.y() + sceneBorder;
+ double right = r.right() - sceneBorder;
+ double bottom = r.bottom() - sceneBorder;
+ const double minSize = 30.0;
+
+ if (title)
+ top += title->boundingRect().height() + titleBorder;
+ // Currently, we only have either none, or an x- and a y-axis
+ if (xAxis)
+ bottom -= xAxis->height();
+ if (bottom - top < minSize)
+ return;
+ if (yAxis) {
+ yAxis->setSize(bottom - top);
+ left += yAxis->width();
+ yAxis->setPos(QPointF(left, bottom));
+ }
+ if (right - left < minSize)
+ return;
+ if (xAxis) {
+ xAxis->setSize(right - left);
+ xAxis->setPos(QPointF(left, bottom));
+ }
+
for (auto &series: series)
series->updatePositions();
for (QuartileMarker &marker: quartileMarkers)
@@ -133,8 +158,6 @@ void StatsView::hover(QPointF pos)
template <typename T, class... Args>
T *StatsView::createSeries(Args&&... args)
{
- StatsAxis *xAxis = axes.size() >= 2 ? axes[0].get() : nullptr;
- StatsAxis *yAxis = axes.size() >= 2 ? axes[1].get() : nullptr;
T *res = new T(chart, xAxis, yAxis, std::forward<Args>(args)...);
series.emplace_back(res);
series.back()->updatePositions();
@@ -156,24 +179,23 @@ void StatsView::updateTitlePos()
if (!title)
return;
QRectF rect = chart->plotArea();
- title->setPos((rect.width() - title->boundingRect().width()) / 2.0,
+ title->setPos(sceneBorder + (rect.width() - title->boundingRect().width()) / 2.0,
sceneBorder);
}
template <typename T, class... Args>
T *StatsView::createAxis(const QString &title, Args&&... args)
{
+ // TODO: set title
T *res = new T(chart, std::forward<Args>(args)...);
axes.emplace_back(res);
- axes.back()->updateLabels();
- axes.back()->qaxis()->setTitleText(title);
return res;
}
-void StatsView::addAxes(StatsAxis *x, StatsAxis *y)
+void StatsView::setAxes(StatsAxis *x, StatsAxis *y)
{
- chart->addAxis(x->qaxis(), Qt::AlignBottom);
- chart->addAxis(y->qaxis(), Qt::AlignLeft);
+ xAxis = x;
+ yAxis = y;
}
void StatsView::reset()
@@ -181,6 +203,7 @@ void StatsView::reset()
if (!chart)
return;
highlightedSeries = nullptr;
+ xAxis = yAxis = nullptr;
legend.reset();
series.clear();
quartileMarkers.clear();
@@ -365,9 +388,9 @@ void StatsView::plotBarChart(const std::vector<dive *> &dives,
CountAxis *valAxis = createCountAxis(maxVal, isHorizontal);
if (isHorizontal)
- addAxes(valAxis, catAxis);
+ setAxes(valAxis, catAxis);
else
- addAxes(catAxis, valAxis);
+ setAxes(catAxis, valAxis);
// Paint legend first, because the bin-names will be moved away from.
if (showLegend)
@@ -478,9 +501,9 @@ void StatsView::plotValueChart(const std::vector<dive *> &dives,
0.0, maxValue, valueVariable->decimals(), isHorizontal);
if (isHorizontal)
- addAxes(valAxis, catAxis);
+ setAxes(valAxis, catAxis);
else
- addAxes(catAxis, valAxis);
+ setAxes(catAxis, valAxis);
std::vector<BarSeries::ValueItem> items;
items.reserve(categoryBins.size());
@@ -546,9 +569,9 @@ void StatsView::plotDiscreteCountChart(const std::vector<dive *> &dives,
CountAxis *valAxis = createCountAxis(maxCount, isHorizontal);
if (isHorizontal)
- addAxes(valAxis, catAxis);
+ setAxes(valAxis, catAxis);
else
- addAxes(catAxis, valAxis);
+ setAxes(catAxis, valAxis);
std::vector<BarSeries::CountItem> items;
items.reserve(categoryBins.size());
@@ -613,7 +636,7 @@ void StatsView::plotDiscreteBoxChart(const std::vector<dive *> &dives,
ValueAxis *valueAxis = createAxis<ValueAxis>(valueVariable->nameWithUnit(),
minY, maxY, valueVariable->decimals(), false);
- addAxes(catAxis, valueAxis);
+ setAxes(catAxis, valueAxis);
BoxSeries *series = createSeries<BoxSeries>(valueVariable->name(), valueVariable->unitSymbol(), valueVariable->decimals());
@@ -648,7 +671,7 @@ void StatsView::plotDiscreteScatter(const std::vector<dive *> &dives,
ValueAxis *valAxis = createAxis<ValueAxis>(valueVariable->nameWithUnit(),
minValue, maxValue, valueVariable->decimals(), false);
- addAxes(catAxis, valAxis);
+ setAxes(catAxis, valAxis);
ScatterSeries *series = createSeries<ScatterSeries>(*categoryVariable, *valueVariable);
double x = 0.0;
@@ -658,18 +681,18 @@ void StatsView::plotDiscreteScatter(const std::vector<dive *> &dives,
if (quartiles) {
StatsQuartiles quartiles = StatsVariable::quartiles(array);
if (quartiles.isValid()) {
- quartileMarkers.emplace_back(x, quartiles.q1, series);
- quartileMarkers.emplace_back(x, quartiles.q2, series);
- quartileMarkers.emplace_back(x, quartiles.q3, series);
+ quartileMarkers.emplace_back(x, quartiles.q1, chart, catAxis, valAxis);
+ quartileMarkers.emplace_back(x, quartiles.q2, chart, catAxis, valAxis);
+ quartileMarkers.emplace_back(x, quartiles.q3, chart, catAxis, valAxis);
}
}
x += 1.0;
}
}
-StatsView::QuartileMarker::QuartileMarker(double pos, double value, QtCharts::QAbstractSeries *series) :
- item(new QGraphicsLineItem(series->chart())),
- series(series),
+StatsView::QuartileMarker::QuartileMarker(double pos, double value, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) :
+ item(new QGraphicsLineItem(chart)),
+ xAxis(xAxis), yAxis(yAxis),
pos(pos),
value(value)
{
@@ -680,15 +703,18 @@ StatsView::QuartileMarker::QuartileMarker(double pos, double value, QtCharts::QA
void StatsView::QuartileMarker::updatePosition()
{
- QtCharts::QChart *chart = series->chart();
- QPointF center = chart->mapToPosition(QPointF(pos, value), series);
- item->setLine(center.x() - quartileMarkerSize / 2.0, center.y(),
- center.x() + quartileMarkerSize / 2.0, center.y());
+ if (!xAxis || !yAxis)
+ return;
+ double x = xAxis->toScreen(pos);
+ double y = yAxis->toScreen(value);
+ item->setLine(x - quartileMarkerSize / 2.0, y,
+ x + quartileMarkerSize / 2.0, y);
}
-StatsView::LineMarker::LineMarker(QPointF from, QPointF to, QPen pen, QtCharts::QAbstractSeries *series) :
- item(new QGraphicsLineItem(series->chart())),
- series(series), from(from), to(to)
+StatsView::LineMarker::LineMarker(QPointF from, QPointF to, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) :
+ item(new QGraphicsLineItem(chart)),
+ xAxis(xAxis), yAxis(yAxis),
+ from(from), to(to)
{
item->setZValue(ZValues::chartFeatures);
item->setPen(pen);
@@ -697,12 +723,16 @@ StatsView::LineMarker::LineMarker(QPointF from, QPointF to, QPen pen, QtCharts::
void StatsView::LineMarker::updatePosition()
{
- QtCharts::QChart *chart = series->chart();
- item->setLine(QLineF(chart->mapToPosition(from, series),
- chart->mapToPosition(to, series)));
+ if (!xAxis || !yAxis)
+ return;
+ double x1 = xAxis->toScreen(from.x());
+ double y1 = yAxis->toScreen(from.y());
+ double x2 = xAxis->toScreen(to.x());
+ double y2 = yAxis->toScreen(to.y());
+ item->setLine(x1, y1, x2, y2);
}
-void StatsView::addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, QtCharts::QAbstractSeries *series)
+void StatsView::addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis)
{
// Sanity check: line above or below chart
double y1 = a * minX + b;
@@ -721,14 +751,14 @@ void StatsView::addLinearRegression(double a, double b, double minX, double maxX
minX = std::max(minX, intersect_x1);
maxX = std::min(maxX, intersect_x2);
}
- lineMarkers.emplace_back(QPointF(minX, a * minX + b), QPointF(maxX, a * maxX + b), QPen(Qt::red), series);
+ lineMarkers.emplace_back(QPointF(minX, a * minX + b), QPointF(maxX, a * maxX + b), QPen(Qt::red), chart, xAxis, yAxis);
}
-void StatsView::addHistogramMarker(double pos, double low, double high, const QPen &pen, bool isHorizontal, QtCharts::QAbstractSeries *series)
+void StatsView::addHistogramMarker(double pos, double low, double high, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis)
{
QPointF from = isHorizontal ? QPointF(low, pos) : QPointF(pos, low);
QPointF to = isHorizontal ? QPointF(high, pos) : QPointF(pos, high);
- lineMarkers.emplace_back(from, to, pen, series);
+ lineMarkers.emplace_back(from, to, pen, chart, xAxis, yAxis);
}
// Yikes, we get our data in different kinds of (bin, value) pairs.
@@ -780,9 +810,9 @@ void StatsView::plotHistogramCountChart(const std::vector<dive *> &dives,
double chartHeight = valAxis->minMax().second;
if (isHorizontal)
- addAxes(valAxis, catAxis);
+ setAxes(valAxis, catAxis);
else
- addAxes(catAxis, valAxis);
+ setAxes(catAxis, valAxis);
std::vector<BarSeries::CountItem> items;
items.reserve(categoryBins.size());
@@ -797,7 +827,7 @@ void StatsView::plotHistogramCountChart(const std::vector<dive *> &dives,
categoryBinner->formatWithUnit(*bin), total });
}
- BarSeries *series = createSeries<BarSeries>(isHorizontal, categoryVariable->name(), items);
+ createSeries<BarSeries>(isHorizontal, categoryVariable->name(), items);
if (categoryVariable->type() == StatsVariable::Type::Numeric) {
if (showMean) {
@@ -805,14 +835,14 @@ void StatsView::plotHistogramCountChart(const std::vector<dive *> &dives,
QPen meanPen(Qt::green);
meanPen.setWidth(2);
if (!std::isnan(mean))
- addHistogramMarker(mean, 0.0, chartHeight, meanPen, isHorizontal, series);
+ addHistogramMarker(mean, 0.0, chartHeight, meanPen, isHorizontal, xAxis, yAxis);
}
if (showMedian) {
double median = categoryVariable->quartiles(dives).q2;
QPen medianPen(Qt::red);
medianPen.setWidth(2);
if (!std::isnan(median))
- addHistogramMarker(median, 0.0, chartHeight, medianPen, isHorizontal, series);
+ addHistogramMarker(median, 0.0, chartHeight, medianPen, isHorizontal, xAxis, yAxis);
}
}
}
@@ -845,9 +875,9 @@ void StatsView::plotHistogramValueChart(const std::vector<dive *> &dives,
0.0, maxValue, decimals, isHorizontal);
if (isHorizontal)
- addAxes(valAxis, catAxis);
+ setAxes(valAxis, catAxis);
else
- addAxes(catAxis, valAxis);
+ setAxes(catAxis, valAxis);
std::vector<BarSeries::ValueItem> items;
items.reserve(categoryBins.size());
@@ -894,9 +924,9 @@ void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives,
CountAxis *valAxis = createCountAxis(data.maxCategoryCount, isHorizontal);
if (isHorizontal)
- addAxes(valAxis, catAxis);
+ setAxes(valAxis, catAxis);
else
- addAxes(catAxis, valAxis);
+ setAxes(catAxis, valAxis);
std::vector<BarSeries::MultiItem> items;
items.reserve(data.hbin_counts.size());
@@ -933,7 +963,7 @@ void StatsView::plotHistogramBoxChart(const std::vector<dive *> &dives,
ValueAxis *valueAxis = createAxis<ValueAxis>(valueVariable->nameWithUnit(),
minY, maxY, valueVariable->decimals(), false);
- addAxes(catAxis, valueAxis);
+ setAxes(catAxis, valueAxis);
BoxSeries *series = createSeries<BoxSeries>(valueVariable->name(), valueVariable->unitSymbol(), valueVariable->decimals());
@@ -1020,7 +1050,7 @@ void StatsView::plotScatter(const std::vector<dive *> &dives, const StatsVariabl
StatsAxis *axisY = createAxis<ValueAxis>(valueVariable->nameWithUnit(), minY, maxY, valueVariable->decimals(), false);
- addAxes(axisX, axisY);
+ setAxes(axisX, axisY);
ScatterSeries *series = createSeries<ScatterSeries>(*categoryVariable, *valueVariable);
for (auto [x, y, dive]: points)
@@ -1031,6 +1061,6 @@ void StatsView::plotScatter(const std::vector<dive *> &dives, const StatsVariabl
if (!std::isnan(a)) {
auto [minx, maxx] = axisX->minMax();
auto [miny, maxy] = axisY->minMax();
- addLinearRegression(a, b, minx, maxx, miny, maxy, series);
+ addLinearRegression(a, b, minx, maxx, miny, maxy, xAxis, yAxis);
}
}
diff --git a/stats/statsview.h b/stats/statsview.h
index fac61a55b..5df05c15b 100644
--- a/stats/statsview.h
+++ b/stats/statsview.h
@@ -41,7 +41,7 @@ private slots:
void replotIfVisible();
private:
void reset(); // clears all series and axes
- void addAxes(StatsAxis *x, StatsAxis *y); // Add new x- and y-axis
+ void setAxes(StatsAxis *x, StatsAxis *y);
void plotBarChart(const std::vector<dive *> &dives,
ChartSubType subType,
const StatsVariable *categoryVariable, const StatsBinner *categoryBinner,
@@ -99,23 +99,23 @@ private:
// A short line used to mark quartiles
struct QuartileMarker {
std::unique_ptr<QGraphicsLineItem> item;
- QtCharts::QAbstractSeries *series; // In case we ever support charts with multiple axes
+ StatsAxis *xAxis, *yAxis;
double pos, value;
- QuartileMarker(double pos, double value, QtCharts::QAbstractSeries *series);
+ QuartileMarker(double pos, double value, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis);
void updatePosition();
};
// A general line marker
struct LineMarker {
std::unique_ptr<QGraphicsLineItem> item;
- QtCharts::QAbstractSeries *series; // In case we ever support charts with multiple axes
+ StatsAxis *xAxis, *yAxis;
QPointF from, to; // In local coordinates
void updatePosition();
- LineMarker(QPointF from, QPointF to, QPen pen, QtCharts::QAbstractSeries *series);
+ LineMarker(QPointF from, QPointF to, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis);
};
- void addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, QtCharts::QAbstractSeries *series);
- void addHistogramMarker(double pos, double low, double high, const QPen &pen, bool isHorizontal, QtCharts::QAbstractSeries *series);
+ void addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis);
+ void addHistogramMarker(double pos, double low, double high, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis);
StatsState state;
QtCharts::QChart *chart;
@@ -127,6 +127,7 @@ private:
std::vector<LineMarker> lineMarkers;
std::unique_ptr<QGraphicsSimpleTextItem> title;
StatsSeries *highlightedSeries;
+ StatsAxis *xAxis, *yAxis;
// This is unfortunate: we can't derive from QChart, because the chart is allocated by QML.
// Therefore, we have to listen to hover events using an events-filter.
diff --git a/stats/zvalues.h b/stats/zvalues.h
index f4e7d6f09..1b6dc5d22 100644
--- a/stats/zvalues.h
+++ b/stats/zvalues.h
@@ -6,12 +6,12 @@
#ifndef ZVALUES_H
struct ZValues {
- static constexpr double axes = 0.0;
- static constexpr double series = 11.0;
- static constexpr double seriesLabels = 12.0;
- static constexpr double chartFeatures = 13.0; // quartile markers and regression lines
- static constexpr double informationBox = 14.0;
- static constexpr double legend = 15.0;
+ static constexpr double series = 0.0;
+ static constexpr double axes = 1.0;
+ static constexpr double seriesLabels = 2.0;
+ static constexpr double chartFeatures = 3.0; // quartile markers and regression lines
+ static constexpr double informationBox = 4.0;
+ static constexpr double legend = 5.0;
};
#endif