Skip to content

Commit

Permalink
Merge pull request #37160 from rosswhitfield/sliceviewer_extent_limits
Browse files Browse the repository at this point in the history
Add x and y limits to the main view of sliceviewer ornl-next
  • Loading branch information
rosswhitfield committed Apr 12, 2024
2 parents 6be7fcf + a730809 commit 241e028
Show file tree
Hide file tree
Showing 4 changed files with 192 additions and 2 deletions.
@@ -0,0 +1 @@
- Added controls for the x and y limits to the main view of Sliceviewer.
Expand Up @@ -108,6 +108,7 @@ def region_selection(self, state):
# incompatible with drag zooming/panning as they both require drag selection
data_view.deactivate_and_disable_tool(ToolItemText.ZOOM)
data_view.deactivate_and_disable_tool(ToolItemText.PAN)
data_view.extents_set_enabled(False)
tool = RectangleSelectionLinePlot
if data_view.line_plots_active:
data_view.switch_line_plots_tool(RectangleSelectionLinePlot, self)
Expand All @@ -116,6 +117,7 @@ def region_selection(self, state):
else:
data_view.enable_tool_button(ToolItemText.ZOOM)
data_view.enable_tool_button(ToolItemText.PAN)
data_view.extents_set_enabled(True)
data_view.switch_line_plots_tool(PixelLinePlot, self)

@abc.abstractmethod
Expand Down
Expand Up @@ -7,10 +7,12 @@
import unittest
from typing import Dict
from unittest import mock
import numpy as np

from mantidqt.utils.qt.testing import start_qapplication
from mantidqt.widgets.sliceviewer.views.toolbar import ToolItemText
from mantidqt.widgets.sliceviewer.views.view import SliceViewerDataView
from mantidqt.widgets.sliceviewer.models.transform import NonOrthogonalTransform


@start_qapplication
Expand Down Expand Up @@ -286,6 +288,102 @@ def test_get_default_scale_norm_scalenorm(self):
scale = self.view.get_default_scale_norm()
self.assertEqual(scale, "SymmetricLog10")

def test_extents_set_correctly_orthogonal(self):
self.view.set_integer_axes_ticks = mock.Mock()

self.view.create_axes_orthogonal()

self.assertEqual(self.view.x_min.value(), 0.0)
self.assertEqual(self.view.x_max.value(), 0.0)
self.assertEqual(self.view.y_min.value(), 0.0)
self.assertEqual(self.view.y_max.value(), 0.0)

self.view.ax.set_xlim(-1.0, 1.0)
self.view.ax.set_ylim(-2.0, 2.0)

self.assertEqual(self.view.x_min.value(), -1.0)
self.assertEqual(self.view.x_max.value(), 1.0)
self.assertEqual(self.view.y_min.value(), -2.0)
self.assertEqual(self.view.y_max.value(), 2.0)

def test_extents_set_correctly_nonorthogonal(self):
self.view.grid_helper = mock.Mock()
self.patched_objs["CurveLinearSubPlot"].return_value = self.view.fig.add_subplot(111)

transform = NonOrthogonalTransform(np.radians(30))

self.view.create_axes_nonorthogonal(transform)

self.assertEqual(self.view.x_min.value(), 0.0)
self.assertEqual(self.view.x_max.value(), 0.0)
self.assertEqual(self.view.y_min.value(), 0.0)
self.assertEqual(self.view.y_max.value(), 0.0)

self.view.ax.set_xlim(-1.0, 1.0)
self.view.ax.set_ylim(-2.0, 2.0)

self.assertEqual(self.view.x_min.value(), -1.0)
self.assertEqual(self.view.x_max.value(), 1.0)

# y should be double because y / sin(angle) = y/0.5 for 30 degrees
self.assertEqual(self.view.y_min.value(), -4.0)
self.assertEqual(self.view.y_max.value(), 4.0)

