Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 32ec60d

Browse filesBrowse files
authored
Add explicit converter setting to Axis (#28970)
* Add explicit converter setting to Axis Closes #19229 The replacement is the get/set_converter method. This includes changes to use the getter and the private setter in the qt figure options dialog menu. The choice to use the private setter was a defensive one as the public setter prevents being called multiple times (though does short circuit if an identical input is provided, which I think is actually true here, therefore the public one is probably functional (and a no-op).) It is not clear to me on analysis how the unit information is or was lost. A quick test commenting out the two lines which reset converter/units displayed no obvious detrimental effect to removing those, suggesting that even if once they were necessary, they may no longer be. These lines were last touched in #24141, though that really only generalized the code into a loop rather than copy/pasted x and y behavior. The original inclusion of resetting was in #4909, which indicated that the dialog reset unit info. AFAICT, that is no longer true, though I have not rigorously proved that.
1 parent c52cb41 commit 32ec60d
Copy full SHA for 32ec60d

File tree

6 files changed

+100
-24
lines changed
Filter options

6 files changed

+100
-24
lines changed

‎doc/api/axis_api.rst

Copy file name to clipboardExpand all lines: doc/api/axis_api.rst
+2Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,8 @@ Units
169169
Axis.convert_units
170170
Axis.set_units
171171
Axis.get_units
172+
Axis.set_converter
173+
Axis.get_converter
172174
Axis.update_units
173175

174176

‎lib/matplotlib/axis.py

Copy file name to clipboardExpand all lines: lib/matplotlib/axis.py
+61-18Lines changed: 61 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,10 @@ class Axis(martist.Artist):
600600
# The class used in _get_tick() to create tick instances. Must either be
601601
# overwritten in subclasses, or subclasses must reimplement _get_tick().
602602
_tick_class = None
603+
converter = _api.deprecate_privatize_attribute(
604+
"3.10",
605+
alternative="get_converter and set_converter methods"
606+
)
603607

604608
def __str__(self):
605609
return "{}({},{})".format(
@@ -656,7 +660,8 @@ def __init__(self, axes, *, pickradius=15, clear=True):
656660
if clear:
657661
self.clear()
658662
else:
659-
self.converter = None
663+
self._converter = None
664+
self._converter_is_explicit = False
660665
self.units = None
661666

662667
self._autoscale_on = True
@@ -886,7 +891,8 @@ def clear(self):
886891
mpl.rcParams['axes.grid.which'] in ('both', 'minor'))
887892
self.reset_ticks()
888893

889-
self.converter = None
894+
self._converter = None
895+
self._converter_is_explicit = False
890896
self.units = None
891897
self.stale = True
892898

@@ -1738,16 +1744,20 @@ def grid(self, visible=None, which='major', **kwargs):
17381744
def update_units(self, data):
17391745
"""
17401746
Introspect *data* for units converter and update the
1741-
``axis.converter`` instance if necessary. Return *True*
1747+
``axis.get_converter`` instance if necessary. Return *True*
17421748
if *data* is registered for unit conversion.
17431749
"""
1744-
converter = munits.registry.get_converter(data)
1750+
if not self._converter_is_explicit:
1751+
converter = munits.registry.get_converter(data)
1752+
else:
1753+
converter = self._converter
1754+
17451755
if converter is None:
17461756
return False
17471757

1748-
neednew = self.converter != converter
1749-
self.converter = converter
1750-
default = self.converter.default_units(data, self)
1758+
neednew = self._converter != converter
1759+
self._set_converter(converter)
1760+
default = self._converter.default_units(data, self)
17511761
if default is not None and self.units is None:
17521762
self.set_units(default)
17531763

@@ -1761,10 +1771,10 @@ def _update_axisinfo(self):
17611771
Check the axis converter for the stored units to see if the
17621772
axis info needs to be updated.
17631773
"""
1764-
if self.converter is None:
1774+
if self._converter is None:
17651775
return
17661776

1767-
info = self.converter.axisinfo(self.units, self)
1777+
info = self._converter.axisinfo(self.units, self)
17681778

17691779
if info is None:
17701780
return
@@ -1791,25 +1801,58 @@ def _update_axisinfo(self):
17911801
self.set_default_intervals()
17921802

17931803
def have_units(self):
1794-
return self.converter is not None or self.units is not None
1804+
return self._converter is not None or self.units is not None
17951805

17961806
def convert_units(self, x):
17971807
# If x is natively supported by Matplotlib, doesn't need converting
17981808
if munits._is_natively_supported(x):
17991809
return x
18001810

1801-
if self.converter is None:
1802-
self.converter = munits.registry.get_converter(x)
1811+
if self._converter is None:
1812+
self._set_converter(munits.registry.get_converter(x))
18031813

1804-
if self.converter is None:
1814+
if self._converter is None:
18051815
return x
18061816
try:
1807-
ret = self.converter.convert(x, self.units, self)
1817+
ret = self._converter.convert(x, self.units, self)
18081818
except Exception as e:
18091819
raise munits.ConversionError('Failed to convert value(s) to axis '
18101820
f'units: {x!r}') from e
18111821
return ret
18121822

1823+
def get_converter(self):
1824+
"""
1825+
Get the unit converter for axis.
1826+
1827+
Returns
1828+
-------
1829+
`~matplotlib.units.ConversionInterface` or None
1830+
"""
1831+
return self._converter
1832+
1833+
def set_converter(self, converter):
1834+
"""
1835+
Set the unit converter for axis.
1836+
1837+
Parameters
1838+
----------
1839+
converter : `~matplotlib.units.ConversionInterface`
1840+
"""
1841+
self._set_converter(converter)
1842+
self._converter_is_explicit = True
1843+
1844+
def _set_converter(self, converter):
1845+
if self._converter == converter:
1846+
return
1847+
if self._converter_is_explicit:
1848+
raise RuntimeError("Axis already has an explicit converter set")
1849+
elif self._converter is not None:
1850+
_api.warn_external(
1851+
"This axis already has a converter set and "
1852+
"is updating to a potentially incompatible converter"
1853+
)
1854+
self._converter = converter
1855+
18131856
def set_units(self, u):
18141857
"""
18151858
Set the units for axis.
@@ -2529,8 +2572,8 @@ def set_default_intervals(self):
25292572
# not changed the view:
25302573
if (not self.axes.dataLim.mutatedx() and
25312574
not self.axes.viewLim.mutatedx()):
2532-
if self.converter is not None:
2533-
info = self.converter.axisinfo(self.units, self)
2575+
if self._converter is not None:
2576+
info = self._converter.axisinfo(self.units, self)
25342577
if info.default_limits is not None:
25352578
xmin, xmax = self.convert_units(info.default_limits)
25362579
self.axes.viewLim.intervalx = xmin, xmax
@@ -2759,8 +2802,8 @@ def set_default_intervals(self):
27592802
# not changed the view:
27602803
if (not self.axes.dataLim.mutatedy() and
27612804
not self.axes.viewLim.mutatedy()):
2762-
if self.converter is not None:
2763-
info = self.converter.axisinfo(self.units, self)
2805+
if self._converter is not None:
2806+
info = self._converter.axisinfo(self.units, self)
27642807
if info.default_limits is not None:
27652808
ymin, ymax = self.convert_units(info.default_limits)
27662809
self.axes.viewLim.intervaly = ymin, ymax

‎lib/matplotlib/axis.pyi

Copy file name to clipboardExpand all lines: lib/matplotlib/axis.pyi
+3Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ from matplotlib.text import Text
1515
from matplotlib.ticker import Locator, Formatter
1616
from matplotlib.transforms import Transform, Bbox
1717
from matplotlib.typing import ColorType
18+
from matplotlib.units import ConversionInterface
1819

1920

2021
GRIDLINE_INTERPOLATION_STEPS: int
@@ -207,6 +208,8 @@ class Axis(martist.Artist):
207208
def update_units(self, data): ...
208209
def have_units(self) -> bool: ...
209210
def convert_units(self, x): ...
211+
def get_converter(self) -> ConversionInterface | None: ...
212+
def set_converter(self, converter: ConversionInterface) -> None: ...
210213
def set_units(self, u) -> None: ...
211214
def get_units(self): ...
212215
def set_label_text(

‎lib/matplotlib/backends/qt_editor/figureoptions.py

Copy file name to clipboardExpand all lines: lib/matplotlib/backends/qt_editor/figureoptions.py
+3-3Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ def convert_limits(lim, converter):
4242
axis_map = axes._axis_map
4343
axis_limits = {
4444
name: tuple(convert_limits(
45-
getattr(axes, f'get_{name}lim')(), axis.converter
45+
getattr(axes, f'get_{name}lim')(), axis.get_converter()
4646
))
4747
for name, axis in axis_map.items()
4848
}
@@ -66,7 +66,7 @@ def convert_limits(lim, converter):
6666

6767
# Save the converter and unit data
6868
axis_converter = {
69-
name: axis.converter
69+
name: axis.get_converter()
7070
for name, axis in axis_map.items()
7171
}
7272
axis_units = {
@@ -209,7 +209,7 @@ def apply_callback(data):
209209
axis.set_label_text(axis_label)
210210

211211
# Restore the unit data
212-
axis.converter = axis_converter[name]
212+
axis._set_converter(axis_converter[name])
213213
axis.set_units(axis_units[name])
214214

215215
# Set / Curves

‎lib/matplotlib/tests/test_dates.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_dates.py
+4-2Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -668,10 +668,12 @@ def test_concise_converter_stays():
668668
fig, ax = plt.subplots()
669669
ax.plot(x, y)
670670
# Bypass Switchable date converter
671-
ax.xaxis.converter = conv = mdates.ConciseDateConverter()
671+
conv = mdates.ConciseDateConverter()
672+
with pytest.warns(UserWarning, match="already has a converter"):
673+
ax.xaxis.set_converter(conv)
672674
assert ax.xaxis.units is None
673675
ax.set_xlim(*x)
674-
assert ax.xaxis.converter == conv
676+
assert ax.xaxis.get_converter() == conv
675677

676678

677679
def test_offset_changes():

‎lib/matplotlib/tests/test_units.py

Copy file name to clipboardExpand all lines: lib/matplotlib/tests/test_units.py
+27-1Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import matplotlib.pyplot as plt
66
from matplotlib.testing.decorators import check_figures_equal, image_comparison
77
import matplotlib.units as munits
8-
from matplotlib.category import UnitData
8+
from matplotlib.category import StrCategoryConverter, UnitData
99
import numpy as np
1010
import pytest
1111

@@ -236,6 +236,32 @@ def test_shared_axis_categorical():
236236
assert "c" in ax2.xaxis.get_units()._mapping.keys()
237237

238238

239+
def test_explicit_converter():
240+
d1 = {"a": 1, "b": 2}
241+
str_cat_converter = StrCategoryConverter()
242+
str_cat_converter_2 = StrCategoryConverter()
243+
244+
# Explicit is set
245+
fig1, ax1 = plt.subplots()
246+
ax1.xaxis.set_converter(str_cat_converter)
247+
assert ax1.xaxis.get_converter() == str_cat_converter
248+
# Explicit not overridden by implicit
249+
ax1.plot(d1.keys(), d1.values())
250+
assert ax1.xaxis.get_converter() == str_cat_converter
251+
# No error when called twice with equivalent input
252+
ax1.xaxis.set_converter(str_cat_converter)
253+
# Error when explicit called twice
254+
with pytest.raises(RuntimeError):
255+
ax1.xaxis.set_converter(str_cat_converter_2)
256+
257+
# Warn when implicit overridden
258+
fig2, ax2 = plt.subplots()
259+
ax2.plot(d1.keys(), d1.values())
260+
261+
with pytest.warns():
262+
ax2.xaxis.set_converter(str_cat_converter)
263+
264+
239265
def test_empty_default_limits(quantity_converter):
240266
munits.registry[Quantity] = quantity_converter
241267
fig, ax1 = plt.subplots()

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.