| 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 <private/qpieseries_p.h> | 
| 6 | #include <QtGraphs/qpieslice.h> | 
| 7 | #include <private/qpieslice_p.h> | 
| 8 |  | 
| 9 | QT_BEGIN_NAMESPACE | 
| 10 |  | 
| 11 | /*! | 
| 12 |     \class QPieSeries | 
| 13 |     \inmodule QtGraphs | 
| 14 |     \ingroup graphs_2D | 
| 15 |     \brief The QPieSeries class presents data in pie graphs. | 
| 16 |  | 
| 17 |     A pie series consists of slices that are defined as QPieSlice objects. | 
| 18 |     The slices can have any values as the QPieSeries object calculates | 
| 19 |     the percentage of a slice compared with the sum of all slices in the series | 
| 20 |     to determine the actual size of the slice in the graph. | 
| 21 |  | 
| 22 |     Pie size and position on the graph are controlled by using relative values | 
| 23 |     that range from 0.0 to 1.0. | 
| 24 |     These relate to the actual graph rectangle. | 
| 25 |  | 
| 26 |     By default, the pie is defined as a full pie. A partial pie can be created | 
| 27 |     by setting a starting angle and angle span for the series. | 
| 28 |     A full pie is 360 degrees, where 0 is at 12 a'clock. | 
| 29 |  | 
| 30 |     \sa QPieSlice | 
| 31 | */ | 
| 32 | /*! | 
| 33 |     \qmltype PieSeries | 
| 34 |     \nativetype QPieSeries | 
| 35 |     \inqmlmodule QtGraphs | 
| 36 |     \ingroup graphs_qml_2D | 
| 37 |     \inherits AbstractSeries | 
| 38 |  | 
| 39 |     \brief Presents data in pie graphs. | 
| 40 |  | 
| 41 |     A pie series consists of slices that are defined using the PieSlice type. | 
| 42 |     The slices can have any values as the PieSeries type calculates | 
| 43 |     the percentage of a slice compared with the sum of all slices in the series | 
| 44 |     to determine the actual size of the slice in the graph. | 
| 45 |  | 
| 46 |     Pie size and position on the graph are controlled by using relative values | 
| 47 |     that range from 0.0 to 1.0. | 
| 48 |     These relate to the actual graph rectangle. | 
| 49 |  | 
| 50 |     By default, the pie is defined as a full pie. A partial pie can be created | 
| 51 |     by setting a starting angle and angle span for the series. | 
| 52 |     A full pie is 360 degrees, where 0 is at 12 o'clock. | 
| 53 |  | 
| 54 |     The following QML example shows how to create a simple pie graph. | 
| 55 |     \qml | 
| 56 |     import QtQuick | 
| 57 |     import QtGraphs | 
| 58 |  | 
| 59 |     Item { | 
| 60 |         id: mainView | 
| 61 |         width: 1280 | 
| 62 |         height: 720 | 
| 63 |  | 
| 64 |         GraphsView { | 
| 65 |             anchors.fill: parent | 
| 66 |             theme: GraphsTheme { | 
| 67 |                  colorScheme: GraphsTheme.ColorScheme.Dark | 
| 68 |                  theme: GraphsTheme.Theme.QtGreen | 
| 69 |             } | 
| 70 |             PieSeries { | 
| 71 |                 id: pieSeries | 
| 72 |                 PieSlice { | 
| 73 |                     value: 1 | 
| 74 |                 } | 
| 75 |                 PieSlice { | 
| 76 |                     value: 2 | 
| 77 |                 } | 
| 78 |             } | 
| 79 |         } | 
| 80 |     } | 
| 81 |     \endqml | 
| 82 |  | 
| 83 |     \sa PieSlice, GraphsView | 
| 84 | */ | 
| 85 |  | 
| 86 | /*! | 
| 87 |     \property QPieSeries::horizontalPosition | 
| 88 |     \brief The horizontal position of the pie. | 
| 89 |  | 
| 90 |     The value is relative to the graph rectangle, so that: | 
| 91 |  | 
| 92 |     \list | 
| 93 |     \li 0.0 is the absolute left. | 
| 94 |     \li 1.0 is the absolute right. | 
| 95 |     \endlist | 
| 96 |     The default value is 0.5 (center). | 
| 97 |     \sa verticalPosition | 
| 98 | */ | 
| 99 |  | 
| 100 | /*! | 
| 101 |     \qmlproperty real PieSeries::horizontalPosition | 
| 102 |  | 
| 103 |     The horizontal position of the pie. | 
| 104 |  | 
| 105 |     The value is relative to the graph rectangle, so that: | 
| 106 |  | 
| 107 |     \list | 
| 108 |     \li 0.0 is the absolute left. | 
| 109 |     \li 1.0 is the absolute right. | 
| 110 |     \endlist | 
| 111 |     The default value is 0.5 (center). | 
| 112 |     \sa verticalPosition | 
| 113 | */ | 
| 114 |  | 
| 115 | /*! | 
| 116 |     \qmlsignal PieSeries::horizontalPositionChanged() | 
| 117 |     This signal is emitted when the horizontal position changes. | 
| 118 |     \sa horizontalPosition | 
| 119 | */ | 
| 120 |  | 
| 121 | /*! | 
| 122 |     \property QPieSeries::verticalPosition | 
| 123 |     \brief The vertical position of the pie. | 
| 124 |  | 
| 125 |     The value is relative to the graph rectangle, so that: | 
| 126 |  | 
| 127 |     \list | 
| 128 |     \li 0.0 is the absolute top. | 
| 129 |     \li 1.0 is the absolute bottom. | 
| 130 |     \endlist | 
| 131 |     The default value is 0.5 (center). | 
| 132 |     \sa horizontalPosition | 
| 133 | */ | 
| 134 |  | 
| 135 | /*! | 
| 136 |     \qmlproperty real PieSeries::verticalPosition | 
| 137 |  | 
| 138 |     The vertical position of the pie. | 
| 139 |  | 
| 140 |     The value is relative to the graph rectangle, so that: | 
| 141 |  | 
| 142 |     \list | 
| 143 |     \li 0.0 is the absolute top. | 
| 144 |     \li 1.0 is the absolute bottom. | 
| 145 |     \endlist | 
| 146 |     The default value is 0.5 (center). | 
| 147 |     \sa horizontalPosition | 
| 148 | */ | 
| 149 | /*! | 
| 150 |     \qmlsignal PieSeries::verticalPositionChanged() | 
| 151 |     This signal is emitted when the vertical position changes. | 
| 152 |     \sa verticalPosition | 
| 153 | */ | 
| 154 |  | 
| 155 | /*! | 
| 156 |     \property QPieSeries::pieSize | 
| 157 |     \brief The pie size. | 
| 158 |  | 
| 159 |     The value is relative to the graph rectangle, so that: | 
| 160 |  | 
| 161 |     \list | 
| 162 |     \li 0.0 is the minimum pieSize (pie not drawn). | 
| 163 |     \li 1.0 is the maximum pieSize that can fit the graph. | 
| 164 |     \endlist | 
| 165 |  | 
| 166 |     When setting this property, the holeSize property is adjusted if necessary, | 
| 167 |     to ensure that the hole size is not greater than the pie size. | 
| 168 |  | 
| 169 |     The default value is 0.7. | 
| 170 | */ | 
| 171 |  | 
| 172 | /*! | 
| 173 |     \qmlproperty real PieSeries::pieSize | 
| 174 |  | 
| 175 |     The pie size. | 
| 176 |  | 
| 177 |     The value is relative to the graph rectangle, so that: | 
| 178 |  | 
| 179 |     \list | 
| 180 |     \li 0.0 is the minimum pieSize (pie not drawn). | 
| 181 |     \li 1.0 is the maximum pieSize that can fit the graph. | 
| 182 |     \endlist | 
| 183 |  | 
| 184 |     When setting this property, the holeSize property is adjusted if necessary, | 
| 185 |     to ensure that the hole size is not greater than the pie size. | 
| 186 |  | 
| 187 |     The default value is 0.7. | 
| 188 | */ | 
| 189 | /*! | 
| 190 |     \qmlsignal PieSeries::pieSizeChanged() | 
| 191 |     This signal is emitted when the pie size changes. | 
| 192 |     \sa pieSize | 
| 193 | */ | 
| 194 |  | 
| 195 | /*! | 
| 196 |     \property QPieSeries::holeSize | 
| 197 |     \brief The donut hole size. | 
| 198 |  | 
| 199 |     When setting the \l pieSize property, this property is adjusted if necessary, | 
| 200 |     to ensure that the hole size is not greater than the pie size. | 
| 201 |  | 
| 202 |     The default value is 0.0. | 
| 203 | */ | 
| 204 |  | 
| 205 | /*! | 
| 206 |     \qmlproperty real PieSeries::holeSize | 
| 207 |  | 
| 208 |     The donut hole size. | 
| 209 |  | 
| 210 |     When setting the \l pieSize property, this property is adjusted if necessary, | 
| 211 |     to ensure that the hole size is not greater than the pie size. | 
| 212 |  | 
| 213 |     The default value is 0.0. | 
| 214 |  | 
| 215 | */ | 
| 216 | /*! | 
| 217 |     \qmlsignal PieSeries::holeSizeChanged() | 
| 218 |     This signal is emitted when the donut hole size changes. | 
| 219 |     \sa holeSize | 
| 220 | */ | 
| 221 |  | 
| 222 | /*! | 
| 223 |     \property QPieSeries::startAngle | 
| 224 |     \brief The starting angle of the pie. | 
| 225 |  | 
| 226 |     A full pie is 360 degrees, where 0 degrees is at 12 o'clock. | 
| 227 |  | 
| 228 |     The default value is 0. | 
| 229 | */ | 
| 230 |  | 
| 231 | /*! | 
| 232 |     \qmlproperty real PieSeries::startAngle | 
| 233 |  | 
| 234 |     The starting angle of the pie. | 
| 235 |  | 
| 236 |     A full pie is 360 degrees, where 0 degrees is at 12 o'clock. | 
| 237 |  | 
| 238 |     The default value is 0. | 
| 239 | */ | 
| 240 | /*! | 
| 241 |     \qmlsignal PieSeries::startAngleChanged() | 
| 242 |     This signal is emitted when the pie start angle changes. | 
| 243 |     \sa startAngle | 
| 244 | */ | 
| 245 |  | 
| 246 | /*! | 
| 247 |     \property QPieSeries::endAngle | 
| 248 |     \brief The ending angle of the pie. | 
| 249 |  | 
| 250 |     A full pie is 360 degrees, where 0 degrees is at 12 o'clock. | 
| 251 |  | 
| 252 |     The default value is 360. | 
| 253 | */ | 
| 254 |  | 
| 255 | /*! | 
| 256 |     \qmlproperty real PieSeries::endAngle | 
| 257 |  | 
| 258 |     The ending angle of the pie. | 
| 259 |  | 
| 260 |     A full pie is 360 degrees, where 0 degrees is at 12 o'clock. | 
| 261 |  | 
| 262 |     The default value is 360. | 
| 263 | */ | 
| 264 | /*! | 
| 265 |     \qmlsignal PieSeries::endAngleChanged() | 
| 266 |     This signal is emitted when the pie end angle changes. | 
| 267 |     \sa endAngle | 
| 268 | */ | 
| 269 |  | 
| 270 | /*! | 
| 271 |     \property QPieSeries::count | 
| 272 |  | 
| 273 |     \brief The number of slices in the series. | 
| 274 | */ | 
| 275 |  | 
| 276 | /*! | 
| 277 |     \qmlproperty int PieSeries::count | 
| 278 |  | 
| 279 |     The number of slices in the series. | 
| 280 | */ | 
| 281 |  | 
| 282 | /*! | 
| 283 |     \qmlsignal PieSeries::countChanged() | 
| 284 |     This signal is emitted when the slice count changes. | 
| 285 |     \sa count | 
| 286 | */ | 
| 287 |  | 
| 288 | /*! | 
| 289 |     \property QPieSeries::sum | 
| 290 |  | 
| 291 |     \brief The sum of all slices. | 
| 292 |  | 
| 293 |     The series keeps track of the sum of all the slices it holds. | 
| 294 | */ | 
| 295 |  | 
| 296 | /*! | 
| 297 |     \qmlproperty real PieSeries::sum | 
| 298 |  | 
| 299 |     The sum of all slices. | 
| 300 |  | 
| 301 |     The series keeps track of the sum of all the slices it holds. | 
| 302 | */ | 
| 303 |  | 
| 304 | /*! | 
| 305 |     \qmlsignal PieSeries::sumChanged() | 
| 306 |     This signal is emitted when the sum of all slices changes. | 
| 307 |     \sa sum | 
| 308 | */ | 
| 309 |  | 
| 310 | /*! | 
| 311 |     \fn void QPieSeries::added(const QList<QPieSlice *> &slices) | 
| 312 |  | 
| 313 |     This signal is emitted when the slices specified by \a slices are added to the series. | 
| 314 |  | 
| 315 |     \sa append() | 
| 316 | */ | 
| 317 | /*! | 
| 318 |     \qmlsignal PieSeries::added(list<PieSlice> slices) | 
| 319 |     This signal is emitted when the slices specified by \a slices are added to the series. | 
| 320 | */ | 
| 321 |  | 
| 322 | /*! | 
| 323 |     \fn void QPieSeries::removed(const QList<QPieSlice *> &slices) | 
| 324 |     This signal is emitted when the slices specified by \a slices are removed from the series. | 
| 325 |     \sa remove() | 
| 326 | */ | 
| 327 | /*! | 
| 328 |     \qmlsignal PieSeries::removed(list<PieSlice> slices) | 
| 329 |     This signal is emitted when the slices specified by \a slices are removed from the series. | 
| 330 | */ | 
| 331 |  | 
| 332 | /*! | 
| 333 |     \qmlmethod PieSlice PieSeries::at(int index) | 
| 334 |     Returns the slice at the position specified by \a index. Returns null if the | 
| 335 |     index is not valid. | 
| 336 | */ | 
| 337 |  | 
| 338 | /*! | 
| 339 |     \qmlmethod PieSlice PieSeries::find(string label) | 
| 340 |     Returns the first slice that has the label \a label. Returns null if the label | 
| 341 |     is not found. | 
| 342 | */ | 
| 343 |  | 
| 344 | /*! | 
| 345 |     \qmlmethod PieSlice PieSeries::append(string label, real value) | 
| 346 |     Adds a new slice with the label \a label and the value \a value to the pie. | 
| 347 | */ | 
| 348 |  | 
| 349 | /*! | 
| 350 |     \qmlmethod bool PieSeries::remove(PieSlice slice) | 
| 351 |     Removes the slice specified by \a slice from the pie. Returns \c true if the | 
| 352 |     removal was successful, \c false otherwise. | 
| 353 | */ | 
| 354 |  | 
| 355 | /*! | 
| 356 |     \qmlmethod bool PieSeries::replace(int index, PieSlice slice) | 
| 357 |     Replaces the slice specified by \a slice from the pie at \a index. Returns \c true if the | 
| 358 |     replace was successful, \c false otherwise. | 
| 359 | */ | 
| 360 |  | 
| 361 | /*! | 
| 362 |     \qmlmethod PieSeries::clear() | 
| 363 |     Removes all slices from the pie. | 
| 364 | */ | 
| 365 |  | 
| 366 | /*! | 
| 367 |     \qmlmethod void PieSeries::removeMultiple(int index, int count) | 
| 368 |     Removes a range of slices as specified by the \a index and \a count. The call | 
| 369 |     traverses over all slices even if removal of one fails. | 
| 370 | */ | 
| 371 |  | 
| 372 | /*! | 
| 373 |     \qmlmethod bool PieSeries::remove(int index) | 
| 374 |     Removes the slice specified by \a index from the pie. Returns \c true if the | 
| 375 |     removal was successful, \c false otherwise. | 
| 376 | */ | 
| 377 |  | 
| 378 | /*! | 
| 379 |     \qmlmethod bool PieSeries::replace(PieSlice oldSlice, PieSlice newSlice) | 
| 380 |     Replaces the slice specified by \a oldSlice with newSlice. Returns \c true if the | 
| 381 |     removal was successful, \c false otherwise. \a oldSlice is destroyed if this  | 
| 382 |     is successful. | 
| 383 | */ | 
| 384 |  | 
| 385 | /*! | 
| 386 |     \qmlmethod bool PieSeries::replace(list<PieSlice> slices) | 
| 387 |     Completely replaces all current slices with \a slices. The size does not need | 
| 388 |     to match. Returns false if any of the PieSlice in \a slices is invalid. | 
| 389 | */ | 
| 390 |  | 
| 391 | /*! | 
| 392 |     \qmlmethod bool PieSeries::take(PieSlice slice) | 
| 393 |     Takes a single slice, specified by \a slice, from the series. Does not delete | 
| 394 |     the slice object. Returns \c true if successful. | 
| 395 | */ | 
| 396 |  | 
| 397 | /*! | 
| 398 |     Constructs a series object that is a child of \a parent. | 
| 399 | */ | 
| 400 | QPieSeries::QPieSeries(QObject *parent) | 
| 401 |     : QAbstractSeries(*(new QPieSeriesPrivate()), parent) | 
| 402 | {} | 
| 403 |  | 
| 404 | QPieSeries::~QPieSeries() {} | 
| 405 |  | 
| 406 | /*! | 
| 407 |     \reimp | 
| 408 |  | 
| 409 |     Returns the type of the series. | 
| 410 | */ | 
| 411 | QAbstractSeries::SeriesType QPieSeries::type() const | 
| 412 | { | 
| 413 |     return QAbstractSeries::SeriesType::Pie; | 
| 414 | } | 
| 415 |  | 
| 416 | /*! | 
| 417 |     Returns the PieSlice at the position \a index. Returns null if no PieSlice was found. | 
| 418 | */ | 
| 419 | QPieSlice *QPieSeries::at(qsizetype index) | 
| 420 | { | 
| 421 |     QList<QPieSlice *> sliceList = slices(); | 
| 422 |     if (index >= 0 && index < sliceList.size()) | 
| 423 |         return sliceList[index]; | 
| 424 |  | 
| 425 |     return 0; | 
| 426 | } | 
| 427 |  | 
| 428 | /*! | 
| 429 |     Searches for a PieSlice which contains the label \a label. Returns the PieSlice if found, null otherwise. | 
| 430 | */ | 
| 431 | QPieSlice *QPieSeries::find(const QString &label) | 
| 432 | { | 
| 433 |     for (QPieSlice *slice : slices()) { | 
| 434 |         if (slice->label() == label) | 
| 435 |             return slice; | 
| 436 |     } | 
| 437 |     return 0; | 
| 438 | } | 
| 439 |  | 
| 440 | /*! | 
| 441 |     Replaces the PieSlice at position \a index with the one specified by \a slice. | 
| 442 |     The original PieSlice will be permanently deleted. Returns \c false if replacing | 
| 443 |     any of the PieSlices fails. | 
| 444 | */ | 
| 445 | bool QPieSeries::replace(qsizetype index, QPieSlice *slice) | 
| 446 | { | 
| 447 |     Q_D(QPieSeries); | 
| 448 |  | 
| 449 |     if (index < 0) | 
| 450 |         index = 0; | 
| 451 |     if (!slice || d->m_slices.contains(t: slice)) | 
| 452 |         return false; | 
| 453 |     if (slice->series()) // already added to some series | 
| 454 |         return false; | 
| 455 |     if (qIsNaN(d: slice->value()) || qIsInf(d: slice->value())) | 
| 456 |         return false; | 
| 457 |     if (d->m_slices.size() <= index) | 
| 458 |         return false; | 
| 459 |  | 
| 460 |     emit removed(slices: QList<QPieSlice *>() << d->m_slices[index]); | 
| 461 |     delete d->m_slices[index]; | 
| 462 |  | 
| 463 |     slice->setParent(this); | 
| 464 |     slice->d_func()->m_series = this; | 
| 465 |  | 
| 466 |     d->m_slices[index] = slice; | 
| 467 |  | 
| 468 |     d->updateData(); | 
| 469 |  | 
| 470 |     QObject::connect(sender: slice, SIGNAL(sliceChanged()), receiver: this, SLOT(handleSliceChange())); | 
| 471 |     emit replaced(slices: QList<QPieSlice *>() << slice); | 
| 472 |  | 
| 473 |     return true; | 
| 474 | } | 
| 475 |  | 
| 476 | /*! | 
| 477 |     Removes multiple PieSlices from the series starting from \a index to a number of \a count. | 
| 478 |     The PieSlices will be permanently deleted. | 
| 479 | */ | 
| 480 | void QPieSeries::removeMultiple(qsizetype index, int count) | 
| 481 | { | 
| 482 |     Q_D(QPieSeries); | 
| 483 |  | 
| 484 |     if (index + count >= d->m_slices.size()) | 
| 485 |         return; | 
| 486 |     if (index < 0 || count < 0) | 
| 487 |         return; | 
| 488 |  | 
| 489 |     QList<QPieSlice *> removedList; | 
| 490 |  | 
| 491 |     for (qsizetype i = index; i < index + count; ++i) { | 
| 492 |         auto slice = d->m_slices[index]; | 
| 493 |         d->m_slices.removeOne(t: slice); | 
| 494 |         d->updateData(); | 
| 495 |  | 
| 496 |         removedList << slice; | 
| 497 |     } | 
| 498 |  | 
| 499 |     emit removed(slices: removedList); | 
| 500 |  | 
| 501 |     for (auto slice : removedList) { | 
| 502 |         delete slice; | 
| 503 |     } | 
| 504 |  | 
| 505 |     emit countChanged(); | 
| 506 | } | 
| 507 |  | 
| 508 | /*! | 
| 509 |     Removes the PieSlice at the location \a index. The PieSlice will be permanently deleted. | 
| 510 |     Returns \c true if removing is successful. | 
| 511 | */ | 
| 512 | bool QPieSeries::remove(qsizetype index) | 
| 513 | { | 
| 514 |     Q_D(QPieSeries); | 
| 515 |  | 
| 516 |     if (index >= d->m_slices.size()) | 
| 517 |         return false; | 
| 518 |     if (index < 0) | 
| 519 |         return false; | 
| 520 |  | 
| 521 |     return remove(slice: d->m_slices[index]); | 
| 522 | } | 
| 523 |  | 
| 524 | /*! | 
| 525 |     Replaces the PieSlice \a oldSlice with \a newSlice if found in the series.\a oldSlice will | 
| 526 |     be permanently deleted. Returns \c true if replacing is successful. | 
| 527 | */ | 
| 528 | bool QPieSeries::replace(QPieSlice *oldSlice, QPieSlice *newSlice) | 
| 529 | { | 
| 530 |     Q_D(QPieSeries); | 
| 531 |  | 
| 532 |     if (!oldSlice || !newSlice) | 
| 533 |         return false; | 
| 534 |     if (oldSlice == newSlice) | 
| 535 |         return false; | 
| 536 |     if (d->m_slices.contains(t: newSlice)) | 
| 537 |         return false; | 
| 538 |     if (newSlice->series()) | 
| 539 |         return false; | 
| 540 |     if (qIsNaN(d: newSlice->value()) || qIsInf(d: newSlice->value())) | 
| 541 |         return false; | 
| 542 |  | 
| 543 |     for (int i = 0; i < d->m_slices.size(); ++i) { | 
| 544 |         if (d->m_slices[i] == oldSlice) { | 
| 545 |             emit removed(slices: QList<QPieSlice *>() << d->m_slices[i]); | 
| 546 |             delete d->m_slices[i]; | 
| 547 |  | 
| 548 |             newSlice->setParent(this); | 
| 549 |             newSlice->d_func()->m_series = this; | 
| 550 |  | 
| 551 |             d->m_slices[i] = newSlice; | 
| 552 |  | 
| 553 |             d->updateData(); | 
| 554 |  | 
| 555 |             QObject::connect(sender: newSlice, SIGNAL(sliceChanged()), receiver: this, SLOT(handleSliceChange())); | 
| 556 |             emit replaced(slices: QList<QPieSlice *>() << newSlice); | 
| 557 |  | 
| 558 |             return true; | 
| 559 |         } | 
| 560 |     } | 
| 561 |  | 
| 562 |     return false; | 
| 563 | } | 
| 564 |  | 
| 565 | /*! | 
| 566 |     Replaces the entire list of PieSlices in the series with the list specified by \a slices. | 
| 567 |     All the original PieSlices will be permanently deleted. Returns \c true if all PieSlices are | 
| 568 |     replaced successfully. | 
| 569 | */ | 
| 570 | bool QPieSeries::replace(const QList<QPieSlice *> &slices) | 
| 571 | { | 
| 572 |     Q_D(QPieSeries); | 
| 573 |  | 
| 574 |     for (const auto slice : slices) { | 
| 575 |         if (!slice || d->m_slices.contains(t: slice)) | 
| 576 |             return false; | 
| 577 |         if (slice->series()) | 
| 578 |             return false; | 
| 579 |         if (qIsNaN(d: slice->value()) || qIsInf(d: slice->value())) | 
| 580 |             return false; | 
| 581 |     } | 
| 582 |  | 
| 583 |     emit removed(slices: d->m_slices); | 
| 584 |     for (auto &slice : d->m_slices) { | 
| 585 |         delete slice; | 
| 586 |         slice = nullptr; | 
| 587 |     } | 
| 588 |  | 
| 589 |     for (auto &slice : slices) { | 
| 590 |         slice->setParent(this); | 
| 591 |         slice->d_func()->m_series = this; | 
| 592 |         QObject::connect(sender: slice, SIGNAL(sliceChanged()), receiver: this, SLOT(handleSliceChange())); | 
| 593 |     } | 
| 594 |  | 
| 595 |     d->m_slices = slices; | 
| 596 |     emit replaced(slices); | 
| 597 |  | 
| 598 |     return true; | 
| 599 | } | 
| 600 |  | 
| 601 | /*! | 
| 602 |     Appends the slice specified by \a slice to the series. | 
| 603 |     Slice ownership is passed to the series. | 
| 604 |  | 
| 605 |     Returns \c true if appending succeeds. | 
| 606 | */ | 
| 607 | bool QPieSeries::append(QPieSlice *slice) | 
| 608 | { | 
| 609 |     return append(slices: QList<QPieSlice *>() << slice); | 
| 610 | } | 
| 611 |  | 
| 612 | /*! | 
| 613 |     Appends the array of slices specified by \a slices to the series. | 
| 614 |     Slice ownership is passed to the series. | 
| 615 |  | 
| 616 |     Returns \c true if appending succeeds. | 
| 617 | */ | 
| 618 | bool QPieSeries::append(const QList<QPieSlice *> &slices) | 
| 619 | { | 
| 620 |     Q_D(QPieSeries); | 
| 621 |  | 
| 622 |     if (slices.size() == 0) | 
| 623 |         return false; | 
| 624 |  | 
| 625 |     for (auto *s : slices) { | 
| 626 |         if (!s || d->m_slices.contains(t: s)) | 
| 627 |             return false; | 
| 628 |         if (s->series()) // already added to some series | 
| 629 |             return false; | 
| 630 |         if (qIsNaN(d: s->value()) || qIsInf(d: s->value())) | 
| 631 |             return false; | 
| 632 |     } | 
| 633 |  | 
| 634 |     for (auto *s : slices) { | 
| 635 |         s->setParent(this); | 
| 636 |         s->d_func()->m_series = this; | 
| 637 |         d->m_slices << s; | 
| 638 |     } | 
| 639 |  | 
| 640 |     d->updateData(); | 
| 641 |  | 
| 642 |     for (auto *s : slices) | 
| 643 |         QObject::connect(sender: s, SIGNAL(sliceChanged()), receiver: this, SLOT(handleSliceChange())); | 
| 644 |  | 
| 645 |     emit added(slices); | 
| 646 |     emit countChanged(); | 
| 647 |  | 
| 648 |     return true; | 
| 649 | } | 
| 650 |  | 
| 651 | /*! | 
| 652 |     Appends the slice specified by \a slice to the series and returns a reference to the series. | 
| 653 |     Slice ownership is passed to the series. | 
| 654 | */ | 
| 655 | QPieSeries &QPieSeries::operator << (QPieSlice *slice) | 
| 656 | { | 
| 657 |     append(slice); | 
| 658 |     return *this; | 
| 659 | } | 
| 660 |  | 
| 661 | /*! | 
| 662 |     Appends a single slice with the specified \a value and \a label to the series. | 
| 663 |     Slice ownership is passed to the series. | 
| 664 |     Returns null if \a value is \c NaN, \c Inf, or \c -Inf and adds nothing to the | 
| 665 |     series. | 
| 666 | */ | 
| 667 | QPieSlice *QPieSeries::append(const QString &label, qreal value) | 
| 668 | { | 
| 669 |     if (!(qIsNaN(d: value) || qIsInf(d: value))) { | 
| 670 |         QPieSlice *slice = new QPieSlice(label, value); | 
| 671 |         append(slice); | 
| 672 |         return slice; | 
| 673 |     } else { | 
| 674 |         return nullptr; | 
| 675 |     } | 
| 676 | } | 
| 677 |  | 
| 678 | /*! | 
| 679 |     Inserts the slice specified by \a slice to the series before the slice at | 
| 680 |     the position specified by \a index. | 
| 681 |     Slice ownership is passed to the series. | 
| 682 |  | 
| 683 |     Returns \c true if inserting succeeds. | 
| 684 | */ | 
| 685 | bool QPieSeries::insert(qsizetype index, QPieSlice *slice) | 
| 686 | { | 
| 687 |     Q_D(QPieSeries); | 
| 688 |  | 
| 689 |     if (index < 0 || index > d->m_slices.size()) | 
| 690 |         return false; | 
| 691 |  | 
| 692 |     if (!slice || d->m_slices.contains(t: slice)) | 
| 693 |         return false; | 
| 694 |  | 
| 695 |     if (slice->series()) // already added to some series | 
| 696 |         return false; | 
| 697 |  | 
| 698 |     if (qIsNaN(d: slice->value()) || qIsInf(d: slice->value())) | 
| 699 |         return false; | 
| 700 |  | 
| 701 |     slice->setParent(this); | 
| 702 |     slice->d_func()->m_series = this; | 
| 703 |     d->m_slices.insert(i: index, t: slice); | 
| 704 |  | 
| 705 |     d->updateData(); | 
| 706 |  | 
| 707 |     connect(sender: slice, SIGNAL(sliceChanged()), receiver: this, SLOT(handleSliceChange())); | 
| 708 |  | 
| 709 |     emit added(slices: QList<QPieSlice *>() << slice); | 
| 710 |     emit countChanged(); | 
| 711 |  | 
| 712 |     return true; | 
| 713 | } | 
| 714 |  | 
| 715 | /*! | 
| 716 |     Removes a single slice, specified by \a slice, from the series and deletes it | 
| 717 |     permanently. | 
| 718 |  | 
| 719 |     The pointer cannot be referenced after this call. | 
| 720 |  | 
| 721 |     Returns \c true if the removal succeeds. | 
| 722 | */ | 
| 723 | bool QPieSeries::remove(QPieSlice *slice) | 
| 724 | { | 
| 725 |     Q_D(QPieSeries); | 
| 726 |  | 
| 727 |     if (!d->m_slices.removeOne(t: slice)) | 
| 728 |         return false; | 
| 729 |  | 
| 730 |     d->updateData(); | 
| 731 |  | 
| 732 |     emit removed(slices: QList<QPieSlice *>() << slice); | 
| 733 |     emit countChanged(); | 
| 734 |  | 
| 735 |     delete slice; | 
| 736 |     slice = 0; | 
| 737 |  | 
| 738 |     return true; | 
| 739 | } | 
| 740 |  | 
| 741 | /*! | 
| 742 |     Takes a single slice, specified by \a slice, from the series. Does not delete | 
| 743 |     the slice object. | 
| 744 |  | 
| 745 |     \note The series remains the slice's parent object. You must set the | 
| 746 |     parent object to take full ownership. | 
| 747 |  | 
| 748 |     Returns \c true if the take operation was successful. | 
| 749 | */ | 
| 750 | bool QPieSeries::take(QPieSlice *slice) | 
| 751 | { | 
| 752 |     Q_D(QPieSeries); | 
| 753 |  | 
| 754 |     if (!d->m_slices.removeOne(t: slice)) | 
| 755 |         return false; | 
| 756 |  | 
| 757 |     slice->d_func()->m_series = 0; | 
| 758 |     slice->disconnect(receiver: this); | 
| 759 |  | 
| 760 |     d->updateData(); | 
| 761 |  | 
| 762 |     emit removed(slices: QList<QPieSlice *>() << slice); | 
| 763 |     emit countChanged(); | 
| 764 |  | 
| 765 |     return true; | 
| 766 | } | 
| 767 |  | 
| 768 | /*! | 
| 769 |     Clears all slices from the series. | 
| 770 | */ | 
| 771 | void QPieSeries::clear() | 
| 772 | { | 
| 773 |     Q_D(QPieSeries); | 
| 774 |     if (d->m_slices.size() == 0) | 
| 775 |         return; | 
| 776 |  | 
| 777 |     QList<QPieSlice *> slices = d->m_slices; | 
| 778 |     for (QPieSlice *s : d->m_slices) | 
| 779 |         d->m_slices.removeOne(t: s); | 
| 780 |  | 
| 781 |     d->updateData(); | 
| 782 |  | 
| 783 |     emit removed(slices); | 
| 784 |     emit countChanged(); | 
| 785 |  | 
| 786 |     for (QPieSlice *s : slices) | 
| 787 |         delete s; | 
| 788 | } | 
| 789 |  | 
| 790 | /*! | 
| 791 |     Returns a list of slices that belong to this series. | 
| 792 | */ | 
| 793 | QList<QPieSlice *> QPieSeries::slices() const | 
| 794 | { | 
| 795 |     Q_D(const QPieSeries); | 
| 796 |     return d->m_slices; | 
| 797 | } | 
| 798 |  | 
| 799 | /*! | 
| 800 |     Returns the number of the slices in this series. | 
| 801 | */ | 
| 802 | qsizetype QPieSeries::count() const | 
| 803 | { | 
| 804 |     Q_D(const QPieSeries); | 
| 805 |     return d->m_slices.size(); | 
| 806 | } | 
| 807 |  | 
| 808 | /*! | 
| 809 |     Returns \c true if the series is empty. | 
| 810 | */ | 
| 811 | bool QPieSeries::isEmpty() const | 
| 812 | { | 
| 813 |     Q_D(const QPieSeries); | 
| 814 |     return d->m_slices.isEmpty(); | 
| 815 | } | 
| 816 |  | 
| 817 | /*! | 
| 818 |     Returns the sum of all slice values in this series. | 
| 819 |  | 
| 820 |     \sa QPieSlice::value(), QPieSlice::setValue(), QPieSlice::percentage() | 
| 821 | */ | 
| 822 | qreal QPieSeries::sum() const | 
| 823 | { | 
| 824 |     Q_D(const QPieSeries); | 
| 825 |     return d->m_sum; | 
| 826 | } | 
| 827 |  | 
| 828 | void QPieSeries::setHorizontalPosition(qreal relativePosition) | 
| 829 | { | 
| 830 |     Q_D(QPieSeries); | 
| 831 |  | 
| 832 |     if (relativePosition < 0.0) | 
| 833 |         relativePosition = 0.0; | 
| 834 |     if (relativePosition > 1.0) | 
| 835 |         relativePosition = 1.0; | 
| 836 |  | 
| 837 |     if (qFuzzyCompare(p1: d->m_pieRelativeHorPos, p2: relativePosition)) | 
| 838 |         return; | 
| 839 |  | 
| 840 |     d->m_pieRelativeHorPos = relativePosition; | 
| 841 |     emit horizontalPositionChanged(); | 
| 842 |     emit update(); | 
| 843 | } | 
| 844 |  | 
| 845 | qreal QPieSeries::horizontalPosition() const | 
| 846 | { | 
| 847 |     Q_D(const QPieSeries); | 
| 848 |     return d->m_pieRelativeHorPos; | 
| 849 | } | 
| 850 |  | 
| 851 | void QPieSeries::setVerticalPosition(qreal relativePosition) | 
| 852 | { | 
| 853 |     Q_D(QPieSeries); | 
| 854 |  | 
| 855 |     if (relativePosition < 0.0) | 
| 856 |         relativePosition = 0.0; | 
| 857 |     if (relativePosition > 1.0) | 
| 858 |         relativePosition = 1.0; | 
| 859 |  | 
| 860 |     if (qFuzzyCompare(p1: d->m_pieRelativeVerPos, p2: relativePosition)) | 
| 861 |         return; | 
| 862 |  | 
| 863 |     d->m_pieRelativeVerPos = relativePosition; | 
| 864 |     emit verticalPositionChanged(); | 
| 865 |     emit update(); | 
| 866 | } | 
| 867 |  | 
| 868 | qreal QPieSeries::verticalPosition() const | 
| 869 | { | 
| 870 |     Q_D(const QPieSeries); | 
| 871 |     return d->m_pieRelativeVerPos; | 
| 872 | } | 
| 873 |  | 
| 874 | void QPieSeries::setPieSize(qreal relativeSize) | 
| 875 | { | 
| 876 |     Q_D(QPieSeries); | 
| 877 |     relativeSize = qBound(min: (qreal)0.0, val: relativeSize, max: (qreal)1.0); | 
| 878 |     d->setSizes(innerSize: qMin(a: d->m_holeRelativeSize, b: relativeSize), outerSize: relativeSize); | 
| 879 | } | 
| 880 |  | 
| 881 | qreal QPieSeries::pieSize() const | 
| 882 | { | 
| 883 |     Q_D(const QPieSeries); | 
| 884 |     return d->m_pieRelativeSize; | 
| 885 | } | 
| 886 |  | 
| 887 | /*! | 
| 888 |     Sets the start angle of the pie. | 
| 889 |  | 
| 890 |     A full pie is 360 degrees, where 0 degrees is at 12 o'clock. | 
| 891 |  | 
| 892 |     \a angle must be smaller than the end angle. | 
| 893 |  | 
| 894 |     \sa startAngle(), endAngle(), setEndAngle() | 
| 895 | */ | 
| 896 | void QPieSeries::setStartAngle(qreal angle) | 
| 897 | { | 
| 898 |     Q_D(QPieSeries); | 
| 899 |     if (qFuzzyCompare(p1: d->m_pieStartAngle, p2: angle)) | 
| 900 |         return; | 
| 901 |     d->m_pieStartAngle = angle; | 
| 902 |     d->updateData(); | 
| 903 |     emit startAngleChanged(); | 
| 904 |     emit update(); | 
| 905 | } | 
| 906 |  | 
| 907 | /*! | 
| 908 |     Returns the start angle of the pie. | 
| 909 |  | 
| 910 |     A full pie is 360 degrees, where 0 degrees is at 12 o'clock. | 
| 911 |  | 
| 912 |     \sa setStartAngle(), endAngle(), setEndAngle() | 
| 913 | */ | 
| 914 | qreal QPieSeries::startAngle() const | 
| 915 | { | 
| 916 |     Q_D(const QPieSeries); | 
| 917 |     return d->m_pieStartAngle; | 
| 918 | } | 
| 919 |  | 
| 920 | /*! | 
| 921 |     Sets the end angle of the pie. | 
| 922 |  | 
| 923 |     A full pie is 360 degrees, where 0 degrees is at 12 o'clock. | 
| 924 |  | 
| 925 |     \a angle must be greater than the start angle. | 
| 926 |  | 
| 927 |     \sa endAngle(), startAngle(), setStartAngle() | 
| 928 | */ | 
| 929 | void QPieSeries::setEndAngle(qreal angle) | 
| 930 | { | 
| 931 |     Q_D(QPieSeries); | 
| 932 |     if (qFuzzyCompare(p1: d->m_pieEndAngle, p2: angle)) | 
| 933 |         return; | 
| 934 |     d->m_pieEndAngle = angle; | 
| 935 |     d->updateData(); | 
| 936 |     emit endAngleChanged(); | 
| 937 |     emit update(); | 
| 938 | } | 
| 939 |  | 
| 940 | /*! | 
| 941 |     Returns the end angle of the pie. | 
| 942 |  | 
| 943 |     A full pie is 360 degrees, where 0 degrees is at 12 o'clock. | 
| 944 |  | 
| 945 |     \sa setEndAngle(), startAngle(), setStartAngle() | 
| 946 | */ | 
| 947 | qreal QPieSeries::endAngle() const | 
| 948 | { | 
| 949 |     Q_D(const QPieSeries); | 
| 950 |     return d->m_pieEndAngle; | 
| 951 | } | 
| 952 |  | 
| 953 | void QPieSeries::componentComplete() | 
| 954 | { | 
| 955 |     for (QObject *child : children()) { | 
| 956 |         if (qobject_cast<QPieSlice *>(object: child)) | 
| 957 |             QPieSeries::append(slice: qobject_cast<QPieSlice *>(object: child)); | 
| 958 |     } | 
| 959 |     QAbstractSeries::componentComplete(); | 
| 960 | } | 
| 961 |  | 
| 962 | /*! | 
| 963 |     Sets the visibility of all slice labels to \a visible. | 
| 964 |  | 
| 965 |     \note This function affects only the current slices in the series. | 
| 966 |     If a new slice is added, the default label visibility is \c false. | 
| 967 |  | 
| 968 |     \sa QPieSlice::isLabelVisible(), QPieSlice::setLabelVisible() | 
| 969 | */ | 
| 970 | void QPieSeries::setLabelsVisible(bool visible) | 
| 971 | { | 
| 972 |     Q_D(QPieSeries); | 
| 973 |     for (QPieSlice *s : d->m_slices) | 
| 974 |         s->setLabelVisible(visible); | 
| 975 | } | 
| 976 |  | 
| 977 | /*! | 
| 978 |     Sets the position of all the slice labels to \a position. | 
| 979 |  | 
| 980 |     \note This function affects only the current slices in the series. | 
| 981 |     If a new slice is added, the default label position is QPieSlice::LabelOutside. | 
| 982 |  | 
| 983 |     \sa QPieSlice::labelPosition(), QPieSlice::setLabelPosition() | 
| 984 | */ | 
| 985 | void QPieSeries::setLabelsPosition(QPieSlice::LabelPosition position) | 
| 986 | { | 
| 987 |     Q_D(QPieSeries); | 
| 988 |     for (QPieSlice *s : d->m_slices) | 
| 989 |         s->setLabelPosition(position); | 
| 990 | } | 
| 991 |  | 
| 992 | void QPieSeries::handleSliceChange() | 
| 993 | { | 
| 994 |     QPieSlice *pSlice = qobject_cast<QPieSlice *>(object: sender()); | 
| 995 |     Q_D(QPieSeries); | 
| 996 |     Q_ASSERT(d->m_slices.contains(pSlice)); | 
| 997 |     d->updateData(); | 
| 998 | } | 
| 999 |  | 
| 1000 | QPieSeries::QPieSeries(QPieSeriesPrivate &dd, QObject *parent) | 
| 1001 |     : QAbstractSeries(dd, parent) | 
| 1002 | {} | 
| 1003 |  | 
| 1004 | void QPieSeries::setHoleSize(qreal holeSize) | 
| 1005 | { | 
| 1006 |     Q_D(QPieSeries); | 
| 1007 |     holeSize = qBound(min: (qreal)0.0, val: holeSize, max: (qreal)1.0); | 
| 1008 |     d->setSizes(innerSize: holeSize, outerSize: qMax(a: d->m_pieRelativeSize, b: holeSize)); | 
| 1009 | } | 
| 1010 |  | 
| 1011 | qreal QPieSeries::holeSize() const | 
| 1012 | { | 
| 1013 |     Q_D(const QPieSeries); | 
| 1014 |     return d->m_holeRelativeSize; | 
| 1015 | } | 
| 1016 |  | 
| 1017 | QPieSeriesPrivate::QPieSeriesPrivate() | 
| 1018 |     : m_pieRelativeHorPos(.5) | 
| 1019 |     , m_pieRelativeVerPos(.5) | 
| 1020 |     , m_pieRelativeSize(.7) | 
| 1021 |     , m_pieStartAngle(0) | 
| 1022 |     , m_pieEndAngle(360) | 
| 1023 |     , m_sum(0) | 
| 1024 |     , m_holeRelativeSize(.0) | 
| 1025 | {} | 
| 1026 |  | 
| 1027 | void QPieSeriesPrivate::updateData() | 
| 1028 | { | 
| 1029 |     Q_Q(QPieSeries); | 
| 1030 |  | 
| 1031 |     // calculate sum of all slices | 
| 1032 |     qreal sum = 0; | 
| 1033 |     for (QPieSlice *s : m_slices) | 
| 1034 |         sum += s->value(); | 
| 1035 |  | 
| 1036 |     if (!qFuzzyCompare(p1: m_sum, p2: sum)) { | 
| 1037 |         m_sum = sum; | 
| 1038 |         emit q->sumChanged(); | 
| 1039 |     } | 
| 1040 |  | 
| 1041 |     // nothing to show.. | 
| 1042 |     if (qFuzzyCompare(p1: m_sum, p2: 0)) | 
| 1043 |         return; | 
| 1044 |  | 
| 1045 |     // update slice attributes | 
| 1046 |     qreal sliceAngle = m_pieStartAngle; | 
| 1047 |     qreal pieSpan = m_pieEndAngle - m_pieStartAngle; | 
| 1048 |     for (QPieSlice *s : m_slices) { | 
| 1049 |         QPieSlicePrivate *d = s->d_func(); | 
| 1050 |         d->setPercentage(s->value() / m_sum); | 
| 1051 |         d->setStartAngle(sliceAngle); | 
| 1052 |         d->setAngleSpan(pieSpan * s->percentage()); | 
| 1053 |         sliceAngle += s->angleSpan(); | 
| 1054 |     } | 
| 1055 |  | 
| 1056 |     emit q->update(); | 
| 1057 | } | 
| 1058 |  | 
| 1059 | void QPieSeriesPrivate::updateLabels() | 
| 1060 | { | 
| 1061 |     Q_Q(QPieSeries); | 
| 1062 |  | 
| 1063 |     emit q->update(); | 
| 1064 | } | 
| 1065 |  | 
| 1066 | void QPieSeriesPrivate::setSizes(qreal innerSize, qreal outerSize) | 
| 1067 | { | 
| 1068 |     Q_Q(QPieSeries); | 
| 1069 |     if (!qFuzzyCompare(p1: m_holeRelativeSize, p2: innerSize)) { | 
| 1070 |         m_holeRelativeSize = innerSize; | 
| 1071 |         emit q->holeSizeChanged(); | 
| 1072 |     } | 
| 1073 |  | 
| 1074 |     if (!qFuzzyCompare(p1: m_pieRelativeSize, p2: outerSize)) { | 
| 1075 |         m_pieRelativeSize = outerSize; | 
| 1076 |         emit q->pieSizeChanged(); | 
| 1077 |     } | 
| 1078 | } | 
| 1079 |  | 
| 1080 | QT_END_NAMESPACE | 
| 1081 |  |