summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--stats/boxseries.cpp70
-rw-r--r--stats/boxseries.h13
-rw-r--r--stats/chartitem.cpp74
-rw-r--r--stats/chartitem.h18
4 files changed, 118 insertions, 57 deletions
diff --git a/stats/boxseries.cpp b/stats/boxseries.cpp
index b3dc2e5bd..2d6800e05 100644
--- a/stats/boxseries.cpp
+++ b/stats/boxseries.cpp
@@ -1,6 +1,7 @@
// SPDX-License-Identifier: GPL-2.0
#include "boxseries.h"
#include "informationbox.h"
+#include "statsaxis.h"
#include "statscolors.h"
#include "statshelper.h"
#include "statstranslations.h"
@@ -11,7 +12,7 @@
// Constants that control the bar layout
static const double boxWidth = 0.8; // 1.0 = full width of category
-static const int boxBorderWidth = 2;
+static const int boxBorderWidth = 2.0;
BoxSeries::BoxSeries(QGraphicsScene *scene, StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis,
const QString &variable, const QString &unit, int decimals) :
@@ -24,23 +25,12 @@ BoxSeries::~BoxSeries()
{
}
-BoxSeries::Item::Item(QGraphicsScene *scene, BoxSeries *series, double lowerBound, double upperBound,
+BoxSeries::Item::Item(StatsView &view, BoxSeries *series, double lowerBound, double upperBound,
const StatsQuartiles &q, const QString &binName) :
lowerBound(lowerBound), upperBound(upperBound), q(q),
binName(binName)
{
- box.setZValue(ZValues::series);
- topWhisker.setZValue(ZValues::series);
- bottomWhisker.setZValue(ZValues::series);
- topBar.setZValue(ZValues::series);
- bottomBar.setZValue(ZValues::series);
- center.setZValue(ZValues::series);
- scene->addItem(&box);
- scene->addItem(&topWhisker);
- scene->addItem(&bottomWhisker);
- scene->addItem(&topBar);
- scene->addItem(&bottomBar);
- scene->addItem(&center);
+ item = view.createChartItem<ChartBoxItem>(ChartZValue::Series, boxBorderWidth);
highlight(false);
updatePosition(series);
}
@@ -51,48 +41,34 @@ BoxSeries::Item::~Item()
void BoxSeries::Item::highlight(bool highlight)
{
- QBrush brush = highlight ? QBrush(highlightedColor) : QBrush(fillColor);
- QPen pen = highlight ? QPen(highlightedBorderColor, boxBorderWidth) : QPen(::borderColor, boxBorderWidth);
- box.setBrush(brush);
- box.setPen(pen);
- topWhisker.setPen(pen);
- bottomWhisker.setPen(pen);
- topBar.setPen(pen);
- bottomBar.setPen(pen);
- center.setPen(pen);
+ if (highlight)
+ item->setColor(highlightedColor, highlightedBorderColor);
+ else
+ item->setColor(fillColor, ::borderColor);
}
void BoxSeries::Item::updatePosition(BoxSeries *series)
{
+ StatsAxis *xAxis = series->xAxis;
+ StatsAxis *yAxis = series->yAxis;
+ if (!xAxis || !yAxis)
+ return;
+
double delta = (upperBound - lowerBound) * boxWidth;
double from = (lowerBound + upperBound - delta) / 2.0;
double to = (lowerBound + upperBound + delta) / 2.0;
- double mid = (from + to) / 2.0;
-
- QPointF topLeft, bottomRight;
- QMarginsF margins(boxBorderWidth / 2.0, boxBorderWidth / 2.0, boxBorderWidth / 2.0, boxBorderWidth / 2.0);
- topLeft = series->toScreen(QPointF(from, q.max));
- bottomRight = series->toScreen(QPointF(to, q.min));
- bounding = QRectF(topLeft, bottomRight).marginsAdded(margins);
- double left = topLeft.x();
- double right = bottomRight.x();
- double width = right - left;
- double top = topLeft.y();
- double bottom = bottomRight.y();
- QPointF q1 = series->toScreen(QPointF(mid, q.q1));
- QPointF q2 = series->toScreen(QPointF(mid, q.q2));
- QPointF q3 = series->toScreen(QPointF(mid, q.q3));
- box.setRect(left, q3.y(), width, q1.y() - q3.y());
- topWhisker.setLine(q3.x(), top, q3.x(), q3.y());
- bottomWhisker.setLine(q1.x(), q1.y(), q1.x(), bottom);
- topBar.setLine(left, top, right, top);
- bottomBar.setLine(left, bottom, right, bottom);
- center.setLine(left, q2.y(), right, q2.y());
+
+ double fromScreen = xAxis->toScreen(from);
+ double toScreen = xAxis->toScreen(to);
+ double q1 = yAxis->toScreen(q.q1);
+ double q3 = yAxis->toScreen(q.q3);
+ QRectF rect(fromScreen, q3, toScreen - fromScreen, q1 - q3);
+ item->setBox(rect, yAxis->toScreen(q.min), yAxis->toScreen(q.max), yAxis->toScreen(q.q2));
}
void BoxSeries::append(double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName)
{
- items.emplace_back(new Item(scene, this, lowerBound, upperBound, q, binName));
+ items.emplace_back(new Item(view, this, lowerBound, upperBound, q, binName));
}
void BoxSeries::updatePositions()
@@ -106,8 +82,8 @@ int BoxSeries::getItemUnderMouse(const QPointF &point)
{
// Search the first item whose "end" position is greater than the cursor position.
auto it = std::lower_bound(items.begin(), items.end(), point.x(),
- [] (const std::unique_ptr<Item> &item, double x) { return item->bounding.right() < x; });
- return it != items.end() && (*it)->bounding.contains(point) ? it - items.begin() : -1;
+ [] (const std::unique_ptr<Item> &item, double x) { return item->item->getRect().right() < x; });
+ return it != items.end() && (*it)->item->getRect().contains(point) ? it - items.begin() : -1;
}
static QString infoItem(const QString &name, const QString &unit, int decimals, double value)
diff --git a/stats/boxseries.h b/stats/boxseries.h
index c32f903ca..487870701 100644
--- a/stats/boxseries.h
+++ b/stats/boxseries.h
@@ -5,13 +5,12 @@
#ifndef BOX_SERIES_H
#define BOX_SERIES_H
+#include "chartitem.h"
#include "statsseries.h"
#include "statsvariables.h" // for StatsQuartiles
#include <memory>
#include <vector>
-#include <QGraphicsLineItem>
-#include <QGraphicsRectItem>
struct InformationBox;
class QGraphicsScene;
@@ -36,16 +35,12 @@ private:
int getItemUnderMouse(const QPointF &f);
struct Item {
- QGraphicsRectItem box;
- QGraphicsLineItem topWhisker, bottomWhisker;
- QGraphicsLineItem topBar, bottomBar;
- QGraphicsLineItem center;
- QRectF bounding; // bounding box in screen coordinates
- ~Item();
+ std::unique_ptr<ChartBoxItem> item;
double lowerBound, upperBound;
StatsQuartiles q;
QString binName;
- Item(QGraphicsScene *scene, BoxSeries *series, double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName);
+ Item(StatsView &view, BoxSeries *series, double lowerBound, double upperBound, const StatsQuartiles &q, const QString &binName);
+ ~Item();
void updatePosition(BoxSeries *series);
void highlight(bool highlight);
};
diff --git a/stats/chartitem.cpp b/stats/chartitem.cpp
index ce5e6860c..67cfb4937 100644
--- a/stats/chartitem.cpp
+++ b/stats/chartitem.cpp
@@ -279,3 +279,77 @@ QRectF ChartBarItem::getRect() const
{
return rect;
}
+
+ChartBoxItem::ChartBoxItem(StatsView &v, ChartZValue z, double borderWidth) :
+ ChartBarItem(v, z, borderWidth, false) // Only support for vertical boxes
+{
+}
+
+ChartBoxItem::~ChartBoxItem()
+{
+}
+
+void ChartBoxItem::render()
+{
+ // Remember old dirty values, since ChartBarItem::render() will clear them
+ bool oldPositionDirty = positionDirty;
+ bool oldColorDirty = colorDirty;
+ ChartBarItem::render(); // This will create the base node, so no need to check for that.
+ if (!whiskersNode) {
+ whiskersGeometry.reset(new QSGGeometry(QSGGeometry::defaultAttributes_Point2D(), 10));
+ whiskersGeometry->setDrawingMode(QSGGeometry::DrawLines);
+ whiskersGeometry->setLineWidth(static_cast<float>(borderWidth));
+ whiskersMaterial.reset(new QSGFlatColorMaterial);
+ whiskersNode.reset(new QSGGeometryNode);
+ whiskersNode->setGeometry(whiskersGeometry.get());
+ whiskersNode->setMaterial(whiskersMaterial.get());
+
+ node->node->appendChildNode(whiskersNode.get());
+ // If this is the first time, make sure to update the geometry.
+ oldPositionDirty = oldColorDirty = true;
+ }
+
+ if (oldColorDirty) {
+ whiskersMaterial->setColor(borderColor);
+ whiskersNode->markDirty(QSGNode::DirtyMaterial);
+ }
+
+ if (oldPositionDirty) {
+ auto vertices = whiskersGeometry->vertexDataAsPoint2D();
+ double left = rect.left();
+ double right = rect.right();
+ double mid = (left + right) / 2.0;
+ // top bar
+ setPoint(vertices[0], QPointF(left, max));
+ setPoint(vertices[1], QPointF(right, max));
+ // top whisker
+ setPoint(vertices[2], QPointF(mid, max));
+ setPoint(vertices[3], QPointF(mid, rect.top()));
+ // bottom bar
+ setPoint(vertices[4], QPointF(left, min));
+ setPoint(vertices[5], QPointF(right, min));
+ // bottom whisker
+ setPoint(vertices[6], QPointF(mid, min));
+ setPoint(vertices[7], QPointF(mid, rect.bottom()));
+ // median indicator
+ setPoint(vertices[8], QPointF(left, median));
+ setPoint(vertices[9], QPointF(right, median));
+ whiskersNode->markDirty(QSGNode::DirtyGeometry);
+ }
+}
+
+void ChartBoxItem::setBox(const QRectF &rect, double minIn, double maxIn, double medianIn)
+{
+ min = minIn;
+ max = maxIn;
+ median = medianIn;
+ setRect(rect);
+}
+
+QRectF ChartBoxItem::getRect() const
+{
+ QRectF res = rect;
+ res.setTop(min);
+ res.setBottom(max);
+ return rect;
+}
diff --git a/stats/chartitem.h b/stats/chartitem.h
index a87d5fe10..650c45d53 100644
--- a/stats/chartitem.h
+++ b/stats/chartitem.h
@@ -123,7 +123,7 @@ public:
void setRect(const QRectF &rect);
QRectF getRect() const;
void render() override; // Only call on render thread!
-private:
+protected:
QColor color, borderColor;
double borderWidth;
QRectF rect;
@@ -135,6 +135,22 @@ private:
std::unique_ptr<QSGGeometry> borderGeometry;
};
+// A box-and-whiskers item. This is a bit lazy: derive from the bar item and add whiskers.
+class ChartBoxItem : public ChartBarItem {
+public:
+ ChartBoxItem(StatsView &v, ChartZValue z, double borderWidth);
+ ~ChartBoxItem();
+ void setBox(const QRectF &rect, double min, double max, double median); // The rect describes Q1, Q3.
+ QRectF getRect() const; // Note: this extends the center rectangle to include the whiskers.
+ void render() override; // Only call on render thread!
+private:
+ double min, max, median;
+ std::unique_ptr<QSGGeometryNode> whiskersNode;
+ std::unique_ptr<QSGFlatColorMaterial> whiskersMaterial;
+ std::unique_ptr<QSGGeometry> whiskersGeometry;
+};
+
+
// Implementation detail of templates - move to serparate header file
template <typename Node>
void HideableChartItem<Node>::setVisible(bool visibleIn)