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.
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.
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: { 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: { 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; }
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.
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:
NewButton { id: orthoToggle width: parent.width / 3 text: "Display Orthographic" anchors.left: rangeToggle.right 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) ...
Files:
Images: