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

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

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