From c926a75a7927d672c044ea8f68771209ae5accc6 Mon Sep 17 00:00:00 2001 From: wyzula-jan Date: Thu, 4 Jul 2024 18:39:31 +0200 Subject: [PATCH] feat(curve_dialog): curves can be added --- .../widgets/figure/plots/axis_settings.py | 47 +-- bec_widgets/widgets/figure/plots/plot_base.py | 3 - .../widgets/waveform/assets/line_axis.svg | 10 + .../__init__.py | 0 .../curve_dialog/__init__.py} | 0 .../curve_dialog/curve_dialog.py | 155 ++++++++++ .../curve_dialog/curve_dialog.ui | 279 ++++++++++++++++++ .../waveform_toolbar/curve_dialog/remove.svg | 5 + .../waveform_toolbar.py | 12 +- .../widgets/waveform/waveform_widget.py | 58 +++- 10 files changed, 526 insertions(+), 43 deletions(-) create mode 100644 bec_widgets/widgets/waveform/assets/line_axis.svg rename bec_widgets/widgets/waveform/{waveform_dialog => waveform_toolbar}/__init__.py (100%) rename bec_widgets/widgets/waveform/{waveform_dialog/curve_dialog.py => waveform_toolbar/curve_dialog/__init__.py} (100%) create mode 100644 bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.py create mode 100644 bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.ui create mode 100644 bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/remove.svg rename bec_widgets/widgets/waveform/{waveform_dialog => waveform_toolbar}/waveform_toolbar.py (74%) diff --git a/bec_widgets/widgets/figure/plots/axis_settings.py b/bec_widgets/widgets/figure/plots/axis_settings.py index 511a2a90..10572883 100644 --- a/bec_widgets/widgets/figure/plots/axis_settings.py +++ b/bec_widgets/widgets/figure/plots/axis_settings.py @@ -1,21 +1,19 @@ import os -from PySide6.QtWidgets import QDialog, QDialogButtonBox from qtpy.QtCore import Slot -from qtpy.QtWidgets import QVBoxLayout, QWidget +from qtpy.QtWidgets import QVBoxLayout +from bec_widgets.qt_utils.settings_dialog import SettingWidget from bec_widgets.utils import UILoader -from bec_widgets.utils.colors import apply_theme from bec_widgets.utils.widget_io import WidgetIO -class AxisSettings(QWidget): - def __init__(self, parent=None, target_widget: QWidget = None, *args, **kwargs): +class AxisSettings(SettingWidget): + def __init__(self, parent=None, *args, **kwargs): super().__init__(parent=parent, *args, **kwargs) current_path = os.path.dirname(__file__) self.ui = UILoader().load_ui(os.path.join(current_path, "axis_settings.ui"), self) - self.target_widget = target_widget self.layout = QVBoxLayout(self) self.layout.addWidget(self.ui) @@ -25,12 +23,10 @@ class AxisSettings(QWidget): self.setMaximumHeight(280) self.resize(380, 280) - self.display_current_settings(self.target_widget._config_dict.get("axis", {})) - @Slot(dict) def display_current_settings(self, axis_config: dict): - if dict == {}: + if axis_config == {}: return # Top Box @@ -45,6 +41,10 @@ class AxisSettings(QWidget): WidgetIO.check_and_adjust_limits(self.ui.x_max, axis_config["x_lim"][1]) WidgetIO.set_value(self.ui.x_min, axis_config["x_lim"][0]) WidgetIO.set_value(self.ui.x_max, axis_config["x_lim"][1]) + if axis_config["x_lim"] is None: + x_range = self.target_widget.fig.widget_list[0].plot_item.viewRange()[0] + WidgetIO.set_value(self.ui.x_min, x_range[0]) + WidgetIO.set_value(self.ui.x_max, x_range[1]) # Y Axis Box WidgetIO.set_value(self.ui.y_label, axis_config["y_label"]) @@ -55,6 +55,10 @@ class AxisSettings(QWidget): WidgetIO.check_and_adjust_limits(self.ui.y_max, axis_config["y_lim"][1]) WidgetIO.set_value(self.ui.y_min, axis_config["y_lim"][0]) WidgetIO.set_value(self.ui.y_max, axis_config["y_lim"][1]) + if axis_config["y_lim"] is None: + y_range = self.target_widget.fig.widget_list[0].plot_item.viewRange()[1] + WidgetIO.set_value(self.ui.y_min, y_range[0]) + WidgetIO.set_value(self.ui.y_max, y_range[1]) @Slot() def accept_changes(self): @@ -82,28 +86,3 @@ class AxisSettings(QWidget): y_lim=y_lim, ) self.target_widget.set_grid(x_grid, y_grid) - - -class AxisSettingsDialog(QDialog): - def __init__(self, parent=None, target_widget: QWidget = None, *args, **kwargs): - super().__init__(parent, *args, **kwargs) - - self.setModal(False) - - self.setWindowTitle("Axis Settings") - self.target_widget = target_widget - self.widget = AxisSettings(target_widget=self.target_widget) - # self.widget.display_current_settings(self.target_widget._config_dict) - self.button_box = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel) - - self.button_box.accepted.connect(self.accept) - self.button_box.rejected.connect(self.reject) - - self.layout = QVBoxLayout(self) - self.layout.addWidget(self.widget) - self.layout.addWidget(self.button_box) - - @Slot() - def accept(self): - self.widget.accept_changes() - super().accept() diff --git a/bec_widgets/widgets/figure/plots/plot_base.py b/bec_widgets/widgets/figure/plots/plot_base.py index 58430603..3260712c 100644 --- a/bec_widgets/widgets/figure/plots/plot_base.py +++ b/bec_widgets/widgets/figure/plots/plot_base.py @@ -2,11 +2,8 @@ from __future__ import annotations from typing import Literal, Optional -import numpy as np import pyqtgraph as pg from pydantic import BaseModel, Field -from qtpy import QT_VERSION -from qtpy.QtGui import QFont, QFontDatabase, QFontInfo from qtpy.QtWidgets import QWidget from bec_widgets.utils import BECConnector, ConnectionConfig diff --git a/bec_widgets/widgets/waveform/assets/line_axis.svg b/bec_widgets/widgets/waveform/assets/line_axis.svg new file mode 100644 index 00000000..e0e713e0 --- /dev/null +++ b/bec_widgets/widgets/waveform/assets/line_axis.svg @@ -0,0 +1,10 @@ + + + + + + + + \ No newline at end of file diff --git a/bec_widgets/widgets/waveform/waveform_dialog/__init__.py b/bec_widgets/widgets/waveform/waveform_toolbar/__init__.py similarity index 100% rename from bec_widgets/widgets/waveform/waveform_dialog/__init__.py rename to bec_widgets/widgets/waveform/waveform_toolbar/__init__.py diff --git a/bec_widgets/widgets/waveform/waveform_dialog/curve_dialog.py b/bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/__init__.py similarity index 100% rename from bec_widgets/widgets/waveform/waveform_dialog/curve_dialog.py rename to bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/__init__.py diff --git a/bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.py b/bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.py new file mode 100644 index 00000000..d8b98a51 --- /dev/null +++ b/bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.py @@ -0,0 +1,155 @@ +from __future__ import annotations + +import os + +from PySide6.QtCore import QObject +from PySide6.QtGui import QIcon +from PySide6.QtWidgets import QComboBox, QLineEdit, QPushButton, QSpinBox, QTableWidget +from pydantic import BaseModel +from qtpy.QtCore import Slot +from qtpy.QtWidgets import QVBoxLayout + +from bec_widgets.qt_utils.settings_dialog import SettingWidget +from bec_widgets.utils import UILoader +from bec_widgets.widgets.color_button.color_button import ColorButton +from bec_widgets.widgets.device_line_edit.device_line_edit import DeviceLineEdit +from bec_widgets.widgets.figure.plots.plot_base import AxisConfig + +from bec_widgets.widgets.figure.plots.waveform.waveform_curve import CurveConfig + + +class CurveSettings(SettingWidget): + def __init__(self, parent=None, *args, **kwargs): + super().__init__(parent, *args, **kwargs) + current_path = os.path.dirname(__file__) + + self.ui = UILoader(self).loader(os.path.join(current_path, "curve_dialog.ui")) + + self.layout = QVBoxLayout(self) + self.layout.addWidget(self.ui) + + self.ui.add_curve.clicked.connect(self.add_curve) + + @Slot(dict) + def display_current_settings(self, config: dict | BaseModel): + curves = config["scan_segment"] + first_label, first_curve = next(iter(curves.items())) + self.ui.x_name.setText(first_curve.config.signals.x.name) + self.ui.x_entry.setText(first_curve.config.signals.x.entry) + for label, curve in curves.items(): + row_count = self.ui.scan_table.rowCount() + self.ui.scan_table.insertRow(row_count) + ScanRow(table_widget=self.ui.scan_table, row=row_count, config=curve.config) + + @Slot() + def accept_changes(self): + self.accept_scan_curve_changes() + + def accept_scan_curve_changes(self): + old_curves = list(self.target_widget.waveform._curves_data["scan_segment"].values()) + for curve in old_curves: + curve.remove() + self.get_curve_params() + + def get_curve_params(self): + x_name = self.ui.x_name.text() + x_entry = self.ui.x_entry.text() + for row in range(self.ui.scan_table.rowCount()): + y_name = self.ui.scan_table.cellWidget(row, 0).text() + y_entry = self.ui.scan_table.cellWidget(row, 1).text() + color = self.ui.scan_table.cellWidget(row, 2).get_color() + style = self.ui.scan_table.cellWidget(row, 3).currentText() + width = self.ui.scan_table.cellWidget(row, 4).value() + symbol_size = self.ui.scan_table.cellWidget(row, 5).value() + self.target_widget.plot( + x_name=x_name, + x_entry=x_entry, + y_name=y_name, + y_entry=y_entry, + color=color, + pen_style=style, + pen_width=width, + symbol_size=symbol_size, + ) + self.target_widget.scan_history(-1) + + def add_curve(self): + row_count = self.ui.scan_table.rowCount() + self.ui.scan_table.insertRow(row_count) + ScanRow(table_widget=self.ui.scan_table, row=row_count, config=None) + + +class ScanRow(QObject): + def __init__( + self, + parent=None, + table_widget: QTableWidget = None, + row=None, + config: dict | CurveConfig = None, + ): + super().__init__(parent=parent) + + current_path = os.path.dirname(__file__) + # Remove Button + icon_path = os.path.join(current_path, "remove.svg") + self.remove_button = QPushButton() + self.remove_button.setIcon(QIcon(icon_path)) + + # Name and Entry + self.device_line_edit = DeviceLineEdit() + self.entry_line_edit = QLineEdit() + + # Styling + self.color_button = ColorButton() + self.style_combo = StyleComboBox() + self.width = QSpinBox() + self.width.setMinimum(1) + self.width.setMaximum(20) + + self.symbol_size = QSpinBox() + self.symbol_size.setMinimum(1) + self.symbol_size.setMaximum(20) + + self.table_widget = table_widget + self.row = row + + self.remove_button.clicked.connect( + lambda: self.remove_row() + ) # From some reason do not work without lambda + + if config is not None: + self.fill_row_from_config(config) + + self.add_row_to_table() + + def fill_row_from_config(self, config): + self.device_line_edit.setText(config.signals.y.name) + self.entry_line_edit.setText(config.signals.y.entry) + self.color_button.setColor(config.color) + self.style_combo.setCurrentText(config.pen_style) + self.width.setValue(config.pen_width) + self.symbol_size.setValue(config.symbol_size) + + def add_row_to_table(self): + self.table_widget.setCellWidget(self.row, 0, self.device_line_edit) + self.table_widget.setCellWidget(self.row, 1, self.entry_line_edit) + self.table_widget.setCellWidget(self.row, 2, self.color_button) + self.table_widget.setCellWidget(self.row, 3, self.style_combo) + self.table_widget.setCellWidget(self.row, 4, self.width) + self.table_widget.setCellWidget(self.row, 5, self.symbol_size) + self.table_widget.setCellWidget(self.row, 6, self.remove_button) + + @Slot() + def remove_row(self): + row = self.table_widget.indexAt(self.remove_button.pos()).row() + self.cleanup() + self.table_widget.removeRow(row) + + def cleanup(self): + self.device_line_edit.cleanup() + + +class StyleComboBox(QComboBox): + def __init__(self, parent=None): + super().__init__(parent) + self.addItems(["solid", "dash", "dot", "dashdot"]) diff --git a/bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.ui b/bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.ui new file mode 100644 index 00000000..32df4fa1 --- /dev/null +++ b/bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/curve_dialog.ui @@ -0,0 +1,279 @@ + + + Form + + + + 0 + 0 + 720 + 806 + + + + Form + + + + 2 + + + 2 + + + 2 + + + 2 + + + + + X Axis + + + + + + Name + + + + + + + + + + Qt::Orientation::Vertical + + + + + + + Entry + + + + + + + + + + + + + Y Axis + + + + + + 0 + + + + Scan + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Add Curve + + + + + + + Qt::Orientation::Horizontal + + + + 40 + 20 + + + + + + + + 0 + + + false + + + true + + + true + + + false + + + + Name + + + + + Entry + + + + + Color + + + + + Style + + + + + Width + + + + + Symbol Size + + + + + Delete + + + + + + + + + DAP + + + + 5 + + + 5 + + + 5 + + + 5 + + + + + Add DAP + + + + + + + Qt::Orientation::Horizontal + + + + 585 + 20 + + + + + + + + true + + + true + + + + Name + + + + + Entry + + + + + Model + + + + + Color + + + + + Style + + + + + Width + + + + + Symbol Size + + + + + Delete + + + + + + + + + Custom + + + + + + + + + + + + DeviceLineEdit + QLineEdit +
device_line_edit
+
+
+ + +
diff --git a/bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/remove.svg b/bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/remove.svg new file mode 100644 index 00000000..5bf6de39 --- /dev/null +++ b/bec_widgets/widgets/waveform/waveform_toolbar/curve_dialog/remove.svg @@ -0,0 +1,5 @@ + + + + \ No newline at end of file diff --git a/bec_widgets/widgets/waveform/waveform_dialog/waveform_toolbar.py b/bec_widgets/widgets/waveform/waveform_toolbar/waveform_toolbar.py similarity index 74% rename from bec_widgets/widgets/waveform/waveform_dialog/waveform_toolbar.py rename to bec_widgets/widgets/waveform/waveform_toolbar/waveform_toolbar.py index d39f6be4..b1bf3419 100644 --- a/bec_widgets/widgets/waveform/waveform_dialog/waveform_toolbar.py +++ b/bec_widgets/widgets/waveform/waveform_toolbar/waveform_toolbar.py @@ -3,7 +3,17 @@ import os from qtpy.QtCore import QSize from qtpy.QtGui import QAction, QIcon -from bec_widgets.widgets.toolbar.toolbar import ToolBarAction +from bec_widgets.qt_utils.toolbar import ToolBarAction + + +class CurveAction(ToolBarAction): + def add_to_toolbar(self, toolbar, target): + current_path = os.path.dirname(__file__) + parent_path = os.path.dirname(current_path) + icon = QIcon() + icon.addFile(os.path.join(parent_path, "assets", "line_axis.svg"), size=QSize(20, 20)) + self.action = QAction(icon, "Open Curves Configuration", target) + toolbar.addAction(self.action) class SettingsAction(ToolBarAction): diff --git a/bec_widgets/widgets/waveform/waveform_widget.py b/bec_widgets/widgets/waveform/waveform_widget.py index 0574f44c..f59f639d 100644 --- a/bec_widgets/widgets/waveform/waveform_widget.py +++ b/bec_widgets/widgets/waveform/waveform_widget.py @@ -7,13 +7,15 @@ import numpy as np from qtpy import PYSIDE6 from qtpy.QtWidgets import QVBoxLayout, QWidget +from bec_widgets.qt_utils.settings_dialog import SettingsDialog +from bec_widgets.qt_utils.toolbar import ModularToolBar from bec_widgets.utils import BECConnector from bec_widgets.widgets.figure import BECFigure -from bec_widgets.widgets.figure.plots.axis_settings import AxisSettingsDialog +from bec_widgets.widgets.figure.plots.axis_settings import AxisSettings from bec_widgets.widgets.figure.plots.waveform.waveform import Waveform1DConfig from bec_widgets.widgets.figure.plots.waveform.waveform_curve import BECCurve -from bec_widgets.widgets.toolbar import ModularToolBar -from bec_widgets.widgets.waveform.waveform_dialog.waveform_toolbar import * +from bec_widgets.widgets.waveform.waveform_toolbar.curve_dialog.curve_dialog import CurveSettings +from bec_widgets.widgets.waveform.waveform_toolbar.waveform_toolbar import * try: import pandas as pd @@ -70,7 +72,7 @@ class BECWaveformWidget(BECConnector, QWidget): self.toolbar = ModularToolBar( actions={ # "connect": ConnectAction(), - # "history": ResetHistoryAction(), + "curves": CurveAction(), "axis_settings": SettingsAction(), "import": ImportAction(), "export": ExportAction(), @@ -88,7 +90,13 @@ class BECWaveformWidget(BECConnector, QWidget): self._hook_actions() + # TEst actions + self.plot(x_name="samx", y_name="bpm4i") + self.plot(x_name="samx", y_name="bpm3a") + self.plot(x_name="samx", y_name="bpm6i") + def _hook_actions(self): + self.toolbar.widgets["curves"].action.triggered.connect(self.show_curve_settings) self.toolbar.widgets["axis_settings"].action.triggered.connect(self.show_axis_settings) self.toolbar.widgets["import"].action.triggered.connect( lambda: self.load_config(path=None, gui=True) @@ -98,9 +106,45 @@ class BECWaveformWidget(BECConnector, QWidget): ) def show_axis_settings(self): - dialog = AxisSettingsDialog(self, target_widget=self) + dialog = SettingsDialog( + self, + settings_widget=AxisSettings(), + window_title="Motor Map Settings", + config=self._config_dict["axis"], + ) dialog.exec() + def show_curve_settings(self): + dialog = SettingsDialog( + self, + settings_widget=CurveSettings(), + window_title="Curve Settings", + config=self.waveform._curves_data, + ) + dialog.resize(800, 600) + dialog.exec() + + def _check_if_scans_have_same_x(self, enabled=True, x_name_to_check: str = None) -> bool: + """ + Check if all scans have the same x-axis. + + Args: + enabled(bool): If True, check if all scans have the same x-axis. + x_name_to_check(str): The x-axis name to check. + + Returns: + bool: True if all scans have the same x-axis, False otherwise. + """ + if enabled and x_name_to_check is not None: + curves = self.waveform._curves_data["scan_segment"] + + for label, curve in curves.items(): + x_name = curve.config.signals.x.name + if x_name != x_name_to_check: + raise ValueError( + f"All scans must have the same x-axis. New curve provided with x-axis: {x_name}" + ) + ################################### # User Access Methods from Waveform ################################### @@ -144,6 +188,7 @@ class BECWaveformWidget(BECConnector, QWidget): label: str | None = None, validate: bool = True, dap: str | None = None, # TODO add dap custom curve wrapper + **kwargs, ) -> BECCurve: """ Plot a curve to the plot widget. @@ -165,6 +210,7 @@ class BECWaveformWidget(BECConnector, QWidget): Returns: BECCurve: The curve object. """ + self._check_if_scans_have_same_x(enabled=True, x_name_to_check=x_name) return self.waveform.plot( x=x, y=y, @@ -179,6 +225,7 @@ class BECWaveformWidget(BECConnector, QWidget): label=label, validate=validate, dap=dap, + **kwargs, ) def add_dap( @@ -410,6 +457,7 @@ class BECWaveformWidget(BECConnector, QWidget): def cleanup(self): self.fig.cleanup() + self.client.shutdown() return super().cleanup() def closeEvent(self, event):