From 14e92e8d6839616191aa0a0cdae4ed04eaaa7520 Mon Sep 17 00:00:00 2001 From: wyzula-jan <133381102+wyzula-jan@users.noreply.github.com> Date: Tue, 8 Aug 2023 14:10:24 +0200 Subject: [PATCH] refactor: changed from bec client to dispatcher --- bec_widgets/line_plot.py | 62 +++++++++++++++++++++++++------ bec_widgets/line_plot.ui | 79 +++++++++++++++++++++++----------------- tests/test_line_plot.py | 4 +- 3 files changed, 99 insertions(+), 46 deletions(-) diff --git a/bec_widgets/line_plot.py b/bec_widgets/line_plot.py index 5e29994a..6a96b872 100644 --- a/bec_widgets/line_plot.py +++ b/bec_widgets/line_plot.py @@ -5,7 +5,8 @@ from typing import Any import numpy as np import pyqtgraph import pyqtgraph as pg -from PyQt5.QtWidgets import QTableWidgetItem +from PyQt5.QtCore import pyqtSlot +from PyQt5.QtWidgets import QTableWidgetItem, QCheckBox from bec_lib import BECClient from pyqtgraph import mkBrush, mkColor, mkPen @@ -33,6 +34,9 @@ class BasicPlot(QtWidgets.QWidget): current_path = os.path.dirname(__file__) uic.loadUi(os.path.join(current_path, "line_plot.ui"), self) + # Set splitter distribution of widgets + self.splitter.setSizes([5, 2]) + self._idle_time = 100 self.title = "" self.label_bottom = "" @@ -40,6 +44,7 @@ class BasicPlot(QtWidgets.QWidget): self.scan_motors = [] self.y_value_list = y_value_list + self.previous_y_value_list = None self.plotter_data_x = [] self.plotter_data_y = [] self.curves = [] @@ -138,9 +143,6 @@ class BasicPlot(QtWidgets.QWidget): if not self.plotter_data_x: return - # Init number of rows in table according to n of devices - self.mouse_table.setRowCount(len(self.y_value_list)) - for ii, y_value in enumerate(self.y_value_list): closest_point = self.closest_x_y_value( mousePoint.x(), self.plotter_data_x, self.plotter_data_y[ii] @@ -150,9 +152,11 @@ class BasicPlot(QtWidgets.QWidget): y_data = f"{closest_point[1]:.{self.precision}f}" # Write coordinate to QTable - self.mouse_table.setItem(ii, 0, QTableWidgetItem(str(y_value))) - self.mouse_table.setItem(ii, 1, QTableWidgetItem(str(x_data))) - self.mouse_table.setItem(ii, 2, QTableWidgetItem(str(y_data))) + self.mouse_table.setItem(ii, 1, QTableWidgetItem(str(y_value))) + self.mouse_table.setItem(ii, 2, QTableWidgetItem(str(x_data))) + self.mouse_table.setItem(ii, 3, QTableWidgetItem(str(y_data))) + + self.mouse_table.resizeColumnsToContents() def closest_x_y_value(self, input_value, list_x, list_y) -> tuple: """ @@ -176,6 +180,11 @@ class BasicPlot(QtWidgets.QWidget): if self.roi_selector not in self.plot.items: self.plot.addItem(self.roi_selector) + # check if QTable was initialised and if list of devices was changed + if self.y_value_list != self.previous_y_value_list: + self.setup_cursor_table() + self.previous_y_value_list = self.y_value_list.copy() if self.y_value_list else None + if len(self.plotter_data_x) <= 1: return self.plot.setLabel("bottom", self.label_bottom) @@ -183,7 +192,8 @@ class BasicPlot(QtWidgets.QWidget): for ii in range(len(self.y_value_list)): self.curves[ii].setData(self.plotter_data_x, self.plotter_data_y[ii]) - def __call__(self, data: dict, metadata: dict, **kwds: Any) -> None: + @pyqtSlot(dict, dict) + def on_scan_segment(self, data: dict, metadata: dict) -> None: """Update function that is called during the scan callback. To avoid too many renderings, the GUI is only processing events every <_idle_time> ms. @@ -199,7 +209,7 @@ class BasicPlot(QtWidgets.QWidget): self.title = f"Scan {metadata['scan_number']}" self.scan_motors = scan_motors = metadata.get("scan_report_devices") - client = BECClient() + # client = BECClient() remove_y_value_index = [ index for index, y_value in enumerate(self.y_value_list) @@ -219,6 +229,7 @@ class BasicPlot(QtWidgets.QWidget): ]["precision"] # TODO after update of bec_lib, this will be new way to access data # self.precision = client.device_manager.devices[scan_motors[0]].precision + x = data["data"][scan_motors[0]][scan_motors[0]]["value"] self.plotter_data_x.append(x) for ii, y_value in enumerate(self.y_value_list): @@ -227,6 +238,9 @@ class BasicPlot(QtWidgets.QWidget): self.label_bottom = scan_motors[0] self.label_left = f"{', '.join(self.y_value_list)}" + # print(f'metadata scan N{metadata["scan_number"]}') #TODO put as label on top of plot + # print(f'Data point = {data["point_id"]}') #TODO can be used for progress bar + if len(self.plotter_data_x) <= 1: return self.update_signal.emit() @@ -239,19 +253,41 @@ class BasicPlot(QtWidgets.QWidget): self.curves[ii].setData([], []) self.plotter_data_y.append([]) + def setup_cursor_table(self): + """QTable formatting according to N of devices displayed in plot.""" + + # Init number of rows in table according to n of devices + self.mouse_table.setRowCount(len(self.y_value_list)) + + for ii, y_value in enumerate(self.y_value_list): + checkbox = QCheckBox() + checkbox.setChecked(True) + # TODO just for testing, will be replaced by removing/adding curve + checkbox.stateChanged.connect(lambda: print("status Changed")) + # checkbox.stateChanged.connect(lambda: self.remove_curve_by_name(plot=self.plot, checkbox=checkbox, name=y_value)) + self.mouse_table.setCellWidget(ii, 0, checkbox) + self.mouse_table.setItem(ii, 1, QTableWidgetItem(str(y_value))) + + self.mouse_table.resizeColumnsToContents() + @staticmethod def remove_curve_by_name(plot: pyqtgraph.PlotItem, name: str) -> None: + # def remove_curve_by_name(plot: pyqtgraph.PlotItem, checkbox: QtWidgets.QCheckBox, name: str) -> None: """Removes a curve from the given plot by the specified name. Args: plot (pyqtgraph.PlotItem): The plot from which to remove the curve. name (str): The name of the curve to remove. """ + # if checkbox.isChecked(): for item in plot.items: if isinstance(item, pg.PlotDataItem) and getattr(item, "opts", {}).get("name") == name: plot.removeItem(item) return + # else: + # return + @staticmethod def golden_ratio(num: int) -> list: """Calculate the golden ratio for a given number of angles. @@ -300,6 +336,7 @@ class BasicPlot(QtWidgets.QWidget): if __name__ == "__main__": import argparse + from bec_widgets.bec_dispatcher import bec_dispatcher from bec_widgets import ctrl_c @@ -312,11 +349,12 @@ if __name__ == "__main__": ) value = parser.parse_args() print(f"Plotting signals for: {', '.join(value.signals)}") - client = BECClient() - client.start() + client = bec_dispatcher.client + # client.start() app = QtWidgets.QApplication([]) ctrl_c.setup(app) plot = BasicPlot(y_value_list=value.signals) + bec_dispatcher.connect(plot) plot.show() - client.callbacks.register("scan_segment", plot, sync=False) + # client.callbacks.register("scan_segment", plot, sync=False) app.exec_() diff --git a/bec_widgets/line_plot.ui b/bec_widgets/line_plot.ui index 1945b642..0660ccf5 100644 --- a/bec_widgets/line_plot.ui +++ b/bec_widgets/line_plot.ui @@ -6,8 +6,8 @@ 0 0 - 713 - 294 + 737 + 303 @@ -15,39 +15,52 @@ - - - - - Debug - - - - - - - - - - - - Qt::ElideMiddle + + + Qt::Horizontal - - - Device + + false + + + + + + + Debug + + + + + + + + + + + Qt::ElideMiddle - - - - X - - - - - Y - - + + + Display + + + + + Device + + + + + X + + + + + Y + + + diff --git a/tests/test_line_plot.py b/tests/test_line_plot.py index 41506dac..a561cb34 100644 --- a/tests/test_line_plot.py +++ b/tests/test_line_plot.py @@ -143,7 +143,9 @@ def test_line_plot_mouse_moved(qtbot): f"Y_data: {y_data:>{string_cap}}", ] ) - with mock.patch.object(plot, "plot") as mock_plot: + with mock.patch.object( + plot, "plot" + ) as mock_plot: # TODO change test to simulate QTable instead of QLabel mock_plot.sceneBoundingRect.contains.return_value = True mock_plot.vb.mapSceneToView((20, 10)).x.return_value = 2.8 plot.mouse_moved((20, 10))