1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "surface3dcontroller_p.h"
5#include "qvalue3daxis_p.h"
6#include "qsurfacedataproxy_p.h"
7#include "qsurface3dseries_p.h"
8#include <QtCore/QMutexLocker>
9
10QT_BEGIN_NAMESPACE
11
12Surface3DController::Surface3DController(QRect rect, Q3DScene *scene)
13 : Abstract3DController(rect, scene),
14 m_selectedPoint(invalidSelectionPosition()),
15 m_selectedSeries(0),
16 m_flatShadingSupported(true),
17 m_flipHorizontalGrid(false),
18 m_isSeriesVisibilityDirty(false)
19{
20 // Setting a null axis creates a new default axis according to orientation and graph type.
21 // Note: these cannot be set in the Abstract3DController constructor, as they will call virtual
22 // functions implemented by subclasses.
23 setAxisX(0);
24 setAxisY(0);
25 setAxisZ(0);
26}
27
28Surface3DController::~Surface3DController()
29{
30}
31
32void Surface3DController::handleAxisAutoAdjustRangeChangedInOrientation(
33 QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
34{
35 Q_UNUSED(orientation);
36 Q_UNUSED(autoAdjust);
37
38 adjustAxisRanges();
39}
40
41void Surface3DController::handleAxisRangeChangedBySender(QObject *sender)
42{
43 Abstract3DController::handleAxisRangeChangedBySender(sender);
44
45 // Update selected point - may be moved offscreen
46 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
47}
48
49void Surface3DController::handleSeriesVisibilityChangedBySender(QObject *sender)
50{
51 Abstract3DController::handleSeriesVisibilityChangedBySender(sender);
52
53 m_isSeriesVisibilityDirty = true;
54 // Visibility changes may require disabling slicing,
55 // so just reset selection to ensure everything is still valid.
56 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
57}
58
59QPoint Surface3DController::invalidSelectionPosition()
60{
61 static QPoint invalidSelectionPoint(-1, -1);
62 return invalidSelectionPoint;
63}
64
65bool Surface3DController::isFlatShadingSupported()
66{
67 return m_flatShadingSupported;
68}
69
70void Surface3DController::addSeries(QAbstract3DSeries *series)
71{
72 Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesTypeSurface);
73
74 Abstract3DController::addSeries(series);
75
76 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
77 if (surfaceSeries->selectedPoint() != invalidSelectionPosition())
78 setSelectedPoint(position: surfaceSeries->selectedPoint(), series: surfaceSeries, enterSlice: false);
79
80 if (!surfaceSeries->texture().isNull())
81 updateSurfaceTexture(series: surfaceSeries);
82}
83
84void Surface3DController::removeSeries(QAbstract3DSeries *series)
85{
86 bool wasVisible = (series && series->d_func()->m_controller == this && series->isVisible());
87
88 Abstract3DController::removeSeries(series);
89
90 if (m_selectedSeries == series)
91 setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false);
92
93 if (wasVisible)
94 adjustAxisRanges();
95}
96
97QList<QSurface3DSeries *> Surface3DController::surfaceSeriesList()
98{
99 QList<QAbstract3DSeries *> abstractSeriesList = seriesList();
100 QList<QSurface3DSeries *> surfaceSeriesList;
101 foreach (QAbstract3DSeries *abstractSeries, abstractSeriesList) {
102 QSurface3DSeries *surfaceSeries = qobject_cast<QSurface3DSeries *>(object: abstractSeries);
103 if (surfaceSeries)
104 surfaceSeriesList.append(t: surfaceSeries);
105 }
106
107 return surfaceSeriesList;
108}
109
110void Surface3DController::setFlipHorizontalGrid(bool flip)
111{
112 if (m_flipHorizontalGrid != flip) {
113 m_flipHorizontalGrid = flip;
114 m_changeTracker.flipHorizontalGridChanged = true;
115 emit flipHorizontalGridChanged(flip);
116 emitNeedRender();
117 }
118}
119
120bool Surface3DController::flipHorizontalGrid() const
121{
122 return m_flipHorizontalGrid;
123}
124
125void Surface3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
126{
127 // Currently surface only supports row and column modes when also slicing
128 if ((mode.testFlag(flag: QAbstract3DGraph::SelectionRow)
129 || mode.testFlag(flag: QAbstract3DGraph::SelectionColumn))
130 && !mode.testFlag(flag: QAbstract3DGraph::SelectionSlice)) {
131 qWarning(msg: "Unsupported selection mode.");
132 return;
133 } else if (mode.testFlag(flag: QAbstract3DGraph::SelectionSlice)
134 && (mode.testFlag(flag: QAbstract3DGraph::SelectionRow)
135 == mode.testFlag(flag: QAbstract3DGraph::SelectionColumn))) {
136 qWarning(msg: "Must specify one of either row or column selection mode in conjunction with slicing mode.");
137 } else {
138 QAbstract3DGraph::SelectionFlags oldMode = selectionMode();
139
140 Abstract3DController::setSelectionMode(mode);
141
142 if (mode != oldMode) {
143 // Refresh selection upon mode change to ensure slicing is correctly updated
144 // according to series the visibility.
145 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: true);
146
147 // Special case: Always deactivate slicing when changing away from slice
148 // automanagement, as this can't be handled in setSelectedBar.
149 if (!mode.testFlag(flag: QAbstract3DGraph::SelectionSlice)
150 && oldMode.testFlag(flag: QAbstract3DGraph::SelectionSlice)) {
151 scene()->setSlicingActive(false);
152 }
153 }
154 }
155}
156
157void Surface3DController::setSelectedPoint(const QPoint &position, QSurface3DSeries *series,
158 bool enterSlice)
159{
160 // If the selection targets non-existent point, clear selection instead.
161 QPoint pos = position;
162
163 // Series may already have been removed, so check it before setting the selection.
164 if (!m_seriesList.contains(t: series))
165 series = 0;
166
167 const QSurfaceDataProxy *proxy = 0;
168 if (series)
169 proxy = series->dataProxy();
170
171 if (!proxy)
172 pos = invalidSelectionPosition();
173
174 if (pos != invalidSelectionPosition()) {
175 int maxRow = proxy->rowCount() - 1;
176 int maxCol = proxy->columnCount() - 1;
177
178 if (pos.x() < 0 || pos.x() > maxRow || pos.y() < 0 || pos.y() > maxCol)
179 pos = invalidSelectionPosition();
180 }
181
182 if (selectionMode().testFlag(flag: QAbstract3DGraph::SelectionSlice)) {
183 if (pos == invalidSelectionPosition() || !series->isVisible()) {
184 scene()->setSlicingActive(false);
185 } else {
186 // If the selected point is outside data window, or there is no selected point, disable slicing
187 float axisMinX = m_axisX->min();
188 float axisMaxX = m_axisX->max();
189 float axisMinZ = m_axisZ->min();
190 float axisMaxZ = m_axisZ->max();
191
192 QSurfaceDataItem item = proxy->array()->at(i: pos.x())->at(i: pos.y());
193 if (item.x() < axisMinX || item.x() > axisMaxX
194 || item.z() < axisMinZ || item.z() > axisMaxZ) {
195 scene()->setSlicingActive(false);
196 } else if (enterSlice) {
197 scene()->setSlicingActive(true);
198 }
199 }
200 emitNeedRender();
201 }
202
203 if (pos != m_selectedPoint || series != m_selectedSeries) {
204 bool seriesChanged = (series != m_selectedSeries);
205 m_selectedPoint = pos;
206 m_selectedSeries = series;
207 m_changeTracker.selectedPointChanged = true;
208
209 // Clear selection from other series and finally set new selection to the specified series
210 foreach (QAbstract3DSeries *otherSeries, m_seriesList) {
211 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(otherSeries);
212 if (surfaceSeries != m_selectedSeries)
213 surfaceSeries->d_func()->setSelectedPoint(invalidSelectionPosition());
214 }
215 if (m_selectedSeries)
216 m_selectedSeries->d_func()->setSelectedPoint(m_selectedPoint);
217
218 if (seriesChanged)
219 emit selectedSeriesChanged(series: m_selectedSeries);
220
221 emitNeedRender();
222 }
223}
224
225void Surface3DController::clearSelection()
226{
227 setSelectedPoint(position: invalidSelectionPosition(), series: 0, enterSlice: false);
228}
229
230void Surface3DController::handleArrayReset()
231{
232 QSurface3DSeries *series;
233 if (qobject_cast<QSurfaceDataProxy *>(object: sender()))
234 series = static_cast<QSurfaceDataProxy *>(sender())->series();
235 else
236 series = static_cast<QSurface3DSeries *>(sender());
237
238 if (series->isVisible()) {
239 adjustAxisRanges();
240 m_isDataDirty = true;
241 }
242 if (!m_changedSeriesList.contains(t: series))
243 m_changedSeriesList.append(t: series);
244
245 // Clear selection unless still valid
246 setSelectedPoint(position: m_selectedPoint, series: m_selectedSeries, enterSlice: false);
247 series->d_func()->markItemLabelDirty();
248 emitNeedRender();
249}
250
251void Surface3DController::handleFlatShadingSupportedChange(bool supported)
252{
253 // Handle renderer flat surface support indicator signal. This happens exactly once per renderer.
254 if (m_flatShadingSupported != supported) {
255 m_flatShadingSupported = supported;
256 // Emit the change for all added surfaces
257 foreach (QAbstract3DSeries *series, m_seriesList) {
258 QSurface3DSeries *surfaceSeries = static_cast<QSurface3DSeries *>(series);
259 emit surfaceSeries->flatShadingSupportedChanged(enable: m_flatShadingSupported);
260 }
261 }
262}
263
264void Surface3DController::handleRowsChanged(int startIndex, int count)
265{
266 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(QObject::sender())->series();
267 int oldChangeCount = m_changedRows.size();
268 if (!oldChangeCount)
269 m_changedRows.reserve(asize: count);
270
271 int selectedRow = m_selectedPoint.x();
272 for (int i = 0; i < count; i++) {
273 bool newItem = true;
274 int candidate = startIndex + i;
275 for (int j = 0; j < oldChangeCount; j++) {
276 const ChangeRow &oldChangeItem = m_changedRows.at(i: j);
277 if (oldChangeItem.row == candidate && series == oldChangeItem.series) {
278 newItem = false;
279 break;
280 }
281 }
282 if (newItem) {
283 ChangeRow newChangeItem = {.series: series, .row: candidate};
284 m_changedRows.append(t: newChangeItem);
285 if (series == m_selectedSeries && selectedRow == candidate)
286 series->d_func()->markItemLabelDirty();
287 }
288 }
289 if (count) {
290 m_changeTracker.rowsChanged = true;
291
292 if (series->isVisible())
293 adjustAxisRanges();
294 emitNeedRender();
295 }
296}
297
298void Surface3DController::handleItemChanged(int rowIndex, int columnIndex)
299{
300 QSurfaceDataProxy *sender = static_cast<QSurfaceDataProxy *>(QObject::sender());
301 QSurface3DSeries *series = sender->series();
302
303 bool newItem = true;
304 QPoint candidate(rowIndex, columnIndex);
305 foreach (ChangeItem item, m_changedItems) {
306 if (item.point == candidate && item.series == series) {
307 newItem = false;
308 break;
309 }
310 }
311 if (newItem) {
312 ChangeItem newItem = {.series: series, .point: candidate};
313 m_changedItems.append(t: newItem);
314 m_changeTracker.itemChanged = true;
315
316 if (series == m_selectedSeries && m_selectedPoint == candidate)
317 series->d_func()->markItemLabelDirty();
318
319 if (series->isVisible())
320 adjustAxisRanges();
321 emitNeedRender();
322 }
323}
324
325void Surface3DController::handleRowsAdded(int startIndex, int count)
326{
327 Q_UNUSED(startIndex);
328 Q_UNUSED(count);
329 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
330 if (series->isVisible()) {
331 adjustAxisRanges();
332 m_isDataDirty = true;
333 }
334 if (!m_changedSeriesList.contains(t: series))
335 m_changedSeriesList.append(t: series);
336 emitNeedRender();
337}
338
339void Surface3DController::handleRowsInserted(int startIndex, int count)
340{
341 Q_UNUSED(startIndex);
342 Q_UNUSED(count);
343 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
344 if (series == m_selectedSeries) {
345 // If rows inserted to selected series before the selection, adjust the selection
346 int selectedRow = m_selectedPoint.x();
347 if (startIndex <= selectedRow) {
348 selectedRow += count;
349 setSelectedPoint(position: QPoint(selectedRow, m_selectedPoint.y()), series: m_selectedSeries, enterSlice: false);
350 }
351 }
352
353 if (series->isVisible()) {
354 adjustAxisRanges();
355 m_isDataDirty = true;
356 }
357 if (!m_changedSeriesList.contains(t: series))
358 m_changedSeriesList.append(t: series);
359
360 emitNeedRender();
361}
362
363void Surface3DController::handleRowsRemoved(int startIndex, int count)
364{
365 Q_UNUSED(startIndex);
366 Q_UNUSED(count);
367 QSurface3DSeries *series = static_cast<QSurfaceDataProxy *>(sender())->series();
368 if (series == m_selectedSeries) {
369 // If rows removed from selected series before the selection, adjust the selection
370 int selectedRow = m_selectedPoint.x();
371 if (startIndex <= selectedRow) {
372 if ((startIndex + count) > selectedRow)
373 selectedRow = -1; // Selected row removed
374 else
375 selectedRow -= count; // Move selected row down by amount of rows removed
376
377 setSelectedPoint(position: QPoint(selectedRow, m_selectedPoint.y()), series: m_selectedSeries, enterSlice: false);
378 }
379 }
380
381 if (series->isVisible()) {
382 adjustAxisRanges();
383 m_isDataDirty = true;
384 }
385 if (!m_changedSeriesList.contains(t: series))
386 m_changedSeriesList.append(t: series);
387
388 emitNeedRender();
389}
390
391void Surface3DController::updateSurfaceTexture(QSurface3DSeries *series)
392{
393 m_changeTracker.surfaceTextureChanged = true;
394
395 if (!m_changedTextures.contains(t: series))
396 m_changedTextures.append(t: series);
397
398 emitNeedRender();
399}
400
401void Surface3DController::adjustAxisRanges()
402{
403 QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
404 QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
405 QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
406 bool adjustX = (valueAxisX && valueAxisX->isAutoAdjustRange());
407 bool adjustY = (valueAxisY && valueAxisY->isAutoAdjustRange());
408 bool adjustZ = (valueAxisZ && valueAxisZ->isAutoAdjustRange());
409 bool first = true;
410
411 if (adjustX || adjustY || adjustZ) {
412 float minValueX = 0.0f;
413 float maxValueX = 0.0f;
414 float minValueY = 0.0f;
415 float maxValueY = 0.0f;
416 float minValueZ = 0.0f;
417 float maxValueZ = 0.0f;
418 int seriesCount = m_seriesList.size();
419 for (int series = 0; series < seriesCount; series++) {
420 const QSurface3DSeries *surfaceSeries =
421 static_cast<QSurface3DSeries *>(m_seriesList.at(i: series));
422 const QSurfaceDataProxy *proxy = surfaceSeries->dataProxy();
423 if (surfaceSeries->isVisible() && proxy) {
424 QVector3D minLimits;
425 QVector3D maxLimits;
426 proxy->d_func()->limitValues(minValues&: minLimits, maxValues&: maxLimits, axisX: valueAxisX, axisY: valueAxisY, axisZ: valueAxisZ);
427 if (adjustX) {
428 if (first) {
429 // First series initializes the values
430 minValueX = minLimits.x();
431 maxValueX = maxLimits.x();
432 } else {
433 minValueX = qMin(a: minValueX, b: minLimits.x());
434 maxValueX = qMax(a: maxValueX, b: maxLimits.x());
435 }
436 }
437 if (adjustY) {
438 if (first) {
439 // First series initializes the values
440 minValueY = minLimits.y();
441 maxValueY = maxLimits.y();
442 } else {
443 minValueY = qMin(a: minValueY, b: minLimits.y());
444 maxValueY = qMax(a: maxValueY, b: maxLimits.y());
445 }
446 }
447 if (adjustZ) {
448 if (first) {
449 // First series initializes the values
450 minValueZ = minLimits.z();
451 maxValueZ = maxLimits.z();
452 } else {
453 minValueZ = qMin(a: minValueZ, b: minLimits.z());
454 maxValueZ = qMax(a: maxValueZ, b: maxLimits.z());
455 }
456 }
457 first = false;
458 }
459 }
460
461 static const float adjustmentRatio = 20.0f;
462 static const float defaultAdjustment = 1.0f;
463
464 if (adjustX) {
465 // If all points at same coordinate, need to default to some valid range
466 float adjustment = 0.0f;
467 if (minValueX == maxValueX) {
468 if (adjustZ) {
469 // X and Z are linked to have similar unit size, so choose the valid range based on it
470 if (minValueZ == maxValueZ)
471 adjustment = defaultAdjustment;
472 else
473 adjustment = qAbs(t: maxValueZ - minValueZ) / adjustmentRatio;
474 } else {
475 if (valueAxisZ)
476 adjustment = qAbs(t: valueAxisZ->max() - valueAxisZ->min()) / adjustmentRatio;
477 else
478 adjustment = defaultAdjustment;
479 }
480 }
481 valueAxisX->d_func()->setRange(min: minValueX - adjustment, max: maxValueX + adjustment, suppressWarnings: true);
482 }
483 if (adjustY) {
484 // If all points at same coordinate, need to default to some valid range
485 // Y-axis unit is not dependent on other axes, so simply adjust +-1.0f
486 float adjustment = 0.0f;
487 if (minValueY == maxValueY)
488 adjustment = defaultAdjustment;
489 valueAxisY->d_func()->setRange(min: minValueY - adjustment, max: maxValueY + adjustment, suppressWarnings: true);
490 }
491 if (adjustZ) {
492 // If all points at same coordinate, need to default to some valid range
493 float adjustment = 0.0f;
494 if (minValueZ == maxValueZ) {
495 if (adjustX) {
496 // X and Z are linked to have similar unit size, so choose the valid range based on it
497 if (minValueX == maxValueX)
498 adjustment = defaultAdjustment;
499 else
500 adjustment = qAbs(t: maxValueX - minValueX) / adjustmentRatio;
501 } else {
502 if (valueAxisX)
503 adjustment = qAbs(t: valueAxisX->max() - valueAxisX->min()) / adjustmentRatio;
504 else
505 adjustment = defaultAdjustment;
506 }
507 }
508 valueAxisZ->d_func()->setRange(min: minValueZ - adjustment, max: maxValueZ + adjustment, suppressWarnings: true);
509 }
510 }
511}
512
513QT_END_NAMESPACE
514

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