summaryrefslogtreecommitdiffstats
path: root/examples/datavisualization/qmlaxishandling
diff options
context:
space:
mode:
Diffstat (limited to 'examples/datavisualization/qmlaxishandling')
-rw-r--r--examples/datavisualization/qmlaxishandling/CMakeLists.txt60
-rw-r--r--examples/datavisualization/qmlaxishandling/customformatter.cpp138
-rw-r--r--examples/datavisualization/qmlaxishandling/customformatter.h55
-rw-r--r--examples/datavisualization/qmlaxishandling/doc/images/qmlaxishandling-example.pngbin0 -> 132862 bytes
-rw-r--r--examples/datavisualization/qmlaxishandling/doc/src/qmlaxishandling.qdoc188
-rw-r--r--examples/datavisualization/qmlaxishandling/main.cpp43
-rw-r--r--examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml298
-rw-r--r--examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml156
-rw-r--r--examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/Data.qml35
-rw-r--r--examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/cube.obj415
-rw-r--r--examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/cubetexture.pngbin0 -> 3386 bytes
-rw-r--r--examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/main.qml47
-rw-r--r--examples/datavisualization/qmlaxishandling/qmlaxishandling.pro16
-rw-r--r--examples/datavisualization/qmlaxishandling/qmlaxishandling.qrc12
-rw-r--r--examples/datavisualization/qmlaxishandling/qmldir2
15 files changed, 1465 insertions, 0 deletions
diff --git a/examples/datavisualization/qmlaxishandling/CMakeLists.txt b/examples/datavisualization/qmlaxishandling/CMakeLists.txt
new file mode 100644
index 00000000..41ce410c
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/CMakeLists.txt
@@ -0,0 +1,60 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+cmake_minimum_required(VERSION 3.16)
+project(qmlaxishandling LANGUAGES CXX)
+
+set(CMAKE_INCLUDE_CURRENT_DIR ON)
+
+set(CMAKE_AUTOMOC ON)
+set(CMAKE_AUTORCC ON)
+set(CMAKE_AUTOUIC ON)
+
+if(NOT DEFINED INSTALL_EXAMPLESDIR)
+ set(INSTALL_EXAMPLESDIR "examples")
+endif()
+
+set(INSTALL_EXAMPLEDIR "${INSTALL_EXAMPLESDIR}")
+
+find_package(Qt6 COMPONENTS Core)
+find_package(Qt6 COMPONENTS Gui)
+find_package(Qt6 COMPONENTS Qml)
+find_package(Qt6 COMPONENTS Quick)
+find_package(Qt6 COMPONENTS DataVisualization)
+
+qt_add_executable(qmlaxishandling
+ main.cpp
+)
+set_target_properties(qmlaxishandling PROPERTIES
+ WIN32_EXECUTABLE TRUE
+ MACOSX_BUNDLE TRUE
+)
+target_link_libraries(qmlaxishandling PUBLIC
+ Qt::Core
+ Qt::Gui
+ Qt::Qml
+ Qt::Quick
+ Qt::DataVisualization
+)
+
+qt6_add_qml_module(qmlaxishandling
+ URI AxisHandling
+ VERSION 1.0
+ NO_RESOURCE_TARGET_PATH
+ SOURCES
+ customformatter.cpp customformatter.h
+ QML_FILES
+ qml/qmlaxishandling/main.qml
+ qml/qmlaxishandling/Data.qml
+ qml/qmlaxishandling/AxisDragging.qml
+ qml/qmlaxishandling/AxisFormatting.qml
+ RESOURCES
+ qml/qmlaxishandling/cube.obj
+ qml/qmlaxishandling/cubetexture.png
+)
+
+install(TARGETS qmlaxishandling
+ RUNTIME DESTINATION "${INSTALL_EXAMPLEDIR}"
+ BUNDLE DESTINATION "${INSTALL_EXAMPLEDIR}"
+ LIBRARY DESTINATION "${INSTALL_EXAMPLEDIR}"
+)
diff --git a/examples/datavisualization/qmlaxishandling/customformatter.cpp b/examples/datavisualization/qmlaxishandling/customformatter.cpp
new file mode 100644
index 00000000..b0473159
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/customformatter.cpp
@@ -0,0 +1,138 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include "customformatter.h"
+#include <QtDataVisualization/qvalue3daxis.h>
+#include <QtQml/qqmlextensionplugin.h>
+
+static const qreal oneDayMs = 60.0 * 60.0 * 24.0 * 1000.0;
+
+CustomFormatter::CustomFormatter(QObject *parent) :
+ QValue3DAxisFormatter(parent)
+{
+ qRegisterMetaType<QValue3DAxisFormatter *>();
+}
+
+CustomFormatter::~CustomFormatter()
+{
+}
+
+//! [1]
+QValue3DAxisFormatter *CustomFormatter::createNewInstance() const
+{
+ return new CustomFormatter();
+}
+
+void CustomFormatter::populateCopy(QValue3DAxisFormatter &copy) const
+{
+ QValue3DAxisFormatter::populateCopy(copy);
+
+ CustomFormatter *customFormatter = static_cast<CustomFormatter *>(&copy);
+ customFormatter->m_originDate = m_originDate;
+ customFormatter->m_selectionFormat = m_selectionFormat;
+}
+//! [1]
+
+//! [2]
+void CustomFormatter::recalculate()
+{
+ // We want our axis to always have gridlines at date breaks
+
+ // Convert range into QDateTimes
+ QDateTime minTime = valueToDateTime(qreal(axis()->min()));
+ QDateTime maxTime = valueToDateTime(qreal(axis()->max()));
+
+ // Find out the grid counts
+ QTime midnight(0, 0);
+ QDateTime minFullDate(minTime.date(), midnight);
+ int gridCount = 0;
+ if (minFullDate != minTime)
+ minFullDate = minFullDate.addDays(1);
+ QDateTime maxFullDate(maxTime.date(), midnight);
+
+ gridCount += minFullDate.daysTo(maxFullDate) + 1;
+ int subGridCount = axis()->subSegmentCount() - 1;
+
+ // Reserve space for position arrays and label strings
+ gridPositions().resize(gridCount);
+ subGridPositions().resize((gridCount + 1) * subGridCount);
+ labelPositions().resize(gridCount);
+ labelStrings().reserve(gridCount);
+
+ // Calculate positions and format labels
+ qint64 startMs = minTime.toMSecsSinceEpoch();
+ qint64 endMs = maxTime.toMSecsSinceEpoch();
+ qreal dateNormalizer = endMs - startMs;
+ qreal firstLineOffset = (minFullDate.toMSecsSinceEpoch() - startMs) / dateNormalizer;
+ qreal segmentStep = oneDayMs / dateNormalizer;
+ qreal subSegmentStep = 0;
+ if (subGridCount > 0)
+ subSegmentStep = segmentStep / qreal(subGridCount + 1);
+
+ for (int i = 0; i < gridCount; i++) {
+ qreal gridValue = firstLineOffset + (segmentStep * qreal(i));
+ gridPositions()[i] = float(gridValue);
+ labelPositions()[i] = float(gridValue);
+ labelStrings() << minFullDate.addDays(i).toString(axis()->labelFormat());
+ }
+
+ for (int i = 0; i <= gridCount; i++) {
+ if (subGridPositions().size()) {
+ for (int j = 0; j < subGridCount; j++) {
+ float position;
+ if (i)
+ position = gridPositions().at(i - 1) + subSegmentStep * (j + 1);
+ else
+ position = gridPositions().at(0) - segmentStep + subSegmentStep * (j + 1);
+ if (position > 1.0f || position < 0.0f)
+ position = gridPositions().at(0);
+ subGridPositions()[i * subGridCount + j] = position;
+ }
+ }
+ }
+}
+//! [2]
+
+//! [3]
+QString CustomFormatter::stringForValue(qreal value, const QString &format) const
+{
+ Q_UNUSED(format);
+
+ return valueToDateTime(value).toString(m_selectionFormat);
+}
+//! [3]
+
+QDate CustomFormatter::originDate() const
+{
+ return m_originDate;
+}
+
+QString CustomFormatter::selectionFormat() const
+{
+ return m_selectionFormat;
+}
+
+void CustomFormatter::setOriginDate(QDate date)
+{
+ if (m_originDate != date) {
+ m_originDate = date;
+ markDirty(true);
+ emit originDateChanged(date);
+ }
+}
+
+void CustomFormatter::setSelectionFormat(const QString &format)
+{
+ if (m_selectionFormat != format) {
+ m_selectionFormat = format;
+ markDirty(true); // Necessary to regenerate already visible selection label
+ emit selectionFormatChanged(format);
+ }
+}
+
+//! [0]
+QDateTime CustomFormatter::valueToDateTime(qreal value) const
+{
+ return m_originDate.startOfDay().addMSecs(qint64(oneDayMs * value));
+}
+//! [0]
diff --git a/examples/datavisualization/qmlaxishandling/customformatter.h b/examples/datavisualization/qmlaxishandling/customformatter.h
new file mode 100644
index 00000000..2c821b49
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/customformatter.h
@@ -0,0 +1,55 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#ifndef CUSTOMFORMATTER_H
+#define CUSTOMFORMATTER_H
+
+#include <QtDataVisualization/qvalue3daxisformatter.h>
+#include <QtCore/qdatetime.h>
+#include <QtQml/qqmlregistration.h>
+
+//! [2]
+class CustomFormatter : public QValue3DAxisFormatter
+{
+ //! [2]
+ Q_OBJECT
+ QML_ELEMENT
+
+ //! [1]
+ Q_PROPERTY(QDate originDate READ originDate WRITE setOriginDate NOTIFY originDateChanged)
+ //! [1]
+ //! [3]
+ Q_PROPERTY(QString selectionFormat READ selectionFormat WRITE setSelectionFormat NOTIFY selectionFormatChanged)
+ //! [3]
+public:
+ explicit CustomFormatter(QObject *parent = 0);
+ virtual ~CustomFormatter();
+
+ //! [0]
+ virtual QValue3DAxisFormatter *createNewInstance() const;
+ virtual void populateCopy(QValue3DAxisFormatter &copy) const;
+ virtual void recalculate();
+ virtual QString stringForValue(qreal value, const QString &format) const;
+ //! [0]
+
+ QDate originDate() const;
+ QString selectionFormat() const;
+
+public Q_SLOTS:
+ void setOriginDate(QDate date);
+ void setSelectionFormat(const QString &format);
+
+Q_SIGNALS:
+ void originDateChanged(QDate date);
+ void selectionFormatChanged(const QString &format);
+
+private:
+ Q_DISABLE_COPY(CustomFormatter)
+
+ QDateTime valueToDateTime(qreal value) const;
+
+ QDate m_originDate;
+ QString m_selectionFormat;
+};
+
+#endif
diff --git a/examples/datavisualization/qmlaxishandling/doc/images/qmlaxishandling-example.png b/examples/datavisualization/qmlaxishandling/doc/images/qmlaxishandling-example.png
new file mode 100644
index 00000000..21e9551b
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/doc/images/qmlaxishandling-example.png
Binary files differ
diff --git a/examples/datavisualization/qmlaxishandling/doc/src/qmlaxishandling.qdoc b/examples/datavisualization/qmlaxishandling/doc/src/qmlaxishandling.qdoc
new file mode 100644
index 00000000..087630c6
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/doc/src/qmlaxishandling.qdoc
@@ -0,0 +1,188 @@
+// Copyright (C) 2016 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GFDL-1.3-no-invariants-only
+
+/*!
+ \example qmlaxishandling
+ \meta tags {DataVisualization, Scatter3D, Custom Input Handler, Dynamic Data, Custom Axis Formatter, Scatter Graph}
+ \title Axis Handling
+ \ingroup qtdatavisualization_qmlexamples
+ \brief Implementing axis dragging with a custom input handler in QML, and creating a custom axis formatter.
+ \since QtDataVisualization 6.5
+
+ \e {Axis Handling} demonstrates two different custom features with axes. The features have their
+ own tabs in the application.
+
+ The following sections concentrate on those features only and skip explaining the basic
+ functionality - for more detailed QML example documentation, see \l{Simple Scatter Graph}.
+
+ \image qmlaxishandling-example.png
+
+ \include examples-run.qdocinc
+
+ \section1 Axis Dragging
+
+ In the \uicontrol {Axis Dragging} tab, implement a custom input handler in QML that enables you
+ to drag axis labels to change axis ranges. Further, use orthographic projection and dynamically
+ update the properties of a custom item.
+
+ \section2 Overriding Default Input Handling
+
+ To deactivate the default input handling mechanism, set the active input handler of Scatter3D
+ graph to \c{null}:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 0
+ \dots
+
+ Then, add a MouseArea and set it to fill the parent, which is the same \c Item our
+ \c scatterGraph is contained in. Also, set it to accept only left mouse button presses,
+ as in this example the other buttons are not needed:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 1
+ \dots
+
+ Then, listen to mouse presses, and when caught, send a selection query to the graph:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 2
+
+ The \c{onPositionChanged} signal handler catches the current mouse position that will be
+ needed for move distance calculation:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 3
+ \dots
+
+ At the end of \c{onPositionChanged}, save the previous mouse position for move distance
+ calculation that will be introduced later:
+
+ \dots 0
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 4
+
+ \section2 Translating Mouse Movement to Axis Range Change
+
+ In \c {scatterGraph}, listen to \c {onSelectedElementChanged}. The signal
+ is emitted after the selection query has been made in the \c{onPressed} of the \c{inputArea}.
+ Set the element type into a property you defined (\c{property int selectedAxisLabel: -1}) in the
+ main component, since it is of a type you are interested in:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 5
+
+ Then, back in the \c onPositionChanged of \c{inputArea}, check if a mouse button is pressed
+ and if you have a current axis label selection. If the conditions are met, call the
+ function that does the conversion from mouse movement to axis range update:
+
+ \dots 0
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 6
+ \dots 0
+
+ The conversion is easy in this case, as the camera rotation is fixed. You can use some
+ precalculated values, calculate mouse move distance, and apply the values to the
+ selected axis range:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 7
+
+ For a more sophisticated conversion from mouse movement to axis range update, see
+ \l{Graph Gallery}.
+
+ \section2 Other Features
+
+ The example also demonstrates how to use orthographic projection and how to update properties
+ of a custom item on the fly.
+
+ Orthographic projection is very simple. You'll just need to change the \c orthoProjection
+ property of \c{scatterGraph}. The example has a button for toggling it on and off:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 8
+
+ For custom items, add one to the \c customItemList of \c{scatterGraph}:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 9
+
+ You implement a timer to add, remove, and rotate all the items in the graph, and use the same
+ timer for rotating the custom item:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml 10
+ \dots
+
+ \section1 Axis Formatters
+
+ In the \uicontrol {Axis Formatter} tab, create a custom axis formatter. It also illustrates how
+ to use predefined axis formatters.
+
+ \section2 Custom Axis Formatter
+
+ Customizing axis formatters requires subclassing the QValue3DAxisFormatter, which cannot be
+ done in QML code alone. In this example, the axis interprets the float values as
+ a timestamp and shows the date in the axis labels. To achieve this, introduce a new class
+ called \c CustomFormatter, which subclasses the QValue3DAxisFormatter:
+
+ \snippet qmlaxishandling/customformatter.h 2
+ \dots 0
+
+ Since float values of a QScatter3DSeries cannot be directly cast into QDateTime values due to
+ difference in data width, some sort of mapping between the two is needed. To do the mapping,
+ specify an origin date for the formatter and interpret the float values from the
+ QScatter3DSeries as date offsets to that origin value. The origin date is given as
+ a property:
+
+ \snippet qmlaxishandling/customformatter.h 1
+
+ For the mapping from value to QDateTime, use the \c valueToDateTime() method:
+
+ \snippet qmlaxishandling/customformatter.cpp 0
+
+ To function as an axis formatter, \c CustomFormatter needs to reimplement some virtual
+ methods:
+
+ \snippet qmlaxishandling/customformatter.h 0
+
+ The first two are simple, just create a new instance of \c CustomFormatter and copy the
+ necessary data over to it. Use these two methods to create and update a cache of formatter for
+ rendering purposes. Remember to call the superclass implementation of \c populateCopy():
+
+ \snippet qmlaxishandling/customformatter.cpp 1
+
+ \c CustomFormatter does the bulk of its work in the \c recalculate() method, where
+ our formatter calculates the grid, subgrid, and label positions, as well as formats the label
+ strings.
+ In the custom formatter, ignore the segment count of the axis and draw a grid line always at
+ midnight. Subsegment count and label positioning is handled normally:
+
+ \snippet qmlaxishandling/customformatter.cpp 2
+
+ The axis labels are formatted to show only the date. However, to increase the resolution of the
+ timestamp of the selection label, specify another property for the custom formatter to allow
+ the user to customize it:
+
+ \snippet qmlaxishandling/customformatter.h 3
+
+ This selection format property is used in the reimplemented \c stringToValue method, where
+ the submitted format is ignored and the custom selection format substituted for it:
+
+ \snippet qmlaxishandling/customformatter.cpp 3
+
+ To expose our new custom formatter to the QML, declare it and make it a QML module.
+ For information about how to do this, see \l{Surface Graph Gallery}.
+
+ \section2 QML
+
+ In the QML code, define a different axis for each dimension:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml 3
+
+ The Z-axis is just a regular ValueAxis3D:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml 0
+
+ For the Y-axis, define a logarithmic axis. To make ValueAxis3D show a logarithmic scale,
+ specify LogValueAxis3DFormatter for \c formatter property of the axis:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml 2
+
+ And finally, for the X-axis use the new \c CustomFormatter:
+
+ \snippet qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml 1
+
+ The rest of the application consists of fairly self-explanatory logic for modifying the axes and
+ showing the graph.
+
+ \section1 Example Contents
+*/
diff --git a/examples/datavisualization/qmlaxishandling/main.cpp b/examples/datavisualization/qmlaxishandling/main.cpp
new file mode 100644
index 00000000..2a145633
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/main.cpp
@@ -0,0 +1,43 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+#include <QtGui/qguiapplication.h>
+#include <QtQuick/qquickview.h>
+#include <QtQml/qqmlengine.h>
+#include <QtQml>
+
+#ifdef QMAKE_BUILD
+#include "customformatter.h"
+Q_DECLARE_METATYPE(CustomFormatter *)
+#endif
+
+int main(int argc, char *argv[])
+{
+ qputenv("QSG_RHI_BACKEND", "opengl");
+ QGuiApplication app(argc, argv);
+
+#ifdef QMAKE_BUILD
+ qmlRegisterType<CustomFormatter>("AxisHandling", 1, 0, "CustomFormatter");
+#endif
+
+ QQuickView viewer;
+
+ // The following are needed to make examples run without having to install the module
+ // in desktop environments.
+#ifdef Q_OS_WIN
+ QString extraImportPath(QStringLiteral("%1/../../../../%2"));
+#else
+ QString extraImportPath(QStringLiteral("%1/../../../%2"));
+#endif
+ viewer.engine()->addImportPath(extraImportPath.arg(QGuiApplication::applicationDirPath(),
+ QString::fromLatin1("qml")));
+ QObject::connect(viewer.engine(), &QQmlEngine::quit, &viewer, &QWindow::close);
+
+ viewer.setTitle(QStringLiteral("Axis Handling"));
+
+ viewer.setSource(QUrl("qrc:/qml/qmlaxishandling/main.qml"));
+ viewer.setResizeMode(QQuickView::SizeRootObjectToView);
+ viewer.show();
+
+ return app.exec();
+}
diff --git a/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml
new file mode 100644
index 00000000..81d2e0a1
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/AxisDragging.qml
@@ -0,0 +1,298 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtDataVisualization
+
+Item {
+ id: axisDragView
+
+ property int selectedAxisLabel: -1
+ property real dragSpeedModifier: 100.0
+ property int currentMouseX: -1
+ property int currentMouseY: -1
+ property int previousMouseX: -1
+ property int previousMouseY: -1
+
+ required property bool portraitMode
+
+ ListModel {
+ id: graphModel
+ ListElement{ xPos: 0.0; yPos: 0.0; zPos: 0.0; rotation: "@0,0,0,0" }
+ ListElement{ xPos: 1.0; yPos: 1.0; zPos: 1.0; rotation: "@45,1,1,1" }
+ }
+
+ Timer {
+ id: dataTimer
+ interval: 1
+ running: true
+ repeat: true
+ property bool isIncreasing: true
+ property real rotationAngle: 0
+
+ function generateQuaternion() {
+ return "@" + Math.random() * 360 + "," + Math.random() + ","
+ + Math.random() + "," + Math.random();
+ }
+
+ function appendRow() {
+ graphModel.append({"xPos": Math.random(),
+ "yPos": Math.random(),
+ "zPos": Math.random(),
+ "rotation": generateQuaternion()
+ });
+ }
+
+ //! [10]
+ onTriggered: {
+ rotationAngle = rotationAngle + 1;
+ qtCube.setRotationAxisAndAngle(Qt.vector3d(1, 0, 1), rotationAngle);
+ //! [10]
+ scatterSeries.setMeshAxisAndAngle(Qt.vector3d(1, 1, 1), rotationAngle);
+ if (isIncreasing) {
+ for (var i = 0; i < 10; i++)
+ appendRow();
+ if (graphModel.count > 2002) {
+ scatterGraph.theme = isabelleTheme;
+ isIncreasing = false;
+ }
+ } else {
+ graphModel.remove(2, 10);
+ if (graphModel.count === 2) {
+ scatterGraph.theme = dynamicColorTheme;
+ isIncreasing = true;
+ }
+ }
+ }
+ }
+
+ ThemeColor {
+ id: dynamicColor
+ ColorAnimation on color {
+ from: "red"
+ to: "yellow"
+ duration: 2000
+ loops: Animation.Infinite
+ }
+ }
+
+ Theme3D {
+ id: dynamicColorTheme
+ type: Theme3D.ThemeEbony
+ baseColors: [dynamicColor]
+ font.pointSize: 50
+ labelBorderEnabled: true
+ labelBackgroundColor: "gold"
+ labelTextColor: "black"
+ }
+
+ Theme3D {
+ id: isabelleTheme
+ type: Theme3D.ThemeIsabelle
+ font.pointSize: 50
+ labelBorderEnabled: true
+ labelBackgroundColor: "gold"
+ labelTextColor: "black"
+ }
+
+ //! [0]
+ Scatter3D {
+ id: scatterGraph
+ inputHandler: null
+ //! [0]
+ anchors.fill: parent
+ theme: dynamicColorTheme
+ shadowQuality: AbstractGraph3D.ShadowQualityMedium
+ scene.activeCamera.yRotation: 45.0
+ scene.activeCamera.xRotation: 45.0
+ scene.activeCamera.zoomLevel: 75.0
+
+ Scatter3DSeries {
+ id: scatterSeries
+ itemLabelFormat: "X:@xLabel Y:@yLabel Z:@zLabel"
+ mesh: Abstract3DSeries.MeshCube
+
+ ItemModelScatterDataProxy {
+ itemModel: graphModel
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ rotationRole: "rotation"
+ }
+ }
+ //! [9]
+ customItemList: [
+ Custom3DItem {
+ id: qtCube
+ meshFile: ":/qml/qmlaxishandling/cube.obj"
+ textureFile: ":/qml/qmlaxishandling/cubetexture.png"
+ position: Qt.vector3d(0.65, 0.35, 0.65)
+ scaling: Qt.vector3d(0.3, 0.3, 0.3)
+ }
+ ]
+ //! [9]
+ //! [5]
+ onSelectedElementChanged: {
+ if (selectedElement >= AbstractGraph3D.ElementAxisXLabel
+ && selectedElement <= AbstractGraph3D.ElementAxisZLabel) {
+ selectedAxisLabel = selectedElement;
+ } else {
+ selectedAxisLabel = -1;
+ }
+ }
+ //! [5]
+ }
+
+ //! [1]
+ MouseArea {
+ anchors.fill: parent
+ hoverEnabled: true
+ acceptedButtons: Qt.LeftButton
+ //! [1]
+
+ //! [3]
+ onPositionChanged: (mouse)=> {
+ currentMouseX = mouse.x;
+ currentMouseY = mouse.y;
+ //! [3]
+ //! [6]
+ if (pressed && selectedAxisLabel != -1)
+ axisDragView.dragAxis();
+ //! [6]
+ //! [4]
+ previousMouseX = currentMouseX;
+ previousMouseY = currentMouseY;
+ }
+ //! [4]
+
+ //! [2]
+ onPressed: (mouse)=> {
+ scatterGraph.scene.selectionQueryPosition = Qt.point(mouse.x, mouse.y);
+ }
+ //! [2]
+
+ onReleased: {
+ // We need to clear mouse positions and selected axis, because touch devices cannot
+ // track position all the time
+ selectedAxisLabel = -1;
+ currentMouseX = -1;
+ currentMouseY = -1;
+ previousMouseX = -1;
+ previousMouseY = -1;
+ }
+ }
+
+ //! [7]
+ function dragAxis() {
+ // Do nothing if previous mouse position is uninitialized
+ if (previousMouseX === -1)
+ return;
+
+ // Directional drag multipliers based on rotation. Camera is locked to 45 degrees, so we
+ // can use one precalculated value instead of calculating xx, xy, zx and zy individually
+ var cameraMultiplier = 0.70710678;
+
+ // Calculate the mouse move amount
+ var moveX = currentMouseX - previousMouseX;
+ var moveY = currentMouseY - previousMouseY;
+
+ // Adjust axes
+ switch (selectedAxisLabel) {
+ case AbstractGraph3D.ElementAxisXLabel:
+ var distance = ((moveX - moveY) * cameraMultiplier) / dragSpeedModifier;
+ // Check if we need to change min or max first to avoid invalid ranges
+ if (distance > 0) {
+ scatterGraph.axisX.min -= distance;
+ scatterGraph.axisX.max -= distance;
+ } else {
+ scatterGraph.axisX.max -= distance;
+ scatterGraph.axisX.min -= distance;
+ }
+ break;
+ case AbstractGraph3D.ElementAxisYLabel:
+ distance = moveY / dragSpeedModifier;
+ // Check if we need to change min or max first to avoid invalid ranges
+ if (distance > 0) {
+ scatterGraph.axisY.max += distance;
+ scatterGraph.axisY.min += distance;
+ } else {
+ scatterGraph.axisY.min += distance;
+ scatterGraph.axisY.max += distance;
+ }
+ break;
+ case AbstractGraph3D.ElementAxisZLabel:
+ distance = ((moveX + moveY) * cameraMultiplier) / dragSpeedModifier;
+ // Check if we need to change min or max first to avoid invalid ranges
+ if (distance > 0) {
+ scatterGraph.axisZ.max += distance;
+ scatterGraph.axisZ.min += distance;
+ } else {
+ scatterGraph.axisZ.min += distance;
+ scatterGraph.axisZ.max += distance;
+ }
+ break;
+ }
+ }
+ //! [7]
+
+ Button {
+ id: rangeToggle
+ // We're adding 3 buttons and want to divide them equally, if not in portrait mode
+ width: axisDragView.portraitMode ? parent.width : parent.width / 3
+ text: "Use Preset Range"
+ anchors.left: parent.left
+ anchors.top: parent.top
+ property bool autoRange: true
+ onClicked: {
+ if (autoRange) {
+ text = "Use Automatic Range";
+ scatterGraph.axisX.min = 0.3;
+ scatterGraph.axisX.max = 0.7;
+ scatterGraph.axisY.min = 0.3;
+ scatterGraph.axisY.max = 0.7;
+ scatterGraph.axisZ.min = 0.3;
+ scatterGraph.axisZ.max = 0.7;
+ autoRange = false;
+ dragSpeedModifier = 200.0;
+ } else {
+ text = "Use Preset Range";
+ autoRange = true;
+ dragSpeedModifier = 100.0;
+ }
+ scatterGraph.axisX.autoAdjustRange = autoRange;
+ scatterGraph.axisY.autoAdjustRange = autoRange;
+ scatterGraph.axisZ.autoAdjustRange = autoRange;
+ }
+ }
+
+ //! [8]
+ Button {
+ id: orthoToggle
+ width: axisDragView.portraitMode ? parent.width : parent.width / 3
+ text: "Display Orthographic"
+ anchors.left: axisDragView.portraitMode ? parent.left : rangeToggle.right
+ anchors.top: axisDragView.portraitMode ? rangeToggle.bottom : parent.top
+ onClicked: {
+ if (scatterGraph.orthoProjection) {
+ text = "Display Orthographic";
+ scatterGraph.orthoProjection = false;
+ // Orthographic projection disables shadows, so we need to switch them back on
+ scatterGraph.shadowQuality = AbstractGraph3D.ShadowQualityMedium
+ } else {
+ text = "Display Perspective";
+ scatterGraph.orthoProjection = true;
+ }
+ }
+ }
+ //! [8]
+
+ Button {
+ id: exitButton
+ width: axisDragView.portraitMode ? parent.width : parent.width / 3
+ text: "Quit"
+ anchors.left: axisDragView.portraitMode ? parent.left : orthoToggle.right
+ anchors.top: axisDragView.portraitMode ? orthoToggle.bottom : parent.top
+ onClicked: Qt.quit();
+ }
+}
diff --git a/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml
new file mode 100644
index 00000000..5ceae315
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/AxisFormatting.qml
@@ -0,0 +1,156 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtDataVisualization
+import AxisHandling
+
+Item {
+ id: axisFormattingView
+
+ required property bool portraitMode
+
+ Data {
+ id: seriesData
+ }
+
+ Theme3D {
+ id: themePrimaryColors
+ type: Theme3D.ThemePrimaryColors
+ font.family: "Lucida Handwriting"
+ font.pointSize: 40
+ }
+
+ //! [1]
+ ValueAxis3D {
+ id: dateAxis
+ formatter: CustomFormatter {
+ originDate: "2023-01-01"
+ selectionFormat: "yyyy-MM-dd HH:mm:ss"
+ }
+ subSegmentCount: 2
+ labelFormat: "yyyy-MM-dd"
+ min: 0
+ max: 14
+ }
+ //! [1]
+
+ //! [2]
+ ValueAxis3D {
+ id: logAxis
+ formatter: LogValueAxis3DFormatter {
+ id: logAxisFormatter
+ base: 10
+ autoSubGrid: true
+ showEdgeLabels: true
+ }
+ labelFormat: "%.2f"
+ }
+ //! [2]
+
+ ValueAxis3D {
+ id: linearAxis
+ labelFormat: "%.2f"
+ min: 0
+ max: 500
+ }
+
+ //! [0]
+ ValueAxis3D {
+ id: valueAxis
+ segmentCount: 5
+ subSegmentCount: 2
+ labelFormat: "%.2f"
+ min: 0
+ max: 10
+ }
+ //! [0]
+
+ Scatter3D {
+ id: scatterGraph
+ anchors.top: exitButton.bottom
+ anchors.bottom: parent.bottom
+ width: parent.width
+ theme: themePrimaryColors
+ shadowQuality: AbstractGraph3D.ShadowQualitySoftMedium
+ scene.activeCamera.cameraPreset: Camera3D.CameraPresetIsometricRight
+ //! [3]
+ axisZ: valueAxis
+ axisY: logAxis
+ axisX: dateAxis
+ //! [3]
+
+ Scatter3DSeries {
+ id: scatterSeries
+ itemLabelFormat: "@xLabel - (@yLabel, @zLabel)"
+ meshSmooth: true
+ ItemModelScatterDataProxy {
+ itemModel: seriesData.model
+ xPosRole: "xPos"
+ yPosRole: "yPos"
+ zPosRole: "zPos"
+ }
+ }
+ }
+
+ Button {
+ id: yAxisBaseChange
+ width: axisFormattingView.portraitMode ? parent.width : parent.width / 3
+ anchors.left: parent.left
+ anchors.top: parent.top
+ state: "enabled"
+ onClicked: {
+ if (logAxisFormatter.base === 10)
+ logAxisFormatter.base = 0;
+ else if (logAxisFormatter.base === 2)
+ logAxisFormatter.base = 10;
+ else
+ logAxisFormatter.base = 2;
+ }
+ states: [
+ State {
+ name: "enabled"
+ PropertyChanges {
+ target: yAxisBaseChange
+ text: "Y-axis log base: " + logAxisFormatter.base
+ enabled: true
+ }
+ },
+ State {
+ name: "disabled"
+ PropertyChanges {
+ target: yAxisBaseChange
+ text: "Y-axis linear"
+ enabled: false
+ }
+ }
+ ]
+ }
+
+ Button {
+ id: yAxisToggle
+ width: axisFormattingView.portraitMode ? parent.width : parent.width / 3
+ anchors.left: axisFormattingView.portraitMode ? parent.left : yAxisBaseChange.right
+ anchors.top: axisFormattingView.portraitMode ? yAxisBaseChange.bottom : parent.top
+ text: "Toggle Y-axis"
+ onClicked: {
+ if (scatterGraph.axisY == linearAxis) {
+ scatterGraph.axisY = logAxis;
+ yAxisBaseChange.state = "enabled";
+ } else {
+ scatterGraph.axisY = linearAxis;
+ yAxisBaseChange.state = "disabled";
+ }
+ }
+ }
+
+ Button {
+ id: exitButton
+ width: axisFormattingView.portraitMode ? parent.width : parent.width / 3
+ anchors.left: axisFormattingView.portraitMode ? parent.left : yAxisToggle.right
+ anchors.top: axisFormattingView.portraitMode ? yAxisToggle.bottom : parent.top
+ text: "Quit"
+ onClicked: Qt.quit();
+ }
+}
diff --git a/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/Data.qml b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/Data.qml
new file mode 100644
index 00000000..b74337fd
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/Data.qml
@@ -0,0 +1,35 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+
+Item {
+ property alias model: dataModel
+
+ ListModel {
+ id: dataModel
+ ListElement{ xPos: 2.456103; yPos: 1.0; zPos: 5.0 }
+ ListElement{ xPos: 5.687549; yPos: 3.0; zPos: 2.5 }
+ ListElement{ xPos: 2.357458; yPos: 4.1; zPos: 1.0 }
+ ListElement{ xPos: 4.567458; yPos: 4.75; zPos: 3.9 }
+ ListElement{ xPos: 6.885439; yPos: 4.9; zPos: 7.2 }
+ ListElement{ xPos: 2.366769; yPos: 13.42; zPos: 3.5 }
+ ListElement{ xPos: 7.546457; yPos: 233.1; zPos: 6.9 }
+ ListElement{ xPos: 2.475867; yPos: 32.91; zPos: 4.1 }
+ ListElement{ xPos: 8.456546; yPos: 153.68; zPos: 9.52 }
+ ListElement{ xPos: 3.456348; yPos: 52.96; zPos: 1.6 }
+ ListElement{ xPos: 1.536446; yPos: 32.4; zPos: 2.92 }
+ ListElement{ xPos: 8.456666; yPos: 114.74; zPos: 8.18 }
+ ListElement{ xPos: 5.468486; yPos: 83.1; zPos: 3.8 }
+ ListElement{ xPos: 6.546586 ; yPos: 63.66; zPos: 3.58 }
+ ListElement{ xPos: 8.567516 ; yPos: 1.82; zPos: 4.64 }
+ ListElement{ xPos: 7.678984 ; yPos: 213.18; zPos: 7.22 }
+ ListElement{ xPos: 7.457569 ; yPos: 63.06; zPos: 4.3 }
+ ListElement{ xPos: 8.456755 ; yPos: 122.64; zPos: 6.44 }
+ ListElement{ xPos: 6.234536 ; yPos: 63.96; zPos: 4.38 }
+ ListElement{ xPos: 9.456718 ; yPos: 243.32; zPos: 4.04 }
+ ListElement{ xPos: 10.789889 ; yPos: 43.4; zPos: 2.78 }
+ ListElement{ xPos: 11.346554 ; yPos: 345.12; zPos: 3.1 }
+ ListElement{ xPos: 12.023454 ; yPos: 500.0; zPos: 3.68 }
+ }
+}
diff --git a/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/cube.obj b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/cube.obj
new file mode 100644
index 00000000..0197618f
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/cube.obj
@@ -0,0 +1,415 @@
+# Blender v2.66 (sub 0) OBJ File: 'beveled_cube.blend'
+# www.blender.org
+v -1.000000 -0.878027 0.878027
+v -0.978771 -0.929277 0.878027
+v -0.950975 -0.932562 0.932562
+v -0.978771 -0.878027 0.929277
+v -0.932562 -0.950975 0.932562
+v -0.929277 -0.978771 0.878027
+v -0.878027 -1.000000 0.878027
+v -0.878027 -0.978771 0.929277
+v -0.932562 -0.932562 0.950975
+v -0.878027 -0.929277 0.978771
+v -0.878027 -0.878027 1.000000
+v -0.929277 -0.878027 0.978771
+v -1.000000 -0.878027 -0.878027
+v -0.978771 -0.878027 -0.929277
+v -0.950975 -0.932562 -0.932562
+v -0.978771 -0.929277 -0.878027
+v -0.932562 -0.932562 -0.950975
+v -0.929277 -0.878027 -0.978771
+v -0.878027 -0.878027 -1.000000
+v -0.878027 -0.929277 -0.978771
+v -0.932562 -0.950975 -0.932562
+v -0.878027 -0.978771 -0.929277
+v -0.878027 -1.000000 -0.878027
+v -0.929277 -0.978771 -0.878027
+v 0.878027 -0.878027 -1.000000
+v 0.929277 -0.878027 -0.978771
+v 0.932562 -0.932562 -0.950975
+v 0.878027 -0.929277 -0.978771
+v 0.950975 -0.932562 -0.932562
+v 0.978771 -0.878027 -0.929277
+v 1.000000 -0.878027 -0.878027
+v 0.978771 -0.929277 -0.878027
+v 0.932562 -0.950975 -0.932562
+v 0.929277 -0.978771 -0.878027
+v 0.878027 -1.000000 -0.878027
+v 0.878027 -0.978771 -0.929277
+v 1.000000 -0.878027 0.878027
+v 0.978771 -0.878027 0.929277
+v 0.950975 -0.932562 0.932562
+v 0.978771 -0.929277 0.878027
+v 0.932562 -0.932562 0.950975
+v 0.929277 -0.878027 0.978771
+v 0.878027 -0.878027 1.000000
+v 0.878027 -0.929277 0.978771
+v 0.932562 -0.950975 0.932562
+v 0.878027 -0.978771 0.929277
+v 0.878027 -1.000000 0.878027
+v 0.929277 -0.978771 0.878027
+v -0.878027 0.878027 1.000000
+v -0.878027 0.929277 0.978771
+v -0.932562 0.932562 0.950975
+v -0.929277 0.878027 0.978771
+v -0.932562 0.950975 0.932562
+v -0.878027 0.978771 0.929277
+v -0.878027 1.000000 0.878027
+v -0.929277 0.978771 0.878027
+v -0.950975 0.932562 0.932562
+v -0.978771 0.929277 0.878027
+v -1.000000 0.878027 0.878027
+v -0.978771 0.878027 0.929277
+v -1.000000 0.878027 -0.878027
+v -0.978771 0.929277 -0.878027
+v -0.950975 0.932562 -0.932562
+v -0.978771 0.878027 -0.929277
+v -0.932562 0.950975 -0.932562
+v -0.929277 0.978771 -0.878027
+v -0.878027 1.000000 -0.878027
+v -0.878027 0.978771 -0.929277
+v -0.932562 0.932562 -0.950975
+v -0.878027 0.929277 -0.978771
+v -0.878027 0.878027 -1.000000
+v -0.929277 0.878027 -0.978771
+v 0.878027 0.878027 -1.000000
+v 0.878027 0.929277 -0.978771
+v 0.932562 0.932562 -0.950975
+v 0.929277 0.878027 -0.978771
+v 0.932562 0.950975 -0.932562
+v 0.878027 0.978771 -0.929277
+v 0.878027 1.000000 -0.878027
+v 0.929277 0.978771 -0.878027
+v 0.950975 0.932562 -0.932562
+v 0.978771 0.929277 -0.878027
+v 1.000000 0.878027 -0.878027
+v 0.978771 0.878027 -0.929277
+v 1.000000 0.878027 0.878027
+v 0.978771 0.929277 0.878027
+v 0.950975 0.932562 0.932562
+v 0.978771 0.878027 0.929277
+v 0.932562 0.950975 0.932562
+v 0.929277 0.978771 0.878027
+v 0.878027 1.000000 0.878027
+v 0.878027 0.978771 0.929277
+v 0.932562 0.932562 0.950975
+v 0.878027 0.929277 0.978771
+v 0.878027 0.878027 1.000000
+v 0.929277 0.878027 0.978771
+vt 0.024513 0.966281
+vt 0.033719 0.975487
+vt 0.033719 0.966281
+vt 0.964639 0.060986
+vt 0.964639 0.939014
+vt 0.989386 0.939014
+vt 0.939014 0.964639
+vt 0.060986 0.964639
+vt 0.939014 0.989386
+vt 0.060986 0.060986
+vt 0.060986 0.939014
+vt 0.939014 0.939014
+vt 0.035361 0.060986
+vt 0.035361 0.939014
+vt 0.010614 0.060986
+vt 0.010614 0.939014
+vt 0.989386 0.060986
+vt 0.939014 0.035361
+vt 0.060986 0.035361
+vt 0.060986 0.010614
+vt 0.060986 0.989386
+vt 0.939014 0.060986
+vt 0.966281 0.975487
+vt 0.966281 0.966281
+vt 0.975487 0.966281
+vt 0.975487 0.033719
+vt 0.966281 0.033719
+vt 0.966281 0.024513
+vt 0.033719 0.024513
+vt 0.033719 0.033719
+vt 0.024513 0.033719
+vt 0.939014 0.010614
+vn -0.713187 -0.495651 -0.495651
+vn -0.495651 -0.495651 -0.713187
+vn -0.495651 -0.713187 -0.495651
+vn 0.539384 -0.823450 0.175909
+vn 0.539384 -0.823450 -0.175909
+vn 0.823450 -0.539384 -0.175909
+vn -0.713187 0.495651 -0.495651
+vn -0.495651 0.713187 -0.495651
+vn -0.495651 0.495651 -0.713187
+vn 0.175909 -0.823450 -0.539384
+vn -0.185644 -0.825892 -0.532365
+vn 0.175909 -0.539384 -0.823450
+vn -0.193426 -0.961852 0.193426
+vn -0.193426 -0.961852 -0.193426
+vn 0.187689 -0.964110 -0.187689
+vn -0.532365 -0.185644 0.825892
+vn -0.532365 0.185644 0.825892
+vn -0.825892 -0.185644 0.532365
+vn -0.532365 0.185644 -0.825892
+vn -0.532365 -0.185644 -0.825892
+vn -0.825892 0.185644 -0.532365
+vn 0.823450 0.175909 -0.539384
+vn 0.823450 -0.175909 -0.539384
+vn 0.539384 -0.175909 -0.823450
+vn 0.539384 0.175909 0.823450
+vn 0.539384 -0.175909 0.823450
+vn 0.823450 -0.175909 0.539384
+vn 0.175909 0.823450 0.539384
+vn -0.185644 0.825892 0.532365
+vn -0.185644 0.532365 0.825892
+vn -0.185644 0.825892 -0.532365
+vn 0.175909 0.823450 -0.539384
+vn -0.185644 0.532365 -0.825892
+vn 0.539384 0.823450 -0.175909
+vn 0.539384 0.823450 0.175909
+vn 0.823450 0.539384 0.175909
+vn 0.187689 0.964110 0.187689
+vn 0.187689 0.964110 -0.187689
+vn -0.193426 0.961852 -0.193426
+vn -0.193426 0.193426 -0.961852
+vn 0.187689 0.187689 -0.964110
+vn -0.193426 -0.193426 -0.961852
+vn -0.961852 0.193426 0.193426
+vn -0.961852 0.193426 -0.193426
+vn -0.961852 -0.193426 0.193426
+vn -0.532365 -0.825892 -0.185644
+vn -0.532365 -0.825892 0.185644
+vn -0.825892 -0.532365 -0.185644
+vn 0.498856 0.498856 -0.708701
+vn 0.498856 0.708701 -0.498856
+vn 0.708701 0.498856 -0.498856
+vn 0.964110 0.187689 -0.187689
+vn 0.964110 0.187689 0.187689
+vn 0.964110 -0.187689 0.187689
+vn 0.498856 -0.498856 -0.708701
+vn 0.708701 -0.498856 -0.498856
+vn 0.498856 -0.708701 -0.498856
+vn 0.708701 0.498856 0.498856
+vn 0.498856 0.708701 0.498856
+vn 0.498856 0.498856 0.708701
+vn -0.495651 0.495651 0.713187
+vn -0.495651 0.713187 0.495651
+vn -0.713187 0.495651 0.495651
+vn 0.708701 -0.498856 0.498856
+vn 0.498856 -0.498856 0.708701
+vn 0.498856 -0.708701 0.498856
+vn -0.532365 0.825892 0.185644
+vn -0.532365 0.825892 -0.185644
+vn -0.825892 0.532365 0.185644
+vn 0.187689 0.187689 0.964110
+vn -0.193426 0.193426 0.961852
+vn -0.193426 -0.193426 0.961852
+vn -0.185644 -0.825892 0.532365
+vn 0.175909 -0.823450 0.539384
+vn 0.175909 -0.539384 0.823450
+vn -0.825892 -0.532365 0.185644
+vn -0.713187 -0.495651 0.495651
+vn -0.495651 -0.713187 0.495651
+vn -0.495651 -0.495651 0.713187
+vn -0.185644 -0.532365 0.825892
+vn -0.961852 -0.193426 -0.193426
+vn -0.825892 -0.185644 -0.532365
+vn 0.187689 -0.187689 -0.964110
+vn 0.823450 -0.539384 0.175909
+vn -0.193426 0.961852 0.193426
+vn -0.825892 0.532365 -0.185644
+vn 0.175909 0.539384 -0.823450
+vn 0.539384 0.175909 -0.823450
+vn 0.823450 0.539384 -0.175909
+vn 0.823450 0.175909 0.539384
+vn 0.175909 0.539384 0.823450
+vn -0.185644 -0.532365 -0.825892
+vn -0.825892 0.185644 0.532365
+vn 0.964110 -0.187689 -0.187689
+vn 0.187689 -0.187689 0.964110
+vn 0.187689 -0.964110 0.187689
+s 1
+f 15/1/1 17/2/2 21/3/3
+f 48/4/4 34/5/5 32/6/6
+f 63/1/7 65/3/8 69/2/9
+f 36/7/10 22/8/11 28/9/12
+f 7/10/13 23/11/14 35/12/15
+f 12/13/16 52/14/17 4/15/18
+f 72/14/19 18/13/20 64/16/21
+f 84/6/22 30/17/23 26/4/24
+f 96/5/25 42/4/26 38/17/27
+f 92/18/28 54/19/29 50/20/30
+f 68/8/31 78/7/32 70/21/33
+f 80/5/34 90/4/35 86/17/36
+f 91/22/37 79/12/38 67/11/39
+f 71/11/40 73/12/41 19/10/42
+f 59/11/43 61/12/44 1/10/45
+f 24/14/46 6/13/47 16/16/48
+f 75/23/49 77/24/50 81/25/51
+f 83/12/52 85/11/53 37/10/54
+f 27/23/55 29/25/56 33/24/57
+f 87/26/58 89/27/59 93/28/60
+f 51/29/61 53/30/62 57/31/63
+f 39/26/64 41/28/65 45/27/66
+f 56/13/67 66/14/68 58/15/69
+f 95/12/70 49/11/71 11/10/72
+f 8/19/73 46/18/74 44/32/75
+f 1/10/45 2/19/76 3/30/77
+f 5/30/78 6/13/47 7/10/13
+f 9/30/79 10/19/80 11/10/72
+f 13/22/81 14/4/82 15/27/1
+f 17/30/2 18/13/20 19/10/42
+f 21/3/3 22/8/11 23/11/14
+f 25/22/83 26/4/24 28/18/12
+f 29/27/56 30/4/23 32/18/6
+f 33/24/57 34/5/5 36/7/10
+f 37/10/54 38/13/27 40/19/84
+f 41/27/65 42/4/26 44/18/75
+f 45/27/66 46/18/74 48/4/4
+f 49/11/71 50/8/30 51/3/61
+f 53/30/62 54/19/29 55/10/85
+f 57/3/63 58/8/69 59/11/43
+f 61/12/44 62/7/86 63/24/7
+f 65/3/8 66/14/68 67/11/39
+f 69/3/9 70/8/33 71/11/40
+f 73/12/41 74/7/87 76/5/88
+f 77/24/50 78/7/32 80/5/34
+f 81/24/51 82/7/89 84/5/22
+f 85/11/53 86/8/36 88/14/90
+f 89/27/59 90/4/35 92/18/28
+f 93/24/60 94/7/91 96/5/25
+f 2/15/76 6/13/47 3/31/77
+f 8/19/73 10/20/80 5/30/78
+f 12/13/16 4/15/18 9/30/79
+f 14/15/82 18/13/20 15/31/1
+f 20/21/92 22/8/11 17/2/2
+f 24/14/46 16/16/48 21/3/3
+f 26/4/24 30/17/23 29/26/56
+f 32/6/6 34/5/5 33/24/57
+f 36/7/10 28/9/12 27/23/55
+f 38/17/27 42/4/26 41/27/65
+f 44/32/75 46/18/74 45/27/66
+f 48/4/4 40/17/84 39/26/64
+f 50/20/30 54/19/29 51/29/61
+f 56/13/67 58/15/69 53/30/62
+f 60/16/93 52/14/17 57/1/63
+f 62/16/86 66/14/68 63/1/7
+f 68/8/31 70/21/33 65/3/8
+f 72/14/19 64/16/21 69/3/9
+f 74/9/87 78/7/32 77/24/50
+f 80/5/34 82/6/89 81/25/51
+f 84/6/22 76/5/88 75/24/49
+f 86/17/36 90/4/35 89/27/59
+f 92/18/28 94/32/91 93/28/60
+f 96/5/25 88/6/90 87/25/58
+f 55/10/85 67/11/39 56/13/67
+f 61/12/44 59/11/43 62/7/86
+f 71/11/40 19/10/42 72/14/19
+f 13/22/81 61/12/44 14/4/82
+f 23/11/14 7/10/13 24/14/46
+f 1/10/45 13/22/81 2/19/76
+f 11/10/72 49/11/71 12/13/16
+f 59/11/43 1/10/45 60/14/93
+f 67/11/39 79/12/38 68/8/31
+f 73/12/41 71/11/40 74/7/87
+f 83/12/52 31/22/94 30/4/23
+f 25/22/83 73/12/41 76/5/88
+f 35/12/15 23/11/14 36/7/10
+f 19/10/42 25/22/83 20/19/92
+f 79/12/38 91/22/37 90/4/35
+f 85/11/53 83/12/52 82/7/89
+f 95/12/70 43/22/95 42/4/26
+f 37/10/54 85/11/53 88/14/90
+f 47/22/96 35/12/15 34/5/5
+f 31/22/94 37/10/54 40/19/84
+f 91/22/37 55/10/85 54/19/29
+f 49/11/71 95/12/70 94/7/91
+f 7/10/13 47/22/96 46/18/74
+f 43/22/95 11/10/72 10/19/80
+f 3/31/77 5/30/78 9/29/79
+f 40/17/84 48/4/4 32/6/6
+f 22/8/11 20/21/92 28/9/12
+f 47/22/96 7/10/13 35/12/15
+f 52/14/17 60/16/93 4/15/18
+f 18/13/20 14/15/82 64/16/21
+f 76/5/88 84/6/22 26/4/24
+f 88/6/90 96/5/25 38/17/27
+f 94/32/91 92/18/28 50/20/30
+f 78/7/32 74/9/87 70/21/33
+f 82/6/89 80/5/34 86/17/36
+f 55/10/85 91/22/37 67/11/39
+f 73/12/41 25/22/83 19/10/42
+f 61/12/44 13/22/81 1/10/45
+f 6/13/47 2/15/76 16/16/48
+f 31/22/94 83/12/52 37/10/54
+f 66/14/68 62/16/86 58/15/69
+f 43/22/95 95/12/70 11/10/72
+f 10/20/80 8/19/73 44/32/75
+f 4/13/18 1/10/45 3/30/77
+f 8/19/73 5/30/78 7/10/13
+f 12/13/16 9/30/79 11/10/72
+f 16/18/48 13/22/81 15/27/1
+f 20/19/92 17/30/2 19/10/42
+f 24/14/46 21/3/3 23/11/14
+f 26/4/24 27/27/55 28/18/12
+f 30/4/23 31/22/94 32/18/6
+f 34/5/5 35/12/15 36/7/10
+f 38/13/27 39/30/64 40/19/84
+f 42/4/26 43/22/95 44/18/75
+f 46/18/74 47/22/96 48/4/4
+f 52/14/17 49/11/71 51/3/61
+f 56/13/67 53/30/62 55/10/85
+f 60/14/93 57/3/63 59/11/43
+f 64/5/21 61/12/44 63/24/7
+f 68/8/31 65/3/8 67/11/39
+f 72/14/19 69/3/9 71/11/40
+f 74/7/87 75/24/49 76/5/88
+f 78/7/32 79/12/38 80/5/34
+f 82/7/89 83/12/52 84/5/22
+f 86/8/36 87/3/58 88/14/90
+f 90/4/35 91/22/37 92/18/28
+f 94/7/91 95/12/70 96/5/25
+f 6/13/47 5/30/78 3/31/77
+f 10/20/80 9/29/79 5/30/78
+f 4/15/18 3/31/77 9/30/79
+f 18/13/20 17/30/2 15/31/1
+f 22/8/11 21/3/3 17/2/2
+f 16/16/48 15/1/1 21/3/3
+f 27/27/55 26/4/24 29/26/56
+f 29/25/56 32/6/6 33/24/57
+f 33/24/57 36/7/10 27/23/55
+f 39/26/64 38/17/27 41/27/65
+f 41/28/65 44/32/75 45/27/66
+f 45/27/66 48/4/4 39/26/64
+f 54/19/29 53/30/62 51/29/61
+f 58/15/69 57/31/63 53/30/62
+f 52/14/17 51/3/61 57/1/63
+f 66/14/68 65/3/8 63/1/7
+f 70/21/33 69/2/9 65/3/8
+f 64/16/21 63/1/7 69/3/9
+f 75/23/49 74/9/87 77/24/50
+f 77/24/50 80/5/34 81/25/51
+f 81/25/51 84/6/22 75/24/49
+f 87/26/58 86/17/36 89/27/59
+f 89/27/59 92/18/28 93/28/60
+f 93/24/60 96/5/25 87/25/58
+f 67/11/39 66/14/68 56/13/67
+f 59/11/43 58/8/69 62/7/86
+f 19/10/42 18/13/20 72/14/19
+f 61/12/44 64/5/21 14/4/82
+f 7/10/13 6/13/47 24/14/46
+f 13/22/81 16/18/48 2/19/76
+f 49/11/71 52/14/17 12/13/16
+f 1/10/45 4/13/18 60/14/93
+f 79/12/38 78/7/32 68/8/31
+f 71/11/40 70/8/33 74/7/87
+f 84/5/22 83/12/52 30/4/23
+f 26/4/24 25/22/83 76/5/88
+f 23/11/14 22/8/11 36/7/10
+f 25/22/83 28/18/12 20/19/92
+f 80/5/34 79/12/38 90/4/35
+f 86/8/36 85/11/53 82/7/89
+f 96/5/25 95/12/70 42/4/26
+f 38/13/27 37/10/54 88/14/90
+f 48/4/4 47/22/96 34/5/5
+f 32/18/6 31/22/94 40/19/84
+f 92/18/28 91/22/37 54/19/29
+f 50/8/30 49/11/71 94/7/91
+f 8/19/73 7/10/13 46/18/74
+f 44/18/75 43/22/95 10/19/80
diff --git a/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/cubetexture.png b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/cubetexture.png
new file mode 100644
index 00000000..6369363f
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/cubetexture.png
Binary files differ
diff --git a/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/main.qml b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/main.qml
new file mode 100644
index 00000000..d4d830a4
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/qml/qmlaxishandling/main.qml
@@ -0,0 +1,47 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
+
+import QtQuick
+import QtQuick.Controls
+import QtQuick.Layouts
+
+Item {
+ id: mainView
+ width: 1280
+ height: 1024
+ visible: true
+
+ property bool portraitMode: width < height
+
+ TabBar {
+ id: tabBar
+ width: parent.width
+
+ TabButton {
+ text: "Axis Dragging"
+ }
+
+ TabButton {
+ text: "Axis Formatting"
+ }
+ }
+
+ StackLayout {
+ anchors.top: tabBar.bottom
+ anchors.bottom: parent.bottom
+ width: parent.width
+ currentIndex: tabBar.currentIndex
+
+ AxisDragging {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ portraitMode: mainView.portraitMode
+ }
+
+ AxisFormatting {
+ Layout.fillHeight: true
+ Layout.fillWidth: true
+ portraitMode: mainView.portraitMode
+ }
+ }
+}
diff --git a/examples/datavisualization/qmlaxishandling/qmlaxishandling.pro b/examples/datavisualization/qmlaxishandling/qmlaxishandling.pro
new file mode 100644
index 00000000..36b0deee
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/qmlaxishandling.pro
@@ -0,0 +1,16 @@
+!include( ../examples.pri ) {
+ error( "Couldn't find the examples.pri file!" )
+}
+
+DEFINES += QMAKE_BUILD
+
+SOURCES += main.cpp \
+ customformatter.cpp
+
+HEADERS += customformatter.h
+
+RESOURCES += qmlaxishandling.qrc
+
+OTHER_FILES += doc/src/* \
+ doc/images/* \
+ qml/qmlaxishandling/*
diff --git a/examples/datavisualization/qmlaxishandling/qmlaxishandling.qrc b/examples/datavisualization/qmlaxishandling/qmlaxishandling.qrc
new file mode 100644
index 00000000..136734f5
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/qmlaxishandling.qrc
@@ -0,0 +1,12 @@
+<RCC>
+ <qresource prefix="/">
+ <file>qml/qmlaxishandling/AxisDragging.qml</file>
+ <file>qml/qmlaxishandling/AxisFormatting.qml</file>
+ <file>qml/qmlaxishandling/cube.obj</file>
+ <file>qml/qmlaxishandling/cubetexture.png</file>
+ <file>qml/qmlaxishandling/Data.qml</file>
+ <file>qml/qmlaxishandling/main.qml</file>
+ </qresource>
+ <qresource prefix="/mesh"/>
+ <qresource prefix="/texture"/>
+</RCC>
diff --git a/examples/datavisualization/qmlaxishandling/qmldir b/examples/datavisualization/qmlaxishandling/qmldir
new file mode 100644
index 00000000..f6da01e7
--- /dev/null
+++ b/examples/datavisualization/qmlaxishandling/qmldir
@@ -0,0 +1,2 @@
+module AxisHandling
+plugin customformatterplugin