summaryrefslogtreecommitdiffstats
path: root/stats/regressionitem.cpp
blob: db02cf6884d7210c68cf573e553b645f9454699a (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
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);
}