Skip to content

Commit 8c45ae0

Browse files
authored
Crosshair ornl (#39268)
### Description of work copy of PR [39242](#39242) into ORNL next #### Summary of work <!-- Please provide a short, high level description of the work that was done. --> <!-- Why has this work been done? If there is no linked issue please provide appropriate context for this work. #### Purpose of work This can be removed if a github issue is referenced below --> Fixes #xxxx. <!-- and fix #xxxx or close #xxxx xor resolves #xxxx. One line per issue fixed. --> <!-- alternative *There is no associated issue.* --> <!-- If the original issue was raised by a user they should be named here. Do not leak email addresses **Report to:** [user name] --> #### Further detail of work <!-- Please provide a more detailed description of the work that has been undertaken. --> ### To test: <!-- Instructions for testing. There should be sufficient instructions for someone unfamiliar with the application to test - unless a specific reviewer is requested. If instructions for replicating the fault are contained in the linked issue then it is OK to refer back to these. --> <!-- delete this if you added release notes *This does not require release notes* because **fill in an explanation of why** If you add release notes please save them as a separate file using the Issue or PR number as the file name. Check the file is located in the correct directory for your note(s). --> <!-- Ensure the base of this PR is correct (e.g. release-next or main) Finally, don't forget to add the appropriate labels, milestones, etc.! --> --- ### Reviewer Please comment on the points listed below ([full description](http://developer.mantidproject.org/ReviewingAPullRequest.html)). **Your comments will be used as part of the gatekeeper process, so please comment clearly on what you have checked during your review.** If changes are made to the PR during the review process then your final comment will be the most important for gatekeepers. In this comment you should make it clear why any earlier review is still valid, or confirm that all requested changes have been addressed. #### Code Review - Is the code of an acceptable quality? - Does the code conform to the [coding standards](http://developer.mantidproject.org/Standards/)? - Are the unit tests small and test the class in isolation? - If there is GUI work does it follow the [GUI standards](http://developer.mantidproject.org/Standards/GUIStandards.html)? - If there are changes in the release notes then do they describe the changes appropriately? - Do the release notes conform to the [release notes guide](https://developer.mantidproject.org/Standards/ReleaseNotesGuide.html)? #### Functional Tests - Do changes function as described? Add comments below that describe the tests performed? - Do the changes handle unexpected situations, e.g. bad input? - Has the relevant (user and developer) documentation been added/updated? Does everything look good? Mark the review as **Approve**. A member of `@mantidproject/gatekeepers` will take care of it. ### Gatekeeper If you need to request changes to a PR then please add a comment and set the review status to "Request changes". This will stop the PR from showing up in the list for other gatekeepers.
1 parent 35ae4c8 commit 8c45ae0

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)