statistics: add notion of Z-value to chart items

The chart items were drawn in order of creation. To control this,
add a notion of Z-value. In contrast to QGraphicsScene, make
this a small integer value.

To controll order of drawing, a plain QSGNode is created for
every possible Z-Value and items are added to these nodes.
Thus, items are rendered by Z-value and if the Z-value is equal
by order of creation.

Likewise split the list of chart-items into Z-values, so that
items can be quickly unregistered: The items that will be
removed individually will usuall be part of Z-levels with only
few items (e.g. legend, infobox). Z-levels with many items
(notably the series) will always be fully rebuilt.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2021-01-13 16:19:27 +01:00 committed by bstoeger
parent 785d5189f6
commit e1c0cace95
7 changed files with 82 additions and 31 deletions

View file

@ -12,8 +12,8 @@ static int round_up(double f)
return static_cast<int>(ceil(f)); return static_cast<int>(ceil(f));
} }
ChartItem::ChartItem(StatsView &v) : ChartItem::ChartItem(StatsView &v, ChartZValue z) :
dirty(false), view(v), positionDirty(false), textureDirty(false) dirty(false), zValue(z), view(v), positionDirty(false), textureDirty(false)
{ {
} }
@ -46,7 +46,7 @@ void ChartItem::render()
return; return;
if (!node) { if (!node) {
node.reset(view.w()->createImageNode()); node.reset(view.w()->createImageNode());
view.addQSGNode(node.get(), 0); view.addQSGNode(node.get(), zValue);
} }
if (!img) { if (!img) {
resize(QSizeF(1,1)); resize(QSizeF(1,1));
@ -85,7 +85,8 @@ QRectF ChartItem::getRect() const
return rect; return rect;
} }
ChartRectItem::ChartRectItem(StatsView &v, const QPen &pen, const QBrush &brush, double radius) : ChartItem(v), ChartRectItem::ChartRectItem(StatsView &v, ChartZValue z,
const QPen &pen, const QBrush &brush, double radius) : ChartItem(v, z),
pen(pen), brush(brush), radius(radius) pen(pen), brush(brush), radius(radius)
{ {
} }

View file