def test_extents_update_from_spinbox_orthogonal(self):
self.view.set_integer_axes_ticks = mock.Mock()

self.view.create_axes_orthogonal()

x_min, x_max = self.view.ax.get_xlim()
y_min, y_max = self.view.ax.get_ylim()
self.assertEqual(x_min, 0.0)
self.assertEqual(x_max, 1.0)
self.assertEqual(y_min, 0.0)
self.assertEqual(y_max, 1.0)

self.view.x_min.setValue(-1.0)
self.view.x_max.setValue(2.0)
self.view.y_min.setValue(-3.0)
self.view.y_max.setValue(4.0)

x_min, x_max = self.view.ax.get_xlim()
y_min, y_max = self.view.ax.get_ylim()

self.assertEqual(x_min, -1.0)
self.assertEqual(x_max, 2.0)
self.assertEqual(y_min, -3.0)
self.assertEqual(y_max, 4.0)

def test_extents_update_from_spinbox_nonorthogonal(self):
self.view.grid_helper = mock.Mock()
self.patched_objs["CurveLinearSubPlot"].return_value = self.view.fig.add_subplot(111)

transform = NonOrthogonalTransform(np.radians(30))

self.view.create_axes_nonorthogonal(transform)

x_min, x_max = self.view.ax.get_xlim()
y_min, y_max = self.view.ax.get_ylim()
self.assertEqual(x_min, 0.0)
self.assertEqual(x_max, 1.0)
self.assertEqual(y_min, 0.0)
self.assertEqual(y_max, 1.0)

self.view.x_min.setValue(-1.0)
self.view.x_max.setValue(2.0)
self.view.y_min.setValue(-3.0)
self.view.y_max.setValue(4.0)

x_min, x_max = self.view.ax.get_xlim()
y_min, y_max = self.view.ax.get_ylim()

self.assertEqual(x_min, -1.0)
self.assertEqual(x_max, 2.0)

# y should be halved because y * sin(angle) = y * 0.5 for 30 degrees
self.assertAlmostEqual(y_min, -1.5, 9)
self.assertAlmostEqual(y_max, 2.0, 9)


