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 | |
17 | PieChart::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 | |
25 | RangeGroup *PieChart::range() const |
26 | { |
27 | return m_range.get(); |
28 | } |
29 | |
30 | bool PieChart::filled() const |
31 | { |
32 | return m_filled; |
33 | } |
34 | |
35 | void 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 | |
46 | qreal PieChart::thickness() const |
47 | { |
48 | return m_thickness; |
49 | } |
50 | |
51 | void 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 | |
62 | qreal PieChart::spacing() const |
63 | { |
64 | return m_spacing; |
65 | } |
66 | |
67 | void 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 | |
78 | QColor PieChart::backgroundColor() const |
79 | { |
80 | return m_backgroundColor; |
81 | } |
82 | |
83 | void 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 | |
93 | qreal PieChart::fromAngle() const |
94 | { |
95 | return m_fromAngle; |
96 | } |
97 | |
98 | void 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 | |
109 | qreal PieChart::toAngle() const |
110 | { |
111 | return m_toAngle; |
112 | } |
113 | |
114 | void 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 | |
125 | bool PieChart::smoothEnds() const |
126 | { |
127 | return m_smoothEnds; |
128 | } |
129 | |
130 | void 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 | |
141 | QSGNode *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 | |
187 | void 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 | |