1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "q3dscene.h"
5#include "qgraphsinputhandler_p.h"
6#include "qquickgraphsscatter_p.h"
7#include "qquickgraphstexturedata_p.h"
8#include "qscatter3dseries_p.h"
9#include "qscatterdataproxy_p.h"
10#include "qvalue3daxis_p.h"
11#include "qgraphs3dlogging_p.h"
12
13#include <QColor>
14#include <QtCore/QMutexLocker>
15#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
16#include <QtQuick3D/private/qquick3ddirectionallight_p.h>
17#include <QtQuick3D/private/qquick3dmodel_p.h>
18#include <QtQuick3D/private/qquick3dperspectivecamera_p.h>
19#include <QtQuick3D/private/qquick3dpointlight_p.h>
20#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
21#include <QtQuick3D/private/qquick3drepeater_p.h>
22
23#include <QtGui/qquaternion.h>
24
25QT_BEGIN_NAMESPACE
26
27static const int insertRemoveRecordReserveSize = 31;
28
29/*!
30 * \qmltype Scatter3D
31 * \inherits GraphsItem3D
32 * \inqmlmodule QtGraphs
33 * \ingroup graphs_qml_3D
34 * \brief 3D scatter graph.
35 *
36 * This type enables developers to render scatter graphs in 3D with Qt Quick.
37 *
38 * You will need to import Qt Graphs module to use this type:
39 *
40 * \snippet doc_src_qmlgraphs.cpp 0
41 *
42 * After that you can use Scatter3D in your qml files:
43 *
44 * \snippet doc_src_qmlgraphs.cpp 2
45 *
46 * See \l{Simple Scatter Graph} for more thorough usage example.
47 *
48 * \sa Scatter3DSeries, Spline3DSeries, ScatterDataProxy, Bars3D, Surface3D,
49 * {Qt Graphs C++ Classes for 3D}
50 */
51
52/*!
53 * \qmlproperty Value3DAxis Scatter3D::axisX
54 * The active x-axis.
55 *
56 * If an axis is not given, a temporary default axis with no labels and an
57 * automatically adjusting range is created.
58 * This temporary axis is destroyed if another axis is explicitly set to the
59 * same orientation.
60 */
61
62/*!
63 * \qmlproperty Value3DAxis Scatter3D::axisY
64 * The active y-axis.
65 *
66 * If an axis is not given, a temporary default axis with no labels and an
67 * automatically adjusting range is created.
68 * This temporary axis is destroyed if another axis is explicitly set to the
69 * same orientation.
70 */
71
72/*!
73 * \qmlproperty Value3DAxis Scatter3D::axisZ
74 * The active z-axis.
75 *
76 * If an axis is not given, a temporary default axis with no labels and an
77 * automatically adjusting range is created.
78 * This temporary axis is destroyed if another axis is explicitly set to the
79 * same orientation.
80 */
81
82/*!
83 * \qmlproperty Scatter3DSeries Scatter3D::selectedSeries
84 * \readonly
85 *
86 * The selected series or null.
87 */
88
89/*!
90 * \qmlproperty list<Scatter3DSeries> Scatter3D::seriesList
91 * \qmldefault
92 * This property holds the series of the graph.
93 * By default, this property contains an empty list.
94 * To set the series, either use the addSeries() method or define them as
95 * children of the graph.
96 */
97
98/*!
99 * \qmlmethod void Scatter3D::addSeries(Scatter3DSeries series)
100 * Adds the \a series to the graph. A graph can contain multiple series, but has
101 * only one set of axes. If the newly added series has specified a selected
102 * item, it will be highlighted and any existing selection will be cleared. Only
103 * one added series can have an active selection.
104 * \sa GraphsItem3D::hasSeries()
105 */
106
107/*!
108 * \qmlmethod void Scatter3D::removeSeries(Scatter3DSeries series)
109 * Remove the \a series from the graph.
110 * \sa GraphsItem3D::hasSeries()
111 */
112
113/*!
114 * \qmlsignal Scatter3D::axisXChanged(ValueAxis3D axis)
115 *
116 * This signal is emitted when axisX changes to \a axis.
117 */
118
119/*!
120 * \qmlsignal Scatter3D::axisYChanged(ValueAxis3D axis)
121 *
122 * This signal is emitted when axisY changes to \a axis.
123 */
124
125/*!
126 * \qmlsignal Scatter3D::axisZChanged(ValueAxis3D axis)
127 *
128 * This signal is emitted when axisZ changes to \a axis.
129 */
130
131/*!
132 * \qmlsignal Scatter3D::selectedSeriesChanged(Scatter3DSeries series)
133 *
134 * This signal is emitted when selectedSeries changes to \a series.
135 */
136
137QQuickGraphsScatter::QQuickGraphsScatter(QQuickItem *parent)
138 : QQuickGraphsItem(parent)
139{
140 m_graphType = QAbstract3DSeries::SeriesType::Scatter;
141 setAxisX(0);
142 setAxisY(0);
143 setAxisZ(0);
144 setAcceptedMouseButtons(Qt::AllButtons);
145 setFlag(flag: ItemHasContents);
146 clearSelection();
147}
148
149QQuickGraphsScatter::~QQuickGraphsScatter()
150{
151 QMutexLocker locker(m_nodeMutex.data());
152 const QMutexLocker locker2(mutex());
153
154 for (auto &graphModel : m_scatterGraphs) {
155 delete graphModel;
156 }
157}
158
159void QQuickGraphsScatter::setAxisX(QValue3DAxis *axis)
160{
161 QQuickGraphsItem::setAxisX(axis);
162}
163
164QValue3DAxis *QQuickGraphsScatter::axisX() const
165{
166 return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisX());
167}
168
169void QQuickGraphsScatter::setAxisY(QValue3DAxis *axis)
170{
171 QQuickGraphsItem::setAxisY(axis);
172}
173
174QValue3DAxis *QQuickGraphsScatter::axisY() const
175{
176 return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisY());
177}
178
179void QQuickGraphsScatter::setAxisZ(QValue3DAxis *axis)
180{
181 QQuickGraphsItem::setAxisZ(axis);
182}
183
184QValue3DAxis *QQuickGraphsScatter::axisZ() const
185{
186 return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisZ());
187}
188
189void QQuickGraphsScatter::disconnectSeries(QScatter3DSeries *series)
190{
191 QObject::disconnect(sender: series, signal: 0, receiver: this, member: 0);
192}
193
194void QQuickGraphsScatter::generatePointsForScatterModel(ScatterModel *graphModel)
195{
196 QList<QQuick3DModel *> itemList;
197 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
198 qsizetype itemCount = graphModel->series->dataProxy()->itemCount();
199 if (graphModel->series->dataProxy()->itemCount() > 0)
200 itemList.resize(size: itemCount);
201
202 for (int i = 0; i < itemCount; i++) {
203 QQuick3DModel *item = createDataItem(series: graphModel->series);
204 item->setPickable(true);
205 item->setParent(graphModel->series);
206 itemList[i] = item;
207 }
208 graphModel->dataItems = itemList;
209 markDataDirty();
210 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
211 graphModel->instancingRootItem = createDataItem(series: graphModel->series);
212 graphModel->instancingRootItem->setParent(graphModel->series);
213 graphModel->instancingRootItem->setInstancing(graphModel->instancing);
214 if (selectionMode() != QtGraphs3D::SelectionFlag::None) {
215 graphModel->selectionIndicator = createDataItem(series: graphModel->series);
216 graphModel->instancingRootItem->setPickable(true);
217 }
218 }
219 markSeriesVisualsDirty();
220}
221
222qsizetype QQuickGraphsScatter::getItemIndex(QQuick3DModel *item)
223{
224 Q_UNUSED(item);
225 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy)
226 return 0;
227
228 return -1;
229}
230
231void QQuickGraphsScatter::clearSelection()
232{
233 clearSelectionModel();
234}
235
236void QQuickGraphsScatter::updateScatterGraphItemPositions(ScatterModel *graphModel)
237{
238 float itemSize = graphModel->series->itemSize() / m_itemScaler;
239 QQuaternion meshRotation = graphModel->series->meshRotation();
240 QScatterDataProxy *dataProxy = graphModel->series->dataProxy();
241 QList<QQuick3DModel *> itemList = graphModel->dataItems;
242
243 auto valueAxisX = static_cast<QValue3DAxis *>(axisX());
244 auto valueAxisY = static_cast<QValue3DAxis *>(axisY());
245 auto valueAxisZ = static_cast<QValue3DAxis *>(axisZ());
246 bool xReversed = valueAxisX->reversed();
247 bool yReversed = valueAxisY->reversed();
248 bool zReversed = valueAxisZ->reversed();
249
250 if (itemSize == 0.0f)
251 itemSize = m_pointScale;
252
253 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
254 if (dataProxy->itemCount() != itemList.size()) {
255 qCWarning(lcProperties3D, "%ls Item count differs from itemList count",
256 qUtf16Printable(QString::fromUtf8(__func__)));
257 }
258
259 for (int i = 0; i < dataProxy->itemCount(); ++i) {
260 const QScatterDataItem item = dataProxy->itemAt(index: i);
261 QQuick3DModel *dataPoint = itemList.at(i);
262
263 QVector3D dotPos = item.position();
264 if (isDotPositionInAxisRange(dotPos)) {
265 dataPoint->setVisible(true);
266 QQuaternion dotRot = item.rotation();
267 float dotPosX = xReversed ? 1.0f - valueAxisX->positionAt(x: dotPos.x())
268 : valueAxisX->positionAt(x: dotPos.x());
269 float dotPosY = yReversed ? 1.0f - valueAxisY->positionAt(x: dotPos.y())
270 : valueAxisY->positionAt(x: dotPos.y());
271 float dotPosZ = zReversed ? 1.0f - valueAxisZ->positionAt(x: dotPos.z())
272 : valueAxisZ->positionAt(x: dotPos.z());
273
274 float posX = dotPosX * scale().x() + translate().x();
275 float posY = dotPosY * scale().y() + translate().y();
276 float posZ = dotPosZ * scale().z() + translate().z();
277 dataPoint->setPosition(QVector3D(posX, posY, posZ));
278 QQuaternion totalRotation;
279
280 if (graphModel->series->mesh() != QAbstract3DSeries::Mesh::Point)
281 totalRotation = dotRot * meshRotation;
282 else
283 totalRotation = cameraTarget()->rotation();
284
285 dataPoint->setRotation(totalRotation);
286 dataPoint->setScale(QVector3D(itemSize, itemSize, itemSize)
287 * dataProxy->scaleAt(index: i));
288 } else {
289 dataPoint->setVisible(false);
290 }
291 }
292 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
293 qsizetype count = dataProxy->itemCount();
294 QList<DataItemHolder> positions;
295
296 for (int i = 0; i < count; i++) {
297 const QScatterDataItem &item = dataProxy->itemAt(index: i);
298 QVector3D dotPos = item.position();
299
300 if (isDotPositionInAxisRange(dotPos)) {
301 float dotPosX = xReversed ? 1.0f - valueAxisX->positionAt(x: dotPos.x())
302 : valueAxisX->positionAt(x: dotPos.x());
303 float dotPosY = yReversed ? 1.0f - valueAxisY->positionAt(x: dotPos.y())
304 : valueAxisY->positionAt(x: dotPos.y());
305 float dotPosZ = zReversed ? 1.0f - valueAxisZ->positionAt(x: dotPos.z())
306 : valueAxisZ->positionAt(x: dotPos.z());
307
308 float posX = dotPosX * scale().x() + translate().x();
309 float posY = dotPosY * scale().y() + translate().y();
310 float posZ = dotPosZ * scale().z() + translate().z();
311
312 QQuaternion totalRotation;
313
314 if (graphModel->series->mesh() != QAbstract3DSeries::Mesh::Point)
315 totalRotation = item.rotation() * meshRotation;
316 else
317 totalRotation = cameraTarget()->rotation();
318
319 DataItemHolder dih;
320 if (isPolar()) {
321 float x;
322 float z;
323 calculatePolarXZ(posX: axisX()->positionAt(x: dotPos.x()),
324 posZ: axisZ()->positionAt(x: dotPos.z()),
325 x,
326 z);
327 dih.position = {x, posY, z};
328 } else {
329 dih.position = {posX, posY, posZ};
330 }
331 dih.rotation = totalRotation;
332 dih.scale = QVector3D(itemSize, itemSize, itemSize) * dataProxy->scaleAt(index: i);
333
334 positions.push_back(t: dih);
335 } else {
336 DataItemHolder dih;
337 dih.hide = true;
338 positions.push_back(t: dih);
339 }
340 }
341 graphModel->instancing->setDataArray(positions);
342
343 if (selectedItemInSeries(series: graphModel->series)) {
344 if (isDotPositionInAxisRange(dotPos: dataProxy->itemAt(index: m_selectedItem).position())) {
345 QQuaternion totalRotation;
346
347 if (graphModel->series->mesh() != QAbstract3DSeries::Mesh::Point) {
348 totalRotation = graphModel->instancing->dataArray().at(i: m_selectedItem).rotation
349 * meshRotation;
350 } else {
351 totalRotation = cameraTarget()->rotation();
352 }
353 graphModel->selectionIndicator->setRotation(totalRotation);
354 graphModel->instancing->hideDataItem(index: m_selectedItem);
355 } else {
356 clearSelectionModel();
357 }
358 }
359 }
360}
361
362void QQuickGraphsScatter::updateScatterGraphItemVisuals(ScatterModel *graphModel)
363{
364 bool useGradient = graphModel->series->d_func()->isUsingGradient();
365 bool usePoint = graphModel->series->mesh() == QAbstract3DSeries::Mesh::Point;
366 qsizetype itemCount = graphModel->series->dataProxy()->itemCount();
367
368 if (useGradient) {
369 if (!graphModel->seriesTexture) {
370 graphModel->seriesTexture = createTexture();
371 graphModel->seriesTexture->setParent(graphModel->series);
372 }
373
374 QLinearGradient gradient = graphModel->series->baseGradient();
375 auto textureData = static_cast<QQuickGraphsTextureData *>(
376 graphModel->seriesTexture->textureData());
377 textureData->createGradient(gradient);
378
379 if (!graphModel->highlightTexture) {
380 graphModel->highlightTexture = createTexture();
381 graphModel->highlightTexture->setParent(graphModel->series);
382 }
383
384 QLinearGradient highlightGradient = graphModel->series->singleHighlightGradient();
385 auto highlightTextureData = static_cast<QQuickGraphsTextureData *>(
386 graphModel->highlightTexture->textureData());
387 highlightTextureData->createGradient(gradient: highlightGradient);
388 } else {
389 if (graphModel->seriesTexture) {
390 graphModel->seriesTexture->deleteLater();
391 graphModel->seriesTexture = nullptr;
392 }
393
394 if (graphModel->highlightTexture) {
395 graphModel->highlightTexture->deleteLater();
396 graphModel->highlightTexture = nullptr;
397 }
398 }
399
400 bool rangeGradient = (useGradient
401 && graphModel->series->d_func()->m_colorStyle
402 == QGraphsTheme::ColorStyle::RangeGradient)
403 ? true
404 : false;
405
406 const bool shaded = graphModel->series->lightingMode()
407 == QAbstract3DSeries::LightingMode::Shaded;
408 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
409 // Release resources that might not have been deleted even though deleteLater had been set
410 if (m_customView)
411 m_customView->window()->releaseResources();
412 else
413 window()->releaseResources();
414
415 if (itemCount != graphModel->dataItems.size())
416 qWarning(msg: "%ls Item count differs from itemList count",
417 qUtf16Printable(QString::fromUtf8(__func__)));
418
419 bool transparentTexture = false;
420 if (graphModel->seriesTexture) {
421 auto textureData = static_cast<QQuickGraphsTextureData *>(
422 graphModel->seriesTexture->textureData());
423 transparentTexture = textureData->hasTransparency();
424 }
425 const bool transparency = (graphModel->series->baseColor().alphaF() < 1.0)
426 || transparentTexture;
427
428 updateMaterialReference(model: graphModel);
429 QQmlListReference baseRef(graphModel->baseRef, "materials");
430 auto baseMat = static_cast<QQuick3DCustomMaterial *>(baseRef.at(0));
431 for (const auto &obj : std::as_const(t&: graphModel->dataItems)) {
432 QQmlListReference matRef(obj, "materials");
433 matRef.clear();
434 matRef.append(baseMat);
435 }
436 if (m_selectedItem != invalidSelectionIndex() && graphModel->series == selectedSeries()) {
437 QQmlListReference selRef(graphModel->selectionRef, "materials");
438 auto selMat = static_cast<QQuick3DCustomMaterial *>(selRef.at(0));
439 QQuick3DModel *selectedItem = graphModel->dataItems.at(i: m_selectedItem);
440 QQmlListReference matRef(selectedItem, "materials");
441 matRef.clear();
442 matRef.append(selMat);
443 }
444 updateItemMaterial(item: graphModel->baseRef,
445 useGradient,
446 rangeGradient,
447 usePoint,
448 QStringLiteral(":/materials/ScatterMaterial"));
449
450 updateItemMaterial(item: graphModel->selectionRef,
451 useGradient,
452 rangeGradient,
453 usePoint,
454 QStringLiteral(":/materials/ScatterMaterial"));
455 updateMaterialProperties(item: graphModel->baseRef,
456 texture: graphModel->seriesTexture,
457 color: graphModel->series->baseColor(),
458 transparency,
459 shaded);
460
461 updateMaterialProperties(item: graphModel->selectionRef,
462 texture: graphModel->highlightTexture,
463 color: graphModel->series->singleHighlightColor(),
464 transparency: shaded);
465
466 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
467 graphModel->instancingRootItem->setVisible(true);
468 graphModel->instancing->setRangeGradient(rangeGradient);
469 if (!rangeGradient) {
470 bool transparentTexture = false;
471 if (graphModel->seriesTexture) {
472 auto textureData = static_cast<QQuickGraphsTextureData *>(
473 graphModel->seriesTexture->textureData());
474 transparentTexture = textureData->hasTransparency();
475 }
476 const bool transparency = (graphModel->series->baseColor().alphaF() < 1.0)
477 || transparentTexture;
478 if (transparency)
479 graphModel->instancing->setTransparency(true);
480 else
481 graphModel->instancing->setTransparency(false);
482
483 updateItemMaterial(item: graphModel->instancingRootItem,
484 useGradient,
485 rangeGradient,
486 usePoint,
487 QStringLiteral(":/materials/ScatterMaterialInstancing"));
488 updateMaterialProperties(item: graphModel->instancingRootItem,
489 texture: graphModel->seriesTexture,
490 color: graphModel->series->baseColor(),
491 transparency,
492 shaded);
493 } else {
494 auto textureData = static_cast<QQuickGraphsTextureData *>(
495 graphModel->seriesTexture->textureData());
496 if (textureData->hasTransparency())
497 graphModel->instancing->setTransparency(true);
498 else
499 graphModel->instancing->setTransparency(false);
500
501 updateItemMaterial(item: graphModel->instancingRootItem,
502 useGradient,
503 rangeGradient,
504 usePoint,
505 QStringLiteral(":/materials/ScatterMaterialInstancing"));
506 updateInstancedMaterialProperties(graphModel,
507 isHighlight: false,
508 seriesTexture: graphModel->seriesTexture,
509 highlightTexture: graphModel->highlightTexture,
510 transparency: textureData->hasTransparency());
511
512 const float scaleY = scaleWithBackground().y();
513 float rangeGradientYScaler = m_rangeGradientYHelper / scaleY;
514
515 QList<float> customData;
516 customData.resize(size: itemCount);
517
518 QList<DataItemHolder> instancingData = graphModel->instancing->dataArray();
519 for (int i = 0; i < instancingData.size(); i++) {
520 auto dih = instancingData.at(i);
521 float value = (dih.position.y() + scaleY) * rangeGradientYScaler;
522 customData[i] = value;
523 }
524 graphModel->instancing->setCustomData(customData);
525 }
526
527 if (selectedItemInSeries(series: graphModel->series)) {
528 // Selection indicator
529 if (!rangeGradient) {
530 updateItemMaterial(item: graphModel->selectionIndicator,
531 useGradient,
532 rangeGradient,
533 usePoint,
534 QStringLiteral(":/materials/ScatterMaterial"));
535 updateMaterialProperties(item: graphModel->selectionIndicator,
536 texture: graphModel->highlightTexture,
537 color: graphModel->series->singleHighlightColor(),
538 transparency: shaded);
539 graphModel->selectionIndicator->setCastsShadows(!usePoint);
540 } else {
541 // Rangegradient
542 updateItemMaterial(item: graphModel->selectionIndicator,
543 useGradient,
544 rangeGradient,
545 usePoint,
546 QStringLiteral(":/materials/ScatterMaterial"));
547 updateInstancedMaterialProperties(graphModel,
548 isHighlight: true,
549 seriesTexture: nullptr,
550 highlightTexture: graphModel->highlightTexture);
551 graphModel->selectionIndicator->setCastsShadows(!usePoint);
552 }
553
554 const DataItemHolder &dih = graphModel->instancing->dataArray().at(i: m_selectedItem);
555
556 graphModel->selectionIndicator->setPosition(dih.position);
557 graphModel->selectionIndicator->setRotation(dih.rotation);
558 graphModel->selectionIndicator->setScale(dih.scale);
559 graphModel->selectionIndicator->setVisible(true);
560 graphModel->instancing->hideDataItem(index: m_selectedItem);
561 updateItemLabel(position: graphModel->selectionIndicator->position());
562 graphModel->instancing->markDataDirty();
563 } else if ((m_selectedItem == -1 || m_selectedItemSeries != graphModel->series)
564 && graphModel->selectionIndicator) {
565 graphModel->selectionIndicator->setVisible(false);
566 }
567 }
568}
569
570void QQuickGraphsScatter::updateMaterialReference(ScatterModel *model)
571{
572 if (model->baseRef == nullptr) {
573 model->baseRef = createDataItem(series: model->series);
574 model->baseRef->setParent(model->series);
575 model->baseRef->setVisible(false);
576 }
577 if (model->selectionRef == nullptr) {
578 model->selectionRef = createDataItem(series: model->series);
579 model->selectionRef->setParent(model->series);
580 model->selectionRef->setVisible(false);
581 }
582
583 QQmlListReference baseRef(model->baseRef, "materials");
584 QQmlListReference selectionRef(model->selectionRef, "materials");
585
586 const QString &materialName = QStringLiteral(":/materials/ScatterMaterial");
587 if (!baseRef.size()) {
588 auto mat = createQmlCustomMaterial(fileName: materialName);
589 mat->setObjectName(materialName);
590 mat->setParent(model->baseRef);
591 baseRef.append(mat);
592 }
593 if (!selectionRef.size()) {
594 auto mat = createQmlCustomMaterial(fileName: materialName);
595 mat->setObjectName(materialName + QStringLiteral("_Selection"));
596 mat->setParent(model->selectionRef);
597 selectionRef.append(mat);
598 }
599}
600void QQuickGraphsScatter::updateItemMaterial(QQuick3DModel *item,
601 bool useGradient,
602 bool rangeGradient,
603 bool usePoint,
604 const QString &materialName)
605{
606 QQmlListReference materialsRef(item, "materials");
607 bool needNewMat = false;
608 if (!materialsRef.size()) {
609 needNewMat = true;
610 } else if (materialsRef.at(0)->objectName().contains(QStringLiteral("Instancing"))
611 != materialName.contains(QStringLiteral("Instancing"))) {
612 needNewMat = true;
613 }
614
615 if (needNewMat) {
616 materialsRef.clear();
617 auto newMaterial = createQmlCustomMaterial(fileName: materialName);
618 newMaterial->setObjectName(materialName);
619 newMaterial->setParent(item);
620 materialsRef.append(newMaterial);
621 }
622
623 auto material = qobject_cast<QQuick3DCustomMaterial *>(object: materialsRef.at(0));
624 if (!useGradient)
625 material->setProperty(name: "colorStyle", value: 0);
626 else if (!rangeGradient)
627 material->setProperty(name: "colorStyle", value: 1);
628 else
629 material->setProperty(name: "colorStyle", value: 2);
630
631 material->setProperty(name: "usePoint", value: usePoint);
632 material->setProperty(name: "rootScale", value: rootNode()->scale().y());
633}
634
635void QQuickGraphsScatter::updateInstancedMaterialProperties(ScatterModel *graphModel,
636 const bool isHighlight,
637 QQuick3DTexture *seriesTexture,
638 QQuick3DTexture *highlightTexture,
639 const bool transparency)
640{
641 QQuick3DModel *model = nullptr;
642 if (isHighlight)
643 model = graphModel->selectionIndicator;
644 else
645 model = graphModel->instancingRootItem;
646
647 QQmlListReference materialsRef(model, "materials");
648
649 auto customMaterial = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
650 customMaterial->setProperty(name: "transparency", value: transparency);
651 customMaterial->setProperty(name: "shaded",
652 value: graphModel->series->lightingMode()
653 == QAbstract3DSeries::LightingMode::Shaded);
654
655 QVariant textureInputAsVariant = customMaterial->property(name: "custex");
656 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
657 .value<QQuick3DShaderUtilsTextureInput *>();
658
659 if (isHighlight) {
660 textureInput->setTexture(highlightTexture);
661
662 if (selectedItemInSeries(series: graphModel->series))
663 m_selectedGradientPos = graphModel->instancing->customData().at(i: m_selectedItem);
664
665 customMaterial->setProperty(name: "gradientPos", value: m_selectedGradientPos);
666 } else {
667 textureInput->setTexture(seriesTexture);
668 }
669}
670
671void QQuickGraphsScatter::updateMaterialProperties(QQuick3DModel *item,
672 QQuick3DTexture *texture,
673 QColor color,
674 const bool transparency,
675 const bool shaded)
676{
677 QQmlListReference materialsRef(item, "materials");
678 auto customMaterial = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
679 customMaterial->setProperty(name: "transparency", value: transparency);
680 customMaterial->setProperty(name: "rootScale", value: rootNode()->scale().y());
681 customMaterial->setProperty(name: "shaded", value: shaded);
682
683 int style = customMaterial->property(name: "colorStyle").value<int>();
684 if (style == 0) {
685 customMaterial->setProperty(name: "uColor", value: color);
686 } else {
687 QVariant textureInputAsVariant = customMaterial->property(name: "custex");
688 QQuick3DShaderUtilsTextureInput *textureInput
689 = textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
690
691 textureInput->setTexture(texture);
692
693 const float scaleY = scaleWithBackground().y();
694 float rangeGradientYScaler = m_rangeGradientYHelper / scaleY;
695 float value = (item->y() + scaleY) * rangeGradientYScaler;
696 customMaterial->setProperty(name: "gradientPos", value);
697 }
698}
699
700QQuick3DTexture *QQuickGraphsScatter::createTexture()
701{
702 QQuick3DTexture *texture = new QQuick3DTexture();
703 texture->setParent(this);
704 texture->setRotationUV(-90.0f);
705 texture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
706 texture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
707 QQuickGraphsTextureData *textureData = new QQuickGraphsTextureData();
708 textureData->setParent(texture);
709 textureData->setParentItem(texture);
710 texture->setTextureData(textureData);
711
712 return texture;
713}
714
715QQuick3DNode *QQuickGraphsScatter::createSeriesRoot()
716{
717 auto model = new QQuick3DNode();
718
719 model->setParentItem(graphNode());
720 return model;
721}
722
723QQuick3DModel *QQuickGraphsScatter::createDataItem(QAbstract3DSeries *series)
724{
725 auto model = new QQuick3DModel();
726 model->setParent(this);
727 model->setParentItem(graphNode());
728 QString fileName = getMeshFileName(series);
729 if (fileName.isEmpty())
730 fileName = series->userDefinedMesh();
731
732 model->setSource(QUrl(fileName));
733 return model;
734}
735
736void QQuickGraphsScatter::removeDataItems(ScatterModel *graphModel,
737 QtGraphs3D::OptimizationHint optimizationHint)
738{
739 if (optimizationHint == QtGraphs3D::OptimizationHint::Default) {
740 delete graphModel->instancing;
741 graphModel->instancing = nullptr;
742 deleteDataItem(item: graphModel->instancingRootItem);
743 deleteDataItem(item: graphModel->selectionIndicator);
744 deleteDataItem(item: graphModel->baseRef);
745 deleteDataItem(item: graphModel->selectionRef);
746 deleteDataItem(item: graphModel->splineModel);
747
748 graphModel->instancingRootItem = nullptr;
749 graphModel->selectionIndicator = nullptr;
750 graphModel->baseRef = nullptr;
751 graphModel->selectionRef = nullptr;
752 graphModel->splineModel = nullptr;
753 } else {
754 QList<QQuick3DModel *> &items = graphModel->dataItems;
755 removeDataItems(items, count: items.count());
756 }
757}
758
759void QQuickGraphsScatter::removeDataItems(QList<QQuick3DModel *> &items, qsizetype count)
760{
761 for (int i = 0; i < count; ++i) {
762 QQuick3DModel *item = items.takeLast();
763 QQmlListReference materialsRef(item, "materials");
764 if (materialsRef.size()) {
765 QObject *material = materialsRef.at(0);
766 delete material;
767 }
768 item->deleteLater();
769 }
770}
771
772QList<QScatter3DSeries *> QQuickGraphsScatter::scatterSeriesList()
773{
774 QList<QScatter3DSeries *> scatterSeriesList;
775 for (QAbstract3DSeries *abstractSeries : std::as_const(t&: m_seriesList)) {
776 QScatter3DSeries *scatterSeries = qobject_cast<QScatter3DSeries *>(object: abstractSeries);
777 if (scatterSeries)
778 scatterSeriesList.append(t: scatterSeries);
779 }
780
781 return scatterSeriesList;
782}
783
784void QQuickGraphsScatter::recreateDataItems()
785{
786 if (!isComponentComplete())
787 return;
788 QList<QScatter3DSeries *> seriesList = scatterSeriesList();
789 for (auto series : std::as_const(t&: seriesList)) {
790 for (const auto &model : std::as_const(t&: m_scatterGraphs)) {
791 if (model->series == series)
792 removeDataItems(graphModel: model, optimizationHint: optimizationHint());
793 }
794 }
795 markDataDirty();
796}
797
798void QQuickGraphsScatter::recreateDataItems(const QList<ScatterModel *> &graphs)
799{
800 if (!isComponentComplete())
801 return;
802 QList<QScatter3DSeries *> seriesList = scatterSeriesList();
803 for (auto series : std::as_const(t&: seriesList)) {
804 for (const auto &model : graphs) {
805 if (model->series == series)
806 removeDataItems(graphModel: model, optimizationHint: optimizationHint());
807 }
808 }
809 markDataDirty();
810}
811
812void QQuickGraphsScatter::addPointsToScatterModel(ScatterModel *graphModel, qsizetype count)
813{
814 for (int i = 0; i < count; i++) {
815 QQuick3DModel *item = createDataItem(series: graphModel->series);
816 item->setPickable(true);
817 item->setParent(graphModel->series);
818 graphModel->dataItems.push_back(t: item);
819 }
820 setSeriesVisualsDirty();
821}
822
823qsizetype QQuickGraphsScatter::sizeDifference(qsizetype size1, qsizetype size2)
824{
825 return size2 - size1;
826}
827
828QVector3D QQuickGraphsScatter::selectedItemPosition()
829{
830 QVector3D position;
831 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy)
832 position = {0.0f, 0.0f, 0.0f};
833 else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default)
834 position = {0.0f, 0.0f, 0.0f};
835
836 return position;
837}
838
839void QQuickGraphsScatter::fixMeshFileName(QString &fileName, QAbstract3DSeries *series)
840{
841 auto meshType = series->mesh();
842 // Should it be smooth?
843 if (series->isMeshSmooth() && meshType != QAbstract3DSeries::Mesh::Point
844 && meshType != QAbstract3DSeries::Mesh::UserDefined) {
845 fileName += QStringLiteral("Smooth");
846 }
847
848 // Should it be filled?
849 if (meshType != QAbstract3DSeries::Mesh::Sphere && meshType != QAbstract3DSeries::Mesh::Arrow
850 && meshType != QAbstract3DSeries::Mesh::Minimal
851 && meshType != QAbstract3DSeries::Mesh::Point
852 && meshType != QAbstract3DSeries::Mesh::UserDefined) {
853 fileName.append(QStringLiteral("Full"));
854 }
855}
856
857QString QQuickGraphsScatter::getMeshFileName(QAbstract3DSeries *series)
858{
859 QString fileName = {};
860 switch (series->mesh()) {
861 case QAbstract3DSeries::Mesh::Sphere:
862 fileName = QStringLiteral("defaultMeshes/sphereMesh");
863 break;
864 case QAbstract3DSeries::Mesh::Bar:
865 case QAbstract3DSeries::Mesh::Cube:
866 fileName = QStringLiteral("defaultMeshes/barMesh");
867 break;
868 case QAbstract3DSeries::Mesh::Pyramid:
869 fileName = QStringLiteral("defaultMeshes/pyramidMesh");
870 break;
871 case QAbstract3DSeries::Mesh::Cone:
872 fileName = QStringLiteral("defaultMeshes/coneMesh");
873 break;
874 case QAbstract3DSeries::Mesh::Cylinder:
875 fileName = QStringLiteral("defaultMeshes/cylinderMesh");
876 break;
877 case QAbstract3DSeries::Mesh::BevelBar:
878 case QAbstract3DSeries::Mesh::BevelCube:
879 fileName = QStringLiteral("defaultMeshes/bevelBarMesh");
880 break;
881 case QAbstract3DSeries::Mesh::Minimal:
882 fileName = QStringLiteral("defaultMeshes/minimalMesh");
883 break;
884 case QAbstract3DSeries::Mesh::Arrow:
885 fileName = QStringLiteral("defaultMeshes/arrowMesh");
886 break;
887 case QAbstract3DSeries::Mesh::Point:
888 fileName = shadowQuality() == QtGraphs3D::ShadowQuality::None
889 ? QStringLiteral("defaultMeshes/planeMesh")
890 : QStringLiteral("defaultMeshes/octagonMesh");
891 break;
892 case QAbstract3DSeries::Mesh::UserDefined:
893 break;
894 default:
895 fileName = QStringLiteral("defaultMeshes/sphereMesh");
896 }
897
898 fixMeshFileName(fileName, series);
899
900 return fileName;
901}
902
903void QQuickGraphsScatter::deleteDataItem(QQuick3DModel *item)
904{
905 if (item) {
906 QQmlListReference materialsRef(item, "materials");
907 if (materialsRef.size()) {
908 QObject *material = materialsRef.at(0);
909 delete material;
910 }
911 item->deleteLater();
912 item = nullptr;
913 }
914}
915
916void QQuickGraphsScatter::handleSeriesChanged(QList<QAbstract3DSeries *> changedSeries)
917{
918 Q_UNUSED(changedSeries)
919 // TODO: generate items and remove old items
920}
921
922bool QQuickGraphsScatter::selectedItemInSeries(const QScatter3DSeries *series)
923{
924 return (m_selectedItem != -1 && m_selectedItemSeries == series);
925}
926
927bool QQuickGraphsScatter::isDotPositionInAxisRange(QVector3D dotPos)
928{
929 return ((dotPos.x() >= axisX()->min() && dotPos.x() <= axisX()->max())
930 && (dotPos.y() >= axisY()->min() && dotPos.y() <= axisY()->max())
931 && (dotPos.z() >= axisZ()->min() && dotPos.z() <= axisZ()->max()));
932}
933
934QScatter3DSeries *QQuickGraphsScatter::selectedSeries() const
935{
936 return m_selectedItemSeries;
937}
938
939void QQuickGraphsScatter::setSelectedItem(qsizetype index, QScatter3DSeries *series)
940{
941 const QScatterDataProxy *proxy = 0;
942
943 // Series may already have been removed, so check it before setting the selection.
944 if (!m_seriesList.contains(t: series))
945 series = nullptr;
946
947 if (series)
948 proxy = series->dataProxy();
949
950 if (!proxy || index < 0 || index >= proxy->itemCount())
951 index = invalidSelectionIndex();
952
953 if (index != m_selectedItem || series != m_selectedItemSeries) {
954 bool seriesChanged = (series != m_selectedItemSeries);
955
956 // Clear hidden point from the previous selected series
957 if (seriesChanged) {
958 for (auto model : std::as_const(t&: m_scatterGraphs)) {
959 if (model->series && model->instancing && model->series == m_selectedItemSeries)
960 model->instancing->unhidePreviousDataItem();
961 }
962 }
963
964 m_selectedItem = index;
965 m_selectedItemSeries = series;
966 m_changeTracker.selectedItemChanged = true;
967
968 // Clear selection from other series and finally set new selection to the
969 // specified series
970 for (QAbstract3DSeries *otherSeries : std::as_const(t&: m_seriesList)) {
971 QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(otherSeries);
972 if (scatterSeries != m_selectedItemSeries)
973 scatterSeries->d_func()->setSelectedItem(invalidSelectionIndex());
974 }
975 if (m_selectedItemSeries)
976 m_selectedItemSeries->d_func()->setSelectedItem(m_selectedItem);
977
978 if (seriesChanged)
979 emit selectedSeriesChanged(series: m_selectedItemSeries);
980
981 emitNeedRender();
982 }
983
984 if (index != invalidSelectionIndex())
985 itemLabel()->setVisible(true);
986}
987
988void QQuickGraphsScatter::setSelectionMode(QtGraphs3D::SelectionFlags mode)
989{
990 // We only support single item selection mode and no selection mode
991 if (mode != QtGraphs3D::SelectionFlag::Item && mode != QtGraphs3D::SelectionFlag::None) {
992 qCWarning(lcProperties3D, "%s unsupported selection mode - only none and item selection modes "
993 "are supported", qUtf8Printable(QLatin1String(__FUNCTION__)));
994 return;
995 }
996
997 QQuickGraphsItem::setSelectionMode(mode);
998}
999
1000void QQuickGraphsScatter::handleAxisAutoAdjustRangeChangedInOrientation(
1001 QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust)
1002{
1003 Q_UNUSED(orientation);
1004 Q_UNUSED(autoAdjust);
1005 adjustAxisRanges();
1006}
1007
1008void QQuickGraphsScatter::handleAxisRangeChangedBySender(QObject *sender)
1009{
1010 QQuickGraphsItem::handleAxisRangeChangedBySender(sender);
1011
1012 m_isDataDirty = true;
1013
1014 // Update selected index - may be moved offscreen
1015 setSelectedItem(index: m_selectedItem, series: m_selectedItemSeries);
1016}
1017
1018void QQuickGraphsScatter::handleLightingModeChanged() {
1019 auto series = static_cast<QScatter3DSeries *>(QObject::sender());
1020 for (auto model : std::as_const(t&: m_scatterGraphs)) {
1021 if (model->series == series) {
1022 updateScatterGraphItemVisuals(graphModel: model);
1023 break;
1024 }
1025 }
1026}
1027
1028QQmlListProperty<QScatter3DSeries> QQuickGraphsScatter::seriesList()
1029{
1030 return QQmlListProperty<QScatter3DSeries>(this,
1031 this,
1032 &QQuickGraphsScatter::appendSeriesFunc,
1033 &QQuickGraphsScatter::countSeriesFunc,
1034 &QQuickGraphsScatter::atSeriesFunc,
1035 &QQuickGraphsScatter::clearSeriesFunc);
1036}
1037
1038void QQuickGraphsScatter::appendSeriesFunc(QQmlListProperty<QScatter3DSeries> *list,
1039 QScatter3DSeries *series)
1040{
1041 reinterpret_cast<QQuickGraphsScatter *>(list->data)->addSeries(series);
1042}
1043
1044qsizetype QQuickGraphsScatter::countSeriesFunc(QQmlListProperty<QScatter3DSeries> *list)
1045{
1046 return reinterpret_cast<QQuickGraphsScatter *>(list->data)->scatterSeriesList().size();
1047}
1048
1049QScatter3DSeries *QQuickGraphsScatter::atSeriesFunc(QQmlListProperty<QScatter3DSeries> *list,
1050 qsizetype index)
1051{
1052 return reinterpret_cast<QQuickGraphsScatter *>(list->data)->scatterSeriesList().at(i: index);
1053}
1054
1055void QQuickGraphsScatter::clearSeriesFunc(QQmlListProperty<QScatter3DSeries> *list)
1056{
1057 QQuickGraphsScatter *declScatter = reinterpret_cast<QQuickGraphsScatter *>(list->data);
1058 QList<QScatter3DSeries *> realList = declScatter->scatterSeriesList();
1059 qsizetype count = realList.size();
1060 for (int i = 0; i < count; i++)
1061 declScatter->removeSeries(series: realList.at(i));
1062}
1063
1064void QQuickGraphsScatter::addSeries(QScatter3DSeries *series)
1065{
1066 Q_ASSERT(series && series->type() == QAbstract3DSeries::SeriesType::Scatter);
1067
1068 QQuickGraphsItem::addSeriesInternal(series);
1069
1070 QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(series);
1071 if (scatterSeries->selectedItem() != invalidSelectionIndex())
1072 setSelectedItem(index: scatterSeries->selectedItem(), series: scatterSeries);
1073
1074 auto graphModel = new ScatterModel;
1075 graphModel->series = series;
1076 graphModel->seriesTexture = nullptr;
1077 graphModel->highlightTexture = nullptr;
1078 m_scatterGraphs.push_back(t: graphModel);
1079
1080 connectSeries(series);
1081
1082 if (series->selectedItem() != invalidSelectionIndex())
1083 setSelectedItem(index: series->selectedItem(), series);
1084}
1085
1086void QQuickGraphsScatter::removeSeries(QScatter3DSeries *series)
1087{
1088 bool wasVisible = (series && series->d_func()->m_graph == this && series->isVisible());
1089
1090 QQuickGraphsItem::removeSeriesInternal(series);
1091
1092 if (m_selectedItemSeries == series)
1093 setSelectedItem(index: invalidSelectionIndex(), series: 0);
1094
1095 if (wasVisible)
1096 adjustAxisRanges();
1097
1098 series->setParent(this); // Reparent as removing will leave series parentless
1099
1100 // Find scattergraph model
1101 for (QList<ScatterModel *>::Iterator it = m_scatterGraphs.begin();
1102 it != m_scatterGraphs.end();) {
1103 if ((*it)->series == series) {
1104 removeDataItems(graphModel: *it, optimizationHint: optimizationHint());
1105
1106 if ((*it)->seriesTexture)
1107 delete (*it)->seriesTexture;
1108 if ((*it)->highlightTexture)
1109 delete (*it)->highlightTexture;
1110
1111 delete *it;
1112 it = m_scatterGraphs.erase(pos: it);
1113 } else {
1114 ++it;
1115 }
1116 }
1117
1118 disconnectSeries(series);
1119}
1120
1121void QQuickGraphsScatter::handleAxisXChanged(QAbstract3DAxis *axis)
1122{
1123 emit axisXChanged(axis: static_cast<QValue3DAxis *>(axis));
1124}
1125
1126void QQuickGraphsScatter::handleAxisYChanged(QAbstract3DAxis *axis)
1127{
1128 emit axisYChanged(axis: static_cast<QValue3DAxis *>(axis));
1129}
1130
1131void QQuickGraphsScatter::handleAxisZChanged(QAbstract3DAxis *axis)
1132{
1133 emit axisZChanged(axis: static_cast<QValue3DAxis *>(axis));
1134}
1135
1136void QQuickGraphsScatter::handleSeriesMeshChanged()
1137{
1138 recreateDataItems();
1139}
1140
1141void QQuickGraphsScatter::handleMeshSmoothChanged(bool enable)
1142{
1143 Q_UNUSED(enable);
1144 QScatter3DSeries *series = qobject_cast<QScatter3DSeries *>(object: sender());
1145 for (auto &model : std::as_const(t&: m_scatterGraphs)) {
1146 if (model->series == series)
1147 removeDataItems(graphModel: model, optimizationHint: optimizationHint());
1148 }
1149 markDataDirty();
1150}
1151
1152void QQuickGraphsScatter::handleArrayReset()
1153{
1154 QScatter3DSeries *series;
1155 if (qobject_cast<QScatterDataProxy *>(object: sender()))
1156 series = static_cast<QScatterDataProxy *>(sender())->series();
1157 else
1158 series = static_cast<QScatter3DSeries *>(sender());
1159
1160 if (series->isVisible()) {
1161 adjustAxisRanges();
1162 m_isDataDirty = true;
1163 }
1164 if (!m_changedSeriesList.contains(t: series))
1165 m_changedSeriesList.append(t: series);
1166 setSelectedItem(index: m_selectedItem, series: m_selectedItemSeries);
1167 series->d_func()->markItemLabelDirty();
1168 emitNeedRender();
1169}
1170
1171void QQuickGraphsScatter::handleItemsAdded(qsizetype startIndex, qsizetype count)
1172{
1173 Q_UNUSED(startIndex);
1174 Q_UNUSED(count);
1175 QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
1176 if (series->isVisible()) {
1177 adjustAxisRanges();
1178 m_isDataDirty = true;
1179 }
1180 if (!m_changedSeriesList.contains(t: series))
1181 m_changedSeriesList.append(t: series);
1182 emitNeedRender();
1183}
1184
1185void QQuickGraphsScatter::handleItemsChanged(qsizetype startIndex, qsizetype count)
1186{
1187 QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
1188 qsizetype oldChangeCount = m_changedItems.size();
1189 if (!oldChangeCount)
1190 m_changedItems.reserve(asize: count);
1191
1192 for (int i = 0; i < count; i++) {
1193 bool newItem = true;
1194 qsizetype candidate = startIndex + i;
1195 for (qsizetype j = 0; j < oldChangeCount; j++) {
1196 const ChangeItem &oldChangeItem = m_changedItems.at(i: j);
1197 if (oldChangeItem.index == candidate && series == oldChangeItem.series) {
1198 newItem = false;
1199 break;
1200 }
1201 }
1202 if (newItem) {
1203 ChangeItem newChangeItem = {.series: series, .index: candidate};
1204 m_changedItems.append(t: newChangeItem);
1205 if (series == m_selectedItemSeries && m_selectedItem == candidate)
1206 series->d_func()->markItemLabelDirty();
1207 }
1208 }
1209
1210 if (count) {
1211 m_changeTracker.itemChanged = true;
1212 if (series->isVisible())
1213 adjustAxisRanges();
1214 emitNeedRender();
1215 }
1216}
1217
1218void QQuickGraphsScatter::handleItemsRemoved(qsizetype startIndex, qsizetype count)
1219{
1220 Q_UNUSED(startIndex);
1221 Q_UNUSED(count);
1222 QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
1223 if (series == m_selectedItemSeries) {
1224 // If items removed from selected series before the selection, adjust the selection
1225 qsizetype selectedItem = m_selectedItem;
1226 if (startIndex <= selectedItem) {
1227 if ((startIndex + count) > selectedItem)
1228 selectedItem = -1; // Selected item removed
1229 else
1230 selectedItem -= count; // Move selected item down by amount of item removed
1231
1232 setSelectedItem(index: selectedItem, series: m_selectedItemSeries);
1233 }
1234 }
1235
1236 if (series->isVisible()) {
1237 adjustAxisRanges();
1238 m_isDataDirty = true;
1239 }
1240 if (!m_changedSeriesList.contains(t: series))
1241 m_changedSeriesList.append(t: series);
1242
1243 if (m_recordInsertsAndRemoves) {
1244 InsertRemoveRecord record(false, startIndex, count, series);
1245 m_insertRemoveRecords.append(t: record);
1246 }
1247
1248 emitNeedRender();
1249}
1250
1251void QQuickGraphsScatter::adjustAxisRanges()
1252{
1253 QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
1254 QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
1255 QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
1256 bool adjustX = (valueAxisX && valueAxisX->isAutoAdjustRange());
1257 bool adjustY = (valueAxisY && valueAxisY->isAutoAdjustRange());
1258 bool adjustZ = (valueAxisZ && valueAxisZ->isAutoAdjustRange());
1259
1260 if (adjustX || adjustY || adjustZ) {
1261 float minValueX = 0.0f;
1262 float maxValueX = 0.0f;
1263 float minValueY = 0.0f;
1264 float maxValueY = 0.0f;
1265 float minValueZ = 0.0f;
1266 float maxValueZ = 0.0f;
1267 qsizetype seriesCount = m_seriesList.size();
1268 for (int series = 0; series < seriesCount; series++) {
1269 const QScatter3DSeries *scatterSeries = static_cast<QScatter3DSeries *>(
1270 m_seriesList.at(i: series));
1271 const QScatterDataProxy *proxy = scatterSeries->dataProxy();
1272 if (scatterSeries->isVisible() && proxy) {
1273 QVector3D minLimits;
1274 QVector3D maxLimits;
1275 proxy->d_func()->limitValues(minValues&: minLimits,
1276 maxValues&: maxLimits,
1277 axisX: valueAxisX,
1278 axisY: valueAxisY,
1279 axisZ: valueAxisZ);
1280 if (adjustX) {
1281 if (!series) {
1282 // First series initializes the values
1283 minValueX = minLimits.x();
1284 maxValueX = maxLimits.x();
1285 } else {
1286 minValueX = qMin(a: minValueX, b: minLimits.x());
1287 maxValueX = qMax(a: maxValueX, b: maxLimits.x());
1288 }
1289 }
1290 if (adjustY) {
1291 if (!series) {
1292 // First series initializes the values
1293 minValueY = minLimits.y();
1294 maxValueY = maxLimits.y();
1295 } else {
1296 minValueY = qMin(a: minValueY, b: minLimits.y());
1297 maxValueY = qMax(a: maxValueY, b: maxLimits.y());
1298 }
1299 }
1300 if (adjustZ) {
1301 if (!series) {
1302 // First series initializes the values
1303 minValueZ = minLimits.z();
1304 maxValueZ = maxLimits.z();
1305 } else {
1306 minValueZ = qMin(a: minValueZ, b: minLimits.z());
1307 maxValueZ = qMax(a: maxValueZ, b: maxLimits.z());
1308 }
1309 }
1310 }
1311 }
1312
1313 static const float adjustmentRatio = 20.0f;
1314 static const float defaultAdjustment = 1.0f;
1315
1316 if (adjustX) {
1317 // If all points at same coordinate, need to default to some valid range
1318 float adjustment = 0.0f;
1319 if (minValueX == maxValueX) {
1320 if (adjustZ) {
1321 // X and Z are linked to have similar unit size, so choose the valid
1322 // range based on it
1323 if (minValueZ == maxValueZ)
1324 adjustment = defaultAdjustment;
1325 else
1326 adjustment = qAbs(t: maxValueZ - minValueZ) / adjustmentRatio;
1327 } else {
1328 if (valueAxisZ)
1329 adjustment = qAbs(t: valueAxisZ->max() - valueAxisZ->min()) / adjustmentRatio;
1330 else
1331 adjustment = defaultAdjustment;
1332 }
1333 }
1334 valueAxisX->d_func()->setRange(min: minValueX - adjustment, max: maxValueX + adjustment, suppressWarnings: true);
1335 }
1336 if (adjustY) {
1337 // If all points at same coordinate, need to default to some valid range
1338 // Y-axis unit is not dependent on other axes, so simply adjust +-1.0f
1339 float adjustment = 0.0f;
1340 if (minValueY == maxValueY)
1341 adjustment = defaultAdjustment;
1342 valueAxisY->d_func()->setRange(min: minValueY - adjustment, max: maxValueY + adjustment, suppressWarnings: true);
1343 }
1344 if (adjustZ) {
1345 // If all points at same coordinate, need to default to some valid range
1346 float adjustment = 0.0f;
1347 if (minValueZ == maxValueZ) {
1348 if (adjustX) {
1349 // X and Z are linked to have similar unit size, so choose the valid
1350 // range based on it
1351 if (minValueX == maxValueX)
1352 adjustment = defaultAdjustment;
1353 else
1354 adjustment = qAbs(t: maxValueX - minValueX) / adjustmentRatio;
1355 } else {
1356 if (valueAxisX)
1357 adjustment = qAbs(t: valueAxisX->max() - valueAxisX->min()) / adjustmentRatio;
1358 else
1359 adjustment = defaultAdjustment;
1360 }
1361 }
1362 valueAxisZ->d_func()->setRange(min: minValueZ - adjustment, max: maxValueZ + adjustment, suppressWarnings: true);
1363 }
1364 }
1365}
1366
1367void QQuickGraphsScatter::handleItemsInserted(qsizetype startIndex, qsizetype count)
1368{
1369 Q_UNUSED(startIndex);
1370 Q_UNUSED(count);
1371 QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
1372 if (series == m_selectedItemSeries) {
1373 // If items inserted to selected series before the selection, adjust the selection
1374 qsizetype selectedItem = m_selectedItem;
1375 if (startIndex <= selectedItem) {
1376 selectedItem += count;
1377 setSelectedItem(index: selectedItem, series: m_selectedItemSeries);
1378 }
1379 }
1380
1381 if (series->isVisible()) {
1382 adjustAxisRanges();
1383 m_isDataDirty = true;
1384 }
1385 if (!m_changedSeriesList.contains(t: series))
1386 m_changedSeriesList.append(t: series);
1387
1388 if (m_recordInsertsAndRemoves) {
1389 InsertRemoveRecord record(true, startIndex, count, series);
1390 m_insertRemoveRecords.append(t: record);
1391 }
1392
1393 emitNeedRender();
1394}
1395
1396bool QQuickGraphsScatter::doPicking(QPointF position)
1397{
1398 if (!QQuickGraphsItem::doPicking(point: position))
1399 return false;
1400
1401 if (selectionMode() == QtGraphs3D::SelectionFlag::Item) {
1402 QList<QQuick3DPickResult> results;
1403 if (m_customView)
1404 results = m_customView->pickAll(x: position.x(), y: position.y());
1405 else
1406 results = pickAll(x: position.x(), y: position.y());
1407 if (!results.empty()) {
1408 for (const auto &result : std::as_const(t&: results)) {
1409 if (const auto &hitItem = result.objectHit()) {
1410 if (hitItem == backgroundBB() || hitItem == background()) {
1411 m_clickedType = QtGraphs3D::ElementType::None;
1412 clearSelectionModel();
1413 continue;
1414 }
1415
1416 if (m_clickedType != QtGraphs3D::ElementType::AxisXLabel
1417 && m_clickedType != QtGraphs3D::ElementType::AxisYLabel
1418 && m_clickedType != QtGraphs3D::ElementType::AxisZLabel) {
1419 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1420 setSelected(hitItem);
1421 handleSelectedElementChange(type: QtGraphs3D::ElementType::Series);
1422 break;
1423 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1424 setSelected(root: hitItem, index: result.instanceIndex());
1425 handleSelectedElementChange(type: QtGraphs3D::ElementType::Series);
1426 const auto& hitSeries = static_cast<QScatter3DSeries *>(hitItem->parent());
1427 qCDebug(lcInput3D) << "pick results:"
1428 << "\n hit item position:" << position
1429 << "\n hit item instance index:" << result.instanceIndex()
1430 << "\n hit item values:" << hitSeries->dataProxy()->itemAt(index: result.instanceIndex()).position();
1431 break;
1432 }
1433 } else {
1434 clearSelectionModel();
1435 }
1436 }
1437 }
1438 } else {
1439 clearSelectionModel();
1440 handleSelectedElementChange(type: QtGraphs3D::ElementType::None);
1441 }
1442 }
1443
1444 return true;
1445}
1446
1447bool QQuickGraphsScatter::doRayPicking(QVector3D origin, QVector3D direction)
1448{
1449 if (!QQuickGraphsItem::doRayPicking(origin, direction))
1450 return false;
1451
1452 if (selectionMode() == QtGraphs3D::SelectionFlag::Item) {
1453 QList<QQuick3DPickResult> results;
1454 if (m_customView)
1455 results = m_customView->rayPickAll(origin, direction);
1456 else
1457 results = rayPickAll(origin, direction);
1458 if (!results.empty()) {
1459 for (const auto &result : std::as_const(t&: results)) {
1460 if (const auto &hit = result.objectHit()) {
1461 if (hit == backgroundBB() || hit == background()) {
1462 clearSelectionModel();
1463 continue;
1464 }
1465 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1466 setSelected(hit);
1467 break;
1468 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
1469 setSelected(root: hit, index: result.instanceIndex());
1470 const auto& hitSeries = static_cast<QScatter3DSeries *>(hit->parent());
1471 qCDebug(lcInput3D) << "pick results:"
1472 << "\n hit item instance index:" << result.instanceIndex()
1473 << "\n hit item values:" << hitSeries->dataProxy()->itemAt(index: result.instanceIndex()).position();
1474 break;
1475 }
1476 }
1477 }
1478 } else {
1479 clearSelectionModel();
1480 }
1481 }
1482 return true;
1483}
1484
1485void QQuickGraphsScatter::updateShadowQuality(QtGraphs3D::ShadowQuality quality)
1486{
1487 // Were shadows visible before?
1488 bool prevShadowsVisible = light()->castsShadow();
1489 QQuickGraphsItem::updateShadowQuality(quality);
1490 setSeriesVisualsDirty();
1491
1492 if (prevShadowsVisible != light()->castsShadow()) {
1493 // Need to change mesh for series using point type
1494 QList<ScatterModel *> graphs;
1495 for (const auto &graph : std::as_const(t&: m_scatterGraphs)) {
1496 if (graph->series->mesh() == QAbstract3DSeries::Mesh::Point)
1497 graphs.append(t: graph);
1498 }
1499 recreateDataItems(graphs);
1500 }
1501}
1502
1503void QQuickGraphsScatter::updateLightStrength()
1504{
1505 for (auto graphModel : std::as_const(t&: m_scatterGraphs)) {
1506 for (const auto &obj : std::as_const(t&: graphModel->dataItems)) {
1507 QQmlListReference materialsRef(obj, "materials");
1508 auto material = qobject_cast<QQuick3DCustomMaterial *>(object: materialsRef.at(0));
1509 material->setProperty(name: "specularBrightness", value: lightStrength() * 0.05);
1510 }
1511 }
1512}
1513
1514void QQuickGraphsScatter::startRecordingRemovesAndInserts()
1515{
1516 m_recordInsertsAndRemoves = false;
1517
1518 if (m_scene->selectionQueryPosition() != m_scene->invalidSelectionPoint()) {
1519 m_recordInsertsAndRemoves = true;
1520 if (m_insertRemoveRecords.size()) {
1521 m_insertRemoveRecords.clear();
1522 // Reserve some space for remove/insert records to avoid unnecessary reallocations.
1523 m_insertRemoveRecords.reserve(asize: insertRemoveRecordReserveSize);
1524 }
1525 }
1526}
1527
1528void QQuickGraphsScatter::componentComplete()
1529{
1530 QQuickGraphsItem::componentComplete();
1531 QObject::connect(sender: cameraTarget(),
1532 signal: &QQuick3DNode::rotationChanged,
1533 context: this,
1534 slot: &QQuickGraphsScatter::cameraRotationChanged);
1535
1536 graphsInputHandler()->setGraphsItem(this);
1537 qCDebug(lcGraphs3D) << "QQuickGraphsScatter::componentComplete";
1538}
1539
1540void QQuickGraphsScatter::connectSeries(QScatter3DSeries *series)
1541{
1542 QObject::connect(sender: series,
1543 signal: &QScatter3DSeries::meshChanged,
1544 context: this,
1545 slot: &QQuickGraphsScatter::handleSeriesMeshChanged);
1546 QObject::connect(sender: series,
1547 signal: &QScatter3DSeries::meshSmoothChanged,
1548 context: this,
1549 slot: &QQuickGraphsScatter::handleMeshSmoothChanged);
1550 QObject::connect(sender: series,
1551 signal: &QScatter3DSeries::itemSizeChanged,
1552 context: this,
1553 slot: &QQuickGraphsScatter::markDataDirty);
1554}
1555
1556void QQuickGraphsScatter::calculateSceneScalingFactors()
1557{
1558 float marginV = 0.0f;
1559 float marginH = 0.0f;
1560 if (margin() < 0.0f) {
1561 if (m_maxItemSize > m_defaultMaxSize)
1562 marginH = m_maxItemSize / m_itemScaler;
1563 else
1564 marginH = m_defaultMaxSize;
1565 marginV = marginH;
1566 } else {
1567 marginH = margin();
1568 marginV = margin();
1569 }
1570 if (isPolar()) {
1571 float polarMargin = calculatePolarBackgroundMargin();
1572 marginH = qMax(a: marginH, b: polarMargin);
1573 }
1574
1575 float tHorizontalAspectRatio;
1576 if (isPolar())
1577 tHorizontalAspectRatio = 1.0f;
1578 else
1579 tHorizontalAspectRatio = horizontalAspectRatio();
1580
1581 QSizeF areaSize;
1582 if (qFuzzyIsNull(f: tHorizontalAspectRatio)) {
1583 areaSize.setHeight(axisZ()->max() - axisZ()->min());
1584 areaSize.setWidth(axisX()->max() - axisX()->min());
1585 } else {
1586 areaSize.setHeight(1.0f);
1587 areaSize.setWidth(tHorizontalAspectRatio);
1588 }
1589
1590 float horizontalMaxDimension;
1591 float scaleY = 0.0f;
1592 if (aspectRatio() > 2.0f) {
1593 horizontalMaxDimension = 2.0f;
1594 scaleY = 2.0f / aspectRatio();
1595 } else {
1596 horizontalMaxDimension = aspectRatio();
1597 scaleY = 1.0f;
1598 }
1599
1600 if (isPolar())
1601 m_polarRadius = horizontalMaxDimension;
1602
1603 float scaleFactor = qMax(a: areaSize.width(), b: areaSize.height());
1604 float scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor;
1605 float scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor;
1606
1607 setBackgroundScaleMargin({marginH, marginV, marginH});
1608
1609 setScaleWithBackground({scaleX, scaleY, scaleZ});
1610 setScale({scaleX * 2.0f, scaleY * 2.0f, scaleZ * -2.0f});
1611 setTranslate({-scaleX, -scaleY, scaleZ});
1612}
1613
1614float QQuickGraphsScatter::calculatePointScaleSize()
1615{
1616 QList<QScatter3DSeries *> series = scatterSeriesList();
1617 int totalDataSize = 0;
1618 for (const auto &scatterSeries : std::as_const(t&: series)) {
1619 if (scatterSeries->isVisible())
1620 totalDataSize += scatterSeries->dataArray().size();
1621 }
1622
1623 return qBound(min: m_defaultMinSize, val: 2.0f / float(qSqrt(v: qreal(totalDataSize))), max: m_defaultMaxSize);
1624}
1625
1626void QQuickGraphsScatter::updatePointScaleSize()
1627{
1628 m_pointScale = calculatePointScaleSize();
1629}
1630
1631void QQuickGraphsScatter::calculatePolarXZ(const float posX,
1632 const float posZ,
1633 float &x,
1634 float &z) const
1635{
1636 const qreal angle = posX * (M_PI * 2.0f);
1637 const qreal radius = posZ;
1638
1639 x = static_cast<float>(radius * qSin(v: angle)) * m_polarRadius;
1640 z = -static_cast<float>(radius * qCos(v: angle)) * m_polarRadius;
1641}
1642
1643void QQuickGraphsScatter::updateSpline(ScatterModel *model)
1644{
1645 if (auto series = qobject_cast<QSpline3DSeries *>(object: model->series)) {
1646 if (!series->isSplineVisible()) {
1647 if (model->splineModel)
1648 model->splineModel->setVisible(false);
1649 return;
1650 } else {
1651 if (!model->splineModel)
1652 createSplineModel(model);
1653
1654 QQmlListReference materialRef(model->splineModel, "materials");
1655 QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(
1656 object: materialRef.at(0));
1657
1658 QVariant splineInputAsVariant = material->property(name: "controlPoints");
1659 QQuick3DShaderUtilsTextureInput *splineInput
1660 = splineInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
1661 QQuick3DTexture *splineTexture = splineInput->texture();
1662 QQuick3DTextureData *splineData = splineTexture->textureData();
1663
1664 bool loop = series->isSplineLooping();
1665 material->setProperty(name: "tension", value: series->splineTension());
1666 material->setProperty(name: "knotting", value: series->splineKnotting());
1667 material->setProperty(name: "loop", value: loop);
1668 material->setProperty(name: "color", value: series->splineColor());
1669
1670 const QScatterDataArray &array = series->dataArray();
1671 qsizetype pointCount = array.size();
1672 if (isDataDirty() && array.size() != 0) {
1673 QVector<QVector4D> splinePoints;
1674 QVector<SplineVertex> vertices;
1675 splinePoints.reserve(asize: pointCount + 2);
1676 splineData->setSize(QSize(pointCount + 2, 1));
1677
1678 auto normalizedPos = [this](QVector3D pos) {
1679 float posX = static_cast<QValue3DAxis *>(axisX())->positionAt(x: pos.x())
1680 * scale().x()
1681 + translate().x();
1682 float posY = static_cast<QValue3DAxis *>(axisY())->positionAt(x: pos.y())
1683 * scale().y()
1684 + translate().y();
1685 float posZ = static_cast<QValue3DAxis *>(axisZ())->positionAt(x: pos.z())
1686 * scale().z()
1687 + translate().z();
1688 return QVector3D(posX, posY, posZ);
1689 };
1690
1691 QVector3D first = normalizedPos(array.at(i: 0).position());
1692 QVector3D second = normalizedPos(array.at(i: 1).position());
1693 QVector3D pStart = first + (first - second) * 0.1f;
1694 QVector3D last = normalizedPos(array.at(i: pointCount - 1).position());
1695 QVector3D secondLast = normalizedPos(array.at(i: pointCount - 2).position());
1696 QVector3D pEnd = last + (last - secondLast) * 0.1f;
1697
1698 if (loop)
1699 splinePoints.append(t: QVector4D(last, 1));
1700 else
1701 splinePoints.append(t: QVector4D(pStart, 1));
1702
1703 const qsizetype resolution = series->splineResolution();
1704 vertices.reserve(asize: resolution * pointCount);
1705 for (int i = 0; i < pointCount; i++) {
1706 splinePoints.push_back(t: QVector4D(normalizedPos(array.at(i).position()), 1));
1707 for (int j = 0; j < resolution; j++) {
1708 SplineVertex vertex;
1709 vertex.position = QVector3D(float(j) / float(resolution), float(i), 0);
1710 vertex.uv = QVector2D(float(j) / float(resolution - 1),
1711 float(i) / float(pointCount + 2));
1712 vertices.push_back(t: vertex);
1713 }
1714 }
1715 if (loop)
1716 splinePoints.append(t: QVector4D(first, 1));
1717 else
1718 splinePoints.append(t: QVector4D(pEnd, 1));
1719
1720 QByteArray pointData = QByteArray(reinterpret_cast<char *>(splinePoints.data()),
1721 splinePoints.size() * sizeof(QVector4D));
1722
1723 splineData->setTextureData(pointData);
1724 material->setProperty(name: "points", value: splinePoints.size());
1725 QQuick3DGeometry *splineGeometry = model->splineModel->geometry();
1726 QByteArray vertexBuffer(reinterpret_cast<char *>(vertices.data()),
1727 vertices.size() * sizeof(SplineVertex));
1728 splineGeometry->setVertexData(vertexBuffer);
1729 splineGeometry->update();
1730 splineTexture->setTextureData(splineData);
1731 splineInput->setTexture(splineTexture);
1732 }
1733 model->splineModel->setVisible(true);
1734 }
1735 }
1736}
1737
1738void QQuickGraphsScatter::createSplineModel(ScatterModel *model)
1739{
1740 QQuick3DModel *splineModel = new QQuick3DModel();
1741 splineModel->setParent(model->series);
1742 splineModel->setParentItem(graphNode());
1743 splineModel->setObjectName(QStringLiteral("SplineModel"));
1744 splineModel->setVisible(true);
1745 splineModel->setPickable(false);
1746 auto geometry = new QQuick3DGeometry();
1747 geometry->setParent(splineModel);
1748 geometry->setStride(sizeof(SplineVertex)); //pos + uv
1749 geometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::LineStrip);
1750 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
1751 offset: 0,
1752 componentType: QQuick3DGeometry::Attribute::F32Type);
1753 geometry->addAttribute(semantic: QQuick3DGeometry::Attribute::TexCoord0Semantic,
1754 offset: sizeof(QVector3D),
1755 componentType: QQuick3DGeometry::Attribute::F32Type);
1756 splineModel->setGeometry(geometry);
1757
1758 QQuick3DTexture *splineTex = new QQuick3DTexture();
1759 splineTex->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
1760 splineTex->setVerticalTiling(QQuick3DTexture::ClampToEdge);
1761 splineTex->setMinFilter(QQuick3DTexture::Nearest);
1762 splineTex->setMagFilter(QQuick3DTexture::Nearest);
1763 QQuick3DTextureData *splineData = new QQuick3DTextureData;
1764 splineData->setSize(QSize(0, 1));
1765 splineData->setFormat(QQuick3DTextureData::RGBA32F);
1766 splineData->setParent(splineTex);
1767 splineData->setParentItem(splineTex);
1768 splineTex->setTextureData(splineData);
1769
1770 QQmlListReference materialRef(splineModel, "materials");
1771 QQuick3DCustomMaterial *material = createQmlCustomMaterial(
1772 QStringLiteral(":/materials/SplineMaterial"));
1773 material->setParent(splineModel);
1774 material->setParentItem(splineModel);
1775 material->setObjectName("splineMaterial");
1776 QVariant textureInputAsVariant = material->property(name: "controlPoints");
1777 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant
1778 .value<QQuick3DShaderUtilsTextureInput *>();
1779 textureInput->setTexture(splineTex);
1780 splineTex->setParent(material);
1781 materialRef.append(material);
1782
1783 model->splineModel = splineModel;
1784
1785 if (auto series = qobject_cast<QSpline3DSeries *>(object: model->series)) {
1786 connect(sender: series,
1787 signal: &QSpline3DSeries::splineTensionChanged,
1788 context: this,
1789 slot: &QQuickGraphsScatter::handleSplineChanged);
1790 connect(sender: series,
1791 signal: &QSpline3DSeries::splineKnottingChanged,
1792 context: this,
1793 slot: &QQuickGraphsScatter::handleSplineChanged);
1794 connect(sender: series,
1795 signal: &QSpline3DSeries::splineLoopingChanged,
1796 context: this,
1797 slot: &QQuickGraphsScatter::handleSplineChanged);
1798 connect(sender: series,
1799 signal: &QSpline3DSeries::splineColorChanged,
1800 context: this,
1801 slot: &QQuickGraphsScatter::handleSplineChanged);
1802 connect(sender: series,
1803 signal: &QSpline3DSeries::splineResolutionChanged,
1804 context: this,
1805 slot: &QQuickGraphsScatter::handleSplineChanged);
1806 }
1807}
1808
1809void QQuickGraphsScatter::handleSplineChanged()
1810{
1811 m_isDataDirty = true;
1812}
1813
1814QQuick3DModel *QQuickGraphsScatter::selected() const
1815{
1816 return m_selected;
1817}
1818
1819void QQuickGraphsScatter::setSelected(QQuick3DModel *newSelected)
1820{
1821 if (newSelected != m_selected) {
1822 m_previousSelected = m_selected;
1823 m_selected = newSelected;
1824
1825 auto series = static_cast<QScatter3DSeries *>(m_selected->parent());
1826
1827 // Find scattermodel
1828 ScatterModel *graphModel = nullptr;
1829
1830 for (const auto &model : std::as_const(t&: m_scatterGraphs)) {
1831 if (model->series == series) {
1832 graphModel = model;
1833 break;
1834 }
1835 }
1836
1837 if (graphModel) {
1838 qsizetype index = graphModel->dataItems.indexOf(t: m_selected);
1839 setSelectedItem(index, series);
1840 setSeriesVisualsDirty();
1841 setSelectedItemChanged(true);
1842 }
1843 }
1844}
1845
1846void QQuickGraphsScatter::setSelected(QQuick3DModel *root, qsizetype index)
1847{
1848 auto series = static_cast<QScatter3DSeries *>(root->parent());
1849 if (index != m_selectedItem || series != m_selectedItemSeries) {
1850 setSeriesVisualsDirty();
1851 setSelectedItem(index, series);
1852 setSelectedItemChanged(true);
1853 }
1854}
1855
1856void QQuickGraphsScatter::clearSelectionModel()
1857{
1858 if (optimizationHint() == QtGraphs3D::OptimizationHint::Default)
1859 clearAllSelectionInstanced();
1860
1861 setSelectedItem(index: invalidSelectionIndex(), series: nullptr);
1862
1863 if (itemLabel())
1864 itemLabel()->setVisible(false);
1865
1866 setSeriesVisualsDirty();
1867 m_selected = nullptr;
1868 m_previousSelected = nullptr;
1869}
1870
1871void QQuickGraphsScatter::clearAllSelectionInstanced()
1872{
1873 for (const auto &graph : std::as_const(t&: m_scatterGraphs)) {
1874 if (graph->instancing)
1875 graph->instancing->resetVisibilty();
1876 }
1877}
1878
1879void QQuickGraphsScatter::optimizationChanged(QtGraphs3D::OptimizationHint toOptimization)
1880{
1881 if (toOptimization == QtGraphs3D::OptimizationHint::Default) {
1882 for (const auto &graph : std::as_const(t&: m_scatterGraphs))
1883 removeDataItems(graphModel: graph, optimizationHint: QtGraphs3D::OptimizationHint::Legacy);
1884 } else {
1885 for (const auto &graph : std::as_const(t&: m_scatterGraphs))
1886 removeDataItems(graphModel: graph, optimizationHint: QtGraphs3D::OptimizationHint::Default);
1887 }
1888 setSeriesVisualsDirty();
1889}
1890
1891void QQuickGraphsScatter::updateGraph()
1892{
1893 updatePointScaleSize();
1894 if (m_optimizationChanged) {
1895 optimizationChanged(toOptimization: optimizationHint());
1896 m_optimizationChanged = false;
1897 }
1898
1899 for (auto graphModel : std::as_const(t&: m_scatterGraphs)) {
1900 bool seriesVisible = graphModel->series->isVisible();
1901 if (isDataDirty()) {
1902 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy && seriesVisible) {
1903 if (graphModel->dataItems.count() != graphModel->series->dataProxy()->itemCount()) {
1904 qsizetype sizeDiff = sizeDifference(size1: graphModel->dataItems.count(),
1905 size2: graphModel->series->dataProxy()->itemCount());
1906
1907 if (sizeDiff > 0)
1908 addPointsToScatterModel(graphModel, count: sizeDiff);
1909 else
1910 removeDataItems(items&: graphModel->dataItems, count: qAbs(t: sizeDiff));
1911 }
1912 } else if (optimizationHint() == QtGraphs3D::OptimizationHint::Default
1913 && seriesVisible) {
1914 if (graphModel->instancing == nullptr) {
1915 graphModel->instancing = new ScatterInstancing;
1916 graphModel->instancing->setParent(graphModel->series);
1917 qCDebug(lcGraphs3D) << "scatter info: "
1918 << "\n series:" << graphModel->series
1919 << "\n dataArray size:" << graphModel->series->dataArray().size()
1920 << "\n basecolor:" << graphModel->series->baseColor()
1921 << "\n customData:" << graphModel->instancing->customData()
1922 << "\n mesh:" << graphModel->series->mesh();
1923 }
1924 if (graphModel->instancingRootItem == nullptr) {
1925 graphModel->instancingRootItem = createDataItem(series: graphModel->series);
1926 graphModel->instancingRootItem->setParent(graphModel->series);
1927 graphModel->instancingRootItem->setInstancing(graphModel->instancing);
1928 if (selectionMode() != QtGraphs3D::SelectionFlag::None) {
1929 graphModel->instancingRootItem->setPickable(true);
1930 graphModel->selectionIndicator = createDataItem(series: graphModel->series);
1931 graphModel->selectionIndicator->setVisible(false);
1932 }
1933 }
1934 }
1935 }
1936
1937 if (seriesVisible && (isDataDirty() || isSeriesVisualsDirty())) {
1938 updateScatterGraphItemPositions(graphModel);
1939 updateSpline(model: graphModel);
1940 }
1941
1942 if (seriesVisible
1943 && (isSeriesVisualsDirty()
1944 || (graphModel->instancing && graphModel->instancing->isDirty()))) {
1945 updateScatterGraphItemVisuals(graphModel);
1946 }
1947
1948 const bool validSelection = (m_selectedItemSeries == graphModel->series
1949 && m_selectedItem != invalidSelectionIndex())
1950 && selectedItemInRange(graphModel);
1951
1952 if (validSelection) {
1953 QVector3D selectionPosition = {0.0f, 0.0f, 0.0f};
1954 if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) {
1955 QQuick3DModel *selectedModel = graphModel->dataItems.at(i: m_selectedItem);
1956
1957 selectionPosition = selectedModel->position();
1958 } else {
1959 selectionPosition = graphModel->instancing->dataArray().at(i: m_selectedItem).position;
1960 }
1961
1962 updateItemLabel(position: selectionPosition);
1963 QString label = m_selectedItemSeries->itemLabel();
1964 itemLabel()->setProperty(name: "labelText", value: label);
1965 if (!label.compare(s: QString(hiddenLabelTag)))
1966 itemLabel()->setVisible(false);
1967 }
1968 }
1969
1970 if (m_selectedItem == invalidSelectionIndex())
1971 itemLabel()->setVisible(false);
1972
1973 setItemSelected(m_selectedItem != invalidSelectionIndex());
1974}
1975
1976void QQuickGraphsScatter::synchData()
1977{
1978 qCDebug(lcGraphs3D, "%s start syncing", qUtf8Printable(QLatin1String(__FUNCTION__)));
1979 QList<QScatter3DSeries *> seriesList = scatterSeriesList();
1980
1981 float maxItemSize = 0.0f;
1982 for (const auto &series : std::as_const(t&: seriesList)) {
1983 if (series->isVisible()) {
1984 float itemSize = series->itemSize();
1985 if (itemSize > maxItemSize)
1986 maxItemSize = itemSize;
1987 }
1988 }
1989
1990 m_maxItemSize = maxItemSize;
1991
1992 updatePointScaleSize();
1993 QQuickGraphsItem::synchData();
1994
1995 m_pointScale = calculatePointScaleSize();
1996
1997 if (hasSelectedItemChanged()) {
1998 if (m_selectedItem != invalidSelectionIndex()) {
1999 QString itemLabelText = m_selectedItemSeries->itemLabel();
2000 itemLabel()->setProperty(name: "labelText", value: itemLabelText);
2001 if (!itemLabelText.compare(s: QString(hiddenLabelTag)))
2002 itemLabel()->setVisible(false);
2003 }
2004 setSelectedItemChanged(false);
2005 }
2006 qCDebug(lcGraphs3D, "%s end syncing", qUtf8Printable(QLatin1String(__FUNCTION__)));
2007}
2008
2009void QQuickGraphsScatter::cameraRotationChanged()
2010{
2011 m_isDataDirty = true;
2012}
2013
2014void QQuickGraphsScatter::handleOptimizationHintChange(QtGraphs3D::OptimizationHint hint)
2015{
2016 Q_UNUSED(hint)
2017 m_optimizationChanged = true;
2018}
2019
2020void QQuickGraphsScatter::handleSeriesVisibilityChangedBySender(QObject *sender)
2021{
2022 QQuickGraphsItem::handleSeriesVisibilityChangedBySender(sender);
2023 auto series = static_cast<QScatter3DSeries *>(sender);
2024 if (optimizationHint() == QtGraphs3D::OptimizationHint::Default) {
2025 ScatterModel *graphModel = findGraphModel(series);
2026 if (graphModel && graphModel->series == series) {
2027 if (graphModel->instancingRootItem)
2028 graphModel->instancingRootItem->setVisible(series->isVisible());
2029 if (series == m_selectedItemSeries) {
2030 itemLabel()->setVisible(series->isVisible());
2031 graphModel->selectionIndicator->setVisible(series->isVisible());
2032 }
2033 }
2034 }
2035}
2036
2037bool QQuickGraphsScatter::selectedItemInRange(const ScatterModel *graphModel)
2038{
2039 qsizetype itemCount;
2040 if (optimizationHint() == QtGraphs3D::OptimizationHint::Default)
2041 itemCount = graphModel->instancing->dataArray().count();
2042 else
2043 itemCount = graphModel->dataItems.count();
2044
2045 return m_selectedItem >= 0 && m_selectedItem < itemCount;
2046}
2047
2048QQuickGraphsScatter::ScatterModel *QQuickGraphsScatter::findGraphModel(QScatter3DSeries *series)
2049{
2050 for (const auto &graphModel : std::as_const(t&: m_scatterGraphs)) {
2051 if (graphModel->series == series)
2052 return graphModel;
2053 }
2054 return nullptr;
2055}
2056QT_END_NAMESPACE
2057

source code of qtgraphs/src/graphs3d/qml/qquickgraphsscatter.cpp