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.
- .gitlab-ci.yml +3 -5
- CHANGELOG.md +639 -0
- PKG-INFO +3 -3
- bec_widgets/__init__.py +4 -0
- bec_widgets/applications/bw_launch.py +23 -0
- bec_widgets/applications/launch_window.py +430 -0
- bec_widgets/assets/app_icons/auto_update.png +0 -0
- bec_widgets/assets/app_icons/ui_loader_tile.png +0 -0
- bec_widgets/cli/__init__.py +0 -1
- bec_widgets/cli/client.py +1779 -2064
- bec_widgets/cli/client_utils.py +346 -174
- bec_widgets/cli/generate_cli.py +143 -37
- bec_widgets/cli/rpc/rpc_base.py +152 -21
- bec_widgets/cli/rpc/rpc_register.py +113 -6
- bec_widgets/cli/rpc/rpc_widget_handler.py +13 -11
- bec_widgets/cli/server.py +125 -239
- bec_widgets/examples/jupyter_console/jupyter_console_window.py +97 -145
- bec_widgets/examples/plugin_example_pyside/tictactoetaskmenu.py +1 -1
- bec_widgets/utils/bec_connector.py +190 -21
- bec_widgets/utils/bec_designer.py +7 -0
- bec_widgets/utils/bec_dispatcher.py +71 -4
- bec_widgets/utils/bec_plugin_helper.py +89 -0
- bec_widgets/utils/bec_signal_proxy.py +1 -1
- bec_widgets/utils/bec_widget.py +26 -10
- bec_widgets/utils/colors.py +1 -1
- bec_widgets/{qt_utils → utils}/compact_popup.py +2 -0
- bec_widgets/utils/container_utils.py +37 -12
- bec_widgets/utils/crosshair.py +25 -8
- bec_widgets/utils/entry_validator.py +3 -1
- bec_widgets/{qt_utils → utils}/error_popups.py +18 -0
- bec_widgets/{qt_utils → utils}/expandable_frame.py +2 -2
- bec_widgets/utils/forms_from_types/forms.py +182 -0
- bec_widgets/{widgets/editors/scan_metadata/_metadata_widgets.py → utils/forms_from_types/items.py} +41 -30
- bec_widgets/utils/generate_designer_plugin.py +40 -36
- bec_widgets/utils/linear_region_selector.py +2 -0
- bec_widgets/utils/name_utils.py +16 -0
- bec_widgets/{qt_utils → utils}/palette_viewer.py +2 -2
- bec_widgets/utils/plot_indicator_items.py +2 -5
- bec_widgets/utils/plugin_utils.py +47 -1
- bec_widgets/{qt_utils → utils}/round_frame.py +14 -14
- bec_widgets/utils/rpc_server.py +277 -0
- bec_widgets/utils/serialization.py +44 -0
- bec_widgets/{qt_utils → utils}/settings_dialog.py +26 -1
- bec_widgets/{qt_utils → utils}/side_panel.py +17 -10
- bec_widgets/{qt_utils → utils}/toolbar.py +69 -25
- bec_widgets/utils/ui_loader.py +8 -8
- bec_widgets/utils/widget_io.py +166 -25
- bec_widgets/widgets/containers/auto_update/auto_updates.py +364 -0
- bec_widgets/widgets/containers/dock/dock.py +157 -49
- bec_widgets/widgets/containers/dock/dock_area.py +188 -138
- bec_widgets/widgets/containers/layout_manager/layout_manager.py +2 -1
- bec_widgets/widgets/containers/main_window/addons/web_links.py +15 -0
- bec_widgets/widgets/containers/main_window/main_window.py +189 -41
- bec_widgets/widgets/control/buttons/button_abort/button_abort.py +3 -4
- bec_widgets/widgets/control/buttons/button_reset/button_reset.py +3 -4
- bec_widgets/widgets/control/buttons/button_resume/button_resume.py +3 -3
- bec_widgets/widgets/control/buttons/stop_button/stop_button.py +18 -7
- bec_widgets/widgets/control/device_control/position_indicator/position_indicator.py +22 -3
- bec_widgets/widgets/control/device_control/positioner_box/_base/positioner_box_base.py +31 -13
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.py +3 -1
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box/positioner_box.ui +27 -4
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.py +5 -2
- bec_widgets/widgets/control/device_control/positioner_box/positioner_box_2d/positioner_box_2d.ui +97 -31
- bec_widgets/widgets/control/device_control/positioner_box/positioner_control_line/positioner_control_line.ui +11 -4
- bec_widgets/widgets/control/device_control/positioner_group/positioner_group.py +2 -3
- bec_widgets/widgets/control/device_input/base_classes/device_input_base.py +29 -4
- bec_widgets/widgets/control/device_input/base_classes/device_signal_input_base.py +1 -0
- bec_widgets/widgets/control/device_input/device_combobox/device_combobox.py +2 -2
- bec_widgets/widgets/control/device_input/device_line_edit/device_line_edit.py +2 -2
- bec_widgets/widgets/control/device_input/signal_combobox/signal_combobox.py +1 -2
- bec_widgets/widgets/control/device_input/signal_line_edit/signal_line_edit.py +1 -2
- bec_widgets/widgets/control/scan_control/scan_control.py +7 -5
- bec_widgets/widgets/control/scan_control/scan_group_box.py +28 -5
- bec_widgets/widgets/dap/dap_combo_box/dap_combo_box.py +1 -2
- bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog.py +3 -4
- bec_widgets/widgets/dap/lmfit_dialog/lmfit_dialog_vertical.ui +14 -8
- bec_widgets/widgets/editors/console/console.py +1 -1
- bec_widgets/widgets/editors/{scan_metadata/additional_metadata_table.py → dict_backed_table.py} +29 -6
- bec_widgets/widgets/editors/scan_metadata/__init__.py +0 -7
- bec_widgets/widgets/editors/scan_metadata/_util.py +1 -1
- bec_widgets/widgets/{plots/motor_map/register_bec_motor_map_widget.py → editors/scan_metadata/register_scan_metadata.py} +2 -4
- bec_widgets/widgets/editors/scan_metadata/scan_metadata.py +42 -136
- bec_widgets/widgets/editors/scan_metadata/scan_metadata.pyproject +1 -0
- bec_widgets/widgets/{plots/multi_waveform/bec_multi_waveform_widget_plugin.py → editors/scan_metadata/scan_metadata_plugin.py} +9 -9
- bec_widgets/widgets/editors/text_box/text_box.py +2 -3
- bec_widgets/widgets/editors/website/website.py +2 -2
- bec_widgets/widgets/games/minesweeper.py +3 -2
- bec_widgets/widgets/plots/image/image.py +960 -0
- bec_widgets/widgets/plots/image/image.pyproject +1 -0
- bec_widgets/widgets/plots/image/image_item.py +279 -0
- bec_widgets/widgets/plots/{motor_map/bec_motor_map_widget_plugin.py → image/image_plugin.py} +11 -13
- bec_widgets/widgets/{containers/figure/plots → plots}/image/image_processor.py +31 -64
- bec_widgets/widgets/plots/image/{register_bec_image_widget.py → register_image.py} +2 -2
- bec_widgets/widgets/plots/image/toolbar_bundles/image_selection.py +59 -0
- bec_widgets/widgets/plots/image/toolbar_bundles/processing.py +79 -0
- bec_widgets/widgets/plots/motor_map/motor_map.py +832 -0
- bec_widgets/widgets/plots/motor_map/motor_map.pyproject +1 -0
- bec_widgets/widgets/plots/motor_map/motor_map_plugin.py +54 -0
- bec_widgets/widgets/plots/{multi_waveform/register_bec_multi_waveform_widget.py → motor_map/register_motor_map.py} +2 -4
- bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.py +129 -0
- bec_widgets/widgets/plots/motor_map/settings/motor_map_settings.ui +120 -0
- bec_widgets/widgets/plots/motor_map/toolbar_bundles/motor_selection.py +70 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform.py +508 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform.pyproject +1 -0
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_plugin.py +54 -0
- bec_widgets/widgets/plots/multi_waveform/register_multi_waveform.py +15 -0
- bec_widgets/widgets/plots/multi_waveform/settings/control_panel.py +144 -0
- bec_widgets/widgets/plots/multi_waveform/settings/multi_waveform_controls.ui +164 -0
- bec_widgets/widgets/plots/multi_waveform/toolbar_bundles/monitor_selection.py +65 -0
- bec_widgets/widgets/{plots_next_gen → plots}/plot_base.py +321 -40
- bec_widgets/widgets/plots/{waveform/register_bec_waveform_widget.py → scatter_waveform/register_scatter_waveform.py} +3 -3
- bec_widgets/widgets/plots/scatter_waveform/scatter_curve.py +197 -0
- bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.py +553 -0
- bec_widgets/widgets/plots/scatter_waveform/scatter_waveform.pyproject +1 -0
- bec_widgets/widgets/plots/{image/bec_image_widget_plugin.py → scatter_waveform/scatter_waveform_plugin.py} +9 -13
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_setting.py +138 -0
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_horizontal.ui +195 -0
- bec_widgets/widgets/plots/scatter_waveform/settings/scatter_curve_settings_vertical.ui +204 -0
- bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings.py +8 -8
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/mouse_interactions.py +4 -18
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/plot_export.py +14 -3
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/roi_bundle.py +6 -1
- bec_widgets/widgets/{plots_next_gen → plots}/toolbar_bundles/save_state.py +2 -2
- bec_widgets/widgets/{containers/figure/plots/waveform/waveform_curve.py → plots/waveform/curve.py} +119 -49
- bec_widgets/widgets/plots/waveform/register_waveform.py +15 -0
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_setting.py +125 -0
- bec_widgets/widgets/plots/waveform/settings/curve_settings/curve_tree.py +576 -0
- bec_widgets/widgets/plots/waveform/utils/__init__.py +0 -0
- bec_widgets/widgets/plots/waveform/utils/roi_manager.py +84 -0
- bec_widgets/widgets/plots/waveform/waveform.py +1794 -0
- bec_widgets/widgets/plots/waveform/waveform.pyproject +1 -0
- bec_widgets/widgets/plots/waveform/{bec_waveform_widget_plugin.py → waveform_plugin.py} +9 -13
- bec_widgets/widgets/progress/bec_progressbar/bec_progressbar.py +1 -2
- bec_widgets/widgets/progress/ring_progress_bar/ring.py +11 -10
- bec_widgets/widgets/progress/ring_progress_bar/ring_progress_bar.py +24 -14
- bec_widgets/widgets/services/bec_queue/bec_queue.py +13 -11
- bec_widgets/widgets/services/bec_status_box/bec_status_box.py +3 -4
- bec_widgets/widgets/services/device_browser/device_browser.py +5 -2
- bec_widgets/widgets/services/device_browser/device_item/device_item.py +1 -1
- bec_widgets/widgets/utility/logpanel/logpanel.py +36 -17
- bec_widgets/widgets/utility/spinbox/decimal_spinbox.py +3 -3
- bec_widgets/widgets/utility/visual/color_button/color_button.py +1 -1
- bec_widgets/widgets/utility/visual/colormap_widget/colormap_widget.py +4 -6
- bec_widgets/widgets/utility/visual/dark_mode_button/dark_mode_button.py +4 -8
- {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/METADATA +3 -3
- {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/RECORD +168 -153
- pyproject.toml +3 -3
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.py +0 -198
- bec_widgets/applications/alignment/alignment_1d/alignment_1d.ui +0 -615
- bec_widgets/applications/bec_app.py +0 -84
- bec_widgets/cli/auto_updates.py +0 -168
- bec_widgets/widgets/containers/figure/__init__.py +0 -1
- bec_widgets/widgets/containers/figure/figure.py +0 -796
- bec_widgets/widgets/containers/figure/plots/axis_settings.py +0 -91
- bec_widgets/widgets/containers/figure/plots/axis_settings.ui +0 -256
- bec_widgets/widgets/containers/figure/plots/image/image.py +0 -772
- bec_widgets/widgets/containers/figure/plots/image/image_item.py +0 -337
- bec_widgets/widgets/containers/figure/plots/motor_map/motor_map.py +0 -525
- bec_widgets/widgets/containers/figure/plots/multi_waveform/multi_waveform.py +0 -340
- bec_widgets/widgets/containers/figure/plots/plot_base.py +0 -505
- bec_widgets/widgets/containers/figure/plots/waveform/waveform.py +0 -1563
- bec_widgets/widgets/plots/image/bec_image_widget.pyproject +0 -1
- bec_widgets/widgets/plots/image/image_widget.py +0 -515
- bec_widgets/widgets/plots/motor_map/bec_motor_map_widget.pyproject +0 -1
- bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.py +0 -56
- bec_widgets/widgets/plots/motor_map/motor_map_dialog/motor_map_settings.ui +0 -108
- bec_widgets/widgets/plots/motor_map/motor_map_widget.py +0 -234
- bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject +0 -1
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui +0 -99
- bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py +0 -536
- bec_widgets/widgets/plots/waveform/bec_waveform_widget.pyproject +0 -1
- bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.py +0 -336
- bec_widgets/widgets/plots/waveform/waveform_popups/curve_dialog/curve_dialog.ui +0 -372
- bec_widgets/widgets/plots/waveform/waveform_popups/dap_summary_dialog/dap_summary_dialog.py +0 -25
- bec_widgets/widgets/plots/waveform/waveform_widget.py +0 -751
- /bec_widgets/{qt_utils → utils}/collapsible_panel_manager.py +0 -0
- /bec_widgets/{applications/alignment → utils/forms_from_types}/__init__.py +0 -0
- /bec_widgets/{qt_utils → utils}/redis_message_waiter.py +0 -0
- /bec_widgets/{applications/alignment/alignment_1d → widgets/containers/auto_update}/__init__.py +0 -0
- /bec_widgets/{qt_utils → widgets/containers/main_window/addons}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots → plots/image/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/image → plots/motor_map/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/motor_map → plots/motor_map/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/multi_waveform → plots/multi_waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{containers/figure/plots/waveform → plots/multi_waveform/toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{motor_map/motor_map_dialog → scatter_waveform}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups → scatter_waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups/curve_dialog → setting_menus}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_horizontal.ui +0 -0
- /bec_widgets/widgets/{plots_next_gen → plots}/setting_menus/axis_settings_vertical.ui +0 -0
- /bec_widgets/widgets/plots/{waveform/waveform_popups/dap_summary_dialog → toolbar_bundles}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen/setting_menus → plots/waveform/settings}/__init__.py +0 -0
- /bec_widgets/widgets/{plots_next_gen/toolbar_bundles → plots/waveform/settings/curve_settings}/__init__.py +0 -0
- {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/WHEEL +0 -0
- {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/entry_points.txt +0 -0
- {bec_widgets-1.25.1.dist-info → bec_widgets-2.0.1.dist-info}/licenses/LICENSE +0 -0
@@ -1,796 +0,0 @@
|
|
1
|
-
# pylint: disable = no-name-in-module,missing-module-docstring
|
2
|
-
from __future__ import annotations
|
3
|
-
|
4
|
-
import uuid
|
5
|
-
from collections import defaultdict
|
6
|
-
from typing import Literal, Optional
|
7
|
-
|
8
|
-
import numpy as np
|
9
|
-
import pyqtgraph as pg
|
10
|
-
from bec_lib.logger import bec_logger
|
11
|
-
from pydantic import Field, ValidationError, field_validator
|
12
|
-
from qtpy.QtCore import Signal as pyqtSignal
|
13
|
-
from qtpy.QtWidgets import QWidget
|
14
|
-
from typeguard import typechecked
|
15
|
-
|
16
|
-
from bec_widgets.utils import ConnectionConfig, WidgetContainerUtils
|
17
|
-
from bec_widgets.utils.bec_widget import BECWidget
|
18
|
-
from bec_widgets.utils.colors import apply_theme
|
19
|
-
from bec_widgets.widgets.containers.figure.plots.image.image import BECImageShow, ImageConfig
|
20
|
-
from bec_widgets.widgets.containers.figure.plots.motor_map.motor_map import (
|
21
|
-
BECMotorMap,
|
22
|
-
MotorMapConfig,
|
23
|
-
)
|
24
|
-
from bec_widgets.widgets.containers.figure.plots.multi_waveform.multi_waveform import (
|
25
|
-
BECMultiWaveform,
|
26
|
-
BECMultiWaveformConfig,
|
27
|
-
)
|
28
|
-
from bec_widgets.widgets.containers.figure.plots.plot_base import BECPlotBase, SubplotConfig
|
29
|
-
from bec_widgets.widgets.containers.figure.plots.waveform.waveform import (
|
30
|
-
BECWaveform,
|
31
|
-
Waveform1DConfig,
|
32
|
-
)
|
33
|
-
|
34
|
-
logger = bec_logger.logger
|
35
|
-
|
36
|
-
|
37
|
-
class FigureConfig(ConnectionConfig):
|
38
|
-
"""Configuration for BECFigure. Inheriting from ConnectionConfig widget_class and gui_id"""
|
39
|
-
|
40
|
-
theme: Literal["dark", "light"] = Field("dark", description="The theme of the figure widget.")
|
41
|
-
num_cols: int = Field(1, description="The number of columns in the figure widget.")
|
42
|
-
num_rows: int = Field(1, description="The number of rows in the figure widget.")
|
43
|
-
widgets: dict[str, Waveform1DConfig | ImageConfig | MotorMapConfig | SubplotConfig] = Field(
|
44
|
-
{}, description="The list of widgets to be added to the figure widget."
|
45
|
-
)
|
46
|
-
|
47
|
-
@field_validator("widgets", mode="before")
|
48
|
-
@classmethod
|
49
|
-
def validate_widgets(cls, v):
|
50
|
-
"""Validate the widgets configuration."""
|
51
|
-
widget_class_map = {
|
52
|
-
"BECWaveform": Waveform1DConfig,
|
53
|
-
"BECImageShow": ImageConfig,
|
54
|
-
"BECMotorMap": MotorMapConfig,
|
55
|
-
}
|
56
|
-
validated_widgets = {}
|
57
|
-
for key, widget_config in v.items():
|
58
|
-
if "widget_class" not in widget_config:
|
59
|
-
raise ValueError(f"Widget config for {key} does not contain 'widget_class'.")
|
60
|
-
widget_class = widget_config["widget_class"]
|
61
|
-
if widget_class not in widget_class_map:
|
62
|
-
raise ValueError(f"Unknown widget_class '{widget_class}' for widget '{key}'.")
|
63
|
-
config_class = widget_class_map[widget_class]
|
64
|
-
validated_widgets[key] = config_class(**widget_config)
|
65
|
-
return validated_widgets
|
66
|
-
|
67
|
-
|
68
|
-
class WidgetHandler:
|
69
|
-
"""Factory for creating and configuring BEC widgets for BECFigure."""
|
70
|
-
|
71
|
-
def __init__(self):
|
72
|
-
self.widget_factory = {
|
73
|
-
"BECPlotBase": (BECPlotBase, SubplotConfig),
|
74
|
-
"BECWaveform": (BECWaveform, Waveform1DConfig),
|
75
|
-
"BECImageShow": (BECImageShow, ImageConfig),
|
76
|
-
"BECMotorMap": (BECMotorMap, MotorMapConfig),
|
77
|
-
"BECMultiWaveform": (BECMultiWaveform, BECMultiWaveformConfig),
|
78
|
-
}
|
79
|
-
|
80
|
-
def create_widget(
|
81
|
-
self,
|
82
|
-
widget_type: str,
|
83
|
-
widget_id: str,
|
84
|
-
parent_figure,
|
85
|
-
parent_id: str,
|
86
|
-
config: dict = None,
|
87
|
-
**axis_kwargs,
|
88
|
-
) -> BECPlotBase:
|
89
|
-
"""
|
90
|
-
Create and configure a widget based on its type.
|
91
|
-
|
92
|
-
Args:
|
93
|
-
widget_type (str): The type of the widget to create.
|
94
|
-
widget_id (str): Unique identifier for the widget.
|
95
|
-
parent_id (str): Identifier of the parent figure.
|
96
|
-
config (dict, optional): Additional configuration for the widget.
|
97
|
-
**axis_kwargs: Additional axis properties to set on the widget after creation.
|
98
|
-
|
99
|
-
Returns:
|
100
|
-
BECPlotBase: The created and configured widget instance.
|
101
|
-
"""
|
102
|
-
entry = self.widget_factory.get(widget_type)
|
103
|
-
if not entry:
|
104
|
-
raise ValueError(f"Unsupported widget type: {widget_type}")
|
105
|
-
|
106
|
-
widget_class, config_class = entry
|
107
|
-
if config is not None and isinstance(config, config_class):
|
108
|
-
config = config.model_dump()
|
109
|
-
widget_config_dict = {
|
110
|
-
"widget_class": widget_class.__name__,
|
111
|
-
"parent_id": parent_id,
|
112
|
-
"gui_id": widget_id,
|
113
|
-
**(config if config is not None else {}),
|
114
|
-
}
|
115
|
-
widget_config = config_class(**widget_config_dict)
|
116
|
-
widget = widget_class(
|
117
|
-
config=widget_config, parent_figure=parent_figure, client=parent_figure.client
|
118
|
-
)
|
119
|
-
|
120
|
-
if axis_kwargs:
|
121
|
-
widget.set(**axis_kwargs)
|
122
|
-
|
123
|
-
return widget
|
124
|
-
|
125
|
-
|
126
|
-
class BECFigure(BECWidget, pg.GraphicsLayoutWidget):
|
127
|
-
USER_ACCESS = [
|
128
|
-
"_rpc_id",
|
129
|
-
"_config_dict",
|
130
|
-
"_get_all_rpc",
|
131
|
-
"axes",
|
132
|
-
"widgets",
|
133
|
-
"plot",
|
134
|
-
"image",
|
135
|
-
"motor_map",
|
136
|
-
"remove",
|
137
|
-
"change_layout",
|
138
|
-
"change_theme",
|
139
|
-
"export",
|
140
|
-
"clear_all",
|
141
|
-
"widget_list",
|
142
|
-
]
|
143
|
-
subplot_map = {
|
144
|
-
"PlotBase": BECPlotBase,
|
145
|
-
"BECWaveform": BECWaveform,
|
146
|
-
"BECImageShow": BECImageShow,
|
147
|
-
"BECMotorMap": BECMotorMap,
|
148
|
-
"BECMultiWaveform": BECMultiWaveform,
|
149
|
-
}
|
150
|
-
widget_method_map = {
|
151
|
-
"BECWaveform": "plot",
|
152
|
-
"BECImageShow": "image",
|
153
|
-
"BECMotorMap": "motor_map",
|
154
|
-
"BECMultiWaveform": "multi_waveform",
|
155
|
-
}
|
156
|
-
|
157
|
-
clean_signal = pyqtSignal()
|
158
|
-
|
159
|
-
def __init__(
|
160
|
-
self,
|
161
|
-
parent: Optional[QWidget] = None,
|
162
|
-
config: Optional[FigureConfig] = None,
|
163
|
-
client=None,
|
164
|
-
gui_id: Optional[str] = None,
|
165
|
-
**kwargs,
|
166
|
-
) -> None:
|
167
|
-
if config is None:
|
168
|
-
config = FigureConfig(widget_class=self.__class__.__name__)
|
169
|
-
else:
|
170
|
-
if isinstance(config, dict):
|
171
|
-
config = FigureConfig(**config)
|
172
|
-
super().__init__(client=client, gui_id=gui_id, config=config, **kwargs)
|
173
|
-
pg.GraphicsLayoutWidget.__init__(self, parent)
|
174
|
-
|
175
|
-
self.widget_handler = WidgetHandler()
|
176
|
-
|
177
|
-
# Widget container to reference widgets by 'widget_id'
|
178
|
-
self._widgets = defaultdict(dict)
|
179
|
-
|
180
|
-
# Container to keep track of the grid
|
181
|
-
self.grid = []
|
182
|
-
# Create config and apply it
|
183
|
-
self.apply_config(config)
|
184
|
-
|
185
|
-
def __getitem__(self, key: tuple | str):
|
186
|
-
if isinstance(key, tuple) and len(key) == 2:
|
187
|
-
return self.axes(*key)
|
188
|
-
if isinstance(key, str):
|
189
|
-
widget = self._widgets.get(key)
|
190
|
-
if widget is None:
|
191
|
-
raise KeyError(f"No widget with ID {key}")
|
192
|
-
return self._widgets.get(key)
|
193
|
-
else:
|
194
|
-
raise TypeError(
|
195
|
-
"Key must be a string (widget id) or a tuple of two integers (grid coordinates)"
|
196
|
-
)
|
197
|
-
|
198
|
-
def apply_config(self, config: dict | FigureConfig): # ,generate_new_id: bool = False):
|
199
|
-
if isinstance(config, dict):
|
200
|
-
try:
|
201
|
-
config = FigureConfig(**config)
|
202
|
-
except ValidationError as e:
|
203
|
-
logger.error(f"Error in applying config: {e}")
|
204
|
-
return
|
205
|
-
self.config = config
|
206
|
-
|
207
|
-
# widget_config has to be reset for not have each widget config twice when added to the figure
|
208
|
-
widget_configs = list(self.config.widgets.values())
|
209
|
-
self.config.widgets = {}
|
210
|
-
for widget_config in widget_configs:
|
211
|
-
getattr(self, self.widget_method_map[widget_config.widget_class])(
|
212
|
-
config=widget_config.model_dump(), row=widget_config.row, col=widget_config.col
|
213
|
-
)
|
214
|
-
|
215
|
-
@property
|
216
|
-
def widget_list(self) -> list[BECPlotBase]:
|
217
|
-
"""
|
218
|
-
Access all widget in BECFigure as a list
|
219
|
-
Returns:
|
220
|
-
list[BECPlotBase]: List of all widgets in the figure.
|
221
|
-
"""
|
222
|
-
axes = [value for value in self._widgets.values() if isinstance(value, BECPlotBase)]
|
223
|
-
return axes
|
224
|
-
|
225
|
-
@widget_list.setter
|
226
|
-
def widget_list(self, value: list[BECPlotBase]):
|
227
|
-
"""
|
228
|
-
Access all widget in BECFigure as a list
|
229
|
-
Returns:
|
230
|
-
list[BECPlotBase]: List of all widgets in the figure.
|
231
|
-
"""
|
232
|
-
self._axes = value
|
233
|
-
|
234
|
-
@property
|
235
|
-
def widgets(self) -> dict:
|
236
|
-
"""
|
237
|
-
All widgets within the figure with gui ids as keys.
|
238
|
-
Returns:
|
239
|
-
dict: All widgets within the figure.
|
240
|
-
"""
|
241
|
-
return self._widgets
|
242
|
-
|
243
|
-
@widgets.setter
|
244
|
-
def widgets(self, value: dict):
|
245
|
-
"""
|
246
|
-
All widgets within the figure with gui ids as keys.
|
247
|
-
Returns:
|
248
|
-
dict: All widgets within the figure.
|
249
|
-
"""
|
250
|
-
self._widgets = value
|
251
|
-
|
252
|
-
def export(self):
|
253
|
-
"""Export the plot widget."""
|
254
|
-
try:
|
255
|
-
plot_item = self.widget_list[0]
|
256
|
-
except Exception as exc:
|
257
|
-
raise ValueError("No plot widget available to export.") from exc
|
258
|
-
|
259
|
-
scene = plot_item.scene()
|
260
|
-
scene.contextMenuItem = plot_item
|
261
|
-
scene.showExportDialog()
|
262
|
-
|
263
|
-
@typechecked
|
264
|
-
def plot(
|
265
|
-
self,
|
266
|
-
arg1: list | np.ndarray | str | None = None,
|
267
|
-
y: list | np.ndarray | None = None,
|
268
|
-
x: list | np.ndarray | None = None,
|
269
|
-
x_name: str | None = None,
|
270
|
-
y_name: str | None = None,
|
271
|
-
z_name: str | None = None,
|
272
|
-
x_entry: str | None = None,
|
273
|
-
y_entry: str | None = None,
|
274
|
-
z_entry: str | None = None,
|
275
|
-
color: str | None = None,
|
276
|
-
color_map_z: str | None = "magma",
|
277
|
-
label: str | None = None,
|
278
|
-
validate: bool = True,
|
279
|
-
new: bool = False,
|
280
|
-
row: int | None = None,
|
281
|
-
col: int | None = None,
|
282
|
-
dap: str | None = None,
|
283
|
-
config: dict | None = None, # TODO make logic more transparent
|
284
|
-
**axis_kwargs,
|
285
|
-
) -> BECWaveform:
|
286
|
-
"""
|
287
|
-
Add a 1D waveform plot to the figure. Always access the first waveform widget in the figure.
|
288
|
-
|
289
|
-
Args:
|
290
|
-
arg1(list | np.ndarray | str | None): First argument which can be x data, y data, or y_name.
|
291
|
-
y(list | np.ndarray): Custom y data to plot.
|
292
|
-
x(list | np.ndarray): Custom x data to plot.
|
293
|
-
x_name(str): The name of the device for the x-axis.
|
294
|
-
y_name(str): The name of the device for the y-axis.
|
295
|
-
z_name(str): The name of the device for the z-axis.
|
296
|
-
x_entry(str): The name of the entry for the x-axis.
|
297
|
-
y_entry(str): The name of the entry for the y-axis.
|
298
|
-
z_entry(str): The name of the entry for the z-axis.
|
299
|
-
color(str): The color of the curve.
|
300
|
-
color_map_z(str): The color map to use for the z-axis.
|
301
|
-
label(str): The label of the curve.
|
302
|
-
validate(bool): If True, validate the device names and entries.
|
303
|
-
new(bool): If True, create a new plot instead of using the first plot.
|
304
|
-
row(int): The row coordinate of the widget in the figure. If not provided, the next empty row will be used.
|
305
|
-
col(int): The column coordinate of the widget in the figure. If not provided, the next empty column will be used.
|
306
|
-
dap(str): The DAP model to use for the curve.
|
307
|
-
config(dict): Recreates the whole BECWaveform widget from provided configuration.
|
308
|
-
**axis_kwargs: Additional axis properties to set on the widget after creation.
|
309
|
-
|
310
|
-
Returns:
|
311
|
-
BECWaveform: The waveform plot widget.
|
312
|
-
"""
|
313
|
-
waveform = self.subplot_factory(
|
314
|
-
widget_type="BECWaveform", config=config, row=row, col=col, new=new, **axis_kwargs
|
315
|
-
)
|
316
|
-
if config is not None:
|
317
|
-
return waveform
|
318
|
-
|
319
|
-
if arg1 is not None or y_name is not None or (y is not None and x is not None):
|
320
|
-
waveform.plot(
|
321
|
-
arg1=arg1,
|
322
|
-
y=y,
|
323
|
-
x=x,
|
324
|
-
x_name=x_name,
|
325
|
-
y_name=y_name,
|
326
|
-
z_name=z_name,
|
327
|
-
x_entry=x_entry,
|
328
|
-
y_entry=y_entry,
|
329
|
-
z_entry=z_entry,
|
330
|
-
color=color,
|
331
|
-
color_map_z=color_map_z,
|
332
|
-
label=label,
|
333
|
-
validate=validate,
|
334
|
-
dap=dap,
|
335
|
-
)
|
336
|
-
return waveform
|
337
|
-
|
338
|
-
def _init_image(
|
339
|
-
self,
|
340
|
-
image,
|
341
|
-
monitor: str = None,
|
342
|
-
monitor_type: Literal["1d", "2d"] = "2d",
|
343
|
-
color_bar: Literal["simple", "full"] = "full",
|
344
|
-
color_map: str = "magma",
|
345
|
-
data: np.ndarray = None,
|
346
|
-
vrange: tuple[float, float] = None,
|
347
|
-
) -> BECImageShow:
|
348
|
-
"""
|
349
|
-
Configure the image based on the provided parameters.
|
350
|
-
|
351
|
-
Args:
|
352
|
-
image (BECImageShow): The image to configure.
|
353
|
-
monitor (str): The name of the monitor to display.
|
354
|
-
color_bar (Literal["simple","full"]): The type of color bar to display.
|
355
|
-
color_map (str): The color map to use for the image.
|
356
|
-
data (np.ndarray): Custom data to display.
|
357
|
-
"""
|
358
|
-
if monitor is not None and data is None:
|
359
|
-
image.image(
|
360
|
-
monitor=monitor,
|
361
|
-
monitor_type=monitor_type,
|
362
|
-
color_map=color_map,
|
363
|
-
vrange=vrange,
|
364
|
-
color_bar=color_bar,
|
365
|
-
)
|
366
|
-
elif data is not None and monitor is None:
|
367
|
-
image.add_custom_image(
|
368
|
-
name="custom", data=data, color_map=color_map, vrange=vrange, color_bar=color_bar
|
369
|
-
)
|
370
|
-
elif data is None and monitor is None:
|
371
|
-
# Setting appearance
|
372
|
-
if vrange is not None:
|
373
|
-
image.set_vrange(vmin=vrange[0], vmax=vrange[1])
|
374
|
-
if color_map is not None:
|
375
|
-
image.set_color_map(color_map)
|
376
|
-
else:
|
377
|
-
raise ValueError("Invalid input. Provide either monitor name or custom data.")
|
378
|
-
return image
|
379
|
-
|
380
|
-
def image(
|
381
|
-
self,
|
382
|
-
monitor: str = None,
|
383
|
-
monitor_type: Literal["1d", "2d"] = "2d",
|
384
|
-
color_bar: Literal["simple", "full"] = "full",
|
385
|
-
color_map: str = "magma",
|
386
|
-
data: np.ndarray = None,
|
387
|
-
vrange: tuple[float, float] = None,
|
388
|
-
new: bool = False,
|
389
|
-
row: int | None = None,
|
390
|
-
col: int | None = None,
|
391
|
-
config: dict | None = None,
|
392
|
-
**axis_kwargs,
|
393
|
-
) -> BECImageShow:
|
394
|
-
"""
|
395
|
-
Add an image to the figure. Always access the first image widget in the figure.
|
396
|
-
|
397
|
-
Args:
|
398
|
-
monitor(str): The name of the monitor to display.
|
399
|
-
color_bar(Literal["simple","full"]): The type of color bar to display.
|
400
|
-
color_map(str): The color map to use for the image.
|
401
|
-
data(np.ndarray): Custom data to display.
|
402
|
-
vrange(tuple[float, float]): The range of values to display.
|
403
|
-
new(bool): If True, create a new plot instead of using the first plot.
|
404
|
-
row(int): The row coordinate of the widget in the figure. If not provided, the next empty row will be used.
|
405
|
-
col(int): The column coordinate of the widget in the figure. If not provided, the next empty column will be used.
|
406
|
-
config(dict): Recreates the whole BECImageShow widget from provided configuration.
|
407
|
-
**axis_kwargs: Additional axis properties to set on the widget after creation.
|
408
|
-
|
409
|
-
Returns:
|
410
|
-
BECImageShow: The image widget.
|
411
|
-
"""
|
412
|
-
|
413
|
-
image = self.subplot_factory(
|
414
|
-
widget_type="BECImageShow", config=config, row=row, col=col, new=new, **axis_kwargs
|
415
|
-
)
|
416
|
-
if config is not None:
|
417
|
-
return image
|
418
|
-
|
419
|
-
image = self._init_image(
|
420
|
-
image=image,
|
421
|
-
monitor=monitor,
|
422
|
-
monitor_type=monitor_type,
|
423
|
-
color_bar=color_bar,
|
424
|
-
color_map=color_map,
|
425
|
-
data=data,
|
426
|
-
vrange=vrange,
|
427
|
-
)
|
428
|
-
return image
|
429
|
-
|
430
|
-
def motor_map(
|
431
|
-
self,
|
432
|
-
motor_x: str = None,
|
433
|
-
motor_y: str = None,
|
434
|
-
new: bool = False,
|
435
|
-
row: int | None = None,
|
436
|
-
col: int | None = None,
|
437
|
-
config: dict | None = None,
|
438
|
-
**axis_kwargs,
|
439
|
-
) -> BECMotorMap:
|
440
|
-
"""
|
441
|
-
Add a motor map to the figure. Always access the first motor map widget in the figure.
|
442
|
-
|
443
|
-
Args:
|
444
|
-
motor_x(str): The name of the motor for the X axis.
|
445
|
-
motor_y(str): The name of the motor for the Y axis.
|
446
|
-
new(bool): If True, create a new plot instead of using the first plot.
|
447
|
-
row(int): The row coordinate of the widget in the figure. If not provided, the next empty row will be used.
|
448
|
-
col(int): The column coordinate of the widget in the figure. If not provided, the next empty column will be used.
|
449
|
-
config(dict): Recreates the whole BECImageShow widget from provided configuration.
|
450
|
-
**axis_kwargs: Additional axis properties to set on the widget after creation.
|
451
|
-
|
452
|
-
Returns:
|
453
|
-
BECMotorMap: The motor map widget.
|
454
|
-
"""
|
455
|
-
motor_map = self.subplot_factory(
|
456
|
-
widget_type="BECMotorMap", config=config, row=row, col=col, new=new, **axis_kwargs
|
457
|
-
)
|
458
|
-
if config is not None:
|
459
|
-
return motor_map
|
460
|
-
|
461
|
-
if motor_x is not None and motor_y is not None:
|
462
|
-
motor_map.change_motors(motor_x, motor_y)
|
463
|
-
|
464
|
-
return motor_map
|
465
|
-
|
466
|
-
def multi_waveform(
|
467
|
-
self,
|
468
|
-
monitor: str = None,
|
469
|
-
new: bool = False,
|
470
|
-
row: int | None = None,
|
471
|
-
col: int | None = None,
|
472
|
-
config: dict | None = None,
|
473
|
-
**axis_kwargs,
|
474
|
-
):
|
475
|
-
multi_waveform = self.subplot_factory(
|
476
|
-
widget_type="BECMultiWaveform", config=config, row=row, col=col, new=new, **axis_kwargs
|
477
|
-
)
|
478
|
-
if config is not None:
|
479
|
-
return multi_waveform
|
480
|
-
multi_waveform.set_monitor(monitor)
|
481
|
-
return multi_waveform
|
482
|
-
|
483
|
-
def subplot_factory(
|
484
|
-
self,
|
485
|
-
widget_type: Literal[
|
486
|
-
"BECPlotBase", "BECWaveform", "BECImageShow", "BECMotorMap", "BECMultiWaveform"
|
487
|
-
] = "BECPlotBase",
|
488
|
-
row: int = None,
|
489
|
-
col: int = None,
|
490
|
-
config=None,
|
491
|
-
new: bool = False,
|
492
|
-
**axis_kwargs,
|
493
|
-
) -> BECPlotBase:
|
494
|
-
# Case 1 - config provided, new plot, possible to define coordinates
|
495
|
-
if config is not None:
|
496
|
-
widget_cls = config["widget_class"]
|
497
|
-
if widget_cls != widget_type:
|
498
|
-
raise ValueError(
|
499
|
-
f"Widget type '{widget_type}' does not match the provided configuration ({widget_cls})."
|
500
|
-
)
|
501
|
-
widget = self.add_widget(
|
502
|
-
widget_type=widget_type, config=config, row=row, col=col, **axis_kwargs
|
503
|
-
)
|
504
|
-
return widget
|
505
|
-
|
506
|
-
# Case 2 - find first plot or create first plot if no plot available, no config provided, no coordinates
|
507
|
-
if new is False and (row is None or col is None):
|
508
|
-
widget = WidgetContainerUtils.find_first_widget_by_class(
|
509
|
-
self._widgets, self.subplot_map[widget_type], can_fail=True
|
510
|
-
)
|
511
|
-
if widget is not None:
|
512
|
-
if axis_kwargs:
|
513
|
-
widget.set(**axis_kwargs)
|
514
|
-
else:
|
515
|
-
widget = self.add_widget(widget_type=widget_type, **axis_kwargs)
|
516
|
-
return widget
|
517
|
-
|
518
|
-
# Case 3 - modifying existing plot wit coordinates provided
|
519
|
-
if new is False and (row is not None and col is not None):
|
520
|
-
try:
|
521
|
-
widget = self.axes(row, col)
|
522
|
-
except ValueError:
|
523
|
-
widget = None
|
524
|
-
if widget is not None:
|
525
|
-
if axis_kwargs:
|
526
|
-
widget.set(**axis_kwargs)
|
527
|
-
else:
|
528
|
-
widget = self.add_widget(widget_type=widget_type, row=row, col=col, **axis_kwargs)
|
529
|
-
return widget
|
530
|
-
|
531
|
-
# Case 4 - no previous plot or new plot, no config provided, possible to define coordinates
|
532
|
-
widget = self.add_widget(widget_type=widget_type, row=row, col=col, **axis_kwargs)
|
533
|
-
return widget
|
534
|
-
|
535
|
-
def add_widget(
|
536
|
-
self,
|
537
|
-
widget_type: Literal[
|
538
|
-
"BECPlotBase", "BECWaveform", "BECImageShow", "BECMotorMap", "BECMultiWaveform"
|
539
|
-
] = "BECPlotBase",
|
540
|
-
widget_id: str = None,
|
541
|
-
row: int = None,
|
542
|
-
col: int = None,
|
543
|
-
config=None,
|
544
|
-
**axis_kwargs,
|
545
|
-
) -> BECPlotBase:
|
546
|
-
"""
|
547
|
-
Add a widget to the figure at the specified position.
|
548
|
-
|
549
|
-
Args:
|
550
|
-
widget_type(Literal["PlotBase","Waveform1D"]): The type of the widget to add.
|
551
|
-
widget_id(str): The unique identifier of the widget. If not provided, a unique ID will be generated.
|
552
|
-
row(int): The row coordinate of the widget in the figure. If not provided, the next empty row will be used.
|
553
|
-
col(int): The column coordinate of the widget in the figure. If not provided, the next empty column will be used.
|
554
|
-
config(dict): Additional configuration for the widget.
|
555
|
-
**axis_kwargs(dict): Additional axis properties to set on the widget after creation.
|
556
|
-
"""
|
557
|
-
if not widget_id:
|
558
|
-
widget_id = str(uuid.uuid4())
|
559
|
-
if widget_id in self._widgets:
|
560
|
-
raise ValueError(f"Widget with ID '{widget_id}' already exists.")
|
561
|
-
|
562
|
-
# Check if position is occupied
|
563
|
-
if row is not None and col is not None:
|
564
|
-
if self.getItem(row, col):
|
565
|
-
raise ValueError(f"Position at row {row} and column {col} is already occupied.")
|
566
|
-
else:
|
567
|
-
row, col = self._find_next_empty_position()
|
568
|
-
|
569
|
-
widget = self.widget_handler.create_widget(
|
570
|
-
widget_type=widget_type,
|
571
|
-
widget_id=widget_id,
|
572
|
-
parent_figure=self,
|
573
|
-
parent_id=self.gui_id,
|
574
|
-
config=config,
|
575
|
-
**axis_kwargs,
|
576
|
-
)
|
577
|
-
widget.set_gui_id(widget_id)
|
578
|
-
widget.config.row = row
|
579
|
-
widget.config.col = col
|
580
|
-
|
581
|
-
# Add widget to the figure
|
582
|
-
self.addItem(widget, row=row, col=col)
|
583
|
-
|
584
|
-
# Update num_cols and num_rows based on the added widget
|
585
|
-
self.config.num_rows = max(self.config.num_rows, row + 1)
|
586
|
-
self.config.num_cols = max(self.config.num_cols, col + 1)
|
587
|
-
|
588
|
-
# Saving config for future referencing
|
589
|
-
|
590
|
-
self.config.widgets[widget_id] = widget.config
|
591
|
-
self._widgets[widget_id] = widget
|
592
|
-
|
593
|
-
# Reflect the grid coordinates
|
594
|
-
self._change_grid(widget_id, row, col)
|
595
|
-
|
596
|
-
return widget
|
597
|
-
|
598
|
-
def remove(
|
599
|
-
self,
|
600
|
-
row: int = None,
|
601
|
-
col: int = None,
|
602
|
-
widget_id: str = None,
|
603
|
-
coordinates: tuple[int, int] = None,
|
604
|
-
) -> None:
|
605
|
-
"""
|
606
|
-
Remove a widget from the figure. Can be removed by its unique identifier or by its coordinates.
|
607
|
-
|
608
|
-
Args:
|
609
|
-
row(int): The row coordinate of the widget to remove.
|
610
|
-
col(int): The column coordinate of the widget to remove.
|
611
|
-
widget_id(str): The unique identifier of the widget to remove.
|
612
|
-
coordinates(tuple[int, int], optional): The coordinates of the widget to remove.
|
613
|
-
"""
|
614
|
-
if widget_id:
|
615
|
-
self._remove_by_id(widget_id)
|
616
|
-
elif row is not None and col is not None:
|
617
|
-
self._remove_by_coordinates(row, col)
|
618
|
-
elif coordinates:
|
619
|
-
self._remove_by_coordinates(*coordinates)
|
620
|
-
else:
|
621
|
-
raise ValueError("Must provide either widget_id or coordinates for removal.")
|
622
|
-
|
623
|
-
def change_theme(self, theme: Literal["dark", "light"]) -> None:
|
624
|
-
"""
|
625
|
-
Change the theme of the figure widget.
|
626
|
-
|
627
|
-
Args:
|
628
|
-
theme(Literal["dark","light"]): The theme to set for the figure widget.
|
629
|
-
"""
|
630
|
-
self.config.theme = theme
|
631
|
-
apply_theme(theme)
|
632
|
-
for plot in self.widget_list:
|
633
|
-
plot.set_x_label(plot.plot_item.getAxis("bottom").label.toPlainText())
|
634
|
-
plot.set_y_label(plot.plot_item.getAxis("left").label.toPlainText())
|
635
|
-
if plot.plot_item.titleLabel.text:
|
636
|
-
plot.set_title(plot.plot_item.titleLabel.text)
|
637
|
-
plot.set_legend_label_size()
|
638
|
-
|
639
|
-
def _remove_by_coordinates(self, row: int, col: int) -> None:
|
640
|
-
"""
|
641
|
-
Remove a widget from the figure by its coordinates.
|
642
|
-
|
643
|
-
Args:
|
644
|
-
row(int): The row coordinate of the widget to remove.
|
645
|
-
col(int): The column coordinate of the widget to remove.
|
646
|
-
"""
|
647
|
-
widget = self.axes(row, col)
|
648
|
-
if widget:
|
649
|
-
widget_id = widget.config.gui_id
|
650
|
-
if widget_id in self._widgets:
|
651
|
-
self._remove_by_id(widget_id)
|
652
|
-
|
653
|
-
def _remove_by_id(self, widget_id: str) -> None:
|
654
|
-
"""
|
655
|
-
Remove a widget from the figure by its unique identifier.
|
656
|
-
|
657
|
-
Args:
|
658
|
-
widget_id(str): The unique identifier of the widget to remove.
|
659
|
-
"""
|
660
|
-
if widget_id in self._widgets:
|
661
|
-
widget = self._widgets.pop(widget_id)
|
662
|
-
widget.cleanup_pyqtgraph()
|
663
|
-
widget.cleanup()
|
664
|
-
self.removeItem(widget)
|
665
|
-
self.grid[widget.config.row][widget.config.col] = None
|
666
|
-
self._reindex_grid()
|
667
|
-
if widget_id in self.config.widgets:
|
668
|
-
self.config.widgets.pop(widget_id)
|
669
|
-
widget.deleteLater()
|
670
|
-
else:
|
671
|
-
raise ValueError(f"Widget with ID '{widget_id}' does not exist.")
|
672
|
-
|
673
|
-
def axes(self, row: int, col: int) -> BECPlotBase:
|
674
|
-
"""
|
675
|
-
Get widget by its coordinates in the figure.
|
676
|
-
|
677
|
-
Args:
|
678
|
-
row(int): the row coordinate
|
679
|
-
col(int): the column coordinate
|
680
|
-
|
681
|
-
Returns:
|
682
|
-
BECPlotBase: the widget at the given coordinates
|
683
|
-
"""
|
684
|
-
widget = self.getItem(row, col)
|
685
|
-
if widget is None:
|
686
|
-
raise ValueError(f"No widget at coordinates ({row}, {col})")
|
687
|
-
return widget
|
688
|
-
|
689
|
-
def _find_next_empty_position(self):
|
690
|
-
"""Find the next empty position (new row) in the figure."""
|
691
|
-
row, col = 0, 0
|
692
|
-
while self.getItem(row, col):
|
693
|
-
row += 1
|
694
|
-
return row, col
|
695
|
-
|
696
|
-
def _change_grid(self, widget_id: str, row: int, col: int):
|
697
|
-
"""
|
698
|
-
Change the grid to reflect the new position of the widget.
|
699
|
-
|
700
|
-
Args:
|
701
|
-
widget_id(str): The unique identifier of the widget.
|
702
|
-
row(int): The new row coordinate of the widget in the figure.
|
703
|
-
col(int): The new column coordinate of the widget in the figure.
|
704
|
-
"""
|
705
|
-
while len(self.grid) <= row:
|
706
|
-
self.grid.append([])
|
707
|
-
row = self.grid[row]
|
708
|
-
while len(row) <= col:
|
709
|
-
row.append(None)
|
710
|
-
row[col] = widget_id
|
711
|
-
|
712
|
-
def _reindex_grid(self):
|
713
|
-
"""Reindex the grid to remove empty rows and columns."""
|
714
|
-
new_grid = []
|
715
|
-
for row in self.grid:
|
716
|
-
new_row = [widget for widget in row if widget is not None]
|
717
|
-
if new_row:
|
718
|
-
new_grid.append(new_row)
|
719
|
-
#
|
720
|
-
# Update the config of each object to reflect its new position
|
721
|
-
for row_idx, row in enumerate(new_grid):
|
722
|
-
for col_idx, widget in enumerate(row):
|
723
|
-
self._widgets[widget].config.row, self._widgets[widget].config.col = (
|
724
|
-
row_idx,
|
725
|
-
col_idx,
|
726
|
-
)
|
727
|
-
|
728
|
-
self.grid = new_grid
|
729
|
-
self._replot_layout()
|
730
|
-
|
731
|
-
def _replot_layout(self):
|
732
|
-
"""Replot the layout based on the current grid configuration."""
|
733
|
-
self.clear()
|
734
|
-
for row_idx, row in enumerate(self.grid):
|
735
|
-
for col_idx, widget in enumerate(row):
|
736
|
-
self.addItem(self._widgets[widget], row=row_idx, col=col_idx)
|
737
|
-
|
738
|
-
def change_layout(self, max_columns=None, max_rows=None):
|
739
|
-
"""
|
740
|
-
Reshuffle the layout of the figure to adjust to a new number of max_columns or max_rows.
|
741
|
-
If both max_columns and max_rows are provided, max_rows is ignored.
|
742
|
-
|
743
|
-
Args:
|
744
|
-
max_columns (Optional[int]): The new maximum number of columns in the figure.
|
745
|
-
max_rows (Optional[int]): The new maximum number of rows in the figure.
|
746
|
-
"""
|
747
|
-
# Calculate total number of widgets
|
748
|
-
total_widgets = len(self._widgets)
|
749
|
-
|
750
|
-
if max_columns:
|
751
|
-
# Calculate the required number of rows based on max_columns
|
752
|
-
required_rows = (total_widgets + max_columns - 1) // max_columns
|
753
|
-
new_grid = [[None for _ in range(max_columns)] for _ in range(required_rows)]
|
754
|
-
elif max_rows:
|
755
|
-
# Calculate the required number of columns based on max_rows
|
756
|
-
required_columns = (total_widgets + max_rows - 1) // max_rows
|
757
|
-
new_grid = [[None for _ in range(required_columns)] for _ in range(max_rows)]
|
758
|
-
else:
|
759
|
-
# If neither max_columns nor max_rows is specified, just return without changing the layout
|
760
|
-
return
|
761
|
-
|
762
|
-
# Populate the new grid with widgets' IDs
|
763
|
-
current_idx = 0
|
764
|
-
for widget_id in self._widgets:
|
765
|
-
row = current_idx // len(new_grid[0])
|
766
|
-
col = current_idx % len(new_grid[0])
|
767
|
-
new_grid[row][col] = widget_id
|
768
|
-
current_idx += 1
|
769
|
-
|
770
|
-
self.config.num_rows = row
|
771
|
-
self.config.num_cols = col
|
772
|
-
|
773
|
-
# Update widgets' positions and replot them according to the new grid
|
774
|
-
self.grid = new_grid
|
775
|
-
self._reindex_grid() # This method should be updated to handle reshuffling correctly
|
776
|
-
self._replot_layout() # Assumes this method re-adds widgets to the layout based on self.grid
|
777
|
-
|
778
|
-
def clear_all(self):
|
779
|
-
"""Clear all widgets from the figure and reset to default state"""
|
780
|
-
for widget in list(self._widgets.values()):
|
781
|
-
widget.remove()
|
782
|
-
self._widgets.clear()
|
783
|
-
self.grid = []
|
784
|
-
theme = self.config.theme
|
785
|
-
self.config = FigureConfig(
|
786
|
-
widget_class=self.__class__.__name__, gui_id=self.gui_id, theme=theme
|
787
|
-
)
|
788
|
-
|
789
|
-
def cleanup_pyqtgraph_all_widgets(self):
|
790
|
-
"""Clean up the pyqtgraph widget."""
|
791
|
-
for widget in self.widget_list:
|
792
|
-
widget.cleanup_pyqtgraph()
|
793
|
-
|
794
|
-
def cleanup(self):
|
795
|
-
"""Close the figure widget."""
|
796
|
-
self.cleanup_pyqtgraph_all_widgets()
|