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

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