aboutsummaryrefslogtreecommitdiffstats
path: root/stats
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2021-02-07 14:33:48 +0100
committerGravatar Dirk Hohndel <dirk@hohndel.org>2021-02-13 13:02:54 -0800
commit06a091643e63e4c0eb1d3bda61871be1b44b93a9 (patch)
tree702cb83aa4814a1241360d6b66acf60f8c3b79a6 /stats
parentd63d4cd3c357a4294e4810ad320acf519b37882d (diff)
downloadsubsurface-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.cpp23
-rw-r--r--stats/barseries.h2
-rw-r--r--stats/chartitem.cpp76
-rw-r--r--stats/chartitem.h10
-rw-r--r--stats/statscolors.h1
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);