1 | // Copyright (C) 2023 The Qt Company Ltd. |
---|---|
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only |
3 | |
4 | #include <QtQuick/private/qquickrectangle_p.h> |
5 | #include <QtGraphs/qbarseries.h> |
6 | #include <QtGraphs/qbarset.h> |
7 | #include <private/barsrenderer_p.h> |
8 | #include <private/axisrenderer_p.h> |
9 | #include <private/qbarseries_p.h> |
10 | #include <private/qgraphsview_p.h> |
11 | |
12 | QT_BEGIN_NAMESPACE |
13 | |
14 | static const char* TAG_BAR_COLOR = "barColor"; |
15 | static const char* TAG_BAR_BORDER_COLOR = "barBorderColor"; |
16 | static const char* TAG_BAR_BORDER_WIDTH = "barBorderWidth"; |
17 | static const char* TAG_BAR_SELECTED = "barSelected"; |
18 | static const char* TAG_BAR_VALUE = "barValue"; |
19 | static const char* TAG_BAR_LABEL = "barLabel"; |
20 | |
21 | BarsRenderer::BarsRenderer(QGraphsView *graph) |
22 | : QQuickItem(graph) |
23 | , m_graph(graph) |
24 | { |
25 | setFlag(flag: QQuickItem::ItemHasContents); |
26 | setClip(true); |
27 | } |
28 | |
29 | BarsRenderer::~BarsRenderer() {} |
30 | |
31 | // Returns color in this order: |
32 | // 1) QBarSet::color if that is defined (alpha > 0). |
33 | // 2) QBarSeries::seriesColors at index if that is defined. |
34 | // 3) QGraphsTheme::seriesColors at index. |
35 | // 4) Black if seriesColors is empty. |
36 | QColor BarsRenderer::getSetColor(QBarSeries *series, QBarSet *set, qsizetype barSeriesIndex) |
37 | { |
38 | const auto &seriesColors = !series->seriesColors().isEmpty() |
39 | ? series->seriesColors() : m_graph->theme()->seriesColors(); |
40 | if (seriesColors.isEmpty()) |
41 | return QColorConstants::Black; |
42 | qsizetype index = m_colorIndex + barSeriesIndex; |
43 | index = index % seriesColors.size(); |
44 | QColor color = set->color().alpha() != 0 |
45 | ? set->color() |
46 | : seriesColors.at(i: index); |
47 | return color; |
48 | } |
49 | |
50 | QColor BarsRenderer::getSetSelectedColor(QBarSeries *series, QBarSet *set) |
51 | { |
52 | Q_UNUSED(series); |
53 | auto theme = m_graph->theme(); |
54 | QColor color = set->selectedColor().alpha() != 0 |
55 | ? set->selectedColor() |
56 | : theme->singleHighlightColor(); |
57 | return color; |
58 | } |
59 | |
60 | QColor BarsRenderer::getSetBorderColor(QBarSeries *series, QBarSet *set, qsizetype barSeriesIndex) |
61 | { |
62 | const auto &borderColors = !series->borderColors().isEmpty() |
63 | ? series->borderColors() : m_graph->theme()->borderColors(); |
64 | if (borderColors.isEmpty()) |
65 | return QColorConstants::Black; |
66 | qsizetype index = m_colorIndex + barSeriesIndex; |
67 | index = index % borderColors.size(); |
68 | QColor color = set->borderColor().alpha() != 0 |
69 | ? set->borderColor() |
70 | : borderColors.at(i: index); |
71 | return color; |
72 | } |
73 | |
74 | qreal BarsRenderer::getSetBorderWidth(QBarSeries *series, QBarSet *set) |
75 | { |
76 | Q_UNUSED(series); |
77 | auto theme = m_graph->theme(); |
78 | qreal borderWidth = set->borderWidth(); |
79 | if (qFuzzyCompare(p1: borderWidth, p2: qreal(-1.0))) |
80 | borderWidth = theme->borderWidth(); |
81 | return borderWidth; |
82 | } |
83 | |
84 | QString BarsRenderer::generateLabelText(QBarSeries *series, qreal value) |
85 | { |
86 | static const QString valueTag(QLatin1String("@value")); |
87 | QString valueString = QString::number(value, format: 'f', precision: series->labelsPrecision()); |
88 | QString valueLabel; |
89 | if (series->labelsFormat().isEmpty()) { |
90 | valueLabel = valueString; |
91 | } else { |
92 | valueLabel = series->labelsFormat(); |
93 | valueLabel.replace(before: valueTag, after: valueString); |
94 | } |
95 | return valueLabel; |
96 | } |
97 | |
98 | void BarsRenderer::positionLabelItem(QBarSeries *series, QQuickText *textItem, const BarSeriesData &d) |
99 | { |
100 | auto pos = series->labelsPosition(); |
101 | const bool vertical = m_graph->orientation() == Qt::Orientation::Vertical; |
102 | const float w = textItem->contentWidth() + series->labelsMargin() * 2; |
103 | const float h = textItem->contentHeight() + series->labelsMargin() * 2; |
104 | textItem->setWidth(w); |
105 | textItem->setHeight(h); |
106 | textItem->setHAlign(QQuickText::HAlignment::AlignHCenter); |
107 | textItem->setVAlign(QQuickText::VAlignment::AlignVCenter); |
108 | if (pos == QBarSeries::LabelsPosition::Center) { |
109 | textItem->setX(d.rect.x() + d.rect.width() * 0.5 - w * 0.5); |
110 | textItem->setY(d.rect.y() + d.rect.height() * 0.5 - h * 0.5); |
111 | } else if (pos == QBarSeries::LabelsPosition::InsideEnd) { |
112 | if (vertical) { |
113 | textItem->setX(d.rect.x() + d.rect.width() * 0.5 - w * 0.5); |
114 | textItem->setY(d.rect.y()); |
115 | } else { |
116 | textItem->setX(d.rect.x() + d.rect.width() - w); |
117 | textItem->setY(d.rect.y() + d.rect.height() * 0.5 - h * 0.5); |
118 | } |
119 | } else if (pos == QBarSeries::LabelsPosition::InsideBase) { |
120 | if (vertical) { |
121 | textItem->setX(d.rect.x() + d.rect.width() * 0.5 - w * 0.5); |
122 | textItem->setY(d.rect.y() + d.rect.height() - h); |
123 | } else { |
124 | textItem->setX(d.rect.x()); |
125 | textItem->setY(d.rect.y() + d.rect.height() * 0.5 - h * 0.5); |
126 | } |
127 | } else { |
128 | // OutsideEnd |
129 | if (vertical) { |
130 | textItem->setX(d.rect.x() + d.rect.width() * 0.5 - w * 0.5); |
131 | textItem->setY(d.rect.y() - h); |
132 | } else { |
133 | textItem->setX(d.rect.x() + d.rect.width()); |
134 | textItem->setY(d.rect.y() + d.rect.height() * 0.5 - h * 0.5); |
135 | } |
136 | } |
137 | textItem->update(); |
138 | } |
139 | |
140 | void BarsRenderer::updateComponents(QBarSeries *series) |
141 | { |
142 | int barIndex = 0; |
143 | auto &seriesData = m_seriesData[series]; |
144 | auto &barItems = m_barItems[series]; |
145 | for (auto i = seriesData.cbegin(), end = seriesData.cend(); i != end; ++i) { |
146 | if (barItems.size() <= barIndex) { |
147 | QQuickItem *item = nullptr; |
148 | // Create more components as needed |
149 | if (series->barDelegate()) { |
150 | item = qobject_cast<QQuickItem *>( |
151 | o: series->barDelegate()->create(context: series->barDelegate()->creationContext())); |
152 | } |
153 | if (!item) |
154 | item = new QQuickRectangle(); |
155 | item->setParent(this); |
156 | item->setParentItem(this); |
157 | barItems << item; |
158 | } |
159 | if (barItems.size() > barIndex) { |
160 | BarSeriesData d = *i; |
161 | if (series->barDelegate()) { |
162 | // Set custom bar components |
163 | auto &barItem = barItems[barIndex]; |
164 | barItem->setX(d.rect.x()); |
165 | barItem->setY(d.rect.y()); |
166 | barItem->setWidth(d.rect.width()); |
167 | barItem->setHeight(d.rect.height()); |
168 | barItem->setVisible(series->isVisible()); |
169 | // Check for specific dynamic properties |
170 | if (barItem->property(name: TAG_BAR_COLOR).isValid()) |
171 | barItem->setProperty(name: TAG_BAR_COLOR, value: d.color); |
172 | if (barItem->property(name: TAG_BAR_BORDER_COLOR).isValid()) |
173 | barItem->setProperty(name: TAG_BAR_BORDER_COLOR, value: d.borderColor); |
174 | if (barItem->property(name: TAG_BAR_BORDER_WIDTH).isValid()) |
175 | barItem->setProperty(name: TAG_BAR_BORDER_WIDTH, value: d.borderWidth); |
176 | if (barItem->property(name: TAG_BAR_SELECTED).isValid()) |
177 | barItem->setProperty(name: TAG_BAR_SELECTED, value: d.isSelected); |
178 | if (barItem->property(name: TAG_BAR_VALUE).isValid()) |
179 | barItem->setProperty(name: TAG_BAR_VALUE, value: d.value); |
180 | if (barItem->property(name: TAG_BAR_LABEL).isValid()) |
181 | barItem->setProperty(name: TAG_BAR_LABEL, value: d.label); |
182 | } else { |
183 | // Set default rectangle bars |
184 | auto barItem = qobject_cast<QQuickRectangle *>(object: barItems[barIndex]); |
185 | if (barItem) { |
186 | barItem->setX(d.rect.x()); |
187 | barItem->setY(d.rect.y()); |
188 | barItem->setWidth(d.rect.width()); |
189 | barItem->setHeight(d.rect.height()); |
190 | barItem->setVisible(series->isVisible()); |
191 | barItem->setColor(d.color); |
192 | barItem->border()->setColor(d.borderColor); |
193 | barItem->border()->setWidth(d.borderWidth); |
194 | barItem->setRadius(4.0); |
195 | } |
196 | } |
197 | } |
198 | barIndex++; |
199 | } |
200 | } |
201 | |
202 | void BarsRenderer::updateValueLabels(QBarSeries *series) |
203 | { |
204 | if (!series->barDelegate() && series->isVisible() && series->labelsVisible()) { |
205 | // Update default value labels |
206 | int barIndex = 0; |
207 | auto &seriesData = m_seriesData[series]; |
208 | auto &labelTextItems = m_labelTextItems[series]; |
209 | for (auto i = seriesData.cbegin(), end = seriesData.cend(); i != end; ++i) { |
210 | if (labelTextItems.size() <= barIndex) { |
211 | // Create more label items as needed |
212 | auto labelItem = new QQuickText(this); |
213 | labelTextItems << labelItem; |
214 | } |
215 | if (labelTextItems.size() > barIndex) { |
216 | // Set label item values |
217 | auto &textItem = labelTextItems[barIndex]; |
218 | const auto d = *i; |
219 | if (qFuzzyIsNull(f: d.value)) { |
220 | textItem->setVisible(false); |
221 | } else { |
222 | textItem->setVisible(series->labelsVisible()); |
223 | QString valueLabel = generateLabelText(series, value: d.value); |
224 | textItem->setText(valueLabel); |
225 | positionLabelItem(series, textItem, d); |
226 | QColor labelColor = d.labelColor; |
227 | if (labelColor.alpha() == 0) { |
228 | // TODO: Use graphs theme labels color. |
229 | labelColor = QColor(255, 255, 255); |
230 | } |
231 | textItem->setColor(labelColor); |
232 | textItem->setRotation(series->labelsAngle()); |
233 | } |
234 | } |
235 | barIndex++; |
236 | } |
237 | } else { |
238 | // Hide all possibly existing label items |
239 | auto &labelTextItems = m_labelTextItems[series]; |
240 | for (auto textItem : labelTextItems) |
241 | textItem->setVisible(false); |
242 | } |
243 | } |
244 | |
245 | void calculateCategoryTotalValues(QBarSeries *series, QList<float> &totalValues, qsizetype valuesPerSet) |
246 | { |
247 | totalValues.fill(t: 0, newSize: valuesPerSet); |
248 | for (auto s : series->barSets()) { |
249 | QVariantList v = s->values(); |
250 | int setIndex = 0; |
251 | for (auto variantValue : std::as_const(t&: v)) { |
252 | if (setIndex < totalValues.size()) |
253 | totalValues[setIndex] += variantValue.toReal(); |
254 | setIndex++; |
255 | } |
256 | } |
257 | } |
258 | |
259 | void BarsRenderer::updateVerticalBars(QBarSeries *series, qsizetype setCount, qsizetype valuesPerSet) |
260 | { |
261 | bool stacked = series->barsType() == QBarSeries::BarsType::Stacked |
262 | || series->barsType() == QBarSeries::BarsType::StackedPercent; |
263 | bool percent = series->barsType() == QBarSeries::BarsType::StackedPercent; |
264 | // Bars area width & height |
265 | float w = width(); |
266 | float h = height(); |
267 | // Max width of a bar if no separation between sets. |
268 | float maxBarWidth = w / (setCount * valuesPerSet) - m_barMargin; |
269 | if (stacked) |
270 | maxBarWidth = w / valuesPerSet; |
271 | // Actual bar width. |
272 | float barWidth = maxBarWidth * series->barWidth(); |
273 | // Helper to keep barsets centered when bar width is less than max width. |
274 | float barCentering = (maxBarWidth - barWidth) * setCount * 0.5; |
275 | if (stacked) |
276 | barCentering = (maxBarWidth - barWidth) * 0.5; |
277 | |
278 | auto &seriesData = m_seriesData[series]; |
279 | auto &rectNodesInputRects = m_rectNodesInputRects[series]; |
280 | // Clear the selection rects |
281 | // These will be filled only if series is selectable |
282 | rectNodesInputRects.clear(); |
283 | seriesData.clear(); |
284 | |
285 | float seriesPos = 0; |
286 | float posXInSet = 0; |
287 | QList<float> posYListInSet; |
288 | if (stacked) |
289 | posYListInSet.fill(t: 0, newSize: valuesPerSet); |
290 | QList<float> totalValuesListInSet; |
291 | if (percent) |
292 | calculateCategoryTotalValues(series, totalValues&: totalValuesListInSet, valuesPerSet); |
293 | |
294 | int barIndexInSet = 0; |
295 | int barSeriesIndex = 0; |
296 | QList<QLegendData> legendDataList; |
297 | for (auto s : series->barSets()) { |
298 | QVariantList v = s->values(); |
299 | qsizetype valuesCount = v.size(); |
300 | if (valuesCount == 0) |
301 | continue; |
302 | seriesPos = 0; |
303 | barIndexInSet = 0; |
304 | BarSelectionRect *barSelectionRect = nullptr; |
305 | if (series->isSelectable() || series->isHoverable()) { |
306 | rectNodesInputRects << BarSelectionRect(); |
307 | barSelectionRect = &rectNodesInputRects.last(); |
308 | barSelectionRect->barSet = s; |
309 | barSelectionRect->series = series; |
310 | } |
311 | |
312 | QColor color = getSetColor(series, set: s, barSeriesIndex); |
313 | QColor borderColor = getSetBorderColor(series, set: s, barSeriesIndex); |
314 | qreal borderWidth = getSetBorderWidth(series, set: s); |
315 | |
316 | // Update legendData |
317 | legendDataList.push_back(t: { |
318 | .color: color, |
319 | .borderColor: borderColor, |
320 | .label: s->label() |
321 | }); |
322 | // Apply series opacity |
323 | color.setAlpha(color.alpha() * series->opacity()); |
324 | borderColor.setAlpha(borderColor.alpha() * series->opacity()); |
325 | const auto selectedBars = s->selectedBars(); |
326 | for (auto variantValue : std::as_const(t&: v)) { |
327 | const float realValue = variantValue.toReal(); |
328 | float value = (realValue - m_graph->m_axisRenderer->m_axisVerticalMinValue) * series->valuesMultiplier(); |
329 | if (percent) { |
330 | if (auto totalValue = totalValuesListInSet.at(i: barIndexInSet)) |
331 | value *= (100.0 / totalValue); |
332 | } |
333 | const bool isSelected = selectedBars.contains(t: barIndexInSet); |
334 | double delta = m_graph->m_axisRenderer->m_axisVerticalMaxValue - m_graph->m_axisRenderer->m_axisVerticalMinValue; |
335 | double maxValues = delta > 0 ? 1.0 / delta : 100.0; |
336 | float barLength = h * value * maxValues; |
337 | float barY = h - barLength; |
338 | float barX = seriesPos + posXInSet + barCentering; |
339 | if (stacked) { |
340 | barY = h - barLength - posYListInSet[barIndexInSet]; |
341 | barX = seriesPos + barCentering; |
342 | } |
343 | QRectF barRect(barX, barY, barWidth, barLength); |
344 | if (barSelectionRect) |
345 | barSelectionRect->rects << barRect; |
346 | |
347 | // Collect the series data |
348 | BarSeriesData d; |
349 | d.rect = barRect; |
350 | d.color = isSelected ? getSetSelectedColor(series, set: s) : color; |
351 | d.borderColor = borderColor; |
352 | d.borderWidth = borderWidth; |
353 | d.isSelected = isSelected; |
354 | d.label = s->label(); |
355 | d.labelColor = s->labelColor(); |
356 | d.value = realValue; |
357 | seriesData << d; |
358 | |
359 | if (stacked) |
360 | posYListInSet[barIndexInSet] += barLength; |
361 | barIndexInSet++; |
362 | seriesPos = ((float)barIndexInSet / valuesPerSet) * w; |
363 | } |
364 | posXInSet += barWidth + m_barMargin; |
365 | barSeriesIndex++; |
366 | } |
367 | series->d_func()->setLegendData(legendDataList); |
368 | } |
369 | |
370 | void BarsRenderer::updateHorizontalBars(QBarSeries *series, qsizetype setCount, qsizetype valuesPerSet) |
371 | { |
372 | bool stacked = series->barsType() == QBarSeries::BarsType::Stacked |
373 | || series->barsType() == QBarSeries::BarsType::StackedPercent; |
374 | bool percent = series->barsType() == QBarSeries::BarsType::StackedPercent; |
375 | // Bars area width & height |
376 | float w = width(); |
377 | float h = height(); |
378 | // Max width of a bar if no separation between sets. |
379 | float maxBarWidth = h / (setCount * valuesPerSet) - m_barMargin; |
380 | if (stacked) |
381 | maxBarWidth = h / valuesPerSet; |
382 | // Actual bar width. |
383 | float barWidth = maxBarWidth * series->barWidth(); |
384 | // Helper to keep barsets centered when bar width is less than max width. |
385 | float barCentering = (maxBarWidth - barWidth) * setCount * 0.5; |
386 | if (stacked) |
387 | barCentering = (maxBarWidth - barWidth) * 0.5; |
388 | |
389 | auto &seriesData = m_seriesData[series]; |
390 | auto &rectNodesInputRects = m_rectNodesInputRects[series]; |
391 | // Clear the selection rects |
392 | // These will be filled only if series is selectable |
393 | rectNodesInputRects.clear(); |
394 | seriesData.clear(); |
395 | |
396 | float seriesPos = 0; |
397 | float posYInSet = 0; |
398 | QList<float> posXListInSet; |
399 | if (stacked) |
400 | posXListInSet.fill(t: 0, newSize: valuesPerSet); |
401 | QList<float> totalValuesListInSet; |
402 | if (percent) |
403 | calculateCategoryTotalValues(series, totalValues&: totalValuesListInSet, valuesPerSet); |
404 | int barIndexInSet = 0; |
405 | int barSerieIndex = 0; |
406 | QList<QLegendData> legendDataList; |
407 | for (auto s : series->barSets()) { |
408 | QVariantList v = s->values(); |
409 | qsizetype valuesCount = v.size(); |
410 | if (valuesCount == 0) |
411 | continue; |
412 | seriesPos = 0; |
413 | barIndexInSet = 0; |
414 | BarSelectionRect *barSelectionRect = nullptr; |
415 | if (series->isSelectable() || series->isHoverable()) { |
416 | rectNodesInputRects << BarSelectionRect(); |
417 | barSelectionRect = &rectNodesInputRects.last(); |
418 | barSelectionRect->barSet = s; |
419 | barSelectionRect->series = series; |
420 | } |
421 | |
422 | QColor color = getSetColor(series, set: s, barSeriesIndex: barSerieIndex); |
423 | QColor borderColor = getSetBorderColor(series, set: s, barSeriesIndex: barSerieIndex); |
424 | qreal borderWidth = getSetBorderWidth(series, set: s); |
425 | // Update legendData |
426 | legendDataList.push_back(t: { |
427 | .color: color, |
428 | .borderColor: borderColor, |
429 | .label: s->label() |
430 | }); |
431 | // Apply series opacity |
432 | color.setAlpha(color.alpha() * series->opacity()); |
433 | borderColor.setAlpha(borderColor.alpha() * series->opacity()); |
434 | const auto selectedBars = s->selectedBars(); |
435 | for (auto variantValue : std::as_const(t&: v)) { |
436 | const float realValue = variantValue.toReal(); |
437 | float value = (realValue - m_graph->m_axisRenderer->m_axisHorizontalMinValue) * series->valuesMultiplier(); |
438 | if (percent) { |
439 | if (auto totalValue = totalValuesListInSet.at(i: barIndexInSet)) |
440 | value *= (100.0 / totalValue); |
441 | } |
442 | const bool isSelected = selectedBars.contains(t: barIndexInSet); |
443 | double delta = m_graph->m_axisRenderer->m_axisHorizontalMaxValue - m_graph->m_axisRenderer->m_axisHorizontalMinValue; |
444 | double maxValues = delta > 0 ? 1.0 / delta : 100.0; |
445 | float barLength = w * value * maxValues; |
446 | float barY = seriesPos + posYInSet + barCentering; |
447 | float barX = 0; |
448 | if (stacked) { |
449 | barY = seriesPos + barCentering; |
450 | barX = posXListInSet[barIndexInSet]; |
451 | } |
452 | QRectF barRect(barX, barY, barLength, barWidth); |
453 | if (barSelectionRect) |
454 | barSelectionRect->rects << barRect; |
455 | |
456 | // Collect the series data |
457 | BarSeriesData d; |
458 | d.rect = barRect; |
459 | d.color = isSelected ? getSetSelectedColor(series, set: s) : color; |
460 | d.borderColor = borderColor; |
461 | d.borderWidth = borderWidth; |
462 | d.isSelected = isSelected; |
463 | d.label = s->label(); |
464 | d.labelColor = s->labelColor(); |
465 | d.value = realValue; |
466 | seriesData << d; |
467 | |
468 | if (stacked) |
469 | posXListInSet[barIndexInSet] += barLength; |
470 | barIndexInSet++; |
471 | seriesPos = ((float)barIndexInSet / valuesPerSet) * h; |
472 | } |
473 | posYInSet += barWidth + m_barMargin; |
474 | barSerieIndex++; |
475 | } |
476 | series->d_func()->setLegendData(legendDataList); |
477 | } |
478 | |
479 | void BarsRenderer::handlePolish(QBarSeries *series) |
480 | { |
481 | auto theme = m_graph->theme(); |
482 | if (!theme) |
483 | return; |
484 | |
485 | if (!m_graph->m_axisRenderer) |
486 | return; |
487 | |
488 | qsizetype setCount = series->barSets().size(); |
489 | auto &seriesData = m_seriesData[series]; |
490 | auto &barItems = m_barItems[series]; |
491 | auto &rectNodesInputRects = m_rectNodesInputRects[series]; |
492 | if (setCount == 0) { |
493 | for (int i = 0; i < barItems.size(); i++) |
494 | barItems[i]->deleteLater(); |
495 | barItems.clear(); |
496 | |
497 | series->d_func()->clearLegendData(); |
498 | rectNodesInputRects.clear(); |
499 | seriesData.clear(); |
500 | return; |
501 | } |
502 | |
503 | if (m_colorIndex < 0) |
504 | m_colorIndex = m_graph->graphSeriesCount(); |
505 | m_graph->setGraphSeriesCount(m_colorIndex + setCount); |
506 | |
507 | if (series->barDelegateDirty() && !barItems.isEmpty()) { |
508 | // Bars delegate has changed, so remove the old items. |
509 | for (int i = 0; i < barItems.size(); i++) |
510 | barItems[i]->deleteLater(); |
511 | barItems.clear(); |
512 | series->setBarDelegateDirty(false); |
513 | } |
514 | |
515 | // Get bars values |
516 | qsizetype valuesPerSet = series->barSets().first()->values().size(); |
517 | if (m_graph->orientation() == Qt::Orientation::Vertical) |
518 | updateVerticalBars(series, setCount, valuesPerSet); |
519 | else |
520 | updateHorizontalBars(series, setCount, valuesPerSet); |
521 | updateComponents(series); |
522 | updateValueLabels(series); |
523 | |
524 | // Remove additional components |
525 | for (qsizetype i = barItems.size() - 1; i >= seriesData.size(); --i) |
526 | barItems[i]->deleteLater(); |
527 | const auto range = barItems.size() - seriesData.size(); |
528 | if (range > 0) |
529 | barItems.remove(i: seriesData.size(), n: range); |
530 | } |
531 | |
532 | void BarsRenderer::updateSeries(QBarSeries *series) |
533 | { |
534 | Q_UNUSED(series); |
535 | } |
536 | |
537 | void BarsRenderer::afterUpdate(QList<QAbstractSeries *> &cleanupSeries) |
538 | { |
539 | Q_UNUSED(cleanupSeries); |
540 | } |
541 | |
542 | void BarsRenderer::afterPolish(QList<QAbstractSeries *> &cleanupSeries) |
543 | { |
544 | for (auto &cleanupSerie : cleanupSeries) { |
545 | auto series = qobject_cast<QBarSeries *>(object: cleanupSerie); |
546 | if (series && m_barItems.contains(key: series)) { |
547 | // Remove custom bar items |
548 | auto &barItems = m_barItems[series]; |
549 | for (int i = 0; i < barItems.size(); i++) |
550 | barItems[i]->deleteLater(); |
551 | barItems.clear(); |
552 | m_barItems.remove(key: series); |
553 | } |
554 | if (series && m_labelTextItems.contains(key: series)) { |
555 | // Remove bar label items |
556 | auto &labelTextItems = m_labelTextItems[series]; |
557 | for (int i = 0; i < labelTextItems.size(); i++) |
558 | labelTextItems[i]->deleteLater(); |
559 | labelTextItems.clear(); |
560 | m_labelTextItems.remove(key: series); |
561 | } |
562 | } |
563 | } |
564 | |
565 | bool BarsRenderer::handleMousePress(QMouseEvent *event) |
566 | { |
567 | bool handled = false; |
568 | for (auto &rectNodesInputRects : m_rectNodesInputRects) { |
569 | for (auto &barSelection : rectNodesInputRects) { |
570 | if (!barSelection.series->isSelectable()) |
571 | continue; |
572 | qsizetype indexInSet = 0; |
573 | for (auto &rect : barSelection.rects) { |
574 | if (rect.contains(p: event->pos())) { |
575 | // TODO: Currently just toggling selection |
576 | QList<qsizetype> indexList = {indexInSet}; |
577 | barSelection.barSet->toggleSelection(indexes: indexList); |
578 | handled = true; |
579 | } |
580 | indexInSet++; |
581 | } |
582 | } |
583 | } |
584 | return handled; |
585 | } |
586 | |
587 | bool BarsRenderer::handleHoverMove(QHoverEvent *event) |
588 | { |
589 | bool handled = false; |
590 | const QPointF &position = event->position(); |
591 | |
592 | bool hovering = false; |
593 | for (auto &rectNodesInputRects : m_rectNodesInputRects) { |
594 | for (auto &barSelection : rectNodesInputRects) { |
595 | int indexInSet = 0; |
596 | |
597 | for (auto &rect : barSelection.rects) { |
598 | if (rect.contains(p: event->position().toPoint())) { |
599 | const QString &name = barSelection.series->name(); |
600 | const QPointF point(indexInSet, barSelection.barSet->at(index: indexInSet)); |
601 | |
602 | if (!m_currentHoverSeries) { |
603 | m_currentHoverSeries = barSelection.series; |
604 | emit barSelection.series->hoverEnter(seriesName: name, position, value: point); |
605 | } |
606 | |
607 | emit barSelection.series->hover(seriesName: name, position, value: point); |
608 | hovering = true; |
609 | handled = true; |
610 | } |
611 | indexInSet++; |
612 | } |
613 | } |
614 | } |
615 | |
616 | if (!hovering && m_currentHoverSeries) { |
617 | emit m_currentHoverSeries->hoverExit(seriesName: m_currentHoverSeries->name(), position); |
618 | m_currentHoverSeries = nullptr; |
619 | handled = true; |
620 | } |
621 | return handled; |
622 | } |
623 | |
624 | QT_END_NAMESPACE |
625 |
Definitions
- TAG_BAR_COLOR
- TAG_BAR_BORDER_COLOR
- TAG_BAR_BORDER_WIDTH
- TAG_BAR_SELECTED
- TAG_BAR_VALUE
- TAG_BAR_LABEL
- BarsRenderer
- ~BarsRenderer
- getSetColor
- getSetSelectedColor
- getSetBorderColor
- getSetBorderWidth
- generateLabelText
- positionLabelItem
- updateComponents
- updateValueLabels
- calculateCategoryTotalValues
- updateVerticalBars
- updateHorizontalBars
- handlePolish
- updateSeries
- afterUpdate
- afterPolish
- handleMousePress
Start learning QML with our Intro Training
Find out more