Repository navigation
Description
Bug report
Calling plot with xunits=None or yunits=None more than once throws an exception.
Matplotlib version
- Operating system: RedHat Enterprise 7
- Matplotlib version: 2.2.2
- Matplotlib backend (
print(matplotlib.get_backend())): Qt5Agg - Python version: 2.7.14
Bug summary
Note that I know this looks weird - but this in an application library that where the user may or may not set units. So we always pass that info though to the kwarg and it may be None in some cases.
import pylab as P
from datetime import datetime
t0 = datetime( 2018, 3, 1 )
t1 = datetime( 2018, 3, 2 )
t2 = datetime( 2018, 3, 3 )
t3 = datetime( 2018, 3, 4 )
# First call works, second call throws.
P.plot( [t0, t1], ["V1", "V1"], xunits=None, yunits=None )
P.plot( [t2, t3], ["V1", "V1"], xunits=None, yunits=None )
P.show()Actual outcome
Traceback (most recent call last):
File "t.py", line 10, in <module>
P.plot( [t2, t3], ["V1", "V1"], xunits=None, yunits=None )
File "site-packages/matplotlib/pyplot.py", line 3358, in plot
ret = ax.plot(*args, **kwargs)
File "site-packages/matplotlib/__init__.py", line 1855, in inner
return func(ax, *args, **kwargs)
File "site-packages/matplotlib/axes/_axes.py", line 1527, in plot
for line in self._get_lines(*args, **kwargs):
File "site-packages/matplotlib/axes/_base.py", line 194, in __call__
self.axes.yaxis.set_units(yunits)
File "site-packages/matplotlib/axis.py", line 1544, in set_units
self._update_axisinfo()
File "site-packages/matplotlib/axis.py", line 1486, in _update_axisinfo
info = self.converter.axisinfo(self.units, self)
File "site-packages/matplotlib/category.py", line 93, in axisinfo
majloc = StrCategoryLocator(unit._mapping)
AttributeError: 'NoneType' object has no attribute '_mapping'The issue is in axes/_base.py:2130 in the _process_unit_info() method. If the xunits or yunits kwarg is set, it's used even if it's None. The question here is what is None in this context. Before 2.2.2, None meant "use the default units". But now that's not possible because it depends on what was plotted before that call whether that will work or not.
My suggestion is that None should mean "I'm not setting this" and behave the way it did before. I think that adding these lines to process_unit_info would fix this (repeat for yunits).
xunits = kwargs.pop('xunits', self.xaxis.units)
if xunits is None:
xunits = self.axis.units
Activity
story645 commentedon Apr 23, 2018
I think the problem is that string categorical is stashing a units mapping object unit. The information you're passing is a string, and so maybe it should be stashed as an attribute on the units object?
TD22057 commentedon Apr 24, 2018
Sorry - I don't understand what you mean. What is "the information you're passing"? I think it's important to answer the question: what does passing xunits=None mean? Then we can talk about solutions and code. This is probably related to the changes that were made for locking units to an axis once they are set but I think None wasn't handled in that case.
jklymak commentedon Apr 24, 2018
I don't think the units-locking PR ever got merged... (#9776), but a bunch of stuff was done to categoricals, which this issue would seem to touch because you are passing string arguments in...
story645 commentedon Apr 24, 2018
@TD22057 sorry, I meant that the yaxis data is ["V1", "V1"], which means it hits the StrCategorical code path. In the StrCategorical code path, the
axis.unitsattribute gets set to a UnitData object:So xunits or yunits = "None" now overwrites that object.
tacaswell commentedon Apr 24, 2018
I think @TD22057 's suggestion is correct, we should ignore
Nonewhen passed as xunit or yunit.story645 commentedon Apr 24, 2018
Probably, but I think it raises an issue of what happens when xunit or yunit isn't none but is a string. The JPL string implementation uses it to store sorting (which I think is a smart idea), but currently any use of it will clobber the object on strings.
dstansby commentedon Jul 2, 2020
I've added a nicer error message in #17824. It seems that unless we want to enforce
Noneas a "do-nothing" unit in Matplotlib (which would be a big API change if external libraries have converters that takeNoneas a validunit), this isn't fixable.tacaswell commentedon Jul 3, 2020
@TD22057 Sorry this fell by the wayside for so long.
@dstansby that is not quite what is being proposed.
Axis.unitsis inited toNoneif we explicitly callaxes.set_units(None)it would still be cleared, what @TD22057 is asking for is if you passxuints=Nonetoax.plotit gets treated as "not really passed" / "do the the default thing" (which is this case is "do no change the units") which seems like the correct API (you can change but not clear) the units fromax.plot. I suspect that this has "worked" in most cases because most other converters are not storing useful / critical information in the units attribute.This first stopped working (but with a slightly different exception that may never have been released) in 34b8eb4 which changed from storing the string <-> float mapping in a second attribute to re-using the 'units` attribute (which seems to be a defend-able position, but I think we may have miss-understood how it was generally being used).
API: treat xunits=None and yunits=None as "default"
API: treat xunits=None and yunits=None as "default"
API: treat xunits=None and yunits=None as "default"
API: treat xunits=None and yunits=None as "default"
API: treat xunits=None and yunits=None as "default"