1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "bars3dcontroller_p.h"
5#include "qvalue3daxis_p.h"
6#include "qcategory3daxis_p.h"
7#include "qbardataproxy_p.h"
8#include "qbar3dseries_p.h"
9#include "thememanager_p.h"
10#include "q3dtheme_p.h"
11#include <QtCore/QMutexLocker>
12
13QT_BEGIN_NAMESPACE
14
15Bars3DController::Bars3DController(QRect boundRect, Q3DScene *scene)
16 : Abstract3DController(boundRect, scene),
17 m_selectedBar(invalidSelectionPosition()),
18 m_selectedBarSeries(0),
19 m_primarySeries(0),
20 m_isMultiSeriesUniform(false),
21 m_isBarSpecRelative(true),
22 m_barThicknessRatio(1.0f),
23 m_barSpacing(QSizeF(1.0, 1.0)),
24 m_floorLevel(0.0f),
25 m_barSeriesMargin(0.0f, 0.0f)
26{
27 // Setting a null axis creates a new default axis according to orientation and graph type.
28 // Note: these cannot be set in the Abstract3DController constructor, as they will call virtual
29 // functions implemented by subclasses.
30 setAxisX(0);
31 setAxisY(0);
32 setAxisZ(0);
33}
34
35Bars3DController::~Bars3DController()
36{
37}
38
39void Bars3DController::handleArrayReset()
40{
41 QBar3DSeries *series;
42 if (qobject_cast<QBarDataProxy *>(object: sender()))
43 series = static_cast<QBarDataProxy *>(sender())->series();
44 else
45 series = static_cast<QBar3DSeries *>(sender());
46
47 if (series->isVisible()) {
48 adjustAxisRanges();
49 m_isDataDirty = true;
50 series->d_func()->markItemLabelDirty();
51 }
52 if (!m_changedSeriesList.contains(t: series))
53 m_changedSeriesList.append(t: series);
54 // Clear selection unless still valid
55 setSelectedBar(position: m_selectedBar, series: m_selectedBarSeries, enterSlice: false);
56 series->d_func()->markItemLabelDirty();
57 emitNeedRender();
58}
59
60void Bars3DController::handleRowsAdded(int startIndex, int count)
61{
62 Q_UNUSED(startIndex);
63 Q_UNUSED(count);
64 QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
65 if (series->isVisible()) {
66 adjustAxisRanges();
67 m_isDataDirty = true;
68 }
69 if (!m_changedSeriesList.contains(t: series))
70 m_changedSeriesList.append(t: series);
71 emitNeedRender();
72}
73
74void Bars3DController::handleRowsChanged(int startIndex, int count)
75{
76 QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
77 int oldChangeCount = m_changedRows.size();
78 if (!oldChangeCount)
79 m_changedRows.reserve(asize: count);
80
81 for (int i = 0; i < count; i++) {
82 bool newItem = true;
83 int candidate = startIndex + i;
84 for (int j = 0; j < oldChangeCount; j++) {
85 const ChangeRow &oldChangeItem = m_changedRows.at(i: j);
86 if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
87 newItem = false;
88 break;
89 }
90 }
91 if (newItem) {
92 ChangeRow newChangeItem = {.series: series, .row: candidate};
93 m_changedRows.append(t: newChangeItem);
94 if (series == m_selectedBarSeries && m_selectedBar.x() == candidate)
95 series->d_func()->markItemLabelDirty();
96 }
97 }
98 if (count) {
99 m_changeTracker.rowsChanged = true;
100
101 if (series->isVisible())
102 adjustAxisRanges();
103
104 // Clear selection unless still valid (row length might have changed)
105 setSelectedBar(position: m_selectedBar, series: m_selectedBarSeries, enterSlice: false);
106 emitNeedRender();
107 }
108}
109
110void Bars3DController::handleRowsRemoved(int startIndex, int count)
111{
112 Q_UNUSED(startIndex);
113 Q_UNUSED(count);
114
115 QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
116 if (series == m_selectedBarSeries) {
117 // If rows removed from selected series before the selection, adjust the selection
118 int selectedRow = m_selectedBar.x();
119 if (startIndex <= selectedRow) {
120 if ((startIndex + count) > selectedRow)
121 selectedRow = -1; // Selected row removed
122 else
123 selectedRow -= count; // Move selected row down by amount of rows removed
124
125 setSelectedBar(position: QPoint(selectedRow, m_selectedBar.y()), series: m_selectedBarSeries, enterSlice: false);
126 }
127 }
128
129 if (series->isVisible()) {
130 adjustAxisRanges();
131 m_isDataDirty = true;
132 }
133 if (!m_changedSeriesList.contains(t: series))
134 m_changedSeriesList.append(t: series);
135
136 emitNeedRender();
137}
138
139void Bars3DController::handleRowsInserted(int startIndex, int count)
140{
141 Q_UNUSED(startIndex);
142 Q_UNUSED(count);
143 QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
144 if (series == m_selectedBarSeries) {
145 // If rows inserted to selected series before the selection, adjust the selection
146 int selectedRow = m_selectedBar.x();
147 if (startIndex <= selectedRow) {
148 selectedRow += count;
149 setSelectedBar(position: QPoint(selectedRow, m_selectedBar.y()), series: m_selectedBarSeries, enterSlice: false);
150 }
151 }
152
153 if (series->isVisible()) {
154 adjustAxisRanges();
155 m_isDataDirty = true;
156 }
157 if (!m_changedSeriesList.contains(t: series))
158 m_changedSeriesList.append(t: series);
159
160 emitNeedRender();
161}
162
163void Bars3DController::handleItemChanged(int rowIndex, int columnIndex)
164{
165 QBar3DSeries *series = static_cast<QBarDataProxy *>(sender())->series();
166
167 bool newItem = true;
168 QPoint candidate(rowIndex, columnIndex);
169 foreach (ChangeItem item, m_changedItems) {
170 if (item.point == candidate && item.series == series) {
171 newItem = false;
172 break;
173 }
174 }
175
176 if (newItem) {
177 ChangeItem newItem = {.series: series, .point: candidate};
178 m_changedItems.append(t: newItem);
179 m_changeTracker.itemChanged = true;
180
181 if (series == m_selectedBarSeries && m_selectedBar == candidate)
182 series->d_func()->markItemLabelDirty();
183 if (series->isVisible())
184 adjustAxisRanges();
185 emitNeedRender();
186 }
187}
188
189void Bars3DController::handleDataRowLabelsChanged()
190{
191 if (m_axisZ) {
192 // Grab a sublist equal to data window (no need to have more labels in axis)
193 int min = int(m_axisZ->min());
194 int count = int(m_axisZ->max()) - min + 1;
195 QStringList subList;
196 if (m_primarySeries && m_primarySeries->dataProxy())
197 subList = m_primarySeries->dataProxy()->rowLabels().mid(pos: min, len: count);
198 static_cast<QCategory3DAxis *>(m_axisZ)->d_func()->setDataLabels(subList);
199 }
200}
201
202void Bars3DController::handleDataColumnLabelsChanged()
203{
204 if (m_axisX) {
205 // Grab a sublist equal to data window (no need to have more labels in axis)
206 int min = int(m_axisX->min());
207 int count = int(m_axisX->max()) - min + 1;
208 QStringList subList;
209 if (m_primarySeries && m_primarySeries->dataProxy()) {
210 subList = static_cast<QBarDataProxy *>(m_primarySeries->dataProxy())
211 ->columnLabels().mid(pos: min, len: count);
212 }
213 static_cast<QCategory3DAxis *>(m_axisX)->d_func()->setDataLabels(subList);
214 }
215}
216
217void Bars3DController::handleRowColorsChanged()
218{
219 emitNeedRender();
220}
221
222void Bars3DController::handleAxisAutoAdjustRangeChangedInOrientation(
223 QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
224{
225 Q_UNUSED(orientation);
226 Q_UNUSED(autoAdjust);
227 adjustAxisRanges();
228}
229
230void Bars3DController::handleSeriesVisibilityChangedBySender(QObject *sender)
231{
232 Abstract3DController::handleSeriesVisibilityChangedBySender(sender);
233
234 // Visibility changes may require disabling slicing,
235 // so just reset selection to ensure everything is still valid.
236 setSelectedBar(position: m_selectedBar, series: m_selectedBarSeries, enterSlice: false);
237}
238
239QPoint Bars3DController::invalidSelectionPosition()
240{
241 static QPoint invalidSelectionPos(-1, -1);
242 return invalidSelectionPos;
243}
244
245void Bars3DController::setAxisX(QAbstract3DAxis *axis)
246{
247 Abstract3DController::setAxisX(axis);
248 handleDataColumnLabelsChanged();
249}
250
251void Bars3DController::setAxisZ(QAbstract3DAxis *axis)
252{
253 Abstract3DController::setAxisZ(axis);
254 handleDataRowLabelsChanged();
255}
256
257void Bars3DController::setPrimarySeries(QBar3DSeries *series)
258{
259 if (!series) {
260 if (m_seriesList.size())
261 series = static_cast<QBar3DSeries *>(m_seriesList.at(i: 0));
262 } else if (!m_seriesList.contains(t: series)) {
263 // Add nonexistent series.
264 addSeries(series);
265 }
266
267 if (m_primarySeries != series) {
268 m_primarySeries = series;
269 handleDataRowLabelsChanged();
270 handleDataColumnLabelsChanged();
271 emit primarySeriesChanged(series: m_primarySeries);
272 }
273}
274
275QBar3DSeries *Bars3DController::primarySeries() const
276{
277 return m_primarySeries;
278}
279
280void Bars3DController::addSeries(QAbstract3DSeries *series)
281{
282 insertSeries(index: m_seriesList.size(), series);
283}
284
285void Bars3DController::removeSeries(QAbstract3DSeries *series)
286{
287 bool wasVisible = (series && series->d_func()->m_controller == this && series->isVisible());
288
289 Abstract3DController::removeSeries(series);
290
291 if (m_selectedBarSeries == series)
292 setSelectedBar(position: invalidSelectionPosition(), series: 0, enterSlice: false);
293
294 if (wasVisible)
295 adjustAxisRanges();
296
297 // If primary series is removed, reset it to default
298 if (series == m_primarySeries) {
299 if (m_seriesList.size())
300 m_primarySeries = static_cast<QBar3DSeries *>(m_seriesList.at(i: 0));
301 else
302 m_primarySeries = 0;
303
304 handleDataRowLabelsChanged();
305 handleDataColumnLabelsChanged();
306
307 emit primarySeriesChanged(series: m_primarySeries);
308 }
309}
310
311void Bars3DController::insertSeries(int index, QAbstract3DSeries *series)
312{
313 Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesTypeBar);
314
315 int oldSize = m_seriesList.size();
316
317 Abstract3DController::insertSeries(index, series);
318
319 if (oldSize != m_seriesList.size()) {
320 QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(series);
321 if (!oldSize) {
322 m_primarySeries = barSeries;
323 handleDataRowLabelsChanged();
324 handleDataColumnLabelsChanged();
325 }
326
327 if (barSeries->selectedBar() != invalidSelectionPosition())
328 setSelectedBar(position: barSeries->selectedBar(), series: barSeries, enterSlice: false);
329
330 if (!oldSize)
331 emit primarySeriesChanged(series: m_primarySeries);
332 }
333}
334
335QList<QBar3DSeries *> Bars3DController::barSeriesList()
336{
337 QList<QAbstract3DSeries *> abstractSeriesList = seriesList();
338 QList<QBar3DSeries *> barSeriesList;
339 foreach (QAbstract3DSeries *abstractSeries, abstractSeriesList) {
340 QBar3DSeries *barSeries = qobject_cast<QBar3DSeries *>(object: abstractSeries);
341 if (barSeries)
342 barSeriesList.append(t: barSeries);
343 }
344
345 return barSeriesList;
346}
347
348void Bars3DController::handleAxisRangeChangedBySender(QObject *sender)
349{
350 // Data window changed
351 if (sender == m_axisX || sender == m_axisZ) {
352 if (sender == m_axisX)
353 handleDataColumnLabelsChanged();
354 if (sender == m_axisZ)
355 handleDataRowLabelsChanged();
356 }
357
358 Abstract3DController::handleAxisRangeChangedBySender(sender);
359
360 m_isDataDirty = true;
361
362 // Update selected bar - may be moved offscreen
363 setSelectedBar(position: m_selectedBar, series: m_selectedBarSeries, enterSlice: false);
364}
365
366void Bars3DController::setMultiSeriesScaling(bool uniform)
367{
368 m_isMultiSeriesUniform = uniform;
369
370 m_changeTracker.multiSeriesScalingChanged = true;
371 emitNeedRender();
372}
373
374bool Bars3DController::multiSeriesScaling() const
375{
376 return m_isMultiSeriesUniform;
377}
378
379void Bars3DController::setBarSpecs(float thicknessRatio, const QSizeF &spacing, bool relative)
380{
381 m_barThicknessRatio = thicknessRatio;
382 m_barSpacing = spacing;
383 m_isBarSpecRelative = relative;
384
385 m_changeTracker.barSpecsChanged = true;
386 emitNeedRender();
387}
388
389float Bars3DController::barThickness()
390{
391 return m_barThicknessRatio;
392}
393
394QSizeF Bars3DController::barSpacing()
395{
396 return m_barSpacing;
397}
398
399void Bars3DController::setBarSeriesMargin(const QSizeF &margin)
400{
401 m_barSeriesMargin = margin;
402 m_changeTracker.barSeriesMarginChanged = true;
403 emitNeedRender();
404}
405
406QSizeF Bars3DController::barSeriesMargin()
407{
408 return m_barSeriesMargin;
409}
410
411bool Bars3DController::isBarSpecRelative()
412{
413 return m_isBarSpecRelative;
414}
415
416void Bars3DController::setFloorLevel(float level)
417{
418 m_floorLevel = level;
419 m_isDataDirty = true;
420 m_changeTracker.floorLevelChanged = true;
421 emitNeedRender();
422}
423
424float Bars3DController::floorLevel() const
425{
426 return m_floorLevel;
427}
428
429void Bars3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
430{
431 if (mode.testFlag(flag: QAbstract3DGraph::SelectionSlice)
432 && (mode.testFlag(flag: QAbstract3DGraph::SelectionRow)
433 == mode.testFlag(flag: QAbstract3DGraph::SelectionColumn))) {
434 qWarning(msg: "Must specify one of either row or column selection mode in conjunction with slicing mode.");
435 } else {
436 QAbstract3DGraph::SelectionFlags oldMode = selectionMode();
437
438 Abstract3DController::setSelectionMode(mode);
439
440 if (mode != oldMode) {
441 // Refresh selection upon mode change to ensure slicing is correctly updated
442 // according to series the visibility.
443 setSelectedBar(position: m_selectedBar, series: m_selectedBarSeries, enterSlice: true);
444
445 // Special case: Always deactivate slicing when changing away from slice
446 // automanagement, as this can't be handled in setSelectedBar.
447 if (!mode.testFlag(flag: QAbstract3DGraph::SelectionSlice)
448 && oldMode.testFlag(flag: QAbstract3DGraph::SelectionSlice)) {
449 scene()->setSlicingActive(false);
450 }
451 }
452 }
453}
454
455void Bars3DController::setSelectedBar(const QPoint &position, QBar3DSeries *series, bool enterSlice)
456{
457 // If the selection targets non-existent bar, clear selection instead.
458 QPoint pos = position;
459
460 // Series may already have been removed, so check it before setting the selection.
461 if (!m_seriesList.contains(t: series))
462 series = nullptr;
463
464 adjustSelectionPosition(pos, series);
465
466 if (series && selectionMode().testFlag(flag: QAbstract3DGraph::SelectionSlice)) {
467 // If the selected bar is outside data window, or there is no visible selected bar,
468 // disable slicing.
469 if (pos.x() < m_axisZ->min() || pos.x() > m_axisZ->max()
470 || pos.y() < m_axisX->min() || pos.y() > m_axisX->max()
471 || !series->isVisible()) {
472 scene()->setSlicingActive(false);
473 } else if (enterSlice) {
474 scene()->setSlicingActive(true);
475 }
476 emitNeedRender();
477 }
478
479 if (pos != m_selectedBar || series != m_selectedBarSeries) {
480 bool seriesChanged = (series != m_selectedBarSeries);
481 m_selectedBar = pos;
482 m_selectedBarSeries = series;
483 m_changeTracker.selectedBarChanged = true;
484
485 // Clear selection from other series and finally set new selection to the specified series
486 foreach (QAbstract3DSeries *otherSeries, m_seriesList) {
487 QBar3DSeries *barSeries = static_cast<QBar3DSeries *>(otherSeries);
488 if (barSeries != m_selectedBarSeries)
489 barSeries->d_func()->setSelectedBar(invalidSelectionPosition());
490 }
491 if (m_selectedBarSeries)
492 m_selectedBarSeries->d_func()->setSelectedBar(m_selectedBar);
493
494 if (seriesChanged)
495 emit selectedSeriesChanged(series: m_selectedBarSeries);
496
497 emitNeedRender();
498 }
499}
500
501void Bars3DController::clearSelection()
502{
503 setSelectedBar(position: invalidSelectionPosition(), series: 0, enterSlice: false);
504}
505
506void Bars3DController::adjustAxisRanges()
507{
508 QCategory3DAxis *categoryAxisZ = static_cast<QCategory3DAxis *>(m_axisZ);
509 QCategory3DAxis *categoryAxisX = static_cast<QCategory3DAxis *>(m_axisX);
510 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(m_axisY);
511
512 bool adjustZ = (categoryAxisZ && categoryAxisZ->isAutoAdjustRange());
513 bool adjustX = (categoryAxisX && categoryAxisX->isAutoAdjustRange());
514 bool adjustY = (valueAxis && categoryAxisX && categoryAxisZ && valueAxis->isAutoAdjustRange());
515
516 if (adjustZ || adjustX || adjustY) {
517 int maxRowCount = 0;
518 int maxColumnCount = 0;
519 float minValue = 0.0f;
520 float maxValue = 0.0f;
521
522 // First figure out row and column counts
523 int seriesCount = m_seriesList.size();
524 if (adjustZ || adjustX) {
525 for (int series = 0; series < seriesCount; series++) {
526 const QBar3DSeries *barSeries =
527 static_cast<QBar3DSeries *>(m_seriesList.at(i: series));
528 if (barSeries->isVisible()) {
529 const QBarDataProxy *proxy = barSeries->dataProxy();
530
531 if (adjustZ && proxy) {
532 int rowCount = proxy->rowCount();
533 if (rowCount)
534 rowCount--;
535
536 maxRowCount = qMax(a: maxRowCount, b: rowCount);
537 }
538
539 if (adjustX && proxy) {
540 const QBarDataArray *array = proxy->array();
541 int columnCount = 0;
542 for (int i = 0; i < array->size(); i++) {
543 if (columnCount < array->at(i)->size())
544 columnCount = array->at(i)->size();
545 }
546 if (columnCount)
547 columnCount--;
548
549 maxColumnCount = qMax(a: maxColumnCount, b: columnCount);
550 }
551 }
552 }
553 // Call private implementations of setRange to avoid unsetting auto adjust flag
554 if (adjustZ)
555 categoryAxisZ->d_func()->setRange(min: 0.0f, max: float(maxRowCount), suppressWarnings: true);
556 if (adjustX)
557 categoryAxisX->d_func()->setRange(min: 0.0f, max: float(maxColumnCount), suppressWarnings: true);
558 }
559
560 // Now that we know the row and column ranges, figure out the value axis range
561 if (adjustY) {
562 for (int series = 0; series < seriesCount; series++) {
563 const QBar3DSeries *barSeries =
564 static_cast<QBar3DSeries *>(m_seriesList.at(i: series));
565 if (barSeries->isVisible()) {
566 const QBarDataProxy *proxy = barSeries->dataProxy();
567 if (adjustY && proxy) {
568 QPair<float, float> limits =
569 proxy->d_func()->limitValues(startRow: categoryAxisZ->min(),
570 startColumn: categoryAxisZ->max(),
571 rowCount: categoryAxisX->min(),
572 columnCount: categoryAxisX->max());
573 if (!series) {
574 // First series initializes the values
575 minValue = limits.first;
576 maxValue = limits.second;
577 } else {
578 minValue = qMin(a: minValue, b: limits.first);
579 maxValue = qMax(a: maxValue, b: limits.second);
580 }
581 }
582 }
583 }
584
585 if (maxValue < 0.0f)
586 maxValue = 0.0f;
587 if (minValue > 0.0f)
588 minValue = 0.0f;
589 if (minValue == 0.0f && maxValue == 0.0f) {
590 // Only zero value values in data set, set range to something.
591 minValue = 0.0f;
592 maxValue = 1.0f;
593 }
594 valueAxis->d_func()->setRange(min: minValue, max: maxValue, suppressWarnings: true);
595 }
596 }
597}
598
599// Invalidate selection position if outside data for the series
600void Bars3DController::adjustSelectionPosition(QPoint &pos, const QBar3DSeries *series)
601{
602 const QBarDataProxy *proxy = 0;
603 if (series)
604 proxy = series->dataProxy();
605
606 if (!proxy)
607 pos = invalidSelectionPosition();
608
609 if (pos != invalidSelectionPosition()) {
610 int maxRow = proxy->rowCount() - 1;
611 int maxCol = (pos.x() <= maxRow && pos.x() >= 0 && proxy->rowAt(rowIndex: pos.x()))
612 ? proxy->rowAt(rowIndex: pos.x())->size() - 1 : -1;
613
614 if (pos.x() < 0 || pos.x() > maxRow || pos.y() < 0 || pos.y() > maxCol)
615 pos = invalidSelectionPosition();
616 }
617}
618
619QAbstract3DAxis *Bars3DController::createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation)
620{
621 QAbstract3DAxis *defaultAxis = 0;
622
623 if (orientation == QAbstract3DAxis::AxisOrientationY)
624 defaultAxis = createDefaultValueAxis();
625 else
626 defaultAxis = createDefaultCategoryAxis();
627
628 return defaultAxis;
629}
630
631QT_END_NAMESPACE
632

source code of qtgraphs/src/graphs/engine/bars3dcontroller.cpp