mirror of
https://github.com/bec-project/bec_widgets.git
synced 2026-06-10 23:28:49 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e747432096 | |||
| 641c2b3481 | |||
| eee86bdfa9 | |||
| 28546f4dfd |
@@ -429,10 +429,10 @@ class Crosshair(QObject):
|
||||
if event is None:
|
||||
return # nothing to do
|
||||
scene_pos = event[0] # SignalProxy bundle
|
||||
if not self.plot_item.vb.sceneBoundingRect().contains(scene_pos):
|
||||
return
|
||||
view_pos = self.plot_item.vb.mapSceneToView(scene_pos)
|
||||
x, y = view_pos.x(), view_pos.y()
|
||||
if not self._is_within_view_range(x, y):
|
||||
return
|
||||
|
||||
# Update cross‑hair visuals
|
||||
self.v_line.setPos(x)
|
||||
@@ -493,8 +493,12 @@ class Crosshair(QObject):
|
||||
if event.button() != Qt.MouseButton.LeftButton:
|
||||
return
|
||||
self.update_markers()
|
||||
if self.plot_item.vb.sceneBoundingRect().contains(event._scenePos):
|
||||
mouse_point = self.plot_item.vb.mapSceneToView(event._scenePos)
|
||||
scene_pos_getter = getattr(event, "scenePos", None)
|
||||
if not callable(scene_pos_getter):
|
||||
return
|
||||
scene_pos = scene_pos_getter()
|
||||
mouse_point = self.plot_item.vb.mapSceneToView(scene_pos)
|
||||
if self._is_within_view_range(mouse_point.x(), mouse_point.y()):
|
||||
x, y = mouse_point.x(), mouse_point.y()
|
||||
scaled_x, scaled_y = self.scale_emitted_coordinates(mouse_point.x(), mouse_point.y())
|
||||
self.crosshairClicked.emit((scaled_x, scaled_y))
|
||||
@@ -545,6 +549,10 @@ class Crosshair(QObject):
|
||||
else:
|
||||
continue
|
||||
|
||||
def _is_within_view_range(self, x: float, y: float) -> bool:
|
||||
x_range, y_range = self.plot_item.vb.viewRange()
|
||||
return min(x_range) <= x <= max(x_range) and min(y_range) <= y <= max(y_range)
|
||||
|
||||
def _get_transformed_position(
|
||||
self, x: float, y: float, transform: QTransform
|
||||
) -> tuple[QPointF, QPointF]:
|
||||
|
||||
+1
-1
@@ -23,7 +23,7 @@ dependencies = [
|
||||
"ophyd_devices~=1.29, >=1.29.1",
|
||||
"pydantic~=2.0",
|
||||
"pylsp-bec~=1.2",
|
||||
"pyqtgraph==0.13.7",
|
||||
"pyqtgraph~=0.14.0",
|
||||
"qtconsole~=5.5, >=5.5.1", # needed for jupyter console
|
||||
"qtmonaco~=0.8, >=0.8.1",
|
||||
"qtpy~=2.4",
|
||||
|
||||
@@ -14,6 +14,18 @@ from .conftest import create_widget
|
||||
# pylint: disable = redefined-outer-name
|
||||
|
||||
|
||||
class _FakeMouseClickEvent:
|
||||
def __init__(self, scene_pos: QPointF, button: Qt.MouseButton = Qt.MouseButton.LeftButton):
|
||||
self._scene_pos = scene_pos
|
||||
self._button = button
|
||||
|
||||
def button(self):
|
||||
return self._button
|
||||
|
||||
def scenePos(self):
|
||||
return self._scene_pos
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def plot_widget_with_crosshair(qtbot):
|
||||
widget = pg.PlotWidget()
|
||||
@@ -22,6 +34,7 @@ def plot_widget_with_crosshair(qtbot):
|
||||
|
||||
widget.plot(x=[1, 2, 3], y=[4, 5, 6], name="Curve 1")
|
||||
plot_item = widget.getPlotItem()
|
||||
plot_item.vb.setRange(xRange=(0, 4), yRange=(0, 10), padding=0)
|
||||
crosshair = Crosshair(plot_item=plot_item, precision=3)
|
||||
|
||||
yield crosshair, plot_item
|
||||
@@ -38,20 +51,17 @@ def image_widget_with_crosshair(qtbot):
|
||||
|
||||
widget.addItem(image_item)
|
||||
plot_item = widget.getPlotItem()
|
||||
plot_item.vb.setRange(xRange=(0, 100), yRange=(0, 100), padding=0)
|
||||
crosshair = Crosshair(plot_item=plot_item, precision=3)
|
||||
|
||||
yield crosshair, plot_item
|
||||
|
||||
|
||||
def test_mouse_moved_lines(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
|
||||
pos_in_view = QPointF(2, 5)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
|
||||
# Simulate mouse movement
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
|
||||
# Check that the vertical line is indeed at x=2
|
||||
assert np.isclose(crosshair.v_line.pos().x(), 2)
|
||||
@@ -59,7 +69,7 @@ def test_mouse_moved_lines(plot_widget_with_crosshair):
|
||||
|
||||
|
||||
def test_mouse_moved_signals(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
|
||||
emitted_values_1D = []
|
||||
|
||||
@@ -68,43 +78,40 @@ def test_mouse_moved_signals(plot_widget_with_crosshair):
|
||||
|
||||
crosshair.coordinatesChanged1D.connect(slot)
|
||||
|
||||
pos_in_view = QPointF(2, 5)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
|
||||
# Assert the expected behavior
|
||||
assert emitted_values_1D == [("Curve 1", 2, 5)]
|
||||
|
||||
|
||||
def test_mouse_moved_signals_outside(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
|
||||
# Create a slot that will store the emitted values as tuples
|
||||
emitted_values_1D = []
|
||||
emitted_positions = []
|
||||
|
||||
def slot(coordinates):
|
||||
emitted_values_1D.append(coordinates)
|
||||
|
||||
# Connect the signal to the custom slot
|
||||
crosshair.coordinatesChanged1D.connect(slot)
|
||||
crosshair.crosshairChanged.connect(emitted_positions.append)
|
||||
|
||||
# Simulate a mouse moved event at a specific position
|
||||
pos_in_view = QPointF(22, 55)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
# Call the mouse_moved method
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
emitted_positions.clear()
|
||||
emitted_values_1D.clear()
|
||||
crosshair.mouse_moved(manual_pos=(22, 55))
|
||||
|
||||
# Assert the expected behavior
|
||||
assert emitted_values_1D == []
|
||||
assert emitted_positions == []
|
||||
assert np.isclose(crosshair.v_line.pos().x(), 2)
|
||||
assert np.isclose(crosshair.h_line.pos().y(), 5)
|
||||
|
||||
|
||||
def test_mouse_moved_signals_2D(image_widget_with_crosshair):
|
||||
crosshair, plot_item = image_widget_with_crosshair
|
||||
image_item = plot_item.items[0]
|
||||
crosshair, _ = image_widget_with_crosshair
|
||||
|
||||
emitted_values_2D = []
|
||||
|
||||
@@ -113,17 +120,16 @@ def test_mouse_moved_signals_2D(image_widget_with_crosshair):
|
||||
|
||||
crosshair.coordinatesChanged2D.connect(slot)
|
||||
|
||||
pos_in_view = QPointF(21.0, 55.0)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair.mouse_moved(manual_pos=(21.0, 55.0))
|
||||
|
||||
assert emitted_values_2D == [("ImageItem", 21, 55)]
|
||||
|
||||
|
||||
def test_mouse_moved_signals_2D_outside(image_widget_with_crosshair):
|
||||
def test_mouse_moved_signals_2D_outside_image_bounds_clamps_inside_view_range(
|
||||
image_widget_with_crosshair,
|
||||
):
|
||||
crosshair, plot_item = image_widget_with_crosshair
|
||||
plot_item.vb.setRange(xRange=(0, 300), yRange=(0, 600), padding=0)
|
||||
|
||||
emitted_values_2D = []
|
||||
|
||||
@@ -132,23 +138,34 @@ def test_mouse_moved_signals_2D_outside(image_widget_with_crosshair):
|
||||
|
||||
crosshair.coordinatesChanged2D.connect(slot)
|
||||
|
||||
pos_in_view = QPointF(220.0, 555.0)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
crosshair.mouse_moved(manual_pos=(220.0, 555.0))
|
||||
|
||||
crosshair.mouse_moved(event_mock)
|
||||
assert emitted_values_2D == [("ImageItem", 99, 99)]
|
||||
|
||||
|
||||
def test_mouse_moved_signals_2D_outside_view_range_ignored(image_widget_with_crosshair):
|
||||
crosshair, _ = image_widget_with_crosshair
|
||||
|
||||
emitted_values_2D = []
|
||||
emitted_positions = []
|
||||
|
||||
crosshair.coordinatesChanged2D.connect(emitted_values_2D.append)
|
||||
crosshair.crosshairChanged.connect(emitted_positions.append)
|
||||
|
||||
crosshair.mouse_moved(manual_pos=(21.0, 55.0))
|
||||
emitted_positions.clear()
|
||||
emitted_values_2D.clear()
|
||||
crosshair.mouse_moved(manual_pos=(220.0, 555.0))
|
||||
|
||||
assert emitted_values_2D == []
|
||||
assert emitted_positions == []
|
||||
assert np.isclose(crosshair.v_line.pos().x(), 21.0)
|
||||
assert np.isclose(crosshair.h_line.pos().y(), 55.0)
|
||||
|
||||
|
||||
def test_marker_positions_after_mouse_move(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
|
||||
pos_in_view = QPointF(2, 5)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
|
||||
marker = crosshair.marker_moved_1d["Curve 1"]
|
||||
marker_x, marker_y = marker.getData()
|
||||
@@ -172,7 +189,7 @@ def test_scale_emitted_coordinates(plot_widget_with_crosshair):
|
||||
|
||||
|
||||
def test_crosshair_changed_signal(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
crosshair, _ = plot_widget_with_crosshair
|
||||
|
||||
emitted_positions = []
|
||||
|
||||
@@ -181,11 +198,7 @@ def test_crosshair_changed_signal(plot_widget_with_crosshair):
|
||||
|
||||
crosshair.crosshairChanged.connect(slot)
|
||||
|
||||
pos_in_view = QPointF(2, 5)
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
crosshair.mouse_moved(event_mock)
|
||||
crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
|
||||
x, y = emitted_positions[0]
|
||||
|
||||
@@ -193,33 +206,33 @@ def test_crosshair_changed_signal(plot_widget_with_crosshair):
|
||||
assert np.isclose(y, 5)
|
||||
|
||||
|
||||
def test_crosshair_clicked_signal(qtbot, plot_widget_with_crosshair):
|
||||
def test_crosshair_clicked_signal(plot_widget_with_crosshair):
|
||||
crosshair, plot_item = plot_widget_with_crosshair
|
||||
|
||||
emitted_positions = []
|
||||
emitted_view_positions = []
|
||||
|
||||
def slot(position):
|
||||
emitted_positions.append(position)
|
||||
|
||||
crosshair.crosshairClicked.connect(slot)
|
||||
crosshair.positionClicked.connect(emitted_view_positions.append)
|
||||
|
||||
x_data = 2
|
||||
y_data = 5
|
||||
crosshair.is_log_x = True
|
||||
crosshair.is_log_y = True
|
||||
plot_item.vb.setRange(xRange=(0, 1), yRange=(0, 1), padding=0)
|
||||
|
||||
# Map data coordinates to scene coordinates
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(QPointF(x_data, y_data))
|
||||
# Map scene coordinates to widget coordinates
|
||||
graphics_view = plot_item.vb.scene().views()[0]
|
||||
qtbot.waitExposed(graphics_view)
|
||||
pos_in_widget = graphics_view.mapFromScene(pos_in_scene)
|
||||
|
||||
# Simulate mouse click
|
||||
qtbot.mouseClick(graphics_view.viewport(), Qt.LeftButton, pos=pos_in_widget)
|
||||
known_view_point = QPointF(np.log10(2), np.log10(5))
|
||||
pos_in_scene = plot_item.vb.mapViewToScene(known_view_point)
|
||||
crosshair.mouse_clicked(_FakeMouseClickEvent(pos_in_scene))
|
||||
|
||||
x, y = emitted_positions[0]
|
||||
view_x, view_y = emitted_view_positions[0]
|
||||
|
||||
assert np.isclose(round(x, 1), 2)
|
||||
assert np.isclose(round(y, 1), 5)
|
||||
assert np.isclose(x, 2)
|
||||
assert np.isclose(y, 5)
|
||||
assert np.isclose(view_x, known_view_point.x())
|
||||
assert np.isclose(view_y, known_view_point.y())
|
||||
|
||||
|
||||
def test_update_coord_label_1D(plot_widget_with_crosshair):
|
||||
@@ -359,20 +372,17 @@ def test_ignore_invisible_curves_on_move(qtbot, mocked_client):
|
||||
c0 = wf.plot(x=[1, 2, 3], y=[1, 4, 9], name="Curve_0")
|
||||
c1 = wf.plot(x=[1, 2, 3], y=[2, 5, 10], name="Curve_1")
|
||||
wf.hook_crosshair()
|
||||
wf.crosshair.plot_item.vb.setRange(xRange=(0, 4), yRange=(0, 10), padding=0)
|
||||
|
||||
# # Simulate a mouse move at (2,5)
|
||||
pos_in_view = QPointF(2, 5)
|
||||
pos_in_scene = wf.plot_item.vb.mapViewToScene(pos_in_view)
|
||||
event_mock = [pos_in_scene]
|
||||
|
||||
# 1) Both curves visible: expect markers for both
|
||||
wf.crosshair.clear_markers()
|
||||
wf.crosshair.mouse_moved(event_mock)
|
||||
wf.crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
assert set(wf.crosshair.marker_moved_1d.keys()) == {"Curve_0", "Curve_1"}
|
||||
|
||||
# 2) Hide Curve B and repeat: only Curve_0 should remain
|
||||
c1.setVisible(False)
|
||||
wf.crosshair.clear_markers()
|
||||
wf.crosshair.mouse_moved(event_mock)
|
||||
wf.crosshair.mouse_moved(manual_pos=(2, 5))
|
||||
qtbot.wait(200)
|
||||
assert set(wf.crosshair.marker_moved_1d.keys()) == {"Curve_0"}
|
||||
|
||||
Reference in New Issue
Block a user