QuLab 2.10.10__cp313-cp313-macosx_10_13_universal2.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.
Files changed (107) hide show
  1. qulab/__init__.py +33 -0
  2. qulab/__main__.py +4 -0
  3. qulab/cli/__init__.py +0 -0
  4. qulab/cli/commands.py +30 -0
  5. qulab/cli/config.py +170 -0
  6. qulab/cli/decorators.py +28 -0
  7. qulab/dicttree.py +523 -0
  8. qulab/executor/__init__.py +5 -0
  9. qulab/executor/analyze.py +188 -0
  10. qulab/executor/cli.py +434 -0
  11. qulab/executor/load.py +563 -0
  12. qulab/executor/registry.py +185 -0
  13. qulab/executor/schedule.py +543 -0
  14. qulab/executor/storage.py +615 -0
  15. qulab/executor/template.py +259 -0
  16. qulab/executor/utils.py +194 -0
  17. qulab/expression.py +827 -0
  18. qulab/fun.cpython-313-darwin.so +0 -0
  19. qulab/monitor/__init__.py +1 -0
  20. qulab/monitor/__main__.py +8 -0
  21. qulab/monitor/config.py +41 -0
  22. qulab/monitor/dataset.py +77 -0
  23. qulab/monitor/event_queue.py +54 -0
  24. qulab/monitor/mainwindow.py +234 -0
  25. qulab/monitor/monitor.py +115 -0
  26. qulab/monitor/ploter.py +123 -0
  27. qulab/monitor/qt_compat.py +16 -0
  28. qulab/monitor/toolbar.py +265 -0
  29. qulab/scan/__init__.py +2 -0
  30. qulab/scan/curd.py +221 -0
  31. qulab/scan/models.py +554 -0
  32. qulab/scan/optimize.py +76 -0
  33. qulab/scan/query.py +387 -0
  34. qulab/scan/record.py +603 -0
  35. qulab/scan/scan.py +1166 -0
  36. qulab/scan/server.py +450 -0
  37. qulab/scan/space.py +213 -0
  38. qulab/scan/utils.py +234 -0
  39. qulab/storage/__init__.py +0 -0
  40. qulab/storage/__main__.py +51 -0
  41. qulab/storage/backend/__init__.py +0 -0
  42. qulab/storage/backend/redis.py +204 -0
  43. qulab/storage/base_dataset.py +352 -0
  44. qulab/storage/chunk.py +60 -0
  45. qulab/storage/dataset.py +127 -0
  46. qulab/storage/file.py +273 -0
  47. qulab/storage/models/__init__.py +22 -0
  48. qulab/storage/models/base.py +4 -0
  49. qulab/storage/models/config.py +28 -0
  50. qulab/storage/models/file.py +89 -0
  51. qulab/storage/models/ipy.py +58 -0
  52. qulab/storage/models/models.py +88 -0
  53. qulab/storage/models/record.py +161 -0
  54. qulab/storage/models/report.py +22 -0
  55. qulab/storage/models/tag.py +93 -0
  56. qulab/storage/storage.py +95 -0
  57. qulab/sys/__init__.py +2 -0
  58. qulab/sys/chat.py +688 -0
  59. qulab/sys/device/__init__.py +3 -0
  60. qulab/sys/device/basedevice.py +255 -0
  61. qulab/sys/device/loader.py +86 -0
  62. qulab/sys/device/utils.py +79 -0
  63. qulab/sys/drivers/FakeInstrument.py +68 -0
  64. qulab/sys/drivers/__init__.py +0 -0
  65. qulab/sys/ipy_events.py +125 -0
  66. qulab/sys/net/__init__.py +0 -0
  67. qulab/sys/net/bencoder.py +205 -0
  68. qulab/sys/net/cli.py +169 -0
  69. qulab/sys/net/dhcp.py +543 -0
  70. qulab/sys/net/dhcpd.py +176 -0
  71. qulab/sys/net/kad.py +1142 -0
  72. qulab/sys/net/kcp.py +192 -0
  73. qulab/sys/net/nginx.py +194 -0
  74. qulab/sys/progress.py +190 -0
  75. qulab/sys/rpc/__init__.py +0 -0
  76. qulab/sys/rpc/client.py +0 -0
  77. qulab/sys/rpc/exceptions.py +96 -0
  78. qulab/sys/rpc/msgpack.py +1052 -0
  79. qulab/sys/rpc/msgpack.pyi +41 -0
  80. qulab/sys/rpc/router.py +35 -0
  81. qulab/sys/rpc/rpc.py +412 -0
  82. qulab/sys/rpc/serialize.py +139 -0
  83. qulab/sys/rpc/server.py +29 -0
  84. qulab/sys/rpc/socket.py +29 -0
  85. qulab/sys/rpc/utils.py +25 -0
  86. qulab/sys/rpc/worker.py +0 -0
  87. qulab/sys/rpc/zmq_socket.py +227 -0
  88. qulab/tools/__init__.py +0 -0
  89. qulab/tools/connection_helper.py +39 -0
  90. qulab/typing.py +2 -0
  91. qulab/utils.py +95 -0
  92. qulab/version.py +1 -0
  93. qulab/visualization/__init__.py +188 -0
  94. qulab/visualization/__main__.py +71 -0
  95. qulab/visualization/_autoplot.py +464 -0
  96. qulab/visualization/plot_circ.py +319 -0
  97. qulab/visualization/plot_layout.py +408 -0
  98. qulab/visualization/plot_seq.py +242 -0
  99. qulab/visualization/qdat.py +152 -0
  100. qulab/visualization/rot3d.py +23 -0
  101. qulab/visualization/widgets.py +86 -0
  102. qulab-2.10.10.dist-info/METADATA +110 -0
  103. qulab-2.10.10.dist-info/RECORD +107 -0
  104. qulab-2.10.10.dist-info/WHEEL +5 -0
  105. qulab-2.10.10.dist-info/entry_points.txt +2 -0
  106. qulab-2.10.10.dist-info/licenses/LICENSE +21 -0
  107. qulab-2.10.10.dist-info/top_level.txt +1 -0
