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 "scatter3dcontroller_p.h"
7#include "scatter3drenderer_p.h"
8#include "qvalue3daxis_p.h"
9#include "qscatterdataproxy_p.h"
10#include "qscatter3dseries_p.h"
11#include <QtCore/QMutexLocker>
12
13QT_BEGIN_NAMESPACE
14
15static const int insertRemoveRecordReserveSize = 31;
16
17Scatter3DController::Scatter3DController(QRect boundRect, Q3DScene *scene)
18 : Abstract3DController(boundRect, scene),
19 m_renderer(0),
20 m_selectedItem(invalidSelectionIndex()),
21 m_selectedItemSeries(0),
22 m_recordInsertsAndRemoves(false)
23{
24 // Setting a null axis creates a new default axis according to orientation and graph type.
25 // Note: These cannot be set in Abstract3DController constructor, as they will call virtual
26 // functions implemented by subclasses.
27 setAxisX(0);
28 setAxisY(0);
29 setAxisZ(0);
30}
31
32Scatter3DController::~Scatter3DController()
33{
34}
35
36void Scatter3DController::initializeOpenGL()
37{
38 QMutexLocker mutexLocker(&m_renderMutex);
39
40 // Initialization is called multiple times when Qt Quick components are used
41 if (isInitialized())
42 return;
43
44 m_renderer = new Scatter3DRenderer(this);
45 setRenderer(m_renderer);
46
47 mutexLocker.unlock();
48 synchDataToRenderer();
49
50 emitNeedRender();
51}
52
53void Scatter3DController::synchDataToRenderer()
54{
55 QMutexLocker mutexLocker(&m_renderMutex);
56
57 if (!isInitialized())
58 return;
59
60 Abstract3DController::synchDataToRenderer();
61
62 // Notify changes to renderer
63 if (m_changeTracker.itemChanged) {
64 m_renderer->updateItems(items: m_changedItems);
65 m_changeTracker.itemChanged = false;
66 m_changedItems.clear();
67 }
68
69 if (m_changeTracker.selectedItemChanged) {
70 m_renderer->updateSelectedItem(index: m_selectedItem, series: m_selectedItemSeries);
71 m_changeTracker.selectedItemChanged = false;
72 }
73}
74
75void Scatter3DController::addSeries(QAbstract3DSeries *series)
76{
77 Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesTypeScatter);
78
79 Abstract3DController::addSeries(series);
80
81 QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(series);
82 if (scatterSeries->selectedItem() != invalidSelectionIndex())
83 setSelectedItem(index: scatterSeries->selectedItem(), series: scatterSeries);
84}
85
86void Scatter3DController::removeSeries(QAbstract3DSeries *series)
87{
88 bool wasVisible = (series && series->d_ptr->m_controller == this && series->isVisible());
89
90 Abstract3DController::removeSeries(series);
91
92 if (m_selectedItemSeries == series)
93 setSelectedItem(index: invalidSelectionIndex(), series: 0);
94
95 if (wasVisible)
96 adjustAxisRanges();
97}
98
99QList<QScatter3DSeries *> Scatter3DController::scatterSeriesList()
100{
101 QList<QAbstract3DSeries *> abstractSeriesList = seriesList();
102 QList<QScatter3DSeries *> scatterSeriesList;
103 foreach (QAbstract3DSeries *abstractSeries, abstractSeriesList) {
104 QScatter3DSeries *scatterSeries = qobject_cast<QScatter3DSeries *>(object: abstractSeries);
105 if (scatterSeries)
106 scatterSeriesList.append(t: scatterSeries);
107 }
108
109 return scatterSeriesList;
110}
111
112void Scatter3DController::handleArrayReset()
113{
114 QScatter3DSeries *series;
115 if (qobject_cast<QScatterDataProxy *>(object: sender()))
116 series = static_cast<QScatterDataProxy *>(sender())->series();
117 else
118 series = static_cast<QScatter3DSeries *>(sender());
119
120 if (series->isVisible()) {
121 adjustAxisRanges();
122 m_isDataDirty = true;
123 }
124 if (!m_changedSeriesList.contains(t: series))
125 m_changedSeriesList.append(t: series);
126 setSelectedItem(index: m_selectedItem, series: m_selectedItemSeries);
127 series->d_ptr->markItemLabelDirty();
128 emitNeedRender();
129}
130
131void Scatter3DController::handleItemsAdded(int startIndex, int count)
132{
133 Q_UNUSED(startIndex);
134 Q_UNUSED(count);
135 QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
136 if (series->isVisible()) {
137 adjustAxisRanges();
138 m_isDataDirty = true;
139 }
140 if (!m_changedSeriesList.contains(t: series))
141 m_changedSeriesList.append(t: series);
142 emitNeedRender();
143}
144
145void Scatter3DController::handleItemsChanged(int startIndex, int count)
146{
147 QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
148 int oldChangeCount = m_changedItems.size();
149 if (!oldChangeCount)
150 m_changedItems.reserve(asize: count);
151
152 for (int i = 0; i < count; i++) {
153 bool newItem = true;
154 int candidate = startIndex + i;
155 for (int j = 0; j < oldChangeCount; j++) {
156 const ChangeItem &oldChangeItem = m_changedItems.at(i: j);
157 if (oldChangeItem.index == candidate && series == oldChangeItem.series) {
158 newItem = false;
159 break;
160 }
161 }
162 if (newItem) {
163 ChangeItem newChangeItem = {.series: series, .index: candidate};
164 m_changedItems.append(t: newChangeItem);
165 if (series == m_selectedItemSeries && m_selectedItem == candidate)
166 series->d_ptr->markItemLabelDirty();
167 }
168 }
169
170 if (count) {
171 m_changeTracker.itemChanged = true;
172 if (series->isVisible())
173 adjustAxisRanges();
174 emitNeedRender();
175 }
176}
177
178void Scatter3DController::handleItemsRemoved(int startIndex, int count)
179{
180 Q_UNUSED(startIndex);
181 Q_UNUSED(count);
182 QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
183 if (series == m_selectedItemSeries) {
184 // If items removed from selected series before the selection, adjust the selection
185 int selectedItem = m_selectedItem;
186 if (startIndex <= selectedItem) {
187 if ((startIndex + count) > selectedItem)
188 selectedItem = -1; // Selected item removed
189 else
190 selectedItem -= count; // Move selected item down by amount of item removed
191
192 setSelectedItem(index: selectedItem, series: m_selectedItemSeries);
193 }
194 }
195
196 if (series->isVisible()) {
197 adjustAxisRanges();
198 m_isDataDirty = true;
199 }
200 if (!m_changedSeriesList.contains(t: series))
201 m_changedSeriesList.append(t: series);
202
203 if (m_recordInsertsAndRemoves) {
204 InsertRemoveRecord record(false, startIndex, count, series);
205 m_insertRemoveRecords.append(t: record);
206 }
207
208 emitNeedRender();
209}
210
211void Scatter3DController::handleItemsInserted(int startIndex, int count)
212{
213 Q_UNUSED(startIndex);
214 Q_UNUSED(count);
215 QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
216 if (series == m_selectedItemSeries) {
217 // If items inserted to selected series before the selection, adjust the selection
218 int selectedItem = m_selectedItem;
219 if (startIndex <= selectedItem) {
220 selectedItem += count;
221 setSelectedItem(index: selectedItem, series: m_selectedItemSeries);
222 }
223 }
224
225 if (series->isVisible()) {
226 adjustAxisRanges();
227 m_isDataDirty = true;
228 }
229 if (!m_changedSeriesList.contains(t: series))
230 m_changedSeriesList.append(t: series);
231
232 if (m_recordInsertsAndRemoves) {
233 InsertRemoveRecord record(true, startIndex, count, series);
234 m_insertRemoveRecords.append(t: record);
235 }
236
237 emitNeedRender();
238}
239
240void Scatter3DController::startRecordingRemovesAndInserts()
241{
242 m_recordInsertsAndRemoves = false;
243
244 if (m_scene->selectionQueryPosition() != Q3DScene::invalidSelectionPoint()) {
245 m_recordInsertsAndRemoves = true;
246 if (m_insertRemoveRecords.size()) {
247 m_insertRemoveRecords.clear();
248 // Reserve some space for remove/insert records to avoid unnecessary reallocations.
249 m_insertRemoveRecords.reserve(asize: insertRemoveRecordReserveSize);
250 }
251 }
252}
253
254void Scatter3DController::handleAxisAutoAdjustRangeChangedInOrientation(
255 QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
256{
257 Q_UNUSED(orientation);
258 Q_UNUSED(autoAdjust);
259 adjustAxisRanges();
260}
261
262void Scatter3DController::handleAxisRangeChangedBySender(QObject *sender)
263{
264 Abstract3DController::handleAxisRangeChangedBySender(sender);
265
266 // Update selected index - may be moved offscreen
267 setSelectedItem(index: m_selectedItem, series: m_selectedItemSeries);
268}
269
270void Scatter3DController::handlePendingClick()
271{
272 int index = m_renderer->clickedIndex();
273 QScatter3DSeries *series = static_cast<QScatter3DSeries *>(m_renderer->clickedSeries());
274
275 // Adjust position according to recorded events
276 int recordCount = m_insertRemoveRecords.size();
277 if (recordCount) {
278 for (int i = 0; i < recordCount; i++) {
279 const InsertRemoveRecord &record = m_insertRemoveRecords.at(i);
280 if (series == record.m_series && record.m_startIndex <= index) {
281 if (record.m_isInsert) {
282 index += record.m_count;
283 } else {
284 if ((record.m_startIndex + record.m_count) > index) {
285 index = -1; // Selected row removed
286 break;
287 } else {
288 index -= record.m_count; // Move selected item down by amount of items removed
289 }
290 }
291 }
292 }
293 }
294
295 setSelectedItem(index, series);
296
297 Abstract3DController::handlePendingClick();
298
299 m_renderer->resetClickedStatus();
300}
301
302void Scatter3DController::setSelectionMode(QAbstract3DGraph::SelectionFlags mode)
303{
304 // We only support single item selection mode and no selection mode
305 if (mode != QAbstract3DGraph::SelectionItem && mode != QAbstract3DGraph::SelectionNone) {
306 qWarning(msg: "Unsupported selection mode - only none and item selection modes are supported.");
307 return;
308 }
309
310 Abstract3DController::setSelectionMode(mode);
311}
312
313void Scatter3DController::setSelectedItem(int index, QScatter3DSeries *series)
314{
315 const QScatterDataProxy *proxy = 0;
316
317 // Series may already have been removed, so check it before setting the selection.
318 if (!m_seriesList.contains(t: series))
319 series = 0;
320
321 if (series)
322 proxy = series->dataProxy();
323
324 if (!proxy || index < 0 || index >= proxy->itemCount())
325 index = invalidSelectionIndex();
326
327 if (index != m_selectedItem || series != m_selectedItemSeries) {
328 bool seriesChanged = (series != m_selectedItemSeries);
329 m_selectedItem = index;
330 m_selectedItemSeries = series;
331 m_changeTracker.selectedItemChanged = true;
332
333 // Clear selection from other series and finally set new selection to the specified series
334 foreach (QAbstract3DSeries *otherSeries, m_seriesList) {
335 QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(otherSeries);
336 if (scatterSeries != m_selectedItemSeries)
337 scatterSeries->dptr()->setSelectedItem(invalidSelectionIndex());
338 }
339 if (m_selectedItemSeries)
340 m_selectedItemSeries->dptr()->setSelectedItem(m_selectedItem);
341
342 if (seriesChanged)
343 emit selectedSeriesChanged(series: m_selectedItemSeries);
344
345 emitNeedRender();
346 }
347}
348
349void Scatter3DController::clearSelection()
350{
351 setSelectedItem(index: invalidSelectionIndex(), series: 0);
352}
353
354void Scatter3DController::adjustAxisRanges()
355{
356 QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
357 QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
358 QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
359 bool adjustX = (valueAxisX && valueAxisX->isAutoAdjustRange());
360 bool adjustY = (valueAxisY && valueAxisY->isAutoAdjustRange());
361 bool adjustZ = (valueAxisZ && valueAxisZ->isAutoAdjustRange());
362
363 if (adjustX || adjustY || adjustZ) {
364 float minValueX = 0.0f;
365 float maxValueX = 0.0f;
366 float minValueY = 0.0f;
367 float maxValueY = 0.0f;
368 float minValueZ = 0.0f;
369 float maxValueZ = 0.0f;
370 int seriesCount = m_seriesList.size();
371 for (int series = 0; series < seriesCount; series++) {
372 const QScatter3DSeries *scatterSeries =
373 static_cast<QScatter3DSeries *>(m_seriesList.at(i: series));
374 const QScatterDataProxy *proxy = scatterSeries->dataProxy();
375 if (scatterSeries->isVisible() && proxy) {
376 QVector3D minLimits;
377 QVector3D maxLimits;
378 proxy->dptrc()->limitValues(minValues&: minLimits, maxValues&: maxLimits, axisX: valueAxisX, axisY: valueAxisY, axisZ: valueAxisZ);
379 if (adjustX) {
380 if (!series) {
381 // First series initializes the values
382 minValueX = minLimits.x();
383 maxValueX = maxLimits.x();
384 } else {
385 minValueX = qMin(a: minValueX, b: minLimits.x());
386 maxValueX = qMax(a: maxValueX, b: maxLimits.x());
387 }
388 }
389 if (adjustY) {
390 if (!series) {
391 // First series initializes the values
392 minValueY = minLimits.y();
393 maxValueY = maxLimits.y();
394 } else {
395 minValueY = qMin(a: minValueY, b: minLimits.y());
396 maxValueY = qMax(a: maxValueY, b: maxLimits.y());
397 }
398 }
399 if (adjustZ) {
400 if (!series) {
401 // First series initializes the values
402 minValueZ = minLimits.z();
403 maxValueZ = maxLimits.z();
404 } else {
405 minValueZ = qMin(a: minValueZ, b: minLimits.z());
406 maxValueZ = qMax(a: maxValueZ, b: maxLimits.z());
407 }
408 }
409 }
410 }
411
412 static const float adjustmentRatio = 20.0f;
413 static const float defaultAdjustment = 1.0f;
414
415 if (adjustX) {
416 // If all points at same coordinate, need to default to some valid range
417 float adjustment = 0.0f;
418 if (minValueX == maxValueX) {
419 if (adjustZ) {
420 // X and Z are linked to have similar unit size, so choose the valid range based on it
421 if (minValueZ == maxValueZ)
422 adjustment = defaultAdjustment;
423 else
424 adjustment = qAbs(t: maxValueZ - minValueZ) / adjustmentRatio;
425 } else {
426 if (valueAxisZ)
427 adjustment = qAbs(t: valueAxisZ->max() - valueAxisZ->min()) / adjustmentRatio;
428 else
429 adjustment = defaultAdjustment;
430 }
431 }
432 valueAxisX->dptr()->setRange(min: minValueX - adjustment, max: maxValueX + adjustment, suppressWarnings: true);
433 }
434 if (adjustY) {
435 // If all points at same coordinate, need to default to some valid range
436 // Y-axis unit is not dependent on other axes, so simply adjust +-1.0f
437 float adjustment = 0.0f;
438 if (minValueY == maxValueY)
439 adjustment = defaultAdjustment;
440 valueAxisY->dptr()->setRange(min: minValueY - adjustment, max: maxValueY + adjustment, suppressWarnings: true);
441 }
442 if (adjustZ) {
443 // If all points at same coordinate, need to default to some valid range
444 float adjustment = 0.0f;
445 if (minValueZ == maxValueZ) {
446 if (adjustX) {
447 // X and Z are linked to have similar unit size, so choose the valid range based on it
448 if (minValueX == maxValueX)
449 adjustment = defaultAdjustment;
450 else
451 adjustment = qAbs(t: maxValueX - minValueX) / adjustmentRatio;
452 } else {
453 if (valueAxisX)
454 adjustment = qAbs(t: valueAxisX->max() - valueAxisX->min()) / adjustmentRatio;
455 else
456 adjustment = defaultAdjustment;
457 }
458 }
459 valueAxisZ->dptr()->setRange(min: minValueZ - adjustment, max: maxValueZ + adjustment, suppressWarnings: true);
460 }
461 }
462}
463
464QT_END_NAMESPACE
465

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtdatavis3d/src/datavisualization/engine/scatter3dcontroller.cpp