statistics: render regression item using QSGNode

Render the confidence area and the regression line into a pixmap
and show that using a QSGNode.

It is unclear whether it is preferred to do it this way or to
triangulate the confidence area into triangles to be drawn by
the shader.

Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
Berthold Stoeger 2021-01-15 18:39:14 +01:00 committed by bstoeger
parent faf3e7079d
commit 2008857660
6 changed files with 124 additions and 75 deletions

View file

@ -6,6 +6,7 @@
#include "legend.h"
#include "pieseries.h"
#include "quartilemarker.h"
#include "regressionitem.h"
#include "scatterseries.h"
#include "statsaxis.h"
#include "statscolors.h"
@ -235,8 +236,8 @@ void StatsView::plotAreaChanged(const QSizeF &s)
series->updatePositions();
for (auto &marker: quartileMarkers)
marker->updatePosition();
for (RegressionLine &line: regressionLines)
line.updatePosition();
if (regressionItem)
regressionItem->updatePosition();
for (auto &marker: histogramMarkers)
marker->updatePosition();
if (legend)
@ -347,8 +348,8 @@ void StatsView::reset()
legend.reset();
series.clear();
quartileMarkers.clear();
regressionLines.clear();
histogramMarkers.clear();
regressionItem.reset();
grid.reset();
axes.clear();
title.reset();
@ -835,61 +836,11 @@ void StatsView::plotDiscreteScatter(const std::vector<dive *> &dives,
}
}
StatsView::RegressionLine::RegressionLine(const struct regression_data reg, QBrush brush, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis) :
item(createItemPtr<QGraphicsPolygonItem>(scene)),
central(createItemPtr<QGraphicsPolygonItem>(scene)),
xAxis(xAxis), yAxis(yAxis),
reg(reg)
{
item->setZValue(ZValues::chartFeatures);
item->setPen(Qt::NoPen);
item->setBrush(brush);
central->setZValue(ZValues::chartFeatures+1);
central->setPen(QPen(Qt::red));
}
void StatsView::RegressionLine::updatePosition()
{
if (!xAxis || !yAxis)
return;
auto [minX, maxX] = xAxis->minMax();
auto [minY, maxY] = yAxis->minMax();
QPolygonF line;
line << QPoint(xAxis->toScreen(minX), yAxis->toScreen(reg.a * minX + reg.b))
<< QPoint(xAxis->toScreen(maxX), yAxis->toScreen(reg.a * maxX + reg.b));
// Draw the confidence interval according to http://www2.stat.duke.edu/~tjl13/s101/slides/unit6lec3H.pdf p.5 with t*=2 for 95% confidence
QPolygonF poly;
for (double x = minX; x <= maxX + 1; x += (maxX - minX) / 100)
poly << QPointF(xAxis->toScreen(x),
yAxis->toScreen(reg.a * x + reg.b + 2.0 * sqrt(reg.res2 / (reg.n - 2) * (1.0 / reg.n + (x - reg.xavg) * (x - reg.xavg) / (reg.n - 1) * (reg.n -2) / reg.sx2))));
for (double x = maxX; x >= minX - 1; x -= (maxX - minX) / 100)
poly << QPointF(xAxis->toScreen(x),
yAxis->toScreen(reg.a * x + reg.b - 2.0 * sqrt(reg.res2 / (reg.n - 2) * (1.0 / reg.n + (x - reg.xavg) * (x - reg.xavg) / (reg.n - 1) * (reg.n -2) / reg.sx2))));
QRectF box(QPoint(xAxis->toScreen(minX), yAxis->toScreen(minY)), QPoint(xAxis->toScreen(maxX), yAxis->toScreen(maxY)));
item->setPolygon(poly.intersected(box));
central->setPolygon(line.intersected(box));
}
void StatsView::addHistogramMarker(double pos, QColor color, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis)
{
histogramMarkers.push_back(createChartItem<HistogramMarker>(pos, isHorizontal, color, xAxis, yAxis));
}
void StatsView::addLinearRegression(const struct regression_data reg, StatsAxis *xAxis, StatsAxis *yAxis)
{
QColor red = QColor(Qt::red);
red.setAlphaF(reg.r2);
QPen pen(red);
QBrush brush(red);
brush.setStyle(Qt::SolidPattern);
regressionLines.emplace_back(reg, brush, &scene, xAxis, yAxis);
}
// Yikes, we get our data in different kinds of (bin, value) pairs.
// To create a category axis from this, we have to templatify the function.
template<typename T>
@ -1194,5 +1145,5 @@ void StatsView::plotScatter(const std::vector<dive *> &dives, const StatsVariabl
// y = ax + b
struct regression_data reg = linear_regression(points);
if (!std::isnan(reg.a))
addLinearRegression(reg, xAxis, yAxis);
regressionItem = createChartItem<RegressionItem>(reg, xAxis, yAxis);
}