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
16BarChart::BarChart(QQuickItem *parent)
17 : XYChart(parent)
18{
19}
20
21qreal BarChart::spacing() const
22{
23 return m_spacing;
24}
25
26void 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
37qreal BarChart::barWidth() const
38{
39 return m_barWidth;
40}
41
42void 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
53qreal BarChart::radius() const
54{
55 return m_radius;
56}
57
58void 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
69BarChart::Orientation BarChart::orientation() const
70{
71 return m_orientation;
72}
73
74void 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
86QColor BarChart::backgroundColor() const
87{
88 return m_backgroundColor;
89}
90
91void 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
102QSGNode *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
151void 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
216QList<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

source code of kquickcharts/src/BarChart.cpp