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