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

source code of qtgraphs/src/graphs2d/qsgrenderer/barsrenderer.cpp