Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +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.
36 changes: 36 additions & 0 deletions qt/applications/workbench/workbench/plotting/figuremanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -232,6 +232,7 @@ def __init__(self, canvas, num):
self.toolbar.sig_waterfall_conversion.connect(self.update_toolbar_waterfall_plot)
self.toolbar.sig_change_line_collection_colour_triggered.connect(self.change_line_collection_colour)
self.toolbar.sig_hide_plot_triggered.connect(self.hide_plot)
self.toolbar.sig_crosshair_toggle_triggered.connect(self.crosshair_toggle)
self.toolbar.setFloatable(False)
tbs_height = self.toolbar.sizeHint().height()
else:
Expand Down Expand Up @@ -603,6 +604,41 @@ def _reverse_axis_lines(ax):
line.remove()
ax.add_line(line)

def crosshair_toggle(self, on):
cid = self.canvas.mpl_connect("motion_notify_event", self.crosshair)
if not on:
self.canvas.mpl_disconnect(cid)

def crosshair(self, event):
axes = self.canvas.figure.gca()

# create a crosshair made from horizontal and verticle lines.
self.horizontal_line = axes.axhline(color="r", lw=1.0, ls="-")
self.vertical_line = axes.axvline(color="r", lw=1.0, ls="-")

def set_cross_hair_visible(visible):
need_redraw = self.horizontal_line.get_visible() != visible
self.horizontal_line.set_visible(visible)
self.vertical_line.set_visible(visible)
return need_redraw

# if event is out-of-bound we update
if not event.inaxes:
need_redraw = set_cross_hair_visible(False)
if need_redraw:
axes.figure.canvas.draw()

else:
set_cross_hair_visible(True)
x, y = event.xdata, event.ydata
self.horizontal_line.set_ydata([y])
self.vertical_line.set_xdata([x])
self.canvas.draw()

# after update we remove
self.horizontal_line.remove()
self.vertical_line.remove()


# -----------------------------------------------------------------------------
# Figure control
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@

import unittest
from unittest.mock import patch
import numpy as np

import matplotlib

Expand All @@ -19,7 +20,7 @@
from workbench.plotting.figuremanager import MantidFigureCanvas, FigureManagerWorkbench
from workbench.plotting.toolbar import WorkbenchNavigationToolbar
from mantid.plots.plotfunctions import plot
from mantid.simpleapi import CreateSampleWorkspace, CreateMDHistoWorkspace
from mantid.simpleapi import CreateSampleWorkspace, CreateMDHistoWorkspace, Load


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

@patch("workbench.plotting.figuremanager.QAppThreadCall")
def test_button_checked_for_plot_with_no_crosshair(self, mock_qappthread):
mock_qappthread.return_value = mock_qappthread

fig, axes = plt.subplots(subplot_kw={"projection": "mantid"})
axes.plot([-10, 10], [1, 2])
# crosshair button should be OFF because we have not enabled the crosshair.
self.assertFalse(self._is_crosshair_button_checked(fig))
# crosshair button should be visible when there is only 1 axis
self.assertTrue(self._is_crosshair_button_visible(fig))

@patch("workbench.plotting.figuremanager.QAppThreadCall")
def test_button_hiden_for_tiled_plots(self, mock_qappthread):
mock_qappthread.return_value = mock_qappthread

fig, axes = plt.subplots(2)
axes[0].plot([-10, 10], [1, 2])
axes[1].plot([3, 2, 1], [1, 2, 3])
# crosshair button should be hidden because this is a tiled plot
self.assertFalse(self._is_crosshair_button_visible(fig))
self.assertFalse(self._is_crosshair_button_checked(fig))

@patch("workbench.plotting.figuremanager.QAppThreadCall")
def test_button_enabled_for_contour_plots(self, mock_qappthread):
mock_qappthread.return_value = mock_qappthread
data = Load("SANSLOQCan2D.nxs")
fig, axes = plt.subplots(subplot_kw={"projection": "mantid"})
axes.imshow(data, origin="lower", cmap="viridis", aspect="auto")
axes.contour(data, levels=np.linspace(10, 60, 6), colors="yellow", alpha=0.5)
# crosshair button should be visible because this is a contour plot
self.assertTrue(self._is_crosshair_button_visible(fig))
self.assertFalse(self._is_crosshair_button_checked(fig))

