1/*
2 * This file is part of KQuickCharts
3 * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl>
4 *
5 * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL
6 */
7
8#include "PieChart.h"
9
10#include <QAbstractItemModel>
11#include <QDebug>
12
13#include "RangeGroup.h"
14#include "datasource/ChartDataSource.h"
15#include "scenegraph/PieChartNode.h"
16
17PieChart::PieChart(QQuickItem *parent)
18 : Chart(parent)
19{
20 setIndexingMode(Chart::IndexSourceValues);
21 m_range = std::make_unique<RangeGroup>();
22 connect(sender: m_range.get(), signal: &RangeGroup::rangeChanged, context: this, slot: &PieChart::onDataChanged);
23}
24
25RangeGroup *PieChart::range() const
26{
27 return m_range.get();
28}
29
30bool PieChart::filled() const
31{
32 return m_filled;
33}
34
35void PieChart::setFilled(bool newFilled)
36{
37 if (newFilled == m_filled) {
38 return;
39 }
40
41 m_filled = newFilled;
42 update();
43 Q_EMIT filledChanged();
44}
45
46qreal PieChart::thickness() const
47{
48 return m_thickness;
49}
50
51void PieChart::setThickness(qreal newThickness)
52{
53 if (newThickness == m_thickness) {
54 return;
55 }
56
57 m_thickness = newThickness;
58 update();
59 Q_EMIT thicknessChanged();
60}
61
62qreal PieChart::spacing() const
63{
64 return m_spacing;
65}
66
67void PieChart::setSpacing(qreal newSpacing)
68{
69 if (newSpacing == m_spacing) {
70 return;
71 }
72
73 m_spacing = newSpacing;
74 update();
75 Q_EMIT spacingChanged();
76}
77
78QColor PieChart::backgroundColor() const
79{
80 return m_backgroundColor;
81}
82
83void PieChart::setBackgroundColor(const QColor &color)
84{
85 if (color == m_backgroundColor) {
86 return;
87 }
88 m_backgroundColor = color;
89 update();
90 Q_EMIT backgroundColorChanged();
91}
92
93qreal PieChart::fromAngle() const
94{
95 return m_fromAngle;
96}
97
98void PieChart::setFromAngle(qreal newFromAngle)
99{
100 if (qFuzzyCompare(p1: newFromAngle, p2: m_fromAngle)) {
101 return;
102 }
103
104 m_fromAngle = newFromAngle;
105 update();
106 Q_EMIT fromAngleChanged();
107}
108
109qreal PieChart::toAngle() const
110{
111 return m_toAngle;
112}
113
114void PieChart::setToAngle(qreal newToAngle)
115{
116 if (qFuzzyCompare(p1: newToAngle, p2: m_toAngle)) {
117 return;
118 }
119
120 m_toAngle = newToAngle;
121 update();
122 Q_EMIT toAngleChanged();
123}
124
125bool PieChart::smoothEnds() const
126{
127 return m_smoothEnds;
128}
129
130void PieChart::setSmoothEnds(bool newSmoothEnds)
131{
132 if (newSmoothEnds == m_smoothEnds) {
133 return;
134 }
135
136 m_smoothEnds = newSmoothEnds;
137 update();
138 Q_EMIT smoothEndsChanged();
139}
140
141QSGNode *PieChart::updatePaintNode(QSGNode *node, UpdatePaintNodeData *data)
142{
143 Q_UNUSED(data);
144 if (!node) {
145 node = new QSGNode{};
146 }
147
148 auto sourceCount = valueSources().size();
149
150 if (m_sections.count() < sourceCount) {
151 return node;
152 }
153
154 auto minDimension = std::min(a: width(), b: height());
155
156 float outerRadius = minDimension;
157 for (int i = 0; i < sourceCount; ++i) {
158 float innerRadius = i == sourceCount - 1 && m_filled ? 0.0 : outerRadius - m_thickness * 2.0;
159
160 if (node->childCount() <= i) {
161 node->appendChildNode(node: new PieChartNode{});
162 }
163
164 auto pieNode = static_cast<PieChartNode *>(node->childAtIndex(i));
165 pieNode->setRect(boundingRect());
166 pieNode->setInnerRadius(innerRadius);
167 pieNode->setOuterRadius(outerRadius);
168 pieNode->setSections(m_sections.at(i));
169 pieNode->setBackgroundColor(m_backgroundColor);
170 pieNode->setColors(m_colors.at(i));
171 pieNode->setFromAngle(m_fromAngle);
172 pieNode->setToAngle(m_toAngle);
173 pieNode->setSmoothEnds(m_smoothEnds);
174
175 outerRadius = innerRadius - m_spacing * 2.0;
176 }
177
178 while (node->childCount() > sourceCount) {
179 auto lastNode = node->childAtIndex(i: node->childCount() - 1);
180 node->removeChildNode(node: lastNode);
181 delete lastNode;
182 }
183
184 return node;
185}
186
187void PieChart::onDataChanged()
188{
189 m_sections.clear();
190 m_colors.clear();
191
192 const auto sources = valueSources();
193 const auto colors = colorSource();
194
195 if (!colors || sources.isEmpty() || !m_range->isValid()) {
196 return;
197 }
198
199 auto maximum = [](ChartDataSource *source) {
200 qreal result = 0.0;
201 for (int i = 0; i < source->itemCount(); ++i) {
202 result += source->item(index: i).toDouble();
203 }
204 return std::max(a: result, b: source->maximum().toDouble());
205 };
206
207 auto indexMode = indexingMode();
208 auto colorIndex = 0;
209 auto calculateZeroRange = [](ChartDataSource *) {
210 return 0.0;
211 };
212 auto range = m_range->calculateRange(sources: valueSources(), minimumCallback: calculateZeroRange, maximumCallback: maximum);
213
214 for (auto source : sources) {
215 qreal threshold = range.start;
216 qreal total = 0.0;
217
218 QList<qreal> sections;
219 QList<QColor> sectionColors;
220
221 for (int i = 0; i < source->itemCount(); ++i) {
222 auto value = source->item(index: i).toReal();
223 auto limited = value - threshold;
224 if (limited > 0.0) {
225 if (total + limited >= range.end) {
226 limited = range.end - total;
227 }
228
229 sections << limited;
230 total += limited;
231
232 auto color = colors->item(index: colorIndex).value<QColor>();
233 sectionColors << color;
234 }
235 threshold = std::max(a: 0.0, b: threshold - value);
236
237 if (indexMode != IndexEachSource) {
238 colorIndex++;
239 }
240 }
241
242 if (qFuzzyCompare(p1: total, p2: 0.0)) {
243 m_sections << QList<qreal>{0.0};
244 m_colors << QList<QColor>{colors->item(index: colorIndex).value<QColor>()};
245 }
246
247 for (auto &value : sections) {
248 value = value / range.distance;
249 }
250
251 m_sections << sections;
252 m_colors << sectionColors;
253
254 if (indexMode == IndexEachSource) {
255 colorIndex++;
256 } else if (indexMode == IndexSourceValues) {
257 colorIndex = 0;
258 }
259 }
260
261 update();
262}
263
264#include "moc_PieChart.cpp"
265

source code of kquickcharts/src/PieChart.cpp