mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
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:
parent
785d5189f6
commit
e1c0cace95
7 changed files with 82 additions and 31 deletions
|
@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
{
|
{
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
Loading…
Add table
Reference in a new issue