Binary file
@@ -0,0 +1 @@
1
+ from .monitor import Monitor, get_monitor
@@ -0,0 +1,8 @@
1
+ import click
2
+
3
+ from .monitor import Monitor
4
+
5
+
6
+ @click.command(name='monitor')
7
+ def main():
8
+ pass
@@ -0,0 +1,41 @@
1
+ import numpy as np
2
+
3
+ style = '''
4
+ QWidget {
5
+ font: medium Ubuntu;
6
+ background-color: #011F2F;
7
+ font-size: 16px;
8
+ font-size: 16px;
9
+ color:#FFFFFF;
10
+ }
11
+ '''
12
+ #
13
+ Nroll = 6
14
+
15
+ # Format of the data.
16
+ forms = {
17
+ "mag": lambda w, ang: np.abs(w),
18
+ "phase": lambda w, ang: np.angle(w),
19
+ "real": lambda w, ang: np.real(w),
20
+ "imag": lambda w, ang: np.imag(w),
21
+ "rot": lambda w, ang: np.real(np.exp(1j * ang) * np.array(w))
22
+ }
23
+ form_keys = list(forms.keys())
24
+ #
25
+ COL_SEL = (0, 0, 0)
26
+ COL_UNSEL = (6, 6, 8)
27
+ #
28
+
29
+ defualt_colors = [
30
+ (200, 0, 0),
31
+ (55, 100, 180),
32
+ (40, 80, 150),
33
+ (30, 50, 110),
34
+ (25, 40, 70),
35
+ (25, 30, 50),
36
+ ]
37
+
38
+ widths = [3, 2, 2, 2, 1, 1]
39
+ SymSize = [5, 0, 0, 0, 0, 0]
40
+ ridx = list(range(Nroll))
41
+ ridx.reverse()
@@ -0,0 +1,77 @@
1
+ from collections import defaultdict, deque
2
+
3
+ from .config import Nroll
4
+
5
+ PAUSE_TIME = 1
6
+
7
+ Number = int | float | complex
8
+
9
+
10
+ def remove_duplicates(input_list: list[str]) -> list[str]:
11
+ """
12
+ Remove duplicates from a list of strings, keeping the order of the elements.
13
+ """
14
+ return list(dict.fromkeys(input_list))
15
+
16
+
17
+ class Dataset():
18
+
19
+ def __init__(self):
20
+ self.column_names = []
21
+ self.box = deque(maxlen=Nroll)
22
+ self.dirty = True
23
+
24
+ def clear(self):
25
+ self.box.clear()
26
+
27
+ def clear_history(self):
28
+ o = self.box.popleft()
29
+ self.box.clear()
30
+ self.box.appendleft(o)
31
+
32
+ def set_column_names(self, column_names: list[str]):
33
+ column_names = remove_duplicates(column_names)
34
+ if column_names != self.column_names:
35
+ self.clear()
36
+ self.column_names = column_names
37
+
38
+ def get_data(self, step: int, xname: str,
39
+ yname: str) -> tuple[list[Number], list[Number]]:
40
+ try:
41
+ b = self.box[step]
42
+ except IndexError:
43
+ return [], []
44
+ return b[xname], b[yname]
45
+
46
+ def append(self, dataframe: list[Number] | list[list[Number]]):
47
+ if not dataframe:
48
+ return
49
+ try:
50
+ iter(dataframe[0]) # test if dataframe is a list of list
51
+ self._append_traces(dataframe)
52
+ except TypeError:
53
+ self._append_points(dataframe)
54
+
55
+ def roll(self):
56
+ self.box.appendleft(defaultdict(list))
57
+
58
+ def _append_points(self, points: list[Number]):
59
+ self.dirty = True
60
+ assert (len(points) == len(
61
+ self.column_names)), (f"-PointDataBox\n"
62
+ f"-ap\n"
63
+ f"-Length Must be same\n"
64
+ f"the column_names : {self.column_names}\n"
65
+ f"given data : {points}")
66
+ for name, p in zip(self.column_names, points):
67
+ self.box[0][name].append(p)
68
+
69
+ def _append_traces(self, traces: list[list[Number]]):
70
+ self.dirty = True
71
+ assert (len(traces) == len(
72
+ self.column_names)), (f"-TraceDataBox\n"
73
+ f"-at\n"
74
+ f"-Length Must be same\n"
75
+ f"the column_names : {self.column_names}\n"
76
+ f"given data : {traces}")
77
+ self.box.appendleft(dict(zip(self.column_names, traces)))
@@ -0,0 +1,54 @@
1
+ import warnings
2
+ from multiprocessing import Queue
3
+
4
+ from .dataset import Dataset
5
+ from .toolbar import ToolBar
6
+
7
+
8
+ class EventQueue():
9
+
10
+ def __init__(self, queue: Queue, toolbar: ToolBar, point_dataset: Dataset,
11
+ trace_dataset: Dataset):
12
+ self.toolbar = toolbar
13
+ self.queue = queue
14
+ self.point_dataset = point_dataset
15
+ self.trace_dataset = trace_dataset
16
+
17
+ def flush(self):
18
+ while (not self.queue.empty()):
19
+ words = self.queue.get()
20
+ try:
21
+ assert (isinstance(
22
+ words, tuple)), "QueueHandler - fifo Content must be tuple"
23
+ assert (
24
+ len(words) == 2
25
+ ), "QueueHandler -the tuple must be like (\"command_type\" , \"DATA\")"
26
+ cmd, data = words
27
+ assert (isinstance(
28
+ cmd, str)), "QueueHandler - the command should be a string"
29
+ except AssertionError as e:
30
+ warnings.warn(e.args[0])
31
+ continue
32
+
33
+ self.handle(cmd, data)
34
+
35
+ def handle(self, cmd, data):
36
+ match cmd:
37
+ case "PN":
38
+ self.point_dataset.set_column_names(data)
39
+ self.toolbar.refresh_comb()
40
+ case "TN":
41
+ self.trace_dataset.set_column_names(data)
42
+ self.toolbar.refresh_comb()
43
+ case "PD":
44
+ self.point_dataset.append(data)
45
+ case "TD":
46
+ self.trace_dataset.append(data)
47
+ case "ROLL":
48
+ self.point_dataset.roll()
49
+ case "PXY":
50
+ self.toolbar.set_point_text(data)
51
+ case "TXY":
52
+ self.toolbar.set_trace_text(data)
53
+ case _:
54
+ warnings.warn(f"QueueHandler - unknown command : {cmd}")
@@ -0,0 +1,234 @@
1
+ from multiprocessing import Queue
2
+ from typing import Literal
3
+
4
+ from .config import forms, ridx, style
5
+ from .dataset import Dataset
6
+ from .event_queue import EventQueue
7
+ from .ploter import PlotWidget
8
+ from .qt_compat import (BottomDockWidgetArea, QtCore, QtWidgets,
9
+ ScrollBarAlwaysOff, ScrollBarAlwaysOn,
10
+ TopDockWidgetArea)
11
+ from .toolbar import ToolBar
12
+
13
+
14
+ class MainWindow(QtWidgets.QMainWindow):
15
+
16
+ def __init__(self,
17
+ queue: Queue,
18
+ ncols=3,
19
+ plot_minimum_height=350,
20
+ plot_colors: list[tuple[int, int, int]] | None = None):
21
+ super().__init__()
22
+ self.ncols = ncols
23
+ self.need_reshuffled = False
24
+ self.plot_minimum_height = plot_minimum_height
25
+ self.plot_widgets: list[PlotWidget] = []
26
+ self.plot_colors = plot_colors
27
+ self.toolbar = ToolBar()
28
+ self.trace_data_box = Dataset()
29
+ self.point_data_box = Dataset()
30
+ self.queue = EventQueue(queue, self.toolbar, self.point_data_box,
31
+ self.trace_data_box)
32
+
33
+ self.init_ui()
34
+
35
+ self.timer = QtCore.QTimer()
36
+ self.timer.timeout.connect(self.update)
37
+ self.timer.start(250)
38
+
39
+ def init_ui(self):
40
+ self.setStyleSheet(style)
41
+ self.setMinimumHeight(500)
42
+ self.setMinimumWidth(700)
43
+ self.scroll = QtWidgets.QScrollArea(
44
+ ) # Scroll Area which contains the widgets, set as the centralWidget
45
+ self.widget = QtWidgets.QWidget(
46
+ ) # Widget that contains the collection of Vertical Box
47
+ self.layout = QtWidgets.QGridLayout()
48
+ self.widget.setLayout(self.layout)
49
+
50
+ #Scroll Area Properties
51
+ #self.setCorner(Qt.TopSection, Qt.TopDockWidgetArea);
52
+ self.scroll.setVerticalScrollBarPolicy(ScrollBarAlwaysOn)
53
+ self.scroll.setHorizontalScrollBarPolicy(ScrollBarAlwaysOff)
54
+ self.scroll.setWidgetResizable(True)
55
+ self.scroll.setWidget(self.widget)
56
+ self.setCentralWidget(self.scroll)
57
+
58
+ self.dock = QtWidgets.QDockWidget(self)
59
+ self.dock.setAllowedAreas(TopDockWidgetArea | BottomDockWidgetArea)
60
+ self.addDockWidget(TopDockWidgetArea, self.dock)
61
+ self.dock.setFloating(False)
62
+ self.dock.setWidget(self.toolbar)
63
+ self.dock.setFeatures(QtWidgets.QDockWidget.DockWidgetMovable)
64
+ #self.tabifyDockWidget(self.dock,None);
65
+ #self.addDockWidget(self.dock);
66
+ #self.setStatusBar(self.toolbar);
67
+ #self.layout.addWidget(self.toolbar , 0 , 0 , 1, self.ncol);
68
+
69
+ self.setWindowTitle('Scroll multi view')
70
+ self.show()
71
+ self.toolbar.set_mainwindow(self)
72
+ self.toolbar.pb.setChecked(True)
73
+
74
+ @property
75
+ def mode(self) -> Literal["P", "T"]:
76
+ return self.toolbar.mode
77
+
78
+ @property
79
+ def dataset(self) -> Dataset:
80
+ return {"P": self.point_data_box, "T": self.trace_data_box}[self.mode]
81
+
82
+ def set_ncols(self, x: int):
83
+ x = max(1, min(10, int(x)))
84
+ if (x != self.ncols):
85
+ self.need_reshuffled = True
86
+ self.ncols = x
87
+
88
+ def add_subplot(self):
89
+ n = len(self.plot_widgets)
90
+ pw = PlotWidget(self.plot_minimum_height, self.plot_colors)
91
+ self.plot_widgets.append(pw)
92
+ grid_row = n // self.ncols
93
+ grid_col = n % self.ncols
94
+ self.layout.addWidget(pw, grid_row + 1, grid_col)
95
+ return pw
96
+
97
+ def create_subplots(self, xy_pairs):
98
+ for xn, yn in xy_pairs:
99
+ pw = self.add_subplot()
100
+ pw.set_X_label(xn)
101
+ pw.set_Y_label(yn)
102
+ self.do_link()
103
+ self.all_enable_auto_range()
104
+
105
+ def clear_subplots(self):
106
+ for i in range(len(self.plot_widgets)):
107
+ self.layout.removeWidget(self.plot_widgets[i])
108
+ self.plot_widgets[i].setParent(None)
109
+ self.plot_widgets.clear()
110
+
111
+ def remove_plot(self, w: PlotWidget):
112
+ w.setParent(None)
113
+ self.plot_widgets.remove(w)
114
+ self.reshuffle()
115
+
116
+ def drop_last_plot(self, i_=-1):
117
+ # delete the one
118
+ i = int(i_)
119
+ if (i < len(self.plot_widgets)):
120
+ w = self.plot_widgets[i]
121
+ w.setParent(None)
122
+ del w
123
+ del self.plot_widgets[i]
124
+ self.reshuffle()
125
+
126
+ def reshuffle(self):
127
+ for idx, widget in enumerate(self.plot_widgets):
128
+ widget.setParent(None)
129
+ grid_row = idx // self.ncols
130
+ grid_col = idx % self.ncols
131
+ self.layout.addWidget(widget, grid_row + 1, grid_col)
132
+
133
+ def keyPressEvent(self, ev):
134
+ #print(ev.text());
135
+ tx = ev.text()
136
+ if (tx in ['_', '-']):
137
+ self.set_ncols(self.ncols - 1)
138
+ elif (tx in ['=', '+']):
139
+ self.set_ncols(self.ncols + 1)
140
+
141
+ def mouse_click(self):
142
+ pass
143
+
144
+ def do_link(self):
145
+ """
146
+ link the plot
147
+
148
+ share the same x or y axis
149
+ """
150
+ same_X = {}
151
+ xy_pairs = self.toolbar.xypairs
152
+ for idx, xyn in enumerate(xy_pairs):
153
+ xn, yn = xyn
154
+ if xn not in same_X:
155
+ same_X[xn] = []
156
+ same_X[xn].append(idx)
157
+
158
+ sharex, sharey = self.toolbar.sharexy()
159
+
160
+ s_A = not (sharex and sharey)
161
+ for x, yidxs in same_X.items():
162
+ pre_yidx = -1
163
+ for yidx in yidxs:
164
+ if (-1 != pre_yidx):
165
+ if (s_A):
166
+ self.plot_widgets[pre_yidx].plotItem.vb.setXLink(None)
167
+ self.plot_widgets[pre_yidx].plotItem.vb.setYLink(None)
168
+
169
+ if sharex:
170
+ self.plot_widgets[pre_yidx].plotItem.vb.setXLink(
171
+ self.plot_widgets[yidx].plotItem.vb)
172
+
173
+ if sharey:
174
+ self.plot_widgets[pre_yidx].plotItem.vb.setYLink(
175
+ self.plot_widgets[yidx].plotItem.vb)
176
+ pre_yidx = yidx
177
+
178
+ def all_auto_range(self):
179
+ for pw in self.plot_widgets:
180
+ pw.auto_range()
181
+
182
+ def all_enable_auto_range(self):
183
+ for pw in self.plot_widgets:
184
+ pw.enable_auto_range()
185
+
186
+ def update(self):
187
+ # update the queue
188
+ self.queue.flush()
189
+
190
+ rescale = False
191
+
192
+ # setup the xyfm
193
+ if (self.toolbar.xypairs_dirty):
194
+ self.clear_subplots()
195
+ self.create_subplots(self.toolbar.xypairs)
196
+ self.toolbar.xypairs_dirty = False
197
+ rescale = True
198
+
199
+ if (self.toolbar.link_dirty):
200
+ self.do_link()
201
+ self.toolbar.link_dirty = False
202
+
203
+ if (self.need_reshuffled):
204
+ self.need_reshuffled = False
205
+ self.reshuffle()
206
+
207
+ # checking the log space
208
+ if (self.toolbar.xyfm_dirty):
209
+ for pw in self.plot_widgets:
210
+ pw.plotItem.ctrl.logXCheck.setChecked(self.toolbar.lx)
211
+ pw.plotItem.ctrl.logYCheck.setChecked(self.toolbar.ly)
212
+
213
+ #update the plot
214
+ # if clear is set then do the clear :
215
+ if (self.toolbar.CR_flag):
216
+ self.toolbar.CR_flag = False
217
+ self.dataset.clear_history()
218
+ self.dataset.dirty = True
219
+
220
+ if (self.dataset.dirty or self.toolbar.xyfm_dirty or rescale):
221
+ self.dataset.dirty = False
222
+ self.xyfm_dirty = False
223
+ for pw in self.plot_widgets:
224
+
225
+ fx = forms[self.toolbar.fx]
226
+ fy = forms[self.toolbar.fy]
227
+ for i in ridx:
228
+ x, y = self.dataset.get_data(i, pw.xname, pw.yname)
229
+ l = min(len(x), len(y))
230
+ x, y = fx(x[:l], 0), fy(y[:l], 0)
231
+ pw.set_data(i, x, y)
232
+ pw.update()
233
+ if rescale:
234
+ pw.auto_range()
@@ -0,0 +1,115 @@
1
+ import multiprocessing as mp
2
+ import sys
3
+
4
+ # try:
5
+ # mp.set_start_method("spawn")
6
+ # except:
7
+ # pass
8
+
9
+
10
+ def main(queue: mp.Queue,
11
+ ncols: int = 4,
12
+ minimum_height: int = 400,
13
+ colors: list[tuple[int, int, int]] = []):
14
+ from .mainwindow import MainWindow
15
+ from .qt_compat import QtWidgets
16
+
17
+ app = QtWidgets.QApplication(sys.argv)
18
+ main = MainWindow(queue, ncols, minimum_height, colors)
19
+ sys.exit(app.exec())
20
+
21
+
22
+ class Monitor():
23
+
24
+ def __init__(self,
25
+ number_of_columns: int = 4,
26
+ minimum_height: int = 400,
27
+ colors: list[tuple[int, int, int]] = []):
28
+ self.colors = [tuple(color) for color in colors]
29
+ self.number_of_columns = number_of_columns
30
+ self.minimum_height = minimum_height
31
+ self.queue = mp.Queue(20)
32
+ self.process = None
33
+ self.start()
34
+
35
+ def start(self):
36
+ if self.process is not None and self.process.is_alive():
37
+ return
38
+ self.queue = mp.Queue(20)
39
+ self.process = mp.Process(target=main,
40
+ args=(self.queue, self.number_of_columns,
41
+ self.minimum_height, self.colors))
42
+ self.process.start()
43
+
44
+ def _put(self, w: tuple):
45
+ self.queue.put(w)
46
+
47
+ def roll(self):
48
+ self._put(("ROLL", None))
49
+
50
+ def set_column_names(self, *arg):
51
+ self._put(('PN', list(arg)))
52
+
53
+ def add_point(self, *arg):
54
+ self._put(('PD', list(arg)))
55
+
56
+ def set_plots(self, arg):
57
+ """
58
+ arg: str, like "(x,y)" or "(x1,y1);(x2,y2);"
59
+ """
60
+ self._put(('PXY', str(arg)))
61
+
62
+ def set_trace_column_names(self, *arg):
63
+ self._put(('TN', list(arg)))
64
+
65
+ def add_trace(self, *arg):
66
+ self._put(('TD', list(arg)))
67
+
68
+ def set_trace_plots(self, arg):
69
+ """
70
+ arg: str, like "(x,y)" or "(x1,y1);(x2,y2);"
71
+ """
72
+ self._put(('TXY', str(arg)))
73
+
74
+ def __del__(self):
75
+ try:
76
+ self.process.kill()
77
+ except:
78
+ pass
79
+
80
+ def is_alive(self):
81
+ return self.process.is_alive()
82
+
83
+
84
+ _monitor = None
85
+
86
+
87
+ def get_monitor(auto_open=True):
88
+ global _monitor
89
+
90
+ if auto_open and (_monitor is None or not _monitor.is_alive()):
91
+ _monitor = Monitor()
92
+
93
+ return _monitor
94
+
95
+
96
+ if __name__ == "__main__":
97
+ import time
98
+
99
+ import numpy as np
100
+
101
+ for i in range(3):
102
+ index = 0
103
+ while True:
104
+ if index >= 100:
105
+ break
106
+
107
+ m = get_monitor()
108
+
109
+ if index == 0:
110
+ m.set_column_names("index", "H", "S")
111
+ m.set_plots("(index,H);(index,S)")
112
+ m.roll()
113
+ m.add_point(index, np.random.randn(), np.sin(index / 20))
114
+ index += 1
115
+ time.sleep(0.2)
@@ -0,0 +1,123 @@
1
+ from .config import COL_SEL, COL_UNSEL, SymSize, defualt_colors, ridx, widths
2
+ from .qt_compat import QtWidgets
3
+
4
+ # the plotting widget
5
+ try:
6
+ import pyqtgraph as pg
7
+ except:
8
+ raise ImportError("Please install pyqtgraph first")
9
+
10
+ try:
11
+ import pyperclip as pc
12
+ hasCliper = True
13
+ except:
14
+ hasCliper = False
15
+
16
+
17
+ class PlotWidget(pg.PlotWidget):
18
+
19
+ def __init__(self, minimum_height=300, colors=None):
20
+ self.XAxisLinked = False
21
+ self.YAxisLinked = False
22
+ if colors is None:
23
+ colors = defualt_colors
24
+ elif len(colors) < len(defualt_colors):
25
+ colors.extend(defualt_colors[len(colors):])
26
+ self.colors = colors
27
+ self.xname = ""
28
+ self.yname = ""
29
+ super().__init__()
30
+
31
+ self.setMinimumHeight(minimum_height)
32
+ self.showGrid(x=True, y=True)
33
+ self.setBackground(COL_UNSEL)
34
+
35
+ self.plotItem.vb.autoRange()
36
+
37
+ ## Labeling
38
+ self.XLabel = QtWidgets.QLabel(self)
39
+ self.XLabel.setText("X:")
40
+ self.XLabel.move(0, 5)
41
+ self.YLabel = QtWidgets.QLabel(self)
42
+ self.YLabel.setText("Y:")
43
+ self.YLabel.move(0, 35)
44
+
45
+ self.plots = {}
46
+ self.clippos1 = 0
47
+ self.clippos2 = 0
48
+ self.range_select = False
49
+ for i in ridx:
50
+ self.plots[i] = \
51
+ self.plot([],[] ,pen={"color":self.colors[i] ,"width":widths[i]} ,
52
+ symbolBrush = self.colors[i],
53
+ symbolPen = { "width":0 ,"color":self.colors[i] } ,
54
+ symbolSize =SymSize[i] ,
55
+ )
56
+ self.update()
57
+
58
+ def set_X_label(self, w):
59
+ self.xname = w
60
+ self.XLabel.setText(f"X:{w}")
61
+
62
+ def set_Y_label(self, w):
63
+ self.yname = w
64
+ self.YLabel.setText(f"Y:{w}")
65
+
66
+ def auto_range(self):
67
+ self.plotItem.vb.autoRange()
68
+
69
+ def enable_auto_range(self):
70
+ self.plotItem.vb.enableAutoRange()
71
+
72
+ def keyPressEvent(self, ev):
73
+ #print(ev.text());
74
+ tx = ev.text()
75
+ if ('f' == tx or 'F' == tx):
76
+ self.plotItem.vb.autoRange()
77
+ if ('a' == tx or 'A' == tx):
78
+ self.plotItem.vb.setAutoPan()
79
+ if ('r' == tx or 'R' == tx):
80
+ self.range_select = True
81
+ super().keyPressEvent(ev)
82
+
83
+ def keyReleaseEvent(self, ev):
84
+ #print(ev.text());
85
+ tx = ev.text()
86
+ if ('f' == tx or 'F' == tx):
87
+ self.plotItem.vb.autoRange()
88
+ if ('a' == tx or 'A' == tx):
89
+ self.plotItem.vb.setAutoPan()
90
+ if ('r' == tx or 'R' == tx):
91
+ self.range_select = False
92
+ super().keyReleaseEvent(ev)
93
+
94
+ def mousePressEvent(self, ev):
95
+ if (4 == ev.button()):
96
+ # print(ev.flags())
97
+ if (hasCliper):
98
+ # print("Mouse is pressed")
99
+ self.clippos1 = self.plotItem.vb.mapSceneToView(ev.pos()).x()
100
+ else:
101
+ super().mousePressEvent(ev)
102
+
103
+ def mouseReleaseEvent(self, ev):
104
+ if (4 == ev.button()):
105
+ p = ev.pos()
106
+ if (hasCliper):
107
+ # print("Mouse is released")
108
+ self.clippos2 = self.plotItem.vb.mapSceneToView(ev.pos()).x()
109
+ if (self.range_select):
110
+ pc.copy(f"{self.clippos1},{self.clippos2}")
111
+ else:
112
+ pc.copy(self.clippos2)
113
+ else:
114
+ super().mouseReleaseEvent(ev)
115
+
116
+ def update(self):
117
+ super().update()
118
+
119
+ def set_data(self, i, x, y):
120
+ self.plots[i].setData(x, y)
121
+
122
+ # def mouseDoubleClickEvent(self, ev):
123
+ # super().mouseDoubleClickEvent(ev)
@@ -0,0 +1,16 @@
1
+ from matplotlib.backends.qt_compat import QT_API, QtCore, QtWidgets
2
+
3
+ if QT_API in ['PySide6', 'PyQt6']:
4
+ AlignRight = QtCore.Qt.AlignmentFlag.AlignRight
5
+ BottomDockWidgetArea = QtCore.Qt.DockWidgetArea.BottomDockWidgetArea
6
+ ScrollBarAlwaysOn = QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOn
7
+ ScrollBarAlwaysOff = QtCore.Qt.ScrollBarPolicy.ScrollBarAlwaysOff
8
+ TopDockWidgetArea = QtCore.Qt.DockWidgetArea.TopDockWidgetArea
9
+ elif QT_API in ['PyQt5', 'PySide2']:
10
+ AlignRight = QtCore.Qt.AlignRight
11
+ BottomDockWidgetArea = QtCore.Qt.BottomDockWidgetArea
12
+ ScrollBarAlwaysOn = QtCore.Qt.ScrollBarAlwaysOn
13
+ ScrollBarAlwaysOff = QtCore.Qt.ScrollBarAlwaysOff
14
+ TopDockWidgetArea = QtCore.Qt.TopDockWidgetArea
15
+ else:
16
+ raise AssertionError(f"Unexpected QT_API: {QT_API}")