qplotter 1.6.0__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.
Files changed (130) hide show
  1. qplot/__init__.py +46 -0
  2. qplot/__main__.py +232 -0
  3. qplot/_auxiliary_paths.py +67 -0
  4. qplot/_build_info.json +1 -0
  5. qplot/_metadata.py +75 -0
  6. qplot/_repair.py +14 -0
  7. qplot/_shutdown_supervisor.py +4049 -0
  8. qplot/_version.py +12 -0
  9. qplot/_windows_shutdown_job.py +1166 -0
  10. qplot/configuration/__init__.py +3 -0
  11. qplot/configuration/config.py +578 -0
  12. qplot/configuration/config_schema.json +357 -0
  13. qplot/configuration/fonts.py +3 -0
  14. qplot/configuration/scripts.py +255 -0
  15. qplot/configuration/themes/__init__.py +13 -0
  16. qplot/configuration/themes/_base.py +592 -0
  17. qplot/configuration/themes/blank.py +17 -0
  18. qplot/configuration/themes/dark.py +54 -0
  19. qplot/configuration/themes/light.py +54 -0
  20. qplot/datahandling/LoadFromDB.py +224 -0
  21. qplot/datahandling/__init__.py +47 -0
  22. qplot/datahandling/_trusted_live_helper.py +812 -0
  23. qplot/datahandling/_trusted_live_protocol.py +1745 -0
  24. qplot/datahandling/database.py +2340 -0
  25. qplot/datahandling/dimensions.py +43 -0
  26. qplot/datahandling/file_identity.py +461 -0
  27. qplot/datahandling/parameter_data.py +149 -0
  28. qplot/datahandling/plot_progress.py +17 -0
  29. qplot/datahandling/qcodes_cache.py +277 -0
  30. qplot/datahandling/qcodes_compat.py +14 -0
  31. qplot/datahandling/readSQL.py +2442 -0
  32. qplot/datahandling/readonly.py +2566 -0
  33. qplot/datahandling/source_activity.py +21 -0
  34. qplot/datahandling/trusted_array_preview.py +148 -0
  35. qplot/datahandling/trusted_derived_cache.py +1605 -0
  36. qplot/datahandling/trusted_derived_rendering.py +876 -0
  37. qplot/datahandling/trusted_heatmap.py +246 -0
  38. qplot/datahandling/trusted_live.py +2808 -0
  39. qplot/datahandling/trusted_live_queries.py +5907 -0
  40. qplot/datahandling/trusted_live_service.py +1919 -0
  41. qplot/datahandling/trusted_live_supervisor.py +2161 -0
  42. qplot/datahandling/trusted_plot.py +283 -0
  43. qplot/datahandling/trusted_presentation.py +1065 -0
  44. qplot/datahandling/trusted_snapshot.py +1132 -0
  45. qplot/datahandling/trusted_work_coordinator.py +2157 -0
  46. qplot/datahandling/trusted_work_scheduler.py +1204 -0
  47. qplot/diagnostics.py +241 -0
  48. qplot/resources/testdata/qplot_test_db_01_10mb.csv +11 -0
  49. qplot/resources/testdata/qplot_test_db_02_25mb.csv +14 -0
  50. qplot/resources/testdata/qplot_test_db_03_50mb.csv +17 -0
  51. qplot/resources/testdata/qplot_test_db_04_100mb.csv +20 -0
  52. qplot/resources/testdata/qplot_test_db_05_250mb.csv +23 -0
  53. qplot/resources/testdata/qplot_test_db_06_500mb.csv +26 -0
  54. qplot/resources/testdata/qplot_test_db_07_1gb.csv +29 -0
  55. qplot/resources/testdata/qplot_test_db_08_5gb.csv +32 -0
  56. qplot/resources/testdata/qplot_test_db_09_10gb.csv +35 -0
  57. qplot/resources/testdata/qplot_test_db_10_30gb.csv +38 -0
  58. qplot/testdata.py +1804 -0
  59. qplot/tools/__init__.py +13 -0
  60. qplot/tools/finite_means.py +123 -0
  61. qplot/tools/general.py +139 -0
  62. qplot/tools/heatmap_geometry.py +391 -0
  63. qplot/tools/operation_registry.py +179 -0
  64. qplot/tools/plot_tools.py +650 -0
  65. qplot/tools/sample_statistics.py +111 -0
  66. qplot/tools/worker.py +2838 -0
  67. qplot/windows/__init__.py +9 -0
  68. qplot/windows/_colorbar.py +422 -0
  69. qplot/windows/_commands.py +523 -0
  70. qplot/windows/_config_persistence.py +123 -0
  71. qplot/windows/_database_actions.py +4933 -0
  72. qplot/windows/_dataset_handle.py +221 -0
  73. qplot/windows/_display_precision.py +48 -0
  74. qplot/windows/_dragdrop.py +70 -0
  75. qplot/windows/_export_paths.py +1167 -0
  76. qplot/windows/_help.py +210 -0
  77. qplot/windows/_mixed_appearance.py +303 -0
  78. qplot/windows/_mixed_plots.py +461 -0
  79. qplot/windows/_native_averaging.py +323 -0
  80. qplot/windows/_native_transforms.py +551 -0
  81. qplot/windows/_plot1d_snap.py +612 -0
  82. qplot/windows/_plot1d_traces.py +2236 -0
  83. qplot/windows/_plot2d_colorbar.py +1127 -0
  84. qplot/windows/_plot2d_colorbar_dialog.py +786 -0
  85. qplot/windows/_plot2d_layers.py +2291 -0
  86. qplot/windows/_plot2d_sweeps.py +712 -0
  87. qplot/windows/_plotWin.py +2082 -0
  88. qplot/windows/_plot_actions.py +2703 -0
  89. qplot/windows/_plot_annotation.py +186 -0
  90. qplot/windows/_plot_appearance.py +283 -0
  91. qplot/windows/_plot_axis_scaling.py +1521 -0
  92. qplot/windows/_plot_data_viewbox.py +94 -0
  93. qplot/windows/_plot_drop_axes.py +224 -0
  94. qplot/windows/_plot_export.py +1445 -0
  95. qplot/windows/_plot_feedback.py +112 -0
  96. qplot/windows/_plot_marquee.py +659 -0
  97. qplot/windows/_plot_refresh.py +950 -0
  98. qplot/windows/_plot_source_promotion.py +178 -0
  99. qplot/windows/_plot_state.py +237 -0
  100. qplot/windows/_plot_status.py +115 -0
  101. qplot/windows/_plot_tiling.py +35 -0
  102. qplot/windows/_plot_transform_labels.py +60 -0
  103. qplot/windows/_preferences.py +724 -0
  104. qplot/windows/_refresh_interval.py +172 -0
  105. qplot/windows/_run_controls.py +789 -0
  106. qplot/windows/_shortcuts.py +47 -0
  107. qplot/windows/_subplots/__init__.py +8 -0
  108. qplot/windows/_subplots/subplot1d.py +532 -0
  109. qplot/windows/_subplots/subplot2d.py +919 -0
  110. qplot/windows/_svg_export.py +209 -0
  111. qplot/windows/_trusted_derived_qt.py +1759 -0
  112. qplot/windows/_widgets/__init__.py +23 -0
  113. qplot/windows/_widgets/_run_formatting.py +387 -0
  114. qplot/windows/_widgets/details_tables.py +1119 -0
  115. qplot/windows/_widgets/dropbox.py +290 -0
  116. qplot/windows/_widgets/operations.py +460 -0
  117. qplot/windows/_widgets/preview.py +2997 -0
  118. qplot/windows/_widgets/run_list_items.py +488 -0
  119. qplot/windows/_widgets/toolbar.py +111 -0
  120. qplot/windows/_widgets/treeWidgets.py +2572 -0
  121. qplot/windows/_window_controls.py +414 -0
  122. qplot/windows/main.py +1632 -0
  123. qplot/windows/plot1d.py +313 -0
  124. qplot/windows/plot2d.py +1935 -0
  125. qplotter-1.6.0.dist-info/METADATA +322 -0
  126. qplotter-1.6.0.dist-info/RECORD +130 -0
  127. qplotter-1.6.0.dist-info/WHEEL +5 -0
  128. qplotter-1.6.0.dist-info/entry_points.txt +4 -0
  129. qplotter-1.6.0.dist-info/licenses/LICENSE +21 -0
  130. qplotter-1.6.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,786 @@
