summaryrefslogtreecommitdiffstats
path: root/stats
diff options
context:
space:
mode:
Diffstat (limited to 'stats')
-rw-r--r--stats/CMakeLists.txt2
-rw-r--r--stats/legend.cpp125
-rw-r--r--stats/legend.h36
3 files changed, 163 insertions, 0 deletions
diff --git a/stats/CMakeLists.txt b/stats/CMakeLists.txt
index e4c110271..11b091d1b 100644
--- a/stats/CMakeLists.txt
+++ b/stats/CMakeLists.txt
@@ -5,6 +5,8 @@ include_directories(.
)
set(SUBSURFACE_STATS_SRCS
+ legend.h
+ legend.cpp
statscolors.h
statscolors.cpp
statsvariables.h
diff --git a/stats/legend.cpp b/stats/legend.cpp
new file mode 100644
index 000000000..b532f483f
--- /dev/null
+++ b/stats/legend.cpp
@@ -0,0 +1,125 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "legend.h"
+#include "statscolors.h"
+#include <QFontMetrics>
+#include <QGraphicsSceneMouseEvent>
+#include <QGraphicsWidget>
+#include <QPen>
+
+static const double legendBorderSize = 2.0;
+static const double legendBoxBorderSize = 1.0;
+static const double legendBoxScale = 0.8; // 1.0: text-height of the used font
+static const double legendInternalBorderSize = 2.0;
+static const QColor legendColor(0x00, 0x8e, 0xcc, 192); // Note: fourth argument is opacity
+static const QColor legendBorderColor(Qt::black);
+
+Legend::Legend(QGraphicsWidget *chart, const std::vector<QString> &names) :
+ QGraphicsRectItem(chart), chart(chart), displayedItems(0), width(0.0), height(0.0)
+{
+ setZValue(30.0); // On top of everything else.
+ entries.reserve(names.size());
+ int idx = 0;
+ for (const QString &name: names)
+ entries.emplace_back(name, idx++, (int)names.size(), this);
+
+ // Calculate the height and width of the elements
+ if (!entries.empty()) {
+ QFontMetrics fm(entries[0].text->font());
+ fontHeight = fm.height();
+ for (Entry &e: entries)
+ e.width = fontHeight + 2.0 * legendBoxBorderSize +
+ fm.size(Qt::TextSingleLine, e.text->text()).width();
+ } else {
+ fontHeight = 0.0;
+ }
+ setPen(QPen(legendBorderColor, legendBorderSize));
+ setBrush(QBrush(legendColor));
+
+ resize(); // Draw initial legend
+}
+
+Legend::Entry::Entry(const QString &name, int idx, int numBins, QGraphicsItem *parent) :
+ rect(new QGraphicsRectItem(parent)),
+ text(new QGraphicsSimpleTextItem(name, parent))
+{
+ rect->setZValue(30.0);
+ rect->setPen(QPen(legendBorderColor, legendBoxBorderSize));
+ rect->setBrush(QBrush(binColor(idx, numBins)));
+ text->setZValue(30.0);
+ text->setBrush(QBrush(darkLabelColor));
+}
+
+void Legend::hide()
+{
+ for (Entry &e: entries) {
+ e.rect->hide();
+ e.text->hide();
+ }
+ QGraphicsRectItem::hide();
+}
+
+void Legend::resize()
+{
+ if (entries.empty())
+ return hide();
+
+ QSizeF size = chart->size();
+
+ // Silly heuristics: make the legend at most half as high and half as wide as the chart.
+ // Not sure if that makes sense - this might need some optimization.
+ int maxRows = static_cast<int>(size.height() / 2.0 - 2.0 * legendInternalBorderSize) / fontHeight;
+ if (maxRows <= 0)
+ return hide();
+ int numColumns = ((int)entries.size() - 1) / maxRows + 1;
+ int numRows = ((int)entries.size() - 1) / numColumns + 1;
+
+ double x = legendInternalBorderSize;
+ displayedItems = 0;
+ for (int col = 0; col < numColumns; ++col) {
+ double y = legendInternalBorderSize;
+ double nextX = x;
+
+ for (int row = 0; row < numRows; ++row) {
+ int idx = col * numRows + row;
+ if (idx >= (int)entries.size())
+ break;
+ entries[idx].pos = QPointF(x, y);
+ nextX = std::max(nextX, x + entries[idx].width);
+ y += fontHeight;
+ ++displayedItems;
+ }
+ x += nextX;
+ width = nextX;
+ if (width >= size.width() / 2.0) // More than half the chart-width -> give up
+ break;
+ }
+ width += legendInternalBorderSize;
+ height = 2 * legendInternalBorderSize + numRows * fontHeight;
+ updatePosition();
+}
+
+void Legend::updatePosition()
+{
+ if (displayedItems <= 0)
+ return hide();
+ // For now, place the legend in the top right corner.
+ QPointF pos(chart->size().width() - width - 10.0, 10.0);
+ setRect(QRectF(pos, QSizeF(width, height)));
+ for (int i = 0; i < displayedItems; ++i) {
+ QPointF itemPos = pos + entries[i].pos;
+ QRectF rect(itemPos, QSizeF(fontHeight, fontHeight));
+ // Decrease box size by legendBoxScale factor
+ double delta = fontHeight * (1.0 - legendBoxScale) / 2.0;
+ rect = rect.adjusted(delta, delta, -delta, -delta);
+ entries[i].rect->setRect(rect);
+ itemPos.rx() += fontHeight + 2.0 * legendBoxBorderSize;
+ entries[i].text->setPos(itemPos);
+ entries[i].rect->show();
+ entries[i].text->show();
+ }
+ for (int i = displayedItems; i < (int)entries.size(); ++i) {
+ entries[i].rect->hide();
+ entries[i].text->hide();
+ }
+ show();
+}
diff --git a/stats/legend.h b/stats/legend.h
new file mode 100644
index 000000000..e6c5af1e0
--- /dev/null
+++ b/stats/legend.h
@@ -0,0 +1,36 @@
+// SPDX-License-Identifier: GPL-2.0
+// A legend box, which is shown on the chart.
+#ifndef STATS_LEGEND_H
+#define STATS_LEGEND_H
+
+#include <memory>
+#include <vector>
+#include <QGraphicsRectItem>
+
+class QGraphicsSceneMouseEvent;
+
+class Legend : public QGraphicsRectItem {
+public:
+ Legend(QGraphicsWidget *chart, const std::vector<QString> &names);
+ void hover(QPointF pos);
+ void resize(); // called when the chart size changes.
+private:
+ // Each entry is a text besides a rectangle showing the color
+ struct Entry {
+ std::unique_ptr<QGraphicsRectItem> rect;
+ std::unique_ptr<QGraphicsSimpleTextItem> text;
+ QPointF pos;
+ double width;
+ Entry(const QString &name, int idx, int numBins, QGraphicsItem *parent);
+ };
+ QGraphicsWidget *chart;
+ int displayedItems;
+ double width;
+ double height;
+ int fontHeight;
+ std::vector<Entry> entries;
+ void updatePosition();
+ void hide();
+};
+
+#endif