aboutsummaryrefslogtreecommitdiffstats
path: root/examples/datavisualization/graphgallery/custominputhandler.py
blob: 0402be60793449b28fae21615c4b9a05ea53628a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

from enum import Enum
from math import sin, cos, degrees

from PySide6.QtCore import Qt
from PySide6.QtDataVisualization import (QAbstract3DGraph, Q3DInputHandler)


class InputState(Enum):
    StateNormal = 0
    StateDraggingX = 1
    StateDraggingZ = 2
    StateDraggingY = 3


class CustomInputHandler(Q3DInputHandler):

    def __init__(self, graph, parent=None):
        super().__init__(parent)
        self._highlight = None
        self._mousePressed = False
        self._state = InputState.StateNormal
        self._axisX = None
        self._axisY = None
        self._axisZ = None
        self._speedModifier = 20.0
        self._aspectRatio = 0.0
        self._axisXMinValue = 0.0
        self._axisXMaxValue = 0.0
        self._axisXMinRange = 0.0
        self._axisZMinValue = 0.0
        self._axisZMaxValue = 0.0
        self._axisZMinRange = 0.0
        self._areaMinValue = 0.0
        self._areaMaxValue = 0.0

        # Connect to the item selection signal from graph
        graph.selectedElementChanged.connect(self.handleElementSelected)

    def setAspectRatio(self, ratio):
        self._aspectRatio = ratio

    def setHighlightSeries(self, series):
        self._highlight = series

    def setDragSpeedModifier(self, modifier):
        self._speedModifier = modifier

    def setLimits(self, min, max, minRange):
        self._areaMinValue = min
        self._areaMaxValue = max
        self._axisXMinValue = self._areaMinValue
        self._axisXMaxValue = self._areaMaxValue
        self._axisZMinValue = self._areaMinValue
        self._axisZMaxValue = self._areaMaxValue
        self._axisXMinRange = minRange
        self._axisZMinRange = minRange

    def setAxes(self, axisX, axisY, axisZ):
        self._axisX = axisX
        self._axisY = axisY
        self._axisZ = axisZ

    def mousePressEvent(self, event, mousePos):
        if Qt.LeftButton == event.button():
            self._highlight.setVisible(False)
            self._mousePressed = True
        super().mousePressEvent(event, mousePos)

    def wheelEvent(self, event):
        delta = float(event.angleDelta().y())

        self._axisXMinValue += delta
        self._axisXMaxValue -= delta
        self._axisZMinValue += delta
        self._axisZMaxValue -= delta
        self.checkConstraints()

        y = (self._axisXMaxValue - self._axisXMinValue) * self._aspectRatio

        self._axisX.setRange(self._axisXMinValue, self._axisXMaxValue)
        self._axisY.setRange(100.0, y)
        self._axisZ.setRange(self._axisZMinValue, self._axisZMaxValue)

    def mouseMoveEvent(self, event, mousePos):
        # Check if we're trying to drag axis label
        if self._mousePressed and self._state != InputState.StateNormal:
            self.setPreviousInputPos(self.inputPosition())
            self.setInputPosition(mousePos)
            self.handleAxisDragging()
        else:
            super().mouseMoveEvent(event, mousePos)

    def mouseReleaseEvent(self, event, mousePos):
        super().mouseReleaseEvent(event, mousePos)
        self._mousePressed = False
        self._state = InputState.StateNormal

    def handleElementSelected(self, type):
        if type == QAbstract3DGraph.ElementAxisXLabel:
            self._state = InputState.StateDraggingX
        elif type == QAbstract3DGraph.ElementAxisZLabel:
            self._state = InputState.StateDraggingZ
        else:
            self._state = InputState.StateNormal

    def handleAxisDragging(self):
        distance = 0.0

        # Get scene orientation from active camera
        xRotation = self.scene().activeCamera().xRotation()

        # Calculate directional drag multipliers based on rotation
        xMulX = cos(degrees(xRotation))
        xMulY = sin(degrees(xRotation))
        zMulX = xMulY
        zMulY = xMulX

        # Get the drag amount
        move = self.inputPosition() - self.previousInputPos()

        # Adjust axes
        if self._state == InputState.StateDraggingX:
            distance = (move.x() * xMulX - move.y() * xMulY) * self._speedModifier
            self._axisXMinValue -= distance
            self._axisXMaxValue -= distance
            if self._axisXMinValue < self._areaMinValue:
                dist = self._axisXMaxValue - self._axisXMinValue
                self._axisXMinValue = self._areaMinValue
                self._axisXMaxValue = self._axisXMinValue + dist

            if self._axisXMaxValue > self._areaMaxValue:
                dist = self._axisXMaxValue - self._axisXMinValue
                self._axisXMaxValue = self._areaMaxValue
                self._axisXMinValue = self._axisXMaxValue - dist

            self._axisX.setRange(self._axisXMinValue, self._axisXMaxValue)
        elif self._state == InputState.StateDraggingZ:
            distance = (move.x() * zMulX + move.y() * zMulY) * self._speedModifier
            self._axisZMinValue += distance
            self._axisZMaxValue += distance
            if self._axisZMinValue < self._areaMinValue:
                dist = self._axisZMaxValue - self._axisZMinValue
                self._axisZMinValue = self._areaMinValue
                self._axisZMaxValue = self._axisZMinValue + dist

            if self._axisZMaxValue > self._areaMaxValue:
                dist = self._axisZMaxValue - self._axisZMinValue
                self._axisZMaxValue = self._areaMaxValue
                self._axisZMinValue = self._axisZMaxValue - dist

            self._axisZ.setRange(self._axisZMinValue, self._axisZMaxValue)

    def checkConstraints(self):
        if self._axisXMinValue < self._areaMinValue:
            self._axisXMinValue = self._areaMinValue
        if self._axisXMaxValue > self._areaMaxValue:
            self._axisXMaxValue = self._areaMaxValue
        # Don't allow too much zoom in
        range = self._axisXMaxValue - self._axisXMinValue
        if range < self._axisXMinRange:
            adjust = (self._axisXMinRange - range) / 2.0
            self._axisXMinValue -= adjust
            self._axisXMaxValue += adjust

        if self._axisZMinValue < self._areaMinValue:
            self._axisZMinValue = self._areaMinValue
        if self._axisZMaxValue > self._areaMaxValue:
            self._axisZMaxValue = self._areaMaxValue
        # Don't allow too much zoom in
        range = self._axisZMaxValue - self._axisZMinValue
        if range < self._axisZMinRange:
            adjust = (self._axisZMinRange - range) / 2.0
            self._axisZMinValue -= adjust
            self._axisZMaxValue += adjust