diff options
Diffstat (limited to 'stats/regressionitem.cpp')
-rw-r--r-- | stats/regressionitem.cpp | 87 |
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); +} |