summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Berthold Stoeger <bstoeger@mail.tuwien.ac.at>2021-01-15 18:39:14 +0100
committerGravatar bstoeger <32835590+bstoeger@users.noreply.github.com>2021-01-20 08:47:18 +0100
commit20088576604a3159cc9891bcfea888c15b096a96 (patch)
tree244e1996c21adb6071346d754e5ac401d726aa15
parentfaf3e7079ddd680ab0e53a27a7ddc0d863792117 (diff)
downloadsubsurface-20088576604a3159cc9891bcfea888c15b096a96.tar.gz
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>
-rw-r--r--Subsurface-mobile.pro2
-rw-r--r--stats/CMakeLists.txt2
-rw-r--r--stats/regressionitem.cpp87
-rw-r--r--stats/regressionitem.h26
-rw-r--r--stats/statsview.cpp59
-rw-r--r--stats/statsview.h23
6 files changed, 124 insertions, 75 deletions
diff --git a/Subsurface-mobile.pro b/Subsurface-mobile.pro
index 588e431d4..8e35c835d 100644
--- a/Subsurface-mobile.pro
+++ b/Subsurface-mobile.pro
@@ -138,6 +138,7 @@ SOURCES += subsurface-mobile-main.cpp \
stats/legend.cpp \
stats/pieseries.cpp \
stats/quartilemarker.cpp \
+ stats/regressionitem.cpp \
stats/scatterseries.cpp \
stats/statsaxis.cpp \
stats/statscolors.cpp \
@@ -289,6 +290,7 @@ HEADERS += \
stats/legend.h \
stats/pieseries.h \
stats/quartilemarker.h \
+ stats/regressionitem.h \
stats/scatterseries.h \
stats/statsaxis.h \
stats/statscolors.h \
diff --git a/stats/CMakeLists.txt b/stats/CMakeLists.txt
index 9048acf97..daeb22146 100644
--- a/stats/CMakeLists.txt
+++ b/stats/CMakeLists.txt
@@ -24,6 +24,8 @@ set(SUBSURFACE_STATS_SRCS
pieseries.cpp
quartilemarker.h
quartilemarker.cpp
+ regressionitem.h
+ regressionitem.cpp
scatterseries.h
scatterseries.cpp
statsaxis.h
diff --git a/stats/regressionitem.cpp b/stats/regressionitem.cpp
new file mode 100644
index 000000000..db02cf688
--- /dev/null
+++ b/stats/regressionitem.cpp
@@ -0,0 +1,87 @@
+// SPDX-License-Identifier: GPL-2.0
+#include "regressionitem.h"
+#include "statsaxis.h"
+#include "zvalues.h"
+
+#include <cmath>
+
+static const QColor regressionItemColor(Qt::red);
+static const double regressionLineWidth = 2.0;
+
+RegressionItem::RegressionItem(StatsView &view, regression_data reg,
+ StatsAxis *xAxis, StatsAxis *yAxis) :
+ ChartPixmapItem(view, ChartZValue::ChartFeatures),
+ xAxis(xAxis), yAxis(yAxis), reg(reg)
+{
+}
+
+RegressionItem::~RegressionItem()
+{
+}
+
+void RegressionItem::updatePosition()
+{
+ if (!xAxis || !yAxis)
+ return;
+ auto [minX, maxX] = xAxis->minMax();
+ auto [minY, maxY] = yAxis->minMax();
+ auto [screenMinX, screenMaxX] = xAxis->minMaxScreen();
+
+ // 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;
+ const int num_samples = 101;
+ poly.reserve(num_samples * 2);
+ for (int i = 0; i < num_samples; ++i) {
+ double x = (maxX - minX) / (num_samples - 1) * static_cast<double>(i) + minX;
+ 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 (int i = num_samples - 1; i >= 0; --i) {
+ double x = (maxX - minX) / (num_samples - 1) * static_cast<double>(i) + minX;
+ 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))));
+ }
+ QPolygonF linePolygon;
+ linePolygon.reserve(2);
+ linePolygon << QPointF(screenMinX, yAxis->toScreen(reg.a * minX + reg.b));
+ linePolygon << QPointF(screenMaxX, yAxis->toScreen(reg.a * maxX + reg.b));
+
+ QRectF box(QPointF(screenMinX, yAxis->toScreen(minY)), QPointF(screenMaxX, yAxis->toScreen(maxY)));
+
+ poly = poly.intersected(box);
+ linePolygon = linePolygon.intersected(box);
+ if (poly.size() < 2 || linePolygon.size() < 2)
+ return;
+
+ // Find lowest and highest point on screen. In principle, we need
+ // only check half of the polygon, but let's not optimize without reason.
+ double screenMinY = std::numeric_limits<double>::max();
+ double screenMaxY = std::numeric_limits<double>::lowest();
+ for (const QPointF &point: poly) {
+ double y = point.y();
+ if (y < screenMinY)
+ screenMinY = y;
+ if (y > screenMaxY)
+ screenMaxY = y;
+ }
+ screenMinY = floor(screenMinY - 1.0);
+ screenMaxY = ceil(screenMaxY + 1.0);
+ QPointF offset(screenMinX, screenMinY);
+ for (QPointF &point: poly)
+ point -= offset;
+ for (QPointF &point: linePolygon)
+ point -= offset;
+ ChartPixmapItem::resize(QSizeF(screenMaxX - screenMinX, screenMaxY - screenMinY));
+
+ img->fill(Qt::transparent);
+ QColor col(regressionItemColor);
+ col.setAlphaF(reg.r2);
+ painter->setPen(Qt::NoPen);
+ painter->setBrush(QBrush(col));
+ painter->drawPolygon(poly);
+
+ painter->setPen(QPen(regressionItemColor, regressionLineWidth));
+ painter->drawLine(QPointF(linePolygon[0]), QPointF(linePolygon[1]));
+
+ ChartPixmapItem::setPos(offset);
+}
diff --git a/stats/regressionitem.h b/stats/regressionitem.h
new file mode 100644
index 000000000..607317d08
--- /dev/null
+++ b/stats/regressionitem.h
@@ -0,0 +1,26 @@
+// A regression line and confidence area
+#ifndef REGRESSION_H
+#define REGRESSION_H
+
+#include "chartitem.h"
+
+class StatsAxis;
+class StatsView;
+
+struct regression_data {
+ double a,b;
+ double res2, r2, sx2, xavg;
+ int n;
+};
+
+class RegressionItem : public ChartPixmapItem {
+public:
+ RegressionItem(StatsView &view, regression_data data, StatsAxis *xAxis, StatsAxis *yAxis);
+ ~RegressionItem();
+ void updatePosition();
+private:
+ StatsAxis *xAxis, *yAxis;
+ regression_data reg;
+};
+
+#endif
diff --git a/stats/statsview.cpp b/stats/statsview.cpp
index 40d23eed3..455589976 100644
--- a/stats/statsview.cpp
+++ b/stats/statsview.cpp
@@ -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);
}
diff --git a/stats/statsview.h b/stats/statsview.h
index f472e1e16..b0a199200 100644
--- a/stats/statsview.h
+++ b/stats/statsview.h
@@ -9,7 +9,6 @@
#include <QImage>
#include <QPainter>
#include <QQuickItem>
-#include <QGraphicsPolygonItem>
struct dive;
struct StatsBinner;
@@ -17,7 +16,6 @@ struct StatsBin;
struct StatsState;
struct StatsVariable;
-class QGraphicsLineItem;
class QGraphicsSimpleTextItem;
class StatsSeries;
class CategoryAxis;
@@ -26,6 +24,7 @@ class CountAxis;
class HistogramAxis;
class HistogramMarker;
class QuartileMarker;
+class RegressionItem;
class StatsAxis;
class StatsGrid;
class Legend;
@@ -36,13 +35,6 @@ enum class ChartSubType : int;
enum class ChartZValue : int;
enum class StatsOperation : int;
-struct regression_data {
- double a,b;
- double res2, r2, sx2, xavg;
- int n;
-};
-
-
class StatsView : public QQuickItem {
Q_OBJECT
public:
@@ -127,17 +119,6 @@ private:
// Helper functions to add feature to the chart
void addLineMarker(double pos, double low, double high, const QPen &pen, bool isHorizontal);
- // A regression line
- struct RegressionLine {
- std::unique_ptr<QGraphicsPolygonItem> item;
- std::unique_ptr<QGraphicsPolygonItem> central;
- StatsAxis *xAxis, *yAxis;
- const struct regression_data reg;
- void updatePosition();
- RegressionLine(const struct regression_data reg, QBrush brush, QGraphicsScene *scene, StatsAxis *xAxis, StatsAxis *yAxis);
- };
-
- void addLinearRegression(const struct regression_data reg, StatsAxis *xAxis, StatsAxis *yAxis);
void addHistogramMarker(double pos, QColor color, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis);
StatsState state;
@@ -147,9 +128,9 @@ private:
std::vector<std::unique_ptr<StatsSeries>> series;
std::unique_ptr<Legend> legend;
std::vector<std::unique_ptr<QuartileMarker>> quartileMarkers;
- std::vector<RegressionLine> regressionLines;
std::vector<std::unique_ptr<HistogramMarker>> histogramMarkers;
std::unique_ptr<QGraphicsSimpleTextItem> title;
+ std::unique_ptr<RegressionItem> regressionItem;
StatsSeries *highlightedSeries;
StatsAxis *xAxis, *yAxis;
Legend *draggedItem;