From e18891cfb9350b5c5157e0d12158b6182800d458 Mon Sep 17 00:00:00 2001 From: Ryan May Date: Wed, 19 Jun 2019 13:51:59 -0600 Subject: [PATCH 1/2] MNT: Update ticks for SkewT (Fixes #987) This updates the implementation for ticks in SkewT based on changes in matplotlib. This greatly cleans this up, and for some reason, is necessary with matplotlib 3.1. --- metpy/plots/skewt.py | 97 +++++++++++++++----------------------------- setup.py | 1 + 2 files changed, 33 insertions(+), 65 deletions(-) diff --git a/metpy/plots/skewt.py b/metpy/plots/skewt.py index 41757bf6622..ac6299f22d2 100644 --- a/metpy/plots/skewt.py +++ b/metpy/plots/skewt.py @@ -7,6 +7,11 @@ `SkewT`, as well as a class for making a `Hodograph`. """ +try: + from contextlib import ExitStack +except ImportError: + from contextlib2 import ExitStack + import matplotlib from matplotlib.axes import Axes import matplotlib.axis as maxis @@ -37,69 +42,31 @@ class SkewXTick(maxis.XTick): and draw as appropriate. It also performs similar checking for gridlines. """ - def update_position(self, loc): - """Set the location of tick in data coords with scalar *loc*.""" - # This ensures that the new value of the location is set before - # any other updates take place. - self._loc = loc - super(SkewXTick, self).update_position(loc) - - def _has_default_loc(self): - return self.get_loc() is None - - def _need_lower(self): - return (self._has_default_loc() - or transforms.interval_contains(self.axes.lower_xlim, self.get_loc())) - - def _need_upper(self): - return (self._has_default_loc() - or transforms.interval_contains(self.axes.upper_xlim, self.get_loc())) - - @property - def gridOn(self): # noqa: N802 - """Control whether the gridline is drawn for this tick.""" - return (self._gridOn and (self._has_default_loc() - or transforms.interval_contains(self.get_view_interval(), self.get_loc()))) - - @gridOn.setter - def gridOn(self, value): # noqa: N802 - self._gridOn = value - - @property - def tick1On(self): # noqa: N802 - """Control whether the lower tick mark is drawn for this tick.""" - return self._tick1On and self._need_lower() - - @tick1On.setter - def tick1On(self, value): # noqa: N802 - self._tick1On = value - - @property - def label1On(self): # noqa: N802 - """Control whether the lower tick label is drawn for this tick.""" - return self._label1On and self._need_lower() - - @label1On.setter - def label1On(self, value): # noqa: N802 - self._label1On = value - - @property - def tick2On(self): # noqa: N802 - """Control whether the upper tick mark is drawn for this tick.""" - return self._tick2On and self._need_upper() - - @tick2On.setter - def tick2On(self, value): # noqa: N802 - self._tick2On = value - - @property - def label2On(self): # noqa: N802 - """Control whether the upper tick label is drawn for this tick.""" - return self._label2On and self._need_upper() - - @label2On.setter - def label2On(self, value): # noqa: N802 - self._label2On = value + # Taken from matplotlib's SkewT example to update for matplotlib 3.1's changes to + # state management for ticks. See matplotlib/matplotlib#10088 + def draw(self, renderer): + """Draw the tick.""" + # When adding the callbacks with `stack.callback`, we fetch the current + # visibility state of the artist with `get_visible`; the ExitStack will + # restore these states (`set_visible`) at the end of the block (after + # the draw). + with ExitStack() as stack: + for artist in [self.gridline, self.tick1line, self.tick2line, + self.label1, self.label2]: + stack.callback(artist.set_visible, artist.get_visible()) + needs_lower = transforms.interval_contains( + self.axes.lower_xlim, self.get_loc()) + needs_upper = transforms.interval_contains( + self.axes.upper_xlim, self.get_loc()) + self.tick1line.set_visible( + self.tick1line.get_visible() and needs_lower) + self.label1.set_visible( + self.label1.get_visible() and needs_lower) + self.tick2line.set_visible( + self.tick2line.get_visible() and needs_upper) + self.label2.set_visible( + self.label2.get_visible() and needs_upper) + super(SkewXTick, self).draw(renderer) def get_view_interval(self): """Get the view interval.""" @@ -168,7 +135,7 @@ def __init__(self, *args, **kwargs): """ # This needs to be popped and set before moving on self.rot = kwargs.pop('rotation', 30) - Axes.__init__(self, *args, **kwargs) + super(Axes, self).__init__(*args, **kwargs) def _init_axis(self): # Taken from Axes and modified to use our modified X-axis @@ -195,7 +162,7 @@ def _set_lim_and_transforms(self): """ # Get the standard transform setup from the Axes base class - Axes._set_lim_and_transforms(self) + super(Axes, self)._set_lim_and_transforms() # Need to put the skew in the middle, after the scale and limits, # but before the transAxes. This way, the skew is done in Axes diff --git a/setup.py b/setup.py index 307698826ae..24c94220407 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*', install_requires=['matplotlib>=2.0.0', 'numpy>=1.12.0', 'scipy>=0.17.0', 'pint!=0.9', 'xarray>=0.10.7', 'enum34;python_version<"3.4"', + 'contextlib2;python_version<"3.6"', 'pooch>=0.1, <0.3', 'traitlets>=4.3.0'], extras_require={ 'dev': ['ipython[all]>=3.1'], From 4434beb699d491a3c0a396b04f4b0f6ceb48305a Mon Sep 17 00:00:00 2001 From: Ryan May Date: Wed, 19 Jun 2019 13:52:55 -0600 Subject: [PATCH 2/2] MNT: Update some skewT image test thresholds This gets the test suite passing with Matplotlib 3.1. Not sure what changed, but a deep look at the differences only reveals some subtle changes to just how black the individual tick marks are. --- metpy/plots/tests/test_skewt.py | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/metpy/plots/tests/test_skewt.py b/metpy/plots/tests/test_skewt.py index 1bb2ea66984..bac12244d67 100644 --- a/metpy/plots/tests/test_skewt.py +++ b/metpy/plots/tests/test_skewt.py @@ -17,7 +17,7 @@ MPL_VERSION = matplotlib.__version__[:3] -@pytest.mark.mpl_image_compare(tolerance=0.021, remove_text=True) +@pytest.mark.mpl_image_compare(tolerance=0.224, remove_text=True) def test_skewt_api(): """Test the SkewT API.""" with matplotlib.rc_context({'axes.autolimit_mode': 'data'}): @@ -87,7 +87,8 @@ def test_profile(): return np.linspace(1000, 100, 10), np.linspace(20, -20, 10), np.linspace(25, -30, 10) -@pytest.mark.mpl_image_compare(tolerance={'2.0': 1.12}.get(MPL_VERSION, 0.), remove_text=True) +@pytest.mark.mpl_image_compare(tolerance={'2.0': 1.12}.get(MPL_VERSION, 0.2432), + remove_text=True) def test_skewt_shade_cape_cin(test_profile): """Test shading CAPE and CIN on a SkewT plot.""" p, t, tp = test_profile @@ -104,7 +105,8 @@ def test_skewt_shade_cape_cin(test_profile): return fig -@pytest.mark.mpl_image_compare(tolerance={'1.4': 1.70}.get(MPL_VERSION, 0.), remove_text=True) +@pytest.mark.mpl_image_compare(tolerance={'1.4': 1.70}.get(MPL_VERSION, 0.2432), + remove_text=True) def test_skewt_shade_area(test_profile): """Test shading areas on a SkewT plot.""" p, t, tp = test_profile @@ -131,7 +133,8 @@ def test_skewt_shade_area_invalid(test_profile): skew.shade_area(p, t, tp, which='positve') -@pytest.mark.mpl_image_compare(tolerance={'1.4': 1.75}.get(MPL_VERSION, 0.), remove_text=True) +@pytest.mark.mpl_image_compare(tolerance={'1.4': 1.75}.get(MPL_VERSION, 0.2432), + remove_text=True) def test_skewt_shade_area_kwargs(test_profile): """Test shading areas on a SkewT plot with kwargs.""" p, t, tp = test_profile @@ -208,7 +211,7 @@ def test_skewt_barb_color(): return fig -@pytest.mark.mpl_image_compare(tolerance={'2.0': 0.2}.get(MPL_VERSION, 0), remove_text=True) +@pytest.mark.mpl_image_compare(tolerance=0.2, remove_text=True) def test_skewt_barb_unit_conversion(): """Test that barbs units can be converted at plot time (#737).""" u_wind = np.array([3.63767155210412]) * units('m/s')