mirror of
https://github.com/subsurface/subsurface.git
synced 2025-01-19 14:25:27 +00:00
statistics: replace PieSeries by QSG nodes
Since there are no disk-segment QSG primitives (one could draw a triangle fan, but that doesn't seem optimal), this draws into a pixmap and blits that as a QSG node. Since this is the only series without axis, it needs a function that returns the size of the plot area. This didn't exist, so add it. Signed-off-by: Berthold Stoeger <bstoeger@mail.tuwien.ac.at>
This commit is contained in:
parent
b07a7fe5f1
commit
b068b2b0e7
6 changed files with 92 additions and 50 deletions
|
@ -224,6 +224,11 @@ ChartTextItem::ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const
|
|||
resize(QSizeF(totalWidth, totalHeight));
|
||||
}
|
||||
|
||||
ChartTextItem::ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const QString &text) :
|
||||
ChartTextItem(v, z, f, std::vector<QString>({ text }), true)
|
||||
{
|
||||
}
|
||||
|
||||
void ChartTextItem::setColor(const QColor &c)
|
||||
{
|
||||
img->fill(Qt::transparent);
|
||||
|
@ -240,6 +245,31 @@ void ChartTextItem::setColor(const QColor &c)
|
|||
setTextureDirty();
|
||||
}
|
||||
|
||||
ChartPieItem::ChartPieItem(StatsView &v, ChartZValue z, double borderWidth) : ChartPixmapItem(v, z),
|
||||
borderWidth(borderWidth)
|
||||
{
|
||||
}
|
||||
|
||||
void ChartPieItem::drawSegment(double from, double to, QColor fill, QColor border)
|
||||
{
|
||||
painter->setPen(QPen(border, borderWidth));
|
||||
painter->setBrush(QBrush(fill));
|
||||
// For whatever obscure reason, angles of pie pieces are given as 16th of a degree...?
|
||||
// Angles increase CCW, whereas pie charts usually are read CW. Therfore, startAngle
|
||||
// is dervied from "from" and subtracted from the origin angle at 12:00.
|
||||
int startAngle = 90 * 16 - static_cast<int>(round(to * 360.0 * 16.0));
|
||||
int spanAngle = static_cast<int>(round((to - from) * 360.0 * 16.0));
|
||||
QRectF drawRect(QPointF(0.0, 0.0), rect.size());
|
||||
painter->drawPie(drawRect, startAngle, spanAngle);
|
||||
setTextureDirty();
|
||||
}
|
||||
|
||||
void ChartPieItem::resize(QSizeF size)
|
||||
{
|
||||
ChartPixmapItem::resize(size);
|
||||
img->fill(Qt::transparent);
|
||||
}
|
||||
|
||||
ChartLineItem::ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width) : HideableChartItem(v, z),
|
||||
color(color), width(width), positionDirty(false), materialDirty(false)
|
||||
{
|
||||
|
|
|
@ -62,8 +62,8 @@ protected:
|
|||
std::unique_ptr<QImage> img;
|
||||
void setTextureDirty();
|
||||
void setPositionDirty();
|
||||
private:
|
||||
QRectF rect;
|
||||
private:
|
||||
bool positionDirty; // true if the position changed since last render
|
||||
bool textureDirty; // true if the pixmap changed since last render
|
||||
std::unique_ptr<QSGTexture> texture;
|
||||
|
@ -85,6 +85,7 @@ private:
|
|||
class ChartTextItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const std::vector<QString> &text, bool center);
|
||||
ChartTextItem(StatsView &v, ChartZValue z, const QFont &f, const QString &text);
|
||||
void setColor(const QColor &color);
|
||||
private:
|
||||
QFont f;
|
||||
|
@ -97,6 +98,16 @@ private:
|
|||
std::vector<Item> items;
|
||||
};
|
||||
|
||||
// A pie chart item: draws disk segments onto a pixmap.
|
||||
class ChartPieItem : public ChartPixmapItem {
|
||||
public:
|
||||
ChartPieItem(StatsView &v, ChartZValue z, double borderWidth);
|
||||
void drawSegment(double from, double to, QColor fill, QColor border); // from and to are relative (0-1 is full disk).
|
||||
void resize(QSizeF size); // As in base class, but clears the canvas
|
||||
private:
|
||||
double borderWidth;
|
||||
};
|
||||
|
||||
class ChartLineItem : public HideableChartItem<HideableQSGNode<QSGGeometryNode>> {
|
||||
public:
|
||||
ChartLineItem(StatsView &v, ChartZValue z, QColor color, double width);
|
||||
|
|
|
@ -9,7 +9,6 @@
|
|||
|
||||
#include <numeric>
|
||||
#include <math.h>
|
||||
#include <QGraphicsEllipseItem>
|
||||
#include <QLocale>
|
||||
|
||||
static const double pieSize = 0.9; // 1.0 = occupy full width of chart
|
||||
|
@ -17,20 +16,15 @@ static const double pieBorderWidth = 1.0;
|
|||
static const double innerLabelRadius = 0.75; // 1.0 = at outer border of pie
|
||||
static const double outerLabelRadius = 1.01; // 1.0 = at outer border of pie
|
||||
|
||||
PieSeries::Item::Item(QGraphicsScene *scene, const QString &name, int from, int count, int totalCount,
|
||||
PieSeries::Item::Item(StatsView &view, const QString &name, int from, int count, int totalCount,
|
||||
int bin_nr, int numBins, bool labels) :
|
||||
item(createItemPtr<QGraphicsEllipseItem>(scene)),
|
||||
name(name),
|
||||
count(count)
|
||||
{
|
||||
QFont f; // make configurable
|
||||
QLocale loc;
|
||||
// For whatever obscure reason, angles in QGraphicsEllipseItem are given as 16th of a degree...?
|
||||
// Angles increase CCW, whereas pie charts usually are read CW.
|
||||
item->setStartAngle(90 * 16 - (from + count) * 360 * 16 / totalCount);
|
||||
item->setSpanAngle(count * 360 * 16 / totalCount);
|
||||
item->setPen(QPen(::borderColor));
|
||||
item->setZValue(ZValues::series);
|
||||
|
||||
angleFrom = static_cast<double>(from) / totalCount;
|
||||
angleTo = static_cast<double>(from + count) / totalCount;
|
||||
double meanAngle = M_PI / 2.0 - (from + count / 2.0) / totalCount * M_PI * 2.0; // Note: "-" because we go CW.
|
||||
innerLabelPos = QPointF(cos(meanAngle) * innerLabelRadius, -sin(meanAngle) * innerLabelRadius);
|
||||
|
@ -39,27 +33,24 @@ PieSeries::Item::Item(QGraphicsScene *scene, const QString &name, int from, int
|
|||
if (labels) {
|
||||
double percentage = count * 100.0 / totalCount;
|
||||
QString innerLabelText = QStringLiteral("%1\%").arg(loc.toString(percentage, 'f', 1));
|
||||
innerLabel = createItemPtr<QGraphicsSimpleTextItem>(scene, innerLabelText);
|
||||
innerLabel->setZValue(ZValues::seriesLabels);
|
||||
innerLabel = view.createChartItem<ChartTextItem>(ChartZValue::SeriesLabels, f, innerLabelText);
|
||||
|
||||
outerLabel = createItemPtr<QGraphicsSimpleTextItem>(scene, name);
|
||||
outerLabel->setBrush(QBrush(darkLabelColor));
|
||||
outerLabel->setZValue(ZValues::seriesLabels);
|
||||
outerLabel = view.createChartItem<ChartTextItem>(ChartZValue::SeriesLabels, f, name);
|
||||
outerLabel->setColor(darkLabelColor);
|
||||
}
|
||||
|
||||
highlight(bin_nr, false, numBins);
|
||||
}
|
||||
|
||||
void PieSeries::Item::updatePositions(const QRectF &rect, const QPointF ¢er, double radius)
|
||||
void PieSeries::Item::updatePositions(const QPointF ¢er, double radius)
|
||||
{
|
||||
item->setRect(rect);
|
||||
// Note: the positions in this functions are rounded to integer values,
|
||||
// because half-integer values gives horrible aliasing artifacts.
|
||||
if (innerLabel) {
|
||||
QRectF labelRect = innerLabel->boundingRect();
|
||||
innerLabel->setPos(center.x() + innerLabelPos.x() * radius - labelRect.width() / 2.0,
|
||||
center.y() + innerLabelPos.y() * radius - labelRect.height() / 2.0);
|
||||
QRectF labelRect = innerLabel->getRect();
|
||||
innerLabel->setPos(QPointF(round(center.x() + innerLabelPos.x() * radius - labelRect.width() / 2.0),
|
||||
round(center.y() + innerLabelPos.y() * radius - labelRect.height() / 2.0)));
|
||||
}
|
||||
if (outerLabel) {
|
||||
QRectF labelRect = outerLabel->boundingRect();
|
||||
QRectF labelRect = outerLabel->getRect();
|
||||
QPointF pos(center.x() + outerLabelPos.x() * radius, center.y() + outerLabelPos.y() * radius);
|
||||
if (outerLabelPos.x() < 0.0) {
|
||||
if (outerLabelPos.y() < 0.0)
|
||||
|
@ -70,25 +61,23 @@ void PieSeries::Item::updatePositions(const QRectF &rect, const QPointF ¢er,
|
|||
pos.ry() -= labelRect.height();
|
||||
}
|
||||
|
||||
outerLabel->setPos(pos);
|
||||
outerLabel->setPos(QPointF(round(pos.x()), round(pos.y())));
|
||||
}
|
||||
}
|
||||
|
||||
void PieSeries::Item::highlight(int bin_nr, bool highlight, int numBins)
|
||||
void PieSeries::Item::highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins)
|
||||
{
|
||||
QBrush brush(highlight ? highlightedColor : binColor(bin_nr, numBins));
|
||||
QPen pen(highlight ? highlightedBorderColor : ::borderColor, pieBorderWidth);
|
||||
item->setBrush(brush);
|
||||
item->setPen(pen);
|
||||
if (innerLabel) {
|
||||
QBrush labelBrush(highlight ? darkLabelColor : labelColor(bin_nr, numBins));
|
||||
innerLabel->setBrush(labelBrush);
|
||||
}
|
||||
if (innerLabel)
|
||||
innerLabel->setColor(highlight ? darkLabelColor : labelColor(bin_nr, numBins));
|
||||
item.drawSegment(angleFrom, angleTo,
|
||||
highlight ? highlightedColor : binColor(bin_nr, numBins),
|
||||
highlight ? highlightedBorderColor : ::borderColor);
|
||||
}
|
||||
|
||||
PieSeries::PieSeries(QGraphicsScene *scene, StatsView &view, StatsAxis *xAxis, StatsAxis *yAxis, const QString &categoryName,
|
||||
const std::vector<std::pair<QString, int>> &data, bool keepOrder, bool labels) :
|
||||
StatsSeries(scene, view, xAxis, yAxis),
|
||||
item(view.createChartItem<ChartPieItem>(ChartZValue::Series, pieBorderWidth)),
|
||||
categoryName(categoryName),
|
||||
highlighted(-1)
|
||||
{
|
||||
|
@ -148,7 +137,7 @@ PieSeries::PieSeries(QGraphicsScene *scene, StatsView &view, StatsAxis *xAxis, S
|
|||
int act = 0;
|
||||
for (auto it2 = sorted.begin(); it2 != it; ++it2) {
|
||||
int count = data[*it2].second;
|
||||
items.emplace_back(scene, data[*it2].first, act, count, totalCount, (int)items.size(), numBins, labels);
|
||||
items.emplace_back(view, data[*it2].first, act, count, totalCount, (int)items.size(), numBins, labels);
|
||||
act += count;
|
||||
}
|
||||
|
||||
|
@ -158,7 +147,7 @@ PieSeries::PieSeries(QGraphicsScene *scene, StatsView &view, StatsAxis *xAxis, S
|
|||
for (auto it2 = it; it2 != sorted.end(); ++it2)
|
||||
other.push_back({ data[*it2].first, data[*it2].second });
|
||||
QString name = StatsTranslations::tr("other (%1 items)").arg(other.size());
|
||||
items.emplace_back(scene, name, act, totalCount - act, totalCount, (int)items.size(), numBins, labels);
|
||||
items.emplace_back(view, name, act, totalCount - act, totalCount, (int)items.size(), numBins, labels);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -168,12 +157,18 @@ PieSeries::~PieSeries()
|
|||
|
||||
void PieSeries::updatePositions()
|
||||
{
|
||||
QRectF plotRect = scene->sceneRect();
|
||||
QRectF plotRect = view.plotArea();
|
||||
center = plotRect.center();
|
||||
radius = std::min(plotRect.width(), plotRect.height()) * pieSize / 2.0;
|
||||
QRectF rect(center.x() - radius, center.y() - radius, 2.0 * radius, 2.0 * radius);
|
||||
for (Item &item: items)
|
||||
item.updatePositions(rect, center, radius);
|
||||
radius = ceil(std::min(plotRect.width(), plotRect.height()) * pieSize / 2.0);
|
||||
QRectF rect(round(center.x() - radius), round(center.y() - radius), ceil(2.0 * radius), ceil(2.0 * radius));
|
||||
item->resize(rect.size());
|
||||
item->setPos(rect.topLeft());
|
||||
int i = 0;
|
||||
for (Item &segment: items) {
|
||||
segment.updatePositions(center, radius);
|
||||
segment.highlight(*item, i, i == highlighted, (int)items.size()); // Draw segment
|
||||
++i;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<QString> PieSeries::binNames()
|
||||
|
@ -245,7 +240,7 @@ bool PieSeries::hover(QPointF pos)
|
|||
|
||||
// Highlight new item (if any)
|
||||
if (highlighted >= 0 && highlighted < (int)items.size()) {
|
||||
items[highlighted].highlight(highlighted, true, (int)items.size());
|
||||
items[highlighted].highlight(*item, highlighted, true, (int)items.size());
|
||||
if (!information)
|
||||
information = view.createChartItem<InformationBox>();
|
||||
information->setText(makeInfo(highlighted), pos);
|
||||
|
@ -258,6 +253,6 @@ bool PieSeries::hover(QPointF pos)
|
|||
void PieSeries::unhighlight()
|
||||
{
|
||||
if (highlighted >= 0 && highlighted < (int)items.size())
|
||||
items[highlighted].highlight(highlighted, false, (int)items.size());
|
||||
items[highlighted].highlight(*item, highlighted, false, (int)items.size());
|
||||
highlighted = -1;
|
||||
}
|
||||
|
|
|
@ -10,9 +10,9 @@
|
|||
#include <QString>
|
||||
|
||||
struct InformationBox;
|
||||
class QGraphicsEllipseItem;
|
||||
struct ChartPieItem;
|
||||
struct ChartTextItem;
|
||||
class QGraphicsScene;
|
||||
class QGraphicsSimpleTextItem;
|
||||
class QRectF;
|
||||
|
||||
class PieSeries : public StatsSeries {
|
||||
|
@ -34,20 +34,20 @@ private:
|
|||
// Get item under mouse pointer, or -1 if none
|
||||
int getItemUnderMouse(const QPointF &f) const;
|
||||
|
||||
std::unique_ptr<ChartPieItem> item;
|
||||
QString categoryName;
|
||||
std::vector<QString> makeInfo(int idx) const;
|
||||
|
||||
struct Item {
|
||||
std::unique_ptr<QGraphicsEllipseItem> item;
|
||||
std::unique_ptr<QGraphicsSimpleTextItem> innerLabel, outerLabel;
|
||||
std::unique_ptr<ChartTextItem> innerLabel, outerLabel;
|
||||
QString name;
|
||||
double angleTo; // In fraction of total
|
||||
double angleFrom, angleTo; // In fraction of total
|
||||
int count;
|
||||
QPointF innerLabelPos, outerLabelPos; // With respect to a (-1, -1)-(1, 1) rectangle.
|
||||
Item(QGraphicsScene *scene, const QString &name, int from, int count, int totalCount,
|
||||
Item(StatsView &view, const QString &name, int from, int count, int totalCount,
|
||||
int bin_nr, int numBins, bool labels);
|
||||
void updatePositions(const QRectF &rect, const QPointF ¢er, double radius);
|
||||
void highlight(int bin_nr, bool highlight, int numBins);
|
||||
void updatePositions(const QPointF ¢er, double radius);
|
||||
void highlight(ChartPieItem &item, int bin_nr, bool highlight, int numBins);
|
||||
};
|
||||
std::vector<Item> items;
|
||||
int totalCount;
|
||||
|
|
|
@ -187,6 +187,11 @@ QSizeF StatsView::size() const
|
|||
return boundingRect().size();
|
||||
}
|
||||
|
||||
QRectF StatsView::plotArea() const
|
||||
{
|
||||
return plotRect;
|
||||
}
|
||||
|
||||
void StatsView::plotAreaChanged(const QSizeF &s)
|
||||
{
|
||||
// Make sure that image is at least one pixel wide / high, otherwise
|
||||
|
|
|
@ -45,6 +45,7 @@ public:
|
|||
void plot(const StatsState &state);
|
||||
QQuickWindow *w() const; // Make window available to items
|
||||
QSizeF size() const;
|
||||
QRectF plotArea() const;
|
||||
void addQSGNode(QSGNode *node, ChartZValue z); // Must only be called in render thread!
|
||||
void registerDirtyChartItem(ChartItem &item);
|
||||
void unregisterDirtyChartItem(ChartItem &item);
|
||||
|
|
Loading…
Add table
Reference in a new issue