diff --git a/doc/api/api_changes/2015-04-12_microsecondlocator.rst b/doc/api/api_changes/2015-04-12_microsecondlocator.rst new file mode 100644 index 000000000000..d8366041c535 --- /dev/null +++ b/doc/api/api_changes/2015-04-12_microsecondlocator.rst @@ -0,0 +1,17 @@ +Removed `args` and `kwargs` from `MicrosecondLocator.__call__` +`````````````````````````````````````````````````````````````` + +The call signature of :meth:`~matplotlib.dates.MicrosecondLocator.__call__` +has changed from `__call__(self, *args, **kwargs)` to `__call__(self)`. +This is consistent with the super class :class:`~matplotlib.ticker.Locator` +and also all the other Locators derived from this super class. + + +No `ValueError` for the MicrosecondLocator and YearLocator +`````````````````````````````````````````````````````````` + +The :class:`~matplotlib.dates.MicrosecondLocator` and +:class:`~matplotlib.dates.YearLocator` objects when called will return +an empty list if the axes have no data or the view has no interval. +Previously, they raised a `ValueError`. This is consistent with all +the Date Locators. diff --git a/doc/users/whats_new/datelocators.rst b/doc/users/whats_new/datelocators.rst new file mode 100644 index 000000000000..b894a323587d --- /dev/null +++ b/doc/users/whats_new/datelocators.rst @@ -0,0 +1,15 @@ +Date Locators +------------- + +Date Locators (derived from :class:`~matplotlib.dates.DateLocator`) now +implement the :meth:`~matplotlib.tickers.Locator.tick_values` method. +This is expected of all Locators derived from :class:`~matplotlib.tickers.Locator`. + +The Date Locators can now be used easily without creating axes + + from datetime import datetime + from matplotlib.dates import YearLocator + t0 = datetime(2002, 10, 9, 12, 10) + tf = datetime(2005, 10, 9, 12, 15) + loc = YearLocator() + values = loc.tick_values(t0, tf) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index e3f480a0c139..33299be854e4 100755 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -667,18 +667,21 @@ def __call__(self): except ValueError: return [] - if dmin > dmax: - dmax, dmin = dmin, dmax - delta = relativedelta(dmax, dmin) + return self.tick_values(dmin, dmax) + + def tick_values(self, vmin, vmax): + if vmin > vmax: + vmax, vmin = vmin, vmax + delta = relativedelta(vmax, vmin) # We need to cap at the endpoints of valid datetime try: - start = dmin - delta + start = vmin - delta except ValueError: start = _from_ordinalf(1.0) try: - stop = dmax + delta + stop = vmax + delta except ValueError: # The magic number! stop = _from_ordinalf(3652059.9999999) @@ -688,19 +691,19 @@ def __call__(self): # estimate the number of ticks very approximately so we don't # have to do a very expensive (and potentially near infinite) # 'between' calculation, only to find out it will fail. - nmax, nmin = date2num((dmax, dmin)) + nmax, nmin = date2num((vmax, vmin)) estimate = (nmax - nmin) / (self._get_unit() * self._get_interval()) # This estimate is only an estimate, so be really conservative # about bailing... if estimate > self.MAXTICKS * 2: raise RuntimeError( 'RRuleLocator estimated to generate %d ticks from %s to %s: ' - 'exceeds Locator.MAXTICKS * 2 (%d) ' % (estimate, dmin, dmax, + 'exceeds Locator.MAXTICKS * 2 (%d) ' % (estimate, vmin, vmax, self.MAXTICKS * 2)) - dates = self.rule.between(dmin, dmax, True) + dates = self.rule.between(vmin, vmax, True) if len(dates) == 0: - return date2num([dmin, dmax]) + return date2num([vmin, vmax]) return self.raise_if_exceeds(date2num(dates)) def _get_unit(self): @@ -866,6 +869,9 @@ def __call__(self): self.refresh() return self._locator() + def tick_values(self, vmin, vmax): + return self.get_locator(vmin, vmax).tick_values(vmin, vmax) + def nonsingular(self, vmin, vmax): # whatever is thrown at us, we can scale the unit. # But default nonsingular date plots at an ~4 year period. @@ -1012,11 +1018,19 @@ def __init__(self, base=1, month=1, day=1, tz=None): } def __call__(self): - dmin, dmax = self.viewlim_to_dt() - ymin = self.base.le(dmin.year) - ymax = self.base.ge(dmax.year) + # if no data have been set, this will tank with a ValueError + try: + dmin, dmax = self.viewlim_to_dt() + except ValueError: + return [] + + return self.tick_values(dmin, dmax) + + def tick_values(self, vmin, vmax): + ymin = self.base.le(vmin.year) + ymax = self.base.ge(vmax.year) - ticks = [dmin.replace(year=ymin, **self.replaced)] + ticks = [vmin.replace(year=ymin, **self.replaced)] while 1: dt = ticks[-1] if dt.year >= ymax: @@ -1184,11 +1198,20 @@ def set_data_interval(self, vmin, vmax): self._wrapped_locator.set_data_interval(vmin, vmax) return DateLocator.set_data_interval(self, vmin, vmax) - def __call__(self, *args, **kwargs): - vmin, vmax = self.axis.get_view_interval() - vmin *= MUSECONDS_PER_DAY - vmax *= MUSECONDS_PER_DAY - ticks = self._wrapped_locator.tick_values(vmin, vmax) + def __call__(self): + # if no data have been set, this will tank with a ValueError + try: + dmin, dmax = self.viewlim_to_dt() + except ValueError: + return [] + + return self.tick_values(dmin, dmax) + + def tick_values(self, vmin, vmax): + nmin, nmax = date2num((vmin, vmax)) + nmin *= MUSECONDS_PER_DAY + nmax *= MUSECONDS_PER_DAY + ticks = self._wrapped_locator.tick_values(nmin, nmax) ticks = [tick / MUSECONDS_PER_DAY for tick in ticks] return ticks