1 | /* |
2 | * SPDX-FileCopyrightText: 2019 Arjen Hiemstra <ahiemstra@heimr.nl> |
3 | * |
4 | * SPDX-License-Identifier: LGPL-2.1-only OR LGPL-3.0-only OR LicenseRef-KDE-Accepted-LGPL |
5 | */ |
6 | |
7 | #include "BarChart.h" |
8 | |
9 | #include <QDebug> |
10 | #include <QSGNode> |
11 | |
12 | #include "RangeGroup.h" |
13 | #include "datasource/ChartDataSource.h" |
14 | #include "scenegraph/BarChartNode.h" |
15 | |
16 | BarChart::BarChart(QQuickItem *parent) |
17 | : XYChart(parent) |
18 | { |
19 | } |
20 | |
21 | qreal BarChart::spacing() const |
22 | { |
23 | return m_spacing; |
24 | } |
25 | |
26 | void BarChart::setSpacing(qreal newSpacing) |
27 | { |
28 | if (newSpacing == m_spacing) { |
29 | return; |
30 | } |
31 | |
32 | m_spacing = newSpacing; |
33 | update(); |
34 | Q_EMIT spacingChanged(); |
35 | } |
36 | |
37 | qreal BarChart::barWidth() const |
38 | { |
39 | return m_barWidth; |
40 | } |
41 | |
42 | void BarChart::setBarWidth(qreal newBarWidth) |
43 | { |
44 | if (newBarWidth == m_barWidth) { |
45 | return; |
46 | } |
47 | |
48 | m_barWidth = newBarWidth; |
49 | update(); |
50 | Q_EMIT barWidthChanged(); |
51 | } |
52 | |
53 | qreal BarChart::radius() const |
54 | { |
55 | return m_radius; |
56 | } |
57 | |
58 | void BarChart::setRadius(qreal newRadius) |
59 | { |
60 | if (newRadius == m_radius) { |
61 | return; |
62 | } |
63 | |
64 | m_radius = newRadius; |
65 | update(); |
66 | Q_EMIT radiusChanged(); |
67 | } |
68 | |
69 | BarChart::Orientation BarChart::orientation() const |
70 | { |
71 | return m_orientation; |
72 | } |
73 | |
74 | void BarChart::setOrientation(BarChart::Orientation newOrientation) |
75 | { |
76 | if (newOrientation == m_orientation) { |
77 | return; |
78 | } |
79 | |
80 | m_orientation = newOrientation; |
81 | m_orientationChanged = true; |
82 | update(); |
83 | Q_EMIT orientationChanged(); |
84 | } |
85 | |
86 | QColor BarChart::backgroundColor() const |
87 | { |
88 | return m_backgroundColor; |
89 | } |
90 | |
91 | void BarChart::setBackgroundColor(const QColor &newBackgroundColor) |
92 | { |
93 | if (newBackgroundColor == m_backgroundColor) { |
94 | return; |
95 | } |
96 | |
97 | m_backgroundColor = newBackgroundColor; |
98 | update(); |
99 | Q_EMIT backgroundColorChanged(); |
100 | } |
101 | |
102 | QSGNode *BarChart::updatePaintNode(QSGNode *node, QQuickItem::UpdatePaintNodeData *) |
103 | { |
104 | BarChartNode *barNode = nullptr; |
105 | |
106 | if (m_orientationChanged) { |
107 | delete node; |
108 | node = nullptr; |
109 | m_orientationChanged = false; |
110 | } |
111 | |
112 | if (!node) { |
113 | barNode = new BarChartNode{}; |
114 | if (m_orientation == VerticalOrientation) { |
115 | node = barNode; |
116 | } else { |
117 | auto transformNode = new QSGTransformNode{}; |
118 | transformNode->appendChildNode(node: barNode); |
119 | QMatrix4x4 matrix; |
120 | matrix.translate(x: width(), y: 0.0); |
121 | matrix.rotate(angle: 90.0, x: 0.0, y: 0.0, z: 1.0); |
122 | transformNode->setMatrix(matrix); |
123 | node = transformNode; |
124 | } |
125 | } else { |
126 | if (m_orientation == VerticalOrientation) { |
127 | barNode = static_cast<BarChartNode *>(node); |
128 | } else { |
129 | barNode = static_cast<BarChartNode *>(node->childAtIndex(i: 0)); |
130 | } |
131 | } |
132 | |
133 | if (m_orientation == VerticalOrientation) { |
134 | barNode->setRect(boundingRect()); |
135 | } else { |
136 | QMatrix4x4 matrix; |
137 | matrix.translate(x: width(), y: 0.0); |
138 | matrix.rotate(angle: 90.0, x: 0.0, y: 0.0, z: 1.0); |
139 | static_cast<QSGTransformNode *>(node)->setMatrix(matrix); |
140 | barNode->setRect(QRectF{boundingRect().topLeft(), QSizeF{height(), width()}}); |
141 | } |
142 | barNode->setBars(calculateBars()); |
143 | barNode->setRadius(m_radius); |
144 | barNode->setBackgroundColor(m_backgroundColor); |
145 | |
146 | barNode->update(); |
147 | |
148 | return node; |
149 | } |
150 | |
151 | void BarChart::onDataChanged() |
152 | { |
153 | if (valueSources().size() == 0 || !colorSource()) { |
154 | return; |
155 | } |
156 | |
157 | m_barDataItems.clear(); |
158 | |
159 | updateComputedRange(); |
160 | |
161 | const auto range = computedRange(); |
162 | const auto sources = valueSources(); |
163 | auto colors = colorSource(); |
164 | auto indexMode = indexingMode(); |
165 | auto colorIndex = 0; |
166 | |
167 | m_barDataItems.fill(t: QList<BarData>{}, newSize: range.distanceX); |
168 | |
169 | const auto highlightIndex = highlight(); |
170 | |
171 | auto generator = [&, this, i = range.startX]() mutable -> QList<BarData> { |
172 | QList<BarData> colorInfos; |
173 | |
174 | for (int j = 0; j < sources.count(); ++j) { |
175 | auto value = (sources.at(i: j)->item(index: i).toReal() - range.startY) / range.distanceY; |
176 | auto color = colors->item(index: colorIndex).value<QColor>(); |
177 | |
178 | if (highlightIndex >= 0 && highlightIndex != colorIndex) { |
179 | color = desaturate(input: color); |
180 | } |
181 | |
182 | colorInfos << BarData{.value: value, .color: color}; |
183 | |
184 | if (indexMode != Chart::IndexSourceValues) { |
185 | colorIndex++; |
186 | } |
187 | } |
188 | |
189 | if (stacked()) { |
190 | auto previous = 0.0; |
191 | for (auto &[colorVal, _] : colorInfos) { |
192 | colorVal += previous; |
193 | previous = colorVal; |
194 | } |
195 | } |
196 | |
197 | if (indexMode == Chart::IndexSourceValues) { |
198 | colorIndex++; |
199 | } else if (indexMode == Chart::IndexEachSource) { |
200 | colorIndex = 0; |
201 | } |
202 | |
203 | i++; |
204 | return colorInfos; |
205 | }; |
206 | |
207 | if (direction() == Direction::ZeroAtStart) { |
208 | std::generate_n(first: m_barDataItems.begin(), n: range.distanceX, gen: generator); |
209 | } else { |
210 | std::generate_n(first: m_barDataItems.rbegin(), n: range.distanceX, gen: generator); |
211 | } |
212 | |
213 | update(); |
214 | } |
215 | |
216 | QList<Bar> BarChart::calculateBars() |
217 | { |
218 | QList<Bar> result; |
219 | |
220 | // TODO: Find some way to clean this up and simplify it, since this is pretty ugly. |
221 | |
222 | auto targetWidth = m_orientation == VerticalOrientation ? width() : height(); |
223 | |
224 | float w = m_barWidth; |
225 | if (w < 0.0) { |
226 | const auto totalItemCount = stacked() ? m_barDataItems.size() : m_barDataItems.size() * valueSources().count(); |
227 | |
228 | w = targetWidth / totalItemCount - m_spacing; |
229 | |
230 | auto x = float(m_spacing / 2); |
231 | const auto itemSpacing = w + m_spacing; |
232 | |
233 | for (const auto &items : std::as_const(t&: m_barDataItems)) { |
234 | result.reserve(asize: result.size() + items.size()); |
235 | if (stacked()) { |
236 | std::transform(first: items.crbegin(), last: items.crend(), result: std::back_inserter(x&: result), unary_op: [x, w](const BarData &entry) { |
237 | return Bar{.x: x, .width: w, .value: float(entry.value), .color: entry.color}; |
238 | }); |
239 | x += itemSpacing; |
240 | } else { |
241 | std::transform(first: items.cbegin(), last: items.cend(), result: std::back_inserter(x&: result), unary_op: [&x, itemSpacing, w](const BarData &entry) { |
242 | Bar bar{.x: x, .width: w, .value: float(entry.value), .color: entry.color}; |
243 | x += itemSpacing; |
244 | return bar; |
245 | }); |
246 | } |
247 | } |
248 | } else { |
249 | const auto itemSpacing = targetWidth / m_barDataItems.size(); |
250 | if (stacked()) { |
251 | auto x = float(itemSpacing / 2 - m_barWidth / 2); |
252 | |
253 | for (const auto &items : std::as_const(t&: m_barDataItems)) { |
254 | result.reserve(asize: result.size() + items.size()); |
255 | std::transform(first: items.crbegin(), last: items.crend(), result: std::back_inserter(x&: result), unary_op: [x, w](const BarData &entry) { |
256 | return Bar{.x: x, .width: w, .value: float(entry.value), .color: entry.color}; |
257 | }); |
258 | |
259 | x += itemSpacing; |
260 | } |
261 | } else { |
262 | const auto totalWidth = m_barWidth * valueSources().count() + m_spacing * (valueSources().count() - 1); |
263 | |
264 | auto x = float(itemSpacing / 2 - totalWidth / 2); |
265 | |
266 | for (const auto &items : std::as_const(t&: m_barDataItems)) { |
267 | result.reserve(asize: result.size() + items.size()); |
268 | for (int i = 0; i < items.count(); ++i) { |
269 | auto entry = items.at(i); |
270 | result << Bar{.x: float(x + i * (m_barWidth + m_spacing)), .width: w, .value: float(entry.value), .color: entry.color}; |
271 | } |
272 | x += itemSpacing; |
273 | } |
274 | } |
275 | } |
276 | |
277 | return result; |
278 | } |
279 | |
280 | #include "moc_BarChart.cpp" |
281 | |