aboutsummaryrefslogtreecommitdiffstats
path: root/stats
diff options
context:
space:
mode:
Diffstat (limited to 'stats')
-rw-r--r--stats/statsaxis.cpp201
-rw-r--r--stats/statsaxis.h39
-rw-r--r--stats/statsview.cpp19
-rw-r--r--stats/statsview.h1
4 files changed, 143 insertions, 117 deletions
diff --git a/stats/statsaxis.cpp b/stats/statsaxis.cpp
index 2c5a5d961..5c4f0403e 100644
--- a/stats/statsaxis.cpp
+++ b/stats/statsaxis.cpp
@@ -4,6 +4,7 @@
#include "statshelper.h"
#include "statstranslations.h"
#include "statsvariables.h"
+#include "statsview.h"
#include "zvalues.h"
#include "core/pref.h"
#include "core/subsurface-time.h"
@@ -23,23 +24,19 @@ static const double axisLabelSpaceVertical = 2.0; // Space between axis or ticks
static const double axisTitleSpaceHorizontal = 2.0; // Space between labels and title
static const double axisTitleSpaceVertical = 2.0; // Space between labels and title
-StatsAxis::StatsAxis(const QString &titleIn, bool horizontal, bool labelsBetweenTicks) :
- horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks),
+StatsAxis::StatsAxis(StatsView &view, const QString &title, bool horizontal, bool labelsBetweenTicks) :
+ ChartPixmapItem(view, ChartZValue::Axes),
+ line(view.createChartItem<ChartLineItem>(ChartZValue::Axes, axisColor, axisWidth)),
+ title(title), horizontal(horizontal), labelsBetweenTicks(labelsBetweenTicks),
size(1.0), zeroOnScreen(0.0), min(0.0), max(1.0), labelWidth(0.0)
{
- // use a Light version of the application fond for both labels and title
+ // use a Light version of the application font for both labels and title
labelFont = QFont();
labelFont.setWeight(QFont::Light);
titleFont = labelFont;
- setPen(QPen(axisColor, axisWidth));
- setZValue(ZValues::axes);
- if (!titleIn.isEmpty()) {
- title.reset(new QGraphicsSimpleTextItem(titleIn, this));
- title->setFont(titleFont);
- title->setBrush(darkLabelColor);
- if (!horizontal)
- title->setRotation(-90.0);
- }
+ QFontMetrics fm(titleFont);
+ titleWidth = title.isEmpty() ? 0.0
+ : static_cast<double>(fm.size(Qt::TextSingleLine, title).width());
}
StatsAxis::~StatsAxis()
@@ -101,7 +98,7 @@ int StatsAxis::guessNumTicks(const std::vector<QString> &strings) const
double StatsAxis::titleSpace() const
{
- if (!title)
+ if (title.isEmpty())
return 0.0;
return horizontal ? QFontMetrics(titleFont).height() + axisTitleSpaceHorizontal
: QFontMetrics(titleFont).height() + axisTitleSpaceVertical;
@@ -124,31 +121,14 @@ double StatsAxis::height() const
(labelsBetweenTicks ? 0.0 : axisTickSizeHorizontal);
}
-StatsAxis::Label::Label(const QString &name, double pos, QGraphicsScene *scene, const QFont &font) :
- label(createItem<QGraphicsSimpleTextItem>(scene, name)),
- pos(pos)
-{
- label->setBrush(QBrush(darkLabelColor));
- label->setFont(font);
- label->setZValue(ZValues::axes);
-}
-
-void StatsAxis::addLabel(const QString &label, double pos)
+void StatsAxis::addLabel(const QFontMetrics &fm, const QString &label, double pos)
{
- labels.emplace_back(label, pos, scene(), labelFont);
-}
-
-StatsAxis::Tick::Tick(double pos, QGraphicsScene *scene) :
- item(createItemPtr<QGraphicsLineItem>(scene)),
- pos(pos)
-{
- item->setPen(QPen(axisColor, axisTickWidth));
- item->setZValue(ZValues::axes);
+ labels.push_back({ label, fm.size(Qt::TextSingleLine, label).width(), pos });
}
void StatsAxis::addTick(double pos)
{
- ticks.emplace_back(pos, scene());
+ ticks.push_back({ view.createChartItem<ChartLineItem>(ChartZValue::Axes, axisColor, axisTickWidth), pos });
}
std::vector<double> StatsAxis::ticksPositions() const
@@ -178,60 +158,105 @@ double StatsAxis::toValue(double pos) const
void StatsAxis::setSize(double sizeIn)
{
size = sizeIn;
+
+ labels.clear();
+ ticks.clear();
updateLabels();
+
labelWidth = 0.0;
for (const Label &label: labels) {
- double w = label.label->boundingRect().width();
- if (w > labelWidth)
- labelWidth = w;
+ if (label.width > labelWidth)
+ labelWidth = label.width;
+ }
+
+ QFontMetrics fm(labelFont);
+ int fontHeight = fm.height();
+ if (horizontal) {
+ double pixmapWidth = size;
+ double offsetX = 0.0;
+ if (!labels.empty() && !labelsBetweenTicks) {
+ pixmapWidth += labels.front().width / 2.0 + labels.back().width / 2.0;
+ offsetX += labels.front().width / 2.0;
+ }
+
+ double pixmapHeight = fontHeight + titleSpace();
+ double offsetY = -axisWidth / 2.0 - axisLabelSpaceHorizontal -
+ (labelsBetweenTicks ? 0.0 : axisTickSizeHorizontal);
+
+ ChartPixmapItem::resize(QSizeF(pixmapWidth, pixmapHeight)); // Note: this rounds the dimensions up
+ offset = QPointF(round(offsetX), round(offsetY));
+ img->fill(Qt::transparent);
+
+ painter->setPen(QPen(darkLabelColor));
+ painter->setFont(labelFont);
+ for (const Label &label: labels) {
+ double x = (label.pos - min) / (max - min) * size + offset.x() - round(label.width / 2.0);
+ QRectF rect(x, 0.0, label.width, fontHeight);
+ painter->drawText(rect, label.label);
+ }
+ if (!title.isEmpty()) {
+ QRectF rect(offset.x() + round((size - titleWidth) / 2.0),
+ fontHeight + axisTitleSpaceHorizontal,
+ titleWidth, fontHeight);
+ painter->setFont(titleFont);
+ painter->drawText(rect, title);
+ }
+ } else {
+ double pixmapWidth = labelWidth + titleSpace();
+ double offsetX = pixmapWidth + axisLabelSpaceVertical + (labelsBetweenTicks ? 0.0 : axisTickSizeVertical);
+
+ double pixmapHeight = ceil(size + axisTickWidth);
+ double offsetY = size;
+ if (!labels.empty() && !labelsBetweenTicks) {
+ pixmapHeight += fontHeight;
+ offsetY += fontHeight / 2.0;
+ }
+
+ ChartPixmapItem::resize(QSizeF(pixmapWidth, pixmapHeight)); // Note: this rounds the dimensions up
+ offset = QPointF(round(offsetX), round(offsetY));
+ img->fill(Qt::transparent);
+
+ painter->setPen(QPen(darkLabelColor));
+ painter->setFont(labelFont);
+ for (const Label &label: labels) {
+ double y = (min - label.pos) / (max - min) * size + offset.y() - round(fontHeight / 2.0);
+ QRectF rect(pixmapWidth - label.width, y, label.width, fontHeight);
+ painter->drawText(rect, label.label);
+ }
+ if (!title.isEmpty()) {
+ painter->rotate(-90.0);
+ QRectF rect(round(-(offsetY + titleWidth) / 2.0), 0.0, titleWidth, fontHeight);
+ painter->setFont(titleFont);
+ painter->drawText(rect, title);
+ painter->resetTransform();
+ }
}
}
void StatsAxis::setPos(QPointF pos)
{
+ zeroOnScreen = horizontal ? pos.x() : pos.y();
+ ChartPixmapItem::setPos(pos - offset);
+
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) {
+ for (const Tick &tick: ticks) {
double x = toScreen(tick.pos);
- tick.item->setLine(x, y, x, y + axisTickSizeHorizontal);
+ tick.item->setLine(QPointF(x, y), QPointF(x, y + axisTickSizeHorizontal));
}
- setLine(zeroOnScreen, y, zeroOnScreen + size, y);
- if (title)
- title->setPos(zeroOnScreen + (size - title->boundingRect().width()) / 2.0,
- labelY + QFontMetrics(labelFont).height() + axisTitleSpaceHorizontal);
+ line->setLine(QPointF(zeroOnScreen, y), QPointF(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) {
+ for (const Tick &tick: ticks) {
double y = toScreen(tick.pos);
- tick.item->setLine(x, y, x - axisTickSizeVertical, y);
+ tick.item->setLine(QPointF(x, y), QPointF(x - axisTickSizeVertical, y));
}
- // This is very confusing: even though we need the height of the title, the correct
- // size is stored in boundingRect().width(). Presumably because the item is rotated
- // by -90°. Apparently, the boundingRect is in item-local coordinates?
- if (title)
- title->setPos(labelX - labelWidth - QFontMetrics(labelFont).height() - axisTitleSpaceVertical,
- zeroOnScreen - (size - title->boundingRect().width()) / 2.0);
- setLine(x, zeroOnScreen, x, zeroOnScreen - size);
+ line->setLine(QPointF(x, zeroOnScreen), QPointF(x, zeroOnScreen - size));
}
}
-ValueAxis::ValueAxis(const QString &title, double min, double max, int decimals, bool horizontal) :
- StatsAxis(title, horizontal, false),
+ValueAxis::ValueAxis(StatsView &view, const QString &title, double min, double max, int decimals, bool horizontal) :
+ StatsAxis(view, title, horizontal, false),
min(min), max(max), decimals(decimals)
{
// Avoid degenerate cases
@@ -251,9 +276,6 @@ std::pair<QString, QString> ValueAxis::getFirstLastLabel() const
void ValueAxis::updateLabels()
{
- labels.clear();
- ticks.clear();
-
QLocale loc;
auto [minString, maxString] = getFirstLastLabel();
int numTicks = guessNumTicks({ minString, maxString});
@@ -283,15 +305,16 @@ void ValueAxis::updateLabels()
double act = actMin;
labels.reserve(num + 1);
ticks.reserve(num + 1);
+ QFontMetrics fm(labelFont);
for (int i = 0; i <= num; ++i) {
- addLabel(loc.toString(act, 'f', decimals), act);
+ addLabel(fm, loc.toString(act, 'f', decimals), act);
addTick(act);
act += actStep;
}
}
-CountAxis::CountAxis(const QString &title, int count, bool horizontal) :
- ValueAxis(title, 0.0, (double)count, 0, horizontal),
+CountAxis::CountAxis(StatsView &view, const QString &title, int count, bool horizontal) :
+ ValueAxis(view, title, 0.0, (double)count, 0, horizontal),
count(count)
{
}
@@ -306,9 +329,6 @@ std::pair<QString, QString> CountAxis::getFirstLastLabel() const
void CountAxis::updateLabels()
{
- labels.clear();
- ticks.clear();
-
QLocale loc;
QString countString = loc.toString(count);
int numTicks = guessNumTicks({ countString });
@@ -345,14 +365,15 @@ void CountAxis::updateLabels()
labels.reserve(max + 1);
ticks.reserve(max + 1);
+ QFontMetrics fm(labelFont);
for (int i = 0; i <= max; i += step) {
- addLabel(loc.toString(i), static_cast<double>(i));
+ addLabel(fm, loc.toString(i), static_cast<double>(i));
addTick(static_cast<double>(i));
}
}
-CategoryAxis::CategoryAxis(const QString &title, const std::vector<QString> &labels, bool horizontal) :
- StatsAxis(title, horizontal, true),
+CategoryAxis::CategoryAxis(StatsView &view, const QString &title, const std::vector<QString> &labels, bool horizontal) :
+ StatsAxis(view, title, horizontal, true),
labelsText(labels)
{
setRange(-0.5, static_cast<double>(labels.size()) + 0.5);
@@ -392,8 +413,6 @@ void CategoryAxis::updateLabels()
QString ellipsis = horizontal ? getEllipsis(fm, size_per_label) : QString();
- labels.clear();
- ticks.clear();
labels.reserve(labelsText.size());
ticks.reserve(labelsText.size() + 1);
double pos = 0.0;
@@ -401,18 +420,18 @@ void CategoryAxis::updateLabels()
for (const QString &s: labelsText) {
if (horizontal) {
double width = static_cast<double>(fm.size(Qt::TextSingleLine, s).width());
- addLabel(width < size_per_label ? s : ellipsis, pos);
+ addLabel(fm, width < size_per_label ? s : ellipsis, pos);
} else {
if (fontHeight < size_per_label)
- addLabel(s, pos);
+ addLabel(fm, s, pos);
}
addTick(pos + 0.5);
pos += 1.0;
}
}
-HistogramAxis::HistogramAxis(const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) :
- StatsAxis(title, horizontal, false),
+HistogramAxis::HistogramAxis(StatsView &view, const QString &title, std::vector<HistogramAxisEntry> bins, bool horizontal) :
+ StatsAxis(view, title, 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
@@ -446,9 +465,6 @@ std::pair<QString, QString> HistogramAxis::getFirstLastLabel() 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;
@@ -488,9 +504,10 @@ void HistogramAxis::updateLabels()
if (first != 0)
addTick(bin_values.front().value);
int last = first;
+ QFontMetrics fm(labelFont);
for (int i = first; i < (int)bin_values.size(); i += step) {
const auto &[name, value, recommended] = bin_values[i];
- addLabel(name, value);
+ addLabel(fm, name, value);
addTick(value);
last = i;
}
@@ -617,7 +634,7 @@ static std::vector<HistogramAxisEntry> timeRangeToBins(double from, double to)
return res;
}
-DateAxis::DateAxis(const QString &title, double from, double to, bool horizontal) :
- HistogramAxis(title, timeRangeToBins(from, to), horizontal)
+DateAxis::DateAxis(StatsView &view, const QString &title, double from, double to, bool horizontal) :
+ HistogramAxis(view, title, timeRangeToBins(from, to), horizontal)
{
}
diff --git a/stats/statsaxis.h b/stats/statsaxis.h
index 9d46f753a..89c9edbe2 100644
--- a/stats/statsaxis.h
+++ b/stats/statsaxis.h
@@ -2,15 +2,19 @@
#ifndef STATS_AXIS_H
#define STATS_AXIS_H
+#include "chartitem.h"
+
#include <memory>
#include <vector>
#include <QFont>
-#include <QGraphicsSimpleTextItem>
-#include <QGraphicsLineItem>
-class QGraphicsScene;
+class StatsView;
+class ChartLineItem;
+class QFontMetrics;
-class StatsAxis : public QGraphicsLineItem {
+// The labels and the title of the axis are rendered into a pixmap.
+// The ticks and the baseline are realized as individual ChartLineItems.
+class StatsAxis : public ChartPixmapItem {
public:
virtual ~StatsAxis();
// Returns minimum and maximum of shown range, not of data points.
@@ -30,22 +34,25 @@ public:
std::vector<double> ticksPositions() const; // Positions in screen coordinates
protected:
- StatsAxis(const QString &title, bool horizontal, bool labelsBetweenTicks);
+ StatsAxis(StatsView &view, const QString &title, bool horizontal, bool labelsBetweenTicks);
+
+ std::unique_ptr<ChartLineItem> line;
+ QString title;
+ double titleWidth;
struct Label {
- std::unique_ptr<QGraphicsSimpleTextItem> label;
+ QString label;
+ int width;
double pos;
- Label(const QString &name, double pos, QGraphicsScene *scene, const QFont &font);
};
std::vector<Label> labels;
- void addLabel(const QString &label, double pos);
+ void addLabel(const QFontMetrics &fm, const QString &label, double pos);
virtual void updateLabels() = 0;
virtual std::pair<QString, QString> getFirstLastLabel() const = 0;
struct Tick {
- std::unique_ptr<QGraphicsLineItem> item;
+ std::unique_ptr<ChartLineItem> item;
double pos;
- Tick(double pos, QGraphicsScene *scene);
};
std::vector<Tick> ticks;
void addTick(double pos);
@@ -55,9 +62,9 @@ protected:
bool labelsBetweenTicks; // When labels are between ticks, they can be moved closer to the axis
QFont labelFont, titleFont;
- std::unique_ptr<QGraphicsSimpleTextItem> title;
double size; // width for horizontal, height for vertical
double zeroOnScreen;
+ QPointF offset; // Offset of the label and title pixmap with respect to the (0,0) position.
double min, max;
double labelWidth; // Maximum width of labels
private:
@@ -66,7 +73,7 @@ private:
class ValueAxis : public StatsAxis {
public:
- ValueAxis(const QString &title, double min, double max, int decimals, bool horizontal);
+ ValueAxis(StatsView &view, const QString &title, double min, double max, int decimals, bool horizontal);
private:
double min, max;
int decimals;
@@ -76,7 +83,7 @@ private:
class CountAxis : public ValueAxis {
public:
- CountAxis(const QString &title, int count, bool horizontal);
+ CountAxis(StatsView &view, const QString &title, int count, bool horizontal);
private:
int count;
void updateLabels() override;
@@ -85,7 +92,7 @@ private:
class CategoryAxis : public StatsAxis {
public:
- CategoryAxis(const QString &title, const std::vector<QString> &labels, bool horizontal);
+ CategoryAxis(StatsView &view, const QString &title, const std::vector<QString> &labels, bool horizontal);
private:
std::vector<QString> labelsText;
void updateLabels();
@@ -100,7 +107,7 @@ struct HistogramAxisEntry {
class HistogramAxis : public StatsAxis {
public:
- HistogramAxis(const QString &title, std::vector<HistogramAxisEntry> bin_values, bool horizontal);
+ HistogramAxis(StatsView &view, const QString &title, std::vector<HistogramAxisEntry> bin_values, bool horizontal);
private:
void updateLabels() override;
std::pair<QString, QString> getFirstLastLabel() const override;
@@ -110,7 +117,7 @@ private:
class DateAxis : public HistogramAxis {
public:
- DateAxis(const QString &title, double from, double to, bool horizontal);
+ DateAxis(StatsView &view, const QString &title, double from, double to, bool horizontal);
};
#endif
diff --git a/stats/statsview.cpp b/stats/statsview.cpp
index 83a586456..e893c3ebb 100644
--- a/stats/statsview.cpp
+++ b/stats/statsview.cpp
@@ -38,6 +38,7 @@ StatsView::StatsView(QQuickItem *parent) : QQuickItem(parent),
draggedItem(nullptr),
rootNode(nullptr)
{
+ chartItems.reset(new std::vector<ChartItem *>[(size_t)ChartZValue::Count]);
setFlag(ItemHasContents, true);
connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &StatsView::replotIfVisible);
@@ -89,7 +90,6 @@ public:
QSGImageNode *imageNode; // imageNode to plot QGRaphicsScene on. Remove in due course.
// We entertain one node per Z-level.
std::array<QSGNode *, (size_t)ChartZValue::Count> zNodes;
- std::array<std::vector<ChartItem *>, (size_t)ChartZValue::Count> items;
};
RootNode::RootNode(QQuickWindow *w)
@@ -124,8 +124,8 @@ QSGNode *StatsView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNod
plotAreaChanged(plotRect.size());
}
- for (auto &v: n->items) {
- for (ChartItem *item: v) {
+ for (int i = 0; i < (int)ChartZValue::Count; ++i) {
+ for (ChartItem *item: chartItems[i]) {
if (item->dirty)
item->render();
}
@@ -151,7 +151,7 @@ void StatsView::addQSGNode(QSGNode *node, ChartZValue z)
void StatsView::unregisterChartItem(const ChartItem *item)
{
int idx = std::clamp((int)item->zValue, 0, (int)ChartZValue::Count - 1);
- std::vector<ChartItem *> &v = rootNode->items[idx];
+ std::vector<ChartItem *> &v = chartItems[idx];
auto it = std::find(v.begin(), v.end(), item);
if (it != v.end())
v.erase(it);
@@ -160,7 +160,7 @@ void StatsView::unregisterChartItem(const ChartItem *item)
void StatsView::registerChartItem(ChartItem *item)
{
int idx = std::clamp((int)item->zValue, 0, (int)ChartZValue::Count - 1);
- rootNode->items[idx].push_back(item);
+ chartItems[idx].push_back(item);
}
QQuickWindow *StatsView::w() const
@@ -307,8 +307,9 @@ void StatsView::updateTitlePos()
template <typename T, class... Args>
T *StatsView::createAxis(const QString &title, Args&&... args)
{
- T *res = createItem<T>(&scene, title, std::forward<Args>(args)...);
- axes.emplace_back(res);
+ std::unique_ptr<T> ptr = createChartItem<T>(title, std::forward<Args>(args)...);
+ T *res = ptr.get();
+ axes.push_back(std::move(ptr));
return res;
}
@@ -326,8 +327,8 @@ void StatsView::reset()
xAxis = yAxis = nullptr;
draggedItem = nullptr;
if (rootNode) {
- for (auto &v: rootNode->items)
- v.clear(); // non-owning pointers
+ for (int i = 0; i < (int)ChartZValue::Count; ++i)
+ chartItems[i].clear(); // non-owning pointers
}
legend.reset();
series.clear();
diff --git a/stats/statsview.h b/stats/statsview.h
index aad9ec977..c8e850d9b 100644
--- a/stats/statsview.h
+++ b/stats/statsview.h
@@ -142,6 +142,7 @@ private:
StatsState state;
QFont titleFont;
+ std::unique_ptr<std::vector<ChartItem *>[]> chartItems;
std::vector<std::unique_ptr<StatsAxis>> axes;
std::unique_ptr<StatsGrid> grid;
std::vector<std::unique_ptr<StatsSeries>> series;