diff options
Diffstat (limited to 'stats')
-rw-r--r-- | stats/chartitem.cpp | 89 | ||||
-rw-r--r-- | stats/chartitem.h | 20 | ||||
-rw-r--r-- | stats/scatterseries.cpp | 76 | ||||
-rw-r--r-- | stats/scatterseries.h | 7 |
4 files changed, 123 insertions, 69 deletions
diff --git a/stats/chartitem.cpp b/stats/chartitem.cpp index 67cfb4937..b8bf0f189 100644 --- a/stats/chartitem.cpp +++ b/stats/chartitem.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include "chartitem.h" +#include "statscolors.h" #include "statsview.h" #include <cmath> @@ -95,6 +96,94 @@ QRectF ChartPixmapItem::getRect() const return rect; } +static const int scatterItemDiameter = 10; +static const int scatterItemBorder = 1; + +ChartScatterItem::ChartScatterItem(StatsView &v, ChartZValue z) : HideableChartItem(v, z), + positionDirty(false), textureDirty(false), highlighted(false) +{ + rect.setSize(QSizeF(static_cast<double>(scatterItemDiameter), static_cast<double>(scatterItemDiameter))); +} + +ChartScatterItem::~ChartScatterItem() +{ +} + +static std::unique_ptr<QSGTexture> createScatterTexture(StatsView &view, const QColor &color, const QColor &borderColor) +{ + QImage img(scatterItemDiameter, scatterItemDiameter, QImage::Format_ARGB32); + img.fill(Qt::transparent); + QPainter painter(&img); + painter.setPen(Qt::NoPen); + painter.setRenderHint(QPainter::Antialiasing); + painter.setBrush(borderColor); + painter.drawEllipse(0, 0, scatterItemDiameter, scatterItemDiameter); + painter.setBrush(color); + painter.drawEllipse(scatterItemBorder, scatterItemBorder, + scatterItemDiameter - 2 * scatterItemBorder, + scatterItemDiameter - 2 * scatterItemBorder); + return std::unique_ptr<QSGTexture>( + view.w()->createTextureFromImage(img, QQuickWindow::TextureHasAlphaChannel) + ); +} + +std::unique_ptr<QSGTexture> scatterItemTexture; +std::unique_ptr<QSGTexture> scatterItemHighlightedTexture; + +void ChartScatterItem::render() +{ + if (!scatterItemTexture) { + scatterItemTexture = createScatterTexture(view, fillColor, borderColor); + scatterItemHighlightedTexture = createScatterTexture(view, highlightedColor, highlightedBorderColor); + } + if (!node) { + createNode(view.w()->createImageNode()); + view.addQSGNode(node.get(), zValue); + textureDirty = positionDirty = true; + } + if (textureDirty) { + node->node->setTexture(highlighted ? scatterItemHighlightedTexture.get() : scatterItemTexture.get()); + textureDirty = false; + } + if (positionDirty) { + node->node->setRect(rect); + positionDirty = false; + } +} + +void ChartScatterItem::setPos(QPointF pos) +{ + pos -= QPointF(scatterItemDiameter / 2.0, scatterItemDiameter / 2.0); + rect.moveTopLeft(pos); + positionDirty = true; + view.registerDirtyChartItem(*this); +} + +static double squareDist(const QPointF &p1, const QPointF &p2) +{ + QPointF diff = p1 - p2; + return QPointF::dotProduct(diff, diff); +} + +bool ChartScatterItem::contains(QPointF point) const +{ + return squareDist(point, rect.center()) <= (scatterItemDiameter / 2.0) * (scatterItemDiameter / 2.0); +} + +void ChartScatterItem::setHighlight(bool highlightedIn) +{ + if (highlighted == highlightedIn) + return; + highlighted = highlightedIn; + textureDirty = true; + view.registerDirtyChartItem(*this); +} + +QRectF ChartScatterItem::getRect() const +{ + return rect; +} + ChartRectItem::ChartRectItem(StatsView &v, ChartZValue z, const QPen &pen, const QBrush &brush, double radius) : ChartPixmapItem(v, z), pen(pen), brush(brush), radius(radius) diff --git a/stats/chartitem.h b/stats/chartitem.h index 650c45d53..338c23b8b 100644 --- a/stats/chartitem.h +++ b/stats/chartitem.h @@ -150,6 +150,26 @@ private: std::unique_ptr<QSGGeometry> whiskersGeometry; }; +// An item in a scatter chart. This is not simply a normal pixmap item, +// because we want that all items share the *same* texture for memory +// efficiency. It is somewhat questionable to define the form of the +// scatter item here, but so it is for now. +class ChartScatterItem : public HideableChartProxyItem<QSGImageNode> { +public: + ChartScatterItem(StatsView &v, ChartZValue z); + ~ChartScatterItem(); + + void setPos(QPointF pos); // Specifies the *center* of the item. + void setHighlight(bool highlight); // In the future, support different kinds of scatter items. + void render() override; // Only call on render thread! + QRectF getRect() const; + bool contains(QPointF point) const; +private: + QRectF rect; + QSizeF textureSize; + bool positionDirty, textureDirty; + bool highlighted; +}; // Implementation detail of templates - move to serparate header file template <typename Node> diff --git a/stats/scatterseries.cpp b/stats/scatterseries.cpp index a073f6992..5453b8ee5 100644 --- a/stats/scatterseries.cpp +++ b/stats/scatterseries.cpp @@ -1,5 +1,6 @@ // SPDX-License-Identifier: GPL-2.0 #include "scatterseries.h" +#include "chartitem.h" #include "informationbox.h" #include "statscolors.h" #include "statshelper.h" @@ -11,12 +12,6 @@ #include "core/divelist.h" #include "core/qthelper.h" -#include <QGraphicsPixmapItem> -#include <QPainter> - -static const int scatterItemDiameter = 10; -static const int scatterItemBorder = 1; - ScatterSeries::ScatterSeries(QGraphicsScene *scene, StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const StatsVariable &varX, const StatsVariable &varY) : StatsSeries(scene, view, xAxis, yAxis), @@ -28,62 +23,28 @@ ScatterSeries::~ScatterSeries() { } -static QPixmap createScatterPixmap(const QColor &color, const QColor &borderColor) -{ - QPixmap res(scatterItemDiameter, scatterItemDiameter); - res.fill(Qt::transparent); - QPainter painter(&res); - painter.setPen(Qt::NoPen); - painter.setRenderHint(QPainter::Antialiasing); - painter.setBrush(borderColor); - painter.drawEllipse(0, 0, scatterItemDiameter, scatterItemDiameter); - painter.setBrush(color); - painter.drawEllipse(scatterItemBorder, scatterItemBorder, - scatterItemDiameter - 2 * scatterItemBorder, - scatterItemDiameter - 2 * scatterItemBorder); - return res; -} - -// Annoying: we can create a QPixmap only after the application was initialized. -// Therefore, do this as a on-demand initialized pointer. A function local static -// variable does unnecesssary (in this case) thread synchronization. -static std::unique_ptr<QPixmap> scatterPixmapPtr; -static std::unique_ptr<QPixmap> scatterPixmapHighlightedPtr; - -static const QPixmap &scatterPixmap(bool highlight) -{ - if (!scatterPixmapPtr) { - scatterPixmapPtr.reset(new QPixmap(createScatterPixmap(fillColor, ::borderColor))); - scatterPixmapHighlightedPtr.reset(new QPixmap(createScatterPixmap(highlightedColor, highlightedBorderColor))); - } - return highlight ? *scatterPixmapHighlightedPtr : *scatterPixmapPtr; -} - -ScatterSeries::Item::Item(QGraphicsScene *scene, ScatterSeries *series, dive *d, double pos, double value) : - item(createItemPtr<QGraphicsPixmapItem>(scene, scatterPixmap(false))), +ScatterSeries::Item::Item(StatsView &view, ScatterSeries *series, dive *d, double pos, double value) : + item(view.createChartItem<ChartScatterItem>(ChartZValue::Series)), d(d), pos(pos), value(value) { - item->setZValue(ZValues::series); updatePosition(series); } void ScatterSeries::Item::updatePosition(ScatterSeries *series) { - QPointF center = series->toScreen(QPointF(pos, value)); - item->setPos(center.x() - scatterItemDiameter / 2.0, - center.y() - scatterItemDiameter / 2.0); + item->setPos(series->toScreen(QPointF(pos, value))); } void ScatterSeries::Item::highlight(bool highlight) { - item->setPixmap(scatterPixmap(highlight)); + item->setHighlight(highlight); } void ScatterSeries::append(dive *d, double pos, double value) { - items.emplace_back(scene, this, d, pos, value); + items.emplace_back(view, this, d, pos, value); } void ScatterSeries::updatePositions() @@ -92,35 +53,20 @@ void ScatterSeries::updatePositions() item.updatePosition(this); } -static double sq(double f) -{ - return f * f; -} - -static double squareDist(const QPointF &p1, const QPointF &p2) -{ - QPointF diff = p1 - p2; - return QPointF::dotProduct(diff, diff); -} - std::vector<int> ScatterSeries::getItemsUnderMouse(const QPointF &point) const { std::vector<int> res; double x = point.x(); - auto low = std::lower_bound(items.begin(), items.end(), x - scatterItemDiameter, - [] (const Item &item, double x) { return item.item->pos().x() < x; }); - auto high = std::upper_bound(low, items.end(), x + scatterItemDiameter, - [] (double x, const Item &item) { return x < item.item->pos().x(); }); + auto low = std::lower_bound(items.begin(), items.end(), x, + [] (const Item &item, double x) { return item.item->getRect().right() < x; }); + auto high = std::upper_bound(low, items.end(), x, + [] (double x, const Item &item) { return x < item.item->getRect().left(); }); // Hopefully that narrows it down enough. For discrete scatter plots, we could also partition // by equal x and do a binary search in these partitions. But that's probably not worth it. res.reserve(high - low); - double minSquare = sq(scatterItemDiameter / 2.0 + scatterItemBorder); for (auto it = low; it < high; ++it) { - QPointF pos = it->item->pos(); - pos.rx() += scatterItemDiameter / 2.0 + scatterItemBorder; - pos.ry() += scatterItemDiameter / 2.0 + scatterItemBorder; - if (squareDist(pos, point) <= minSquare) + if (it->item->contains(point)) res.push_back(it - items.begin()); } return res; diff --git a/stats/scatterseries.h b/stats/scatterseries.h index a9930b797..b83b6bcad 100644 --- a/stats/scatterseries.h +++ b/stats/scatterseries.h @@ -8,10 +8,9 @@ #include <memory> #include <vector> -#include <QGraphicsRectItem> -class QGraphicsPixmapItem; class QGraphicsScene; +class ChartScatterItem; struct InformationBox; struct StatsVariable; struct dive; @@ -34,10 +33,10 @@ private: std::vector<int> getItemsUnderMouse(const QPointF &f) const; struct Item { - std::unique_ptr<QGraphicsPixmapItem> item; + std::unique_ptr<ChartScatterItem> item; dive *d; double pos, value; - Item(QGraphicsScene *scene, ScatterSeries *series, dive *d, double pos, double value); + Item(StatsView &view, ScatterSeries *series, dive *d, double pos, double value); void updatePosition(ScatterSeries *series); void highlight(bool highlight); }; |