diff --git a/glue/viewers/image/qt/tests/test_data_viewer.py b/glue/viewers/image/qt/tests/test_data_viewer.py index c607ddf40..a681efbd2 100644 --- a/glue/viewers/image/qt/tests/test_data_viewer.py +++ b/glue/viewers/image/qt/tests/test_data_viewer.py @@ -5,6 +5,7 @@ from collections import Counter import pytest +from unittest.mock import MagicMock from astropy.wcs import WCS @@ -740,6 +741,41 @@ def test_legend(self): assert to_hex(handles[1].get_facecolor()) == viewer_state.layers[1].color + def test_recalculate_wcs(self): + + # Test to make sure we skip recalculating WCS when appropriate + + mock = MagicMock(wraps=self.viewer.axes.reset_wcs) + self.viewer.axes.reset_wcs = mock + + coords = self.hypercube_wcs.coords + coords.wcs.crpix = [1.2, 2, 2.25, 1.9] + coords.wcs.cdelt = np.array([-6.7, 6.7, 5.1, -4.6]) + coords.wcs.crval = [0, -90, 10, 40] + coords.wcs.ctype = ["RA---SFL", "DEC--SFL", "VOPT", "ZOPT"] + coords.wcs.specsys = "LSRK" + self.viewer.add_data(self.hypercube_wcs) + self.viewer.state.x_att = self.hypercube_wcs.pixel_component_ids[0] + self.viewer.state.y_att = self.hypercube_wcs.pixel_component_ids[3] + self.viewer.state.slices = [0, 0, 0, 0] + + mock.reset_mock() + self.viewer.state.slices = [0, 2, 3, 0] + mock.assert_called() + + mock.reset_mock() + self.viewer.state.show_axes = False + self.viewer.state.slices = [0, 1, 0, 0] + mock.assert_not_called() + + mock.reset_mock() + self.viewer.state.x_att = self.hypercube_wcs.pixel_component_ids[1] + mock.assert_called() + + mock.reset_mock() + self.viewer.state.slices = [1, 1, 0, 0] + mock.assert_not_called() + class TestSessions(object): diff --git a/glue/viewers/image/qt/tests/test_python_export.py b/glue/viewers/image/qt/tests/test_python_export.py index 3ee339619..01ed82a7c 100644 --- a/glue/viewers/image/qt/tests/test_python_export.py +++ b/glue/viewers/image/qt/tests/test_python_export.py @@ -7,6 +7,8 @@ from glue.viewers.image.qt import ImageViewer from glue.viewers.matplotlib.qt.tests.test_python_export import BaseTestExportPython +from astropy.wcs import WCS + class TestExportPython(BaseTestExportPython): @@ -85,3 +87,16 @@ def test_subset_transposed(self, tmpdir): self.viewer.state.y_att = self.data.pixel_component_ids[1] self.data_collection.new_subset_group('mysubset', self.data.id['cube'] > 0.5) self.assert_same(tmpdir) + + def test_hide_axes(self, tmpdir): + self.viewer.state.aspect = 'auto' + self.viewer.state.x_min = -5 + self.viewer.state.y_min = -3 + self.viewer.state.x_max = 30 + self.viewer.state.y_max = 55 + super().test_hide_axes(tmpdir) + + def test_wcs_image(self, tmpdir): + coords = WCS(naxis=3) + wcs_data = Data(self.data.data) + self.assert_same(tmpdir) diff --git a/glue/viewers/image/viewer.py b/glue/viewers/image/viewer.py index a0c24dd5d..29837952f 100644 --- a/glue/viewers/image/viewer.py +++ b/glue/viewers/image/viewer.py @@ -68,6 +68,13 @@ def update_y_ticklabel(self, *event): self.axes.coords[axis].set_ticklabel(size=self.state.y_ticklabel_size) self.redraw() + def update_axes_visibility(self, *event): + if self.state.show_axes: + self._set_wcs() + else: + self._changing_slice_requires_wcs_update = False + super(MatplotlibImageMixin, self).update_axes_visibility(event) + def _update_axes(self, *args): if self.state.x_att_world is not None: @@ -132,7 +139,7 @@ def _set_wcs(self, event=None, relim=True): y_dep.remove(ix) if iy in y_dep: y_dep.remove(iy) - self._changing_slice_requires_wcs_update = bool(x_dep or y_dep) + self._changing_slice_requires_wcs_update = bool(x_dep or y_dep) and self.state.show_axes self._wcs_set = True @@ -219,7 +226,7 @@ def _script_header(self): ref_coords = self.state.reference_data.coords if hasattr(ref_coords, 'wcs'): - script += "ax.reset_wcs(slices={0}, wcs=ref_data.coords.wcs)\n".format(self.state.wcsaxes_slice) + script += "ax.reset_wcs(slices={0}, wcs=ref_data.coords)\n".format(self.state.wcsaxes_slice) elif hasattr(ref_coords, 'wcsaxes_dict'): raise NotImplementedError() else: diff --git a/glue/viewers/matplotlib/mpl_axes.py b/glue/viewers/matplotlib/mpl_axes.py index 61ab8e313..efc0e13db 100644 --- a/glue/viewers/matplotlib/mpl_axes.py +++ b/glue/viewers/matplotlib/mpl_axes.py @@ -5,6 +5,8 @@ __all__ = ['update_appearance_from_settings', 'init_mpl'] +DEFAULT_MARGIN = [1, 0.25, 0.50, 0.25] + def set_background_color(axes, color): axes.figure.set_facecolor(color) @@ -59,7 +61,7 @@ def init_mpl(figure=None, axes=None, wcs=False, axes_factory=None): else: _axes = axes_factory(_figure) - freeze_margins(_axes, margins=[1, 0.25, 0.50, 0.25]) + freeze_margins(_axes, margins=DEFAULT_MARGIN) update_appearance_from_settings(_axes) diff --git a/glue/viewers/matplotlib/qt/axes_editor.ui b/glue/viewers/matplotlib/qt/axes_editor.ui index eee77d0dd..91986aaed 100644 --- a/glue/viewers/matplotlib/qt/axes_editor.ui +++ b/glue/viewers/matplotlib/qt/axes_editor.ui @@ -6,8 +6,8 @@ 0 0 - 272 - 286 + 259 + 237 @@ -29,6 +29,59 @@ 5 + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Apply to all plots + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + axis label weight + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + @@ -39,24 +92,56 @@ - - + + + + + 50 + false + + + + y + + + Qt::AlignCenter + + - - - - - 70 - 0 - + + + + + + + + 50 + false + + + + x + + + Qt::AlignCenter - - + + + + axis label size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + - - + + + + + @@ -68,7 +153,7 @@ - + tick label size @@ -78,17 +163,20 @@ - - - - - - - Qt::Horizontal + + + + + 70 + 0 + - + + + + @@ -98,46 +186,30 @@ - - - - axis label size - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter - - - - - - - axis label weight - - - Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + Qt::Horizontal - - - - - 50 - false - - - - x + + + + Qt::Vertical - - Qt::AlignCenter + + + 20 + 0 + - + - - + + - + Qt::Horizontal @@ -150,14 +222,17 @@ - + - Apply to all plots + Show axes + + + true - + Qt::Horizontal @@ -171,41 +246,6 @@ - - - - - - - Qt::Vertical - - - - 20 - 0 - - - - - - - - - 50 - false - - - - y - - - Qt::AlignCenter - - - - - - diff --git a/glue/viewers/matplotlib/qt/tests/test_data_viewer.py b/glue/viewers/matplotlib/qt/tests/test_data_viewer.py index 87e9af8e0..847be6e74 100644 --- a/glue/viewers/matplotlib/qt/tests/test_data_viewer.py +++ b/glue/viewers/matplotlib/qt/tests/test_data_viewer.py @@ -16,6 +16,7 @@ from glue.core.roi import XRangeROI from glue.utils.qt import process_events from glue.tests.helpers import requires_matplotlib_ge_22 +from glue.viewers.matplotlib.state import MatplotlibDataViewerState class MatplotlibDrawCounter(object): @@ -667,3 +668,31 @@ def test_legend_numerical_data_changed(self): data.add_component(self.data[cid], cid.label) self.data.update_values_from_data(data) assert self.draw_count == 2 + + def test_hide_axes(self): + assert self.viewer.axes.axison + + self.viewer.state.show_axes = False + assert not self.viewer.axes.axison + + self.viewer.state.show_axes = True + assert self.viewer.axes.axison + + def test_update_settings_from(self): + copy_from_state = MatplotlibDataViewerState() + copy_from_state.x_axislabel_size = 1 + copy_from_state.y_axislabel_size = 2 + copy_from_state.x_axislabel_weight = 'heavy' + copy_from_state.y_axislabel_weight = 'black' + copy_from_state.x_ticklabel_size = 5 + copy_from_state.y_ticklabel_size = 6 + copy_from_state.show_axes = False + state = self.viewer.state + state.update_axes_settings_from(copy_from_state) + assert state.x_axislabel_size == 1 + assert state.y_axislabel_size == 2 + assert state.x_axislabel_weight == 'heavy' + assert state.y_axislabel_weight == 'black' + assert state.x_ticklabel_size == 5 + assert state.y_ticklabel_size == 6 + assert not state.show_axes diff --git a/glue/viewers/matplotlib/qt/tests/test_python_export.py b/glue/viewers/matplotlib/qt/tests/test_python_export.py index 40c0760df..d2c00f9be 100644 --- a/glue/viewers/matplotlib/qt/tests/test_python_export.py +++ b/glue/viewers/matplotlib/qt/tests/test_python_export.py @@ -50,3 +50,7 @@ def assert_same(self, tmpdir, tol=0.1): print(b64encode(f.read()).decode()) pytest.fail(msg, pytrace=False) + + def test_hide_axes(self, tmpdir): + self.viewer.state.show_axes = False + self.assert_same(tmpdir) diff --git a/glue/viewers/matplotlib/state.py b/glue/viewers/matplotlib/state.py index 7905e5410..267256b2d 100644 --- a/glue/viewers/matplotlib/state.py +++ b/glue/viewers/matplotlib/state.py @@ -91,8 +91,8 @@ def mpl_location(self): return self.location def update_axes_settings_from(self, state): - self.visible = state.show_legend - self.loc_and_drag = state.loc_and_drag + self.visible = state.visible + self.location = state.location self.alpha = state.alpha self.title = state.title self.fontsize = state.fontsize @@ -244,6 +244,7 @@ def update_axes_settings_from(self, state): self.y_ticklabel_size = state.y_ticklabel_size # legend self.legend.update_axes_settings_from(state.legend) + self.show_axes = state.show_axes @defer_draw def _notify_global(self, *args, **kwargs): diff --git a/glue/viewers/matplotlib/viewer.py b/glue/viewers/matplotlib/viewer.py index b068ec30d..f2e62e51c 100644 --- a/glue/viewers/matplotlib/viewer.py +++ b/glue/viewers/matplotlib/viewer.py @@ -5,9 +5,10 @@ from matplotlib.patches import Rectangle from matplotlib.artist import setp as msetp -from glue.viewers.matplotlib.mpl_axes import update_appearance_from_settings +from glue.viewers.matplotlib.mpl_axes import update_appearance_from_settings, DEFAULT_MARGIN from echo import delay_callback from glue.utils import mpl_to_datetime64 +from glue.utils.matplotlib import freeze_margins __all__ = ['MatplotlibViewerMixin'] @@ -31,6 +32,10 @@ ax.set_xscale('{x_log_str}') ax.set_yscale('{y_log_str}') +# Enable or disable the drawing of axes and remove margin if hiding axes +ax.set_axis_{show_axes}() +{margin} + # Set axis label properties ax.set_xlabel('{x_axislabel}', weight='{x_axislabel_weight}', size={x_axislabel_size}) ax.set_ylabel('{y_axislabel}', weight='{y_axislabel_weight}', size={y_axislabel_size}) @@ -117,6 +122,7 @@ def setup_callbacks(self): self.state.add_callback('x_ticklabel_size', self.update_x_ticklabel) self.state.add_callback('y_ticklabel_size', self.update_y_ticklabel) + self.state.add_callback('show_axes', self.update_axes_visibility) self.state.legend.add_callback('visible', self.draw_legend) self.state.legend.add_callback('location', self.draw_legend) @@ -207,6 +213,16 @@ def draw_legend(self, *args): legend.remove() self.redraw() + def update_axes_visibility(self, *event): + if self.state.show_axes: + self.axes.set_axis_on() + freeze_margins(self.axes, margins=DEFAULT_MARGIN) + else: + self.axes.set_axis_off() + freeze_margins(self.axes, margins=[0., 0., 0., 0.]) + # Have to force a resize event to update margins + self.axes.figure.canvas.resize_event() + def redraw(self): self.figure.canvas.draw_idle() @@ -312,6 +328,8 @@ def _script_footer(self): state_dict = self.state.as_dict() state_dict['x_log_str'] = 'log' if self.state.x_log else 'linear' state_dict['y_log_str'] = 'log' if self.state.y_log else 'linear' + state_dict['show_axes'] = 'on' if self.state.show_axes else 'off' + state_dict['margin'] = 'ax.set_position([0, 0, 1, 1])' if not self.state.show_axes else '' return [], SCRIPT_FOOTER.format(**state_dict) def _script_legend(self):