diff options
-rw-r--r-- | Subsurface-mobile.pro | 2 | ||||
-rw-r--r-- | stats/CMakeLists.txt | 2 | ||||
-rw-r--r-- | stats/chartitem.cpp | 77 | ||||
-rw-r--r-- | stats/chartitem.h | 50 | ||||
-rw-r--r-- | stats/quartilemarker.cpp | 30 | ||||
-rw-r--r-- | stats/quartilemarker.h | 20 | ||||
-rw-r--r-- | stats/statsview.cpp | 37 | ||||
-rw-r--r-- | stats/statsview.h | 12 |
8 files changed, 172 insertions, 58 deletions
diff --git a/Subsurface-mobile.pro b/Subsurface-mobile.pro index e2a724633..b113eebfe 100644 --- a/Subsurface-mobile.pro +++ b/Subsurface-mobile.pro @@ -136,6 +136,7 @@ SOURCES += subsurface-mobile-main.cpp \ stats/informationbox.cpp \ stats/legend.cpp \ stats/pieseries.cpp \ + stats/quartilemarker.cpp \ stats/scatterseries.cpp \ stats/statsaxis.cpp \ stats/statscolors.cpp \ @@ -285,6 +286,7 @@ HEADERS += \ stats/informationbox.h \ stats/legend.h \ stats/pieseries.h \ + stats/quartilemarker.h \ stats/scatterseries.h \ stats/statsaxis.h \ stats/statscolors.h \ diff --git a/stats/CMakeLists.txt b/stats/CMakeLists.txt index c0fbe3c51..105096436 100644 --- a/stats/CMakeLists.txt +++ b/stats/CMakeLists.txt @@ -19,6 +19,8 @@ set(SUBSURFACE_STATS_SRCS legend.cpp pieseries.h pieseries.cpp + quartilemarker.h + quartilemarker.cpp scatterseries.h scatterseries.cpp statsaxis.h diff --git a/stats/chartitem.cpp b/stats/chartitem.cpp index 68e7fa371..6fef5e44f 100644 --- a/stats/chartitem.cpp +++ b/stats/chartitem.cpp @@ -4,6 +4,7 @@ #include <cmath> #include <QQuickWindow> +#include <QSGFlatColorMaterial> #include <QSGImageNode> #include <QSGTexture> @@ -13,13 +14,12 @@ static int round_up(double f) } ChartItem::ChartItem(StatsView &v, ChartZValue z) : - dirty(false), zValue(z), view(v), positionDirty(false), textureDirty(false) + dirty(false), zValue(z), view(v) { } ChartItem::~ChartItem() { - painter.reset(); // Make sure to destroy painter before image that is painted on view.unregisterChartItem(this); } @@ -28,19 +28,29 @@ QSizeF ChartItem::sceneSize() const return view.size(); } -void ChartItem::setTextureDirty() +ChartPixmapItem::ChartPixmapItem(StatsView &v, ChartZValue z) : ChartItem(v, z), + positionDirty(false), textureDirty(false) +{ +} + +ChartPixmapItem::~ChartPixmapItem() +{ + painter.reset(); // Make sure to destroy painter before image that is painted on +} + +void ChartPixmapItem::setTextureDirty() { textureDirty = true; dirty = true; } -void ChartItem::setPositionDirty() +void ChartPixmapItem::setPositionDirty() { positionDirty = true; dirty = true; } -void ChartItem::render() +void ChartPixmapItem::render() { if (!dirty) return; @@ -64,7 +74,7 @@ void ChartItem::render() dirty = false; } -void ChartItem::resize(QSizeF size) +void ChartPixmapItem::resize(QSizeF size) { painter.reset(); img.reset(new QImage(round_up(size.width()), round_up(size.height()), QImage::Format_ARGB32)); @@ -74,19 +84,19 @@ void ChartItem::resize(QSizeF size) setTextureDirty(); } -void ChartItem::setPos(QPointF pos) +void ChartPixmapItem::setPos(QPointF pos) { rect.moveTopLeft(pos); setPositionDirty(); } -QRectF ChartItem::getRect() const +QRectF ChartPixmapItem::getRect() const { return rect; } ChartRectItem::ChartRectItem(StatsView &v, ChartZValue z, - const QPen &pen, const QBrush &brush, double radius) : ChartItem(v, z), + const QPen &pen, const QBrush &brush, double radius) : ChartPixmapItem(v, z), pen(pen), brush(brush), radius(radius) { } @@ -97,7 +107,7 @@ ChartRectItem::~ChartRectItem() void ChartRectItem::resize(QSizeF size) { - ChartItem::resize(size); + ChartPixmapItem::resize(size); img->fill(Qt::transparent); painter->setPen(pen); painter->setBrush(brush); @@ -106,3 +116,50 @@ void ChartRectItem::resize(QSizeF size) QRect rect(width / 2, width / 2, imgSize.width() - width, imgSize.height() - width); painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize); } + +ChartLineItem::ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width) : ChartItem(v, z), + color(color), width(width), positionDirty(false), materialDirty(false) +{ +} + +ChartLineItem::~ChartLineItem() +{ +} + +void ChartLineItem::render() +{ + if (!node) { + geometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 2)); + geometry->setDrawingMode(QSGGeometry::DrawLines); + material.reset(new QSGFlatColorMaterial); + node.reset(new QSGGeometryNode); + node->setGeometry(geometry.get()); + node->setMaterial(material.get()); + view.addQSGNode(node.get(), zValue); + positionDirty = materialDirty = true; + } + + if (positionDirty) { + // Attention: width is a geometry property and therefore handled by position dirty! + geometry->setLineWidth(static_cast<float>(width)); + auto vertices = geometry->vertexDataAsPoint2D(); + vertices[0].set(static_cast<float>(from.x()), static_cast<float>(from.y())); + vertices[1].set(static_cast<float>(to.x()), static_cast<float>(to.y())); + node->markDirty(QSGNode::DirtyGeometry); + } + + if (materialDirty) { + material->setColor(color); + node->markDirty(QSGNode::DirtyMaterial); + } + + positionDirty = materialDirty = false; +} + +void ChartLineItem::setLine(QPointF fromIn, QPointF toIn) +{ + from = fromIn; + to = toIn; + positionDirty = true; + dirty = true; +} diff --git a/stats/chartitem.h b/stats/chartitem.h index 8cb9ef3a7..5dab8d1ea 100644 --- a/stats/chartitem.h +++ b/stats/chartitem.h @@ -7,6 +7,9 @@ #include <memory> #include <QPainter> +class QSGGeometry; +class QSGGeometryNode; +class QSGFlatColorMaterial; class QSGImageNode; class QSGTexture; class StatsView; @@ -15,31 +18,41 @@ enum class ChartZValue : int; class ChartItem { public: ChartItem(StatsView &v, ChartZValue z); - ~ChartItem(); - // Attention: The children are responsible for updating the item. None of these calls will. - void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*. - void setPos(QPointF pos); - void render(); // Only call on render thread! + virtual ~ChartItem(); + virtual void render() = 0; // Only call on render thread! QRectF getRect() const; bool dirty; // If true, call render() when rebuilding the scene const ChartZValue zValue; protected: + QSizeF sceneSize() const; + StatsView &view; +}; + +// A chart item that blits a precalculated pixmap onto the scene. +class ChartPixmapItem : public ChartItem { +public: + ChartPixmapItem(StatsView &v, ChartZValue z); + ~ChartPixmapItem(); + + void setPos(QPointF pos); + void render() override; // Only call on render thread! + QRectF getRect() const; +protected: + void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*. std::unique_ptr<QPainter> painter; std::unique_ptr<QImage> img; - QSizeF sceneSize() const; void setTextureDirty(); void setPositionDirty(); private: - StatsView &view; QRectF rect; - bool positionDirty; - bool textureDirty; + bool positionDirty; // true if the position changed since last render + bool textureDirty; // true if the pixmap changed since last render std::unique_ptr<QSGImageNode> node; std::unique_ptr<QSGTexture> texture; }; // Draw a rectangular background after resize. Children are responsible for calling update(). -class ChartRectItem : public ChartItem { +class ChartRectItem : public ChartPixmapItem { public: ChartRectItem(StatsView &v, ChartZValue z, const QPen &pen, const QBrush &brush, double radius); ~ChartRectItem(); @@ -50,4 +63,21 @@ private: double radius; }; +class ChartLineItem : public ChartItem { +public: + ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width); + ~ChartLineItem(); + void setLine(QPointF from, QPointF to); + void render() override; // Only call on render thread! +private: + QPointF from, to; + QColor color; + double width; + bool positionDirty; + bool materialDirty; + std::unique_ptr<QSGGeometryNode> node; + std::unique_ptr<QSGFlatColorMaterial> material; + std::unique_ptr<QSGGeometry> geometry; +}; + #endif diff --git a/stats/quartilemarker.cpp b/stats/quartilemarker.cpp new file mode 100644 index 000000000..ace019df5 --- /dev/null +++ b/stats/quartilemarker.cpp @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: GPL-2.0 +#include "quartilemarker.h" +#include "statsaxis.h" +#include "zvalues.h" + +static const QColor quartileMarkerColor(Qt::red); +static const double quartileMarkerSize = 15.0; + +QuartileMarker::QuartileMarker(StatsView &view, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis) : + ChartLineItem(view, ChartZValue::ChartFeatures, quartileMarkerColor, 2.0), + xAxis(xAxis), yAxis(yAxis), + pos(pos), + value(value) +{ + updatePosition(); +} + +QuartileMarker::~QuartileMarker() +{ +} + +void QuartileMarker::updatePosition() +{ + if (!xAxis || !yAxis) + return; + double x = xAxis->toScreen(pos); + double y = yAxis->toScreen(value); + setLine(QPointF(x - quartileMarkerSize / 2.0, y), + QPointF(x + quartileMarkerSize / 2.0, y)); +} diff --git a/stats/quartilemarker.h b/stats/quartilemarker.h new file mode 100644 index 000000000..2e754248d --- /dev/null +++ b/stats/quartilemarker.h @@ -0,0 +1,20 @@ +// A short line used to mark quartiles +#ifndef QUARTILE_MARKER_H +#define QUARTILE_MARKER_H + +#include "chartitem.h" + +class StatsAxis; +class StatsView; + +class QuartileMarker : public ChartLineItem { +public: + QuartileMarker(StatsView &view, double pos, double value, StatsAxis *xAxis, StatsAxis *yAxis); + ~QuartileMarker(); + void updatePosition(); +private: + StatsAxis *xAxis, *yAxis; + double pos, value; +}; + +#endif diff --git a/stats/statsview.cpp b/stats/statsview.cpp index f07b00896..b4b82ccfa 100644 --- a/stats/statsview.cpp +++ b/stats/statsview.cpp @@ -4,6 +4,7 @@ #include "boxseries.h" #include "legend.h" #include "pieseries.h" +#include "quartilemarker.h" #include "scatterseries.h" #include "statsaxis.h" #include "statscolors.h" @@ -25,8 +26,6 @@ #include <QSGTexture> // Constants that control the graph layouts -static const QColor quartileMarkerColor(Qt::red); -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 @@ -211,8 +210,8 @@ void StatsView::plotAreaChanged(const QSizeF &s) grid->updatePositions(); for (auto &series: series) series->updatePositions(); - for (QuartileMarker &marker: quartileMarkers) - marker.updatePosition(); + for (auto &marker: quartileMarkers) + marker->updatePosition(); for (RegressionLine &line: regressionLines) line.updatePosition(); for (HistogramMarker &marker: histogramMarkers) @@ -799,36 +798,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, &scene, catAxis, valAxis); - quartileMarkers.emplace_back(x, quartiles.q2, &scene, catAxis, valAxis); - quartileMarkers.emplace_back(x, quartiles.q3, &scene, catAxis, valAxis); + quartileMarkers.push_back(createChartItem<QuartileMarker>( + x, quartiles.q1, catAxis, valAxis)); + quartileMarkers.push_back(createChartItem<QuartileMarker>( + x, quartiles.q2, catAxis, valAxis)); + quartileMarkers.push_back(createChartItem<QuartileMarker>( + x, quartiles.q3, catAxis, valAxis)); } } x += 1.0; } } -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) -{ - item->setZValue(ZValues::chartFeatures); - item->setPen(QPen(quartileMarkerColor, 2.0)); - updatePosition(); -} - -void StatsView::QuartileMarker::updatePosition() -{ - 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::RegressionLine::RegressionLine(const struct regression_data reg, QBrush brush, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) : item(createItemPtr<QGraphicsPolygonItem>(scene)), central(createItemPtr<QGraphicsPolygonItem>(scene)), diff --git a/stats/statsview.h b/stats/statsview.h index 1a922ac98..073a200fd 100644 --- a/stats/statsview.h +++ b/stats/statsview.h @@ -24,6 +24,7 @@ class CategoryAxis; class ChartItem; class CountAxis; class HistogramAxis; +class QuartileMarker; class StatsAxis; class StatsGrid; class Legend; @@ -125,15 +126,6 @@ private: // Helper functions to add feature to the chart void addLineMarker(double pos, double low, double high, const QPen &pen, bool isHorizontal); - // A short line used to mark quartiles - struct QuartileMarker { - std::unique_ptr<QGraphicsLineItem> item; - StatsAxis *xAxis, *yAxis; - double pos, value; - QuartileMarker(double pos, double value, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis); - void updatePosition(); - }; - // A regression line struct RegressionLine { std::unique_ptr<QGraphicsPolygonItem> item; @@ -163,7 +155,7 @@ private: std::unique_ptr<StatsGrid> grid; std::vector<std::unique_ptr<StatsSeries>> series; std::unique_ptr<Legend> legend; - std::vector<QuartileMarker> quartileMarkers; + std::vector<std::unique_ptr<QuartileMarker>> quartileMarkers; std::vector<RegressionLine> regressionLines; std::vector<HistogramMarker> histogramMarkers; std::unique_ptr<QGraphicsSimpleTextItem> title; |