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

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