qplotter 1.6.0b3__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.
- qplot/__init__.py +46 -0
- qplot/__main__.py +232 -0
- qplot/_auxiliary_paths.py +63 -0
- qplot/_metadata.py +12 -0
- qplot/_repair.py +14 -0
- qplot/_shutdown_supervisor.py +4049 -0
- qplot/_version.py +12 -0
- qplot/_windows_shutdown_job.py +1166 -0
- qplot/configuration/__init__.py +3 -0
- qplot/configuration/config.py +573 -0
- qplot/configuration/config_schema.json +319 -0
- qplot/configuration/scripts.py +255 -0
- qplot/configuration/themes/__init__.py +13 -0
- qplot/configuration/themes/_base.py +579 -0
- qplot/configuration/themes/blank.py +17 -0
- qplot/configuration/themes/dark.py +54 -0
- qplot/configuration/themes/light.py +54 -0
- qplot/datahandling/LoadFromDB.py +224 -0
- qplot/datahandling/__init__.py +47 -0
- qplot/datahandling/_trusted_live_helper.py +812 -0
- qplot/datahandling/_trusted_live_protocol.py +1745 -0
- qplot/datahandling/database.py +2330 -0
- qplot/datahandling/dimensions.py +43 -0
- qplot/datahandling/file_identity.py +461 -0
- qplot/datahandling/parameter_data.py +149 -0
- qplot/datahandling/plot_progress.py +17 -0
- qplot/datahandling/qcodes_cache.py +277 -0
- qplot/datahandling/qcodes_compat.py +14 -0
- qplot/datahandling/readSQL.py +2442 -0
- qplot/datahandling/readonly.py +2561 -0
- qplot/datahandling/source_activity.py +21 -0
- qplot/datahandling/trusted_array_preview.py +127 -0
- qplot/datahandling/trusted_derived_cache.py +1605 -0
- qplot/datahandling/trusted_derived_rendering.py +868 -0
- qplot/datahandling/trusted_heatmap.py +241 -0
- qplot/datahandling/trusted_live.py +2808 -0
- qplot/datahandling/trusted_live_queries.py +5907 -0
- qplot/datahandling/trusted_live_service.py +1894 -0
- qplot/datahandling/trusted_live_supervisor.py +2161 -0
- qplot/datahandling/trusted_plot.py +283 -0
- qplot/datahandling/trusted_presentation.py +1065 -0
- qplot/datahandling/trusted_snapshot.py +1132 -0
- qplot/datahandling/trusted_work_coordinator.py +2157 -0
- qplot/datahandling/trusted_work_scheduler.py +1204 -0
- qplot/diagnostics.py +241 -0
- qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
- qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
- qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
- qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
- qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
- qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
- qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
- qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
- qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
- qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
- qplot/testdata.py +1733 -0
- qplot/tools/__init__.py +13 -0
- qplot/tools/finite_means.py +114 -0
- qplot/tools/general.py +134 -0
- qplot/tools/heatmap_geometry.py +381 -0
- qplot/tools/operation_registry.py +179 -0
- qplot/tools/plot_tools.py +625 -0
- qplot/tools/sample_statistics.py +56 -0
- qplot/tools/worker.py +2787 -0
- qplot/windows/__init__.py +9 -0
- qplot/windows/_colorbar.py +422 -0
- qplot/windows/_commands.py +523 -0
- qplot/windows/_config_persistence.py +123 -0
- qplot/windows/_database_actions.py +4904 -0
- qplot/windows/_dataset_handle.py +221 -0
- qplot/windows/_dragdrop.py +71 -0
- qplot/windows/_export_paths.py +1167 -0
- qplot/windows/_help.py +169 -0
- qplot/windows/_native_averaging.py +173 -0
- qplot/windows/_native_transforms.py +389 -0
- qplot/windows/_plot1d_snap.py +608 -0
- qplot/windows/_plot1d_traces.py +2183 -0
- qplot/windows/_plot2d_colorbar.py +1126 -0
- qplot/windows/_plot2d_colorbar_dialog.py +786 -0
- qplot/windows/_plot2d_layers.py +2197 -0
- qplot/windows/_plot2d_sweeps.py +712 -0
- qplot/windows/_plotWin.py +1993 -0
- qplot/windows/_plot_actions.py +2695 -0
- qplot/windows/_plot_appearance.py +276 -0
- qplot/windows/_plot_axis_scaling.py +1485 -0
- qplot/windows/_plot_export.py +1445 -0
- qplot/windows/_plot_feedback.py +110 -0
- qplot/windows/_plot_marquee.py +656 -0
- qplot/windows/_plot_refresh.py +942 -0
- qplot/windows/_plot_state.py +239 -0
- qplot/windows/_plot_transform_labels.py +60 -0
- qplot/windows/_preferences.py +635 -0
- qplot/windows/_refresh_interval.py +70 -0
- qplot/windows/_run_controls.py +828 -0
- qplot/windows/_shortcuts.py +47 -0
- qplot/windows/_subplots/__init__.py +8 -0
- qplot/windows/_subplots/subplot1d.py +531 -0
- qplot/windows/_subplots/subplot2d.py +918 -0
- qplot/windows/_svg_export.py +209 -0
- qplot/windows/_trusted_derived_qt.py +1759 -0
- qplot/windows/_widgets/__init__.py +23 -0
- qplot/windows/_widgets/_run_formatting.py +387 -0
- qplot/windows/_widgets/details_tables.py +1119 -0
- qplot/windows/_widgets/dropbox.py +290 -0
- qplot/windows/_widgets/operations.py +460 -0
- qplot/windows/_widgets/preview.py +2997 -0
- qplot/windows/_widgets/run_list_items.py +488 -0
- qplot/windows/_widgets/toolbar.py +111 -0
- qplot/windows/_widgets/treeWidgets.py +2572 -0
- qplot/windows/_window_controls.py +413 -0
- qplot/windows/main.py +1635 -0
- qplot/windows/plot1d.py +311 -0
- qplot/windows/plot2d.py +1901 -0
- qplotter-1.6.0b3.dist-info/METADATA +318 -0
- qplotter-1.6.0b3.dist-info/RECORD +119 -0
- qplotter-1.6.0b3.dist-info/WHEEL +5 -0
- qplotter-1.6.0b3.dist-info/entry_points.txt +4 -0
- qplotter-1.6.0b3.dist-info/licenses/LICENSE +21 -0
- qplotter-1.6.0b3.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
from time import perf_counter
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from PyQt6 import QtCore
|
|
5
|
+
from PyQt6 import QtWidgets as qtw
|
|
6
|
+
|
|
7
|
+
from qplot.datahandling.plot_progress import PlotProgress
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class PlotStateOverlay(QtCore.QObject):
|
|
11
|
+
"""
|
|
12
|
+
Lightweight status overlay shown inside a plot widget.
|
|
13
|
+
|
|
14
|
+
The status bar is easy to miss while a blank plot is loading. This overlay
|
|
15
|
+
keeps transient plot states visible in the plot area without intercepting
|
|
16
|
+
mouse interaction once it is hidden.
|
|
17
|
+
"""
|
|
18
|
+
|
|
19
|
+
_styles: dict[str, str] = {
|
|
20
|
+
"info": (
|
|
21
|
+
"background-color: rgba(250, 252, 255, 235);"
|
|
22
|
+
"border: 1px solid rgba(119, 135, 153, 190);"
|
|
23
|
+
"color: #1f2933;"
|
|
24
|
+
),
|
|
25
|
+
"loading": (
|
|
26
|
+
"background-color: rgba(246, 251, 255, 238);"
|
|
27
|
+
"border: 1px solid rgba(70, 130, 180, 190);"
|
|
28
|
+
"color: #102a43;"
|
|
29
|
+
),
|
|
30
|
+
"empty": (
|
|
31
|
+
"background-color: rgba(255, 251, 235, 238);"
|
|
32
|
+
"border: 1px solid rgba(180, 132, 35, 190);"
|
|
33
|
+
"color: #3b2f12;"
|
|
34
|
+
),
|
|
35
|
+
"error": (
|
|
36
|
+
"background-color: rgba(255, 245, 245, 240);"
|
|
37
|
+
"border: 1px solid rgba(190, 72, 72, 200);"
|
|
38
|
+
"color: #3b0d0c;"
|
|
39
|
+
),
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
def __init__(self, target: qtw.QWidget) -> None:
|
|
43
|
+
super().__init__(target)
|
|
44
|
+
self.owner: qtw.QWidget = target
|
|
45
|
+
target_obj: Any = target
|
|
46
|
+
viewport = target_obj.viewport() if hasattr(target_obj, "viewport") else None
|
|
47
|
+
self.target: qtw.QWidget = viewport or target
|
|
48
|
+
|
|
49
|
+
self.frame = qtw.QFrame(self.target)
|
|
50
|
+
self.frame.setObjectName("plotStateOverlay")
|
|
51
|
+
self.frame.setAttribute(QtCore.Qt.WidgetAttribute.WA_TransparentForMouseEvents, True)
|
|
52
|
+
self.frame.setAutoFillBackground(False)
|
|
53
|
+
|
|
54
|
+
layout = qtw.QVBoxLayout(self.frame)
|
|
55
|
+
layout.setContentsMargins(14, 10, 14, 10)
|
|
56
|
+
layout.setSpacing(4)
|
|
57
|
+
|
|
58
|
+
self.title_label = qtw.QLabel(self.frame)
|
|
59
|
+
self.title_label.setObjectName("plotStateOverlayTitle")
|
|
60
|
+
title_font = self.title_label.font()
|
|
61
|
+
title_font.setBold(True)
|
|
62
|
+
self.title_label.setFont(title_font)
|
|
63
|
+
self.title_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
64
|
+
layout.addWidget(self.title_label)
|
|
65
|
+
|
|
66
|
+
self.detail_label = qtw.QLabel(self.frame)
|
|
67
|
+
self.detail_label.setObjectName("plotStateOverlayDetail")
|
|
68
|
+
self.detail_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
69
|
+
self.detail_label.setWordWrap(True)
|
|
70
|
+
layout.addWidget(self.detail_label)
|
|
71
|
+
|
|
72
|
+
self.progress_bar = qtw.QProgressBar(self.frame)
|
|
73
|
+
self.progress_bar.setAccessibleName("Plot loading progress for the current stage")
|
|
74
|
+
self.progress_bar.setMinimumWidth(260)
|
|
75
|
+
self.progress_bar.setToolTip(
|
|
76
|
+
"Progress within the named stage, not total loading time. Scan progress "
|
|
77
|
+
"covers the captured source range, including partial array records."
|
|
78
|
+
)
|
|
79
|
+
layout.addWidget(self.progress_bar)
|
|
80
|
+
self.elapsed_label = qtw.QLabel(self.frame)
|
|
81
|
+
self.elapsed_label.setObjectName("plotStateOverlayElapsed")
|
|
82
|
+
self.elapsed_label.setAlignment(QtCore.Qt.AlignmentFlag.AlignCenter)
|
|
83
|
+
self.elapsed_label.setAccessibleName("Elapsed plot loading time")
|
|
84
|
+
layout.addWidget(self.elapsed_label)
|
|
85
|
+
self._worker: Any = None
|
|
86
|
+
self._last_progress: PlotProgress | None = None
|
|
87
|
+
self._started_at: float | None = None
|
|
88
|
+
self._rendering = False
|
|
89
|
+
self.progress_timer = QtCore.QTimer(self)
|
|
90
|
+
self.progress_timer.setInterval(100)
|
|
91
|
+
self.progress_timer.timeout.connect(self._poll_progress)
|
|
92
|
+
|
|
93
|
+
self.target.installEventFilter(self)
|
|
94
|
+
if self.owner is not self.target:
|
|
95
|
+
self.owner.installEventFilter(self)
|
|
96
|
+
self.hide()
|
|
97
|
+
|
|
98
|
+
def show(
|
|
99
|
+
self,
|
|
100
|
+
title: object,
|
|
101
|
+
detail: object | None = None,
|
|
102
|
+
kind: str = "info",
|
|
103
|
+
) -> None:
|
|
104
|
+
if kind != "loading":
|
|
105
|
+
self._stop_progress()
|
|
106
|
+
self.title_label.setText(str(title or ""))
|
|
107
|
+
self.detail_label.setText(str(detail or ""))
|
|
108
|
+
self.detail_label.setVisible(bool(detail))
|
|
109
|
+
self.frame.setStyleSheet(self._stylesheet(kind))
|
|
110
|
+
self._sync_geometry()
|
|
111
|
+
self.frame.show()
|
|
112
|
+
self.frame.raise_()
|
|
113
|
+
|
|
114
|
+
def hide(self) -> None:
|
|
115
|
+
self._stop_progress()
|
|
116
|
+
self.frame.hide()
|
|
117
|
+
|
|
118
|
+
def track(self, worker: Any) -> None:
|
|
119
|
+
"""Poll one latest value, never queue an event per reader chunk."""
|
|
120
|
+
self._worker = worker
|
|
121
|
+
self._last_progress = None
|
|
122
|
+
self._started_at = getattr(worker, "started_at", perf_counter())
|
|
123
|
+
self._rendering = False
|
|
124
|
+
self._poll_progress()
|
|
125
|
+
if self._worker is worker:
|
|
126
|
+
self.progress_timer.start()
|
|
127
|
+
|
|
128
|
+
def _stop_progress(self) -> None:
|
|
129
|
+
self.progress_timer.stop()
|
|
130
|
+
self._worker = None
|
|
131
|
+
self._last_progress = None
|
|
132
|
+
self._started_at = None
|
|
133
|
+
self._rendering = False
|
|
134
|
+
self.progress_bar.hide()
|
|
135
|
+
self.elapsed_label.hide()
|
|
136
|
+
|
|
137
|
+
def _update_elapsed(self) -> None:
|
|
138
|
+
if self._started_at is None:
|
|
139
|
+
return
|
|
140
|
+
seconds = max(0, int(perf_counter() - self._started_at))
|
|
141
|
+
minutes, seconds = divmod(seconds, 60)
|
|
142
|
+
hours, minutes = divmod(minutes, 60)
|
|
143
|
+
duration = (f"{hours}h {minutes:02d}m {seconds:02d}s" if hours else
|
|
144
|
+
f"{minutes}m {seconds:02d}s" if minutes else f"{seconds}s")
|
|
145
|
+
text = f"Elapsed: {duration}"
|
|
146
|
+
if self.elapsed_label.text() != text:
|
|
147
|
+
self.elapsed_label.setText(text)
|
|
148
|
+
self.elapsed_label.show()
|
|
149
|
+
|
|
150
|
+
def _poll_progress(self) -> None:
|
|
151
|
+
worker = self._worker
|
|
152
|
+
if worker is None:
|
|
153
|
+
return
|
|
154
|
+
if worker.is_cancelled():
|
|
155
|
+
self.show("Plot load cancelled")
|
|
156
|
+
return
|
|
157
|
+
self._update_elapsed()
|
|
158
|
+
if self._rendering:
|
|
159
|
+
return
|
|
160
|
+
progress = worker.progress
|
|
161
|
+
if progress == self._last_progress:
|
|
162
|
+
return
|
|
163
|
+
self._last_progress = progress
|
|
164
|
+
self.detail_label.setText(progress.title)
|
|
165
|
+
self.detail_label.show()
|
|
166
|
+
if progress.total is None or progress.total <= 0:
|
|
167
|
+
self.progress_bar.setRange(0, 0)
|
|
168
|
+
self.progress_bar.setFormat("")
|
|
169
|
+
else:
|
|
170
|
+
self.progress_bar.setRange(0, 1000)
|
|
171
|
+
self.progress_bar.setValue(min(1000, max(0, progress.completed * 1000 // progress.total)))
|
|
172
|
+
self.progress_bar.setFormat(f"%p% of {progress.unit}")
|
|
173
|
+
self.progress_bar.show()
|
|
174
|
+
self._sync_geometry()
|
|
175
|
+
|
|
176
|
+
def rendering(self, worker: Any) -> None:
|
|
177
|
+
if worker is not self._worker:
|
|
178
|
+
return
|
|
179
|
+
self._rendering = True
|
|
180
|
+
self._update_elapsed()
|
|
181
|
+
self.detail_label.setText(PlotProgress("Rendering plot", stage=2).title)
|
|
182
|
+
self.progress_bar.setRange(0, 0)
|
|
183
|
+
self.progress_bar.setFormat("")
|
|
184
|
+
|
|
185
|
+
def finish(self, worker: Any) -> None:
|
|
186
|
+
if worker is self._worker:
|
|
187
|
+
# Only a loading state is removed. Empty/error explanations stay.
|
|
188
|
+
self.hide()
|
|
189
|
+
|
|
190
|
+
def eventFilter(
|
|
191
|
+
self,
|
|
192
|
+
source: QtCore.QObject | None,
|
|
193
|
+
event: QtCore.QEvent | None,
|
|
194
|
+
) -> bool:
|
|
195
|
+
if (
|
|
196
|
+
event is not None
|
|
197
|
+
and (source is self.owner or source is self.target)
|
|
198
|
+
and event.type() in (QtCore.QEvent.Type.Resize, QtCore.QEvent.Type.Show)
|
|
199
|
+
):
|
|
200
|
+
self._sync_geometry()
|
|
201
|
+
return super().eventFilter(source, event)
|
|
202
|
+
|
|
203
|
+
def _stylesheet(self, kind: str) -> str:
|
|
204
|
+
panel_style = self._styles.get(kind, self._styles["info"])
|
|
205
|
+
return (
|
|
206
|
+
"QFrame#plotStateOverlay {"
|
|
207
|
+
f"{panel_style}"
|
|
208
|
+
"border-radius: 6px;"
|
|
209
|
+
"}"
|
|
210
|
+
"QLabel#plotStateOverlayTitle {"
|
|
211
|
+
"background: transparent;"
|
|
212
|
+
"font-size: 10pt;"
|
|
213
|
+
"}"
|
|
214
|
+
"QLabel#plotStateOverlayDetail {"
|
|
215
|
+
"background: transparent;"
|
|
216
|
+
"font-size: 8pt;"
|
|
217
|
+
"}"
|
|
218
|
+
"QLabel#plotStateOverlayElapsed {"
|
|
219
|
+
"background: transparent;"
|
|
220
|
+
"font-size: 8pt;"
|
|
221
|
+
"}"
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
def _sync_geometry(self) -> None:
|
|
225
|
+
if not self.frame.isVisible() and not self.title_label.text():
|
|
226
|
+
return
|
|
227
|
+
|
|
228
|
+
target_rect = self.target.rect()
|
|
229
|
+
margin = 24
|
|
230
|
+
max_width = max(160, min(420, target_rect.width() - (2 * margin)))
|
|
231
|
+
self.frame.setMaximumWidth(max_width)
|
|
232
|
+
self.frame.adjustSize()
|
|
233
|
+
|
|
234
|
+
size = self.frame.sizeHint()
|
|
235
|
+
width = min(max_width, max(160, size.width()))
|
|
236
|
+
height = size.height()
|
|
237
|
+
x = target_rect.left() + max(margin, (target_rect.width() - width) // 2)
|
|
238
|
+
y = target_rect.top() + max(margin, (target_rect.height() - height) // 2)
|
|
239
|
+
self.frame.setGeometry(x, y, width, height)
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""Display quantities for PyQtGraph's native PlotDataItem mappings.
|
|
2
|
+
|
|
3
|
+
Input parameters describe samples after qPlot operations. These immutable
|
|
4
|
+
descriptions never replace measurement or operation metadata.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from dataclasses import dataclass
|
|
8
|
+
from typing import Any
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass(frozen=True)
|
|
12
|
+
class AxisQuantity:
|
|
13
|
+
label: str
|
|
14
|
+
unit: str
|
|
15
|
+
|
|
16
|
+
@classmethod
|
|
17
|
+
def from_parameter(cls, parameter: Any) -> "AxisQuantity":
|
|
18
|
+
return cls(
|
|
19
|
+
str(getattr(parameter, "label", None) or getattr(parameter, "name", "")),
|
|
20
|
+
str(getattr(parameter, "unit", "") or ""),
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _unit_operand(unit: str) -> str:
|
|
25
|
+
return f"({unit})" if any(char in unit for char in "/ *") else unit
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _quotient_unit(numerator: str, denominator: str) -> str:
|
|
29
|
+
if not denominator:
|
|
30
|
+
return numerator
|
|
31
|
+
return f"{_unit_operand(numerator) if numerator else '1'}/{_unit_operand(denominator)}"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def native_axis_quantities(
|
|
35
|
+
x_parameter: Any, y_parameter: Any, options: dict[str, Any],
|
|
36
|
+
) -> dict[str, AxisQuantity]:
|
|
37
|
+
"""Mirror native mapping order, including mappings that reset to input.
|
|
38
|
+
|
|
39
|
+
PyQtGraph 0.14 subtracts the mean, then computes abs(rfft(y)/N).
|
|
40
|
+
Derivative mode replaces Y with diff(input Y)/diff(input X), retaining
|
|
41
|
+
the current X. Phase-map mode replaces both coordinates with input Y
|
|
42
|
+
and its derivative. Log ticking still describes these physical quantities.
|
|
43
|
+
"""
|
|
44
|
+
input_x = AxisQuantity.from_parameter(x_parameter)
|
|
45
|
+
input_y = AxisQuantity.from_parameter(y_parameter)
|
|
46
|
+
x, y = input_x, input_y
|
|
47
|
+
if options.get("subtractMeanMode"):
|
|
48
|
+
y = AxisQuantity(f"{y.label} - mean({y.label})", y.unit)
|
|
49
|
+
if options.get("fftMode"):
|
|
50
|
+
x = AxisQuantity(f"Frequency of {input_x.label}", _quotient_unit("", input_x.unit))
|
|
51
|
+
fft_input = f"({y.label})" if options.get("subtractMeanMode") else y.label
|
|
52
|
+
y = AxisQuantity(f"FFT magnitude of {fft_input}", y.unit)
|
|
53
|
+
if options.get("derivativeMode") or options.get("phasemapMode"):
|
|
54
|
+
y = AxisQuantity(
|
|
55
|
+
f"d({input_y.label})/d({input_x.label})",
|
|
56
|
+
_quotient_unit(input_y.unit, input_x.unit),
|
|
57
|
+
)
|
|
58
|
+
if options.get("phasemapMode"):
|
|
59
|
+
x = input_y
|
|
60
|
+
return {"x": x, "y": y}
|