| 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 |     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 |  | 
| 217 | void 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 |  | 
| 239 | void 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 |  | 
| 254 | void PieRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries) | 
| 255 | { | 
| 256 |     Q_UNUSED(cleanupSeries); | 
| 257 | } | 
| 258 |  | 
| 259 | void 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 |  |