summaryrefslogtreecommitdiffstats
path: root/stats
diff options
context:
space:
mode:
Diffstat (limited to 'stats')
-rw-r--r--stats/chartitem.cpp89
-rw-r--r--stats/chartitem.h20
-rw-r--r--stats/scatterseries.cpp76
-rw-r--r--stats/scatterseries.h7
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);
};