QuLab 2.11.7__py3-none-any.whl → 2.11.9__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- qulab/executor/analyze.py +1 -0
- qulab/executor/cli.py +224 -45
- qulab/executor/utils.py +16 -2
- qulab/monitor/__init__.py +1 -1
- qulab/monitor/__main__.py +31 -3
- qulab/monitor/config.py +55 -30
- qulab/monitor/dataset.py +145 -38
- qulab/monitor/event_queue.py +98 -25
- qulab/monitor/mainwindow.py +165 -131
- qulab/monitor/monitor.py +220 -30
- qulab/monitor/ploter.py +98 -73
- qulab/monitor/qt_compat.py +30 -1
- qulab/monitor/toolbar.py +152 -121
- qulab/utils.py +16 -17
- qulab/version.py +1 -1
- {qulab-2.11.7.dist-info → qulab-2.11.9.dist-info}/METADATA +1 -1
- {qulab-2.11.7.dist-info → qulab-2.11.9.dist-info}/RECORD +21 -21
- {qulab-2.11.7.dist-info → qulab-2.11.9.dist-info}/WHEEL +1 -1
- {qulab-2.11.7.dist-info → qulab-2.11.9.dist-info}/entry_points.txt +0 -0
- {qulab-2.11.7.dist-info → qulab-2.11.9.dist-info}/licenses/LICENSE +0 -0
- {qulab-2.11.7.dist-info → qulab-2.11.9.dist-info}/top_level.txt +0 -0
qulab/monitor/monitor.py
CHANGED
@@ -1,5 +1,17 @@
|
|
1
|
+
"""
|
2
|
+
QuLab Monitor Module
|
3
|
+
|
4
|
+
This module provides real-time data visualization capabilities for QuLab.
|
5
|
+
It implements a multiprocessing-based monitoring system that can display
|
6
|
+
multiple data streams in a configurable grid layout.
|
7
|
+
|
8
|
+
Classes:
|
9
|
+
Monitor: Main class for creating and managing the monitoring window
|
10
|
+
"""
|
11
|
+
|
1
12
|
import multiprocessing as mp
|
2
13
|
import sys
|
14
|
+
import zmq
|
3
15
|
|
4
16
|
# try:
|
5
17
|
# mp.set_start_method("spawn")
|
@@ -7,19 +19,40 @@ import sys
|
|
7
19
|
# pass
|
8
20
|
|
9
21
|
|
10
|
-
def main(
|
11
|
-
|
22
|
+
def main(data_queue: mp.Queue,
|
23
|
+
num_columns: int = 4,
|
12
24
|
minimum_height: int = 400,
|
13
|
-
|
25
|
+
plot_colors: list[tuple[int, int, int]] = []) -> None:
|
26
|
+
"""
|
27
|
+
Initialize and run the main monitoring window.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
data_queue: Multiprocessing queue for data communication
|
31
|
+
num_columns: Number of columns in the plot grid layout
|
32
|
+
minimum_height: Minimum height of each plot in pixels
|
33
|
+
plot_colors: List of RGB color tuples for plot lines
|
34
|
+
"""
|
14
35
|
from .mainwindow import MainWindow
|
15
36
|
from .qt_compat import QtWidgets
|
16
37
|
|
17
38
|
app = QtWidgets.QApplication(sys.argv)
|
18
|
-
|
39
|
+
main_window = MainWindow(data_queue, num_columns, minimum_height, plot_colors)
|
19
40
|
sys.exit(app.exec())
|
20
41
|
|
21
42
|
|
22
|
-
class
|
43
|
+
class MonitorUI:
|
44
|
+
"""
|
45
|
+
Real-time data monitoring interface.
|
46
|
+
|
47
|
+
This class manages a separate process that displays real-time data plots
|
48
|
+
in a grid layout. Data can be added through various methods and will be
|
49
|
+
displayed immediately.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
number_of_columns: Number of columns in the plot grid layout
|
53
|
+
minimum_height: Minimum height of each plot in pixels
|
54
|
+
colors: List of RGB color tuples for plot lines
|
55
|
+
"""
|
23
56
|
|
24
57
|
def __init__(self,
|
25
58
|
number_of_columns: int = 4,
|
@@ -32,72 +65,203 @@ class Monitor():
|
|
32
65
|
self.process = None
|
33
66
|
self.start()
|
34
67
|
|
35
|
-
def start(self):
|
68
|
+
def start(self) -> None:
|
69
|
+
"""Start the monitoring process if not already running."""
|
36
70
|
if self.process is not None and self.process.is_alive():
|
37
71
|
return
|
38
72
|
self.queue = mp.Queue(20)
|
39
73
|
self.process = mp.Process(target=main,
|
40
|
-
|
41
|
-
|
74
|
+
args=(self.queue, self.number_of_columns,
|
75
|
+
self.minimum_height, self.colors))
|
42
76
|
self.process.start()
|
43
77
|
|
44
|
-
def _put(self,
|
45
|
-
|
78
|
+
def _put(self, message: tuple) -> None:
|
79
|
+
"""Send a message to the monitoring process."""
|
80
|
+
self.queue.put(message)
|
46
81
|
|
47
|
-
def roll(self):
|
82
|
+
def roll(self) -> None:
|
83
|
+
"""Clear and reset all plots."""
|
48
84
|
self._put(("ROLL", None))
|
49
85
|
|
50
|
-
def set_column_names(self, *
|
51
|
-
|
86
|
+
def set_column_names(self, *column_names) -> None:
|
87
|
+
"""Set the names of data columns for plotting."""
|
88
|
+
self._put(('PN', list(column_names)))
|
52
89
|
|
53
|
-
def add_point(self, *
|
54
|
-
|
90
|
+
def add_point(self, *values) -> None:
|
91
|
+
"""Add a new data point to the plots."""
|
92
|
+
self._put(('PD', list(values)))
|
55
93
|
|
56
|
-
def set_plots(self,
|
94
|
+
def set_plots(self, plot_config: str) -> None:
|
57
95
|
"""
|
58
|
-
|
96
|
+
Configure which columns to plot against each other.
|
97
|
+
|
98
|
+
Args:
|
99
|
+
plot_config: String specifying plot configurations, e.g. "(x,y)" or "(x1,y1);(x2,y2);"
|
59
100
|
"""
|
60
|
-
self._put(('PXY', str(
|
101
|
+
self._put(('PXY', str(plot_config)))
|
61
102
|
|
62
|
-
def set_trace_column_names(self, *
|
63
|
-
|
103
|
+
def set_trace_column_names(self, *column_names) -> None:
|
104
|
+
"""Set the names of trace data columns."""
|
105
|
+
self._put(('TN', list(column_names)))
|
64
106
|
|
65
|
-
def add_trace(self, *
|
66
|
-
|
107
|
+
def add_trace(self, *values) -> None:
|
108
|
+
"""Add a new trace data point."""
|
109
|
+
self._put(('TD', list(values)))
|
67
110
|
|
68
|
-
def set_trace_plots(self,
|
111
|
+
def set_trace_plots(self, plot_config: str) -> None:
|
69
112
|
"""
|
70
|
-
|
113
|
+
Configure which columns to plot for traces.
|
114
|
+
|
115
|
+
Args:
|
116
|
+
plot_config: String specifying trace plot configurations, e.g. "(x,y)" or "(x1,y1);(x2,y2);"
|
71
117
|
"""
|
72
|
-
self._put(('TXY', str(
|
118
|
+
self._put(('TXY', str(plot_config)))
|
119
|
+
|
120
|
+
def is_alive(self) -> bool:
|
121
|
+
"""Check if the monitoring process is running."""
|
122
|
+
return self.process.is_alive()
|
73
123
|
|
74
124
|
def __del__(self):
|
125
|
+
"""Clean up resources when the Monitor object is deleted."""
|
75
126
|
try:
|
76
127
|
self.process.kill()
|
77
128
|
except:
|
78
129
|
pass
|
79
130
|
|
80
|
-
|
81
|
-
|
131
|
+
|
132
|
+
class Monitor:
|
133
|
+
def __init__(self, address: str="127.0.0.1", port: int=5555):
|
134
|
+
self.context = zmq.Context()
|
135
|
+
self.socket = self.context.socket(zmq.REQ)
|
136
|
+
self.socket.connect(f"tcp://{address}:{port}")
|
137
|
+
|
138
|
+
def _send_command(self, cmd: str, data=None) -> None:
|
139
|
+
try:
|
140
|
+
self.socket.send_pyobj((cmd, data))
|
141
|
+
response = self.socket.recv_string()
|
142
|
+
if response.startswith("Error"):
|
143
|
+
raise RuntimeError(response)
|
144
|
+
except Exception as e:
|
145
|
+
raise RuntimeError(f"Failed to send command: {str(e)}")
|
146
|
+
|
147
|
+
def roll(self) -> None:
|
148
|
+
self._send_command("ROLL", None)
|
149
|
+
|
150
|
+
def set_column_names(self, *column_names) -> None:
|
151
|
+
self._send_command("PN", list(column_names))
|
152
|
+
|
153
|
+
def add_point(self, *values) -> None:
|
154
|
+
self._send_command("PD", list(values))
|
155
|
+
|
156
|
+
def set_plots(self, plot_config: str) -> None:
|
157
|
+
self._send_command("PXY", plot_config)
|
158
|
+
|
159
|
+
def set_trace_column_names(self, *column_names) -> None:
|
160
|
+
self._send_command("TN", list(column_names))
|
161
|
+
|
162
|
+
def add_trace(self, *values) -> None:
|
163
|
+
self._send_command("TD", list(values))
|
164
|
+
|
165
|
+
def set_trace_plots(self, plot_config: str) -> None:
|
166
|
+
self._send_command("TXY", plot_config)
|
167
|
+
|
168
|
+
def __del__(self):
|
169
|
+
try:
|
170
|
+
self.socket.close()
|
171
|
+
self.context.term()
|
172
|
+
except:
|
173
|
+
pass
|
82
174
|
|
83
175
|
|
176
|
+
class MonitorServer:
|
177
|
+
def __init__(self, address: str="*", port: int=5555, number_of_columns: int = 4,
|
178
|
+
minimum_height: int = 400,
|
179
|
+
colors: list[tuple[int, int, int]] = []):
|
180
|
+
self.address = address
|
181
|
+
self.port = port
|
182
|
+
self.number_of_columns = number_of_columns
|
183
|
+
self.minimum_height = minimum_height
|
184
|
+
self.colors = colors
|
185
|
+
self.running = True
|
186
|
+
self.process = mp.Process(target=self._run)
|
187
|
+
self.process.start()
|
188
|
+
|
189
|
+
def _run(self):
|
190
|
+
try:
|
191
|
+
# Create Monitor instance in the child process
|
192
|
+
self.monitor = MonitorUI(self.number_of_columns, self.minimum_height, self.colors)
|
193
|
+
|
194
|
+
# Create ZMQ context and socket in the child process
|
195
|
+
self.context = zmq.Context()
|
196
|
+
self.socket = self.context.socket(zmq.REP)
|
197
|
+
self.socket.bind(f"tcp://{self.address}:{self.port}")
|
198
|
+
|
199
|
+
while self.running:
|
200
|
+
try:
|
201
|
+
message = self.socket.recv_pyobj()
|
202
|
+
cmd, data = message
|
203
|
+
if cmd == 'ROLL':
|
204
|
+
self.monitor.roll()
|
205
|
+
elif cmd == 'PN':
|
206
|
+
self.monitor.set_column_names(*data)
|
207
|
+
elif cmd == 'PD':
|
208
|
+
self.monitor.add_point(*data)
|
209
|
+
elif cmd == 'PXY':
|
210
|
+
self.monitor.set_plots(data)
|
211
|
+
elif cmd == 'TN':
|
212
|
+
self.monitor.set_trace_column_names(*data)
|
213
|
+
elif cmd == 'TD':
|
214
|
+
self.monitor.add_trace(*data)
|
215
|
+
elif cmd == 'TXY':
|
216
|
+
self.monitor.set_trace_plots(data)
|
217
|
+
self.socket.send_string("OK")
|
218
|
+
except Exception as e:
|
219
|
+
self.socket.send_string(f"Error: {str(e)}")
|
220
|
+
finally:
|
221
|
+
# Clean up resources in child process
|
222
|
+
try:
|
223
|
+
self.socket.close()
|
224
|
+
self.context.term()
|
225
|
+
except:
|
226
|
+
pass
|
227
|
+
|
228
|
+
def __del__(self):
|
229
|
+
self.running = False
|
230
|
+
try:
|
231
|
+
self.process.terminate()
|
232
|
+
self.process.join()
|
233
|
+
except:
|
234
|
+
pass
|
235
|
+
|
236
|
+
|
237
|
+
# Global monitor instance
|
84
238
|
_monitor = None
|
85
239
|
|
86
240
|
|
87
|
-
def get_monitor(auto_open=True):
|
241
|
+
def get_monitor(auto_open: bool = True) -> MonitorUI:
|
242
|
+
"""
|
243
|
+
Get or create a global Monitor instance.
|
244
|
+
|
245
|
+
Args:
|
246
|
+
auto_open: If True, create a new Monitor if none exists or if existing one is not running
|
247
|
+
|
248
|
+
Returns:
|
249
|
+
Monitor instance
|
250
|
+
"""
|
88
251
|
global _monitor
|
89
252
|
|
90
253
|
if auto_open and (_monitor is None or not _monitor.is_alive()):
|
91
|
-
_monitor =
|
254
|
+
_monitor = MonitorUI()
|
92
255
|
|
93
256
|
return _monitor
|
94
257
|
|
95
258
|
|
96
259
|
if __name__ == "__main__":
|
260
|
+
# Example usage and testing code
|
97
261
|
import time
|
98
|
-
|
99
262
|
import numpy as np
|
100
263
|
|
264
|
+
# Example 1: Using Monitor directly
|
101
265
|
for i in range(3):
|
102
266
|
index = 0
|
103
267
|
while True:
|
@@ -113,3 +277,29 @@ if __name__ == "__main__":
|
|
113
277
|
m.add_point(index, np.random.randn(), np.sin(index / 20))
|
114
278
|
index += 1
|
115
279
|
time.sleep(0.2)
|
280
|
+
|
281
|
+
# Example 2: Using MonitorServer and Monitor
|
282
|
+
def run_server():
|
283
|
+
server = MonitorServer("127.0.0.1", 5555)
|
284
|
+
try:
|
285
|
+
while True:
|
286
|
+
time.sleep(1)
|
287
|
+
except KeyboardInterrupt:
|
288
|
+
pass
|
289
|
+
|
290
|
+
def run_client():
|
291
|
+
client = Monitor("127.0.0.1", 5555)
|
292
|
+
time.sleep(1) # Wait for server to start
|
293
|
+
|
294
|
+
client.set_column_names("index", "H", "S")
|
295
|
+
client.set_plots("(index,H);(index,S)")
|
296
|
+
client.roll()
|
297
|
+
|
298
|
+
for i in range(100):
|
299
|
+
client.add_point(i, np.random.randn(), np.sin(i / 20))
|
300
|
+
time.sleep(0.2)
|
301
|
+
|
302
|
+
if len(sys.argv) > 1 and sys.argv[1] == "server":
|
303
|
+
run_server()
|
304
|
+
elif len(sys.argv) > 1 and sys.argv[1] == "client":
|
305
|
+
run_client()
|
qulab/monitor/ploter.py
CHANGED
@@ -1,123 +1,148 @@
|
|
1
|
-
|
1
|
+
"""
|
2
|
+
QuLab Monitor Plotter Module
|
3
|
+
|
4
|
+
This module provides a custom plotting widget based on pyqtgraph for real-time
|
5
|
+
data visualization. It includes features like auto-ranging, mouse interaction
|
6
|
+
for data selection, and clipboard integration.
|
7
|
+
"""
|
8
|
+
|
9
|
+
from .config import (COLOR_SELECTED, COLOR_UNSELECTED, SYMBOL_SIZES,
|
10
|
+
DEFAULT_COLORS, ROLL_INDICES, LINE_WIDTHS)
|
2
11
|
from .qt_compat import QtWidgets
|
3
12
|
|
4
13
|
# the plotting widget
|
5
14
|
try:
|
6
15
|
import pyqtgraph as pg
|
7
|
-
except:
|
16
|
+
except ImportError:
|
8
17
|
raise ImportError("Please install pyqtgraph first")
|
9
18
|
|
10
19
|
try:
|
11
20
|
import pyperclip as pc
|
12
|
-
|
13
|
-
except:
|
14
|
-
|
21
|
+
HAS_CLIPBOARD = True
|
22
|
+
except ImportError:
|
23
|
+
HAS_CLIPBOARD = False
|
15
24
|
|
16
25
|
|
17
26
|
class PlotWidget(pg.PlotWidget):
|
27
|
+
"""
|
28
|
+
Custom plotting widget extending pyqtgraph's PlotWidget.
|
29
|
+
|
30
|
+
This widget provides additional features like:
|
31
|
+
- Configurable plot colors and styles
|
32
|
+
- Mouse interaction for data point selection
|
33
|
+
- Clipboard integration for selected data points
|
34
|
+
- Auto-ranging and axis control
|
35
|
+
|
36
|
+
Args:
|
37
|
+
minimum_height: Minimum height of the plot widget in pixels
|
38
|
+
colors: List of RGB color tuples for plot lines
|
39
|
+
"""
|
18
40
|
|
19
41
|
def __init__(self, minimum_height=300, colors=None):
|
20
|
-
self.
|
21
|
-
self.
|
42
|
+
self.x_axis_linked = False
|
43
|
+
self.y_axis_linked = False
|
22
44
|
if colors is None:
|
23
|
-
colors =
|
24
|
-
elif len(colors) < len(
|
25
|
-
colors.extend(
|
45
|
+
colors = DEFAULT_COLORS
|
46
|
+
elif len(colors) < len(DEFAULT_COLORS):
|
47
|
+
colors.extend(DEFAULT_COLORS[len(colors):])
|
26
48
|
self.colors = colors
|
27
|
-
self.
|
28
|
-
self.
|
49
|
+
self.x_name = ""
|
50
|
+
self.y_name = ""
|
29
51
|
super().__init__()
|
30
52
|
|
31
53
|
self.setMinimumHeight(minimum_height)
|
32
54
|
self.showGrid(x=True, y=True)
|
33
|
-
self.setBackground(
|
55
|
+
self.setBackground(COLOR_UNSELECTED)
|
34
56
|
|
35
57
|
self.plotItem.vb.autoRange()
|
36
58
|
|
37
59
|
## Labeling
|
38
|
-
self.
|
39
|
-
self.
|
40
|
-
self.
|
41
|
-
self.
|
42
|
-
self.
|
43
|
-
self.
|
60
|
+
self.x_label = QtWidgets.QLabel(self)
|
61
|
+
self.x_label.setText("X:")
|
62
|
+
self.x_label.move(0, 5)
|
63
|
+
self.y_label = QtWidgets.QLabel(self)
|
64
|
+
self.y_label.setText("Y:")
|
65
|
+
self.y_label.move(0, 35)
|
44
66
|
|
45
67
|
self.plots = {}
|
46
|
-
self.
|
47
|
-
self.
|
68
|
+
self.clip_pos_start = 0
|
69
|
+
self.clip_pos_end = 0
|
48
70
|
self.range_select = False
|
49
|
-
for
|
50
|
-
self.plots[
|
51
|
-
self.plot([],[] ,pen={"color":self.colors[
|
52
|
-
symbolBrush = self.colors[
|
53
|
-
symbolPen = { "width":0 ,"color":self.colors[
|
54
|
-
symbolSize =
|
71
|
+
for idx in ROLL_INDICES:
|
72
|
+
self.plots[idx] = \
|
73
|
+
self.plot([],[] ,pen={"color":self.colors[idx] ,"width":LINE_WIDTHS[idx]} ,
|
74
|
+
symbolBrush = self.colors[idx],
|
75
|
+
symbolPen = { "width":0 ,"color":self.colors[idx] } ,
|
76
|
+
symbolSize =SYMBOL_SIZES[idx] ,
|
55
77
|
)
|
56
78
|
self.update()
|
57
79
|
|
58
|
-
def
|
59
|
-
|
60
|
-
self.
|
80
|
+
def set_x_label(self, label: str) -> None:
|
81
|
+
"""Set the X-axis label."""
|
82
|
+
self.x_name = label
|
83
|
+
self.x_label.setText(f"X:{label}")
|
61
84
|
|
62
|
-
def
|
63
|
-
|
64
|
-
self.
|
85
|
+
def set_y_label(self, label: str) -> None:
|
86
|
+
"""Set the Y-axis label."""
|
87
|
+
self.y_name = label
|
88
|
+
self.y_label.setText(f"Y:{label}")
|
65
89
|
|
66
|
-
def auto_range(self):
|
90
|
+
def auto_range(self) -> None:
|
91
|
+
"""Automatically adjust plot range to show all data."""
|
67
92
|
self.plotItem.vb.autoRange()
|
68
93
|
|
69
|
-
def enable_auto_range(self):
|
94
|
+
def enable_auto_range(self) -> None:
|
95
|
+
"""Enable automatic range adjustment."""
|
70
96
|
self.plotItem.vb.enableAutoRange()
|
71
97
|
|
72
|
-
def keyPressEvent(self,
|
73
|
-
|
74
|
-
|
75
|
-
|
98
|
+
def keyPressEvent(self, event):
|
99
|
+
"""Handle keyboard events for plot control.
|
100
|
+
|
101
|
+
Keys:
|
102
|
+
F/f: Auto-range the plot
|
103
|
+
A/a: Enable auto-pan
|
104
|
+
R/r: Enable range selection mode
|
105
|
+
"""
|
106
|
+
key = event.text().lower()
|
107
|
+
if key == 'f':
|
76
108
|
self.plotItem.vb.autoRange()
|
77
|
-
|
109
|
+
elif key == 'a':
|
78
110
|
self.plotItem.vb.setAutoPan()
|
79
|
-
|
111
|
+
elif key == 'r':
|
80
112
|
self.range_select = True
|
81
|
-
super().keyPressEvent(
|
113
|
+
super().keyPressEvent(event)
|
82
114
|
|
83
|
-
def keyReleaseEvent(self,
|
84
|
-
|
85
|
-
|
86
|
-
if
|
87
|
-
self.plotItem.vb.autoRange()
|
88
|
-
if ('a' == tx or 'A' == tx):
|
89
|
-
self.plotItem.vb.setAutoPan()
|
90
|
-
if ('r' == tx or 'R' == tx):
|
115
|
+
def keyReleaseEvent(self, event):
|
116
|
+
"""Handle key release events."""
|
117
|
+
key = event.text().lower()
|
118
|
+
if key == 'r':
|
91
119
|
self.range_select = False
|
92
|
-
super().keyReleaseEvent(
|
93
|
-
|
94
|
-
def mousePressEvent(self,
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
# print("Mouse is pressed")
|
99
|
-
self.clippos1 = self.plotItem.vb.mapSceneToView(ev.pos()).x()
|
120
|
+
super().keyReleaseEvent(event)
|
121
|
+
|
122
|
+
def mousePressEvent(self, event):
|
123
|
+
"""Handle mouse press events for data selection."""
|
124
|
+
if event.button() == 4 and HAS_CLIPBOARD:
|
125
|
+
self.clip_pos_start = self.plotItem.vb.mapSceneToView(event.pos()).x()
|
100
126
|
else:
|
101
|
-
super().mousePressEvent(
|
102
|
-
|
103
|
-
def mouseReleaseEvent(self,
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
self.
|
109
|
-
|
110
|
-
|
111
|
-
else:
|
112
|
-
pc.copy(self.clippos2)
|
127
|
+
super().mousePressEvent(event)
|
128
|
+
|
129
|
+
def mouseReleaseEvent(self, event):
|
130
|
+
"""Handle mouse release events and copy selected data to clipboard."""
|
131
|
+
if event.button() == 4 and HAS_CLIPBOARD:
|
132
|
+
self.clip_pos_end = self.plotItem.vb.mapSceneToView(event.pos()).x()
|
133
|
+
if self.range_select:
|
134
|
+
pc.copy(f"{self.clip_pos_start},{self.clip_pos_end}")
|
135
|
+
else:
|
136
|
+
pc.copy(str(self.clip_pos_end))
|
113
137
|
else:
|
114
|
-
super().mouseReleaseEvent(
|
138
|
+
super().mouseReleaseEvent(event)
|
115
139
|
|
116
140
|
def update(self):
|
117
141
|
super().update()
|
118
142
|
|
119
|
-
def set_data(self,
|
120
|
-
|
143
|
+
def set_data(self, index: int, x_data, y_data) -> None:
|
144
|
+
"""Update plot data for the specified index."""
|
145
|
+
self.plots[index].setData(x_data, y_data)
|
121
146
|
|
122
147
|
# def mouseDoubleClickEvent(self, ev):
|
123
148
|
# super().mouseDoubleClickEvent(ev)
|
qulab/monitor/qt_compat.py
CHANGED
@@ -1,16 +1,45 @@
|
|
1
|
+
"""
|
2
|
+
QuLab Monitor Qt Compatibility Module
|
3
|
+
|
4
|
+
This module provides compatibility layer for Qt constants and enums across
|
5
|
+
different Qt bindings (PyQt5, PyQt6, PySide2, PySide6). It ensures consistent
|
6
|
+
access to Qt constants regardless of the Qt binding being used.
|
7
|
+
|
8
|
+
The module exports the following constants:
|
9
|
+
- AlignRight: Right alignment flag
|
10
|
+
- BottomDockWidgetArea: Bottom dock widget area constant
|
11
|
+
- ScrollBarAlwaysOn: Always show scrollbar policy
|
12
|
+
- ScrollBarAlwaysOff: Never show scrollbar policy
|
13
|
+
- TopDockWidgetArea: Top dock widget area constant
|
14
|
+
"""
|
15
|
+
|
1
16
|
from matplotlib.backends.qt_compat import QT_API, QtCore, QtWidgets
|
2
17
|
|
18
|
+
# Define Qt constants based on the Qt binding being used
|
3
19
|
if QT_API in ['PySide6', 'PyQt6']:
|
20
|
+
# Qt6 uses enum flags
|
4
21
|
AlignRight = QtCore.Qt.AlignmentFlag.AlignRight
|
5
22
|
BottomDockWidgetArea = QtCore.Qt.DockWidgetArea.BottomDockWidgetArea
|
6
23
|
ScrollBarAlwaysOn = QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn
|
7
24
|
ScrollBarAlwaysOff = QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
|
8
25
|
TopDockWidgetArea = QtCore.Qt.DockWidgetArea.TopDockWidgetArea
|
9
26
|
elif QT_API in ['PyQt5', 'PySide2']:
|
27
|
+
# Qt5 uses direct constants
|
10
28
|
AlignRight = QtCore.Qt.AlignRight
|
11
29
|
BottomDockWidgetArea = QtCore.Qt.BottomDockWidgetArea
|
12
30
|
ScrollBarAlwaysOn = QtCore.Qt.ScrollBarAlwaysOn
|
13
31
|
ScrollBarAlwaysOff = QtCore.Qt.ScrollBarAlwaysOff
|
14
32
|
TopDockWidgetArea = QtCore.Qt.TopDockWidgetArea
|
15
33
|
else:
|
16
|
-
raise
|
34
|
+
raise ValueError(f"Unsupported Qt binding: {QT_API}")
|
35
|
+
|
36
|
+
# Export all constants
|
37
|
+
__all__ = [
|
38
|
+
'QtCore',
|
39
|
+
'QtWidgets',
|
40
|
+
'AlignRight',
|
41
|
+
'BottomDockWidgetArea',
|
42
|
+
'ScrollBarAlwaysOn',
|
43
|
+
'ScrollBarAlwaysOff',
|
44
|
+
'TopDockWidgetArea',
|
45
|
+
]
|