1
+ from collections.abc import Callable
2
+ from typing import TYPE_CHECKING, Any, cast
3
+
4
+ import numpy as np
5
+ from PyQt6 import QtCore, QtGui
6
+ from PyQt6 import QtWidgets as qtw
7
+
8
+ from ._colorbar import (
9
+ _CET_COLORBAR_SUBTYPES,
10
+ _COLORBAR_COLORMAP_LABELS,
11
+ _COLORBAR_TABLE_SORT_ROLE,
12
+ _MATPLOTLIB_COLORBAR_SUBTYPES,
13
+ _colorbar_colormap_preview,
14
+ _colorbar_colormap_type_label,
15
+ _colorbar_subtype_config_key,
16
+ _ColorbarColormapTableItem,
17
+ _config_value,
18
+ )
19
+ from ._config_persistence import persist_config_value
20
+
21
+ if TYPE_CHECKING:
22
+ class _ColorbarScaleDialogBase:
23
+ _colorbar_colormap_name: str
24
+ _colorbar_manual_levels: tuple[float, float] | None
25
+ bar: Any
26
+ colorbar_auto_radio: qtw.QRadioButton
27
+ colorbar_button_group: qtw.QButtonGroup
28
+ colorbar_cet_subtype_checks: dict[str, qtw.QCheckBox]
29
+ colorbar_colormap_table: qtw.QTableWidget
30
+ colorbar_include_cet_check: qtw.QCheckBox
31
+ colorbar_include_custom_check: qtw.QCheckBox
32
+ colorbar_include_local_check: qtw.QCheckBox
33
+ colorbar_include_matplotlib_check: qtw.QCheckBox
34
+ colorbar_manual_radio: qtw.QRadioButton
35
+ colorbar_matplotlib_subtype_checks: dict[str, qtw.QCheckBox]
36
+ colorbar_max_label: qtw.QLabel
37
+ colorbar_max_text: qtw.QLineEdit
38
+ colorbar_min_label: qtw.QLabel
39
+ colorbar_min_text: qtw.QLineEdit
40
+ colorbar_copy_auto_limits_button: qtw.QToolButton
41
+ colorbar_scale_action: QtGui.QAction
42
+ colorbar_scale_controls: qtw.QWidget
43
+ colorbar_scale_dialog: qtw.QDialog
44
+ relevel_refresh: qtw.QCheckBox
45
+
46
+ def _available_colorbar_colormaps(self) -> tuple[str, ...]: ...
47
+ def _colorbar_colormap(self, name: str | None = None) -> Any: ...
48
+ def _current_colorbar_colormap_name(self) -> str: ...
49
+ def _current_colorbar_levels(self) -> tuple[float, float] | None: ...
50
+ def _data_colorbar_levels(self) -> tuple[float, float] | None: ...
51
+ def _set_colorbar_levels(self, vmin: float, vmax: float) -> None: ...
52
+ def scaleColorbar(self, event: Any = None) -> None: ...
53
+ def show_status(self, message: str, timeout: int = 5000) -> None: ...
54
+ def refreshWindow(self, force: bool = False) -> None: ...
55
+ else:
56
+ class _ColorbarScaleDialogBase:
57
+ pass
58
+
59
+
60
+ class _ColorbarAutoLimitsButton(qtw.QToolButton):
61
+ """Refresh its prospective-limit tooltip immediately before display."""
62
+
63
+ def __init__(self, refresh_tooltip: Callable[[], None], parent: qtw.QWidget):
64
+ super().__init__(parent)
65
+ self._refresh_tooltip = refresh_tooltip
66
+
67
+ def event(self, event: QtCore.QEvent | None) -> bool:
68
+ if event is not None and event.type() == QtCore.QEvent.Type.ToolTip:
69
+ self._refresh_tooltip()
70
+ return super().event(event)
71
+
72
+
73
+ class _CenteredIconDelegate(qtw.QStyledItemDelegate):
74
+ """
75
+ Paint icon-only table cells with the icon centered in the cell.
76
+
77
+ """
78
+
79
+ def paint(self, painter, option, index):
80
+ icon = index.data(QtCore.Qt.ItemDataRole.DecorationRole)
81
+ if not isinstance(icon, QtGui.QIcon) or icon.isNull():
82
+ super().paint(painter, option, index)
83
+ return
84
+
85
+ opt = qtw.QStyleOptionViewItem(option)
86
+ self.initStyleOption(opt, index)
87
+ opt.text = ""
88
+ opt.icon = QtGui.QIcon()
89
+
90
+ widget = opt.widget
91
+ style = widget.style() if widget else qtw.QApplication.style()
92
+ if style is None:
93
+ super().paint(painter, option, index)
94
+ return
95
+ style.drawControl(qtw.QStyle.ControlElement.CE_ItemViewItem, opt, painter, widget)
96
+
97
+ icon_size = opt.decorationSize
98
+ if not icon_size.isValid() or icon_size.isEmpty():
99
+ icon_size = icon.actualSize(opt.rect.size())
100
+ icon_size = icon_size.boundedTo(opt.rect.size())
101
+ icon_rect = QtCore.QRect(QtCore.QPoint(0, 0), icon_size)
102
+ icon_rect.moveCenter(opt.rect.center())
103
+
104
+ mode = QtGui.QIcon.Mode.Normal
105
+ if not (opt.state & qtw.QStyle.StateFlag.State_Enabled):
106
+ mode = QtGui.QIcon.Mode.Disabled
107
+ elif opt.state & qtw.QStyle.StateFlag.State_Selected:
108
+ mode = QtGui.QIcon.Mode.Selected
109
+
110
+ icon.paint(painter, icon_rect, QtCore.Qt.AlignmentFlag.AlignCenter, mode)
111
+
112
+
113
+ class ColorbarScaleDialogMixin(_ColorbarScaleDialogBase):
114
+ """
115
+ Dialog and table controls for heatmap color-scale configuration.
116
+
117
+ """
118
+
119
+ def _init_colorbar_scale_controls(self):
120
+ """
121
+ Build manual/auto color scale controls for the dialog.
122
+
123
+ """
124
+ controls = qtw.QWidget()
125
+ layout = qtw.QVBoxLayout(controls)
126
+ layout.setContentsMargins(6, 4, 6, 4)
127
+ layout.setSpacing(8)
128
+
129
+ self.colorbar_manual_radio = qtw.QRadioButton("Manual")
130
+ self.colorbar_auto_radio = qtw.QRadioButton("Auto")
131
+ self.colorbar_min_label = qtw.QLabel("Minimum")
132
+ self.colorbar_max_label = qtw.QLabel("Maximum")
133
+ self.colorbar_min_text = qtw.QLineEdit()
134
+ self.colorbar_max_text = qtw.QLineEdit()
135
+ self.colorbar_copy_auto_limits_button = _ColorbarAutoLimitsButton(
136
+ self._update_colorbar_auto_limits_tooltip,
137
+ controls,
138
+ )
139
+ self.colorbar_copy_auto_limits_button.setObjectName(
140
+ "colorbarCopyAutoLimitsButton"
141
+ )
142
+ self.colorbar_copy_auto_limits_button.setAccessibleName(
143
+ "Use auto color scale limits as manual limits"
144
+ )
145
+ self.colorbar_copy_auto_limits_button.setFixedSize(26, 24)
146
+ style = qtw.QApplication.style()
147
+ if style is not None:
148
+ self.colorbar_copy_auto_limits_button.setIcon(
149
+ style.standardIcon(qtw.QStyle.StandardPixmap.SP_BrowserReload)
150
+ )
151
+ self.colorbar_colormap_table = qtw.QTableWidget()
152
+ self.colorbar_include_cet_check = qtw.QCheckBox("CET")
153
+ self.colorbar_include_matplotlib_check = qtw.QCheckBox("Matplotlib")
154
+ self.colorbar_include_local_check = qtw.QCheckBox("Local")
155
+ self.colorbar_include_custom_check = qtw.QCheckBox("Custom")
156
+ self.colorbar_cet_subtype_checks = {}
157
+ self.colorbar_matplotlib_subtype_checks = {}
158
+ for subtype, label in _CET_COLORBAR_SUBTYPES:
159
+ self.colorbar_cet_subtype_checks[subtype] = qtw.QCheckBox(label)
160
+ for subtype, label in _MATPLOTLIB_COLORBAR_SUBTYPES:
161
+ self.colorbar_matplotlib_subtype_checks[subtype] = qtw.QCheckBox(label)
162
+ self._init_colorbar_colormap_table()
163
+
164
+ validator = QtGui.QDoubleValidator(cast(QtCore.QObject, self))
165
+ # Match Python's dot-decimal parsing/formatting regardless of Qt's
166
+ # default locale. Grouping must never change an input's meaning.
167
+ numeric_locale = QtCore.QLocale.c()
168
+ numeric_locale.setNumberOptions(
169
+ numeric_locale.numberOptions()
170
+ | QtCore.QLocale.NumberOption.RejectGroupSeparator
171
+ )
172
+ validator.setLocale(numeric_locale)
173
+ validator.setNotation(QtGui.QDoubleValidator.Notation.ScientificNotation)
174
+ self.colorbar_min_text.setValidator(validator)
175
+ self.colorbar_max_text.setValidator(validator)
176
+ self.colorbar_min_label.setBuddy(self.colorbar_min_text)
177
+ self.colorbar_max_label.setBuddy(self.colorbar_max_text)
178
+ self.colorbar_min_text.setAccessibleName("Color scale minimum")
179
+ self.colorbar_max_text.setAccessibleName("Color scale maximum")
180
+ for line_edit in (self.colorbar_min_text, self.colorbar_max_text):
181
+ line_edit.setMinimumWidth(80)
182
+ line_edit.setToolTip(
183
+ "Use a decimal point (e.g. 1.5 or 1.5e-3), without grouping separators."
184
+ )
185
+
186
+ self.colorbar_button_group = qtw.QButtonGroup(cast(QtCore.QObject, self))
187
+ self.colorbar_button_group.addButton(self.colorbar_manual_radio)
188
+ self.colorbar_button_group.addButton(self.colorbar_auto_radio)
189
+ self.colorbar_button_group.setExclusive(True)
190
+
191
+ range_controls = qtw.QWidget()
192
+ range_layout = qtw.QGridLayout(range_controls)
193
+ range_layout.setContentsMargins(0, 0, 0, 0)
194
+ range_layout.setHorizontalSpacing(4)
195
+ range_layout.setVerticalSpacing(4)
196
+ align_vcenter = QtCore.Qt.AlignmentFlag.AlignVCenter
197
+ range_layout.addWidget(self.colorbar_manual_radio, 0, 0, align_vcenter)
198
+ range_layout.setColumnMinimumWidth(1, 36)
199
+ range_layout.addWidget(self.colorbar_min_label, 0, 2, align_vcenter)
200
+ range_layout.addWidget(self.colorbar_min_text, 0, 3, align_vcenter)
201
+ range_layout.addWidget(self.colorbar_max_label, 0, 4, align_vcenter)
202
+ range_layout.addWidget(self.colorbar_max_text, 0, 5, align_vcenter)
203
+ range_layout.addWidget(
204
+ self.colorbar_copy_auto_limits_button,
205
+ 0,
206
+ 6,
207
+ align_vcenter,
208
+ )
209
+ range_layout.addWidget(self.colorbar_auto_radio, 1, 0)
210
+
211
+ filter_controls = self._init_colorbar_filter_controls()
212
+
213
+ layout.addWidget(qtw.QLabel("Colors"))
214
+ layout.addWidget(filter_controls)
215
+ layout.addWidget(self.colorbar_colormap_table, 1)
216
+ layout.addWidget(range_controls)
217
+
218
+ self.colorbar_scale_controls = controls
219
+
220
+ menu = getattr(self, "vbMenu", None)
221
+ if menu is not None:
222
+ self.colorbar_scale_action = QtGui.QAction("&Color Scale...", cast(QtCore.QObject, self))
223
+ self.colorbar_scale_action.triggered.connect(self.open_colorbar_scale_dialog)
224
+ before = getattr(self, "autoscaleSep", None)
225
+ menu.insertAction(before, self.colorbar_scale_action)
226
+
227
+ self.colorbar_colormap_table.itemSelectionChanged.connect(
228
+ self._colorbar_colormap_selection_changed
229
+ )
230
+ self.colorbar_include_cet_check.toggled.connect(
231
+ self._colorbar_include_cet_changed
232
+ )
233
+ self.colorbar_include_matplotlib_check.toggled.connect(
234
+ self._colorbar_include_matplotlib_changed
235
+ )
236
+ self.colorbar_include_local_check.toggled.connect(
237
+ self._colorbar_include_local_changed
238
+ )
239
+ self.colorbar_include_custom_check.toggled.connect(
240
+ self._colorbar_include_custom_changed
241
+ )
242
+ for subtype, widget in self.colorbar_cet_subtype_checks.items():
243
+ widget.toggled.connect(
244
+ lambda enabled, subtype=subtype: self._colorbar_include_subtype_changed(
245
+ "cet",
246
+ subtype,
247
+ enabled,
248
+ )
249
+ )
250
+ for subtype, widget in self.colorbar_matplotlib_subtype_checks.items():
251
+ widget.toggled.connect(
252
+ lambda enabled, subtype=subtype: self._colorbar_include_subtype_changed(
253
+ "matplotlib",
254
+ subtype,
255
+ enabled,
256
+ )
257
+ )
258
+ self.colorbar_manual_radio.clicked.connect(self._apply_colorbar_manual_fields)
259
+ self.colorbar_min_text.editingFinished.connect(self._apply_colorbar_manual_fields)
260
+ self.colorbar_max_text.editingFinished.connect(self._apply_colorbar_manual_fields)
261
+ self.colorbar_auto_radio.clicked.connect(self.setColorbarAuto)
262
+ self.colorbar_copy_auto_limits_button.clicked.connect(
263
+ self._copy_colorbar_auto_limits
264
+ )
265
+
266
+ self._sync_colorbar_scale_controls()
267
+
268
+ def _init_colorbar_filter_controls(self):
269
+ """
270
+ Build broad and subtype color-map filter controls.
271
+
272
+ """
273
+ controls = qtw.QWidget()
274
+ layout = qtw.QGridLayout(controls)
275
+ layout.setContentsMargins(0, 0, 0, 0)
276
+ layout.setHorizontalSpacing(8)
277
+ layout.setVerticalSpacing(2)
278
+
279
+ layout.addWidget(qtw.QLabel("Show"), 0, 0)
280
+ layout.addWidget(self.colorbar_include_cet_check, 0, 1)
281
+ layout.addWidget(self.colorbar_include_matplotlib_check, 0, 2)
282
+ layout.addWidget(self.colorbar_include_local_check, 0, 3)
283
+ layout.addWidget(self.colorbar_include_custom_check, 0, 4)
284
+
285
+ layout.addWidget(qtw.QLabel("CET"), 1, 0)
286
+ for column, (_subtype, widget) in enumerate(
287
+ self.colorbar_cet_subtype_checks.items(),
288
+ 1,
289
+ ):
290
+ layout.addWidget(widget, 1, column)
291
+
292
+ layout.addWidget(qtw.QLabel("Matplotlib"), 2, 0)
293
+ for column, (_subtype, widget) in enumerate(
294
+ self.colorbar_matplotlib_subtype_checks.items(),
295
+ 1,
296
+ ):
297
+ layout.addWidget(widget, 2, column)
298
+
299
+ layout.setColumnStretch(7, 1)
300
+ return controls
301
+
302
+ def _init_colorbar_colormap_table(self):
303
+ """
304
+ Build the scrollable color-map chooser with rendered previews.
305
+
306
+ """
307
+ table = self.colorbar_colormap_table
308
+ table.setColumnCount(3)
309
+ table.setHorizontalHeaderLabels(("Color map", "Preview", "Type"))
310
+ vertical_header = table.verticalHeader()
311
+ if vertical_header is not None:
312
+ vertical_header.hide()
313
+ table.setShowGrid(False)
314
+ table.setAlternatingRowColors(True)
315
+ table.setSelectionBehavior(qtw.QAbstractItemView.SelectionBehavior.SelectRows)
316
+ table.setSelectionMode(qtw.QAbstractItemView.SelectionMode.SingleSelection)
317
+ table.setEditTriggers(qtw.QAbstractItemView.EditTrigger.NoEditTriggers)
318
+ table.setSortingEnabled(True)
319
+ table.setMinimumSize(560, 360)
320
+ table.setIconSize(QtCore.QSize(220, 14))
321
+ table.setItemDelegateForColumn(1, _CenteredIconDelegate(table))
322
+
323
+ header = table.horizontalHeader()
324
+ if header is None:
325
+ return
326
+ header.setSectionResizeMode(0, qtw.QHeaderView.ResizeMode.ResizeToContents)
327
+ header.setSectionResizeMode(1, qtw.QHeaderView.ResizeMode.Stretch)
328
+ header.setSectionResizeMode(2, qtw.QHeaderView.ResizeMode.ResizeToContents)
329
+
330
+ self._populate_colorbar_colormap_table()
331
+
332
+ def _populate_colorbar_colormap_table(self):
333
+ """
334
+ Fill the color-map table using the current filter settings.
335
+
336
+ """
337
+ table = self.colorbar_colormap_table
338
+ table.blockSignals(True)
339
+ sorting_enabled = table.isSortingEnabled()
340
+ table.setSortingEnabled(False)
341
+ table.setRowCount(0)
342
+
343
+ colormaps = self._available_colorbar_colormaps()
344
+ table.setRowCount(len(colormaps))
345
+ for row, name in enumerate(colormaps):
346
+ label = _COLORBAR_COLORMAP_LABELS.get(name, name)
347
+ type_label = _colorbar_colormap_type_label(name)
348
+ name_item = _ColorbarColormapTableItem(label)
349
+ name_item.setData(QtCore.Qt.ItemDataRole.UserRole, name)
350
+ name_item.setData(_COLORBAR_TABLE_SORT_ROLE, label)
351
+ type_item = _ColorbarColormapTableItem(type_label)
352
+ type_item.setData(QtCore.Qt.ItemDataRole.UserRole, name)
353
+ type_item.setData(_COLORBAR_TABLE_SORT_ROLE, type_label)
354
+ preview_item = _ColorbarColormapTableItem()
355
+ preview_item.setData(QtCore.Qt.ItemDataRole.UserRole, name)
356
+ preview_item.setData(_COLORBAR_TABLE_SORT_ROLE, label)
357
+ preview_item.setIcon(QtGui.QIcon(_colorbar_colormap_preview(name)))
358
+
359
+ table.setItem(row, 0, name_item)
360
+ table.setItem(row, 1, preview_item)
361
+ table.setItem(row, 2, type_item)
362
+ table.setRowHeight(row, 20)
363
+
364
+ table.resizeColumnToContents(0)
365
+ table.resizeColumnToContents(2)
366
+ table.setSortingEnabled(sorting_enabled)
367
+ table.blockSignals(False)
368
+
369
+ def _sync_colorbar_filter_controls(self):
370
+ """
371
+ Mirror persisted broad color-map filters into the dialog controls.
372
+
373
+ """
374
+ config_obj = self.__dict__.get("config")
375
+ for widget, key in (
376
+ (
377
+ self.colorbar_include_cet_check,
378
+ "user_preference.bar_colour_include_cet",
379
+ ),
380
+ (
381
+ self.colorbar_include_matplotlib_check,
382
+ "user_preference.bar_colour_include_matplotlib",
383
+ ),
384
+ (
385
+ self.colorbar_include_local_check,
386
+ "user_preference.bar_colour_include_local",
387
+ ),
388
+ (
389
+ self.colorbar_include_custom_check,
390
+ "user_preference.bar_colour_include_custom",
391
+ ),
392
+ ):
393
+ widget.blockSignals(True)
394
+ widget.setChecked(_config_value(config_obj, key, True))
395
+ widget.blockSignals(False)
396
+
397
+ include_cet = self.colorbar_include_cet_check.isChecked()
398
+ include_matplotlib = self.colorbar_include_matplotlib_check.isChecked()
399
+ for subtype, widget in self.colorbar_cet_subtype_checks.items():
400
+ key = _colorbar_subtype_config_key("cet", subtype)
401
+ widget.blockSignals(True)
402
+ widget.setChecked(_config_value(config_obj, key, True))
403
+ widget.setEnabled(include_cet)
404
+ widget.blockSignals(False)
405
+
406
+ for subtype, widget in self.colorbar_matplotlib_subtype_checks.items():
407
+ key = _colorbar_subtype_config_key("matplotlib", subtype)
408
+ widget.blockSignals(True)
409
+ widget.setChecked(_config_value(config_obj, key, True))
410
+ widget.setEnabled(include_matplotlib)
411
+ widget.blockSignals(False)
412
+
413
+ def _set_colorbar_filter_setting(self, key, enabled):
414
+ """
415
+ Persist a broad color-map filter and rebuild the chooser.
416
+
417
+ """
418
+ config_obj = self.__dict__.get("config")
419
+ if config_obj is not None:
420
+ if not persist_config_value(
421
+ self,
422
+ config_obj,
423
+ key,
424
+ bool(enabled),
425
+ "the color-map filter",
426
+ self._sync_colorbar_filter_controls,
427
+ ):
428
+ return False
429
+
430
+ self._populate_colorbar_colormap_table()
431
+ self._sync_colorbar_scale_controls()
432
+ return True
433
+
434
+ def _set_colorbar_subtype_filter_setting(self, group, subtype, enabled):
435
+ """
436
+ Persist a color-map subtype filter and rebuild the chooser.
437
+
438
+ """
439
+ self._set_colorbar_filter_setting(
440
+ _colorbar_subtype_config_key(group, subtype),
441
+ enabled,
442
+ )
443
+
444
+ @QtCore.pyqtSlot(bool)
445
+ def _colorbar_include_cet_changed(self, enabled):
446
+ """
447
+ Include or exclude CET color maps in the chooser.
448
+
449
+ """
450
+ self._set_colorbar_filter_setting(
451
+ "user_preference.bar_colour_include_cet",
452
+ enabled,
453
+ )
454
+
455
+ @QtCore.pyqtSlot(bool)
456
+ def _colorbar_include_matplotlib_changed(self, enabled):
457
+ """
458
+ Include or exclude matplotlib color maps in the chooser.
459
+
460
+ """
461
+ self._set_colorbar_filter_setting(
462
+ "user_preference.bar_colour_include_matplotlib",
463
+ enabled,
464
+ )
465
+
466
+ @QtCore.pyqtSlot(bool)
467
+ def _colorbar_include_local_changed(self, enabled):
468
+ """
469
+ Include or exclude pyqtgraph local color maps in the chooser.
470
+
471
+ """
472
+ self._set_colorbar_filter_setting(
473
+ "user_preference.bar_colour_include_local",
474
+ enabled,
475
+ )
476
+
477
+ @QtCore.pyqtSlot(bool)
478
+ def _colorbar_include_custom_changed(self, enabled):
479
+ """
480
+ Include or exclude app-provided custom color maps in the chooser.
481
+
482
+ """
483
+ self._set_colorbar_filter_setting(
484
+ "user_preference.bar_colour_include_custom",
485
+ enabled,
486
+ )
487
+
488
+ def _colorbar_include_subtype_changed(self, group, subtype, enabled):
489
+ """
490
+ Include or exclude a color-map subtype in the chooser.
491
+
492
+ """
493
+ self._set_colorbar_subtype_filter_setting(group, subtype, enabled)
494
+
495
+ def _sync_colorbar_scale_controls(self):
496
+ """
497
+ Update color scale controls from the current colorbar state.
498
+
499
+ """
500
+ levels = self._current_colorbar_levels()
501
+ if levels is not None:
502
+ self._sync_colorbar_level_fields(*levels)
503
+
504
+ table = getattr(self, "colorbar_colormap_table", None)
505
+ if table is not None:
506
+ self._sync_colorbar_filter_controls()
507
+ self._select_colorbar_colormap(self._current_colorbar_colormap_name())
508
+
509
+ manual = getattr(self, "_colorbar_manual_levels", None) is not None
510
+ for widget in (self.colorbar_manual_radio, self.colorbar_auto_radio):
511
+ widget.blockSignals(True)
512
+
513
+ self.colorbar_manual_radio.setChecked(manual)
514
+ self.colorbar_auto_radio.setChecked(not manual)
515
+ self._update_colorbar_auto_limits_tooltip()
516
+
517
+ for widget in (self.colorbar_manual_radio, self.colorbar_auto_radio):
518
+ widget.blockSignals(False)
519
+
520
+ def _colorbar_auto_limits(self):
521
+ """Return validated prospective automatic color-scale limits."""
522
+
523
+ levels = self._data_colorbar_levels()
524
+ if levels is None:
525
+ return None
526
+ try:
527
+ vmin, vmax = (float(value) for value in levels)
528
+ except (TypeError, ValueError):
529
+ return None
530
+ if not np.isfinite(vmin) or not np.isfinite(vmax) or vmin >= vmax:
531
+ return None
532
+ return vmin, vmax
533
+
534
+ def _update_colorbar_auto_limits_tooltip(self):
535
+ """Describe the range that the copy-auto button would apply."""
536
+
537
+ button = self.colorbar_copy_auto_limits_button
538
+ levels = self._colorbar_auto_limits()
539
+ button.setEnabled(levels is not None)
540
+ if levels is None:
541
+ button.setToolTip("Auto limits are unavailable for the color scale.")
542
+ return
543
+ button.setToolTip(
544
+ f"Set manual limits to {levels[0]:.6g} and {levels[1]:.6g}."
545
+ )
546
+
547
+ @QtCore.pyqtSlot()
548
+ def _copy_colorbar_auto_limits(self):
549
+ """Freeze the prospective automatic color range as a manual range."""
550
+
551
+ levels = self._colorbar_auto_limits()
552
+ if levels is None:
553
+ self._update_colorbar_auto_limits_tooltip()
554
+ return
555
+ if self.setColorbarManualRange(*levels):
556
+ self._sync_colorbar_level_fields(*levels)
557
+ self._update_colorbar_auto_limits_tooltip()
558
+
559
+ def _sync_colorbar_level_fields(self, vmin, vmax):
560
+ """
561
+ Mirror colorbar levels into the dialog text fields.
562
+
563
+ """
564
+ if "colorbar_min_text" not in self.__dict__:
565
+ return
566
+
567
+ for widget, value in (
568
+ (self.colorbar_min_text, vmin),
569
+ (self.colorbar_max_text, vmax),
570
+ ):
571
+ widget.blockSignals(True)
572
+ widget.setText(repr(float(value)))
573
+ widget.blockSignals(False)
574
+
575
+ def _colorbar_colormap_row(self, name):
576
+ """
577
+ Return the table row for a color map name.
578
+
579
+ """
580
+ table = getattr(self, "colorbar_colormap_table", None)
581
+ if table is None:
582
+ return -1
583
+
584
+ for row in range(table.rowCount()):
585
+ item = table.item(row, 0)
586
+ if item is not None and item.data(QtCore.Qt.ItemDataRole.UserRole) == name:
587
+ return row
588
+
589
+ return -1
590
+
591
+ def _select_colorbar_colormap(self, name):
592
+ """
593
+ Select the current color map row without applying it again.
594
+
595
+ """
596
+ table = getattr(self, "colorbar_colormap_table", None)
597
+ if table is None:
598
+ return
599
+
600
+ row = self._colorbar_colormap_row(name)
601
+ if row < 0:
602
+ row = self._colorbar_colormap_row("viridis")
603
+ if row < 0:
604
+ return
605
+
606
+ table.blockSignals(True)
607
+ table.setCurrentCell(row, 0)
608
+ table.selectRow(row)
609
+ table.blockSignals(False)
610
+
611
+ @QtCore.pyqtSlot()
612
+ def _colorbar_colormap_selection_changed(self):
613
+ """
614
+ Apply the color map selected in the dialog.
615
+
616
+ """
617
+ table = self.colorbar_colormap_table
618
+ row = table.currentRow()
619
+ if row < 0:
620
+ return
621
+
622
+ item = table.item(row, 0)
623
+ if item is None:
624
+ return
625
+
626
+ name = item.data(QtCore.Qt.ItemDataRole.UserRole)
627
+ if name is None:
628
+ return
629
+
630
+ self.setColorbarColorMap(name)
631
+
632
+ @QtCore.pyqtSlot(int)
633
+ def _colorbar_colormap_changed(self, index):
634
+ """
635
+ Compatibility slot for older combo-box based tests or callers.
636
+
637
+ """
638
+ combo = getattr(self, "colorbar_colormap_combo", None)
639
+ if combo is None:
640
+ return
641
+
642
+ name = combo.itemData(index)
643
+ if name is not None:
644
+ self.setColorbarColorMap(name)
645
+
646
+ @QtCore.pyqtSlot()
647
+ def open_colorbar_scale_dialog(self):
648
+ """
649
+ Opens the color scale controls in a dialog.
650
+
651
+ """
652
+ controls = getattr(self, "colorbar_scale_controls", None)
653
+ if controls is None:
654
+ return
655
+
656
+ self._populate_colorbar_colormap_table()
657
+ self._sync_colorbar_scale_controls()
658
+
659
+ dialog = getattr(self, "colorbar_scale_dialog", None)
660
+ if dialog is None:
661
+ dialog = qtw.QDialog(cast(qtw.QWidget, self))
662
+ dialog.setWindowTitle("Color scale")
663
+ dialog.resize(520, 560)
664
+ layout = qtw.QVBoxLayout(dialog)
665
+ layout.addWidget(controls)
666
+
667
+ buttons = qtw.QDialogButtonBox(qtw.QDialogButtonBox.StandardButton.Close)
668
+ buttons.rejected.connect(dialog.close)
669
+ layout.addWidget(buttons)
670
+
671
+ self.colorbar_scale_dialog = dialog
672
+
673
+ dialog.show()
674
+ dialog.raise_()
675
+ dialog.activateWindow()
676
+
677
+ @QtCore.pyqtSlot()
678
+ def _apply_colorbar_manual_fields(self):
679
+ """
680
+ Apply color scale levels entered in the dialog.
681
+
682
+ """
683
+ if not all(
684
+ field.hasAcceptableInput()
685
+ for field in (self.colorbar_min_text, self.colorbar_max_text)
686
+ ):
687
+ self.show_status("Invalid color scale range.", 5000)
688
+ self._sync_colorbar_scale_controls()
689
+ return
690
+
691
+ try:
692
+ vmin = float(self.colorbar_min_text.text())
693
+ vmax = float(self.colorbar_max_text.text())
694
+ except ValueError:
695
+ self.show_status("Invalid color scale range.", 5000)
696
+ self._sync_colorbar_scale_controls()
697
+ return
698
+
699
+ self.setColorbarManualRange(vmin, vmax)
700
+
701
+ def setColorbarColorMap(self, name):
702
+ """
703
+ Set and persist the colorbar color map.
704
+
705
+ """
706
+ if name not in self._available_colorbar_colormaps():
707
+ show_status = getattr(self, "show_status", None)
708
+ if show_status is not None:
709
+ show_status("Unknown color map.", 5000)
710
+ return False
711
+
712
+ config_obj = self.__dict__.get("config")
713
+ if config_obj is not None:
714
+ previous_name = config_obj.get("user_preference.bar_colour")
715
+
716
+ def rollback():
717
+ self._select_colorbar_colormap(previous_name)
718
+
719
+ if not persist_config_value(
720
+ self,
721
+ config_obj,
722
+ "user_preference.bar_colour",
723
+ name,
724
+ "the color map",
725
+ rollback,
726
+ ):
727
+ return False
728
+
729
+ self._colorbar_colormap_name = name
730
+ bar = self.__dict__.get("bar")
731
+ if bar is not None:
732
+ bar.setColorMap(self._colorbar_colormap(name))
733
+
734
+ appearance_dialog = self.__dict__.get("_heatmap_appearance_dialog")
735
+ if appearance_dialog is not None:
736
+ appearance_dialog.refresh_color_scale()
737
+
738
+ return True
739
+
740
+ def setColorbarManualRange(self, vmin, vmax):
741
+ """
742
+ Set a persistent manual color scale range.
743
+
744
+ """
745
+ if not np.isfinite(vmin) or not np.isfinite(vmax) or vmin >= vmax:
746
+ self.show_status("Color scale minimum must be below maximum.", 5000)
747
+ self._sync_colorbar_scale_controls()
748
+ return False
749
+
750
+ if not np.isfinite(float(vmax) - float(vmin)):
751
+ self.show_status("The color scale range is too wide to display safely.", 5000)
752
+ self._sync_colorbar_scale_controls()
753
+ return False
754
+
755
+ self._colorbar_manual_levels = (float(vmin), float(vmax))
756
+
757
+ if "relevel_refresh" in self.__dict__:
758
+ self.relevel_refresh.setChecked(False)
759
+
760
+ self._set_colorbar_levels(*self._colorbar_manual_levels)
761
+ if "colorbar_manual_radio" in self.__dict__:
762
+ self.colorbar_manual_radio.setChecked(True)
763
+ if self.__dict__.get("_heatmap_color_range_rejected", False):
764
+ self.refreshWindow(force=True)
765
+ return True
766
+
767
+ @QtCore.pyqtSlot()
768
+ def setColorbarAuto(self):
769
+ """
770
+ Return the color scale to automatic data-range scaling.
771
+
772
+ """
773
+ self._colorbar_manual_levels = None
774
+ if "relevel_refresh" in self.__dict__:
775
+ self.relevel_refresh.setChecked(True)
776
+ self.scaleColorbar()
777
+
778
+ @QtCore.pyqtSlot(bool)
779
+ def _colorbar_auto_refresh_changed(self, enabled):
780
+ """
781
+ Keep colorbar manual state in sync with the refresh toolbar checkbox.
782
+
783
+ """
784
+ if enabled:
785
+ self._colorbar_manual_levels = None
786
+ self.scaleColorbar()