- Qt 6.4
- Qt Data Visualization
- Qt Quick 2 Axis Dragging Example
- Qt Data Visualization | Commercial or GPLv3

# Qt Quick 2 Axis Dragging Example

Implementing axis dragging in QML.

The Qt Quick 2 axis dragging example concentrates on showing how to implement axis range changing by dragging axis labels in QML. It also gives a quick peek to two other new features in Qt Data Visualization 1.1: orthographic projection and dynamic custom item handling.

#### Running the Example

To run the example from Qt Creator, open the Welcome mode and select the example from Examples. For more information, visit Building and Running an Example.

#### Overriding Default Input Handling

First we deactivate the default input handling mechanism by setting the active input handler of Scatter3D graph to `null`:

     Scatter3D {
         id: scatterGraph
         inputHandler: null
         ...

Then we add a MouseArea and set it to fill the parent, which is the same `Item` our `scatterGraph` is contained in. We also set it to accept only left mouse button presses, as in this example we are not interested in other buttons:

     MouseArea {
         anchors.fill: parent
         hoverEnabled: true
         acceptedButtons: Qt.LeftButton
         ...

Then we need to listen to mouse presses, and when caught, send a selection query to the graph:

     onPressed: (mouse)=> {
         scatterGraph.scene.selectionQueryPosition = Qt.point(mouse.x, mouse.y);
     }

Current mouse position, that will be needed for move distance calculation, is caught in `onPositionChanged`:

     onPositionChanged: (mouse)=> {
         currentMouseX = mouse.x;
         currentMouseY = mouse.y;
         ...

At the end of `onPositionChanged`, we'll save the previous mouse position for move distance calculation that will be introduced later:

     ...
     previousMouseX = currentMouseX;
     previousMouseY = currentMouseY;
     }

#### Translating Mouse Movement to Axis Range Change

in `scatterGraph` we will need to listen to `onSelectedElementChanged` signal. The signal is emitted after the selection query has been made in the `onPressed` of `inputArea`. We set the element type into a property we defined (`property int selectedAxisLabel: -1`) in our main component, since it is of a type we are interested in:

     onSelectedElementChanged: {
         if (selectedElement >= AbstractGraph3D.ElementAxisXLabel
                 && selectedElement <= AbstractGraph3D.ElementAxisZLabel)
             selectedAxisLabel = selectedElement
         else
             selectedAxisLabel = -1
     }

Then, back in the `onPositionChanged` of `inputArea`, we check if a mouse button is pressed and if we have a current axis label selection. If the conditions are met, we'll call the function that does the conversion from mouse movement to axis range update:

     ...
     if (pressed && selectedAxisLabel != -1)
         dragAxis();
     ...

The conversion is easy in this case, as we have a fixed camera rotation. We can use some precalculated values, calculate mouse move distance, and apply the values to the selected axis range:

     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
         }
     }

For a more sophisticated conversion from mouse movement to axis range update, see this example.

#### 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 `orthoProjection` property of `scatterGraph`. In this example we have a button for toggling it on and off:

     Button {
         id: orthoToggle
         width: portraitMode ? parent.width : parent.width / 3
         text: "Display Orthographic"
         anchors.left: portraitMode ? parent.left : rangeToggle.right
         anchors.top: 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.ShadowQualityLow
             } else {
                 text = "Display Perspective";
                 scatterGraph.orthoProjection = true
             }
         }
     }

For custom items, first we'll add one in the `customItemList` of `scatterGraph`:

     customItemList: [
         Custom3DItem {
             id: qtCube
             meshFile: ":/mesh/cube"
             textureFile: ":/texture/texture"
             position: Qt.vector3d(0.65,0.35,0.65)
             scaling: Qt.vector3d(0.3,0.3,0.3)
         }
     ]

We have implemented a timer to add, remove, and rotate all the items in the graph, and we'll use the same timer for rotating the custom item:

     onTriggered: {
         rotationAngle = rotationAngle + 1
         qtCube.setRotationAxisAndAngle(Qt.vector3d(1,0,1), rotationAngle)
         ...

#### Example Contents

Example project @ code.qt.io