@patch("workbench.plotting.figuremanager.QAppThreadCall")
def test_button_checked_for_plot_with_grid_using_kwargs(self, mock_qappthread):
mock_qappthread.return_value = mock_qappthread
Expand Down Expand Up @@ -266,6 +300,32 @@ def _is_button_enabled(cls, fig, button):
fig_manager.toolbar.set_buttons_visibility(fig)
return fig_manager.toolbar._actions[button].isEnabled()

@classmethod
def _is_crosshair_button_checked(cls, fig):
"""
Create the figure manager and check whether its toolbar is toggled on or off for the given figure.
We have to explicitly call set_button_visibility() here, which would otherwise be called within the show()
function.
"""
canvas = MantidFigureCanvas(fig)
fig_manager = FigureManagerWorkbench(canvas, 1)
# This is only called when show() is called on the figure manager, so we have to manually call it here.
fig_manager.toolbar.set_buttons_visibility(fig)
return fig_manager.toolbar._actions["toggle_crosshair"].isChecked()

@classmethod
def _is_crosshair_button_visible(cls, fig):
"""
Create the figure manager and check whether its toolbar is visible for the given figure.
We have to explicitly call set_button_visibility() here, which would otherwise be called within the show()
function.
"""
canvas = MantidFigureCanvas(fig)
fig_manager = FigureManagerWorkbench(canvas, 1)
# This is only called when show() is called on the figure manager, so we have to manually call it here.
fig_manager.toolbar.set_buttons_visibility(fig)
return fig_manager.toolbar._actions["toggle_crosshair"].isVisible()


if __name__ == "__main__":
unittest.main()
21 changes: 21 additions & 0 deletions qt/applications/workbench/workbench/plotting/toolbar.py
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ def _create_script_action(self, text, tooltip_text, mdi_icon, *args):
class WorkbenchNavigationToolbar(MantidNavigationToolbar):
sig_home_clicked = QtCore.Signal()
sig_grid_toggle_triggered = QtCore.Signal(bool)
sig_crosshair_toggle_triggered = QtCore.Signal(bool)
sig_active_triggered = QtCore.Signal()
sig_hold_triggered = QtCore.Signal()
sig_toggle_fit_triggered = QtCore.Signal()
Expand Down Expand Up @@ -84,6 +85,7 @@ class WorkbenchNavigationToolbar(MantidNavigationToolbar):
MantidNavigationTool("Fill Area", "Fill area under curves", "mdi.format-color-fill", "waterfall_fill_area", None),
MantidStandardNavigationTools.SEPARATOR,
MantidNavigationTool("Help", "Open plotting help documentation", "mdi.help", "launch_plot_help", None),
MantidNavigationTool("Crosshair", "Toggle crosshair", "mdi.plus", "toggle_crosshair", False),
MantidNavigationTool("Hide", "Hide the plot", "mdi.eye", "hide_plot", None),
)

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

def toggle_crosshair(self, enable=None):
if enable is None:
enable = self._actions["toggle_crosshair"].isChecked()
else:
self._actions["toggle_crosshair"].setChecked(enable)
self.sig_crosshair_toggle_triggered.emit(enable)

def set_crosshair_enabled(self, on):
action = self._actions["toggle_crosshair"]
action.setEnabled(on)
action.setVisible(on)
# Show/hide the separator between this button and help button / waterfall options
self.toggle_separator_visibility(action, on)

def hide_plot(self):
self.sig_hide_plot_triggered.emit()

Expand Down Expand Up @@ -217,6 +233,11 @@ def set_buttons_visibility(self, fig):
if figure_type(fig) not in [FigureType.Line, FigureType.Errorbar] or len(fig.get_axes()) > 1:
self.set_fit_enabled(False)
self.set_superplot_enabled(False)

# disable crosshair in tiled plots but keep it enabled in color contour plot
if len(fig.get_axes()) > 1 and figure_type(fig) not in [FigureType.Contour]:
self.set_crosshair_enabled(False)

for ax in fig.get_axes():
for artist in ax.get_lines():
try:
Expand Down