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