Skip to content

Commit 297e6fa

Browse files
authored
disable crosshair in titled plot (#39242)
1 parent bc298b2 commit 297e6fa

File tree

4 files changed

+119
-1
lines changed

4 files changed

+119
-1
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
- A crosshair toggle option has been added in mantidplots on the top right of the toolbar area. THe crosshair button is disabled (invisible) in titled plots.

qt/applications/workbench/workbench/plotting/figuremanager.py

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,7 @@ def __init__(self, canvas, num):
232232
self.toolbar.sig_waterfall_conversion.connect(self.update_toolbar_waterfall_plot)
233233
self.toolbar.sig_change_line_collection_colour_triggered.connect(self.change_line_collection_colour)
234234
self.toolbar.sig_hide_plot_triggered.connect(self.hide_plot)
235+
self.toolbar.sig_crosshair_toggle_triggered.connect(self.crosshair_toggle)
235236
self.toolbar.setFloatable(False)
236237
tbs_height = self.toolbar.sizeHint().height()
237238
else:
@@ -603,6 +604,41 @@ def _reverse_axis_lines(ax):
603604
line.remove()
604605
ax.add_line(line)
605606

607+
def crosshair_toggle(self, on):
608+
cid = self.canvas.mpl_connect("motion_notify_event", self.crosshair)
609+
if not on:
610+
self.canvas.mpl_disconnect(cid)
611+
612+
def crosshair(self, event):
613+
axes = self.canvas.figure.gca()
614+
615+
# create a crosshair made from horizontal and verticle lines.
616+
self.horizontal_line = axes.axhline(color="r", lw=1.0, ls="-")
617+
self.vertical_line = axes.axvline(color="r", lw=1.0, ls="-")
618+
619+
def set_cross_hair_visible(visible):
620+
need_redraw = self.horizontal_line.get_visible() != visible
621+
self.horizontal_line.set_visible(visible)
622+
self.vertical_line.set_visible(visible)
623+
return need_redraw
624+
625+
# if event is out-of-bound we update
626+
if not event.inaxes:
627+
need_redraw = set_cross_hair_visible(False)
628+
if need_redraw:
629+
axes.figure.canvas.draw()
630+
631+
else:
632+
set_cross_hair_visible(True)
633+
x, y = event.xdata, event.ydata
634+
self.horizontal_line.set_ydata([y])
635+
self.vertical_line.set_xdata([x])
636+
self.canvas.draw()
637+
638+
# after update we remove
639+
self.horizontal_line.remove()
640+
self.vertical_line.remove()
641+
606642

607643
# -----------------------------------------------------------------------------
608644
# Figure control

qt/applications/workbench/workbench/plotting/test/test_toolbar.py

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import unittest
1010
from unittest.mock import patch
11+
import numpy as np
1112

1213
import matplotlib
1314

@@ -19,7 +20,7 @@
1920
from workbench.plotting.figuremanager import MantidFigureCanvas, FigureManagerWorkbench
2021
from workbench.plotting.toolbar import WorkbenchNavigationToolbar
2122
from mantid.plots.plotfunctions import plot
22-
from mantid.simpleapi import CreateSampleWorkspace, CreateMDHistoWorkspace
23+
from mantid.simpleapi import CreateSampleWorkspace, CreateMDHistoWorkspace, Load
2324

2425

2526
@start_qapplication
@@ -117,6 +118,39 @@ def test_button_checked_for_plot_with_grid(self, mock_qappthread):
117118
# Grid button should be ON because we enabled the grid.
118119
self.assertTrue(self._is_grid_button_checked(fig))
119120

121+
@patch("workbench.plotting.figuremanager.QAppThreadCall")
122+
def test_button_checked_for_plot_with_no_crosshair(self, mock_qappthread):
123+
mock_qappthread.return_value = mock_qappthread
124+
125+
fig, axes = plt.subplots(subplot_kw={"projection": "mantid"})
126+
axes.plot([-10, 10], [1, 2])
127+
# crosshair button should be OFF because we have not enabled the crosshair.
128+
self.assertFalse(self._is_crosshair_button_checked(fig))
129+
# crosshair button should be visible when there is only 1 axis
130+
self.assertTrue(self._is_crosshair_button_visible(fig))
131+
132+
@patch("workbench.plotting.figuremanager.QAppThreadCall")
133+
def test_button_hiden_for_tiled_plots(self, mock_qappthread):
134+
mock_qappthread.return_value = mock_qappthread
135+
136+
fig, axes = plt.subplots(2)
137+
axes[0].plot([-10, 10], [1, 2])
138+
axes[1].plot([3, 2, 1], [1, 2, 3])
139+
# crosshair button should be hidden because this is a tiled plot
140+
self.assertFalse(self._is_crosshair_button_visible(fig))
141+
self.assertFalse(self._is_crosshair_button_checked(fig))
142+
143+
@patch("workbench.plotting.figuremanager.QAppThreadCall")
144+
def test_button_enabled_for_contour_plots(self, mock_qappthread):
145+
mock_qappthread.return_value = mock_qappthread
146+
data = Load("SANSLOQCan2D.nxs")
147+
fig, axes = plt.subplots(subplot_kw={"projection": "mantid"})
148+
axes.imshow(data, origin="lower", cmap="viridis", aspect="auto")
149+
axes.contour(data, levels=np.linspace(10, 60, 6), colors="yellow", alpha=0.5)
150+
# crosshair button should be visible because this is a contour plot
151+
self.assertTrue(self._is_crosshair_button_visible(fig))
152+
self.assertFalse(self._is_crosshair_button_checked(fig))
153+
120154
@patch("workbench.plotting.figuremanager.QAppThreadCall")
121155
def test_button_checked_for_plot_with_grid_using_kwargs(self, mock_qappthread):
122156
mock_qappthread.return_value = mock_qappthread
@@ -266,6 +300,32 @@ def _is_button_enabled(cls, fig, button):
266300
fig_manager.toolbar.set_buttons_visibility(fig)
267301
return fig_manager.toolbar._actions[button].isEnabled()
268302

303+
@classmethod
304+
def _is_crosshair_button_checked(cls, fig):
305+
"""
306+
Create the figure manager and check whether its toolbar is toggled on or off for the given figure.
307+
We have to explicitly call set_button_visibility() here, which would otherwise be called within the show()
308+
function.
309+
"""
310+
canvas = MantidFigureCanvas(fig)
311+
fig_manager = FigureManagerWorkbench(canvas, 1)
312+
# This is only called when show() is called on the figure manager, so we have to manually call it here.
313+
fig_manager.toolbar.set_buttons_visibility(fig)
314+
return fig_manager.toolbar._actions["toggle_crosshair"].isChecked()
315+
316+
@classmethod
317+
def _is_crosshair_button_visible(cls, fig):
318+
"""
319+
Create the figure manager and check whether its toolbar is visible for the given figure.
320+
We have to explicitly call set_button_visibility() here, which would otherwise be called within the show()
321+
function.
322+
"""
323+
canvas = MantidFigureCanvas(fig)
324+
fig_manager = FigureManagerWorkbench(canvas, 1)
325+
# This is only called when show() is called on the figure manager, so we have to manually call it here.
326+
fig_manager.toolbar.set_buttons_visibility(fig)
327+
return fig_manager.toolbar._actions["toggle_crosshair"].isVisible()
328+
269329

270330
if __name__ == "__main__":
271331
unittest.main()

qt/applications/workbench/workbench/plotting/toolbar.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ def _create_script_action(self, text, tooltip_text, mdi_icon, *args):
3535
class WorkbenchNavigationToolbar(MantidNavigationToolbar):
3636
sig_home_clicked = QtCore.Signal()
3737
sig_grid_toggle_triggered = QtCore.Signal(bool)
38+
sig_crosshair_toggle_triggered = QtCore.Signal(bool)
3839
sig_active_triggered = QtCore.Signal()
3940
sig_hold_triggered = QtCore.Signal()
4041
sig_toggle_fit_triggered = QtCore.Signal()
@@ -84,6 +85,7 @@ class WorkbenchNavigationToolbar(MantidNavigationToolbar):
8485
MantidNavigationTool("Fill Area", "Fill area under curves", "mdi.format-color-fill", "waterfall_fill_area", None),
8586
MantidStandardNavigationTools.SEPARATOR,
8687
MantidNavigationTool("Help", "Open plotting help documentation", "mdi.help", "launch_plot_help", None),
88+
MantidNavigationTool("Crosshair", "Toggle crosshair", "mdi.plus", "toggle_crosshair", False),
8789
MantidNavigationTool("Hide", "Hide the plot", "mdi.eye", "hide_plot", None),
8890
)
8991

