| 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 |  |