diff options
author | Berthold Stoeger <bstoeger@mail.tuwien.ac.at> | 2021-01-18 12:08:46 +0100 |
---|---|---|
committer | bstoeger <32835590+bstoeger@users.noreply.github.com> | 2021-01-20 08:47:18 +0100 |
commit | b068b2b0e70c131ee146cc7962979684a0b3a527 (patch) | |
tree | 949c2a4b4ca0d7b219af435e29286e26fc65e4ee /stats | |
parent | b07a7fe5f10529f7fde0b7d5f614aa311b18dcc7 (diff) | |
download | subsurface-b068b2b0e70c131ee146cc7962979684a0b3a527.tar.gz |
statistics: replace PieSeries by QSG nodes
Since there are no disk-segment QSG primitives (one could draw
a triangle fan, but that doesn't seem optimal), this draws
into a pixmap and blits that as a QSG node.
Since this is the only series without axis, it needs a function
that returns the size of the plot area. This didn't exist, so
add it.
Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
Diffstat (limited to 'stats')
-rw-r--r-- | stats/chartitem.cpp | 30 | ||||
-rw-r--r-- | stats/chartitem.h | 13 | ||||
-rw-r--r-- | stats/pieseries.cpp | 77 | ||||
-rw-r--r-- | stats/pieseries.h | 16 | ||||
-rw-r--r-- | stats/statsview.cpp | 5 | ||||
-rw-r--r-- | stats/statsview.h | 1 |
6 files changed, 92 insertions, 50 deletions
diff --git a/stats/chartitem.cpp b/stats/chartitem.cpp index b8bf0f189..be2b3d635 100644 --- a/stats/chartitem.cpp +++ b/stats/chartitem.cpp @@ -224,6 +224,11 @@ ChartTextItem::ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const resize(QSizeF(totalWidth, totalHeight)); } +ChartTextItem::ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const QString &text) : + ChartTextItem(v, z, f, std::vector<QString>({ text }), true) +{ +} + void ChartTextItem::setColor(const QColor &c) { img->fill(Qt::transparent); @@ -240,6 +245,31 @@ void ChartTextItem::setColor(const QColor &c) setTextureDirty(); } +ChartPieItem::ChartPieItem(StatsView &v, ChartZValue z, double borderWidth) : ChartPixmapItem(v, z), + borderWidth(borderWidth) +{ +} + +void ChartPieItem::drawSegment(double from, double to, QColor fill, QColor border) +{ + painter->setPen(QPen(border, borderWidth)); + painter->setBrush(QBrush(fill)); + // For whatever obscure reason, angles of pie pieces are given as 16th of a degree...? + // Angles increase CCW, whereas pie charts usually are read CW. Therfore, startAngle + // is dervied from "from" and subtracted from the origin angle at 12:00. + int startAngle = 90 * 16 - static_cast<int>(round(to * 360.0 * 16.0)); + int spanAngle = static_cast<int>(round((to - from) * 360.0 * 16.0)); + QRectF drawRect(QPointF(0.0, 0.0), rect.size()); + painter->drawPie(drawRect, startAngle, spanAngle); + setTextureDirty(); +} + +void ChartPieItem::resize(QSizeF size) +{ + ChartPixmapItem::resize(size); + img->fill(Qt::transparent); +} + ChartLineItem::ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width) : HideableChartItem(v, z), color(color), width(width), positionDirty(false), materialDirty(false) { diff --git a/stats/chartitem.h b/stats/chartitem.h index 338c23b8b..44caae9cc 100644 --- a/stats/chartitem.h +++ b/stats/chartitem.h @@ -62,8 +62,8 @@ protected: std::unique_ptr<QImage> img; void setTextureDirty(); void setPositionDirty(); -private: QRectF rect; +private: bool positionDirty; // true if the position changed since last render bool textureDirty; // true if the pixmap changed since last render std::unique_ptr<QSGTexture> texture; @@ -85,6 +85,7 @@ private: class ChartTextItem : public ChartPixmapItem { public: ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const std::vector<QString> &text, bool center); + ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const QString &text); void setColor(const QColor &color); private: QFont f; @@ -97,6 +98,16 @@ private: std::vector<Item> items; }; +// A pie chart item: draws disk segments onto a pixmap. +class ChartPieItem : public ChartPixmapItem { +public: + ChartPieItem(StatsView &v, ChartZValue z, double borderWidth); + void drawSegment(double from, double to, QColor fill, QColor border); // from and to are relative (0-1 is full disk). + void resize(QSizeF size); // As in base class, but clears the canvas +private: + double borderWidth; +}; + class ChartLineItem : public HideableChartItem<HideableQSGNode<QSGGeometryNode>> { public: ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width); diff --git a/stats/pieseries.cpp b/stats/pieseries.cpp index dc337aa8d..09e74961a 100644 --- a/stats/pieseries.cpp +++ b/stats/pieseries.cpp @@ -9,7 +9,6 @@ #include <numeric> #include <math.h> -#include <QGraphicsEllipseItem> #include <QLocale> static const double pieSize = 0.9; // 1.0 = occupy full width of chart @@ -17,20 +16,15 @@ static const double pieBorderWidth = 1.0; static const double innerLabelRadius = 0.75; // 1.0 = at outer border of pie static const double outerLabelRadius = 1.01; // 1.0 = at outer border of pie -PieSeries::Item::Item(QGraphicsScene *scene, const QString &name, int from, int count, int totalCount, +PieSeries::Item::Item(StatsView &view, const QString &name, int from, int count, int totalCount, int bin_nr, int numBins, bool labels) : - item(createItemPtr<QGraphicsEllipseItem>(scene)), name(name), count(count) { + QFont f; // make configurable QLocale loc; - // For whatever obscure reason, angles in QGraphicsEllipseItem are given as 16th of a degree...? - // Angles increase CCW, whereas pie charts usually are read CW. - item->setStartAngle(90 * 16 - (from + count) * 360 * 16 / totalCount); - item->setSpanAngle(count * 360 * 16 / totalCount); - item->setPen(QPen(::borderColor)); - item->setZValue(ZValues::series); + angleFrom = static_cast<double>(from) / totalCount; angleTo = static_cast<double>(from + count) / totalCount; double meanAngle = M_PI / 2.0 - (from + count / 2.0) / totalCount * M_PI * 2.0; // Note: "-" because we go CW. innerLabelPos = QPointF(cos(meanAngle) * innerLabelRadius, -sin(meanAngle) * innerLabelRadius); @@ -39,27 +33,24 @@ PieSeries::Item::Item(QGraphicsScene *scene, const QString &name, int from, int if (labels) { double percentage = count * 100.0 / totalCount; QString innerLabelText = QStringLiteral("%1\%").arg(loc.toString(percentage, 'f', 1)); - innerLabel = createItemPtr<QGraphicsSimpleTextItem>(scene, innerLabelText); - innerLabel->setZValue(ZValues::seriesLabels); + innerLabel = view.createChartItem<ChartTextItem>(ChartZValue::SeriesLabels, f, innerLabelText); - outerLabel = createItemPtr<QGraphicsSimpleTextItem>(scene, name); - outerLabel->setBrush(QBrush(darkLabelColor)); - outerLabel->setZValue(ZValues::seriesLabels); + outerLabel = view.createChartItem<ChartTextItem>(ChartZValue::SeriesLabels, f, name); + outerLabel->setColor(darkLabelColor); } - - highlight(bin_nr, false, numBins); } -void PieSeries::Item::updatePositions(const QRectF &rect, const QPointF ¢er, double radius) +void PieSeries::Item::updatePositions(const QPointF ¢er, double radius) { - item->setRect(rect); + // Note: the positions in this functions are rounded to integer values, + // because half-integer values gives horrible aliasing artifacts. if (innerLabel) { - QRectF labelRect = innerLabel->boundingRect(); - innerLabel->setPos(center.x() + innerLabelPos.x() * radius - labelRect.width() / 2.0, - center.y() + innerLabelPos.y() * radius - labelRect.height() / 2.0); + QRectF labelRect = innerLabel->getRect(); + innerLabel->setPos(QPointF(round(center.x() + innerLabelPos.x() * radius - labelRect.width() / 2.0), + round(center.y() + innerLabelPos.y() * radius - labelRect.height() / 2.0))); } if (outerLabel) { - QRectF labelRect = outerLabel->boundingRect(); + QRectF labelRect = outerLabel->getRect(); QPointF pos(center.x() + outerLabelPos.x() * radius, center.y() + outerLabelPos.y() * radius); if (outerLabelPos.x() < 0.0) { if (outerLabelPos.y() < 0.0) @@ -70,25 +61,23 @@ void PieSeries::Item::updatePositions(const QRectF &rect, const QPointF ¢er, pos.ry() -= labelRect.height(); } - outerLabel->setPos(pos); + outerLabel->setPos(QPointF(round(pos.x()), round(pos.y()))); } } -void PieSeries::Item::highlight(int bin_nr, bool highlight, int numBins) +void PieSeries::Item::highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins) { - QBrush brush(highlight ? highlightedColor : binColor(bin_nr, numBins)); - QPen pen(highlight ? highlightedBorderColor : ::borderColor, pieBorderWidth); - item->setBrush(brush); - item->setPen(pen); - if (innerLabel) { - QBrush labelBrush(highlight ? darkLabelColor : labelColor(bin_nr, numBins)); - innerLabel->setBrush(labelBrush); - } + if (innerLabel) + innerLabel->setColor(highlight ? darkLabelColor : labelColor(bin_nr, numBins)); + item.drawSegment(angleFrom, angleTo, + highlight ? highlightedColor : binColor(bin_nr, numBins), + highlight ? highlightedBorderColor : ::borderColor); } PieSeries::PieSeries(QGraphicsScene *scene, StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName, const std::vector<std::pair<QString, int>> &data, bool keepOrder, bool labels) : StatsSeries(scene, view, xAxis, yAxis), + item(view.createChartItem<ChartPieItem>(ChartZValue::Series, pieBorderWidth)), categoryName(categoryName), highlighted(-1) { @@ -148,7 +137,7 @@ PieSeries::PieSeries(QGraphicsScene *scene, StatsView &view, StatsAxis *xAxis, S int act = 0; for (auto it2 = sorted.begin(); it2 != it; ++it2) { int count = data[*it2].second; - items.emplace_back(scene, data[*it2].first, act, count, totalCount, (int)items.size(), numBins, labels); + items.emplace_back(view, data[*it2].first, act, count, totalCount, (int)items.size(), numBins, labels); act += count; } @@ -158,7 +147,7 @@ PieSeries::PieSeries(QGraphicsScene *scene, StatsView &view, StatsAxis *xAxis, S for (auto it2 = it; it2 != sorted.end(); ++it2) other.push_back({ data[*it2].first, data[*it2].second }); QString name = StatsTranslations::tr("other (%1 items)").arg(other.size()); - items.emplace_back(scene, name, act, totalCount - act, totalCount, (int)items.size(), numBins, labels); + items.emplace_back(view, name, act, totalCount - act, totalCount, (int)items.size(), numBins, labels); } } @@ -168,12 +157,18 @@ PieSeries::~PieSeries() void PieSeries::updatePositions() { - QRectF plotRect = scene->sceneRect(); + QRectF plotRect = view.plotArea(); center = plotRect.center(); - radius = std::min(plotRect.width(), plotRect.height()) * pieSize / 2.0; - QRectF rect(center.x() - radius, center.y() - radius, 2.0 * radius, 2.0 * radius); - for (Item &item: items) - item.updatePositions(rect, center, radius); + radius = ceil(std::min(plotRect.width(), plotRect.height()) * pieSize / 2.0); + QRectF rect(round(center.x() - radius), round(center.y() - radius), ceil(2.0 * radius), ceil(2.0 * radius)); + item->resize(rect.size()); + item->setPos(rect.topLeft()); + int i = 0; + for (Item &segment: items) { + segment.updatePositions(center, radius); + segment.highlight(*item, i, i == highlighted, (int)items.size()); // Draw segment + ++i; + } } std::vector<QString> PieSeries::binNames() @@ -245,7 +240,7 @@ bool PieSeries::hover(QPointF pos) // Highlight new item (if any) if (highlighted >= 0 && highlighted < (int)items.size()) { - items[highlighted].highlight(highlighted, true, (int)items.size()); + items[highlighted].highlight(*item, highlighted, true, (int)items.size()); if (!information) information = view.createChartItem<InformationBox>(); information->setText(makeInfo(highlighted), pos); @@ -258,6 +253,6 @@ bool PieSeries::hover(QPointF pos) void PieSeries::unhighlight() { if (highlighted >= 0 && highlighted < (int)items.size()) - items[highlighted].highlight(highlighted, false, (int)items.size()); + items[highlighted].highlight(*item, highlighted, false, (int)items.size()); highlighted = -1; } diff --git a/stats/pieseries.h b/stats/pieseries.h index 56e56c7d6..d9dd1b5fb 100644 --- a/stats/pieseries.h +++ b/stats/pieseries.h @@ -10,9 +10,9 @@ #include <QString> struct InformationBox; -class QGraphicsEllipseItem; +struct ChartPieItem; +struct ChartTextItem; class QGraphicsScene; -class QGraphicsSimpleTextItem; class QRectF; class PieSeries : public StatsSeries { @@ -34,20 +34,20 @@ private: // Get item under mouse pointer, or -1 if none int getItemUnderMouse(const QPointF &f) const; + std::unique_ptr<ChartPieItem> item; QString categoryName; std::vector<QString> makeInfo(int idx) const; struct Item { - std::unique_ptr<QGraphicsEllipseItem> item; - std::unique_ptr<QGraphicsSimpleTextItem> innerLabel, outerLabel; + std::unique_ptr<ChartTextItem> innerLabel, outerLabel; QString name; - double angleTo; // In fraction of total + double angleFrom, angleTo; // In fraction of total int count; QPointF innerLabelPos, outerLabelPos; // With respect to a (-1, -1)-(1, 1) rectangle. - Item(QGraphicsScene *scene, const QString &name, int from, int count, int totalCount, + Item(StatsView &view, const QString &name, int from, int count, int totalCount, int bin_nr, int numBins, bool labels); - void updatePositions(const QRectF &rect, const QPointF ¢er, double radius); - void highlight(int bin_nr, bool highlight, int numBins); + void updatePositions(const QPointF ¢er, double radius); + void highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins); }; std::vector<Item> items; int totalCount; diff --git a/stats/statsview.cpp b/stats/statsview.cpp index 455589976..e616c3e12 100644 --- a/stats/statsview.cpp +++ b/stats/statsview.cpp @@ -187,6 +187,11 @@ QSizeF StatsView::size() const return boundingRect().size(); } +QRectF StatsView::plotArea() const +{ + return plotRect; +} + void StatsView::plotAreaChanged(const QSizeF &s) { // Make sure that image is at least one pixel wide / high, otherwise diff --git a/stats/statsview.h b/stats/statsview.h index b0a199200..05893ad33 100644 --- a/stats/statsview.h +++ b/stats/statsview.h @@ -45,6 +45,7 @@ public: void plot(const StatsState &state); QQuickWindow *w() const; // Make window available to items QSizeF size() const; + QRectF plotArea() const; void addQSGNode(QSGNode *node, ChartZValue z); // Must only be called in render thread! void registerDirtyChartItem(ChartItem &item); void unregisterDirtyChartItem(ChartItem &item); |