Skip to content

Commit e08d7ba

Browse files
dopplershifttimhoffm
authored andcommitted
Fix set_ylim unit handling (matplotlib#12468)
* Refactor tests for units Allows re-using the code setting up a unit converter interface for Quantity. * Fix missing call to _process_unit_info in set_ylim Without it, calling set_ylim using unit support on a blank plot results in trying to convert None.
1 parent 74e0665 commit e08d7ba

File tree

2 files changed

+35
-11
lines changed

2 files changed

+35
-11
lines changed

lib/matplotlib/axes/_base.py

+1
Original file line numberDiff line numberDiff line change
@@ -3480,6 +3480,7 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False,
34803480
raise TypeError('Cannot pass both `ymax` and `top`')
34813481
top = ymax
34823482

3483+
self._process_unit_info(ydata=(bottom, top))
34833484
bottom = self._validate_converted_limits(bottom, self.convert_yunits)
34843485
top = self._validate_converted_limits(top, self.convert_yunits)
34853486

lib/matplotlib/tests/test_units.py

+34-11
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import matplotlib.units as munits
66
import numpy as np
77
import platform
8+
import pytest
89

910

1011
# Basic class that wraps numpy array and has units
@@ -36,12 +37,8 @@ def __array__(self):
3637
return np.asarray(self.magnitude)
3738

3839

39-
# Tests that the conversion machinery works properly for classes that
40-
# work as a facade over numpy arrays (like pint)
41-
@image_comparison(baseline_images=['plot_pint'],
42-
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
43-
extensions=['png'], remove_text=False, style='mpl20')
44-
def test_numpy_facade():
40+
@pytest.fixture
41+
def quantity_converter():
4542
# Create an instance of the conversion interface and
4643
# mock so we can check methods called
4744
qc = munits.ConversionInterface()
@@ -58,12 +55,29 @@ def convert(value, unit, axis):
5855
else:
5956
return Quantity(value, axis.get_units()).to(unit).magnitude
6057

58+
def default_units(value, axis):
59+
if hasattr(value, 'units'):
60+
return value.units
61+
elif np.iterable(value):
62+
for v in value:
63+
if hasattr(v, 'units'):
64+
return v.units
65+
return None
66+
6167
qc.convert = MagicMock(side_effect=convert)
6268
qc.axisinfo = MagicMock(side_effect=lambda u, a: munits.AxisInfo(label=u))
63-
qc.default_units = MagicMock(side_effect=lambda x, a: x.units)
69+
qc.default_units = MagicMock(side_effect=default_units)
70+
return qc
6471

72+
73+
# Tests that the conversion machinery works properly for classes that
74+
# work as a facade over numpy arrays (like pint)
75+
@image_comparison(baseline_images=['plot_pint'],
76+
tol={'aarch64': 0.02}.get(platform.machine(), 0.0),
77+
extensions=['png'], remove_text=False, style='mpl20')
78+
def test_numpy_facade(quantity_converter):
6579
# Register the class
66-
munits.registry[Quantity] = qc
80+
munits.registry[Quantity] = quantity_converter
6781

6882
# Simple test
6983
y = Quantity(np.linspace(0, 30), 'miles')
@@ -77,9 +91,9 @@ def convert(value, unit, axis):
7791
ax.yaxis.set_units('inches')
7892
ax.xaxis.set_units('seconds')
7993

80-
assert qc.convert.called
81-
assert qc.axisinfo.called
82-
assert qc.default_units.called
94+
assert quantity_converter.convert.called
95+
assert quantity_converter.axisinfo.called
96+
assert quantity_converter.default_units.called
8397

8498

8599
# Tests gh-8908
@@ -95,6 +109,15 @@ def test_plot_masked_units():
95109
ax.plot(data_masked_units)
96110

97111

112+
def test_empty_set_limits_with_units(quantity_converter):
113+
# Register the class
114+
munits.registry[Quantity] = quantity_converter
115+
116+
fig, ax = plt.subplots()
117+
ax.set_xlim(Quantity(-1, 'meters'), Quantity(6, 'meters'))
118+
ax.set_ylim(Quantity(-1, 'hours'), Quantity(16, 'hours'))
119+
120+
98121
@image_comparison(baseline_images=['jpl_bar_units'], extensions=['png'],
99122
savefig_kwarg={'dpi': 120}, style='mpl20')
100123
def test_jpl_bar_units():

0 commit comments

Comments
 (0)