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

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