1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qml/qquickgraphsscatter_p.h"
5#include "qml/declarativescene_p.h"
6#include "data/qscatter3dseries_p.h"
7#include "qvalue3daxis_p.h"
8#include "qcategory3daxis_p.h"
9#include "axis/qvalue3daxisformatter_p.h"
10#include "engine/q3dcamera_p.h"
11#include "quickgraphstexturedata_p.h"
12
13#include <QtCore/QMutexLocker>
14#include <QColor>
15#include <QtQuick3D/private/qquick3drepeater_p.h>
16#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
17#include <QtQuick3D/private/qquick3dperspectivecamera_p.h>
18#include <QtQuick3D/private/qquick3dmodel_p.h>
19#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
20#include <QtQuick3D/private/qquick3ddirectionallight_p.h>
21#include <QtQuick3D/private/qquick3dpointlight_p.h>
22
23QT_BEGIN_NAMESPACE
24
25QQuickGraphsScatter::QQuickGraphsScatter(QQuickItem *parent)
26 : QQuickGraphsItem(parent),
27 m_scatterController(0)
28{
29 setAcceptedMouseButtons(Qt::AllButtons);
30 setFlag(flag: ItemHasContents);
31 // Create the shared component on the main GUI thread.
32 m_scatterController = new Scatter3DController(boundingRect().toRect(), new Declarative3DScene);
33
34 setSharedController(m_scatterController);
35
36 QObject::connect(sender: m_scatterController, signal: &Scatter3DController::selectedSeriesChanged,
37 context: this, slot: &QQuickGraphsScatter::selectedSeriesChanged);
38}
39
40QQuickGraphsScatter::~QQuickGraphsScatter()
41{
42 QMutexLocker locker(m_nodeMutex.data());
43 const QMutexLocker locker2(mutex());
44
45 for (auto &graphModel : m_scatterGraphs) {
46 delete graphModel;
47 }
48
49 delete m_scatterController;
50}
51
52QValue3DAxis *QQuickGraphsScatter::axisX() const
53{
54 return static_cast<QValue3DAxis *>(m_scatterController->axisX());
55}
56
57void QQuickGraphsScatter::setAxisX(QValue3DAxis *axis)
58{
59 m_scatterController->setAxisX(axis);
60}
61
62QValue3DAxis *QQuickGraphsScatter::axisY() const
63{
64 return static_cast<QValue3DAxis *>(m_scatterController->axisY());
65}
66
67void QQuickGraphsScatter::setAxisY(QValue3DAxis *axis)
68{
69 m_scatterController->setAxisY(axis);
70}
71
72QValue3DAxis *QQuickGraphsScatter::axisZ() const
73{
74 return static_cast<QValue3DAxis *>(m_scatterController->axisZ());
75}
76
77void QQuickGraphsScatter::setAxisZ(QValue3DAxis *axis)
78{
79 m_scatterController->setAxisZ(axis);
80}
81
82void QQuickGraphsScatter::disconnectSeries(QScatter3DSeries *series)
83{
84 QObject::disconnect(sender: series, signal: 0, receiver: this, member: 0);
85}
86
87void QQuickGraphsScatter::generatePointsForScatterModel(ScatterModel *graphModel)
88{
89 QList<QQuick3DModel *> itemList;
90 if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationLegacy) {
91 int itemCount = graphModel->series->dataProxy()->itemCount();
92 if (graphModel->series->dataProxy()->itemCount() > 0)
93 itemList.resize(size: itemCount);
94
95 for (int i = 0; i < itemCount; i++) {
96 QQuick3DModel *item = createDataItem(series: graphModel->series);
97 item->setPickable(true);
98 item->setParent(graphModel->series);
99 itemList[i] = item;
100 }
101 graphModel->dataItems = itemList;
102 m_scatterController->markDataDirty();
103 } else if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
104 graphModel->instancingRootItem = createDataItem(series: graphModel->series);
105 graphModel->instancingRootItem->setParent(graphModel->series);
106 graphModel->instancingRootItem->setInstancing(graphModel->instancing);
107 if (m_scatterController->selectionMode() != QAbstract3DGraph::SelectionNone) {
108 graphModel->selectionIndicator = createDataItem(series: graphModel->series);
109 graphModel->instancingRootItem->setPickable(true);
110 }
111 }
112 m_scatterController->markSeriesVisualsDirty();
113}
114
115qsizetype QQuickGraphsScatter::getItemIndex(QQuick3DModel *item)
116{
117 Q_UNUSED(item);
118 if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationLegacy)
119 return 0;
120
121 return -1;
122}
123
124void QQuickGraphsScatter::updateScatterGraphItemPositions(ScatterModel *graphModel)
125{
126 float itemSize = graphModel->series->itemSize() / m_itemScaler;
127 QQuaternion meshRotation = graphModel->series->meshRotation();
128 QScatterDataProxy *dataProxy = graphModel->series->dataProxy();
129 QList<QQuick3DModel *> itemList = graphModel->dataItems;
130
131 if (itemSize == 0.0f)
132 itemSize = m_pointScale;
133
134 if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationLegacy) {
135 if (dataProxy->itemCount() != itemList.size())
136 qWarning() << __func__ << "Item count differs from itemList count";
137
138 for (int i = 0; i < dataProxy->itemCount(); ++i) {
139 const QScatterDataItem *item = dataProxy->itemAt(index: i);
140 QQuick3DModel *dataPoint = itemList.at(i);
141
142 QVector3D dotPos = item->position();
143 if (isDotPositionInAxisRange(dotPos)) {
144 dataPoint->setVisible(true);
145 QQuaternion dotRot = item->rotation();
146 float posX = axisX()->positionAt(x: dotPos.x()) * scale().x() + translate().x();
147 float posY = axisY()->positionAt(x: dotPos.y()) * scale().y() + translate().y();
148 float posZ = axisZ()->positionAt(x: dotPos.z()) * scale().z() + translate().z();
149 dataPoint->setPosition(QVector3D(posX, posY, posZ));
150 QQuaternion totalRotation;
151
152 if (graphModel->series->mesh() != QAbstract3DSeries::MeshPoint)
153 totalRotation = dotRot * meshRotation;
154 else
155 totalRotation = cameraTarget()->rotation();
156
157 dataPoint->setRotation(totalRotation);
158 dataPoint->setScale(QVector3D(itemSize, itemSize, itemSize));
159 } else {
160 dataPoint->setVisible(false);
161 }
162 }
163 } else if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
164 int count = dataProxy->itemCount();
165 QList<DataItemHolder> positions;
166
167 for (int i = 0; i < count; i++) {
168 auto item = dataProxy->itemAt(index: i);
169 auto dotPos = item->position();
170
171 if (isDotPositionInAxisRange(dotPos)) {
172 auto posX = axisX()->positionAt(x: dotPos.x()) * scale().x() + translate().x();
173 auto posY = axisY()->positionAt(x: dotPos.y()) * scale().y() + translate().y();
174 auto posZ = axisZ()->positionAt(x: dotPos.z()) * scale().z() + translate().z();
175
176 QQuaternion totalRotation;
177 if (graphModel->series->mesh() != QAbstract3DSeries::MeshPoint)
178 totalRotation = item->rotation() * meshRotation;
179 else
180 totalRotation = cameraTarget()->rotation();
181
182 DataItemHolder dih;
183 dih.position = {posX, posY, posZ};
184 dih.rotation = totalRotation;
185 dih.scale = {itemSize, itemSize, itemSize};
186
187 positions.push_back(t: dih);
188 }
189 }
190 graphModel->instancing->setDataArray(positions);
191 }
192}
193
194void QQuickGraphsScatter::updateScatterGraphItemVisuals(ScatterModel *graphModel)
195{
196 bool useGradient = graphModel->series->d_func()->isUsingGradient();
197 bool usePoint = graphModel->series->mesh() == QAbstract3DSeries::MeshPoint;
198 int itemCount = graphModel->series->dataProxy()->itemCount();
199
200 if (useGradient) {
201 if (!graphModel->seriesTexture) {
202 graphModel->seriesTexture = createTexture();
203 graphModel->seriesTexture->setParent(graphModel->series);
204 }
205
206 QLinearGradient gradient = graphModel->series->baseGradient();
207 auto textureData = static_cast<QuickGraphsTextureData *>(
208 graphModel->seriesTexture->textureData());
209 textureData->createGradient(gradient);
210
211 if (!graphModel->highlightTexture) {
212 graphModel->highlightTexture = createTexture();
213 graphModel->highlightTexture->setParent(graphModel->series);
214 }
215
216 QLinearGradient highlightGradient = graphModel->series->singleHighlightGradient();
217 auto highlightTextureData = static_cast<QuickGraphsTextureData *>(
218 graphModel->highlightTexture->textureData());
219 highlightTextureData->createGradient(gradient: highlightGradient);
220 } else {
221 if (graphModel->seriesTexture) {
222 graphModel->seriesTexture->deleteLater();
223 graphModel->seriesTexture = nullptr;
224 }
225
226 if (graphModel->highlightTexture) {
227 graphModel->highlightTexture->deleteLater();
228 graphModel->highlightTexture = nullptr;
229 }
230 }
231
232 bool rangeGradient = (useGradient && graphModel->series->d_func()->m_colorStyle
233 == Q3DTheme::ColorStyleRangeGradient) ? true : false;
234
235 if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationLegacy) {
236
237 if (itemCount != graphModel->dataItems.size())
238 qWarning() << __func__ << "Item count differs from itemList count";
239
240 if (!rangeGradient) {
241 if (!usePoint) {
242 for (const auto &obj : std::as_const(t&: graphModel->dataItems)) {
243 updateItemMaterial(item: obj, useGradient, rangeGradient,
244 QStringLiteral(":/materials/ObjectGradientMaterial"));
245
246 updatePrincipledMaterial(model: obj, color: graphModel->series->baseColor(),
247 useGradient, texture: graphModel->seriesTexture);
248 }
249 if (m_scatterController->m_selectedItem != invalidSelectionIndex()
250 && graphModel->series == m_scatterController->m_selectedItemSeries) {
251 QQuick3DModel *selectedItem = graphModel->dataItems.at(i: m_scatterController->m_selectedItem);
252 updatePrincipledMaterial(model: selectedItem, color: graphModel->series->singleHighlightColor(),
253 useGradient, texture: graphModel->highlightTexture);
254 }
255 } else {
256 for (const auto &obj : std::as_const(t&: graphModel->dataItems)) {
257 updatePointItemMaterial(item: obj, QStringLiteral(":/materials/PointMaterial"),
258 QStringLiteral("pointmaterial"));
259 // Update point material
260 QQmlListReference materialsRef(obj, "materials");
261 auto pointMaterial = qobject_cast<QQuick3DCustomMaterial *>(object: materialsRef.at(0));
262 pointMaterial->setProperty(name: "uColor", value: graphModel->series->baseColor());
263 }
264 if (m_scatterController->m_selectedItem != invalidSelectionIndex()
265 && graphModel->series == m_scatterController->m_selectedItemSeries) {
266 QQuick3DModel *selectedItem = graphModel->dataItems.at(i: m_scatterController->m_selectedItem);
267 QQmlListReference materialsRef(selectedItem, "materials");
268 auto pointMaterial = qobject_cast<QQuick3DCustomMaterial *>(object: materialsRef.at(0));
269 pointMaterial->setProperty(name: "uColor", value: graphModel->series->singleHighlightColor());
270 }
271 }
272 } else {
273 if (!usePoint) {
274 for (const auto &obj : std::as_const(t&: graphModel->dataItems)) {
275 updateItemMaterial(item: obj, useGradient, rangeGradient,
276 QStringLiteral(":/materials/RangeGradientScatterMaterial"));
277 updateCustomMaterial(item: obj, texture: graphModel->seriesTexture);
278 }
279
280 if (m_scatterController->m_selectedItem != -1) {
281 QQuick3DModel *obj = graphModel->dataItems.at(i: m_scatterController->m_selectedItem);
282
283 updateCustomMaterial(item: obj, texture: graphModel->highlightTexture);
284 }
285 } else {
286 for (const auto &obj : std::as_const(t&: graphModel->dataItems)) {
287 updatePointItemMaterial(item: obj,
288 QStringLiteral(":/materials/PointRangeGradientMaterial"),
289 QStringLiteral("pointrangegradientmaterial"));
290 // Update point material
291 updateCustomMaterial(item: obj, texture: graphModel->seriesTexture);
292 }
293
294 if (m_scatterController->m_selectedItem != invalidSelectionIndex()
295 && graphModel->series == m_scatterController->selectedSeries()) {
296 QQuick3DModel *selectedItem = graphModel->dataItems.at(i: m_scatterController->m_selectedItem);
297 updateCustomMaterial(item: selectedItem, texture: graphModel->highlightTexture);
298 }
299 }
300 }
301 } else if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
302 graphModel->instancing->setRangeGradient(rangeGradient);
303 if (!rangeGradient) {
304 if (!usePoint) {
305 updateItemMaterial(item: graphModel->instancingRootItem, useGradient, rangeGradient,
306 QStringLiteral(":/materials/ObjectGradientMaterialInstancing"));
307 updatePrincipledMaterial(model: graphModel->instancingRootItem, color: graphModel->series->baseColor(),
308 useGradient, texture: graphModel->seriesTexture);
309 } else {
310 QQuick3DModel *obj = graphModel->instancingRootItem;
311 updateInstancedPointItemMaterial(item: obj,
312 QStringLiteral(":/materials/PointMaterialInstancing"),
313 QStringLiteral("pointmaterialinstancing"));
314 QQmlListReference materialsRef(obj, "materials");
315 QQuick3DMaterial *pointInstancingMaterial = qobject_cast<QQuick3DCustomMaterial *> (
316 object: materialsRef.at(0));
317 pointInstancingMaterial->setProperty(name: "uColor", value: graphModel->series->baseColor());
318 }
319 } else {
320 if (!usePoint) {
321 updateItemMaterial(item: graphModel->instancingRootItem, useGradient, rangeGradient,
322 QStringLiteral(":/materials/RangeGradientMaterialInstancing"));
323 float rangeGradientYScaler = m_rangeGradientYHelper / m_scaleY;
324
325 updateInstancedCustomMaterial(graphModel, isHighlight: false, seriesTexture: graphModel->seriesTexture);
326
327 QList<float> customData;
328 customData.resize(size: itemCount);
329
330 QList<DataItemHolder> instancingData = graphModel->instancing->dataArray();
331 for (int i = 0; i < instancingData.size(); i++) {
332 auto dih = instancingData.at(i);
333 float value = (dih.position.y() + m_scaleY) * rangeGradientYScaler;
334 customData[i] = value;
335 }
336 graphModel->instancing->setCustomData(customData);
337 } else {
338 QQuick3DModel *obj = graphModel->instancingRootItem;
339 updateInstancedPointItemMaterial(item: obj,
340 QStringLiteral(":/materials/PointRangeGradientMaterialInstancing"),
341 QStringLiteral("pointrangegradientinstancingmaterial"));
342 updateInstancedCustomMaterial(graphModel, isHighlight: false, seriesTexture: graphModel->seriesTexture,
343 highlightTexture: graphModel->highlightTexture);
344
345 float rangeGradientYScaler = m_rangeGradientYHelper / m_scaleY;
346
347 QList<float> customData;
348 customData.resize(size: itemCount);
349
350 QList<DataItemHolder> instancingData = graphModel->instancing->dataArray();
351 for (int i = 0; i < instancingData.size(); i++) {
352 auto dih = instancingData.at(i);
353 float value = (dih.position.y() + m_scaleY) * rangeGradientYScaler;
354 customData[i] = value;
355 }
356 graphModel->instancing->setCustomData(customData);
357 }
358 }
359
360 if ((m_scatterController->m_selectedItem != -1
361 && m_scatterController->m_selectedItemSeries == graphModel->series)
362 && !m_selectionActive) {
363 // Selection indicator
364 if (!rangeGradient) {
365 if (!usePoint) {
366 updateItemMaterial(item: graphModel->selectionIndicator, useGradient, rangeGradient,
367 QStringLiteral(":/materials/ObjectGradientMaterial"));
368 updatePrincipledMaterial(model: graphModel->selectionIndicator,
369 color: graphModel->series->singleHighlightColor(),
370 useGradient, texture: graphModel->highlightTexture);
371 } else {
372 graphModel->selectionIndicator->setCastsShadows(false);
373 updatePointItemMaterial(item: graphModel->selectionIndicator,
374 QStringLiteral(":/materials/PointMaterial"),
375 QStringLiteral("pointmaterial"));
376 QQmlListReference materialsRef(graphModel->selectionIndicator, "materials");
377 auto pointMaterial = qobject_cast<QQuick3DCustomMaterial *>(object: materialsRef.at(0));
378 pointMaterial->setProperty(name: "uColor", value: graphModel->series->singleHighlightColor());
379 }
380 } else {
381 // Rangegradient
382 if (!usePoint) {
383 updateItemMaterial(item: graphModel->selectionIndicator, useGradient, rangeGradient,
384 QStringLiteral(":/materials/RangeGradientMaterial"));
385 updateInstancedCustomMaterial(graphModel, isHighlight: true, seriesTexture: nullptr, highlightTexture: graphModel->highlightTexture);
386 } else {
387 graphModel->selectionIndicator->setCastsShadows(false);
388 updatePointItemMaterial(item: graphModel->selectionIndicator,
389 QStringLiteral(":/materials/PointRangeGradientMaterial"),
390 QStringLiteral("pointrangegradientmaterial"));
391 // Update point material
392 updateInstancedCustomMaterial(graphModel, isHighlight: true, seriesTexture: nullptr, highlightTexture: graphModel->highlightTexture);
393 }
394 }
395
396 const DataItemHolder &dih = graphModel->instancing->dataArray().at(i: m_scatterController->m_selectedItem);
397
398 graphModel->selectionIndicator->setPosition(dih.position);
399 graphModel->selectionIndicator->setRotation(dih.rotation);
400 graphModel->selectionIndicator->setScale(dih.scale);
401 graphModel->selectionIndicator->setVisible(true);
402 graphModel->instancing->hideDataItem(index: m_scatterController->m_selectedItem);
403 updateItemLabel(position: graphModel->selectionIndicator->position());
404 m_selectionActive = true;
405 graphModel->instancing->markDataDirty();
406 } else if ((m_scatterController->m_selectedItem == -1
407 || m_scatterController->m_selectedItemSeries != graphModel->series)
408 && graphModel->selectionIndicator) {
409 graphModel->selectionIndicator->setVisible(false);
410 }
411 }
412}
413
414void QQuickGraphsScatter::updateItemMaterial(QQuick3DModel *item, bool useGradient,
415 bool rangeGradient, const QString &materialName)
416{
417 QQmlListReference materialsRef(item, "materials");
418 if (!rangeGradient) {
419 if (materialsRef.size()) {
420 QObject *material = materialsRef.at(0);
421 if (useGradient && !material->objectName().contains(QStringLiteral("objectgradient"))) {
422 // The item has an existing material which is principled or range gradient.
423 // The item needs an object gradient material.
424 QQuick3DCustomMaterial *objectGradientMaterial = createQmlCustomMaterial(
425 fileName: materialName);
426 objectGradientMaterial->setParent(item);
427 QObject *oldMaterial = materialsRef.at(0);
428 materialsRef.replace(0, objectGradientMaterial);
429 objectGradientMaterial->setObjectName("objectgradient");
430 delete oldMaterial;
431 } else if (!useGradient && !qobject_cast<QQuick3DPrincipledMaterial *>(object: material)) {
432 // The item has an existing material which is object gradient or range gradient.
433 // The item needs a principled material for uniform color.
434 auto principledMaterial = new QQuick3DPrincipledMaterial();
435 principledMaterial->setParent(item);
436 QObject *oldCustomMaterial = materialsRef.at(0);
437 materialsRef.replace(0, principledMaterial);
438 delete oldCustomMaterial;
439 }
440 } else {
441 if (useGradient) {
442 // The item needs object gradient material.
443 QQuick3DCustomMaterial *objectGradientMaterial = createQmlCustomMaterial(
444 fileName: materialName);
445 objectGradientMaterial->setParent(item);
446 materialsRef.append(objectGradientMaterial);
447 objectGradientMaterial->setObjectName("objectgradient");
448 } else {
449 // The item needs a principled material.
450 auto principledMaterial = new QQuick3DPrincipledMaterial();
451 principledMaterial->setParent(item);
452 materialsRef.append(principledMaterial);
453 }
454 }
455 } else {
456 if (materialsRef.size()) {
457 QObject *material = materialsRef.at(0);
458 if (!qobject_cast<QQuick3DCustomMaterial *>(object: material)
459 || material->objectName().contains(QStringLiteral("objectgradient"))) {
460 // The item has an existing material which is principled or object gradient.
461 // The item needs a range gradient material.
462 QQuick3DCustomMaterial *customMaterial = createQmlCustomMaterial(
463 fileName: materialName);
464 customMaterial->setParent(item);
465 QObject *oldPrincipledMaterial = materialsRef.at(0);
466 materialsRef.replace(0, customMaterial);
467 delete oldPrincipledMaterial;
468 }
469 } else {
470 // The item needs a range gradient material.
471 QQuick3DCustomMaterial *customMaterial = createQmlCustomMaterial(
472 fileName: materialName);
473 customMaterial->setParent(item);
474 materialsRef.append(customMaterial);
475 }
476 }
477}
478
479void QQuickGraphsScatter::updatePointItemMaterial(QQuick3DModel *item,
480 const QString &materialName,
481 const QString &objectName)
482{
483 QQmlListReference materialsRef(item, "materials");
484 if (materialsRef.size()) {
485 QObject *material = materialsRef.at(0);
486 if (!material->objectName().contains(s: objectName)) {
487 QQuick3DCustomMaterial *pointMaterial = createQmlCustomMaterial(fileName: materialName);
488 pointMaterial->setParent(item);
489 QObject *oldMaterial = materialsRef.at(0);
490 materialsRef.replace(0, pointMaterial);
491 pointMaterial->setObjectName(objectName);
492 delete oldMaterial;
493 }
494 } else {
495 QQuick3DCustomMaterial *pointMaterial = createQmlCustomMaterial(fileName: materialName);
496 pointMaterial->setParent(item);
497 pointMaterial->setObjectName(objectName);
498 materialsRef.append(pointMaterial);
499 }
500}
501
502void QQuickGraphsScatter::updateInstancedPointItemMaterial(QQuick3DModel *item,
503 const QString &materialName,
504 const QString &objectName)
505{
506 QQmlListReference materialsRef(item, "materials");
507 if (materialsRef.size()) {
508 QObject *material = materialsRef.at(0);
509 if (!material->objectName().contains(s: objectName)) {
510 QQuick3DCustomMaterial *pointMaterial = createQmlCustomMaterial(fileName: materialName);
511 pointMaterial->setParent(item);
512 QObject *oldMaterial = materialsRef.at(0);
513 materialsRef.replace(0, pointMaterial);
514 pointMaterial->setObjectName(objectName);
515 delete oldMaterial;
516 }
517 } else {
518 QQuick3DCustomMaterial *pointMaterial = createQmlCustomMaterial(fileName: materialName);
519 pointMaterial->setParent(item);
520 pointMaterial->setObjectName(objectName);
521 materialsRef.append(pointMaterial);
522 }
523}
524
525void QQuickGraphsScatter::updateInstancedCustomMaterial(ScatterModel *graphModel, bool isHighlight,
526 QQuick3DTexture *seriesTexture,
527 QQuick3DTexture *highlightTexture)
528{
529 QQuick3DModel *model = nullptr;
530 if (isHighlight)
531 model = graphModel->selectionIndicator;
532 else
533 model = graphModel->instancingRootItem;
534
535 QQmlListReference materialsRef(model, "materials");
536
537 auto customMaterial = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
538
539 QVariant textureInputAsVariant = customMaterial->property(name: "custex");
540 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
541
542 if (isHighlight) {
543 textureInput->setTexture(highlightTexture);
544
545 if ((m_scatterController->m_selectedItem != -1
546 && m_scatterController->m_selectedItemSeries == graphModel->series)
547 && !m_selectionActive) {
548 m_selectedGradientPos = graphModel->instancing->customData().at(
549 i: m_scatterController->m_selectedItem);
550 }
551
552 customMaterial->setProperty(name: "gradientPos", value: m_selectedGradientPos);
553 } else {
554 textureInput->setTexture(seriesTexture);
555 }
556}
557
558void QQuickGraphsScatter::updateCustomMaterial(QQuick3DModel *item, QQuick3DTexture *texture)
559{
560 QQmlListReference materialsRef(item, "materials");
561 auto customMaterial = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
562 QVariant textureInputAsVariant = customMaterial->property(name: "custex");
563 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
564
565 textureInput->setTexture(texture);
566
567 float rangeGradientYScaler = m_rangeGradientYHelper / m_scaleY;
568 float value = (item->y() + m_scaleY) * rangeGradientYScaler;
569 customMaterial->setProperty(name: "gradientPos", value);
570}
571
572void QQuickGraphsScatter::updatePrincipledMaterial(QQuick3DModel *model, const QColor &color,
573 bool useGradient, QQuick3DTexture *texture)
574{
575 QQmlListReference materialsRef(model, "materials");
576
577 if (useGradient) {
578 auto objectGradientMaterial = qobject_cast<QQuick3DCustomMaterial *>(object: materialsRef.at(0));
579 QVariant textureInputAsVariant = objectGradientMaterial->property(name: "custex");
580 QQuick3DShaderUtilsTextureInput *textureInput = textureInputAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
581
582 textureInput->setTexture(texture);
583 } else {
584 auto principledMaterial = static_cast<QQuick3DPrincipledMaterial *>(materialsRef.at(0));
585 principledMaterial->setBaseColor(color);
586 }
587}
588
589QQuick3DTexture *QQuickGraphsScatter::createTexture()
590{
591 QQuick3DTexture *texture = new QQuick3DTexture();
592 texture->setParent(this);
593 texture->setRotationUV(-90.0f);
594 texture->setHorizontalTiling(QQuick3DTexture::ClampToEdge);
595 texture->setVerticalTiling(QQuick3DTexture::ClampToEdge);
596 QuickGraphsTextureData *textureData = new QuickGraphsTextureData();
597 textureData->setParent(texture);
598 textureData->setParentItem(texture);
599 texture->setTextureData(textureData);
600
601 return texture;
602}
603
604QQuick3DNode *QQuickGraphsScatter::createSeriesRoot()
605{
606 auto model = new QQuick3DNode();
607
608 model->setParentItem(QQuick3DViewport::scene());
609 return model;
610}
611
612QQuick3DModel *QQuickGraphsScatter::createDataItem(QAbstract3DSeries *series)
613{
614 auto model = new QQuick3DModel();
615 model->setParent(this);
616 model->setParentItem(QQuick3DViewport::scene());
617 QString fileName = getMeshFileName(meshType: series->mesh());
618 if (fileName.isEmpty())
619 fileName = series->userDefinedMesh();
620
621 model->setSource(QUrl(fileName));
622 return model;
623}
624
625void QQuickGraphsScatter::removeDataItems(ScatterModel *graphModel)
626{
627 if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
628 delete graphModel->instancing;
629 graphModel->instancing = nullptr;
630 deleteDataItem(item: graphModel->instancingRootItem);
631 deleteDataItem(item: graphModel->selectionIndicator);
632
633 graphModel->instancingRootItem = nullptr;
634 graphModel->selectionIndicator = nullptr;
635 } else {
636 QList<QQuick3DModel *> &items = graphModel->dataItems;
637 removeDataItems(items, count: items.count());
638 }
639}
640
641void QQuickGraphsScatter::removeDataItems(QList<QQuick3DModel *> &items, qsizetype count)
642{
643 for (int i = 0; i < count; ++i) {
644 QQuick3DModel *item = items.takeLast();
645 QQmlListReference materialsRef(item, "materials");
646 if (materialsRef.size()) {
647 QObject *material = materialsRef.at(0);
648 delete material;
649 }
650 item->deleteLater();
651 }
652}
653
654void QQuickGraphsScatter::recreateDataItems()
655{
656 if (!isComponentComplete())
657 return;
658 QList<QScatter3DSeries *> seriesList = m_scatterController->scatterSeriesList();
659 for (auto series : seriesList) {
660 for (const auto &model : std::as_const(t&: m_scatterGraphs)) {
661 if (model->series == series)
662 removeDataItems(graphModel: model);
663 }
664 }
665 m_scatterController->markDataDirty();
666}
667
668void QQuickGraphsScatter::recreateDataItems(const QList<ScatterModel *> &graphs)
669{
670 if (!isComponentComplete())
671 return;
672 QList<QScatter3DSeries *> seriesList = m_scatterController->scatterSeriesList();
673 for (auto series : seriesList) {
674 for (const auto &model : graphs) {
675 if (model->series == series)
676 removeDataItems(graphModel: model);
677 }
678 }
679 m_scatterController->markDataDirty();
680}
681
682void QQuickGraphsScatter::addPointsToScatterModel(ScatterModel *graphModel, qsizetype count)
683{
684 for (int i = 0; i < count; i++) {
685 QQuick3DModel *item = createDataItem(series: graphModel->series);
686 item->setPickable(true);
687 item->setParent(graphModel->series);
688 graphModel->dataItems.push_back(t: item);
689 }
690 m_scatterController->setSeriesVisualsDirty();
691}
692
693int QQuickGraphsScatter::sizeDifference(qsizetype size1, qsizetype size2)
694{
695 return size2 - size1;
696}
697
698QVector3D QQuickGraphsScatter::selectedItemPosition()
699{
700 QVector3D position;
701 if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationLegacy)
702 position = {0.0f, 0.0f, 0.0f};
703 else if (m_scatterController->optimizationHints() == QAbstract3DGraph::OptimizationDefault)
704 position = {0.0f, 0.0f, 0.0f};
705
706 return position;
707}
708
709void QQuickGraphsScatter::fixMeshFileName(QString &fileName, QAbstract3DSeries::Mesh meshType)
710{
711 // Should it be smooth?
712 if (m_smooth && meshType != QAbstract3DSeries::MeshPoint
713 && meshType != QAbstract3DSeries::MeshUserDefined) {
714 fileName += QStringLiteral("Smooth");
715 }
716
717 // Should it be filled?
718 if (meshType != QAbstract3DSeries::MeshSphere && meshType != QAbstract3DSeries::MeshArrow
719 && meshType != QAbstract3DSeries::MeshMinimal
720 && meshType != QAbstract3DSeries::MeshPoint
721 && meshType != QAbstract3DSeries::MeshUserDefined) {
722 fileName.append(QStringLiteral("Full"));
723 }
724}
725
726QString QQuickGraphsScatter::getMeshFileName(QAbstract3DSeries::Mesh meshType)
727{
728 QString fileName = {};
729 switch (meshType) {
730 case QAbstract3DSeries::MeshSphere:
731 fileName = QStringLiteral("defaultMeshes/sphereMesh");
732 break;
733 case QAbstract3DSeries::MeshBar:
734 case QAbstract3DSeries::MeshCube:
735 fileName = QStringLiteral("defaultMeshes/barMesh");
736 break;
737 case QAbstract3DSeries::MeshPyramid:
738 fileName = QStringLiteral("defaultMeshes/pyramidMesh");
739 break;
740 case QAbstract3DSeries::MeshCone:
741 fileName = QStringLiteral("defaultMeshes/coneMesh");
742 break;
743 case QAbstract3DSeries::MeshCylinder:
744 fileName = QStringLiteral("defaultMeshes/cylinderMesh");
745 break;
746 case QAbstract3DSeries::MeshBevelBar:
747 case QAbstract3DSeries::MeshBevelCube:
748 fileName = QStringLiteral("defaultMeshes/bevelBarMesh");
749 break;
750 case QAbstract3DSeries::MeshMinimal:
751 fileName = QStringLiteral("defaultMeshes/minimalMesh");
752 break;
753 case QAbstract3DSeries::MeshArrow:
754 fileName = QStringLiteral("defaultMeshes/arrowMesh");
755 break;
756 case QAbstract3DSeries::MeshPoint:
757 fileName = shadowQuality() == QAbstract3DGraph::ShadowQualityNone
758 ? QStringLiteral("defaultMeshes/planeMesh")
759 : QStringLiteral("defaultMeshes/octagonMesh");
760 break;
761 case QAbstract3DSeries::MeshUserDefined:
762 break;
763 default:
764 fileName = QStringLiteral("defaultMeshes/sphereMesh");
765 }
766
767 fixMeshFileName(fileName, meshType);
768
769 return fileName;
770}
771
772void QQuickGraphsScatter::deleteDataItem(QQuick3DModel *item)
773{
774 QQmlListReference materialsRef(item, "materials");
775 if (materialsRef.size()) {
776 QObject *material = materialsRef.at(0);
777 delete material;
778 }
779 item->deleteLater();
780 item = nullptr;
781}
782
783void QQuickGraphsScatter::handleSeriesChanged(QList<QAbstract3DSeries *> changedSeries)
784{
785 Q_UNUSED(changedSeries)
786 // TODO: generate items and remove old items
787}
788
789bool QQuickGraphsScatter::isDotPositionInAxisRange(const QVector3D &dotPos)
790{
791 return ((dotPos.x() >= axisX()->min() && dotPos.x() <= axisX()->max())
792 && (dotPos.y() >= axisY()->min() && dotPos.y() <= axisY()->max())
793 && (dotPos.z() >= axisZ()->min() && dotPos.z() <= axisZ()->max()));
794}
795
796QScatter3DSeries *QQuickGraphsScatter::selectedSeries() const
797{
798 return m_scatterController->selectedSeries();
799}
800
801void QQuickGraphsScatter::setSelectedItem(int index, QScatter3DSeries *series)
802{
803 m_scatterController->setSelectedItem(index, series);
804
805 if (index != invalidSelectionIndex())
806 itemLabel()->setVisible(true);
807}
808
809QQmlListProperty<QScatter3DSeries> QQuickGraphsScatter::seriesList()
810{
811 return QQmlListProperty<QScatter3DSeries>(this, this,
812 &QQuickGraphsScatter::appendSeriesFunc,
813 &QQuickGraphsScatter::countSeriesFunc,
814 &QQuickGraphsScatter::atSeriesFunc,
815 &QQuickGraphsScatter::clearSeriesFunc);
816}
817
818void QQuickGraphsScatter::appendSeriesFunc(QQmlListProperty<QScatter3DSeries> *list,
819 QScatter3DSeries *series)
820{
821 reinterpret_cast<QQuickGraphsScatter *>(list->data)->addSeries(series);
822}
823
824qsizetype QQuickGraphsScatter::countSeriesFunc(QQmlListProperty<QScatter3DSeries> *list)
825{
826 return reinterpret_cast<QQuickGraphsScatter *>(list->data)->m_scatterController->scatterSeriesList().size();
827}
828
829QScatter3DSeries *QQuickGraphsScatter::atSeriesFunc(QQmlListProperty<QScatter3DSeries> *list,
830 qsizetype index)
831{
832 return reinterpret_cast<QQuickGraphsScatter *>(list->data)->m_scatterController->scatterSeriesList().at(i: index);
833}
834
835void QQuickGraphsScatter::clearSeriesFunc(QQmlListProperty<QScatter3DSeries> *list)
836{
837 QQuickGraphsScatter *declScatter = reinterpret_cast<QQuickGraphsScatter *>(list->data);
838 QList<QScatter3DSeries *> realList = declScatter->m_scatterController->scatterSeriesList();
839 int count = realList.size();
840 for (int i = 0; i < count; i++)
841 declScatter->removeSeries(series: realList.at(i));
842}
843
844void QQuickGraphsScatter::addSeries(QScatter3DSeries *series)
845{
846 m_scatterController->addSeries(series);
847
848 auto graphModel = new ScatterModel;
849 graphModel->series = series;
850 graphModel->seriesTexture = nullptr;
851 graphModel->highlightTexture = nullptr;
852 m_scatterGraphs.push_back(t: graphModel);
853
854 connectSeries(series);
855
856 if (series->selectedItem() != invalidSelectionIndex())
857 setSelectedItem(index: series->selectedItem(), series);
858}
859
860void QQuickGraphsScatter::removeSeries(QScatter3DSeries *series)
861{
862 m_scatterController->removeSeries(series);
863 series->setParent(this); // Reparent as removing will leave series parentless
864
865 // Find scattergraph model
866 for (QList<ScatterModel *>::ConstIterator it = m_scatterGraphs.cbegin();
867 it != m_scatterGraphs.cend();) {
868 if ((*it)->series == series) {
869 removeDataItems(graphModel: *it);
870
871 if ((*it)->seriesTexture)
872 delete (*it)->seriesTexture;
873 if ((*it)->highlightTexture)
874 delete (*it)->highlightTexture;
875
876 delete *it;
877 it = m_scatterGraphs.erase(pos: it);
878 } else {
879 ++it;
880 }
881 }
882
883 disconnectSeries(series);
884}
885
886void QQuickGraphsScatter::handleAxisXChanged(QAbstract3DAxis *axis)
887{
888 emit axisXChanged(axis: static_cast<QValue3DAxis *>(axis));
889}
890
891void QQuickGraphsScatter::handleAxisYChanged(QAbstract3DAxis *axis)
892{
893 emit axisYChanged(axis: static_cast<QValue3DAxis *>(axis));
894}
895
896void QQuickGraphsScatter::handleAxisZChanged(QAbstract3DAxis *axis)
897{
898 emit axisZChanged(axis: static_cast<QValue3DAxis *>(axis));
899}
900
901void QQuickGraphsScatter::handleSeriesMeshChanged()
902{
903 recreateDataItems();
904}
905
906void QQuickGraphsScatter::handleMeshSmoothChanged(bool enable)
907{
908 m_smooth = enable;
909 recreateDataItems();
910}
911
912bool QQuickGraphsScatter::handleMousePressedEvent(QMouseEvent *event)
913{
914 if (Qt::LeftButton == event->button())
915 doPicking(position: event->pos());
916
917 return true;
918}
919
920bool QQuickGraphsScatter::handleTouchEvent(QTouchEvent *event)
921{
922 if (scene()->selectionQueryPosition() != scene()->invalidSelectionPoint()
923 && !event->isUpdateEvent()) {
924 doPicking(position: event->point(i: 0).position());
925 scene()->setSelectionQueryPosition(scene()->invalidSelectionPoint());
926 }
927
928 return true;
929}
930
931bool QQuickGraphsScatter::doPicking(const QPointF &position)
932{
933 if (!QQuickGraphsItem::doPicking(point: position))
934 return false;
935
936 if (selectionMode() == QAbstract3DGraph::SelectionItem) {
937 QList<QQuick3DPickResult> results = pickAll(x: position.x(), y: position.y());
938 if (!results.empty()) {
939 for (const auto &result : std::as_const(t&: results)) {
940 if (const auto &hit = result.objectHit()) {
941 if (hit == backgroundBB() || hit == background()) {
942 clearSelectionModel();
943 continue;
944 }
945 if (optimizationHints() == QAbstract3DGraph::OptimizationLegacy) {
946 setSelected(hit);
947 break;
948 } else if (optimizationHints() == QAbstract3DGraph::OptimizationDefault) {
949 setSelected(root: hit, index: result.instanceIndex());
950 break;
951 }
952 }
953 }
954 } else {
955 clearSelectionModel();
956 }
957 }
958 return true;
959}
960
961void QQuickGraphsScatter::updateShadowQuality(QAbstract3DGraph::ShadowQuality quality)
962{
963 // Were shadows enabled before?
964 bool prevShadowsEnabled = light()->castsShadow();
965 QQuickGraphsItem::updateShadowQuality(quality);
966 m_scatterController->setSeriesVisualsDirty();
967
968 if (prevShadowsEnabled != light()->castsShadow()) {
969 // Need to change mesh for series using point type
970 QList<ScatterModel *> graphs;
971 for (const auto &graph : std::as_const(t&: m_scatterGraphs)) {
972 if (graph->series->mesh() == QAbstract3DSeries::MeshPoint)
973 graphs.append(t: graph);
974 }
975 recreateDataItems(graphs);
976 }
977}
978
979void QQuickGraphsScatter::componentComplete()
980{
981 QQuickGraphsItem::componentComplete();
982 QObject::connect(sender: cameraTarget(), signal: &QQuick3DNode::rotationChanged,
983 context: this, slot: &QQuickGraphsScatter::cameraRotationChanged);
984}
985
986void QQuickGraphsScatter::connectSeries(QScatter3DSeries *series)
987{
988 m_smooth = series->isMeshSmooth();
989
990 QObject::connect(sender: series, signal: &QScatter3DSeries::meshChanged, context: this,
991 slot: &QQuickGraphsScatter::handleSeriesMeshChanged);
992 QObject::connect(sender: series, signal: &QScatter3DSeries::meshSmoothChanged, context: this,
993 slot: &QQuickGraphsScatter::handleMeshSmoothChanged);
994}
995
996void QQuickGraphsScatter::calculateSceneScalingFactors()
997{
998 if (m_requestedMargin < 0.0f) {
999 if (m_maxItemSize > m_defaultMaxSize)
1000 m_hBackgroundMargin = m_maxItemSize / m_itemScaler;
1001 else
1002 m_hBackgroundMargin = m_defaultMaxSize;
1003 m_vBackgroundMargin = m_hBackgroundMargin;
1004 } else {
1005 m_hBackgroundMargin = m_requestedMargin;
1006 m_vBackgroundMargin = m_requestedMargin;
1007 }
1008
1009 float hAspectRatio = horizontalAspectRatio();
1010
1011 QSizeF areaSize;
1012 auto *axisX = static_cast<QValue3DAxis *>(m_scatterController->axisX());
1013 auto *axisZ = static_cast<QValue3DAxis *>(m_scatterController->axisZ());
1014
1015 if (qFuzzyIsNull(f: hAspectRatio)) {
1016 areaSize.setHeight(axisZ->max() - axisZ->min());
1017 areaSize.setWidth(axisX->max() - axisX->min());
1018 } else {
1019 areaSize.setHeight(1.0f);
1020 areaSize.setWidth(hAspectRatio);
1021 }
1022
1023 float horizontalMaxDimension;
1024 float graphAspectRatio = aspectRatio();
1025
1026 if (graphAspectRatio > 2.0f) {
1027 horizontalMaxDimension = 2.0f;
1028 m_scaleY = 2.0f / graphAspectRatio;
1029 } else {
1030 horizontalMaxDimension = graphAspectRatio;
1031 m_scaleY = 1.0f;
1032 }
1033 float scaleFactor = qMax(a: areaSize.width(), b: areaSize.height());
1034 m_scaleX = horizontalMaxDimension * areaSize.width() / scaleFactor;
1035 m_scaleZ = horizontalMaxDimension * areaSize.height() / scaleFactor;
1036
1037 setBackgroundScaleMargin({m_hBackgroundMargin, m_vBackgroundMargin, m_hBackgroundMargin});
1038
1039 setLineLengthScaleFactor(0.02f);
1040 setScaleWithBackground({m_scaleX, m_scaleY, m_scaleZ});
1041 setScale({m_scaleX * 2.0f, m_scaleY * 2.0f, m_scaleZ * -2.0f});
1042 setTranslate({-m_scaleX, -m_scaleY, m_scaleZ});
1043}
1044
1045float QQuickGraphsScatter::calculatePointScaleSize()
1046{
1047 QList<QScatter3DSeries *> series = m_scatterController->scatterSeriesList();
1048 int totalDataSize = 0;
1049 for (const auto &scatterSeries : std::as_const(t&: series)) {
1050 if (scatterSeries->isVisible())
1051 totalDataSize += scatterSeries->dataProxy()->array()->size();
1052 }
1053
1054 return qBound(min: m_defaultMinSize, val: 2.0f / float(qSqrt(v: qreal(totalDataSize))), max: m_defaultMaxSize);
1055}
1056
1057void QQuickGraphsScatter::updatePointScaleSize()
1058{
1059 m_pointScale = calculatePointScaleSize();
1060}
1061
1062QQuick3DModel *QQuickGraphsScatter::selected() const
1063{
1064 return m_selected;
1065}
1066
1067void QQuickGraphsScatter::setSelected(QQuick3DModel *newSelected)
1068{
1069 if (newSelected != m_selected) {
1070 m_previousSelected = m_selected;
1071 m_selected = newSelected;
1072
1073 auto series = static_cast<QScatter3DSeries *>(m_selected->parent());
1074
1075 // Find scattermodel
1076 ScatterModel *graphModel = nullptr;
1077
1078 for (const auto &model : std::as_const(t&: m_scatterGraphs)) {
1079 if (model->series == series) {
1080 graphModel = model;
1081 break;
1082 }
1083 }
1084
1085 if (graphModel) {
1086 qsizetype index = graphModel->dataItems.indexOf(t: m_selected);
1087 setSelectedItem(index, series);
1088 m_selectionActive = false;
1089 m_scatterController->setSeriesVisualsDirty();
1090 m_scatterController->setSelectedItemChanged(true);
1091 }
1092 }
1093}
1094
1095void QQuickGraphsScatter::setSelected(QQuick3DModel *root, qsizetype index)
1096{
1097 if (index != m_scatterController->m_selectedItem) {
1098 auto series = static_cast<QScatter3DSeries *>(root->parent());
1099
1100 m_scatterController->setSeriesVisualsDirty();
1101 setSelectedItem(index, series);
1102 m_scatterController->setSelectedItemChanged(true);
1103 m_selectionActive = false;
1104 }
1105}
1106
1107void QQuickGraphsScatter::clearSelectionModel()
1108{
1109 if (optimizationHints() == QAbstract3DGraph::OptimizationDefault)
1110 clearAllSelectionInstanced();
1111
1112 setSelectedItem(index: invalidSelectionIndex(), series: nullptr);
1113
1114 itemLabel()->setVisible(false);
1115 m_scatterController->setSeriesVisualsDirty();
1116 m_selected = nullptr;
1117 m_previousSelected = nullptr;
1118}
1119
1120void QQuickGraphsScatter::clearAllSelectionInstanced()
1121{
1122 for (const auto &graph : m_scatterGraphs)
1123 graph->instancing->resetVisibilty();
1124}
1125
1126void QQuickGraphsScatter::updateGraph()
1127{
1128 updatePointScaleSize();
1129 for (auto graphModel : std::as_const(t&: m_scatterGraphs)) {
1130 if (m_scatterController->isDataDirty()) {
1131 if (optimizationHints() == QAbstract3DGraph::OptimizationHint::OptimizationLegacy) {
1132 if (graphModel->dataItems.count() != graphModel->series->dataProxy()->itemCount()) {
1133 int sizeDiff = sizeDifference(size1: graphModel->dataItems.count(),
1134 size2: graphModel->series->dataProxy()->itemCount());
1135
1136 if (sizeDiff > 0)
1137 addPointsToScatterModel(graphModel, count: sizeDiff);
1138 else
1139 removeDataItems(items&: graphModel->dataItems, count: qAbs(t: sizeDiff));
1140 }
1141 } else {
1142 if (graphModel->instancing == nullptr) {
1143 graphModel->instancing = new ScatterInstancing;
1144 graphModel->instancing->setParent(graphModel->series);
1145 }
1146 if (graphModel->instancingRootItem == nullptr) {
1147 graphModel->instancingRootItem = createDataItem(series: graphModel->series);
1148 graphModel->instancingRootItem->setParent(graphModel->series);
1149 graphModel->instancingRootItem->setInstancing(graphModel->instancing);
1150 if (selectionMode() != QAbstract3DGraph::SelectionNone) {
1151 graphModel->instancingRootItem->setPickable(true);
1152 graphModel->selectionIndicator = createDataItem(series: graphModel->series);
1153 graphModel->selectionIndicator->setVisible(false);
1154 }
1155 }
1156 }
1157
1158 updateScatterGraphItemPositions(graphModel);
1159 }
1160
1161 if (m_scatterController->isSeriesVisualsDirty())
1162 updateScatterGraphItemVisuals(graphModel);
1163
1164 if (m_scatterController->m_selectedItemSeries == graphModel->series
1165 && m_scatterController->m_selectedItem != invalidSelectionIndex()) {
1166 QVector3D selectionPosition = {0.0f, 0.0f, 0.0f};
1167 if (optimizationHints() == QAbstract3DGraph::OptimizationHint::OptimizationLegacy) {
1168 QQuick3DModel *selectedModel = graphModel->dataItems.at(
1169 i: m_scatterController->m_selectedItem);
1170
1171 selectionPosition = selectedModel->position();
1172 } else {
1173 selectionPosition = graphModel->instancing->dataArray().at(
1174 i: m_scatterController->m_selectedItem).position;
1175 }
1176 updateItemLabel(position: selectionPosition);
1177 QString label = m_scatterController->m_selectedItemSeries->itemLabel();
1178 itemLabel()->setProperty(name: "labelText", value: label);
1179 }
1180 }
1181
1182 if (m_scatterController->m_selectedItem == invalidSelectionIndex()) {
1183 itemLabel()->setVisible(false);
1184 }
1185}
1186
1187void QQuickGraphsScatter::synchData()
1188{
1189 QList<QScatter3DSeries *> seriesList = m_scatterController->scatterSeriesList();
1190
1191 float maxItemSize = 0.0f;
1192 for (const auto &series : std::as_const(t&: seriesList)) {
1193 if (series->isVisible()) {
1194 float itemSize = series->itemSize();
1195 if (itemSize > maxItemSize)
1196 maxItemSize = itemSize;
1197 }
1198 }
1199
1200 m_maxItemSize = maxItemSize;
1201
1202 updatePointScaleSize();
1203 QQuickGraphsItem::synchData();
1204 scene()->activeCamera()->d_func()->setMinYRotation(-90.0f);
1205
1206 m_pointScale = calculatePointScaleSize();
1207
1208 if (m_scatterController->hasSelectedItemChanged()) {
1209 if (m_scatterController->m_selectedItem != m_scatterController->invalidSelectionIndex()) {
1210 QString itemLabelText = m_scatterController->m_selectedItemSeries->itemLabel();
1211 itemLabel()->setProperty(name: "labelText", value: itemLabelText);
1212 }
1213 m_scatterController->setSelectedItemChanged(false);
1214 }
1215}
1216
1217void QQuickGraphsScatter::cameraRotationChanged()
1218{
1219 m_scatterController->m_isDataDirty = true;
1220}
1221QT_END_NAMESPACE
1222

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