@@ -94,6 +96,20 @@ def __init__(self, canvas, parent, coordinates=True):
9496
dpi_ratio = QtWidgets.QApplication.instance().desktop().physicalDpiX() / 100
9597
self.setIconSize(QtCore.QSize(int(24 * dpi_ratio), int(24 * dpi_ratio)))
9698

99+
def toggle_crosshair(self, enable=None):
100+
if enable is None:
101+
enable = self._actions["toggle_crosshair"].isChecked()
102+
else:
103+
self._actions["toggle_crosshair"].setChecked(enable)
104+
self.sig_crosshair_toggle_triggered.emit(enable)
105+
106+
def set_crosshair_enabled(self, on):
107+
action = self._actions["toggle_crosshair"]
108+
action.setEnabled(on)
109+
action.setVisible(on)
110+
# Show/hide the separator between this button and help button / waterfall options
111+
self.toggle_separator_visibility(action, on)
112+
97113
def hide_plot(self):
98114
self.sig_hide_plot_triggered.emit()
99115

@@ -217,6 +233,11 @@ def set_buttons_visibility(self, fig):
217233
if figure_type(fig) not in [FigureType.Line, FigureType.Errorbar] or len(fig.get_axes()) > 1:
218234
self.set_fit_enabled(False)
219235
self.set_superplot_enabled(False)
236+
237+
# disable crosshair in tiled plots but keep it enabled in color contour plot
238+
if len(fig.get_axes()) > 1 and figure_type(fig) not in [FigureType.Contour]:
239+
self.set_crosshair_enabled(False)
240+
220241
for ax in fig.get_axes():
221242
for artist in ax.get_lines():
222243
try:

0 commit comments

Comments
 (0)