diff --git a/lib/matplotlib/axes/_base.py b/lib/matplotlib/axes/_base.py index 0e63b57aea37..6653d85c9cf3 100644 --- a/lib/matplotlib/axes/_base.py +++ b/lib/matplotlib/axes/_base.py @@ -7,9 +7,10 @@ from six.moves import xrange import itertools -import warnings +import logging import math from operator import attrgetter +import warnings import numpy as np @@ -38,6 +39,8 @@ from matplotlib.rcsetup import cycler from matplotlib.rcsetup import validate_axisbelow +_log = logging.getLogger(__name__) + rcParams = matplotlib.rcParams is_string_like = cbook.is_string_like @@ -1002,7 +1005,7 @@ def cla(self): else: self.xaxis._set_scale('linear') try: - self.set_xlim(0, 1) + self.set_xlim(0, 1, _converter=False) except TypeError: pass @@ -1016,7 +1019,7 @@ def cla(self): else: self.yaxis._set_scale('linear') try: - self.set_ylim(0, 1) + self.set_ylim(0, 1, _converter=False) except TypeError: pass @@ -2377,6 +2380,7 @@ def autoscale_view(self, tight=None, scalex=True, scaley=True): case, use :meth:`matplotlib.axes.Axes.relim` prior to calling autoscale_view. """ + _log.debug('Autoscaling x: %s y: %s', scalex, scaley) if tight is not None: self._tight = bool(tight) @@ -2446,6 +2450,7 @@ def handle_single_axis(scale, autoscaleon, shared_axes, interval, if not self._tight: x0, x1 = locator.view_limits(x0, x1) + _log.debug('Autolims: %e %e', x0, x1) set_bound(x0, x1) # End of definition of internal function 'handle_single_axis'. @@ -2923,6 +2928,7 @@ def set_xbound(self, lower=None, upper=None): self.set_xlim(upper, lower, auto=None) else: self.set_xlim(lower, upper, auto=None) + self.set_xlim(lower, upper, auto=None) else: if lower < upper: self.set_xlim(lower, upper, auto=None) @@ -3028,15 +3034,19 @@ def set_xlim(self, left=None, right=None, emit=True, auto=False, **kw): left = kw.pop('xmin') if 'xmax' in kw: right = kw.pop('xmax') + # _converter is private, usually True, but can be false + _converter = kw.pop('_converter', True) if kw: raise ValueError("unrecognized kwargs: %s" % list(kw)) if right is None and iterable(left): left, right = left - self._process_unit_info(xdata=(left, right)) - left = self._validate_converted_limits(left, self.convert_xunits) - right = self._validate_converted_limits(right, self.convert_xunits) + if _converter: + _log.debug('Converting xlim %s %s', left, right) + self._process_unit_info(xdata=(left, right)) + left = self._validate_converted_limits(left, self.convert_xunits) + right = self._validate_converted_limits(right, self.convert_xunits) old_left, old_right = self.get_xlim() if left is None: @@ -3352,14 +3362,20 @@ def set_ylim(self, bottom=None, top=None, emit=True, auto=False, **kw): bottom = kw.pop('ymin') if 'ymax' in kw: top = kw.pop('ymax') + _converter = kw.pop('_converter', True) if kw: raise ValueError("unrecognized kwargs: %s" % list(kw)) if top is None and iterable(bottom): bottom, top = bottom - bottom = self._validate_converted_limits(bottom, self.convert_yunits) - top = self._validate_converted_limits(top, self.convert_yunits) + if _converter: + _log.debug('Converting ylim %s %s', bottom, top) + self._process_unit_info(ydata=(bottom, top)) + bottom = self._validate_converted_limits(bottom, + self.convert_yunits) + top = self._validate_converted_limits(top, + self.convert_yunits) old_bottom, old_top = self.get_ylim() diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 6e40f4138042..e6bec5cb1da7 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -7,6 +7,7 @@ import six import logging +import warnings from matplotlib import rcParams import matplotlib.artist as artist @@ -22,7 +23,8 @@ import matplotlib.transforms as mtransforms import matplotlib.units as munits import numpy as np -import warnings + +_log = logging.getLogger(__name__) _log = logging.getLogger(__name__) @@ -1434,10 +1436,25 @@ def update_units(self, data): if *data* is registered for unit conversion. """ + _log.debug('update_units converter due to %s %s', + data, data.__class__,) converter = munits.registry.get_converter(data) + _log.debug('new converter: %s', converter) if converter is None: + _log.info('No converter found for this data') return False + if self.converter is not None: + if self.converter == converter: + _log.debug('Axis already has a converter that matches ' + 'data units') + return True + else: + _log.info('Axis already has a converter, but it does ' + 'not match new data units.') + return False + + neednew = self.converter != converter self.converter = converter default = self.converter.default_units(data, self) @@ -1487,6 +1504,10 @@ def have_units(self): return self.converter is not None or self.units is not None def convert_units(self, x): + """ + convert the units given + """ + if self.converter is None: self.converter = munits.registry.get_converter(x) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index f71f12eea32e..a46cd628a9bb 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -122,6 +122,7 @@ from six.moves import zip import re import time +import logging import math import datetime import functools diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index d7231e61ec47..eed00fc2bd6b 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -607,3 +607,14 @@ def test_tz_utc(): def test_num2timedelta(x, tdelta): dt = mdates.num2timedelta(x) assert dt == tdelta + + +def test_one_non_None_converter(): + # test that we only allow one non-None converter at once: + base = datetime.datetime(2017, 1, 1, 0, 10, 0) + time = [base - datetime.timedelta(days=x) for x in range(0, 3)] + data = [0., 2., 4.] + fig, ax = plt.subplots() + ax.plot(time, data) + with pytest.raises(AttributeError): + ax.plot(['a', 'b'], [1., 2.]) diff --git a/lib/matplotlib/units.py b/lib/matplotlib/units.py index 793fad3cb01f..314643cdd01c 100644 --- a/lib/matplotlib/units.py +++ b/lib/matplotlib/units.py @@ -171,4 +171,25 @@ def get_converter(self, x): return converter +class DefaultConverter(ConversionInterface): + """ + Do-nothing converter to catch good entries and lock out other + converters. + """ + @staticmethod + def convert(obj, unit, axis): + """ + convert obj using unit for the specified axis. If obj is a sequence, + return the converted sequence. The output must be a sequence of + scalars that can be used by the numpy array layer + """ + return np.asarray(obj, dtype=np.float64) + + registry = Registry() +registry[np.float64] = DefaultConverter() +registry[np.float32] = DefaultConverter() +registry[np.int32] = DefaultConverter() +registry[np.int64] = DefaultConverter() +registry[float] = DefaultConverter() +registry[int] = DefaultConverter() diff --git a/lib/mpl_toolkits/mplot3d/axes3d.py b/lib/mpl_toolkits/mplot3d/axes3d.py index a6c3251c2028..052cb74a5bea 100644 --- a/lib/mpl_toolkits/mplot3d/axes3d.py +++ b/lib/mpl_toolkits/mplot3d/axes3d.py @@ -612,15 +612,17 @@ def set_xlim3d(self, left=None, right=None, emit=True, auto=False, **kw): left = kw.pop('xmin') if 'xmax' in kw: right = kw.pop('xmax') + _converter = kw.pop('_converter', True) if kw: raise ValueError("unrecognized kwargs: %s" % list(kw)) if right is None and cbook.iterable(left): left, right = left - self._process_unit_info(xdata=(left, right)) - left = self._validate_converted_limits(left, self.convert_xunits) - right = self._validate_converted_limits(right, self.convert_xunits) + if _converter: + self._process_unit_info(xdata=(left, right)) + left = self._validate_converted_limits(left, self.convert_xunits) + right = self._validate_converted_limits(right, self.convert_xunits) old_left, old_right = self.get_xlim() if left is None: @@ -664,15 +666,18 @@ def set_ylim3d(self, bottom=None, top=None, emit=True, auto=False, **kw): bottom = kw.pop('ymin') if 'ymax' in kw: top = kw.pop('ymax') + _converter = kw.pop('_converter', True) if kw: raise ValueError("unrecognized kwargs: %s" % list(kw)) if top is None and cbook.iterable(bottom): bottom, top = bottom - self._process_unit_info(ydata=(bottom, top)) - bottom = self._validate_converted_limits(bottom, self.convert_yunits) - top = self._validate_converted_limits(top, self.convert_yunits) + if _converter: + self._process_unit_info(ydata=(bottom, top)) + bottom = self._validate_converted_limits(bottom, + self.convert_yunits) + top = self._validate_converted_limits(top, self.convert_yunits) old_bottom, old_top = self.get_ylim() if bottom is None: