diff --git a/lib/matplotlib/artist.py b/lib/matplotlib/artist.py index 7fb017f262ad..f0ce9aae2f28 100644 --- a/lib/matplotlib/artist.py +++ b/lib/matplotlib/artist.py @@ -190,6 +190,40 @@ def convert_yunits(self, y): return y return ax.yaxis.convert_units(y) + def convert_xunits_delta(self, dx): + """ + Convert *dx* using the unit type of the yaxis. + + This is used for APIs where the data is passed as ``(x, dx)``, and + the ``dx`` can be in different units than the ``x``. For instance, + for dates ``x`` is often of type ``datetime``, and ``dx`` of type + ``timedelta`` and these are converted differently. + + If the artist is not in contained in an Axes or if the yaxis does not + have units, *deltax* itself is returned. + """ + ax = getattr(self, 'axes', None) + if ax is None or ax.xaxis is None: + return dx + return ax.xaxis.convert_units_delta(dx) + + def convert_yunits_delta(self, dy): + """ + Convert *dy* using the unit type of the yaxis. + + This is used for APIs where the data is passed as ``(y, dy)``, and + the ``dy`` can be in different units than the ``y``. For instance, + for dates ``y`` is often of type ``datetime``, and ``dy`` of type + ``timedelta`` and these are converted differently. + + If the artist is not in contained in an Axes or if the yaxis does not + have units, *deltay* itself is returned. + """ + ax = getattr(self, 'axes', None) + if ax is None or ax.yaxis is None: + return dy + return ax.yaxis.convert_units_delta(dy) + @property def axes(self): """The `~.axes.Axes` instance the artist resides in, or *None*.""" diff --git a/lib/matplotlib/axes/_axes.py b/lib/matplotlib/axes/_axes.py index 9037674780e2..de99ea918617 100644 --- a/lib/matplotlib/axes/_axes.py +++ b/lib/matplotlib/axes/_axes.py @@ -2526,10 +2526,16 @@ def broken_barh(self, xranges, yrange, **kwargs): self._process_unit_info(xdata=xdata, ydata=ydata, kwargs=kwargs) - xranges = self.convert_xunits(xranges) + + xnew = [] + for ind in range(len(xranges)): + xr = [[],[]] + xr[0] = self.convert_xunits(xranges[ind][0]) + xr[1] = self.convert_xunits_delta(xranges[ind][1]) + xnew.append(xr) yrange = self.convert_yunits(yrange) - col = mcoll.BrokenBarHCollection(xranges, yrange, **kwargs) + col = mcoll.BrokenBarHCollection(xnew, yrange, **kwargs) self.add_collection(col, autolim=True) self.autoscale_view() diff --git a/lib/matplotlib/axis.py b/lib/matplotlib/axis.py index 4f174b0da45e..34cf2ad3e1ca 100644 --- a/lib/matplotlib/axis.py +++ b/lib/matplotlib/axis.py @@ -795,6 +795,7 @@ def cla(self): self.reset_ticks() self.converter = None + self.converter_delta = None self.units = None self.set_units(None) self.stale = True @@ -1490,6 +1491,18 @@ def convert_units(self, x): ret = self.converter.convert(x, self.units, self) return ret + def convert_units_delta(self, dx): + # If dx is already a number, doesn't need converting + if (munits.ConversionInterface.is_numlike(dx) or + self.converter is None): + return dx + + if hasattr(self.converter, 'convert_delta'): + return self.converter.convert_delta(dx, self.units, self) + else: + return self.converter.convert(dx, self.units, self) + + def set_units(self, u): """ Set the units for axis. diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index c9eb3a1ca230..93d91ce8c666 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -238,8 +238,21 @@ def _to_ordinalf(dt): return base +def _td_to_ordinalf(dt): + if isinstance(dt, datetime.timedelta): + base = dt / datetime.timedelta(days=1) + return base + + # a version of _to_ordinalf that can operate on numpy arrays _to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf) +# a version of _to_ordinalf that can operate on numpy arrays +_td_to_ordinalf_np_vectorized = np.vectorize(_td_to_ordinalf) + + +def _td64_to_ordinalf(d): + print('d', d) + return d / np.timedelta64(1, 'D') def _dt64_to_ordinalf(d): @@ -271,6 +284,16 @@ def _dt64_to_ordinalf(d): return dt +def _dt64_to_ordinalf_iterable(d): + return np.fromiter((_dt64_to_ordinalf(dd) for dd in d), + float, count=len(d)) + + +def _td64_to_ordinalf_iterable(d): + return np.fromiter((_td64_to_ordinalf(dd) for dd in d), + float, count=len(d)) + + def _from_ordinalf(x, tz=None): """ Convert Gregorian float of the date, preserving hours, minutes, @@ -405,22 +428,86 @@ def date2num(d): Gregorian calendar is assumed; this is not universal practice. For details see the module docstring. """ + if hasattr(d, "values"): # this unpacks pandas series or dataframes... d = d.values - if not np.iterable(d): - if (isinstance(d, np.datetime64) or (isinstance(d, np.ndarray) and - np.issubdtype(d.dtype, np.datetime64))): - return _dt64_to_ordinalf(d) - return _to_ordinalf(d) - else: - d = np.asarray(d) - if np.issubdtype(d.dtype, np.datetime64): + if not np.iterable(d) and not isinstance(d, np.ndarray): + # single value logic... + if (isinstance(d, np.datetime64) or isinstance(d, np.timedelta64)): return _dt64_to_ordinalf(d) - if not d.size: - return d + else: + return _to_ordinalf(d) + + elif (isinstance(d, np.ndarray) and + (np.issubdtype(d.dtype, np.datetime64) or + np.issubdtype(d.dtype, np.timedelta64))): + # array with all one type of datetime64 object. + return _dt64_to_ordinalf(d) + + elif len(d): + # this is a list or tuple... + if (isinstance(d[0], np.datetime64) or + isinstance(d[0], np.timedelta64)): + return _dt64_to_ordinalf_iterable(d) return _to_ordinalf_np_vectorized(d) + elif hasattr(d, 'size') and not d.size: + # this elif doesn't get tested, but leaving here in case anyone + # needs it. + return d + else: + return [] + + +def timedelta2num(d): + """ + Convert datetime objects to Matplotlib dates. + + Parameters + ---------- + d : `datetime.datetime` or `numpy.datetime64` or sequences of these + + Returns + ------- + float or sequence of floats + Number of days (fraction part represents hours, minutes, seconds, ms) + since 0001-01-01 00:00:00 UTC, plus one. + + Notes + ----- + The addition of one here is a historical artifact. Also, note that the + Gregorian calendar is assumed; this is not universal practice. + For details see the module docstring. + """ + + if hasattr(d, "values"): + # this unpacks pandas series or dataframes... + d = d.values + + if not np.iterable(d) and not isinstance(d, np.ndarray): + # single value logic... + if isinstance(d, np.timedelta64): + return _td64_to_ordinalf(d) + else: + return _td_to_ordinalf(d) + + elif (isinstance(d, np.ndarray) and + np.issubdtype(d.dtype, np.timedelta64)): + # array with all one type of datetime64 object. + return _td64_to_ordinalf(d) + + elif len(d): + # this is a list or tuple... + if isinstance(d[0], np.timedelta64): + return _td64_to_ordinalf_iterable(d) + return _td_to_ordinalf_np_vectorized(d) + elif hasattr(d, 'size') and not d.size: + # this elif doesn't get tested, but leaving here in case anyone + # needs it. + return d + else: + return [] def julian2num(j): @@ -1806,6 +1893,10 @@ def convert(value, unit, axis): """ return date2num(value) + @staticmethod + def convert_delta(value, units, axis): + return timedelta2num(value) + @staticmethod def default_units(x, axis): """ diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index 35e005346335..10ba781609b0 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -680,3 +680,36 @@ def test_datetime64_in_list(): dt = [np.datetime64('2000-01-01'), np.datetime64('2001-01-01')] dn = mdates.date2num(dt) assert np.array_equal(dn, [730120., 730486.]) + + +def test_timedelta(): + """ + Test that timedelta objects are properly translated into days. + """ + dt = [datetime.datetime(2000, 1, 1, 0, 0, 0), + datetime.timedelta(days=1, hours=2)] + assert mdates.date2num(dt[1]) == 1 + 2 / 24 + # check that mixed lists work.... + assert mdates.date2num(dt)[0] == 730120.0 + assert mdates.date2num(dt)[1] == 1 + 2 / 24 + + dt = (np.datetime64('2000-01-01'), + np.timedelta64(26, 'h')) + assert mdates.date2num(dt[1]) == 1 + 2 / 24 + # check that mixed lists work.... + assert mdates.date2num(dt)[0] == 730120.0 + assert mdates.date2num(dt)[1] == 1 + 2 / 24 + + dt = [datetime.timedelta(days=1, hours=1), + datetime.timedelta(days=1, hours=2)] + assert mdates.date2num(dt)[0] == 1 + 1 / 24 + assert mdates.date2num(dt)[1] == 1 + 2 / 24 + + dt = (np.timedelta64(25, 'h'), + np.timedelta64(26, 'h')) + assert mdates.date2num(dt)[0] == 1 + 1 / 24 + assert mdates.date2num(dt)[1] == 1 + 2 / 24 + + dt = np.array([25, 26], dtype='timedelta64[h]') + assert mdates.date2num(dt)[0] == 1 + 1 / 24 + assert mdates.date2num(dt)[1] == 1 + 2 / 24 pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy