1// Copyright (C) 2023 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
3
4#include "qquickgraphsitem_p.h"
5
6#include "q3dscene_p.h"
7#include "qabstract3daxis_p.h"
8#include "qabstract3dseries.h"
9#include "qabstract3dseries_p.h"
10#include "qcategory3daxis.h"
11#include "qcategory3daxis_p.h"
12#include "qcustom3ditem.h"
13#include "qcustom3ditem_p.h"
14#include "qcustom3dlabel.h"
15#include "qcustom3dvolume.h"
16#include "qgraphsinputhandler_p.h"
17#include "qgraphstheme.h"
18#include "qvalue3daxis.h"
19#include "qvalue3daxis_p.h"
20#include "utils_p.h"
21#include "qgraphs3dlogging_p.h"
22
23#include <QtGui/QGuiApplication>
24
25#include <QtQuick/private/qquickitem_p.h>
26#include <QtQuick3D/private/qquick3dcustommaterial_p.h>
27#include <QtQuick3D/private/qquick3ddirectionallight_p.h>
28#include <QtQuick3D/private/qquick3dloader_p.h>
29#include <QtQuick3D/private/qquick3dorthographiccamera_p.h>
30#include <QtQuick3D/private/qquick3dperspectivecamera_p.h>
31#include <QtQuick3D/private/qquick3dprincipledmaterial_p.h>
32#include <QtQuick3D/private/qquick3drepeater_p.h>
33
34#include <QtGui/qquaternion.h>
35
36#include <qtgraphs_tracepoints_p.h>
37
38#if defined(Q_OS_IOS)
39#include <QtCore/QTimer>
40#endif
41
42#if defined(Q_OS_MACOS)
43#include <qpa/qplatformnativeinterface.h>
44#endif
45
46QT_BEGIN_NAMESPACE
47
48Q_TRACE_PREFIX(qtgraphs,
49 "QT_BEGIN_NAMESPACE" \
50 "class QQuickGraphsItem;" \
51 "QT_END_NAMESPACE"
52 )
53
54Q_TRACE_POINT(qtgraphs, QGraphs3DItemCtor_entry);
55Q_TRACE_POINT(qtgraphs, QGraphs3DItemCtor_exit);
56
57Q_TRACE_POINT(qtgraphs, QGraphs3DItemInit_entry);
58Q_TRACE_POINT(qtgraphs, QGraphs3DItemInit_exit);
59
60Q_TRACE_POINT(qtgraphs, QGraphs3DItemSynch_entry);
61Q_TRACE_POINT(qtgraphs, QGraphs3DItemSynch_exit);
62
63Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateGrid_entry);
64Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateGrid_exit);
65
66Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateLabels_entry);
67Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateLabels_exit);
68
69Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateCamera_entry);
70Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateCamera_exit);
71
72Q_TRACE_POINT(qtgraphs, QGraphs3DItemDoPicking_entry, float posX, float posY);
73Q_TRACE_POINT(qtgraphs, QGraphs3DItemDoPicking_exit);
74
75Q_TRACE_POINT(qtgraphs, QGraphs3DItemDoRayPicking_entry, float originX, float originY,
76 float originZ, float directionX, float directionY, float directionZ);
77Q_TRACE_POINT(qtgraphs, QGraphs3DItemDoRayPicking_exit);
78
79Q_TRACE_POINT(qtgraphs, QGraphs3DItemCreateSliceView_entry);
80Q_TRACE_POINT(qtgraphs, QGraphs3DItemCreateSliceView_exit);
81
82Q_TRACE_POINT(qtgraphs, QGraphs3DItemCreateOffscreenSliceView_entry, int sliceType);
83Q_TRACE_POINT(qtgraphs, QGraphs3DItemCreateOffscreenSliceView_exit);
84
85Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateSliceGrid_entry);
86Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateSliceGrid_exit);
87
88Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateSliceLabels_entry);
89Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateSliceLabels_exit);
90
91Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateCustomData_entry);
92Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateCustomData_exit);
93
94Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateCustomVolumes_entry);
95Q_TRACE_POINT(qtgraphs, QGraphs3DItemUpdateCustomVolumes_exit);
96
97
98constexpr float doublePi = static_cast<float>(M_PI) * 2.0f;
99constexpr float polarRoundness = 64.0f;
100
101/*!
102 * \qmltype GraphsItem3D
103 * \qmlabstract
104 * \inqmlmodule QtGraphs
105 * \ingroup graphs_qml_3D
106 * \brief Base type for 3D graphs.
107 *
108 * The uncreatable base type for all 3D graphs in QtGraphs.
109 *
110 * \sa Bars3D, Scatter3D, Surface3D, {Qt Graphs C++ Classes for 3D}
111 */
112
113/*!
114 * \qmlproperty Graphs3D.SelectionMode GraphsItem3D::selectionMode
115 * The active selection mode in the graph.
116 * One of the \l Graphs3D.SelectionFlag enum values.
117 */
118
119/*!
120 * \qmlproperty Graphs3D.ShadowQuality GraphsItem3D::shadowQuality
121 * The quality of shadows. One of the \l Graphs3D.ShadowQuality enum
122 * values.
123 */
124
125/*!
126 * \qmlproperty Graphs3D.CameraPreset GraphsItem3D::cameraPreset
127 *
128 * The currently active camera preset, which is one of
129 * \l{Graphs3D.CameraPreset}. If no
130 * preset is active, the value is \c {Graphs3D.CameraPreset.NoPreset}.
131 */
132
133/*!
134 * \qmlproperty real GraphsItem3D::cameraXRotation
135 *
136 * The X-rotation angle of the camera around the target point in degrees
137 * starting from the current base position.
138 */
139
140/*!
141 * \qmlproperty real GraphsItem3D::cameraYRotation
142 *
143 * The Y-rotation angle of the camera around the target point in degrees
144 * starting from the current base position.
145 */
146
147/*!
148 * \qmlproperty real GraphsItem3D::minCameraXRotation
149 * \since 6.9
150 *
151 * \brief The minimum X-rotation angle of the camera around the target point in degrees.
152 * The default value is \c{-180.0}
153 */
154
155/*!
156 * \qmlproperty real GraphsItem3D::maxCameraXRotation
157 * \since 6.9
158 *
159 * \brief The maximum X-rotation angle of the camera around the target point in degrees.
160 * The default value is \c{180.0}
161 */
162
163/*!
164 * \qmlproperty real GraphsItem3D::minCameraYRotation
165 * \since 6.9
166 *
167 * \brief The minimum Y-rotation angle of the camera around the target point in degrees.
168 * The default value is \c{0.0}
169 */
170
171/*!
172 * \qmlproperty real GraphsItem3D::maxCameraYRotation
173 * \since 6.9
174 *
175 * \brief The maximum Y-rotation angle of the camera around the target point in degrees.
176 * The default value is \c{90.0}
177 */
178
179/*!
180 * \qmlproperty bool GraphsItem3D::zoomAtTargetEnabled
181 *
182 * Whether zooming should change the camera target so that the zoomed point
183 * of the graph stays at the same location after the zoom.
184 *
185 * Defaults to \c{true}.
186 */
187
188/*!
189 * \qmlproperty bool GraphsItem3D::zoomEnabled
190 *
191 * Whether this input handler allows graph zooming.
192 *
193 * Defaults to \c{true}.
194 */
195
196/*!
197 * \qmlproperty bool GraphsItem3D::selectionEnabled
198 *
199 * Whether this input handler allows selection from the graph.
200 *
201 * Defaults to \c{true}.
202 */
203
204/*!
205 * \qmlproperty bool GraphsItem3D::rotationEnabled
206 *
207 * Whether this input handler allows graph rotation.
208 *
209 * Defaults to \c{true}.
210 */
211
212/*!
213 * \qmlproperty real GraphsItem3D::cameraZoomLevel
214 *
215 * The camera zoom level in percentage. The default value of \c{100.0}
216 * means there is no zoom in or out set in the camera.
217 * The value is limited by the minCameraZoomLevel and maxCameraZoomLevel
218 * properties.
219 *
220 * \sa minCameraZoomLevel, maxCameraZoomLevel
221 */
222
223/*!
224 * \qmlproperty real GraphsItem3D::minCameraZoomLevel
225 *
226 * Sets the minimum allowed camera zoom level.
227 * If the new minimum level is higher than the existing maximum level, the
228 * maximum level is adjusted to the new minimum as well.
229 * If the current cameraZoomLevel is outside the new bounds, it is adjusted as
230 * well. The minCameraZoomLevel cannot be set below \c{1.0}.
231 * Defaults to \c{10.0}.
232 *
233 * \sa cameraZoomLevel, maxCameraZoomLevel
234 */
235
236/*!
237 * \qmlproperty real GraphsItem3D::maxCameraZoomLevel
238 *
239 * Sets the maximum allowed camera zoom level.
240 * If the new maximum level is lower than the existing minimum level, the
241 * minimum level is adjusted to the new maximum as well.
242 * If the current cameraZoomLevel is outside the new bounds, it is adjusted as
243 * well. Defaults to \c{500.0f}.
244 *
245 * \sa cameraZoomLevel, minCameraZoomLevel
246 */
247
248/*!
249 * \qmlproperty bool GraphsItem3D::wrapCameraXRotation
250 *
251 * The behavior of the minimum and maximum limits in the X-rotation.
252 * By default, the X-rotation wraps from minimum value to maximum and from
253 * maximum to minimum.
254 *
255 * If set to \c true, the X-rotation of the camera is wrapped from minimum to
256 * maximum and from maximum to minimum. If set to \c false, the X-rotation of
257 * the camera is limited to the sector determined by the minimum and maximum
258 * values.
259 */
260
261/*!
262 * \qmlproperty bool GraphsItem3D::wrapCameraYRotation
263 *
264 * The behavior of the minimum and maximum limits in the Y-rotation.
265 * By default, the Y-rotation is limited between the minimum and maximum values
266 * without any wrapping.
267 *
268 * If \c true, the Y-rotation of the camera is wrapped from minimum to maximum
269 * and from maximum to minimum. If \c false, the Y-rotation of the camera is
270 * limited to the sector determined by the minimum and maximum values.
271 */
272
273/*!
274 * \qmlproperty vector3d GraphsItem3D::cameraTargetPosition
275 *
276 * The camera target as a vector3d. Defaults to \c {vector3d(0.0, 0.0, 0.0)}.
277 *
278 * Valid coordinate values are between \c{-1.0...1.0}, where the edge values
279 * indicate the edges of the corresponding axis range. Any values outside this
280 * range are clamped to the edge.
281 *
282 */
283
284/*!
285 * \qmlproperty Node GraphsItem3D::rootNode
286 * \readonly
287 * \since 6.9
288 *
289 * Returns a pointer to the root node of the 3D graph. Use this property
290 * for injecting a 3D graph into a separate \l {View3D} using
291 * \l {View3D::}{importScene}:
292 *
293 * \code
294 * Bars3D {
295 * id: bars
296 * }
297 * View3D {
298 * importScene: bars.rootNode
299 * }
300 * \endcode
301 *
302 * \sa {View3D}
303 */
304
305/*!
306 * \qmlproperty Scene3D GraphsItem3D::scene
307 * \readonly
308 *
309 * The Scene3D pointer that can be used to manipulate the scene and access the
310 * scene elements.
311 *
312 * This property is read-only.
313 */
314
315/*!
316 * \qmlproperty GraphsTheme GraphsItem3D::theme
317 * The active theme of the graph.
318 *
319 * \sa GraphsTheme
320 */
321
322/*!
323 * \qmlproperty Graphs3D.RenderingMode GraphsItem3D::renderingMode
324 *
325 * How the graph will be rendered. Defaults to \c{Indirect}.
326 *
327 * \note Setting the \c antialiasing property of the graph does not do anything.
328 * However, it is set by the graph itself if the current rendering mode uses
329 * antialiasing.
330 *
331 * \sa msaaSamples, Graphs3D.RenderingMode
332 */
333
334/*!
335 * \qmlproperty Graphs3D.TransparencyTechnique GraphsItem3D::transparencyTechnique
336 * \since 6.9
337 *
338 * Specifies which transparency technique to use. The Default value is \c{Default}.
339 * When rendering transparent surface graphs, use \c{Approximate} or \c{Accurate}.
340 *
341 * \value Default
342 * Indicates that order-independent transparency techniques are not used.
343 * Offers the best performance. Use when graphs don't contain
344 * transparency or when a bar or scatter graph is also using instancing,
345 * that is \l optimizationHint is {QtGraphs3D::OptimizationHint::Default}.
346 *
347 * \value Approximate
348 * Indicates that a graph attempts an approximation of order-independent
349 * transparency. This method is faster than \c Accurate and works on older
350 * hardware but may yield inaccurate results. Use when the order-independent
351 * transparency is needed, but the performance cost has to be lower than
352 * when using accurate order-independent transparency.
353 *
354 * \value Accurate
355 * Indicates that accurate order-independent transparency is used.
356 * Use when perfect transparency rendering is needed.
357 * \note Accurate transparency is not yet implemented
358 * and will be enabled when the required functionality
359 * is added to QtQuick3D.
360 */
361
362/*!
363 * \qmlproperty int GraphsItem3D::msaaSamples
364 * The number of samples used in multisample antialiasing when renderingMode
365 * is \c Indirect. When renderingMode is \c DirectToBackground, this property
366 * value is read-only and returns the number of samples specified by the window
367 * surface format.
368 * Defaults to \c{4}.
369 *
370 * \sa renderingMode
371 */
372
373/*!
374 * \qmlproperty bool GraphsItem3D::measureFps
375 *
376 * If \c {true}, the rendering is done continuously instead of on demand, and
377 * the value of the currentFps property is updated. Defaults to \c{false}.
378 *
379 * \sa currentFps
380 */
381
382/*!
383 * \qmlproperty int GraphsItem3D::currentFps
384 *
385 * When FPS measuring is enabled, the results for the last second are stored in
386 * this read-only property. It takes at least a second before this value updates
387 * after measuring is activated.
388 *
389 * \sa measureFps
390 */
391
392/*!
393 * \qmlproperty list<Custom3DItem> GraphsItem3D::customItemList
394 *
395 * The list of \l{Custom3DItem} items added to the graph. The graph takes
396 * ownership of the added items.
397 */
398
399/*!
400 * \qmlproperty bool GraphsItem3D::polar
401 *
402 * If \c {true}, the horizontal axes are changed into polar axes. The x-axis
403 * becomes the angular axis and the z-axis becomes the radial axis.
404 * Polar mode is not available for bar graphs.
405 *
406 * Defaults to \c{false}.
407 *
408 * \sa orthoProjection, radialLabelOffset
409 */
410
411/*!
412 * \qmlproperty real GraphsItem3D::labelMargin
413 *
414 * \brief This property specifies the margin for the placement of the axis labels.
415 *
416 * Negative values place the labels inside the plot-area while positive values
417 * place them outside the plot-area. Label automatic rotation is disabled when
418 * the value is negative. Defaults to \c 0.1
419 *
420 * \sa QAbstract3DAxis::labelAutoAngle
421 *
422 */
423
424/*!
425 * \qmlproperty real GraphsItem3D::radialLabelOffset
426 *
427 * This property specifies the normalized horizontal offset for the axis labels
428 * of the radial polar axis. The value \c 0.0 indicates that the labels should
429 * be drawn next to the 0-angle angular axis grid line. The value \c 1.0
430 * indicates that the labels are drawn in their usual place at the edge of the
431 * graph background. This property is ignored if the polar property value is
432 * \c{false}. Defaults to \c 1.0.
433 *
434 * \sa polar
435 */
436
437/*!
438 * \qmlmethod void GraphsItem3D::clearSelection()
439 * Clears selection from all attached series.
440 */
441
442/*!
443 * \qmlmethod bool GraphsItem3D::hasSeries(Abstract3DSeries series)
444 * Returns whether the \a series has already been added to the graph.
445 */
446
447/*!
448 * \qmlmethod qsizetype GraphsItem3D::addCustomItem(Custom3DItem item)
449 *
450 * Adds a Custom3DItem \a item to the graph. Graph takes ownership of the added
451 * item.
452 *
453 * \return index to the added item if add was successful, -1 if trying to add a
454 * null item, and index of the item if trying to add an already added item.
455 *
456 * \sa removeCustomItems(), removeCustomItem(), removeCustomItemAt()
457 */
458
459/*!
460 * \qmlmethod void GraphsItem3D::removeCustomItems()
461 *
462 * Removes all custom items. Deletes the resources allocated to them.
463 */
464
465/*!
466 * \qmlmethod void GraphsItem3D::removeCustomItem(Custom3DItem item)
467 *
468 * Removes the custom \a {item}. Deletes the resources allocated to it.
469 */
470
471/*!
472 * \qmlmethod void GraphsItem3D::removeCustomItemAt(vector3d position)
473 *
474 * Removes all custom items at \a {position}. Deletes the resources allocated to them.
475 */
476
477/*!
478 * \qmlmethod void GraphsItem3D::releaseCustomItem(Custom3DItem item)
479 *
480 * Gets ownership of \a item back and removes the \a item from the graph.
481 *
482 * \note If the same item is added back to the graph, the texture file needs to
483 * be re-set.
484 *
485 * \sa Custom3DItem::textureFile
486 */
487
488/*!
489 * \qmlmethod void GraphsItem3D::doPicking(QPoint point)
490 *
491 * Performs picking using view coordinates from \a point
492 * on the elements of the graph, selecting the first item hit.
493 * Default input handling performs this upon receiving the onTapped event.
494 *
495 * \sa selectedElement
496 */
497
498/*!
499 * \qmlmethod void GraphsItem3D::doRayPicking(QVector3D origin, QVector3D direction)
500 *
501 * Performs picking starting from \a origin and in \a direction
502 * on the elements of the graph, selecting the first item hit.
503 *
504 * \sa selectedElement
505 */
506
507/*!
508 * \qmlmethod int GraphsItem3D::selectedLabelIndex()
509 *
510 * Can be used to query the index of the selected label after receiving
511 * \c selectedElementChanged signal with any label type. Selection is valid
512 * until the next \c selectedElementChanged signal.
513 *
514 * \return index of the selected label, or -1.
515 *
516 * \sa selectedElement
517 */
518
519/*!
520 * \qmlmethod Abstract3DAxis GraphsItem3D::selectedAxis()
521 *
522 * Can be used to get the selected axis after receiving \c selectedElementChanged
523 * signal with any label type. Selection is valid until the next
524 * \c selectedElementChanged signal.
525 *
526 * \return the selected axis, or null.
527 *
528 * \sa selectedElement
529 */
530
531/*!
532 * \qmlmethod qsizetype GraphsItem3D::selectedCustomItemIndex()
533 *
534 * Can be used to query the index of the selected custom item after receiving
535 * \c selectedElementChanged signal with
536 * \l{QtGraphs3D::ElementType::CustomItem}{ElementType.CustomItem} type.
537 * Selection is valid until the next \c selectedElementChanged signal.
538 *
539 * \return index of the selected custom item, or -1.
540 *
541 * \sa selectedElement
542 */
543
544/*!
545 * \qmlmethod Custom3DItem GraphsItem3D::selectedCustomItem()
546 *
547 * Can be used to get the selected custom item after receiving
548 * \c selectedElementChanged signal with
549 * \l{QtGraphs3D::ElementType::CustomItem}{ElementType.CustomItem} type.
550 * Ownership of the item remains with the graph.
551 * Selection is valid until the next \c selectedElementChanged signal.
552 *
553 * \return the selected custom item, or null.
554 *
555 * \sa selectedElement
556 */
557
558/*!
559 * \qmlproperty Graphs3D.ElementType GraphsItem3D::selectedElement
560 * \readonly
561 *
562 * The element selected in the graph.
563 *
564 * This property can be used to query the selected element type.
565 * The type is valid until a new selection is made in the graph and the
566 * \c selectedElementChanged signal is emitted.
567 *
568 * The signal can be used for example for implementing customized input
569 * handling, as demonstrated by the \l {Axis Handling} example.
570 *
571 * \sa selectedLabelIndex(), selectedAxis(), selectedCustomItemIndex(),
572 * selectedCustomItem(), Bars3D::selectedSeries, Scatter3D::selectedSeries,
573 * Scene3D::selectionQueryPosition, Graphs3D.ElementType
574 */
575
576/*!
577 * \qmlproperty bool GraphsItem3D::orthoProjection
578 *
579 * If \c {true}, orthographic projection will be used for displaying the graph.
580 * Defaults to \c{false}.
581 * \note Shadows will be disabled when set to \c{true}.
582 */
583
584/*!
585 * \qmlproperty real GraphsItem3D::aspectRatio
586 *
587 * The ratio of the graph scaling between the longest axis on the horizontal
588 * plane and the y-axis. Defaults to \c{2.0}.
589 *
590 * \note Has no effect on Bars3D.
591 *
592 * \sa horizontalAspectRatio
593 */
594
595/*!
596 * \qmlproperty real GraphsItem3D::horizontalAspectRatio
597 *
598 * The ratio of the graph scaling between the x-axis and z-axis.
599 * The value of \c 0.0 indicates automatic scaling according to axis ranges.
600 * Defaults to \c{0.0}.
601 *
602 * \note Has no effect on Bars3D, which handles scaling on the horizontal plane
603 * via the \l{Bars3D::barThickness}{barThickness} and
604 * \l{Bars3D::barSpacing}{barSpacing} properties. Polar graphs also ignore this
605 * property.
606 *
607 * \sa aspectRatio, polar, Bars3D::barThickness, Bars3D::barSpacing
608 */
609
610/*!
611 * \qmlproperty Graphs3D.OptimizationHint GraphsItem3D::optimizationHint
612 *
613 * \brief Specifies whether the default or legacy mode is used for rendering optimization.
614 *
615 * The default mode uses instanced rendering, and provides the full feature set
616 * at the best level of performance on most systems. The static mode optimizes
617 * graph rendering and is ideal for large non-changing data sets. It is slower
618 * with dynamic data changes and item rotations. Selection is not optimized, so
619 * using the static mode with massive data sets is not advisable. Legacy mode
620 * renders all items in th graph individually, without instancing. It should be
621 * used only if default mode does not work, that is the same as if the target
622 * system does not support instancing. Defaults to
623 * \l{QtGraphs3D::OptimizationHint::Default}{Default}.
624 *
625 * \note On some environments, large graphs using static optimization may not
626 * render, because all of the items are rendered using a single draw call, and
627 * different graphics drivers support different maximum vertice counts per call.
628 * This is mostly an issue on 32bit and OpenGL ES2 platforms. To work around
629 * this issue, choose an item mesh with a low vertex count or use the point mesh.
630 *
631 * \sa Abstract3DSeries::mesh, Graphs3D.OptimizationHint
632 */
633
634/*!
635 * \qmlproperty locale GraphsItem3D::locale
636 *
637 * Sets the locale used for formatting various numeric labels.
638 * Defaults to the \c{"C"} locale.
639 *
640 * \sa Value3DAxis::labelFormat
641 */
642
643/*!
644 * \qmlproperty vector3d GraphsItem3D::queriedGraphPosition
645 * \readonly
646 *
647 * This read-only property contains the latest graph position values along each
648 * axis queried using Scene3D::graphPositionQuery. The values are normalized to
649 * range \c{[-1, 1]}. If the queried position was outside the graph bounds, the
650 * values will not reflect the real position, but will instead be some undefined
651 * position outside the range \c{[-1, 1]}. The value will be undefined until a
652 * query is made.
653 *
654 * There is no single correct 3D coordinate to match a particular screen
655 * position, so to be consistent, the queries are always done against the inner
656 * sides of an invisible box surrounding the graph.
657 *
658 * \note Bar graphs only allow querying graph position at the graph floor level,
659 * so the y-value is always zero for bar graphs and valid queries can be only
660 * made at screen positions that contain the floor of the graph.
661 *
662 * \sa Scene3D::graphPositionQuery
663 */
664
665/*!
666 * \qmlproperty real GraphsItem3D::margin
667 *
668 * The absolute value used for the space left between the edge of the
669 * plottable graph area and the edge of the graph background.
670 *
671 * If the margin value is negative, the margins are determined automatically and
672 * can vary according to the size of the items in the series and the type of the
673 * graph. The value is interpreted as a fraction of the y-axis range if the
674 * graph aspect ratios have not been changed from the default values.
675 * Defaults to \c{-1.0}.
676 *
677 * \note Setting a smaller margin for a scatter graph than the automatically
678 * determined margin can cause the scatter items at the edges of the graph to
679 * overlap with the graph background.
680 *
681 * \note On scatter and surface graphs, if the margin is small in comparison to
682 * the axis label size, the positions of the edge labels of the axes are
683 * adjusted to avoid overlap with the edge labels of the neighboring axes.
684 */
685
686/*!
687 * \qmlproperty Graphs3D.GridLineType GraphsItem3D::gridLineType
688 *
689 * Defines whether the grid lines type is \c Graphs3D.GridLineType.Shader or
690 * \c Graphs3D.GridLineType.Geometry.
691 *
692 * This value affects all grid lines.
693 *
694 * \sa Graphs3D.GridLineType
695 */
696
697/*!
698 * \qmlproperty real GraphsItem3D::shadowStrength
699 *
700 * The shadow strength for the whole graph. The higher the number, the darker
701 * the shadows will be. The value must be between \c 0.0 and \c 100.0.
702 *
703 * This value affects the light specified in Scene3D.
704 */
705
706/*!
707 * \qmlproperty real GraphsItem3D::lightStrength
708 *
709 * The specular light strength for the whole graph. The value must be between
710 * \c 0.0 and \c 10.0.
711 *
712 * This value affects the light specified in Scene3D.
713 */
714
715/*!
716 * \qmlproperty real GraphsItem3D::ambientLightStrength
717 *
718 * The ambient light strength for the whole graph. This value determines how
719 * evenly and brightly the colors are shown throughout the graph regardless of
720 * the light position. The value must be between \c 0.0 and \c 1.0.
721 */
722
723/*!
724 * \qmlproperty color GraphsItem3D::lightColor
725 *
726 * The color of the ambient and specular light defined in Scene3D.
727 */
728
729/*!
730 * \qmlsignal GraphsItem3D::tapped(QEventPoint eventPoint, Qt::MouseButton button)
731 *
732 * This signal is emitted when the graph item is tapped once. The \a eventPoint
733 * signal parameter contains information from the release event about the point
734 * that was tapped, and \a button is the \l {Qt::MouseButton}{mouse button} that was clicked,
735 * or \c NoButton on a touchscreen.
736 *
737 * \sa QEventPoint, Qt::MouseButtons, TapHandler::singleTapped
738 */
739
740/*!
741 * \qmlsignal GraphsItem3D::doubleTapped(QEventPoint eventPoint, Qt::MouseButton button)
742 *
743 * This signal is emitted when the graph item is tapped twice within a short span of time.
744 * The \a eventPoint signal parameter contains information from the release event about the
745 * point that was tapped, and \a button is the \l {Qt::MouseButton}{mouse button} that was
746 * clicked, or \c NoButton on a touchscreen.
747 *
748 * \sa QEventPoint, Qt::MouseButtons, TapHandler::doubleTapped
749 */
750
751/*!
752 * \qmlsignal GraphsItem3D::longPressed()
753 *
754 * This signal is emitted when the \c parent Item is pressed and held for a
755 * time period greater than \l TapHandler::longPressThreshold.
756 *
757 * \sa TapHandler::longPressed
758 */
759
760/*!
761 * \qmlsignal GraphsItem3D::dragged(QVector2D delta)
762 *
763 * This signal is emitted when the translation of the cluster of points
764 * on the graph is changed while the pinch gesture is being performed.
765 * The \a delta vector gives the change in translation.
766 *
767 * \sa PinchHandler::translationChanged
768 */
769
770/*!
771 * \qmlsignal GraphsItem3D::wheel(QQuickWheelEvent *event)
772 *
773 * This signal is emitted every time the graph receives an \a event
774 * of type \l QWheelEvent: that is, every time the wheel is moved or the
775 * scrolling gesture is updated.
776 *
777 * \sa WheelEvent, WheelHandler::wheel
778 */
779
780/*!
781 * \qmlsignal GraphsItem3D::pinch(qreal delta)
782 *
783 * This signal is emitted when the scale factor on the graph
784 * changes while the pinch gesture is being performed.
785 * The \a delta value gives the multiplicative change in scale.
786 *
787 * \sa PinchHandler::scaleChanged
788 */
789
790/*!
791 * \qmlsignal GraphsItem3D::mouseMove(QPoint mousePos)
792 *
793 * This signal is emitted when the graph receives a mouseMove event.
794 * \a mousePos value gives the position of mouse while mouse is moving.
795 *
796 * \sa QQuickItem::mouseMoveEvent
797 */
798
799QQuickGraphsItem::QQuickGraphsItem(QQuickItem *parent)
800 : QQuick3DViewport(parent)
801 , m_locale(QLocale::c())
802{
803 Q_TRACE(QGraphs3DItemCtor_entry);
804 if (!m_scene)
805 m_scene = new Q3DScene;
806 m_scene->setParent(this);
807
808 m_qml = this;
809
810 // Set initial theme
811 QGraphsTheme *theme = new QGraphsTheme(m_scene);
812 setTheme(theme);
813 QGraphsLine grid = theme->grid();
814 grid.setMainWidth(0.25);
815 theme->setGrid(grid);
816 m_themes.append(t: theme);
817
818 m_scene->d_func()->setViewport(boundingRect().toRect());
819
820 connect(sender: m_scene, signal: &Q3DScene::needRender, context: this, slot: &QQuickGraphsItem::emitNeedRender);
821 connect(sender: m_scene,
822 signal: &Q3DScene::graphPositionQueryChanged,
823 context: this,
824 slot: &QQuickGraphsItem::handleQueryPositionChanged);
825 connect(sender: m_scene, signal: &Q3DScene::primarySubViewportChanged,
826 context: this,
827 slot: &QQuickGraphsItem::handlePrimarySubViewportChanged);
828 connect(sender: m_scene, signal: &Q3DScene::secondarySubViewportChanged,
829 context: this,
830 slot: &QQuickGraphsItem::handleSecondarySubViewportChanged);
831
832 m_nodeMutex = QSharedPointer<QMutex>::create();
833
834 QQuick3DSceneEnvironment *scene = environment();
835 scene->setBackgroundMode(QQuick3DSceneEnvironment::QQuick3DEnvironmentBackgroundTypes::Color);
836 scene->setClearColor(Qt::transparent);
837
838 auto sceneManager = QQuick3DObjectPrivate::get(item: rootNode())->sceneManager;
839 connect(sender: sceneManager.data(),
840 signal: &QQuick3DSceneManager::windowChanged,
841 context: this,
842 slot: &QQuickGraphsItem::handleWindowChanged);
843 // Set contents to false in case we are in qml designer to make component look
844 // nice
845 m_runningInDesigner = QGuiApplication::applicationDisplayName() == QLatin1String("Qml2Puppet");
846 setFlag(flag: ItemHasContents /*, !m_runningInDesigner*/); // Is this relevant anymore?
847
848 // Set 4x MSAA by default
849 setRenderingMode(QtGraphs3D::RenderingMode::Indirect);
850 setMsaaSamples(4);
851
852 setTransparencyTechnique(QtGraphs3D::TransparencyTechnique::Default);
853
854 // Accept touchevents
855 setAcceptTouchEvents(true);
856
857 m_inputHandler = new QGraphsInputHandler(this);
858 m_inputHandler->bindableHeight().setBinding(f: [&] { return height(); });
859 m_inputHandler->bindableWidth().setBinding(f: [&] { return width(); });
860 Q_TRACE(QGraphs3DItemCtor_exit);
861}
862
863QQuickGraphsItem::~QQuickGraphsItem()
864{
865 disconnect(sender: this, signal: 0, receiver: this, member: 0);
866 checkWindowList(window: 0);
867
868 m_repeaterX->model().clear();
869 m_repeaterY->model().clear();
870 m_repeaterZ->model().clear();
871 m_repeaterX->deleteLater();
872 m_repeaterY->deleteLater();
873 m_repeaterZ->deleteLater();
874
875 delete m_gridGeometryModel;
876 delete m_subgridGeometryModel;
877 delete m_sliceGridGeometryModel;
878
879 // Make sure not deleting locked mutex
880 QMutexLocker locker(&m_mutex);
881 locker.unlock();
882
883 m_nodeMutex.clear();
884}
885
886void QQuickGraphsItem::handleAxisTitleChanged(const QString &title)
887{
888 Q_UNUSED(title);
889 handleAxisTitleChangedBySender(sender: sender());
890}
891
892void QQuickGraphsItem::handleAxisTitleChangedBySender(QObject *sender)
893{
894 if (sender == m_axisX)
895 m_changeTracker.axisXTitleChanged = true;
896 else if (sender == m_axisY)
897 m_changeTracker.axisYTitleChanged = true;
898 else if (sender == m_axisZ)
899 m_changeTracker.axisZTitleChanged = true;
900 else
901 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
902 qUtf16Printable(QString::fromUtf8(__func__)));
903
904 markSeriesItemLabelsDirty();
905 emitNeedRender();
906}
907
908void QQuickGraphsItem::handleAxisLabelsChanged()
909{
910 handleAxisLabelsChangedBySender(sender: sender());
911}
912
913void QQuickGraphsItem::handleAxisLabelsChangedBySender(QObject *sender)
914{
915 if (sender == m_axisX)
916 m_changeTracker.axisXLabelsChanged = true;
917 else if (sender == m_axisY)
918 m_changeTracker.axisYLabelsChanged = true;
919 else if (sender == m_axisZ)
920 m_changeTracker.axisZLabelsChanged = true;
921 else
922 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
923 qUtf16Printable(QString::fromUtf8(__func__)));
924
925 markSeriesItemLabelsDirty();
926 emitNeedRender();
927}
928
929void QQuickGraphsItem::handleAxisRangeChanged(float min, float max)
930{
931 Q_UNUSED(min);
932 Q_UNUSED(max);
933 handleAxisRangeChangedBySender(sender: sender());
934}
935
936void QQuickGraphsItem::handleAxisRangeChangedBySender(QObject *sender)
937{
938 if (sender == m_axisX) {
939 m_isSeriesVisualsDirty = true;
940 m_changeTracker.axisXRangeChanged = true;
941 } else if (sender == m_axisY) {
942 m_isSeriesVisualsDirty = true;
943 m_changeTracker.axisYRangeChanged = true;
944 } else if (sender == m_axisZ) {
945 m_isSeriesVisualsDirty = true;
946 m_changeTracker.axisZRangeChanged = true;
947 } else {
948 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
949 qUtf16Printable(QString::fromUtf8(__func__)));
950 }
951 emitNeedRender();
952}
953
954void QQuickGraphsItem::handleAxisSegmentCountChanged(qsizetype count)
955{
956 Q_UNUSED(count);
957 handleAxisSegmentCountChangedBySender(sender: sender());
958}
959
960void QQuickGraphsItem::handleAxisSegmentCountChangedBySender(QObject *sender)
961{
962 if (sender == m_axisX)
963 m_changeTracker.axisXSegmentCountChanged = true;
964 else if (sender == m_axisY)
965 m_changeTracker.axisYSegmentCountChanged = true;
966 else if (sender == m_axisZ)
967 m_changeTracker.axisZSegmentCountChanged = true;
968 else
969 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
970 qUtf16Printable(QString::fromUtf8(__func__)));
971 emitNeedRender();
972}
973
974void QQuickGraphsItem::handleAxisSubSegmentCountChanged(qsizetype count)
975{
976 Q_UNUSED(count);
977 handleAxisSubSegmentCountChangedBySender(sender: sender());
978}
979
980void QQuickGraphsItem::handleAxisSubSegmentCountChangedBySender(QObject *sender)
981{
982 if (sender == m_axisX)
983 m_changeTracker.axisXSubSegmentCountChanged = true;
984 else if (sender == m_axisY)
985 m_changeTracker.axisYSubSegmentCountChanged = true;
986 else if (sender == m_axisZ)
987 m_changeTracker.axisZSubSegmentCountChanged = true;
988 else
989 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
990 qUtf16Printable(QString::fromUtf8(__func__)));
991 emitNeedRender();
992}
993
994void QQuickGraphsItem::handleAxisAutoAdjustRangeChanged(bool autoAdjust)
995{
996 QObject *sender = QObject::sender();
997 if (sender != m_axisX && sender != m_axisY && sender != m_axisZ)
998 return;
999
1000 QAbstract3DAxis *axis = static_cast<QAbstract3DAxis *>(sender);
1001 handleAxisAutoAdjustRangeChangedInOrientation(orientation: axis->orientation(), autoAdjust);
1002}
1003
1004void QQuickGraphsItem::handleAxisLabelFormatChanged(const QString &format)
1005{
1006 Q_UNUSED(format);
1007 handleAxisLabelFormatChangedBySender(sender: sender());
1008}
1009
1010void QQuickGraphsItem::handleAxisReversedChanged(bool enable)
1011{
1012 Q_UNUSED(enable);
1013 handleAxisReversedChangedBySender(sender: sender());
1014}
1015
1016void QQuickGraphsItem::handleAxisFormatterDirty()
1017{
1018 handleAxisFormatterDirtyBySender(sender: sender());
1019}
1020
1021void QQuickGraphsItem::handleAxisLabelAutoRotationChanged(float angle)
1022{
1023 Q_UNUSED(angle);
1024 handleAxisLabelAutoRotationChangedBySender(sender: sender());
1025}
1026
1027void QQuickGraphsItem::handleAxisScaleLabelsByCountChanged(bool adjust)
1028{
1029 Q_UNUSED(adjust);
1030 handleAxisScaleLabelsByCountChangedBySender(sender: sender());
1031}
1032
1033void QQuickGraphsItem::handleAxisLabelSizeChanged(qreal size)
1034{
1035 Q_UNUSED(size);
1036 handleAxisLabelSizeChangedBySender(sender: sender());
1037}
1038
1039void QQuickGraphsItem::handleAxisTitleVisibilityChanged(bool visible)
1040{
1041 Q_UNUSED(visible);
1042 handleAxisTitleVisibilityChangedBySender(sender: sender());
1043}
1044
1045void QQuickGraphsItem::handleAxisLabelVisibilityChanged(bool visible)
1046{
1047 Q_UNUSED(visible);
1048 handleAxisLabelVisibilityChangedBySender(sender: sender());
1049}
1050
1051void QQuickGraphsItem::handleAxisTitleFixedChanged(bool fixed)
1052{
1053 Q_UNUSED(fixed);
1054 handleAxisTitleFixedChangedBySender(sender: sender());
1055}
1056
1057void QQuickGraphsItem::handleAxisTitleOffsetChanged(float offset)
1058{
1059 Q_UNUSED(offset);
1060 handleAxisTitleFixedChangedBySender(sender: sender());
1061}
1062
1063void QQuickGraphsItem::handleInputPositionChanged(QPoint position)
1064{
1065 Q_UNUSED(position);
1066 emitNeedRender();
1067}
1068
1069void QQuickGraphsItem::handleSeriesVisibilityChanged(bool visible)
1070{
1071 Q_UNUSED(visible);
1072
1073 handleSeriesVisibilityChangedBySender(sender: sender());
1074}
1075
1076void QQuickGraphsItem::handleRequestShadowQuality(QtGraphs3D::ShadowQuality quality)
1077{
1078 setShadowQuality(quality);
1079}
1080
1081void QQuickGraphsItem::handleQueryPositionChanged(QPoint position)
1082{
1083 QVector3D data = graphPositionAt(point: position);
1084 setGraphPositionQueryPending(false);
1085 setQueriedGraphPosition(data);
1086 emit queriedGraphPositionChanged(data);
1087}
1088
1089void QQuickGraphsItem::handlePrimarySubViewportChanged(const QRect rect)
1090{
1091 m_primarySubView = rect;
1092 updateSubViews();
1093}
1094
1095void QQuickGraphsItem::handleSecondarySubViewportChanged(const QRect rect)
1096{
1097 m_secondarySubView = rect;
1098 updateSubViews();
1099}
1100
1101void QQuickGraphsItem::handleAxisLabelFormatChangedBySender(QObject *sender)
1102{
1103 // Label format changing needs to dirty the data so that labels are reset.
1104 if (sender == m_axisX) {
1105 m_isDataDirty = true;
1106 m_changeTracker.axisXLabelFormatChanged = true;
1107 } else if (sender == m_axisY) {
1108 m_isDataDirty = true;
1109 m_changeTracker.axisYLabelFormatChanged = true;
1110 } else if (sender == m_axisZ) {
1111 m_isDataDirty = true;
1112 m_changeTracker.axisZLabelFormatChanged = true;
1113 } else {
1114 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
1115 qUtf16Printable(QString::fromUtf8(__func__)));
1116 }
1117 emitNeedRender();
1118}
1119
1120void QQuickGraphsItem::handleAxisReversedChangedBySender(QObject *sender)
1121{
1122 // Reversing change needs to dirty the data so item positions are recalculated
1123 if (sender == m_axisX) {
1124 m_isDataDirty = true;
1125 m_changeTracker.axisXReversedChanged = true;
1126 } else if (sender == m_axisY) {
1127 m_isDataDirty = true;
1128 m_changeTracker.axisYReversedChanged = true;
1129 } else if (sender == m_axisZ) {
1130 m_isDataDirty = true;
1131 m_changeTracker.axisZReversedChanged = true;
1132 } else {
1133 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
1134 qUtf16Printable(QString::fromUtf8(__func__)));
1135 }
1136 emitNeedRender();
1137}
1138
1139void QQuickGraphsItem::handleAxisFormatterDirtyBySender(QObject *sender)
1140{
1141 // Sender is QValue3DAxisPrivate
1142 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(sender);
1143 if (valueAxis == m_axisX) {
1144 m_isDataDirty = true;
1145 m_changeTracker.axisXFormatterChanged = true;
1146 } else if (valueAxis == m_axisY) {
1147 m_isDataDirty = true;
1148 m_changeTracker.axisYFormatterChanged = true;
1149 } else if (valueAxis == m_axisZ) {
1150 m_isDataDirty = true;
1151 m_changeTracker.axisZFormatterChanged = true;
1152 } else {
1153 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
1154 qUtf16Printable(QString::fromUtf8(__func__)));
1155 }
1156 emitNeedRender();
1157}
1158
1159void QQuickGraphsItem::handleAxisLabelAutoRotationChangedBySender(QObject *sender)
1160{
1161 if (sender == m_axisX)
1162 m_changeTracker.axisXLabelAutoRotationChanged = true;
1163 else if (sender == m_axisY)
1164 m_changeTracker.axisYLabelAutoRotationChanged = true;
1165 else if (sender == m_axisZ)
1166 m_changeTracker.axisZLabelAutoRotationChanged = true;
1167 else
1168 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
1169 qUtf16Printable(QString::fromUtf8(__func__)));
1170
1171 emitNeedRender();
1172}
1173
1174void QQuickGraphsItem::handleAxisScaleLabelsByCountChangedBySender(QObject *sender)
1175{
1176 if (sender == m_axisX)
1177 m_changeTracker.axisXScaleLabelsByCountChanged = true;
1178 else if (sender == m_axisY)
1179 m_changeTracker.axisYScaleLabelsByCountChanged = true;
1180 else if (sender == m_axisZ)
1181 m_changeTracker.axisZScaleLabelsByCountChanged = true;
1182 else
1183 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
1184 qUtf16Printable(QString::fromUtf8(__func__)));
1185
1186 emitNeedRender();
1187}
1188
1189void QQuickGraphsItem::handleAxisLabelSizeChangedBySender(QObject *sender)
1190{
1191 if (sender == m_axisX)
1192 m_changeTracker.axisXLabelSizeChanged = true;
1193 else if (sender == m_axisY)
1194 m_changeTracker.axisYLabelSizeChanged = true;
1195 else if (sender == m_axisZ)
1196 m_changeTracker.axisZLabelSizeChanged = true;
1197 else
1198 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
1199 qUtf16Printable(QString::fromUtf8(__func__)));
1200
1201 emitNeedRender();
1202}
1203
1204void QQuickGraphsItem::handleAxisTitleVisibilityChangedBySender(QObject *sender)
1205{
1206 if (sender == m_axisX)
1207 m_changeTracker.axisXTitleVisibilityChanged = true;
1208 else if (sender == m_axisY)
1209 m_changeTracker.axisYTitleVisibilityChanged = true;
1210 else if (sender == m_axisZ)
1211 m_changeTracker.axisZTitleVisibilityChanged = true;
1212 else
1213 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
1214 qUtf16Printable(QString::fromUtf8(__func__)));
1215
1216 emitNeedRender();
1217}
1218
1219void QQuickGraphsItem::handleAxisLabelVisibilityChangedBySender(QObject *sender)
1220{
1221 if (sender == m_axisX)
1222 m_changeTracker.axisXLabelVisibilityChanged = true;
1223 else if (sender == m_axisY)
1224 m_changeTracker.axisYLabelVisibilityChanged = true;
1225 else if (sender == m_axisZ)
1226 m_changeTracker.axisZLabelVisibilityChanged = true;
1227 else
1228 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
1229 qUtf16Printable(QString::fromUtf8(__func__)));
1230
1231 emitNeedRender();
1232}
1233
1234void QQuickGraphsItem::handleAxisTitleFixedChangedBySender(QObject *sender)
1235{
1236 if (sender == m_axisX)
1237 m_changeTracker.axisXTitleFixedChanged = true;
1238 else if (sender == m_axisY)
1239 m_changeTracker.axisYTitleFixedChanged = true;
1240 else if (sender == m_axisZ)
1241 m_changeTracker.axisZTitleFixedChanged = true;
1242 else
1243 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
1244 qUtf16Printable(QString::fromUtf8(__func__)));
1245
1246 emitNeedRender();
1247}
1248
1249void QQuickGraphsItem::handleAxisTitleOffsetChangedBySender(QObject *sender)
1250{
1251 if (sender == m_axisX)
1252 m_changeTracker.axisXTitleOffsetChanged = true;
1253 else if (sender == m_axisY)
1254 m_changeTracker.axisYTitleOffsetChanged = true;
1255 else if (sender == m_axisZ)
1256 m_changeTracker.axisZTitleOffsetChanged = true;
1257 else
1258 qCWarning(lcGraphs3D, "%ls invoked for invalid axis",
1259 qUtf16Printable(QString::fromUtf8(__func__)));
1260
1261 emitNeedRender();
1262}
1263
1264void QQuickGraphsItem::handleSeriesVisibilityChangedBySender(QObject *sender)
1265{
1266 QAbstract3DSeries *series = static_cast<QAbstract3DSeries *>(sender);
1267 series->d_func()->m_changeTracker.visibilityChanged = true;
1268
1269 m_isDataDirty = true;
1270 m_isSeriesVisualsDirty = true;
1271
1272 adjustAxisRanges();
1273
1274 emitNeedRender();
1275}
1276
1277void QQuickGraphsItem::markDataDirty()
1278{
1279 m_isDataDirty = true;
1280
1281 markSeriesItemLabelsDirty();
1282 emitNeedRender();
1283}
1284
1285void QQuickGraphsItem::markSeriesVisualsDirty()
1286{
1287 m_isSeriesVisualsDirty = true;
1288 emitNeedRender();
1289}
1290
1291void QQuickGraphsItem::markSeriesItemLabelsDirty()
1292{
1293 for (int i = 0; i < m_seriesList.size(); i++)
1294 m_seriesList.at(i)->d_func()->markItemLabelDirty();
1295}
1296
1297QAbstract3DAxis *QQuickGraphsItem::createDefaultAxis(QAbstract3DAxis::AxisOrientation orientation)
1298{
1299 Q_UNUSED(orientation);
1300
1301 // The default default axis is a value axis. If the graph type has a different
1302 // default axis for some orientation, this function needs to be overridden.
1303 QAbstract3DAxis *defaultAxis = createDefaultValueAxis();
1304 return defaultAxis;
1305}
1306
1307QValue3DAxis *QQuickGraphsItem::createDefaultValueAxis()
1308{
1309 // Default value axis has single segment, empty label format, and auto scaling
1310 QValue3DAxis *defaultAxis = new QValue3DAxis;
1311 defaultAxis->d_func()->setDefaultAxis(true);
1312
1313 return defaultAxis;
1314}
1315
1316QCategory3DAxis *QQuickGraphsItem::createDefaultCategoryAxis()
1317{
1318 // Default category axis has no labels
1319 QCategory3DAxis *defaultAxis = new QCategory3DAxis;
1320 defaultAxis->d_func()->setDefaultAxis(true);
1321 return defaultAxis;
1322}
1323
1324void QQuickGraphsItem::setAxisHelper(QAbstract3DAxis::AxisOrientation orientation,
1325 QAbstract3DAxis *axis,
1326 QAbstract3DAxis **axisPtr)
1327{
1328 // Setting null axis indicates using default axis
1329 if (!axis)
1330 axis = createDefaultAxis(orientation);
1331
1332 // If old axis is default axis, delete it
1333 QAbstract3DAxis *oldAxis = *axisPtr;
1334 if (oldAxis) {
1335 if (oldAxis->d_func()->isDefaultAxis()) {
1336 m_axes.removeAll(t: oldAxis);
1337 delete oldAxis;
1338 oldAxis = 0;
1339 } else {
1340 // Disconnect the old axis from use
1341 QObject::disconnect(sender: oldAxis, signal: 0, receiver: this, member: 0);
1342 oldAxis->d_func()->setOrientation(QAbstract3DAxis::AxisOrientation::None);
1343 }
1344 }
1345
1346 // Assume ownership
1347 addAxis(axis);
1348
1349 // Connect the new axis
1350 *axisPtr = axis;
1351
1352 axis->d_func()->setOrientation(orientation);
1353
1354 QObject::connect(sender: axis,
1355 signal: &QAbstract3DAxis::titleChanged,
1356 context: this,
1357 slot: &QQuickGraphsItem::handleAxisTitleChanged);
1358 QObject::connect(sender: axis,
1359 signal: &QAbstract3DAxis::labelsChanged,
1360 context: this,
1361 slot: &QQuickGraphsItem::handleAxisLabelsChanged);
1362 QObject::connect(sender: axis,
1363 signal: &QAbstract3DAxis::rangeChanged,
1364 context: this,
1365 slot: &QQuickGraphsItem::handleAxisRangeChanged);
1366 QObject::connect(sender: axis,
1367 signal: &QAbstract3DAxis::autoAdjustRangeChanged,
1368 context: this,
1369 slot: &QQuickGraphsItem::handleAxisAutoAdjustRangeChanged);
1370 QObject::connect(sender: axis,
1371 signal: &QAbstract3DAxis::labelAutoAngleChanged,
1372 context: this,
1373 slot: &QQuickGraphsItem::handleAxisLabelAutoRotationChanged);
1374 QObject::connect(sender: axis,
1375 signal: &QAbstract3DAxis::labelAutoAngleChanged,
1376 context: this,
1377 slot: &QQuickGraphsItem::handleAxisLabelAutoRotationChanged);
1378 QObject::connect(sender: axis,
1379 signal: &QAbstract3DAxis::scaleLabelsByCountChanged,
1380 context: this,
1381 slot: &QQuickGraphsItem::handleAxisScaleLabelsByCountChanged);
1382 QObject::connect(sender: axis,
1383 signal: &QAbstract3DAxis::labelSizeChanged,
1384 context: this,
1385 slot: &QQuickGraphsItem::handleAxisLabelSizeChanged);
1386 QObject::connect(sender: axis,
1387 signal: &QAbstract3DAxis::titleVisibleChanged,
1388 context: this,
1389 slot: &QQuickGraphsItem::handleAxisTitleVisibilityChanged);
1390 QObject::connect(sender: axis,
1391 signal: &QAbstract3DAxis::labelVisibleChanged,
1392 context: this,
1393 slot: &QQuickGraphsItem::handleAxisLabelVisibilityChanged);
1394 QObject::connect(sender: axis,
1395 signal: &QAbstract3DAxis::titleFixedChanged,
1396 context: this,
1397 slot: &QQuickGraphsItem::handleAxisTitleFixedChanged);
1398 QObject::connect(sender: axis,
1399 signal: &QAbstract3DAxis::titleOffsetChanged,
1400 context: this,
1401 slot: &QQuickGraphsItem::handleAxisTitleOffsetChanged);
1402
1403 if (orientation == QAbstract3DAxis::AxisOrientation::X)
1404 m_changeTracker.axisXTypeChanged = true;
1405 else if (orientation == QAbstract3DAxis::AxisOrientation::Y)
1406 m_changeTracker.axisYTypeChanged = true;
1407 else if (orientation == QAbstract3DAxis::AxisOrientation::Z)
1408 m_changeTracker.axisZTypeChanged = true;
1409
1410 handleAxisTitleChangedBySender(sender: axis);
1411 handleAxisLabelsChangedBySender(sender: axis);
1412 handleAxisRangeChangedBySender(sender: axis);
1413 handleAxisAutoAdjustRangeChangedInOrientation(orientation: axis->orientation(), autoAdjust: axis->isAutoAdjustRange());
1414 handleAxisLabelAutoRotationChangedBySender(sender: axis);
1415 handleAxisTitleVisibilityChangedBySender(sender: axis);
1416 handleAxisLabelVisibilityChangedBySender(sender: axis);
1417 handleAxisTitleFixedChangedBySender(sender: axis);
1418 handleAxisTitleOffsetChangedBySender(sender: axis);
1419
1420 if (axis->type() == QAbstract3DAxis::AxisType::Value) {
1421 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(axis);
1422 QObject::connect(sender: valueAxis,
1423 signal: &QValue3DAxis::segmentCountChanged,
1424 context: this,
1425 slot: &QQuickGraphsItem::handleAxisSegmentCountChanged);
1426 QObject::connect(sender: valueAxis,
1427 signal: &QValue3DAxis::subSegmentCountChanged,
1428 context: this,
1429 slot: &QQuickGraphsItem::handleAxisSubSegmentCountChanged);
1430 QObject::connect(sender: valueAxis,
1431 signal: &QValue3DAxis::labelFormatChanged,
1432 context: this,
1433 slot: &QQuickGraphsItem::handleAxisLabelFormatChanged);
1434 QObject::connect(sender: valueAxis,
1435 signal: &QValue3DAxis::reversedChanged,
1436 context: this,
1437 slot: &QQuickGraphsItem::handleAxisReversedChanged);
1438 // TODO: Handle this somehow (add API to QValue3DAxis?)
1439 // QObject::connect(valueAxis->d_func(), &QValue3DAxisPrivate::formatterDirty,
1440 // this, &Abstract3DController::handleAxisFormatterDirty);
1441
1442 handleAxisSegmentCountChangedBySender(sender: valueAxis);
1443 handleAxisSubSegmentCountChangedBySender(sender: valueAxis);
1444 handleAxisLabelFormatChangedBySender(sender: valueAxis);
1445 handleAxisReversedChangedBySender(sender: valueAxis);
1446 // TODO: Handle this somehow (add API to QValue3DAxis?)
1447 // handleAxisFormatterDirtyBySender(valueAxis->d_func());
1448
1449 valueAxis->formatter()->setLocale(m_locale);
1450 }
1451}
1452
1453void QQuickGraphsItem::startRecordingRemovesAndInserts()
1454{
1455 // Default implementation does nothing
1456}
1457
1458int QQuickGraphsItem::horizontalFlipFactor() const
1459{
1460 return m_horizontalFlipFactor;
1461}
1462
1463void QQuickGraphsItem::setHorizontalFlipFactor(int newHorizontalFlipFactor)
1464{
1465 m_gridUpdate = true;
1466 m_horizontalFlipFactor = newHorizontalFlipFactor;
1467}
1468
1469void QQuickGraphsItem::emitNeedRender()
1470{
1471 if (!m_renderPending) {
1472 emit needRender();
1473 m_renderPending = true;
1474 }
1475}
1476
1477void QQuickGraphsItem::handleThemeColorStyleChanged(QGraphsTheme::ColorStyle style)
1478{
1479 // Set value for series that have not explicitly set this value
1480 for (QAbstract3DSeries *series : std::as_const(t&: m_seriesList)) {
1481 if (!series->d_func()->m_themeTracker.colorStyleOverride) {
1482 series->setColorStyle(style);
1483 series->d_func()->m_themeTracker.colorStyleOverride = false;
1484 }
1485 }
1486 theme()->dirtyBits()->colorStyleDirty = false;
1487 markSeriesVisualsDirty();
1488}
1489
1490void QQuickGraphsItem::handleThemeBaseColorsChanged(const QList<QColor> &colors)
1491{
1492 int colorIdx = 0;
1493 // Set value for series that have not explicitly set this value
1494 if (!colors.size())
1495 return;
1496
1497 for (QAbstract3DSeries *series : std::as_const(t&: m_seriesList)) {
1498 if (!series->d_func()->m_themeTracker.baseColorOverride) {
1499 series->setBaseColor(colors.at(i: colorIdx));
1500 series->d_func()->m_themeTracker.baseColorOverride = false;
1501 }
1502 if (++colorIdx >= colors.size())
1503 colorIdx = 0;
1504 }
1505
1506 theme()->dirtyBits()->seriesColorsDirty = false;
1507 markSeriesVisualsDirty();
1508}
1509
1510void QQuickGraphsItem::handleThemeBaseGradientsChanged(const QList<QLinearGradient> &gradients)
1511{
1512 int gradientIdx = 0;
1513 // Set value for series that have not explicitly set this value
1514 for (QAbstract3DSeries *series : std::as_const(t&: m_seriesList)) {
1515 if (!series->d_func()->m_themeTracker.baseGradientOverride) {
1516 series->setBaseGradient(gradients.at(i: gradientIdx));
1517 series->d_func()->m_themeTracker.baseGradientOverride = false;
1518 }
1519 if (++gradientIdx >= gradients.size())
1520 gradientIdx = 0;
1521 }
1522 theme()->dirtyBits()->seriesGradientDirty = false;
1523 markSeriesVisualsDirty();
1524}
1525
1526void QQuickGraphsItem::handleThemeSingleHighlightColorChanged(QColor color)
1527{
1528 // Set value for series that have not explicitly set this value
1529 for (QAbstract3DSeries *series : std::as_const(t&: m_seriesList)) {
1530 if (!series->d_func()->m_themeTracker.singleHighlightColorOverride) {
1531 series->setSingleHighlightColor(color);
1532 series->d_func()->m_themeTracker.singleHighlightColorOverride = false;
1533 }
1534 }
1535 markSeriesVisualsDirty();
1536}
1537
1538void QQuickGraphsItem::handleThemeSingleHighlightGradientChanged(const QLinearGradient &gradient)
1539{
1540 // Set value for series that have not explicitly set this value
1541 for (QAbstract3DSeries *series : std::as_const(t&: m_seriesList)) {
1542 if (!series->d_func()->m_themeTracker.singleHighlightGradientOverride) {
1543 series->setSingleHighlightGradient(gradient);
1544 series->d_func()->m_themeTracker.singleHighlightGradientOverride = false;
1545 }
1546 }
1547 markSeriesVisualsDirty();
1548}
1549
1550void QQuickGraphsItem::handleThemeMultiHighlightColorChanged(QColor color)
1551{
1552 // Set value for series that have not explicitly set this value
1553 for (QAbstract3DSeries *series : std::as_const(t&: m_seriesList)) {
1554 if (!series->d_func()->m_themeTracker.multiHighlightColorOverride) {
1555 series->setMultiHighlightColor(color);
1556 series->d_func()->m_themeTracker.multiHighlightColorOverride = false;
1557 }
1558 }
1559 markSeriesVisualsDirty();
1560}
1561
1562void QQuickGraphsItem::handleThemeMultiHighlightGradientChanged(const QLinearGradient &gradient)
1563{
1564 // Set value for series that have not explicitly set this value
1565 for (QAbstract3DSeries *series : std::as_const(t&: m_seriesList)) {
1566 if (!series->d_func()->m_themeTracker.multiHighlightGradientOverride) {
1567 series->setMultiHighlightGradient(gradient);
1568 series->d_func()->m_themeTracker.multiHighlightGradientOverride = false;
1569 }
1570 }
1571 markSeriesVisualsDirty();
1572}
1573
1574void QQuickGraphsItem::handleThemeTypeChanged(QGraphsTheme::Theme theme)
1575{
1576 Q_UNUSED(theme);
1577
1578 // Changing theme type is logically equivalent of changing the entire theme
1579 // object, so reset all attached series to the new theme.
1580 bool force = m_qml->isReady();
1581 QGraphsTheme *activeTheme = this->theme();
1582 for (int i = 0; i < m_seriesList.size(); i++)
1583 m_seriesList.at(i)->d_func()->resetToTheme(theme: *activeTheme, seriesIndex: i, force);
1584
1585 markSeriesVisualsDirty();
1586
1587 emit themeTypeChanged();
1588}
1589
1590void QQuickGraphsItem::addSeriesInternal(QAbstract3DSeries *series)
1591{
1592 insertSeries(index: m_seriesList.size(), series);
1593}
1594
1595void QQuickGraphsItem::insertSeries(qsizetype index, QAbstract3DSeries *series)
1596{
1597 if (series) {
1598 if (m_seriesList.contains(t: series)) {
1599 qsizetype oldIndex = m_seriesList.indexOf(t: series);
1600 if (index != oldIndex) {
1601 m_seriesList.removeOne(t: series);
1602 if (oldIndex < index)
1603 index--;
1604 m_seriesList.insert(i: index, t: series);
1605 qCDebug(lcSeries3D) << __FUNCTION__
1606 << series << "already exists at index of:" << oldIndex
1607 << "removing it and inserting to index of:" << index;
1608 }
1609 } else {
1610 qsizetype oldSize = m_seriesList.size();
1611 m_seriesList.insert(i: index, t: series);
1612 series->d_func()->setGraph(this);
1613 QObject::connect(sender: series,
1614 signal: &QAbstract3DSeries::visibleChanged,
1615 context: this,
1616 slot: &QQuickGraphsItem::handleSeriesVisibilityChanged);
1617 QObject::connect(sender: series,
1618 signal: &QAbstract3DSeries::lightingModeChanged,
1619 context: this,
1620 slot: &QQuickGraphsItem::handleLightingModeChanged);
1621 series->d_func()->resetToTheme(theme: *theme(), seriesIndex: oldSize, force: false);
1622 qCDebug(lcSeries3D) << __FUNCTION__
1623 << "insert" << series << "at index of:" << index;
1624 }
1625 if (series->isVisible())
1626 handleSeriesVisibilityChangedBySender(sender: series);
1627 }
1628}
1629
1630void QQuickGraphsItem::removeSeriesInternal(QAbstract3DSeries *series)
1631{
1632 if (series && series->d_func()->m_graph == this) {
1633 m_seriesList.removeAll(t: series);
1634 QObject::disconnect(sender: series,
1635 signal: &QAbstract3DSeries::visibleChanged,
1636 receiver: this,
1637 slot: &QQuickGraphsItem::handleSeriesVisibilityChanged);
1638 QObject::disconnect(sender: series,
1639 signal: &QAbstract3DSeries::lightingModeChanged,
1640 receiver: this,
1641 slot: &QQuickGraphsItem::handleLightingModeChanged);
1642 series->d_func()->setGraph(0);
1643 m_isDataDirty = true;
1644 m_isSeriesVisualsDirty = true;
1645 qCDebug(lcSeries3D) << __FUNCTION__ << "removed" << series << "from seriesList";
1646 emitNeedRender();
1647 }
1648}
1649
1650QList<QAbstract3DSeries *> QQuickGraphsItem::seriesList()
1651{
1652 return m_seriesList;
1653}
1654
1655void QQuickGraphsItem::setAxisX(QAbstract3DAxis *axis)
1656{
1657 // Setting null axis will always create new default axis
1658 if (!axis || axis != m_axisX) {
1659 setAxisHelper(orientation: QAbstract3DAxis::AxisOrientation::X, axis, axisPtr: &m_axisX);
1660 emit axisXChanged(axis: m_axisX);
1661 } else {
1662 qCDebug(lcProperties3D) << __FUNCTION__
1663 << "value is already set to:" << axis;
1664 }
1665}
1666
1667QAbstract3DAxis *QQuickGraphsItem::axisX() const
1668{
1669 return m_axisX;
1670}
1671
1672void QQuickGraphsItem::setAxisY(QAbstract3DAxis *axis)
1673{
1674 // Setting null axis will always create new default axis
1675 if (!axis || axis != m_axisY) {
1676 setAxisHelper(orientation: QAbstract3DAxis::AxisOrientation::Y, axis, axisPtr: &m_axisY);
1677 emit axisYChanged(axis: m_axisY);
1678 } else {
1679 qCDebug(lcProperties3D) << __FUNCTION__
1680 << "value is already set to:" << axis;
1681 }
1682}
1683
1684QAbstract3DAxis *QQuickGraphsItem::axisY() const
1685{
1686 return m_axisY;
1687}
1688
1689void QQuickGraphsItem::setAxisZ(QAbstract3DAxis *axis)
1690{
1691 // Setting null axis will always create new default axis
1692 if (!axis || axis != m_axisZ) {
1693 setAxisHelper(orientation: QAbstract3DAxis::AxisOrientation::Z, axis, axisPtr: &m_axisZ);
1694 emit axisZChanged(axis: m_axisZ);
1695 } else {
1696 qCDebug(lcProperties3D) << __FUNCTION__
1697 << "value is already set to:" << axis;
1698 }
1699}
1700
1701QAbstract3DAxis *QQuickGraphsItem::axisZ() const
1702{
1703 return m_axisZ;
1704}
1705
1706void QQuickGraphsItem::addAxis(QAbstract3DAxis *axis)
1707{
1708 Q_ASSERT(axis);
1709 QQuickGraphsItem *owner = qobject_cast<QQuickGraphsItem *>(object: axis->parent());
1710 if (owner != this) {
1711 Q_ASSERT_X(!owner, "addAxis", "Axis already attached to a graph.");
1712 axis->setParent(this);
1713 }
1714 if (!m_axes.contains(t: axis))
1715 m_axes.append(t: axis);
1716}
1717
1718void QQuickGraphsItem::releaseAxis(QAbstract3DAxis *axis)
1719{
1720 if (axis && m_axes.contains(t: axis)) {
1721 // Clear the default status from released default axes
1722 if (axis->d_func()->isDefaultAxis())
1723 axis->d_func()->setDefaultAxis(false);
1724
1725 // If the axis is in use, replace it with a temporary one
1726 switch (axis->orientation()) {
1727 case QAbstract3DAxis::AxisOrientation::X:
1728 setAxisX(0);
1729 break;
1730 case QAbstract3DAxis::AxisOrientation::Y:
1731 setAxisY(0);
1732 break;
1733 case QAbstract3DAxis::AxisOrientation::Z:
1734 setAxisZ(0);
1735 break;
1736 default:
1737 break;
1738 }
1739
1740 m_axes.removeAll(t: axis);
1741 axis->setParent(0);
1742 }
1743}
1744
1745QList<QAbstract3DAxis *> QQuickGraphsItem::axes() const
1746{
1747 return m_axes;
1748}
1749
1750void QQuickGraphsItem::setRenderingMode(QtGraphs3D::RenderingMode mode)
1751{
1752 if (mode == m_renderMode || mode < QtGraphs3D::RenderingMode::DirectToBackground
1753 || mode > QtGraphs3D::RenderingMode::Indirect) {
1754 qCWarning(lcProperties3D, "%s invalid rendering mode used",
1755 qUtf8Printable(QLatin1String(__FUNCTION__)));
1756 return;
1757 }
1758
1759 QtGraphs3D::RenderingMode previousMode = m_renderMode;
1760
1761 m_renderMode = mode;
1762
1763 m_initialisedSize = QSize(0, 0);
1764 setFlag(flag: ItemHasContents /*, !m_runningInDesigner*/);
1765
1766 // TODO - Need to check if the mode is set properly
1767 switch (mode) {
1768 case QtGraphs3D::RenderingMode::DirectToBackground:
1769 update();
1770 setRenderMode(QQuick3DViewport::Underlay);
1771 if (previousMode == QtGraphs3D::RenderingMode::Indirect) {
1772 checkWindowList(window: window());
1773 setAntialiasing(m_windowSamples > 0);
1774 if (m_windowSamples != m_samples)
1775 emit msaaSamplesChanged(samples: m_windowSamples);
1776 }
1777 break;
1778 case QtGraphs3D::RenderingMode::Indirect:
1779 update();
1780 setRenderMode(QQuick3DViewport::Offscreen);
1781 break;
1782 }
1783 if (m_sliceView)
1784 m_sliceView->setRenderMode(renderMode());
1785
1786 updateWindowParameters();
1787
1788 emit renderingModeChanged(mode);
1789}
1790
1791QtGraphs3D::RenderingMode QQuickGraphsItem::renderingMode() const
1792{
1793 return m_renderMode;
1794}
1795
1796void QQuickGraphsItem::setTransparencyTechnique(QtGraphs3D::TransparencyTechnique technique)
1797{
1798 if (technique == m_transparencyTechnique) {
1799 qCDebug(lcProperties3D) << __FUNCTION__
1800 << "value is already set to:" << technique;
1801 return;
1802 }
1803
1804 switch (technique) {
1805 case QtGraphs3D::TransparencyTechnique::Default:
1806 environment()->setOitMethod(QQuick3DSceneEnvironment::OITNone);
1807 break;
1808 case QtGraphs3D::TransparencyTechnique::Approximate:
1809 environment()->setOitMethod(QQuick3DSceneEnvironment::OITWeightedBlended);
1810 break;
1811 case QtGraphs3D::TransparencyTechnique::Accurate:
1812 // environment()->setOitMethod(QQuick3DSceneEnvironment::OITSpinlock);
1813 //TODO: Add this method when it is implemended in QtQuick3D
1814 break;
1815 }
1816 m_transparencyTechnique = technique;
1817
1818 emit transparencyTechniqueChanged(technique);
1819}
1820
1821QtGraphs3D::TransparencyTechnique QQuickGraphsItem::transparencyTechnique() const
1822{
1823 return m_transparencyTechnique;
1824}
1825
1826void QQuickGraphsItem::keyPressEvent(QKeyEvent *ev)
1827{
1828 ev->ignore();
1829 setFlag(flag: ItemHasContents);
1830 update();
1831}
1832
1833void QQuickGraphsItem::checkSliceEnabled()
1834{
1835 if (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Slice)
1836 && (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column)
1837 != selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row))) {
1838 m_sliceEnabled = true;
1839 } else {
1840 m_sliceEnabled = false;
1841 }
1842}
1843
1844QtGraphs3D::GridLineType QQuickGraphsItem::gridLineType() const
1845{
1846 return m_gridLineType;
1847}
1848
1849void QQuickGraphsItem::setGridLineType(const QtGraphs3D::GridLineType &gridLineType)
1850{
1851 m_gridLineTypeDirty = true;
1852 if (m_gridLineType == gridLineType) {
1853 qCDebug(lcProperties3D) << __FUNCTION__
1854 << "value is already set to:" << gridLineType;
1855 return;
1856 }
1857 m_gridLineType = gridLineType;
1858 emit gridLineTypeChanged();
1859 emitNeedRender();
1860}
1861
1862void QQuickGraphsItem::handleThemeTypeChange() {}
1863
1864void QQuickGraphsItem::handleFpsChanged()
1865{
1866 int fps = renderStats()->fps();
1867 if (m_currentFps != fps) {
1868 m_currentFps = fps;
1869 emit currentFpsChanged(fps);
1870 }
1871}
1872
1873void QQuickGraphsItem::handleParentWidthChange()
1874{
1875 m_cachedGeometry = parentItem()->boundingRect();
1876 updateWindowParameters();
1877 updateSubViews();
1878}
1879
1880void QQuickGraphsItem::handleParentHeightChange()
1881{
1882 m_cachedGeometry = parentItem()->boundingRect();
1883 updateWindowParameters();
1884 updateSubViews();
1885}
1886
1887void QQuickGraphsItem::componentComplete()
1888{
1889 QQuick3DViewport::componentComplete();
1890 Q_TRACE(QGraphs3DItemInit_entry);
1891 rootNode()->setScale(QVector3D(100,100,100));
1892
1893 auto url = QUrl(QStringLiteral("defaultMeshes/backgroundMesh"));
1894 m_background = new QQuick3DModel();
1895 m_backgroundScale = new QQuick3DNode();
1896 m_backgroundRotation = new QQuick3DNode();
1897 m_graphNode = new QQuick3DNode();
1898
1899 m_backgroundScale->setParent(graphNode());
1900 m_backgroundScale->setParentItem(graphNode());
1901
1902 m_backgroundRotation->setParent(m_backgroundScale);
1903 m_backgroundRotation->setParentItem(m_backgroundScale);
1904
1905 m_background->setObjectName("Background");
1906 m_background->setParent(m_backgroundRotation);
1907 m_background->setParentItem(m_backgroundRotation);
1908
1909 m_background->setSource(url);
1910
1911 m_backgroundBB = new QQuick3DModel();
1912 m_backgroundBB->setObjectName("BackgroundBB");
1913 m_backgroundBB->setParent(m_background);
1914 m_backgroundBB->setParentItem(m_background);
1915 m_backgroundBB->setSource(QUrl(QStringLiteral("defaultMeshes/barMeshFull")));
1916 m_backgroundBB->setPickable(true);
1917
1918 m_graphNode->setParent(rootNode());
1919 m_graphNode->setParentItem(rootNode());
1920
1921 setUpCamera();
1922 setUpLight();
1923
1924 // Create repeaters for each axis X, Y, Z
1925 m_repeaterX = createRepeater();
1926 m_repeaterY = createRepeater();
1927 m_repeaterZ = createRepeater();
1928
1929 m_delegateModelX.reset(p: new QQmlComponent(qmlEngine(this), (QStringLiteral(":/axis/AxisLabel"))));
1930 m_delegateModelY.reset(p: new QQmlComponent(qmlEngine(this), (QStringLiteral(":/axis/AxisLabel"))));
1931 m_delegateModelZ.reset(p: new QQmlComponent(qmlEngine(this), (QStringLiteral(":/axis/AxisLabel"))));
1932
1933 m_repeaterX->setDelegate(m_delegateModelX.get());
1934 m_repeaterY->setDelegate(m_delegateModelY.get());
1935 m_repeaterZ->setDelegate(m_delegateModelZ.get());
1936
1937 // title labels for axes
1938 m_titleLabelX = createTitleLabel();
1939 m_titleLabelX->setVisible(axisX()->isTitleVisible());
1940 m_titleLabelX->setProperty(name: "labelText", value: axisX()->title());
1941
1942 m_titleLabelY = createTitleLabel();
1943 m_titleLabelY->setVisible(axisY()->isTitleVisible());
1944 m_titleLabelY->setProperty(name: "labelText", value: axisY()->title());
1945
1946 m_titleLabelZ = createTitleLabel();
1947 m_titleLabelZ->setVisible(axisZ()->isTitleVisible());
1948 m_titleLabelZ->setProperty(name: "labelText", value: axisZ()->title());
1949
1950 // Grid with geometry
1951 m_gridGeometryModel = new QQuick3DModel(m_graphNode);
1952 m_gridGeometryModel->setCastsShadows(false);
1953 m_gridGeometryModel->setReceivesShadows(false);
1954 auto gridGeometry = new QQuick3DGeometry(m_gridGeometryModel);
1955 gridGeometry->setStride(sizeof(QVector3D));
1956 gridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
1957 gridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
1958 offset: 0,
1959 componentType: QQuick3DGeometry::Attribute::F32Type);
1960 m_gridGeometryModel->setGeometry(gridGeometry);
1961 QQmlListReference gridMaterialRef(m_gridGeometryModel, "materials");
1962 auto gridMaterial = new QQuick3DPrincipledMaterial(m_gridGeometryModel);
1963 gridMaterial->setLighting(QQuick3DPrincipledMaterial::Lighting::NoLighting);
1964 gridMaterial->setCullMode(QQuick3DMaterial::CullMode::BackFaceCulling);
1965 gridMaterial->setBaseColor(theme()->grid().mainColor());
1966 gridMaterialRef.append(gridMaterial);
1967
1968 // subgrid with geometry
1969 m_subgridGeometryModel = new QQuick3DModel(m_graphNode);
1970 m_subgridGeometryModel->setCastsShadows(false);
1971 m_subgridGeometryModel->setReceivesShadows(false);
1972 auto subgridGeometry = new QQuick3DGeometry(m_subgridGeometryModel);
1973 subgridGeometry->setStride(sizeof(QVector3D));
1974 subgridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
1975 subgridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
1976 offset: 0,
1977 componentType: QQuick3DGeometry::Attribute::F32Type);
1978 m_subgridGeometryModel->setGeometry(subgridGeometry);
1979
1980 QQmlListReference subgridMaterialRef(m_subgridGeometryModel, "materials");
1981 auto subgridMaterial = new QQuick3DPrincipledMaterial(m_subgridGeometryModel);
1982 subgridMaterial->setLighting(QQuick3DPrincipledMaterial::Lighting::NoLighting);
1983 subgridMaterial->setCullMode(QQuick3DMaterial::CullMode::BackFaceCulling);
1984 subgridMaterialRef.append(subgridMaterial);
1985
1986 createItemLabel();
1987
1988 auto axis = axisX();
1989 m_repeaterX->setModel(axis->labels().size());
1990 handleAxisLabelsChangedBySender(sender: axisX());
1991
1992 axis = axisY();
1993 m_repeaterY->setModel(2 * axis->labels().size());
1994 handleAxisLabelsChangedBySender(sender: axisY());
1995
1996 axis = axisZ();
1997 m_repeaterZ->setModel(axis->labels().size());
1998 handleAxisLabelsChangedBySender(sender: axisZ());
1999
2000 if (!m_pendingCustomItemList.isEmpty()) {
2001 for (const auto &item : std::as_const(t&: m_pendingCustomItemList))
2002 addCustomItem(item);
2003 }
2004 qCDebug(lcGraphs3D, "QQuickGraphsItem::componentComplete.");
2005 Q_TRACE(QGraphs3DItemInit_exit);
2006}
2007
2008QQuick3DDirectionalLight *QQuickGraphsItem::light() const
2009{
2010 return m_light;
2011}
2012
2013bool QQuickGraphsItem::isSlicingActive() const
2014{
2015 return m_scene->isSlicingActive();
2016}
2017
2018void QQuickGraphsItem::setSlicingActive(bool isSlicing)
2019{
2020 m_scene->setSlicingActive(isSlicing);
2021}
2022
2023bool QQuickGraphsItem::isCustomLabelItem(QCustom3DItem *item) const
2024{
2025 return item->d_func()->m_isLabelItem;
2026}
2027
2028bool QQuickGraphsItem::isCustomVolumeItem(QCustom3DItem *item) const
2029{
2030 return item->d_func()->m_isVolumeItem;
2031}
2032
2033QImage QQuickGraphsItem::customTextureImage(QCustom3DItem *item)
2034{
2035 return item->d_func()->textureImage();
2036}
2037
2038Q3DScene *QQuickGraphsItem::scene()
2039{
2040 return m_scene;
2041}
2042
2043void QQuickGraphsItem::addTheme(QGraphsTheme *theme)
2044{
2045 Q_ASSERT(theme);
2046 QQuickGraphsItem *owner = qobject_cast<QQuickGraphsItem *>(object: theme->parent());
2047 if (owner != this) {
2048 Q_ASSERT_X(!owner, "addTheme", "Theme already attached to a graph.");
2049 theme->setParent(this);
2050 }
2051 if (!m_themes.contains(t: theme))
2052 m_themes.append(t: theme);
2053}
2054
2055void QQuickGraphsItem::releaseTheme(QGraphsTheme *theme)
2056{
2057 QGraphsTheme *oldTheme = m_activeTheme;
2058
2059 if (theme && m_themes.contains(t: theme)) {
2060 // If the theme is in use, replace it with a temporary one
2061 if (theme == m_activeTheme) {
2062 m_activeTheme = nullptr;
2063 disconnect(sender: theme, signal: &QGraphsTheme::themeChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeTypeChanged);
2064 disconnect(sender: theme, signal: &QGraphsTheme::colorStyleChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeColorStyleChanged);
2065 disconnect(sender: theme, signal: &QGraphsTheme::seriesColorsChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeBaseColorsChanged);
2066 disconnect(sender: theme, signal: &QGraphsTheme::seriesGradientsChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeBaseGradientsChanged);
2067 disconnect(sender: theme, signal: &QGraphsTheme::singleHighlightColorChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeSingleHighlightColorChanged);
2068 disconnect(sender: theme, signal: &QGraphsTheme::singleHighlightGradientChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeSingleHighlightGradientChanged);
2069 disconnect(sender: theme, signal: &QGraphsTheme::multiHighlightColorChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeMultiHighlightColorChanged);
2070 disconnect(sender: theme, signal: &QGraphsTheme::multiHighlightGradientChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeMultiHighlightGradientChanged);
2071 disconnect(sender: theme, signal: &QGraphsTheme::update, receiver: this, slot: &QQuickGraphsItem::emitNeedRender);
2072 }
2073 m_themes.removeAll(t: theme);
2074 theme->setParent(nullptr);
2075 }
2076
2077 if (oldTheme != m_activeTheme)
2078 emit activeThemeChanged(activeTheme: m_activeTheme);
2079}
2080
2081QList<QGraphsTheme *> QQuickGraphsItem::themes() const
2082{
2083 return m_themes;
2084}
2085
2086void QQuickGraphsItem::setTheme(QGraphsTheme *theme)
2087{
2088 if (theme != m_activeTheme) {
2089 if (m_activeTheme) {
2090 disconnect(sender: m_activeTheme, signal: &QGraphsTheme::themeChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeTypeChanged);
2091 disconnect(sender: m_activeTheme, signal: &QGraphsTheme::colorStyleChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeColorStyleChanged);
2092 disconnect(sender: m_activeTheme, signal: &QGraphsTheme::seriesColorsChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeBaseColorsChanged);
2093 disconnect(sender: m_activeTheme, signal: &QGraphsTheme::seriesGradientsChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeBaseGradientsChanged);
2094 disconnect(sender: m_activeTheme, signal: &QGraphsTheme::singleHighlightColorChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeSingleHighlightColorChanged);
2095 disconnect(sender: m_activeTheme, signal: &QGraphsTheme::singleHighlightGradientChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeSingleHighlightGradientChanged);
2096 disconnect(sender: m_activeTheme, signal: &QGraphsTheme::multiHighlightColorChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeMultiHighlightColorChanged);
2097 disconnect(sender: m_activeTheme, signal: &QGraphsTheme::multiHighlightGradientChanged, receiver: this, slot: &QQuickGraphsItem::handleThemeMultiHighlightGradientChanged);
2098 disconnect(sender: m_activeTheme, signal: &QGraphsTheme::update, receiver: this, slot: &QQuickGraphsItem::emitNeedRender);
2099 }
2100
2101 connect(sender: theme, signal: &QGraphsTheme::themeChanged, context: this, slot: &QQuickGraphsItem::handleThemeTypeChanged);
2102 connect(sender: theme, signal: &QGraphsTheme::colorStyleChanged, context: this, slot: &QQuickGraphsItem::handleThemeColorStyleChanged);
2103 connect(sender: theme, signal: &QGraphsTheme::seriesColorsChanged, context: this, slot: &QQuickGraphsItem::handleThemeBaseColorsChanged);
2104 connect(sender: theme, signal: &QGraphsTheme::seriesGradientsChanged, context: this, slot: &QQuickGraphsItem::handleThemeBaseGradientsChanged);
2105 connect(sender: theme, signal: &QGraphsTheme::singleHighlightColorChanged, context: this, slot: &QQuickGraphsItem::handleThemeSingleHighlightColorChanged);
2106 connect(sender: theme, signal: &QGraphsTheme::singleHighlightGradientChanged, context: this, slot: &QQuickGraphsItem::handleThemeSingleHighlightGradientChanged);
2107 connect(sender: theme, signal: &QGraphsTheme::multiHighlightColorChanged, context: this, slot: &QQuickGraphsItem::handleThemeMultiHighlightColorChanged);
2108 connect(sender: theme, signal: &QGraphsTheme::multiHighlightGradientChanged, context: this, slot: &QQuickGraphsItem::handleThemeMultiHighlightGradientChanged);
2109 connect(sender: theme, signal: &QGraphsTheme::update, context: this, slot: &QQuickGraphsItem::emitNeedRender);
2110
2111 m_activeTheme = theme;
2112 m_changeTracker.themeChanged = true;
2113 // Default theme can be created by theme manager, so ensure we have correct theme
2114 QGraphsTheme *newActiveTheme = m_activeTheme;
2115 // Reset all attached series to the new theme
2116 for (int i = 0; i < m_seriesList.size(); i++)
2117 m_seriesList.at(i)->d_func()->resetToTheme(theme: *newActiveTheme, seriesIndex: i, force: isComponentComplete());
2118 markSeriesVisualsDirty();
2119 emit activeThemeChanged(activeTheme: newActiveTheme);
2120 } else {
2121 qCDebug(lcProperties3D) << __FUNCTION__
2122 << "theme is already set to:" << theme;
2123 }
2124}
2125
2126QGraphsTheme *QQuickGraphsItem::theme() const
2127{
2128 return m_activeTheme;
2129}
2130
2131bool QQuickGraphsItem::hasSeries(QAbstract3DSeries *series)
2132{
2133 return m_seriesList.contains(t: series);
2134}
2135
2136void QQuickGraphsItem::setSelectionMode(QtGraphs3D::SelectionFlags mode)
2137{
2138 if (mode == m_selectionMode) {
2139 qCDebug(lcProperties3D) << __FUNCTION__
2140 << "value is already set to:" << mode.toInt();
2141 return;
2142 }
2143 m_selectionMode = mode;
2144 m_changeTracker.selectionModeChanged = true;
2145 emit selectionModeChanged(mode);
2146 emitNeedRender();
2147}
2148
2149QtGraphs3D::SelectionFlags QQuickGraphsItem::selectionMode() const
2150{
2151 return m_selectionMode;
2152}
2153
2154void QQuickGraphsItem::doSetShadowQuality(QtGraphs3D::ShadowQuality quality)
2155{
2156 if (quality == m_shadowQuality) {
2157 qCDebug(lcProperties3D) << __FUNCTION__
2158 << "value is already set to:" << quality;
2159 return;
2160 }
2161 m_shadowQuality = quality;
2162 m_changeTracker.shadowQualityChanged = true;
2163 emit shadowQualityChanged(quality: m_shadowQuality);
2164 emitNeedRender();
2165}
2166
2167void QQuickGraphsItem::setShadowQuality(QtGraphs3D::ShadowQuality quality)
2168{
2169 if (!m_useOrthoProjection)
2170 doSetShadowQuality(quality);
2171}
2172
2173QtGraphs3D::ShadowQuality QQuickGraphsItem::shadowQuality() const
2174{
2175 return m_shadowQuality;
2176}
2177
2178qsizetype QQuickGraphsItem::addCustomItem(QCustom3DItem *item)
2179{
2180 if (!item) {
2181 qCWarning(lcProperties3D, "%s invalid item", qUtf8Printable(QLatin1String(__FUNCTION__)));
2182 return -1;
2183 }
2184
2185 if (isComponentComplete()) {
2186 if (isCustomLabelItem(item)) {
2187 QQuick3DNode *label = createTitleLabel();
2188 QCustom3DLabel *key = static_cast<QCustom3DLabel *>(item);
2189 m_customLabelList.insert(key, value: label);
2190 } else if (isCustomVolumeItem(item)) {
2191 QQuick3DModel *model = new QQuick3DModel();
2192 model->setParent(graphNode());
2193 model->setParentItem(graphNode());
2194 m_customItemList.insert(key: item, value: model);
2195 } else {
2196 QQuick3DModel *model = new QQuick3DModel();
2197 model->setParent(graphNode());
2198 model->setParentItem(graphNode());
2199 QQmlListReference materialsRef(model, "materials");
2200 QQuick3DPrincipledMaterial *material = new QQuick3DPrincipledMaterial();
2201 material->setParent(model);
2202 material->setParentItem(model);
2203 materialsRef.append(material);
2204 if (!selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::None))
2205 model->setPickable(true);
2206 m_customItemList.insert(key: item, value: model);
2207 }
2208 } else {
2209 m_pendingCustomItemList.append(t: item);
2210 }
2211
2212 qsizetype index = m_customItems.indexOf(t: item);
2213
2214 if (index != -1) {
2215 qCWarning(lcProperties3D, "%s tried to access customItems list at invalid index.",
2216 qUtf8Printable(QLatin1String(__FUNCTION__)));
2217 return index;
2218 }
2219
2220 item->setParent(this);
2221 connect(sender: item, signal: &QCustom3DItem::needUpdate, context: this, slot: &QQuickGraphsItem::updateCustomItem);
2222 m_customItems.append(t: item);
2223 item->d_func()->resetDirtyBits();
2224 m_isCustomDataDirty = true;
2225 emitNeedRender();
2226 return m_customItems.size() - 1;
2227}
2228
2229void QQuickGraphsItem::deleteCustomItems()
2230{
2231 for (QCustom3DItem *item : std::as_const(t&: m_customItems))
2232 delete item;
2233 m_customItems.clear();
2234 m_isCustomDataDirty = true;
2235 emitNeedRender();
2236}
2237
2238void QQuickGraphsItem::deleteCustomItem(QCustom3DItem *item)
2239{
2240 if (!item)
2241 return;
2242
2243 m_customItems.removeOne(t: item);
2244 delete item;
2245 item = 0;
2246 m_isCustomDataDirty = true;
2247 emitNeedRender();
2248}
2249
2250void QQuickGraphsItem::deleteCustomItem(QVector3D position)
2251{
2252 // Get the item for the position
2253 for (QCustom3DItem *item : std::as_const(t&: m_customItems)) {
2254 if (item->position() == position)
2255 deleteCustomItem(item);
2256 }
2257}
2258
2259QList<QCustom3DItem *> QQuickGraphsItem::customItems() const
2260{
2261 return m_customItems;
2262}
2263
2264void QQuickGraphsItem::updateCustomItem()
2265{
2266 m_isCustomItemDirty = true;
2267 m_isCustomDataDirty = true;
2268 emitNeedRender();
2269}
2270
2271void QQuickGraphsItem::removeCustomItems()
2272{
2273 m_customItemList.clear();
2274 m_customLabelList.clear();
2275 deleteCustomItems();
2276}
2277
2278void QQuickGraphsItem::removeCustomItem(QCustom3DItem *item)
2279{
2280 if (isCustomLabelItem(item)) {
2281 m_customLabelList.remove(key: static_cast<QCustom3DLabel *>(item));
2282 } else if (isCustomVolumeItem(item)) {
2283 m_customItemList.remove(key: item);
2284 auto volume = static_cast<QCustom3DVolume *>(item);
2285 if (m_customVolumes.contains(key: volume)) {
2286 m_customVolumes[volume].model->deleteLater();
2287 m_customVolumes.remove(key: volume);
2288 }
2289 } else {
2290 m_customItemList[item]->deleteLater();
2291 m_customItemList.remove(key: item);
2292 }
2293 deleteCustomItem(item);
2294}
2295
2296void QQuickGraphsItem::removeCustomItemAt(QVector3D position)
2297{
2298 auto labelIterator = m_customLabelList.begin();
2299 while (labelIterator != m_customLabelList.end()) {
2300 QCustom3DLabel *label = labelIterator.key();
2301 if (label->position() == position) {
2302 labelIterator.value()->setVisible(false);
2303 labelIterator = m_customLabelList.erase(it: labelIterator);
2304 } else {
2305 ++labelIterator;
2306 }
2307 }
2308
2309 auto itemIterator = m_customItemList.begin();
2310 while (itemIterator != m_customItemList.end()) {
2311 QCustom3DItem *item = itemIterator.key();
2312 if (item->position() == position) {
2313 m_customItemList[item]->deleteLater();
2314 itemIterator = m_customItemList.erase(it: itemIterator);
2315 if (isCustomVolumeItem(item)) {
2316 auto volume = static_cast<QCustom3DVolume *>(item);
2317 if (m_customVolumes.contains(key: volume)) {
2318 m_customVolumes[volume].model->deleteLater();
2319 m_customVolumes.remove(key: volume);
2320 }
2321 }
2322 } else {
2323 ++itemIterator;
2324 }
2325 }
2326 deleteCustomItem(position);
2327}
2328
2329void QQuickGraphsItem::releaseCustomItem(QCustom3DItem *item)
2330{
2331 if (isCustomLabelItem(item)) {
2332 m_customLabelList.remove(key: static_cast<QCustom3DLabel *>(item));
2333 } else if (isCustomVolumeItem(item)) {
2334 m_customItemList.remove(key: item);
2335 auto volume = static_cast<QCustom3DVolume *>(item);
2336 if (m_customVolumes.contains(key: volume)) {
2337 m_customVolumes[volume].model->deleteLater();
2338 m_customVolumes.remove(key: volume);
2339 }
2340 } else {
2341 m_customItemList.remove(key: item);
2342 }
2343
2344 if (item && m_customItems.contains(t: item)) {
2345 disconnect(sender: item, signal: &QCustom3DItem::needUpdate, receiver: this, slot: &QQuickGraphsItem::updateCustomItem);
2346 m_customItems.removeOne(t: item);
2347 item->setParent(0);
2348 m_isCustomDataDirty = true;
2349 emitNeedRender();
2350 }
2351}
2352
2353int QQuickGraphsItem::selectedLabelIndex() const
2354{
2355 int index = m_selectedLabelIndex;
2356 QAbstract3DAxis *axis = selectedAxis();
2357 if (axis && axis->labels().size() <= index)
2358 index = -1;
2359 return index;
2360}
2361
2362QAbstract3DAxis *QQuickGraphsItem::selectedAxis() const
2363{
2364 QAbstract3DAxis *axis = 0;
2365 QtGraphs3D::ElementType type = m_clickedType;
2366 switch (type) {
2367 case QtGraphs3D::ElementType::AxisXLabel:
2368 axis = axisX();
2369 break;
2370 case QtGraphs3D::ElementType::AxisYLabel:
2371 axis = axisY();
2372 break;
2373 case QtGraphs3D::ElementType::AxisZLabel:
2374 axis = axisZ();
2375 break;
2376 default:
2377 axis = 0;
2378 break;
2379 }
2380
2381 return axis;
2382}
2383
2384qsizetype QQuickGraphsItem::selectedCustomItemIndex() const
2385{
2386 qsizetype index = m_selectedCustomItemIndex;
2387 if (m_customItems.size() <= index)
2388 index = -1;
2389 return index;
2390}
2391
2392QCustom3DItem *QQuickGraphsItem::selectedCustomItem() const
2393{
2394 QCustom3DItem *item = 0;
2395 qsizetype index = selectedCustomItemIndex();
2396 if (index >= 0)
2397 item = m_customItems[index];
2398 return item;
2399}
2400
2401QQmlListProperty<QCustom3DItem> QQuickGraphsItem::customItemList()
2402{
2403 return QQmlListProperty<QCustom3DItem>(this,
2404 this,
2405 &QQuickGraphsItem::appendCustomItemFunc,
2406 &QQuickGraphsItem::countCustomItemFunc,
2407 &QQuickGraphsItem::atCustomItemFunc,
2408 &QQuickGraphsItem::clearCustomItemFunc);
2409}
2410
2411void QQuickGraphsItem::appendCustomItemFunc(QQmlListProperty<QCustom3DItem> *list,
2412 QCustom3DItem *item)
2413{
2414 QQuickGraphsItem *decl = reinterpret_cast<QQuickGraphsItem *>(list->data);
2415 decl->addCustomItem(item);
2416}
2417
2418qsizetype QQuickGraphsItem::countCustomItemFunc(QQmlListProperty<QCustom3DItem> *list)
2419{
2420 Q_UNUSED(list);
2421 return reinterpret_cast<QQuickGraphsItem *>(list->data)->m_customItems.size();
2422}
2423
2424QCustom3DItem *QQuickGraphsItem::atCustomItemFunc(QQmlListProperty<QCustom3DItem> *list,
2425 qsizetype index)
2426{
2427 Q_UNUSED(list);
2428 Q_UNUSED(index);
2429 return reinterpret_cast<QQuickGraphsItem *>(list->data)->m_customItems.at(i: index);
2430}
2431
2432void QQuickGraphsItem::clearCustomItemFunc(QQmlListProperty<QCustom3DItem> *list)
2433{
2434 QQuickGraphsItem *decl = reinterpret_cast<QQuickGraphsItem *>(list->data);
2435 decl->removeCustomItems();
2436}
2437
2438void QQuickGraphsItem::synchData()
2439{
2440 qCDebug(lcGraphs3D, "%s start sync", qUtf8Printable(QLatin1String(__FUNCTION__)));
2441 if (!isVisible())
2442 return;
2443
2444 Q_TRACE(QGraphs3DItemSynch_entry);
2445
2446 m_renderPending = false;
2447
2448 if (m_changeTracker.selectionModeChanged) {
2449 updateSelectionMode(newMode: selectionMode());
2450 m_changeTracker.selectionModeChanged = false;
2451 }
2452
2453 bool recalculateScale = false;
2454 if (m_changeTracker.aspectRatioChanged) {
2455 recalculateScale = true;
2456 m_changeTracker.aspectRatioChanged = false;
2457 }
2458
2459 if (m_changeTracker.horizontalAspectRatioChanged) {
2460 recalculateScale = true;
2461 m_changeTracker.horizontalAspectRatioChanged = false;
2462 }
2463
2464 if (m_changeTracker.marginChanged) {
2465 recalculateScale = true;
2466 m_changeTracker.marginChanged = false;
2467 }
2468
2469 if (m_changeTracker.polarChanged) {
2470 recalculateScale = true;
2471 m_changeTracker.polarChanged = false;
2472 }
2473
2474 if (recalculateScale)
2475 calculateSceneScalingFactors();
2476
2477 bool axisDirty = recalculateScale;
2478 if (m_changeTracker.axisXFormatterChanged) {
2479 m_changeTracker.axisXFormatterChanged = false;
2480 if (axisX()->type() == QAbstract3DAxis::AxisType::Value) {
2481 QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(axisX());
2482 valueAxisX->recalculate();
2483 repeaterX()->setModel(valueAxisX->formatter()->labelPositions().size());
2484 }
2485 axisDirty = true;
2486 }
2487
2488 if (m_changeTracker.axisYFormatterChanged) {
2489 m_changeTracker.axisYFormatterChanged = false;
2490 if (axisY()->type() == QAbstract3DAxis::AxisType::Value) {
2491 QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(axisY());
2492 valueAxisY->recalculate();
2493 repeaterY()->setModel(2 * valueAxisY->formatter()->labelPositions().size());
2494 }
2495 axisDirty = true;
2496 }
2497
2498 if (m_changeTracker.axisZFormatterChanged) {
2499 m_changeTracker.axisZFormatterChanged = false;
2500 if (axisZ()->type() == QAbstract3DAxis::AxisType::Value) {
2501 QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(axisZ());
2502 valueAxisZ->recalculate();
2503 repeaterZ()->setModel(valueAxisZ->formatter()->labelPositions().size());
2504 }
2505 axisDirty = true;
2506 }
2507
2508 if (m_changeTracker.axisXSegmentCountChanged) {
2509 if (axisX()->type() == QAbstract3DAxis::AxisType::Value) {
2510 QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(axisX());
2511 valueAxisX->recalculate();
2512 }
2513 m_changeTracker.axisXSegmentCountChanged = false;
2514 axisDirty = true;
2515 }
2516
2517 if (m_changeTracker.axisYSegmentCountChanged) {
2518 if (axisY()->type() == QAbstract3DAxis::AxisType::Value) {
2519 QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(axisY());
2520 valueAxisY->recalculate();
2521 }
2522 m_changeTracker.axisYSegmentCountChanged = false;
2523 axisDirty = true;
2524 }
2525
2526 if (m_changeTracker.axisZSegmentCountChanged) {
2527 if (axisZ()->type() == QAbstract3DAxis::AxisType::Value) {
2528 QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(axisZ());
2529 valueAxisZ->recalculate();
2530 }
2531 m_changeTracker.axisZSegmentCountChanged = false;
2532 axisDirty = true;
2533 }
2534
2535 if (m_changeTracker.axisXSubSegmentCountChanged) {
2536 if (axisX()->type() == QAbstract3DAxis::AxisType::Value) {
2537 QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(axisX());
2538 valueAxisX->recalculate();
2539 }
2540 m_changeTracker.axisXSubSegmentCountChanged = false;
2541 axisDirty = true;
2542 }
2543
2544 if (m_changeTracker.axisYSubSegmentCountChanged) {
2545 if (axisY()->type() == QAbstract3DAxis::AxisType::Value) {
2546 QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(axisY());
2547 valueAxisY->recalculate();
2548 }
2549 m_changeTracker.axisYSubSegmentCountChanged = false;
2550 axisDirty = true;
2551 }
2552
2553 if (m_changeTracker.axisZSubSegmentCountChanged) {
2554 if (axisZ()->type() == QAbstract3DAxis::AxisType::Value) {
2555 QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(axisZ());
2556 valueAxisZ->recalculate();
2557 }
2558 m_changeTracker.axisZSubSegmentCountChanged = false;
2559 axisDirty = true;
2560 }
2561
2562 if (m_changeTracker.axisXLabelsChanged) {
2563 if (axisX()->type() == QAbstract3DAxis::AxisType::Value) {
2564 auto valueAxisX = static_cast<QValue3DAxis *>(axisX());
2565 valueAxisX->recalculate();
2566 repeaterX()->setModel(valueAxisX->formatter()->labelPositions().size());
2567 } else if (axisX()->type() == QAbstract3DAxis::AxisType::Category) {
2568 auto categoryAxis = static_cast<QCategory3DAxis *>(axisX());
2569 repeaterX()->setModel(categoryAxis->labels().size());
2570 }
2571
2572 m_changeTracker.axisXLabelsChanged = false;
2573 handleLabelCountChanged(repeater: m_repeaterX, axisLabelColor: theme()->axisX().labelTextColor());
2574 axisDirty = true;
2575 }
2576
2577 if (m_changeTracker.axisYLabelsChanged) {
2578 if (axisY()->type() == QAbstract3DAxis::AxisType::Value) {
2579 auto valueAxisY = static_cast<QValue3DAxis *>(axisY());
2580 valueAxisY->recalculate();
2581 repeaterY()->setModel(2 * valueAxisY->formatter()->labelPositions().size());
2582 } else if (axisY()->type() == QAbstract3DAxis::AxisType::Category) {
2583 auto categoryAxis = static_cast<QCategory3DAxis *>(axisY());
2584 repeaterY()->setModel(2 * categoryAxis->labels().size());
2585 }
2586
2587 m_changeTracker.axisYLabelsChanged = false;
2588 handleLabelCountChanged(repeater: m_repeaterY, axisLabelColor: theme()->axisY().labelTextColor());
2589 axisDirty = true;
2590 }
2591
2592 if (m_changeTracker.axisZLabelsChanged) {
2593 if (axisZ()->type() == QAbstract3DAxis::AxisType::Value) {
2594 auto valueAxisZ = static_cast<QValue3DAxis *>(axisZ());
2595 valueAxisZ->recalculate();
2596 repeaterZ()->setModel(valueAxisZ->formatter()->labelPositions().size());
2597 } else if (axisZ()->type() == QAbstract3DAxis::AxisType::Category) {
2598 auto categoryAxis = static_cast<QCategory3DAxis *>(axisZ());
2599 repeaterZ()->setModel(categoryAxis->labels().size());
2600 }
2601
2602 m_changeTracker.axisZLabelsChanged = false;
2603 handleLabelCountChanged(repeater: m_repeaterZ, axisLabelColor: theme()->axisZ().labelTextColor());
2604 axisDirty = true;
2605 }
2606
2607 if (m_changeTracker.axisXLabelVisibilityChanged) {
2608 repeaterX()->setVisible(axisX()->labelsVisible());
2609 m_changeTracker.axisXLabelVisibilityChanged = false;
2610 }
2611
2612 if (m_changeTracker.axisYLabelVisibilityChanged) {
2613 repeaterY()->setVisible(axisY()->labelsVisible());
2614 m_changeTracker.axisYLabelVisibilityChanged = false;
2615 }
2616
2617 if (m_changeTracker.axisZLabelVisibilityChanged) {
2618 repeaterZ()->setVisible(axisZ()->labelsVisible());
2619 m_changeTracker.axisZLabelVisibilityChanged = false;
2620 }
2621 updateTitleLabels();
2622
2623 if (m_changeTracker.shadowQualityChanged) {
2624 updateShadowQuality(quality: shadowQuality());
2625 m_changeTracker.shadowQualityChanged = false;
2626 }
2627
2628 if (m_changeTracker.axisXRangeChanged) {
2629 axisDirty = true;
2630 calculateSceneScalingFactors();
2631 m_changeTracker.axisXRangeChanged = false;
2632 }
2633
2634 if (m_changeTracker.axisYRangeChanged) {
2635 axisDirty = true;
2636 QAbstract3DAxis *axis = axisY();
2637 updateAxisRange(min: axis->min(), max: axis->max());
2638 calculateSceneScalingFactors();
2639 m_changeTracker.axisYRangeChanged = false;
2640 }
2641
2642 if (m_changeTracker.axisZRangeChanged) {
2643 axisDirty = true;
2644 calculateSceneScalingFactors();
2645 m_changeTracker.axisZRangeChanged = false;
2646 }
2647
2648 if (m_changeTracker.axisXReversedChanged) {
2649 m_changeTracker.axisXReversedChanged = false;
2650 if (m_axisX->type() == QAbstract3DAxis::AxisType::Value) {
2651 QValue3DAxis *valueAxisX = static_cast<QValue3DAxis *>(m_axisX);
2652 updateAxisReversed(enable: valueAxisX->reversed());
2653 m_labelsNeedupdate = true;
2654 }
2655 }
2656
2657 if (m_changeTracker.axisYReversedChanged) {
2658 m_changeTracker.axisYReversedChanged = false;
2659 if (m_axisY->type() == QAbstract3DAxis::AxisType::Value) {
2660 QValue3DAxis *valueAxisY = static_cast<QValue3DAxis *>(m_axisY);
2661 updateAxisReversed(enable: valueAxisY->reversed());
2662 m_labelsNeedupdate = true;
2663 }
2664 }
2665
2666 if (m_changeTracker.axisZReversedChanged) {
2667 m_changeTracker.axisZReversedChanged = false;
2668 if (m_axisZ->type() == QAbstract3DAxis::AxisType::Value) {
2669 QValue3DAxis *valueAxisZ = static_cast<QValue3DAxis *>(m_axisZ);
2670 updateAxisReversed(enable: valueAxisZ->reversed());
2671 m_labelsNeedupdate = true;
2672 }
2673 }
2674
2675 if (m_changeTracker.axisXLabelAutoRotationChanged) {
2676 axisDirty = true;
2677 m_changeTracker.axisXLabelAutoRotationChanged = false;
2678 }
2679
2680 if (m_changeTracker.axisYLabelAutoRotationChanged) {
2681 axisDirty = true;
2682 m_changeTracker.axisYLabelAutoRotationChanged = false;
2683 }
2684
2685 if (m_changeTracker.axisZLabelAutoRotationChanged) {
2686 axisDirty = true;
2687 m_changeTracker.axisZLabelAutoRotationChanged = false;
2688 }
2689 if (m_changeTracker.axisXScaleLabelsByCountChanged) {
2690 axisDirty = true;
2691 m_changeTracker.axisXScaleLabelsByCountChanged = false;
2692 }
2693
2694 if (m_changeTracker.axisYScaleLabelsByCountChanged) {
2695 axisDirty = true;
2696 m_changeTracker.axisYScaleLabelsByCountChanged = false;
2697 }
2698
2699 if (m_changeTracker.axisZScaleLabelsByCountChanged) {
2700 axisDirty = true;
2701 m_changeTracker.axisZScaleLabelsByCountChanged = false;
2702 }
2703
2704 if (m_changeTracker.axisXLabelSizeChanged) {
2705 axisDirty = true;
2706 m_changeTracker.axisXLabelSizeChanged = false;
2707 }
2708
2709 if (m_changeTracker.axisYLabelSizeChanged) {
2710 axisDirty = true;
2711 m_changeTracker.axisYLabelSizeChanged = false;
2712 }
2713
2714 if (m_changeTracker.axisZLabelSizeChanged) {
2715 axisDirty = true;
2716 m_changeTracker.axisZLabelSizeChanged = false;
2717 }
2718
2719 if (m_changeTracker.axisXTitleFixedChanged) {
2720 axisDirty = true;
2721 m_changeTracker.axisXTitleFixedChanged = false;
2722 }
2723
2724 if (m_changeTracker.axisYTitleFixedChanged) {
2725 axisDirty = true;
2726 m_changeTracker.axisYTitleFixedChanged = false;
2727 }
2728
2729 if (m_changeTracker.axisZTitleFixedChanged) {
2730 axisDirty = true;
2731 m_changeTracker.axisZTitleFixedChanged = false;
2732 }
2733
2734 if (m_changeTracker.axisXTitleOffsetChanged) {
2735 axisDirty = true;
2736 m_changeTracker.axisXTitleOffsetChanged = false;
2737 }
2738 if (m_changeTracker.axisYTitleOffsetChanged) {
2739 axisDirty = true;
2740 m_changeTracker.axisYTitleOffsetChanged = false;
2741 }
2742 if (m_changeTracker.axisZTitleOffsetChanged) {
2743 axisDirty = true;
2744 m_changeTracker.axisZTitleOffsetChanged = false;
2745 }
2746
2747 if (m_changeTracker.cameraChanged) {
2748 updateCamera();
2749 m_changeTracker.cameraChanged = false;
2750 }
2751
2752 QVector3D forward = camera()->forward();
2753 auto targetRotation = cameraTarget()->eulerRotation();
2754 if (m_yFlipped != (targetRotation.x() > 0)) {
2755 m_yFlipped = (targetRotation.x() > 0);
2756 axisDirty = true;
2757 }
2758 if (m_xFlipped != (forward.x() > 0)) {
2759 m_xFlipped = (forward.x() > 0);
2760 axisDirty = true;
2761 }
2762 if (m_zFlipped != ((forward.z() > .1f))) {
2763 m_zFlipped = ((forward.z() > .1f));
2764 axisDirty = true;
2765 }
2766
2767 if (axisDirty) {
2768 QQmlListReference materialsRef(m_background, "materials");
2769 if (!materialsRef.size()) {
2770 QQuick3DCustomMaterial *bgMat
2771 = createQmlCustomMaterial(QStringLiteral(":/materials/BackgroundMaterial"));
2772 bgMat->setParent(m_background);
2773 materialsRef.append(bgMat);
2774 }
2775 if (m_gridLineType == QtGraphs3D::GridLineType::Shader)
2776 updateGridLineType();
2777 else
2778 updateGrid();
2779 m_labelsNeedupdate = true;
2780 updateCustomData();
2781 if (m_sliceView && isSliceEnabled()) {
2782 updateSliceGrid();
2783 updateSliceLabels();
2784 }
2785 m_gridUpdated = true;
2786 }
2787
2788 if (m_changeTracker.radialLabelOffsetChanged) {
2789 updateRadialLabelOffset();
2790 m_changeTracker.radialLabelOffsetChanged = false;
2791 }
2792 if (m_changeTracker.labelMarginChanged) {
2793 m_labelsNeedupdate = true;
2794 m_changeTracker.labelMarginChanged = false;
2795 }
2796
2797 QMatrix4x4 modelMatrix;
2798 m_backgroundScale->setScale(m_scaleWithBackground + m_backgroundScaleMargin);
2799
2800 QVector3D rotVec;
2801 if (!m_yFlipped) {
2802 rotVec = QVector3D(0, 270, 0);
2803 if (m_xFlipped && m_zFlipped)
2804 rotVec.setY(90);
2805 else if (!m_xFlipped && m_zFlipped)
2806 rotVec.setY(0);
2807 else if (m_xFlipped && !m_zFlipped)
2808 rotVec.setY(180);
2809 } else {
2810 rotVec = QVector3D(0, 180, 180);
2811 if (m_xFlipped && m_zFlipped)
2812 rotVec.setY(0);
2813 else if (!m_xFlipped && m_zFlipped)
2814 rotVec.setY(270);
2815 else if (m_xFlipped && !m_zFlipped)
2816 rotVec.setY(90);
2817 }
2818
2819 auto rotation = Utils::calculateRotation(xyzRotations: rotVec);
2820
2821 if (rotation != m_backgroundRotation->rotation()) {
2822 if (m_yFlipped) {
2823 m_backgroundRotation->setRotation(rotation);
2824 if (m_axisX->labelAutoAngle() > 0.0f ||
2825 m_axisY->labelAutoAngle() > 0.0f ||
2826 m_axisZ->labelAutoAngle() > 0.0f) {
2827 m_labelsNeedupdate = true;
2828 }
2829 } else {
2830 modelMatrix.rotate(quaternion: rotation);
2831 m_backgroundRotation->setRotation(rotation);
2832 if (m_axisX->labelAutoAngle() > 0.0f ||
2833 m_axisY->labelAutoAngle() > 0.0f ||
2834 m_axisZ->labelAutoAngle() > 0.0f) {
2835 m_labelsNeedupdate = true;
2836 }
2837 }
2838 }
2839
2840 bool forceUpdateCustomVolumes = false;
2841 if (m_changeTracker.projectionChanged) {
2842 forceUpdateCustomVolumes = true;
2843 bool useOrtho = isOrthoProjection();
2844 if (useOrtho)
2845 setCamera(m_oCamera);
2846 else
2847 setCamera(m_pCamera);
2848 m_changeTracker.projectionChanged = false;
2849 }
2850
2851 if (m_changeTracker.themeChanged) {
2852 theme()->resetDirtyBits();
2853 m_changeTracker.themeChanged = false;
2854 }
2855
2856 if (m_lightStrengthDirty) {
2857 light()->setBrightness(lightStrength() * .2f);
2858 if (qFuzzyIsNull(f: light()->brightness()))
2859 light()->setBrightness(.0000001f);
2860 updateLightStrength();
2861 m_lightStrengthDirty = false;
2862 }
2863
2864 if (m_ambientLightStrengthDirty) {
2865 float ambientStrength = m_ambientLightStrength;
2866 QColor ambientColor = QColor::fromRgbF(r: ambientStrength, g: ambientStrength, b: ambientStrength);
2867 light()->setAmbientColor(ambientColor);
2868 if (qFuzzyIsNull(f: light()->brightness()))
2869 light()->setBrightness(.0000001f);
2870 m_ambientLightStrengthDirty = false;
2871 }
2872
2873 if (m_lightColorDirty) {
2874 light()->setColor(lightColor());
2875 m_lightColorDirty = false;
2876 }
2877
2878 if (m_shadowStrengthDirty) {
2879 light()->setShadowFactor(shadowStrength());
2880 m_shadowStrengthDirty = false;
2881 }
2882
2883 if (theme()->dirtyBits()->gridDirty) {
2884 QQmlListReference materialRef(m_background, "materials");
2885 Q_ASSERT(materialRef.size());
2886 float mainWidth = theme()->grid().mainWidth();
2887 if ((m_gridLineType == QtGraphs3D::GridLineType::Shader) && mainWidth > 1.0f) {
2888 qCWarning(lcProperties3D, "%s invalid value for shader grid. Valid range for grid width is between"
2889 " 0.0 and 1.0. Value exceeds 1.0. Set it to 1.0", qUtf8Printable(QLatin1String(__FUNCTION__)));
2890 mainWidth = 1.0f;
2891 }
2892
2893 if ((m_gridLineType == QtGraphs3D::GridLineType::Shader) && mainWidth < 0.0f) {
2894 qCWarning(lcProperties3D, "%s invalid value for shader grid. Valid range for grid width is between"
2895 " 0.0 and 1.0. Value is smaller than 0.0. Set it to 0.0", qUtf8Printable(QLatin1String(__FUNCTION__)));
2896 mainWidth = 0.0f;
2897 }
2898 auto *material = static_cast<QQuick3DCustomMaterial *>(materialRef.at(0));
2899 material->setProperty(name: "gridWidth", value: mainWidth);
2900
2901 QColor gridMainColor = theme()->grid().mainColor();
2902 QQmlListReference backgroundRef(m_background, "materials");
2903 auto *backgroundMaterial = static_cast<QQuick3DCustomMaterial *>(backgroundRef.at(0));
2904 backgroundMaterial->setProperty(name: "gridLineColor", value: gridMainColor);
2905 QQmlListReference mainGridRef(m_gridGeometryModel, "materials");
2906 auto *gridMaterial = static_cast<QQuick3DPrincipledMaterial *>(mainGridRef.at(0));
2907 gridMaterial->setBaseColor(gridMainColor);
2908
2909 QColor gridSubColor = theme()->grid().subColor();
2910 backgroundMaterial->setProperty(name: "subgridLineColor", value: gridSubColor);
2911
2912 QQmlListReference subGridRef(m_subgridGeometryModel, "materials");
2913 auto *subgridMaterial = static_cast<QQuick3DPrincipledMaterial *>(subGridRef.at(0));
2914 subgridMaterial->setBaseColor(gridSubColor);
2915
2916 theme()->dirtyBits()->gridDirty = false;
2917 }
2918
2919 // label Adjustments
2920 if (theme()->dirtyBits()->labelBackgroundColorDirty) {
2921 QColor labelBackgroundColor = theme()->labelBackgroundColor();
2922 changeLabelBackgroundColor(repeater: m_repeaterX, color: labelBackgroundColor);
2923 changeLabelBackgroundColor(repeater: m_repeaterY, color: labelBackgroundColor);
2924 changeLabelBackgroundColor(repeater: m_repeaterZ, color: labelBackgroundColor);
2925 m_titleLabelX->setProperty(name: "backgroundColor", value: labelBackgroundColor);
2926 m_titleLabelY->setProperty(name: "backgroundColor", value: labelBackgroundColor);
2927 m_titleLabelZ->setProperty(name: "backgroundColor", value: labelBackgroundColor);
2928 m_itemLabel->setProperty(name: "backgroundColor", value: labelBackgroundColor);
2929
2930 if (m_sliceView) {
2931 changeLabelBackgroundColor(repeater: m_sliceHorizontalLabelRepeater, color: labelBackgroundColor);
2932 changeLabelBackgroundColor(repeater: m_sliceVerticalLabelRepeater, color: labelBackgroundColor);
2933 m_sliceItemLabel->setProperty(name: "backgroundColor", value: labelBackgroundColor);
2934 m_sliceHorizontalTitleLabel->setProperty(name: "backgroundColor", value: labelBackgroundColor);
2935 m_sliceVerticalTitleLabel->setProperty(name: "backgroundColor", value: labelBackgroundColor);
2936 }
2937 theme()->dirtyBits()->labelBackgroundColorDirty = false;
2938 }
2939
2940 if (theme()->dirtyBits()->labelBackgroundVisibilityDirty) {
2941 bool visible = theme()->isLabelBackgroundVisible();
2942 changeLabelBackgroundVisible(repeater: m_repeaterX, visible);
2943 changeLabelBackgroundVisible(repeater: m_repeaterY, visible);
2944 changeLabelBackgroundVisible(repeater: m_repeaterZ, visible);
2945 m_titleLabelX->setProperty(name: "backgroundVisible", value: visible);
2946 m_titleLabelY->setProperty(name: "backgroundVisible", value: visible);
2947 m_titleLabelZ->setProperty(name: "backgroundVisible", value: visible);
2948 m_itemLabel->setProperty(name: "backgroundVisible", value: visible);
2949
2950 if (m_sliceView) {
2951 changeLabelBackgroundVisible(repeater: m_sliceHorizontalLabelRepeater, visible);
2952 changeLabelBackgroundVisible(repeater: m_sliceVerticalLabelRepeater, visible);
2953 m_sliceItemLabel->setProperty(name: "backgroundVisible", value: visible);
2954 m_sliceHorizontalTitleLabel->setProperty(name: "backgroundVisible", value: visible);
2955 m_sliceVerticalTitleLabel->setProperty(name: "backgroundVisible", value: visible);
2956 }
2957 theme()->dirtyBits()->labelBackgroundVisibilityDirty = false;
2958 }
2959
2960 if (theme()->dirtyBits()->labelBorderVisibilityDirty) {
2961 bool visible = theme()->isLabelBorderVisible();
2962 changeLabelBorderVisible(repeater: m_repeaterX, visible);
2963 changeLabelBorderVisible(repeater: m_repeaterY, visible);
2964 changeLabelBorderVisible(repeater: m_repeaterZ, visible);
2965 m_titleLabelX->setProperty(name: "borderVisible", value: visible);
2966 m_titleLabelY->setProperty(name: "borderVisible", value: visible);
2967 m_titleLabelZ->setProperty(name: "borderVisible", value: visible);
2968 m_itemLabel->setProperty(name: "borderVisible", value: visible);
2969
2970 if (m_sliceView) {
2971 changeLabelBorderVisible(repeater: m_sliceHorizontalLabelRepeater, visible);
2972 changeLabelBorderVisible(repeater: m_sliceVerticalLabelRepeater, visible);
2973 m_sliceItemLabel->setProperty(name: "borderVisible", value: visible);
2974 m_sliceHorizontalTitleLabel->setProperty(name: "borderVisible", value: visible);
2975 m_sliceVerticalTitleLabel->setProperty(name: "borderVisible", value: visible);
2976 }
2977 theme()->dirtyBits()->labelBorderVisibilityDirty = false;
2978 }
2979
2980 if (theme()->dirtyBits()->labelTextColorDirty) {
2981 QColor labelTextColor = theme()->labelTextColor();
2982 m_itemLabel->setProperty(name: "labelTextColor", value: labelTextColor);
2983
2984 if (m_sliceView && isSliceEnabled())
2985 m_sliceItemLabel->setProperty(name: "labelTextColor", value: labelTextColor);
2986 theme()->dirtyBits()->labelTextColorDirty = false;
2987 }
2988
2989 if (theme()->dirtyBits()->axisXDirty) {
2990 QColor labelTextColor = theme()->axisX().labelTextColor();
2991 changeLabelTextColor(repeater: m_repeaterX, color: labelTextColor);
2992 m_titleLabelX->setProperty(name: "labelTextColor", value: labelTextColor);
2993 if (m_sliceView && isSliceEnabled()) {
2994 if (m_selectionMode == QtGraphs3D::SelectionFlag::Row)
2995 changeLabelTextColor(repeater: m_sliceHorizontalLabelRepeater, color: labelTextColor);
2996 m_sliceHorizontalTitleLabel->setProperty(name: "labelTextColor", value: labelTextColor);
2997 }
2998 theme()->dirtyBits()->axisXDirty = false;
2999 }
3000
3001 if (theme()->dirtyBits()->axisYDirty) {
3002 QColor labelTextColor = theme()->axisY().labelTextColor();
3003 changeLabelTextColor(repeater: m_repeaterY, color: labelTextColor);
3004 m_titleLabelY->setProperty(name: "labelTextColor", value: labelTextColor);
3005 if (m_sliceView && isSliceEnabled()) {
3006 changeLabelTextColor(repeater: m_sliceVerticalLabelRepeater, color: labelTextColor);
3007 m_sliceVerticalTitleLabel->setProperty(name: "labelTextColor", value: labelTextColor);
3008 }
3009 theme()->dirtyBits()->axisYDirty = false;
3010 }
3011
3012 if (theme()->dirtyBits()->axisZDirty) {
3013 QColor labelTextColor = theme()->axisZ().labelTextColor();
3014 changeLabelTextColor(repeater: m_repeaterZ, color: labelTextColor);
3015 m_titleLabelZ->setProperty(name: "labelTextColor", value: labelTextColor);
3016 if (m_sliceView && isSliceEnabled()) {
3017 if (m_selectionMode == QtGraphs3D::SelectionFlag::Column)
3018 changeLabelTextColor(repeater: m_sliceHorizontalLabelRepeater, color: labelTextColor);
3019 m_sliceHorizontalTitleLabel->setProperty(name: "labelTextColor", value: labelTextColor);
3020 }
3021 theme()->dirtyBits()->axisZDirty = false;
3022 }
3023
3024 if (theme()->dirtyBits()->labelFontDirty) {
3025 auto font = theme()->labelFont();
3026 changeLabelFont(repeater: m_repeaterX, font);
3027 changeLabelFont(repeater: m_repeaterY, font);
3028 changeLabelFont(repeater: m_repeaterZ, font);
3029 m_titleLabelX->setProperty(name: "labelFont", value: font);
3030 m_titleLabelY->setProperty(name: "labelFont", value: font);
3031 m_titleLabelZ->setProperty(name: "labelFont", value: font);
3032 m_itemLabel->setProperty(name: "labelFont", value: font);
3033 m_labelsNeedupdate = true;
3034
3035 if (m_sliceView && isSliceEnabled()) {
3036 changeLabelFont(repeater: m_sliceHorizontalLabelRepeater, font);
3037 changeLabelFont(repeater: m_sliceVerticalLabelRepeater, font);
3038 m_sliceItemLabel->setProperty(name: "labelFont", value: font);
3039 m_sliceHorizontalTitleLabel->setProperty(name: "labelFont", value: font);
3040 m_sliceVerticalTitleLabel->setProperty(name: "labelFont", value: font);
3041 updateSliceLabels();
3042 }
3043 theme()->dirtyBits()->labelFontDirty = false;
3044 m_isSeriesVisualsDirty = true;
3045 }
3046
3047 if (theme()->dirtyBits()->labelsVisibilityDirty) {
3048 bool visible = theme()->labelsVisible();
3049 changeLabelsVisible(repeater: m_repeaterX, visible);
3050 changeLabelsVisible(repeater: m_repeaterY, visible);
3051 changeLabelsVisible(repeater: m_repeaterZ, visible);
3052 m_titleLabelX->setProperty(name: "visible", value: visible && axisX()->isTitleVisible());
3053 m_titleLabelY->setProperty(name: "visible", value: visible && axisY()->isTitleVisible());
3054 m_titleLabelZ->setProperty(name: "visible", value: visible && axisZ()->isTitleVisible());
3055 m_itemLabel->setProperty(name: "visible", value: visible && m_itemSelected);
3056
3057 if (m_sliceView) {
3058 changeLabelsVisible(repeater: m_sliceHorizontalLabelRepeater, visible);
3059 changeLabelsVisible(repeater: m_sliceVerticalLabelRepeater, visible);
3060 m_sliceItemLabel->setProperty(name: "visible", value: visible && selectionMode()
3061 .testFlag(flag: QtGraphs3D::SelectionFlag::Item));
3062 m_sliceHorizontalTitleLabel->setProperty(name: "visible", value: visible);
3063 m_sliceVerticalTitleLabel->setProperty(name: "visible", value: visible);
3064 }
3065 theme()->dirtyBits()->labelsVisibilityDirty = false;
3066 }
3067
3068 // Grid and background adjustments
3069 if (theme()->dirtyBits()->plotAreaBackgroundColorDirty) {
3070 QQmlListReference materialRef(m_background, "materials");
3071 Q_ASSERT(materialRef.size());
3072 auto material = static_cast<QQuick3DCustomMaterial *>(materialRef.at(0));
3073 material->setProperty(name: "baseColor", value: theme()->plotAreaBackgroundColor());
3074 theme()->dirtyBits()->plotAreaBackgroundColorDirty = false;
3075 }
3076
3077 if (theme()->dirtyBits()->plotAreaBackgroundVisibilityDirty) {
3078 QQmlListReference materialRef(m_background, "materials");
3079 Q_ASSERT(materialRef.size());
3080 auto *material = static_cast<QQuick3DCustomMaterial *>(materialRef.at(0));
3081 material->setProperty(name: "baseVisible", value: theme()->isPlotAreaBackgroundVisible());
3082 theme()->dirtyBits()->plotAreaBackgroundVisibilityDirty = false;
3083 }
3084
3085 if (m_gridLineTypeDirty) {
3086 m_gridLineType = gridLineType();
3087 theme()->dirtyBits()->gridVisibilityDirty = true;
3088 theme()->dirtyBits()->gridDirty = true;
3089 m_gridUpdate = true;
3090 m_gridLineTypeDirty = false;
3091 }
3092
3093 if (theme()->dirtyBits()->gridVisibilityDirty) {
3094 bool visible = theme()->isGridVisible();
3095 QQmlListReference materialRef(m_background, "materials");
3096 Q_ASSERT(materialRef.size());
3097 auto *material = static_cast<QQuick3DCustomMaterial *>(materialRef.at(0));
3098 material->setProperty(name: "gridVisible", value: visible && (m_gridLineType == QtGraphs3D::GridLineType::Shader));
3099 m_gridGeometryModel->setVisible(visible &! (m_gridLineType == QtGraphs3D::GridLineType::Shader));
3100 m_subgridGeometryModel->setVisible(visible &! (m_gridLineType == QtGraphs3D::GridLineType::Shader));
3101
3102 if (m_sliceView && isSliceEnabled())
3103 m_sliceGridGeometryModel->setVisible(visible);
3104
3105 theme()->dirtyBits()->gridVisibilityDirty = false;
3106 }
3107
3108 if (theme()->dirtyBits()->singleHighlightColorDirty) {
3109 updateSingleHighlightColor();
3110 theme()->dirtyBits()->singleHighlightColorDirty = false;
3111 }
3112
3113 // Other adjustments
3114 if (theme()->dirtyBits()->backgroundColorDirty || theme()->dirtyBits()->backgroundVisibilityDirty) {
3115 updateBackgroundColor();
3116 theme()->dirtyBits()->backgroundColorDirty = false;
3117 theme()->dirtyBits()->backgroundVisibilityDirty = false;
3118 }
3119
3120 if (isCustomDataDirty()) {
3121 forceUpdateCustomVolumes = true;
3122 updateCustomData();
3123 setCustomDataDirty(false);
3124 }
3125
3126 if (m_changedSeriesList.size()) {
3127 forceUpdateCustomVolumes = true;
3128 updateGraph();
3129 m_changedSeriesList.clear();
3130 }
3131
3132 if (m_isSeriesVisualsDirty) {
3133 forceUpdateCustomVolumes = true;
3134 if (m_gridLineType == QtGraphs3D::GridLineType::Shader)
3135 updateGridLineType();
3136 else
3137 updateGrid();
3138 m_labelsNeedupdate = true;
3139 if (m_sliceView && isSliceEnabled()) {
3140 updateSliceGrid();
3141 updateSliceLabels();
3142 }
3143 updateGraph();
3144 m_isSeriesVisualsDirty = false;
3145 }
3146
3147 if (m_gridUpdate) {
3148 if (m_gridLineType == QtGraphs3D::GridLineType::Shader)
3149 updateGridLineType();
3150 else
3151 updateGrid();
3152 }
3153
3154 if (m_isDataDirty) {
3155 forceUpdateCustomVolumes = true;
3156 updateGraph();
3157 m_isDataDirty = false;
3158 }
3159
3160 if (m_sliceActivatedChanged)
3161 toggleSliceGraph();
3162
3163 if (isCustomItemDirty() || forceUpdateCustomVolumes)
3164 updateCustomVolumes();
3165
3166 if (m_measureFps)
3167 QQuickItem::update();
3168
3169 if (m_labelsNeedupdate)
3170 updateLabels();
3171
3172 Q_TRACE(QGraphs3DItemSynch_exit);
3173
3174 qCDebug(lcGraphs3D, "%s end syncing", qUtf8Printable(QLatin1String(__FUNCTION__)));
3175}
3176
3177void QQuickGraphsItem::updateGrid()
3178{
3179 Q_TRACE(QGraphs3DItemUpdateGrid_entry);
3180
3181 QQmlListReference materialsRef(m_background, "materials");
3182 auto *bgMat = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
3183 bgMat->setProperty(name: "scale", value: m_scaleWithBackground);
3184 qsizetype gridLineCountX = 0;
3185 qsizetype subGridLineCountX = 0;
3186 gridLineCountHelper(axis: axisX(), lineCount&: gridLineCountX, sublineCount&: subGridLineCountX);
3187
3188 qsizetype gridLineCountY = 0;
3189 qsizetype subGridLineCountY = 0;
3190 gridLineCountHelper(axis: axisY(), lineCount&: gridLineCountY, sublineCount&: subGridLineCountY);
3191
3192 qsizetype gridLineCountZ = 0;
3193 qsizetype subGridLineCountZ = 0;
3194 gridLineCountHelper(axis: axisZ(), lineCount&: gridLineCountZ, sublineCount&: subGridLineCountZ);
3195
3196 auto backgroundScale = m_scaleWithBackground + m_backgroundScaleMargin;
3197 QVector3D scaleX(backgroundScale.x() * lineLengthScaleFactor(),
3198 lineWidthScaleFactor(),
3199 lineWidthScaleFactor());
3200 QVector3D scaleY(lineWidthScaleFactor(),
3201 backgroundScale.y() * lineLengthScaleFactor(),
3202 lineWidthScaleFactor());
3203 const QVector3D scaleZ(backgroundScale.z() * lineLengthScaleFactor(),
3204 lineWidthScaleFactor(),
3205 lineWidthScaleFactor());
3206
3207 const bool xFlipped = isXFlipped();
3208 const bool yFlipped = isYFlipped();
3209 const bool zFlipped = isZFlipped();
3210
3211 const float lineOffset = 0.01f;
3212 const float backOffsetAdjustment = 0.005f;
3213
3214 QQuaternion lineRotation(.0f, .0f, .0f, .0f);
3215 QVector3D rotation(90.0f, 0.0f, 0.0f);
3216
3217 QByteArray vertices;
3218 qsizetype calculatedSize = 0;
3219
3220 QByteArray subvertices;
3221 qsizetype subCalculatedSize = 0;
3222
3223 bool usePolar = isPolar() && (m_graphType != QAbstract3DSeries::SeriesType::Bar);
3224
3225 if (!usePolar) {
3226 int factor = m_hasVerticalSegmentLine ? 2 : 1;
3227 calculatedSize = (factor * gridLineCountX + factor * gridLineCountZ + 2 * gridLineCountY)
3228 * 2 * sizeof(QVector3D);
3229 subCalculatedSize = (factor * subGridLineCountX + factor * subGridLineCountZ + 2 * subGridLineCountY)
3230 * 2 * sizeof(QVector3D);
3231 } else {
3232 int radialMainGridSize = static_cast<QValue3DAxis *>(axisZ())->gridSize() * polarRoundness;
3233 int radialSubGridSize = static_cast<QValue3DAxis *>(axisZ())->subGridSize()
3234 * polarRoundness;
3235
3236 qsizetype angularMainGridsize = static_cast<QValue3DAxis *>(axisX())->gridSize();
3237 qsizetype angularSubGridsize = static_cast<QValue3DAxis *>(axisX())->subGridSize();
3238
3239 calculatedSize = (radialMainGridSize + angularMainGridsize + (2 * gridLineCountY) - 1)
3240 * 2 * sizeof(QVector3D);
3241 subCalculatedSize = (radialSubGridSize + + angularSubGridsize + (2 * subGridLineCountY))
3242
3243 * 2 * sizeof(QVector3D);
3244 }
3245 vertices.resize(size: calculatedSize);
3246 QVector3D *data = reinterpret_cast<QVector3D *>(vertices.data());
3247
3248 subvertices.resize(size: subCalculatedSize);
3249 QVector3D *subdata = reinterpret_cast<QVector3D *>(subvertices.data());
3250
3251 // Floor horizontal line
3252 float linePosX = 0.0f;
3253 float linePosY = backgroundScale.y();
3254 float linePosZ = 0.0f;
3255 float scale = m_scaleWithBackground.z();
3256
3257 float x0 = backgroundScale.x();
3258 float x1 = -backgroundScale.x();
3259
3260 float tempLineOffset = -lineOffset;
3261 if (!yFlipped) {
3262 linePosY *= -1.0f;
3263 rotation.setZ(180.0f);
3264 tempLineOffset *= -1.0f;
3265 }
3266 lineRotation = Utils::calculateRotation(xyzRotations: rotation);
3267 linePosY *= m_horizontalFlipFactor;
3268 tempLineOffset *= m_horizontalFlipFactor;
3269 if (!usePolar) {
3270 for (int i = 0; i < subGridLineCountZ; i++) {
3271 if (axisZ()->type() == QAbstract3DAxis::AxisType::Value) {
3272 linePosZ = static_cast<QValue3DAxis *>(axisZ())->subGridPositionAt(gridLine: i) * -scale
3273 * 2.0f
3274 + scale;
3275 } else if (axisZ()->type() == QAbstract3DAxis::AxisType::Category) {
3276 linePosZ = calculateCategoryGridLinePosition(axis: axisZ(), index: i);
3277 linePosY = calculateCategoryGridLinePosition(axis: axisY(), index: i);
3278 }
3279
3280 *subdata++ = QVector3D(x0, linePosY + tempLineOffset, linePosZ);
3281 *subdata++ = QVector3D(x1, linePosY + tempLineOffset, linePosZ);
3282 }
3283
3284 for (int i = 0; i < gridLineCountZ; i++) {
3285 if (axisZ()->type() == QAbstract3DAxis::AxisType::Value) {
3286 linePosZ = static_cast<QValue3DAxis *>(axisZ())->gridPositionAt(gridLine: i) * -scale * 2.0f
3287 + scale;
3288 } else if (axisZ()->type() == QAbstract3DAxis::AxisType::Category) {
3289 linePosZ = calculateCategoryGridLinePosition(axis: axisZ(), index: i);
3290 linePosY = calculateCategoryGridLinePosition(axis: axisY(), index: i);
3291 }
3292
3293 *data++ = QVector3D(x0, linePosY + tempLineOffset, linePosZ);
3294 *data++ = QVector3D(x1, linePosY + tempLineOffset, linePosZ);
3295 }
3296 } else {
3297 auto valueAxisZ = static_cast<QValue3DAxis *>(axisZ());
3298
3299 for (int k = 0; k < subGridLineCountZ; k++) {
3300 float degrees = 0.0f;
3301 const float r = (m_polarRadius) *valueAxisZ->subGridPositionAt(gridLine: k);
3302 QVector3D lastPoint(r * qCos(v: degrees), linePosY + tempLineOffset, r * qSin(v: degrees));
3303 for (int i = 1; i <= polarRoundness; i++) {
3304 degrees = doublePi * i / polarRoundness;
3305 const float xPos = qCos(v: degrees);
3306 const float zPos = qSin(v: degrees);
3307
3308 const QVector3D pos(r * xPos, linePosY + tempLineOffset, r * zPos);
3309 *subdata++ = lastPoint;
3310 *subdata++ = pos;
3311 lastPoint = pos;
3312 }
3313 }
3314
3315 for (int k = 0; k < gridLineCountZ; k++) {
3316 float degrees = 0.0f;
3317 const float r = (m_polarRadius) *valueAxisZ->gridPositionAt(gridLine: k);
3318 QVector3D lastPoint(r * qCos(v: degrees), linePosY + tempLineOffset, r * qSin(v: degrees));
3319
3320 for (int i = 1; i <= polarRoundness; i++) {
3321 degrees = doublePi * i / polarRoundness;
3322 const float xPos = qCos(v: degrees);
3323 const float zPos = qSin(v: degrees);
3324
3325 const QVector3D pos(r * xPos, linePosY + tempLineOffset, r * zPos);
3326 *data++ = lastPoint;
3327 *data++ = pos;
3328 lastPoint = pos;
3329 }
3330 }
3331 }
3332
3333 // Side vertical line
3334 linePosX = -backgroundScale.x();
3335 linePosY = 0.0f;
3336 rotation = QVector3D(0.0f, 90.0f, 0.0f);
3337
3338 float y0 = -backgroundScale.y();
3339 float y1 = backgroundScale.y();
3340
3341 x0 = -backgroundScale.x();
3342 x1 = -backgroundScale.x();
3343
3344 tempLineOffset = lineOffset;
3345
3346 if (xFlipped) {
3347 linePosX *= -1.0f;
3348 rotation.setY(-90.0f);
3349 tempLineOffset *= -1.0f;
3350 x0 *= -1.0f;
3351 x1 *= -1.0f;
3352 }
3353 lineRotation = Utils::calculateRotation(xyzRotations: rotation);
3354 if (m_hasVerticalSegmentLine) {
3355 for (int i = 0; i < subGridLineCountZ; i++) {
3356 if (axisZ()->type() == QAbstract3DAxis::AxisType::Value) {
3357 linePosZ = static_cast<QValue3DAxis *>(axisZ())->subGridPositionAt(gridLine: i) * scale * 2.0f
3358 - scale;
3359 }
3360
3361 *subdata++ = QVector3D(x0 + tempLineOffset, y0, linePosZ);
3362 *subdata++ = QVector3D(x1 + tempLineOffset, y1, linePosZ);
3363 }
3364
3365 for (int i = 0; i < gridLineCountZ; i++) {
3366 if (axisZ()->type() == QAbstract3DAxis::AxisType::Value) {
3367 linePosZ = static_cast<QValue3DAxis *>(axisZ())->gridPositionAt(gridLine: i) * scale * 2.0f
3368 - scale;
3369 }
3370
3371 *data++ = QVector3D(x0 + tempLineOffset, y0, linePosZ);
3372 *data++ = QVector3D(x1 + tempLineOffset, y1, linePosZ);
3373 }
3374 }
3375
3376 // Side horizontal line
3377 scale = m_scaleWithBackground.y();
3378 rotation = QVector3D(180.0f, -90.0f, 0.0f);
3379
3380 float z0 = backgroundScale.z();
3381 float z1 = -backgroundScale.z();
3382
3383 x0 = -backgroundScale.x();
3384 x1 = -backgroundScale.x();
3385
3386 tempLineOffset = lineOffset;
3387
3388 if (xFlipped) {
3389 rotation.setY(90.0f);
3390 tempLineOffset *= -1.0f;
3391 x0 *= -1.0f;
3392 x1 *= -1.0f;
3393 }
3394 lineRotation = Utils::calculateRotation(xyzRotations: rotation);
3395 for (int i = 0; i < gridLineCountY; i++) {
3396 if (axisY()->type() == QAbstract3DAxis::AxisType::Value) {
3397 linePosY = static_cast<QValue3DAxis *>(axisY())->gridPositionAt(gridLine: i) * scale * 2.0f
3398 - scale;
3399 } else if (axisY()->type() == QAbstract3DAxis::AxisType::Category) {
3400 linePosY = calculateCategoryGridLinePosition(axis: axisY(), index: i);
3401 }
3402
3403 *data++ = QVector3D(x0 + tempLineOffset, linePosY, z0);
3404 *data++ = QVector3D(x1 + tempLineOffset, linePosY, z1);
3405 }
3406
3407 for (int i = 0; i < subGridLineCountY; i++) {
3408 if (axisY()->type() == QAbstract3DAxis::AxisType::Value) {
3409 linePosY = static_cast<QValue3DAxis *>(axisY())->subGridPositionAt(gridLine: i) * scale * 2.0f
3410 - scale;
3411 } else if (axisY()->type() == QAbstract3DAxis::AxisType::Category) {
3412 linePosY = calculateCategoryGridLinePosition(axis: axisY(), index: i);
3413 }
3414
3415 *subdata++ = QVector3D(x0 + tempLineOffset, linePosY, z0);
3416 *subdata++ = QVector3D(x1 + tempLineOffset, linePosY, z1);
3417 }
3418
3419 // Floor vertical line
3420 linePosY = -backgroundScale.y();
3421 rotation = QVector3D(-90.0f, 90.0f, 0.0f);
3422
3423 tempLineOffset = lineOffset;
3424 z0 = backgroundScale.z();
3425 z1 = -backgroundScale.z();
3426
3427 if (yFlipped) {
3428 linePosY *= -1.0f;
3429 rotation.setZ(180.0f);
3430 tempLineOffset *= -1.0f;
3431 }
3432 scale = m_scaleWithBackground.x();
3433 linePosY *= m_horizontalFlipFactor;
3434 tempLineOffset *= m_horizontalFlipFactor;
3435
3436 if (!usePolar) {
3437 for (int i = 0; i < subGridLineCountX; i++) {
3438 if (axisX()->type() == QAbstract3DAxis::AxisType::Value) {
3439 linePosX = static_cast<QValue3DAxis *>(axisX())->subGridPositionAt(gridLine: i) * scale * 2.0f
3440 - scale;
3441 } else if (axisX()->type() == QAbstract3DAxis::AxisType::Category) {
3442 linePosX = calculateCategoryGridLinePosition(axis: axisX(), index: i);
3443 linePosY = calculateCategoryGridLinePosition(axis: axisY(), index: i);
3444 }
3445
3446 *subdata++ = QVector3D(linePosX, linePosY + tempLineOffset, z0);
3447 *subdata++ = QVector3D(linePosX, linePosY + tempLineOffset, z1);
3448 }
3449
3450 for (int i = 0; i < gridLineCountX; i++) {
3451 if (axisX()->type() == QAbstract3DAxis::AxisType::Value) {
3452 linePosX = static_cast<QValue3DAxis *>(axisX())->gridPositionAt(gridLine: i) * scale * 2.0f
3453 - scale;
3454 } else if (axisX()->type() == QAbstract3DAxis::AxisType::Category) {
3455 linePosX = calculateCategoryGridLinePosition(axis: axisX(), index: i);
3456 linePosY = calculateCategoryGridLinePosition(axis: axisY(), index: i);
3457 }
3458
3459 *data++ = QVector3D(linePosX, linePosY + tempLineOffset, backgroundScale.z());
3460 *data++ = QVector3D(linePosX, linePosY + tempLineOffset, -backgroundScale.z());
3461 }
3462 } else {
3463 auto valueAxisX = static_cast<QValue3DAxis *>(axisX());
3464 const QVector3D center(0.0f, linePosY + tempLineOffset, 0.0f);
3465 const float halfRatio = ((m_polarRadius) + (m_labelMargin * 0.5f));
3466
3467 for (int i = 0; i < subGridLineCountX; i++) {
3468 float angle = valueAxisX->subGridPositionAt(gridLine: i) * 360.0f - rotationOffset;
3469 float posX = halfRatio * qCos(v: qDegreesToRadians(degrees: angle));
3470 float posZ = halfRatio * qSin(v: qDegreesToRadians(degrees: angle));
3471 *subdata++ = center;
3472 *subdata++ = QVector3D(posX, linePosY + tempLineOffset, posZ);
3473 }
3474
3475 for (int i = 0; i < gridLineCountX - 1; i++) {
3476 float angle = valueAxisX->gridPositionAt(gridLine: i) * 360.0f - rotationOffset;
3477 float posX = halfRatio * qCos(v: qDegreesToRadians(degrees: angle));
3478 float posZ = halfRatio * qSin(v: qDegreesToRadians(degrees: angle));
3479 *data++ = center;
3480 *data++ = QVector3D(posX, linePosY + tempLineOffset, posZ);
3481 }
3482 }
3483
3484 // Back horizontal line
3485 linePosX = 0.0f;
3486 rotation = QVector3D(0.0f, 0.0f, 0.0f);
3487
3488 x0 = -backgroundScale.x();
3489 x1 = backgroundScale.x();
3490
3491 z0 = -backgroundScale.z();
3492 z1 = -backgroundScale.z();
3493
3494 tempLineOffset = lineOffset;
3495 float tempBackOffsetAdjustment = backOffsetAdjustment;
3496
3497 if (zFlipped) {
3498 rotation.setX(180.0f);
3499 z0 *= -1.0f;
3500 z1 *= -1.0f;
3501 tempLineOffset *= -1.0f;
3502 tempBackOffsetAdjustment *= -1.0f;
3503 }
3504 lineRotation = Utils::calculateRotation(xyzRotations: rotation);
3505 scale = m_scaleWithBackground.y();
3506 for (int i = 0; i < subGridLineCountY; i++) {
3507 if (axisY()->type() == QAbstract3DAxis::AxisType::Value) {
3508 linePosY = static_cast<QValue3DAxis *>(axisY())->subGridPositionAt(gridLine: i) * scale * 2.0f
3509 - scale;
3510 } else if (axisY()->type() == QAbstract3DAxis::AxisType::Category) {
3511 linePosY = calculateCategoryGridLinePosition(axis: axisY(), index: i);
3512 }
3513 *subdata++ = QVector3D(x0, linePosY, z0 + tempLineOffset + tempBackOffsetAdjustment);
3514 *subdata++ = QVector3D(x1, linePosY, z1 + tempLineOffset + tempBackOffsetAdjustment);
3515 }
3516
3517 for (int i = 0; i < gridLineCountY; i++) {
3518 if (axisY()->type() == QAbstract3DAxis::AxisType::Value) {
3519 linePosY = static_cast<QValue3DAxis *>(axisY())->gridPositionAt(gridLine: i) * scale * 2.0f
3520 - scale;
3521 } else if (axisY()->type() == QAbstract3DAxis::AxisType::Category) {
3522 linePosY = calculateCategoryGridLinePosition(axis: axisY(), index: i);
3523 }
3524 *data++ = QVector3D(x0, linePosY, z0 + tempLineOffset + tempBackOffsetAdjustment);
3525 *data++ = QVector3D(x1, linePosY, z1 + tempLineOffset + tempBackOffsetAdjustment);
3526 }
3527
3528 // Back vertical line
3529 scale = m_scaleWithBackground.x();
3530 rotation = QVector3D(0.0f, 0.0f, 0.0f);
3531
3532 y0 = -backgroundScale.y();
3533 y1 = backgroundScale.y();
3534
3535 z0 = -backgroundScale.z();
3536 z1 = -backgroundScale.z();
3537
3538 tempLineOffset = lineOffset;
3539 tempBackOffsetAdjustment = backOffsetAdjustment;
3540
3541 if (zFlipped) {
3542 rotation.setY(180.0f);
3543 z0 *= -1.0f;
3544 z1 *= -1.0f;
3545 tempLineOffset *= -1.0f;
3546 tempBackOffsetAdjustment *= -1.0f;
3547 }
3548 lineRotation = Utils::calculateRotation(xyzRotations: rotation);
3549 if (m_hasVerticalSegmentLine) {
3550 for (int i = 0; i < gridLineCountX; i++) {
3551 if (axisX()->type() == QAbstract3DAxis::AxisType::Value) {
3552 linePosX = static_cast<QValue3DAxis *>(axisX())->gridPositionAt(gridLine: i) * scale * 2.0f
3553 - scale;
3554 }
3555 *data++ = QVector3D(linePosX, y0, z0 + tempLineOffset + tempBackOffsetAdjustment);
3556 *data++ = QVector3D(linePosX, y1, z1 + tempLineOffset + tempBackOffsetAdjustment);
3557 }
3558
3559 for (int i = 0; i < subGridLineCountX; i++) {
3560 if (axisX()->type() == QAbstract3DAxis::AxisType::Value) {
3561 linePosX = static_cast<QValue3DAxis *>(axisX())->subGridPositionAt(gridLine: i) * scale * 2.0f
3562 - scale;
3563 }
3564 *subdata++ = QVector3D(linePosX, y0, z0 + tempLineOffset + tempBackOffsetAdjustment);
3565 *subdata++ = QVector3D(linePosX, y1, z1 + tempLineOffset + tempBackOffsetAdjustment);
3566 }
3567 }
3568 QQuick3DGeometry *gridGeometry = m_gridGeometryModel->geometry();
3569 gridGeometry->setVertexData(vertices);
3570 gridGeometry->update();
3571 QQuick3DGeometry *subgridGeometry = m_subgridGeometryModel->geometry();
3572 subgridGeometry->setVertexData(subvertices);
3573 subgridGeometry->update();
3574 m_gridUpdate = false;
3575 Q_TRACE(QGraphs3DItemUpdateGrid_exit);
3576}
3577
3578void QQuickGraphsItem::updateGridLineType()
3579{
3580 const int textureSize = 4096;
3581 QVector<QVector4D> grid(textureSize * 2, QVector4D(0, 0, 0, 0));
3582 QQmlListReference materialsRef(m_background, "materials");
3583 QQuick3DCustomMaterial *bgMat;
3584 if (!materialsRef.size()) {
3585 bgMat = createQmlCustomMaterial(QStringLiteral(":/materials/BackgroundMaterial"));
3586 bgMat->setParent(m_background);
3587 materialsRef.append(bgMat);
3588 } else {
3589 bgMat = static_cast<QQuick3DCustomMaterial *>(materialsRef.at(0));
3590 }
3591
3592 QVariant texAsVariant = bgMat->property(name: "gridTex");
3593 auto *texinput = texAsVariant.value<QQuick3DShaderUtilsTextureInput *>();
3594 QQuick3DTexture *texMap = texinput->texture();
3595 QQuick3DTextureData *mapData = nullptr;
3596 if (!texMap) {
3597 texMap = new QQuick3DTexture();
3598 texMap->setParent(this);
3599 texMap->setHorizontalTiling(QQuick3DTexture::MirroredRepeat);
3600 texMap->setVerticalTiling(QQuick3DTexture::MirroredRepeat);
3601 texMap->setMinFilter(QQuick3DTexture::Linear);
3602 texMap->setMagFilter(QQuick3DTexture::Nearest);
3603 mapData = new QQuick3DTextureData();
3604 mapData->setSize(QSize(textureSize, 2));
3605 mapData->setFormat(QQuick3DTextureData::RGBA32F);
3606 mapData->setParent(texMap);
3607 mapData->setParentItem(texMap);
3608 } else {
3609 mapData = texMap->textureData();
3610 }
3611
3612 QVector<qsizetype> lineCounts(6);
3613 gridLineCountHelper(axis: axisX(), lineCount&: lineCounts[0], sublineCount&: lineCounts[3]);
3614 gridLineCountHelper(axis: axisY(), lineCount&: lineCounts[1], sublineCount&: lineCounts[4]);
3615 gridLineCountHelper(axis: axisZ(), lineCount&: lineCounts[2], sublineCount&: lineCounts[5]);
3616
3617 float baseWidth = 100;
3618 QVector<int> lineWidths(3);
3619 lineWidths[0] = baseWidth / m_scaleWithBackground.x();
3620 lineWidths[1] = baseWidth / m_scaleWithBackground.y();
3621 lineWidths[2] = baseWidth / m_scaleWithBackground.z();
3622
3623 QVector<QVector4D> axisMask = {QVector4D(1, 0, 0, 1),
3624 QVector4D(0, 1, 0, 1),
3625 QVector4D(0, 0, 1, 1)};
3626
3627 bgMat->setProperty(name: "scale", value: m_scaleWithBackground);
3628 bgMat->setProperty(name: "polar", value: isPolar());
3629 bool xCat = axisX()->type() == QAbstract3DAxis::AxisType::Category;
3630 bool zCat = axisZ()->type() == QAbstract3DAxis::AxisType::Category;
3631 bgMat->setProperty(name: "xCategory", value: xCat);
3632 bgMat->setProperty(name: "zCategory", value: zCat);
3633 bgMat->setProperty(name: "margin", value: backgroundScaleMargin());
3634
3635 for (int i = 0; i < lineCounts.size(); i++) {
3636 qsizetype lineCount = lineCounts[i];
3637 int axis = i % 3;
3638 int subGridOffset = textureSize * float(i > 2);
3639 QVector4D mask = axisMask.at(i: axis);
3640 QVector4D revMask = QVector4D(1, 1, 1, 1) - mask;
3641 for (int j = 0; j < lineCount; j++) {
3642 float linePos = -1;
3643 switch (i) {
3644 case 0:
3645 if (!xCat)
3646 linePos = static_cast<QValue3DAxis *>(axisX())->gridPositionAt(gridLine: j);
3647 else
3648 linePos = float(j) / float(lineCount);
3649 break;
3650 case 1:
3651 if (axisY()->type() == QAbstract3DAxis::AxisType::Value)
3652 linePos = static_cast<QValue3DAxis *>(axisY())->gridPositionAt(gridLine: j);
3653 else
3654 linePos = float(j) / float(lineCount);
3655 break;
3656 case 2:
3657 if (!zCat)
3658 linePos = static_cast<QValue3DAxis *>(axisZ())->gridPositionAt(gridLine: j);
3659 else
3660 linePos = float(j) / float(lineCount);
3661 break;
3662 case 3:
3663 if (!xCat)
3664 linePos = static_cast<QValue3DAxis *>(axisX())->subGridPositionAt(gridLine: j);
3665 break;
3666 case 4:
3667 if (axisY()->type() == QAbstract3DAxis::AxisType::Value)
3668 linePos = static_cast<QValue3DAxis *>(axisY())->subGridPositionAt(gridLine: j);
3669 break;
3670 case 5:
3671 if (!zCat)
3672 linePos = static_cast<QValue3DAxis *>(axisZ())->subGridPositionAt(gridLine: j);
3673 break;
3674 }
3675 if (linePos < 0)
3676 continue;
3677
3678 int index = ((textureSize - 1) * linePos) + subGridOffset;
3679 for (int k = 0; k < lineWidths[axis]; k++) {
3680 float nextIdx = qMin(a: index + k, b: textureSize * 2 - 1);
3681 float prevIdx = qMax(a: index - k, b: 0);
3682
3683 float dist = float(lineWidths[axis] - k) / float(lineWidths[axis]);
3684 float curDist = (grid[nextIdx] * mask).toVector3D().length();
3685
3686 if (dist > curDist)
3687 grid[nextIdx] = grid[nextIdx] * revMask + dist * mask;
3688
3689 curDist = (grid[prevIdx] * mask).toVector3D().length();
3690 if (dist > curDist)
3691 grid[prevIdx] = grid[prevIdx] * revMask + dist * mask;
3692 }
3693 }
3694 }
3695
3696 QByteArray data = QByteArray(reinterpret_cast<char *>(grid.data()),
3697 grid.size() * sizeof(QVector4D));
3698 mapData->setTextureData(data);
3699 texMap->setTextureData(mapData);
3700 texinput->setTexture(texMap);
3701 m_gridUpdate = false;
3702}
3703
3704float QQuickGraphsItem::fontScaleFactor(float pointSize)
3705{
3706 return 0.00007f + pointSize / (500000.0f * pointSize);
3707}
3708
3709float QQuickGraphsItem::labelAdjustment(float width)
3710{
3711 float a = -2.43761e-13f;
3712 float b = 4.23579e-10f;
3713 float c = 0.00414881f;
3714
3715 float factor = a * qPow(x: width, y: 3) + b * qPow(x: width, y: 2) + c;
3716#if defined(Q_OS_WIN)
3717 factor *= .8f;
3718#endif
3719 float ret = width * .5f * factor;
3720 return ret;
3721}
3722
3723void QQuickGraphsItem::gridLineCountHelper(QAbstract3DAxis *axis, qsizetype &lineCount, qsizetype &sublineCount)
3724{
3725 if (axis->type() == QAbstract3DAxis::AxisType::Value) {
3726 auto valueAxis = static_cast<QValue3DAxis *>(axis);
3727 lineCount = valueAxis->gridSize();
3728 sublineCount = valueAxis->subGridSize();
3729 } else if (axis->type() == QAbstract3DAxis::AxisType::Category) {
3730 lineCount = axis->labels().size();
3731 sublineCount = 0;
3732 }
3733}
3734
3735QVector3D QQuickGraphsItem::graphPosToAbsolute(QVector3D position)
3736{
3737 QVector3D pos = position;
3738 const int maxX = axisX()->max();
3739 const int minX = axisX()->min();
3740 const int maxY = axisY()->max();
3741 const int minY = axisY()->min();
3742 const int maxZ = axisZ()->max();
3743 const int minZ = axisZ()->min();
3744
3745 float xAdjust = 1.0f;
3746 float yAdjust = 1.0f;
3747 float zAdjust = 1.0f;
3748
3749 auto xValueAxis = qobject_cast<QValue3DAxis *>(object: axisX());
3750 auto yValueAxis = qobject_cast<QValue3DAxis *>(object: axisY());
3751 auto zValueAxis = qobject_cast<QValue3DAxis *>(object: axisZ());
3752
3753 if (xValueAxis)
3754 xAdjust = xValueAxis->reversed()? -1.0f: 1.0f;
3755 if (yValueAxis)
3756 yAdjust = yValueAxis->reversed()? -1.0f: 1.0f;
3757 if (zValueAxis)
3758 zAdjust = zValueAxis->reversed()? 1.0f: -1.0f;
3759
3760 const QVector3D adjustment = m_scaleWithBackground * QVector3D(xAdjust, yAdjust, zAdjust);
3761
3762 float xNormalizer = maxX - minX;
3763 float xPos = (pos.x() - minX) / xNormalizer;
3764 float yNormalizer = maxY - minY;
3765 float yPos = (pos.y() - minY) / yNormalizer;
3766 float zNormalizer = maxZ - minZ;
3767 float zPos = (pos.z() - minZ) / zNormalizer;
3768 pos = QVector3D(xPos, yPos, zPos);
3769 if (isPolar()) {
3770 float angle = xPos * M_PI * 2.0f;
3771 float radius = zPos;
3772 xPos = radius * qSin(v: angle) * 1.0f;
3773 zPos = -(radius * qCos(v: angle)) * 1.0f;
3774 yPos = yPos * adjustment.y() * 2.0f - adjustment.y();
3775 pos = QVector3D(xPos, yPos, zPos);
3776 } else {
3777 pos = pos * adjustment * 2.0f - adjustment;
3778 }
3779 return pos;
3780}
3781
3782void QQuickGraphsItem::updateLabels()
3783{
3784 Q_TRACE(QGraphs3DItemUpdateLabels_entry);
3785
3786 auto labels = axisX()->labels();
3787 qsizetype labelCount = labels.size();
3788 float labelAutoAngle = m_labelMargin >= 0? axisX()->labelAutoAngle() : 0;
3789 float labelAngleFraction = labelAutoAngle / 90.0f;
3790 float fractionCamX = m_xRotation * labelAngleFraction;
3791 float fractionCamY = m_yRotation * labelAngleFraction;
3792
3793 QVector3D labelRotation = QVector3D(0.0f, 0.0f, 0.0f);
3794
3795 float xPos = 0.0f;
3796 float yPos = 0.0f;
3797 float zPos = 0.0f;
3798
3799 const bool xFlipped = isXFlipped();
3800 const bool yFlipped = isYFlipped();
3801 const bool zFlipped = isZFlipped();
3802
3803 auto backgroundScale = m_scaleWithBackground + m_backgroundScaleMargin;
3804
3805 if (labelAutoAngle == 0.0f) {
3806 labelRotation = QVector3D(-90.0f, 90.0f, 0.0f);
3807 if (xFlipped)
3808 labelRotation.setY(-90.0f);
3809 if (yFlipped) {
3810 if (xFlipped)
3811 labelRotation.setY(-90.0f);
3812 else
3813 labelRotation.setY(90.0f);
3814 labelRotation.setX(90.0f);
3815 }
3816 } else {
3817 if (xFlipped)
3818 labelRotation.setY(-90.0f);
3819 else
3820 labelRotation.setY(90.0f);
3821 if (yFlipped) {
3822 if (zFlipped) {
3823 if (xFlipped) {
3824 labelRotation.setX(90.0f
3825 - (2.0f * labelAutoAngle - fractionCamX)
3826 * (labelAutoAngle + fractionCamY) / labelAutoAngle);
3827 labelRotation.setZ(-labelAutoAngle - fractionCamY);
3828 } else {
3829 labelRotation.setX(90.0f
3830 - (2.0f * labelAutoAngle + fractionCamX)
3831 * (labelAutoAngle + fractionCamY) / labelAutoAngle);
3832 labelRotation.setZ(labelAutoAngle + fractionCamY);
3833 }
3834 } else {
3835 if (xFlipped) {
3836 labelRotation.setX(
3837 90.0f + fractionCamX * -(labelAutoAngle + fractionCamY) / labelAutoAngle);
3838 labelRotation.setZ(labelAutoAngle + fractionCamY);
3839 } else {
3840 labelRotation.setX(
3841 90.0f - fractionCamX * (-labelAutoAngle - fractionCamY) / labelAutoAngle);
3842 labelRotation.setZ(-labelAutoAngle - fractionCamY);
3843 }
3844 }
3845 } else {
3846 if (zFlipped) {
3847 if (xFlipped) {
3848 labelRotation.setX(-90.0f
3849 + (2.0f * labelAutoAngle - fractionCamX)
3850 * (labelAutoAngle - fractionCamY) / labelAutoAngle);
3851 labelRotation.setZ(labelAutoAngle - fractionCamY);
3852 } else {
3853 labelRotation.setX(-90.0f
3854 + (2.0f * labelAutoAngle + fractionCamX)
3855 * (labelAutoAngle - fractionCamY) / labelAutoAngle);
3856 labelRotation.setZ(-labelAutoAngle + fractionCamY);
3857 }
3858 } else {
3859 if (xFlipped) {
3860 labelRotation.setX(
3861 -90.0f - fractionCamX * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
3862 labelRotation.setZ(-labelAutoAngle + fractionCamY);
3863 } else {
3864 labelRotation.setX(
3865 -90.0f + fractionCamX * -(labelAutoAngle - fractionCamY) / labelAutoAngle);
3866 labelRotation.setZ(labelAutoAngle - fractionCamY);
3867 }
3868 }
3869 }
3870 }
3871 if (isPolar())
3872 labelRotation.setY(0.0f);
3873 QQuaternion totalRotation = Utils::calculateRotation(xyzRotations: labelRotation);
3874
3875 float scale = backgroundScale.x() - m_backgroundScaleMargin.x();
3876
3877 float relativeScale = scale / m_axisX->labels().count();
3878
3879 float pointSize = theme()->labelFont().pointSizeF();
3880
3881 float textPadding = pointSize * .5f;
3882
3883 float labelsMaxWidth = float(findLabelsMaxWidth(labels: axisX()->labels())) + textPadding;
3884
3885 QFontMetrics fm(theme()->labelFont());
3886 float labelHeight = fm.height() + textPadding;
3887
3888 // set base size to some reasonable value
3889 float fontRatio = labelsMaxWidth / labelHeight;
3890
3891 float adjustment;
3892 const float baseSize = 25.0f;
3893 const float relativePointSize = theme()->labelFont().pointSizeF() / baseSize;
3894 const float scaleFactor = fontScaleFactor(pointSize) * pointSize;
3895
3896 if (axisX()->isScaleLabelsByCount()) {
3897 m_fontScaled = QVector3D(0.01f * fontRatio * relativePointSize * relativeScale,
3898 0.01f * relativePointSize * relativeScale,
3899 0.01f) * axisX()->labelSize();
3900 adjustment = m_fontScaled.y() * 110.0f;
3901 } else {
3902 m_fontScaled = QVector3D(scaleFactor * fontRatio, scaleFactor, 0.00001f)
3903 * axisX()->labelSize();
3904 adjustment = labelAdjustment(width: labelsMaxWidth * axisX()->labelSize());
3905 }
3906
3907 zPos = backgroundScale.z() + adjustment + m_labelMargin;
3908
3909 adjustment *= qAbs(t: qSin(v: qDegreesToRadians(degrees: labelRotation.z())));
3910 const float labelDepthMargin = 0.03f; //margin to prevent z-fighting
3911 yPos = backgroundScale.y() + adjustment - labelDepthMargin;
3912
3913 float yOffset = -0.1f;
3914 if (!yFlipped) {
3915 yPos *= -1.0f;
3916 yOffset *= -1.0f;
3917 }
3918
3919 if (zFlipped)
3920 zPos *= -1.0f;
3921
3922 auto labelTrans = QVector3D(0.0f, yPos, zPos);
3923 float angularLabelZPos = 0.0f;
3924
3925 const float angularAdjustment{1.1f};
3926 if (axisX()->type() == QAbstract3DAxis::AxisType::Value) {
3927 auto valueAxisX = static_cast<QValue3DAxis *>(axisX());
3928 for (int i = 0; i < repeaterX()->count(); i++) {
3929 if (labelCount <= i)
3930 break;
3931 auto obj = static_cast<QQuick3DNode *>(repeaterX()->objectAt(index: i));
3932 if (isPolar()) {
3933 if (i == repeaterX()->count() - 1) {
3934 obj->setVisible(false);
3935 break;
3936 }
3937 float rad = qDegreesToRadians(degrees: valueAxisX->labelPositionAt(index: i) * 360.0f);
3938 labelTrans.setX((-qSin(v: rad) * -scale + qSin(v: rad) * m_labelMargin * m_polarRadius)
3939 * angularAdjustment);
3940 labelTrans.setY(yPos + yOffset);
3941 labelTrans.setZ((qCos(v: rad) * -scale - qCos(v: rad) * m_labelMargin * m_polarRadius)
3942 * angularAdjustment);
3943 if (i == 0) {
3944 angularLabelZPos = labelTrans.z();
3945 rad = qDegreesToRadians(degrees: valueAxisX->labelPositionAt(index: i) * 360.0f);
3946 labelTrans.setX(
3947 (-qSin(v: rad) * -scale + qSin(v: rad) * m_labelMargin * m_polarRadius));
3948 labelTrans.setY(yPos + yOffset);
3949 labelTrans.setZ(
3950 (qCos(v: rad) * -scale - qCos(v: rad) * m_labelMargin * m_polarRadius));
3951 }
3952 } else {
3953 labelTrans.setX(valueAxisX->labelPositionAt(index: i) * scale * 2.0f - scale);
3954 }
3955 obj->setObjectName(QStringLiteral("ElementAxisXLabel"));
3956 obj->setScale(m_fontScaled);
3957 obj->setPosition(labelTrans);
3958 obj->setRotation(totalRotation);
3959 qsizetype labelIndex =
3960 valueAxisX->reversed() ? labelCount - 1 - i : i;
3961 obj->setProperty(name: "labelText", value: labels[labelIndex]);
3962 obj->setProperty(name: "labelWidth", value: labelsMaxWidth);
3963 obj->setProperty(name: "labelHeight", value: labelHeight);
3964 if (!labels[i].compare(s: QString(hiddenLabelTag)))
3965 obj->setVisible(false);
3966 }
3967 } else if (axisX()->type() == QAbstract3DAxis::AxisType::Category) {
3968 for (int i = 0; i < repeaterX()->count(); i++) {
3969 if (labelCount <= i)
3970 break;
3971 labelTrans = calculateCategoryLabelPosition(axis: axisX(), labelPosition: labelTrans, index: i);
3972 auto obj = static_cast<QQuick3DNode *>(repeaterX()->objectAt(index: i));
3973 obj->setObjectName(QStringLiteral("ElementAxisXLabel"));
3974 obj->setScale(m_fontScaled);
3975 obj->setPosition(labelTrans);
3976 obj->setRotation(totalRotation);
3977 obj->setProperty(name: "labelText", value: labels[i]);
3978 obj->setProperty(name: "labelWidth", value: labelsMaxWidth);
3979 obj->setProperty(name: "labelHeight", value: labelHeight);
3980 }
3981 }
3982
3983 float x = labelTrans.x();
3984 labelTrans.setX(0.0f);
3985 updateXTitle(labelRotation, labelTrans, totalRotation, labelsMaxWidth, scale: m_fontScaled);
3986 if (isPolar()) {
3987 m_titleLabelX->setZ(angularLabelZPos - m_labelMargin * 2.0f);
3988 m_titleLabelX->setRotation(totalRotation);
3989 }
3990 labelTrans.setX(x);
3991
3992 labels = axisY()->labels();
3993 labelCount = labels.size();
3994 labelAutoAngle = m_labelMargin >= 0 ? axisY()->labelAutoAngle() : 0;
3995 labelAngleFraction = labelAutoAngle / 90.0f;
3996 fractionCamX = m_xRotation * labelAngleFraction;
3997 fractionCamY = m_yRotation * labelAngleFraction;
3998 QVector3D sideLabelRotation(0.0f, -90.0f, 0.0f);
3999 QVector3D backLabelRotation(0.0f, 0.0f, 0.0f);
4000
4001 if (labelAutoAngle == 0.0f) {
4002 if (!xFlipped)
4003 sideLabelRotation.setY(90.0f);
4004 if (zFlipped)
4005 backLabelRotation.setY(180.f);
4006 } else {
4007 // Orient side labels somewhat towards the camera
4008 if (xFlipped) {
4009 if (zFlipped)
4010 backLabelRotation.setY(180.0f + (2.0f * labelAutoAngle) - fractionCamX);
4011 else
4012 backLabelRotation.setY(-fractionCamX);
4013 sideLabelRotation.setY(-90.0f + labelAutoAngle - fractionCamX);
4014 } else {
4015 if (zFlipped)
4016 backLabelRotation.setY(180.0f - (2.0f * labelAutoAngle) - fractionCamX);
4017 else
4018 backLabelRotation.setY(-fractionCamX);
4019 sideLabelRotation.setY(90.0f - labelAutoAngle - fractionCamX);
4020 }
4021 }
4022
4023 backLabelRotation.setX(-fractionCamY);
4024 sideLabelRotation.setX(-fractionCamY);
4025
4026 totalRotation = Utils::calculateRotation(xyzRotations: sideLabelRotation);
4027 scale = backgroundScale.y() - m_backgroundScaleMargin.y();
4028 labelsMaxWidth = float(findLabelsMaxWidth(labels: axisY()->labels())) + textPadding;
4029 fontRatio = labelsMaxWidth / labelHeight;
4030 relativeScale = scale / m_axisY->labels().count();
4031 if (axisY()->isScaleLabelsByCount()) {
4032 m_fontScaled = QVector3D(0.01f * fontRatio * relativePointSize * relativeScale,
4033 0.01f * relativePointSize * relativeScale,
4034 0.01f) * axisY()->labelSize();
4035 adjustment = m_fontScaled.y() * 190.0f;
4036 } else {
4037 m_fontScaled = QVector3D(scaleFactor * fontRatio, scaleFactor, 0.00001f)
4038 * axisY()->labelSize();
4039 adjustment = labelAdjustment(width: labelsMaxWidth * axisY()->labelSize());
4040 }
4041
4042 xPos = backgroundScale.x() - labelDepthMargin;
4043 if (!xFlipped)
4044 xPos *= -1.0f;
4045 labelTrans.setX(xPos);
4046
4047 zPos = backgroundScale.z() + adjustment + m_labelMargin;
4048 if (zFlipped)
4049 zPos *= -1.0f;
4050 labelTrans.setZ(zPos);
4051 for (int i = 0; i < repeaterY()->count() / 2; i++) {
4052 if (labelCount <= i)
4053 break;
4054 auto obj = static_cast<QQuick3DNode *>(repeaterY()->objectAt(index: i));
4055 auto valueAxisY = static_cast<QValue3DAxis *>(axisY());
4056 labelTrans.setY(valueAxisY->labelPositionAt(index: i) * scale * 2.0f - scale);
4057
4058 obj->setObjectName(QStringLiteral("ElementAxisYLabel"));
4059 obj->setScale(m_fontScaled);
4060 obj->setPosition(labelTrans);
4061 obj->setRotation(totalRotation);
4062 qsizetype labelIndex = valueAxisY->reversed() ? labelCount - 1 - i : i;
4063 obj->setProperty(name: "labelText", value: labels[labelIndex]);
4064 obj->setProperty(name: "labelWidth", value: labelsMaxWidth);
4065 obj->setProperty(name: "labelHeight", value: labelHeight);
4066 if (!labels[i].compare(s: QString(hiddenLabelTag)))
4067 obj->setVisible(false);
4068 }
4069
4070 auto sideLabelTrans = labelTrans;
4071 auto totalSideLabelRotation = totalRotation;
4072
4073 labels = axisZ()->labels();
4074 labelCount = labels.size();
4075 labelAutoAngle = m_labelMargin >= 0 ? axisZ()->labelAutoAngle() : 0;
4076 labelAngleFraction = labelAutoAngle / 90.0f;
4077 fractionCamX = m_xRotation * labelAngleFraction;
4078 fractionCamY = m_yRotation * labelAngleFraction;
4079
4080 if (labelAutoAngle == 0.0f) {
4081 labelRotation = QVector3D(90.0f, 0.0f, 0.0f);
4082 if (zFlipped)
4083 labelRotation.setY(180.0f);
4084 if (yFlipped) {
4085 if (zFlipped)
4086 labelRotation.setY(180.0f);
4087 else
4088 labelRotation.setY(0.0f);
4089 labelRotation.setX(90.0f);
4090 } else {
4091 labelRotation.setX(-90.0f);
4092 }
4093 } else {
4094 if (zFlipped)
4095 labelRotation.setY(180.0f);
4096 else
4097 labelRotation.setY(0.0f);
4098 if (yFlipped) {
4099 if (zFlipped) {
4100 if (xFlipped) {
4101 labelRotation.setX(90.0f
4102 - (labelAutoAngle - fractionCamX)
4103 * (-labelAutoAngle - fractionCamY) / labelAutoAngle);
4104 labelRotation.setZ(labelAutoAngle + fractionCamY);
4105 } else {
4106 labelRotation.setX(90.0f
4107 + (labelAutoAngle + fractionCamX)
4108 * (labelAutoAngle + fractionCamY) / labelAutoAngle);
4109 labelRotation.setZ(-labelAutoAngle - fractionCamY);
4110 }
4111 } else {
4112 if (xFlipped) {
4113 labelRotation.setX(90.0f
4114 + (labelAutoAngle - fractionCamX)
4115 * -(labelAutoAngle + fractionCamY) / labelAutoAngle);
4116 labelRotation.setZ(-labelAutoAngle - fractionCamY);
4117 } else {
4118 labelRotation.setX(90.0f
4119 - (labelAutoAngle + fractionCamX)
4120 * (labelAutoAngle + fractionCamY) / labelAutoAngle);
4121 labelRotation.setZ(labelAutoAngle + fractionCamY);
4122 }
4123 }
4124 } else {
4125 if (zFlipped) {
4126 if (xFlipped) {
4127 labelRotation.setX(-90.0f
4128 + (labelAutoAngle - fractionCamX)
4129 * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
4130 labelRotation.setZ(-labelAutoAngle + fractionCamY);
4131 } else {
4132 labelRotation.setX(-90.0f
4133 - (labelAutoAngle + fractionCamX)
4134 * (labelAutoAngle - fractionCamY) / labelAutoAngle);
4135 labelRotation.setZ(labelAutoAngle - fractionCamY);
4136 }
4137 } else {
4138 if (xFlipped) {
4139 labelRotation.setX(-90.0f
4140 - (labelAutoAngle - fractionCamX)
4141 * (-labelAutoAngle + fractionCamY) / labelAutoAngle);
4142 labelRotation.setZ(labelAutoAngle - fractionCamY);
4143 } else {
4144 labelRotation.setX(-90.0f
4145 + (labelAutoAngle + fractionCamX)
4146 * (labelAutoAngle - fractionCamY) / labelAutoAngle);
4147 labelRotation.setZ(-labelAutoAngle + fractionCamY);
4148 }
4149 }
4150 }
4151 }
4152
4153 totalRotation = Utils::calculateRotation(xyzRotations: labelRotation);
4154
4155 scale = backgroundScale.z() - m_backgroundScaleMargin.z();
4156 labelsMaxWidth = float(findLabelsMaxWidth(labels: axisZ()->labels())) + textPadding;
4157 fontRatio = labelsMaxWidth / labelHeight;
4158 relativeScale = scale / m_axisZ->labels().count();
4159
4160 if (axisZ()->isScaleLabelsByCount()) {
4161 m_fontScaled = QVector3D(0.01f * fontRatio * relativePointSize * relativeScale,
4162 0.01f * relativePointSize * relativeScale,
4163 0.01f) * axisZ()->labelSize();
4164 adjustment = m_fontScaled.y() * 110.0f;
4165 } else {
4166 m_fontScaled = QVector3D(scaleFactor * fontRatio, scaleFactor, 0.00001f)
4167 * axisZ()->labelSize();
4168 adjustment = labelAdjustment(width: labelsMaxWidth * axisZ()->labelSize());
4169 }
4170
4171 xPos = backgroundScale.x() + adjustment + m_labelMargin;
4172 if (xFlipped)
4173 xPos *= -1.0f;
4174
4175 adjustment *= qAbs(t: qSin(v: qDegreesToRadians(degrees: labelRotation.z())));
4176 yPos = backgroundScale.y() + adjustment - labelDepthMargin;
4177 if (!yFlipped)
4178 yPos *= -1.0f;
4179
4180 labelTrans = QVector3D(xPos, yPos, 0.0f);
4181 if (axisZ()->type() == QAbstract3DAxis::AxisType::Value) {
4182 auto valueAxisZ = static_cast<QValue3DAxis *>(axisZ());
4183 float offsetAdjustment = 0.05f;
4184 float offset = radialLabelOffset() + offsetAdjustment;
4185 for (int i = 0; i < repeaterZ()->count(); i++) {
4186 if (labelCount <= i)
4187 break;
4188
4189 auto obj = static_cast<QQuick3DNode *>(repeaterZ()->objectAt(index: i));
4190 if (isPolar()) {
4191 // RADIAL LABELS
4192 float polarX = backgroundScale.x() * offset + m_labelMargin * 2.0f;
4193 if (xFlipped)
4194 polarX *= -1;
4195 labelTrans.setX(polarX);
4196 labelTrans.setY(yPos + yOffset);
4197
4198 labelTrans.setZ(-valueAxisZ->labelPositionAt(index: i) * m_polarRadius);
4199 } else {
4200 labelTrans.setZ(valueAxisZ->labelPositionAt(index: i) * scale * -2.0f + scale);
4201 }
4202 obj->setObjectName(QStringLiteral("ElementAxisZLabel"));
4203 obj->setScale(m_fontScaled);
4204 obj->setPosition(labelTrans);
4205 obj->setRotation(totalRotation);
4206 qsizetype labelIndex = valueAxisZ->reversed() ? labelCount - 1 - i : i;
4207 obj->setProperty(name: "labelText", value: labels[labelIndex]);
4208 obj->setProperty(name: "labelWidth", value: labelsMaxWidth);
4209 obj->setProperty(name: "labelHeight", value: labelHeight);
4210 if (!labels[i].compare(s: QString(hiddenLabelTag)))
4211 obj->setVisible(false);
4212 }
4213 } else if (axisZ()->type() == QAbstract3DAxis::AxisType::Category) {
4214 for (int i = 0; i < repeaterZ()->count(); i++) {
4215 if (labelCount <= i)
4216 break;
4217 labelTrans = calculateCategoryLabelPosition(axis: axisZ(), labelPosition: labelTrans, index: i);
4218 auto obj = static_cast<QQuick3DNode *>(repeaterZ()->objectAt(index: i));
4219 obj->setObjectName(QStringLiteral("ElementAxisZLabel"));
4220 obj->setScale(m_fontScaled);
4221 obj->setPosition(labelTrans);
4222 obj->setRotation(totalRotation);
4223 obj->setProperty(name: "labelText", value: labels[i]);
4224 obj->setProperty(name: "labelWidth", value: labelsMaxWidth);
4225 obj->setProperty(name: "labelHeight", value: labelHeight);
4226 }
4227 }
4228
4229 float z = labelTrans.z();
4230 labelTrans.setZ(0.0f);
4231 updateZTitle(labelRotation, labelTrans, totalRotation, labelsMaxWidth, scale: m_fontScaled);
4232 labelTrans.setZ(z);
4233
4234 labels = axisY()->labels();
4235 labelCount = labels.size();
4236 totalRotation = Utils::calculateRotation(xyzRotations: backLabelRotation);
4237 scale = backgroundScale.y() - m_backgroundScaleMargin.y();
4238 labelsMaxWidth = float(findLabelsMaxWidth(labels: axisY()->labels())) + textPadding;
4239 fontRatio = labelsMaxWidth / labelHeight;
4240 relativeScale = scale / m_axisY->labels().count();
4241 if (axisY()->isScaleLabelsByCount()) {
4242 m_fontScaled = QVector3D(0.01f * fontRatio * relativePointSize * relativeScale,
4243 0.01f * relativePointSize * relativeScale,
4244 0.01f) * axisY()->labelSize();
4245 adjustment = m_fontScaled.y() * 190.0f;
4246 } else {
4247 m_fontScaled = QVector3D(scaleFactor * fontRatio, scaleFactor, 0.00001f)
4248 * axisY()->labelSize();
4249 adjustment = labelAdjustment(width: labelsMaxWidth * axisY()->labelSize());
4250 }
4251
4252 xPos = backgroundScale.x() + adjustment + m_labelMargin;
4253 if (xFlipped)
4254 xPos *= -1.0f;
4255 labelTrans.setX(xPos);
4256
4257 zPos = -backgroundScale.z() + labelDepthMargin;
4258 if (zFlipped)
4259 zPos *= -1.0f;
4260 labelTrans.setZ(zPos);
4261
4262 for (int i = 0; i < repeaterY()->count() / 2; i++) {
4263 if (labelCount <= i)
4264 break;
4265 auto obj = static_cast<QQuick3DNode *>(
4266 repeaterY()->objectAt(index: i + (repeaterY()->count() / 2)));
4267 auto valueAxisY = static_cast<QValue3DAxis *>(axisY());
4268 labelTrans.setY(valueAxisY->labelPositionAt(index: i) * scale * 2.0f
4269 - scale);
4270 obj->setObjectName(QStringLiteral("ElementAxisYLabel"));
4271 obj->setScale(m_fontScaled);
4272 obj->setPosition(labelTrans);
4273 obj->setRotation(totalRotation);
4274 qsizetype labelIndex = valueAxisY->reversed() ? labelCount - 1 - i : i;
4275 obj->setProperty(name: "labelText", value: labels[labelIndex]);
4276 obj->setProperty(name: "labelWidth", value: labelsMaxWidth);
4277 obj->setProperty(name: "labelHeight", value: labelHeight);
4278 if (!labels[i].compare(s: QString(hiddenLabelTag)))
4279 obj->setVisible(false);
4280 }
4281
4282 QVector3D backLabelTrans = labelTrans;
4283 QQuaternion totalBackLabelRotation = totalRotation;
4284 updateYTitle(sideLabelRotation,
4285 backLabelRotation,
4286 sideLabelTrans,
4287 backLabelTrans,
4288 totalSideRotation: totalSideLabelRotation,
4289 totalBackRotation: totalBackLabelRotation,
4290 labelsMaxWidth,
4291 scale: m_fontScaled);
4292 m_labelsNeedupdate = false;
4293 Q_TRACE(QGraphs3DItemUpdateLabels_exit);
4294}
4295
4296void QQuickGraphsItem::updateRadialLabelOffset()
4297{
4298 if (!isPolar())
4299 return;
4300
4301 QVector3D backgroundScale = m_scaleWithBackground + m_backgroundScaleMargin;
4302 float offset = radialLabelOffset();
4303 float scale = backgroundScale.x() + (m_backgroundScaleMargin.x());
4304 float polarX = scale * offset + m_labelMargin * 2.0f;
4305 if (isXFlipped())
4306 polarX *= -1;
4307 if (axisZ()->type() == QAbstract3DAxis::AxisType::Value) {
4308 for (int i = 0; i < repeaterZ()->count(); i++) {
4309 QQuick3DNode *obj = static_cast<QQuick3DNode *>(repeaterZ()->objectAt(index: i));
4310 QVector3D pos = obj->position();
4311 pos.setX(polarX);
4312 obj->setPosition(pos);
4313 }
4314 }
4315
4316 polarX += m_labelMargin * 2.5f;
4317 QVector3D pos = m_titleLabelZ->position();
4318 pos.setX(polarX);
4319 m_titleLabelZ->setPosition(pos);
4320}
4321
4322void QQuickGraphsItem::positionAndScaleLine(QQuick3DNode *lineNode,
4323 QVector3D scale,
4324 QVector3D position)
4325{
4326 lineNode->setScale(scale);
4327 lineNode->setPosition(position);
4328}
4329
4330QVector3D QQuickGraphsItem::graphPositionAt(const QPoint point)
4331{
4332 auto result = pick(x: point.x(), y: point.y());
4333 QVector3D position = QVector3D();
4334 if (result.objectHit())
4335 position = result.scenePosition();
4336
4337 return position;
4338}
4339
4340void QQuickGraphsItem::updateShadowQuality(QtGraphs3D::ShadowQuality quality)
4341{
4342 if (quality != QtGraphs3D::ShadowQuality::None) {
4343 light()->setCastsShadow(true);
4344 light()->setShadowFactor(25.f);
4345
4346 QQuick3DAbstractLight::QSSGShadowMapQuality shadowMapQuality;
4347 switch (quality) {
4348 case QtGraphs3D::ShadowQuality::Low:
4349 case QtGraphs3D::ShadowQuality::SoftLow:
4350 shadowMapQuality = QQuick3DAbstractLight::QSSGShadowMapQuality::ShadowMapQualityMedium;
4351 break;
4352 case QtGraphs3D::ShadowQuality::Medium:
4353 case QtGraphs3D::ShadowQuality::SoftMedium:
4354 shadowMapQuality = QQuick3DAbstractLight::QSSGShadowMapQuality::ShadowMapQualityHigh;
4355 break;
4356 case QtGraphs3D::ShadowQuality::High:
4357 case QtGraphs3D::ShadowQuality::SoftHigh:
4358 shadowMapQuality = QQuick3DAbstractLight::QSSGShadowMapQuality::ShadowMapQualityVeryHigh;
4359 break;
4360 default:
4361 shadowMapQuality = QQuick3DAbstractLight::QSSGShadowMapQuality::ShadowMapQualityHigh;
4362 break;
4363 }
4364 light()->setShadowMapQuality(shadowMapQuality);
4365 if (quality >= QtGraphs3D::ShadowQuality::SoftLow)
4366 light()->setShadowFilter(10.f);
4367 else
4368 light()->setShadowFilter(2.f);
4369 } else {
4370 light()->setCastsShadow(false);
4371 light()->setShadowFactor(0.f);
4372 }
4373}
4374
4375void QQuickGraphsItem::updateItemLabel(QVector3D position)
4376{
4377 if (m_customView)
4378 m_itemLabel->setParentItem(m_customView);
4379
4380 if (m_labelPosition != position)
4381 m_labelPosition = position;
4382
4383 // QVector3D pos2d = mapFrom3DScene(m_labelPosition * rootNode()->scale().z());
4384 QVector3D scenePos = rootNode()->mapPositionToScene(localPosition: m_labelPosition);
4385 QVector3D pos2d = mapFrom3DScene(scenePos);
4386 if (m_customView)
4387 pos2d = m_customView->mapFrom3DScene(scenePos);
4388
4389 int pointSize = theme()->labelFont().pointSize();
4390 float scale = m_labelScale.x() * ((-10.0f * pointSize) + 650.0f)
4391 / (pos2d.z() / rootNode()->scale().z());
4392 scale = scale < 0 ? -scale : scale;
4393 if (m_sliceView && m_sliceView->isVisible())
4394 m_itemLabel->setScale(scale * .2f);
4395 else
4396 m_itemLabel->setScale(scale);
4397 pos2d.setX(pos2d.x() - (m_itemLabel->width() / 2.f));
4398 pos2d.setY(pos2d.y() - (m_itemLabel->height() / 2.f)
4399 - (m_itemLabel->height() * m_itemLabel->scale()));
4400 m_itemLabel->setPosition(pos2d.toPointF());
4401}
4402
4403void QQuickGraphsItem::updateSliceItemLabel(const QString &label, QVector3D position)
4404{
4405 Q_UNUSED(position);
4406
4407 QFontMetrics fm(theme()->labelFont());
4408 float textPadding = theme()->labelFont().pointSizeF() * .7f;
4409 float labelHeight = fm.height() + textPadding;
4410 float labelWidth = fm.horizontalAdvance(label) + textPadding;
4411
4412 float pointSize = theme()->labelFont().pointSizeF();
4413 float scaleFactor = fontScaleFactor(pointSize) * pointSize;
4414 float fontRatio = labelWidth / labelHeight;
4415
4416 QVector3D fontScaled = QVector3D(scaleFactor * fontRatio, scaleFactor, 0.00001f);
4417 m_sliceItemLabel->setScale(fontScaled);
4418}
4419
4420void QQuickGraphsItem::createVolumeMaterial(QCustom3DVolume *volume, Volume &volumeItem)
4421{
4422 if (volumeItem.texture)
4423 volumeItem.texture->deleteLater();
4424 volumeItem.texture = new QQuick3DTexture();
4425 auto texture = volumeItem.texture;
4426
4427 texture->setParent(this);
4428 texture->setMinFilter(QQuick3DTexture::Filter::Nearest);
4429 texture->setMagFilter(QQuick3DTexture::Filter::Nearest);
4430 texture->setHorizontalTiling(QQuick3DTexture::TilingMode::ClampToEdge);
4431 texture->setVerticalTiling(QQuick3DTexture::TilingMode::ClampToEdge);
4432
4433 if (volumeItem.textureData)
4434 volumeItem.textureData->deleteLater();
4435 volumeItem.textureData = new QQuick3DTextureData();
4436 auto textureData = volumeItem.textureData;
4437
4438 int color8Bit = (volume->textureFormat() == QImage::Format_Indexed8) ? 1 : 0;
4439
4440 textureData->setParent(texture);
4441 textureData->setParentItem(texture);
4442 textureData->setSize(QSize(volume->textureWidth(), volume->textureHeight()));
4443 textureData->setDepth(volume->textureDepth());
4444 if (color8Bit)
4445 textureData->setFormat(QQuick3DTextureData::R8);
4446 else
4447 textureData->setFormat(QQuick3DTextureData::RGBA8);
4448 textureData->setTextureData(
4449 QByteArray::fromRawData(data: reinterpret_cast<const char *>(volume->textureData()->constData()),
4450 size: volume->textureData()->size()));
4451 texture->setTextureData(textureData);
4452
4453 QObject::connect(sender: volume, signal: &QCustom3DVolume::textureDataChanged, context: this, slot: [this, volume] {
4454 m_customVolumes[volume].updateTextureData = true;
4455 });
4456
4457 if (color8Bit) {
4458 if (volumeItem.colorTexture)
4459 volumeItem.colorTexture->deleteLater();
4460 volumeItem.colorTexture = new QQuick3DTexture();
4461 auto colorTexture = volumeItem.colorTexture;
4462
4463 colorTexture->setParent(this);
4464 colorTexture->setMinFilter(QQuick3DTexture::Filter::Nearest);
4465 colorTexture->setMagFilter(QQuick3DTexture::Filter::Nearest);
4466 colorTexture->setHorizontalTiling(QQuick3DTexture::TilingMode::ClampToEdge);
4467 colorTexture->setVerticalTiling(QQuick3DTexture::TilingMode::ClampToEdge);
4468
4469 QByteArray colorTableBytes;
4470 const QList<QRgb> &colorTable = volume->colorTable();
4471 for (int i = 0; i < colorTable.size(); i++) {
4472 QRgb shifted = qRgba(r: qBlue(rgb: colorTable[i]),
4473 g: qGreen(rgb: colorTable[i]),
4474 b: qRed(rgb: colorTable[i]),
4475 a: qAlpha(rgb: colorTable[i]));
4476 colorTableBytes.append(
4477 a: QByteArray::fromRawData(data: reinterpret_cast<const char *>(&shifted), size: sizeof(shifted)));
4478 }
4479
4480 if (volumeItem.colorTextureData)
4481 volumeItem.colorTextureData->deleteLater();
4482 volumeItem.colorTextureData = new QQuick3DTextureData();
4483 auto colorTextureData = volumeItem.colorTextureData;
4484
4485 colorTextureData->setParent(colorTexture);
4486 colorTextureData->setParentItem(colorTexture);
4487 colorTextureData->setSize(QSize(int(volume->colorTable().size()), 1));
4488 colorTextureData->setFormat(QQuick3DTextureData::RGBA8);
4489 colorTextureData->setTextureData(colorTableBytes);
4490 colorTexture->setTextureData(colorTextureData);
4491
4492 QObject::connect(sender: volume, signal: &QCustom3DVolume::colorTableChanged, context: this, slot: [this, volume] {
4493 m_customVolumes[volume].updateColorTextureData = true;
4494 });
4495 }
4496
4497 auto model = volumeItem.model;
4498 QQmlListReference materialsRef(model, "materials");
4499
4500 QQuick3DCustomMaterial *material = nullptr;
4501
4502 if (volume->drawSlices() && m_validVolumeSlice)
4503 material = createQmlCustomMaterial(QStringLiteral(":/materials/VolumeSliceMaterial"));
4504 else if (volume->useHighDefShader())
4505 material = createQmlCustomMaterial(QStringLiteral(":/materials/VolumeMaterial"));
4506 else
4507 material = createQmlCustomMaterial(QStringLiteral(":/materials/VolumeLowDefMaterial"));
4508
4509 auto textureSamplerVariant = material->property(name: "textureSampler");
4510 auto textureSampler = textureSamplerVariant.value<QQuick3DShaderUtilsTextureInput *>();
4511 textureSampler->setTexture(volumeItem.texture);
4512
4513 if (color8Bit) {
4514 auto colorSamplerVariant = material->property(name: "colorSampler");
4515 auto colorSampler = colorSamplerVariant.value<QQuick3DShaderUtilsTextureInput *>();
4516 colorSampler->setTexture(volumeItem.colorTexture);
4517 }
4518
4519 material->setProperty(name: "textureDimensions",
4520 value: QVector3D(1.0f / float(volume->textureWidth()),
4521 1.0f / float(volume->textureHeight()),
4522 1.0f / float(volume->textureDepth())));
4523
4524 materialsRef.append(material);
4525
4526 volumeItem.useHighDefShader = volume->useHighDefShader();
4527 volumeItem.drawSlices = volume->drawSlices() && m_validVolumeSlice;
4528}
4529
4530QQuick3DModel *QQuickGraphsItem::createSliceFrame(Volume &volumeItem)
4531{
4532 QQuick3DModel *model = new QQuick3DModel();
4533 model->setParent(volumeItem.model);
4534 model->setParentItem(volumeItem.model);
4535 model->setSource(QUrl(QStringLiteral("defaultMeshes/barMeshFull")));
4536 model->setScale(QVector3D(1, 1, 0.01f));
4537 model->setDepthBias(-100.0f);
4538
4539 QQmlListReference materialsRef(model, "materials");
4540 QQuick3DCustomMaterial *material = createQmlCustomMaterial(
4541 QStringLiteral(":/materials/VolumeFrameMaterial"));
4542 material->setParent(model);
4543 material->setParentItem(model);
4544 material->setCullMode(QQuick3DMaterial::NoCulling);
4545 materialsRef.append(material);
4546
4547 return model;
4548}
4549
4550void QQuickGraphsItem::updateSliceFrameMaterials(QCustom3DVolume *volume, Volume &volumeItem)
4551{
4552 QQmlListReference materialsRefX(volumeItem.sliceFrameX, "materials");
4553 QQmlListReference materialsRefY(volumeItem.sliceFrameY, "materials");
4554 QQmlListReference materialsRefZ(volumeItem.sliceFrameZ, "materials");
4555
4556 QVector2D frameWidth;
4557 QVector3D frameScaling;
4558
4559 frameScaling = QVector3D(volume->scaling().z()
4560 + (volume->scaling().z() * volume->sliceFrameGaps().z())
4561 + (volume->scaling().z() * volume->sliceFrameWidths().z()),
4562 volume->scaling().y()
4563 + (volume->scaling().y() * volume->sliceFrameGaps().y())
4564 + (volume->scaling().y() * volume->sliceFrameWidths().y()),
4565 volume->scaling().x() * volume->sliceFrameThicknesses().x());
4566
4567 frameWidth = QVector2D(volume->scaling().z() * volume->sliceFrameWidths().z(),
4568 volume->scaling().y() * volume->sliceFrameWidths().y());
4569
4570 frameWidth.setX(1.0f - (frameWidth.x() / frameScaling.x()));
4571 frameWidth.setY(1.0f - (frameWidth.y() / frameScaling.y()));
4572
4573 auto material = materialsRefX.at(0);
4574 material->setProperty(name: "color", value: volume->sliceFrameColor());
4575 material->setProperty(name: "sliceFrameWidth", value: frameWidth);
4576
4577 frameScaling = QVector3D(volume->scaling().x()
4578 + (volume->scaling().x() * volume->sliceFrameGaps().x())
4579 + (volume->scaling().x() * volume->sliceFrameWidths().x()),
4580 volume->scaling().z()
4581 + (volume->scaling().z() * volume->sliceFrameGaps().z())
4582 + (volume->scaling().z() * volume->sliceFrameWidths().z()),
4583 volume->scaling().y() * volume->sliceFrameThicknesses().y());
4584 frameWidth = QVector2D(volume->scaling().x() * volume->sliceFrameWidths().x(),
4585 volume->scaling().z() * volume->sliceFrameWidths().z());
4586
4587 frameWidth.setX(1.0f - (frameWidth.x() / frameScaling.x()));
4588 frameWidth.setY(1.0f - (frameWidth.y() / frameScaling.y()));
4589
4590 material = materialsRefY.at(0);
4591 material->setProperty(name: "color", value: volume->sliceFrameColor());
4592 material->setProperty(name: "sliceFrameWidth", value: frameWidth);
4593
4594 frameScaling = QVector3D(volume->scaling().x()
4595 + (volume->scaling().x() * volume->sliceFrameGaps().x())
4596 + (volume->scaling().x() * volume->sliceFrameWidths().x()),
4597 volume->scaling().y()
4598 + (volume->scaling().y() * volume->sliceFrameGaps().y())
4599 + (volume->scaling().y() * volume->sliceFrameWidths().y()),
4600 volume->scaling().z() * volume->sliceFrameThicknesses().z());
4601 frameWidth = QVector2D(volume->scaling().x() * volume->sliceFrameWidths().x(),
4602 volume->scaling().y() * volume->sliceFrameWidths().y());
4603
4604 frameWidth.setX(1.0f - (frameWidth.x() / frameScaling.x()));
4605 frameWidth.setY(1.0f - (frameWidth.y() / frameScaling.y()));
4606
4607 material = materialsRefZ.at(0);
4608 material->setProperty(name: "color", value: volume->sliceFrameColor());
4609 material->setProperty(name: "sliceFrameWidth", value: frameWidth);
4610}
4611
4612void QQuickGraphsItem::updateCustomVolumes()
4613{
4614 Q_TRACE_SCOPE(QGraphs3DItemUpdateCustomVolumes);
4615
4616 auto itemIterator = m_customItemList.constBegin();
4617 while (itemIterator != m_customItemList.constEnd()) {
4618 QCustom3DItem *item = itemIterator.key();
4619 QQuick3DModel *model = itemIterator.value();
4620
4621 if (auto volume = qobject_cast<QCustom3DVolume *>(object: item)) {
4622 auto &&volumeItem = m_customVolumes[volume];
4623
4624 QQmlListReference materialsRef(model, "materials");
4625 if (volumeItem.useHighDefShader != volume->useHighDefShader()) {
4626 materialsRef.clear();
4627 createVolumeMaterial(volume, volumeItem);
4628 }
4629
4630 m_validVolumeSlice = volume->sliceIndexX() >= 0
4631 || volume->sliceIndexY() >= 0
4632 || volume->sliceIndexZ() >= 0;
4633
4634 if (volumeItem.drawSlices != (volume->drawSlices() && m_validVolumeSlice)) {
4635 materialsRef.clear();
4636 createVolumeMaterial(volume, volumeItem);
4637 }
4638
4639 QVector3D sliceIndices(
4640 (float(volume->sliceIndexX()) + 0.5f) / float(volume->textureWidth()) * 2.0 - 1.0,
4641 (float(volume->sliceIndexY()) + 0.5f) / float(volume->textureHeight()) * 2.0 - 1.0,
4642 (float(volume->sliceIndexZ()) + 0.5f) / float(volume->textureDepth()) * 2.0 - 1.0);
4643
4644 if (volumeItem.drawSliceFrames != volume->drawSliceFrames()) {
4645 if (volume->drawSliceFrames()) {
4646 volumeItem.sliceFrameX->setVisible(true);
4647 volumeItem.sliceFrameY->setVisible(true);
4648 volumeItem.sliceFrameZ->setVisible(true);
4649
4650 volumeItem.sliceFrameX->setRotation(QQuaternion::fromEulerAngles(pitch: 0, yaw: 90, roll: 0));
4651 volumeItem.sliceFrameY->setRotation(QQuaternion::fromEulerAngles(pitch: 90, yaw: 0, roll: 0));
4652
4653 updateSliceFrameMaterials(volume, volumeItem);
4654 } else {
4655 volumeItem.sliceFrameX->setVisible(false);
4656 volumeItem.sliceFrameY->setVisible(false);
4657 volumeItem.sliceFrameZ->setVisible(false);
4658 }
4659 volumeItem.drawSliceFrames = volume->drawSliceFrames();
4660 }
4661
4662 auto material = materialsRef.at(0);
4663 QVector3D minBounds(-1, 1, 1);
4664 QVector3D maxBounds(1, -1, -1);
4665 QVector3D translation(0, 0, 0);
4666 QVector3D scaling(1, 1, 1);
4667
4668 model->setVisible(volume->isVisible());
4669 if (!volume->isScalingAbsolute() && !volume->isPositionAbsolute()) {
4670
4671 QVector3D pos = volume->position();
4672 QVector3D scale = volume->scaling() / 2;
4673
4674 QVector3D minGraphBounds(pos.x() - scale.x(),
4675 pos.y() - scale.y(),
4676 pos.z() + scale.z());
4677 QVector3D maxGraphBounds(pos.x() + scale.x(),
4678 pos.y() + scale.y(),
4679 pos.z() - scale.z());
4680
4681 QVector3D minCorner = graphPosToAbsolute(position: minGraphBounds);
4682 QVector3D maxCorner = graphPosToAbsolute(position: maxGraphBounds);
4683
4684 scale = QVector3D(qAbs(t: maxCorner.x() - minCorner.x()),
4685 qAbs(t: maxCorner.y() - minCorner.y()),
4686 qAbs(t: maxCorner.z() - minCorner.z())) / 2.0f;
4687
4688 const QVector3D mScale = scaleWithBackground();
4689 const QVector3D itemRange = maxCorner - minCorner;
4690 if (minCorner.x() < -mScale.x())
4691 minBounds.setX(-1.0f + (2.0f * qAbs(t: minCorner.x() + mScale.x()) / itemRange.x()));
4692 if (minCorner.y() < -mScale.y())
4693 minBounds.setY(-(-1.0f + (2.0f * qAbs(t: minCorner.y() + mScale.y()) / itemRange.y())));
4694 if (minCorner.z() < -mScale.z())
4695 minBounds.setZ(-(-1.0f + (2.0f * qAbs(t: minCorner.z() + mScale.z()) / itemRange.z())));
4696
4697 if (maxCorner.x() > mScale.x())
4698 maxBounds.setX(1.0f - (2.0f * qAbs(t: maxCorner.x() - mScale.x()) / itemRange.x()));
4699 if (maxCorner.y() > mScale.y())
4700 maxBounds.setY(-(1.0f - (2.0f * qAbs(t: maxCorner.y() - mScale.y()) / itemRange.y())));
4701 if (maxCorner.z() > mScale.z())
4702 maxBounds.setZ(-(1.0f - (2.0f * qAbs(t: maxCorner.z() - mScale.z()) / itemRange.z())));
4703
4704 QVector3D minBoundsNorm = minBounds;
4705 QVector3D maxBoundsNorm = maxBounds;
4706
4707 minBoundsNorm.setY(-minBoundsNorm.y());
4708 minBoundsNorm.setZ(-minBoundsNorm.z());
4709 minBoundsNorm = 0.5f * (minBoundsNorm + QVector3D(1,1,1));
4710
4711 maxBoundsNorm.setY(-maxBoundsNorm.y());
4712 maxBoundsNorm.setZ(-maxBoundsNorm.z());
4713 maxBoundsNorm = 0.5f * (maxBoundsNorm + QVector3D(1,1,1));
4714
4715 QVector3D adjScaling = scale * (maxBoundsNorm - minBoundsNorm);
4716 model->setScale(adjScaling);
4717
4718 QVector3D adjPos = volume->position();
4719 QVector3D dataExtents = (maxGraphBounds - minGraphBounds) / 2.0f;
4720
4721 adjPos = adjPos + (dataExtents * minBoundsNorm)
4722 - (dataExtents * (QVector3D(1,1,1) - maxBoundsNorm));
4723 adjPos = graphPosToAbsolute(position: adjPos);
4724 model->setPosition(adjPos);
4725 } else {
4726 model->setScale(volume->scaling());
4727 }
4728 model->setRotation(volume->rotation());
4729
4730 material->setProperty(name: "minBounds", value: minBounds);
4731 material->setProperty(name: "maxBounds", value: maxBounds);
4732
4733 if (volume->drawSlices())
4734 material->setProperty(name: "volumeSliceIndices", value: sliceIndices);
4735
4736 if (volume->drawSliceFrames()) {
4737 float sliceFrameX = sliceIndices.x();
4738 float sliceFrameY = sliceIndices.y();
4739 float sliceFrameZ = sliceIndices.z();
4740 if (volume->sliceIndexX() >= 0 && scaling.x() > 0)
4741 sliceFrameX = (sliceFrameX + translation.x()) / scaling.x();
4742 if (volume->sliceIndexY() >= 0 && scaling.y() > 0)
4743 sliceFrameY = (sliceFrameY - translation.y()) / scaling.y();
4744 if (volume->sliceIndexZ() >= 0 && scaling.z() > 0)
4745 sliceFrameZ = (sliceFrameZ + translation.z()) / scaling.z();
4746
4747 if (sliceFrameX < -1 || sliceFrameX > 1)
4748 volumeItem.sliceFrameX->setVisible(false);
4749 else
4750 volumeItem.sliceFrameX->setVisible(true);
4751
4752 if (sliceFrameY < -1 || sliceFrameY > 1)
4753 volumeItem.sliceFrameY->setVisible(false);
4754 else
4755 volumeItem.sliceFrameY->setVisible(true);
4756
4757 if (sliceFrameZ < -1 || sliceFrameZ > 1)
4758 volumeItem.sliceFrameZ->setVisible(false);
4759 else
4760 volumeItem.sliceFrameZ->setVisible(true);
4761
4762 volumeItem.sliceFrameX->setX(sliceFrameX);
4763 volumeItem.sliceFrameY->setY(-sliceFrameY);
4764 volumeItem.sliceFrameZ->setZ(-sliceFrameZ);
4765 }
4766
4767 material->setProperty(name: "alphaMultiplier", value: volume->alphaMultiplier());
4768 material->setProperty(name: "preserveOpacity", value: volume->preserveOpacity());
4769 material->setProperty(name: "useOrtho", value: isOrthoProjection());
4770
4771 int sampleCount = volume->textureWidth() + volume->textureHeight()
4772 + volume->textureDepth();
4773 material->setProperty(name: "sampleCount", value: sampleCount);
4774
4775 int color8Bit = (volume->textureFormat() == QImage::Format_Indexed8) ? 1 : 0;
4776 material->setProperty(name: "color8Bit", value: color8Bit);
4777
4778 if (volumeItem.updateTextureData) {
4779 auto textureData = volumeItem.textureData;
4780 textureData->setSize(QSize(volume->textureWidth(), volume->textureHeight()));
4781 textureData->setDepth(volume->textureDepth());
4782
4783 if (color8Bit)
4784 textureData->setFormat(QQuick3DTextureData::R8);
4785 else
4786 textureData->setFormat(QQuick3DTextureData::RGBA8);
4787
4788 textureData->setTextureData(
4789 QByteArray::fromRawData(data: reinterpret_cast<const char *>(
4790 volume->textureData()->constData()),
4791 size: volume->textureData()->size()));
4792
4793 material->setProperty(name: "textureDimensions",
4794 value: QVector3D(1.0f / float(volume->textureWidth()),
4795 1.0f / float(volume->textureHeight()),
4796 1.0f / float(volume->textureDepth())));
4797
4798 volumeItem.updateTextureData = false;
4799 }
4800
4801 if (volumeItem.updateColorTextureData) {
4802 auto colorTextureData = volumeItem.colorTextureData;
4803 QByteArray colorTableBytes;
4804 const QList<QRgb> &colorTable = volume->colorTable();
4805 for (int i = 0; i < colorTable.size(); i++) {
4806 QRgb shifted = qRgba(r: qBlue(rgb: colorTable[i]),
4807 g: qGreen(rgb: colorTable[i]),
4808 b: qRed(rgb: colorTable[i]),
4809 a: qAlpha(rgb: colorTable[i]));
4810 colorTableBytes.append(
4811 a: QByteArray::fromRawData(data: reinterpret_cast<const char *>(&shifted),
4812 size: sizeof(shifted)));
4813 }
4814 colorTextureData->setTextureData(colorTableBytes);
4815 }
4816 }
4817 ++itemIterator;
4818 }
4819}
4820
4821void QQuickGraphsItem::updateAxisRange(float min, float max)
4822{
4823 Q_UNUSED(min);
4824 Q_UNUSED(max);
4825}
4826
4827void QQuickGraphsItem::updateAxisReversed(bool enable)
4828{
4829 Q_UNUSED(enable);
4830}
4831
4832int QQuickGraphsItem::findLabelsMaxWidth(const QStringList &labels)
4833{
4834 int labelWidth = 0;
4835 QFontMetrics labelFM(theme()->labelFont());
4836
4837 for (const auto &label : std::as_const(t: labels)) {
4838 auto width = labelFM.horizontalAdvance(label);
4839 if (labelWidth < width)
4840 labelWidth = width;
4841 }
4842 return labelWidth;
4843}
4844
4845QVector3D QQuickGraphsItem::calculateCategoryLabelPosition(QAbstract3DAxis *axis,
4846 QVector3D labelPosition,
4847 int index)
4848{
4849 Q_UNUSED(axis);
4850 Q_UNUSED(index);
4851 return labelPosition;
4852}
4853
4854float QQuickGraphsItem::calculateCategoryGridLinePosition(QAbstract3DAxis *axis, int index)
4855{
4856 Q_UNUSED(axis);
4857 Q_UNUSED(index);
4858 return 0.0f;
4859}
4860
4861float QQuickGraphsItem::calculatePolarBackgroundMargin()
4862{
4863 // Check each extents of each angular label
4864 // Calculate angular position
4865 auto valueAxisX = static_cast<QValue3DAxis *>(axisX());
4866 auto labelPositions = const_cast<QList<float> &>(valueAxisX->formatter()->labelPositions());
4867 float actualLabelHeight = m_fontScaled.y() * 2.0f; // All labels are same height
4868 float maxNeededMargin = 0.0f;
4869
4870 // Axis title needs to be accounted for
4871 if (valueAxisX->isTitleVisible())
4872 maxNeededMargin = 2.0f * actualLabelHeight + 3.0f * labelMargin();
4873
4874 for (int label = 0; label < labelPositions.size(); label++) {
4875 QSizeF labelSize{m_fontScaled.x(), m_fontScaled.z()};
4876 float actualLabelWidth = actualLabelHeight / labelSize.height() * labelSize.width();
4877 float labelPosition = labelPositions.at(i: label);
4878 qreal angle = labelPosition * M_PI * 2.0;
4879 float x = qAbs(t: (m_polarRadius + labelMargin()) * float(qSin(v: angle))) + actualLabelWidth
4880 - m_polarRadius + labelMargin();
4881 float z = qAbs(t: -(m_polarRadius + labelMargin()) * float(qCos(v: angle))) + actualLabelHeight
4882 - m_polarRadius + labelMargin();
4883 float neededMargin = qMax(a: x, b: z);
4884 maxNeededMargin = qMax(a: maxNeededMargin, b: neededMargin);
4885 }
4886
4887 maxNeededMargin *= 0.2f;
4888 return maxNeededMargin;
4889}
4890
4891void QQuickGraphsItem::updateXTitle(QVector3D labelRotation,
4892 QVector3D labelTrans,
4893 const QQuaternion &totalRotation,
4894 float labelsMaxWidth,
4895 QVector3D scale)
4896{
4897 QFont font = theme()->axisXLabelFont() == QFont() ? theme()->labelFont() : theme()->axisXLabelFont();
4898 float pointSize = font.pointSizeF();
4899 float textPadding = pointSize * .5f;
4900 QFontMetrics fm(font);
4901 float height = fm.height() + textPadding;
4902 float width = fm.horizontalAdvance(axisX()->title()) + textPadding;
4903
4904 float titleOffset;
4905
4906 bool radial = false;
4907 if (radial)
4908 titleOffset = -2.0f * (m_labelMargin + scale.y());
4909 else
4910 titleOffset = 2.0f * m_labelMargin + (labelsMaxWidth * scale.y());
4911
4912 float zRotation = 0.0f;
4913 float yRotation = 0.0f;
4914 float xRotation = -90.0f + labelRotation.z();
4915 float offsetRotation = labelRotation.z();
4916 float extraRotation = -90.0f;
4917 if (m_yFlipped) {
4918 zRotation = 180.0f;
4919 if (m_zFlipped) {
4920 titleOffset = -titleOffset;
4921 if (m_xFlipped) {
4922 offsetRotation = -offsetRotation;
4923 extraRotation = -extraRotation;
4924 } else {
4925 xRotation = -90.0f - labelRotation.z();
4926 }
4927 } else {
4928 yRotation = 180.0f;
4929 if (m_xFlipped) {
4930 offsetRotation = -offsetRotation;
4931 xRotation = -90.0f - labelRotation.z();
4932 } else {
4933 extraRotation = -extraRotation;
4934 }
4935 }
4936 } else {
4937 if (m_zFlipped) {
4938 titleOffset = -titleOffset;
4939 if (m_xFlipped) {
4940 offsetRotation = -offsetRotation;
4941 } else {
4942 xRotation = -90.0f - labelRotation.z();
4943 extraRotation = -extraRotation;
4944 }
4945 yRotation = 180.0f;
4946 if (m_yFlipped) {
4947 extraRotation = -extraRotation;
4948 if (m_xFlipped)
4949 xRotation = 90.0f + labelRotation.z();
4950 else
4951 xRotation = 90.0f - labelRotation.z();
4952 }
4953 } else {
4954 if (m_xFlipped) {
4955 offsetRotation = -offsetRotation;
4956 xRotation = -90.0f - labelRotation.z();
4957 extraRotation = -extraRotation;
4958 }
4959 if (m_yFlipped) {
4960 xRotation = 90.0f + labelRotation.z();
4961 extraRotation = -extraRotation;
4962 if (m_xFlipped)
4963 xRotation = 90.0f - labelRotation.z();
4964 }
4965 }
4966 }
4967
4968 if (offsetRotation == 180.0f || offsetRotation == -180.0f)
4969 offsetRotation = 0.0f;
4970
4971 QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: offsetRotation);
4972 QVector3D titleOffsetVector = offsetRotator.rotatedVector(vector: QVector3D(0.0f, 0.0f, titleOffset));
4973 titleOffsetVector.setX(axisX()->titleOffset() * scaleWithBackground().x());
4974
4975 QQuaternion titleRotation;
4976 if (axisX()->isTitleFixed()) {
4977 titleRotation = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: zRotation)
4978 * QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: yRotation)
4979 * QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: xRotation);
4980 } else {
4981 titleRotation = totalRotation
4982 * QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: extraRotation);
4983 }
4984
4985 QVector3D titleScale = scale;
4986 titleScale.setX(titleScale.y() * width / height);
4987 m_titleLabelX->setScale(titleScale);
4988 m_titleLabelX->setPosition(labelTrans + titleOffsetVector);
4989 m_titleLabelX->setRotation(titleRotation);
4990 m_titleLabelX->setProperty(name: "labelWidth", value: width);
4991 m_titleLabelX->setProperty(name: "labelHeight", value: height);
4992}
4993
4994void QQuickGraphsItem::updateYTitle(QVector3D sideLabelRotation,
4995 QVector3D backLabelRotation,
4996 QVector3D sideLabelTrans,
4997 QVector3D backLabelTrans,
4998 const QQuaternion &totalSideRotation,
4999 const QQuaternion &totalBackRotation,
5000 float labelsMaxWidth,
5001 QVector3D scale)
5002{
5003 QFont font = theme()->axisYLabelFont() == QFont() ? theme()->labelFont() : theme()->axisYLabelFont();
5004 float pointSize = font.pointSizeF();
5005 float textPadding = pointSize * .5f;
5006 QFontMetrics fm(font);
5007 float height = fm.height() + textPadding;
5008 float width = fm.horizontalAdvance(axisY()->title()) + textPadding;
5009
5010 float titleOffset = m_labelMargin + (labelsMaxWidth * scale.x());
5011
5012 QQuaternion zRightAngleRotation = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: 90.0f);
5013 float yRotation;
5014 QVector3D titleTrans;
5015 QQuaternion totalRotation;
5016 if (m_xFlipped != m_zFlipped) {
5017 yRotation = backLabelRotation.y();
5018 titleTrans = backLabelTrans;
5019 totalRotation = totalBackRotation;
5020 } else {
5021 yRotation = sideLabelRotation.y();
5022 titleTrans = sideLabelTrans;
5023 totalRotation = totalSideRotation;
5024 }
5025 titleTrans.setY(.0f);
5026
5027 QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: yRotation);
5028 QVector3D titleOffsetVector = offsetRotator.rotatedVector(vector: QVector3D(-titleOffset, 0.0f, 0.0f));
5029 titleOffsetVector.setY(axisY()->titleOffset() * scaleWithBackground().y());
5030
5031 QQuaternion titleRotation;
5032 if (axisY()->isTitleFixed()) {
5033 titleRotation = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: yRotation)
5034 * zRightAngleRotation;
5035 } else {
5036 titleRotation = totalRotation * zRightAngleRotation;
5037 }
5038
5039 QVector3D titleScale = scale;
5040 titleScale.setX(titleScale.y() * width / height);
5041 m_titleLabelY->setScale(titleScale);
5042 m_titleLabelY->setPosition(titleTrans + titleOffsetVector);
5043 m_titleLabelY->setRotation(titleRotation);
5044 m_titleLabelY->setProperty(name: "labelWidth", value: width);
5045 m_titleLabelY->setProperty(name: "labelHeight", value: height);
5046}
5047
5048void QQuickGraphsItem::updateZTitle(QVector3D labelRotation,
5049 QVector3D labelTrans,
5050 const QQuaternion &totalRotation,
5051 float labelsMaxWidth,
5052 QVector3D scale)
5053{
5054 QFont font = theme()->axisZLabelFont() == QFont() ? theme()->labelFont() : theme()->axisZLabelFont();
5055 float pointSize = font.pointSizeF();
5056 float textPadding = pointSize * .5f;
5057 QFontMetrics fm(font);
5058 float height = fm.height() + textPadding;
5059 float width = fm.horizontalAdvance(axisZ()->title()) + textPadding;
5060
5061 float titleOffset = m_labelMargin + (labelsMaxWidth * scale.x());
5062
5063 float zRotation = labelRotation.z();
5064 float yRotation = -90.0f;
5065 float xRotation = -90.0f;
5066 float extraRotation = 90.0f;
5067
5068 if (m_yFlipped) {
5069 xRotation = -xRotation;
5070 if (m_zFlipped) {
5071 if (m_xFlipped) {
5072 titleOffset = -titleOffset;
5073 zRotation = -zRotation;
5074 extraRotation = -extraRotation;
5075 } else {
5076 zRotation = -zRotation;
5077 yRotation = -yRotation;
5078 }
5079 } else {
5080 if (m_xFlipped) {
5081 titleOffset = -titleOffset;
5082 } else {
5083 extraRotation = -extraRotation;
5084 yRotation = -yRotation;
5085 }
5086 }
5087 } else {
5088 if (m_zFlipped) {
5089 zRotation = -zRotation;
5090 if (m_xFlipped) {
5091 titleOffset = -titleOffset;
5092 } else {
5093 extraRotation = -extraRotation;
5094 yRotation = -yRotation;
5095 }
5096 } else {
5097 if (m_xFlipped) {
5098 titleOffset = -titleOffset;
5099 extraRotation = -extraRotation;
5100 } else {
5101 yRotation = -yRotation;
5102 }
5103 }
5104 if (m_yFlipped) {
5105 xRotation = -xRotation;
5106 extraRotation = -extraRotation;
5107 }
5108 }
5109
5110 float offsetRotation = zRotation;
5111 if (offsetRotation == 180.0f || offsetRotation == -180.0f)
5112 offsetRotation = 0.0f;
5113
5114 QQuaternion offsetRotator = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: offsetRotation);
5115 QVector3D titleOffsetVector = offsetRotator.rotatedVector(vector: QVector3D(titleOffset, 0.0f, 0.0f));
5116 titleOffsetVector.setZ(axisZ()->titleOffset() * scaleWithBackground().z());
5117
5118 QQuaternion titleRotation;
5119 if (axisZ()->isTitleFixed()) {
5120 titleRotation = QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: zRotation)
5121 * QQuaternion::fromAxisAndAngle(x: 0.0f, y: 1.0f, z: 0.0f, angle: yRotation)
5122 * QQuaternion::fromAxisAndAngle(x: 1.0f, y: 0.0f, z: 0.0f, angle: xRotation);
5123 } else {
5124 titleRotation = totalRotation
5125 * QQuaternion::fromAxisAndAngle(x: 0.0f, y: 0.0f, z: 1.0f, angle: extraRotation);
5126 }
5127
5128 QVector3D titleScale = scale;
5129 titleScale.setX(titleScale.y() * width / height);
5130 m_titleLabelZ->setScale(titleScale);
5131 m_titleLabelZ->setPosition(labelTrans + titleOffsetVector);
5132 m_titleLabelZ->setRotation(titleRotation);
5133 m_titleLabelZ->setProperty(name: "labelWidth", value: width);
5134 m_titleLabelZ->setProperty(name: "labelHeight", value: height);
5135}
5136
5137void QQuickGraphsItem::updateCamera()
5138{
5139 Q_TRACE(QGraphs3DItemUpdateCamera_entry);
5140 QVector3D lookingPosition = m_requestedTarget;
5141
5142 const float scale = qMin(a: width(), b: height() * 1.6f);
5143 const float magnificationScaleFactor = 1.0f / 640.0f;
5144 const float magnification = scale * magnificationScaleFactor / rootNode()->scale().x();
5145
5146 auto useOrtho = isOrthoProjection();
5147 if (useOrtho) {
5148 if (m_sliceView && m_sliceView->isVisible()) {
5149 m_oCamera->setVerticalMagnification(m_zoomLevel * .4f);
5150 m_oCamera->setHorizontalMagnification(m_zoomLevel * .4f);
5151 } else {
5152 m_oCamera->setVerticalMagnification(m_zoomLevel * magnification);
5153 m_oCamera->setHorizontalMagnification(m_zoomLevel * magnification);
5154 }
5155 }
5156 cameraTarget()->setPosition(lookingPosition);
5157 auto rotation = QVector3D(-m_yRotation, -m_xRotation, 0);
5158 cameraTarget()->setEulerRotation(rotation);
5159 float zoom = 720.f / m_zoomLevel;
5160 m_pCamera->setZ(zoom);
5161 updateCustomLabelsRotation();
5162 updateItemLabel(position: m_labelPosition);
5163 Q_TRACE(QGraphs3DItemUpdateCamera_exit);
5164}
5165
5166void QQuickGraphsItem::handleLabelCountChanged(QQuick3DRepeater *repeater, QColor axisLabelColor)
5167{
5168 changeLabelBackgroundColor(repeater, color: theme()->labelBackgroundColor());
5169 changeLabelBackgroundVisible(repeater, visible: theme()->isLabelBackgroundVisible());
5170 changeLabelBorderVisible(repeater, visible: theme()->isLabelBorderVisible());
5171 changeLabelTextColor(repeater, color: axisLabelColor);
5172 changeLabelFont(repeater, font: theme()->labelFont());
5173
5174 if (m_sliceView) {
5175 changeLabelBackgroundColor(repeater: m_sliceHorizontalLabelRepeater, color: theme()->labelBackgroundColor());
5176 changeLabelBackgroundColor(repeater: m_sliceVerticalLabelRepeater, color: theme()->labelBackgroundColor());
5177 changeLabelBackgroundVisible(repeater: m_sliceHorizontalLabelRepeater,
5178 visible: theme()->isLabelBackgroundVisible());
5179 changeLabelBackgroundVisible(repeater: m_sliceVerticalLabelRepeater,
5180 visible: theme()->isLabelBackgroundVisible());
5181 changeLabelBorderVisible(repeater: m_sliceHorizontalLabelRepeater, visible: theme()->isLabelBorderVisible());
5182 changeLabelBorderVisible(repeater: m_sliceVerticalLabelRepeater, visible: theme()->isLabelBorderVisible());
5183 if (m_selectionMode == QtGraphs3D::SelectionFlag::Row)
5184 changeLabelTextColor(repeater: m_sliceHorizontalLabelRepeater, color: theme()->axisX().labelTextColor());
5185 else if (m_selectionMode == QtGraphs3D::SelectionFlag::Column)
5186 changeLabelTextColor(repeater: m_sliceHorizontalLabelRepeater, color: theme()->axisZ().labelTextColor());
5187 changeLabelTextColor(repeater: m_sliceVerticalLabelRepeater, color: theme()->axisY().labelTextColor());
5188 changeLabelFont(repeater: m_sliceHorizontalLabelRepeater, font: theme()->labelFont());
5189 changeLabelFont(repeater: m_sliceVerticalLabelRepeater, font: theme()->labelFont());
5190 }
5191}
5192
5193void QQuickGraphsItem::updateCustomData()
5194{
5195 Q_TRACE_SCOPE(QGraphs3DItemUpdateCustomData);
5196
5197 int maxX = axisX()->max();
5198 int minX = axisX()->min();
5199 int maxY = axisY()->max();
5200 int minY = axisY()->min();
5201 int maxZ = axisZ()->max();
5202 int minZ = axisZ()->min();
5203
5204 auto labelIterator = m_customLabelList.constBegin();
5205 while (labelIterator != m_customLabelList.constEnd()) {
5206 QCustom3DLabel *label = labelIterator.key();
5207 QQuick3DNode *customLabel = labelIterator.value();
5208
5209 QVector3D pos = label->position();
5210 // We incorrectly assumed label position to be normalized by default, when it in
5211 // reality is -1...1. Because of this we need to multiply the x and z by 2.
5212 // (QTBUG-131138)
5213 pos.setX(pos.x() * 2.f);
5214 pos.setZ(pos.z() * 2.f);
5215 if (!label->isPositionAbsolute()) {
5216 if (pos.x() < minX || pos.x() > maxX
5217 || pos.y() < minY || pos.y() > maxY
5218 || pos.z() < minZ || pos.z() > maxZ) {
5219 customLabel->setVisible(false);
5220 ++labelIterator;
5221 continue;
5222 }
5223 pos = graphPosToAbsolute(position: pos);
5224 }
5225
5226 QFontMetrics fm(label->font());
5227 int width = fm.horizontalAdvance(label->text());
5228 int height = fm.height();
5229 customLabel->setProperty(name: "labelWidth", value: width);
5230 customLabel->setProperty(name: "labelHeight", value: height);
5231 customLabel->setPosition(pos);
5232 QQuaternion rotation = label->rotation();
5233 if (label->isFacingCamera())
5234 rotation = Utils::calculateRotation(xyzRotations: QVector3D(-m_yRotation, -m_xRotation, 0));
5235 customLabel->setRotation(rotation);
5236 float pointSize = theme()->labelFont().pointSizeF();
5237 float scaleFactor = fontScaleFactor(pointSize) * pointSize;
5238 float fontRatio = float(height) / float(width);
5239 QVector3D fontScaled = QVector3D(scaleFactor / fontRatio, scaleFactor, 0.0f);
5240 customLabel->setScale(fontScaled);
5241 customLabel->setProperty(name: "labelText", value: label->text());
5242 customLabel->setProperty(name: "labelTextColor", value: label->textColor());
5243 customLabel->setProperty(name: "labelFont", value: label->font());
5244 customLabel->setProperty(name: "backgroundVisible", value: label->isBackgroundVisible());
5245 customLabel->setProperty(name: "backgroundColor", value: label->backgroundColor());
5246 customLabel->setProperty(name: "borderVisible", value: label->isBorderVisible());
5247 customLabel->setVisible(label->isVisible());
5248
5249 ++labelIterator;
5250 }
5251
5252 auto itemIterator = m_customItemList.constBegin();
5253 while (itemIterator != m_customItemList.constEnd()) {
5254 QCustom3DItem *item = itemIterator.key();
5255 QQuick3DModel *model = itemIterator.value();
5256
5257 QVector3D pos = item->position();
5258 QVector<QAbstract3DAxis *> axes{axisX(), axisY(), axisZ()};
5259 QVector<float> bScales{scaleWithBackground().x(),
5260 scaleWithBackground().y(),
5261 scaleWithBackground().z()};
5262 if (!item->isPositionAbsolute()) {
5263 if (item->position().x() < minX || item->position().x() > maxX
5264 || item->position().y() < minY || item->position().y() > maxY
5265 || item->position().z() < minZ || item->position().z() > maxZ) {
5266 model->setVisible(false);
5267 ++itemIterator;
5268 continue;
5269 }
5270 pos = graphPosToAbsolute(position: pos);
5271 }
5272 model->setPosition(pos);
5273
5274 if (!item->isScalingAbsolute()) {
5275 QVector<float> iScales{item->scaling().x(), item->scaling().y(), item->scaling().z()};
5276 for (int i = 0; i < axes.count(); i++) {
5277 if (auto vAxis = static_cast<QValue3DAxis *>(axes.at(i))) {
5278 float axisRange = vAxis->max() - vAxis->min();
5279 float realRange = bScales.at(i);
5280 float ratio = realRange / axisRange;
5281 iScales[i] *= ratio;
5282 }
5283 }
5284 // We incorrectly assumed models to be scaled to 0...1 by default, when they in
5285 // reality are scaled to -1...1. Because of this we need to multiply the scale by 2
5286 // (QTBUG-126611)
5287 model->setScale(QVector3D(iScales.at(i: 0), iScales.at(i: 1), iScales.at(i: 2)) * 2.f);
5288 } else {
5289 // We incorrectly assumed models to be scaled to 0...1 by default, when they in
5290 // reality are scaled to -1...1. Because of this we need to multiply the scale by 2
5291 // (QTBUG-126611)
5292 model->setScale(item->scaling() * 2.f);
5293 }
5294
5295 if (auto volume = qobject_cast<QCustom3DVolume *>(object: item)) {
5296 if (!m_customVolumes.contains(key: volume)) {
5297 auto &&volumeItem = m_customVolumes[volume];
5298
5299 volumeItem.model = model;
5300 model->setSource(QUrl(volume->meshFile()));
5301
5302 volumeItem.useHighDefShader = volume->useHighDefShader();
5303
5304 m_validVolumeSlice = volume->sliceIndexX() >= 0
5305 || volume->sliceIndexY() >= 0
5306 || volume->sliceIndexZ() >= 0;
5307
5308 volumeItem.drawSlices = volume->drawSlices() && m_validVolumeSlice;
5309
5310 createVolumeMaterial(volume, volumeItem);
5311
5312 volumeItem.sliceFrameX = createSliceFrame(volumeItem);
5313 volumeItem.sliceFrameY = createSliceFrame(volumeItem);
5314 volumeItem.sliceFrameZ = createSliceFrame(volumeItem);
5315
5316 if (volume->drawSliceFrames()) {
5317 volumeItem.sliceFrameX->setVisible(true);
5318 volumeItem.sliceFrameY->setVisible(true);
5319 volumeItem.sliceFrameZ->setVisible(true);
5320
5321 QVector3D sliceIndices((float(volume->sliceIndexX()) + 0.5f)
5322 / float(volume->textureWidth()) * 2.0
5323 - 1.0,
5324 (float(volume->sliceIndexY()) + 0.5f)
5325 / float(volume->textureHeight()) * 2.0
5326 - 1.0,
5327 (float(volume->sliceIndexZ()) + 0.5f)
5328 / float(volume->textureDepth()) * 2.0
5329 - 1.0);
5330
5331 volumeItem.sliceFrameX->setX(sliceIndices.x());
5332 volumeItem.sliceFrameY->setY(-sliceIndices.y());
5333 volumeItem.sliceFrameZ->setZ(-sliceIndices.z());
5334
5335 volumeItem.sliceFrameX->setRotation(QQuaternion::fromEulerAngles(pitch: 0, yaw: 90, roll: 0));
5336 volumeItem.sliceFrameY->setRotation(QQuaternion::fromEulerAngles(pitch: 90, yaw: 0, roll: 0));
5337
5338 updateSliceFrameMaterials(volume, volumeItem);
5339 } else {
5340 volumeItem.sliceFrameX->setVisible(false);
5341 volumeItem.sliceFrameY->setVisible(false);
5342 volumeItem.sliceFrameZ->setVisible(false);
5343 }
5344 volumeItem.drawSliceFrames = volume->drawSliceFrames();
5345 m_customItemList.insert(key: item, value: model);
5346 }
5347 } else {
5348 model->setSource(QUrl::fromLocalFile(localfile: item->meshFile()));
5349 QQmlListReference materialsRef(model, "materials");
5350 QQuick3DPrincipledMaterial *material = static_cast<QQuick3DPrincipledMaterial *>(
5351 materialsRef.at(0));
5352 QQuick3DTexture *texture = material->baseColorMap();
5353 if (!texture) {
5354 texture = new QQuick3DTexture();
5355 texture->setParent(model);
5356 texture->setParentItem(model);
5357 material->setBaseColorMap(texture);
5358 }
5359 if (!item->textureFile().isEmpty()) {
5360 texture->setSource(QUrl::fromLocalFile(localfile: item->textureFile()));
5361 } else {
5362 QImage textureImage = customTextureImage(item);
5363 textureImage.convertTo(f: QImage::Format_RGBA32FPx4);
5364 QQuick3DTextureData *textureData = texture->textureData();
5365 if (!textureData) {
5366 textureData = new QQuick3DTextureData();
5367 textureData->setParent(texture);
5368 textureData->setParentItem(texture);
5369 textureData->setFormat(QQuick3DTextureData::RGBA32F);
5370 texture->setTextureData(textureData);
5371 }
5372 textureData->setSize(textureImage.size());
5373 textureData->setTextureData(
5374 QByteArray(reinterpret_cast<const char *>(textureImage.bits()),
5375 textureImage.sizeInBytes()));
5376 }
5377 model->setRotation(item->rotation());
5378 model->setVisible(item->isVisible());
5379 }
5380 ++itemIterator;
5381 }
5382}
5383
5384void QQuickGraphsItem::updateCustomLabelsRotation()
5385{
5386 auto labelIterator = m_customLabelList.constBegin();
5387 while (labelIterator != m_customLabelList.constEnd()) {
5388 QCustom3DLabel *label = labelIterator.key();
5389 QQuick3DNode *customLabel = labelIterator.value();
5390 QQuaternion rotation = label->rotation();
5391 if (label->isFacingCamera())
5392 rotation = Utils::calculateRotation(xyzRotations: QVector3D(-m_yRotation, -m_xRotation, 0));
5393 customLabel->setRotation(rotation);
5394 ++labelIterator;
5395 }
5396}
5397
5398int QQuickGraphsItem::msaaSamples() const
5399{
5400 if (m_renderMode == QtGraphs3D::RenderingMode::Indirect)
5401 return m_samples;
5402 else
5403 return m_windowSamples;
5404}
5405
5406void QQuickGraphsItem::setMsaaSamples(int samples)
5407{
5408 if (m_renderMode != QtGraphs3D::RenderingMode::Indirect) {
5409 qCWarning(lcProperties3D, "%s multisampling cannot be adjusted in this render mode",
5410 qUtf8Printable(QLatin1String(__FUNCTION__)));
5411 } else if (m_samples != samples) {
5412 m_samples = samples;
5413 setAntialiasing(m_samples > 0);
5414 auto sceneEnv = environment();
5415 sceneEnv->setAntialiasingMode(
5416 m_samples > 0 ? QQuick3DSceneEnvironment::QQuick3DEnvironmentAAModeValues::MSAA
5417 : QQuick3DSceneEnvironment::QQuick3DEnvironmentAAModeValues::NoAA);
5418 switch (m_samples) {
5419 case 0:
5420 // no-op
5421 break;
5422 case 2:
5423 sceneEnv->setAntialiasingQuality(
5424 QQuick3DSceneEnvironment::QQuick3DEnvironmentAAQualityValues::Medium);
5425 break;
5426 case 4:
5427 sceneEnv->setAntialiasingQuality(
5428 QQuick3DSceneEnvironment::QQuick3DEnvironmentAAQualityValues::High);
5429 break;
5430 case 8:
5431 sceneEnv->setAntialiasingQuality(
5432 QQuick3DSceneEnvironment::QQuick3DEnvironmentAAQualityValues::VeryHigh);
5433 break;
5434 default:
5435 qCWarning(lcProperties3D, "%s invalid multisampling sample number, using 4x instead",
5436 qUtf8Printable(QLatin1String(__FUNCTION__)));
5437 sceneEnv->setAntialiasingQuality(
5438 QQuick3DSceneEnvironment::QQuick3DEnvironmentAAQualityValues::High);
5439 m_samples = 4;
5440 break;
5441 }
5442 emit msaaSamplesChanged(samples: m_samples);
5443 update();
5444 }
5445}
5446
5447void QQuickGraphsItem::setParentNode(QQuick3DNode *node) {
5448 if (node) {
5449 m_parentNode = node;
5450
5451 // find active sceneManager
5452 auto *p = node->parent();
5453 QQuick3DViewport *view = nullptr;
5454 while (p && !view) {
5455 view = qobject_cast<QQuick3DViewport *>(object: p);
5456 p = p->parent();
5457 }
5458
5459 if (view) {
5460 m_customView = view;
5461 auto sceneManager = QQuick3DObjectPrivate::get(item: view->scene())->sceneManager;
5462 setParent(view);
5463
5464 if (graphNode()) {
5465 graphNode()->setParent(view->parent());
5466 graphNode()->setParentItem(node);
5467 }
5468
5469 connect(sender: sceneManager.data(),
5470 signal: &QQuick3DSceneManager::windowChanged,
5471 context: this,
5472 slot: &QQuickGraphsItem::handleWindowChanged);
5473
5474 handleWindowChanged();
5475 }
5476
5477 }
5478}
5479
5480void QQuickGraphsItem::handleWindowChanged(/*QQuickWindow *window*/)
5481{
5482
5483 QQuick3DSceneManager *manager = nullptr;
5484 if (m_customView)
5485 manager = QQuick3DObjectPrivate::get(item: m_customView->scene())->sceneManager;
5486 else
5487 manager = QQuick3DObjectPrivate::get(item: rootNode())->sceneManager;
5488
5489 auto window = manager->window();
5490 checkWindowList(window);
5491
5492 if (!window)
5493 return;
5494
5495#if defined(Q_OS_MACOS)
5496 bool previousVisibility = window->isVisible();
5497 // Enable touch events for Mac touchpads
5498 window->setVisible(true);
5499 typedef void (*EnableTouch)(QWindow *, bool);
5500 EnableTouch enableTouch = reinterpret_cast<EnableTouch>(
5501 QFunctionPointer(QGuiApplication::platformNativeInterface()
5502 ->nativeResourceFunctionForIntegration("registertouchwindow")));
5503 if (enableTouch)
5504 enableTouch(window, true);
5505 window->setVisible(previousVisibility);
5506#endif
5507
5508 connect(sender: window, signal: &QObject::destroyed, context: this, slot: &QQuickGraphsItem::windowDestroyed);
5509
5510 int oldWindowSamples = m_windowSamples;
5511 m_windowSamples = window->format().samples();
5512 if (m_windowSamples < 0)
5513 m_windowSamples = 0;
5514
5515 connect(sender: window, signal: &QQuickWindow::beforeSynchronizing, context: this, slot: &QQuickGraphsItem::synchData);
5516
5517 if (m_renderMode == QtGraphs3D::RenderingMode::DirectToBackground) {
5518 setAntialiasing(m_windowSamples > 0);
5519 if (m_windowSamples != oldWindowSamples)
5520 emit msaaSamplesChanged(samples: m_windowSamples);
5521 }
5522
5523 connect(sender: this, signal: &QQuickGraphsItem::needRender, context: window, slot: &QQuickWindow::update);
5524 // Force camera update before rendering the first frame
5525 // to workaround a Quick3D device pixel ratio bug
5526 connect(sender: window, signal: &QQuickWindow::beforeRendering, context: this, slot: [this, window]() {
5527 m_oCamera->setClipNear(0.001f);
5528 disconnect(sender: window, signal: &QQuickWindow::beforeRendering, receiver: this, zero: nullptr);
5529 });
5530 updateWindowParameters();
5531
5532#if defined(Q_OS_IOS)
5533 // Scenegraph render cycle in iOS sometimes misses update after
5534 // beforeSynchronizing signal. This ensures we don't end up displaying the
5535 // graph without any data, in case update is skipped after synchData.
5536 QTimer::singleShot(0, window, SLOT(update()));
5537#endif
5538}
5539
5540void QQuickGraphsItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
5541{
5542 QQuickItem::geometryChange(newGeometry, oldGeometry);
5543 // Do not cache primary subviewport geometry, as that will mess up window size
5544 if (!parentItem())
5545 return;
5546 m_cachedGeometry = parentItem()->boundingRect();
5547 updateWindowParameters();
5548}
5549
5550void QQuickGraphsItem::itemChange(ItemChange change, const ItemChangeData &value)
5551{
5552 QQuick3DViewport::itemChange(change, value);
5553 updateWindowParameters();
5554}
5555
5556void QQuickGraphsItem::updateWindowParameters()
5557{
5558 const QMutexLocker locker(&m_mutex);
5559 // Update the device pixel ratio, window size and bounding box
5560 QQuickWindow *win = window();
5561 if (win) {
5562 if (win->devicePixelRatio() != scene()->devicePixelRatio()) {
5563 scene()->setDevicePixelRatio(win->devicePixelRatio());
5564 win->update();
5565 }
5566
5567 QSize windowSize;
5568
5569 if (m_renderMode == QtGraphs3D::RenderingMode::DirectToBackground)
5570 windowSize = win->size();
5571 else
5572 windowSize = m_cachedGeometry.size().toSize();
5573
5574 if (windowSize != scene()->d_func()->windowSize()) {
5575 scene()->d_func()->setWindowSize(windowSize);
5576 win->update();
5577 }
5578
5579 resizeViewports(viewportSize: m_cachedGeometry.size());
5580 }
5581}
5582
5583void QQuickGraphsItem::handleSelectionModeChange(QtGraphs3D::SelectionFlags mode)
5584{
5585 emit selectionModeChanged(mode);
5586}
5587
5588void QQuickGraphsItem::handleShadowQualityChange(QtGraphs3D::ShadowQuality quality)
5589{
5590 emit shadowQualityChanged(quality);
5591}
5592
5593void QQuickGraphsItem::handleSelectedElementChange(QtGraphs3D::ElementType type)
5594{
5595 m_clickedType = type;
5596 emit selectedElementChanged(type);
5597}
5598
5599void QQuickGraphsItem::handleOptimizationHintChange(QtGraphs3D::OptimizationHint hint)
5600{
5601 Q_UNUSED(hint)
5602}
5603
5604void QQuickGraphsItem::resizeViewports(QSizeF viewportSize)
5605{
5606 if (!viewportSize.isEmpty()) {
5607 scene()->d_func()->setViewport(
5608 QRect(0.0f, 0.0f, viewportSize.width() + 0.5f, viewportSize.height() + 0.5f));
5609 }
5610}
5611
5612void QQuickGraphsItem::checkWindowList(QQuickWindow *window)
5613{
5614 QQuickWindow *oldWindow = m_graphWindowList.value(key: this);
5615 m_graphWindowList[this] = window;
5616
5617 if (oldWindow != window && oldWindow) {
5618 QObject::disconnect(sender: oldWindow,
5619 signal: &QObject::destroyed,
5620 receiver: this,
5621 slot: &QQuickGraphsItem::windowDestroyed);
5622 QObject::disconnect(sender: oldWindow,
5623 signal: &QQuickWindow::beforeSynchronizing,
5624 receiver: this,
5625 slot: &QQuickGraphsItem::synchData);
5626 QObject::disconnect(sender: this, signal: &QQuickGraphsItem::needRender, receiver: oldWindow, slot: &QQuickWindow::update);
5627 }
5628
5629 QList<QQuickWindow *> windowList;
5630
5631 const auto keys = m_graphWindowList.keys();
5632 for (const auto &graph : keys) {
5633 if (graph->m_renderMode == QtGraphs3D::RenderingMode::DirectToBackground)
5634 windowList.append(t: m_graphWindowList.value(key: graph));
5635 }
5636
5637 if (!window) {
5638 m_graphWindowList.remove(key: this);
5639 return;
5640 }
5641}
5642
5643void QQuickGraphsItem::setMeasureFps(bool enable)
5644{
5645 if (m_measureFps == enable) {
5646 qCDebug(lcProperties3D) << __FUNCTION__
5647 << "value is already set to:" << enable;
5648 return;
5649 }
5650
5651 m_measureFps = enable;
5652 if (enable) {
5653 QObject::connect(sender: renderStats(),
5654 signal: &QQuick3DRenderStats::fpsChanged,
5655 context: this,
5656 slot: &QQuickGraphsItem::handleFpsChanged);
5657 emitNeedRender();
5658 } else {
5659 QObject::disconnect(sender: renderStats(), signal: 0, receiver: this, member: 0);
5660 }
5661 emit measureFpsChanged(enabled: enable);
5662}
5663
5664bool QQuickGraphsItem::measureFps() const
5665{
5666 return m_measureFps;
5667}
5668
5669int QQuickGraphsItem::currentFps() const
5670{
5671 return m_currentFps;
5672}
5673
5674void QQuickGraphsItem::setOrthoProjection(bool enable)
5675{
5676 if (enable == m_useOrthoProjection) {
5677 qCDebug(lcProperties3D) << __FUNCTION__
5678 << "graph is already using orthoProjection.";
5679 return;
5680 }
5681 m_useOrthoProjection = enable;
5682 m_changeTracker.projectionChanged = true;
5683 emit orthoProjectionChanged(enabled: m_useOrthoProjection);
5684 // If changed to ortho, disable shadows
5685 if (m_useOrthoProjection)
5686 doSetShadowQuality(quality: QtGraphs3D::ShadowQuality::None);
5687 emitNeedRender();
5688}
5689
5690bool QQuickGraphsItem::isOrthoProjection() const
5691{
5692 return m_useOrthoProjection;
5693}
5694
5695QtGraphs3D::ElementType QQuickGraphsItem::selectedElement() const
5696{
5697 return m_clickedType;
5698}
5699
5700void QQuickGraphsItem::setAspectRatio(qreal ratio)
5701{
5702 if (m_aspectRatio == ratio || ratio <= 0.0) {
5703 qCDebug(lcProperties3D, "%s value is already set to: %.1f or aspect ratio is 0.0f or smaller",
5704 qUtf8Printable(QLatin1String(__FUNCTION__)), ratio);
5705 return;
5706 }
5707 m_aspectRatio = ratio;
5708 m_changeTracker.aspectRatioChanged = true;
5709 emit aspectRatioChanged(ratio: m_aspectRatio);
5710 m_isDataDirty = true;
5711 emitNeedRender();
5712}
5713
5714qreal QQuickGraphsItem::aspectRatio() const
5715{
5716 return m_aspectRatio;
5717}
5718
5719void QQuickGraphsItem::setOptimizationHint(QtGraphs3D::OptimizationHint hint)
5720{
5721 if (hint != m_optimizationHint) {
5722 m_optimizationHint = hint;
5723 m_changeTracker.optimizationHintChanged = true;
5724 m_isDataDirty = true;
5725 handleOptimizationHintChange(hint: m_optimizationHint);
5726 emit optimizationHintChanged(hint);
5727 emitNeedRender();
5728 } else {
5729 qCDebug(lcProperties3D) << qUtf8Printable(QLatin1String(__FUNCTION__))
5730 << "Value is already set to:" << hint;
5731 }
5732}
5733
5734QtGraphs3D::OptimizationHint QQuickGraphsItem::optimizationHint() const
5735{
5736 return m_optimizationHint;
5737}
5738
5739void QQuickGraphsItem::setPolar(bool enable)
5740{
5741 if (enable == m_isPolar) {
5742 qCDebug(lcProperties3D) << __FUNCTION__
5743 << "value is already set to:" << enable;
5744 return;
5745 }
5746 if (m_graphType == QAbstract3DSeries::SeriesType::Bar) {
5747 qCWarning(lcProperties3D, "%s polar type with bars is not supported.",
5748 qUtf8Printable(QLatin1String(__FUNCTION__)));
5749 }
5750 m_isPolar = enable;
5751 m_changeTracker.polarChanged = true;
5752 setVerticalSegmentLine(!m_isPolar);
5753 m_isDataDirty = true;
5754 emit polarChanged(enabled: m_isPolar);
5755 emitNeedRender();
5756}
5757
5758bool QQuickGraphsItem::isPolar() const
5759{
5760 return m_isPolar;
5761}
5762
5763void QQuickGraphsItem::setLabelMargin(float margin)
5764{
5765 if (qFuzzyCompare(p1: m_labelMargin, p2: margin)) {
5766 qCDebug(lcProperties3D, "%s value is already set to: %.1f",
5767 qUtf8Printable(QLatin1String(__FUNCTION__)), margin);
5768 return;
5769 }
5770 m_labelMargin = margin;
5771 m_changeTracker.labelMarginChanged = true;
5772 emit labelMarginChanged(margin: m_labelMargin);
5773 emitNeedRender();
5774}
5775
5776float QQuickGraphsItem::labelMargin() const
5777{
5778 return m_labelMargin;
5779}
5780
5781void QQuickGraphsItem::setRadialLabelOffset(float offset)
5782{
5783 if (qFuzzyCompare(p1: m_radialLabelOffset, p2: offset)) {
5784 qCDebug(lcProperties3D, "%s value is already set to: %.1f",
5785 qUtf8Printable(QLatin1String(__FUNCTION__)), offset);
5786 return;
5787 }
5788 m_radialLabelOffset = offset;
5789 m_changeTracker.radialLabelOffsetChanged = true;
5790 emit radialLabelOffsetChanged(offset: m_radialLabelOffset);
5791 emitNeedRender();
5792}
5793
5794float QQuickGraphsItem::radialLabelOffset() const
5795{
5796 return m_radialLabelOffset;
5797}
5798
5799void QQuickGraphsItem::setHorizontalAspectRatio(qreal ratio)
5800{
5801 if (qFuzzyCompare(p1: 1 + m_horizontalAspectRatio, p2: 1 + ratio) || ratio <= 0.0) {
5802 qCDebug(lcProperties3D, "%s invalid value or value is already set to: %f",
5803 qUtf8Printable(QLatin1String(__FUNCTION__)), ratio);
5804 return;
5805 }
5806 m_horizontalAspectRatio = ratio;
5807 m_changeTracker.horizontalAspectRatioChanged = true;
5808 emit horizontalAspectRatioChanged(ratio: m_horizontalAspectRatio);
5809 m_isDataDirty = true;
5810 emitNeedRender();
5811}
5812
5813qreal QQuickGraphsItem::horizontalAspectRatio() const
5814{
5815 return m_horizontalAspectRatio;
5816}
5817
5818void QQuickGraphsItem::setLocale(const QLocale &locale)
5819{
5820 if (m_locale == locale) {
5821 qCDebug(lcProperties3D) << __FUNCTION__
5822 << "value is already set to:" << locale;
5823 return;
5824 }
5825 m_locale = locale;
5826 // Value axis formatters need to be updated
5827 QValue3DAxis *axis = qobject_cast<QValue3DAxis *>(object: m_axisX);
5828 if (axis)
5829 axis->formatter()->setLocale(m_locale);
5830 axis = qobject_cast<QValue3DAxis *>(object: m_axisY);
5831 if (axis)
5832 axis->formatter()->setLocale(m_locale);
5833 axis = qobject_cast<QValue3DAxis *>(object: m_axisZ);
5834 if (axis)
5835 axis->formatter()->setLocale(m_locale);
5836 emit localeChanged(locale: m_locale);
5837}
5838
5839QLocale QQuickGraphsItem::locale() const
5840{
5841 return m_locale;
5842}
5843
5844QVector3D QQuickGraphsItem::queriedGraphPosition() const
5845{
5846 return m_queriedGraphPosition;
5847}
5848
5849void QQuickGraphsItem::setMargin(qreal margin)
5850{
5851 if (m_margin == margin) {
5852 qCDebug(lcProperties3D, "%s value is already set to: %.1f",
5853 qUtf8Printable(QLatin1String(__FUNCTION__)), margin);
5854 return;
5855 }
5856 m_margin = margin;
5857 m_changeTracker.marginChanged = true;
5858 emit marginChanged(margin);
5859 emitNeedRender();
5860}
5861
5862qreal QQuickGraphsItem::margin() const
5863{
5864 return m_margin;
5865}
5866
5867QQuick3DNode *QQuickGraphsItem::rootNode() const
5868{
5869 if (m_parentNode)
5870 return m_parentNode;
5871 else
5872 return QQuick3DViewport::scene();
5873}
5874
5875void QQuickGraphsItem::changeLabelBackgroundColor(QQuick3DRepeater *repeater, QColor color)
5876{
5877 int count = repeater->count();
5878 for (int i = 0; i < count; i++) {
5879 auto label = static_cast<QQuick3DNode *>(repeater->objectAt(index: i));
5880 label->setProperty(name: "backgroundColor", value: color);
5881 }
5882}
5883
5884void QQuickGraphsItem::changeLabelBackgroundVisible(QQuick3DRepeater *repeater, const bool &visible)
5885{
5886 int count = repeater->count();
5887 for (int i = 0; i < count; i++) {
5888 auto label = static_cast<QQuick3DNode *>(repeater->objectAt(index: i));
5889 label->setProperty(name: "backgroundVisible", value: visible);
5890 }
5891}
5892
5893void QQuickGraphsItem::changeLabelBorderVisible(QQuick3DRepeater *repeater, const bool &visible)
5894{
5895 int count = repeater->count();
5896 for (int i = 0; i < count; i++) {
5897 auto label = static_cast<QQuick3DNode *>(repeater->objectAt(index: i));
5898 label->setProperty(name: "borderVisible", value: visible);
5899 }
5900}
5901
5902void QQuickGraphsItem::changeLabelTextColor(QQuick3DRepeater *repeater, QColor color)
5903{
5904 int count = repeater->count();
5905 for (int i = 0; i < count; i++) {
5906 auto label = static_cast<QQuick3DNode *>(repeater->objectAt(index: i));
5907 label->setProperty(name: "labelTextColor", value: color);
5908 }
5909}
5910
5911void QQuickGraphsItem::changeLabelFont(QQuick3DRepeater *repeater, const QFont &font)
5912{
5913 int count = repeater->count();
5914 for (int i = 0; i < count; i++) {
5915 auto label = static_cast<QQuick3DNode *>(repeater->objectAt(index: i));
5916 label->setProperty(name: "labelFont", value: font);
5917 }
5918}
5919
5920void QQuickGraphsItem::changeLabelsVisible(QQuick3DRepeater *repeater, const bool &visible)
5921{
5922 int count = repeater->count();
5923 for (int i = 0; i < count; i++) {
5924 auto label = static_cast<QQuick3DNode *>(repeater->objectAt(index: i));
5925 label->setProperty(name: "visible", value: visible);
5926 }
5927}
5928
5929void QQuickGraphsItem::changeGridLineColor(QQuick3DRepeater *repeater, QColor color)
5930{
5931 for (int i = 0; i < repeater->count(); i++) {
5932 auto lineNode = static_cast<QQuick3DNode *>(repeater->objectAt(index: i));
5933 lineNode->setProperty(name: "lineColor", value: color);
5934 }
5935}
5936
5937void QQuickGraphsItem::updateTitleLabels()
5938{
5939 if (m_changeTracker.axisXTitleVisibilityChanged) {
5940 m_titleLabelX->setVisible(axisX()->isTitleVisible());
5941 m_changeTracker.axisXTitleVisibilityChanged = false;
5942 }
5943
5944 if (m_changeTracker.axisYTitleVisibilityChanged) {
5945 m_titleLabelY->setVisible(axisY()->isTitleVisible());
5946 m_changeTracker.axisYTitleVisibilityChanged = false;
5947 }
5948
5949 if (m_changeTracker.axisZTitleVisibilityChanged) {
5950 m_titleLabelZ->setVisible(axisZ()->isTitleVisible());
5951 m_changeTracker.axisZTitleVisibilityChanged = false;
5952 }
5953
5954 if (m_changeTracker.axisXTitleChanged) {
5955 m_titleLabelX->setProperty(name: "labelText", value: axisX()->title());
5956 m_changeTracker.axisXTitleChanged = false;
5957 }
5958
5959 if (m_changeTracker.axisYTitleChanged) {
5960 m_titleLabelY->setProperty(name: "labelText", value: axisY()->title());
5961 m_changeTracker.axisYTitleChanged = false;
5962 }
5963
5964 if (m_changeTracker.axisZTitleChanged) {
5965 m_titleLabelZ->setProperty(name: "labelText", value: axisZ()->title());
5966 m_changeTracker.axisZTitleChanged = false;
5967 }
5968}
5969
5970
5971void QQuickGraphsItem::updateSelectionMode(QtGraphs3D::SelectionFlags newMode)
5972{
5973 Q_UNUSED(newMode);
5974
5975 if (m_sliceView && m_sliceView->isVisible())
5976 toggleSliceGraph();
5977}
5978
5979bool QQuickGraphsItem::doPicking(QPointF point)
5980{
5981 checkSliceEnabled();
5982
5983 Q_TRACE_SCOPE(QGraphs3DItemDoPicking, point.x(), point.y());
5984
5985 QList<QQuick3DPickResult> results = pickAll(x: point.x(), y: point.y());
5986 if (!m_customItemList.isEmpty()) {
5987 // Try to pick custom item only
5988 for (const auto &result : std::as_const(t&: results)) {
5989 QCustom3DItem *customItem = m_customItemList.key(value: result.objectHit(), defaultKey: nullptr);
5990
5991 if (customItem) {
5992 qsizetype selectedIndex = m_customItems.indexOf(t: customItem);
5993 m_selectedCustomItemIndex = selectedIndex;
5994 handleSelectedElementChange(type: QtGraphs3D::ElementType::CustomItem);
5995 // Don't allow picking in subclasses if custom item is picked
5996 return false;
5997 }
5998 }
5999 }
6000
6001 for (const auto &result : std::as_const(t&: results)) {
6002 if (!result.objectHit())
6003 continue;
6004 QString objName = result.objectHit()->objectName();
6005 if (objName.contains(QStringLiteral("ElementAxisXLabel"))) {
6006 for (int i = 0; i < repeaterX()->count(); i++) {
6007 auto obj = static_cast<QQuick3DNode *>(repeaterX()->objectAt(index: i));
6008 if (result.objectHit() == obj)
6009 m_selectedLabelIndex = i;
6010 }
6011 handleSelectedElementChange(type: QtGraphs3D::ElementType::AxisXLabel);
6012 break;
6013 } else if (objName.contains(QStringLiteral("ElementAxisYLabel"))) {
6014 handleSelectedElementChange(type: QtGraphs3D::ElementType::AxisYLabel);
6015 break;
6016 } else if (objName.contains(QStringLiteral("ElementAxisZLabel"))) {
6017 for (int i = 0; i < repeaterX()->count(); i++) {
6018 auto obj = static_cast<QQuick3DNode *>(repeaterZ()->objectAt(index: i));
6019 if (result.objectHit() == obj)
6020 m_selectedLabelIndex = i;
6021 }
6022 handleSelectedElementChange(type: QtGraphs3D::ElementType::AxisZLabel);
6023 break;
6024 } else {
6025 continue;
6026 }
6027 }
6028 return true;
6029}
6030
6031bool QQuickGraphsItem::doRayPicking(QVector3D origin, QVector3D direction)
6032{
6033 checkSliceEnabled();
6034
6035 Q_TRACE_SCOPE(QGraphs3DItemDoRayPicking, origin.x(), origin.y(), origin.z(), direction.x(),
6036 direction.y(), direction.z());
6037 QList<QQuick3DPickResult> results = rayPickAll(origin, direction);
6038 if (!m_customItemList.isEmpty()) {
6039 // Try to pick custom item only
6040 for (const auto &result : std::as_const(t&: results)) {
6041 QCustom3DItem *customItem = m_customItemList.key(value: result.objectHit(), defaultKey: nullptr);
6042
6043 if (customItem) {
6044 qsizetype selectedIndex = m_customItems.indexOf(t: customItem);
6045 m_selectedCustomItemIndex = selectedIndex;
6046 handleSelectedElementChange(type: QtGraphs3D::ElementType::CustomItem);
6047 // Don't allow picking in subclasses if custom item is picked
6048 return false;
6049 }
6050 }
6051 }
6052
6053 for (const auto &result : std::as_const(t&: results)) {
6054 if (!result.objectHit())
6055 continue;
6056 QString objName = result.objectHit()->objectName();
6057 if (objName.contains(QStringLiteral("ElementAxisXLabel"))) {
6058 for (int i = 0; i < repeaterX()->count(); i++) {
6059 auto obj = static_cast<QQuick3DNode *>(repeaterX()->objectAt(index: i));
6060 if (result.objectHit() == obj)
6061 m_selectedLabelIndex = i;
6062 }
6063 handleSelectedElementChange(type: QtGraphs3D::ElementType::AxisXLabel);
6064 break;
6065 } else if (objName.contains(QStringLiteral("ElementAxisYLabel"))) {
6066 handleSelectedElementChange(type: QtGraphs3D::ElementType::AxisYLabel);
6067 break;
6068 } else if (objName.contains(QStringLiteral("ElementAxisZLabel"))) {
6069 for (int i = 0; i < repeaterX()->count(); i++) {
6070 auto obj = static_cast<QQuick3DNode *>(repeaterZ()->objectAt(index: i));
6071 if (result.objectHit() == obj)
6072 m_selectedLabelIndex = i;
6073 }
6074 handleSelectedElementChange(type: QtGraphs3D::ElementType::AxisZLabel);
6075 break;
6076 } else {
6077 continue;
6078 }
6079 }
6080 return true;
6081}
6082
6083void QQuickGraphsItem::minimizeMainGraph()
6084{
6085 QQuickItem *anchor = QQuickItemPrivate::get(item: this)->anchors()->fill();
6086 if (anchor)
6087 QQuickItemPrivate::get(item: this)->anchors()->resetFill();
6088
6089 m_inputHandler->setX(x());
6090 m_inputHandler->setY(y());
6091}
6092
6093void QQuickGraphsItem::toggleSliceGraph()
6094{
6095 if (!m_sliceView || !m_sliceActivatedChanged)
6096 return;
6097
6098 if (m_sliceView->isVisible()) {
6099 // Maximize main view
6100 m_sliceView->setVisible(false);
6101 setSlicingActive(false);
6102 updateSubViews();
6103 qCDebug(lcEvents3D, "%s exit sliceView", qUtf8Printable(QLatin1String(__FUNCTION__)));
6104 } else {
6105 // Minimize main view
6106 setSlicingActive(true);
6107 m_sliceView->setVisible(true);
6108 minimizeMainGraph();
6109 updateSubViews();
6110 updateSliceGrid();
6111 updateSliceLabels();
6112 qCDebug(lcEvents3D, "%s enter sliceView", qUtf8Printable(QLatin1String(__FUNCTION__)));
6113 }
6114
6115 m_sliceActivatedChanged = false;
6116}
6117
6118void QQuickGraphsItem::updateSubViews()
6119{
6120 QRect newMainView = isSlicingActive() ? scene()->primarySubViewport() : scene()->viewport();
6121 QRect newSliceView = scene()->secondarySubViewport();
6122
6123 if (newMainView.isValid() && newMainView.toRectF() != boundingRect()) {
6124 // Set main view dimensions and position
6125 setX(newMainView.x());
6126 setY(newMainView.y());
6127 setSize(newMainView.size());
6128 update();
6129 }
6130
6131 if (sliceView()) {
6132 if (newSliceView.isValid() && m_sliceView->boundingRect() != newSliceView.toRectF()) {
6133 // Set slice view dimensions and position
6134 m_sliceView->setX(newSliceView.x());
6135 m_sliceView->setY(newSliceView.y());
6136 m_sliceView->setSize(newSliceView.size());
6137 m_sliceView->update();
6138 }
6139
6140 if (isSliceOrthoProjection()) {
6141 const float scale = qMin(a: m_sliceView->width(), b: m_sliceView->height());
6142 QQuick3DOrthographicCamera *camera = static_cast<QQuick3DOrthographicCamera *>(
6143 m_sliceView->camera());
6144 const float magnificationScaleFactor = .16f; // this controls the size of the slice view
6145 const float magnification = scale * magnificationScaleFactor;
6146 camera->setHorizontalMagnification(magnification);
6147 camera->setVerticalMagnification(magnification);
6148 }
6149 }
6150}
6151
6152void QQuickGraphsItem::windowDestroyed(QObject *obj)
6153{
6154 // Remove destroyed window from window lists
6155 QQuickWindow *win = static_cast<QQuickWindow *>(obj);
6156 QQuickWindow *oldWindow = m_graphWindowList.value(key: this);
6157
6158 if (win == oldWindow)
6159 m_graphWindowList.remove(key: this);
6160}
6161
6162QQmlComponent *QQuickGraphsItem::createRepeaterDelegateComponent(const QString &fileName)
6163{
6164 QQmlComponent component(qmlEngine(this), fileName);
6165 return qobject_cast<QQmlComponent *>(object: component.create());
6166}
6167
6168QQuick3DRepeater *QQuickGraphsItem::createRepeater(QQuick3DNode *parent)
6169{
6170 auto engine = qmlEngine(this);
6171 QQmlComponent repeaterComponent(engine);
6172 repeaterComponent.setData("import QtQuick3D; Repeater3D{}", baseUrl: QUrl());
6173 auto repeater = qobject_cast<QQuick3DRepeater *>(object: repeaterComponent.create());
6174 repeater->setParent(parent ? parent : graphNode());
6175 repeater->setParentItem(parent ? parent : graphNode());
6176 return repeater;
6177}
6178
6179QQuick3DNode *QQuickGraphsItem::createTitleLabel(QQuick3DNode *parent)
6180{
6181 auto engine = qmlEngine(this);
6182 QQmlComponent comp(engine, QStringLiteral(":/axis/TitleLabel"));
6183 auto titleLabel = qobject_cast<QQuick3DNode *>(object: comp.create());
6184 titleLabel->setParent(parent ? parent : graphNode());
6185 titleLabel->setParentItem(parent ? parent : graphNode());
6186 titleLabel->setVisible(false);
6187 titleLabel->setScale(m_labelScale);
6188 return titleLabel;
6189}
6190
6191void QQuickGraphsItem::createItemLabel()
6192{
6193 auto engine = qmlEngine(this);
6194 QQmlComponent comp(engine, QStringLiteral(":/axis/ItemLabel"));
6195 m_itemLabel = qobject_cast<QQuickItem *>(o: comp.create());
6196 m_itemLabel->setParent(this);
6197 m_itemLabel->setParentItem(this);
6198 m_itemLabel->setVisible(false);
6199}
6200
6201QQuick3DCustomMaterial *QQuickGraphsItem::createQmlCustomMaterial(const QString &fileName)
6202{
6203 QQmlComponent component(qmlEngine(this), fileName);
6204 QQuick3DCustomMaterial *material = qobject_cast<QQuick3DCustomMaterial *>(object: component.create());
6205 return material;
6206}
6207
6208QQuick3DPrincipledMaterial *QQuickGraphsItem::createPrincipledMaterial()
6209{
6210 QQmlComponent component(qmlEngine(this));
6211 component.setData("import QtQuick3D; PrincipledMaterial{}", baseUrl: QUrl());
6212 return qobject_cast<QQuick3DPrincipledMaterial *>(object: component.create());
6213}
6214
6215QtGraphs3D::CameraPreset QQuickGraphsItem::cameraPreset() const
6216{
6217 return m_activePreset;
6218}
6219
6220void QQuickGraphsItem::setCameraPreset(QtGraphs3D::CameraPreset preset)
6221{
6222 switch (preset) {
6223 case QtGraphs3D::CameraPreset::FrontLow: {
6224 m_xRotation = 0.0f;
6225 m_yRotation = 0.0f;
6226 break;
6227 }
6228 case QtGraphs3D::CameraPreset::Front: {
6229 m_xRotation = 0.0f;
6230 m_yRotation = 22.5f;
6231 break;
6232 }
6233 case QtGraphs3D::CameraPreset::FrontHigh: {
6234 m_xRotation = 0.0f;
6235 m_yRotation = 45.0f;
6236 break;
6237 }
6238 case QtGraphs3D::CameraPreset::LeftLow: {
6239 m_xRotation = 90.0f;
6240 m_yRotation = 0.0f;
6241 break;
6242 }
6243 case QtGraphs3D::CameraPreset::Left: {
6244 m_xRotation = 90.0f;
6245 m_yRotation = 22.5f;
6246 break;
6247 }
6248 case QtGraphs3D::CameraPreset::LeftHigh: {
6249 m_xRotation = 90.0f;
6250 m_yRotation = 45.0f;
6251 break;
6252 }
6253 case QtGraphs3D::CameraPreset::RightLow: {
6254 m_xRotation = -90.0f;
6255 m_yRotation = 0.0f;
6256 break;
6257 }
6258 case QtGraphs3D::CameraPreset::Right: {
6259 m_xRotation = -90.0f;
6260 m_yRotation = 22.5f;
6261 break;
6262 }
6263 case QtGraphs3D::CameraPreset::RightHigh: {
6264 m_xRotation = -90.0f;
6265 m_yRotation = 45.0f;
6266 break;
6267 }
6268 case QtGraphs3D::CameraPreset::BehindLow: {
6269 m_xRotation = 180.0f;
6270 m_yRotation = 0.0f;
6271 break;
6272 }
6273 case QtGraphs3D::CameraPreset::Behind: {
6274 m_xRotation = 180.0f;
6275 m_yRotation = 22.5f;
6276 break;
6277 }
6278 case QtGraphs3D::CameraPreset::BehindHigh: {
6279 m_xRotation = 180.0f;
6280 m_yRotation = 45.0f;
6281 break;
6282 }
6283 case QtGraphs3D::CameraPreset::IsometricLeft: {
6284 m_xRotation = 45.0f;
6285 m_yRotation = 22.5f;
6286 break;
6287 }
6288 case QtGraphs3D::CameraPreset::IsometricLeftHigh: {
6289 m_xRotation = 45.0f;
6290 m_yRotation = 45.0f;
6291 break;
6292 }
6293 case QtGraphs3D::CameraPreset::IsometricRight: {
6294 m_xRotation = -45.0f;
6295 m_yRotation = 22.5f;
6296 break;
6297 }
6298 case QtGraphs3D::CameraPreset::IsometricRightHigh: {
6299 m_xRotation = -45.0f;
6300 m_yRotation = 45.0f;
6301 break;
6302 }
6303 case QtGraphs3D::CameraPreset::DirectlyAbove: {
6304 m_xRotation = 0.0f;
6305 m_yRotation = 90.0f;
6306 break;
6307 }
6308 case QtGraphs3D::CameraPreset::DirectlyAboveCW45: {
6309 m_xRotation = -45.0f;
6310 m_yRotation = 90.0f;
6311 break;
6312 }
6313 case QtGraphs3D::CameraPreset::DirectlyAboveCCW45: {
6314 m_xRotation = 45.0f;
6315 m_yRotation = 90.0f;
6316 break;
6317 }
6318 case QtGraphs3D::CameraPreset::FrontBelow: {
6319 m_xRotation = 0.0f;
6320 m_yRotation = -45.0f;
6321 break;
6322 }
6323 case QtGraphs3D::CameraPreset::LeftBelow: {
6324 m_xRotation = 90.0f;
6325 m_yRotation = -45.0f;
6326 break;
6327 }
6328 case QtGraphs3D::CameraPreset::RightBelow: {
6329 m_xRotation = -90.0f;
6330 m_yRotation = -45.0f;
6331 break;
6332 }
6333 case QtGraphs3D::CameraPreset::BehindBelow: {
6334 m_xRotation = 180.0f;
6335 m_yRotation = -45.0f;
6336 break;
6337 }
6338 case QtGraphs3D::CameraPreset::DirectlyBelow: {
6339 m_xRotation = 0.0f;
6340 m_yRotation = -90.0f;
6341 break;
6342 }
6343 default:
6344 preset = QtGraphs3D::CameraPreset::NoPreset;
6345 break;
6346 }
6347
6348 // All presets target the center of the graph
6349 setCameraTargetPosition(QVector3D());
6350
6351 if (m_activePreset != preset) {
6352 m_activePreset = preset;
6353 emit cameraPresetChanged(preset);
6354 }
6355 if (camera()) {
6356 updateCamera();
6357 connect(sender: this, signal: &QQuickGraphsItem::cameraXRotationChanged, context: m_scene, slot: &Q3DScene::needRender);
6358 connect(sender: this, signal: &QQuickGraphsItem::cameraYRotationChanged, context: m_scene, slot: &Q3DScene::needRender);
6359 connect(sender: this, signal: &QQuickGraphsItem::cameraZoomLevelChanged, context: m_scene, slot: &Q3DScene::needRender);
6360 }
6361 m_changeTracker.cameraChanged = true;
6362}
6363
6364void QQuickGraphsItem::setCameraXRotation(float rotation)
6365{
6366 if (m_wrapXRotation)
6367 rotation = Utils::wrapValue(value: rotation, min: m_minXRotation, max: m_maxXRotation);
6368 else
6369 rotation = qBound(min: m_minXRotation, val: rotation, max: m_maxXRotation);
6370 if (rotation != m_xRotation) {
6371 m_xRotation = rotation;
6372 qCDebug(lcEvents3D, "%s x rotation: %.1f ", qUtf8Printable(QLatin1String(__FUNCTION__)), rotation);
6373 m_changeTracker.cameraChanged = true;
6374 emit cameraXRotationChanged(rotation: m_xRotation);
6375 }
6376}
6377
6378void QQuickGraphsItem::setCameraYRotation(float rotation)
6379{
6380 if (m_wrapYRotation)
6381 rotation = Utils::wrapValue(value: rotation, min: m_minYRotation, max: m_maxYRotation);
6382 else
6383 rotation = qBound(min: m_minYRotation, val: rotation, max: m_maxYRotation);
6384 if (rotation != m_yRotation) {
6385 m_yRotation = rotation;
6386 qCDebug(lcEvents3D, "%s y rotation: %.1f ", qUtf8Printable(QLatin1String(__FUNCTION__)), rotation);
6387 m_changeTracker.cameraChanged = true;
6388 emit cameraYRotationChanged(rotation: m_yRotation);
6389 }
6390}
6391
6392void QQuickGraphsItem::setMinCameraXRotation(float rotation)
6393{
6394 if (m_minXRotation == rotation) {
6395 qCDebug(lcProperties3D, "%s value is already set to: %.1f",
6396 qUtf8Printable(QLatin1String(__FUNCTION__)), rotation);
6397 return;
6398 }
6399
6400 m_minXRotation = rotation;
6401 setUserCameraRotationRange(true);
6402 emit minCameraXRotationChanged(rotation);
6403}
6404
6405void QQuickGraphsItem::setMaxCameraXRotation(float rotation)
6406{
6407 if (m_maxXRotation == rotation) {
6408 qCDebug(lcProperties3D,"%s value is already set to: %.1f",
6409 qUtf8Printable(QLatin1String(__FUNCTION__)), rotation);
6410 return;
6411 }
6412
6413 m_maxXRotation = rotation;
6414 setUserCameraRotationRange(true);
6415 emit maxCameraXRotationChanged(rotation);
6416}
6417
6418void QQuickGraphsItem::setMinCameraYRotation(float rotation)
6419{
6420 if (m_minYRotation == rotation) {
6421 qCDebug(lcProperties3D, "%s value is already set to: %.1f",
6422 qUtf8Printable(QLatin1String(__FUNCTION__)), rotation);
6423 return;
6424 }
6425
6426 m_minYRotation = rotation;
6427 setUserCameraRotationRange(true);
6428 emit minCameraYRotationChanged(rotation);
6429}
6430
6431void QQuickGraphsItem::setMaxCameraYRotation(float rotation)
6432{
6433 if (m_maxYRotation == rotation) {
6434 qCDebug(lcProperties3D, "%s value is already set to: %.1f",
6435 qUtf8Printable(QLatin1String(__FUNCTION__)), rotation);
6436 return;
6437 }
6438
6439 m_maxYRotation = rotation;
6440 setUserCameraRotationRange(true);
6441 emit maxCameraYRotationChanged(rotation);
6442}
6443
6444void QQuickGraphsItem::setZoomAtTargetEnabled(bool enable)
6445{
6446 m_inputHandler->setZoomAtTargetEnabled(enable);
6447}
6448
6449bool QQuickGraphsItem::zoomAtTargetEnabled()
6450{
6451 return m_inputHandler->isZoomAtTargetEnabled();
6452}
6453
6454void QQuickGraphsItem::setZoomEnabled(bool enable)
6455{
6456 m_inputHandler->setZoomEnabled(enable);
6457}
6458
6459bool QQuickGraphsItem::zoomEnabled()
6460{
6461 return m_inputHandler->isZoomEnabled();
6462}
6463
6464void QQuickGraphsItem::setSelectionEnabled(bool enable)
6465{
6466 m_inputHandler->setSelectionEnabled(enable);
6467}
6468
6469bool QQuickGraphsItem::selectionEnabled()
6470{
6471 return m_inputHandler->isSelectionEnabled();
6472}
6473
6474void QQuickGraphsItem::setRotationEnabled(bool enable)
6475{
6476 m_inputHandler->setRotationEnabled(enable);
6477}
6478
6479bool QQuickGraphsItem::rotationEnabled()
6480{
6481 return m_inputHandler->isRotationEnabled();
6482}
6483
6484void QQuickGraphsItem::unsetDefaultInputHandler()
6485{
6486 m_inputHandler->unsetDefaultInputHandler();
6487}
6488
6489void QQuickGraphsItem::unsetDefaultTapHandler()
6490{
6491 m_inputHandler->unsetDefaultTapHandler();
6492}
6493
6494void QQuickGraphsItem::unsetDefaultDragHandler()
6495{
6496 m_inputHandler->unsetDefaultDragHandler();
6497}
6498
6499void QQuickGraphsItem::unsetDefaultWheelHandler()
6500{
6501 m_inputHandler->unsetDefaultWheelHandler();
6502}
6503
6504void QQuickGraphsItem::unsetDefaultPinchHandler()
6505{
6506 m_inputHandler->unsetDefaultPinchHandler();
6507}
6508
6509void QQuickGraphsItem::setDragButton(Qt::MouseButtons button)
6510{
6511 m_inputHandler->setDragButton(button);
6512}
6513
6514void QQuickGraphsItem::setDefaultInputHandler()
6515{
6516 m_inputHandler->setDefaultInputHandler();
6517}
6518
6519void QQuickGraphsItem::setCameraZoomLevel(float level)
6520{
6521 if (m_zoomLevel == level)
6522 return;
6523
6524 m_zoomLevel = level;
6525 qCDebug(lcEvents3D, "%s zoom level: %.1f", qUtf8Printable(QLatin1String(__FUNCTION__)), level);
6526 m_changeTracker.cameraChanged = true;
6527 emit cameraZoomLevelChanged(zoomLevel: level);
6528}
6529
6530void QQuickGraphsItem::setMinCameraZoomLevel(float level)
6531{
6532 if (m_minZoomLevel == level || level < 1.f) {
6533 qCDebug(lcProperties3D, "%s value: %.1f is either same or it is lower than 1.0f",
6534 qUtf8Printable(QLatin1String(__FUNCTION__)), level);
6535 return;
6536 }
6537
6538 m_minZoomLevel = level;
6539 emit minCameraZoomLevelChanged(zoomLevel: level);
6540
6541 setMaxCameraZoomLevel(std::max(a: m_minZoomLevel, b: m_maxZoomLevel));
6542
6543 if (cameraZoomLevel() < level)
6544 setCameraZoomLevel(level);
6545}
6546
6547void QQuickGraphsItem::setMaxCameraZoomLevel(float level)
6548{
6549 if (m_maxZoomLevel == level) {
6550 qCDebug(lcProperties3D, "%s value is already set to: %.1f",
6551 qUtf8Printable(QLatin1String(__FUNCTION__)), level);
6552 return;
6553 }
6554
6555 m_maxZoomLevel = level;
6556 emit maxCameraZoomLevelChanged(zoomLevel: level);
6557
6558 setMinCameraZoomLevel(std::min(a: m_minZoomLevel, b: m_maxZoomLevel));
6559
6560 if (cameraZoomLevel() > level)
6561 setCameraZoomLevel(level);
6562}
6563
6564void QQuickGraphsItem::setCameraTargetPosition(QVector3D target)
6565{
6566 if (m_requestedTarget == target) {
6567 qCDebug(lcProperties3D) << qUtf8Printable(QLatin1String(__FUNCTION__))
6568 << "position is already set to:" << target;
6569 return;
6570 }
6571
6572 m_requestedTarget.setX(std::clamp(val: target.x(), lo: -1.0f, hi: 1.0f));
6573 m_requestedTarget.setY(std::clamp(val: target.y(), lo: -1.0f, hi: 1.0f));
6574 m_requestedTarget.setZ(std::clamp(val: target.z(), lo: -1.0f, hi: 1.0f));
6575 m_changeTracker.cameraChanged = true;
6576 emit cameraTargetPositionChanged(target);
6577}
6578
6579void QQuickGraphsItem::setCameraPosition(float horizontal, float vertical, float zoom)
6580{
6581 setCameraZoomLevel(zoom);
6582 setCameraXRotation(horizontal);
6583 setCameraYRotation(vertical);
6584}
6585
6586bool QQuickGraphsItem::event(QEvent *event)
6587{
6588 return QQuickItem::event(event);
6589}
6590
6591void QQuickGraphsItem::createSliceView()
6592{
6593 if (m_sliceView)
6594 return;
6595 Q_TRACE_SCOPE(QGraphs3DItemCreateSliceView);
6596
6597 connect(sender: parentItem(),
6598 signal: &QQuickItem::widthChanged,
6599 context: this,
6600 slot: &QQuickGraphsItem::handleParentWidthChange);
6601 connect(sender: parentItem(),
6602 signal: &QQuickItem::heightChanged,
6603 context: this,
6604 slot: &QQuickGraphsItem::handleParentHeightChange);
6605 connect(sender: this, signal: &QQuickItem::heightChanged,
6606 context: this,
6607 slot: &QQuickGraphsItem::handleParentHeightChange);
6608 connect(sender: this, signal: &QQuickItem::widthChanged,
6609 context: this,
6610 slot: &QQuickGraphsItem::handleParentWidthChange);
6611
6612 m_sliceView = new QQuick3DViewport();
6613 m_sliceView->setParent(parent());
6614 m_sliceView->setParentItem(parentItem());
6615 m_sliceView->setVisible(false);
6616 if (!m_parentNode) {
6617 m_sliceView->setHeight(parentItem()->height());
6618 m_sliceView->setWidth(parentItem()->width());
6619 }
6620 m_sliceView->setZ(-1);
6621 m_sliceView->environment()->setBackgroundMode(QQuick3DSceneEnvironment::QQuick3DEnvironmentBackgroundTypes::Color);
6622 m_sliceView->environment()->setClearColor(environment()->clearColor());
6623 m_sliceView->setRenderMode(renderMode());
6624
6625 auto scene = m_sliceView->scene();
6626
6627 createSliceCamera(sliceView: m_sliceView);
6628
6629 // auto gridDelegate = createRepeaterDelegateComponent(QStringLiteral(":/axis/GridLine"));
6630 m_labelDelegate.reset(p: new QQmlComponent(qmlEngine(this), QStringLiteral(":/axis/AxisLabel")));
6631
6632 m_sliceGridGeometryModel = new QQuick3DModel(scene);
6633
6634 auto sliceGridGeometry = new QQuick3DGeometry(m_sliceGridGeometryModel);
6635 sliceGridGeometry->setStride(sizeof(QVector3D));
6636 sliceGridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
6637 sliceGridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
6638 offset: 0,
6639 componentType: QQuick3DGeometry::Attribute::F32Type);
6640 m_sliceGridGeometryModel->setGeometry(sliceGridGeometry);
6641
6642 QQmlListReference gridMaterialRef(m_sliceGridGeometryModel, "materials");
6643 auto gridMaterial = new QQuick3DPrincipledMaterial(m_sliceGridGeometryModel);
6644 gridMaterial->setLighting(QQuick3DPrincipledMaterial::Lighting::NoLighting);
6645 gridMaterial->setCullMode(QQuick3DMaterial::CullMode::BackFaceCulling);
6646 gridMaterial->setBaseColor(Qt::red);
6647 gridMaterialRef.append(gridMaterial);
6648
6649 m_sliceHorizontalLabelRepeater = createRepeater(parent: scene);
6650 m_sliceHorizontalLabelRepeater->setDelegate(m_labelDelegate.get());
6651
6652 m_sliceVerticalLabelRepeater = createRepeater(parent: scene);
6653 m_sliceVerticalLabelRepeater->setDelegate(m_labelDelegate.get());
6654
6655 m_sliceHorizontalTitleLabel = createTitleLabel(parent: scene);
6656 m_sliceHorizontalTitleLabel->setVisible(true);
6657
6658 m_sliceVerticalTitleLabel = createTitleLabel(parent: scene);
6659 m_sliceVerticalTitleLabel->setVisible(true);
6660
6661 m_sliceItemLabel = createTitleLabel(parent: scene);
6662 m_sliceItemLabel->setVisible(false);
6663}
6664
6665QQuick3DViewport *QQuickGraphsItem::createOffscreenSliceView(QtGraphs3D::SliceCaptureType sliceType)
6666{
6667 Q_TRACE_SCOPE(QGraphs3DItemCreateOffscreenSliceView, static_cast<int>(sliceType));
6668
6669 auto sliceView = new QQuick3DViewport();
6670 sliceView->setParent(this);
6671 sliceView->setParentItem(this);
6672 sliceView->setWidth(parentItem()->width() * .5);
6673 sliceView->setHeight(parentItem()->height() * .5);
6674 sliceView->setX(sliceView->width() * -1);
6675 sliceView->environment()->setBackgroundMode(
6676 QQuick3DSceneEnvironment::QQuick3DEnvironmentBackgroundTypes::Color);
6677 sliceView->environment()->setClearColor(environment()->clearColor());
6678 sliceView->setRenderMode(renderMode());
6679
6680 auto scene = sliceView->scene();
6681
6682 createSliceCamera(sliceView);
6683
6684 std::unique_ptr<QQmlComponent> labelDelegate;
6685 labelDelegate.reset(p: new QQmlComponent(qmlEngine(this), QStringLiteral(":/axis/AxisLabel")));
6686
6687 auto sliceGridGeometryModel = new QQuick3DModel(scene);
6688
6689 auto sliceGridGeometry = new QQuick3DGeometry(sliceGridGeometryModel);
6690 sliceGridGeometry->setStride(sizeof(QVector3D));
6691 sliceGridGeometry->setPrimitiveType(QQuick3DGeometry::PrimitiveType::Lines);
6692 sliceGridGeometry->addAttribute(semantic: QQuick3DGeometry::Attribute::PositionSemantic,
6693 offset: 0,
6694 componentType: QQuick3DGeometry::Attribute::F32Type);
6695 sliceGridGeometryModel->setGeometry(sliceGridGeometry);
6696
6697 QQmlListReference gridMaterialRef(sliceGridGeometryModel, "materials");
6698 auto gridMaterial = new QQuick3DPrincipledMaterial(sliceGridGeometryModel);
6699 gridMaterial->setLighting(QQuick3DPrincipledMaterial::Lighting::NoLighting);
6700 gridMaterial->setCullMode(QQuick3DMaterial::CullMode::BackFaceCulling);
6701 gridMaterial->setBaseColor(Qt::red);
6702 gridMaterialRef.append(gridMaterial);
6703
6704 updateSliceGrid(sliceGrid: sliceGridGeometryModel, selectedFlag: sliceType);
6705
6706 auto sliceHorizontalLabelRepeater = createRepeater(parent: scene);
6707 sliceHorizontalLabelRepeater->setDelegate(labelDelegate.get());
6708
6709 auto sliceVerticalLabelRepeater = createRepeater(parent: scene);
6710 sliceVerticalLabelRepeater->setDelegate(labelDelegate.get());
6711
6712 auto sliceHorizontalTitleLabel = createTitleLabel(parent: scene);
6713 sliceHorizontalTitleLabel->setVisible(true);
6714
6715 auto sliceVerticalTitleLabel = createTitleLabel(parent: scene);
6716 sliceVerticalTitleLabel->setVisible(true);
6717
6718 auto sliceItemLabel = createTitleLabel(parent: scene);
6719 sliceItemLabel->setVisible(false);
6720
6721 updateSliceLabels(horizontalLabel: sliceHorizontalLabelRepeater, verticalLabel: sliceVerticalLabelRepeater,
6722 horizontalTitle: sliceHorizontalTitleLabel, verticalTitle: sliceVerticalTitleLabel, itemLabel: sliceItemLabel,
6723 selectedFlag: sliceType);
6724
6725 return sliceView;
6726}
6727
6728void QQuickGraphsItem::createSliceCamera(QQuick3DViewport *sliceView)
6729{
6730 if (isSliceOrthoProjection()) {
6731 auto camera = new QQuick3DOrthographicCamera(sliceView->scene());
6732 camera->setPosition(QVector3D(.0f, .0f, 20.0f));
6733 const float scale = qMin(a: sliceView->width(), b: sliceView->height());
6734 const float magnificationScaleFactor = 2 * window()->devicePixelRatio()
6735 * .08f; // this controls the size of the slice view
6736 const float magnification = scale * magnificationScaleFactor;
6737 camera->setHorizontalMagnification(magnification);
6738 camera->setVerticalMagnification(magnification);
6739 sliceView->setCamera(camera);
6740
6741 auto light = new QQuick3DDirectionalLight(sliceView->scene());
6742 light->setParent(camera);
6743 light->setParentItem(camera);
6744 } else {
6745 auto camera = new QQuick3DPerspectiveCamera(sliceView->scene());
6746 camera->setFieldOfViewOrientation(
6747 QQuick3DPerspectiveCamera::FieldOfViewOrientation::Vertical);
6748 camera->setClipNear(5.f);
6749 camera->setClipFar(15.f);
6750 camera->setFieldOfView(35.f);
6751 camera->setPosition(QVector3D(.0f, .0f, 10.f));
6752 sliceView->setCamera(camera);
6753
6754 auto light = new QQuick3DDirectionalLight(sliceView->scene());
6755 light->setParent(camera);
6756 light->setParentItem(camera);
6757 light->setAmbientColor(QColor::fromRgbF(r: 1.f, g: 1.f, b: 1.f));
6758 }
6759}
6760
6761void QQuickGraphsItem::updateSliceGrid(QQuick3DModel *sliceGridGeometryModel,
6762 QtGraphs3D::SliceCaptureType sliceType)
6763{
6764 Q_TRACE_SCOPE(QGraphs3DItemUpdateSliceGrid);
6765 QAbstract3DAxis *horizontalAxis = nullptr;
6766 QAbstract3DAxis *verticalAxis = axisY();
6767 auto backgroundScale = m_scaleWithBackground + m_backgroundScaleMargin;
6768 float scale;
6769 float translate;
6770
6771 float horizontalScale = 0.0f;
6772
6773 bool isRow = (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row)
6774 || sliceType == QtGraphs3D::SliceCaptureType::RowImage);
6775 bool isColumn = (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column)
6776 || sliceType == QtGraphs3D::SliceCaptureType::ColumnImage);
6777
6778 if (isRow) {
6779 horizontalAxis = axisX();
6780 horizontalScale = backgroundScale.x();
6781 scale = m_scaleWithBackground.x();
6782 translate = m_scaleWithBackground.x();
6783 } else if (isColumn) {
6784 horizontalAxis = axisZ();
6785 horizontalScale = backgroundScale.z();
6786 scale = m_scaleWithBackground.z();
6787 translate = m_scaleWithBackground.z();
6788 }
6789
6790 if (horizontalAxis == nullptr) {
6791 qCWarning(lcGraphs3D, "%s invalid axis type",
6792 qUtf8Printable(QLatin1String(__FUNCTION__)));
6793 return;
6794 }
6795 int lineCount = 0;
6796 if (m_hasVerticalSegmentLine || isPolar()) {
6797 if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Value) {
6798 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(horizontalAxis);
6799 lineCount += valueAxis->gridSize() + valueAxis->subGridSize();
6800 } else if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Category) {
6801 lineCount += horizontalAxis->labels().size();
6802 }
6803 }
6804
6805 if (verticalAxis->type() == QAbstract3DAxis::AxisType::Value) {
6806 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(verticalAxis);
6807 lineCount += valueAxis->gridSize() + valueAxis->subGridSize();
6808 } else if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Category) {
6809 lineCount += verticalAxis->labels().size();
6810 }
6811
6812 QByteArray vertices;
6813 vertices.resize(size: lineCount * 2 * sizeof(QVector3D));
6814 auto data = reinterpret_cast<QVector3D *>(vertices.data());
6815 float linePosX = .0f;
6816 float linePosY = .0f;
6817 const float linePosZ = -1.f; // Draw grid lines behind slice (especially for surface)
6818
6819 float x0, x1;
6820 float y0, y1;
6821 y0 = -backgroundScale.y();
6822 y1 = backgroundScale.y();
6823 if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Value) {
6824 auto axis = static_cast<QValue3DAxis *>(horizontalAxis);
6825 for (int i = 0; i < axis->subGridSize(); i++) {
6826 linePosX = axis->subGridPositionAt(gridLine: i) * scale * 2.0f - translate;
6827 *data++ = QVector3D(linePosX, y0, linePosZ);
6828 *data++ = QVector3D(linePosX, y1, linePosZ);
6829 }
6830 for (int i = 0; i < axis->gridSize(); i++) {
6831 linePosX = axis->gridPositionAt(gridLine: i) * scale * 2.0f - translate;
6832 *data++ = QVector3D(linePosX, y0, linePosZ);
6833 *data++ = QVector3D(linePosX, y1, linePosZ);
6834 }
6835 }
6836
6837 scale = m_scaleWithBackground.y();
6838 translate = m_scaleWithBackground.y();
6839
6840 x0 = horizontalScale * 1.1f;
6841 x1 = -horizontalScale * 1.1f;
6842 if (verticalAxis->type() == QAbstract3DAxis::AxisType::Value) {
6843 auto axis = static_cast<QValue3DAxis *>(verticalAxis);
6844 for (int i = 0; i < axis->gridSize(); i++) {
6845 linePosY = axis->gridPositionAt(gridLine: i) * scale * 2.0f - translate;
6846 *data++ = QVector3D(x0, linePosY, linePosZ);
6847 *data++ = QVector3D(x1, linePosY, linePosZ);
6848 }
6849 for (int i = 0; i < axis->subGridSize(); i++) {
6850 linePosY = axis->subGridPositionAt(gridLine: i) * scale * 2.0f - translate;
6851 *data++ = QVector3D(x0, linePosY, linePosZ);
6852 *data++ = QVector3D(x1, linePosY, linePosZ);
6853 }
6854 } else if (verticalAxis->type() == QAbstract3DAxis::AxisType::Category) {
6855 for (int i = 0; i < verticalAxis->labels().size(); i++) {
6856 linePosY = calculateCategoryGridLinePosition(axis: verticalAxis, index: i);
6857 *data++ = QVector3D(x0, linePosY, linePosZ);
6858 *data++ = QVector3D(x1, linePosY, linePosZ);
6859 }
6860 }
6861
6862 QQuick3DModel *sliceModel = nullptr;
6863 if (sliceGridGeometryModel)
6864 sliceModel = sliceGridGeometryModel;
6865 else
6866 sliceModel = m_sliceGridGeometryModel;
6867 QQuick3DGeometry *geometry = sliceModel->geometry();
6868 geometry->setVertexData(vertices);
6869 geometry->update();
6870
6871 QQmlListReference materialRef(sliceModel, "materials");
6872 auto material = static_cast<QQuick3DPrincipledMaterial *>(materialRef.at(0));
6873 material->setBaseColor(theme()->grid().mainColor());
6874}
6875
6876void QQuickGraphsItem::updateSliceLabels(QQuick3DRepeater *horizontalLabel,
6877 QQuick3DRepeater *verticalLabel,
6878 QQuick3DNode *horizontalTitle,
6879 QQuick3DNode *verticalTitle,
6880 QQuick3DNode *itemLabel,
6881 QtGraphs3D::SliceCaptureType sliceType)
6882{
6883 Q_TRACE_SCOPE(QGraphs3DItemUpdateSliceLabels);
6884 QAbstract3DAxis *horizontalAxis = nullptr;
6885 QAbstract3DAxis *verticalAxis = axisY();
6886 auto backgroundScale = m_scaleWithBackground + m_backgroundScaleMargin;
6887 float scale;
6888 float translate;
6889 QColor horizontalLabelTextColor;
6890
6891 QQuick3DRepeater *sliceHorizontalLabelRepeater = nullptr;
6892 if (horizontalLabel)
6893 sliceHorizontalLabelRepeater = horizontalLabel;
6894 else
6895 sliceHorizontalLabelRepeater = m_sliceHorizontalLabelRepeater;
6896
6897 QQuick3DRepeater *sliceVerticalLabelRepeater = nullptr;
6898 if (verticalLabel)
6899 sliceVerticalLabelRepeater = verticalLabel;
6900 else
6901 sliceVerticalLabelRepeater = m_sliceVerticalLabelRepeater;
6902
6903 bool isRow = (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Row)
6904 || sliceType == QtGraphs3D::SliceCaptureType::RowImage);
6905 bool isColumn = (selectionMode().testFlag(flag: QtGraphs3D::SelectionFlag::Column)
6906 || sliceType == QtGraphs3D::SliceCaptureType::ColumnImage);
6907
6908 if (isRow) {
6909 horizontalAxis = axisX();
6910 scale = backgroundScale.x() - m_backgroundScaleMargin.x();
6911 translate = backgroundScale.x() - m_backgroundScaleMargin.x();
6912 horizontalLabelTextColor = theme()->axisX().labelTextColor();
6913 } else if (isColumn) {
6914 horizontalAxis = axisZ();
6915 scale = backgroundScale.z() - m_backgroundScaleMargin.z();
6916 translate = backgroundScale.z() - m_backgroundScaleMargin.z();
6917 horizontalLabelTextColor = theme()->axisZ().labelTextColor();
6918 }
6919
6920 if (horizontalAxis == nullptr) {
6921 qCWarning(lcProperties3D, "%s invalid selection mode",
6922 qUtf8Printable(QLatin1String(__FUNCTION__)));
6923 return;
6924 }
6925
6926 if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Value) {
6927 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(horizontalAxis);
6928 sliceHorizontalLabelRepeater->model().clear();
6929 sliceHorizontalLabelRepeater->setModel(valueAxis->labels().size());
6930 } else if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Category) {
6931 sliceHorizontalLabelRepeater->model().clear();
6932 sliceHorizontalLabelRepeater->setModel(horizontalAxis->labels().size());
6933 }
6934
6935 if (verticalAxis->type() == QAbstract3DAxis::AxisType::Value) {
6936 QValue3DAxis *valueAxis = static_cast<QValue3DAxis *>(verticalAxis);
6937 sliceVerticalLabelRepeater->model().clear();
6938 sliceVerticalLabelRepeater->setModel(valueAxis->labels().size());
6939 } else if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Category) {
6940 sliceVerticalLabelRepeater->model().clear();
6941 sliceVerticalLabelRepeater->setModel(verticalAxis->labels().size());
6942 }
6943
6944 float textPadding = 12.0f;
6945 float labelsMaxWidth = float(findLabelsMaxWidth(labels: horizontalAxis->labels())) + textPadding;
6946 QFontMetrics fm(theme()->labelFont());
6947 float labelHeight = fm.height() + textPadding;
6948
6949 float pointSize = theme()->labelFont().pointSizeF();
6950 float scaleFactor = fontScaleFactor(pointSize) * pointSize;
6951 float fontRatio = labelsMaxWidth / labelHeight;
6952 QVector3D fontScaled = QVector3D(scaleFactor * fontRatio, scaleFactor, 0.00001f);
6953
6954 float adjustment = labelsMaxWidth * scaleFactor;
6955 float yPos = backgroundScale.y() + adjustment;
6956
6957 QVector3D labelTrans = QVector3D(0.0f, -yPos, 0.0f);
6958 QStringList labels = horizontalAxis->labels();
6959 QFont font = theme()->labelFont();
6960 bool borderVisible = theme()->isLabelBorderVisible();
6961
6962 bool backgroundVisible = theme()->isLabelBackgroundVisible();
6963 QColor backgroundColor = theme()->labelBackgroundColor();
6964
6965 if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Value) {
6966 for (int i = 0; i < sliceHorizontalLabelRepeater->count(); i++) {
6967 auto obj = static_cast<QQuick3DNode *>(sliceHorizontalLabelRepeater->objectAt(index: i));
6968 // It is important to use the position of vertical grids so that they can be in the same
6969 // position when col/row ranges are updated.
6970 float linePosX = static_cast<QValue3DAxis *>(horizontalAxis)->gridPositionAt(gridLine: i) * scale
6971 * 2.0f
6972 - translate;
6973 labelTrans.setX(linePosX);
6974 labelTrans.setY(-yPos - adjustment);
6975 obj->setScale(fontScaled);
6976 obj->setPosition(labelTrans);
6977 obj->setProperty(name: "labelText", value: labels[i]);
6978 obj->setProperty(name: "labelWidth", value: labelsMaxWidth);
6979 obj->setProperty(name: "labelHeight", value: labelHeight);
6980 obj->setProperty(name: "labelFont", value: font);
6981 obj->setProperty(name: "borderVisible", value: borderVisible);
6982 obj->setProperty(name: "labelTextColor", value: horizontalLabelTextColor);
6983 obj->setProperty(name: "backgroundVisible", value: backgroundVisible);
6984 obj->setProperty(name: "backgroundColor", value: backgroundColor);
6985 obj->setEulerRotation(QVector3D(.0f, .0f, -45.0f));
6986 if (!labels[i].compare(s: QString(hiddenLabelTag)))
6987 obj->setVisible(false);
6988 }
6989 } else if (horizontalAxis->type() == QAbstract3DAxis::AxisType::Category) {
6990 for (int i = 0; i < sliceHorizontalLabelRepeater->count(); i++) {
6991 labelTrans = calculateCategoryLabelPosition(axis: horizontalAxis, labelPosition: labelTrans, index: i);
6992 labelTrans.setY(-yPos /*- (adjustment / 2.f)*/);
6993 if (isColumn)
6994 labelTrans.setX(labelTrans.z());
6995 labelTrans.setZ(1.0f); // Bring the labels on top of bars and grid
6996 auto obj = static_cast<QQuick3DNode *>(sliceHorizontalLabelRepeater->objectAt(index: i));
6997 obj->setScale(fontScaled);
6998 obj->setPosition(labelTrans);
6999 obj->setProperty(name: "labelText", value: labels[i]);
7000 obj->setProperty(name: "labelWidth", value: labelsMaxWidth);
7001 obj->setProperty(name: "labelHeight", value: labelHeight);
7002 obj->setProperty(name: "labelFont", value: font);
7003 obj->setProperty(name: "borderVisible", value: borderVisible);
7004 obj->setProperty(name: "labelTextColor", value: horizontalLabelTextColor);
7005 obj->setProperty(name: "backgroundVisible", value: backgroundVisible);
7006 obj->setProperty(name: "backgroundColor", value: backgroundColor);
7007 obj->setEulerRotation(QVector3D(0.0f, 0.0f, -60.0f));
7008 }
7009 }
7010
7011 scale = backgroundScale.y() - m_backgroundScaleMargin.y();
7012 translate = backgroundScale.y() - m_backgroundScaleMargin.y();
7013 labels = verticalAxis->labels();
7014 labelsMaxWidth = float(findLabelsMaxWidth(labels)) + textPadding;
7015 // Since labelsMaxWidth changes for each axis, these needs to be recalculated for scaling.
7016 fontRatio = labelsMaxWidth / labelHeight;
7017 fontScaled.setX(scaleFactor * fontRatio);
7018 adjustment = labelsMaxWidth * scaleFactor;
7019 float xPos = 0.0f;
7020 if (isRow)
7021 xPos = backgroundScale.x() + (adjustment * 1.5f);
7022 else if (isColumn)
7023 xPos = backgroundScale.z() + (adjustment * 1.5f);
7024 labelTrans = QVector3D(xPos, 0.0f, 0.0f);
7025 QColor verticalLabelTextColor = theme()->axisY().labelTextColor();
7026
7027 if (verticalAxis->type() == QAbstract3DAxis::AxisType::Value) {
7028 auto valueAxis = static_cast<QValue3DAxis *>(verticalAxis);
7029 for (int i = 0; i < sliceVerticalLabelRepeater->count(); i++) {
7030 auto obj = static_cast<QQuick3DNode *>(sliceVerticalLabelRepeater->objectAt(index: i));
7031 labelTrans.setY(valueAxis->labelPositionAt(index: i) * scale * 2.0f - translate);
7032 obj->setScale(fontScaled);
7033 obj->setPosition(labelTrans);
7034 obj->setProperty(name: "labelText", value: labels[i]);
7035 obj->setProperty(name: "labelWidth", value: labelsMaxWidth);
7036 obj->setProperty(name: "labelHeight", value: labelHeight);
7037 obj->setProperty(name: "labelFont", value: font);
7038 obj->setProperty(name: "borderVisible", value: borderVisible);
7039 obj->setProperty(name: "labelTextColor", value: verticalLabelTextColor);
7040 obj->setProperty(name: "backgroundVisible", value: backgroundVisible);
7041 obj->setProperty(name: "backgroundColor", value: backgroundColor);
7042 if (!labels[i].compare(s: QString(hiddenLabelTag)))
7043 obj->setVisible(false);
7044 }
7045 } else if (verticalAxis->type() == QAbstract3DAxis::AxisType::Category) {
7046 for (int i = 0; i < sliceVerticalLabelRepeater->count(); i++) {
7047 labelTrans = calculateCategoryLabelPosition(axis: verticalAxis, labelPosition: labelTrans, index: i);
7048 auto obj = static_cast<QQuick3DNode *>(sliceVerticalLabelRepeater->objectAt(index: i));
7049 obj->setScale(fontScaled);
7050 obj->setPosition(labelTrans);
7051 obj->setProperty(name: "labelText", value: labels[i]);
7052 obj->setProperty(name: "labelWidth", value: labelsMaxWidth);
7053 obj->setProperty(name: "labelHeight", value: labelHeight);
7054 obj->setProperty(name: "labelFont", value: font);
7055 obj->setProperty(name: "borderVisible", value: borderVisible);
7056 obj->setProperty(name: "labelTextColor", value: verticalLabelTextColor);
7057 obj->setProperty(name: "backgroundVisible", value: backgroundVisible);
7058 obj->setProperty(name: "backgroundColor", value: backgroundColor);
7059 }
7060 }
7061
7062 labelHeight = fm.height() + textPadding;
7063 float labelWidth = fm.horizontalAdvance(verticalAxis->title()) + textPadding;
7064 QVector3D vTitleScale = fontScaled;
7065 vTitleScale.setX(fontScaled.y() * labelWidth / labelHeight);
7066 adjustment = labelHeight * scaleFactor;
7067 if (isRow)
7068 xPos = backgroundScale.x() + adjustment;
7069 else if (isColumn)
7070 xPos = backgroundScale.z() + adjustment;
7071 labelTrans = QVector3D(-(xPos + adjustment), 0.0f, 0.0f);
7072
7073 QQuick3DNode *sliceVerticalTitleLabel = nullptr;
7074 if (verticalTitle)
7075 sliceVerticalTitleLabel = verticalTitle;
7076 else
7077 sliceVerticalTitleLabel = m_sliceVerticalTitleLabel;
7078 if (!verticalAxis->title().isEmpty()) {
7079 sliceVerticalTitleLabel->setScale(vTitleScale);
7080 sliceVerticalTitleLabel->setPosition(labelTrans);
7081 sliceVerticalTitleLabel->setProperty(name: "labelWidth", value: labelWidth);
7082 sliceVerticalTitleLabel->setProperty(name: "labelHeight", value: labelHeight);
7083 sliceVerticalTitleLabel->setProperty(name: "labelText", value: verticalAxis->title());
7084 sliceVerticalTitleLabel->setProperty(name: "labelFont", value: font);
7085 sliceVerticalTitleLabel->setProperty(name: "borderVisible", value: borderVisible);
7086 sliceVerticalTitleLabel->setProperty(name: "labelTextColor", value: verticalLabelTextColor);
7087 sliceVerticalTitleLabel->setProperty(name: "backgroundVisible", value: backgroundVisible);
7088 sliceVerticalTitleLabel->setProperty(name: "backgroundColor", value: backgroundColor);
7089 sliceVerticalTitleLabel->setEulerRotation(QVector3D(.0f, .0f, 90.0f));
7090 } else {
7091 sliceVerticalTitleLabel->setVisible(false);
7092 }
7093
7094 labelHeight = fm.height() + textPadding;
7095 labelWidth = fm.horizontalAdvance(horizontalAxis->title()) + textPadding;
7096 QVector3D hTitleScale = fontScaled;
7097 hTitleScale.setX(fontScaled.y() * labelWidth / labelHeight);
7098 adjustment = labelHeight * scaleFactor;
7099 yPos = backgroundScale.y() * 1.5f + (adjustment * 6.f);
7100 labelTrans = QVector3D(0.0f, -yPos, 0.0f);
7101
7102 QQuick3DNode *sliceHorizontalTitleLabel = nullptr;
7103 if (horizontalTitle)
7104 sliceHorizontalTitleLabel = horizontalTitle;
7105 else
7106 sliceHorizontalTitleLabel = m_sliceHorizontalTitleLabel;
7107 if (!horizontalAxis->title().isEmpty()) {
7108 sliceHorizontalTitleLabel->setScale(hTitleScale);
7109 sliceHorizontalTitleLabel->setPosition(labelTrans);
7110 sliceHorizontalTitleLabel->setProperty(name: "labelWidth", value: labelWidth);
7111 sliceHorizontalTitleLabel->setProperty(name: "labelHeight", value: labelHeight);
7112 sliceHorizontalTitleLabel->setProperty(name: "labelText", value: horizontalAxis->title());
7113 sliceHorizontalTitleLabel->setProperty(name: "labelFont", value: font);
7114 sliceHorizontalTitleLabel->setProperty(name: "borderVisible", value: borderVisible);
7115 sliceHorizontalTitleLabel->setProperty(name: "labelTextColor", value: horizontalLabelTextColor);
7116 sliceHorizontalTitleLabel->setProperty(name: "backgroundVisible", value: backgroundVisible);
7117 sliceHorizontalTitleLabel->setProperty(name: "backgroundColor", value: backgroundColor);
7118 } else {
7119 sliceHorizontalTitleLabel->setVisible(false);
7120 }
7121
7122 QQuick3DNode *sliceItemLabel = nullptr;
7123 if (itemLabel)
7124 sliceItemLabel = itemLabel;
7125 else
7126 sliceItemLabel = m_sliceItemLabel;
7127 sliceItemLabel->setProperty(name: "labelFont", value: font);
7128 sliceItemLabel->setProperty(name: "borderVisible", value: borderVisible);
7129 sliceItemLabel->setProperty(name: "labelTextColor", value: theme()->labelTextColor());
7130 sliceItemLabel->setProperty(name: "backgroundVisible", value: backgroundVisible);
7131 sliceItemLabel->setProperty(name: "backgroundColor", value: backgroundColor);
7132}
7133
7134void QQuickGraphsItem::setUpCamera()
7135{
7136 // By default we could get away with a value of 10 or 15, but as camera zoom is implemented
7137 // by moving it, we have to take into account the maximum zoom out level. The other
7138 // option would be to adjust far clip whenever zoom level changes.
7139 const float farclip = 7000.f;
7140
7141 m_pCamera = new QQuick3DPerspectiveCamera(rootNode());
7142 m_pCamera->setClipNear(0.1f);
7143 m_pCamera->setClipFar(farclip);
7144 m_pCamera->setFieldOfView(45.0f);
7145 m_pCamera->setPosition(QVector3D(.0f, .0f, 5.f));
7146
7147 auto cameraTarget = new QQuick3DNode(rootNode());
7148 cameraTarget->setParentItem(rootNode());
7149
7150 setCameraTarget(cameraTarget);
7151 cameraTarget->setPosition(QVector3D(0, 0, 0));
7152 QQuick3DObjectPrivate::get(item: cameraTarget)
7153 ->refSceneManager(*QQuick3DObjectPrivate::get(item: rootNode())->sceneManager);
7154
7155 m_pCamera->lookAt(node: cameraTarget);
7156 m_pCamera->setParent(cameraTarget);
7157 m_pCamera->setParentItem(cameraTarget);
7158
7159 m_oCamera = new QQuick3DOrthographicCamera(rootNode());
7160 // Set clip near 0.0001f so that it can be set correct value to workaround
7161 // a Quick3D device pixel ratio bug
7162 m_oCamera->setClipNear(0.0001f);
7163 m_oCamera->setClipFar(farclip);
7164 m_oCamera->setPosition(QVector3D(0.f, 0.f, 5.f));
7165 m_oCamera->setParent(cameraTarget);
7166 m_oCamera->setParentItem(cameraTarget);
7167 m_oCamera->lookAt(node: cameraTarget);
7168
7169 auto useOrtho = isOrthoProjection();
7170 if (useOrtho)
7171 setCamera(m_oCamera);
7172 else
7173 setCamera(m_pCamera);
7174 m_changeTracker.cameraChanged = true;
7175}
7176
7177void QQuickGraphsItem::setUpLight()
7178{
7179 auto light = new QQuick3DDirectionalLight(rootNode());
7180 QQuick3DObjectPrivate::get(item: light)->refSceneManager(
7181 *QQuick3DObjectPrivate::get(item: rootNode())->sceneManager);
7182 light->setParent(camera());
7183 light->setParentItem(camera());
7184 light->setSoftShadowQuality(QQuick3DAbstractLight::QSSGSoftShadowQuality::Hard);
7185 m_light = light;
7186}
7187
7188void QQuickGraphsItem::setWrapCameraXRotation(bool wrap)
7189{
7190 if (m_wrapXRotation == wrap) {
7191 qCDebug(lcProperties3D) << __FUNCTION__
7192 << "value is already set to:" << wrap;
7193 return;
7194 }
7195 m_wrapXRotation = wrap;
7196 emit wrapCameraXRotationChanged(wrap);
7197}
7198
7199void QQuickGraphsItem::setWrapCameraYRotation(bool wrap)
7200{
7201 if (m_wrapYRotation == wrap) {
7202 qCDebug(lcProperties3D) << __FUNCTION__
7203 << "value is already set to:" << wrap;
7204 return;
7205 }
7206 m_wrapYRotation = wrap;
7207 emit wrapCameraYRotationChanged(wrap);
7208}
7209
7210float QQuickGraphsItem::ambientLightStrength() const
7211{
7212 return m_ambientLightStrength;
7213}
7214
7215void QQuickGraphsItem::setAmbientLightStrength(float newAmbientLightStrength)
7216{
7217 if (qFuzzyCompare(p1: m_ambientLightStrength, p2: newAmbientLightStrength)) {
7218 qCDebug(lcProperties3D, "%s value is already set to: %.1f",
7219 qUtf8Printable(QLatin1String(__FUNCTION__)), newAmbientLightStrength);
7220 return;
7221 }
7222
7223 if (newAmbientLightStrength < 0.0f || newAmbientLightStrength > 1.0f) {
7224 qCWarning(lcProperties3D, "%s invalid value. Valid range for ambientLightStrength is between "
7225 "0.0f and 1.0f", qUtf8Printable(QLatin1String(__FUNCTION__)));
7226 } else {
7227 m_ambientLightStrengthDirty = true;
7228 m_ambientLightStrength = newAmbientLightStrength;
7229 emit ambientLightStrengthChanged();
7230 emitNeedRender();
7231 }
7232}
7233
7234float QQuickGraphsItem::lightStrength() const
7235{
7236 return m_lightStrength;
7237}
7238
7239void QQuickGraphsItem::setLightStrength(float newLightStrength)
7240{
7241 if (qFuzzyCompare(p1: m_lightStrength, p2: newLightStrength)) {
7242 qCDebug(lcProperties3D, "%s value is already set to: %.1f",
7243 qUtf8Printable(QLatin1String(__FUNCTION__)), newLightStrength);
7244 return;
7245 }
7246
7247 if (newLightStrength < 0.0f || newLightStrength > 10.0f) {
7248 qCWarning(lcProperties3D, "%s invalid value. Valid range for lightStrength is between 0.0f and "
7249 "10.0f", qUtf8Printable(QLatin1String(__FUNCTION__)));
7250 } else {
7251 m_lightStrengthDirty = true;
7252 m_lightStrength = newLightStrength;
7253 emit lightStrengthChanged();
7254 emitNeedRender();
7255 }
7256}
7257
7258float QQuickGraphsItem::shadowStrength() const
7259{
7260 return m_shadowStrength;
7261}
7262
7263void QQuickGraphsItem::setShadowStrength(float newShadowStrength)
7264{
7265 if (qFuzzyCompare(p1: m_shadowStrength, p2: newShadowStrength)) {
7266 qCDebug(lcProperties3D, "%s value is already set to: %.1f",
7267 qUtf8Printable(QLatin1String(__FUNCTION__)), newShadowStrength);
7268 return;
7269 }
7270
7271 if (newShadowStrength < 0.0f || newShadowStrength > 100.0f) {
7272 qCWarning(lcProperties3D, "%s invalid value. Valid range for shadowStrength is between 0.0f "
7273 "and 100.0f", qUtf8Printable(QLatin1String(__FUNCTION__)));
7274 } else {
7275 m_shadowStrengthDirty = true;
7276 m_shadowStrength = newShadowStrength;
7277 emit shadowStrengthChanged();
7278 emitNeedRender();
7279 }
7280}
7281
7282QColor QQuickGraphsItem::lightColor() const
7283{
7284 return m_lightColor;
7285}
7286
7287void QQuickGraphsItem::setLightColor(QColor newLightColor)
7288{
7289 if (m_lightColor == newLightColor) {
7290 qCDebug(lcProperties3D) << __FUNCTION__
7291 << "value is already set to:" << newLightColor;
7292 return;
7293 }
7294 m_lightColorDirty = true;
7295 m_lightColor = newLightColor;
7296 emit lightColorChanged();
7297 emitNeedRender();
7298}
7299
7300void QQuickGraphsItem::updateBackgroundColor()
7301{
7302 if (theme()->isBackgroundVisible())
7303 environment()->setClearColor(theme()->backgroundColor());
7304 else
7305 environment()->setClearColor(Qt::transparent);
7306
7307 if (m_sliceView)
7308 m_sliceView->environment()->setClearColor(environment()->clearColor());
7309
7310}
7311
7312void QQuickGraphsItem::setItemSelected(bool selected)
7313{
7314 m_itemSelected = selected;
7315}
7316
7317QT_END_NAMESPACE
7318

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