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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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