File indexing completed on 2024-04-21 15:02:56

0001 /*
0002  * This file is part of KQuickCharts
0003  * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
0004  *
0005  * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
0006  */
0007 
0008 #include "PieChart.h"
0009 
0010 #include <QAbstractItemModel>
0011 #include <QDebug>
0012 
0013 #include "RangeGroup.h"
0014 #include "datasource/ChartDataSource.h"
0015 #include "scenegraph/PieChartNode.h"
0016 
0017 PieChart::PieChart(QQuickItem *parent)
0018     : Chart(parent)
0019 {
0020     setIndexingMode(Chart::IndexSourceValues);
0021     m_range = std::make_unique<RangeGroup>();
0022     connect(m_range.get(), &RangeGroup::rangeChanged, this, &PieChart::onDataChanged);
0023 }
0024 
0025 RangeGroup *PieChart::range() const
0026 {
0027     return m_range.get();
0028 }
0029 
0030 bool PieChart::filled() const
0031 {
0032     return m_filled;
0033 }
0034 
0035 void PieChart::setFilled(bool newFilled)
0036 {
0037     if (newFilled == m_filled) {
0038         return;
0039     }
0040 
0041     m_filled = newFilled;
0042     update();
0043     Q_EMIT filledChanged();
0044 }
0045 
0046 qreal PieChart::thickness() const
0047 {
0048     return m_thickness;
0049 }
0050 
0051 void PieChart::setThickness(qreal newThickness)
0052 {
0053     if (newThickness == m_thickness) {
0054         return;
0055     }
0056 
0057     m_thickness = newThickness;
0058     update();
0059     Q_EMIT thicknessChanged();
0060 }
0061 
0062 qreal PieChart::spacing() const
0063 {
0064     return m_spacing;
0065 }
0066 
0067 void PieChart::setSpacing(qreal newSpacing)
0068 {
0069     if (newSpacing == m_spacing) {
0070         return;
0071     }
0072 
0073     m_spacing = newSpacing;
0074     update();
0075     Q_EMIT spacingChanged();
0076 }
0077 
0078 QColor PieChart::backgroundColor() const
0079 {
0080     return m_backgroundColor;
0081 }
0082 
0083 void PieChart::setBackgroundColor(const QColor &color)
0084 {
0085     if (color == m_backgroundColor) {
0086         return;
0087     }
0088     m_backgroundColor = color;
0089     update();
0090     Q_EMIT backgroundColorChanged();
0091 }
0092 
0093 qreal PieChart::fromAngle() const
0094 {
0095     return m_fromAngle;
0096 }
0097 
0098 void PieChart::setFromAngle(qreal newFromAngle)
0099 {
0100     if (qFuzzyCompare(newFromAngle, m_fromAngle)) {
0101         return;
0102     }
0103 
0104     m_fromAngle = newFromAngle;
0105     update();
0106     Q_EMIT fromAngleChanged();
0107 }
0108 
0109 qreal PieChart::toAngle() const
0110 {
0111     return m_toAngle;
0112 }
0113 
0114 void PieChart::setToAngle(qreal newToAngle)
0115 {
0116     if (qFuzzyCompare(newToAngle, m_toAngle)) {
0117         return;
0118     }
0119 
0120     m_toAngle = newToAngle;
0121     update();
0122     Q_EMIT toAngleChanged();
0123 }
0124 
0125 bool PieChart::smoothEnds() const
0126 {
0127     return m_smoothEnds;
0128 }
0129 
0130 void PieChart::setSmoothEnds(bool newSmoothEnds)
0131 {
0132     if (newSmoothEnds == m_smoothEnds) {
0133         return;
0134     }
0135 
0136     m_smoothEnds = newSmoothEnds;
0137     update();
0138     Q_EMIT smoothEndsChanged();
0139 }
0140 
0141 QSGNode *PieChart::updatePaintNode(QSGNode *node, UpdatePaintNodeData *data)
0142 {
0143     Q_UNUSED(data);
0144     if (!node) {
0145         node = new QSGNode{};
0146     }
0147 
0148     auto sourceCount = valueSources().size();
0149 
0150     if (m_sections.count() < sourceCount) {
0151         return node;
0152     }
0153 
0154     auto minDimension = std::min(width(), height());
0155 
0156     float outerRadius = minDimension;
0157     for (int i = 0; i < sourceCount; ++i) {
0158         float innerRadius = i == sourceCount - 1 && m_filled ? 0.0 : outerRadius - m_thickness;
0159 
0160         if (node->childCount() <= i) {
0161             node->appendChildNode(new PieChartNode{});
0162         }
0163 
0164         auto pieNode = static_cast<PieChartNode *>(node->childAtIndex(i));
0165         pieNode->setRect(boundingRect());
0166         pieNode->setInnerRadius(innerRadius);
0167         pieNode->setOuterRadius(outerRadius);
0168         pieNode->setSections(m_sections.at(i));
0169         pieNode->setBackgroundColor(m_backgroundColor);
0170         pieNode->setColors(m_colors.at(i));
0171         pieNode->setFromAngle(m_fromAngle);
0172         pieNode->setToAngle(m_toAngle);
0173         pieNode->setSmoothEnds(m_smoothEnds);
0174 
0175         outerRadius = outerRadius - m_thickness - m_spacing;
0176     }
0177 
0178     while (node->childCount() > sourceCount) {
0179         auto lastNode = node->childAtIndex(node->childCount() - 1);
0180         node->removeChildNode(lastNode);
0181         delete lastNode;
0182     }
0183 
0184     return node;
0185 }
0186 
0187 void PieChart::onDataChanged()
0188 {
0189     m_sections.clear();
0190     m_colors.clear();
0191 
0192     const auto sources = valueSources();
0193     const auto colors = colorSource();
0194 
0195     if (!colors || sources.isEmpty() || !m_range->isValid()) {
0196         return;
0197     }
0198 
0199     auto maximum = [](ChartDataSource *source) {
0200         qreal result = 0.0;
0201         for (int i = 0; i < source->itemCount(); ++i) {
0202             result += source->item(i).toDouble();
0203         }
0204         return std::max(result, source->maximum().toDouble());
0205     };
0206 
0207     auto indexMode = indexingMode();
0208     auto colorIndex = 0;
0209     auto calculateZeroRange = [](ChartDataSource *) {
0210         return 0.0;
0211     };
0212     auto range = m_range->calculateRange(valueSources(), calculateZeroRange, maximum);
0213 
0214     for (auto source : sources) {
0215         qreal threshold = range.start;
0216         qreal total = 0.0;
0217 
0218         QVector<qreal> sections;
0219         QVector<QColor> sectionColors;
0220 
0221         for (int i = 0; i < source->itemCount(); ++i) {
0222             auto value = source->item(i).toReal();
0223             auto limited = value - threshold;
0224             if (limited > 0.0) {
0225                 if (total + limited >= range.end) {
0226                     limited = range.end - total;
0227                 }
0228 
0229                 sections << limited;
0230                 total += limited;
0231 
0232                 auto color = colors->item(colorIndex).value<QColor>();
0233                 sectionColors << color;
0234             }
0235             threshold = std::max(0.0, threshold - value);
0236 
0237             if (indexMode != IndexEachSource) {
0238                 colorIndex++;
0239             }
0240         }
0241 
0242         if (qFuzzyCompare(total, 0.0)) {
0243             m_sections << QVector<qreal>{0.0};
0244             m_colors << QVector<QColor>{colors->item(colorIndex).value<QColor>()};
0245         }
0246 
0247         for (auto &value : sections) {
0248             value = value / range.distance;
0249         }
0250 
0251         m_sections << sections;
0252         m_colors << sectionColors;
0253 
0254         if (indexMode == IndexEachSource) {
0255             colorIndex++;
0256         } else if (indexMode == IndexSourceValues) {
0257             colorIndex = 0;
0258         }
0259     }
0260 
0261     update();
0262 }
0263 
0264 #include "moc_PieChart.cpp"