summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2021-01-15 22:48:32 +0100
committerGravatar bstoeger <32835590+bstoeger@users.noreply.github.com>2021-01-20 08:47:18 +0100
commitb42e19e36b4d80716059011581851c8600338cbb (patch)
treee752618c95a6ab4dcb980209800d3c3bbf292a0e
parent20088576604a3159cc9891bcfea888c15b096a96 (diff)
downloadsubsurface-b42e19e36b4d80716059011581851c8600338cbb.tar.gz
statistics: convert bar series to QSGNodes
To this end, two new ChartItems were added: A "bar" (a rectangle with a border) and a "text" (multiple lines of text). It turns out that the text on the bars now looks atrocious. The reason appears to be that the antialiasing of the font-rendering does not blend into the alpha channel, but into a supposed background color? This will have to be investigated. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
-rw-r--r--stats/barseries.cpp96
-rw-r--r--stats/barseries.h14
-rw-r--r--stats/chartitem.cpp119
-rw-r--r--stats/chartitem.h41
4 files changed, 200 insertions, 70 deletions
diff --git a/stats/barseries.cpp b/stats/barseries.cpp
index 733549d3d..d7211212e 100644
--- a/stats/barseries.cpp
+++ b/stats/barseries.cpp
@@ -13,6 +13,7 @@
// Constants that control the bar layout
static const double barWidth = 0.8; // 1.0 = full width of category
static const double subBarWidth = 0.9; // For grouped bar charts
+static const double barBorderWidth = 1.0;
// Default constructor: invalid index.
BarSeries::Index::Index() : bar(-1), subitem(-1)
@@ -86,97 +87,76 @@ BarSeries::~BarSeries()
{
}
-BarSeries::BarLabel::BarLabel(QGraphicsScene *scene, const std::vector<QString> &labels, int bin_nr, int binCount) :
- totalWidth(0.0), totalHeight(0.0), isOutside(false)
+BarSeries::BarLabel::BarLabel(StatsView &view, const std::vector<QString> &labels, int bin_nr, int binCount) :
+ isOutside(false)
{
- items.reserve(labels.size());
- for (const QString &label: labels) {
- items.emplace_back(createItem<QGraphicsSimpleTextItem>(scene));
- items.back()->setText(label);
- items.back()->setZValue(ZValues::seriesLabels);
- QRectF rect = items.back()->boundingRect();
- if (rect.width() > totalWidth)
- totalWidth = rect.width();
- totalHeight += rect.height();
- }
+ QFont f; // make configurable
+ item = view.createChartItem<ChartTextItem>(ChartZValue::SeriesLabels, f, labels, true);
highlight(false, bin_nr, binCount);
}
void BarSeries::BarLabel::setVisible(bool visible)
{
- for (auto &item: items)
- item->setVisible(visible);
+ // item->setVisible(visible); TODO!
}
void BarSeries::BarLabel::highlight(bool highlight, int bin_nr, int binCount)
{
- QBrush brush(highlight || isOutside ? darkLabelColor : labelColor(bin_nr, binCount));
- for (auto &item: items)
- item->setBrush(brush);
+ item->setColor(highlight || isOutside ? darkLabelColor : labelColor(bin_nr, binCount));
}
void BarSeries::BarLabel::updatePosition(bool horizontal, bool center, const QRectF &rect,
int bin_nr, int binCount)
{
+ QSizeF itemSize = item->getRect().size();
if (!horizontal) {
- if (totalWidth > rect.width()) {
+ if (itemSize.width() > rect.width()) {
setVisible(false);
return;
}
QPointF pos = rect.center();
+ pos.rx() -= round(itemSize.width() / 2.0);
// Heuristics: if the label fits nicely into the bar (bar height is at least twice the label height),
// then put the label in the middle of the bar. Otherwise, put it at the top of the bar.
- isOutside = !center && rect.height() < 2.0 * totalHeight;
+ isOutside = !center && rect.height() < 2.0 * itemSize.height();
if (isOutside) {
- pos.ry() = rect.top() - (totalHeight + 2.0); // Leave two pixels(?) space
+ pos.ry() = rect.top() - (itemSize.height() + 2.0); // Leave two pixels(?) space
} else {
- if (totalHeight > rect.height()) {
+ if (itemSize.height() > rect.height()) {
setVisible(false);
return;
}
- pos.ry() -= totalHeight / 2.0;
- }
- for (auto &it: items) {
- QPointF itemPos = pos;
- QRectF rect = it->boundingRect();
- itemPos.rx() -= rect.width() / 2.0;
- it->setPos(itemPos);
- pos.ry() += rect.height();
+ pos.ry() -= round(itemSize.height() / 2.0);
}
+ item->setPos(pos);
} else {
- if (totalHeight > rect.height()) {
+ if (itemSize.height() > rect.height()) {
setVisible(false);
return;
}
QPointF pos = rect.center();
- pos.ry() -= totalHeight / 2.0;
+ pos.ry() -= round(itemSize.height() / 2.0);
// Heuristics: if the label fits nicely into the bar (bar width is at least twice the label height),
// then put the label in the middle of the bar. Otherwise, put it to the right of the bar.
- isOutside = !center && rect.width() < 2.0 * totalWidth;
+ isOutside = !center && rect.width() < 2.0 * itemSize.width();
if (isOutside) {
- pos.rx() = rect.right() + (totalWidth / 2.0 + 2.0); // Leave two pixels(?) space
+ pos.rx() = round(rect.right() + 2.0); // Leave two pixels(?) space
} else {
- if (totalWidth > rect.width()) {
+ if (itemSize.width() > rect.width()) {
setVisible(false);
return;
}
}
- for (auto &it: items) {
- QPointF itemPos = pos;
- QRectF rect = it->boundingRect();
- itemPos.rx() -= rect.width() / 2.0;
- it->setPos(itemPos);
- pos.ry() += rect.height();
- }
+ item->setPos(pos);
}
setVisible(true);
// If label changed from inside to outside, or vice-versa, the color might change.
highlight(false, bin_nr, binCount);
}
-BarSeries::Item::Item(QGraphicsScene *scene, BarSeries *series, double lowerBound, double upperBound,
+BarSeries::Item::Item(BarSeries *series, double lowerBound, double upperBound,
std::vector<SubItem> subitemsIn,
const QString &binName, const StatsOperationResults &res, int total,
bool horizontal, bool stacked, int binCount) :
@@ -187,10 +167,8 @@ BarSeries::Item::Item(QGraphicsScene *scene, BarSeries *series, double lowerBoun
res(res),
total(total)
{
- for (SubItem &item: subitems) {
- item.item->setZValue(ZValues::series);
+ for (SubItem &item: subitems)
item.highlight(false, binCount);
- }
updatePosition(series, horizontal, stacked, binCount);
}
@@ -203,13 +181,10 @@ void BarSeries::Item::highlight(int subitem, bool highlight, int binCount)
void BarSeries::SubItem::highlight(bool highlight, int binCount)
{
- if (highlight) {
- item->setBrush(QBrush(highlightedColor));
- item->setPen(QPen(highlightedBorderColor));
- } else {
- item->setBrush(QBrush(binColor(bin_nr, binCount)));
- item->setPen(QPen(::borderColor));
- }
+ if (highlight)
+ item->setColor(highlightedColor, highlightedBorderColor);
+ else
+ item->setColor(binColor(bin_nr, binCount), ::borderColor);
if (label)
label->highlight(highlight, bin_nr, binCount);
}
@@ -235,9 +210,9 @@ void BarSeries::Item::updatePosition(BarSeries *series, bool horizontal, bool st
double center = (idx + 0.5) * fullSubWidth + from;
item.updatePosition(series, horizontal, stacked, center - subWidth / 2.0, center + subWidth / 2.0, binCount);
}
- rect = subitems[0].item->rect();
+ rect = subitems[0].item->getRect();
for (auto it = std::next(subitems.begin()); it != subitems.end(); ++it)
- rect = rect.united(it->item->rect());
+ rect = rect.united(it->item->getRect());
}
void BarSeries::SubItem::updatePosition(BarSeries *series, bool horizontal, bool stacked,
@@ -265,9 +240,10 @@ std::vector<BarSeries::SubItem> BarSeries::makeSubItems(const std::vector<std::p
int bin_nr = 0;
for (auto &[v, label]: values) {
if (v > 0.0) {
- res.push_back({ createItemPtr<QGraphicsRectItem>(scene), {}, from, from + v, bin_nr });
+ res.push_back({ view.createChartItem<ChartBarItem>(ChartZValue::Series, barBorderWidth, horizontal),
+ {}, from, from + v, bin_nr });
if (!label.empty())
- res.back().label = std::make_unique<BarLabel>(scene, label, bin_nr, binCount());
+ res.back().label = std::make_unique<BarLabel>(view, label, bin_nr, binCount());
}
if (stacked)
from += v;
@@ -293,7 +269,7 @@ void BarSeries::add_item(double lowerBound, double upperBound, std::vector<SubIt
// Don't add empty items, as that messes with the "find item under mouse" routine.
if (subitems.empty())
return;
- items.emplace_back(scene, this, lowerBound, upperBound, std::move(subitems), binName, res,
+ items.emplace_back(this, lowerBound, upperBound, std::move(subitems), binName, res,
total, horizontal, stacked, binCount());
}
@@ -323,10 +299,10 @@ int BarSeries::Item::getSubItemUnderMouse(const QPointF &point, bool horizontal,
// Search the first item whose "end" position is greater than the cursor position.
bool search_x = horizontal == stacked;
auto it = search_x ? std::lower_bound(subitems.begin(), subitems.end(), point.x(),
- [] (const SubItem &item, double x) { return item.item->rect().right() < x; })
+ [] (const SubItem &item, double x) { return item.item->getRect().right() < x; })
: std::lower_bound(subitems.begin(), subitems.end(), point.y(),
- [] (const SubItem &item, double y) { return item.item->rect().top() > y; });
- return it != subitems.end() && it->item->rect().contains(point) ? it - subitems.begin() : -1;
+ [] (const SubItem &item, double y) { return item.item->getRect().top() > y; });
+ return it != subitems.end() && it->item->getRect().contains(point) ? it - subitems.begin() : -1;
}
// Format information in a count-based bar chart.
diff --git a/stats/barseries.h b/stats/barseries.h
index 33a36395e..5655bc977 100644
--- a/stats/barseries.h
+++ b/stats/barseries.h
@@ -10,9 +10,11 @@
#include <memory>
#include <vector>
-#include <QGraphicsRectItem>
+#include <QRectF>
class QGraphicsScene;
+class ChartBarItem;
+class ChartTextItem;
struct InformationBox;
struct StatsVariable;
@@ -80,17 +82,16 @@ private:
// A label that is composed of multiple lines
struct BarLabel {
- std::vector<std::unique_ptr<QGraphicsSimpleTextItem>> items;
- double totalWidth, totalHeight; // Size of the item
+ std::unique_ptr<ChartTextItem> item;
bool isOutside; // Is shown outside of bar
- BarLabel(QGraphicsScene *scene, const std::vector<QString> &labels, int bin_nr, int binCount);
+ BarLabel(StatsView &view, const std::vector<QString> &labels, int bin_nr, int binCount);
void setVisible(bool visible);
void updatePosition(bool horizontal, bool center, const QRectF &rect, int bin_nr, int binCount);
void highlight(bool highlight, int bin_nr, int binCount);
};
struct SubItem {
- std::unique_ptr<QGraphicsRectItem> item;
+ std::unique_ptr<ChartBarItem> item;
std::unique_ptr<BarLabel> label;
double value_from;
double value_to;
@@ -107,7 +108,7 @@ private:
const QString binName;
StatsOperationResults res;
int total;
- Item(QGraphicsScene *scene, BarSeries *series, double lowerBound, double upperBound,
+ Item(BarSeries *series, double lowerBound, double upperBound,
std::vector<SubItem> subitems,
const QString &binName, const StatsOperationResults &res, int total, bool horizontal,
bool stacked, int binCount);
@@ -118,7 +119,6 @@ private:
std::unique_ptr<InformationBox> information;
std::vector<Item> items;
- std::vector<BarLabel> barLabels;
bool horizontal;
bool stacked;
QString categoryName;
diff --git a/stats/chartitem.cpp b/stats/chartitem.cpp
index aeb395d41..bb01c6e2c 100644
--- a/stats/chartitem.cpp
+++ b/stats/chartitem.cpp
@@ -6,6 +6,7 @@
#include <QQuickWindow>
#include <QSGFlatColorMaterial>
#include <QSGImageNode>
+#include <QSGRectangleNode>
#include <QSGTexture>
static int round_up(double f)
@@ -116,6 +117,40 @@ void ChartRectItem::resize(QSizeF size)
painter->drawRoundedRect(rect, radius, radius, Qt::AbsoluteSize);
}
+ChartTextItem::ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const std::vector<QString> &text, bool center) :
+ ChartPixmapItem(v, z), f(f), center(center)
+{
+ QFontMetrics fm(f);
+ double totalWidth = 1.0;
+ fontHeight = static_cast<double>(fm.height());
+ double totalHeight = std::max(1.0, static_cast<double>(text.size()) * fontHeight);
+
+ items.reserve(text.size());
+ for (const QString &s: text) {
+ double w = fm.size(Qt::TextSingleLine, s).width();
+ items.push_back({ s, w });
+ if (w > totalWidth)
+ totalWidth = w;
+ }
+ resize(QSizeF(totalWidth, totalHeight));
+}
+
+void ChartTextItem::setColor(const QColor &c)
+{
+ img->fill(Qt::transparent);
+ double y = 0.0;
+ painter->setPen(QPen(c));
+ painter->setFont(f);
+ double totalWidth = getRect().width();
+ for (const auto &[s, w]: items) {
+ double x = center ? round((totalWidth - w) / 2.0) : 0.0;
+ QRectF rect(x, y, w, fontHeight);
+ painter->drawText(rect, s);
+ y += fontHeight;
+ }
+ setTextureDirty();
+}
+
ChartLineItem::ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width) : ChartItem(v, z),
color(color), width(width), positionDirty(false), materialDirty(false)
{
@@ -125,6 +160,12 @@ ChartLineItem::~ChartLineItem()
{
}
+// Helper function to set points
+void setPoint(QSGGeometry::Point2D &v, const QPointF &p)
+{
+ v.set(static_cast<float>(p.x()), static_cast<float>(p.y()));
+}
+
void ChartLineItem::render()
{
if (!node) {
@@ -142,8 +183,8 @@ void ChartLineItem::render()
// Attention: width is a geometry property and therefore handled by position dirty!
geometry->setLineWidth(static_cast<float>(width));
auto vertices = geometry->vertexDataAsPoint2D();
- vertices[0].set(static_cast<float>(from.x()), static_cast<float>(from.y()));
- vertices[1].set(static_cast<float>(to.x()), static_cast<float>(to.y()));
+ setPoint(vertices[0], from);
+ setPoint(vertices[1], to);
node->markDirty(QSGNode::DirtyGeometry);
}
@@ -162,3 +203,77 @@ void ChartLineItem::setLine(QPointF fromIn, QPointF toIn)
positionDirty = true;
view.registerDirtyChartItem(*this);
}
+
+ChartBarItem::ChartBarItem(StatsView &v, ChartZValue z, double borderWidth, bool horizontal) : ChartItem(v, z),
+ borderWidth(borderWidth), horizontal(horizontal),
+ positionDirty(false), colorDirty(false)
+{
+}
+
+ChartBarItem::~ChartBarItem()
+{
+}
+
+void ChartBarItem::render()
+{
+ if (!node) {
+ node.reset(view.w()->createRectangleNode());
+
+ borderGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 4));
+ borderGeometry->setDrawingMode(QSGGeometry::DrawLineLoop);
+ borderGeometry->setLineWidth(static_cast<float>(borderWidth));
+ borderMaterial.reset(new QSGFlatColorMaterial);
+ borderNode.reset(new QSGGeometryNode);
+ borderNode->setGeometry(borderGeometry.get());
+ borderNode->setMaterial(borderMaterial.get());
+
+ node->appendChildNode(borderNode.get());
+ view.addQSGNode(node.get(), zValue);
+ positionDirty = colorDirty = true;
+ }
+
+ if (colorDirty) {
+ node->setColor(color);
+ borderMaterial->setColor(borderColor);
+ borderNode->markDirty(QSGNode::DirtyMaterial);
+ }
+
+ if (positionDirty) {
+ node->setRect(rect);
+ auto vertices = borderGeometry->vertexDataAsPoint2D();
+ if (horizontal) {
+ setPoint(vertices[0], rect.topLeft());
+ setPoint(vertices[1], rect.topRight());
+ setPoint(vertices[2], rect.bottomRight());
+ setPoint(vertices[3], rect.bottomLeft());
+ } else {
+ setPoint(vertices[0], rect.bottomLeft());
+ setPoint(vertices[1], rect.topLeft());
+ setPoint(vertices[2], rect.topRight());
+ setPoint(vertices[3], rect.bottomRight());
+ }
+ borderNode->markDirty(QSGNode::DirtyGeometry);
+ }
+
+ positionDirty = colorDirty = false;
+}
+
+void ChartBarItem::setColor(QColor colorIn, QColor borderColorIn)
+{
+ color = colorIn;
+ borderColor = borderColorIn;
+ colorDirty = true;
+ view.registerDirtyChartItem(*this);
+}
+
+void ChartBarItem::setRect(const QRectF &rectIn)
+{
+ rect = rectIn;
+ positionDirty = true;
+ view.registerDirtyChartItem(*this);
+}
+
+QRectF ChartBarItem::getRect() const
+{
+ return rect;
+}
diff --git a/stats/chartitem.h b/stats/chartitem.h
index f13cb3bc4..93c80547f 100644
--- a/stats/chartitem.h
+++ b/stats/chartitem.h
@@ -11,6 +11,7 @@ class QSGGeometry;
class QSGGeometryNode;
class QSGFlatColorMaterial;
class QSGImageNode;
+class QSGRectangleNode;
class QSGTexture;
class StatsView;
enum class ChartZValue : int;
@@ -20,7 +21,6 @@ public:
ChartItem(StatsView &v, ChartZValue z);
virtual ~ChartItem();
virtual void render() = 0; // Only call on render thread!
- QRectF getRect() const;
bool dirty; // If true, call render() when rebuilding the scene
ChartItem *dirtyPrev, *dirtyNext; // Double linked list of dirty items
const ChartZValue zValue;
@@ -64,6 +64,22 @@ private:
double radius;
};
+// Attention: text is only drawn after calling setColor()!
+class ChartTextItem : public ChartPixmapItem {
+public:
+ ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const std::vector<QString> &text, bool center);
+ void setColor(const QColor &color);
+private:
+ QFont f;
+ double fontHeight;
+ bool center;
+ struct Item {
+ QString s;
+ double width;
+ };
+ std::vector<Item> items;
+};
+
class ChartLineItem : public ChartItem {
public:
ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width);
@@ -74,6 +90,7 @@ private:
QPointF from, to;
QColor color;
double width;
+ bool horizontal;
bool positionDirty;
bool materialDirty;
std::unique_ptr<QSGGeometryNode> node;
@@ -81,4 +98,26 @@ private:
std::unique_ptr<QSGGeometry> geometry;
};
+// A bar in a bar chart: a rectangle bordered by lines.
+class ChartBarItem : public ChartItem {
+public:
+ ChartBarItem(StatsView &v, ChartZValue z, double borderWidth, bool horizontal);
+ ~ChartBarItem();
+ void setColor(QColor color, QColor borderColor);
+ void setRect(const QRectF &rect);
+ QRectF getRect() const;
+ void render() override; // Only call on render thread!
+private:
+ QColor color, borderColor;
+ double borderWidth;
+ QRectF rect;
+ bool horizontal;
+ bool positionDirty;
+ bool colorDirty;
+ std::unique_ptr<QSGRectangleNode> node;
+ std::unique_ptr<QSGGeometryNode> borderNode;
+ std::unique_ptr<QSGFlatColorMaterial> borderMaterial;
+ std::unique_ptr<QSGGeometry> borderGeometry;
+};
+
#endif