summaryrefslogtreecommitdiffstats
path: root/stats/regressionitem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'stats/regressionitem.cpp')
-rw-r--r--stats/regressionitem.cpp87
1 files changed, 87 insertions, 0 deletions
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);
+}