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()) {
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 (m_requestedMargin < 0.0f) {
974 m_hBackgroundMargin = 0.0f;
975 m_vBackgroundMargin = 0.0f;
976 } else {
977 m_hBackgroundMargin = m_requestedMargin;
978 m_vBackgroundMargin = m_requestedMargin;
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 break;
1228 }
1229 }
1230
1231 if (newItem) {
1232 ChangeItem newItem = {.series: series, .point: candidate};
1233 m_changedItems.append(t: newItem);
1234 m_changeTracker.itemChanged = true;
1235
1236 if (series == m_selectedBarSeries && m_selectedBar == candidate)
1237 series->d_func()->markItemLabelDirty();
1238 if (series->isVisible())
1239 adjustAxisRanges();
1240 emitNeedRender();
1241 }
1242}
1243
1244void QQuickGraphsBars::handleDataRowLabelsChanged()
1245{
1246 if (m_axisZ) {
1247 // Grab a sublist equal to data window (no need to have more labels in axis)
1248 int min = int(m_axisZ->min());
1249 int count = int(m_axisZ->max()) - min + 1;
1250 QStringList subList;
1251 if (m_primarySeries && m_primarySeries->dataProxy())
1252 subList = m_primarySeries->rowLabels().mid(pos: min, len: count);
1253 static_cast<QCategory3DAxis *>(m_axisZ)->d_func()->setDataLabels(subList);
1254 }
1255
1256 if (repeaterZ()) {
1257 updateParameters();
1258 repeaterZ()->setModel(m_axisZ->labels().size());
1259 }
1260}
1261
1262void QQuickGraphsBars::handleDataColumnLabelsChanged()
1263{
1264 if (m_axisX) {
1265 // Grab a sublist equal to data window (no need to have more labels in axis)
1266 int min = int(m_axisX->min());
1267 int count = int(m_axisX->max()) - min + 1;
1268 QStringList subList;
1269 if (m_primarySeries && m_primarySeries->dataProxy())
1270 subList = m_primarySeries->columnLabels().mid(pos: min, len: count);
1271 static_cast<QCategory3DAxis *>(m_axisX)->d_func()->setDataLabels(subList);
1272 }
1273
1274 if (repeaterX()) {
1275 updateParameters();
1276 repeaterX()->setModel(m_axisX->labels().size());
1277 }
1278}
1279
1280void QQuickGraphsBars::handleRowColorsChanged()
1281{
1282 setSeriesVisualsDirty(true);
1283 emitNeedRender();
1284}
1285
1286void QQuickGraphsBars::connectSeries(QBar3DSeries *series)
1287{
1288 m_meshType = series->mesh();
1289 m_smooth = series->isMeshSmooth();
1290
1291 QObject::connect(sender: series,
1292 signal: &QBar3DSeries::meshChanged,
1293 context: this,
1294 slot: &QQuickGraphsBars::handleSeriesMeshChanged);
1295 QObject::connect(sender: series,
1296 signal: &QBar3DSeries::meshSmoothChanged,
1297 context: this,
1298 slot: &QQuickGraphsBars::handleMeshSmoothChanged);
1299 QObject::connect(sender: series->dataProxy(),
1300 signal: &QBarDataProxy::colCountChanged,
1301 context: this,
1302 slot: &QQuickGraphsBars::handleDataColumnLabelsChanged);
1303 QObject::connect(sender: series->dataProxy(),
1304 signal: &QBarDataProxy::rowCountChanged,
1305 context: this,
1306 slot: &QQuickGraphsBars::handleDataRowLabelsChanged);
1307 QObject::connect(sender: series,
1308 signal: &QBar3DSeries::rowColorsChanged,
1309 context: this,
1310 slot: &QQuickGraphsBars::handleRowColorsChanged);
1311}
1312
1313void QQuickGraphsBars::disconnectSeries(QBar3DSeries *series)
1314{
1315 QObject::disconnect(sender: series, signal: 0, receiver: this, member: 0);
1316}
1317
1318void QQuickGraphsBars::generateBars(QList<QBar3DSeries *> &barSeriesList)
1319{
1320 m_visibleSeriesCount = 0;
1321 for (const auto &barSeries : std::as_const(t&: barSeriesList)) {
1322 QQuick3DTexture *texture = createTexture();
1323 texture->setParent(this);
1324 auto gradient = barSeries->baseGradient();
1325 auto textureData = static_cast<QQuickGraphsTextureData *>(texture->textureData());
1326 textureData->createGradient(gradient);
1327
1328 bool visible = barSeries->isVisible();
1329
1330 QList<BarModel *> *barList = m_barModelsMap.value(key: barSeries);
1331
1332 if (!barList) {
1333 barList = new QList<BarModel *>;
1334 m_barModelsMap[barSeries] = barList;
1335 }
1336
1337 if (barList->isEmpty()) {
1338 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1339 QBarDataProxy *dataProxy = barSeries->dataProxy();
1340 qsizetype dataRowIndex = m_minRow;
1341 qsizetype newRowSize = qMin(a: dataProxy->rowCount() - dataRowIndex, b: m_newRows);
1342
1343 for (int row = 0; row < newRowSize; ++row) {
1344 const QBarDataRow &dataRow = dataProxy->rowAt(rowIndex: dataRowIndex);
1345 if (!dataRow.isEmpty()) {
1346 qsizetype dataColIndex = m_minCol;
1347 qsizetype newColSize = qMin(a: dataRow.size() - dataColIndex, b: m_newCols);
1348 for (int col = 0; col < newColSize; ++col) {
1349 QBarDataItem &dataItem = const_cast<QBarDataItem &>(
1350 dataRow.at(i: dataColIndex));
1351 auto scene = QQuick3DViewport::scene();
1352 QQuick3DModel *model = createDataItem(scene, series: barSeries);
1353 model->setVisible(visible);
1354
1355 BarModel *barModel = new BarModel();
1356 barModel->model = model;
1357 barModel->barItem = &dataItem;
1358 barModel->coord = QPoint(int(dataRowIndex), col);
1359 barModel->texture = texture;
1360
1361 if (!barList->contains(t: barModel))
1362 barList->append(t: barModel);
1363
1364 ++dataColIndex;
1365 }
1366 ++dataRowIndex;
1367 }
1368 }
1369 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1370 auto scene = QQuick3DViewport::scene();
1371 BarModel *barInstancing = new BarModel();
1372 barInstancing->texture = texture;
1373
1374 if (barInstancing->instancing == nullptr) {
1375 barInstancing->instancing = new BarInstancing;
1376 barInstancing->instancing->setParent(barSeries);
1377 barInstancing->selectionInstancing = new BarInstancing;
1378 barInstancing->selectionInstancing->setParent(barSeries);
1379 barInstancing->multiSelectionInstancing = new BarInstancing;
1380 barInstancing->multiSelectionInstancing->setParent(barSeries);
1381 }
1382
1383 if (barInstancing->model == nullptr) {
1384 barInstancing->model = createDataItem(scene, series: barSeries);
1385 barInstancing->model->setInstancing(barInstancing->instancing);
1386 barInstancing->model->setVisible(visible);
1387 barInstancing->model->setPickable(true);
1388 barInstancing->selectedModel = createDataItem(scene, series: barSeries);
1389 barInstancing->selectedModel->setInstancing(barInstancing->selectionInstancing);
1390 barInstancing->selectedModel->setVisible(visible);
1391 barInstancing->selectedModel->setPickable(true);
1392 barInstancing->multiSelectedModel = createDataItem(scene, series: barSeries);
1393 barInstancing->multiSelectedModel->setInstancing(
1394 barInstancing->multiSelectionInstancing);
1395 barInstancing->multiSelectedModel->setVisible(visible);
1396 barInstancing->multiSelectedModel->setPickable(true);
1397 }
1398
1399 if (!barList->contains(t: barInstancing))
1400 barList->append(t: barInstancing);
1401 }
1402
1403 markSeriesVisualsDirty();
1404 }
1405
1406 if (barSeries->isVisible())
1407 m_visibleSeriesCount++;
1408 }
1409}
1410
1411QQuick3DModel *QQuickGraphsBars::createDataItem(QQuick3DNode *scene, QAbstract3DSeries *series)
1412{
1413 auto model = new QQuick3DModel();
1414 model->setParent(scene);
1415 model->setParentItem(scene);
1416 model->setObjectName(QStringLiteral("BarModel"));
1417 QString fileName = getMeshFileName();
1418 if (fileName.isEmpty())
1419 fileName = series->userDefinedMesh();
1420
1421 model->setSource(QUrl(fileName));
1422 return model;
1423}
1424
1425QString QQuickGraphsBars::getMeshFileName()
1426{
1427 QString fileName = {};
1428 switch (m_meshType) {
1429 case QAbstract3DSeries::Mesh::Sphere:
1430 fileName = QStringLiteral("defaultMeshes/sphereMesh");
1431 break;
1432 case QAbstract3DSeries::Mesh::Bar:
1433 case QAbstract3DSeries::Mesh::Cube:
1434 fileName = QStringLiteral("defaultMeshes/barMesh");
1435 break;
1436 case QAbstract3DSeries::Mesh::Pyramid:
1437 fileName = QStringLiteral("defaultMeshes/pyramidMesh");
1438 break;
1439 case QAbstract3DSeries::Mesh::Cone:
1440 fileName = QStringLiteral("defaultMeshes/coneMesh");
1441 break;
1442 case QAbstract3DSeries::Mesh::Cylinder:
1443 fileName = QStringLiteral("defaultMeshes/cylinderMesh");
1444 break;
1445 case QAbstract3DSeries::Mesh::BevelBar:
1446 case QAbstract3DSeries::Mesh::BevelCube:
1447 fileName = QStringLiteral("defaultMeshes/bevelBarMesh");
1448 break;
1449 case QAbstract3DSeries::Mesh::UserDefined:
1450 break;
1451 default:
1452 fileName = QStringLiteral("defaultMeshes/sphereMesh");
1453 }
1454
1455 fixMeshFileName(fileName, meshType: m_meshType);
1456
1457 return fileName;
1458}
1459
1460void QQuickGraphsBars::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh meshType)
1461{
1462 // Should it be smooth?
1463 if (m_smooth && meshType != QAbstract3DSeries::Mesh::Point
1464 && meshType != QAbstract3DSeries::Mesh::UserDefined) {
1465 fileName += QStringLiteral("Smooth");
1466 }
1467
1468 // Should it be filled?
1469 if (!theme()->isPlotAreaBackgroundVisible() && meshType != QAbstract3DSeries::Mesh::Sphere
1470 && meshType != QAbstract3DSeries::Mesh::Point
1471 && meshType != QAbstract3DSeries::Mesh::UserDefined) {
1472 fileName.append(QStringLiteral("Full"));
1473 }
1474}
1475
1476void QQuickGraphsBars::updateBarVisuality(QBar3DSeries *series, int visualIndex)
1477{
1478 QList<BarModel *> barList = *m_barModelsMap.value(key: series);
1479 for (int i = 0; i < barList.count(); i++) {
1480 barList.at(i)->visualIndex = visualIndex;
1481 barList.at(i)->model->setVisible(series->isVisible());
1482 if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1483 barList.at(i: 0)->selectedModel->setVisible(false);
1484 barList.at(i: 0)->multiSelectedModel->setVisible(false);
1485 }
1486 }
1487
1488 m_changeTracker.selectedBarChanged = true;
1489 itemLabel()->setVisible(false);
1490}
1491
1492void QQuickGraphsBars::updateBarPositions(QBar3DSeries *series)
1493{
1494 QBarDataProxy *dataProxy = series->dataProxy();
1495
1496 m_seriesScaleX = 1.0f / float(m_visibleSeriesCount);
1497 m_seriesStep = 1.0f / float(m_visibleSeriesCount);
1498 m_seriesStart = -((float(m_visibleSeriesCount) - 1.0f) * 0.5f)
1499 * (m_seriesStep - (m_seriesStep * m_cachedBarSeriesMargin.width()));
1500
1501 if (m_keepSeriesUniform)
1502 m_seriesScaleZ = m_seriesScaleX;
1503 else
1504 m_seriesScaleZ = 1.0f;
1505
1506 m_zeroPosition = valueAxis()->positionAt(x: m_actualFloorLevel);
1507
1508 QList<BarModel *> barList = *m_barModelsMap.value(key: series);
1509
1510 qsizetype dataRowIndex = m_minRow;
1511 qsizetype newRowSize = qMin(a: dataProxy->rowCount() - dataRowIndex, b: m_newRows);
1512 qsizetype row = 0;
1513 qsizetype dataColIndex = m_minCol;
1514 qsizetype newColSize = qMin(a: dataProxy->colCount() - dataColIndex, b: m_newCols);
1515 qsizetype col = 0;
1516 for (int i = 0; i < barList.count(); i++) {
1517 float seriesPos = m_seriesStart + 0.5f
1518 + (m_seriesStep
1519 * (barList.at(i)->visualIndex
1520 - (barList.at(i)->visualIndex * m_cachedBarSeriesMargin.width())));
1521 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1522 QBarDataItem *item = barList.at(i)->barItem;
1523 QQuick3DModel *model = barList.at(i)->model;
1524 float heightValue = updateBarHeightParameters(item);
1525 float angle = item->rotation();
1526
1527 if (angle)
1528 model->setRotation(QQuaternion::fromAxisAndAngle(axis: upVector, angle));
1529 else
1530 model->setRotation(QQuaternion());
1531
1532 if (heightValue < 0.f) {
1533 const QVector3D rot = model->eulerRotation();
1534 model->setEulerRotation(QVector3D(-180.f, rot.y(), rot.z()));
1535 }
1536
1537 float colPos = (col + seriesPos) * m_cachedBarSpacing.width();
1538 float xPos = (colPos - m_rowWidth) / m_scaleFactor;
1539 float rowPos = (row + 0.5f) * (m_cachedBarSpacing.height());
1540 float zPos = (m_columnDepth - rowPos) / m_scaleFactor;
1541 float yPos;
1542
1543 if (heightValue < 0.f)
1544 yPos = heightValue - m_backgroundAdjustment - 0.015f;
1545 else
1546 yPos = heightValue - m_backgroundAdjustment + 0.015f;
1547
1548 barList.at(i)->heightValue = heightValue;
1549 model->setPosition(QVector3D(xPos, yPos, zPos));
1550
1551 if (heightValue == 0) {
1552 model->setScale(QVector3D(.0f, .0f, .0f));
1553 model->setPickable(false);
1554 } else {
1555 model->setScale(QVector3D(m_xScale * m_seriesScaleX,
1556 qAbs(t: heightValue),
1557 m_zScale * m_seriesScaleZ));
1558 model->setPickable(true);
1559 }
1560
1561 if (col < newColSize - 1) {
1562 ++col;
1563 } else {
1564 col = 0;
1565 if (row < newRowSize - 1)
1566 ++row;
1567 else
1568 row = 0;
1569 }
1570 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1571 deleteBarItemHolders(instancing: barList.at(i)->instancing);
1572 QList<BarItemHolder *> positions;
1573 for (int row = 0; row < newRowSize; ++row) {
1574 const QBarDataRow &dataRow = dataProxy->rowAt(rowIndex: dataRowIndex);
1575 if (!dataRow.isEmpty()) {
1576 dataColIndex = m_minCol;
1577 newColSize = qMin(a: dataRow.size() - dataColIndex, b: m_newCols);
1578 for (int col = 0; col < newColSize; col++) {
1579 const QBarDataItem &item = dataRow.at(i: dataColIndex);
1580 float heightValue = updateBarHeightParameters(item: &item);
1581 BarItemHolder *bih = new BarItemHolder();
1582
1583 if (heightValue < 0.f) {
1584 const QVector3D eulerRot = barList.at(i)->model->eulerRotation();
1585 bih->eulerRotation = QVector3D(-180.f, eulerRot.y(), eulerRot.z());
1586 }
1587
1588 float colPos = (col + seriesPos) * m_cachedBarSpacing.width();
1589 float xPos = (colPos - m_rowWidth) / m_scaleFactor;
1590 float rowPos = (row + 0.5f) * (m_cachedBarSpacing.height());
1591 float zPos = (m_columnDepth - rowPos) / m_scaleFactor;
1592 float yPos;
1593
1594 if (heightValue < 0.f)
1595 yPos = heightValue - m_backgroundAdjustment - 0.015f;
1596 else
1597 yPos = heightValue - m_backgroundAdjustment + 0.015f;
1598
1599 bih->position = {xPos, yPos, zPos};
1600 bih->coord = QPoint(row, col);
1601
1602 if (heightValue == 0) {
1603 bih->scale = {.0f, .0f, .0f};
1604 } else {
1605 bih->scale = {m_xScale * m_seriesScaleX,
1606 qAbs(t: heightValue),
1607 m_zScale * m_seriesScaleZ};
1608 }
1609
1610 bih->heightValue = heightValue;
1611 bih->selectedBar = false;
1612
1613 bool colorStyleIsUniform = (series->colorStyle()
1614 == QGraphsTheme::ColorStyle::Uniform);
1615 if (colorStyleIsUniform) {
1616 QList<QColor> rowColors = series->rowColors();
1617 if (rowColors.size() == 0) {
1618 bih->color = series->baseColor();
1619 } else {
1620 int rowColorIndex = bih->coord.x() % rowColors.size();
1621 bih->color = rowColors[rowColorIndex];
1622 }
1623 }
1624
1625 positions.push_back(t: bih);
1626 dataColIndex++;
1627 }
1628 }
1629 dataRowIndex++;
1630 }
1631 barList.at(i)->instancing->setDataArray(positions);
1632 }
1633 }
1634}
1635
1636float QQuickGraphsBars::updateBarHeightParameters(const QBarDataItem *item)
1637{
1638 float value = item->value();
1639 float heightValue = valueAxis()->positionAt(x: value);
1640
1641 if (m_noZeroInRange) {
1642 if (m_hasNegativeValues) {
1643 heightValue = -1.0f + heightValue;
1644 if (heightValue > 0.0f)
1645 heightValue = 0.0f;
1646 } else {
1647 if (heightValue < 0.0f)
1648 heightValue = 0.0f;
1649 }
1650 } else {
1651 heightValue -= m_zeroPosition;
1652 }
1653
1654 if (valueAxis()->reversed())
1655 heightValue = -heightValue;
1656
1657 return heightValue;
1658}
1659
1660void QQuickGraphsBars::updateBarVisuals(QBar3DSeries *series)
1661{
1662 QList<BarModel *> barList = *m_barModelsMap.value(key: series);
1663 bool useGradient = series->d_func()->isUsingGradient();
1664
1665 if (useGradient) {
1666 if (!m_hasHighlightTexture) {
1667 m_highlightTexture = createTexture();
1668 m_highlightTexture->setParent(this);
1669 m_multiHighlightTexture = createTexture();
1670 m_multiHighlightTexture->setParent(this);
1671 m_hasHighlightTexture = true;
1672 }
1673 auto highlightGradient = series->singleHighlightGradient();
1674 auto highlightTextureData = static_cast<QQuickGraphsTextureData *>(
1675 m_highlightTexture->textureData());
1676 highlightTextureData->createGradient(gradient: highlightGradient);
1677 auto multiHighlightGradient = series->multiHighlightGradient();
1678 auto multiHighlightTextureData = static_cast<QQuickGraphsTextureData *>(
1679 m_multiHighlightTexture->textureData());
1680 multiHighlightTextureData->createGradient(gradient: multiHighlightGradient);
1681 } else {
1682 if (m_hasHighlightTexture) {
1683 m_highlightTexture->deleteLater();
1684 m_multiHighlightTexture->deleteLater();
1685 m_hasHighlightTexture = false;
1686 }
1687 }
1688
1689 bool rangeGradient = (useGradient
1690 && series->d_func()->m_colorStyle == QGraphsTheme::ColorStyle::RangeGradient);
1691 QColor baseColor = series->baseColor();
1692 QColor barColor;
1693 QLinearGradient gradient = series->baseGradient();
1694
1695 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1696 // Release resources that might not have been deleted even though deleteLater had been set
1697 window()->releaseResources();
1698
1699 for (int i = 0; i < barList.count(); i++) {
1700 QQuick3DModel *model = barList.at(i)->model;
1701 auto textureData = static_cast<QQuickGraphsTextureData *>(
1702 barList.at(i)->texture->textureData());
1703 textureData->createGradient(gradient: series->baseGradient());
1704 const bool transparency = textureData->hasTransparency();
1705 updateItemMaterial(item: model,
1706 useGradient,
1707 rangeGradient,
1708 QStringLiteral(":/materials/BarsMaterial"));
1709 if (useGradient) {
1710 updateMaterialProperties(item: model,
1711 isHighlight: false,
1712 isMultiHighlight: false,
1713 texture: barList.at(i)->texture,
1714 color: QColor(Qt::white),
1715 transparency);
1716 } else {
1717 QList<QColor> rowColors = series->rowColors();
1718 if (rowColors.size() == 0) {
1719 barColor = baseColor;
1720 } else {
1721 int rowColorIndex = barList.at(i)->coord.x() % rowColors.size();
1722 barColor = rowColors[rowColorIndex];
1723 }
1724 const bool transparency = barColor.alphaF() < 1.0;
1725 updateMaterialProperties(item: model,
1726 isHighlight: false,
1727 isMultiHighlight: false,
1728 texture: barList.at(i)->texture,
1729 color: barColor,
1730 transparency);
1731 }
1732 }
1733 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1734 for (int i = 0; i < barList.count(); i++) {
1735 auto textureData = static_cast<QQuickGraphsTextureData *>(
1736 barList.at(i)->texture->textureData());
1737 textureData->createGradient(gradient: series->baseGradient());
1738 barList.at(i)->instancing->setTransparency(textureData->hasTransparency());
1739 updateItemMaterial(item: barList.at(i)->model,
1740 useGradient,
1741 rangeGradient,
1742 QStringLiteral(":/materials/BarsMaterialInstancing"));
1743 if (useGradient) {
1744 updateMaterialProperties(item: barList.at(i)->model,
1745 isHighlight: false,
1746 isMultiHighlight: false,
1747 texture: barList.at(i)->texture,
1748 color: QColor(Qt::white),
1749 transparency: textureData->hasTransparency());
1750 } else {
1751 if (!barList.at(i)->instancing->dataArray().isEmpty()) {
1752 const bool transparency
1753 = barList.at(i)->instancing->dataArray().at(i: 0)->color.alphaF() < 1.0;
1754 updateMaterialProperties(item: barList.at(i)->model,
1755 isHighlight: false,
1756 isMultiHighlight: false,
1757 texture: barList.at(i)->texture,
1758 color: QColor(Qt::white),
1759 transparency);
1760 }
1761 }
1762 }
1763 }
1764}
1765
1766void QQuickGraphsBars::updateItemMaterial(QQuick3DModel *item,
1767 bool useGradient,
1768 bool rangeGradient,
1769 const QString &materialName)
1770{
1771 QQmlListReference materialsRef(item, "materials");
1772
1773 bool needNewMaterial = false;
1774 if (!materialsRef.size())
1775 needNewMaterial = true;
1776 else if (materialsRef.at(0)->objectName().contains(QStringLiteral("Instancing"))
1777 == materialName.contains(QStringLiteral("Instancing"))) {
1778 needNewMaterial = true;
1779 }
1780
1781 if (needNewMaterial) {
1782 materialsRef.clear();
1783 auto *material = createQmlCustomMaterial(fileName: materialName);
1784 material->setObjectName(materialName);
1785 material->setParent(item);
1786 materialsRef.append(material);
1787 }
1788 int colorStyle;
1789 if (!useGradient)
1790 colorStyle = 0; // style is uniform
1791 else if (!rangeGradient)
1792 colorStyle = 1; // style is objectGradient
1793 else
1794 colorStyle = 2; // style is rangeGradient
1795
1796 auto *material = qobject_cast<QQuick3DCustomMaterial *>(object: materialsRef.at(0));
1797 material->setProperty(name: "colorStyle", value: colorStyle);
1798}
1799
1800void QQuickGraphsBars::updateMaterialProperties(QQuick3DModel *item,
1801 const bool isHighlight,
1802 const bool isMultiHighlight,
1803 QQuick3DTexture *texture,
1804 QColor color,
1805 const bool transparency)
1806{
1807 QQmlListReference materialsRef(item, "materials");
1808 auto customMaterial = qobject_cast<QQuick3DCustomMaterial *>(object: materialsRef.at(0));
1809 if (!customMaterial)
1810 return;
1811 customMaterial->setProperty(name: "transparency", value: transparency);
1812 QVariant textureInputAsVariant = customMaterial->property(name: "custex");
1813 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
1814 .value<QQuick3DShaderUtilsTextureInput *>();
1815
1816 int colorStyle = customMaterial->property(name: "colorStyle").value<int>();
1817 if (colorStyle == 0) {
1818 customMaterial->setProperty(name: "uniformColor", value: color);
1819 } else {
1820 if (!isHighlight && !isMultiHighlight)
1821 textureInput->setTexture(texture);
1822 else
1823 textureInput->setTexture(isHighlight ? m_highlightTexture : m_multiHighlightTexture);
1824 customMaterial->setProperty(name: "isHighlight", value: isHighlight || isMultiHighlight);
1825 }
1826 customMaterial->setProperty(name: "specularBrightness", value: lightStrength() * 0.05);
1827}
1828
1829void QQuickGraphsBars::removeBarModels()
1830{
1831 for (const auto list : std::as_const(t&: m_barModelsMap)) {
1832 for (auto barModel : *list) {
1833 deleteBarModels(model: barModel->model);
1834 if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1835 deleteBarItemHolders(instancing: barModel->instancing);
1836 deleteBarItemHolders(instancing: barModel->selectionInstancing);
1837 deleteBarItemHolders(instancing: barModel->multiSelectionInstancing);
1838 deleteBarModels(model: barModel->selectedModel);
1839 deleteBarModels(model: barModel->multiSelectedModel);
1840 }
1841 delete barModel;
1842 }
1843 delete list;
1844 }
1845
1846 m_barModelsMap.clear();
1847 setSelectedBar(coord: m_selectedBar, series: m_selectedBarSeries, enterSlice: false);
1848}
1849
1850void QQuickGraphsBars::deleteBarModels(QQuick3DModel *model)
1851{
1852 if (model) {
1853 model->setPickable(false);
1854 model->setVisible(false);
1855 QQmlListReference materialsRef(model, "materials");
1856 if (materialsRef.size()) {
1857 auto material = materialsRef.at(0);
1858 delete material;
1859 }
1860 delete model;
1861 }
1862}
1863
1864void QQuickGraphsBars::deleteBarItemHolders(BarInstancing *instancing)
1865{
1866 if (instancing) {
1867 const QList<BarItemHolder *> barItemList = instancing->dataArray();
1868 for (const auto bih : barItemList)
1869 delete bih;
1870 instancing->clearDataArray();
1871 }
1872}
1873
1874QQuick3DTexture *QQuickGraphsBars::createTexture()
1875{
1876 QQuick3DTexture *texture = new QQuick3DTexture();
1877 texture->setParent(this);
1878 texture->setRotationUV(-90.0f);
1879 texture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
1880 texture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
1881 QQuickGraphsTextureData *textureData = new QQuickGraphsTextureData();
1882 textureData->setParent(texture);
1883 textureData->setParentItem(texture);
1884 texture->setTextureData(textureData);
1885
1886 return texture;
1887}
1888
1889bool QQuickGraphsBars::doPicking(QPointF position)
1890{
1891 if (!QQuickGraphsItem::doPicking(point: position))
1892 return false;
1893
1894 m_selectionDirty = true;
1895 QList<QQuick3DPickResult> pickResults = pickAll(x: position.x(), y: position.y());
1896 QQuick3DModel *selectedModel = nullptr;
1897 QVector3D instancePos = {.0f, .0f, .0f};
1898 if (!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None)) {
1899 if (!pickResults.isEmpty()) {
1900 for (const auto &picked : std::as_const(t&: pickResults)) {
1901 if (const auto &hit = picked.objectHit()) {
1902 if (hit == backgroundBB() || hit == background()) {
1903 resetClickedStatus();
1904 continue;
1905 } else if (hit->objectName().contains(QStringLiteral("BarModel"))) {
1906 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1907 selectedModel = hit;
1908 for (const auto barlist : std::as_const(t&: m_barModelsMap)) {
1909 for (const auto barModel : *barlist) {
1910 if (barModel->model == selectedModel) {
1911 setSelectedBar(coord: barModel->coord,
1912 series: m_barModelsMap.key(value: barlist),
1913 enterSlice: false);
1914 }
1915 }
1916 }
1917 break;
1918 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1919 BarInstancing *barIns = static_cast<BarInstancing *>(hit->instancing());
1920 // Prevents to select bars with a height of 0 which affect picking.
1921 if (!barIns->dataArray().isEmpty()
1922 && barIns->dataArray().at(i: picked.instanceIndex())->heightValue
1923 != 0) {
1924 selectedModel = hit;
1925 instancePos = selectedModel->instancing()->instancePosition(
1926 index: picked.instanceIndex());
1927 for (const auto barlist : std::as_const(t&: m_barModelsMap)) {
1928 for (const auto barModel : *barlist) {
1929 QList<BarItemHolder *> barItemList = barModel->instancing
1930 ->dataArray();
1931 for (const auto bih : barItemList) {
1932 if (bih->position == instancePos) {
1933 setSelectedBar(coord: bih->coord,
1934 series: m_barModelsMap.key(value: barlist),
1935 enterSlice: false);
1936 if (isSliceEnabled())
1937 setSliceActivatedChanged(true);
1938 }
1939 }
1940 }
1941 }
1942 break;
1943 }
1944 }
1945 } else if (hit->objectName().contains(QStringLiteral("ElementAxis"))) {
1946 QPoint coord = invalidSelectionPosition();
1947 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column)
1948 && selectedAxis() == axisX()) {
1949 // Use row from previous selection in case of row + column mode
1950 int previousRow = qMax(a: 0, b: m_selectedBar.x());
1951 coord = QPoint(previousRow, selectedLabelIndex());
1952 } else if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row)
1953 && selectedAxis() == axisZ()) {
1954 // Use column from previous selection in case of row + column mode
1955 int previousCol = qMax(a: 0, b: m_selectedBar.y());
1956 coord = QPoint(selectedLabelIndex(), previousCol);
1957 }
1958 for (auto it = m_barModelsMap.begin(); it != m_barModelsMap.end(); it++) {
1959 if (it.key()->isVisible())
1960 setSelectedBar(coord, series: it.key(), enterSlice: false);
1961 }
1962 break;
1963 }
1964 }
1965 }
1966 } else {
1967 resetClickedStatus();
1968 }
1969 }
1970 return true;
1971}
1972
1973QAbstract3DAxis *QQuickGraphsBars::createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation)
1974{
1975 QAbstract3DAxis *defaultAxis = 0;
1976
1977 if (orientation == QAbstract3DAxis::AxisOrientation::Y)
1978 defaultAxis = createDefaultValueAxis();
1979 else
1980 defaultAxis = createDefaultCategoryAxis();
1981
1982 return defaultAxis;
1983}
1984
1985void QQuickGraphsBars::adjustSelectionPosition(QPoint &pos, const QBar3DSeries *series)
1986{
1987 const QBarDataProxy *proxy = 0;
1988 if (series)
1989 proxy = series->dataProxy();
1990
1991 if (!proxy)
1992 pos = invalidSelectionPosition();
1993
1994 if (pos != invalidSelectionPosition()) {
1995 qsizetype maxRow = proxy->rowCount() - 1;
1996 qsizetype maxCol = (pos.x() <= maxRow && pos.x() >= 0 && !proxy->rowAt(rowIndex: pos.x()).isEmpty())
1997 ? proxy->rowAt(rowIndex: pos.x()).size() - 1
1998 : -1;
1999
2000 if (pos.x() < 0 || pos.x() > maxRow || pos.y() < 0 || pos.y() > maxCol)
2001 pos = invalidSelectionPosition();
2002 }
2003}
2004
2005void QQuickGraphsBars::setSelectedBar(QPoint coord, QBar3DSeries *series, bool enterSlice)
2006{
2007 // If the selection targets non-existent bar, clear selection instead.
2008 QPoint pos = coord;
2009
2010 // Series may already have been removed, so check it before setting the
2011 // selection.
2012 if (!m_seriesList.contains(t: series))
2013 series = nullptr;
2014
2015 adjustSelectionPosition(pos, series);
2016
2017 if (series && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Slice)) {
2018 // If the selected bar is outside data window, or there is no visible
2019 // selected bar, disable slicing.
2020 if (pos.x() < m_axisZ->min() || pos.x() > m_axisZ->max() || pos.y() < m_axisX->min()
2021 || pos.y() > m_axisX->max() || !series->isVisible()) {
2022 scene()->setSlicingActive(false);
2023 } else if (enterSlice) {
2024 scene()->setSlicingActive(true);
2025 }
2026 emitNeedRender();
2027 }
2028
2029 if (pos != m_selectedBar || series != m_selectedBarSeries) {
2030 bool seriesChanged = (series != m_selectedBarSeries);
2031 m_selectedBar = pos;
2032 m_selectedBarSeries = series;
2033 m_changeTracker.selectedBarChanged = true;
2034 checkSliceEnabled();
2035
2036 // Clear selection from other series and finally set new selection to the
2037 // specified series
2038 for (QAbstract3DSeries *otherSeries : m_seriesList) {
2039 QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(otherSeries);
2040 if (barSeries != m_selectedBarSeries)
2041 barSeries->d_func()->setSelectedBar(invalidSelectionPosition());
2042 }
2043 if (m_selectedBarSeries)
2044 m_selectedBarSeries->d_func()->setSelectedBar(m_selectedBar);
2045
2046 if (seriesChanged)
2047 emit selectedSeriesChanged(series: m_selectedBarSeries);
2048
2049 if (pos == invalidSelectionPosition())
2050 resetClickedStatus();
2051
2052 setSeriesVisualsDirty(true);
2053 emitNeedRender();
2054 }
2055}
2056
2057void QQuickGraphsBars::updateSelectedBar()
2058{
2059 for (auto it = m_barModelsMap.begin(); it != m_barModelsMap.end(); it++) {
2060 if (m_selectedBarSeries && it.key()->isVisible()) {
2061 QString label = m_selectedBarSeries->itemLabel();
2062 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
2063 for (auto barList : *it.value()) {
2064 QQuickGraphsBars::SelectionType selectionType = isSelected(row: barList->coord.x(),
2065 bar: barList->coord.y(),
2066 series: it.key());
2067 switch (selectionType) {
2068 case QQuickGraphsBars::SelectionItem: {
2069 updateMaterialProperties(item: barList->model,
2070 isHighlight: true,
2071 isMultiHighlight: false,
2072 texture: barList->texture,
2073 color: it.key()->singleHighlightColor());
2074
2075 m_selectedBarPos = barList->model->position();
2076 QString label = (m_selectedBarSeries->d_func()->itemLabel());
2077
2078 if (barList->heightValue >= 0.0f) {
2079 m_selectedBarPos.setY(m_selectedBarPos.y() + barList->heightValue
2080 + 0.2f);
2081 } else {
2082 m_selectedBarPos.setY(m_selectedBarPos.y() + barList->heightValue
2083 - 0.2f);
2084 }
2085
2086 updateItemLabel(position: m_selectedBarPos);
2087 itemLabel()->setVisible(theme()->labelsVisible());
2088 itemLabel()->setProperty(name: "labelText", value: label);
2089 if (isSliceEnabled())
2090 updateSliceItemLabel(label, position: m_selectedBarPos);
2091 break;
2092 }
2093 case QQuickGraphsBars::SelectionRow:
2094 case QQuickGraphsBars::SelectionColumn: {
2095 updateMaterialProperties(item: barList->model,
2096 isHighlight: false,
2097 isMultiHighlight: true,
2098 texture: barList->texture,
2099 color: it.key()->multiHighlightColor());
2100 break;
2101 }
2102 default:
2103 break;
2104 }
2105 }
2106 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2107 QList<BarModel *> barList = *m_barModelsMap.value(key: it.key());
2108 deleteBarItemHolders(instancing: barList.at(i: 0)->selectionInstancing);
2109 deleteBarItemHolders(instancing: barList.at(i: 0)->multiSelectionInstancing);
2110 createBarItemHolders(series: it.key(), barList, slice: false);
2111 }
2112 }
2113 }
2114}
2115
2116QQuickGraphsItem::SelectionType QQuickGraphsBars::isSelected(int row, int bar, QBar3DSeries *series)
2117{
2118 QQuickGraphsBars::SelectionType isSelectedType = QQuickGraphsBars::SelectionNone;
2119 if ((selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries) && m_selectedBarSeries)
2120 || series == m_selectedBarSeries) {
2121 if (row == m_selectedBar.x() && bar == m_selectedBar.y()
2122 && (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item))) {
2123 isSelectedType = QQuickGraphsBars::SelectionItem;
2124 } else if (row == m_selectedBar.x()
2125 && (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row))) {
2126 isSelectedType = QQuickGraphsBars::SelectionRow;
2127 } else if (bar == m_selectedBar.y()
2128 && (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column))) {
2129 isSelectedType = QQuickGraphsBars::SelectionColumn;
2130 }
2131 }
2132
2133 return isSelectedType;
2134}
2135
2136void QQuickGraphsBars::updateSliceItemLabel(const QString &label, QVector3D position)
2137{
2138 QQuickGraphsItem::updateSliceItemLabel(label, position);
2139
2140 QFontMetrics fm(theme()->labelFont());
2141 float textPadding = theme()->labelFont().pointSizeF() * .7f;
2142 float labelHeight = fm.height() + textPadding;
2143 float labelWidth = fm.horizontalAdvance(label) + textPadding;
2144 sliceItemLabel()->setProperty(name: "labelWidth", value: labelWidth);
2145 sliceItemLabel()->setProperty(name: "labelHeight", value: labelHeight);
2146 QVector3D slicePos = position;
2147 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column))
2148 slicePos.setX(slicePos.z());
2149 else if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row))
2150 slicePos.setX(slicePos.x());
2151 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(axisY());
2152 if (valueAxis->reversed())
2153 slicePos.setY(slicePos.y() - (textPadding * .06f));
2154 else
2155 slicePos.setY(slicePos.y() + (textPadding * .06f));
2156 slicePos.setZ(.1f);
2157 sliceItemLabel()->setPosition(slicePos);
2158 sliceItemLabel()->setProperty(name: "labelText", value: label);
2159 sliceItemLabel()->setEulerRotation(QVector3D(0.0f, 0.0f, 90.0f));
2160 sliceItemLabel()->setVisible(theme()->labelsVisible());
2161}
2162
2163void QQuickGraphsBars::resetClickedStatus()
2164{
2165 m_selectedBarPos = QVector3D(0.0f, 0.0f, 0.0f);
2166 m_selectedBar = QQuickGraphsBars::invalidSelectionPosition();
2167 m_selectedBarSeries = 0;
2168 clearSelection();
2169
2170 if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2171 for (const auto barList : std::as_const(t&: m_barModelsMap)) {
2172 QList<BarItemHolder *> barItemList = barList->at(i: 0)->instancing->dataArray();
2173 for (auto bih : barItemList)
2174 bih->selectedBar = false;
2175 }
2176 }
2177
2178 if (sliceView() && sliceView()->isVisible()) {
2179 setSliceActivatedChanged(true);
2180 m_selectionDirty = true;
2181 }
2182 setSeriesVisualsDirty(true);
2183}
2184
2185void QQuickGraphsBars::createSliceView()
2186{
2187 setSliceOrthoProjection(false);
2188 QQuickGraphsItem::createSliceView();
2189 QList<QBar3DSeries *> barSeries = barSeriesList();
2190 for (const auto &barSeries : std::as_const(t&: barSeries)) {
2191 QList<BarModel *> &slicedBarList = m_slicedBarModels[barSeries];
2192
2193 if (slicedBarList.isEmpty()) {
2194 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
2195 qsizetype dataRowIndex = m_minRow;
2196 qsizetype newRowSize = qMin(a: barSeries->dataProxy()->rowCount() - dataRowIndex,
2197 b: m_newRows);
2198 qsizetype newColSize = 0;
2199 if (newRowSize) {
2200 const QBarDataRow *dataRow = &barSeries->dataProxy()->rowAt(rowIndex: dataRowIndex);
2201 if (dataRow) {
2202 qsizetype dataColIndex = m_minCol;
2203 newColSize = qMin(a: dataRow->size() - dataColIndex, b: m_newCols);
2204 }
2205 }
2206 qsizetype slicedBarListSize = 0;
2207
2208 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row))
2209 slicedBarListSize = newColSize;
2210 else if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column))
2211 slicedBarListSize = newRowSize;
2212
2213 for (int ind = 0; ind < slicedBarListSize; ++ind) {
2214 QQuick3DModel *model = createDataItem(scene: sliceView()->scene(), series: barSeries);
2215 model->setVisible(false);
2216 BarModel *barModel = new BarModel();
2217 barModel->model = model;
2218
2219 if (!slicedBarList.contains(t: barModel))
2220 slicedBarList.append(t: barModel);
2221 }
2222 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2223 BarModel *barInstancing = new BarModel();
2224
2225 if (barInstancing->selectionInstancing == nullptr) {
2226 barInstancing->selectionInstancing = new BarInstancing;
2227 barInstancing->selectionInstancing->setParent(barSeries);
2228 barInstancing->multiSelectionInstancing = new BarInstancing;
2229 barInstancing->multiSelectionInstancing->setParent(barSeries);
2230 }
2231
2232 if (barInstancing->selectedModel == nullptr) {
2233 barInstancing->selectedModel = createDataItem(scene: sliceView()->scene(), series: barSeries);
2234 barInstancing->selectedModel->setInstancing(barInstancing->selectionInstancing);
2235 barInstancing->selectedModel->setVisible(false);
2236 barInstancing->multiSelectedModel = createDataItem(scene: sliceView()->scene(),
2237 series: barSeries);
2238 barInstancing->multiSelectedModel->setInstancing(
2239 barInstancing->multiSelectionInstancing);
2240 barInstancing->multiSelectedModel->setVisible(false);
2241 }
2242
2243 if (!slicedBarList.contains(t: barInstancing))
2244 slicedBarList.append(t: barInstancing);
2245 }
2246 }
2247 }
2248}
2249
2250void QQuickGraphsBars::toggleSliceGraph()
2251{
2252 if (m_selectionDirty)
2253 QQuickGraphsItem::toggleSliceGraph();
2254
2255 if (sliceView() && !sliceView()->isVisible()) {
2256 removeSlicedBarModels();
2257 m_changeTracker.selectedBarChanged = false;
2258 return;
2259 }
2260
2261 qsizetype index = 0;
2262 bool rowMode = selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row);
2263 for (auto it = m_slicedBarModels.begin(); it != m_slicedBarModels.end(); it++) {
2264 bool useGradient = it.key()->d_func()->isUsingGradient();
2265 bool rangeGradient = (useGradient
2266 && it.key()->d_func()->m_colorStyle
2267 == QGraphsTheme::ColorStyle::RangeGradient);
2268 QList<BarModel *> barList = *m_barModelsMap.value(key: it.key());
2269 QList<BarModel *> sliceList = it.value();
2270 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
2271 for (int ind = 0; ind < it.value().count(); ++ind) {
2272 if (rowMode)
2273 index = (m_selectedBar.x() * it.key()->dataProxy()->colCount()) + ind;
2274 else
2275 index = m_selectedBar.y() + (ind * it.key()->dataProxy()->colCount());
2276 bool visible = ((m_selectedBarSeries == it.key()
2277 || selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries))
2278 && it.key()->isVisible());
2279 if (index < barList.size() && m_selectedBar != invalidSelectionPosition()) {
2280 BarModel *sliceBarModel = sliceList.at(i: ind);
2281 BarModel *barModel = barList.at(i: index);
2282
2283 sliceBarModel->model->setVisible(visible);
2284 if (rowMode) {
2285 sliceBarModel->model->setPosition(
2286 QVector3D(barModel->model->x(), barModel->model->y(), 0.0f));
2287 } else {
2288 sliceBarModel->model->setX(barModel->model->z()
2289 - (barList.at(i: 0)->visualIndex * .1f));
2290 sliceBarModel->model->setY(barModel->model->y());
2291 sliceBarModel->model->setZ(0.0f);
2292 }
2293 sliceBarModel->model->setScale(barModel->model->scale());
2294
2295 updateItemMaterial(item: sliceBarModel->model,
2296 useGradient,
2297 rangeGradient,
2298 QStringLiteral(":/materials/BarsMaterial"));
2299
2300 if (barModel->coord == m_selectedBar
2301 && selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Item)) {
2302 updateMaterialProperties(item: sliceBarModel->model,
2303 isHighlight: true,
2304 isMultiHighlight: false,
2305 texture: barList.at(i: index)->texture,
2306 color: it.key()->singleHighlightColor());
2307 updateSliceItemLabel(label: m_selectedBarSeries->itemLabel(), position: m_selectedBarPos);
2308 } else {
2309 updateMaterialProperties(item: sliceBarModel->model,
2310 isHighlight: false,
2311 isMultiHighlight: false,
2312 texture: barList.at(i: index)->texture,
2313 color: it.key()->baseColor());
2314 }
2315 } else {
2316 setSliceActivatedChanged(true);
2317 QQuickGraphsItem::toggleSliceGraph();
2318 setSliceEnabled(false);
2319 return;
2320 }
2321 }
2322 setSliceActivatedChanged(false);
2323 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2324 deleteBarItemHolders(instancing: sliceList.at(i: 0)->selectionInstancing);
2325 deleteBarItemHolders(instancing: sliceList.at(i: 0)->multiSelectionInstancing);
2326 createBarItemHolders(series: it.key(), barList, slice: true);
2327 setSliceActivatedChanged(false);
2328 }
2329 }
2330}
2331
2332void QQuickGraphsBars::handleLabelCountChanged(QQuick3DRepeater *repeater, QColor axisLabelColor)
2333{
2334 QQuickGraphsItem::handleLabelCountChanged(repeater, axisLabelColor);
2335
2336 if (repeater == repeaterX())
2337 handleDataColumnLabelsChanged();
2338 if (repeater == repeaterZ())
2339 handleDataRowLabelsChanged();
2340}
2341
2342void QQuickGraphsBars::removeSlicedBarModels()
2343{
2344 for (auto &list : std::as_const(t&: m_slicedBarModels)) {
2345 for (auto barModel : list) {
2346 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
2347 deleteBarModels(model: barModel->model);
2348 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2349 deleteBarItemHolders(instancing: barModel->selectionInstancing);
2350 deleteBarItemHolders(instancing: barModel->multiSelectionInstancing);
2351 deleteBarModels(model: barModel->selectedModel);
2352 deleteBarModels(model: barModel->multiSelectedModel);
2353 }
2354 delete barModel;
2355 }
2356 }
2357
2358 m_slicedBarModels.clear();
2359}
2360
2361void QQuickGraphsBars::createBarItemHolders(QBar3DSeries *series,
2362 QList<BarModel *> barList,
2363 bool slice)
2364{
2365 bool useGradient = series->d_func()->isUsingGradient();
2366 bool rangeGradient = (useGradient
2367 && series->d_func()->m_colorStyle == QGraphsTheme::ColorStyle::RangeGradient);
2368 bool visible = ((m_selectedBarSeries == series
2369 || selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::MultiSeries))
2370 && series->isVisible());
2371 QList<BarItemHolder *> barItemList;
2372 QList<BarItemHolder *> multiBarItemList;
2373 QQuick3DModel *selectedModel = nullptr;
2374 QQuick3DModel *multiSelectedModel = nullptr;
2375 QList<BarItemHolder *> selectedItem;
2376 QList<BarItemHolder *> multiSelectedItems;
2377 BarInstancing *instancing;
2378 BarInstancing *multiInstancing;
2379
2380 if (slice) {
2381 QList<BarModel *> sliceBarList = m_slicedBarModels.value(key: series);
2382 barItemList = barList.at(i: 0)->selectionInstancing->dataArray();
2383 multiBarItemList = barList.at(i: 0)->multiSelectionInstancing->dataArray();
2384 selectedModel = sliceBarList.at(i: 0)->selectedModel;
2385 multiSelectedModel = sliceBarList.at(i: 0)->multiSelectedModel;
2386 instancing = sliceBarList.at(i: 0)->selectionInstancing;
2387 multiInstancing = sliceBarList.at(i: 0)->multiSelectionInstancing;
2388 } else {
2389 barItemList = barList.at(i: 0)->instancing->dataArray();
2390 selectedModel = barList.at(i: 0)->selectedModel;
2391 multiSelectedModel = barList.at(i: 0)->multiSelectedModel;
2392 instancing = barList.at(i: 0)->selectionInstancing;
2393 multiInstancing = barList.at(i: 0)->multiSelectionInstancing;
2394 }
2395
2396 for (const auto bih : std::as_const(t&: barItemList)) {
2397 QQuickGraphsBars::SelectionType selectionType = isSelected(row: bih->coord.x(),
2398 bar: bih->coord.y(),
2399 series);
2400 switch (selectionType) {
2401 case QQuickGraphsBars::SelectionItem: {
2402 updateItemMaterial(item: selectedModel,
2403 useGradient,
2404 rangeGradient,
2405 QStringLiteral(":/materials/BarsMaterialInstancing"));
2406 updateMaterialProperties(item: selectedModel,
2407 isHighlight: true,
2408 isMultiHighlight: false,
2409 texture: barList.at(i: 0)->texture,
2410 color: QColor(Qt::white));
2411 if (!slice)
2412 bih->selectedBar = true;
2413 selectedModel->setVisible(visible);
2414 BarItemHolder *selectedBih = new BarItemHolder();
2415 selectedBih->selectedBar = false;
2416 selectedBih->color = series->singleHighlightColor();
2417 selectedBih->coord = bih->coord;
2418 selectedBih->eulerRotation = bih->eulerRotation;
2419 selectedBih->heightValue = bih->heightValue;
2420 selectedBih->position = bih->position;
2421 selectedBih->scale = bih->scale;
2422
2423 QString label = m_selectedBarSeries->itemLabel();
2424 if (slice) {
2425 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row)) {
2426 selectedBih->position.setZ(.0f);
2427 } else {
2428 selectedBih->position.setX(selectedBih->position.z()
2429 - (barList.at(i: 0)->visualIndex * .1f));
2430 selectedBih->position.setZ(.0f);
2431 }
2432 updateSliceItemLabel(label, position: m_selectedBarPos);
2433 }
2434
2435 selectedItem.push_back(t: selectedBih);
2436 instancing->setDataArray(selectedItem);
2437
2438 m_selectedBarPos = bih->position;
2439
2440 if (bih->heightValue >= 0.0f)
2441 m_selectedBarPos.setY(m_selectedBarPos.y() + bih->heightValue + 0.2f);
2442 else
2443 m_selectedBarPos.setY(m_selectedBarPos.y() + bih->heightValue - 0.2f);
2444
2445 updateItemLabel(position: m_selectedBarPos);
2446 itemLabel()->setVisible(theme()->labelsVisible());
2447 itemLabel()->setProperty(name: "labelText", value: label);
2448 if (isSliceEnabled())
2449 updateSliceItemLabel(label, position: m_selectedBarPos);
2450 break;
2451 }
2452 case QQuickGraphsBars::SelectionRow:
2453 case QQuickGraphsBars::SelectionColumn: {
2454 updateItemMaterial(item: multiSelectedModel,
2455 useGradient,
2456 rangeGradient,
2457 QStringLiteral(":/materials/BarsMaterialInstancing"));
2458 updateMaterialProperties(item: multiSelectedModel,
2459 isHighlight: false,
2460 isMultiHighlight: true,
2461 texture: barList.at(i: 0)->texture,
2462 color: QColor(Qt::white));
2463 if (!slice)
2464 bih->selectedBar = true;
2465 multiSelectedModel->setVisible(visible);
2466 BarItemHolder *selectedBih = new BarItemHolder();
2467 selectedBih->selectedBar = false;
2468 selectedBih->color = series->multiHighlightColor();
2469 selectedBih->coord = bih->coord;
2470 selectedBih->eulerRotation = bih->eulerRotation;
2471 selectedBih->heightValue = bih->heightValue;
2472 selectedBih->position = bih->position;
2473 selectedBih->scale = bih->scale;
2474
2475 multiSelectedItems.push_back(t: selectedBih);
2476 multiInstancing->setDataArray(multiSelectedItems);
2477 break;
2478 }
2479 default:
2480 break;
2481 }
2482 }
2483
2484 if (slice) {
2485 for (const auto bih : std::as_const(t&: multiBarItemList)) {
2486 updateItemMaterial(item: multiSelectedModel,
2487 useGradient,
2488 rangeGradient,
2489 QStringLiteral(":/materials/BarsMaterialInstancing"));
2490 updateMaterialProperties(item: multiSelectedModel,
2491 isHighlight: false,
2492 isMultiHighlight: false,
2493 texture: barList.at(i: 0)->texture,
2494 color: QColor(Qt::white));
2495
2496 multiSelectedModel->setVisible(visible);
2497 BarItemHolder *selectedBih = new BarItemHolder();
2498 selectedBih->selectedBar = false;
2499 selectedBih->color = series->baseColor();
2500 selectedBih->coord = bih->coord;
2501 selectedBih->eulerRotation = bih->eulerRotation;
2502 selectedBih->heightValue = bih->heightValue;
2503 selectedBih->position = bih->position;
2504 selectedBih->scale = bih->scale;
2505
2506 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row)) {
2507 selectedBih->position.setZ(.0f);
2508 } else {
2509 selectedBih->position.setX(selectedBih->position.z()
2510 - (barList.at(i: 0)->visualIndex * .1f));
2511 selectedBih->position.setZ(.0f);
2512 }
2513
2514 multiSelectedItems.push_back(t: selectedBih);
2515 multiInstancing->setDataArray(multiSelectedItems);
2516 }
2517 }
2518}
2519
2520void QQuickGraphsBars::updateSelectionMode(QtGraphs3D::SelectionFlags mode)
2521{
2522 checkSliceEnabled();
2523 if (!sliceView())
2524 createSliceView();
2525
2526 bool validSlice = mode.testFlag(flag: QtGraphs3D::SelectionFlag::Slice) && m_selectedBarSeries;
2527 if (sliceView() && sliceView()->isVisible()) {
2528 if (validSlice) {
2529 removeSlicedBarModels();
2530 createSliceView();
2531 toggleSliceGraph();
2532 } else {
2533 m_selectionDirty = true;
2534 setSliceActivatedChanged(true);
2535 }
2536 } else if (validSlice) {
2537 m_selectionDirty = true;
2538 setSliceActivatedChanged(true);
2539 }
2540
2541 setSeriesVisualsDirty(true);
2542 itemLabel()->setVisible(false);
2543 if (sliceView() && !mode.testFlag(flag: QtGraphs3D::SelectionFlag::Item))
2544 sliceItemLabel()->setVisible(false);
2545}
2546
2547void QQuickGraphsBars::updateBarSpecs(float thicknessRatio, QSizeF spacing, bool relative)
2548{
2549 // Convert ratio to QSizeF, as we need it in that format for autoscaling
2550 // calculations
2551 m_cachedBarThickness.setWidth(1.0);
2552 m_cachedBarThickness.setHeight(1.0f / thicknessRatio);
2553
2554 if (relative) {
2555 m_cachedBarSpacing.setWidth((m_cachedBarThickness.width() * 2) * (spacing.width() + 1.0f));
2556 m_cachedBarSpacing.setHeight((m_cachedBarThickness.height() * 2)
2557 * (spacing.height() + 1.0f));
2558 } else {
2559 m_cachedBarSpacing = m_cachedBarThickness * 2 + spacing * 2;
2560 }
2561
2562 m_axisRangeChanged = true;
2563 m_changeTracker.selectedBarChanged = true;
2564
2565 // Calculate here and at setting sample space
2566 calculateSceneScalingFactors();
2567}
2568
2569void QQuickGraphsBars::updateBarSeriesMargin(QSizeF margin)
2570{
2571 m_cachedBarSeriesMargin = margin;
2572 calculateSeriesStartPosition();
2573 calculateSceneScalingFactors();
2574 setSeriesVisualsDirty(true);
2575}
2576

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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