From 7c31bbd9c2e0230e54f4dca0f1e5c4d2cd6e7674 Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Thu, 20 Mar 2025 20:44:15 +0100 Subject: [PATCH] refactor(multi_waveform_widget): BECMultiWaveformWidget removed --- bec_widgets/cli/client.py | 200 ------- .../widgets/containers/dock/dock_area.py | 6 +- bec_widgets/widgets/plots/__init__.py | 0 .../widgets/plots/multi_waveform/__init__.py | 0 .../bec_multi_waveform_widget.pyproject | 1 - .../bec_multi_waveform_widget_plugin.py | 54 -- .../multi_waveform/multi_waveform_controls.ui | 99 ---- .../multi_waveform/multi_waveform_widget.py | 536 ------------------ .../register_bec_multi_waveform_widget.py | 17 - tests/unit_tests/test_bec_dock.py | 7 +- .../unit_tests/test_multi_waveform_widget.py | 296 ---------- 11 files changed, 5 insertions(+), 1211 deletions(-) delete mode 100644 bec_widgets/widgets/plots/__init__.py delete mode 100644 bec_widgets/widgets/plots/multi_waveform/__init__.py delete mode 100644 bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject delete mode 100644 bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget_plugin.py delete mode 100644 bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui delete mode 100644 bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py delete mode 100644 bec_widgets/widgets/plots/multi_waveform/register_bec_multi_waveform_widget.py delete mode 100644 tests/unit_tests/test_multi_waveform_widget.py diff --git a/bec_widgets/cli/client.py b/bec_widgets/cli/client.py index a022454f..22d29abf 100644 --- a/bec_widgets/cli/client.py +++ b/bec_widgets/cli/client.py @@ -18,7 +18,6 @@ class Widgets(str, enum.Enum): AbortButton = "AbortButton" BECColorMapWidget = "BECColorMapWidget" BECDockArea = "BECDockArea" - BECMultiWaveformWidget = "BECMultiWaveformWidget" BECProgressBar = "BECProgressBar" BECQueue = "BECQueue" BECStatusBox = "BECStatusBox" @@ -1547,205 +1546,6 @@ class BECMultiWaveform(RPCBase): """ -class BECMultiWaveformWidget(RPCBase): - @property - @rpc_call - def curves(self) -> list[pyqtgraph.graphicsItems.PlotDataItem.PlotDataItem]: - """ - Get the curves of the plot widget as a list - Returns: - list: List of curves. - """ - - @rpc_call - def set_monitor(self, monitor: str) -> None: - """ - Set the monitor of the plot widget. - - Args: - monitor(str): The monitor to set. - """ - - @rpc_call - def set_curve_highlight(self, index: int) -> None: - """ - Set the curve highlight of the plot widget by index - - Args: - index(int): The index of the curve to highlight. - """ - - @rpc_call - def set_opacity(self, opacity: int) -> None: - """ - Set the opacity of the plot widget. - - Args: - opacity(int): The opacity to set. - """ - - @rpc_call - def set_curve_limit(self, curve_limit: int) -> None: - """ - Set the maximum number of traces to display on the plot widget. - - Args: - curve_limit(int): The maximum number of traces to display. - """ - - @rpc_call - def set_buffer_flush(self, flush_buffer: bool) -> None: - """ - Set the buffer flush property of the plot widget. - - Args: - flush_buffer(bool): True to flush the buffer, False to not flush the buffer. - """ - - @rpc_call - def set_highlight_last_curve(self, enable: bool) -> None: - """ - Enable or disable highlighting of the last curve. - - Args: - enable(bool): True to enable highlighting of the last curve, False to disable. - """ - - @rpc_call - def set_colormap(self, colormap: str) -> None: - """ - Set the colormap of the plot widget. - - Args: - colormap(str): The colormap to set. - """ - - @rpc_call - def set(self, **kwargs): - """ - Set the properties of the plot widget. - - Args: - **kwargs: Keyword arguments for the properties to be set. - - Possible properties: - - title: str - - x_label: str - - y_label: str - - x_scale: Literal["linear", "log"] - - y_scale: Literal["linear", "log"] - - x_lim: tuple - - y_lim: tuple - - legend_label_size: int - """ - - @rpc_call - def set_title(self, title: str): - """ - Set the title of the plot widget. - - Args: - title(str): The title to set. - """ - - @rpc_call - def set_x_label(self, x_label: str): - """ - Set the x-axis label of the plot widget. - - Args: - x_label(str): The x-axis label to set. - """ - - @rpc_call - def set_y_label(self, y_label: str): - """ - Set the y-axis label of the plot widget. - - Args: - y_label(str): The y-axis label to set. - """ - - @rpc_call - def set_x_scale(self, x_scale: Literal["linear", "log"]): - """ - Set the x-axis scale of the plot widget. - - Args: - x_scale(str): The x-axis scale to set. - """ - - @rpc_call - def set_y_scale(self, y_scale: Literal["linear", "log"]): - """ - Set the y-axis scale of the plot widget. - - Args: - y_scale(str): The y-axis scale to set. - """ - - @rpc_call - def set_x_lim(self, x_lim: tuple): - """ - Set x-axis limits of the plot widget. - - Args: - x_lim(tuple): The x-axis limits to set. - """ - - @rpc_call - def set_y_lim(self, y_lim: tuple): - """ - Set y-axis limits of the plot widget. - - Args: - y_lim(tuple): The y-axis limits to set. - """ - - @rpc_call - def set_grid(self, x_grid: bool, y_grid: bool): - """ - Set the grid of the plot widget. - - Args: - x_grid(bool): True to enable the x-grid, False to disable. - y_grid(bool): True to enable the y-grid, False to disable. - """ - - @rpc_call - def set_colormap(self, colormap: str) -> None: - """ - Set the colormap of the plot widget. - - Args: - colormap(str): The colormap to set. - """ - - @rpc_call - def enable_fps_monitor(self, enabled: bool): - """ - Enable or disable the FPS monitor - - Args: - enabled(bool): True to enable the FPS monitor, False to disable. - """ - - @rpc_call - def lock_aspect_ratio(self, lock: bool): - """ - Lock the aspect ratio of the plot widget. - - Args: - lock(bool): True to lock the aspect ratio, False to unlock. - """ - - @rpc_call - def export(self): - """ - Export the plot widget. - """ - - class BECPlotBase(RPCBase): @property @rpc_call diff --git a/bec_widgets/widgets/containers/dock/dock_area.py b/bec_widgets/widgets/containers/dock/dock_area.py index d561c65e..f9a279c6 100644 --- a/bec_widgets/widgets/containers/dock/dock_area.py +++ b/bec_widgets/widgets/containers/dock/dock_area.py @@ -25,9 +25,9 @@ from bec_widgets.widgets.containers.dock.dock import BECDock, DockConfig from bec_widgets.widgets.control.device_control.positioner_box import PositionerBox from bec_widgets.widgets.control.scan_control.scan_control import ScanControl from bec_widgets.widgets.editors.vscode.vscode import VSCodeEditor -from bec_widgets.widgets.plots.multi_waveform.multi_waveform_widget import BECMultiWaveformWidget from bec_widgets.widgets.plots_next_gen.image.image import Image from bec_widgets.widgets.plots_next_gen.motor_map.motor_map import MotorMap +from bec_widgets.widgets.plots_next_gen.multi_waveform.multi_waveform import MultiWaveform from bec_widgets.widgets.plots_next_gen.scatter_waveform.scatter_waveform import ScatterWaveform from bec_widgets.widgets.plots_next_gen.waveform.waveform import Waveform from bec_widgets.widgets.progress.ring_progress_bar.ring_progress_bar import RingProgressBar @@ -103,7 +103,7 @@ class BECDockArea(BECWidget, QWidget): filled=True, ), "multi_waveform": MaterialIconAction( - icon_name=BECMultiWaveformWidget.ICON_NAME, + icon_name=MultiWaveform.ICON_NAME, tooltip="Add Multi Waveform", filled=True, ), @@ -184,7 +184,7 @@ class BECDockArea(BECWidget, QWidget): lambda: self._create_widget_from_toolbar(widget_name="ScatterWaveform") ) self.toolbar.widgets["menu_plots"].widgets["multi_waveform"].triggered.connect( - lambda: self._create_widget_from_toolbar(widget_name="BECMultiWaveformWidget") + lambda: self._create_widget_from_toolbar(widget_name="MultiWaveform") ) self.toolbar.widgets["menu_plots"].widgets["image"].triggered.connect( lambda: self._create_widget_from_toolbar(widget_name="Image") diff --git a/bec_widgets/widgets/plots/__init__.py b/bec_widgets/widgets/plots/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/plots/multi_waveform/__init__.py b/bec_widgets/widgets/plots/multi_waveform/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject b/bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject deleted file mode 100644 index 76c159ae..00000000 --- a/bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget.pyproject +++ /dev/null @@ -1 +0,0 @@ -{'files': ['multi_waveform_widget.py','multi-waveform_controls.ui']} \ No newline at end of file diff --git a/bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget_plugin.py b/bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget_plugin.py deleted file mode 100644 index feb035bc..00000000 --- a/bec_widgets/widgets/plots/multi_waveform/bec_multi_waveform_widget_plugin.py +++ /dev/null @@ -1,54 +0,0 @@ -# Copyright (C) 2022 The Qt Company Ltd. -# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause - -from qtpy.QtDesigner import QDesignerCustomWidgetInterface - -from bec_widgets.utils.bec_designer import designer_material_icon -from bec_widgets.widgets.plots.multi_waveform.multi_waveform_widget import BECMultiWaveformWidget - -DOM_XML = """ - - - - -""" - - -class BECMultiWaveformWidgetPlugin(QDesignerCustomWidgetInterface): # pragma: no cover - def __init__(self): - super().__init__() - self._form_editor = None - - def createWidget(self, parent): - t = BECMultiWaveformWidget(parent) - return t - - def domXml(self): - return DOM_XML - - def group(self): - return "BEC Plots" - - def icon(self): - return designer_material_icon(BECMultiWaveformWidget.ICON_NAME) - - def includeFile(self): - return "bec_multi_waveform_widget" - - def initialize(self, form_editor): - self._form_editor = form_editor - - def isContainer(self): - return False - - def isInitialized(self): - return self._form_editor is not None - - def name(self): - return "BECMultiWaveformWidget" - - def toolTip(self): - return "BECMultiWaveformWidget" - - def whatsThis(self): - return self.toolTip() diff --git a/bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui b/bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui deleted file mode 100644 index 516631bd..00000000 --- a/bec_widgets/widgets/plots/multi_waveform/multi_waveform_controls.ui +++ /dev/null @@ -1,99 +0,0 @@ - - - Form - - - - 0 - 0 - 561 - 86 - - - - Form - - - - - - Curve Index - - - - - - - Qt::Orientation::Horizontal - - - - - - - - - - Highlight always last curve - - - - - - - Opacity - - - - - - - 100 - - - Qt::Orientation::Horizontal - - - - - - - Max Trace - - - - - - - How many curves should be displayed - - - 500 - - - 200 - - - - - - - If hiddne curves should be deleted. - - - Flush Buffer - - - - - - - 100 - - - - - - - - diff --git a/bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py b/bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py deleted file mode 100644 index 99cc52de..00000000 --- a/bec_widgets/widgets/plots/multi_waveform/multi_waveform_widget.py +++ /dev/null @@ -1,536 +0,0 @@ -import os -from typing import Literal - -import pyqtgraph as pg -from bec_lib.device import ReadoutPriority -from bec_lib.logger import bec_logger -from qtpy.QtCore import Slot -from qtpy.QtWidgets import QVBoxLayout, QWidget - -from bec_widgets.qt_utils.error_popups import SafeSlot -from bec_widgets.qt_utils.settings_dialog import SettingsDialog -from bec_widgets.qt_utils.toolbar import ( - DeviceSelectionAction, - MaterialIconAction, - ModularToolBar, - SeparatorAction, - WidgetAction, -) -from bec_widgets.utils import UILoader -from bec_widgets.utils.bec_widget import BECWidget -from bec_widgets.widgets.containers.figure import BECFigure -from bec_widgets.widgets.containers.figure.plots.axis_settings import AxisSettings -from bec_widgets.widgets.containers.figure.plots.multi_waveform.multi_waveform import ( - BECMultiWaveformConfig, -) -from bec_widgets.widgets.control.device_input.base_classes.device_input_base import BECDeviceFilter -from bec_widgets.widgets.control.device_input.device_combobox.device_combobox import DeviceComboBox -from bec_widgets.widgets.utility.visual.colormap_widget.colormap_widget import BECColorMapWidget - -logger = bec_logger.logger - - -class BECMultiWaveformWidget(BECWidget, QWidget): - PLUGIN = True - ICON_NAME = "ssid_chart" - USER_ACCESS = [ - "curves", - "set_monitor", - "set_curve_highlight", - "set_opacity", - "set_curve_limit", - "set_buffer_flush", - "set_highlight_last_curve", - "set_colormap", - "set", - "set_title", - "set_x_label", - "set_y_label", - "set_x_scale", - "set_y_scale", - "set_x_lim", - "set_y_lim", - "set_grid", - "set_colormap", - "enable_fps_monitor", - "lock_aspect_ratio", - "export", - ] - - def __init__( - self, - parent: QWidget | None = None, - config: BECMultiWaveformConfig | dict = None, - client=None, - gui_id: str | None = None, - **kwargs, - ) -> None: - if config is None: - config = BECMultiWaveformConfig(widget_class=self.__class__.__name__) - else: - if isinstance(config, dict): - config = BECMultiWaveformConfig(**config) - super().__init__(client=client, gui_id=gui_id, **kwargs) - QWidget.__init__(self, parent) - - self.layout = QVBoxLayout(self) - self.layout.setSpacing(0) - self.layout.setContentsMargins(0, 0, 0, 0) - - self.fig = BECFigure() - self.colormap_button = BECColorMapWidget(cmap="magma") - self.toolbar = ModularToolBar( - actions={ - "monitor": DeviceSelectionAction( - "", - DeviceComboBox( - device_filter=BECDeviceFilter.DEVICE, - readout_priority_filter=ReadoutPriority.ASYNC, - ), - ), - "connect": MaterialIconAction(icon_name="link", tooltip="Connect Device"), - "separator_0": SeparatorAction(), - "colormap": WidgetAction(widget=self.colormap_button), - "separator_1": SeparatorAction(), - "save": MaterialIconAction(icon_name="save", tooltip="Open Export Dialog"), - "matplotlib": MaterialIconAction( - icon_name="photo_library", tooltip="Open Matplotlib Plot" - ), - "separator_2": SeparatorAction(), - "drag_mode": MaterialIconAction( - icon_name="drag_pan", tooltip="Drag Mouse Mode", checkable=True - ), - "rectangle_mode": MaterialIconAction( - icon_name="frame_inspect", tooltip="Rectangle Zoom Mode", checkable=True - ), - "auto_range": MaterialIconAction( - icon_name="open_in_full", tooltip="Autorange Plot" - ), - "crosshair": MaterialIconAction( - icon_name="point_scan", tooltip="Show Crosshair", checkable=True - ), - "separator_3": SeparatorAction(), - "fps_monitor": MaterialIconAction( - icon_name="speed", tooltip="Show FPS Monitor", checkable=True - ), - "axis_settings": MaterialIconAction( - icon_name="settings", tooltip="Open Configuration Dialog" - ), - }, - target_widget=self, - ) - - self.layout.addWidget(self.toolbar) - self.layout.addWidget(self.fig) - - self.waveform = self.fig.multi_waveform() # FIXME config should be injected here - self.config = config - - self.create_multi_waveform_controls() - - self._hook_actions() - self.waveform.monitor_signal_updated.connect(self.update_controls_limits) - - def create_multi_waveform_controls(self): - """ - Create the controls for the multi waveform widget. - """ - current_path = os.path.dirname(__file__) - self.controls = UILoader(self).loader( - os.path.join(current_path, "multi_waveform_controls.ui") - ) - self.layout.addWidget(self.controls) - - # Hook default controls properties - self.controls.checkbox_highlight.setChecked(self.config.highlight_last_curve) - self.controls.spinbox_opacity.setValue(self.config.opacity) - self.controls.slider_opacity.setValue(self.config.opacity) - self.controls.spinbox_max_trace.setValue(self.config.curve_limit) - self.controls.checkbox_flush_buffer.setChecked(self.config.flush_buffer) - - # Connect signals - self.controls.spinbox_max_trace.valueChanged.connect(self.set_curve_limit) - self.controls.checkbox_flush_buffer.toggled.connect(self.set_buffer_flush) - self.controls.slider_opacity.valueChanged.connect(self.controls.spinbox_opacity.setValue) - self.controls.spinbox_opacity.valueChanged.connect(self.controls.slider_opacity.setValue) - self.controls.slider_opacity.valueChanged.connect(self.set_opacity) - self.controls.spinbox_opacity.valueChanged.connect(self.set_opacity) - self.controls.slider_index.valueChanged.connect(self.controls.spinbox_index.setValue) - self.controls.spinbox_index.valueChanged.connect(self.controls.slider_index.setValue) - self.controls.slider_index.valueChanged.connect(self.set_curve_highlight) - self.controls.spinbox_index.valueChanged.connect(self.set_curve_highlight) - self.controls.checkbox_highlight.toggled.connect(self.set_highlight_last_curve) - - # Trigger first round of settings - self.set_curve_limit(self.config.curve_limit) - self.set_opacity(self.config.opacity) - self.set_highlight_last_curve(self.config.highlight_last_curve) - - @Slot() - def update_controls_limits(self): - """ - Update the limits of the controls. - """ - num_curves = len(self.waveform.curves) - if num_curves == 0: - num_curves = 1 # Avoid setting max to 0 - current_index = num_curves - 1 - self.controls.slider_index.setMinimum(0) - self.controls.slider_index.setMaximum(self.waveform.number_of_visible_curves - 1) - self.controls.spinbox_index.setMaximum(self.waveform.number_of_visible_curves - 1) - if self.controls.checkbox_highlight.isChecked(): - self.controls.slider_index.setValue(current_index) - self.controls.spinbox_index.setValue(current_index) - - def _hook_actions(self): - self.toolbar.widgets["connect"].action.triggered.connect(self._connect_action) - # Separator 0 - self.toolbar.widgets["save"].action.triggered.connect(self.export) - self.toolbar.widgets["matplotlib"].action.triggered.connect(self.export_to_matplotlib) - self.toolbar.widgets["colormap"].widget.colormap_changed_signal.connect(self.set_colormap) - # Separator 1 - self.toolbar.widgets["drag_mode"].action.triggered.connect(self.enable_mouse_pan_mode) - self.toolbar.widgets["rectangle_mode"].action.triggered.connect( - self.enable_mouse_rectangle_mode - ) - self.toolbar.widgets["auto_range"].action.triggered.connect(self._auto_range_from_toolbar) - self.toolbar.widgets["crosshair"].action.triggered.connect(self.waveform.toggle_crosshair) - # Separator 2 - self.toolbar.widgets["fps_monitor"].action.triggered.connect(self.enable_fps_monitor) - self.toolbar.widgets["axis_settings"].action.triggered.connect(self.show_axis_settings) - - ################################### - # Dialog Windows - ################################### - @SafeSlot(popup_error=True) - def _connect_action(self): - monitor_combo = self.toolbar.widgets["monitor"].device_combobox - monitor_name = monitor_combo.currentText() - self.set_monitor(monitor=monitor_name) - monitor_combo.setStyleSheet("QComboBox { background-color: " "; }") - - def show_axis_settings(self): - dialog = SettingsDialog( - self, - settings_widget=AxisSettings(), - window_title="Axis Settings", - config=self.waveform._config_dict["axis"], - ) - dialog.exec() - - ######################################## - # User Access Methods from MultiWaveform - ######################################## - @property - def curves(self) -> list[pg.PlotDataItem]: - """ - Get the curves of the plot widget as a list - Returns: - list: List of curves. - """ - return list(self.waveform.curves) - - @curves.setter - def curves(self, value: list[pg.PlotDataItem]): - self.waveform.curves = value - - @SafeSlot(popup_error=True) - def set_monitor(self, monitor: str) -> None: - """ - Set the monitor of the plot widget. - - Args: - monitor(str): The monitor to set. - """ - self.waveform.set_monitor(monitor) - if self.toolbar.widgets["monitor"].device_combobox.currentText() != monitor: - self.toolbar.widgets["monitor"].device_combobox.setCurrentText(monitor) - self.toolbar.widgets["monitor"].device_combobox.setStyleSheet( - "QComboBox { background-color: " "; }" - ) - - @SafeSlot(int) - def set_curve_highlight(self, index: int) -> None: - """ - Set the curve highlight of the plot widget by index - - Args: - index(int): The index of the curve to highlight. - """ - if self.controls.checkbox_highlight.isChecked(): - # If always highlighting the last curve, set index to -1 - self.waveform.set_curve_highlight(-1) - else: - self.waveform.set_curve_highlight(index) - - @SafeSlot(int) - def set_opacity(self, opacity: int) -> None: - """ - Set the opacity of the plot widget. - - Args: - opacity(int): The opacity to set. - """ - self.waveform.set_opacity(opacity) - - @SafeSlot(int) - def set_curve_limit(self, curve_limit: int) -> None: - """ - Set the maximum number of traces to display on the plot widget. - - Args: - curve_limit(int): The maximum number of traces to display. - """ - flush_buffer = self.controls.checkbox_flush_buffer.isChecked() - self.waveform.set_curve_limit(curve_limit, flush_buffer) - self.update_controls_limits() - - @SafeSlot(bool) - def set_buffer_flush(self, flush_buffer: bool) -> None: - """ - Set the buffer flush property of the plot widget. - - Args: - flush_buffer(bool): True to flush the buffer, False to not flush the buffer. - """ - curve_limit = self.controls.spinbox_max_trace.value() - self.waveform.set_curve_limit(curve_limit, flush_buffer) - self.update_controls_limits() - - @SafeSlot(bool) - def set_highlight_last_curve(self, enable: bool) -> None: - """ - Enable or disable highlighting of the last curve. - - Args: - enable(bool): True to enable highlighting of the last curve, False to disable. - """ - self.waveform.config.highlight_last_curve = enable - if enable: - self.controls.slider_index.setEnabled(False) - self.controls.spinbox_index.setEnabled(False) - self.controls.checkbox_highlight.setChecked(True) - self.waveform.set_curve_highlight(-1) - else: - self.controls.slider_index.setEnabled(True) - self.controls.spinbox_index.setEnabled(True) - self.controls.checkbox_highlight.setChecked(False) - index = self.controls.spinbox_index.value() - self.waveform.set_curve_highlight(index) - - @SafeSlot() - def set_colormap(self, colormap: str) -> None: - """ - Set the colormap of the plot widget. - - Args: - colormap(str): The colormap to set. - """ - self.waveform.set_colormap(colormap) - - ################################### - # User Access Methods from PlotBase - ################################### - def set(self, **kwargs): - """ - Set the properties of the plot widget. - - Args: - **kwargs: Keyword arguments for the properties to be set. - - Possible properties: - - title: str - - x_label: str - - y_label: str - - x_scale: Literal["linear", "log"] - - y_scale: Literal["linear", "log"] - - x_lim: tuple - - y_lim: tuple - - legend_label_size: int - """ - self.waveform.set(**kwargs) - - def set_title(self, title: str): - """ - Set the title of the plot widget. - - Args: - title(str): The title to set. - """ - self.waveform.set_title(title) - - def set_x_label(self, x_label: str): - """ - Set the x-axis label of the plot widget. - - Args: - x_label(str): The x-axis label to set. - """ - self.waveform.set_x_label(x_label) - - def set_y_label(self, y_label: str): - """ - Set the y-axis label of the plot widget. - - Args: - y_label(str): The y-axis label to set. - """ - self.waveform.set_y_label(y_label) - - def set_x_scale(self, x_scale: Literal["linear", "log"]): - """ - Set the x-axis scale of the plot widget. - - Args: - x_scale(str): The x-axis scale to set. - """ - self.waveform.set_x_scale(x_scale) - - def set_y_scale(self, y_scale: Literal["linear", "log"]): - """ - Set the y-axis scale of the plot widget. - - Args: - y_scale(str): The y-axis scale to set. - """ - self.waveform.set_y_scale(y_scale) - - def set_x_lim(self, x_lim: tuple): - """ - Set x-axis limits of the plot widget. - - Args: - x_lim(tuple): The x-axis limits to set. - """ - self.waveform.set_x_lim(x_lim) - - def set_y_lim(self, y_lim: tuple): - """ - Set y-axis limits of the plot widget. - - Args: - y_lim(tuple): The y-axis limits to set. - """ - self.waveform.set_y_lim(y_lim) - - def set_legend_label_size(self, legend_label_size: int): - """ - Set the legend label size of the plot widget. - - Args: - legend_label_size(int): The legend label size to set. - """ - self.waveform.set_legend_label_size(legend_label_size) - - def set_auto_range(self, enabled: bool, axis: str = "xy"): - """ - Set the auto range of the plot widget. - - Args: - enabled(bool): True to enable auto range, False to disable. - axis(str): The axis to set the auto range for. Default is "xy". - """ - self.waveform.set_auto_range(enabled, axis) - - def enable_fps_monitor(self, enabled: bool): - """ - Enable or disable the FPS monitor - - Args: - enabled(bool): True to enable the FPS monitor, False to disable. - """ - self.waveform.enable_fps_monitor(enabled) - if self.toolbar.widgets["fps_monitor"].action.isChecked() != enabled: - self.toolbar.widgets["fps_monitor"].action.setChecked(enabled) - - @SafeSlot() - def _auto_range_from_toolbar(self): - """ - Set the auto range of the plot widget from the toolbar. - """ - self.waveform.set_auto_range(True, "xy") - - def set_grid(self, x_grid: bool, y_grid: bool): - """ - Set the grid of the plot widget. - - Args: - x_grid(bool): True to enable the x-grid, False to disable. - y_grid(bool): True to enable the y-grid, False to disable. - """ - self.waveform.set_grid(x_grid, y_grid) - - def set_outer_axes(self, show: bool): - """ - Set the outer axes of the plot widget. - - Args: - show(bool): True to show the outer axes, False to hide. - """ - self.waveform.set_outer_axes(show) - - def lock_aspect_ratio(self, lock: bool): - """ - Lock the aspect ratio of the plot widget. - - Args: - lock(bool): True to lock the aspect ratio, False to unlock. - """ - self.waveform.lock_aspect_ratio(lock) - - @SafeSlot() - def enable_mouse_rectangle_mode(self): - """ - Enable the mouse rectangle mode of the plot widget. - """ - self.toolbar.widgets["rectangle_mode"].action.setChecked(True) - self.toolbar.widgets["drag_mode"].action.setChecked(False) - self.waveform.plot_item.getViewBox().setMouseMode(pg.ViewBox.RectMode) - - @SafeSlot() - def enable_mouse_pan_mode(self): - """ - Enable the mouse pan mode of the plot widget. - """ - self.toolbar.widgets["drag_mode"].action.setChecked(True) - self.toolbar.widgets["rectangle_mode"].action.setChecked(False) - self.waveform.plot_item.getViewBox().setMouseMode(pg.ViewBox.PanMode) - - def export(self): - """ - Export the plot widget. - """ - self.waveform.export() - - def export_to_matplotlib(self): - """ - Export the plot widget to matplotlib. - """ - try: - import matplotlib as mpl - except ImportError: - self.warning_util.show_warning( - title="Matplotlib not installed", - message="Matplotlib is required for this feature.", - detailed_text="Please install matplotlib in your Python environment by using 'pip install matplotlib'.", - ) - return - self.waveform.export_to_matplotlib() - - ####################################### - # User Access Methods from BECConnector - ###################################### - def cleanup(self): - self.fig.cleanup() - return super().cleanup() - - -if __name__ == "__main__": # pragma: no cover - import sys - - from qtpy.QtWidgets import QApplication - - app = QApplication(sys.argv) - widget = BECMultiWaveformWidget() - widget.show() - sys.exit(app.exec()) diff --git a/bec_widgets/widgets/plots/multi_waveform/register_bec_multi_waveform_widget.py b/bec_widgets/widgets/plots/multi_waveform/register_bec_multi_waveform_widget.py deleted file mode 100644 index 70aa03f8..00000000 --- a/bec_widgets/widgets/plots/multi_waveform/register_bec_multi_waveform_widget.py +++ /dev/null @@ -1,17 +0,0 @@ -def main(): # pragma: no cover - from qtpy import PYSIDE6 - - if not PYSIDE6: - print("PYSIDE6 is not available in the environment. Cannot patch designer.") - return - from PySide6.QtDesigner import QPyDesignerCustomWidgetCollection - - from bec_widgets.widgets.plots.multi_waveform.bec_multi_waveform_widget_plugin import ( - BECMultiWaveformWidgetPlugin, - ) - - QPyDesignerCustomWidgetCollection.addCustomWidget(BECMultiWaveformWidgetPlugin()) - - -if __name__ == "__main__": # pragma: no cover - main() diff --git a/tests/unit_tests/test_bec_dock.py b/tests/unit_tests/test_bec_dock.py index c175db79..9ecfa00c 100644 --- a/tests/unit_tests/test_bec_dock.py +++ b/tests/unit_tests/test_bec_dock.py @@ -141,11 +141,8 @@ def test_toolbar_add_plot_motor_map(bec_dock_area): def test_toolbar_add_multi_waveform(bec_dock_area): bec_dock_area.toolbar.widgets["menu_plots"].widgets["multi_waveform"].trigger() - assert "BECMultiWaveformWidget_0" in bec_dock_area.panels - assert ( - bec_dock_area.panels["BECMultiWaveformWidget_0"].widgets[0].config.widget_class - == "BECMultiWaveformWidget" - ) + assert "MultiWaveform_0" in bec_dock_area.panels + assert bec_dock_area.panels["MultiWaveform_0"].widgets[0].config.widget_class == "MultiWaveform" def test_toolbar_add_device_positioner_box(bec_dock_area): diff --git a/tests/unit_tests/test_multi_waveform_widget.py b/tests/unit_tests/test_multi_waveform_widget.py deleted file mode 100644 index 5a331f87..00000000 --- a/tests/unit_tests/test_multi_waveform_widget.py +++ /dev/null @@ -1,296 +0,0 @@ -from unittest.mock import MagicMock, patch - -import pytest -from qtpy.QtGui import QColor -from qtpy.QtWidgets import QApplication - -from bec_widgets.qt_utils.settings_dialog import SettingsDialog -from bec_widgets.utils.colors import apply_theme, get_theme_palette, set_theme -from bec_widgets.widgets.containers.figure.plots.axis_settings import AxisSettings -from bec_widgets.widgets.plots.multi_waveform.multi_waveform_widget import BECMultiWaveformWidget - -from .client_mocks import mocked_client - - -@pytest.fixture -def multi_waveform_widget(qtbot, mocked_client): - widget = BECMultiWaveformWidget(client=mocked_client()) - qtbot.addWidget(widget) - qtbot.waitExposed(widget) - return widget - - -@pytest.fixture -def mock_waveform(multi_waveform_widget): - waveform_mock = MagicMock() - multi_waveform_widget.waveform = waveform_mock - return waveform_mock - - -def test_multi_waveform_widget_init(multi_waveform_widget): - assert multi_waveform_widget is not None - assert multi_waveform_widget.client is not None - assert isinstance(multi_waveform_widget, BECMultiWaveformWidget) - assert multi_waveform_widget.config.widget_class == "BECMultiWaveformWidget" - - -################################### -# Wrapper methods for Waveform -################################### - - -def test_multi_waveform_widget_set_monitor(multi_waveform_widget, mock_waveform): - multi_waveform_widget.set_monitor("waveform1d") - mock_waveform.set_monitor.assert_called_once_with("waveform1d") - - -def test_multi_waveform_widget_set_curve_highlight_last_active( - multi_waveform_widget, mock_waveform -): - multi_waveform_widget.set_curve_highlight(1) - mock_waveform.set_curve_highlight.assert_called_once_with(-1) - - -def test_multi_waveform_widget_set_curve_highlight_last_not_active( - multi_waveform_widget, mock_waveform -): - multi_waveform_widget.set_highlight_last_curve(False) - multi_waveform_widget.set_curve_highlight(1) - mock_waveform.set_curve_highlight.assert_called_with(1) - - -def test_multi_waveform_widget_set_opacity(multi_waveform_widget, mock_waveform): - multi_waveform_widget.set_opacity(50) - mock_waveform.set_opacity.assert_called_once_with(50) - - -def test_multi_waveform_widget_set_curve_limit(multi_waveform_widget, mock_waveform): - multi_waveform_widget.set_curve_limit(10) - mock_waveform.set_curve_limit.assert_called_once_with( - 10, multi_waveform_widget.controls.checkbox_flush_buffer.isChecked() - ) - - -def test_multi_waveform_widget_set_buffer_flush(multi_waveform_widget, mock_waveform): - multi_waveform_widget.set_buffer_flush(True) - mock_waveform.set_curve_limit.assert_called_once_with( - multi_waveform_widget.controls.spinbox_max_trace.value(), True - ) - - -def test_multi_waveform_widget_set_highlight_last_curve(multi_waveform_widget, mock_waveform): - multi_waveform_widget.set_highlight_last_curve(True) - assert multi_waveform_widget.waveform.config.highlight_last_curve is True - assert not multi_waveform_widget.controls.slider_index.isEnabled() - assert not multi_waveform_widget.controls.spinbox_index.isEnabled() - mock_waveform.set_curve_highlight.assert_called_once_with(-1) - - -def test_multi_waveform_widget_set_colormap(multi_waveform_widget, mock_waveform): - multi_waveform_widget.set_colormap("viridis") - mock_waveform.set_colormap.assert_called_once_with("viridis") - - -def test_multi_waveform_widget_set_base(multi_waveform_widget, mock_waveform): - multi_waveform_widget.set( - title="Test Title", - x_label="X Label", - y_label="Y Label", - x_scale="linear", - y_scale="log", - x_lim=(0, 10), - y_lim=(0, 10), - ) - mock_waveform.set.assert_called_once_with( - title="Test Title", - x_label="X Label", - y_label="Y Label", - x_scale="linear", - y_scale="log", - x_lim=(0, 10), - y_lim=(0, 10), - ) - - -################################### -# Toolbar interactions -################################### - - -def test_toolbar_connect_action_triggered(multi_waveform_widget, qtbot): - action_connect = multi_waveform_widget.toolbar.widgets["connect"].action - device_combobox = multi_waveform_widget.toolbar.widgets["monitor"].device_combobox - device_combobox.addItem("test_monitor") - device_combobox.setCurrentText("test_monitor") - - with patch.object(multi_waveform_widget, "set_monitor") as mock_set_monitor: - action_connect.trigger() - mock_set_monitor.assert_called_once_with(monitor="test_monitor") - - -def test_toolbar_drag_mode_action_triggered(multi_waveform_widget, qtbot): - action_drag = multi_waveform_widget.toolbar.widgets["drag_mode"].action - action_rectangle = multi_waveform_widget.toolbar.widgets["rectangle_mode"].action - action_drag.trigger() - assert action_drag.isChecked() == True - assert action_rectangle.isChecked() == False - - -def test_toolbar_rectangle_mode_action_triggered(multi_waveform_widget, qtbot): - action_drag = multi_waveform_widget.toolbar.widgets["drag_mode"].action - action_rectangle = multi_waveform_widget.toolbar.widgets["rectangle_mode"].action - action_rectangle.trigger() - assert action_drag.isChecked() == False - assert action_rectangle.isChecked() == True - - -def test_toolbar_auto_range_action_triggered(multi_waveform_widget, mock_waveform, qtbot): - action = multi_waveform_widget.toolbar.widgets["auto_range"].action - action.trigger() - qtbot.wait(200) - mock_waveform.set_auto_range.assert_called_once_with(True, "xy") - - -################################### -# Control Panel interactions -################################### - - -def test_controls_opacity_slider(multi_waveform_widget, mock_waveform): - multi_waveform_widget.controls.slider_opacity.setValue(75) - mock_waveform.set_opacity.assert_called_with(75) - assert multi_waveform_widget.controls.spinbox_opacity.value() == 75 - - -def test_controls_opacity_spinbox(multi_waveform_widget, mock_waveform): - multi_waveform_widget.controls.spinbox_opacity.setValue(25) - mock_waveform.set_opacity.assert_called_with(25) - assert multi_waveform_widget.controls.slider_opacity.value() == 25 - - -def test_controls_max_trace_spinbox(multi_waveform_widget, mock_waveform): - multi_waveform_widget.controls.spinbox_max_trace.setValue(15) - mock_waveform.set_curve_limit.assert_called_with( - 15, multi_waveform_widget.controls.checkbox_flush_buffer.isChecked() - ) - - -def test_controls_flush_buffer_checkbox(multi_waveform_widget, mock_waveform): - multi_waveform_widget.controls.checkbox_flush_buffer.setChecked(True) - mock_waveform.set_curve_limit.assert_called_with( - multi_waveform_widget.controls.spinbox_max_trace.value(), True - ) - - -def test_controls_highlight_checkbox(multi_waveform_widget, mock_waveform): - multi_waveform_widget.controls.checkbox_highlight.setChecked(False) - assert multi_waveform_widget.waveform.config.highlight_last_curve is False - assert multi_waveform_widget.controls.slider_index.isEnabled() - assert multi_waveform_widget.controls.spinbox_index.isEnabled() - index = multi_waveform_widget.controls.spinbox_index.value() - mock_waveform.set_curve_highlight.assert_called_with(index) - - -################################### -# Axis Settings Dialog Tests -################################### - - -def show_axis_dialog(qtbot, multi_waveform_widget): - axis_dialog = SettingsDialog( - multi_waveform_widget, - settings_widget=AxisSettings(), - window_title="Axis Settings", - config=multi_waveform_widget.waveform._config_dict["axis"], - ) - qtbot.addWidget(axis_dialog) - qtbot.waitExposed(axis_dialog) - return axis_dialog - - -def test_axis_dialog_with_axis_limits(qtbot, multi_waveform_widget): - multi_waveform_widget.set( - title="Test Title", - x_label="X Label", - y_label="Y Label", - x_scale="linear", - y_scale="log", - x_lim=(0, 10), - y_lim=(0, 10), - ) - - axis_dialog = show_axis_dialog(qtbot, multi_waveform_widget) - - assert axis_dialog is not None - assert axis_dialog.widget.ui.plot_title.text() == "Test Title" - assert axis_dialog.widget.ui.x_label.text() == "X Label" - assert axis_dialog.widget.ui.y_label.text() == "Y Label" - assert axis_dialog.widget.ui.x_scale.currentText() == "linear" - assert axis_dialog.widget.ui.y_scale.currentText() == "log" - assert axis_dialog.widget.ui.x_min.value() == 0 - assert axis_dialog.widget.ui.x_max.value() == 10 - assert axis_dialog.widget.ui.y_min.value() == 0 - assert axis_dialog.widget.ui.y_max.value() == 10 - - -def test_axis_dialog_set_properties(qtbot, multi_waveform_widget): - axis_dialog = show_axis_dialog(qtbot, multi_waveform_widget) - - axis_dialog.widget.ui.plot_title.setText("New Title") - axis_dialog.widget.ui.x_label.setText("New X Label") - axis_dialog.widget.ui.y_label.setText("New Y Label") - axis_dialog.widget.ui.x_scale.setCurrentText("log") - axis_dialog.widget.ui.y_scale.setCurrentText("linear") - axis_dialog.widget.ui.x_min.setValue(5) - axis_dialog.widget.ui.x_max.setValue(15) - axis_dialog.widget.ui.y_min.setValue(5) - axis_dialog.widget.ui.y_max.setValue(15) - - axis_dialog.accept() - - assert multi_waveform_widget.waveform.config.axis.title == "New Title" - assert multi_waveform_widget.waveform.config.axis.x_label == "New X Label" - assert multi_waveform_widget.waveform.config.axis.y_label == "New Y Label" - assert multi_waveform_widget.waveform.config.axis.x_scale == "log" - assert multi_waveform_widget.waveform.config.axis.y_scale == "linear" - assert multi_waveform_widget.waveform.config.axis.x_lim == (5, 15) - assert multi_waveform_widget.waveform.config.axis.y_lim == (5, 15) - - -################################### -# Theme Update Test -################################### - - -def test_multi_waveform_widget_theme_update(qtbot, multi_waveform_widget): - """Test theme update for multi waveform widget.""" - qapp = QApplication.instance() - - # Set the theme to dark - set_theme("dark") - palette = get_theme_palette() - waveform_color_dark = multi_waveform_widget.waveform.plot_item.getAxis("left").pen().color() - bg_color = multi_waveform_widget.fig.backgroundBrush().color() - - assert bg_color == QColor(20, 20, 20) - assert waveform_color_dark == palette.text().color() - - # Set the theme to light - set_theme("light") - palette = get_theme_palette() - waveform_color_light = multi_waveform_widget.waveform.plot_item.getAxis("left").pen().color() - bg_color = multi_waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor(233, 236, 239) - assert waveform_color_light == palette.text().color() - - assert waveform_color_dark != waveform_color_light - - # Set the theme to auto and simulate OS theme change - set_theme("auto") - qapp.theme_signal.theme_updated.emit("dark") - apply_theme("dark") - - waveform_color = multi_waveform_widget.waveform.plot_item.getAxis("left").pen().color() - bg_color = multi_waveform_widget.fig.backgroundBrush().color() - assert bg_color == QColor(20, 20, 20) - assert waveform_color == waveform_color_dark