bec-widgets 1.25.1__py3-none-any.whl → 2.0.1__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 (196) hide show
  1. .gitlab-ci.yml +3 -5
  2. CHANGELOG.md +639 -0
  3. PKG-INFO +3 -3
  4. bec_widgets/__init__.py +4 -0
  5. bec_widgets/applications/bw_launch.py +23 -0
  6. bec_widgets/applications/launch_window.py +430 -0
  7. bec_widgets/assets/app_icons/auto_update.png +0 -0
  8. bec_widgets/assets/app_icons/ui_loader_tile.png +0 -0
  9. bec_widgets/cli/__init__.py +0 -1
  10. bec_widgets/cli/client.py +1779 -2064
  11. bec_widgets/cli/client_utils.py +346 -174
  12. bec_widgets/cli/generate_cli.py +143 -37
  13. bec_widgets/cli/rpc/rpc_base.py +152 -21
  14. bec_widgets/cli/rpc/rpc_register.py +113 -6
  15. bec_widgets/cli/rpc/rpc_widget_handler.py +13 -11
  16. bec_widgets/cli/server.py +125 -239
  17. bec_widgets/examples/jupyter_console/jupyter_console_window.py +97 -145
  18. bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py +1 -1
  19. bec_widgets/utils/bec_connector.py +190 -21
  20. bec_widgets/utils/bec_designer.py +7 -0
  21. bec_widgets/utils/bec_dispatcher.py +71 -4
  22. bec_widgets/utils/bec_plugin_helper.py +89 -0
  23. bec_widgets/utils/bec_signal_proxy.py +1 -1
  24. bec_widgets/utils/bec_widget.py +26 -10
  25. bec_widgets/utils/colors.py +1 -1
  26. bec_widgets/{qt_utils → utils}/compact_popup.py +2 -0
  27. bec_widgets/utils/container_utils.py +37 -12
  28. bec_widgets/utils/crosshair.py +25 -8
  29. bec_widgets/utils/entry_validator.py +3 -1
  30. bec_widgets/{qt_utils → utils}/error_popups.py +18 -0
  31. bec_widgets/{qt_utils → utils}/expandable_frame.py +2 -2
  32. bec_widgets/utils/forms_from_types/forms.py +182 -0
  33. bec_widgets/{widgets/editors/scan_metadata/_metadata_widgets.py → utils/forms_from_types/items.py} +41 -30
  34. bec_widgets/utils/generate_designer_plugin.py +40 -36
  35. bec_widgets/utils/linear_region_selector.py +2 -0
  36. bec_widgets/utils/name_utils.py +16 -0
  37. bec_widgets/{qt_utils → utils}/palette_viewer.py +2 -2
  38. bec_widgets/utils/plot_indicator_items.py +2 -5
  39. bec_widgets/utils/plugin_utils.py +47 -1
  40. bec_widgets/{qt_utils → utils}/round_frame.py +14 -14
  41. bec_widgets/utils/rpc_server.py +277 -0
  42. bec_widgets/utils/serialization.py +44 -0
  43. bec_widgets/{qt_utils → utils}/settings_dialog.py +26 -1
  44. bec_widgets/{qt_utils → utils}/side_panel.py +17 -10
  45. bec_widgets/{qt_utils → utils}/toolbar.py +69 -25
  46. bec_widgets/utils/ui_loader.py +8 -8
  47. bec_widgets/utils/widget_io.py +166 -25
  48. bec_widgets/widgets/containers/auto_update/auto_updates.py +364 -0
  49. bec_widgets/widgets/containers/dock/dock.py +157 -49
  50. bec_widgets/widgets/containers/dock/dock_area.py +188 -138
  51. bec_widgets/widgets/containers/layout_manager/layout_manager.py +2 -1
  52. bec_widgets/widgets/containers/main_window/addons/web_links.py +15 -0
  53. bec_widgets/widgets/containers/main_window/main_window.py +189 -41
  54. bec_widgets/widgets/control/buttons/button_abort/button_abort.py +3 -4
  55. bec_widgets/widgets/control/buttons/button_reset/button_reset.py +3 -4
  56. bec_widgets/widgets/control/buttons/button_resume/button_resume.py +3 -3
  57. bec_widgets/widgets/control/buttons/stop_button/stop_button.py +18 -7
  58. bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py +22 -3
  59. bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py +31 -13
  60. bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py +3 -1
  61. bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.ui +27 -4
  62. bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py +5 -2
  63. bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.ui +97 -31
  64. bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui +11 -4
  65. bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py +2 -3
  66. bec_widgets/widgets/control/device_input/base_classes/device_input_base.py +29 -4
  67. bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +1 -0
  68. bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +2 -2
  69. bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py +2 -2
  70. bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py +1 -2
  71. bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py +1 -2
  72. bec_widgets/widgets/control/scan_control/scan_control.py +7 -5
  73. bec_widgets/widgets/control/scan_control/scan_group_box.py +28 -5
  74. bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py +1 -2
  75. bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py +3 -4
  76. bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog_vertical.ui +14 -8
  77. bec_widgets/widgets/editors/console/console.py +1 -1
  78. bec_widgets/widgets/editors/{scan_metadata/additional_metadata_table.py → dict_backed_table.py} +29 -6
  79. bec_widgets/widgets/editors/scan_metadata/__init__.py +0 -7
  80. bec_widgets/widgets/editors/scan_metadata/_util.py +1 -1
  81. bec_widgets/widgets/{plots/motor_map/register_bec_motor_map_widget.py → editors/scan_metadata/register_scan_metadata.py} +2 -4
  82. bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +42 -136
  83. bec_widgets/widgets/editors/scan_metadata/scan_metadata.pyproject +1 -0
  84. bec_widgets/widgets/{plots/multi_waveform/bec_multi_waveform_widget_plugin.py → editors/scan_metadata/scan_metadata_plugin.py} +9 -9
  85. bec_widgets/widgets/editors/text_box/text_box.py +2 -3
  86. bec_widgets/widgets/editors/website/website.py +2 -2
  87. bec_widgets/widgets/games/minesweeper.py +3 -2
  88. bec_widgets/widgets/plots/image/image.py +960 -0
  89. bec_widgets/widgets/plots/image/image.pyproject +1 -0
  90. bec_widgets/widgets/plots/image/image_item.py +279 -0
  91. bec_widgets/widgets/plots/{motor_map/bec_motor_map_widget_plugin.py → image/image_plugin.py} +11 -13
  92. bec_widgets/widgets/{containers/figure/plots → plots}/image/image_processor.py +31 -64
  93. bec_widgets/widgets/plots/image/{register_bec_image_widget.py → register_image.py} +2 -2
  94. bec_widgets/widgets/plots/image/toolbar_bundles/image_selection.py +59 -0
  95. bec_widgets/widgets/plots/image/toolbar_bundles/processing.py +79 -0
  96. bec_widgets/widgets/plots/motor_map/motor_map.py +832 -0
  97. bec_widgets/widgets/plots/motor_map/motor_map.pyproject +1 -0
  98. bec_widgets/widgets/plots/motor_map/motor_map_plugin.py +54 -0
  99. bec_widgets/widgets/plots/{multi_waveform/register_bec_multi_waveform_widget.py → motor_map/register_motor_map.py} +2 -4
  100. bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py +129 -0
  101. bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui +120 -0
  102. bec_widgets/widgets/plots/motor_map/toolbar_bundles/motor_selection.py +70 -0
  103. bec_widgets/widgets/plots/multi_waveform/multi_waveform.py +508 -0
  104. bec_widgets/widgets/plots/multi_waveform/multi_waveform.pyproject +1 -0
  105. bec_widgets/widgets/plots/multi_waveform/multi_waveform_plugin.py +54 -0
  106. bec_widgets/widgets/plots/multi_waveform/register_multi_waveform.py +15 -0
  107. bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py +144 -0
  108. bec_widgets/widgets/plots/multi_waveform/settings/multi_waveform_controls.ui +164 -0
  109. bec_widgets/widgets/plots/multi_waveform/toolbar_bundles/monitor_selection.py +65 -0
  110. bec_widgets/widgets/{plots_next_gen → plots}/plot_base.py +321 -40
  111. bec_widgets/widgets/plots/{waveform/register_bec_waveform_widget.py → scatter_waveform/register_scatter_waveform.py} +3 -3
  112. bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py +197 -0
  113. bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py +553 -0
  114. bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.pyproject +1 -0
  115. bec_widgets/widgets/plots/{image/bec_image_widget_plugin.py → scatter_waveform/scatter_waveform_plugin.py} +9 -13
  116. bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py +138 -0
  117. bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_horizontal.ui +195 -0
  118. bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_vertical.ui +204 -0
  119. bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings.py +8 -8
  120. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/mouse_interactions.py +4 -18
  121. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/plot_export.py +14 -3
  122. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/roi_bundle.py +6 -1
  123. bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/save_state.py +2 -2
  124. bec_widgets/widgets/{containers/figure/plots/waveform/waveform_curve.py → plots/waveform/curve.py} +119 -49
  125. bec_widgets/widgets/plots/waveform/register_waveform.py +15 -0
  126. bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_setting.py +125 -0
  127. bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +576 -0
  128. bec_widgets/widgets/plots/waveform/utils/__init__.py +0 -0
  129. bec_widgets/widgets/plots/waveform/utils/roi_manager.py +84 -0
  130. bec_widgets/widgets/plots/waveform/waveform.py +1794 -0
  131. bec_widgets/widgets/plots/waveform/waveform.pyproject +1 -0
  132. bec_widgets/widgets/plots/waveform/{bec_waveform_widget_plugin.py → waveform_plugin.py} +9 -13
  133. bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py +1 -2
  134. bec_widgets/widgets/progress/ring_progress_bar/ring.py +11 -10
  135. bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py +24 -14
  136. bec_widgets/widgets/services/bec_queue/bec_queue.py +13 -11
  137. bec_widgets/widgets/services/bec_status_box/bec_status_box.py +3 -4
  138. bec_widgets/widgets/services/device_browser/device_browser.py +5 -2
  139. bec_widgets/widgets/services/device_browser/device_item/device_item.py +1 -1
  140. bec_widgets/widgets/utility/logpanel/logpanel.py +36 -17
  141. bec_widgets/widgets/utility/spinbox/decimal_spinbox.py +3 -3
  142. bec_widgets/widgets/utility/visual/color_button/color_button.py +1 -1
  143. bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py +4 -6
  144. bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py +4 -8
  145. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/METADATA +3 -3
  146. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/RECORD +168 -153
  147. pyproject.toml +3 -3
  148. bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +0 -198
  149. bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +0 -615
  150. bec_widgets/applications/bec_app.py +0 -84
  151. bec_widgets/cli/auto_updates.py +0 -168
  152. bec_widgets/widgets/containers/figure/__init__.py +0 -1
  153. bec_widgets/widgets/containers/figure/figure.py +0 -796
  154. bec_widgets/widgets/containers/figure/plots/axis_settings.py +0 -91
  155. bec_widgets/widgets/containers/figure/plots/axis_settings.ui +0 -256
  156. bec_widgets/widgets/containers/figure/plots/image/image.py +0 -772
  157. bec_widgets/widgets/containers/figure/plots/image/image_item.py +0 -337
  158. bec_widgets/widgets/containers/figure/plots/motor_map/motor_map.py +0 -525
  159. bec_widgets/widgets/containers/figure/plots/multi_waveform/multi_waveform.py +0 -340
  160. bec_widgets/widgets/containers/figure/plots/plot_base.py +0 -505
  161. bec_widgets/widgets/containers/figure/plots/waveform/waveform.py +0 -1563
  162. bec_widgets/widgets/plots/image/bec_image_widget.pyproject +0 -1
  163. bec_widgets/widgets/plots/image/image_widget.py +0 -515
  164. bec_widgets/widgets/plots/motor_map/bec_motor_map_widget.pyproject +0 -1
  165. bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.py +0 -56
  166. bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.ui +0 -108
  167. bec_widgets/widgets/plots/motor_map/motor_map_widget.py +0 -234
  168. bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject +0 -1
  169. bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui +0 -99
  170. bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py +0 -536
  171. bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject +0 -1
  172. bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py +0 -336
  173. bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui +0 -372
  174. bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py +0 -25
  175. bec_widgets/widgets/plots/waveform/waveform_widget.py +0 -751
  176. /bec_widgets/{qt_utils → utils}/collapsible_panel_manager.py +0 -0
  177. /bec_widgets/{applications/alignment → utils/forms_from_types}/__init__.py +0 -0
  178. /bec_widgets/{qt_utils → utils}/redis_message_waiter.py +0 -0
  179. /bec_widgets/{applications/alignment/alignment_1d → widgets/containers/auto_update}/__init__.py +0 -0
  180. /bec_widgets/{qt_utils → widgets/containers/main_window/addons}/__init__.py +0 -0
  181. /bec_widgets/widgets/{containers/figure/plots → plots/image/toolbar_bundles}/__init__.py +0 -0
  182. /bec_widgets/widgets/{containers/figure/plots/image → plots/motor_map/settings}/__init__.py +0 -0
  183. /bec_widgets/widgets/{containers/figure/plots/motor_map → plots/motor_map/toolbar_bundles}/__init__.py +0 -0
  184. /bec_widgets/widgets/{containers/figure/plots/multi_waveform → plots/multi_waveform/settings}/__init__.py +0 -0
  185. /bec_widgets/widgets/{containers/figure/plots/waveform → plots/multi_waveform/toolbar_bundles}/__init__.py +0 -0
  186. /bec_widgets/widgets/plots/{motor_map/motor_map_dialog → scatter_waveform}/__init__.py +0 -0
  187. /bec_widgets/widgets/plots/{waveform/waveform_popups → scatter_waveform/settings}/__init__.py +0 -0
  188. /bec_widgets/widgets/plots/{waveform/waveform_popups/curve_dialog → setting_menus}/__init__.py +0 -0
  189. /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_horizontal.ui +0 -0
  190. /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_vertical.ui +0 -0
  191. /bec_widgets/widgets/plots/{waveform/waveform_popups/dap_summary_dialog → toolbar_bundles}/__init__.py +0 -0
  192. /bec_widgets/widgets/{plots_next_gen/setting_menus → plots/waveform/settings}/__init__.py +0 -0
  193. /bec_widgets/widgets/{plots_next_gen/toolbar_bundles → plots/waveform/settings/curve_settings}/__init__.py +0 -0
  194. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/WHEEL +0 -0
  195. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/entry_points.txt +0 -0
  196. {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,508 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import deque
4
+
5
+ import pyqtgraph as pg
6
+ from bec_lib.endpoints import MessageEndpoints
7
+ from bec_lib.logger import bec_logger
8
+ from pydantic import Field, ValidationError, field_validator
9
+ from qtpy.QtCore import Signal
10
+ from qtpy.QtWidgets import QWidget
11
+
12
+ from bec_widgets.utils import Colors, ConnectionConfig
13
+ from bec_widgets.utils.error_popups import SafeProperty, SafeSlot
14
+ from bec_widgets.utils.side_panel import SidePanel
15
+ from bec_widgets.widgets.plots.multi_waveform.settings.control_panel import (
16
+ MultiWaveformControlPanel,
17
+ )
18
+ from bec_widgets.widgets.plots.multi_waveform.toolbar_bundles.monitor_selection import (
19
+ MultiWaveformSelectionToolbarBundle,
20
+ )
21
+ from bec_widgets.widgets.plots.plot_base import PlotBase
22
+
23
+ logger = bec_logger.logger
24
+
25
+
26
+ class MultiWaveformConfig(ConnectionConfig):
27
+ color_palette: str | None = Field(
28
+ "plasma", description="The color palette of the figure widget.", validate_default=True
29
+ )
30
+ curve_limit: int | None = Field(
31
+ 200, description="The maximum number of curves to display on the plot."
32
+ )
33
+ flush_buffer: bool | None = Field(
34
+ False, description="Flush the buffer of the plot widget when the curve limit is reached."
35
+ )
36
+ monitor: str | None = Field(None, description="The monitor to set for the plot widget.")
37
+ curve_width: int | None = Field(1, description="The width of the curve on the plot.")
38
+ opacity: int | None = Field(50, description="The opacity of the curve on the plot.")
39
+ highlight_last_curve: bool | None = Field(
40
+ True, description="Highlight the last curve on the plot."
41
+ )
42
+
43
+ model_config: dict = {"validate_assignment": True}
44
+ _validate_color_map_z = field_validator("color_palette")(Colors.validate_color_map)
45
+
46
+
47
+ class MultiWaveform(PlotBase):
48
+ """
49
+ MultiWaveform widget for displaying multiple waveforms emitted by a single signal.
50
+ """
51
+
52
+ PLUGIN = True
53
+ RPC = True
54
+ ICON_NAME = "ssid_chart"
55
+ USER_ACCESS = [
56
+ # General PlotBase Settings
57
+ "enable_toolbar",
58
+ "enable_toolbar.setter",
59
+ "enable_side_panel",
60
+ "enable_side_panel.setter",
61
+ "enable_fps_monitor",
62
+ "enable_fps_monitor.setter",
63
+ "set",
64
+ "title",
65
+ "title.setter",
66
+ "x_label",
67
+ "x_label.setter",
68
+ "y_label",
69
+ "y_label.setter",
70
+ "x_limits",
71
+ "x_limits.setter",
72
+ "y_limits",
73
+ "y_limits.setter",
74
+ "x_grid",
75
+ "x_grid.setter",
76
+ "y_grid",
77
+ "y_grid.setter",
78
+ "inner_axes",
79
+ "inner_axes.setter",
80
+ "outer_axes",
81
+ "outer_axes.setter",
82
+ "lock_aspect_ratio",
83
+ "lock_aspect_ratio.setter",
84
+ "auto_range_x",
85
+ "auto_range_x.setter",
86
+ "auto_range_y",
87
+ "auto_range_y.setter",
88
+ "x_log",
89
+ "x_log.setter",
90
+ "y_log",
91
+ "y_log.setter",
92
+ "legend_label_size",
93
+ "legend_label_size.setter",
94
+ # MultiWaveform Specific RPC Access
95
+ "highlighted_index",
96
+ "highlighted_index.setter",
97
+ "highlight_last_curve",
98
+ "highlight_last_curve.setter",
99
+ "color_palette",
100
+ "color_palette.setter",
101
+ "opacity",
102
+ "opacity.setter",
103
+ "flush_buffer",
104
+ "flush_buffer.setter",
105
+ "max_trace",
106
+ "max_trace.setter",
107
+ "monitor",
108
+ "monitor.setter",
109
+ "set_curve_limit",
110
+ "plot",
111
+ "set_curve_highlight",
112
+ "clear_curves",
113
+ ]
114
+
115
+ monitor_signal_updated = Signal()
116
+ highlighted_curve_index_changed = Signal(int)
117
+
118
+ def __init__(
119
+ self,
120
+ parent: QWidget | None = None,
121
+ config: MultiWaveformConfig | None = None,
122
+ client=None,
123
+ gui_id: str | None = None,
124
+ popups: bool = True,
125
+ **kwargs,
126
+ ):
127
+ if config is None:
128
+ config = MultiWaveformConfig(widget_class=self.__class__.__name__)
129
+ super().__init__(
130
+ parent=parent, config=config, client=client, gui_id=gui_id, popups=popups, **kwargs
131
+ )
132
+
133
+ # Scan Data
134
+ self.old_scan_id = None
135
+ self.scan_id = None
136
+ self.connected = False
137
+ self._current_highlight_index = 0
138
+ self._curves = deque()
139
+ self.visible_curves = []
140
+ self.number_of_visible_curves = 0
141
+
142
+ self._init_control_panel()
143
+
144
+ ################################################################################
145
+ # Widget Specific GUI interactions
146
+ ################################################################################
147
+ def _init_toolbar(self):
148
+ self.monitor_selection_bundle = MultiWaveformSelectionToolbarBundle(
149
+ bundle_id="motor_selection", target_widget=self
150
+ )
151
+ self.toolbar.add_bundle(self.monitor_selection_bundle, target_widget=self)
152
+ super()._init_toolbar()
153
+ self.toolbar.widgets["reset_legend"].action.setVisible(False)
154
+
155
+ def _init_control_panel(self):
156
+ self.control_panel = SidePanel(self, orientation="top", panel_max_width=90)
157
+ self.layout_manager.add_widget_relative(
158
+ self.control_panel, self.round_plot_widget, "bottom"
159
+ )
160
+ self.controls = MultiWaveformControlPanel(parent=self, target_widget=self)
161
+ self.control_panel.add_menu(
162
+ action_id="control",
163
+ icon_name="tune",
164
+ tooltip="Show Control panel",
165
+ widget=self.controls,
166
+ title=None,
167
+ )
168
+ self.control_panel.toolbar.widgets["control"].action.trigger()
169
+
170
+ ################################################################################
171
+ # Widget Specific Properties
172
+ ################################################################################
173
+
174
+ @property
175
+ def curves(self) -> deque:
176
+ """
177
+ Get the curves of the plot widget as a deque.
178
+ Returns:
179
+ deque: Deque of curves.
180
+ """
181
+ return self._curves
182
+
183
+ @curves.setter
184
+ def curves(self, value: deque):
185
+ self._curves = value
186
+
187
+ @SafeProperty(int, designable=False)
188
+ def highlighted_index(self):
189
+ return self._current_highlight_index
190
+
191
+ @highlighted_index.setter
192
+ def highlighted_index(self, value: int):
193
+ self._current_highlight_index = value
194
+ self.property_changed.emit("highlighted_index", value)
195
+ self.set_curve_highlight(value)
196
+
197
+ @SafeProperty(bool)
198
+ def highlight_last_curve(self) -> bool:
199
+ """
200
+ Get the highlight_last_curve property.
201
+ Returns:
202
+ bool: The highlight_last_curve property.
203
+ """
204
+ return self.config.highlight_last_curve
205
+
206
+ @highlight_last_curve.setter
207
+ def highlight_last_curve(self, value: bool):
208
+ self.config.highlight_last_curve = value
209
+ self.property_changed.emit("highlight_last_curve", value)
210
+ self.set_curve_highlight(-1)
211
+
212
+ @SafeProperty(str)
213
+ def color_palette(self) -> str:
214
+ """
215
+ The color palette of the figure widget.
216
+ """
217
+ return self.config.color_palette
218
+
219
+ @color_palette.setter
220
+ def color_palette(self, value: str):
221
+ """
222
+ Set the color palette of the figure widget.
223
+
224
+ Args:
225
+ value(str): The color palette to set.
226
+ """
227
+ try:
228
+ self.config.color_palette = value
229
+ except ValidationError:
230
+ return
231
+ self.set_curve_highlight(self._current_highlight_index)
232
+ self._sync_monitor_selection_toolbar()
233
+
234
+ @SafeProperty(int)
235
+ def opacity(self) -> int:
236
+ """
237
+ The opacity of the figure widget.
238
+ """
239
+ return self.config.opacity
240
+
241
+ @opacity.setter
242
+ def opacity(self, value: int):
243
+ """
244
+ Set the opacity of the figure widget.
245
+
246
+ Args:
247
+ value(int): The opacity to set.
248
+ """
249
+ self.config.opacity = max(0, min(100, value))
250
+ self.property_changed.emit("opacity", value)
251
+ self.set_curve_highlight(self._current_highlight_index)
252
+
253
+ @SafeProperty(bool)
254
+ def flush_buffer(self) -> bool:
255
+ """
256
+ The flush_buffer property.
257
+ """
258
+ return self.config.flush_buffer
259
+
260
+ @flush_buffer.setter
261
+ def flush_buffer(self, value: bool):
262
+ self.config.flush_buffer = value
263
+ self.property_changed.emit("flush_buffer", value)
264
+ self.set_curve_limit(
265
+ max_trace=self.config.curve_limit, flush_buffer=self.config.flush_buffer
266
+ )
267
+
268
+ @SafeProperty(int)
269
+ def max_trace(self) -> int:
270
+ """
271
+ The maximum number of traces to display on the plot.
272
+ """
273
+ return self.config.curve_limit
274
+
275
+ @max_trace.setter
276
+ def max_trace(self, value: int):
277
+ """
278
+ Set the maximum number of traces to display on the plot.
279
+
280
+ Args:
281
+ value(int): The maximum number of traces to display.
282
+ """
283
+ self.config.curve_limit = value
284
+ self.property_changed.emit("max_trace", value)
285
+ self.set_curve_limit(
286
+ max_trace=self.config.curve_limit, flush_buffer=self.config.flush_buffer
287
+ )
288
+
289
+ @SafeProperty(str)
290
+ def monitor(self) -> str:
291
+ """
292
+ The monitor of the figure widget.
293
+ """
294
+ return self.config.monitor
295
+
296
+ @monitor.setter
297
+ def monitor(self, value: str):
298
+ """
299
+ Set the monitor of the figure widget.
300
+
301
+ Args:
302
+ value(str): The monitor to set.
303
+ """
304
+ self.plot(value)
305
+
306
+ ################################################################################
307
+ # High Level methods for API
308
+ ################################################################################
309
+
310
+ @SafeSlot(popup_error=True)
311
+ def plot(self, monitor: str, color_palette: str | None = "plasma"):
312
+ """
313
+ Create a plot for the given monitor.
314
+ Args:
315
+ monitor (str): The monitor to set.
316
+ color_palette (str|None): The color palette to use for the plot.
317
+ """
318
+ self.entry_validator.validate_monitor(monitor)
319
+ self._disconnect_monitor()
320
+ self.config.monitor = monitor
321
+ self._connect_monitor()
322
+ if color_palette is not None:
323
+ self.color_palette = color_palette
324
+ self._sync_monitor_selection_toolbar()
325
+
326
+ @SafeSlot(int, bool)
327
+ def set_curve_limit(self, max_trace: int, flush_buffer: bool):
328
+ """
329
+ Set the maximum number of traces to display on the plot.
330
+
331
+ Args:
332
+ max_trace (int): The maximum number of traces to display.
333
+ flush_buffer (bool): Flush the buffer.
334
+ """
335
+ if max_trace != self.config.curve_limit:
336
+ self.config.curve_limit = max_trace
337
+ if flush_buffer != self.config.flush_buffer:
338
+ self.config.flush_buffer = flush_buffer
339
+
340
+ if self.config.curve_limit is None:
341
+ self.scale_colors()
342
+ return
343
+
344
+ if self.config.flush_buffer:
345
+ # Remove excess curves from the plot and the deque
346
+ while len(self.curves) > self.config.curve_limit:
347
+ curve = self.curves.popleft()
348
+ self.plot_item.removeItem(curve)
349
+ else:
350
+ # Hide or show curves based on the new max_trace
351
+ num_curves_to_show = min(self.config.curve_limit, len(self.curves))
352
+ for i, curve in enumerate(self.curves):
353
+ if i < len(self.curves) - num_curves_to_show:
354
+ curve.hide()
355
+ else:
356
+ curve.show()
357
+ self.scale_colors()
358
+ self.monitor_signal_updated.emit()
359
+
360
+ ################################################################################
361
+ # BEC Update Methods
362
+ ################################################################################
363
+ @SafeSlot(dict, dict)
364
+ def on_monitor_1d_update(self, msg: dict, metadata: dict):
365
+ """
366
+ Update the plot widget with the monitor data.
367
+
368
+ Args:
369
+ msg(dict): The message data.
370
+ metadata(dict): The metadata of the message.
371
+ """
372
+ data = msg.get("data", None)
373
+ current_scan_id = metadata.get("scan_id", None)
374
+
375
+ if current_scan_id != self.scan_id:
376
+ self.scan_id = current_scan_id
377
+ self.clear_curves()
378
+ self.curves.clear()
379
+ if self.crosshair:
380
+ self.crosshair.clear_markers()
381
+
382
+ # Always create a new curve and add it
383
+ curve = pg.PlotDataItem()
384
+ curve.setData(data)
385
+ self.plot_item.addItem(curve)
386
+ self.curves.append(curve)
387
+
388
+ # Max Trace and scale colors
389
+ self.set_curve_limit(self.config.curve_limit, self.config.flush_buffer)
390
+
391
+ @SafeSlot(int)
392
+ def set_curve_highlight(self, index: int):
393
+ """
394
+ Set the curve highlight based on visible curves.
395
+
396
+ Args:
397
+ index (int): The index of the curve to highlight among visible curves.
398
+ """
399
+ self.plot_item.visible_curves = [curve for curve in self.curves if curve.isVisible()]
400
+ num_visible_curves = len(self.plot_item.visible_curves)
401
+ self.number_of_visible_curves = num_visible_curves
402
+
403
+ if num_visible_curves == 0:
404
+ return # No curves to highlight
405
+
406
+ if index >= num_visible_curves:
407
+ index = num_visible_curves - 1
408
+ elif index < 0:
409
+ index = num_visible_curves + index
410
+ self._current_highlight_index = index
411
+ num_colors = num_visible_curves
412
+ colors = Colors.evenly_spaced_colors(
413
+ colormap=self.config.color_palette, num=num_colors, format="HEX"
414
+ )
415
+ for i, curve in enumerate(self.plot_item.visible_curves):
416
+ curve.setPen()
417
+ if i == self._current_highlight_index:
418
+ curve.setPen(pg.mkPen(color=colors[i], width=5))
419
+ curve.setAlpha(alpha=1, auto=False)
420
+ curve.setZValue(1)
421
+ else:
422
+ curve.setPen(pg.mkPen(color=colors[i], width=1))
423
+ curve.setAlpha(alpha=self.config.opacity / 100, auto=False)
424
+ curve.setZValue(0)
425
+
426
+ self.highlighted_curve_index_changed.emit(self._current_highlight_index)
427
+
428
+ def _disconnect_monitor(self):
429
+ try:
430
+ previous_monitor = self.config.monitor
431
+ except AttributeError:
432
+ previous_monitor = None
433
+
434
+ if previous_monitor and self.connected is True:
435
+ self.bec_dispatcher.disconnect_slot(
436
+ self.on_monitor_1d_update, MessageEndpoints.device_monitor_1d(previous_monitor)
437
+ )
438
+ self.connected = False
439
+
440
+ def _connect_monitor(self):
441
+ """
442
+ Connect the monitor to the plot widget.
443
+ """
444
+
445
+ if self.config.monitor and self.connected is False:
446
+ self.bec_dispatcher.connect_slot(
447
+ self.on_monitor_1d_update, MessageEndpoints.device_monitor_1d(self.config.monitor)
448
+ )
449
+ self.connected = True
450
+
451
+ ################################################################################
452
+ # Utility Methods
453
+ ################################################################################
454
+ def scale_colors(self):
455
+ """
456
+ Scale the colors of the curves based on the current colormap.
457
+ """
458
+ # TODO probably has to be changed to property
459
+ if self.config.highlight_last_curve:
460
+ self.set_curve_highlight(-1) # Use -1 to highlight the last visible curve
461
+ else:
462
+ self.set_curve_highlight(self._current_highlight_index)
463
+
464
+ def hook_crosshair(self) -> None:
465
+ """
466
+ Specific hookfor crosshair, since it is for multiple curves.
467
+ """
468
+ super().hook_crosshair()
469
+ if self.crosshair:
470
+ self.highlighted_curve_index_changed.connect(self.crosshair.update_highlighted_curve)
471
+ if self.curves:
472
+ self.crosshair.update_highlighted_curve(self._current_highlight_index)
473
+
474
+ def clear_curves(self):
475
+ """
476
+ Remove all curves from the plot, excluding crosshair items.
477
+ """
478
+ items_to_remove = []
479
+ for item in self.plot_item.items:
480
+ if not getattr(item, "is_crosshair", False) and isinstance(item, pg.PlotDataItem):
481
+ items_to_remove.append(item)
482
+ for item in items_to_remove:
483
+ self.plot_item.removeItem(item)
484
+
485
+ def _sync_monitor_selection_toolbar(self):
486
+ """
487
+ Sync the motor map selection toolbar with the current motor map.
488
+ """
489
+ if self.monitor_selection_bundle is not None:
490
+ monitor = self.monitor_selection_bundle.monitor.currentText()
491
+ color_palette = self.monitor_selection_bundle.colormap_widget.colormap
492
+
493
+ if monitor != self.config.monitor:
494
+ self.monitor_selection_bundle.monitor.blockSignals(True)
495
+ self.monitor_selection_bundle.monitor.set_device(self.config.monitor)
496
+ self.monitor_selection_bundle.monitor.check_validity(self.config.monitor)
497
+ self.monitor_selection_bundle.monitor.blockSignals(False)
498
+
499
+ if color_palette != self.config.color_palette:
500
+ self.monitor_selection_bundle.colormap_widget.blockSignals(True)
501
+ self.monitor_selection_bundle.colormap_widget.colormap = self.config.color_palette
502
+ self.monitor_selection_bundle.colormap_widget.blockSignals(False)
503
+
504
+ def cleanup(self):
505
+ self._disconnect_monitor()
506
+ self.clear_curves()
507
+ self.monitor_selection_bundle.cleanup()
508
+ super().cleanup()
@@ -0,0 +1 @@
1
+ {'files': ['multi_waveform.py']}
@@ -0,0 +1,54 @@
1
+ # Copyright (C) 2022 The Qt Company Ltd.
2
+ # SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
3
+
4
+ from qtpy.QtDesigner import QDesignerCustomWidgetInterface
5
+
6
+ from bec_widgets.utils.bec_designer import designer_material_icon
7
+ from bec_widgets.widgets.plots.multi_waveform.multi_waveform import MultiWaveform
8
+
9
+ DOM_XML = """
10
+ <ui language='c++'>
11
+ <widget class='MultiWaveform' name='multi_waveform'>
12
+ </widget>
13
+ </ui>
14
+ """
15
+
16
+
17
+ class MultiWaveformPlugin(QDesignerCustomWidgetInterface): # pragma: no cover
18
+ def __init__(self):
19
+ super().__init__()
20
+ self._form_editor = None
21
+
22
+ def createWidget(self, parent):
23
+ t = MultiWaveform(parent)
24
+ return t
25
+
26
+ def domXml(self):
27
+ return DOM_XML
28
+
29
+ def group(self):
30
+ return "Plot Widgets"
31
+
32
+ def icon(self):
33
+ return designer_material_icon(MultiWaveform.ICON_NAME)
34
+
35
+ def includeFile(self):
36
+ return "multi_waveform"
37
+
38
+ def initialize(self, form_editor):
39
+ self._form_editor = form_editor
40
+
41
+ def isContainer(self):
42
+ return False
43
+
44
+ def isInitialized(self):
45
+ return self._form_editor is not None
46
+
47
+ def name(self):
48
+ return "MultiWaveform"
49
+
50
+ def toolTip(self):
51
+ return "MultiWaveform"
52
+
53
+ def whatsThis(self):
54
+ return self.toolTip()
@@ -0,0 +1,15 @@
1
+ def main(): # pragma: no cover
2
+ from qtpy import PYSIDE6
3
+
4
+ if not PYSIDE6:
5
+ print("PYSIDE6 is not available in the environment. Cannot patch designer.")
6
+ return
7
+ from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection
8
+
9
+ from bec_widgets.widgets.plots.multi_waveform.multi_waveform_plugin import MultiWaveformPlugin
10
+
11
+ QPyDesignerCustomWidgetCollection.addCustomWidget(MultiWaveformPlugin())
12
+
13
+
14
+ if __name__ == "__main__": # pragma: no cover
15
+ main()