mirror of
https://github.com/subsurface/subsurface.git
synced 2025-02-19 22:16:15 +00:00
statistics: fix chart features: regression line and median/mean
The coordinates of these were calculated when creating the feature. This is wrong, because the min/max values of the axes can change on resize to get "nice" number. Therefore, recalculate after resizing. This means that the general "LineMarker" class has to be split into two classes, one for regression lines and one for median/mean markers. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
ab324ed769
commit
405e6b6b69
4 changed files with 70 additions and 34 deletions
|
@ -52,6 +52,12 @@ std::pair<double, double> StatsAxis::minMax() const
|
||||||
return { min, max };
|
return { min, max };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::pair<double, double> StatsAxis::minMaxScreen() const
|
||||||
|
{
|
||||||
|
return horizontal ? std::make_pair(zeroOnScreen, zeroOnScreen + size)
|
||||||
|
: std::make_pair(zeroOnScreen, zeroOnScreen - size);
|
||||||
|
}
|
||||||
|
|
||||||
void StatsAxis::setRange(double minIn, double maxIn)
|
void StatsAxis::setRange(double minIn, double maxIn)
|
||||||
{
|
{
|
||||||
min = minIn;
|
min = minIn;
|
||||||
|
|
|
@ -20,6 +20,7 @@ public:
|
||||||
virtual ~StatsAxis();
|
virtual ~StatsAxis();
|
||||||
// Returns minimum and maximum of shown range, not of data points.
|
// Returns minimum and maximum of shown range, not of data points.
|
||||||
std::pair<double, double> minMax() const;
|
std::pair<double, double> minMax() const;
|
||||||
|
std::pair<double, double> minMaxScreen() const; // minimum and maximum in screen coordinates
|
||||||
|
|
||||||
double width() const; // Only supported by vertical axes. Only valid after setSize().
|
double width() const; // Only supported by vertical axes. Only valid after setSize().
|
||||||
double height() const; // Only supported for horizontal axes. Always valid.
|
double height() const; // Only supported for horizontal axes. Always valid.
|
||||||
|
|
|
@ -125,7 +125,9 @@ void StatsView::plotAreaChanged(const QRectF &r)
|
||||||
series->updatePositions();
|
series->updatePositions();
|
||||||
for (QuartileMarker &marker: quartileMarkers)
|
for (QuartileMarker &marker: quartileMarkers)
|
||||||
marker.updatePosition();
|
marker.updatePosition();
|
||||||
for (LineMarker &marker: lineMarkers)
|
for (RegressionLine &line: regressionLines)
|
||||||
|
line.updatePosition();
|
||||||
|
for (HistogramMarker &marker: histogramMarkers)
|
||||||
marker.updatePosition();
|
marker.updatePosition();
|
||||||
if (legend)
|
if (legend)
|
||||||
legend->resize();
|
legend->resize();
|
||||||
|
@ -211,7 +213,8 @@ void StatsView::reset()
|
||||||
legend.reset();
|
legend.reset();
|
||||||
series.clear();
|
series.clear();
|
||||||
quartileMarkers.clear();
|
quartileMarkers.clear();
|
||||||
lineMarkers.clear();
|
regressionLines.clear();
|
||||||
|
histogramMarkers.clear();
|
||||||
chart->removeAllSeries();
|
chart->removeAllSeries();
|
||||||
grid.reset();
|
grid.reset();
|
||||||
axes.clear();
|
axes.clear();
|
||||||
|
@ -716,37 +719,25 @@ void StatsView::QuartileMarker::updatePosition()
|
||||||
x + quartileMarkerSize / 2.0, y);
|
x + quartileMarkerSize / 2.0, y);
|
||||||
}
|
}
|
||||||
|
|
||||||
StatsView::LineMarker::LineMarker(QPointF from, QPointF to, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) :
|
StatsView::RegressionLine::RegressionLine(double a, double b, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||||
item(new QGraphicsLineItem(chart)),
|
item(new QGraphicsLineItem(chart)),
|
||||||
xAxis(xAxis), yAxis(yAxis),
|
xAxis(xAxis), yAxis(yAxis),
|
||||||
from(from), to(to)
|
a(a), b(b)
|
||||||
{
|
{
|
||||||
item->setZValue(ZValues::chartFeatures);
|
item->setZValue(ZValues::chartFeatures);
|
||||||
item->setPen(pen);
|
item->setPen(pen);
|
||||||
updatePosition();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatsView::LineMarker::updatePosition()
|
void StatsView::RegressionLine::updatePosition()
|
||||||
{
|
{
|
||||||
if (!xAxis || !yAxis)
|
if (!xAxis || !yAxis)
|
||||||
return;
|
return;
|
||||||
double x1 = xAxis->toScreen(from.x());
|
auto [minX, maxX] = xAxis->minMax();
|
||||||
double y1 = yAxis->toScreen(from.y());
|
auto [minY, maxY] = yAxis->minMax();
|
||||||
double x2 = xAxis->toScreen(to.x());
|
|
||||||
double y2 = yAxis->toScreen(to.y());
|
|
||||||
item->setLine(x1, y1, x2, y2);
|
|
||||||
}
|
|
||||||
|
|
||||||
void StatsView::addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis)
|
|
||||||
{
|
|
||||||
// Sanity check: line above or below chart
|
|
||||||
double y1 = a * minX + b;
|
double y1 = a * minX + b;
|
||||||
double y2 = a * maxX + b;
|
double y2 = a * maxX + b;
|
||||||
if ((y1 <= minY && y2 <= minY) || (y1 >= maxY && y2 >= maxY))
|
|
||||||
return;
|
|
||||||
|
|
||||||
// If not fully inside drawing region, do clipping. With the check above this guarantees that a != 0,
|
// If not fully inside drawing region, do clipping.
|
||||||
// but owing to floating point imprecision, let's test again.
|
|
||||||
if ((y1 < minY || y1 > maxY || y2 < minY || y2 > maxY) && fabs(a) > 0.0001) {
|
if ((y1 < minY || y1 > maxY || y2 < minY || y2 > maxY) && fabs(a) > 0.0001) {
|
||||||
// Intersections with y = minY and y = maxY lines
|
// Intersections with y = minY and y = maxY lines
|
||||||
double intersect_x1 = (minY - b) / a;
|
double intersect_x1 = (minY - b) / a;
|
||||||
|
@ -756,14 +747,42 @@ void StatsView::addLinearRegression(double a, double b, double minX, double maxX
|
||||||
minX = std::max(minX, intersect_x1);
|
minX = std::max(minX, intersect_x1);
|
||||||
maxX = std::min(maxX, intersect_x2);
|
maxX = std::min(maxX, intersect_x2);
|
||||||
}
|
}
|
||||||
lineMarkers.emplace_back(QPointF(minX, a * minX + b), QPointF(maxX, a * maxX + b), QPen(Qt::red), chart, xAxis, yAxis);
|
item->setLine(xAxis->toScreen(minX), yAxis->toScreen(a * minX + b),
|
||||||
|
xAxis->toScreen(maxX), yAxis->toScreen(a * maxX + b));
|
||||||
}
|
}
|
||||||
|
|
||||||
void StatsView::addHistogramMarker(double pos, double low, double high, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis)
|
StatsView::HistogramMarker::HistogramMarker(double val, bool horizontal, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis) :
|
||||||
|
item(new QGraphicsLineItem(chart)),
|
||||||
|
xAxis(xAxis), yAxis(yAxis),
|
||||||
|
val(val), horizontal(horizontal)
|
||||||
{
|
{
|
||||||
QPointF from = isHorizontal ? QPointF(low, pos) : QPointF(pos, low);
|
item->setZValue(ZValues::chartFeatures);
|
||||||
QPointF to = isHorizontal ? QPointF(high, pos) : QPointF(pos, high);
|
item->setPen(pen);
|
||||||
lineMarkers.emplace_back(from, to, pen, chart, xAxis, yAxis);
|
}
|
||||||
|
|
||||||
|
void StatsView::HistogramMarker::updatePosition()
|
||||||
|
{
|
||||||
|
if (!xAxis || !yAxis)
|
||||||
|
return;
|
||||||
|
if (horizontal) {
|
||||||
|
double y = yAxis->toScreen(val);
|
||||||
|
auto [x1, x2] = xAxis->minMaxScreen();
|
||||||
|
item->setLine(x1, y, x2, y);
|
||||||
|
} else {
|
||||||
|
double x = xAxis->toScreen(val);
|
||||||
|
auto [y1, y2] = yAxis->minMaxScreen();
|
||||||
|
item->setLine(x, y1, x, y2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatsView::addHistogramMarker(double pos, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis)
|
||||||
|
{
|
||||||
|
histogramMarkers.emplace_back(pos, isHorizontal, pen, chart, xAxis, yAxis);
|
||||||
|
}
|
||||||
|
|
||||||
|
void StatsView::addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis)
|
||||||
|
{
|
||||||
|
regressionLines.emplace_back(a, b, QPen(Qt::red), chart, xAxis, yAxis);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Yikes, we get our data in different kinds of (bin, value) pairs.
|
// Yikes, we get our data in different kinds of (bin, value) pairs.
|
||||||
|
@ -812,7 +831,6 @@ void StatsView::plotHistogramCountChart(const std::vector<dive *> &dives,
|
||||||
int total = getTotalCount(categoryBins);
|
int total = getTotalCount(categoryBins);
|
||||||
|
|
||||||
StatsAxis *valAxis = createCountAxis(maxCategoryCount, isHorizontal);
|
StatsAxis *valAxis = createCountAxis(maxCategoryCount, isHorizontal);
|
||||||
double chartHeight = valAxis->minMax().second;
|
|
||||||
|
|
||||||
if (isHorizontal)
|
if (isHorizontal)
|
||||||
setAxes(valAxis, catAxis);
|
setAxes(valAxis, catAxis);
|
||||||
|
@ -840,14 +858,14 @@ void StatsView::plotHistogramCountChart(const std::vector<dive *> &dives,
|
||||||
QPen meanPen(Qt::green);
|
QPen meanPen(Qt::green);
|
||||||
meanPen.setWidth(2);
|
meanPen.setWidth(2);
|
||||||
if (!std::isnan(mean))
|
if (!std::isnan(mean))
|
||||||
addHistogramMarker(mean, 0.0, chartHeight, meanPen, isHorizontal, xAxis, yAxis);
|
addHistogramMarker(mean, meanPen, isHorizontal, xAxis, yAxis);
|
||||||
}
|
}
|
||||||
if (showMedian) {
|
if (showMedian) {
|
||||||
double median = categoryVariable->quartiles(dives).q2;
|
double median = categoryVariable->quartiles(dives).q2;
|
||||||
QPen medianPen(Qt::red);
|
QPen medianPen(Qt::red);
|
||||||
medianPen.setWidth(2);
|
medianPen.setWidth(2);
|
||||||
if (!std::isnan(median))
|
if (!std::isnan(median))
|
||||||
addHistogramMarker(median, 0.0, chartHeight, medianPen, isHorizontal, xAxis, yAxis);
|
addHistogramMarker(median, medianPen, isHorizontal, xAxis, yAxis);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -106,17 +106,27 @@ private:
|
||||||
void updatePosition();
|
void updatePosition();
|
||||||
};
|
};
|
||||||
|
|
||||||
// A general line marker
|
// A regression line
|
||||||
struct LineMarker {
|
struct RegressionLine {
|
||||||
std::unique_ptr<QGraphicsLineItem> item;
|
std::unique_ptr<QGraphicsLineItem> item;
|
||||||
StatsAxis *xAxis, *yAxis;
|
StatsAxis *xAxis, *yAxis;
|
||||||
QPointF from, to; // In local coordinates
|
double a, b; // y = ax + b
|
||||||
void updatePosition();
|
void updatePosition();
|
||||||
LineMarker(QPointF from, QPointF to, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis);
|
RegressionLine(double a, double b, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||||
|
};
|
||||||
|
|
||||||
|
// A line marking median or mean in histograms
|
||||||
|
struct HistogramMarker {
|
||||||
|
std::unique_ptr<QGraphicsLineItem> item;
|
||||||
|
StatsAxis *xAxis, *yAxis;
|
||||||
|
double val;
|
||||||
|
bool horizontal;
|
||||||
|
void updatePosition();
|
||||||
|
HistogramMarker(double val, bool horizontal, QPen pen, QtCharts::QChart *chart, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||||
};
|
};
|
||||||
|
|
||||||
void addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis);
|
void addLinearRegression(double a, double b, double minX, double maxX, double minY, double maxY, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||||
void addHistogramMarker(double pos, double low, double high, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis);
|
void addHistogramMarker(double pos, const QPen &pen, bool isHorizontal, StatsAxis *xAxis, StatsAxis *yAxis);
|
||||||
|
|
||||||
StatsState state;
|
StatsState state;
|
||||||
QtCharts::QChart *chart;
|
QtCharts::QChart *chart;
|
||||||
|
@ -126,7 +136,8 @@ private:
|
||||||
std::vector<std::unique_ptr<StatsSeries>> series;
|
std::vector<std::unique_ptr<StatsSeries>> series;
|
||||||
std::unique_ptr<Legend> legend;
|
std::unique_ptr<Legend> legend;
|
||||||
std::vector<QuartileMarker> quartileMarkers;
|
std::vector<QuartileMarker> quartileMarkers;
|
||||||
std::vector<LineMarker> lineMarkers;
|
std::vector<RegressionLine> regressionLines;
|
||||||
|
std::vector<HistogramMarker> histogramMarkers;
|
||||||
std::unique_ptr<QGraphicsSimpleTextItem> title;
|
std::unique_ptr<QGraphicsSimpleTextItem> title;
|
||||||
StatsSeries *highlightedSeries;
|
StatsSeries *highlightedSeries;
|
||||||
StatsAxis *xAxis, *yAxis;
|
StatsAxis *xAxis, *yAxis;
|
||||||
|
|
Loading…
Add table
Reference in a new issue