0
0
mirror of https://github.com/bec-project/bec_widgets.git synced 2025-07-14 11:41:49 +02:00

feat(curve_dialog): curves can be added

This commit is contained in:
2024-07-04 18:39:31 +02:00
parent fa9b17191d
commit c926a75a79
10 changed files with 526 additions and 43 deletions

View File

@ -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()

View File

@ -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

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 20 20" height="48px" viewBox="0 0 20 20" width="48px"
fill="#FFFFFF">
<g>
<rect fill="none" height="20" width="20" x="0"/>
</g>
<g>
<polygon
points="14.68,10.61 18,6.88 16.88,5.89 13.62,9.56 8,4 2,10.01 3.06,11.07 8,6.12 12.62,10.68 11.27,12.2 8,8.93 2,14.94 3.06,16 8,11.05 11.33,14.38 13.69,11.73 17,15.01 18.06,13.95"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 454 B

View File

@ -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"])

View File

@ -0,0 +1,279 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Form</class>
<widget class="QWidget" name="Form">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>720</width>
<height>806</height>
</rect>
</property>
<property name="windowTitle">
<string>Form</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<property name="leftMargin">
<number>2</number>
</property>
<property name="topMargin">
<number>2</number>
</property>
<property name="rightMargin">
<number>2</number>
</property>
<property name="bottomMargin">
<number>2</number>
</property>
<item>
<widget class="QGroupBox" name="x_group_box">
<property name="title">
<string>X Axis</string>
</property>
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,3,0,1,3">
<item>
<widget class="QLabel" name="x_name_label">
<property name="text">
<string>Name</string>
</property>
</widget>
</item>
<item>
<widget class="DeviceLineEdit" name="x_name"/>
</item>
<item>
<widget class="Line" name="line">
<property name="orientation">
<enum>Qt::Orientation::Vertical</enum>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="x_entry_label">
<property name="text">
<string>Entry</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="x_entry"/>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QGroupBox" name="y_group_box">
<property name="title">
<string>Y Axis</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>0</number>
</property>
<widget class="QWidget" name="tab_scan">
<attribute name="title">
<string>Scan</string>
</attribute>
<layout class="QGridLayout" name="gridLayout">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item row="0" column="0">
<widget class="QPushButton" name="add_curve">
<property name="text">
<string>Add Curve</string>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>40</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<widget class="QTableWidget" name="scan_table">
<property name="rowCount">
<number>0</number>
</property>
<attribute name="horizontalHeaderCascadingSectionResizes">
<bool>false</bool>
</attribute>
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<attribute name="verticalHeaderStretchLastSection">
<bool>false</bool>
</attribute>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Entry</string>
</property>
</column>
<column>
<property name="text">
<string>Color</string>
</property>
</column>
<column>
<property name="text">
<string>Style</string>
</property>
</column>
<column>
<property name="text">
<string>Width</string>
</property>
</column>
<column>
<property name="text">
<string>Symbol Size</string>
</property>
</column>
<column>
<property name="text">
<string>Delete</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_DAP">
<attribute name="title">
<string>DAP</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<property name="leftMargin">
<number>5</number>
</property>
<property name="topMargin">
<number>5</number>
</property>
<property name="rightMargin">
<number>5</number>
</property>
<property name="bottomMargin">
<number>5</number>
</property>
<item row="0" column="0">
<widget class="QPushButton" name="add_dap">
<property name="text">
<string>Add DAP</string>
</property>
</widget>
</item>
<item row="0" column="1">
<spacer name="horizontalSpacer_2">
<property name="orientation">
<enum>Qt::Orientation::Horizontal</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>585</width>
<height>20</height>
</size>
</property>
</spacer>
</item>
<item row="1" column="0" colspan="2">
<widget class="QTableWidget" name="dap_table">
<attribute name="horizontalHeaderShowSortIndicator" stdset="0">
<bool>true</bool>
</attribute>
<attribute name="horizontalHeaderStretchLastSection">
<bool>true</bool>
</attribute>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Entry</string>
</property>
</column>
<column>
<property name="text">
<string>Model</string>
</property>
</column>
<column>
<property name="text">
<string>Color</string>
</property>
</column>
<column>
<property name="text">
<string>Style</string>
</property>
</column>
<column>
<property name="text">
<string>Width</string>
</property>
</column>
<column>
<property name="text">
<string>Symbol Size</string>
</property>
</column>
<column>
<property name="text">
<string>Delete</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_custom">
<attribute name="title">
<string>Custom</string>
</attribute>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>DeviceLineEdit</class>
<extends>QLineEdit</extends>
<header>device_line_edit</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" enable-background="new 0 0 24 24" height="48px" viewBox="0 0 24 24" width="48px"
fill="#8B1A10">
<rect fill="none" height="24" width="24"/>
<path d="M19,19H5V5h14V19z M3,3v18h18V3H3z M17,15.59L15.59,17L12,13.41L8.41,17L7,15.59L10.59,12L7,8.41L8.41,7L12,10.59L15.59,7 L17,8.41L13.41,12L17,15.59z"/>
</svg>

After

Width:  |  Height:  |  Size: 357 B

View File

@ -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):

View File

@ -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):