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,1126 @@
|
|
|
1
|
+
import math
|
|
2
|
+
from typing import TYPE_CHECKING, Any
|
|
3
|
+
|
|
4
|
+
import numpy as np
|
|
5
|
+
import pyqtgraph as pg
|
|
6
|
+
from PyQt6 import QtCore
|
|
7
|
+
|
|
8
|
+
from ._colorbar import (
|
|
9
|
+
_CET_COLORBAR_SUBTYPES,
|
|
10
|
+
_COLORBAR_COLORMAPS,
|
|
11
|
+
_DEFAULT_HIDDEN_COLORBAR_NAMES,
|
|
12
|
+
_DEFAULT_HIDDEN_COLORBAR_PREFIXES,
|
|
13
|
+
_DEFAULT_HIDDEN_COLORBAR_SUFFIXES,
|
|
14
|
+
_MATPLOTLIB_COLORBAR_SUBTYPES,
|
|
15
|
+
_cet_colorbar_colormap_subtype,
|
|
16
|
+
_colorbar_colormap_for_name,
|
|
17
|
+
_colorbar_colormap_group,
|
|
18
|
+
_colorbar_subtype_config_key,
|
|
19
|
+
_config_value,
|
|
20
|
+
_matplotlib_colorbar_colormap_subtype,
|
|
21
|
+
)
|
|
22
|
+
from ._plot2d_colorbar_dialog import ColorbarScaleDialogMixin
|
|
23
|
+
from ._plot_axis_scaling import (
|
|
24
|
+
_axis_scale_power_text,
|
|
25
|
+
_install_flush_axis_draw_specs,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
COLORBAR_WIDTH_KEY = "user_preference.colorbar_width"
|
|
29
|
+
DEFAULT_COLORBAR_WIDTH = 15
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class Plot2DColorbarMixin(ColorbarScaleDialogMixin):
|
|
33
|
+
"""
|
|
34
|
+
Heatmap colorbar controls for plot2d windows.
|
|
35
|
+
|
|
36
|
+
This mixin owns color autoscaling, colorbar interaction handlers, color-map
|
|
37
|
+
filtering, and the color scale dialog.
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
def _configured_colorbar_width(self):
|
|
41
|
+
"""Return the configured heatmap colour-scale width in pixels."""
|
|
42
|
+
|
|
43
|
+
return int(_config_value(
|
|
44
|
+
self.__dict__.get("config"),
|
|
45
|
+
COLORBAR_WIDTH_KEY,
|
|
46
|
+
DEFAULT_COLORBAR_WIDTH,
|
|
47
|
+
))
|
|
48
|
+
|
|
49
|
+
def apply_colorbar_width_preference(self):
|
|
50
|
+
"""Apply the configured width to an existing colour bar, if present."""
|
|
51
|
+
|
|
52
|
+
bar = self.__dict__.get("bar")
|
|
53
|
+
layout = getattr(bar, "layout", None)
|
|
54
|
+
if layout is None:
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
width = self._configured_colorbar_width()
|
|
58
|
+
if getattr(bar, "horizontal", False):
|
|
59
|
+
layout.setRowFixedHeight(1, width)
|
|
60
|
+
else:
|
|
61
|
+
layout.setColumnFixedWidth(1, width)
|
|
62
|
+
|
|
63
|
+
if TYPE_CHECKING:
|
|
64
|
+
plot: Any
|
|
65
|
+
|
|
66
|
+
def _heatmap_colorbar_items(self) -> list[Any]: ...
|
|
67
|
+
def _reset_heatmap_hover(self) -> None: ...
|
|
68
|
+
def _set_sweep_lines_visible(self, visible: bool) -> None: ...
|
|
69
|
+
def show_plot_state(self, title: object, detail: object | None = None, kind: str = "info") -> None: ...
|
|
70
|
+
|
|
71
|
+
@QtCore.pyqtSlot(bool)
|
|
72
|
+
def scaleColorbar(self, event = None):
|
|
73
|
+
"""
|
|
74
|
+
Sets colorbar range to match heatmap value range, giving effect of
|
|
75
|
+
rescaling color bar
|
|
76
|
+
|
|
77
|
+
Parameters
|
|
78
|
+
----------
|
|
79
|
+
Unused but required by slot
|
|
80
|
+
|
|
81
|
+
"""
|
|
82
|
+
levels = self._data_colorbar_levels()
|
|
83
|
+
if levels is None:
|
|
84
|
+
return
|
|
85
|
+
|
|
86
|
+
self._colorbar_manual_levels = None
|
|
87
|
+
self._set_colorbar_levels(*levels)
|
|
88
|
+
|
|
89
|
+
def _data_colorbar_levels(self):
|
|
90
|
+
"""
|
|
91
|
+
Return finite min/max levels from the current heatmap data.
|
|
92
|
+
|
|
93
|
+
"""
|
|
94
|
+
data_range = self._finite_data_colorbar_range()
|
|
95
|
+
if data_range is None:
|
|
96
|
+
return None
|
|
97
|
+
|
|
98
|
+
vmin, vmax = data_range
|
|
99
|
+
if vmin == vmax:
|
|
100
|
+
return self._constant_colorbar_levels(vmin)
|
|
101
|
+
if vmin > vmax:
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
return vmin, vmax
|
|
105
|
+
|
|
106
|
+
@staticmethod
|
|
107
|
+
def _constant_colorbar_levels(value):
|
|
108
|
+
"""Return a small finite range containing one constant data value."""
|
|
109
|
+
|
|
110
|
+
value = float(value)
|
|
111
|
+
if not np.isfinite(value):
|
|
112
|
+
return None
|
|
113
|
+
|
|
114
|
+
if value == 0.0:
|
|
115
|
+
padding = 1e-6
|
|
116
|
+
else:
|
|
117
|
+
padding = abs(value) * 1e-6
|
|
118
|
+
|
|
119
|
+
with np.errstate(over="ignore", under="ignore", invalid="ignore"):
|
|
120
|
+
low = value - padding
|
|
121
|
+
high = value + padding
|
|
122
|
+
if np.isfinite(low) and np.isfinite(high) and low < high:
|
|
123
|
+
return float(low), float(high)
|
|
124
|
+
|
|
125
|
+
low = float(np.nextafter(value, -np.inf))
|
|
126
|
+
high = float(np.nextafter(value, np.inf))
|
|
127
|
+
if not np.isfinite(low):
|
|
128
|
+
low = value
|
|
129
|
+
if not np.isfinite(high):
|
|
130
|
+
high = value
|
|
131
|
+
if low < high:
|
|
132
|
+
return low, high
|
|
133
|
+
return None
|
|
134
|
+
|
|
135
|
+
def _finite_data_colorbar_range(self):
|
|
136
|
+
"""Return the finite range across every heatmap sharing the colorbar."""
|
|
137
|
+
|
|
138
|
+
data_provider = getattr(self, "_heatmap_colorbar_data_arrays", None)
|
|
139
|
+
if callable(data_provider):
|
|
140
|
+
arrays = data_provider()
|
|
141
|
+
else:
|
|
142
|
+
data = getattr(self, "dataGrid", None)
|
|
143
|
+
arrays = () if data is None else (data,)
|
|
144
|
+
|
|
145
|
+
lower = None
|
|
146
|
+
upper = None
|
|
147
|
+
for data in arrays:
|
|
148
|
+
values = np.asarray(data, dtype=float)
|
|
149
|
+
finite_values = values[np.isfinite(values)]
|
|
150
|
+
if finite_values.size == 0:
|
|
151
|
+
continue
|
|
152
|
+
data_lower = float(np.min(finite_values))
|
|
153
|
+
data_upper = float(np.max(finite_values))
|
|
154
|
+
lower = data_lower if lower is None else min(lower, data_lower)
|
|
155
|
+
upper = data_upper if upper is None else max(upper, data_upper)
|
|
156
|
+
|
|
157
|
+
if lower is None or upper is None:
|
|
158
|
+
return None
|
|
159
|
+
return lower, upper
|
|
160
|
+
|
|
161
|
+
def _data_colorbar_rounding(self):
|
|
162
|
+
"""Return a finite, positive colorbar interaction step."""
|
|
163
|
+
|
|
164
|
+
levels = self._data_colorbar_levels()
|
|
165
|
+
if levels is None:
|
|
166
|
+
return 1e-5
|
|
167
|
+
|
|
168
|
+
return self._colorbar_rounding_for_levels(*levels)
|
|
169
|
+
|
|
170
|
+
@staticmethod
|
|
171
|
+
def _colorbar_rounding_for_levels(vmin, vmax):
|
|
172
|
+
"""Return an interaction step appropriate for specific levels."""
|
|
173
|
+
|
|
174
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
175
|
+
span = float(vmax) - float(vmin)
|
|
176
|
+
if not np.isfinite(span) or span <= 0:
|
|
177
|
+
span = max(abs(vmin), abs(vmax))
|
|
178
|
+
if not np.isfinite(span) or span <= 0:
|
|
179
|
+
span = 1.0
|
|
180
|
+
|
|
181
|
+
rounding = span / 1e5
|
|
182
|
+
minimum_rounding = np.nextafter(0.0, 1.0)
|
|
183
|
+
if not np.isfinite(rounding) or rounding < minimum_rounding:
|
|
184
|
+
return float(minimum_rounding)
|
|
185
|
+
return float(rounding)
|
|
186
|
+
|
|
187
|
+
def _set_colorbar_levels(self, vmin, vmax):
|
|
188
|
+
"""
|
|
189
|
+
Apply levels to the colorbar and mirror them in the menu fields.
|
|
190
|
+
|
|
191
|
+
"""
|
|
192
|
+
if not np.isfinite(float(vmax) - float(vmin)):
|
|
193
|
+
self._reject_heatmap_color_range()
|
|
194
|
+
return False
|
|
195
|
+
|
|
196
|
+
bar = self.__dict__.get("bar")
|
|
197
|
+
if bar is not None:
|
|
198
|
+
bar.rounding = self._colorbar_rounding_for_levels(vmin, vmax)
|
|
199
|
+
finite_limit = np.finfo(float).max
|
|
200
|
+
bar.lo_lim = -finite_limit
|
|
201
|
+
bar.hi_lim = finite_limit
|
|
202
|
+
with np.errstate(over="ignore", invalid="ignore"):
|
|
203
|
+
span = float(vmax) - float(vmin)
|
|
204
|
+
interaction_enabled = bool(np.isfinite(span) and span > 0)
|
|
205
|
+
region = getattr(bar, "region", None)
|
|
206
|
+
if region is not None:
|
|
207
|
+
region.setEnabled(interaction_enabled)
|
|
208
|
+
bar.region_changed_enable = interaction_enabled
|
|
209
|
+
bar.setLevels((vmin, vmax))
|
|
210
|
+
self._sync_colorbar_axis_scaling()
|
|
211
|
+
|
|
212
|
+
self._sync_colorbar_level_fields(vmin, vmax)
|
|
213
|
+
return True
|
|
214
|
+
|
|
215
|
+
def _reject_heatmap_color_range(self):
|
|
216
|
+
"""Hide an unusable color display while retaining measured samples."""
|
|
217
|
+
self._heatmap_color_range_rejected = True
|
|
218
|
+
for item in self._heatmap_colorbar_items():
|
|
219
|
+
item.hide()
|
|
220
|
+
bar = self.__dict__.get("bar")
|
|
221
|
+
if bar is not None:
|
|
222
|
+
bar.hide()
|
|
223
|
+
self._reset_heatmap_hover()
|
|
224
|
+
self._set_sweep_lines_visible(False)
|
|
225
|
+
message = (
|
|
226
|
+
"The heatmap color range is too wide to display safely. "
|
|
227
|
+
"Recorded samples remain available for CSV export and operations."
|
|
228
|
+
)
|
|
229
|
+
self.show_status(message, 10_000)
|
|
230
|
+
self.show_plot_state("Unsupported heatmap color range", message, kind="error")
|
|
231
|
+
|
|
232
|
+
def _set_colorbar_tick_formatter(self):
|
|
233
|
+
"""
|
|
234
|
+
Use plain scaled tick labels and show the scale in the colorbar title.
|
|
235
|
+
|
|
236
|
+
"""
|
|
237
|
+
bar = self.__dict__.get("bar")
|
|
238
|
+
axis = getattr(bar, "axis", None)
|
|
239
|
+
if axis is None:
|
|
240
|
+
return
|
|
241
|
+
|
|
242
|
+
layout = getattr(bar, "layout", None)
|
|
243
|
+
get_margins = getattr(layout, "getContentsMargins", None)
|
|
244
|
+
set_margins = getattr(layout, "setContentsMargins", None)
|
|
245
|
+
if callable(get_margins) and callable(set_margins):
|
|
246
|
+
left, _top, right, _bottom = get_margins()
|
|
247
|
+
set_margins(left, 0, right, 0)
|
|
248
|
+
|
|
249
|
+
get_axis = getattr(bar, "getAxis", None)
|
|
250
|
+
if callable(get_axis):
|
|
251
|
+
for side in ("left", "right", "top", "bottom"):
|
|
252
|
+
_install_flush_axis_draw_specs(get_axis(side))
|
|
253
|
+
|
|
254
|
+
self._restore_colorbar_default_tick_formatter(axis)
|
|
255
|
+
axis.autoSIPrefix = False
|
|
256
|
+
axis.setWidth(70)
|
|
257
|
+
axis.setStyle(tickTextWidth=60)
|
|
258
|
+
self._set_colorbar_label_direction(bar)
|
|
259
|
+
self._install_colorbar_axis_scale_sync(bar)
|
|
260
|
+
self._install_colorbar_mirrored_ticks(bar)
|
|
261
|
+
self._sync_colorbar_axis_scaling()
|
|
262
|
+
self._install_colorbar_scale_bar_handlers(bar)
|
|
263
|
+
self._install_colorbar_scale_axis_handlers(axis)
|
|
264
|
+
|
|
265
|
+
def _restore_colorbar_default_tick_formatter(self, axis):
|
|
266
|
+
try:
|
|
267
|
+
del axis.tickStrings
|
|
268
|
+
except AttributeError:
|
|
269
|
+
pass
|
|
270
|
+
|
|
271
|
+
def _install_colorbar_axis_scale_sync(self, bar):
|
|
272
|
+
axis = getattr(bar, "axis", None)
|
|
273
|
+
if axis is None or getattr(axis, "_qplot_colorbar_scale_sync_installed", False):
|
|
274
|
+
return
|
|
275
|
+
|
|
276
|
+
previous_set_range = getattr(axis, "setRange", None)
|
|
277
|
+
if previous_set_range is None:
|
|
278
|
+
return
|
|
279
|
+
|
|
280
|
+
def set_range(low, high, previous_set_range=previous_set_range):
|
|
281
|
+
previous_set_range(low, high)
|
|
282
|
+
self._sync_colorbar_axis_scaling()
|
|
283
|
+
|
|
284
|
+
axis.setRange = set_range
|
|
285
|
+
axis._qplot_colorbar_scale_sync_installed = True
|
|
286
|
+
|
|
287
|
+
def _install_colorbar_mirrored_ticks(self, bar):
|
|
288
|
+
"""Mirror vertical colour-bar ticks on the left without their labels."""
|
|
289
|
+
|
|
290
|
+
if getattr(bar, "horizontal", False):
|
|
291
|
+
return
|
|
292
|
+
|
|
293
|
+
get_axis = getattr(bar, "getAxis", None)
|
|
294
|
+
axis = getattr(bar, "axis", None)
|
|
295
|
+
if not callable(get_axis) or axis is None:
|
|
296
|
+
return
|
|
297
|
+
|
|
298
|
+
mirror = get_axis("left")
|
|
299
|
+
if mirror is None:
|
|
300
|
+
return
|
|
301
|
+
|
|
302
|
+
mirror.unlinkFromView()
|
|
303
|
+
mirror.setStyle(showValues=False)
|
|
304
|
+
|
|
305
|
+
def sync_ticks():
|
|
306
|
+
low, high = axis.range
|
|
307
|
+
mirror.setRange(low, high)
|
|
308
|
+
length = max(1, int(axis.height()))
|
|
309
|
+
tick_values = axis.tickValues(low, high, length)
|
|
310
|
+
mirror.setTicks([
|
|
311
|
+
[(value, "") for value in values]
|
|
312
|
+
for _spacing, values in tick_values
|
|
313
|
+
])
|
|
314
|
+
|
|
315
|
+
sync_ticks()
|
|
316
|
+
if getattr(axis, "_qplot_colorbar_mirrored_ticks_installed", False):
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
previous_set_range = axis.setRange
|
|
320
|
+
|
|
321
|
+
def set_range(low, high, previous_set_range=previous_set_range):
|
|
322
|
+
previous_set_range(low, high)
|
|
323
|
+
sync_ticks()
|
|
324
|
+
|
|
325
|
+
previous_resize_event = getattr(axis, "resizeEvent", None)
|
|
326
|
+
|
|
327
|
+
def resize_event(event=None, previous_handler=previous_resize_event):
|
|
328
|
+
if previous_handler is not None:
|
|
329
|
+
previous_handler(event)
|
|
330
|
+
sync_ticks()
|
|
331
|
+
|
|
332
|
+
axis.setRange = set_range
|
|
333
|
+
axis.resizeEvent = resize_event
|
|
334
|
+
axis._qplot_colorbar_mirrored_ticks_installed = True
|
|
335
|
+
|
|
336
|
+
def _sync_colorbar_axis_scaling(self):
|
|
337
|
+
bar = self.__dict__.get("bar")
|
|
338
|
+
axis = getattr(bar, "axis", None)
|
|
339
|
+
if axis is None:
|
|
340
|
+
return
|
|
341
|
+
|
|
342
|
+
scale = self._colorbar_axis_display_scale(bar, axis)
|
|
343
|
+
axis.autoSIPrefixScale = scale
|
|
344
|
+
axis.labelUnitPrefix = ""
|
|
345
|
+
axis.picture = None
|
|
346
|
+
axis.update()
|
|
347
|
+
self._set_colorbar_scaled_label(bar, scale)
|
|
348
|
+
|
|
349
|
+
def _colorbar_axis_display_scale(self, bar, axis):
|
|
350
|
+
levels = self._colorbar_levels_from_bar(bar)
|
|
351
|
+
if levels is None:
|
|
352
|
+
levels = getattr(axis, "range", None)
|
|
353
|
+
if levels is None:
|
|
354
|
+
return 1.0
|
|
355
|
+
|
|
356
|
+
low, high = levels
|
|
357
|
+
scale_value = max(abs(low), abs(high))
|
|
358
|
+
if not np.isfinite(scale_value):
|
|
359
|
+
return 1.0
|
|
360
|
+
|
|
361
|
+
scale, _prefix = pg.functions.siScale(scale_value, allowUnicode=False)
|
|
362
|
+
return scale
|
|
363
|
+
|
|
364
|
+
def _set_colorbar_scaled_label(self, bar, scale):
|
|
365
|
+
param = self.__dict__.get("display_param", self.__dict__.get("param"))
|
|
366
|
+
if param is None or not hasattr(bar, "setLabel"):
|
|
367
|
+
return
|
|
368
|
+
|
|
369
|
+
label = getattr(param, "label", "")
|
|
370
|
+
unit = getattr(param, "unit", "")
|
|
371
|
+
unit_scale = 1.0 / scale if scale else 1.0
|
|
372
|
+
scale_text = _axis_scale_power_text(unit_scale)
|
|
373
|
+
|
|
374
|
+
if unit and scale_text:
|
|
375
|
+
unit_text = f"{scale_text} {unit}"
|
|
376
|
+
elif unit:
|
|
377
|
+
unit_text = unit
|
|
378
|
+
else:
|
|
379
|
+
unit_text = scale_text
|
|
380
|
+
|
|
381
|
+
text = f"{label} ({unit_text})" if unit_text else label
|
|
382
|
+
axis = "bottom" if getattr(bar, "horizontal", False) else "right"
|
|
383
|
+
bar.setLabel(axis, text)
|
|
384
|
+
self._set_colorbar_label_direction(bar)
|
|
385
|
+
|
|
386
|
+
def _set_colorbar_label_direction(self, bar):
|
|
387
|
+
if getattr(bar, "horizontal", False):
|
|
388
|
+
return
|
|
389
|
+
|
|
390
|
+
axis = getattr(bar, "axis", None)
|
|
391
|
+
label = getattr(axis, "label", None)
|
|
392
|
+
if axis is None or label is None or not hasattr(label, "setRotation"):
|
|
393
|
+
return
|
|
394
|
+
|
|
395
|
+
label.setRotation(90)
|
|
396
|
+
if not getattr(axis, "_qplot_downward_colorbar_label_installed", False):
|
|
397
|
+
previous_resize_event = getattr(axis, "resizeEvent", None)
|
|
398
|
+
|
|
399
|
+
def resize_event(event=None, previous_handler=previous_resize_event):
|
|
400
|
+
if previous_handler is not None:
|
|
401
|
+
previous_handler(event)
|
|
402
|
+
self._position_downward_colorbar_label(axis)
|
|
403
|
+
|
|
404
|
+
axis.resizeEvent = resize_event
|
|
405
|
+
axis._qplot_downward_colorbar_label_installed = True
|
|
406
|
+
|
|
407
|
+
self._position_downward_colorbar_label(axis)
|
|
408
|
+
|
|
409
|
+
def _position_downward_colorbar_label(self, axis):
|
|
410
|
+
label = getattr(axis, "label", None)
|
|
411
|
+
if label is None or not all(
|
|
412
|
+
hasattr(label, name) for name in ("boundingRect", "setPos")
|
|
413
|
+
):
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
nudge = 5
|
|
417
|
+
bounds = label.boundingRect()
|
|
418
|
+
size = axis.size()
|
|
419
|
+
position = QtCore.QPointF(
|
|
420
|
+
int(size.width() + nudge),
|
|
421
|
+
int(size.height() / 2 - bounds.width() / 2),
|
|
422
|
+
)
|
|
423
|
+
label.setPos(position)
|
|
424
|
+
axis.picture = None
|
|
425
|
+
|
|
426
|
+
def _install_colorbar_scale_bar_handlers(self, bar):
|
|
427
|
+
"""
|
|
428
|
+
Open the color-scale dialog from colorbar interactions.
|
|
429
|
+
|
|
430
|
+
"""
|
|
431
|
+
self._install_colorbar_scale_double_click_handler(bar)
|
|
432
|
+
self._suppress_colorbar_right_click_menu(bar)
|
|
433
|
+
self._install_colorbar_level_sync_handlers(bar)
|
|
434
|
+
self._install_colorbar_alt_range_drag_handler(bar)
|
|
435
|
+
|
|
436
|
+
def _install_colorbar_scale_axis_handlers(self, axis):
|
|
437
|
+
"""
|
|
438
|
+
Open the color-scale dialog from direct colorbar axis interactions.
|
|
439
|
+
|
|
440
|
+
"""
|
|
441
|
+
self._install_colorbar_scale_double_click_handler(axis)
|
|
442
|
+
|
|
443
|
+
def _install_colorbar_scale_double_click_handler(self, item):
|
|
444
|
+
"""
|
|
445
|
+
Open the color-scale dialog when a colorbar item is double-clicked.
|
|
446
|
+
|
|
447
|
+
"""
|
|
448
|
+
if getattr(item, "_qplot_colorbar_scale_handlers_installed", False):
|
|
449
|
+
return
|
|
450
|
+
|
|
451
|
+
previous_double_click_handler = getattr(item, "mouseDoubleClickEvent", None)
|
|
452
|
+
|
|
453
|
+
def mouse_double_click(event, previous_handler=previous_double_click_handler):
|
|
454
|
+
if event.button() == QtCore.Qt.MouseButton.LeftButton:
|
|
455
|
+
self.open_colorbar_scale_dialog()
|
|
456
|
+
event.accept()
|
|
457
|
+
return
|
|
458
|
+
|
|
459
|
+
if previous_handler is not None:
|
|
460
|
+
previous_handler(event)
|
|
461
|
+
|
|
462
|
+
item.mouseDoubleClickEvent = mouse_double_click
|
|
463
|
+
item._qplot_colorbar_scale_handlers_installed = True
|
|
464
|
+
|
|
465
|
+
def _suppress_colorbar_right_click_menu(self, bar):
|
|
466
|
+
"""
|
|
467
|
+
Prevent pyqtgraph's colorbar right-click menu from opening.
|
|
468
|
+
|
|
469
|
+
"""
|
|
470
|
+
if getattr(bar, "_qplot_colorbar_right_click_suppressed", False):
|
|
471
|
+
return
|
|
472
|
+
|
|
473
|
+
previous_mouse_click_handler = getattr(bar, "mouseClickEvent", None)
|
|
474
|
+
|
|
475
|
+
def mouse_click(event, previous_handler=previous_mouse_click_handler):
|
|
476
|
+
if event.button() == QtCore.Qt.MouseButton.RightButton:
|
|
477
|
+
event.accept()
|
|
478
|
+
return
|
|
479
|
+
|
|
480
|
+
if previous_handler is not None:
|
|
481
|
+
previous_handler(event)
|
|
482
|
+
|
|
483
|
+
bar.mouseClickEvent = mouse_click
|
|
484
|
+
bar._qplot_colorbar_right_click_suppressed = True
|
|
485
|
+
|
|
486
|
+
def _install_colorbar_level_sync_handlers(self, bar):
|
|
487
|
+
"""
|
|
488
|
+
Treat direct colorbar handle adjustment as a manual color range.
|
|
489
|
+
|
|
490
|
+
"""
|
|
491
|
+
if getattr(bar, "_qplot_colorbar_level_sync_installed", False):
|
|
492
|
+
return
|
|
493
|
+
|
|
494
|
+
changed = getattr(bar, "sigLevelsChanged", None)
|
|
495
|
+
if changed is not None:
|
|
496
|
+
changed.connect(self._colorbar_interactive_levels_changed)
|
|
497
|
+
|
|
498
|
+
finished = getattr(bar, "sigLevelsChangeFinished", None)
|
|
499
|
+
if finished is not None:
|
|
500
|
+
finished.connect(self._colorbar_interactive_levels_finished)
|
|
501
|
+
|
|
502
|
+
bar._qplot_colorbar_level_sync_installed = True
|
|
503
|
+
|
|
504
|
+
def _install_colorbar_alt_range_drag_handler(self, bar):
|
|
505
|
+
"""
|
|
506
|
+
Add outside-bar drag on colorbar regions to widen or narrow levels.
|
|
507
|
+
|
|
508
|
+
"""
|
|
509
|
+
region = getattr(bar, "region", None)
|
|
510
|
+
if region is None:
|
|
511
|
+
return
|
|
512
|
+
|
|
513
|
+
if getattr(region, "_qplot_colorbar_alt_range_drag_installed", False):
|
|
514
|
+
return
|
|
515
|
+
|
|
516
|
+
previous_bounding_rect = getattr(region, "boundingRect", None)
|
|
517
|
+
previous_paint = getattr(region, "paint", None)
|
|
518
|
+
previous_mouse_drag_handler = getattr(region, "mouseDragEvent", None)
|
|
519
|
+
previous_hover_handler = getattr(region, "hoverEvent", None)
|
|
520
|
+
|
|
521
|
+
def bounding_rect(previous_handler=previous_bounding_rect):
|
|
522
|
+
rect = self._colorbar_full_region_rect(region)
|
|
523
|
+
if rect is not None:
|
|
524
|
+
return rect
|
|
525
|
+
if previous_handler is not None:
|
|
526
|
+
return previous_handler()
|
|
527
|
+
return QtCore.QRectF()
|
|
528
|
+
|
|
529
|
+
def paint(painter, *args, previous_handler=previous_paint):
|
|
530
|
+
mode = getattr(region, "_qplot_colorbar_drag_visual_area", "inside")
|
|
531
|
+
if mode in ("inside", "outside"):
|
|
532
|
+
rects = self._colorbar_drag_visual_rects(region, mode)
|
|
533
|
+
if rects:
|
|
534
|
+
painter.setBrush(region.currentBrush)
|
|
535
|
+
painter.setPen(pg.mkPen(None))
|
|
536
|
+
for rect in rects:
|
|
537
|
+
painter.drawRect(rect)
|
|
538
|
+
return
|
|
539
|
+
|
|
540
|
+
if previous_handler is not None:
|
|
541
|
+
previous_handler(painter, *args)
|
|
542
|
+
|
|
543
|
+
def mouse_drag(event, previous_handler=previous_mouse_drag_handler):
|
|
544
|
+
active = getattr(region, "_qplot_colorbar_alt_range_drag_source", None)
|
|
545
|
+
source, direction = self._colorbar_alt_range_drag_source_at(
|
|
546
|
+
bar,
|
|
547
|
+
region,
|
|
548
|
+
event.buttonDownPos(),
|
|
549
|
+
)
|
|
550
|
+
if (
|
|
551
|
+
event.button() == QtCore.Qt.MouseButton.LeftButton
|
|
552
|
+
and (
|
|
553
|
+
active is not None
|
|
554
|
+
or (
|
|
555
|
+
source is not None
|
|
556
|
+
and getattr(region, "movable", True)
|
|
557
|
+
)
|
|
558
|
+
)
|
|
559
|
+
):
|
|
560
|
+
if active is not None:
|
|
561
|
+
source = active
|
|
562
|
+
direction = active[1]
|
|
563
|
+
self._colorbar_alt_range_drag_event(
|
|
564
|
+
bar,
|
|
565
|
+
region,
|
|
566
|
+
event,
|
|
567
|
+
source,
|
|
568
|
+
direction,
|
|
569
|
+
region,
|
|
570
|
+
)
|
|
571
|
+
return
|
|
572
|
+
|
|
573
|
+
if previous_handler is not None:
|
|
574
|
+
previous_handler(event)
|
|
575
|
+
|
|
576
|
+
def hover(event, previous_handler=previous_hover_handler):
|
|
577
|
+
if previous_handler is not None:
|
|
578
|
+
previous_handler(event)
|
|
579
|
+
|
|
580
|
+
if event.isExit():
|
|
581
|
+
region._qplot_colorbar_drag_visual_area = None
|
|
582
|
+
else:
|
|
583
|
+
source, _direction = self._colorbar_alt_range_drag_source_at(
|
|
584
|
+
bar,
|
|
585
|
+
region,
|
|
586
|
+
event.pos(),
|
|
587
|
+
)
|
|
588
|
+
region._qplot_colorbar_drag_visual_area = (
|
|
589
|
+
"outside" if source is not None else "inside"
|
|
590
|
+
)
|
|
591
|
+
region.update()
|
|
592
|
+
|
|
593
|
+
region.boundingRect = bounding_rect
|
|
594
|
+
region.paint = paint
|
|
595
|
+
region.mouseDragEvent = mouse_drag
|
|
596
|
+
region.hoverEvent = hover
|
|
597
|
+
region._qplot_colorbar_drag_visual_area = None
|
|
598
|
+
|
|
599
|
+
region._qplot_colorbar_alt_range_drag_installed = True
|
|
600
|
+
|
|
601
|
+
def _install_colorbar_alt_handle_drag_handler(self, bar, region, line, index):
|
|
602
|
+
"""
|
|
603
|
+
Patch one pyqtgraph colorbar handle for symmetric Alt/Option-drag.
|
|
604
|
+
|
|
605
|
+
"""
|
|
606
|
+
if index not in (0, 1):
|
|
607
|
+
return
|
|
608
|
+
|
|
609
|
+
if getattr(line, "_qplot_colorbar_alt_range_drag_installed", False):
|
|
610
|
+
return
|
|
611
|
+
|
|
612
|
+
previous_mouse_drag_handler = getattr(line, "mouseDragEvent", None)
|
|
613
|
+
source = ("line", index)
|
|
614
|
+
direction = -1.0 if index == 0 else 1.0
|
|
615
|
+
|
|
616
|
+
def mouse_drag(event, previous_handler=previous_mouse_drag_handler):
|
|
617
|
+
modifiers = event.modifiers()
|
|
618
|
+
active = getattr(region, "_qplot_colorbar_alt_range_drag_source", None)
|
|
619
|
+
if (
|
|
620
|
+
event.button() == QtCore.Qt.MouseButton.LeftButton
|
|
621
|
+
and (
|
|
622
|
+
active == source
|
|
623
|
+
or (
|
|
624
|
+
modifiers & QtCore.Qt.KeyboardModifier.AltModifier
|
|
625
|
+
and getattr(line, "movable", True)
|
|
626
|
+
and getattr(region, "movable", True)
|
|
627
|
+
)
|
|
628
|
+
)
|
|
629
|
+
):
|
|
630
|
+
self._colorbar_alt_range_drag_event(
|
|
631
|
+
bar,
|
|
632
|
+
region,
|
|
633
|
+
event,
|
|
634
|
+
source,
|
|
635
|
+
direction,
|
|
636
|
+
line,
|
|
637
|
+
)
|
|
638
|
+
return
|
|
639
|
+
|
|
640
|
+
if previous_handler is not None:
|
|
641
|
+
previous_handler(event)
|
|
642
|
+
|
|
643
|
+
line.mouseDragEvent = mouse_drag
|
|
644
|
+
line._qplot_colorbar_alt_range_drag_installed = True
|
|
645
|
+
|
|
646
|
+
def _colorbar_full_region_rect(self, region):
|
|
647
|
+
"""
|
|
648
|
+
Return the full colorbar interaction rectangle in region coordinates.
|
|
649
|
+
|
|
650
|
+
"""
|
|
651
|
+
try:
|
|
652
|
+
rect = QtCore.QRectF(region.viewRect())
|
|
653
|
+
except (AttributeError, RuntimeError, TypeError):
|
|
654
|
+
return None
|
|
655
|
+
|
|
656
|
+
span = getattr(region, "span", (0, 1))
|
|
657
|
+
orientation = getattr(region, "orientation", None)
|
|
658
|
+
if orientation in ("vertical", 0):
|
|
659
|
+
length = rect.height()
|
|
660
|
+
rect.setBottom(rect.top() + length * span[1])
|
|
661
|
+
rect.setTop(rect.top() + length * span[0])
|
|
662
|
+
else:
|
|
663
|
+
length = rect.width()
|
|
664
|
+
rect.setRight(rect.left() + length * span[1])
|
|
665
|
+
rect.setLeft(rect.left() + length * span[0])
|
|
666
|
+
|
|
667
|
+
return rect.normalized()
|
|
668
|
+
|
|
669
|
+
def _colorbar_drag_visual_rects(self, region, mode):
|
|
670
|
+
"""
|
|
671
|
+
Return shaded rectangles for colorbar range-slide or outside-scale zones.
|
|
672
|
+
|
|
673
|
+
"""
|
|
674
|
+
rect = self._colorbar_full_region_rect(region)
|
|
675
|
+
if rect is None:
|
|
676
|
+
return ()
|
|
677
|
+
|
|
678
|
+
levels = self._colorbar_region_line_positions(region)
|
|
679
|
+
if levels is None:
|
|
680
|
+
return ()
|
|
681
|
+
low, high = levels
|
|
682
|
+
|
|
683
|
+
orientation = getattr(region, "orientation", None)
|
|
684
|
+
if orientation in ("vertical", 0):
|
|
685
|
+
inside = QtCore.QRectF(rect)
|
|
686
|
+
inside.setLeft(low)
|
|
687
|
+
inside.setRight(high)
|
|
688
|
+
outside_low = QtCore.QRectF(
|
|
689
|
+
rect.left(),
|
|
690
|
+
rect.top(),
|
|
691
|
+
low - rect.left(),
|
|
692
|
+
rect.height(),
|
|
693
|
+
)
|
|
694
|
+
outside_high = QtCore.QRectF(
|
|
695
|
+
high,
|
|
696
|
+
rect.top(),
|
|
697
|
+
rect.right() - high,
|
|
698
|
+
rect.height(),
|
|
699
|
+
)
|
|
700
|
+
else:
|
|
701
|
+
inside = QtCore.QRectF(rect)
|
|
702
|
+
inside.setTop(low)
|
|
703
|
+
inside.setBottom(high)
|
|
704
|
+
outside_low = QtCore.QRectF(
|
|
705
|
+
rect.left(),
|
|
706
|
+
rect.top(),
|
|
707
|
+
rect.width(),
|
|
708
|
+
low - rect.top(),
|
|
709
|
+
)
|
|
710
|
+
outside_high = QtCore.QRectF(
|
|
711
|
+
rect.left(),
|
|
712
|
+
high,
|
|
713
|
+
rect.width(),
|
|
714
|
+
rect.bottom() - high,
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
if mode == "inside":
|
|
718
|
+
return (inside.normalized(),)
|
|
719
|
+
|
|
720
|
+
return tuple(
|
|
721
|
+
outside.normalized()
|
|
722
|
+
for outside in (outside_low, outside_high)
|
|
723
|
+
if outside.width() > 0 and outside.height() > 0
|
|
724
|
+
)
|
|
725
|
+
|
|
726
|
+
def _colorbar_region_line_positions(self, region):
|
|
727
|
+
"""
|
|
728
|
+
Return sorted colorbar handle positions in region coordinates.
|
|
729
|
+
|
|
730
|
+
"""
|
|
731
|
+
lines = getattr(region, "lines", None)
|
|
732
|
+
if lines is None or len(lines) != 2:
|
|
733
|
+
return None
|
|
734
|
+
|
|
735
|
+
positions = []
|
|
736
|
+
for line in lines:
|
|
737
|
+
value = getattr(line, "value", None)
|
|
738
|
+
if callable(value):
|
|
739
|
+
positions.append(float(value()))
|
|
740
|
+
else:
|
|
741
|
+
position = getattr(line, "position", None)
|
|
742
|
+
if position is None:
|
|
743
|
+
return None
|
|
744
|
+
positions.append(float(position))
|
|
745
|
+
|
|
746
|
+
if not all(np.isfinite(position) for position in positions):
|
|
747
|
+
return None
|
|
748
|
+
|
|
749
|
+
return tuple(sorted(positions))
|
|
750
|
+
|
|
751
|
+
def _colorbar_alt_range_drag_source_at(self, bar, region, position):
|
|
752
|
+
"""
|
|
753
|
+
Return the outside-bar range-scale drag source and direction at a point.
|
|
754
|
+
|
|
755
|
+
"""
|
|
756
|
+
levels = self._colorbar_region_line_positions(region)
|
|
757
|
+
if levels is None:
|
|
758
|
+
return None, 0.0
|
|
759
|
+
|
|
760
|
+
axis_position = self._colorbar_alt_range_drag_axis_position(
|
|
761
|
+
bar,
|
|
762
|
+
position,
|
|
763
|
+
region,
|
|
764
|
+
)
|
|
765
|
+
low, high = levels
|
|
766
|
+
if axis_position < low:
|
|
767
|
+
return ("bar", -1.0), -1.0
|
|
768
|
+
if axis_position > high:
|
|
769
|
+
return ("bar", 1.0), 1.0
|
|
770
|
+
return None, 0.0
|
|
771
|
+
|
|
772
|
+
def _colorbar_alt_range_drag_event(
|
|
773
|
+
self,
|
|
774
|
+
bar,
|
|
775
|
+
region,
|
|
776
|
+
event,
|
|
777
|
+
source,
|
|
778
|
+
direction,
|
|
779
|
+
event_item,
|
|
780
|
+
):
|
|
781
|
+
"""
|
|
782
|
+
Expand or contract the color range around its midpoint during Alt-drag.
|
|
783
|
+
|
|
784
|
+
"""
|
|
785
|
+
event.accept()
|
|
786
|
+
|
|
787
|
+
if event.isStart():
|
|
788
|
+
levels = self._colorbar_levels_from_bar(bar)
|
|
789
|
+
if levels is None:
|
|
790
|
+
region._qplot_colorbar_alt_range_drag_active = False
|
|
791
|
+
region._qplot_colorbar_alt_range_drag_source = None
|
|
792
|
+
return
|
|
793
|
+
|
|
794
|
+
region._qplot_colorbar_alt_range_drag_active = True
|
|
795
|
+
region._qplot_colorbar_alt_range_drag_source = source
|
|
796
|
+
region._qplot_colorbar_alt_range_drag_levels = levels
|
|
797
|
+
region._qplot_colorbar_alt_range_drag_start_pos = (
|
|
798
|
+
self._colorbar_alt_range_drag_axis_position(
|
|
799
|
+
bar,
|
|
800
|
+
event.buttonDownPos(),
|
|
801
|
+
event_item,
|
|
802
|
+
)
|
|
803
|
+
)
|
|
804
|
+
region._qplot_colorbar_drag_visual_area = "outside"
|
|
805
|
+
|
|
806
|
+
if (
|
|
807
|
+
not getattr(region, "_qplot_colorbar_alt_range_drag_active", False)
|
|
808
|
+
or getattr(region, "_qplot_colorbar_alt_range_drag_source", None) != source
|
|
809
|
+
):
|
|
810
|
+
return
|
|
811
|
+
|
|
812
|
+
start_levels = region._qplot_colorbar_alt_range_drag_levels
|
|
813
|
+
start_pos = region._qplot_colorbar_alt_range_drag_start_pos
|
|
814
|
+
current_pos = self._colorbar_alt_range_drag_axis_position(
|
|
815
|
+
bar,
|
|
816
|
+
event.pos(),
|
|
817
|
+
event_item,
|
|
818
|
+
)
|
|
819
|
+
axis_delta = (current_pos - start_pos) * direction
|
|
820
|
+
self._set_colorbar_alt_range_drag_visual(region, axis_delta)
|
|
821
|
+
levels = self._colorbar_alt_range_drag_levels(
|
|
822
|
+
bar,
|
|
823
|
+
start_levels,
|
|
824
|
+
axis_delta,
|
|
825
|
+
)
|
|
826
|
+
if levels is not None:
|
|
827
|
+
bar.setLevels(levels)
|
|
828
|
+
self._colorbar_interactive_levels_changed(bar)
|
|
829
|
+
|
|
830
|
+
if event.isFinish():
|
|
831
|
+
region._qplot_colorbar_alt_range_drag_active = False
|
|
832
|
+
region._qplot_colorbar_alt_range_drag_source = None
|
|
833
|
+
region._qplot_colorbar_drag_visual_area = None
|
|
834
|
+
self._set_colorbar_alt_range_drag_visual(region, 0.0)
|
|
835
|
+
self._colorbar_interactive_levels_finished(bar)
|
|
836
|
+
|
|
837
|
+
def _colorbar_alt_range_drag_axis_position(self, bar, position, item=None):
|
|
838
|
+
"""
|
|
839
|
+
Return the colorbar-axis coordinate from a region mouse position.
|
|
840
|
+
|
|
841
|
+
"""
|
|
842
|
+
if item is not None:
|
|
843
|
+
try:
|
|
844
|
+
position = item.mapToParent(position)
|
|
845
|
+
except (AttributeError, RuntimeError):
|
|
846
|
+
pass
|
|
847
|
+
|
|
848
|
+
if getattr(bar, "horizontal", False):
|
|
849
|
+
return position.x()
|
|
850
|
+
|
|
851
|
+
return position.y()
|
|
852
|
+
|
|
853
|
+
def _set_colorbar_alt_range_drag_visual(self, region, axis_delta):
|
|
854
|
+
"""
|
|
855
|
+
Move pyqtgraph's range markers apart or together during Alt-drag.
|
|
856
|
+
|
|
857
|
+
"""
|
|
858
|
+
lines = getattr(region, "lines", None)
|
|
859
|
+
if lines is None or len(lines) != 2:
|
|
860
|
+
return
|
|
861
|
+
|
|
862
|
+
axis_delta = min(max(axis_delta, -63.0), 63.0)
|
|
863
|
+
previous_block = getattr(region, "blockLineSignal", False)
|
|
864
|
+
region.blockLineSignal = True
|
|
865
|
+
try:
|
|
866
|
+
lines[0].setPos(63.0 - axis_delta)
|
|
867
|
+
lines[1].setPos(191.0 + axis_delta)
|
|
868
|
+
region.prepareGeometryChange()
|
|
869
|
+
finally:
|
|
870
|
+
region.blockLineSignal = previous_block
|
|
871
|
+
|
|
872
|
+
def _colorbar_alt_range_drag_levels(self, bar, start_levels, axis_delta):
|
|
873
|
+
"""
|
|
874
|
+
Return levels widened or narrowed symmetrically by an Alt-drag delta.
|
|
875
|
+
|
|
876
|
+
"""
|
|
877
|
+
low, high = start_levels
|
|
878
|
+
span = high - low
|
|
879
|
+
if not np.isfinite(span) or span <= 0:
|
|
880
|
+
return None
|
|
881
|
+
|
|
882
|
+
rounding = getattr(bar, "rounding", 0.0)
|
|
883
|
+
try:
|
|
884
|
+
rounding = float(rounding)
|
|
885
|
+
except (TypeError, ValueError):
|
|
886
|
+
rounding = 0.0
|
|
887
|
+
if not np.isfinite(rounding) or rounding <= 0:
|
|
888
|
+
rounding = 0.0
|
|
889
|
+
|
|
890
|
+
drag_units = axis_delta / 64.0
|
|
891
|
+
signed_drag = math.copysign(drag_units * drag_units, drag_units)
|
|
892
|
+
scale = span + 2 * rounding
|
|
893
|
+
center = (low + high) / 2.0
|
|
894
|
+
half_span = span / 2.0 + scale * signed_drag
|
|
895
|
+
min_span = rounding if rounding > 0 else max(abs(span) * 1e-9, 1e-300)
|
|
896
|
+
half_span = max(half_span, min_span / 2.0)
|
|
897
|
+
|
|
898
|
+
lo_lim = getattr(bar, "lo_lim", None)
|
|
899
|
+
hi_lim = getattr(bar, "hi_lim", None)
|
|
900
|
+
if lo_lim is not None and np.isfinite(lo_lim):
|
|
901
|
+
half_span = min(half_span, center - lo_lim)
|
|
902
|
+
if hi_lim is not None and np.isfinite(hi_lim):
|
|
903
|
+
half_span = min(half_span, hi_lim - center)
|
|
904
|
+
if half_span <= 0:
|
|
905
|
+
return None
|
|
906
|
+
|
|
907
|
+
low = center - half_span
|
|
908
|
+
high = center + half_span
|
|
909
|
+
|
|
910
|
+
if rounding > 0:
|
|
911
|
+
low = rounding * round(low / rounding)
|
|
912
|
+
high = rounding * round(high / rounding)
|
|
913
|
+
|
|
914
|
+
if lo_lim is not None and np.isfinite(lo_lim):
|
|
915
|
+
low = max(low, lo_lim)
|
|
916
|
+
if hi_lim is not None and np.isfinite(hi_lim):
|
|
917
|
+
high = min(high, hi_lim)
|
|
918
|
+
if not np.isfinite(low) or not np.isfinite(high) or low >= high:
|
|
919
|
+
return None
|
|
920
|
+
|
|
921
|
+
return float(low), float(high)
|
|
922
|
+
|
|
923
|
+
def _colorbar_levels_from_bar(self, bar):
|
|
924
|
+
"""
|
|
925
|
+
Return validated numeric levels from a pyqtgraph colorbar.
|
|
926
|
+
|
|
927
|
+
"""
|
|
928
|
+
try:
|
|
929
|
+
low, high = bar.levels()
|
|
930
|
+
except (AttributeError, TypeError, ValueError):
|
|
931
|
+
return None
|
|
932
|
+
|
|
933
|
+
try:
|
|
934
|
+
low = float(low)
|
|
935
|
+
high = float(high)
|
|
936
|
+
except (TypeError, ValueError):
|
|
937
|
+
return None
|
|
938
|
+
|
|
939
|
+
if not np.isfinite(low) or not np.isfinite(high) or low >= high:
|
|
940
|
+
return None
|
|
941
|
+
|
|
942
|
+
return low, high
|
|
943
|
+
|
|
944
|
+
def _colorbar_interactive_levels_changed(self, bar):
|
|
945
|
+
"""
|
|
946
|
+
Mirror interactively adjusted levels into the scale dialog.
|
|
947
|
+
|
|
948
|
+
"""
|
|
949
|
+
levels = self._colorbar_levels_from_bar(bar)
|
|
950
|
+
if levels is None:
|
|
951
|
+
return
|
|
952
|
+
|
|
953
|
+
self._sync_colorbar_axis_scaling()
|
|
954
|
+
self._sync_colorbar_level_fields(*levels)
|
|
955
|
+
|
|
956
|
+
def _colorbar_interactive_levels_finished(self, bar):
|
|
957
|
+
"""
|
|
958
|
+
Persist interactively adjusted colorbar levels as manual scaling.
|
|
959
|
+
|
|
960
|
+
"""
|
|
961
|
+
levels = self._colorbar_levels_from_bar(bar)
|
|
962
|
+
if levels is None:
|
|
963
|
+
return
|
|
964
|
+
|
|
965
|
+
self._colorbar_manual_levels = levels
|
|
966
|
+
if "relevel_refresh" in self.__dict__:
|
|
967
|
+
self.relevel_refresh.setChecked(False)
|
|
968
|
+
if "colorbar_manual_radio" in self.__dict__:
|
|
969
|
+
self.colorbar_manual_radio.setChecked(True)
|
|
970
|
+
if "colorbar_auto_radio" in self.__dict__:
|
|
971
|
+
self.colorbar_auto_radio.setChecked(False)
|
|
972
|
+
self._sync_colorbar_level_fields(*levels)
|
|
973
|
+
|
|
974
|
+
def _current_colorbar_levels(self):
|
|
975
|
+
"""
|
|
976
|
+
Return the currently displayed colorbar levels for menu synchronisation.
|
|
977
|
+
|
|
978
|
+
"""
|
|
979
|
+
bar = self.__dict__.get("bar")
|
|
980
|
+
if bar is not None:
|
|
981
|
+
return bar.levels()
|
|
982
|
+
|
|
983
|
+
manual_levels = getattr(self, "_colorbar_manual_levels", None)
|
|
984
|
+
if manual_levels is not None:
|
|
985
|
+
return manual_levels
|
|
986
|
+
|
|
987
|
+
return self._data_colorbar_levels()
|
|
988
|
+
|
|
989
|
+
def _current_colorbar_colormap_name(self):
|
|
990
|
+
"""
|
|
991
|
+
Return the selected colorbar color map preference.
|
|
992
|
+
|
|
993
|
+
"""
|
|
994
|
+
available_colormaps = self._available_colorbar_colormaps()
|
|
995
|
+
name = getattr(self, "_colorbar_colormap_name", None)
|
|
996
|
+
if name in available_colormaps:
|
|
997
|
+
return name
|
|
998
|
+
|
|
999
|
+
name = _config_value(
|
|
1000
|
+
self.__dict__.get("config"),
|
|
1001
|
+
"user_preference.bar_colour",
|
|
1002
|
+
"viridis",
|
|
1003
|
+
)
|
|
1004
|
+
|
|
1005
|
+
if name not in available_colormaps:
|
|
1006
|
+
name = self._fallback_colorbar_colormap_name()
|
|
1007
|
+
|
|
1008
|
+
self._colorbar_colormap_name = name
|
|
1009
|
+
return name
|
|
1010
|
+
|
|
1011
|
+
def _available_colorbar_colormaps(self):
|
|
1012
|
+
"""
|
|
1013
|
+
Return color maps after applying persistent user filters.
|
|
1014
|
+
|
|
1015
|
+
"""
|
|
1016
|
+
config_obj = self.__dict__.get("config")
|
|
1017
|
+
include_cet = _config_value(
|
|
1018
|
+
config_obj,
|
|
1019
|
+
"user_preference.bar_colour_include_cet",
|
|
1020
|
+
True,
|
|
1021
|
+
)
|
|
1022
|
+
include_matplotlib = _config_value(
|
|
1023
|
+
config_obj,
|
|
1024
|
+
"user_preference.bar_colour_include_matplotlib",
|
|
1025
|
+
True,
|
|
1026
|
+
)
|
|
1027
|
+
include_local = _config_value(
|
|
1028
|
+
config_obj,
|
|
1029
|
+
"user_preference.bar_colour_include_local",
|
|
1030
|
+
True,
|
|
1031
|
+
)
|
|
1032
|
+
include_custom = _config_value(
|
|
1033
|
+
config_obj,
|
|
1034
|
+
"user_preference.bar_colour_include_custom",
|
|
1035
|
+
True,
|
|
1036
|
+
)
|
|
1037
|
+
include_cet_subtypes = {
|
|
1038
|
+
subtype: _config_value(
|
|
1039
|
+
config_obj,
|
|
1040
|
+
_colorbar_subtype_config_key("cet", subtype),
|
|
1041
|
+
True,
|
|
1042
|
+
)
|
|
1043
|
+
for subtype, _label in _CET_COLORBAR_SUBTYPES
|
|
1044
|
+
}
|
|
1045
|
+
include_matplotlib_subtypes = {
|
|
1046
|
+
subtype: _config_value(
|
|
1047
|
+
config_obj,
|
|
1048
|
+
_colorbar_subtype_config_key("matplotlib", subtype),
|
|
1049
|
+
True,
|
|
1050
|
+
)
|
|
1051
|
+
for subtype, _label in _MATPLOTLIB_COLORBAR_SUBTYPES
|
|
1052
|
+
}
|
|
1053
|
+
excluded_names = set(_config_value(
|
|
1054
|
+
config_obj,
|
|
1055
|
+
"user_preference.bar_colour_excluded",
|
|
1056
|
+
[],
|
|
1057
|
+
))
|
|
1058
|
+
excluded_prefixes = tuple(_config_value(
|
|
1059
|
+
config_obj,
|
|
1060
|
+
"user_preference.bar_colour_excluded_prefixes",
|
|
1061
|
+
[],
|
|
1062
|
+
))
|
|
1063
|
+
|
|
1064
|
+
available = []
|
|
1065
|
+
for name in _COLORBAR_COLORMAPS:
|
|
1066
|
+
if name in _DEFAULT_HIDDEN_COLORBAR_NAMES:
|
|
1067
|
+
continue
|
|
1068
|
+
if name.startswith(_DEFAULT_HIDDEN_COLORBAR_PREFIXES):
|
|
1069
|
+
continue
|
|
1070
|
+
if name.endswith(_DEFAULT_HIDDEN_COLORBAR_SUFFIXES):
|
|
1071
|
+
continue
|
|
1072
|
+
if name in excluded_names:
|
|
1073
|
+
continue
|
|
1074
|
+
if excluded_prefixes and name.startswith(excluded_prefixes):
|
|
1075
|
+
continue
|
|
1076
|
+
|
|
1077
|
+
group = _colorbar_colormap_group(name)
|
|
1078
|
+
if group == "cet" and not include_cet:
|
|
1079
|
+
continue
|
|
1080
|
+
if (
|
|
1081
|
+
group == "cet"
|
|
1082
|
+
and not include_cet_subtypes[_cet_colorbar_colormap_subtype(name)]
|
|
1083
|
+
):
|
|
1084
|
+
continue
|
|
1085
|
+
if group == "matplotlib" and not include_matplotlib:
|
|
1086
|
+
continue
|
|
1087
|
+
if (
|
|
1088
|
+
group == "matplotlib"
|
|
1089
|
+
and not include_matplotlib_subtypes[
|
|
1090
|
+
_matplotlib_colorbar_colormap_subtype(name)
|
|
1091
|
+
]
|
|
1092
|
+
):
|
|
1093
|
+
continue
|
|
1094
|
+
if group == "pyqtgraph" and not include_local:
|
|
1095
|
+
continue
|
|
1096
|
+
if group == "custom" and not include_custom:
|
|
1097
|
+
continue
|
|
1098
|
+
|
|
1099
|
+
available.append(name)
|
|
1100
|
+
|
|
1101
|
+
return tuple(available)
|
|
1102
|
+
|
|
1103
|
+
def _fallback_colorbar_colormap_name(self):
|
|
1104
|
+
"""
|
|
1105
|
+
Choose a usable map when the saved preference is filtered out.
|
|
1106
|
+
|
|
1107
|
+
"""
|
|
1108
|
+
available_colormaps = self._available_colorbar_colormaps()
|
|
1109
|
+
if "viridis" in available_colormaps:
|
|
1110
|
+
return "viridis"
|
|
1111
|
+
if not available_colormaps:
|
|
1112
|
+
return "viridis"
|
|
1113
|
+
return available_colormaps[0]
|
|
1114
|
+
|
|
1115
|
+
def _colorbar_colormap(self, name=None):
|
|
1116
|
+
"""
|
|
1117
|
+
Return a pyqtgraph color map or built-in map name for the colorbar.
|
|
1118
|
+
|
|
1119
|
+
"""
|
|
1120
|
+
if name is None:
|
|
1121
|
+
name = self._current_colorbar_colormap_name()
|
|
1122
|
+
|
|
1123
|
+
return _colorbar_colormap_for_name(name)
|
|
1124
|
+
|
|
1125
|
+
###############################################################################
|
|
1126
|
+
# Subplot control
|