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 | |
22 | QT_BEGIN_NAMESPACE |
23 | |
24 | static 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 | |
134 | QQuickGraphsScatter::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 | |
146 | QQuickGraphsScatter::~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 | |
156 | void QQuickGraphsScatter::setAxisX(QValue3DAxis *axis) |
157 | { |
158 | QQuickGraphsItem::setAxisX(axis); |
159 | } |
160 | |
161 | QValue3DAxis *QQuickGraphsScatter::axisX() const |
162 | { |
163 | return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisX()); |
164 | } |
165 | |
166 | void QQuickGraphsScatter::setAxisY(QValue3DAxis *axis) |
167 | { |
168 | QQuickGraphsItem::setAxisY(axis); |
169 | } |
170 | |
171 | QValue3DAxis *QQuickGraphsScatter::axisY() const |
172 | { |
173 | return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisY()); |
174 | } |
175 | |
176 | void QQuickGraphsScatter::setAxisZ(QValue3DAxis *axis) |
177 | { |
178 | QQuickGraphsItem::setAxisZ(axis); |
179 | } |
180 | |
181 | QValue3DAxis *QQuickGraphsScatter::axisZ() const |
182 | { |
183 | return static_cast<QValue3DAxis *>(QQuickGraphsItem::axisZ()); |
184 | } |
185 | |
186 | void QQuickGraphsScatter::disconnectSeries(QScatter3DSeries *series) |
187 | { |
188 | QObject::disconnect(sender: series, signal: 0, receiver: this, member: 0); |
189 | } |
190 | |
191 | void 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 | |
219 | qsizetype QQuickGraphsScatter::getItemIndex(QQuick3DModel *item) |
220 | { |
221 | Q_UNUSED(item); |
222 | if (optimizationHint() == QtGraphs3D::OptimizationHint::Legacy) |
223 | return 0; |
224 | |
225 | return -1; |
226 | } |
227 | |
228 | void QQuickGraphsScatter::clearSelection() |
229 | { |
230 | setSelectedItem(index: invalidSelectionIndex(), series: 0); |
231 | } |
232 | |
233 | void 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 | |
342 | void 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 | |
540 | void 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 | } |
570 | void 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 | |
604 | void 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 | |
637 | void 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 | |
663 | QQuick3DTexture *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 | |
678 | QQuick3DNode *QQuickGraphsScatter::createSeriesRoot() |
679 | { |
680 | auto model = new QQuick3DNode(); |
681 | |
682 | model->setParentItem(QQuick3DViewport::scene()); |
683 | return model; |
684 | } |
685 | |
686 | QQuick3DModel *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 | |
699 | void 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 | |
720 | void 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 | |
733 | QList<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 | |
745 | void 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 | |
759 | void 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 | |
773 | void 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 | |
784 | qsizetype QQuickGraphsScatter::sizeDifference(qsizetype size1, qsizetype size2) |
785 | { |
786 | return size2 - size1; |
787 | } |
788 | |
789 | QVector3D 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 | |
800 | void 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 | |
818 | QString 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 | |
864 | void 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 | |
877 | void QQuickGraphsScatter::handleSeriesChanged(QList<QAbstract3DSeries *> changedSeries) |
878 | { |
879 | Q_UNUSED(changedSeries) |
880 | // TODO: generate items and remove old items |
881 | } |
882 | |
883 | bool QQuickGraphsScatter::selectedItemInSeries(const QScatter3DSeries *series) |
884 | { |
885 | return (m_selectedItem != -1 && m_selectedItemSeries == series); |
886 | } |
887 | |
888 | bool 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 | |
895 | QScatter3DSeries *QQuickGraphsScatter::selectedSeries() const |
896 | { |
897 | return m_selectedItemSeries; |
898 | } |
899 | |
900 | void 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 | |
949 | void 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 | |
961 | void QQuickGraphsScatter::handleAxisAutoAdjustRangeChangedInOrientation( |
962 | QAbstract3DAxis::AxisOrientation orientation, bool autoAdjust) |
963 | { |
964 | Q_UNUSED(orientation); |
965 | Q_UNUSED(autoAdjust); |
966 | adjustAxisRanges(); |
967 | } |
968 | |
969 | void 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 | |
979 | QQmlListProperty<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 | |
989 | void QQuickGraphsScatter::appendSeriesFunc(QQmlListProperty<QScatter3DSeries> *list, |
990 | QScatter3DSeries *series) |
991 | { |
992 | reinterpret_cast<QQuickGraphsScatter *>(list->data)->addSeries(series); |
993 | } |
994 | |
995 | qsizetype QQuickGraphsScatter::countSeriesFunc(QQmlListProperty<QScatter3DSeries> *list) |
996 | { |
997 | return reinterpret_cast<QQuickGraphsScatter *>(list->data)->scatterSeriesList().size(); |
998 | } |
999 | |
1000 | QScatter3DSeries *QQuickGraphsScatter::atSeriesFunc(QQmlListProperty<QScatter3DSeries> *list, |
1001 | qsizetype index) |
1002 | { |
1003 | return reinterpret_cast<QQuickGraphsScatter *>(list->data)->scatterSeriesList().at(i: index); |
1004 | } |
1005 | |
1006 | void 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 | |
1015 | void 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 | |
1037 | void 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 | |
1072 | void QQuickGraphsScatter::handleAxisXChanged(QAbstract3DAxis *axis) |
1073 | { |
1074 | emit axisXChanged(axis: static_cast<QValue3DAxis *>(axis)); |
1075 | } |
1076 | |
1077 | void QQuickGraphsScatter::handleAxisYChanged(QAbstract3DAxis *axis) |
1078 | { |
1079 | emit axisYChanged(axis: static_cast<QValue3DAxis *>(axis)); |
1080 | } |
1081 | |
1082 | void QQuickGraphsScatter::handleAxisZChanged(QAbstract3DAxis *axis) |
1083 | { |
1084 | emit axisZChanged(axis: static_cast<QValue3DAxis *>(axis)); |
1085 | } |
1086 | |
1087 | void QQuickGraphsScatter::handleSeriesMeshChanged() |
1088 | { |
1089 | recreateDataItems(); |
1090 | } |
1091 | |
1092 | void 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 | |
1103 | void 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 | |
1122 | void 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 | |
1136 | void 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 | |
1169 | void 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 | |
1202 | void 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 | |
1318 | void 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 | |
1347 | bool 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 | |
1377 | void 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 | |
1395 | void 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 | |
1406 | void 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 | |
1420 | void 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 | |
1431 | void 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 | |
1447 | void 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 | |
1505 | float 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 | |
1517 | void QQuickGraphsScatter::updatePointScaleSize() |
1518 | { |
1519 | m_pointScale = calculatePointScaleSize(); |
1520 | } |
1521 | |
1522 | void 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 | |
1534 | QQuick3DModel *QQuickGraphsScatter::selected() const |
1535 | { |
1536 | return m_selected; |
1537 | } |
1538 | |
1539 | void 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 | |
1566 | void 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 | |
1576 | void 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 | |
1589 | void QQuickGraphsScatter::clearAllSelectionInstanced() |
1590 | { |
1591 | for (const auto &graph : m_scatterGraphs) |
1592 | graph->instancing->resetVisibilty(); |
1593 | } |
1594 | |
1595 | void 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 | |
1607 | void 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 | |
1676 | void 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 | |
1706 | void QQuickGraphsScatter::cameraRotationChanged() |
1707 | { |
1708 | m_isDataDirty = true; |
1709 | } |
1710 | |
1711 | void QQuickGraphsScatter::handleOptimizationHintChange(QtGraphs3D::OptimizationHint hint) |
1712 | { |
1713 | Q_UNUSED(hint) |
1714 | m_optimizationChanged = true; |
1715 | } |
1716 | |
1717 | bool 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 | } |
1727 | QT_END_NAMESPACE |
1728 |
Definitions
- insertRemoveRecordReserveSize
- QQuickGraphsScatter
- ~QQuickGraphsScatter
- setAxisX
- axisX
- setAxisY
- axisY
- setAxisZ
- axisZ
- disconnectSeries
- generatePointsForScatterModel
- getItemIndex
- clearSelection
- updateScatterGraphItemPositions
- updateScatterGraphItemVisuals
- updateMaterialReference
- updateItemMaterial
- updateInstancedMaterialProperties
- updateMaterialProperties
- createTexture
- createSeriesRoot
- createDataItem
- removeDataItems
- removeDataItems
- scatterSeriesList
- recreateDataItems
- recreateDataItems
- addPointsToScatterModel
- sizeDifference
- selectedItemPosition
- fixMeshFileName
- getMeshFileName
- deleteDataItem
- handleSeriesChanged
- selectedItemInSeries
- isDotPositionInAxisRange
- selectedSeries
- setSelectedItem
- setSelectionMode
- handleAxisAutoAdjustRangeChangedInOrientation
- handleAxisRangeChangedBySender
- seriesList
- appendSeriesFunc
- countSeriesFunc
- atSeriesFunc
- clearSeriesFunc
- addSeries
- removeSeries
- handleAxisXChanged
- handleAxisYChanged
- handleAxisZChanged
- handleSeriesMeshChanged
- handleMeshSmoothChanged
- handleArrayReset
- handleItemsAdded
- handleItemsChanged
- handleItemsRemoved
- adjustAxisRanges
- handleItemsInserted
- doPicking
- updateShadowQuality
- updateLightStrength
- startRecordingRemovesAndInserts
- componentComplete
- connectSeries
- calculateSceneScalingFactors
- calculatePointScaleSize
- updatePointScaleSize
- calculatePolarXZ
- selected
- setSelected
- setSelected
- clearSelectionModel
- clearAllSelectionInstanced
- optimizationChanged
- updateGraph
- synchData
- cameraRotationChanged
- handleOptimizationHintChange
Learn to use CMake with our Intro Training
Find out more