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
|