mirror of
https://github.com/bec-project/bec_widgets.git
synced 2025-07-14 03:31:50 +02:00
fix: crosshair handles dynamic changes of number of curves in 1D plot
This commit is contained in:
@ -26,59 +26,78 @@ class Crosshair(QObject):
|
|||||||
)
|
)
|
||||||
self.plot_item.scene().sigMouseClicked.connect(self.mouse_clicked)
|
self.plot_item.scene().sigMouseClicked.connect(self.mouse_clicked)
|
||||||
|
|
||||||
# Add marker for clicked and selected point
|
# Initialize markers
|
||||||
data = self.get_data()
|
self.marker_moved_1d = []
|
||||||
if isinstance(data, list): # 1D plot
|
self.marker_clicked_1d = []
|
||||||
num_curves = len(data)
|
self.marker_2d = None
|
||||||
self.marker_moved_1d = []
|
self.update_markers()
|
||||||
self.marker_clicked_1d = []
|
|
||||||
for i in range(num_curves):
|
def update_markers(self):
|
||||||
color = plot_item.listDataItems()[i].opts["pen"].color()
|
# 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(
|
marker_moved = pg.ScatterPlotItem(
|
||||||
size=10, pen=pg.mkPen(color), brush=pg.mkBrush(None)
|
size=10, pen=pg.mkPen(color), brush=pg.mkBrush(None)
|
||||||
) # Hollow
|
)
|
||||||
marker_clicked = pg.ScatterPlotItem(
|
marker_clicked = pg.ScatterPlotItem(
|
||||||
size=10, pen=pg.mkPen(None), brush=pg.mkBrush(color)
|
size=10, pen=pg.mkPen(None), brush=pg.mkBrush(color)
|
||||||
) # Full
|
)
|
||||||
self.marker_moved_1d.append(marker_moved)
|
self.marker_moved_1d.append(marker_moved)
|
||||||
self.marker_clicked_1d.append(marker_clicked)
|
self.marker_clicked_1d.append(marker_clicked)
|
||||||
self.plot_item.addItem(marker_moved)
|
self.plot_item.addItem(marker_moved)
|
||||||
self.plot_item.addItem(marker_clicked)
|
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
|
elif isinstance(item, pg.ImageItem): # 2D plot
|
||||||
return item.image, None
|
self.marker_2d = pg.ROI(
|
||||||
|
[0, 0], size=[1, 1], pen=pg.mkPen("r", width=2), movable=False
|
||||||
return curves
|
)
|
||||||
|
self.plot_item.addItem(self.marker_2d)
|
||||||
# if curves:
|
|
||||||
# return curves
|
|
||||||
# else:
|
|
||||||
# return None
|
|
||||||
|
|
||||||
def snap_to_data(self, x, y):
|
def snap_to_data(self, x, y):
|
||||||
data = self.get_data()
|
y_values_1d = []
|
||||||
# if data is None: #TODO hadle if data are None
|
x_values_1d = []
|
||||||
# return x, y
|
image_2d = None
|
||||||
|
|
||||||
if isinstance(data, list): # 1D plot
|
# Iterate through items in the plot
|
||||||
y_values = []
|
for item in self.plot_item.items:
|
||||||
for x_data, y_data in data:
|
if isinstance(item, pg.PlotDataItem): # 1D plot
|
||||||
closest_x, closest_y = self.closest_x_y_value(x, x_data, y_data)
|
x_data, y_data = item.xData, item.yData
|
||||||
y_values.append(closest_y)
|
if x_data is not None and y_data is not None:
|
||||||
return x, y_values
|
if self.is_log_x:
|
||||||
elif isinstance(data[0], np.ndarray): # 2D plot
|
min_x_data = np.min(x_data[x_data > 0])
|
||||||
x_idx = int(np.clip(x, 0, data[0].shape[0] - 1))
|
else:
|
||||||
y_idx = int(np.clip(y, 0, data[0].shape[1] - 1))
|
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_idx, y_idx
|
||||||
return x, y
|
|
||||||
|
return None, None
|
||||||
|
|
||||||
def closest_x_y_value(self, input_value, list_x, list_y):
|
def closest_x_y_value(self, input_value, list_x, list_y):
|
||||||
"""
|
"""
|
||||||
@ -111,40 +130,54 @@ class Crosshair(QObject):
|
|||||||
y = 10**y
|
y = 10**y
|
||||||
x, y_values = self.snap_to_data(x, y)
|
x, y_values = self.snap_to_data(x, y)
|
||||||
|
|
||||||
if isinstance(y_values, list): # 1D plot
|
for item in self.plot_item.items:
|
||||||
self.coordinatesChanged1D.emit(
|
if isinstance(item, pg.PlotDataItem):
|
||||||
round(x, self.precision), [round(y_val, self.precision) for y_val in y_values]
|
if x is None or all(v is None for v in y_values):
|
||||||
)
|
return
|
||||||
for i, y_val in enumerate(y_values):
|
self.coordinatesChanged1D.emit(
|
||||||
self.marker_moved_1d[i].setData(
|
round(x, self.precision),
|
||||||
[x if not self.is_log_x else np.log10(x)],
|
[round(y_val, self.precision) for y_val in y_values],
|
||||||
[y_val if not self.is_log_y else np.log10(y_val)],
|
|
||||||
)
|
)
|
||||||
else: # 2D plot
|
for i, y_val in enumerate(y_values):
|
||||||
self.coordinatesChanged2D.emit(x, 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):
|
def mouse_clicked(self, event):
|
||||||
self.check_log()
|
self.check_log()
|
||||||
if self.plot_item.vb.sceneBoundingRect().contains(event._scenePos):
|
if self.plot_item.vb.sceneBoundingRect().contains(event._scenePos):
|
||||||
mouse_point = self.plot_item.vb.mapSceneToView(event._scenePos)
|
mouse_point = self.plot_item.vb.mapSceneToView(event._scenePos)
|
||||||
x, y = mouse_point.x(), mouse_point.y()
|
x, y = mouse_point.x(), mouse_point.y()
|
||||||
|
|
||||||
if self.is_log_x:
|
if self.is_log_x:
|
||||||
x = 10**x
|
x = 10**x
|
||||||
if self.is_log_y:
|
if self.is_log_y:
|
||||||
y = 10**y
|
y = 10**y
|
||||||
x, y_values = self.snap_to_data(x, y)
|
x, y_values = self.snap_to_data(x, y)
|
||||||
if isinstance(y_values, list): # 1D plot
|
|
||||||
self.coordinatesClicked1D.emit(
|
for item in self.plot_item.items:
|
||||||
round(x, self.precision), [round(y_val, self.precision) for y_val in y_values]
|
if isinstance(item, pg.PlotDataItem):
|
||||||
)
|
if x is None or all(v is None for v in y_values):
|
||||||
for i, y_val in enumerate(y_values):
|
return
|
||||||
self.marker_clicked_1d[i].setData(
|
self.coordinatesClicked1D.emit(
|
||||||
[x if not self.is_log_x else np.log10(x)],
|
round(x, self.precision),
|
||||||
[y_val if not self.is_log_y else np.log10(y_val)],
|
[round(y_val, self.precision) for y_val in y_values],
|
||||||
)
|
)
|
||||||
else: # 2D plot
|
for i, y_val in enumerate(y_values):
|
||||||
self.coordinatesClicked2D.emit(x, y_values)
|
self.marker_clicked_1d[i].setData(
|
||||||
self.marker_2d.setPos([x, y_values])
|
[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):
|
def check_log(self):
|
||||||
"""
|
"""
|
||||||
|
@ -8,6 +8,7 @@ from PyQt5.QtWidgets import (
|
|||||||
QHBoxLayout,
|
QHBoxLayout,
|
||||||
QTableWidget,
|
QTableWidget,
|
||||||
QTableWidgetItem,
|
QTableWidgetItem,
|
||||||
|
QSpinBox,
|
||||||
)
|
)
|
||||||
from pyqtgraph import mkPen
|
from pyqtgraph import mkPen
|
||||||
from pyqtgraph.Qt import QtCore
|
from pyqtgraph.Qt import QtCore
|
||||||
@ -40,25 +41,15 @@ class ExampleApp(QWidget):
|
|||||||
|
|
||||||
# same convention as in line_plot.py
|
# same convention as in line_plot.py
|
||||||
self.y_value_list = [
|
self.y_value_list = [
|
||||||
np.sin(self.x_data),
|
gauss(self.x_data, 1, 1),
|
||||||
np.cos(self.x_data),
|
gauss(self.x_data, 1.5, 3),
|
||||||
np.sin(2 * self.x_data),
|
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
|
] # 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)", "Abs(Sine)", "Abs(Cosine)", "Abs(Sine2x)"]
|
||||||
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.curves = []
|
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
|
# 2D Plot
|
||||||
@ -78,24 +69,13 @@ class ExampleApp(QWidget):
|
|||||||
self.table.resizeColumnsToContents()
|
self.table.resizeColumnsToContents()
|
||||||
|
|
||||||
##########################
|
##########################
|
||||||
# Signals & Cross-hairs
|
# Spinbox for N curves
|
||||||
##########################
|
##########################
|
||||||
# 1D
|
self.spin_box = QSpinBox()
|
||||||
self.crosshair_1d = Crosshair(self.plot_item_1d, precision=4)
|
self.spin_box.setMinimum(0)
|
||||||
self.crosshair_1d.coordinatesChanged1D.connect(
|
self.spin_box.setMaximum(len(self.y_value_list))
|
||||||
lambda x, y: self.update_table(self.table, x, y, column=0)
|
self.spin_box.setValue(2)
|
||||||
)
|
self.spin_box.valueChanged.connect(lambda: self.update_curves(self.spin_box.value()))
|
||||||
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}")
|
|
||||||
)
|
|
||||||
|
|
||||||
##########################
|
##########################
|
||||||
# Adding widgets to layout
|
# Adding widgets to layout
|
||||||
@ -105,6 +85,12 @@ class ExampleApp(QWidget):
|
|||||||
self.column1 = QVBoxLayout()
|
self.column1 = QVBoxLayout()
|
||||||
self.layout.addLayout(self.column1)
|
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
|
# label
|
||||||
self.clicked_label_1d = QLabel("Clicked Coordinates (1D):")
|
self.clicked_label_1d = QLabel("Clicked Coordinates (1D):")
|
||||||
self.column1.addWidget(self.clicked_label_1d)
|
self.column1.addWidget(self.clicked_label_1d)
|
||||||
@ -128,12 +114,53 @@ class ExampleApp(QWidget):
|
|||||||
# 2D plot
|
# 2D plot
|
||||||
self.column2.addWidget(self.plot_widget_2d)
|
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):
|
def update_table(self, table_widget, x, y_values, column):
|
||||||
"""Update the table with the new coordinates"""
|
"""Update the table with the new coordinates"""
|
||||||
for i, y in enumerate(y_values):
|
for i, y in enumerate(y_values):
|
||||||
table_widget.setItem(i, column, QTableWidgetItem(f"({x}, {y})"))
|
table_widget.setItem(i, column, QTableWidgetItem(f"({x}, {y})"))
|
||||||
table_widget.resizeColumnsToContents()
|
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__":
|
if __name__ == "__main__":
|
||||||
app = QApplication([])
|
app = QApplication([])
|
||||||
|
Reference in New Issue
Block a user