Qt Quick 2 Axis Dragging Example

 /****************************************************************************
 **
 ** Copyright (C) 2016 The Qt Company Ltd.
 ** Contact: https://www.qt.io/licensing/
 **
 ** This file is part of the Qt Data Visualization module of the Qt Toolkit.
 **
 ** $QT_BEGIN_LICENSE:GPL$
 ** Commercial License Usage
 ** Licensees holding valid commercial Qt licenses may use this file in
 ** accordance with the commercial license agreement provided with the
 ** Software or, alternatively, in accordance with the terms contained in
 ** a written agreement between you and The Qt Company. For licensing terms
 ** and conditions see https://www.qt.io/terms-conditions. For further
 ** information use the contact form at https://www.qt.io/contact-us.
 **
 ** GNU General Public License Usage
 ** Alternatively, this file may be used under the terms of the GNU
 ** General Public License version 3 or (at your option) any later version
 ** approved by the KDE Free Qt Foundation. The licenses are as published by
 ** the Free Software Foundation and appearing in the file LICENSE.GPL3
 ** included in the packaging of this file. Please review the following
 ** information to ensure the GNU General Public License requirements will
 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
 **
 ** $QT_END_LICENSE$
 **
 ****************************************************************************/

 import QtQuick 2.1
 import QtDataVisualization 1.1
 import "."

 Item {
     id: mainView
     width: 800
     height: 600
     visible: true

     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

     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()
                               });
         }

         onTriggered: {
             rotationAngle = rotationAngle + 1
             qtCube.setRotationAxisAndAngle(Qt.vector3d(1,0,1), rotationAngle)
             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"
     }

     Item {
         id: dataView
         anchors.bottom: parent.bottom
         width: parent.width
         height: parent.height

         Scatter3D {
             id: scatterGraph
             inputHandler: null
             width: dataView.width
             height: dataView.height
             theme: dynamicColorTheme
             shadowQuality: AbstractGraph3D.ShadowQualityLow
             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"
                 }
             }
             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)
                 }
             ]
             onSelectedElementChanged: {
                 if (selectedElement >= AbstractGraph3D.ElementAxisXLabel
                         && selectedElement <= AbstractGraph3D.ElementAxisZLabel)
                     selectedAxisLabel = selectedElement
                 else
                     selectedAxisLabel = -1
             }
         }

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

             onPositionChanged: {
                 currentMouseX = mouse.x;
                 currentMouseY = mouse.y;
                 if (pressed && selectedAxisLabel != -1)
                     dragAxis();
                 previousMouseX = currentMouseX;
                 previousMouseY = currentMouseY;
             }

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

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

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

     NewButton {
         id: rangeToggle
         width: parent.width / 3 // We're adding 3 buttons and want to divide them equally
         text: "Use Preset Range"
         anchors.left: parent.left
         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
         }
     }

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

     NewButton {
         id: exitButton
         width: parent.width / 3
         text: "Quit"
         anchors.left: orthoToggle.right
         onClicked: Qt.quit(0);
     }
 }