1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "q3dscene.h"
5#include "qbar3dseries_p.h"
6#include "qbardataproxy_p.h"
7#include "qcategory3daxis_p.h"
8#include "qgraphsinputhandler_p.h"
9#include "qgraphstheme.h"
10#include "qquickgraphsbars_p.h"
11#include "qquickgraphstexturedata_p.h"
12#include "qvalue3daxis_p.h"
13
14#include <QColor>
15#include <QtCore/QMutexLocker>
16#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
17#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
18#include <QtQuick3D/private/qquick3drepeater_p.h>
19
20/*!
21 * \qmltype Bars3D
22 * \inherits GraphsItem3D
23 * \inqmlmodule QtGraphs
24 * \ingroup graphs_qml_3D
25 * \brief 3D bar graph.
26 *
27 * This type enables developers to render bar graphs in 3D with Qt Quick.
28 *
29 * You will need to import Qt Graphs module to use this type:
30 *
31 * \snippet doc_src_qmlgraphs.cpp 0
32 *
33 * After that you can use Bars3D in your qml files:
34 *
35 * \snippet doc_src_qmlgraphs.cpp 1
36 *
37 * See \l{Simple Bar Graph} for more thorough usage example.
38 *
39 * \sa Bar3DSeries, ItemModelBarDataProxy, Scatter3D, Surface3D, {Qt Graphs C++ Classes for 3D}
40 */
41
42/*!
43 * \qmlproperty Category3DAxis Bars3D::rowAxis
44 * The active row axis.
45 *
46 * If an axis is not given, a temporary default axis with no labels is created.
47 * This temporary axis is destroyed if another axis is explicitly set to the
48 * same orientation.
49 */
50
51/*!
52 * \qmlproperty ValueAxis3D Bars3D::valueAxis
53 * The active value axis.
54 *
55 * If an axis is not given, a temporary default axis with no labels and an
56 * automatically adjusting range is created.
57 * This temporary axis is destroyed if another axis is explicitly set to the
58 * same orientation.
59 */
60
61/*!
62 * \qmlproperty Category3DAxis Bars3D::columnAxis
63 * The active column axis.
64 *
65 * If an axis is not given, a temporary default axis with no labels is created.
66 * This temporary axis is destroyed if another axis is explicitly set to the
67 * same orientation.
68 */
69
70/*!
71 * \qmlproperty bool Bars3D::multiSeriesUniform
72 * Defines whether bars are to be scaled with proportions set to a single series bar even
73 * if there are multiple series displayed. If set to \c {true}, \l{barSpacing}{bar spacing} will
74 * be correctly applied only to the X-axis. Preset to \c false by default.
75 */
76
77/*!
78 * \qmlproperty real Bars3D::barThickness
79 * The bar thickness ratio between the X and Z dimensions. The value \c 1.0
80 * means that the bars are as wide as they are deep, whereas \c 0.5
81 * makes them twice as deep as they are wide.
82 */
83
84/*!
85 * \qmlproperty size Bars3D::barSpacing
86 * Bar spacing in X and Z dimensions.
87 *
88 * Preset to \c {(1.0, 1.0)} by default. Spacing is affected by the
89 * barSpacingRelative property.
90 */
91
92/*!
93 * \qmlproperty bool Bars3D::barSpacingRelative
94 * Whether spacing is absolute or relative to bar thickness.
95 *
96 * If \c true, the value of \c 0.0 means that the bars are placed
97 * side-to-side, \c 1.0 means that a space as wide as the thickness of one bar
98 * is left between the bars, and so on. Preset to \c true.
99 */
100
101/*!
102 * \qmlproperty size Bars3D::barSeriesMargin
103 *
104 * Margin between series columns in X and Z dimensions. Preset to \c {(0.0, 0.0)} by default.
105 * Sensible values are on the range [0,1).
106 */
107
108/*!
109 * \qmlproperty Bar3DSeries Bars3D::selectedSeries
110 * \readonly
111 *
112 * The selected series or \c null. If \l {GraphsItem3D::selectionMode}{selectionMode} has
113 * the \c SelectionMultiSeries flag set, this property holds the series that
114 * owns the selected bar.
115 */
116
117/*!
118 * \qmlproperty list<Bar3DSeries> Bars3D::seriesList
119 * \qmldefault
120 * The series of the graph.
121 * By default, this property contains an empty list.
122 * To set the series, either use the addSeries() function or define them as children of the graph.
123 */
124
125/*!
126 * \qmlproperty Bar3DSeries Bars3D::primarySeries
127 * The primary series of the graph. It
128 * is used to determine the row and column axis labels when the labels are not explicitly
129 * set to the axes.
130 *
131 * If the specified series is not yet added to the graph, setting it as the
132 * primary series will also implicitly add it to the graph.
133 *
134 * If the primary series itself is removed from the graph, this property
135 * resets to default.
136 *
137 * If the series is null, this property resets to default.
138 * Defaults to the first added series or zero if no series are added to the graph.
139 */
140
141/*!
142 * \qmlproperty real Bars3D::floorLevel
143 *
144 * The floor level for the bar graph in Y-axis data coordinates.
145 *
146 * The actual floor level will be restricted by the Y-axis minimum and maximum
147 * values.
148 * Defaults to zero.
149 */
150
151/*!
152 * \qmlmethod void Bars3D::addSeries(Bar3DSeries series)
153 * Adds the \a series to the graph. A graph can contain multiple series, but only one set of axes,
154 * so the rows and columns of all series must match for the visualized data to be meaningful.
155 * If the graph has multiple visible series, only the first one added will
156 * generate the row or column labels on the axes in cases where the labels are not explicitly set
157 * to the axes. If the newly added series has specified a selected bar, it will be highlighted and
158 * any existing selection will be cleared. Only one added series can have an active selection.
159 * \sa GraphsItem3D::hasSeries()
160 */
161
162/*!
163 * \qmlmethod void Bars3D::removeSeries(Bar3DSeries series)
164 * Remove the \a series from the graph.
165 * \sa GraphsItem3D::hasSeries()
166 */
167
168/*!
169 * \qmlmethod void Bars3D::insertSeries(int index, Bar3DSeries series)
170 * Inserts the \a series into the position \a index in the series list.
171 * If the \a series has already been added to the list, it is moved to the
172 * new \a index.
173 * \note When moving a series to a new \a index that is after its old index,
174 * the new position in list is calculated as if the series was still in its old
175 * index, so the final index is actually the \a index decremented by one.
176 * \sa GraphsItem3D::hasSeries()
177 */
178
179/*!
180 * \qmlsignal Bars3D::multiSeriesUniformChanged(bool uniform)
181 *
182 * This signal is emitted when multiSeriesUniform changes to \a uniform.
183 */
184
185/*!
186 * \qmlsignal Bars3D::barThicknessChanged(real thicknessRatio)
187 *
188 * This signal is emitted when barThickness changes to \a thicknessRatio.
189 */
190
191/*!
192 * \qmlsignal Bars3D::barSpacingChanged(size spacing)
193 *
194 * This signal is emitted when barSpacing changes to \a spacing.
195 */
196
197/*!
198 * \qmlsignal Bars3D::barSpacingRelativeChanged(bool relative)
199 *
200 * This signal is emitted when barSpacingRelative changes to \a relative.
201 */
202
203/*!
204 * \qmlsignal Bars3D::barSeriesMarginChanged(size margin)
205 *
206 * This signal is emitted when barSeriesMargin changes to \a margin.
207 */
208
209/*!
210 * \qmlsignal Bars3D::rowAxisChanged(Category3DAxis axis)
211 *
212 * This signal is emitted when rowAxis changes to \a axis.
213 */
214
215/*!
216 * \qmlsignal Bars3D::columnAxisChanged(Category3DAxis axis)
217 *
218 * This signal is emitted when columnAxis changes to \a axis.
219 */
220
221/*!
222 * \qmlsignal Bars3D::valueAxisChanged(ValueAxis3D axis)
223 *
224 * This signal is emitted when valueAxis changes to \a axis.
225 */
226
227/*!
228 * \qmlsignal Bars3D::primarySeriesChanged(Bar3DSeries series)
229 *
230 * This signal is emitted when primarySeries changes to \a series.
231 */
232
233/*!
234 * \qmlsignal Bars3D::selectedSeriesChanged(Bar3DSeries series)
235 *
236 * This signal is emitted when selectedSeries changes to \a series.
237 */
238
239/*!
240 * \qmlsignal Bars3D::floorLevelChanged(real level)
241 *
242 * This signal is emitted when floorLevel changes to \a level.
243 */
244
245QQuickGraphsBars::QQuickGraphsBars(QQuickItem *parent)
246 : QQuickGraphsItem(parent)
247{
248 m_graphType = QAbstract3DSeries::SeriesType::Bar;
249 setAxisX(0);
250 setAxisY(0);
251 setAxisZ(0);
252 setAcceptedMouseButtons(Qt::AllButtons);
253 setFlags(ItemHasContents);
254 clearSelection();
255}
256
257QQuickGraphsBars::~QQuickGraphsBars()
258{
259 QMutexLocker locker(m_nodeMutex.data());
260 const QMutexLocker locker2(mutex());
261 removeBarModels();
262 removeSlicedBarModels();
263}
264
265QCategory3DAxis *QQuickGraphsBars::rowAxis() const
266{
267 return static_cast<QCategory3DAxis *>(axisZ());
268}
269
270void QQuickGraphsBars::setRowAxis(QCategory3DAxis *axis)
271{
272 QQuickGraphsItem::setAxisZ(axis);
273}
274
275QValue3DAxis *QQuickGraphsBars::valueAxis() const
276{
277 return static_cast<QValue3DAxis *>(axisY());
278}
279
280void QQuickGraphsBars::setValueAxis(QValue3DAxis *axis)
281{
282 QQuickGraphsItem::setAxisY(axis);
283}
284
285QCategory3DAxis *QQuickGraphsBars::columnAxis() const
286{
287 return static_cast<QCategory3DAxis *>(axisX());
288}
289
290void QQuickGraphsBars::setColumnAxis(QCategory3DAxis *axis)
291{
292 QQuickGraphsItem::setAxisX(axis);
293}
294
295void QQuickGraphsBars::setMultiSeriesScaling(bool uniform)
296{
297 m_isMultiSeriesUniform = uniform;
298
299 m_changeTracker.multiSeriesScalingChanged = true;
300 emitNeedRender();
301}
302
303bool QQuickGraphsBars::multiSeriesScaling() const
304{
305 return m_isMultiSeriesUniform;
306}
307
308void QQuickGraphsBars::setMultiSeriesUniform(bool uniform)
309{
310 if (uniform != isMultiSeriesUniform()) {
311 setMultiSeriesScaling(uniform);
312 emit multiSeriesUniformChanged(uniform);
313 }
314}
315
316bool QQuickGraphsBars::isMultiSeriesUniform() const
317{
318 return multiSeriesScaling();
319}
320
321void QQuickGraphsBars::setBarSpecs(float thicknessRatio, QSizeF spacing, bool relative)
322{
323 m_barThicknessRatio = thicknessRatio;
324 m_barSpacing = spacing;
325 m_isBarSpecRelative = relative;
326
327 m_changeTracker.barSpecsChanged = true;
328 emitNeedRender();
329}
330
331void QQuickGraphsBars::setBarThickness(float thicknessRatio)
332{
333 if (thicknessRatio != barThickness() && thicknessRatio > 0.f) {
334 setBarSpecs(thicknessRatio, spacing: barSpacing(), relative: isBarSpacingRelative());
335 emit barThicknessChanged(thicknessRatio);
336 }
337}
338
339float QQuickGraphsBars::barThickness() const
340{
341 return m_barThicknessRatio;
342}
343
344void QQuickGraphsBars::setBarSpacing(QSizeF spacing)
345{
346 if (spacing != barSpacing()) {
347 setBarSpecs(thicknessRatio: barThickness(), spacing, relative: isBarSpacingRelative());
348 emit barSpacingChanged(spacing);
349 }
350}
351
352QSizeF QQuickGraphsBars::barSpacing() const
353{
354 return m_barSpacing;
355}
356
357void QQuickGraphsBars::setBarSpacingRelative(bool relative)
358{
359 if (relative != isBarSpacingRelative()) {
360 setBarSpecs(thicknessRatio: barThickness(), spacing: barSpacing(), relative);
361 emit barSpacingRelativeChanged(relative);
362 }
363}
364
365bool QQuickGraphsBars::isBarSpacingRelative() const
366{
367 return m_isBarSpecRelative;
368}
369
370void QQuickGraphsBars::setBarSeriesMargin(QSizeF margin)
371{
372 if (margin != barSeriesMargin()) {
373 m_barSeriesMargin = margin;
374 m_changeTracker.barSeriesMarginChanged = true;
375 emitNeedRender();
376 emit barSeriesMarginChanged(margin: barSeriesMargin());
377 }
378}
379
380QSizeF QQuickGraphsBars::barSeriesMargin() const
381{
382 return m_barSeriesMargin;
383}
384
385QList<QBar3DSeries *> QQuickGraphsBars::barSeriesList()
386{
387 QList<QBar3DSeries *> barSeriesList;
388 for (QAbstract3DSeries *abstractSeries : m_seriesList) {
389 QBar3DSeries *barSeries = qobject_cast<QBar3DSeries *>(object: abstractSeries);
390 if (barSeries)
391 barSeriesList.append(t: barSeries);
392 }
393
394 return barSeriesList;
395}
396
397QQmlListProperty<QBar3DSeries> QQuickGraphsBars::seriesList()
398{
399 return QQmlListProperty<QBar3DSeries>(this,
400 this,
401 &QQuickGraphsBars::appendSeriesFunc,
402 &QQuickGraphsBars::countSeriesFunc,
403 &QQuickGraphsBars::atSeriesFunc,
404 &QQuickGraphsBars::clearSeriesFunc);
405}
406
407void QQuickGraphsBars::appendSeriesFunc(QQmlListProperty<QBar3DSeries> *list, QBar3DSeries *series)
408{
409 reinterpret_cast<QQuickGraphsBars *>(list->data)->addSeries(series);
410}
411
412qsizetype QQuickGraphsBars::countSeriesFunc(QQmlListProperty<QBar3DSeries> *list)
413{
414 return reinterpret_cast<QQuickGraphsBars *>(list->data)->barSeriesList().size();
415}
416
417QBar3DSeries *QQuickGraphsBars::atSeriesFunc(QQmlListProperty<QBar3DSeries> *list, qsizetype index)
418{
419 return reinterpret_cast<QQuickGraphsBars *>(list->data)->barSeriesList().at(i: index);
420}
421
422void QQuickGraphsBars::clearSeriesFunc(QQmlListProperty<QBar3DSeries> *list)
423{
424 QQuickGraphsBars *declBars = reinterpret_cast<QQuickGraphsBars *>(list->data);
425 QList<QBar3DSeries *> realList = declBars->barSeriesList();
426 qsizetype count = realList.size();
427 for (int i = 0; i < count; i++)
428 declBars->removeSeries(series: realList.at(i));
429}
430
431void QQuickGraphsBars::addSeries(QBar3DSeries *series)
432{
433 insertSeries(index: m_seriesList.size(), series);
434 connectSeries(series);
435 if (series->selectedBar() != invalidSelectionPosition())
436 updateSelectedBar();
437}
438
439void QQuickGraphsBars::removeSeries(QBar3DSeries *series)
440{
441 if (!series)
442 return;
443
444 bool wasVisible = (series && series->d_func()->m_graph == this && series->isVisible());
445
446 QQuickGraphsItem::removeSeriesInternal(series);
447
448 if (m_selectedBarSeries == series)
449 setSelectedBar(coord: invalidSelectionPosition(), series: 0, enterSlice: false);
450
451 if (wasVisible)
452 adjustAxisRanges();
453
454 // If primary series is removed, reset it to default
455 if (series == m_primarySeries) {
456 if (m_seriesList.size())
457 m_primarySeries = static_cast<QBar3DSeries *>(m_seriesList.at(i: 0));
458 else
459 m_primarySeries = 0;
460
461 handleDataRowLabelsChanged();
462 handleDataColumnLabelsChanged();
463
464 emit primarySeriesChanged(series: m_primarySeries);
465 }
466
467 for (auto it = m_slicedBarModels.begin(); it != m_slicedBarModels.end(); it++) {
468 // Remove series also from slice bar list
469 if (it.key() == series)
470 m_slicedBarModels.remove(key: it.key());
471 }
472
473 removeBarModels();
474 if (m_selectedBarSeries == series)
475 resetClickedStatus();
476 series->setParent(this); // Reparent as removing will leave series parentless
477 disconnectSeries(series);
478}
479
480void QQuickGraphsBars::insertSeries(qsizetype index, QBar3DSeries *series)
481{
482 Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesType::Bar);
483
484 qsizetype oldSize = m_seriesList.size();
485
486 QQuickGraphsItem::insertSeries(index, series);
487
488 if (oldSize != m_seriesList.size()) {
489 QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(series);
490 if (!oldSize) {
491 m_primarySeries = barSeries;
492 handleDataRowLabelsChanged();
493 handleDataColumnLabelsChanged();
494 }
495
496 if (barSeries->selectedBar() != invalidSelectionPosition())
497 setSelectedBar(coord: barSeries->selectedBar(), series: barSeries, enterSlice: false);
498
499 if (!oldSize)
500 emit primarySeriesChanged(series: m_primarySeries);
501 }
502}
503
504void QQuickGraphsBars::clearSelection()
505{
506 setSelectedBar(coord: invalidSelectionPosition(), series: 0, enterSlice: false);
507}
508
509void QQuickGraphsBars::setPrimarySeries(QBar3DSeries *series)
510{
511 if (!series) {
512 if (m_seriesList.size())
513 series = static_cast<QBar3DSeries *>(m_seriesList.at(i: 0));
514 } else if (!m_seriesList.contains(t: series)) {
515 // Add nonexistent series.
516 addSeries(series);
517 }
518
519 if (m_primarySeries != series) {
520 m_primarySeries = series;
521 handleDataRowLabelsChanged();
522 handleDataColumnLabelsChanged();
523 emit primarySeriesChanged(series: m_primarySeries);
524 }
525}
526
527QBar3DSeries *QQuickGraphsBars::primarySeries() const
528{
529 return m_primarySeries;
530}
531
532QBar3DSeries *QQuickGraphsBars::selectedSeries() const
533{
534 return m_selectedBarSeries;
535}
536
537void QQuickGraphsBars::setSelectionMode(QtGraphs3D::SelectionFlags mode)
538{
539 if (mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
540 && (mode.testFlag(flag: QtGraphs3D::SelectionFlag::Row)
541 == mode.testFlag(flag: QtGraphs3D::SelectionFlag::Column))) {
542 qWarning(msg: "Must specify one of either row or column selection mode"
543 "in conjunction with slicing mode.");
544 } else {
545 QtGraphs3D::SelectionFlags oldMode = selectionMode();
546
547 QQuickGraphsItem::setSelectionMode(mode);
548
549 if (mode != oldMode) {
550 // Refresh selection upon mode change to ensure slicing is correctly
551 // updated according to series the visibility.
552 setSelectedBar(coord: m_selectedBar, series: m_selectedBarSeries, enterSlice: true);
553
554 // Special case: Always deactivate slicing when changing away from slice
555 // automanagement, as this can't be handled in setSelectedBar.
556 if (!mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
557 && oldMode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
558 scene()->setSlicingActive(false);
559 }
560 }
561 }
562}
563
564void QQuickGraphsBars::handleAxisAutoAdjustRangeChangedInOrientation(
565 QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
566{
567 Q_UNUSED(orientation);
568 Q_UNUSED(autoAdjust);
569 adjustAxisRanges();
570}
571
572void QQuickGraphsBars::handleSeriesVisibilityChangedBySender(QObject *sender)
573{
574 QQuickGraphsItem::handleSeriesVisibilityChangedBySender(sender);
575
576 // Visibility changes may require disabling slicing,
577 // so just reset selection to ensure everything is still valid.
578 setSelectedBar(coord: m_selectedBar, series: m_selectedBarSeries, enterSlice: false);
579}
580
581void QQuickGraphsBars::handleAxisRangeChangedBySender(QObject *sender)
582{
583 // Data window changed
584 if (sender == m_axisX || sender == m_axisZ) {
585 if (sender == m_axisX)
586 handleDataColumnLabelsChanged();
587 if (sender == m_axisZ)
588 handleDataRowLabelsChanged();
589 }
590
591 QQuickGraphsItem::handleAxisRangeChangedBySender(sender);
592
593 setDataDirty(true);
594
595 // Update selected bar - may be moved offscreen
596 setSelectedBar(coord: m_selectedBar, series: m_selectedBarSeries, enterSlice: false);
597}
598
599void QQuickGraphsBars::adjustAxisRanges()
600{
601 QCategory3DAxis *categoryAxisZ = static_cast<QCategory3DAxis *>(m_axisZ);
602 QCategory3DAxis *categoryAxisX = static_cast<QCategory3DAxis *>(m_axisX);
603 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(m_axisY);
604
605 bool adjustZ = (categoryAxisZ && categoryAxisZ->isAutoAdjustRange());
606 bool adjustX = (categoryAxisX && categoryAxisX->isAutoAdjustRange());
607 bool adjustY = (valueAxis && categoryAxisX && categoryAxisZ && valueAxis->isAutoAdjustRange());
608
609 if (adjustZ || adjustX || adjustY) {
610 qsizetype maxRowCount = 0;
611 qsizetype maxColumnCount = 0;
612 float minValue = 0.0f;
613 float maxValue = 0.0f;
614
615 // First figure out row and column counts
616 qsizetype seriesCount = m_seriesList.size();
617 if (adjustZ || adjustX) {
618 for (qsizetype series = 0; series < seriesCount; series++) {
619 const QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(m_seriesList.at(i: series));
620 if (barSeries->isVisible()) {
621 const QBarDataProxy *proxy = barSeries->dataProxy();
622
623 if (adjustZ && proxy) {
624 qsizetype rowCount = proxy->rowCount();
625 if (rowCount)
626 rowCount--;
627
628 maxRowCount = qMax(a: maxRowCount, b: rowCount);
629 }
630
631 if (adjustX && proxy) {
632 const QBarDataArray &array = barSeries->dataArray();
633 qsizetype columnCount = 0;
634 for (int i = 0; i < array.size(); i++) {
635 if (columnCount < array.at(i).size())
636 columnCount = array.at(i).size();
637 }
638 if (columnCount)
639 columnCount--;
640
641 maxColumnCount = qMax(a: maxColumnCount, b: columnCount);
642 }
643 }
644 }
645 // Call private implementations of setRange to avoid unsetting auto adjust
646 // flag
647 if (adjustZ)
648 categoryAxisZ->d_func()->setRange(min: 0.0f, max: float(maxRowCount), suppressWarnings: true);
649 if (adjustX)
650 categoryAxisX->d_func()->setRange(min: 0.0f, max: float(maxColumnCount), suppressWarnings: true);
651 }
652
653 // Now that we know the row and column ranges, figure out the value axis
654 // range
655 if (adjustY) {
656 for (int series = 0; series < seriesCount; series++) {
657 const QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(m_seriesList.at(i: series));
658 if (barSeries->isVisible()) {
659 const QBarDataProxy *proxy = barSeries->dataProxy();
660 if (adjustY && proxy) {
661 QPair<float, float> limits = proxy->d_func()
662 ->limitValues(startRow: categoryAxisZ->min(),
663 startColumn: categoryAxisZ->max(),
664 rowCount: categoryAxisX->min(),
665 columnCount: categoryAxisX->max());
666 if (!series) {
667 // First series initializes the values
668 minValue = limits.first;
669 maxValue = limits.second;
670 } else {
671 minValue = qMin(a: minValue, b: limits.first);
672 maxValue = qMax(a: maxValue, b: limits.second);
673 }
674 }
675 }
676 }
677
678 if (maxValue < 0.0f)
679 maxValue = 0.0f;
680 if (minValue > 0.0f)
681 minValue = 0.0f;
682 if (minValue == 0.0f && maxValue == 0.0f) {
683 // Only zero value values in data set, set range to something.
684 minValue = 0.0f;
685 maxValue = 1.0f;
686 }
687 valueAxis->d_func()->setRange(min: minValue, max: maxValue, suppressWarnings: true);
688 }
689 }
690}
691
692void QQuickGraphsBars::setFloorLevel(float level)
693{
694 if (level != floorLevel()) {
695 m_floorLevel = level;
696 setDataDirty(true);
697 m_changeTracker.floorLevelChanged = true;
698 emitNeedRender();
699 emit floorLevelChanged(level);
700 }
701}
702
703float QQuickGraphsBars::floorLevel() const
704{
705 return m_floorLevel;
706}
707
708void QQuickGraphsBars::componentComplete()
709{
710 QQuickGraphsItem::componentComplete();
711
712 auto wallBackground = background();
713 QUrl wallUrl = QUrl(QStringLiteral("defaultMeshes/backgroundNoFloorMesh"));
714 wallBackground->setSource(wallUrl);
715 setBackground(wallBackground);
716
717 QUrl floorUrl = QUrl(QStringLiteral(":/defaultMeshes/barMeshFull"));
718 m_floorBackground = new QQuick3DModel();
719 m_floorBackgroundScale = new QQuick3DNode();
720 m_floorBackgroundRotation = new QQuick3DNode();
721
722 m_floorBackgroundScale->setParent(rootNode());
723 m_floorBackgroundScale->setParentItem(rootNode());
724
725 m_floorBackgroundRotation->setParent(m_floorBackgroundScale);
726 m_floorBackgroundRotation->setParentItem(m_floorBackgroundScale);
727
728 m_floorBackground->setObjectName("Floor Background");
729 m_floorBackground->setParent(m_floorBackgroundRotation);
730 m_floorBackground->setParentItem(m_floorBackgroundRotation);
731
732 m_floorBackground->setSource(floorUrl);
733
734 setFloorGridInRange(true);
735 setVerticalSegmentLine(false);
736
737 QObject::connect(sender: cameraTarget(),
738 signal: &QQuick3DNode::rotationChanged,
739 context: this,
740 slot: &QQuickGraphsBars::handleCameraRotationChanged);
741
742 graphsInputHandler()->setGraphsItem(this);
743}
744
745void QQuickGraphsBars::synchData()
746{
747 if (!m_noZeroInRange) {
748 setMinCameraYRotation(-90.0f);
749 setMaxCameraYRotation(90.0f);
750 } else {
751 if ((m_hasNegativeValues && !valueAxis()->reversed())
752 || (!m_hasNegativeValues && valueAxis()->reversed())) {
753 setMinCameraYRotation(-90.0f);
754 setMaxCameraYRotation(0.0f);
755 } else {
756 setMinCameraYRotation(0.0f);
757 setMaxCameraYRotation(90.0f);
758 }
759 }
760 if (m_changeTracker.barSpecsChanged || !m_cachedBarThickness.isValid()) {
761 updateBarSpecs(thicknessRatio: m_barThicknessRatio, spacing: m_barSpacing, relative: m_isBarSpecRelative);
762 m_changeTracker.barSpecsChanged = false;
763 }
764
765 // Floor level update requires data update, so do before qquickgraphicsitem sync
766 if (m_changeTracker.floorLevelChanged) {
767 updateFloorLevel(level: m_floorLevel);
768 m_changeTracker.floorLevelChanged = false;
769 }
770
771 if (m_changeTracker.multiSeriesScalingChanged) {
772 m_keepSeriesUniform = m_isMultiSeriesUniform;
773 setSeriesVisualsDirty(true);
774 m_changeTracker.multiSeriesScalingChanged = false;
775 }
776
777 // Do not clear dirty flag, we need to react to it in qquickgraphicsitem as well
778 if (theme()->dirtyBits()->plotAreaBackgroundVisibilityDirty) {
779 setSeriesVisualsDirty(true);
780 for (auto it = m_barModelsMap.begin(); it != m_barModelsMap.end(); it++)
781 it.key()->d_func()->m_changeTracker.meshChanged = true;
782 }
783
784 if (m_changeTracker.barSeriesMarginChanged) {
785 updateBarSeriesMargin(margin: barSeriesMargin());
786 m_changeTracker.barSeriesMarginChanged = false;
787 }
788
789 if (m_axisRangeChanged) {
790 theme()->resetDirtyBits();
791 m_axisRangeChanged = false;
792 }
793
794 QQuickGraphsItem::synchData();
795
796 // Draw floor
797
798 //margin for a line to be fully visible on the edge in the grid shader
799 const float halfLineWidth = 50.0;
800 const float gridTextureSize = 4096.0;
801 const float gridMargin = halfLineWidth / gridTextureSize;
802 m_floorBackground->setPickable(false);
803 auto min = qMin(a: scaleWithBackground().x(), b: scaleWithBackground().z());
804 m_floorBackgroundScale->setScale(QVector3D(scaleWithBackground().x() + gridMargin,
805 min * gridOffset(),
806 scaleWithBackground().z() + gridMargin));
807 m_floorBackgroundScale->setPosition(QVector3D(0.0f, -m_backgroundAdjustment, 0.0f));
808
809 QQuaternion m_xRightAngleRotation(QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: 90.0f));
810 QQuaternion m_xRightAngleRotationNeg(QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: -90.0f));
811
812 if (isYFlipped())
813 m_floorBackgroundRotation->setRotation(m_xRightAngleRotation);
814 else
815 m_floorBackgroundRotation->setRotation(m_xRightAngleRotationNeg);
816
817 QQmlListReference materialsRefF(m_floorBackground, "materials");
818 QQmlListReference bbRef(background(), "materials");
819 QQuick3DCustomMaterial *bgMatFloor;
820 if (!materialsRefF.size() && bbRef.size()) {
821 bgMatFloor = static_cast<QQuick3DCustomMaterial *>(bbRef.at(0));
822 materialsRefF.append(bgMatFloor);
823 }
824 if (m_selectedBarPos.isNull())
825 itemLabel()->setVisible(false);
826}
827
828void QQuickGraphsBars::updateParameters()
829{
830 m_minRow = m_axisZ->min();
831 m_maxRow = m_axisZ->max();
832 m_minCol = m_axisX->min();
833 m_maxCol = m_axisX->max();
834 m_newRows = m_maxRow - m_minRow + 1;
835 m_newCols = m_maxCol - m_minCol + 1;
836
837 if (m_cachedRowCount != m_newRows || m_cachedColumnCount != m_newCols) {
838 m_changeTracker.selectedBarChanged = true;
839 m_cachedColumnCount = m_newCols;
840 m_cachedRowCount = m_newRows;
841
842 // Calculate max scene size
843 float sceneRatio = qMin(a: float(m_newCols) / float(m_newRows),
844 b: float(m_newRows) / float(m_newCols));
845 m_maxSceneSize = 2.0f * qSqrt(v: sceneRatio * m_newCols * m_newRows);
846
847 if (m_cachedBarThickness.isValid())
848 calculateSceneScalingFactors();
849 }
850 m_axisRangeChanged = true;
851 setDataDirty(true);
852}
853
854void QQuickGraphsBars::updateFloorLevel(float level)
855{
856 setFloorLevel(level);
857 calculateHeightAdjustment();
858}
859
860void QQuickGraphsBars::updateGraph()
861{
862 QList<QBar3DSeries *> barSeriesAsList = barSeriesList();
863 calculateSceneScalingFactors();
864
865 for (const auto &series : std::as_const(t&: barSeriesAsList)) {
866 QAbstract3DSeriesChangeBitField &changeTracker = series->d_func()->m_changeTracker;
867 if (changeTracker.meshChanged || changeTracker.meshSmoothChanged) {
868 changeTracker.meshChanged = false;
869 changeTracker.meshSmoothChanged = false;
870 setDataDirty(true);
871 }
872 }
873
874 if (isDataDirty()) {
875 removeBarModels();
876 generateBars(barSeriesList&: barSeriesAsList);
877 }
878
879 if (isSeriesVisualsDirty()) {
880 int visualIndex = 0;
881 for (const auto &barSeries : std::as_const(t&: barSeriesAsList)) {
882 if (barSeries->isVisible()) {
883 updateBarVisuality(series: barSeries, visualIndex);
884 updateBarPositions(series: barSeries);
885 updateBarVisuals(series: barSeries);
886 ++visualIndex;
887 } else {
888 updateBarVisuality(series: barSeries, visualIndex: -1);
889 }
890 }
891 }
892
893 // Needs to be done after data is set, as it needs to know the visual array.
894 if (m_changeTracker.selectedBarChanged) {
895 updateSelectedBar();
896 setItemSelected(m_selectedBar != invalidSelectionPosition());
897 if (isSliceEnabled()) {
898 createSliceView();
899 for (const auto &series : std::as_const(t&: barSeriesAsList)) {
900 bool visible = (m_selectedBarSeries == series) && series->isVisible();
901 if (sliceView()->isVisible()) {
902 if (visible) {
903 removeSlicedBarModels();
904 createSliceView();
905 setSliceActivatedChanged(false);
906 toggleSliceGraph();
907 break;
908 } else {
909 setSliceActivatedChanged(true);
910 }
911 } else {
912 if (visible) {
913 m_selectionDirty = true;
914 setSliceActivatedChanged(true);
915 }
916 }
917 }
918 }
919 m_changeTracker.selectedBarChanged = false;
920 }
921
922 setDataDirty(false);
923 setSeriesVisualsDirty(false);
924}
925
926void QQuickGraphsBars::updateAxisRange(float min, float max)
927{
928 QQuickGraphsItem::updateAxisRange(min, max);
929 calculateHeightAdjustment();
930}
931
932void QQuickGraphsBars::updateAxisReversed(bool enable)
933{
934 Q_UNUSED(enable);
935 setSeriesVisualsDirty(true);
936 calculateHeightAdjustment();
937}
938
939void QQuickGraphsBars::updateLightStrength()
940{
941 for (const auto list : std::as_const(t&: m_barModelsMap)) {
942 for (auto barModel : *list) {
943 QQmlListReference materialRef(barModel->model, "materials");
944 if (materialRef.size()) {
945 QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(
946 object: materialRef.at(0));
947 material->setProperty(name: "specularBrightness", value: lightStrength() * 0.05);
948 }
949 }
950 }
951}
952
953void QQuickGraphsBars::calculateSceneScalingFactors()
954{
955 m_rowWidth = (m_cachedColumnCount * m_cachedBarSpacing.width()) * 0.5f;
956 m_columnDepth = (m_cachedRowCount * m_cachedBarSpacing.height()) * 0.5f;
957 m_maxDimension = qMax(a: m_rowWidth, b: m_columnDepth);
958 m_scaleFactor = qMin(a: (m_cachedColumnCount * (m_maxDimension / m_maxSceneSize)),
959 b: (m_cachedRowCount * (m_maxDimension / m_maxSceneSize)));
960
961 // Single bar scaling
962 m_xScale = m_cachedBarThickness.width() / m_scaleFactor;
963 m_zScale = m_cachedBarThickness.height() / m_scaleFactor;
964
965 // Adjust scaling according to margin
966 m_xScale = m_xScale - m_xScale * m_cachedBarSeriesMargin.width();
967 m_zScale = m_zScale - m_zScale * m_cachedBarSeriesMargin.height();
968
969 // Whole graph scale factors
970 m_xScaleFactor = m_rowWidth / m_scaleFactor;
971 m_zScaleFactor = m_columnDepth / m_scaleFactor;
972
973 if (margin() < 0.0f) {
974 m_hBackgroundMargin = 0.0f;
975 m_vBackgroundMargin = 0.0f;
976 } else {
977 m_hBackgroundMargin = margin();
978 m_vBackgroundMargin = margin();
979 }
980
981 auto scale = QVector3D(m_xScaleFactor, 1.0f, m_zScaleFactor);
982 setScaleWithBackground(scale);
983 setBackgroundScaleMargin({m_hBackgroundMargin, m_vBackgroundMargin, m_hBackgroundMargin});
984 setScale(scale);
985}
986
987void QQuickGraphsBars::calculateHeightAdjustment()
988{
989 float newAdjustment = 1.0f;
990 m_actualFloorLevel = qBound(min: valueAxis()->min(), val: floorLevel(), max: valueAxis()->max());
991 float maxAbs = qFabs(v: valueAxis()->max() - m_actualFloorLevel);
992
993 // Check if we have negative values
994 if (valueAxis()->min() < m_actualFloorLevel)
995 m_hasNegativeValues = true;
996 else if (valueAxis()->min() >= m_actualFloorLevel)
997 m_hasNegativeValues = false;
998
999 if (valueAxis()->max() < m_actualFloorLevel) {
1000 m_heightNormalizer = float(qFabs(v: valueAxis()->min()) - qFabs(v: valueAxis()->max()));
1001 maxAbs = qFabs(v: valueAxis()->max()) - qFabs(v: valueAxis()->min());
1002 } else {
1003 m_heightNormalizer = float(valueAxis()->max() - valueAxis()->min());
1004 }
1005
1006 // Height fractions are used in gradient calculations and are therefore
1007 // doubled Note that if max or min is exactly zero, we still consider it
1008 // outside the range
1009 if (valueAxis()->max() <= m_actualFloorLevel || valueAxis()->min() >= m_actualFloorLevel)
1010 m_noZeroInRange = true;
1011 else
1012 m_noZeroInRange = false;
1013
1014 // Calculate translation adjustment for background floor
1015 newAdjustment = (qBound(min: 0.0f, val: (maxAbs / m_heightNormalizer), max: 1.0f) - 0.5f) * 2.0f;
1016 if (valueAxis()->reversed())
1017 newAdjustment = -newAdjustment;
1018
1019 if (newAdjustment != m_backgroundAdjustment)
1020 m_backgroundAdjustment = newAdjustment;
1021}
1022
1023void QQuickGraphsBars::calculateSeriesStartPosition()
1024{
1025 m_seriesStart = -((float(m_visibleSeriesCount) - 1.0f) * 0.5f)
1026 * (m_seriesStep - (m_seriesStep * m_cachedBarSeriesMargin.width()));
1027}
1028
1029QVector3D QQuickGraphsBars::calculateCategoryLabelPosition(QAbstract3DAxis *axis,
1030 QVector3D labelPosition,
1031 int index)
1032{
1033 QVector3D ret = labelPosition;
1034 if (axis->orientation() == QAbstract3DAxis::AxisOrientation::X) {
1035 float xPos = (index + 0.5f) * m_cachedBarSpacing.width();
1036 ret.setX((xPos - m_rowWidth) / m_scaleFactor);
1037 }
1038 if (axis->orientation() == QAbstract3DAxis::AxisOrientation::Z) {
1039 float zPos = (index + 0.5f) * m_cachedBarSpacing.height();
1040 ret.setZ((m_columnDepth - zPos) / m_scaleFactor);
1041 }
1042 ret.setY(-m_backgroundAdjustment);
1043 return ret;
1044}
1045
1046float QQuickGraphsBars::calculateCategoryGridLinePosition(QAbstract3DAxis *axis, int index)
1047{
1048 float ret = 0.0f;
1049 if (axis->orientation() == QAbstract3DAxis::AxisOrientation::Z) {
1050 float colPos = index * -(m_cachedBarSpacing.height() / m_scaleFactor);
1051 ret = colPos + scale().z();
1052 }
1053 if (axis->orientation() == QAbstract3DAxis::AxisOrientation::X) {
1054 float rowPos = index * (m_cachedBarSpacing.width() / m_scaleFactor);
1055 ret = rowPos - scale().x();
1056 }
1057 if (axis->orientation() == QAbstract3DAxis::AxisOrientation::Y)
1058 ret = -m_backgroundAdjustment;
1059 return ret;
1060}
1061
1062void QQuickGraphsBars::handleAxisXChanged(QAbstract3DAxis *axis)
1063{
1064 emit columnAxisChanged(axis: static_cast<QCategory3DAxis *>(axis));
1065}
1066
1067void QQuickGraphsBars::handleAxisYChanged(QAbstract3DAxis *axis)
1068{
1069 emit valueAxisChanged(axis: static_cast<QValue3DAxis *>(axis));
1070}
1071
1072void QQuickGraphsBars::handleAxisZChanged(QAbstract3DAxis *axis)
1073{
1074 emit rowAxisChanged(axis: static_cast<QCategory3DAxis *>(axis));
1075}
1076
1077void QQuickGraphsBars::handleSeriesMeshChanged(QAbstract3DSeries::Mesh mesh)
1078{
1079 m_meshType = mesh;
1080}
1081
1082void QQuickGraphsBars::handleMeshSmoothChanged(bool enable)
1083{
1084 m_smooth = enable;
1085}
1086
1087void QQuickGraphsBars::handleCameraRotationChanged()
1088{
1089 updateLabels();
1090}
1091
1092void QQuickGraphsBars::handleArrayReset()
1093{
1094 QBar3DSeries *series;
1095 if (qobject_cast<QBarDataProxy *>(object: sender()))
1096 series = static_cast<QBarDataProxy *>(sender())->series();
1097 else
1098 series = static_cast<QBar3DSeries *>(sender());
1099
1100 if (series->isVisible()) {
1101 adjustAxisRanges();
1102 setDataDirty(true);
1103 series->d_func()->markItemLabelDirty();
1104 }
1105 if (!m_changedSeriesList.contains(t: series))
1106 m_changedSeriesList.append(t: series);
1107 // Clear selection unless still valid
1108 setSelectedBar(coord: m_selectedBar, series: m_selectedBarSeries, enterSlice: false);
1109 series->d_func()->markItemLabelDirty();
1110 emitNeedRender();
1111}
1112
1113void QQuickGraphsBars::handleRowsAdded(qsizetype startIndex, qsizetype count)
1114{
1115 Q_UNUSED(startIndex);
1116 Q_UNUSED(count);
1117 QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
1118 if (series->isVisible()) {
1119 adjustAxisRanges();
1120 setDataDirty(true);
1121 }
1122 if (!m_changedSeriesList.contains(t: series))
1123 m_changedSeriesList.append(t: series);
1124 emitNeedRender();
1125}
1126
1127void QQuickGraphsBars::handleRowsChanged(qsizetype startIndex, qsizetype count)
1128{
1129 QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
1130 qsizetype oldChangeCount = m_changedRows.size();
1131 if (!oldChangeCount)
1132 m_changedRows.reserve(asize: count);
1133
1134 for (int i = 0; i < count; i++) {
1135 bool newItem = true;
1136 qsizetype candidate = startIndex + i;
1137 for (qsizetype j = 0; j < oldChangeCount; j++) {
1138 const ChangeRow &oldChangeItem = m_changedRows.at(i: j);
1139 if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
1140 newItem = false;
1141 break;
1142 }
1143 }
1144 if (newItem) {
1145 ChangeRow newChangeItem = {.series: series, .row: candidate};
1146 m_changedRows.append(t: newChangeItem);
1147 if (series == m_selectedBarSeries && m_selectedBar.x() == candidate)
1148 series->d_func()->markItemLabelDirty();
1149 }
1150 }
1151 if (count) {
1152 m_changeTracker.rowsChanged = true;
1153
1154 if (series->isVisible())
1155 adjustAxisRanges();
1156
1157 // Clear selection unless still valid (row length might have changed)
1158 setSelectedBar(coord: m_selectedBar, series: m_selectedBarSeries, enterSlice: false);
1159 emitNeedRender();
1160 }
1161}
1162
1163void QQuickGraphsBars::handleRowsRemoved(qsizetype startIndex, qsizetype count)
1164{
1165 Q_UNUSED(startIndex);
1166 Q_UNUSED(count);
1167
1168 QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
1169 if (series == m_selectedBarSeries) {
1170 // If rows removed from selected series before the selection, adjust the
1171 // selection
1172 int selectedRow = m_selectedBar.x();
1173 if (startIndex <= selectedRow) {
1174 if ((startIndex + count) > selectedRow)
1175 selectedRow = -1; // Selected row removed
1176 else
1177 selectedRow -= count; // Move selected row down by amount of rows removed
1178
1179 setSelectedBar(coord: QPoint(selectedRow, m_selectedBar.y()), series: m_selectedBarSeries, enterSlice: false);
1180 }
1181 }
1182
1183 if (series->isVisible()) {
1184 adjustAxisRanges();
1185 setDataDirty(true);
1186 }
1187 if (!m_changedSeriesList.contains(t: series))
1188 m_changedSeriesList.append(t: series);
1189
1190 emitNeedRender();
1191}
1192
1193void QQuickGraphsBars::handleRowsInserted(qsizetype startIndex, qsizetype count)
1194{
1195 Q_UNUSED(startIndex);
1196 Q_UNUSED(count);
1197 QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
1198 if (series == m_selectedBarSeries) {
1199 // If rows inserted to selected series before the selection, adjust the
1200 // selection
1201 int selectedRow = m_selectedBar.x();
1202 if (startIndex <= selectedRow) {
1203 selectedRow += count;
1204 setSelectedBar(coord: QPoint(selectedRow, m_selectedBar.y()), series: m_selectedBarSeries, enterSlice: false);
1205 }
1206 }
1207
1208 if (series->isVisible()) {
1209 adjustAxisRanges();
1210 setDataDirty(true);
1211 }
1212 if (!m_changedSeriesList.contains(t: series))
1213 m_changedSeriesList.append(t: series);
1214
1215 emitNeedRender();
1216}
1217
1218void QQuickGraphsBars::handleItemChanged(qsizetype rowIndex, qsizetype columnIndex)
1219{
1220 QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
1221
1222 bool newItem = true;
1223 QPoint candidate((int(rowIndex)), (int(columnIndex)));
1224 for (ChangeItem item : m_changedItems) {
1225 if (item.point == candidate && item.series == series) {
1226 newItem = false;
1227 setDataDirty(true);
1228 break;
1229 }
1230 }
1231
1232 if (newItem) {
1233 ChangeItem newItem = {.series: series, .point: candidate};
1234 m_changedItems.append(t: newItem);
1235 m_changeTracker.itemChanged = true;
1236
1237 if (series == m_selectedBarSeries && m_selectedBar == candidate)
1238 series->d_func()->markItemLabelDirty();
1239 if (series->isVisible())
1240 adjustAxisRanges();
1241 emitNeedRender();
1242 }
1243}
1244
1245void QQuickGraphsBars::handleDataRowLabelsChanged()
1246{
1247 if (m_axisZ) {
1248 // Grab a sublist equal to data window (no need to have more labels in axis)
1249 int min = int(m_axisZ->min());
1250 int count = int(m_axisZ->max()) - min + 1;
1251 QStringList subList;
1252 if (m_primarySeries && m_primarySeries->dataProxy())
1253 subList = m_primarySeries->rowLabels().mid(pos: min, len: count);
1254 static_cast<QCategory3DAxis *>(m_axisZ)->d_func()->setDataLabels(subList);
1255 }
1256
1257 if (repeaterZ()) {
1258 updateParameters();
1259 repeaterZ()->setModel(m_axisZ->labels().size());
1260 }
1261}
1262
1263void QQuickGraphsBars::handleDataColumnLabelsChanged()
1264{
1265 if (m_axisX) {
1266 // Grab a sublist equal to data window (no need to have more labels in axis)
1267 int min = int(m_axisX->min());
1268 int count = int(m_axisX->max()) - min + 1;
1269 QStringList subList;
1270 if (m_primarySeries && m_primarySeries->dataProxy())
1271 subList = m_primarySeries->columnLabels().mid(pos: min, len: count);
1272 static_cast<QCategory3DAxis *>(m_axisX)->d_func()->setDataLabels(subList);
1273 }
1274
1275 if (repeaterX()) {
1276 updateParameters();
1277 repeaterX()->setModel(m_axisX->labels().size());
1278 }
1279}
1280
1281void QQuickGraphsBars::handleRowColorsChanged()
1282{
1283 setSeriesVisualsDirty(true);
1284 emitNeedRender();
1285}
1286
1287void QQuickGraphsBars::connectSeries(QBar3DSeries *series)
1288{
1289 m_meshType = series->mesh();
1290 m_smooth = series->isMeshSmooth();
1291
1292 QObject::connect(sender: series,
1293 signal: &QBar3DSeries::meshChanged,
1294 context: this,
1295 slot: &QQuickGraphsBars::handleSeriesMeshChanged);
1296 QObject::connect(sender: series,
1297 signal: &QBar3DSeries::meshSmoothChanged,
1298 context: this,
1299 slot: &QQuickGraphsBars::handleMeshSmoothChanged);
1300 QObject::connect(sender: series->dataProxy(),
1301 signal: &QBarDataProxy::colCountChanged,
1302 context: this,
1303 slot: &QQuickGraphsBars::handleDataColumnLabelsChanged);
1304 QObject::connect(sender: series->dataProxy(),
1305 signal: &QBarDataProxy::rowCountChanged,
1306 context: this,
1307 slot: &QQuickGraphsBars::handleDataRowLabelsChanged);
1308 QObject::connect(sender: series,
1309 signal: &QBar3DSeries::rowColorsChanged,
1310 context: this,
1311 slot: &QQuickGraphsBars::handleRowColorsChanged);
1312}
1313
1314void QQuickGraphsBars::disconnectSeries(QBar3DSeries *series)
1315{
1316 QObject::disconnect(sender: series, signal: 0, receiver: this, member: 0);
1317}
1318
1319void QQuickGraphsBars::generateBars(QList<QBar3DSeries *> &barSeriesList)
1320{
1321 m_visibleSeriesCount = 0;
1322 for (const auto &barSeries : std::as_const(t&: barSeriesList)) {
1323 QQuick3DTexture *texture = createTexture();
1324 texture->setParent(this);
1325 auto gradient = barSeries->baseGradient();
1326 auto textureData = static_cast<QQuickGraphsTextureData *>(texture->textureData());
1327 textureData->createGradient(gradient);
1328
1329 bool visible = barSeries->isVisible();
1330
1331 QList<BarModel *> *barList = m_barModelsMap.value(key: barSeries);
1332
1333 if (!barList) {
1334 barList = new QList<BarModel *>;
1335 m_barModelsMap[barSeries] = barList;
1336 }
1337
1338 if (barList->isEmpty()) {
1339 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1340 QBarDataProxy *dataProxy = barSeries->dataProxy();
1341 qsizetype dataRowIndex = m_minRow;
1342 qsizetype newRowSize = qMin(a: dataProxy->rowCount() - dataRowIndex, b: m_newRows);
1343
1344 for (int row = 0; row < newRowSize; ++row) {
1345 const QBarDataRow &dataRow = dataProxy->rowAt(rowIndex: dataRowIndex);
1346 if (!dataRow.isEmpty()) {
1347 qsizetype dataColIndex = m_minCol;
1348 qsizetype newColSize = qMin(a: dataRow.size() - dataColIndex, b: m_newCols);
1349 for (int col = 0; col < newColSize; ++col) {
1350 QBarDataItem &dataItem = const_cast<QBarDataItem &>(
1351 dataRow.at(i: dataColIndex));
1352 auto scene = QQuick3DViewport::scene();
1353 QQuick3DModel *model = createDataItem(scene, series: barSeries);
1354 model->setVisible(visible);
1355
1356 BarModel *barModel = new BarModel();
1357 barModel->model = model;
1358 barModel->barItem = &dataItem;
1359 barModel->coord = QPoint(int(dataRowIndex), col);
1360 barModel->texture = texture;
1361
1362 if (!barList->contains(t: barModel))
1363 barList->append(t: barModel);
1364
1365 ++dataColIndex;
1366 }
1367 ++dataRowIndex;
1368 }
1369 }
1370 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1371 auto scene = QQuick3DViewport::scene();
1372 BarModel *barInstancing = new BarModel();
1373 barInstancing->texture = texture;
1374
1375 if (barInstancing->instancing == nullptr) {
1376 barInstancing->instancing = new BarInstancing;
1377 barInstancing->instancing->setParent(barSeries);
1378 barInstancing->selectionInstancing = new BarInstancing;
1379 barInstancing->selectionInstancing->setParent(barSeries);
1380 barInstancing->multiSelectionInstancing = new BarInstancing;
1381 barInstancing->multiSelectionInstancing->setParent(barSeries);
1382 }
1383
1384 if (barInstancing->model == nullptr) {
1385 barInstancing->model = createDataItem(scene, series: barSeries);
1386 barInstancing->model->setInstancing(barInstancing->instancing);
1387 barInstancing->model->setVisible(visible);
1388 barInstancing->model->setPickable(true);
1389 barInstancing->selectedModel = createDataItem(scene, series: barSeries);
1390 barInstancing->selectedModel->setInstancing(barInstancing->selectionInstancing);
1391 barInstancing->selectedModel->setVisible(visible);
1392 barInstancing->selectedModel->setPickable(true);
1393 barInstancing->multiSelectedModel = createDataItem(scene, series: barSeries);
1394 barInstancing->multiSelectedModel->setInstancing(
1395 barInstancing->multiSelectionInstancing);
1396 barInstancing->multiSelectedModel->setVisible(visible);
1397 barInstancing->multiSelectedModel->setPickable(true);
1398 }
1399
1400 if (!barList->contains(t: barInstancing))
1401 barList->append(t: barInstancing);
1402 }
1403
1404 markSeriesVisualsDirty();
1405 }
1406
1407 if (barSeries->isVisible())
1408 m_visibleSeriesCount++;
1409 }
1410}
1411
1412QQuick3DModel *QQuickGraphsBars::createDataItem(QQuick3DNode *scene, QAbstract3DSeries *series)
1413{
1414 auto model = new QQuick3DModel();
1415 model->setParent(scene);
1416 model->setParentItem(scene);
1417 model->setObjectName(QStringLiteral("BarModel"));
1418 QString fileName = getMeshFileName();
1419 if (fileName.isEmpty())
1420 fileName = series->userDefinedMesh();
1421
1422 model->setSource(QUrl(fileName));
1423 return model;
1424}
1425
1426QString QQuickGraphsBars::getMeshFileName()
1427{
1428 QString fileName = {};
1429 switch (m_meshType) {
1430 case QAbstract3DSeries::Mesh::Sphere:
1431 fileName = QStringLiteral("defaultMeshes/sphereMesh");
1432 break;
1433 case QAbstract3DSeries::Mesh::Bar:
1434 case QAbstract3DSeries::Mesh::Cube:
1435 fileName = QStringLiteral("defaultMeshes/barMesh");
1436 break;
1437 case QAbstract3DSeries::Mesh::Pyramid:
1438 fileName = QStringLiteral("defaultMeshes/pyramidMesh");
1439 break;
1440 case QAbstract3DSeries::Mesh::Cone:
1441 fileName = QStringLiteral("defaultMeshes/coneMesh");
1442 break;
1443 case QAbstract3DSeries::Mesh::Cylinder:
1444 fileName = QStringLiteral("defaultMeshes/cylinderMesh");
1445 break;
1446 case QAbstract3DSeries::Mesh::BevelBar:
1447 case QAbstract3DSeries::Mesh::BevelCube:
1448 fileName = QStringLiteral("defaultMeshes/bevelBarMesh");
1449 break;
1450 case QAbstract3DSeries::Mesh::UserDefined:
1451 break;
1452 default:
1453 fileName = QStringLiteral("defaultMeshes/sphereMesh");
1454 }
1455
1456 fixMeshFileName(fileName, meshType: m_meshType);
1457
1458 return fileName;
1459}
1460
1461void QQuickGraphsBars::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh meshType)
1462{
1463 // Should it be smooth?
1464 if (m_smooth && meshType != QAbstract3DSeries::Mesh::Point
1465 && meshType != QAbstract3DSeries::Mesh::UserDefined) {
1466 fileName += QStringLiteral("Smooth");
1467 }
1468
1469 // Should it be filled?
1470 if (!theme()->isPlotAreaBackgroundVisible() && meshType != QAbstract3DSeries::Mesh::Sphere
1471 && meshType != QAbstract3DSeries::Mesh::Point
1472 && meshType != QAbstract3DSeries::Mesh::UserDefined) {
1473 fileName.append(QStringLiteral("Full"));
1474 }
1475}
1476
1477void QQuickGraphsBars::updateBarVisuality(QBar3DSeries *series, int visualIndex)
1478{
1479 QList<BarModel *> barList = *m_barModelsMap.value(key: series);
1480 for (int i = 0; i < barList.count(); i++) {
1481 barList.at(i)->visualIndex = visualIndex;
1482 barList.at(i)->model->setVisible(series->isVisible());
1483 if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1484 barList.at(i: 0)->selectedModel->setVisible(false);
1485 barList.at(i: 0)->multiSelectedModel->setVisible(false);
1486 }
1487 }
1488
1489 m_changeTracker.selectedBarChanged = true;
1490 itemLabel()->setVisible(false);
1491}
1492
1493void QQuickGraphsBars::updateBarPositions(QBar3DSeries *series)
1494{
1495 QBarDataProxy *dataProxy = series->dataProxy();
1496
1497 m_seriesScaleX = 1.0f / float(m_visibleSeriesCount);
1498 m_seriesStep = 1.0f / float(m_visibleSeriesCount);
1499 m_seriesStart = -((float(m_visibleSeriesCount) - 1.0f) * 0.5f)
1500 * (m_seriesStep - (m_seriesStep * m_cachedBarSeriesMargin.width()));
1501
1502 if (m_keepSeriesUniform)
1503 m_seriesScaleZ = m_seriesScaleX;
1504 else
1505 m_seriesScaleZ = 1.0f;
1506
1507 m_zeroPosition = valueAxis()->positionAt(x: m_actualFloorLevel);
1508
1509 QList<BarModel *> barList = *m_barModelsMap.value(key: series);
1510
1511 qsizetype dataRowIndex = m_minRow;
1512 qsizetype newRowSize = qMin(a: dataProxy->rowCount() - dataRowIndex, b: m_newRows);
1513 qsizetype row = 0;
1514 qsizetype dataColIndex = m_minCol;
1515 qsizetype newColSize = qMin(a: dataProxy->colCount() - dataColIndex, b: m_newCols);
1516 qsizetype col = 0;
1517 for (int i = 0; i < barList.count(); i++) {
1518 float seriesPos = m_seriesStart + 0.5f
1519 + (m_seriesStep
1520 * (barList.at(i)->visualIndex
1521 - (barList.at(i)->visualIndex * m_cachedBarSeriesMargin.width())));
1522 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1523 QBarDataItem *item = barList.at(i)->barItem;
1524 QQuick3DModel *model = barList.at(i)->model;
1525 float heightValue = updateBarHeightParameters(item);
1526 float angle = item->rotation();
1527
1528 if (angle) {
1529 model->setRotation(QQuaternion::fromAxisAndAngle(axis: upVector, angle)
1530 + series->meshRotation());
1531 } else {
1532 model->setRotation(QQuaternion() + series->meshRotation());
1533 }
1534
1535 if (heightValue < 0.f) {
1536 const QVector3D rot = model->eulerRotation();
1537 model->setEulerRotation(QVector3D(-180.f, rot.y(), rot.z()));
1538 }
1539
1540 float colPos = (col + seriesPos) * m_cachedBarSpacing.width();
1541 float xPos = (colPos - m_rowWidth) / m_scaleFactor;
1542 float rowPos = (row + 0.5f) * (m_cachedBarSpacing.height());
1543 float zPos = (m_columnDepth - rowPos) / m_scaleFactor;
1544 float yPos;
1545
1546 if (heightValue < 0.f)
1547 yPos = heightValue - m_backgroundAdjustment - 0.015f;
1548 else
1549 yPos = heightValue - m_backgroundAdjustment + 0.015f;
1550
1551 barList.at(i)->heightValue = heightValue;
1552 model->setPosition(QVector3D(xPos, yPos, zPos));
1553
1554 if (heightValue == 0) {
1555 model->setScale(QVector3D(.0f, .0f, .0f));
1556 model->setPickable(false);
1557 } else {
1558 model->setScale(QVector3D(m_xScale * m_seriesScaleX,
1559 qAbs(t: heightValue),
1560 m_zScale * m_seriesScaleZ));
1561 model->setPickable(true);
1562 }
1563
1564 if (col < newColSize - 1) {
1565 ++col;
1566 } else {
1567 col = 0;
1568 if (row < newRowSize - 1)
1569 ++row;
1570 else
1571 row = 0;
1572 }
1573 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1574 deleteBarItemHolders(instancing: barList.at(i)->instancing);
1575 QList<BarItemHolder *> positions;
1576 for (int row = 0; row < newRowSize; ++row) {
1577 const QBarDataRow &dataRow = dataProxy->rowAt(rowIndex: dataRowIndex);
1578 if (!dataRow.isEmpty()) {
1579 dataColIndex = m_minCol;
1580 newColSize = qMin(a: dataRow.size() - dataColIndex, b: m_newCols);
1581 for (int col = 0; col < newColSize; col++) {
1582 const QBarDataItem &item = dataRow.at(i: dataColIndex);
1583 float heightValue = updateBarHeightParameters(item: &item);
1584 BarItemHolder *bih = new BarItemHolder();
1585
1586 if (barList.at(i)->model->eulerRotation() != QVector3D()
1587 || series->meshRotation() != QQuaternion()) {
1588 const QQuaternion rotation =
1589 QQuaternion(barList.at(i)->model->eulerRotation().toVector4D())
1590 + series->meshRotation();
1591
1592 bih->rotation = rotation;
1593 if (heightValue < 0.f) {
1594 bih->rotation = QQuaternion(
1595 QVector3D(-180.f, rotation.y(), rotation.z()).toVector4D());
1596 }
1597 } else {
1598 bih->rotation = QQuaternion::fromEulerAngles(
1599 eulerAngles: QVector3D(.0f, item.rotation(), .0f));
1600 }
1601
1602 float colPos = (col + seriesPos) * m_cachedBarSpacing.width();
1603 float xPos = (colPos - m_rowWidth) / m_scaleFactor;
1604 float rowPos = (row + 0.5f) * (m_cachedBarSpacing.height());
1605 float zPos = (m_columnDepth - rowPos) / m_scaleFactor;
1606 float yPos;
1607
1608 if (heightValue < 0.f)
1609 yPos = heightValue - m_backgroundAdjustment - 0.015f;
1610 else
1611 yPos = heightValue - m_backgroundAdjustment + 0.015f;
1612
1613 bih->position = {xPos, yPos, zPos};
1614 bih->coord = QPoint(row, col);
1615
1616 if (heightValue == 0) {
1617 bih->scale = {.0f, .0f, .0f};
1618 } else {
1619 bih->scale = {m_xScale * m_seriesScaleX,
1620 qAbs(t: heightValue),
1621 m_zScale * m_seriesScaleZ};
1622 }
1623
1624 bih->heightValue = heightValue;
1625 bih->selectedBar = false;
1626
1627 bool colorStyleIsUniform = (series->colorStyle()
1628 == QGraphsTheme::ColorStyle::Uniform);
1629 if (colorStyleIsUniform) {
1630 QList<QColor> rowColors = series->rowColors();
1631 if (rowColors.size() == 0) {
1632 bih->color = series->baseColor();
1633 } else {
1634 int rowColorIndex = bih->coord.x() % rowColors.size();
1635 bih->color = rowColors[rowColorIndex];
1636 }
1637 }
1638
1639 positions.push_back(t: bih);
1640 dataColIndex++;
1641 }
1642 }
1643 dataRowIndex++;
1644 }
1645 barList.at(i)->instancing->setDataArray(positions);
1646 }
1647 }
1648}
1649
1650float QQuickGraphsBars::updateBarHeightParameters(const QBarDataItem *item)
1651{
1652 float value = item->value();
1653 float heightValue = valueAxis()->positionAt(x: value);
1654
1655 if (m_noZeroInRange) {
1656 if (m_hasNegativeValues) {
1657 heightValue = -1.0f + heightValue;
1658 if (heightValue > 0.0f)
1659 heightValue = 0.0f;
1660 } else {
1661 if (heightValue < 0.0f)
1662 heightValue = 0.0f;
1663 }
1664 } else {
1665 heightValue -= m_zeroPosition;
1666 }
1667
1668 if (valueAxis()->reversed())
1669 heightValue = -heightValue;
1670
1671 return heightValue;
1672}
1673
1674void QQuickGraphsBars::updateBarVisuals(QBar3DSeries *series)
1675{
1676 QList<BarModel *> barList = *m_barModelsMap.value(key: series);
1677 bool useGradient = series->d_func()->isUsingGradient();
1678
1679 if (useGradient) {
1680 if (!m_hasHighlightTexture) {
1681 m_highlightTexture = createTexture();
1682 m_highlightTexture->setParent(this);
1683 m_multiHighlightTexture = createTexture();
1684 m_multiHighlightTexture->setParent(this);
1685 m_hasHighlightTexture = true;
1686 }
1687 auto highlightGradient = series->singleHighlightGradient();
1688 auto highlightTextureData = static_cast<QQuickGraphsTextureData *>(
1689 m_highlightTexture->textureData());
1690 highlightTextureData->createGradient(gradient: highlightGradient);
1691 auto multiHighlightGradient = series->multiHighlightGradient();
1692 auto multiHighlightTextureData = static_cast<QQuickGraphsTextureData *>(
1693 m_multiHighlightTexture->textureData());
1694 multiHighlightTextureData->createGradient(gradient: multiHighlightGradient);
1695 } else {
1696 if (m_hasHighlightTexture) {
1697 m_highlightTexture->deleteLater();
1698 m_multiHighlightTexture->deleteLater();
1699 m_hasHighlightTexture = false;
1700 }
1701 }
1702
1703 bool rangeGradient = (useGradient
1704 && series->d_func()->m_colorStyle == QGraphsTheme::ColorStyle::RangeGradient);
1705 QColor baseColor = series->baseColor();
1706 QColor barColor;
1707 QLinearGradient gradient = series->baseGradient();
1708
1709 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1710 // Release resources that might not have been deleted even though deleteLater had been set
1711 window()->releaseResources();
1712
1713 for (int i = 0; i < barList.count(); i++) {
1714 QQuick3DModel *model = barList.at(i)->model;
1715 auto textureData = static_cast<QQuickGraphsTextureData *>(
1716 barList.at(i)->texture->textureData());
1717 textureData->createGradient(gradient: series->baseGradient());
1718 const bool transparency = textureData->hasTransparency();
1719 updateItemMaterial(item: model,
1720 useGradient,
1721 rangeGradient,
1722 QStringLiteral(":/materials/BarsMaterial"));
1723 if (useGradient) {
1724 updateMaterialProperties(item: model,
1725 isHighlight: false,
1726 isMultiHighlight: false,
1727 texture: barList.at(i)->texture,
1728 color: QColor(Qt::white),
1729 transparency);
1730 } else {
1731 QList<QColor> rowColors = series->rowColors();
1732 if (rowColors.size() == 0) {
1733 barColor = baseColor;
1734 } else {
1735 int rowColorIndex = barList.at(i)->coord.x() % rowColors.size();
1736 barColor = rowColors[rowColorIndex];
1737 }
1738 const bool transparency = barColor.alphaF() < 1.0;
1739 updateMaterialProperties(item: model,
1740 isHighlight: false,
1741 isMultiHighlight: false,
1742 texture: barList.at(i)->texture,
1743 color: barColor,
1744 transparency);
1745 }
1746 }
1747 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1748 for (int i = 0; i < barList.count(); i++) {
1749 auto textureData = static_cast<QQuickGraphsTextureData *>(
1750 barList.at(i)->texture->textureData());
1751 textureData->createGradient(gradient: series->baseGradient());
1752 barList.at(i)->instancing->setTransparency(textureData->hasTransparency());
1753 updateItemMaterial(item: barList.at(i)->model,
1754 useGradient,
1755 rangeGradient,
1756 QStringLiteral(":/materials/BarsMaterialInstancing"));
1757 if (useGradient) {
1758 updateMaterialProperties(item: barList.at(i)->model,
1759 isHighlight: false,
1760 isMultiHighlight: false,
1761 texture: barList.at(i)->texture,
1762 color: QColor(Qt::white),
1763 transparency: textureData->hasTransparency());
1764 } else {
1765 if (!barList.at(i)->instancing->dataArray().isEmpty()) {
1766 const bool transparency
1767 = barList.at(i)->instancing->dataArray().at(i: 0)->color.alphaF() < 1.0;
1768 updateMaterialProperties(item: barList.at(i)->model,
1769 isHighlight: false,
1770 isMultiHighlight: false,
1771 texture: barList.at(i)->texture,
1772 color: QColor(Qt::white),
1773 transparency);
1774 }
1775 }
1776 }
1777 }
1778}
1779
1780void QQuickGraphsBars::updateItemMaterial(QQuick3DModel *item,
1781 bool useGradient,
1782 bool rangeGradient,
1783 const QString &materialName)
1784{
1785 QQmlListReference materialsRef(item, "materials");
1786
1787 bool needNewMaterial = false;
1788 if (!materialsRef.size())
1789 needNewMaterial = true;
1790 else if (materialsRef.at(0)->objectName().contains(QStringLiteral("Instancing"))
1791 == materialName.contains(QStringLiteral("Instancing"))) {
1792 needNewMaterial = true;
1793 }
1794
1795 if (needNewMaterial) {
1796 materialsRef.clear();
1797 auto *material = createQmlCustomMaterial(fileName: materialName);
1798 material->setObjectName(materialName);
1799 material->setParent(item);
1800 materialsRef.append(material);
1801 }
1802 int colorStyle;
1803 if (!useGradient)
1804 colorStyle = 0; // style is uniform
1805 else if (!rangeGradient)
1806 colorStyle = 1; // style is objectGradient
1807 else
1808 colorStyle = 2; // style is rangeGradient
1809
1810 auto *material = qobject_cast<QQuick3DCustomMaterial *>(object: materialsRef.at(0));
1811 material->setProperty(name: "colorStyle", value: colorStyle);
1812}
1813
1814void QQuickGraphsBars::updateMaterialProperties(QQuick3DModel *item,
1815 const bool isHighlight,
1816 const bool isMultiHighlight,
1817 QQuick3DTexture *texture,
1818 QColor color,
1819 const bool transparency)
1820{
1821 QQmlListReference materialsRef(item, "materials");
1822 auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(object: materialsRef.at(0));
1823 if (!customMaterial)
1824 return;
1825 customMaterial->setProperty(name: "transparency", value: transparency);
1826 QVariant textureInputAsVariant = customMaterial->property(name: "custex");
1827 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
1828 .value<QQuick3DShaderUtilsTextureInput *>();
1829
1830 int colorStyle = customMaterial->property(name: "colorStyle").value<int>();
1831 if (colorStyle == 0) {
1832 customMaterial->setProperty(name: "uniformColor", value: color);
1833 } else {
1834 if (!isHighlight && !isMultiHighlight)
1835 textureInput->setTexture(texture);
1836 else
1837 textureInput->setTexture(isHighlight ? m_highlightTexture : m_multiHighlightTexture);
1838 customMaterial->setProperty(name: "isHighlight", value: isHighlight || isMultiHighlight);
1839 }
1840 customMaterial->setProperty(name: "specularBrightness", value: lightStrength() * 0.05);
1841}
1842
1843void QQuickGraphsBars::removeBarModels()
1844{
1845 for (const auto list : std::as_const(t&: m_barModelsMap)) {
1846 for (auto barModel : *list) {
1847 deleteBarModels(model: barModel->model);
1848 if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1849 deleteBarItemHolders(instancing: barModel->instancing);
1850 deleteBarItemHolders(instancing: barModel->selectionInstancing);
1851 deleteBarItemHolders(instancing: barModel->multiSelectionInstancing);
1852 deleteBarModels(model: barModel->selectedModel);
1853 deleteBarModels(model: barModel->multiSelectedModel);
1854 }
1855 delete barModel;
1856 }
1857 delete list;
1858 }
1859
1860 m_barModelsMap.clear();
1861 setSelectedBar(coord: m_selectedBar, series: m_selectedBarSeries, enterSlice: false);
1862}
1863
1864void QQuickGraphsBars::deleteBarModels(QQuick3DModel *model)
1865{
1866 if (model) {
1867 model->setPickable(false);
1868 model->setVisible(false);
1869 QQmlListReference materialsRef(model, "materials");
1870 if (materialsRef.size()) {
1871 auto material = materialsRef.at(0);
1872 delete material;
1873 }
1874 delete model;
1875 }
1876}
1877
1878void QQuickGraphsBars::deleteBarItemHolders(BarInstancing *instancing)
1879{
1880 if (instancing) {
1881 const QList<BarItemHolder *> barItemList = instancing->dataArray();
1882 for (const auto bih : barItemList)
1883 delete bih;
1884 instancing->clearDataArray();
1885 }
1886}
1887
1888QQuick3DTexture *QQuickGraphsBars::createTexture()
1889{
1890 QQuick3DTexture *texture = new QQuick3DTexture();
1891 texture->setParent(this);
1892 texture->setRotationUV(-90.0f);
1893 texture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
1894 texture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
1895 QQuickGraphsTextureData *textureData = new QQuickGraphsTextureData();
1896 textureData->setParent(texture);
1897 textureData->setParentItem(texture);
1898 texture->setTextureData(textureData);
1899
1900 return texture;
1901}
1902
1903bool QQuickGraphsBars::doPicking(QPointF position)
1904{
1905 if (!QQuickGraphsItem::doPicking(point: position))
1906 return false;
1907
1908 m_selectionDirty = true;
1909 QList<QQuick3DPickResult> pickResults = pickAll(x: position.x(), y: position.y());
1910 QQuick3DModel *selectedModel = nullptr;
1911 QVector3D instancePos = {.0f, .0f, .0f};
1912 if (!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None)) {
1913 if (!pickResults.isEmpty()) {
1914 for (const auto &picked : std::as_const(t&: pickResults)) {
1915 if (const auto &hit = picked.objectHit()) {
1916 if (hit == backgroundBB() || hit == background()) {
1917 resetClickedStatus();
1918 continue;
1919 } else if (hit->objectName().contains(QStringLiteral("BarModel"))) {
1920 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1921 selectedModel = hit;
1922 for (const auto barlist : std::as_const(t&: m_barModelsMap)) {
1923 for (const auto barModel : *barlist) {
1924 if (barModel->model == selectedModel) {
1925 setSelectedBar(coord: barModel->coord,
1926 series: m_barModelsMap.key(value: barlist),
1927 enterSlice: false);
1928 }
1929 }
1930 }
1931 break;
1932 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1933 BarInstancing *barIns = static_cast<BarInstancing *>(hit->instancing());
1934 // Prevents to select bars with a height of 0 which affect picking.
1935 if (!barIns->dataArray().isEmpty()
1936 && barIns->dataArray().at(i: picked.instanceIndex())->heightValue
1937 != 0) {
1938 selectedModel = hit;
1939 instancePos = selectedModel->instancing()->instancePosition(
1940 index: picked.instanceIndex());
1941 for (const auto barlist : std::as_const(t&: m_barModelsMap)) {
1942 for (const auto barModel : *barlist) {
1943 QList<BarItemHolder *> barItemList = barModel->instancing
1944 ->dataArray();
1945 for (const auto bih : barItemList) {
1946 if (bih->position == instancePos) {
1947 setSelectedBar(coord: bih->coord,
1948 series: m_barModelsMap.key(value: barlist),
1949 enterSlice: false);
1950 if (isSliceEnabled())
1951 setSliceActivatedChanged(true);
1952 }
1953 }
1954 }
1955 }
1956 break;
1957 }
1958 }
1959 } else if (hit->objectName().contains(QStringLiteral("ElementAxis"))) {
1960 QPoint coord = invalidSelectionPosition();
1961 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column)
1962 && selectedAxis() == axisX()) {
1963 // Use row from previous selection in case of row + column mode
1964 int previousRow = qMax(a: 0, b: m_selectedBar.x());
1965 coord = QPoint(previousRow, selectedLabelIndex());
1966 } else if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row)
1967 && selectedAxis() == axisZ()) {
1968 // Use column from previous selection in case of row + column mode
1969 int previousCol = qMax(a: 0, b: m_selectedBar.y());
1970 coord = QPoint(selectedLabelIndex(), previousCol);
1971 }
1972 for (auto it = m_barModelsMap.begin(); it != m_barModelsMap.end(); it++) {
1973 if (it.key()->isVisible())
1974 setSelectedBar(coord, series: it.key(), enterSlice: false);
1975 }
1976 break;
1977 }
1978 }
1979 }
1980 } else {
1981 resetClickedStatus();
1982 }
1983 }
1984 return true;
1985}
1986
1987QAbstract3DAxis *QQuickGraphsBars::createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation)
1988{
1989 QAbstract3DAxis *defaultAxis = 0;
1990
1991 if (orientation == QAbstract3DAxis::AxisOrientation::Y)
1992 defaultAxis = createDefaultValueAxis();
1993 else
1994 defaultAxis = createDefaultCategoryAxis();
1995
1996 return defaultAxis;
1997}
1998
1999void QQuickGraphsBars::adjustSelectionPosition(QPoint &pos, const QBar3DSeries *series)
2000{
2001 const QBarDataProxy *proxy = 0;
2002 if (series)
2003 proxy = series->dataProxy();
2004
2005 if (!proxy)
2006 pos = invalidSelectionPosition();
2007
2008 if (pos != invalidSelectionPosition()) {
2009 qsizetype maxRow = proxy->rowCount() - 1;
2010 qsizetype maxCol = (pos.x() <= maxRow && pos.x() >= 0 && !proxy->rowAt(rowIndex: pos.x()).isEmpty())
2011 ? proxy->rowAt(rowIndex: pos.x()).size() - 1
2012 : -1;
2013
2014 if (pos.x() < 0 || pos.x() > maxRow || pos.y() < 0 || pos.y() > maxCol)
2015 pos = invalidSelectionPosition();
2016 }
2017}
2018
2019void QQuickGraphsBars::setSelectedBar(QPoint coord, QBar3DSeries *series, bool enterSlice)
2020{
2021 // If the selection targets non-existent bar, clear selection instead.
2022 QPoint pos = coord;
2023
2024 // Series may already have been removed, so check it before setting the
2025 // selection.
2026 if (!m_seriesList.contains(t: series))
2027 series = nullptr;
2028
2029 adjustSelectionPosition(pos, series);
2030
2031 if (series && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
2032 // If the selected bar is outside data window, or there is no visible
2033 // selected bar, disable slicing.
2034 if (pos.x() < m_axisZ->min() || pos.x() > m_axisZ->max() || pos.y() < m_axisX->min()
2035 || pos.y() > m_axisX->max() || !series->isVisible()) {
2036 scene()->setSlicingActive(false);
2037 } else if (enterSlice) {
2038 scene()->setSlicingActive(true);
2039 }
2040 emitNeedRender();
2041 }
2042
2043 if (pos != m_selectedBar || series != m_selectedBarSeries) {
2044 bool seriesChanged = (series != m_selectedBarSeries);
2045 m_selectedBar = pos;
2046 m_selectedBarSeries = series;
2047 m_changeTracker.selectedBarChanged = true;
2048 checkSliceEnabled();
2049
2050 // Clear selection from other series and finally set new selection to the
2051 // specified series
2052 for (QAbstract3DSeries *otherSeries : m_seriesList) {
2053 QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(otherSeries);
2054 if (barSeries != m_selectedBarSeries)
2055 barSeries->d_func()->setSelectedBar(invalidSelectionPosition());
2056 }
2057 if (m_selectedBarSeries)
2058 m_selectedBarSeries->d_func()->setSelectedBar(m_selectedBar);
2059
2060 if (seriesChanged)
2061 emit selectedSeriesChanged(series: m_selectedBarSeries);
2062
2063 if (pos == invalidSelectionPosition())
2064 resetClickedStatus();
2065
2066 setSeriesVisualsDirty(true);
2067 emitNeedRender();
2068 }
2069}
2070
2071void QQuickGraphsBars::updateSelectedBar()
2072{
2073 for (auto it = m_barModelsMap.begin(); it != m_barModelsMap.end(); it++) {
2074 if (m_selectedBarSeries && it.key()->isVisible()) {
2075 QString label = m_selectedBarSeries->itemLabel();
2076 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
2077 for (auto barList : *it.value()) {
2078 QQuickGraphsBars::SelectionType selectionType = isSelected(row: barList->coord.x(),
2079 bar: barList->coord.y(),
2080 series: it.key());
2081 switch (selectionType) {
2082 case QQuickGraphsBars::SelectionItem: {
2083 updateMaterialProperties(item: barList->model,
2084 isHighlight: true,
2085 isMultiHighlight: false,
2086 texture: barList->texture,
2087 color: it.key()->singleHighlightColor());
2088
2089 m_selectedBarPos = barList->model->position();
2090 QString label = (m_selectedBarSeries->d_func()->itemLabel());
2091
2092 if (barList->heightValue >= 0.0f) {
2093 m_selectedBarPos.setY(m_selectedBarPos.y() + barList->heightValue
2094 + 0.2f);
2095 } else {
2096 m_selectedBarPos.setY(m_selectedBarPos.y() + barList->heightValue
2097 - 0.2f);
2098 }
2099
2100 updateItemLabel(position: m_selectedBarPos);
2101 itemLabel()->setVisible(theme()->labelsVisible());
2102 itemLabel()->setProperty(name: "labelText", value: label);
2103 if (!label.compare(s: hiddenLabelTag))
2104 itemLabel()->setVisible(false);
2105 if (isSliceEnabled())
2106 updateSliceItemLabel(label, position: m_selectedBarPos);
2107 break;
2108 }
2109 case QQuickGraphsBars::SelectionRow:
2110 case QQuickGraphsBars::SelectionColumn: {
2111 updateMaterialProperties(item: barList->model,
2112 isHighlight: false,
2113 isMultiHighlight: true,
2114 texture: barList->texture,
2115 color: it.key()->multiHighlightColor());
2116 break;
2117 }
2118 default:
2119 break;
2120 }
2121 }
2122 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2123 QList<BarModel *> barList = *m_barModelsMap.value(key: it.key());
2124 deleteBarItemHolders(instancing: barList.at(i: 0)->selectionInstancing);
2125 deleteBarItemHolders(instancing: barList.at(i: 0)->multiSelectionInstancing);
2126 createBarItemHolders(series: it.key(), barList, slice: false);
2127 }
2128 }
2129 }
2130}
2131
2132QQuickGraphsItem::SelectionType QQuickGraphsBars::isSelected(int row, int bar, QBar3DSeries *series)
2133{
2134 QQuickGraphsBars::SelectionType isSelectedType = QQuickGraphsBars::SelectionNone;
2135 if ((selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries) && m_selectedBarSeries)
2136 || series == m_selectedBarSeries) {
2137 if (row == m_selectedBar.x() && bar == m_selectedBar.y()
2138 && (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item))) {
2139 isSelectedType = QQuickGraphsBars::SelectionItem;
2140 } else if (row == m_selectedBar.x()
2141 && (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row))) {
2142 isSelectedType = QQuickGraphsBars::SelectionRow;
2143 } else if (bar == m_selectedBar.y()
2144 && (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column))) {
2145 isSelectedType = QQuickGraphsBars::SelectionColumn;
2146 }
2147 }
2148
2149 return isSelectedType;
2150}
2151
2152void QQuickGraphsBars::updateSliceItemLabel(const QString &label, QVector3D position)
2153{
2154 QQuickGraphsItem::updateSliceItemLabel(label, position);
2155
2156 QFontMetrics fm(theme()->labelFont());
2157 float textPadding = theme()->labelFont().pointSizeF() * .7f;
2158 float labelHeight = fm.height() + textPadding;
2159 float labelWidth = fm.horizontalAdvance(label) + textPadding;
2160 sliceItemLabel()->setProperty(name: "labelWidth", value: labelWidth);
2161 sliceItemLabel()->setProperty(name: "labelHeight", value: labelHeight);
2162 QVector3D slicePos = position;
2163 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column))
2164 slicePos.setX(slicePos.z());
2165 else if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row))
2166 slicePos.setX(slicePos.x());
2167 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(axisY());
2168 if (valueAxis->reversed())
2169 slicePos.setY(slicePos.y() - (textPadding * .06f));
2170 else
2171 slicePos.setY(slicePos.y() + (textPadding * .06f));
2172 slicePos.setZ(.1f);
2173 sliceItemLabel()->setPosition(slicePos);
2174 sliceItemLabel()->setProperty(name: "labelText", value: label);
2175 if (!label.compare(s: hiddenLabelTag))
2176 sliceItemLabel()->setVisible(false);
2177 sliceItemLabel()->setEulerRotation(QVector3D(0.0f, 0.0f, 90.0f));
2178 sliceItemLabel()->setVisible(theme()->labelsVisible());
2179}
2180
2181void QQuickGraphsBars::resetClickedStatus()
2182{
2183 m_selectedBarPos = QVector3D(0.0f, 0.0f, 0.0f);
2184 m_selectedBar = QQuickGraphsBars::invalidSelectionPosition();
2185 m_selectedBarSeries = 0;
2186 clearSelection();
2187
2188 if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2189 for (const auto barList : std::as_const(t&: m_barModelsMap)) {
2190 QList<BarItemHolder *> barItemList = barList->at(i: 0)->instancing->dataArray();
2191 for (auto bih : barItemList)
2192 bih->selectedBar = false;
2193 }
2194 }
2195
2196 if (sliceView() && sliceView()->isVisible()) {
2197 setSliceActivatedChanged(true);
2198 m_selectionDirty = true;
2199 }
2200 setSeriesVisualsDirty(true);
2201}
2202
2203void QQuickGraphsBars::createSliceView()
2204{
2205 setSliceOrthoProjection(false);
2206 QQuickGraphsItem::createSliceView();
2207 QList<QBar3DSeries *> barSeries = barSeriesList();
2208 for (const auto &barSeries : std::as_const(t&: barSeries)) {
2209 QList<BarModel *> &slicedBarList = m_slicedBarModels[barSeries];
2210
2211 if (slicedBarList.isEmpty()) {
2212 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
2213 qsizetype dataRowIndex = m_minRow;
2214 qsizetype newRowSize = qMin(a: barSeries->dataProxy()->rowCount() - dataRowIndex,
2215 b: m_newRows);
2216 qsizetype newColSize = 0;
2217 if (newRowSize) {
2218 const QBarDataRow *dataRow = &barSeries->dataProxy()->rowAt(rowIndex: dataRowIndex);
2219 if (dataRow) {
2220 qsizetype dataColIndex = m_minCol;
2221 newColSize = qMin(a: dataRow->size() - dataColIndex, b: m_newCols);
2222 }
2223 }
2224 qsizetype slicedBarListSize = 0;
2225
2226 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row))
2227 slicedBarListSize = newColSize;
2228 else if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column))
2229 slicedBarListSize = newRowSize;
2230
2231 for (int ind = 0; ind < slicedBarListSize; ++ind) {
2232 QQuick3DModel *model = createDataItem(scene: sliceView()->scene(), series: barSeries);
2233 model->setVisible(false);
2234 BarModel *barModel = new BarModel();
2235 barModel->model = model;
2236
2237 if (!slicedBarList.contains(t: barModel))
2238 slicedBarList.append(t: barModel);
2239 }
2240 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2241 BarModel *barInstancing = new BarModel();
2242
2243 if (barInstancing->selectionInstancing == nullptr) {
2244 barInstancing->selectionInstancing = new BarInstancing;
2245 barInstancing->selectionInstancing->setParent(barSeries);
2246 barInstancing->multiSelectionInstancing = new BarInstancing;
2247 barInstancing->multiSelectionInstancing->setParent(barSeries);
2248 }
2249
2250 if (barInstancing->selectedModel == nullptr) {
2251 barInstancing->selectedModel = createDataItem(scene: sliceView()->scene(), series: barSeries);
2252 barInstancing->selectedModel->setInstancing(barInstancing->selectionInstancing);
2253 barInstancing->selectedModel->setVisible(false);
2254 barInstancing->multiSelectedModel = createDataItem(scene: sliceView()->scene(),
2255 series: barSeries);
2256 barInstancing->multiSelectedModel->setInstancing(
2257 barInstancing->multiSelectionInstancing);
2258 barInstancing->multiSelectedModel->setVisible(false);
2259 }
2260
2261 if (!slicedBarList.contains(t: barInstancing))
2262 slicedBarList.append(t: barInstancing);
2263 }
2264 }
2265 }
2266}
2267
2268void QQuickGraphsBars::toggleSliceGraph()
2269{
2270 if (m_selectionDirty)
2271 QQuickGraphsItem::toggleSliceGraph();
2272
2273 if (sliceView() && !sliceView()->isVisible()) {
2274 removeSlicedBarModels();
2275 m_changeTracker.selectedBarChanged = false;
2276 return;
2277 }
2278
2279 qsizetype index = 0;
2280 bool rowMode = selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row);
2281 for (auto it = m_slicedBarModels.begin(); it != m_slicedBarModels.end(); it++) {
2282 bool useGradient = it.key()->d_func()->isUsingGradient();
2283 bool rangeGradient = (useGradient
2284 && it.key()->d_func()->m_colorStyle
2285 == QGraphsTheme::ColorStyle::RangeGradient);
2286 QList<BarModel *> barList = *m_barModelsMap.value(key: it.key());
2287 QList<BarModel *> sliceList = it.value();
2288 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
2289 for (int ind = 0; ind < it.value().count(); ++ind) {
2290 if (rowMode)
2291 index = (m_selectedBar.x() * it.key()->dataProxy()->colCount()) + ind;
2292 else
2293 index = m_selectedBar.y() + (ind * it.key()->dataProxy()->colCount());
2294 bool visible = ((m_selectedBarSeries == it.key()
2295 || selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries))
2296 && it.key()->isVisible());
2297 if (index < barList.size() && m_selectedBar != invalidSelectionPosition()) {
2298 BarModel *sliceBarModel = sliceList.at(i: ind);
2299 BarModel *barModel = barList.at(i: index);
2300
2301 sliceBarModel->model->setVisible(visible);
2302 if (rowMode) {
2303 sliceBarModel->model->setPosition(
2304 QVector3D(barModel->model->x(), barModel->model->y(), 0.0f));
2305 } else {
2306 sliceBarModel->model->setX(barModel->model->z()
2307 - (barList.at(i: 0)->visualIndex * .1f));
2308 sliceBarModel->model->setY(barModel->model->y());
2309 sliceBarModel->model->setZ(0.0f);
2310 }
2311 sliceBarModel->model->setScale(barModel->model->scale());
2312
2313 updateItemMaterial(item: sliceBarModel->model,
2314 useGradient,
2315 rangeGradient,
2316 QStringLiteral(":/materials/BarsMaterial"));
2317
2318 if (barModel->coord == m_selectedBar
2319 && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item)) {
2320 updateMaterialProperties(item: sliceBarModel->model,
2321 isHighlight: true,
2322 isMultiHighlight: false,
2323 texture: barList.at(i: index)->texture,
2324 color: it.key()->singleHighlightColor());
2325 updateSliceItemLabel(label: m_selectedBarSeries->itemLabel(), position: m_selectedBarPos);
2326 } else {
2327 updateMaterialProperties(item: sliceBarModel->model,
2328 isHighlight: false,
2329 isMultiHighlight: false,
2330 texture: barList.at(i: index)->texture,
2331 color: it.key()->baseColor());
2332 }
2333 } else {
2334 setSliceActivatedChanged(true);
2335 QQuickGraphsItem::toggleSliceGraph();
2336 setSliceEnabled(false);
2337 return;
2338 }
2339 }
2340 setSliceActivatedChanged(false);
2341 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2342 deleteBarItemHolders(instancing: sliceList.at(i: 0)->selectionInstancing);
2343 deleteBarItemHolders(instancing: sliceList.at(i: 0)->multiSelectionInstancing);
2344 createBarItemHolders(series: it.key(), barList, slice: true);
2345 setSliceActivatedChanged(false);
2346 }
2347 }
2348}
2349
2350void QQuickGraphsBars::handleLabelCountChanged(QQuick3DRepeater *repeater, QColor axisLabelColor)
2351{
2352 QQuickGraphsItem::handleLabelCountChanged(repeater, axisLabelColor);
2353
2354 if (repeater == repeaterX())
2355 handleDataColumnLabelsChanged();
2356 if (repeater == repeaterZ())
2357 handleDataRowLabelsChanged();
2358}
2359
2360void QQuickGraphsBars::removeSlicedBarModels()
2361{
2362 for (auto &list : std::as_const(t&: m_slicedBarModels)) {
2363 for (auto barModel : list) {
2364 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
2365 deleteBarModels(model: barModel->model);
2366 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2367 deleteBarItemHolders(instancing: barModel->selectionInstancing);
2368 deleteBarItemHolders(instancing: barModel->multiSelectionInstancing);
2369 deleteBarModels(model: barModel->selectedModel);
2370 deleteBarModels(model: barModel->multiSelectedModel);
2371 }
2372 delete barModel;
2373 }
2374 }
2375
2376 m_slicedBarModels.clear();
2377}
2378
2379void QQuickGraphsBars::createBarItemHolders(QBar3DSeries *series,
2380 QList<BarModel *> barList,
2381 bool slice)
2382{
2383 bool useGradient = series->d_func()->isUsingGradient();
2384 bool rangeGradient = (useGradient
2385 && series->d_func()->m_colorStyle == QGraphsTheme::ColorStyle::RangeGradient);
2386 bool visible = ((m_selectedBarSeries == series
2387 || selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries))
2388 && series->isVisible());
2389 QList<BarItemHolder *> barItemList;
2390 QList<BarItemHolder *> multiBarItemList;
2391 QQuick3DModel *selectedModel = nullptr;
2392 QQuick3DModel *multiSelectedModel = nullptr;
2393 QList<BarItemHolder *> selectedItem;
2394 QList<BarItemHolder *> multiSelectedItems;
2395 BarInstancing *instancing;
2396 BarInstancing *multiInstancing;
2397
2398 if (slice) {
2399 QList<BarModel *> sliceBarList = m_slicedBarModels.value(key: series);
2400 barItemList = barList.at(i: 0)->selectionInstancing->dataArray();
2401 multiBarItemList = barList.at(i: 0)->multiSelectionInstancing->dataArray();
2402 selectedModel = sliceBarList.at(i: 0)->selectedModel;
2403 multiSelectedModel = sliceBarList.at(i: 0)->multiSelectedModel;
2404 instancing = sliceBarList.at(i: 0)->selectionInstancing;
2405 multiInstancing = sliceBarList.at(i: 0)->multiSelectionInstancing;
2406 } else {
2407 barItemList = barList.at(i: 0)->instancing->dataArray();
2408 selectedModel = barList.at(i: 0)->selectedModel;
2409 multiSelectedModel = barList.at(i: 0)->multiSelectedModel;
2410 instancing = barList.at(i: 0)->selectionInstancing;
2411 multiInstancing = barList.at(i: 0)->multiSelectionInstancing;
2412 }
2413
2414 for (const auto bih : std::as_const(t&: barItemList)) {
2415 QQuickGraphsBars::SelectionType selectionType = isSelected(row: bih->coord.x(),
2416 bar: bih->coord.y(),
2417 series);
2418 switch (selectionType) {
2419 case QQuickGraphsBars::SelectionItem: {
2420 updateItemMaterial(item: selectedModel,
2421 useGradient,
2422 rangeGradient,
2423 QStringLiteral(":/materials/BarsMaterialInstancing"));
2424 updateMaterialProperties(item: selectedModel,
2425 isHighlight: true,
2426 isMultiHighlight: false,
2427 texture: barList.at(i: 0)->texture,
2428 color: QColor(Qt::white));
2429 if (!slice)
2430 bih->selectedBar = true;
2431 selectedModel->setVisible(visible);
2432 BarItemHolder *selectedBih = new BarItemHolder();
2433 selectedBih->selectedBar = false;
2434 selectedBih->color = series->singleHighlightColor();
2435 selectedBih->coord = bih->coord;
2436 selectedBih->rotation = bih->rotation;
2437 selectedBih->heightValue = bih->heightValue;
2438 selectedBih->position = bih->position;
2439 selectedBih->scale = bih->scale;
2440
2441 QString label = m_selectedBarSeries->itemLabel();
2442 if (slice) {
2443 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row)) {
2444 selectedBih->position.setZ(.0f);
2445 } else {
2446 selectedBih->position.setX(selectedBih->position.z()
2447 - (barList.at(i: 0)->visualIndex * .1f));
2448 selectedBih->position.setZ(.0f);
2449 }
2450 updateSliceItemLabel(label, position: m_selectedBarPos);
2451 }
2452
2453 selectedItem.push_back(t: selectedBih);
2454 instancing->setDataArray(selectedItem);
2455
2456 m_selectedBarPos = bih->position;
2457
2458 if (bih->heightValue >= 0.0f)
2459 m_selectedBarPos.setY(m_selectedBarPos.y() + bih->heightValue + 0.2f);
2460 else
2461 m_selectedBarPos.setY(m_selectedBarPos.y() + bih->heightValue - 0.2f);
2462
2463 updateItemLabel(position: m_selectedBarPos);
2464 itemLabel()->setVisible(theme()->labelsVisible());
2465 itemLabel()->setProperty(name: "labelText", value: label);
2466 if (!label.compare(s: hiddenLabelTag))
2467 itemLabel()->setVisible(false);
2468 if (isSliceEnabled())
2469 updateSliceItemLabel(label, position: m_selectedBarPos);
2470 break;
2471 }
2472 case QQuickGraphsBars::SelectionRow:
2473 case QQuickGraphsBars::SelectionColumn: {
2474 updateItemMaterial(item: multiSelectedModel,
2475 useGradient,
2476 rangeGradient,
2477 QStringLiteral(":/materials/BarsMaterialInstancing"));
2478 updateMaterialProperties(item: multiSelectedModel,
2479 isHighlight: false,
2480 isMultiHighlight: true,
2481 texture: barList.at(i: 0)->texture,
2482 color: QColor(Qt::white));
2483 if (!slice)
2484 bih->selectedBar = true;
2485 multiSelectedModel->setVisible(visible);
2486 BarItemHolder *selectedBih = new BarItemHolder();
2487 selectedBih->selectedBar = false;
2488 selectedBih->color = series->multiHighlightColor();
2489 selectedBih->coord = bih->coord;
2490 selectedBih->rotation = bih->rotation;
2491 selectedBih->heightValue = bih->heightValue;
2492 selectedBih->position = bih->position;
2493 selectedBih->scale = bih->scale;
2494
2495 multiSelectedItems.push_back(t: selectedBih);
2496 multiInstancing->setDataArray(multiSelectedItems);
2497 break;
2498 }
2499 default:
2500 break;
2501 }
2502 }
2503
2504 if (slice) {
2505 for (const auto bih : std::as_const(t&: multiBarItemList)) {
2506 updateItemMaterial(item: multiSelectedModel,
2507 useGradient,
2508 rangeGradient,
2509 QStringLiteral(":/materials/BarsMaterialInstancing"));
2510 updateMaterialProperties(item: multiSelectedModel,
2511 isHighlight: false,
2512 isMultiHighlight: false,
2513 texture: barList.at(i: 0)->texture,
2514 color: QColor(Qt::white));
2515
2516 multiSelectedModel->setVisible(visible);
2517 BarItemHolder *selectedBih = new BarItemHolder();
2518 selectedBih->selectedBar = false;
2519 selectedBih->color = series->baseColor();
2520 selectedBih->coord = bih->coord;
2521 selectedBih->rotation = bih->rotation;
2522 selectedBih->heightValue = bih->heightValue;
2523 selectedBih->position = bih->position;
2524 selectedBih->scale = bih->scale;
2525
2526 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row)) {
2527 selectedBih->position.setZ(.0f);
2528 } else {
2529 selectedBih->position.setX(selectedBih->position.z()
2530 - (barList.at(i: 0)->visualIndex * .1f));
2531 selectedBih->position.setZ(.0f);
2532 }
2533
2534 multiSelectedItems.push_back(t: selectedBih);
2535 multiInstancing->setDataArray(multiSelectedItems);
2536 }
2537 }
2538}
2539
2540void QQuickGraphsBars::updateSelectionMode(QtGraphs3D::SelectionFlags mode)
2541{
2542 checkSliceEnabled();
2543 if (!sliceView())
2544 createSliceView();
2545
2546 bool validSlice = mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice) && m_selectedBarSeries;
2547 if (sliceView() && sliceView()->isVisible()) {
2548 if (validSlice) {
2549 removeSlicedBarModels();
2550 createSliceView();
2551 toggleSliceGraph();
2552 } else {
2553 m_selectionDirty = true;
2554 setSliceActivatedChanged(true);
2555 }
2556 } else if (validSlice) {
2557 m_selectionDirty = true;
2558 setSliceActivatedChanged(true);
2559 }
2560
2561 setSeriesVisualsDirty(true);
2562 itemLabel()->setVisible(false);
2563 if (sliceView() && !mode.testFlag(flag: QtGraphs3D::SelectionFlag::Item))
2564 sliceItemLabel()->setVisible(false);
2565}
2566
2567void QQuickGraphsBars::updateBarSpecs(float thicknessRatio, QSizeF spacing, bool relative)
2568{
2569 // Convert ratio to QSizeF, as we need it in that format for autoscaling
2570 // calculations
2571 m_cachedBarThickness.setWidth(1.0);
2572 m_cachedBarThickness.setHeight(1.0f / thicknessRatio);
2573
2574 if (relative) {
2575 m_cachedBarSpacing.setWidth((m_cachedBarThickness.width() * 2) * (spacing.width() + 1.0f));
2576 m_cachedBarSpacing.setHeight((m_cachedBarThickness.height() * 2)
2577 * (spacing.height() + 1.0f));
2578 } else {
2579 m_cachedBarSpacing = m_cachedBarThickness * 2 + spacing * 2;
2580 }
2581
2582 m_axisRangeChanged = true;
2583 m_changeTracker.selectedBarChanged = true;
2584
2585 // Calculate here and at setting sample space
2586 calculateSceneScalingFactors();
2587}
2588
2589void QQuickGraphsBars::updateBarSeriesMargin(QSizeF margin)
2590{
2591 m_cachedBarSeriesMargin = margin;
2592 calculateSeriesStartPosition();
2593 calculateSceneScalingFactors();
2594 setSeriesVisualsDirty(true);
2595}
2596

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtgraphs/src/graphs3d/qml/qquickgraphsbars.cpp