@ -10,10 +10,11 @@
class QSGImageNode; class QSGImageNode;
class QSGTexture; class QSGTexture;
class StatsView; class StatsView;
enum class ChartZValue : int;
class ChartItem { class ChartItem {
public: public:
ChartItem(StatsView &v); ChartItem(StatsView &v, ChartZValue z);
~ChartItem(); ~ChartItem();
// Attention: The children are responsible for updating the item. None of these calls will. // Attention: The children are responsible for updating the item. None of these calls will.
void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*. void resize(QSizeF size); // Resets the canvas. Attention: image is *unitialized*.
@ -21,6 +22,7 @@ public:
void render(); // Only call on render thread! void render(); // Only call on render thread!
QRectF getRect() const; QRectF getRect() const;
bool dirty; // If true, call render() when rebuilding the scene bool dirty; // If true, call render() when rebuilding the scene
const ChartZValue zValue;
protected: protected:
std::unique_ptr<QPainter> painter; std::unique_ptr<QPainter> painter;
std::unique_ptr<QImage> img; std::unique_ptr<QImage> img;
@ -39,7 +41,7 @@ private:
// Draw a rectangular background after resize. Children are responsible for calling update(). // Draw a rectangular background after resize. Children are responsible for calling update().
class ChartRectItem : public ChartItem { class ChartRectItem : public ChartItem {
public: public:
ChartRectItem(StatsView &v, const QPen &pen, const QBrush &brush, double radius); ChartRectItem(StatsView &v, ChartZValue z, const QPen &pen, const QBrush &brush, double radius);
~ChartRectItem(); ~ChartRectItem();
void resize(QSizeF size); void resize(QSizeF size);
private: private:

View file

@ -12,7 +12,8 @@ static const double informationBorderRadius = 4.0; // Radius of rounded corners
static const int distanceFromPointer = 10; // Distance to place box from mouse pointer or scatter item static const int distanceFromPointer = 10; // Distance to place box from mouse pointer or scatter item
InformationBox::InformationBox(StatsView &v) : InformationBox::InformationBox(StatsView &v) :
ChartRectItem(v, QPen(informationBorderColor, informationBorder), ChartRectItem(v, ChartZValue::InformationBox,
QPen(informationBorderColor, informationBorder),
QBrush(informationColor), informationBorderRadius) QBrush(informationColor), informationBorderRadius)
{ {
} }

View file

@ -15,7 +15,8 @@ static const QColor legendColor(0x00, 0x8e, 0xcc, 192); // Note: fourth argument
static const QColor legendBorderColor(Qt::black); static const QColor legendBorderColor(Qt::black);
Legend::Legend(StatsView &view, const std::vector<QString> &names) : Legend::Legend(StatsView &view, const std::vector<QString> &names) :
ChartRectItem(view, QPen(legendBorderColor, legendBorderSize), QBrush(legendColor), legendBoxBorderRadius), ChartRectItem(view, ChartZValue::Legend,
QPen(legendBorderColor, legendBorderSize), QBrush(legendColor), legendBoxBorderRadius),
displayedItems(0), width(0.0), height(0.0), displayedItems(0), width(0.0), height(0.0),
font(QFont()) // Make configurable font(QFont()) // Make configurable
{ {

View file

@ -80,17 +80,33 @@ void StatsView::mouseReleaseEvent(QMouseEvent *)
} }
} }
class RootNode : public QSGNode
{
public:
RootNode();
QSGImageNode *imageNode; // imageNode to plot QGRaphicsScene on. Remove in due course.
// We entertain one node per Z-level.
std::array<QSGNode *, (size_t)ChartZValue::Count> zNodes;
std::array<std::vector<ChartItem *>, (size_t)ChartZValue::Count> items;
};
RootNode::RootNode()
{
for (QSGNode *&zNode: zNodes) {
zNode = new QSGNode;
appendChildNode(zNode);
}
}
QSGNode *StatsView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) QSGNode *StatsView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *)
{ {
// The QtQuick drawing interface is utterly bizzare with a distinct 1980ies-style memory management. // The QtQuick drawing interface is utterly bizzare with a distinct 1980ies-style memory management.
// This is just a copy of what is found in Qt's documentation. // This is just a copy of what is found in Qt's documentation.
QSGImageNode *n = static_cast<QSGImageNode *>(oldNode); RootNode *n = static_cast<RootNode *>(oldNode);
if (!n) if (!n) {
n = rootNode = window()->createImageNode(); n = rootNode = new RootNode;
n->imageNode = window()->createImageNode();
for (ChartItem *item: items) { n->zNodes[(int)ChartZValue::Series]->appendChildNode(n->imageNode);
if (item->dirty)
item->render();
} }
QRectF rect = boundingRect(); QRectF rect = boundingRect();
@ -99,28 +115,43 @@ QSGNode *StatsView::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNod
plotAreaChanged(plotRect.size()); plotAreaChanged(plotRect.size());
} }
for (auto &v: n->items) {
for (ChartItem *item: v) {
if (item->dirty)
item->render();
}
}
img->fill(backgroundColor); img->fill(backgroundColor);
scene.render(painter.get()); scene.render(painter.get());
texture.reset(window()->createTextureFromImage(*img, QQuickWindow::TextureIsOpaque)); texture.reset(window()->createTextureFromImage(*img, QQuickWindow::TextureIsOpaque));
n->setTexture(texture.get()); n->imageNode->setTexture(texture.get());
n->setRect(rect); n->imageNode->setRect(rect);
return n; return n;
} }
void StatsView::addQSGNode(QSGNode *node, int) void StatsView::addQSGNode(QSGNode *node, ChartZValue z)
{ {
rootNode->appendChildNode(node); int idx = std::clamp((int)z, 0, (int)ChartZValue::Count - 1);
rootNode->zNodes[idx]->appendChildNode(node);
} }
// Currently this does an inefficient linear search in the chart-item vector. // Currently this does an inefficient linear search in the chart-item vector.
// The reason is that removing individual chart items is very rare: for now, // However, we entertain one vector of items per Z-value and currently
// it is only done when hiding an InfoBox. In the future, this might have to // only the infobox is explicitly deleted, which has a unique Z-value.
// be improved.
void StatsView::unregisterChartItem(const ChartItem *item) void StatsView::unregisterChartItem(const ChartItem *item)
{ {
auto it = std::find(items.begin(), items.end(), item); int idx = std::clamp((int)item->zValue, 0, (int)ChartZValue::Count - 1);
if (it != items.end()) std::vector<ChartItem *> &v = rootNode->items[idx];
items.erase(it); auto it = std::find(v.begin(), v.end(), item);
if (it != v.end())
v.erase(it);
}
void StatsView::registerChartItem(ChartItem *item)
{
int idx = std::clamp((int)item->zValue, 0, (int)ChartZValue::Count - 1);
rootNode->items[idx].push_back(item);
} }
QQuickWindow *StatsView::w() const QQuickWindow *StatsView::w() const
@ -292,7 +323,10 @@ void StatsView::reset()
highlightedSeries = nullptr; highlightedSeries = nullptr;
xAxis = yAxis = nullptr; xAxis = yAxis = nullptr;
draggedItem = nullptr; draggedItem = nullptr;
items.clear(); // non-owning pointers if (rootNode) {
for (auto &v: rootNode->items)
v.clear(); // non-owning pointers
}
legend.reset(); legend.reset();
series.clear(); series.clear();
quartileMarkers.clear(); quartileMarkers.clear();

