Skip to content

Commit 6a5ee26

Browse files
authored
Crosshair bugfix release next (#39598)
### Description of work This PR handles errors that arises from crosshair functions interacting with mantid. PR [39491](#39491) The issue is when changing normalisation and error bars, the figure object is treated as mantid object such as mantidaxes with key word arg such as "workspaces". The crosshair, at the moment, is implemented purely as matplotlib object and would cause error and crash the plot. A quick fix is to catch these errors. In the long term, we will integrate the crosshair as a mantid specific object for handling mantid data. #### 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 #39592 . <!-- 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: Run the instructions in the smoking tests, specifically focusing on changing normalisation and error bars while crosshair is open. This script can be used to quickly open the plot pannel for testing: ``` # import mantid algorithms, numpy and matplotlib from mantid.simpleapi import * import matplotlib.pyplot as plt from mantid.plots.utility import MantidAxType MAR11060 = Load('MAR11060') fig, axes = plt.subplots(edgecolor='#ffffff', ncols=2, nrows=2, num='MAR11060-1', subplot_kw={'projection': 'mantid'}) axes[0][0].plot(MAR11060, color='#1f77b4', label='MAR11060: spec 1', wkspIndex=0) axes[0][0].set_xlabel('Time-of-flight ($\\mu s$)') axes[0][0].set_ylabel('Counts ($\\mu s$)$^{-1}$') legend = axes[0][0].legend(fontsize=8.0) #.set_draggable(True).legend # uncomment to set the legend draggable axes[0][1].plot(MAR11060, color='#1f77b4', label='MAR11060: spec 2', wkspIndex=1) axes[0][1].set_xlabel('Time-of-flight ($\\mu s$)') axes[0][1].set_ylabel('Counts ($\\mu s$)$^{-1}$') legend = axes[0][1].legend(fontsize=8.0) #.set_draggable(True).legend # uncomment to set the legend draggable axes[1][0].plot(MAR11060, color='#1f77b4', label='MAR11060: spec 3', wkspIndex=2) axes[1][0].set_xlabel('Time-of-flight ($\\mu s$)') axes[1][0].set_ylabel('Counts ($\\mu s$)$^{-1}$') legend = axes[1][0].legend(fontsize=8.0) #.set_draggable(True).legend # uncomment to set the legend draggable axes[1][1].plot(MAR11060, color='#1f77b4', label='MAR11060: spec 4', wkspIndex=3) axes[1][1].set_xlabel('Time-of-flight ($\\mu s$)') axes[1][1].set_ylabel('Counts ($\\mu s$)$^{-1}$') legend = axes[1][1].legend(fontsize=8.0) #.set_draggable(True).legend # uncomment to set the legend draggable fig.show() ``` <!-- 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 06a461a commit 6a5ee26

File tree

5 files changed

+45
-38
lines changed

5 files changed

+45
-38
lines changed

Framework/PythonInterface/mantid/plots/mantidaxes.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1325,7 +1325,7 @@ def _remove_matching_curve_from_creation_args(self, workspace_name, workspace_in
13251325
:returns: None
13261326
"""
13271327
for index, creation_arg in enumerate(self.creation_args): # type: int, dict
1328-
if workspace_name == creation_arg["workspaces"]:
1328+
if "workspaces" in creation_arg and workspace_name == creation_arg["workspaces"]:
13291329
if creation_arg.get("wkspIndex", -1) == workspace_index or creation_arg.get("specNum", -1) == spec_num:
13301330
del self.creation_args[index]
13311331
return

qt/applications/workbench/workbench/plotting/figureinteraction.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -874,8 +874,7 @@ def _change_plot_normalization(self, ax):
874874
for arg_set in ax.creation_args:
875875
if arg_set["function"] == "contour":
876876
continue
877-
878-
if arg_set["workspaces"] in ax.tracked_workspaces:
877+
if "workspaces" in arg_set and arg_set["workspaces"] in ax.tracked_workspaces:
879878
workspace = ads.retrieve(arg_set["workspaces"])
880879
arg_set["distribution"] = is_normalized
881880
if "specNum" not in arg_set:

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

Lines changed: 38 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -605,39 +605,49 @@ def _reverse_axis_lines(ax):
605605
ax.add_line(line)
606606

607607
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="-")
608+
if on:
609+
# Create crosshair lines for each axes (except colorbars)
610+
self._crosshair_lines = {}
611+
for ax in self._axes_that_are_not_colour_bars():
612+
ax.set_autoscalex_on(False)
613+
ax.set_autoscaley_on(False)
614+
hline = ax.axhline(color="r", lw=1.0, ls="-", visible=False)
615+
vline = ax.axvline(color="r", lw=1.0, ls="-", visible=False)
616+
self._crosshair_lines[ax] = (hline, vline)
617+
618+
self._crosshair_cid = self.canvas.mpl_connect("motion_notify_event", self.crosshair)
619+
else:
620+
if hasattr(self, "_crosshair_cid"):
621+
self.canvas.mpl_disconnect(self._crosshair_cid)
622+
del self._crosshair_cid
618623

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+
if hasattr(self, "_crosshair_lines"):
625+
# Remove the crosshair lines
626+
for hline, vline in self._crosshair_lines.values():
627+
hline.remove()
628+
vline.remove()
629+
del self._crosshair_lines
624630

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()
631+
self.canvas.draw_idle()
630632

633+
def crosshair(self, event):
634+
if event.inaxes and event.xdata is not None and event.ydata is not None:
635+
x = event.xdata
636+
y = event.ydata
637+
638+
# Update all crosshairs to same (x, y)
639+
for ax, (hline, vline) in self._crosshair_lines.items():
640+
hline.set_ydata([y])
641+
vline.set_xdata([x])
642+
hline.set_visible(True)
643+
vline.set_visible(True)
631644
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()
645+
# Hide all crosshairs when mouse is out of axes
646+
for hline, vline in self._crosshair_lines.values():
647+
hline.set_visible(False)
648+
vline.set_visible(False)
637649

638-
# after update we remove
639-
self.horizontal_line.remove()
640-
self.vertical_line.remove()
650+
self.canvas.draw_idle()
641651

642652

643653
# -----------------------------------------------------------------------------

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,14 +132,14 @@ def test_button_checked_for_plot_with_no_crosshair(self, mock_qappthread):
132132
self.assertTrue(self._is_crosshair_button_visible(fig))
133133

134134
@patch("workbench.plotting.figuremanager.QAppThreadCall")
135-
def test_button_hiden_for_tiled_plots(self, mock_qappthread):
135+
def test_button_for_tiled_plots(self, mock_qappthread):
136136
mock_qappthread.return_value = mock_qappthread
137137

138138
fig, axes = plt.subplots(2)
139139
axes[0].plot([-10, 10], [1, 2])
140140
axes[1].plot([3, 2, 1], [1, 2, 3])
141141
# crosshair button should be hidden because this is a tiled plot
142-
self.assertFalse(self._is_crosshair_button_visible(fig))
142+
self.assertTrue(self._is_crosshair_button_visible(fig))
143143
self.assertFalse(self._is_crosshair_button_checked(fig))
144144

145145
@patch("workbench.plotting.figuremanager.QAppThreadCall")

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

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -85,7 +85,7 @@ class WorkbenchNavigationToolbar(MantidNavigationToolbar):
8585
MantidNavigationTool("Fill Area", "Fill area under curves", "mdi.format-color-fill", "waterfall_fill_area", None),
8686
MantidStandardNavigationTools.SEPARATOR,
8787
MantidNavigationTool("Help", "Open plotting help documentation", "mdi.help", "launch_plot_help", None),
88-
MantidNavigationTool("Crosshair", "Toggle crosshair", "mdi.plus", "toggle_crosshair", False),
88+
MantidNavigationTool("Crosshair", "Toggle crosshair", "mdi.target", "toggle_crosshair", False),
8989
MantidNavigationTool("Hide", "Hide the plot", "mdi.eye", "hide_plot", None),
9090
)
9191

@@ -234,10 +234,8 @@ def set_buttons_visibility(self, fig):
234234
self.set_fit_enabled(False)
235235
self.set_superplot_enabled(False)
236236

237-
# disable crosshair in tiled plots, 3D plots but keep it enabled in color contour plot
238-
if (len(fig.get_axes()) > 1 and figure_type(fig) not in [FigureType.Contour]) or (
239-
figure_type(fig) in [FigureType.Surface, FigureType.Wireframe, FigureType.Mesh]
240-
):
237+
# disable crosshair in 3D plots
238+
if figure_type(fig) in [FigureType.Surface, FigureType.Wireframe, FigureType.Mesh]:
241239
self.set_crosshair_enabled(False)
242240

243241
for ax in fig.get_axes():

0 commit comments

Comments
 (0)