if __name__ == "__main__":
unittest.main()
93 changes: 91 additions & 2 deletions qt/python/mantidqt/mantidqt/widgets/sliceviewer/views/dataview.py
Expand Up @@ -19,6 +19,7 @@
QStatusBar,
QToolButton,
QSizePolicy,
QDoubleSpinBox,
)
from matplotlib.figure import Figure
from mpl_toolkits.axisartist import Subplot as CurveLinearSubPlot, GridHelperCurveLinear
Expand Down Expand Up @@ -153,14 +154,18 @@ def __init__(self, presenter: IDataViewSubscriber, dims_info, can_normalise, par
self.status_bar.addWidget(self.help_button)
self.status_bar.addWidget(self.status_bar_label)

# min/max extents
self.extents = self.create_extents_layout()

# layout
layout = QGridLayout(self)
layout.setSpacing(1)
layout.addLayout(self.dimensions_layout, 0, 0, 1, 2)
layout.addLayout(self.toolbar_layout, 1, 0, 1, 1)
layout.addLayout(self.colorbar_layout, 1, 1, 3, 1)
layout.addLayout(self.colorbar_layout, 1, 1, 4, 1)
layout.addWidget(self.canvas, 2, 0, 1, 1)
layout.addWidget(self.status_bar, 3, 0, 1, 1)
layout.addLayout(self.extents, 3, 0, 1, 1)
layout.addWidget(self.status_bar, 4, 0, 1, 1)
layout.setRowStretch(2, 1)

def create_dimensions(self, dims_info, custom_image_info=False):
Expand Down Expand Up @@ -200,6 +205,9 @@ def create_axes_orthogonal(self, redraw_on_zoom=False):
self.plot_MDH = self.plot_MDH_orthogonal
self.set_integer_axes_ticks()

self.ax.callbacks.connect("xlim_changed", self.xlim_changed)
self.ax.callbacks.connect("ylim_changed", self.ylim_changed)

self.canvas.draw_idle()

def grid_helper(self, transform):
Expand Down Expand Up @@ -231,6 +239,9 @@ def create_axes_nonorthogonal(self, transform):
self.fig.add_subplot(self.ax)
self.plot_MDH = self.plot_MDH_nonorthogonal

self.ax.callbacks.connect("xlim_changed", self.xlim_changed)
self.ax.callbacks.connect("ylim_changed", self.ylim_changed)

self.canvas.draw_idle()

def enable_zoom_on_mouse_scroll(self, redraw):
Expand Down Expand Up @@ -521,6 +532,84 @@ def set_axes_limits(self, xlim, ylim):
self.ax.set_xlim(xlim)
self.ax.set_ylim(ylim)

def xlim_changed(self, ax):
x_min, x_max = ax.get_xlim()
self.x_min.blockSignals(True)
self.x_min.setValue(x_min)
self.x_min.blockSignals(False)
self.x_max.blockSignals(True)
self.x_max.setValue(x_max)
self.x_max.blockSignals(False)

def update_xlim(self, _):
self.ax.set_xlim(self.x_min.value(), self.x_max.value(), emit=False)
self.on_data_limits_changed()

def ylim_changed(self, ax):
y_min, y_max = ax.get_ylim()
if self.nonorthogonal_mode:
y_min = self.nonortho_transform.inv_tr(0, y_min)[1]
y_max = self.nonortho_transform.inv_tr(0, y_max)[1]
self.y_min.blockSignals(True)
self.y_min.setValue(y_min)
self.y_min.blockSignals(False)
self.y_max.blockSignals(True)
self.y_max.setValue(y_max)
self.y_max.blockSignals(False)

def update_ylim(self, _):
y_min = self.y_min.value()
y_max = self.y_max.value()
if self.nonorthogonal_mode:
y_min = self.nonortho_transform.tr(0, y_min)[1]
y_max = self.nonortho_transform.tr(0, y_max)[1]
self.ax.set_ylim(y_min, y_max, emit=False)
self.on_data_limits_changed()

def create_extents_layout(self):
self.x_min = QDoubleSpinBox()
self.x_min.setRange(-DBLMAX, DBLMAX)
self.x_min.setMaximumWidth(100)
self.x_min.valueChanged.connect(self.update_xlim)

self.x_max = QDoubleSpinBox()
self.x_max.setRange(-DBLMAX, DBLMAX)
self.x_max.setMaximumWidth(100)
self.x_max.valueChanged.connect(self.update_xlim)

self.y_min = QDoubleSpinBox()
self.y_min.setRange(-DBLMAX, DBLMAX)
self.y_min.setMaximumWidth(100)
self.y_min.valueChanged.connect(self.update_ylim)

self.y_max = QDoubleSpinBox()
self.y_max.setRange(-DBLMAX, DBLMAX)
self.y_max.setMaximumWidth(100)
self.y_max.valueChanged.connect(self.update_ylim)

extents = QHBoxLayout()
extents.addStretch(1)
extents.addWidget(QLabel("X min:"))
extents.addWidget(self.x_min)
extents.addSpacing(10)
extents.addWidget(QLabel("X max:"))
extents.addWidget(self.x_max)
extents.addSpacing(30)
extents.addWidget(QLabel("Y min:"))
extents.addWidget(self.y_min)
extents.addSpacing(10)
extents.addWidget(QLabel("Y max:"))
extents.addWidget(self.y_max)
extents.addStretch(1)

return extents

def extents_set_enabled(self, state):
self.x_min.setEnabled(state)
self.x_max.setEnabled(state)
self.y_min.setEnabled(state)
self.y_max.setEnabled(state)

def set_integer_axes_ticks(self):
"""
Set axis locators at integer positions, if possible
Expand Down

0 comments on commit 241e028

Please sign in to comment.