mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 03:31:50 +02:00
fix: after removing plot from BECFigure, the coordinates are correctly resigned
This commit is contained in:
@ -26,7 +26,7 @@ for ii in range(20):
|
||||
topic=MessageEndpoints.device_async_readback(
|
||||
scanID=scanID, device="mca"
|
||||
), # scanID will be different for each scan
|
||||
msg={"data": msg},
|
||||
msg={"data": msg}, # TODO should be msg_dict
|
||||
expire=1800,
|
||||
)
|
||||
|
||||
|
@ -59,6 +59,10 @@ class BECConnector:
|
||||
"""
|
||||
self.config.gui_id = gui_id
|
||||
|
||||
def get_obj_by_id(self, obj_id: str):
|
||||
if obj_id == self.gui_id:
|
||||
return self
|
||||
|
||||
def get_bec_shortcuts(self):
|
||||
"""Get BEC shortcuts for the widget."""
|
||||
self.dev = self.client.device_manager.devices
|
||||
|
@ -2,6 +2,8 @@
|
||||
import itertools
|
||||
import os
|
||||
import sys
|
||||
from collections import defaultdict
|
||||
from dataclasses import dataclass
|
||||
from typing import Literal, Optional
|
||||
|
||||
import numpy as np
|
||||
@ -26,6 +28,7 @@ class FigureConfig(ConnectionConfig):
|
||||
|
||||
theme: Literal["dark", "light"] = Field("dark", description="The theme of the figure widget.")
|
||||
num_columns: int = Field(1, description="The number of columns in the figure widget.")
|
||||
num_rows: int = Field(1, description="The number of rows in the figure widget.")
|
||||
widgets: dict[str, WidgetConfig] = Field(
|
||||
{}, description="The list of widgets to be added to the figure widget."
|
||||
)
|
||||
@ -98,16 +101,48 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
||||
pg.GraphicsLayoutWidget.__init__(self, parent) # in case of inheritance
|
||||
|
||||
self.widget_handler = WidgetHandler()
|
||||
self.widgets = {}
|
||||
self.widgets = defaultdict(dict)
|
||||
|
||||
# def show(self): # TODO check if useful for anything
|
||||
# self.window = QMainWindow()
|
||||
# self.window.setCentralWidget(self)
|
||||
# self.window.show()
|
||||
#
|
||||
# def close(self): # TODO check if useful for anything
|
||||
# if hasattr(self, "window"):
|
||||
# self.window.close()
|
||||
self.grid = []
|
||||
|
||||
def change_grid(self, widget_id: str, row: int, col: int):
|
||||
"""
|
||||
Change the grid to reflect the new position of the widget.
|
||||
Args:
|
||||
widget_id(str): The unique identifier of the widget.
|
||||
row(int): The new row coordinate of the widget in the figure.
|
||||
col(int): The new column coordinate of the widget in the figure.
|
||||
"""
|
||||
while len(self.grid) <= row:
|
||||
self.grid.append([])
|
||||
row = self.grid[row]
|
||||
while len(row) <= col:
|
||||
row.append(None)
|
||||
row[col] = widget_id
|
||||
|
||||
def reindex_grid(self):
|
||||
"""Reindex the grid to remove empty rows and columns."""
|
||||
print(f"old grid: {self.grid}")
|
||||
new_grid = []
|
||||
for row in self.grid:
|
||||
new_row = [widget for widget in row if widget is not None]
|
||||
if new_row:
|
||||
new_grid.append(new_row)
|
||||
#
|
||||
# Update the config of each object to reflect its new position
|
||||
for row_idx, row in enumerate(new_grid):
|
||||
for col_idx, widget in enumerate(row):
|
||||
self.widgets[widget].config.row, self.widgets[widget].config.col = row_idx, col_idx
|
||||
|
||||
self.grid = new_grid
|
||||
self.replot_layout()
|
||||
|
||||
def replot_layout(self):
|
||||
"""Replot the layout based on the current grid configuration."""
|
||||
self.clear()
|
||||
for row_idx, row in enumerate(self.grid):
|
||||
for col_idx, widget in enumerate(row):
|
||||
self.addItem(self.widgets[widget], row=row_idx, col=col_idx)
|
||||
|
||||
@user_access
|
||||
def add_widget(
|
||||
@ -116,7 +151,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
||||
widget_id: str = None,
|
||||
row: int = None,
|
||||
col: int = None,
|
||||
config: dict = None,
|
||||
config=None,
|
||||
**axis_kwargs,
|
||||
):
|
||||
"""
|
||||
@ -149,22 +184,34 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
||||
raise ValueError(f"Position at row {row} and column {col} is already occupied.")
|
||||
else:
|
||||
widget.config.row = row
|
||||
widget.config.column = col
|
||||
widget.config.col = col
|
||||
|
||||
# Add widget to the figure
|
||||
self.addItem(widget, row=row, col=col)
|
||||
else:
|
||||
row, col = self._find_next_empty_position()
|
||||
widget.config.row = row
|
||||
widget.config.column = col
|
||||
widget.config.col = col
|
||||
|
||||
# Add widget to the figure
|
||||
self.addItem(widget, row=row, col=col)
|
||||
|
||||
#
|
||||
# TODO decide if needed
|
||||
# Update num_columns and num_rows based on the added widget
|
||||
self.config.num_rows = max(self.config.num_rows, row + 1)
|
||||
self.config.num_columns = max(self.config.num_columns, col + 1)
|
||||
|
||||
# By default, set the title of the widget to its unique identifier #TODO will be removed after debugging
|
||||
widget.set_title(f"{widget_id}")
|
||||
|
||||
# Saving config for future referencing
|
||||
self.config.widgets[widget_id] = widget.config
|
||||
self.widgets[widget_id] = widget
|
||||
|
||||
# Reflect the grid coordinates
|
||||
self.change_grid(widget_id, row, col)
|
||||
|
||||
@user_access
|
||||
def remove(
|
||||
self,
|
||||
@ -216,7 +263,8 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
||||
if widget_id in self.widgets:
|
||||
widget = self.widgets.pop(widget_id)
|
||||
self.removeItem(widget)
|
||||
# Assuming self.config.widgets is a dict tracking widgets by their IDs
|
||||
self.grid[widget.config.row][widget.config.col] = None
|
||||
self.reindex_grid()
|
||||
if widget_id in self.config.widgets:
|
||||
self.config.widgets.pop(widget_id)
|
||||
print(f"Removed widget {widget_id}.")
|
||||
@ -262,7 +310,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
||||
"""Generate a unique widget ID."""
|
||||
existing_ids = set(self.widgets.keys())
|
||||
for i in itertools.count(1):
|
||||
widget_id = f"widget_{i}"
|
||||
widget_id = f"Widget {i}"
|
||||
if widget_id not in existing_ids:
|
||||
return widget_id
|
||||
|
||||
@ -283,6 +331,7 @@ class BECFigure(BECConnector, pg.GraphicsLayoutWidget):
|
||||
|
||||
from qtconsole.rich_jupyter_widget import RichJupyterWidget
|
||||
from qtconsole.inprocess import QtInProcessKernelManager
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
class JupyterConsoleWidget(RichJupyterWidget):
|
||||
@ -294,7 +343,7 @@ class JupyterConsoleWidget(RichJupyterWidget):
|
||||
self.kernel_client = self.kernel_manager.client()
|
||||
self.kernel_client.start_channels()
|
||||
|
||||
self.kernel_manager.kernel.shell.push({"np": np, "pg": pg})
|
||||
self.kernel_manager.kernel.shell.push({"np": np, "pg": pg, "plt": plt})
|
||||
|
||||
def shutdown_kernel(self):
|
||||
self.kernel_client.stop_channels()
|
||||
@ -314,9 +363,10 @@ class DebugWindow(QWidget):
|
||||
|
||||
self.splitter.setSizes([200, 100])
|
||||
|
||||
# self.con_w1 =
|
||||
# console push
|
||||
self.console.kernel_manager.kernel.shell.push(
|
||||
{"fig": self.figure, "w1": self.w1, "w2": self.w2, "np": np, "pg": pg}
|
||||
{"fig": self.figure, "w1": self.w1, "w2": self.w2}
|
||||
)
|
||||
|
||||
def _init_ui(self):
|
||||
@ -334,11 +384,15 @@ class DebugWindow(QWidget):
|
||||
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.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.figure.add_widget(widget_type="Waveform1D", row=0, col=1) # , title="Plot 3")
|
||||
self.figure.add_widget(widget_type="Waveform1D", row=1, col=1) # , title="Plot 4")
|
||||
|
||||
self.w1 = self.figure[0, 0]
|
||||
self.w2 = self.figure[1, 0]
|
||||
self.w3 = self.figure[0, 1]
|
||||
self.w4 = self.figure[1, 1]
|
||||
|
||||
# curves for w1
|
||||
self.w1.add_scan("samx", "samx", "bpm4i", "bpm4i", pen_style="dash")
|
||||
@ -355,6 +409,26 @@ class DebugWindow(QWidget):
|
||||
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")
|
||||
|
||||
# curves for w3
|
||||
self.w3.add_scan("samx", "samx", "bpm4i", "bpm4i", pen_style="dash")
|
||||
self.w3.add_curve(
|
||||
x=[1, 2, 3, 4, 5],
|
||||
y=[1, 2, 3, 4, 5],
|
||||
label="curve-custom",
|
||||
color="blue",
|
||||
pen_style="dashdot",
|
||||
)
|
||||
|
||||
# curves for w4
|
||||
self.w4.add_scan("samx", "samx", "bpm4i", "bpm4i", pen_style="dash")
|
||||
self.w4.add_curve(
|
||||
x=[1, 2, 3, 4, 5],
|
||||
y=[1, 2, 3, 4, 5],
|
||||
label="curve-custom",
|
||||
color="blue",
|
||||
pen_style="dashdot",
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__": # pragma: no cover
|
||||
import sys
|
||||
|
@ -26,7 +26,7 @@ class WidgetConfig(ConnectionConfig):
|
||||
|
||||
# Coordinates in the figure
|
||||
row: int = Field(0, description="The row coordinate in the figure.")
|
||||
column: int = Field(0, description="The column coordinate in the figure.")
|
||||
col: int = Field(0, description="The column coordinate in the figure.")
|
||||
|
||||
# Appearance settings
|
||||
axis: AxisConfig = Field(
|
||||
|
@ -217,6 +217,52 @@ class BECWaveform1D(BECPlotBase):
|
||||
|
||||
self.addLegend()
|
||||
|
||||
self.apply_config()
|
||||
|
||||
# # TODO decide if to use class methods or not
|
||||
# wid = BECWaveform1D.from_config()
|
||||
#
|
||||
# @classmethod
|
||||
# def from_config(
|
||||
# cls,
|
||||
# parent: Optional[QWidget],
|
||||
# config: Waveform1DConfig,
|
||||
# client=None,
|
||||
# gui_id: Optional[str] = None,
|
||||
# replot_last_scan: bool = False,
|
||||
# ):
|
||||
# """
|
||||
# Class method to create an instance of BECWaveform1D from a configuration object.
|
||||
#
|
||||
# Args:
|
||||
# parent: The parent widget.
|
||||
# config: Configuration object for the Waveform1D widget.
|
||||
# client: Client for communication with backend services.
|
||||
# gui_id: Optional unique identifier for the GUI component.
|
||||
#
|
||||
# Returns:
|
||||
# An instance of BECWaveform1D configured according to the provided config.
|
||||
# """
|
||||
# # Initialize the widget with the provided config
|
||||
# widget = cls(parent=parent, config=config, client=client, gui_id=gui_id)
|
||||
# widget.apply_axis_config()
|
||||
#
|
||||
# # Reconstruct curves based on the config
|
||||
# for curve_id, curve_config in config.curves.items():
|
||||
# widget.add_curve_by_config(curve_config)
|
||||
# if replot_last_scan:
|
||||
# widget.update_scan_curve_history(-1)
|
||||
#
|
||||
# return widget
|
||||
|
||||
# TODO check the functionality of config generator
|
||||
def apply_config(self, replot_last_scan: bool = False):
|
||||
self.apply_axis_config()
|
||||
for curve_id, curve_config in self.config.curves.items():
|
||||
self.add_curve_by_config(curve_config)
|
||||
if replot_last_scan:
|
||||
self.update_scan_curve_history(-1)
|
||||
|
||||
def add_curve_by_config(self, curve_config: CurveConfig | dict):
|
||||
"""
|
||||
Add a curve to the plot widget by its configuration.
|
||||
@ -269,7 +315,7 @@ class BECWaveform1D(BECPlotBase):
|
||||
color: Optional[str] = None,
|
||||
label: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
): # add_scan_curve
|
||||
# Check if curve already exists
|
||||
curve_source = "scan_segment"
|
||||
label = label or f"{y_name}-{y_entry}"
|
||||
@ -282,7 +328,7 @@ class BECWaveform1D(BECPlotBase):
|
||||
color = (
|
||||
color
|
||||
or Colors.golden_angle_color(
|
||||
colormap=self.config.color_palette, num=len(self.curves) + 1
|
||||
colormap=self.config.color_palette, num=len(self.curves) + 1, format="HEX"
|
||||
)[-1]
|
||||
)
|
||||
|
||||
@ -486,8 +532,10 @@ class BECWaveform1D(BECPlotBase):
|
||||
raise ValueError("Only one of scanID or scan_index can be provided.")
|
||||
|
||||
if scan_index is not None:
|
||||
self.scanID = self.queue.scan_storage.storage[scan_index].scanID
|
||||
data = self.queue.scan_storage.storage[scan_index].data
|
||||
elif scanID is not None:
|
||||
self.scanID = scanID
|
||||
data = self.queue.scan_storage.find_scan_by_ID(self.scanID).data
|
||||
|
||||
self._update_scan_curves(data)
|
||||
|
Reference in New Issue
Block a user