1// Copyright (C) 2024 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include <QtGraphs/qpieseries.h>
5#include <QtGraphs/qpieslice.h>
6#include <QtQuick/private/qquicktext_p.h>
7#include <private/pierenderer_p.h>
8#include <private/qabstractseries_p.h>
9#include <private/qgraphsview_p.h>
10#include <private/qpieseries_p.h>
11#include <private/qpieslice_p.h>
12#include <private/qquickshape_p.h>
13#include <private/qquicksvgparser_p.h>
14
15PieRenderer::PieRenderer(QGraphsView *graph)
16 : QQuickItem(graph)
17 , m_graph(graph)
18 , m_painterPath()
19{
20 setFlag(flag: QQuickItem::ItemHasContents);
21 setClip(true);
22
23 m_shape = new QQuickShape(this);
24 m_shape->setParentItem(this);
25 m_shape->setPreferredRendererType(QQuickShape::CurveRenderer);
26}
27
28PieRenderer::~PieRenderer() {}
29
30void PieRenderer::setSize(QSizeF size)
31{
32 QQuickItem::setSize(size);
33}
34
35void PieRenderer::handlePolish(QPieSeries *series)
36{
37 for (QPieSlice *slice : series->slices()) {
38 QPieSlicePrivate *d = slice->d_func();
39 QQuickShapePath *shapePath = d->m_shapePath;
40 QQuickShapePath *labelPath = d->m_labelPath;
41 auto labelElements = labelPath->pathElements();
42 auto pathElements = shapePath->pathElements();
43 auto labelItem = d->m_labelItem;
44
45 if (!m_activeSlices.contains(key: slice)) {
46 auto data = m_shape->data();
47 data.append(&data, shapePath);
48 SliceData sliceData{};
49 sliceData.initialized = false;
50 m_activeSlices.insert(key: slice, value: sliceData);
51 }
52
53 QQuickShape *labelShape = d->m_labelShape;
54 labelShape->setVisible(series->isVisible() && d->m_isLabelVisible);
55 labelItem->setVisible(series->isVisible() && d->m_isLabelVisible);
56
57 if (!series->isVisible()) {
58 pathElements.clear(&pathElements);
59 labelElements.clear(&labelElements);
60 continue;
61 }
62
63 if (!shapePath->parent())
64 shapePath->setParent(m_shape);
65
66 if (!d->m_labelItem->parent()) {
67 d->m_labelItem->setParent(this);
68 d->m_labelItem->setParentItem(this);
69 }
70
71 if (!labelShape->parent()) {
72 labelShape->setParent(this);
73 labelShape->setParentItem(this);
74 }
75 }
76
77 if (!series->isVisible())
78 return;
79
80 QPointF center = QPointF(size().width() * series->horizontalPosition(),
81 size().height() * series->verticalPosition());
82 qreal radius = size().width() > size().height() ? size().height() : size().width();
83 radius *= (.5 * series->pieSize());
84
85 QGraphsTheme *theme = m_graph->theme();
86
87 if (!theme)
88 return;
89
90 if (m_colorIndex < 0)
91 m_colorIndex = m_graph->graphSeriesCount();
92 m_graph->setGraphSeriesCount(m_colorIndex + series->slices().size());
93
94 qreal sliceAngle = series->startAngle();
95 int sliceIndex = 0;
96 QList<QLegendData> legendDataList;
97 for (QPieSlice *slice : series->slices()) {
98 m_painterPath.clear();
99
100 QPieSlicePrivate *d = slice->d_func();
101 d->setStartAngle(sliceAngle);
102 d->setAngleSpan((series->endAngle() - series->startAngle()) * slice->percentage()
103 * series->valuesMultiplier());
104
105 // update slice
106 QQuickShapePath *shapePath = d->m_shapePath;
107
108 const auto &borderColors = theme->borderColors();
109 int index = sliceIndex % borderColors.size();
110 QColor borderColor = borderColors.at(i: index);
111 if (d->m_borderColor.isValid())
112 borderColor = d->m_borderColor;
113 qreal borderWidth = theme->borderWidth();
114 if (d->m_borderWidth >= 1.0)
115 borderWidth = d->m_borderWidth;
116 const auto &seriesColors = theme->seriesColors();
117 index = sliceIndex % seriesColors.size();
118 QColor color = seriesColors.at(i: index);
119 if (d->m_color.isValid())
120 color = d->m_color;
121 shapePath->setStrokeWidth(borderWidth);
122 shapePath->setStrokeColor(borderColor);
123 shapePath->setFillColor(color);
124
125 QColor labelTextColor = theme->labelTextColor();
126 if (d->m_labelColor.isValid())
127 labelTextColor = d->m_labelColor;
128 d->m_labelItem->setColor(labelTextColor);
129 d->m_labelPath->setStrokeColor(labelTextColor);
130
131 if (!m_activeSlices.contains(key: slice))
132 return;
133
134 qreal radian = qDegreesToRadians(degrees: slice->startAngle());
135 qreal startBigX = radius * qSin(v: radian);
136 qreal startBigY = radius * qCos(v: radian);
137
138 qreal startSmallX = startBigX * series->holeSize();
139 qreal startSmallY = startBigY * series->holeSize();
140
141 qreal explodeDistance = .0;
142 if (slice->isExploded())
143 explodeDistance = slice->explodeDistanceFactor() * radius;
144 radian = qDegreesToRadians(degrees: slice->startAngle() + (slice->angleSpan() * .5));
145 qreal xShift = center.x() + (explodeDistance * qSin(v: radian));
146 qreal yShift = center.y() - (explodeDistance * qCos(v: radian));
147
148 qreal pointX = startBigY * qSin(v: radian) + startBigX * qCos(v: radian);
149 qreal pointY = startBigY * qCos(v: radian) - startBigX * qSin(v: radian);
150
151 QRectF rect(center.x() - radius + (explodeDistance * qSin(v: radian)),
152 center.y() - radius - (explodeDistance * qCos(v: radian)),
153 radius * 2,
154 radius * 2);
155
156 shapePath->setStartX(center.x());
157 shapePath->setStartY(center.y());
158
159 if (series->holeSize() > 0) {
160 QRectF insideRect(center.x() - series->holeSize() * radius
161 + (explodeDistance * qSin(v: radian)),
162 center.y() - series->holeSize() * radius
163 - (explodeDistance * qCos(v: radian)),
164 series->holeSize() * radius * 2,
165 series->holeSize() * radius * 2);
166
167 m_painterPath.arcMoveTo(rect, angle: -slice->startAngle() + 90);
168 m_painterPath.arcTo(rect, startAngle: -slice->startAngle() + 90, arcLength: -slice->angleSpan());
169 m_painterPath.arcTo(rect: insideRect,
170 startAngle: -slice->startAngle() + 90 - slice->angleSpan(),
171 arcLength: slice->angleSpan());
172 m_painterPath.closeSubpath();
173 } else {
174 m_painterPath.moveTo(p: rect.center());
175 m_painterPath.arcTo(rect, startAngle: -slice->startAngle() + 90, arcLength: -slice->angleSpan());
176 m_painterPath.closeSubpath();
177 }
178
179 radian = qDegreesToRadians(degrees: slice->angleSpan());
180
181 pointX = startSmallY * qSin(v: radian) + startSmallX * qCos(v: radian);
182 pointY = startSmallY * qCos(v: radian) - startSmallX * qSin(v: radian);
183
184 d->m_largeArc = {xShift + pointX, yShift - pointY};
185
186 shapePath->setPath(m_painterPath);
187 m_painterPath.clear();
188
189 radian = qDegreesToRadians(degrees: slice->startAngle() + (slice->angleSpan() * .5));
190 startBigX = radius * qSin(v: radian);
191 startBigY = radius * qCos(v: radian);
192
193 pointX = radius * (1.0 + d->m_labelArmLengthFactor) * qSin(v: radian);
194 pointY = radius * (1.0 + d->m_labelArmLengthFactor) * qCos(v: radian);
195
196 m_painterPath.moveTo(x: xShift + startBigX, y: yShift - startBigY);
197 m_painterPath.lineTo(x: xShift + pointX, y: yShift - pointY);
198
199 d->m_centerLine = {xShift + pointX, yShift - pointY};
200
201 d->m_labelArm = {xShift + pointX, yShift - pointY};
202
203 auto labelWidth = radian > M_PI ? -d->m_labelItem->width() : d->m_labelItem->width();
204 m_painterPath.lineTo(x: d->m_labelArm.x() + labelWidth, y: d->m_labelArm.y());
205
206 d->setLabelPosition(d->m_labelPosition);
207 d->m_labelPath->setPath(m_painterPath);
208
209 sliceAngle += slice->angleSpan();
210 sliceIndex++;
211 legendDataList.push_back(t: {.color: color, .borderColor: borderColor, .label: d->m_labelText});
212 }
213
214 series->d_func()->setLegendData(legendDataList);
215}
216
217void PieRenderer::afterPolish(QList<QAbstractSeries *> &cleanupSeries)
218{
219 for (auto series : cleanupSeries) {
220 auto pieSeries = qobject_cast<QPieSeries *>(object: series);
221 if (pieSeries) {
222 for (QPieSlice *slice : pieSeries->slices()) {
223 QPieSlicePrivate *d = slice->d_func();
224 auto labelElements = d->m_labelPath->pathElements();
225 auto shapeElements = d->m_shapePath->pathElements();
226
227 labelElements.clear(&labelElements);
228 shapeElements.clear(&shapeElements);
229
230 slice->deleteLater();
231 d->m_labelItem->deleteLater();
232
233 m_activeSlices.remove(key: slice);
234 }
235 }
236 }
237}
238
239void PieRenderer::updateSeries(QPieSeries *series)
240{
241 auto needPolish = false;
242
243 for (auto &sliceData : m_activeSlices) {
244 if (!sliceData.initialized) {
245 sliceData.initialized = true;
246 needPolish = true;
247 }
248 }
249
250 if (needPolish)
251 handlePolish(series);
252}
253
254void PieRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries)
255{
256 Q_UNUSED(cleanupSeries);
257}
258
259void PieRenderer::markedDeleted(QList<QPieSlice *> deleted)
260{
261 auto emptyPath = QPainterPath{};
262
263 for (auto slice : deleted) {
264 auto d = slice->d_func();
265 d->m_shapePath->setPath(emptyPath);
266 d->m_labelPath->setPath(emptyPath);
267 d->m_labelItem->deleteLater();
268 m_activeSlices.remove(key: slice);
269 }
270}
271

source code of qtgraphs/src/graphs2d/qsgrenderer/pierenderer.cpp