summaryrefslogtreecommitdiffstats
path: root/stats/statsview.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'stats/statsview.cpp')
-rw-r--r--stats/statsview.cpp172
1 files changed, 85 insertions, 87 deletions
diff --git a/stats/statsview.cpp b/stats/statsview.cpp
index f42296fc2..1533583ba 100644
--- a/stats/statsview.cpp
+++ b/stats/statsview.cpp
@@ -6,7 +6,9 @@
#include "pieseries.h"
#include "scatterseries.h"
#include "statsaxis.h"
+#include "statscolors.h"
#include "statsgrid.h"
+#include "statshelper.h"
#include "statsstate.h"
#include "statstranslations.h"
#include "statsvariables.h"
@@ -15,12 +17,35 @@
#include "core/subsurface-qt/divelistnotifier.h"
#include <cmath>
-#include <QQuickItem>
-#include <QAbstractSeries>
-#include <QChart>
+#include <QGraphicsScene>
#include <QGraphicsSceneHoverEvent>
#include <QGraphicsSimpleTextItem>
-#include <QLocale>
+#include <QQuickItem>
+#include <QQuickWindow>
+#include <QSGImageNode>
+#include <QSGTexture>
+
+QSGNode *StatsView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
+{
+ // The QtQuick drawing interface is utterly bizzare with a distinct 1980ies-style memory management.
+ // This is just a copy of what is found in Qt's documentation.
+ QSGImageNode *n = static_cast<QSGImageNode *>(oldNode);
+ if (!n)
+ n = window()->createImageNode();
+
+ QRectF rect = boundingRect();
+ if (plotRect != rect) {
+ plotRect = rect;
+ plotAreaChanged(plotRect.size());
+ }
+
+ img->fill(backgroundColor);
+ scene.render(painter.get());
+ texture.reset(window()->createTextureFromImage(*img, QQuickWindow::TextureIsOpaque));
+ n->setTexture(texture.get());
+ n->setRect(rect);
+ return n;
+}
// Constants that control the graph layouts
static const QColor quartileMarkerColor(Qt::red);
@@ -28,76 +53,45 @@ static const double quartileMarkerSize = 15.0;
static const double sceneBorder = 5.0; // Border between scene edges and statitistics view
static const double titleBorder = 2.0; // Border between title and chart
-static const QUrl urlStatsView = QUrl(QStringLiteral("qrc:/qml/statsview.qml"));
-
-// We use QtQuick's ChartView so that we can show the statistics on mobile.
-// However, accessing the ChartView from C++ is maliciously cumbersome and
-// the full QChart interface is not exported. Fortunately, the interface
-// leaks the QChart object: We can create a dummy-series and access the chart
-// object via the chart() accessor function. By creating a "PieSeries", the
-// ChartView does not automatically add axes.
-static QtCharts::QChart *getChart(QQuickItem *item)
-{
- QtCharts::QAbstractSeries *abstract_series;
- if (!item)
- return nullptr;
- if (!QMetaObject::invokeMethod(item, "createSeries", Qt::AutoConnection,
- Q_RETURN_ARG(QtCharts::QAbstractSeries *, abstract_series),
- Q_ARG(int, QtCharts::QAbstractSeries::SeriesTypePie),
- Q_ARG(QString, QString()))) {
- qWarning("Couldn't call createSeries()");
- return nullptr;
- }
- QtCharts::QChart *res = abstract_series->chart();
- res->removeSeries(abstract_series);
- delete abstract_series;
- return res;
-}
-
-bool StatsView::EventFilter::eventFilter(QObject *o, QEvent *event)
-{
- if (event->type() == QEvent::GraphicsSceneHoverMove) {
- QGraphicsSceneHoverEvent *hover = static_cast<QGraphicsSceneHoverEvent *>(event);
- view->hover(hover->pos());
- return true;
- }
- return QObject::eventFilter(o, event);
-}
-
-StatsView::StatsView(QWidget *parent) : QQuickWidget(parent),
+StatsView::StatsView(QQuickItem *parent) : QQuickItem(parent),
highlightedSeries(nullptr),
xAxis(nullptr),
- yAxis(nullptr),
- eventFilter(this)
+ yAxis(nullptr)
{
- setResizeMode(QQuickWidget::SizeRootObjectToView);
- // if we get a failure to load the QML file (e.g., when the QtCharts QML modules aren't found)
- // the chart will be null
- setSource(urlStatsView);
- chart = getChart(rootObject());
- if (chart) {
- connect(chart, &QtCharts::QChart::plotAreaChanged, this, &StatsView::plotAreaChanged);
- connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &StatsView::replotIfVisible);
-
- chart->installEventFilter(&eventFilter);
- chart->setAcceptHoverEvents(true);
- chart->legend()->setVisible(false);
- }
+ setFlag(ItemHasContents, true);
+
+ connect(&diveListNotifier, &DiveListNotifier::numShownChanged, this, &StatsView::replotIfVisible);
+
+ setAcceptHoverEvents(true);
QFont font;
titleFont = QFont(font.family(), font.pointSize(), QFont::Light); // Make configurable
}
+StatsView::StatsView() : StatsView(nullptr)
+{
+}
+
StatsView::~StatsView()
{
}
-void StatsView::plotAreaChanged(const QRectF &r)
+void StatsView::plotAreaChanged(const QSizeF &s)
{
- double left = r.x() + sceneBorder;
- double top = r.y() + sceneBorder;
- double right = r.right() - sceneBorder;
- double bottom = r.bottom() - sceneBorder;
+ // Make sure that image is at least one pixel wide / high, otherwise
+ // the painter starts acting up.
+ int w = std::max(1, static_cast<int>(floor(s.width())));
+ int h = std::max(1, static_cast<int>(floor(s.height())));
+ scene.setSceneRect(QRectF(0, 0, static_cast<double>(w), static_cast<double>(h)));
+ painter.reset();
+ img.reset(new QImage(w, h, QImage::Format_RGB32));
+ painter.reset(new QPainter(img.get()));
+ painter->setRenderHint(QPainter::Antialiasing);
+
+ double left = sceneBorder;
+ double top = sceneBorder;
+ double right = s.width() - sceneBorder;
+ double bottom = s.height() - sceneBorder;
const double minSize = 30.0;
if (title)
@@ -140,8 +134,13 @@ void StatsView::replotIfVisible()
plot(state);
}
-void StatsView::hover(QPointF pos)
+void StatsView::hoverEnterEvent(QHoverEvent *)
{
+}
+
+void StatsView::hoverMoveEvent(QHoverEvent *event)
+{
+ QPointF pos(event->pos());
for (auto &series: series) {
if (series->hover(pos)) {
if (series.get() != highlightedSeries) {
@@ -149,7 +148,7 @@ void StatsView::hover(QPointF pos)
highlightedSeries->unhighlight();
highlightedSeries = series.get();
}
- return;
+ return update();
}
}
@@ -157,13 +156,14 @@ void StatsView::hover(QPointF pos)
if (highlightedSeries) {
highlightedSeries->unhighlight();
highlightedSeries = nullptr;
+ update();
}
}
template <typename T, class... Args>
T *StatsView::createSeries(Args&&... args)
{
- T *res = new T(chart, xAxis, yAxis, std::forward<Args>(args)...);
+ T *res = new T(&scene, xAxis, yAxis, std::forward<Args>(args)...);
series.emplace_back(res);
series.back()->updatePositions();
return res;
@@ -175,7 +175,7 @@ void StatsView::setTitle(const QString &s)
title.reset();
return;
}
- title = std::make_unique<QGraphicsSimpleTextItem>(s, chart);
+ title = createItemPtr<QGraphicsSimpleTextItem>(&scene, s);
title->setFont(titleFont);
}
@@ -183,7 +183,7 @@ void StatsView::updateTitlePos()
{
if (!title)
return;
- QRectF rect = chart->plotArea();
+ QRectF rect = scene.sceneRect();
title->setPos(sceneBorder + (rect.width() - title->boundingRect().width()) / 2.0,
sceneBorder);
}
@@ -191,7 +191,7 @@ void StatsView::updateTitlePos()
template <typename T, class... Args>
T *StatsView::createAxis(const QString &title, Args&&... args)
{
- T *res = new T(chart, title, std::forward<Args>(args)...);
+ T *res = createItem<T>(&scene, title, std::forward<Args>(args)...);
axes.emplace_back(res);
return res;
}
@@ -201,13 +201,11 @@ void StatsView::setAxes(StatsAxis *x, StatsAxis *y)
xAxis = x;
yAxis = y;
if (x && y)
- grid = std::make_unique<StatsGrid>(chart, *x, *y);
+ grid = std::make_unique<StatsGrid>(&scene, *x, *y);
}
void StatsView::reset()
{
- if (!chart)
- return;
highlightedSeries = nullptr;
xAxis = yAxis = nullptr;
legend.reset();
@@ -215,7 +213,6 @@ void StatsView::reset()
quartileMarkers.clear();
regressionLines.clear();
histogramMarkers.clear();
- chart->removeAllSeries();
grid.reset();
axes.clear();
title.reset();
@@ -225,12 +222,13 @@ void StatsView::plot(const StatsState &stateIn)
{
state = stateIn;
plotChart();
- plotAreaChanged(chart->plotArea());
+ plotAreaChanged(scene.sceneRect().size());
+ update();
}
void StatsView::plotChart()
{
- if (!chart || !state.var1)
+ if (!state.var1)
return;
reset();
@@ -402,7 +400,7 @@ void StatsView::plotBarChart(const std::vector<dive *> &dives,
// Paint legend first, because the bin-names will be moved away from.
if (showLegend)
- legend = std::make_unique<Legend>(chart, data.vbinNames);
+ legend = createItemPtr<Legend>(&scene, data.vbinNames);
std::vector<BarSeries::MultiItem> items;
items.reserve(data.hbin_counts.size());
@@ -619,7 +617,7 @@ void StatsView::plotPieChart(const std::vector<dive *> &dives,
PieSeries *series = createSeries<PieSeries>(categoryVariable->name(), data, keepOrder, labels);
if (showLegend)
- legend = std::make_unique<Legend>(chart, series->binNames());
+ legend = createItemPtr<Legend>(&scene, series->binNames());
}
void StatsView::plotDiscreteBoxChart(const std::vector<dive *> &dives,
@@ -689,17 +687,17 @@ void StatsView::plotDiscreteScatter(const std::vector<dive *> &dives,
if (quartiles) {
StatsQuartiles quartiles = StatsVariable::quartiles(array);
if (quartiles.isValid()) {
- 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);
+ quartileMarkers.emplace_back(x, quartiles.q1, &scene, catAxis, valAxis);
+ quartileMarkers.emplace_back(x, quartiles.q2, &scene, catAxis, valAxis);
+ quartileMarkers.emplace_back(x, quartiles.q3, &scene, catAxis, valAxis);
}
}
x += 1.0;
}
}
-StatsView::QuartileMarker::QuartileMarker(double pos, double value, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) :
- item(new QGraphicsLineItem(chart)),
+StatsView::QuartileMarker::QuartileMarker(double pos, double value, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) :
+ item(createItemPtr<QGraphicsLineItem>(scene)),
xAxis(xAxis), yAxis(yAxis),
pos(pos),
value(value)
@@ -719,8 +717,8 @@ void StatsView::QuartileMarker::updatePosition()
x + quartileMarkerSize / 2.0, y);
}
-StatsView::RegressionLine::RegressionLine(double a, double b, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) :
- item(new QGraphicsLineItem(chart)),
+StatsView::RegressionLine::RegressionLine(double a, double b, QPen pen, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) :
+ item(createItemPtr<QGraphicsLineItem>(scene)),
xAxis(xAxis), yAxis(yAxis),
a(a), b(b)
{
@@ -751,8 +749,8 @@ void StatsView::RegressionLine::updatePosition()
xAxis->toScreen(maxX), yAxis->toScreen(a * maxX + b));
}
-StatsView::HistogramMarker::HistogramMarker(double val, bool horizontal, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) :
- item(new QGraphicsLineItem(chart)),
+StatsView::HistogramMarker::HistogramMarker(double val, bool horizontal, QPen pen, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) :
+ item(createItemPtr<QGraphicsLineItem>(scene)),
xAxis(xAxis), yAxis(yAxis),
val(val), horizontal(horizontal)
{
@@ -777,12 +775,12 @@ void StatsView::HistogramMarker::updatePosition()
void StatsView::addHistogramMarker(double pos, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis)
{
- histogramMarkers.emplace_back(pos, isHorizontal, pen, chart, xAxis, yAxis);
+ histogramMarkers.emplace_back(pos, isHorizontal, pen, &scene, xAxis, yAxis);
}
void StatsView::addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis)
{
- regressionLines.emplace_back(a, b, QPen(Qt::red), chart, xAxis, yAxis);
+ regressionLines.emplace_back(a, b, QPen(Qt::red), &scene, xAxis, yAxis);
}
// Yikes, we get our data in different kinds of (bin, value) pairs.
@@ -942,7 +940,7 @@ void StatsView::plotHistogramStackedChart(const std::vector<dive *> &dives,
BarPlotData data(categoryBins, *valueBinner);
if (showLegend)
- legend = std::make_unique<Legend>(chart, data.vbinNames);
+ legend = createItemPtr<Legend>(&scene, data.vbinNames);
CountAxis *valAxis = createCountAxis(data.maxCategoryCount, isHorizontal);