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