diff options
author | Berthold Stoeger <bstoeger@mail.tuwien.ac.at> | 2021-02-07 14:33:48 +0100 |
---|---|---|
committer | Dirk Hohndel <dirk@hohndel.org> | 2021-02-13 13:02:54 -0800 |
commit | 06a091643e63e4c0eb1d3bda61871be1b44b93a9 (patch) | |
tree | 702cb83aa4814a1241360d6b66acf60f8c3b79a6 /stats | |
parent | d63d4cd3c357a4294e4810ad320acf519b37882d (diff) | |
download | subsurface-06a091643e63e4c0eb1d3bda61871be1b44b93a9.tar.gz |
statistics: highlight selected bar
When all items of a bar in a bar chart are selected, highlight them
by overlaying with a checkerboard pattern. A gray checkerboard seems to
work reasonably well, regardless of base color.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Signed-off-by: Dirk Hohndel <dirk@hohndel.org>
Diffstat (limited to 'stats')
-rw-r--r-- | stats/barseries.cpp | 23 | ||||
-rw-r--r-- | stats/barseries.h | 2 | ||||
-rw-r--r-- | stats/chartitem.cpp | 76 | ||||
-rw-r--r-- | stats/chartitem.h | 10 | ||||
-rw-r--r-- | stats/statscolors.h | 1 |
5 files changed, 107 insertions, 5 deletions
diff --git a/stats/barseries.cpp b/stats/barseries.cpp index 58c6511a6..facdcae6c 100644 --- a/stats/barseries.cpp +++ b/stats/barseries.cpp @@ -6,6 +6,7 @@ #include "statstranslations.h" #include "statsview.h" #include "zvalues.h" +#include "core/dive.h" #include "core/selection.h" #include <math.h> // for lrint() @@ -183,11 +184,14 @@ void BarSeries::Item::highlight(int subitem, bool highlight, int binCount) subitems[subitem].highlight(highlight, binCount); } +// For single-bin charts, selected items are marked with a special fill and border color. +// For multi-bin charts, they are marked by a differend border color and border width. void BarSeries::SubItem::highlight(bool highlight, int binCount) { fill = highlight ? highlightedColor : binColor(bin_nr, binCount); QColor border = highlight ? highlightedBorderColor : ::borderColor; item->setColor(fill, border); + item->setSelected(selected); if (label) label->highlight(highlight, bin_nr, binCount, fill); } @@ -243,9 +247,10 @@ std::vector<BarSeries::SubItem> BarSeries::makeSubItems(std::vector<SubItemDesc> int bin_nr = 0; for (auto &[v, dives, label]: items) { if (v > 0.0) { + bool selected = std::all_of(dives.begin(), dives.end(), [] (const dive *d) { return d->selected; }); res.push_back({ view.createChartItem<ChartBarItem>(ChartZValue::Series, barBorderWidth, horizontal), std::move(dives), - {}, from, from + v, bin_nr }); + {}, from, from + v, bin_nr, selected }); if (!label.empty()) res.back().label = std::make_unique<BarLabel>(view, label, bin_nr, binCount()); } @@ -418,3 +423,19 @@ bool BarSeries::selectItemsUnderMouse(const QPointF &pos, bool) setSelection(dives, dives.empty() ? nullptr : dives.front()); return true; } + +void BarSeries::divesSelected(const QVector<dive *> &) +{ + for (Item &item: items) { + for (SubItem &subitem: item.subitems) { + bool selected = std::all_of(subitem.dives.begin(), subitem.dives.end(), [] (const dive *d) { return d->selected; }); + if (subitem.selected != selected) { + subitem.selected = selected; + + Index idx(&item - &items[0], &subitem - &item.subitems[0]); + bool highlight = idx == highlighted; + item.highlight(idx.subitem, highlight, binCount()); + } + } + } +} diff --git a/stats/barseries.h b/stats/barseries.h index 5d592d4fa..d5cfb10e4 100644 --- a/stats/barseries.h +++ b/stats/barseries.h @@ -104,6 +104,7 @@ private: double value_from; double value_to; int bin_nr; + bool selected; QColor fill; void updatePosition(BarSeries *series, bool horizontal, bool stacked, double from, double to, int binCount); @@ -146,6 +147,7 @@ private: bool stacked); std::vector<QString> makeInfo(const Item &item, int subitem) const; int binCount() const; + void divesSelected(const QVector<dive *> &) override; }; #endif diff --git a/stats/chartitem.cpp b/stats/chartitem.cpp index acd303928..5aa45be13 100644 --- a/stats/chartitem.cpp +++ b/stats/chartitem.cpp @@ -9,6 +9,7 @@ #include <QSGImageNode> #include <QSGRectangleNode> #include <QSGTexture> +#include <QSGTextureMaterial> static int round_up(double f) { @@ -140,6 +141,7 @@ static QSGTexture *createScatterTexture(StatsView &view, const QColor &color, co static QSGTexture *scatterItemTexture = nullptr; static QSGTexture *scatterItemSelectedTexture = nullptr; static QSGTexture *scatterItemHighlightedTexture = nullptr; +static QSGTexture *selectedTexture = nullptr; // A checkerboard pattern. QSGTexture *ChartScatterItem::getTexture() const { @@ -330,6 +332,12 @@ void setPoint(QSGGeometry::Point2D &v, const QPointF &p) v.set(static_cast<float>(p.x()), static_cast<float>(p.y())); } +void setPoint(QSGGeometry::TexturedPoint2D &v, const QPointF &p, const QPointF &t) +{ + v.set(static_cast<float>(p.x()), static_cast<float>(p.y()), + static_cast<float>(t.x()), static_cast<float>(t.y())); +} + void ChartLineItem::render() { if (!node) { @@ -395,8 +403,8 @@ void ChartRectLineItem::render() } ChartBarItem::ChartBarItem(StatsView &v, ChartZValue z, double borderWidth, bool horizontal) : HideableChartItem(v, z), - borderWidth(borderWidth), horizontal(horizontal), - positionDirty(false), colorDirty(false) + borderWidth(borderWidth), selected(false), horizontal(horizontal), + positionDirty(false), colorDirty(false), selectedDirty(false) { } @@ -404,6 +412,18 @@ ChartBarItem::~ChartBarItem() { } +QSGTexture *ChartBarItem::getSelectedTexture() const +{ + if (!selectedTexture) { + QImage img(2, 2, QImage::Format_ARGB32); + img.fill(Qt::transparent); + img.setPixelColor(0, 0, selectionOverlayColor); + img.setPixelColor(1, 1, selectionOverlayColor); + selectedTexture = view.w()->createTextureFromImage(img, QQuickWindow::TextureHasAlphaChannel); + } + return selectedTexture; +} + void ChartBarItem::render() { if (!node) { @@ -419,7 +439,7 @@ void ChartBarItem::render() node->node->appendChildNode(borderNode.get()); view.addQSGNode(node.get(), zValue); - positionDirty = colorDirty = true; + positionDirty = colorDirty = selectedDirty = true; } updateVisible(); @@ -448,11 +468,48 @@ void ChartBarItem::render() borderNode->markDirty(QSGNode::DirtyGeometry); } - positionDirty = colorDirty = false; + if (selectedDirty) { + if (selected) { + if (!selectionNode) { + // Create the selection overlay if it didn't exist up to now. + selectionGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4)); + selectionGeometry->setDrawingMode(QSGGeometry::DrawTriangleFan); + selectionMaterial.reset(new QSGTextureMaterial); + selectionMaterial->setTexture(getSelectedTexture()); + selectionMaterial->setHorizontalWrapMode(QSGTexture::Repeat); + selectionMaterial->setVerticalWrapMode(QSGTexture::Repeat); + selectionNode.reset(new QSGGeometryNode); + selectionNode->setGeometry(selectionGeometry.get()); + selectionNode->setMaterial(selectionMaterial.get()); + } + + node->node->appendChildNode(selectionNode.get()); + + // Update the position of the selection overlay, even if the position didn't change. + positionDirty = true; + } else { + if (selectionNode) + node->node->removeChildNode(selectionNode.get()); + } + } + + if (selected && positionDirty) { + // The checkerboard texture is 2x2. By dividing the coordinates by 4, every square is 2x2 pixels on the screen. + auto selectionVertices = selectionGeometry->vertexDataAsTexturedPoint2D(); + selectionNode->markDirty(QSGNode::DirtyGeometry); + setPoint(selectionVertices[0], rect.topLeft(), QPointF()); + setPoint(selectionVertices[1], rect.topRight(), QPointF(rect.width() / 4.0, 0.0)); + setPoint(selectionVertices[2], rect.bottomRight(), QPointF(rect.width() / 4.0, rect.height() / 4.0)); + setPoint(selectionVertices[3], rect.bottomLeft(), QPointF(0.0, rect.height() / 4.0)); + } + + positionDirty = colorDirty = selectedDirty = false; } void ChartBarItem::setColor(QColor colorIn, QColor borderColorIn) { + if (color == colorIn) + return; color = colorIn; borderColor = borderColorIn; colorDirty = true; @@ -461,11 +518,22 @@ void ChartBarItem::setColor(QColor colorIn, QColor borderColorIn) void ChartBarItem::setRect(const QRectF &rectIn) { + if (rect == rectIn) + return; rect = rectIn; positionDirty = true; markDirty(); } +void ChartBarItem::setSelected(bool selectedIn) +{ + if (selected == selectedIn) + return; + selected = selectedIn; + selectedDirty = true; + markDirty(); +} + QRectF ChartBarItem::getRect() const { return rect; diff --git a/stats/chartitem.h b/stats/chartitem.h index 038054c39..87fb0f993 100644 --- a/stats/chartitem.h +++ b/stats/chartitem.h @@ -15,6 +15,7 @@ class QSGFlatColorMaterial; class QSGImageNode; class QSGRectangleNode; class QSGTexture; +class QSGTextureMaterial; class StatsView; enum class ChartZValue : int; @@ -148,18 +149,27 @@ public: ~ChartBarItem(); void setColor(QColor color, QColor borderColor); void setRect(const QRectF &rect); + void setSelected(bool selected); QRectF getRect() const; void render() override; // Only call on render thread! protected: QColor color, borderColor; double borderWidth; QRectF rect; + bool selected; bool horizontal; bool positionDirty; bool colorDirty; + bool selectedDirty; std::unique_ptr<QSGGeometryNode> borderNode; std::unique_ptr<QSGFlatColorMaterial> borderMaterial; std::unique_ptr<QSGGeometry> borderGeometry; +private: + // Overlay for selected items. Created on demand. + std::unique_ptr<QSGGeometryNode> selectionNode; + std::unique_ptr<QSGTextureMaterial> selectionMaterial; + std::unique_ptr<QSGGeometry> selectionGeometry; + QSGTexture *getSelectedTexture() const; }; // A box-and-whiskers item. This is a bit lazy: derive from the bar item and add whiskers. diff --git a/stats/statscolors.h b/stats/statscolors.h index 178cf8b00..47344b082 100644 --- a/stats/statscolors.h +++ b/stats/statscolors.h @@ -25,6 +25,7 @@ inline const QColor regressionItemColor(Qt::red); inline const QColor meanMarkerColor(Qt::green); inline const QColor medianMarkerColor(Qt::red); inline const QColor selectionLassoColor(Qt::black); +inline const QColor selectionOverlayColor(Qt::lightGray); QColor binColor(int bin, int numBins); QColor labelColor(int bin, size_t numBins); |