View file

@ -19,7 +19,6 @@ struct StatsVariable;
class QGraphicsLineItem; class QGraphicsLineItem;
class QGraphicsSimpleTextItem; class QGraphicsSimpleTextItem;
class QSGImageNode;
class StatsSeries; class StatsSeries;
class CategoryAxis; class CategoryAxis;
class ChartItem; class ChartItem;
@ -29,8 +28,10 @@ class StatsAxis;
class StatsGrid; class StatsGrid;
class Legend; class Legend;
class QSGTexture; class QSGTexture;
class RootNode; // Internal implementation detail
enum class ChartSubType : int; enum class ChartSubType : int;
enum class ChartZValue : int;
enum class StatsOperation : int; enum class StatsOperation : int;
struct regression_data { struct regression_data {
@ -50,7 +51,8 @@ public:
void plot(const StatsState &state); void plot(const StatsState &state);
QQuickWindow *w() const; // Make window available to items QQuickWindow *w() const; // Make window available to items
QSizeF size() const; QSizeF size() const;
void addQSGNode(QSGNode *node, int z); // Must only be called in render thread! void addQSGNode(QSGNode *node, ChartZValue z); // Must only be called in render thread!
void registerChartItem(ChartItem *item);
void unregisterChartItem(const ChartItem *item); void unregisterChartItem(const ChartItem *item);
template <typename T, class... Args> template <typename T, class... Args>
std::unique_ptr<T> createChartItem(Args&&... args); std::unique_ptr<T> createChartItem(Args&&... args);
@ -165,7 +167,6 @@ private:
std::vector<RegressionLine> regressionLines; std::vector<RegressionLine> regressionLines;
std::vector<HistogramMarker> histogramMarkers; std::vector<HistogramMarker> histogramMarkers;
std::unique_ptr<QGraphicsSimpleTextItem> title; std::unique_ptr<QGraphicsSimpleTextItem> title;
std::vector<ChartItem *> items; // Attention: currently, items are not automatically removed on destruction!
StatsSeries *highlightedSeries; StatsSeries *highlightedSeries;
StatsAxis *xAxis, *yAxis; StatsAxis *xAxis, *yAxis;
Legend *draggedItem; Legend *draggedItem;
@ -176,7 +177,7 @@ private:
void mousePressEvent(QMouseEvent *event) override; void mousePressEvent(QMouseEvent *event) override;
void mouseReleaseEvent(QMouseEvent *event) override; void mouseReleaseEvent(QMouseEvent *event) override;
void mouseMoveEvent(QMouseEvent *event) override; void mouseMoveEvent(QMouseEvent *event) override;
QSGImageNode *rootNode; RootNode *rootNode;
}; };
// This implementation detail must be known to users of the class. // This implementation detail must be known to users of the class.
@ -185,7 +186,7 @@ template <typename T, class... Args>
std::unique_ptr<T> StatsView::createChartItem(Args&&... args) std::unique_ptr<T> StatsView::createChartItem(Args&&... args)
{ {
std::unique_ptr<T> res(new T(*this, std::forward<Args>(args)...)); std::unique_ptr<T> res(new T(*this, std::forward<Args>(args)...));
items.push_back(res.get()); registerChartItem(res.get());
return res; return res;
} }

View file

@ -15,4 +15,15 @@ struct ZValues {
static constexpr double legend = 5.0; static constexpr double legend = 5.0;
}; };
enum class ChartZValue {
Grid = 0,
Series,
Axes,
SeriesLabels,
ChartFeatures, // quartile markers and regression lines
InformationBox,
Legend,
Count
};
#endif #endif