diff --git a/bec_widgets/qt_utils/crosshair.py b/bec_widgets/qt_utils/crosshair.py index 1185da7b..17cc1f1b 100644 --- a/bec_widgets/qt_utils/crosshair.py +++ b/bec_widgets/qt_utils/crosshair.py @@ -26,59 +26,78 @@ class Crosshair(QObject): ) self.plot_item.scene().sigMouseClicked.connect(self.mouse_clicked) - # Add marker for clicked and selected point - data = self.get_data() - if isinstance(data, list): # 1D plot - num_curves = len(data) - self.marker_moved_1d = [] - self.marker_clicked_1d = [] - for i in range(num_curves): - color = plot_item.listDataItems()[i].opts["pen"].color() + # Initialize markers + self.marker_moved_1d = [] + self.marker_clicked_1d = [] + self.marker_2d = None + self.update_markers() + + def update_markers(self): + # Clear existing markers + for marker in self.marker_moved_1d + self.marker_clicked_1d: + self.plot_item.removeItem(marker) + if self.marker_2d: + self.plot_item.removeItem(self.marker_2d) + + # Create new markers + self.marker_moved_1d = [] + self.marker_clicked_1d = [] + self.marker_2d = None + for item in self.plot_item.items: + if isinstance(item, pg.PlotDataItem): # 1D plot + color = item.opts["pen"].color() marker_moved = pg.ScatterPlotItem( size=10, pen=pg.mkPen(color), brush=pg.mkBrush(None) - ) # Hollow + ) marker_clicked = pg.ScatterPlotItem( size=10, pen=pg.mkPen(None), brush=pg.mkBrush(color) - ) # Full + ) self.marker_moved_1d.append(marker_moved) self.marker_clicked_1d.append(marker_clicked) self.plot_item.addItem(marker_moved) self.plot_item.addItem(marker_clicked) - else: # 2D plot - self.marker_2d = pg.ROI([0, 0], size=[1, 1], pen=pg.mkPen("r", width=2), movable=False) - self.plot_item.addItem(self.marker_2d) - - def get_data(self): - curves = [] - for item in self.plot_item.items: - if isinstance(item, pg.PlotDataItem): # 1D plot - curves.append((item.xData, item.yData)) elif isinstance(item, pg.ImageItem): # 2D plot - return item.image, None - - return curves - - # if curves: - # return curves - # else: - # return None + self.marker_2d = pg.ROI( + [0, 0], size=[1, 1], pen=pg.mkPen("r", width=2), movable=False + ) + self.plot_item.addItem(self.marker_2d) def snap_to_data(self, x, y): - data = self.get_data() - # if data is None: #TODO hadle if data are None - # return x, y + y_values_1d = [] + x_values_1d = [] + image_2d = None - if isinstance(data, list): # 1D plot - y_values = [] - for x_data, y_data in data: - closest_x, closest_y = self.closest_x_y_value(x, x_data, y_data) - y_values.append(closest_y) - return x, y_values - elif isinstance(data[0], np.ndarray): # 2D plot - x_idx = int(np.clip(x, 0, data[0].shape[0] - 1)) - y_idx = int(np.clip(y, 0, data[0].shape[1] - 1)) + # Iterate through items in the plot + for item in self.plot_item.items: + if isinstance(item, pg.PlotDataItem): # 1D plot + x_data, y_data = item.xData, item.yData + if x_data is not None and y_data is not None: + if self.is_log_x: + min_x_data = np.min(x_data[x_data > 0]) + else: + min_x_data = np.min(x_data) + max_x_data = np.max(x_data) + if x < min_x_data or x > max_x_data: + return None, None + closest_x, closest_y = self.closest_x_y_value(x, x_data, y_data) + y_values_1d.append(closest_y) + x_values_1d.append(closest_x) + elif isinstance(item, pg.ImageItem): # 2D plot + image_2d = item.image + + # Handle 1D plot + if y_values_1d: + if all(v is None for v in x_values_1d) or all(v is None for v in y_values_1d): + return None, None + return x, y_values_1d + + # Handle 2D plot + if image_2d is not None: + x_idx = int(np.clip(x, 0, image_2d.shape[0] - 1)) + y_idx = int(np.clip(y, 0, image_2d.shape[1] - 1)) return x_idx, y_idx - return x, y + + return None, None def closest_x_y_value(self, input_value, list_x, list_y): """ @@ -111,40 +130,54 @@ class Crosshair(QObject): y = 10**y x, y_values = self.snap_to_data(x, y) - if isinstance(y_values, list): # 1D plot - self.coordinatesChanged1D.emit( - round(x, self.precision), [round(y_val, self.precision) for y_val in y_values] - ) - for i, y_val in enumerate(y_values): - self.marker_moved_1d[i].setData( - [x if not self.is_log_x else np.log10(x)], - [y_val if not self.is_log_y else np.log10(y_val)], + for item in self.plot_item.items: + if isinstance(item, pg.PlotDataItem): + if x is None or all(v is None for v in y_values): + return + self.coordinatesChanged1D.emit( + round(x, self.precision), + [round(y_val, self.precision) for y_val in y_values], ) - else: # 2D plot - self.coordinatesChanged2D.emit(x, y_values) + for i, y_val in enumerate(y_values): + self.marker_moved_1d[i].setData( + [x if not self.is_log_x else np.log10(x)], + [y_val if not self.is_log_y else np.log10(y_val)], + ) + elif isinstance(item, pg.ImageItem): + if x is None or y_values is None: + return + self.coordinatesChanged2D.emit(x, y_values) def mouse_clicked(self, event): self.check_log() if self.plot_item.vb.sceneBoundingRect().contains(event._scenePos): mouse_point = self.plot_item.vb.mapSceneToView(event._scenePos) x, y = mouse_point.x(), mouse_point.y() + if self.is_log_x: x = 10**x if self.is_log_y: y = 10**y x, y_values = self.snap_to_data(x, y) - if isinstance(y_values, list): # 1D plot - self.coordinatesClicked1D.emit( - round(x, self.precision), [round(y_val, self.precision) for y_val in y_values] - ) - for i, y_val in enumerate(y_values): - self.marker_clicked_1d[i].setData( - [x if not self.is_log_x else np.log10(x)], - [y_val if not self.is_log_y else np.log10(y_val)], + + for item in self.plot_item.items: + if isinstance(item, pg.PlotDataItem): + if x is None or all(v is None for v in y_values): + return + self.coordinatesClicked1D.emit( + round(x, self.precision), + [round(y_val, self.precision) for y_val in y_values], ) - else: # 2D plot - self.coordinatesClicked2D.emit(x, y_values) - self.marker_2d.setPos([x, y_values]) + for i, y_val in enumerate(y_values): + self.marker_clicked_1d[i].setData( + [x if not self.is_log_x else np.log10(x)], + [y_val if not self.is_log_y else np.log10(y_val)], + ) + elif isinstance(item, pg.ImageItem): + if x is None or y_values is None: + return + self.coordinatesClicked2D.emit(x, y_values) + self.marker_2d.setPos([x, y_values]) def check_log(self): """ diff --git a/bec_widgets/qt_utils/utils_example.py b/bec_widgets/qt_utils/utils_example.py index cca82215..f15ed110 100644 --- a/bec_widgets/qt_utils/utils_example.py +++ b/bec_widgets/qt_utils/utils_example.py @@ -8,6 +8,7 @@ from PyQt5.QtWidgets import ( QHBoxLayout, QTableWidget, QTableWidgetItem, + QSpinBox, ) from pyqtgraph import mkPen from pyqtgraph.Qt import QtCore @@ -40,25 +41,15 @@ class ExampleApp(QWidget): # same convention as in line_plot.py self.y_value_list = [ - np.sin(self.x_data), - np.cos(self.x_data), - np.sin(2 * self.x_data), + gauss(self.x_data, 1, 1), + gauss(self.x_data, 1.5, 3), + abs(np.sin(self.x_data)), + abs(np.cos(self.x_data)), + abs(np.sin(2 * self.x_data)), ] # List of y-values for multiple curves - self.y_value_list = [gauss(self.x_data, 1, 1), gauss(self.x_data, 1.5, 3)] - self.curve_names = ["Gauss(1,1)", "Gauss(1.5,3)"] # ,"Sine", "Cosine", "Sine2x"] - - # Curves - color_list = ["#384c6b", "#e28a2b", "#5E3023", "#e41a1c", "#984e83", "#4daf4a"] - self.plot_item_1d.addLegend() + self.curve_names = ["Gauss(1,1)", "Gauss(1.5,3)", "Abs(Sine)", "Abs(Cosine)", "Abs(Sine2x)"] self.curves = [] - for ii, y_value in enumerate(self.y_value_list): - pen = mkPen(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) - curve = pg.PlotDataItem( - self.x_data, y_value, pen=pen, skipFiniteCheck=True, name=self.curve_names[ii] - ) - self.plot_item_1d.addItem(curve) - self.curves.append(curve) ########################## # 2D Plot @@ -78,24 +69,13 @@ class ExampleApp(QWidget): self.table.resizeColumnsToContents() ########################## - # Signals & Cross-hairs + # Spinbox for N curves ########################## - # 1D - self.crosshair_1d = Crosshair(self.plot_item_1d, precision=4) - self.crosshair_1d.coordinatesChanged1D.connect( - lambda x, y: self.update_table(self.table, x, y, column=0) - ) - self.crosshair_1d.coordinatesClicked1D.connect( - lambda x, y: self.update_table(self.table, x, y, column=1) - ) - # 2D - self.crosshair_2d = Crosshair(self.plot_item_2d) - self.crosshair_2d.coordinatesChanged2D.connect( - lambda x, y: self.moved_label_2d.setText(f"Mouse Moved Coordinates (2D): x={x}, y={y}") - ) - self.crosshair_2d.coordinatesClicked2D.connect( - lambda x, y: self.clicked_label_2d.setText(f"Clicked Coordinates (2D): x={x}, y={y}") - ) + self.spin_box = QSpinBox() + self.spin_box.setMinimum(0) + self.spin_box.setMaximum(len(self.y_value_list)) + self.spin_box.setValue(2) + self.spin_box.valueChanged.connect(lambda: self.update_curves(self.spin_box.value())) ########################## # Adding widgets to layout @@ -105,6 +85,12 @@ class ExampleApp(QWidget): self.column1 = QVBoxLayout() self.layout.addLayout(self.column1) + # SpinBox + self.spin_row = QHBoxLayout() + self.column1.addLayout(self.spin_row) + self.spin_row.addWidget(QLabel("Number of curves:")) + self.spin_row.addWidget(self.spin_box) + # label self.clicked_label_1d = QLabel("Clicked Coordinates (1D):") self.column1.addWidget(self.clicked_label_1d) @@ -128,12 +114,53 @@ class ExampleApp(QWidget): # 2D plot self.column2.addWidget(self.plot_widget_2d) + self.update_curves(2) # just Gaussian curves + + def hook_crosshair(self): + self.crosshair_1d = Crosshair(self.plot_item_1d, precision=10) + self.crosshair_1d.coordinatesChanged1D.connect( + lambda x, y: self.update_table(self.table, x, y, column=0) + ) + self.crosshair_1d.coordinatesClicked1D.connect( + lambda x, y: self.update_table(self.table, x, y, column=1) + ) + # 2D + self.crosshair_2d = Crosshair(self.plot_item_2d) + self.crosshair_2d.coordinatesChanged2D.connect( + lambda x, y: self.moved_label_2d.setText(f"Mouse Moved Coordinates (2D): x={x}, y={y}") + ) + self.crosshair_2d.coordinatesClicked2D.connect( + lambda x, y: self.clicked_label_2d.setText(f"Clicked Coordinates (2D): x={x}, y={y}") + ) + def update_table(self, table_widget, x, y_values, column): """Update the table with the new coordinates""" for i, y in enumerate(y_values): table_widget.setItem(i, column, QTableWidgetItem(f"({x}, {y})")) table_widget.resizeColumnsToContents() + def update_curves(self, num_curves): + """Update the number of curves""" + + self.plot_item_1d.clear() + + # Curves + color_list = ["#384c6b", "#e28a2b", "#5E3023", "#e41a1c", "#984e83", "#4daf4a"] + self.plot_item_1d.addLegend() + self.curves = [] + + y_value_list = self.y_value_list[:num_curves] + + for ii, y_value in enumerate(y_value_list): + pen = mkPen(color=color_list[ii], width=2, style=QtCore.Qt.DashLine) + curve = pg.PlotDataItem( + self.x_data, y_value, pen=pen, skipFiniteCheck=True, name=self.curve_names[ii] + ) + self.plot_item_1d.addItem(curve) + self.curves.append(curve) + + self.hook_crosshair() + if __name__ == "__main__": app = QApplication([])