mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 03:31:50 +02:00
feat: curve can be modified after adding to the plot
This commit is contained in:
@ -6,7 +6,7 @@ from typing import Literal, Optional
|
||||
|
||||
import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from PyQt6.QtWidgets import QVBoxLayout, QMainWindow
|
||||
from qtpy.QtWidgets import QVBoxLayout, QMainWindow
|
||||
from pydantic import Field
|
||||
from pyqtgraph.Qt import uic
|
||||
from qtpy.QtWidgets import QApplication, QWidget
|
||||
@ -100,15 +100,6 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
||||
self.widget_handler = WidgetHandler()
|
||||
self.widgets = {}
|
||||
|
||||
# TODO just testing adding plot
|
||||
self.add_widget(widget_id="widget_1", row=0, col=0, title="Plot 1")
|
||||
self.widgets["widget_1"].plot_data(
|
||||
np.linspace(0, 10, 100), np.sin(np.linspace(0, 10, 100)), label="sin(x)"
|
||||
)
|
||||
|
||||
# TODO debug 1dwaveform
|
||||
self.add_widget(widget_type="Waveform1D", widget_id="widget_2", row=1, col=0)
|
||||
|
||||
# def show(self): # TODO check if useful for anything
|
||||
# self.window = QMainWindow()
|
||||
# self.window.setCentralWidget(self)
|
||||
@ -174,9 +165,6 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
||||
self.config.widgets[widget_id] = widget.config
|
||||
self.widgets[widget_id] = widget
|
||||
|
||||
# TODO rpc debug
|
||||
print(f"Added widget {widget_id} at position ({row}, {col}).")
|
||||
|
||||
@user_access
|
||||
def remove(
|
||||
self,
|
||||
@ -324,8 +312,12 @@ class DebugWindow(QWidget):
|
||||
|
||||
self._init_ui()
|
||||
|
||||
self.splitter.setSizes([200, 100])
|
||||
|
||||
# console push
|
||||
self.console.kernel_manager.kernel.shell.push({"fig": self.figure})
|
||||
self.console.kernel_manager.kernel.shell.push(
|
||||
{"fig": self.figure, "w1": self.w1, "w2": self.w2, "np": np, "pg": pg}
|
||||
)
|
||||
|
||||
def _init_ui(self):
|
||||
# Plotting window
|
||||
@ -333,11 +325,36 @@ class DebugWindow(QWidget):
|
||||
self.figure = BECFigure(parent=self) # Create a new BECDeviceMonitor
|
||||
self.glw_1_layout.addWidget(self.figure) # Add BECDeviceMonitor to the layout
|
||||
|
||||
# add stuff to figure
|
||||
self._init_figure()
|
||||
|
||||
self.console_layout = QVBoxLayout(self.widget_console)
|
||||
self.console = JupyterConsoleWidget()
|
||||
self.console_layout.addWidget(self.console)
|
||||
self.console.set_default_style("linux")
|
||||
|
||||
def _init_figure(self):
|
||||
self.figure.add_widget(widget_type="Waveform1D", row=0, col=0, title="Plot 1")
|
||||
self.figure.add_widget(widget_type="Waveform1D", row=1, col=0, title="Plot 2")
|
||||
|
||||
self.w1 = self.figure[0, 0]
|
||||
self.w2 = self.figure[1, 0]
|
||||
|
||||
# curves for w1
|
||||
self.w1.add_scan("samx", "samx", "bpm4i", "bpm4i", pen_style="dash")
|
||||
self.w1.add_curve(
|
||||
x=[1, 2, 3, 4, 5],
|
||||
y=[1, 2, 3, 4, 5],
|
||||
label="curve-custom",
|
||||
color="blue",
|
||||
pen_style="dashdot",
|
||||
)
|
||||
|
||||
# curves for w2
|
||||
self.w2.add_scan("samx", "samx", "bpm3a", "bpm3a", pen_style="solid")
|
||||
self.w2.add_scan("samx", "samx", "bpm4d", "bpm4d", pen_style="dot")
|
||||
self.w2.add_curve(x=[1, 2, 3, 4, 5], y=[5, 4, 3, 2, 1], color="red", pen_style="dashdot")
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
@ -5,17 +5,14 @@ import numpy as np
|
||||
import pyqtgraph as pg
|
||||
from pydantic import Field, BaseModel
|
||||
from pyqtgraph import mkBrush
|
||||
|
||||
from qtpy.QtWidgets import QWidget
|
||||
from qtpy import QtCore
|
||||
from qtpy.QtCore import Slot as pyqtSlot
|
||||
from qtpy.QtCore import Signal as pyqtSignal
|
||||
from qtpy.QtGui import QColor
|
||||
from qtpy.QtCore import Slot as pyqtSlot
|
||||
from qtpy.QtWidgets import QWidget
|
||||
|
||||
from bec_lib.scan_data import ScanData
|
||||
from bec_lib.scan_items import ScanItem
|
||||
from bec_lib.utils import user_access
|
||||
from bec_lib import MessageEndpoints
|
||||
from bec_lib.scan_data import ScanData
|
||||
from bec_lib.utils import user_access
|
||||
from bec_widgets.utils import Colors
|
||||
from bec_widgets.widgets.plots import BECPlotBase, WidgetConfig
|
||||
|
||||
@ -47,7 +44,7 @@ class CurveConfig(BaseModel):
|
||||
pen_width: Optional[int] = Field(2, description="The width of the pen of the curve.")
|
||||
pen_style: Optional[Literal["solid", "dash", "dot", "dashdot"]] = Field(
|
||||
"solid", description="The style of the pen of the curve."
|
||||
) # TODO check if valid
|
||||
)
|
||||
source: Optional[str] = Field(
|
||||
None, description="The source of the curve."
|
||||
) # TODO here on or curve??
|
||||
@ -96,12 +93,97 @@ class BECCurve(pg.PlotDataItem): # TODO decide what will be accessible from the
|
||||
self.setSymbolSize(self.config.symbol_size)
|
||||
self.setSymbol(self.config.symbol)
|
||||
|
||||
def update_data(self, x, y):
|
||||
def set_data(self, x, y):
|
||||
if self.config.source == "custom":
|
||||
self.setData(x, y)
|
||||
else:
|
||||
raise ValueError(f"Source {self.config.source} do not allow custom data setting.")
|
||||
|
||||
def set(self, **kwargs):
|
||||
"""
|
||||
Set the properties of the curve.
|
||||
Args:
|
||||
**kwargs: Keyword arguments for the properties to be set.
|
||||
Possible properties:
|
||||
- color: str
|
||||
- symbol: str
|
||||
- symbol_color: str
|
||||
- symbol_size: int
|
||||
- pen_width: int
|
||||
- pen_style: Literal["solid", "dash", "dot", "dashdot"]
|
||||
"""
|
||||
|
||||
# Mapping of keywords to setter methods
|
||||
method_map = {
|
||||
"color": self.set_color,
|
||||
"symbol": self.set_symbol,
|
||||
"symbol_color": self.set_symbol_color,
|
||||
"symbol_size": self.set_symbol_size,
|
||||
"pen_width": self.set_pen_width,
|
||||
"pen_style": self.set_pen_style,
|
||||
}
|
||||
for key, value in kwargs.items():
|
||||
if key in method_map:
|
||||
method_map[key](value)
|
||||
else:
|
||||
print(f"Warning: '{key}' is not a recognized property.")
|
||||
|
||||
def set_color(self, color: str, symbol_color: Optional[str] = None):
|
||||
"""
|
||||
Change the color of the curve.
|
||||
Args:
|
||||
color(str): Color of the curve.
|
||||
symbol_color(str, optional): Color of the symbol. Defaults to None.
|
||||
"""
|
||||
self.config.color = color
|
||||
self.config.symbol_color = symbol_color or color
|
||||
self.apply_config()
|
||||
|
||||
def set_symbol(self, symbol: str):
|
||||
"""
|
||||
Change the symbol of the curve.
|
||||
Args:
|
||||
symbol(str): Symbol of the curve.
|
||||
"""
|
||||
self.config.symbol = symbol
|
||||
self.apply_config()
|
||||
|
||||
def set_symbol_color(self, symbol_color: str):
|
||||
"""
|
||||
Change the symbol color of the curve.
|
||||
Args:
|
||||
symbol_color(str): Color of the symbol.
|
||||
"""
|
||||
self.config.symbol_color = symbol_color
|
||||
self.apply_config()
|
||||
|
||||
def set_symbol_size(self, symbol_size: int):
|
||||
"""
|
||||
Change the symbol size of the curve.
|
||||
Args:
|
||||
symbol_size(int): Size of the symbol.
|
||||
"""
|
||||
self.config.symbol_size = symbol_size
|
||||
self.apply_config()
|
||||
|
||||
def set_pen_width(self, pen_width: int):
|
||||
"""
|
||||
Change the pen width of the curve.
|
||||
Args:
|
||||
pen_width(int): Width of the pen.
|
||||
"""
|
||||
self.config.pen_width = pen_width
|
||||
self.apply_config()
|
||||
|
||||
def set_pen_style(self, pen_style: Literal["solid", "dash", "dot", "dashdot"]):
|
||||
"""
|
||||
Change the pen style of the curve.
|
||||
Args:
|
||||
pen_style(Literal["solid", "dash", "dot", "dashdot"]): Style of the pen.
|
||||
"""
|
||||
self.config.pen_style = pen_style
|
||||
self.apply_config()
|
||||
|
||||
|
||||
class BECWaveform1D(BECPlotBase):
|
||||
scan_signal_update = pyqtSignal()
|
||||
@ -120,7 +202,7 @@ class BECWaveform1D(BECPlotBase):
|
||||
parent=parent, parent_figure=parent_figure, config=config, client=client, gui_id=gui_id
|
||||
)
|
||||
|
||||
self.curve_data = defaultdict(dict)
|
||||
self.curves_data = defaultdict(dict)
|
||||
self.scanID = None
|
||||
|
||||
self.proxy_update_plot = pg.SignalProxy(
|
||||
@ -133,26 +215,49 @@ class BECWaveform1D(BECPlotBase):
|
||||
# Connect dispatcher signals
|
||||
self.bec_dispatcher.connect_slot(self.on_scan_segment, MessageEndpoints.scan_segment())
|
||||
|
||||
# TODO DEbug
|
||||
# Scan curves
|
||||
self.add_scan("samx", "samx", "bpm4i", "bpm4i", pen_style="dash")
|
||||
self.add_scan("samx", "samx", "bpm3a", "bpm3a", pen_style="solid")
|
||||
self.add_scan("samx", "samx", "bpm4d", "bpm4d", pen_style="dot")
|
||||
# Custom curves
|
||||
self.add_curve(
|
||||
x=[1, 2, 3, 4, 5],
|
||||
y=[1, 2, 3, 4, 5],
|
||||
label="curve-custom",
|
||||
color="blue",
|
||||
pen_style="dashdot",
|
||||
)
|
||||
self.add_curve(x=[1, 2, 3, 4, 5], y=[5, 4, 3, 2, 1], color="red", pen_style="dashdot")
|
||||
|
||||
self.addLegend()
|
||||
|
||||
def add_curve_by_config(self, curve_config: CurveConfig): ...
|
||||
def add_curve_by_config(self, curve_config: CurveConfig | dict):
|
||||
"""
|
||||
Add a curve to the plot widget by its configuration.
|
||||
Args:
|
||||
curve_config(CurveConfig|dict): Configuration of the curve to be added.
|
||||
"""
|
||||
if isinstance(curve_config, dict):
|
||||
curve_config = CurveConfig(**curve_config)
|
||||
self._add_curve_object(
|
||||
name=curve_config.label, source=curve_config.source, config=curve_config
|
||||
)
|
||||
|
||||
def save_curve_config(self): ...
|
||||
def get_curve_config(self, curve_id: str) -> CurveConfig:
|
||||
"""
|
||||
Get the configuration of a curve by its ID.
|
||||
Args:
|
||||
curve_id(str): ID of the curve.
|
||||
Returns:
|
||||
CurveConfig: Configuration of the curve.
|
||||
"""
|
||||
for source, curves in self.curves_data.items():
|
||||
if curve_id in curves:
|
||||
return curves[curve_id].config
|
||||
|
||||
@user_access
|
||||
def curves(self) -> list: # TODO discuss if it should be marked as @property for RPC
|
||||
"""
|
||||
Get the curves of the plot widget as a list
|
||||
Returns:
|
||||
list: List of curves.
|
||||
"""
|
||||
return self.curves
|
||||
|
||||
@user_access
|
||||
def curves_data(self) -> dict: # TODO discuss if it should be marked as @property for RPC
|
||||
"""
|
||||
Get the curves data of the plot widget as a dictionary
|
||||
Returns:
|
||||
dict: Dictionary of curves data.
|
||||
"""
|
||||
return self.curves_data
|
||||
|
||||
@user_access
|
||||
def add_scan(
|
||||
@ -169,7 +274,7 @@ class BECWaveform1D(BECPlotBase):
|
||||
curve_source = "scan_segment"
|
||||
label = label or f"{y_name}-{y_entry}"
|
||||
|
||||
curve_exits = self._check_curve_id(label, self.curve_data)
|
||||
curve_exits = self._check_curve_id(label, self.curves_data)
|
||||
if curve_exits:
|
||||
raise ValueError(f"Curve with ID '{label}' already exists in widget '{self.gui_id}'.")
|
||||
return
|
||||
@ -207,7 +312,7 @@ class BECWaveform1D(BECPlotBase):
|
||||
curve_source = "custom"
|
||||
curve_id = label or f"Curve {len(self.curves) + 1}"
|
||||
|
||||
curve_exits = self._check_curve_id(curve_id, self.curve_data)
|
||||
curve_exits = self._check_curve_id(curve_id, self.curves_data)
|
||||
if curve_exits:
|
||||
raise ValueError(
|
||||
f"Curve with ID '{curve_id}' already exists in widget '{self.gui_id}'."
|
||||
@ -230,6 +335,8 @@ class BECWaveform1D(BECPlotBase):
|
||||
|
||||
self._add_curve_object(name=curve_id, source=curve_source, config=curve_config, data=(x, y))
|
||||
|
||||
# self.crosshair = Crosshair(self, precision=3)
|
||||
|
||||
def _add_curve_object(
|
||||
self,
|
||||
name: str,
|
||||
@ -246,7 +353,7 @@ class BECWaveform1D(BECPlotBase):
|
||||
data(tuple[list|np.ndarray,list|np.ndarray], optional): Data (x,y) to be plotted. Defaults to None.
|
||||
"""
|
||||
curve = BECCurve(config=config, name=name)
|
||||
self.curve_data[source][name] = curve
|
||||
self.curves_data[source][name] = curve
|
||||
self.addItem(curve)
|
||||
self.config.curves[name] = curve.config
|
||||
if data is not None:
|
||||
@ -292,7 +399,7 @@ class BECWaveform1D(BECPlotBase):
|
||||
Args:
|
||||
curve_id(str): ID of the curve to be removed.
|
||||
"""
|
||||
for source, curves in self.curve_data.items():
|
||||
for source, curves in self.curves_data.items():
|
||||
if curve_id in curves:
|
||||
curve = curves.pop(curve_id)
|
||||
self.removeItem(curve)
|
||||
@ -314,7 +421,7 @@ class BECWaveform1D(BECPlotBase):
|
||||
self.removeItem(curve)
|
||||
del self.config.curves[curve_id]
|
||||
# Remove from self.curve_data
|
||||
for source, curves in self.curve_data.items():
|
||||
for source, curves in self.curves_data.items():
|
||||
if curve_id in curves:
|
||||
del curves[curve_id]
|
||||
break
|
||||
@ -352,7 +459,7 @@ class BECWaveform1D(BECPlotBase):
|
||||
Args:
|
||||
data(ScanData): Data from the scan segment.
|
||||
"""
|
||||
for curve_id, curve in self.curve_data["scan_segment"].items():
|
||||
for curve_id, curve in self.curves_data["scan_segment"].items():
|
||||
x_name = curve.config.signals.x.name
|
||||
x_entry = curve.config.signals.x.entry
|
||||
y_name = curve.config.signals.y.name
|
||||
|
Reference in New Issue
Block a user