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 | |
15 | PieRenderer::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 | |
28 | PieRenderer::~PieRenderer() {} |
29 | |
30 | void PieRenderer::setSize(QSizeF size) |
31 | { |
32 | QQuickItem::setSize(size); |
33 | } |
34 | |
35 | void 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 | int sliceIndex = 0; |
95 | QList<QLegendData> legendDataList; |
96 | for (QPieSlice *slice : series->slices()) { |
97 | m_painterPath.clear(); |
98 | |
99 | QPieSlicePrivate *d = slice->d_func(); |
100 | |
101 | // update slice |
102 | QQuickShapePath *shapePath = d->m_shapePath; |
103 | |
104 | const auto &borderColors = theme->borderColors(); |
105 | int index = sliceIndex % borderColors.size(); |
106 | QColor borderColor = borderColors.at(i: index); |
107 | if (d->m_borderColor.isValid()) |
108 | borderColor = d->m_borderColor; |
109 | qreal borderWidth = theme->borderWidth(); |
110 | if (d->m_borderWidth > 0.0) |
111 | borderWidth = d->m_borderWidth; |
112 | const auto &seriesColors = theme->seriesColors(); |
113 | index = sliceIndex % seriesColors.size(); |
114 | QColor color = seriesColors.at(i: index); |
115 | if (d->m_color.isValid()) |
116 | color = d->m_color; |
117 | shapePath->setStrokeWidth(borderWidth); |
118 | shapePath->setStrokeColor(borderColor); |
119 | shapePath->setFillColor(color); |
120 | |
121 | if (!m_activeSlices.contains(key: slice)) |
122 | return; |
123 | |
124 | qreal radian = qDegreesToRadians(degrees: slice->startAngle()); |
125 | qreal startBigX = radius * qSin(v: radian); |
126 | qreal startBigY = radius * qCos(v: radian); |
127 | |
128 | qreal startSmallX = startBigX * series->holeSize(); |
129 | qreal startSmallY = startBigY * series->holeSize(); |
130 | |
131 | qreal explodeDistance = .0; |
132 | if (slice->isExploded()) |
133 | explodeDistance = slice->explodeDistanceFactor() * radius; |
134 | radian = qDegreesToRadians(degrees: slice->startAngle() + (slice->angleSpan() * .5)); |
135 | qreal xShift = center.x() + (explodeDistance * qSin(v: radian)); |
136 | qreal yShift = center.y() - (explodeDistance * qCos(v: radian)); |
137 | |
138 | qreal pointX = startBigY * qSin(v: radian) + startBigX * qCos(v: radian); |
139 | qreal pointY = startBigY * qCos(v: radian) - startBigX * qSin(v: radian); |
140 | |
141 | QRectF rect(center.x() - radius + (explodeDistance * qSin(v: radian)), |
142 | center.y() - radius - (explodeDistance * qCos(v: radian)), |
143 | radius * 2, |
144 | radius * 2); |
145 | |
146 | shapePath->setStartX(center.x()); |
147 | shapePath->setStartY(center.y()); |
148 | |
149 | if (series->holeSize() > 0) { |
150 | QRectF insideRect(center.x() - series->holeSize() * radius |
151 | + (explodeDistance * qSin(v: radian)), |
152 | center.y() - series->holeSize() * radius |
153 | - (explodeDistance * qCos(v: radian)), |
154 | series->holeSize() * radius * 2, |
155 | series->holeSize() * radius * 2); |
156 | |
157 | m_painterPath.arcMoveTo(rect, angle: -slice->startAngle() + 90); |
158 | m_painterPath.arcTo(rect, startAngle: -slice->startAngle() + 90, arcLength: -slice->angleSpan()); |
159 | m_painterPath.arcTo(rect: insideRect, |
160 | startAngle: -slice->startAngle() + 90 - slice->angleSpan(), |
161 | arcLength: slice->angleSpan()); |
162 | m_painterPath.closeSubpath(); |
163 | } else { |
164 | m_painterPath.moveTo(p: rect.center()); |
165 | m_painterPath.arcTo(rect, startAngle: -slice->startAngle() + 90, arcLength: -slice->angleSpan()); |
166 | m_painterPath.closeSubpath(); |
167 | } |
168 | |
169 | radian = qDegreesToRadians(degrees: slice->angleSpan()); |
170 | |
171 | pointX = startSmallY * qSin(v: radian) + startSmallX * qCos(v: radian); |
172 | pointY = startSmallY * qCos(v: radian) - startSmallX * qSin(v: radian); |
173 | |
174 | d->m_largeArc = {xShift + pointX, yShift - pointY}; |
175 | |
176 | shapePath->setPath(m_painterPath); |
177 | m_painterPath.clear(); |
178 | |
179 | radian = qDegreesToRadians(degrees: slice->startAngle() + (slice->angleSpan() * .5)); |
180 | startBigX = radius * qSin(v: radian); |
181 | startBigY = radius * qCos(v: radian); |
182 | |
183 | pointX = radius * (1.0 + d->m_labelArmLengthFactor) * qSin(v: radian); |
184 | pointY = radius * (1.0 + d->m_labelArmLengthFactor) * qCos(v: radian); |
185 | |
186 | m_painterPath.moveTo(x: xShift + startBigX, y: yShift - startBigY); |
187 | m_painterPath.lineTo(x: xShift + pointX, y: yShift - pointY); |
188 | |
189 | d->m_centerLine = {xShift + pointX, yShift - pointY}; |
190 | |
191 | d->m_labelArm = {xShift + pointX, yShift - pointY}; |
192 | |
193 | auto labelWidth = radian > M_PI ? -d->m_labelItem->width() : d->m_labelItem->width(); |
194 | m_painterPath.lineTo(x: d->m_labelArm.x() + labelWidth, y: d->m_labelArm.y()); |
195 | |
196 | d->setLabelPosition(d->m_labelPosition); |
197 | d->m_labelPath->setPath(m_painterPath); |
198 | |
199 | sliceIndex++; |
200 | legendDataList.push_back(t: {.color: color, .borderColor: borderColor, .label: d->m_labelText}); |
201 | } |
202 | series->d_func()->setLegendData(legendDataList); |
203 | } |
204 | |
205 | void PieRenderer::afterPolish(QList<QAbstractSeries *> &cleanupSeries) |
206 | { |
207 | for (auto series : cleanupSeries) { |
208 | auto pieSeries = qobject_cast<QPieSeries *>(object: series); |
209 | if (pieSeries) { |
210 | for (QPieSlice *slice : pieSeries->slices()) { |
211 | QPieSlicePrivate *d = slice->d_func(); |
212 | auto labelElements = d->m_labelPath->pathElements(); |
213 | auto shapeElements = d->m_shapePath->pathElements(); |
214 | |
215 | labelElements.clear(&labelElements); |
216 | shapeElements.clear(&shapeElements); |
217 | |
218 | slice->deleteLater(); |
219 | d->m_labelItem->deleteLater(); |
220 | |
221 | m_activeSlices.remove(key: slice); |
222 | } |
223 | } |
224 | } |
225 | } |
226 | |
227 | void PieRenderer::updateSeries(QPieSeries *series) |
228 | { |
229 | auto needPolish = false; |
230 | |
231 | for (auto &sliceData : m_activeSlices) { |
232 | if (!sliceData.initialized) { |
233 | sliceData.initialized = true; |
234 | needPolish = true; |
235 | } |
236 | } |
237 | |
238 | if (needPolish) |
239 | handlePolish(series); |
240 | } |
241 | |
242 | void PieRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries) |
243 | { |
244 | Q_UNUSED(cleanupSeries); |
245 | } |
246 | |
247 | void PieRenderer::markedDeleted(QList<QPieSlice *> deleted) |
248 | { |
249 | auto emptyPath = QPainterPath{}; |
250 | |
251 | for (auto slice : deleted) { |
252 | auto d = slice->d_func(); |
253 | d->m_shapePath->setPath(emptyPath); |
254 | d->m_labelPath->setPath(emptyPath); |
255 | d->m_labelItem->deleteLater(); |
256 | m_activeSlices.remove(key: slice); |
257 | } |
258 | } |
259 | |