Buckets:
| """ | |
| Matplotlib provides sophisticated date plotting capabilities, standing on the | |
| shoulders of python :mod:`datetime` and the add-on module dateutil_. | |
| By default, Matplotlib uses the units machinery described in | |
| `~matplotlib.units` to convert `datetime.datetime`, and `numpy.datetime64` | |
| objects when plotted on an x- or y-axis. The user does not | |
| need to do anything for dates to be formatted, but dates often have strict | |
| formatting needs, so this module provides many tick locators and formatters. | |
| A basic example using `numpy.datetime64` is:: | |
| import numpy as np | |
| times = np.arange(np.datetime64('2001-01-02'), | |
| np.datetime64('2002-02-03'), np.timedelta64(75, 'm')) | |
| y = np.random.randn(len(times)) | |
| fig, ax = plt.subplots() | |
| ax.plot(times, y) | |
| .. seealso:: | |
| - :doc:`/gallery/text_labels_and_annotations/date` | |
| - :doc:`/gallery/ticks/date_concise_formatter` | |
| - :doc:`/gallery/ticks/date_demo_convert` | |
| .. _date-format: | |
| Matplotlib date format | |
| ---------------------- | |
| Matplotlib represents dates using floating point numbers specifying the number | |
| of days since a default epoch of 1970-01-01 UTC; for example, | |
| 1970-01-01, 06:00 is the floating point number 0.25. The formatters and | |
| locators require the use of `datetime.datetime` objects, so only dates between | |
| year 0001 and 9999 can be represented. Microsecond precision | |
| is achievable for (approximately) 70 years on either side of the epoch, and | |
| 20 microseconds for the rest of the allowable range of dates (year 0001 to | |
| 9999). The epoch can be changed at import time via `.dates.set_epoch` or | |
| :rc:`date.epoch` to other dates if necessary; see | |
| :doc:`/gallery/ticks/date_precision_and_epochs` for a discussion. | |
| .. note:: | |
| Before Matplotlib 3.3, the epoch was 0000-12-31 which lost modern | |
| microsecond precision and also made the default axis limit of 0 an invalid | |
| datetime. In 3.3 the epoch was changed as above. To convert old | |
| ordinal floats to the new epoch, users can do:: | |
| new_ordinal = old_ordinal + mdates.date2num(np.datetime64('0000-12-31')) | |
| There are a number of helper functions to convert between :mod:`datetime` | |
| objects and Matplotlib dates: | |
| .. currentmodule:: matplotlib.dates | |
| .. autosummary:: | |
| :nosignatures: | |
| datestr2num | |
| date2num | |
| num2date | |
| num2timedelta | |
| drange | |
| set_epoch | |
| get_epoch | |
| .. note:: | |
| Like Python's `datetime.datetime`, Matplotlib uses the Gregorian calendar | |
| for all conversions between dates and floating point numbers. This practice | |
| is not universal, and calendar differences can cause confusing | |
| differences between what Python and Matplotlib give as the number of days | |
| since 0001-01-01 and what other software and databases yield. For | |
| example, the US Naval Observatory uses a calendar that switches | |
| from Julian to Gregorian in October, 1582. Hence, using their | |
| calculator, the number of days between 0001-01-01 and 2006-04-01 is | |
| 732403, whereas using the Gregorian calendar via the datetime | |
| module we find:: | |
| In [1]: date(2006, 4, 1).toordinal() - date(1, 1, 1).toordinal() | |
| Out[1]: 732401 | |
| All the Matplotlib date converters, locators and formatters are timezone aware. | |
| If no explicit timezone is provided, :rc:`timezone` is assumed, provided as a | |
| string. If you want to use a different timezone, pass the *tz* keyword | |
| argument of `num2date` to any date tick locators or formatters you create. This | |
| can be either a `datetime.tzinfo` instance or a string with the timezone name | |
| that can be parsed by `~dateutil.tz.gettz`. | |
| A wide range of specific and general purpose date tick locators and | |
| formatters are provided in this module. See | |
| :mod:`matplotlib.ticker` for general information on tick locators | |
| and formatters. These are described below. | |
| The dateutil_ module provides additional code to handle date ticking, making it | |
| easy to place ticks on any kinds of dates. See examples below. | |
| .. _dateutil: https://dateutil.readthedocs.io | |
| .. _date-locators: | |
| Date tick locators | |
| ------------------ | |
| Most of the date tick locators can locate single or multiple ticks. For example:: | |
| # import constants for the days of the week | |
| from matplotlib.dates import MO, TU, WE, TH, FR, SA, SU | |
| # tick on Mondays every week | |
| loc = WeekdayLocator(byweekday=MO, tz=tz) | |
| # tick on Mondays and Saturdays | |
| loc = WeekdayLocator(byweekday=(MO, SA)) | |
| In addition, most of the constructors take an interval argument:: | |
| # tick on Mondays every second week | |
| loc = WeekdayLocator(byweekday=MO, interval=2) | |
| The rrule locator allows completely general date ticking:: | |
| # tick every 5th easter | |
| rule = rrulewrapper(YEARLY, byeaster=1, interval=5) | |
| loc = RRuleLocator(rule) | |
| The available date tick locators are: | |
| * `MicrosecondLocator`: Locate microseconds. | |
| * `SecondLocator`: Locate seconds. | |
| * `MinuteLocator`: Locate minutes. | |
| * `HourLocator`: Locate hours. | |
| * `DayLocator`: Locate specified days of the month. | |
| * `WeekdayLocator`: Locate days of the week, e.g., MO, TU. | |
| * `MonthLocator`: Locate months, e.g., 7 for July. | |
| * `YearLocator`: Locate years that are multiples of base. | |
| * `RRuleLocator`: Locate using a `rrulewrapper`. | |
| `rrulewrapper` is a simple wrapper around dateutil_'s `dateutil.rrule` | |
| which allow almost arbitrary date tick specifications. | |
| See :doc:`rrule example </gallery/ticks/date_demo_rrule>`. | |
| * `AutoDateLocator`: On autoscale, this class picks the best `DateLocator` | |
| (e.g., `RRuleLocator`) to set the view limits and the tick locations. If | |
| called with ``interval_multiples=True`` it will make ticks line up with | |
| sensible multiples of the tick intervals. For example, if the interval is | |
| 4 hours, it will pick hours 0, 4, 8, etc. as ticks. This behaviour is not | |
| guaranteed by default. | |
| .. _date-formatters: | |
| Date formatters | |
| --------------- | |
| The available date formatters are: | |
| * `AutoDateFormatter`: attempts to figure out the best format to use. This is | |
| most useful when used with the `AutoDateLocator`. | |
| * `ConciseDateFormatter`: also attempts to figure out the best format to use, | |
| and to make the format as compact as possible while still having complete | |
| date information. This is most useful when used with the `AutoDateLocator`. | |
| * `DateFormatter`: use `~datetime.datetime.strftime` format strings. | |
| """ | |
| import datetime | |
| import functools | |
| import logging | |
| import re | |
| from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY, | |
| MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, | |
| SECONDLY) | |
| from dateutil.relativedelta import relativedelta | |
| import dateutil.parser | |
| import dateutil.tz | |
| import numpy as np | |
| import matplotlib as mpl | |
| from matplotlib import _api, cbook, ticker, units | |
| __all__ = ('datestr2num', 'date2num', 'num2date', 'num2timedelta', 'drange', | |
| 'set_epoch', 'get_epoch', 'DateFormatter', 'ConciseDateFormatter', | |
| 'AutoDateFormatter', 'DateLocator', 'RRuleLocator', | |
| 'AutoDateLocator', 'YearLocator', 'MonthLocator', 'WeekdayLocator', | |
| 'DayLocator', 'HourLocator', 'MinuteLocator', | |
| 'SecondLocator', 'MicrosecondLocator', | |
| 'rrule', 'MO', 'TU', 'WE', 'TH', 'FR', 'SA', 'SU', | |
| 'YEARLY', 'MONTHLY', 'WEEKLY', 'DAILY', | |
| 'HOURLY', 'MINUTELY', 'SECONDLY', 'MICROSECONDLY', 'relativedelta', | |
| 'DateConverter', 'ConciseDateConverter', 'rrulewrapper') | |
| _log = logging.getLogger(__name__) | |
| UTC = datetime.timezone.utc | |
| def _get_tzinfo(tz=None): | |
| """ | |
| Generate `~datetime.tzinfo` from a string or return `~datetime.tzinfo`. | |
| If None, retrieve the preferred timezone from the rcParams dictionary. | |
| """ | |
| tz = mpl._val_or_rc(tz, 'timezone') | |
| if tz == 'UTC': | |
| return UTC | |
| if isinstance(tz, str): | |
| tzinfo = dateutil.tz.gettz(tz) | |
| if tzinfo is None: | |
| raise ValueError(f"{tz} is not a valid timezone as parsed by" | |
| " dateutil.tz.gettz.") | |
| return tzinfo | |
| if isinstance(tz, datetime.tzinfo): | |
| return tz | |
| raise TypeError(f"tz must be string or tzinfo subclass, not {tz!r}.") | |
| # Time-related constants. | |
| EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal()) | |
| # EPOCH_OFFSET is not used by matplotlib | |
| MICROSECONDLY = SECONDLY + 1 | |
| HOURS_PER_DAY = 24. | |
| MIN_PER_HOUR = 60. | |
| SEC_PER_MIN = 60. | |
| MONTHS_PER_YEAR = 12. | |
| DAYS_PER_WEEK = 7. | |
| DAYS_PER_MONTH = 30. | |
| DAYS_PER_YEAR = 365.0 | |
| MINUTES_PER_DAY = MIN_PER_HOUR * HOURS_PER_DAY | |
| SEC_PER_HOUR = SEC_PER_MIN * MIN_PER_HOUR | |
| SEC_PER_DAY = SEC_PER_HOUR * HOURS_PER_DAY | |
| SEC_PER_WEEK = SEC_PER_DAY * DAYS_PER_WEEK | |
| MUSECONDS_PER_DAY = 1e6 * SEC_PER_DAY | |
| MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY = ( | |
| MO, TU, WE, TH, FR, SA, SU) | |
| WEEKDAYS = (MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY) | |
| # default epoch: passed to np.datetime64... | |
| _epoch = None | |
| def _reset_epoch_test_example(): | |
| """ | |
| Reset the Matplotlib date epoch so it can be set again. | |
| Only for use in tests and examples. | |
| """ | |
| global _epoch | |
| _epoch = None | |
| def set_epoch(epoch): | |
| """ | |
| Set the epoch (origin for dates) for datetime calculations. | |
| The default epoch is :rc:`date.epoch`. | |
| If microsecond accuracy is desired, the date being plotted needs to be | |
| within approximately 70 years of the epoch. Matplotlib internally | |
| represents dates as days since the epoch, so floating point dynamic | |
| range needs to be within a factor of 2^52. | |
| `~.dates.set_epoch` must be called before any dates are converted | |
| (i.e. near the import section) or a RuntimeError will be raised. | |
| See also :doc:`/gallery/ticks/date_precision_and_epochs`. | |
| Parameters | |
| ---------- | |
| epoch : str | |
| valid UTC date parsable by `numpy.datetime64` (do not include | |
| timezone). | |
| """ | |
| global _epoch | |
| if _epoch is not None: | |
| raise RuntimeError('set_epoch must be called before dates plotted.') | |
| _epoch = epoch | |
| def get_epoch(): | |
| """ | |
| Get the epoch used by `.dates`. | |
| Returns | |
| ------- | |
| epoch : str | |
| String for the epoch (parsable by `numpy.datetime64`). | |
| """ | |
| global _epoch | |
| _epoch = mpl._val_or_rc(_epoch, 'date.epoch') | |
| return _epoch | |
| def _dt64_to_ordinalf(d): | |
| """ | |
| Convert `numpy.datetime64` or an `numpy.ndarray` of those types to | |
| Gregorian date as UTC float relative to the epoch (see `.get_epoch`). | |
| Roundoff is float64 precision. Practically: microseconds for dates | |
| between 290301 BC, 294241 AD, milliseconds for larger dates | |
| (see `numpy.datetime64`). | |
| """ | |
| # the "extra" ensures that we at least allow the dynamic range out to | |
| # seconds. That should get out to +/-2e11 years. | |
| dseconds = d.astype('datetime64[s]') | |
| extra = (d - dseconds).astype('timedelta64[ns]') | |
| t0 = np.datetime64(get_epoch(), 's') | |
| dt = (dseconds - t0).astype(np.float64) | |
| dt += extra.astype(np.float64) / 1.0e9 | |
| dt = dt / SEC_PER_DAY | |
| NaT_int = np.datetime64('NaT').astype(np.int64) | |
| d_int = d.astype(np.int64) | |
| dt[d_int == NaT_int] = np.nan | |
| return dt | |
| def _from_ordinalf(x, tz=None): | |
| """ | |
| Convert Gregorian float of the date, preserving hours, minutes, | |
| seconds and microseconds. Return value is a `.datetime`. | |
| The input date *x* is a float in ordinal days at UTC, and the output will | |
| be the specified `.datetime` object corresponding to that time in | |
| timezone *tz*, or if *tz* is ``None``, in the timezone specified in | |
| :rc:`timezone`. | |
| """ | |
| tz = _get_tzinfo(tz) | |
| dt = (np.datetime64(get_epoch()) + | |
| np.timedelta64(int(np.round(x * MUSECONDS_PER_DAY)), 'us')) | |
| if dt < np.datetime64('0001-01-01') or dt >= np.datetime64('10000-01-01'): | |
| raise ValueError(f'Date ordinal {x} converts to {dt} (using ' | |
| f'epoch {get_epoch()}), but Matplotlib dates must be ' | |
| 'between year 0001 and 9999.') | |
| # convert from datetime64 to datetime: | |
| dt = dt.tolist() | |
| # datetime64 is always UTC: | |
| dt = dt.replace(tzinfo=dateutil.tz.gettz('UTC')) | |
| # but maybe we are working in a different timezone so move. | |
| dt = dt.astimezone(tz) | |
| # fix round off errors | |
| if np.abs(x) > 70 * 365: | |
| # if x is big, round off to nearest twenty microseconds. | |
| # This avoids floating point roundoff error | |
| ms = round(dt.microsecond / 20) * 20 | |
| if ms == 1000000: | |
| dt = dt.replace(microsecond=0) + datetime.timedelta(seconds=1) | |
| else: | |
| dt = dt.replace(microsecond=ms) | |
| return dt | |
| # a version of _from_ordinalf that can operate on numpy arrays | |
| _from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf, otypes="O") | |
| # a version of dateutil.parser.parse that can operate on numpy arrays | |
| _dateutil_parser_parse_np_vectorized = np.vectorize(dateutil.parser.parse) | |
| def datestr2num(d, default=None): | |
| """ | |
| Convert a date string to a datenum using `dateutil.parser.parse`. | |
| Parameters | |
| ---------- | |
| d : str or sequence of str | |
| The dates to convert. | |
| default : datetime.datetime, optional | |
| The default date to use when fields are missing in *d*. | |
| """ | |
| if isinstance(d, str): | |
| dt = dateutil.parser.parse(d, default=default) | |
| return date2num(dt) | |
| else: | |
| if default is not None: | |
| d = [date2num(dateutil.parser.parse(s, default=default)) | |
| for s in d] | |
| return np.asarray(d) | |
| d = np.asarray(d) | |
| if not d.size: | |
| return d | |
| return date2num(_dateutil_parser_parse_np_vectorized(d)) | |
| def date2num(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 since the epoch. See `.get_epoch` for the | |
| epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`. If | |
| the epoch is "1970-01-01T00:00:00" (default) then noon Jan 1 1970 | |
| ("1970-01-01T12:00:00") returns 0.5. | |
| Notes | |
| ----- | |
| The Gregorian calendar is assumed; this is not universal practice. | |
| For details see the module docstring. | |
| """ | |
| # Unpack in case of e.g. Pandas or xarray object | |
| d = cbook._unpack_to_numpy(d) | |
| # make an iterable, but save state to unpack later: | |
| iterable = np.iterable(d) | |
| if not iterable: | |
| d = [d] | |
| masked = np.ma.is_masked(d) | |
| mask = np.ma.getmask(d) | |
| d = np.asarray(d) | |
| # convert to datetime64 arrays, if not already: | |
| if not np.issubdtype(d.dtype, np.datetime64): | |
| # datetime arrays | |
| if not d.size: | |
| # deals with an empty array... | |
| return d | |
| tzi = getattr(d[0], 'tzinfo', None) | |
| if tzi is not None: | |
| # make datetime naive: | |
| d = [dt.astimezone(UTC).replace(tzinfo=None) for dt in d] | |
| d = np.asarray(d) | |
| d = d.astype('datetime64[us]') | |
| d = np.ma.masked_array(d, mask=mask) if masked else d | |
| d = _dt64_to_ordinalf(d) | |
| return d if iterable else d[0] | |
| def num2date(x, tz=None): | |
| """ | |
| Convert Matplotlib dates to `~datetime.datetime` objects. | |
| Parameters | |
| ---------- | |
| x : float or sequence of floats | |
| Number of days (fraction part represents hours, minutes, seconds) | |
| since the epoch. See `.get_epoch` for the | |
| epoch, which can be changed by :rc:`date.epoch` or `.set_epoch`. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Timezone of *x*. If a string, *tz* is passed to `dateutil.tz`. | |
| Returns | |
| ------- | |
| `~datetime.datetime` or sequence of `~datetime.datetime` | |
| Dates are returned in timezone *tz*. | |
| If *x* is a sequence, a sequence of `~datetime.datetime` objects will | |
| be returned. | |
| Notes | |
| ----- | |
| The Gregorian calendar is assumed; this is not universal practice. | |
| For details, see the module docstring. | |
| """ | |
| tz = _get_tzinfo(tz) | |
| return _from_ordinalf_np_vectorized(x, tz).tolist() | |
| _ordinalf_to_timedelta_np_vectorized = np.vectorize( | |
| lambda x: datetime.timedelta(days=x), otypes="O") | |
| def num2timedelta(x): | |
| """ | |
| Convert number of days to a `~datetime.timedelta` object. | |
| If *x* is a sequence, a sequence of `~datetime.timedelta` objects will | |
| be returned. | |
| Parameters | |
| ---------- | |
| x : float, sequence of floats | |
| Number of days. The fraction part represents hours, minutes, seconds. | |
| Returns | |
| ------- | |
| `datetime.timedelta` or list[`datetime.timedelta`] | |
| """ | |
| return _ordinalf_to_timedelta_np_vectorized(x).tolist() | |
| def drange(dstart, dend, delta): | |
| """ | |
| Return a sequence of equally spaced Matplotlib dates. | |
| The dates start at *dstart* and reach up to, but not including *dend*. | |
| They are spaced by *delta*. | |
| Parameters | |
| ---------- | |
| dstart, dend : `~datetime.datetime` | |
| The date limits. | |
| delta : `datetime.timedelta` | |
| Spacing of the dates. | |
| Returns | |
| ------- | |
| `numpy.array` | |
| A list floats representing Matplotlib dates. | |
| """ | |
| f1 = date2num(dstart) | |
| f2 = date2num(dend) | |
| step = delta.total_seconds() / SEC_PER_DAY | |
| # calculate the difference between dend and dstart in times of delta | |
| num = int(np.ceil((f2 - f1) / step)) | |
| # calculate end of the interval which will be generated | |
| dinterval_end = dstart + num * delta | |
| # ensure, that an half open interval will be generated [dstart, dend) | |
| if dinterval_end >= dend: | |
| # if the endpoint is greater than or equal to dend, | |
| # just subtract one delta | |
| dinterval_end -= delta | |
| num -= 1 | |
| f2 = date2num(dinterval_end) # new float-endpoint | |
| return np.linspace(f1, f2, num + 1) | |
| def _wrap_in_tex(text): | |
| p = r'([a-zA-Z]+)' | |
| ret_text = re.sub(p, r'}$\1$\\mathdefault{', text) | |
| # Braces ensure symbols are not spaced like binary operators. | |
| ret_text = ret_text.replace('-', '{-}').replace(':', '{:}') | |
| # To not concatenate space between numbers. | |
| ret_text = ret_text.replace(' ', r'\;') | |
| ret_text = '$\\mathdefault{' + ret_text + '}$' | |
| ret_text = ret_text.replace('$\\mathdefault{}$', '') | |
| return ret_text | |
| ## date tick locators and formatters ### | |
| class DateFormatter(ticker.Formatter): | |
| """ | |
| Format a tick (in days since the epoch) with a | |
| `~datetime.datetime.strftime` format string. | |
| """ | |
| def __init__(self, fmt, tz=None, *, usetex=None): | |
| """ | |
| Parameters | |
| ---------- | |
| fmt : str | |
| `~datetime.datetime.strftime` format string | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| usetex : bool, default: :rc:`text.usetex` | |
| To enable/disable the use of TeX's math mode for rendering the | |
| results of the formatter. | |
| """ | |
| self.tz = _get_tzinfo(tz) | |
| self.fmt = fmt | |
| self._usetex = mpl._val_or_rc(usetex, 'text.usetex') | |
| def __call__(self, x, pos=0): | |
| result = num2date(x, self.tz).strftime(self.fmt) | |
| return _wrap_in_tex(result) if self._usetex else result | |
| def set_tzinfo(self, tz): | |
| self.tz = _get_tzinfo(tz) | |
| class ConciseDateFormatter(ticker.Formatter): | |
| """ | |
| A `.Formatter` which attempts to figure out the best format to use for the | |
| date, and to make it as compact as possible, but still be complete. This is | |
| most useful when used with the `AutoDateLocator`:: | |
| >>> locator = AutoDateLocator() | |
| >>> formatter = ConciseDateFormatter(locator) | |
| Parameters | |
| ---------- | |
| locator : `.ticker.Locator` | |
| Locator that this axis is using. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone, passed to `.dates.num2date`. | |
| formats : list of 6 strings, optional | |
| Format strings for 6 levels of tick labelling: mostly years, | |
| months, days, hours, minutes, and seconds. Strings use | |
| the same format codes as `~datetime.datetime.strftime`. Default is | |
| ``['%Y', '%b', '%d', '%H:%M', '%H:%M', '%S.%f']`` | |
| zero_formats : list of 6 strings, optional | |
| Format strings for tick labels that are "zeros" for a given tick | |
| level. For instance, if most ticks are months, ticks around 1 Jan 2005 | |
| will be labeled "Dec", "2005", "Feb". The default is | |
| ``['', '%Y', '%b', '%b-%d', '%H:%M', '%H:%M']`` | |
| offset_formats : list of 6 strings, optional | |
| Format strings for the 6 levels that is applied to the "offset" | |
| string found on the right side of an x-axis, or top of a y-axis. | |
| Combined with the tick labels this should completely specify the | |
| date. The default is:: | |
| ['', '%Y', '%Y-%b', '%Y-%b-%d', '%Y-%b-%d', '%Y-%b-%d %H:%M'] | |
| show_offset : bool, default: True | |
| Whether to show the offset or not. | |
| usetex : bool, default: :rc:`text.usetex` | |
| To enable/disable the use of TeX's math mode for rendering the results | |
| of the formatter. | |
| Examples | |
| -------- | |
| See :doc:`/gallery/ticks/date_concise_formatter` | |
| .. plot:: | |
| import datetime | |
| import matplotlib.dates as mdates | |
| base = datetime.datetime(2005, 2, 1) | |
| dates = np.array([base + datetime.timedelta(hours=(2 * i)) | |
| for i in range(732)]) | |
| N = len(dates) | |
| np.random.seed(19680801) | |
| y = np.cumsum(np.random.randn(N)) | |
| fig, ax = plt.subplots(constrained_layout=True) | |
| locator = mdates.AutoDateLocator() | |
| formatter = mdates.ConciseDateFormatter(locator) | |
| ax.xaxis.set_major_locator(locator) | |
| ax.xaxis.set_major_formatter(formatter) | |
| ax.plot(dates, y) | |
| ax.set_title('Concise Date Formatter') | |
| """ | |
| def __init__(self, locator, tz=None, formats=None, offset_formats=None, | |
| zero_formats=None, show_offset=True, *, usetex=None): | |
| """ | |
| Autoformat the date labels. The default format is used to form an | |
| initial string, and then redundant elements are removed. | |
| """ | |
| self._locator = locator | |
| self._tz = tz | |
| self.defaultfmt = '%Y' | |
| # there are 6 levels with each level getting a specific format | |
| # 0: mostly years, 1: months, 2: days, | |
| # 3: hours, 4: minutes, 5: seconds | |
| if formats: | |
| if len(formats) != 6: | |
| raise ValueError('formats argument must be a list of ' | |
| '6 format strings (or None)') | |
| self.formats = formats | |
| else: | |
| self.formats = ['%Y', # ticks are mostly years | |
| '%b', # ticks are mostly months | |
| '%d', # ticks are mostly days | |
| '%H:%M', # hrs | |
| '%H:%M', # min | |
| '%S.%f', # secs | |
| ] | |
| # fmt for zeros ticks at this level. These are | |
| # ticks that should be labeled w/ info the level above. | |
| # like 1 Jan can just be labelled "Jan". 02:02:00 can | |
| # just be labeled 02:02. | |
| if zero_formats: | |
| if len(zero_formats) != 6: | |
| raise ValueError('zero_formats argument must be a list of ' | |
| '6 format strings (or None)') | |
| self.zero_formats = zero_formats | |
| elif formats: | |
| # use the users formats for the zero tick formats | |
| self.zero_formats = [''] + self.formats[:-1] | |
| else: | |
| # make the defaults a bit nicer: | |
| self.zero_formats = [''] + self.formats[:-1] | |
| self.zero_formats[3] = '%b-%d' | |
| if offset_formats: | |
| if len(offset_formats) != 6: | |
| raise ValueError('offset_formats argument must be a list of ' | |
| '6 format strings (or None)') | |
| self.offset_formats = offset_formats | |
| else: | |
| self.offset_formats = ['', | |
| '%Y', | |
| '%Y-%b', | |
| '%Y-%b-%d', | |
| '%Y-%b-%d', | |
| '%Y-%b-%d %H:%M'] | |
| self.offset_string = '' | |
| self.show_offset = show_offset | |
| self._usetex = mpl._val_or_rc(usetex, 'text.usetex') | |
| def __call__(self, x, pos=None): | |
| formatter = DateFormatter(self.defaultfmt, self._tz, | |
| usetex=self._usetex) | |
| return formatter(x, pos=pos) | |
| def format_ticks(self, values): | |
| tickdatetime = [num2date(value, tz=self._tz) for value in values] | |
| tickdate = np.array([tdt.timetuple()[:6] for tdt in tickdatetime]) | |
| # basic algorithm: | |
| # 1) only display a part of the date if it changes over the ticks. | |
| # 2) don't display the smaller part of the date if: | |
| # it is always the same or if it is the start of the | |
| # year, month, day etc. | |
| # fmt for most ticks at this level | |
| fmts = self.formats | |
| # format beginnings of days, months, years, etc. | |
| zerofmts = self.zero_formats | |
| # offset fmt are for the offset in the upper left of the | |
| # or lower right of the axis. | |
| offsetfmts = self.offset_formats | |
| show_offset = self.show_offset | |
| # determine the level we will label at: | |
| # mostly 0: years, 1: months, 2: days, | |
| # 3: hours, 4: minutes, 5: seconds, 6: microseconds | |
| for level in range(5, -1, -1): | |
| unique = np.unique(tickdate[:, level]) | |
| if len(unique) > 1: | |
| # if 1 is included in unique, the year is shown in ticks | |
| if level < 2 and np.any(unique == 1): | |
| show_offset = False | |
| break | |
| elif level == 0: | |
| # all tickdate are the same, so only micros might be different | |
| # set to the most precise (6: microseconds doesn't exist...) | |
| level = 5 | |
| # level is the basic level we will label at. | |
| # now loop through and decide the actual ticklabels | |
| zerovals = [0, 1, 1, 0, 0, 0, 0] | |
| labels = [''] * len(tickdate) | |
| for nn in range(len(tickdate)): | |
| if level < 5: | |
| if tickdate[nn][level] == zerovals[level]: | |
| fmt = zerofmts[level] | |
| else: | |
| fmt = fmts[level] | |
| else: | |
| # special handling for seconds + microseconds | |
| if (tickdatetime[nn].second == tickdatetime[nn].microsecond | |
| == 0): | |
| fmt = zerofmts[level] | |
| else: | |
| fmt = fmts[level] | |
| labels[nn] = tickdatetime[nn].strftime(fmt) | |
| # special handling of seconds and microseconds: | |
| # strip extra zeros and decimal if possible. | |
| # this is complicated by two factors. 1) we have some level-4 strings | |
| # here (i.e. 03:00, '0.50000', '1.000') 2) we would like to have the | |
| # same number of decimals for each string (i.e. 0.5 and 1.0). | |
| if level >= 5: | |
| trailing_zeros = min( | |
| (len(s) - len(s.rstrip('0')) for s in labels if '.' in s), | |
| default=None) | |
| if trailing_zeros: | |
| for nn in range(len(labels)): | |
| if '.' in labels[nn]: | |
| labels[nn] = labels[nn][:-trailing_zeros].rstrip('.') | |
| if show_offset: | |
| # set the offset string: | |
| if (self._locator.axis and | |
| self._locator.axis.__name__ in ('xaxis', 'yaxis') | |
| and self._locator.axis.get_inverted()): | |
| self.offset_string = tickdatetime[0].strftime(offsetfmts[level]) | |
| else: | |
| self.offset_string = tickdatetime[-1].strftime(offsetfmts[level]) | |
| if self._usetex: | |
| self.offset_string = _wrap_in_tex(self.offset_string) | |
| else: | |
| self.offset_string = '' | |
| if self._usetex: | |
| return [_wrap_in_tex(l) for l in labels] | |
| else: | |
| return labels | |
| def get_offset(self): | |
| return self.offset_string | |
| def format_data_short(self, value): | |
| return num2date(value, tz=self._tz).strftime('%Y-%m-%d %H:%M:%S') | |
| class AutoDateFormatter(ticker.Formatter): | |
| """ | |
| A `.Formatter` which attempts to figure out the best format to use. This | |
| is most useful when used with the `AutoDateLocator`. | |
| `.AutoDateFormatter` has a ``.scale`` dictionary that maps tick scales (the | |
| interval in days between one major tick) to format strings; this dictionary | |
| defaults to :: | |
| self.scaled = { | |
| DAYS_PER_YEAR: rcParams['date.autoformatter.year'], | |
| DAYS_PER_MONTH: rcParams['date.autoformatter.month'], | |
| 1: rcParams['date.autoformatter.day'], | |
| 1 / HOURS_PER_DAY: rcParams['date.autoformatter.hour'], | |
| 1 / MINUTES_PER_DAY: rcParams['date.autoformatter.minute'], | |
| 1 / SEC_PER_DAY: rcParams['date.autoformatter.second'], | |
| 1 / MUSECONDS_PER_DAY: rcParams['date.autoformatter.microsecond'], | |
| } | |
| The formatter uses the format string corresponding to the lowest key in | |
| the dictionary that is greater or equal to the current scale. Dictionary | |
| entries can be customized:: | |
| locator = AutoDateLocator() | |
| formatter = AutoDateFormatter(locator) | |
| formatter.scaled[1/(24*60)] = '%M:%S' # only show min and sec | |
| Custom callables can also be used instead of format strings. The following | |
| example shows how to use a custom format function to strip trailing zeros | |
| from decimal seconds and adds the date to the first ticklabel:: | |
| def my_format_function(x, pos=None): | |
| x = matplotlib.dates.num2date(x) | |
| if pos == 0: | |
| fmt = '%D %H:%M:%S.%f' | |
| else: | |
| fmt = '%H:%M:%S.%f' | |
| label = x.strftime(fmt) | |
| label = label.rstrip("0") | |
| label = label.rstrip(".") | |
| return label | |
| formatter.scaled[1/(24*60)] = my_format_function | |
| """ | |
| # This can be improved by providing some user-level direction on | |
| # how to choose the best format (precedence, etc.). | |
| # Perhaps a 'struct' that has a field for each time-type where a | |
| # zero would indicate "don't show" and a number would indicate | |
| # "show" with some sort of priority. Same priorities could mean | |
| # show all with the same priority. | |
| # Or more simply, perhaps just a format string for each | |
| # possibility... | |
| def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d', *, | |
| usetex=None): | |
| """ | |
| Autoformat the date labels. | |
| Parameters | |
| ---------- | |
| locator : `.ticker.Locator` | |
| Locator that this axis is using. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| defaultfmt : str | |
| The default format to use if none of the values in ``self.scaled`` | |
| are greater than the unit returned by ``locator._get_unit()``. | |
| usetex : bool, default: :rc:`text.usetex` | |
| To enable/disable the use of TeX's math mode for rendering the | |
| results of the formatter. If any entries in ``self.scaled`` are set | |
| as functions, then it is up to the customized function to enable or | |
| disable TeX's math mode itself. | |
| """ | |
| self._locator = locator | |
| self._tz = tz | |
| self.defaultfmt = defaultfmt | |
| self._formatter = DateFormatter(self.defaultfmt, tz) | |
| rcParams = mpl.rcParams | |
| self._usetex = mpl._val_or_rc(usetex, 'text.usetex') | |
| self.scaled = { | |
| DAYS_PER_YEAR: rcParams['date.autoformatter.year'], | |
| DAYS_PER_MONTH: rcParams['date.autoformatter.month'], | |
| 1: rcParams['date.autoformatter.day'], | |
| 1 / HOURS_PER_DAY: rcParams['date.autoformatter.hour'], | |
| 1 / MINUTES_PER_DAY: rcParams['date.autoformatter.minute'], | |
| 1 / SEC_PER_DAY: rcParams['date.autoformatter.second'], | |
| 1 / MUSECONDS_PER_DAY: rcParams['date.autoformatter.microsecond'] | |
| } | |
| def _set_locator(self, locator): | |
| self._locator = locator | |
| def __call__(self, x, pos=None): | |
| try: | |
| locator_unit_scale = float(self._locator._get_unit()) | |
| except AttributeError: | |
| locator_unit_scale = 1 | |
| # Pick the first scale which is greater than the locator unit. | |
| fmt = next((fmt for scale, fmt in sorted(self.scaled.items()) | |
| if scale >= locator_unit_scale), | |
| self.defaultfmt) | |
| if isinstance(fmt, str): | |
| self._formatter = DateFormatter(fmt, self._tz, usetex=self._usetex) | |
| result = self._formatter(x, pos) | |
| elif callable(fmt): | |
| result = fmt(x, pos) | |
| else: | |
| raise TypeError(f'Unexpected type passed to {self!r}.') | |
| return result | |
| class rrulewrapper: | |
| """ | |
| A simple wrapper around a `dateutil.rrule` allowing flexible | |
| date tick specifications. | |
| """ | |
| def __init__(self, freq, tzinfo=None, **kwargs): | |
| """ | |
| Parameters | |
| ---------- | |
| freq : {YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, SECONDLY} | |
| Tick frequency. These constants are defined in `dateutil.rrule`, | |
| but they are accessible from `matplotlib.dates` as well. | |
| tzinfo : `datetime.tzinfo`, optional | |
| Time zone information. The default is None. | |
| **kwargs | |
| Additional keyword arguments are passed to the `dateutil.rrule`. | |
| """ | |
| kwargs['freq'] = freq | |
| self._base_tzinfo = tzinfo | |
| self._update_rrule(**kwargs) | |
| def set(self, **kwargs): | |
| """Set parameters for an existing wrapper.""" | |
| self._construct.update(kwargs) | |
| self._update_rrule(**self._construct) | |
| def _update_rrule(self, **kwargs): | |
| tzinfo = self._base_tzinfo | |
| # rrule does not play nicely with timezones - especially pytz time | |
| # zones, it's best to use naive zones and attach timezones once the | |
| # datetimes are returned | |
| if 'dtstart' in kwargs: | |
| dtstart = kwargs['dtstart'] | |
| if dtstart.tzinfo is not None: | |
| if tzinfo is None: | |
| tzinfo = dtstart.tzinfo | |
| else: | |
| dtstart = dtstart.astimezone(tzinfo) | |
| kwargs['dtstart'] = dtstart.replace(tzinfo=None) | |
| if 'until' in kwargs: | |
| until = kwargs['until'] | |
| if until.tzinfo is not None: | |
| if tzinfo is not None: | |
| until = until.astimezone(tzinfo) | |
| else: | |
| raise ValueError('until cannot be aware if dtstart ' | |
| 'is naive and tzinfo is None') | |
| kwargs['until'] = until.replace(tzinfo=None) | |
| self._construct = kwargs.copy() | |
| self._tzinfo = tzinfo | |
| self._rrule = rrule(**self._construct) | |
| def _attach_tzinfo(self, dt, tzinfo): | |
| # pytz zones are attached by "localizing" the datetime | |
| if hasattr(tzinfo, 'localize'): | |
| return tzinfo.localize(dt, is_dst=True) | |
| return dt.replace(tzinfo=tzinfo) | |
| def _aware_return_wrapper(self, f, returns_list=False): | |
| """Decorator function that allows rrule methods to handle tzinfo.""" | |
| # This is only necessary if we're actually attaching a tzinfo | |
| if self._tzinfo is None: | |
| return f | |
| # All datetime arguments must be naive. If they are not naive, they are | |
| # converted to the _tzinfo zone before dropping the zone. | |
| def normalize_arg(arg): | |
| if isinstance(arg, datetime.datetime) and arg.tzinfo is not None: | |
| if arg.tzinfo is not self._tzinfo: | |
| arg = arg.astimezone(self._tzinfo) | |
| return arg.replace(tzinfo=None) | |
| return arg | |
| def normalize_args(args, kwargs): | |
| args = tuple(normalize_arg(arg) for arg in args) | |
| kwargs = {kw: normalize_arg(arg) for kw, arg in kwargs.items()} | |
| return args, kwargs | |
| # There are two kinds of functions we care about - ones that return | |
| # dates and ones that return lists of dates. | |
| if not returns_list: | |
| def inner_func(*args, **kwargs): | |
| args, kwargs = normalize_args(args, kwargs) | |
| dt = f(*args, **kwargs) | |
| return self._attach_tzinfo(dt, self._tzinfo) | |
| else: | |
| def inner_func(*args, **kwargs): | |
| args, kwargs = normalize_args(args, kwargs) | |
| dts = f(*args, **kwargs) | |
| return [self._attach_tzinfo(dt, self._tzinfo) for dt in dts] | |
| return functools.wraps(f)(inner_func) | |
| def __getattr__(self, name): | |
| if name in self.__dict__: | |
| return self.__dict__[name] | |
| f = getattr(self._rrule, name) | |
| if name in {'after', 'before'}: | |
| return self._aware_return_wrapper(f) | |
| elif name in {'xafter', 'xbefore', 'between'}: | |
| return self._aware_return_wrapper(f, returns_list=True) | |
| else: | |
| return f | |
| def __setstate__(self, state): | |
| self.__dict__.update(state) | |
| class DateLocator(ticker.Locator): | |
| """ | |
| Determines the tick locations when plotting dates. | |
| This class is subclassed by other Locators and | |
| is not meant to be used on its own. | |
| """ | |
| hms0d = {'byhour': 0, 'byminute': 0, 'bysecond': 0} | |
| def __init__(self, tz=None): | |
| """ | |
| Parameters | |
| ---------- | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| """ | |
| self.tz = _get_tzinfo(tz) | |
| def set_tzinfo(self, tz): | |
| """ | |
| Set timezone info. | |
| Parameters | |
| ---------- | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| """ | |
| self.tz = _get_tzinfo(tz) | |
| def datalim_to_dt(self): | |
| """Convert axis data interval to datetime objects.""" | |
| dmin, dmax = self.axis.get_data_interval() | |
| if dmin > dmax: | |
| dmin, dmax = dmax, dmin | |
| return num2date(dmin, self.tz), num2date(dmax, self.tz) | |
| def viewlim_to_dt(self): | |
| """Convert the view interval to datetime objects.""" | |
| vmin, vmax = self.axis.get_view_interval() | |
| if vmin > vmax: | |
| vmin, vmax = vmax, vmin | |
| return num2date(vmin, self.tz), num2date(vmax, self.tz) | |
| def _get_unit(self): | |
| """ | |
| Return how many days a unit of the locator is; used for | |
| intelligent autoscaling. | |
| """ | |
| return 1 | |
| def _get_interval(self): | |
| """ | |
| Return the number of units for each tick. | |
| """ | |
| return 1 | |
| def nonsingular(self, vmin, vmax): | |
| """ | |
| Given the proposed upper and lower extent, adjust the range | |
| if it is too close to being singular (i.e. a range of ~0). | |
| """ | |
| if not np.isfinite(vmin) or not np.isfinite(vmax): | |
| # Except if there is no data, then use 1970 as default. | |
| return (date2num(datetime.date(1970, 1, 1)), | |
| date2num(datetime.date(1970, 1, 2))) | |
| if vmax < vmin: | |
| vmin, vmax = vmax, vmin | |
| unit = self._get_unit() | |
| interval = self._get_interval() | |
| if abs(vmax - vmin) < 1e-6: | |
| vmin -= 2 * unit * interval | |
| vmax += 2 * unit * interval | |
| return vmin, vmax | |
| class RRuleLocator(DateLocator): | |
| # use the dateutil rrule instance | |
| def __init__(self, o, tz=None): | |
| super().__init__(tz) | |
| self.rule = o | |
| 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): | |
| start, stop = self._create_rrule(vmin, vmax) | |
| dates = self.rule.between(start, stop, True) | |
| if len(dates) == 0: | |
| return date2num([vmin, vmax]) | |
| return self.raise_if_exceeds(date2num(dates)) | |
| def _create_rrule(self, vmin, vmax): | |
| # set appropriate rrule dtstart and until and return | |
| # start and end | |
| delta = relativedelta(vmax, vmin) | |
| # We need to cap at the endpoints of valid datetime | |
| try: | |
| start = vmin - delta | |
| except (ValueError, OverflowError): | |
| # cap | |
| start = datetime.datetime(1, 1, 1, 0, 0, 0, | |
| tzinfo=datetime.timezone.utc) | |
| try: | |
| stop = vmax + delta | |
| except (ValueError, OverflowError): | |
| # cap | |
| stop = datetime.datetime(9999, 12, 31, 23, 59, 59, | |
| tzinfo=datetime.timezone.utc) | |
| self.rule.set(dtstart=start, until=stop) | |
| return vmin, vmax | |
| def _get_unit(self): | |
| # docstring inherited | |
| freq = self.rule._rrule._freq | |
| return self.get_unit_generic(freq) | |
| def get_unit_generic(freq): | |
| if freq == YEARLY: | |
| return DAYS_PER_YEAR | |
| elif freq == MONTHLY: | |
| return DAYS_PER_MONTH | |
| elif freq == WEEKLY: | |
| return DAYS_PER_WEEK | |
| elif freq == DAILY: | |
| return 1.0 | |
| elif freq == HOURLY: | |
| return 1.0 / HOURS_PER_DAY | |
| elif freq == MINUTELY: | |
| return 1.0 / MINUTES_PER_DAY | |
| elif freq == SECONDLY: | |
| return 1.0 / SEC_PER_DAY | |
| else: | |
| # error | |
| return -1 # or should this just return '1'? | |
| def _get_interval(self): | |
| return self.rule._rrule._interval | |
| class AutoDateLocator(DateLocator): | |
| """ | |
| On autoscale, this class picks the best `DateLocator` to set the view | |
| limits and the tick locations. | |
| Attributes | |
| ---------- | |
| intervald : dict | |
| Mapping of tick frequencies to multiples allowed for that ticking. | |
| The default is :: | |
| self.intervald = { | |
| YEARLY : [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500, | |
| 1000, 2000, 4000, 5000, 10000], | |
| MONTHLY : [1, 2, 3, 4, 6], | |
| DAILY : [1, 2, 3, 7, 14, 21], | |
| HOURLY : [1, 2, 3, 4, 6, 12], | |
| MINUTELY: [1, 5, 10, 15, 30], | |
| SECONDLY: [1, 5, 10, 15, 30], | |
| MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, | |
| 1000, 2000, 5000, 10000, 20000, 50000, | |
| 100000, 200000, 500000, 1000000], | |
| } | |
| where the keys are defined in `dateutil.rrule`. | |
| The interval is used to specify multiples that are appropriate for | |
| the frequency of ticking. For instance, every 7 days is sensible | |
| for daily ticks, but for minutes/seconds, 15 or 30 make sense. | |
| When customizing, you should only modify the values for the existing | |
| keys. You should not add or delete entries. | |
| Example for forcing ticks every 3 hours:: | |
| locator = AutoDateLocator() | |
| locator.intervald[HOURLY] = [3] # only show every 3 hours | |
| """ | |
| def __init__(self, tz=None, minticks=5, maxticks=None, | |
| interval_multiples=True): | |
| """ | |
| Parameters | |
| ---------- | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| minticks : int | |
| The minimum number of ticks desired; controls whether ticks occur | |
| yearly, monthly, etc. | |
| maxticks : int | |
| The maximum number of ticks desired; controls the interval between | |
| ticks (ticking every other, every 3, etc.). For fine-grained | |
| control, this can be a dictionary mapping individual rrule | |
| frequency constants (YEARLY, MONTHLY, etc.) to their own maximum | |
| number of ticks. This can be used to keep the number of ticks | |
| appropriate to the format chosen in `AutoDateFormatter`. Any | |
| frequency not specified in this dictionary is given a default | |
| value. | |
| interval_multiples : bool, default: True | |
| Whether ticks should be chosen to be multiple of the interval, | |
| locking them to 'nicer' locations. For example, this will force | |
| the ticks to be at hours 0, 6, 12, 18 when hourly ticking is done | |
| at 6 hour intervals. | |
| """ | |
| super().__init__(tz=tz) | |
| self._freq = YEARLY | |
| self._freqs = [YEARLY, MONTHLY, DAILY, HOURLY, MINUTELY, | |
| SECONDLY, MICROSECONDLY] | |
| self.minticks = minticks | |
| self.maxticks = {YEARLY: 11, MONTHLY: 12, DAILY: 11, HOURLY: 12, | |
| MINUTELY: 11, SECONDLY: 11, MICROSECONDLY: 8} | |
| if maxticks is not None: | |
| try: | |
| self.maxticks.update(maxticks) | |
| except TypeError: | |
| # Assume we were given an integer. Use this as the maximum | |
| # number of ticks for every frequency and create a | |
| # dictionary for this | |
| self.maxticks = dict.fromkeys(self._freqs, maxticks) | |
| self.interval_multiples = interval_multiples | |
| self.intervald = { | |
| YEARLY: [1, 2, 4, 5, 10, 20, 40, 50, 100, 200, 400, 500, | |
| 1000, 2000, 4000, 5000, 10000], | |
| MONTHLY: [1, 2, 3, 4, 6], | |
| DAILY: [1, 2, 3, 7, 14, 21], | |
| HOURLY: [1, 2, 3, 4, 6, 12], | |
| MINUTELY: [1, 5, 10, 15, 30], | |
| SECONDLY: [1, 5, 10, 15, 30], | |
| MICROSECONDLY: [1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, | |
| 5000, 10000, 20000, 50000, 100000, 200000, 500000, | |
| 1000000], | |
| } | |
| if interval_multiples: | |
| # Swap "3" for "4" in the DAILY list; If we use 3 we get bad | |
| # tick loc for months w/ 31 days: 1, 4, ..., 28, 31, 1 | |
| # If we use 4 then we get: 1, 5, ... 25, 29, 1 | |
| self.intervald[DAILY] = [1, 2, 4, 7, 14] | |
| self._byranges = [None, range(1, 13), range(1, 32), | |
| range(0, 24), range(0, 60), range(0, 60), None] | |
| def __call__(self): | |
| # docstring inherited | |
| dmin, dmax = self.viewlim_to_dt() | |
| locator = self.get_locator(dmin, dmax) | |
| return 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. | |
| if not np.isfinite(vmin) or not np.isfinite(vmax): | |
| # Except if there is no data, then use 1970 as default. | |
| return (date2num(datetime.date(1970, 1, 1)), | |
| date2num(datetime.date(1970, 1, 2))) | |
| if vmax < vmin: | |
| vmin, vmax = vmax, vmin | |
| if vmin == vmax: | |
| vmin = vmin - DAYS_PER_YEAR * 2 | |
| vmax = vmax + DAYS_PER_YEAR * 2 | |
| return vmin, vmax | |
| def _get_unit(self): | |
| if self._freq in [MICROSECONDLY]: | |
| return 1. / MUSECONDS_PER_DAY | |
| else: | |
| return RRuleLocator.get_unit_generic(self._freq) | |
| def get_locator(self, dmin, dmax): | |
| """Pick the best locator based on a distance.""" | |
| delta = relativedelta(dmax, dmin) | |
| tdelta = dmax - dmin | |
| # take absolute difference | |
| if dmin > dmax: | |
| delta = -delta | |
| tdelta = -tdelta | |
| # The following uses a mix of calls to relativedelta and timedelta | |
| # methods because there is incomplete overlap in the functionality of | |
| # these similar functions, and it's best to avoid doing our own math | |
| # whenever possible. | |
| numYears = float(delta.years) | |
| numMonths = numYears * MONTHS_PER_YEAR + delta.months | |
| numDays = tdelta.days # Avoids estimates of days/month, days/year. | |
| numHours = numDays * HOURS_PER_DAY + delta.hours | |
| numMinutes = numHours * MIN_PER_HOUR + delta.minutes | |
| numSeconds = np.floor(tdelta.total_seconds()) | |
| numMicroseconds = np.floor(tdelta.total_seconds() * 1e6) | |
| nums = [numYears, numMonths, numDays, numHours, numMinutes, | |
| numSeconds, numMicroseconds] | |
| use_rrule_locator = [True] * 6 + [False] | |
| # Default setting of bymonth, etc. to pass to rrule | |
| # [unused (for year), bymonth, bymonthday, byhour, byminute, | |
| # bysecond, unused (for microseconds)] | |
| byranges = [None, 1, 1, 0, 0, 0, None] | |
| # Loop over all the frequencies and try to find one that gives at | |
| # least a minticks tick positions. Once this is found, look for | |
| # an interval from a list specific to that frequency that gives no | |
| # more than maxticks tick positions. Also, set up some ranges | |
| # (bymonth, etc.) as appropriate to be passed to rrulewrapper. | |
| for i, (freq, num) in enumerate(zip(self._freqs, nums)): | |
| # If this particular frequency doesn't give enough ticks, continue | |
| if num < self.minticks: | |
| # Since we're not using this particular frequency, set | |
| # the corresponding by_ to None so the rrule can act as | |
| # appropriate | |
| byranges[i] = None | |
| continue | |
| # Find the first available interval that doesn't give too many | |
| # ticks | |
| for interval in self.intervald[freq]: | |
| if num <= interval * (self.maxticks[freq] - 1): | |
| break | |
| else: | |
| if not (self.interval_multiples and freq == DAILY): | |
| _api.warn_external( | |
| f"AutoDateLocator was unable to pick an appropriate " | |
| f"interval for this date range. It may be necessary " | |
| f"to add an interval value to the AutoDateLocator's " | |
| f"intervald dictionary. Defaulting to {interval}.") | |
| # Set some parameters as appropriate | |
| self._freq = freq | |
| if self._byranges[i] and self.interval_multiples: | |
| byranges[i] = self._byranges[i][::interval] | |
| if i in (DAILY, WEEKLY): | |
| if interval == 14: | |
| # just make first and 15th. Avoids 30th. | |
| byranges[i] = [1, 15] | |
| elif interval == 7: | |
| byranges[i] = [1, 8, 15, 22] | |
| interval = 1 | |
| else: | |
| byranges[i] = self._byranges[i] | |
| break | |
| else: | |
| interval = 1 | |
| if (freq == YEARLY) and self.interval_multiples: | |
| locator = YearLocator(interval, tz=self.tz) | |
| elif use_rrule_locator[i]: | |
| _, bymonth, bymonthday, byhour, byminute, bysecond, _ = byranges | |
| rrule = rrulewrapper(self._freq, interval=interval, | |
| dtstart=dmin, until=dmax, | |
| bymonth=bymonth, bymonthday=bymonthday, | |
| byhour=byhour, byminute=byminute, | |
| bysecond=bysecond) | |
| locator = RRuleLocator(rrule, tz=self.tz) | |
| else: | |
| locator = MicrosecondLocator(interval, tz=self.tz) | |
| if date2num(dmin) > 70 * 365 and interval < 1000: | |
| _api.warn_external( | |
| 'Plotting microsecond time intervals for dates far from ' | |
| f'the epoch (time origin: {get_epoch()}) is not well-' | |
| 'supported. See matplotlib.dates.set_epoch to change the ' | |
| 'epoch.') | |
| locator.set_axis(self.axis) | |
| return locator | |
| class YearLocator(RRuleLocator): | |
| """ | |
| Make ticks on a given day of each year that is a multiple of base. | |
| Examples:: | |
| # Tick every year on Jan 1st | |
| locator = YearLocator() | |
| # Tick every 5 years on July 4th | |
| locator = YearLocator(5, month=7, day=4) | |
| """ | |
| def __init__(self, base=1, month=1, day=1, tz=None): | |
| """ | |
| Parameters | |
| ---------- | |
| base : int, default: 1 | |
| Mark ticks every *base* years. | |
| month : int, default: 1 | |
| The month on which to place the ticks, starting from 1. Default is | |
| January. | |
| day : int, default: 1 | |
| The day on which to place the ticks. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| """ | |
| rule = rrulewrapper(YEARLY, interval=base, bymonth=month, | |
| bymonthday=day, **self.hms0d) | |
| super().__init__(rule, tz=tz) | |
| self.base = ticker._Edge_integer(base, 0) | |
| def _create_rrule(self, vmin, vmax): | |
| # 'start' needs to be a multiple of the interval to create ticks on | |
| # interval multiples when the tick frequency is YEARLY | |
| ymin = max(self.base.le(vmin.year) * self.base.step, 1) | |
| ymax = min(self.base.ge(vmax.year) * self.base.step, 9999) | |
| c = self.rule._construct | |
| replace = {'year': ymin, | |
| 'month': c.get('bymonth', 1), | |
| 'day': c.get('bymonthday', 1), | |
| 'hour': 0, 'minute': 0, 'second': 0} | |
| start = vmin.replace(**replace) | |
| stop = start.replace(year=ymax) | |
| self.rule.set(dtstart=start, until=stop) | |
| return start, stop | |
| class MonthLocator(RRuleLocator): | |
| """ | |
| Make ticks on occurrences of each month, e.g., 1, 3, 12. | |
| """ | |
| def __init__(self, bymonth=None, bymonthday=1, interval=1, tz=None): | |
| """ | |
| Parameters | |
| ---------- | |
| bymonth : int or list of int, default: all months | |
| Ticks will be placed on every month in *bymonth*. Default is | |
| ``range(1, 13)``, i.e. every month. | |
| bymonthday : int, default: 1 | |
| The day on which to place the ticks. | |
| interval : int, default: 1 | |
| The interval between each iteration. For example, if | |
| ``interval=2``, mark every second occurrence. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| """ | |
| if bymonth is None: | |
| bymonth = range(1, 13) | |
| rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday, | |
| interval=interval, **self.hms0d) | |
| super().__init__(rule, tz=tz) | |
| class WeekdayLocator(RRuleLocator): | |
| """ | |
| Make ticks on occurrences of each weekday. | |
| """ | |
| def __init__(self, byweekday=1, interval=1, tz=None): | |
| """ | |
| Parameters | |
| ---------- | |
| byweekday : int or list of int, default: all days | |
| Ticks will be placed on every weekday in *byweekday*. Default is | |
| every day. | |
| Elements of *byweekday* must be one of MO, TU, WE, TH, FR, SA, | |
| SU, the constants from :mod:`dateutil.rrule`, which have been | |
| imported into the :mod:`matplotlib.dates` namespace. | |
| interval : int, default: 1 | |
| The interval between each iteration. For example, if | |
| ``interval=2``, mark every second occurrence. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| """ | |
| rule = rrulewrapper(DAILY, byweekday=byweekday, | |
| interval=interval, **self.hms0d) | |
| super().__init__(rule, tz=tz) | |
| class DayLocator(RRuleLocator): | |
| """ | |
| Make ticks on occurrences of each day of the month. For example, | |
| 1, 15, 30. | |
| """ | |
| def __init__(self, bymonthday=None, interval=1, tz=None): | |
| """ | |
| Parameters | |
| ---------- | |
| bymonthday : int or list of int, default: all days | |
| Ticks will be placed on every day in *bymonthday*. Default is | |
| ``bymonthday=range(1, 32)``, i.e., every day of the month. | |
| interval : int, default: 1 | |
| The interval between each iteration. For example, if | |
| ``interval=2``, mark every second occurrence. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| """ | |
| if interval != int(interval) or interval < 1: | |
| raise ValueError("interval must be an integer greater than 0") | |
| if bymonthday is None: | |
| bymonthday = range(1, 32) | |
| rule = rrulewrapper(DAILY, bymonthday=bymonthday, | |
| interval=interval, **self.hms0d) | |
| super().__init__(rule, tz=tz) | |
| class HourLocator(RRuleLocator): | |
| """ | |
| Make ticks on occurrences of each hour. | |
| """ | |
| def __init__(self, byhour=None, interval=1, tz=None): | |
| """ | |
| Parameters | |
| ---------- | |
| byhour : int or list of int, default: all hours | |
| Ticks will be placed on every hour in *byhour*. Default is | |
| ``byhour=range(24)``, i.e., every hour. | |
| interval : int, default: 1 | |
| The interval between each iteration. For example, if | |
| ``interval=2``, mark every second occurrence. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| """ | |
| if byhour is None: | |
| byhour = range(24) | |
| rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval, | |
| byminute=0, bysecond=0) | |
| super().__init__(rule, tz=tz) | |
| class MinuteLocator(RRuleLocator): | |
| """ | |
| Make ticks on occurrences of each minute. | |
| """ | |
| def __init__(self, byminute=None, interval=1, tz=None): | |
| """ | |
| Parameters | |
| ---------- | |
| byminute : int or list of int, default: all minutes | |
| Ticks will be placed on every minute in *byminute*. Default is | |
| ``byminute=range(60)``, i.e., every minute. | |
| interval : int, default: 1 | |
| The interval between each iteration. For example, if | |
| ``interval=2``, mark every second occurrence. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| """ | |
| if byminute is None: | |
| byminute = range(60) | |
| rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval, | |
| bysecond=0) | |
| super().__init__(rule, tz=tz) | |
| class SecondLocator(RRuleLocator): | |
| """ | |
| Make ticks on occurrences of each second. | |
| """ | |
| def __init__(self, bysecond=None, interval=1, tz=None): | |
| """ | |
| Parameters | |
| ---------- | |
| bysecond : int or list of int, default: all seconds | |
| Ticks will be placed on every second in *bysecond*. Default is | |
| ``bysecond = range(60)``, i.e., every second. | |
| interval : int, default: 1 | |
| The interval between each iteration. For example, if | |
| ``interval=2``, mark every second occurrence. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| """ | |
| if bysecond is None: | |
| bysecond = range(60) | |
| rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval) | |
| super().__init__(rule, tz=tz) | |
| class MicrosecondLocator(DateLocator): | |
| """ | |
| Make ticks on regular intervals of one or more microsecond(s). | |
| .. note:: | |
| By default, Matplotlib uses a floating point representation of time in | |
| days since the epoch, so plotting data with | |
| microsecond time resolution does not work well for | |
| dates that are far (about 70 years) from the epoch (check with | |
| `~.dates.get_epoch`). | |
| If you want sub-microsecond resolution time plots, it is strongly | |
| recommended to use floating point seconds, not datetime-like | |
| time representation. | |
| If you really must use datetime.datetime() or similar and still | |
| need microsecond precision, change the time origin via | |
| `.dates.set_epoch` to something closer to the dates being plotted. | |
| See :doc:`/gallery/ticks/date_precision_and_epochs`. | |
| """ | |
| def __init__(self, interval=1, tz=None): | |
| """ | |
| Parameters | |
| ---------- | |
| interval : int, default: 1 | |
| The interval between each iteration. For example, if | |
| ``interval=2``, mark every second occurrence. | |
| tz : str or `~datetime.tzinfo`, default: :rc:`timezone` | |
| Ticks timezone. If a string, *tz* is passed to `dateutil.tz`. | |
| """ | |
| super().__init__(tz=tz) | |
| self._interval = interval | |
| self._wrapped_locator = ticker.MultipleLocator(interval) | |
| def set_axis(self, axis): | |
| self._wrapped_locator.set_axis(axis) | |
| return super().set_axis(axis) | |
| 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)) | |
| t0 = np.floor(nmin) | |
| nmax = nmax - t0 | |
| nmin = nmin - t0 | |
| nmin *= MUSECONDS_PER_DAY | |
| nmax *= MUSECONDS_PER_DAY | |
| ticks = self._wrapped_locator.tick_values(nmin, nmax) | |
| ticks = ticks / MUSECONDS_PER_DAY + t0 | |
| return ticks | |
| def _get_unit(self): | |
| # docstring inherited | |
| return 1. / MUSECONDS_PER_DAY | |
| def _get_interval(self): | |
| # docstring inherited | |
| return self._interval | |
| class DateConverter(units.ConversionInterface): | |
| """ | |
| Converter for `datetime.date` and `datetime.datetime` data, or for | |
| date/time data represented as it would be converted by `date2num`. | |
| The 'unit' tag for such data is None or a `~datetime.tzinfo` instance. | |
| """ | |
| def __init__(self, *, interval_multiples=True): | |
| self._interval_multiples = interval_multiples | |
| super().__init__() | |
| def axisinfo(self, unit, axis): | |
| """ | |
| Return the `~matplotlib.units.AxisInfo` for *unit*. | |
| *unit* is a `~datetime.tzinfo` instance or None. | |
| The *axis* argument is required but not used. | |
| """ | |
| tz = unit | |
| majloc = AutoDateLocator(tz=tz, | |
| interval_multiples=self._interval_multiples) | |
| majfmt = AutoDateFormatter(majloc, tz=tz) | |
| datemin = datetime.date(1970, 1, 1) | |
| datemax = datetime.date(1970, 1, 2) | |
| return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='', | |
| default_limits=(datemin, datemax)) | |
| def convert(value, unit, axis): | |
| """ | |
| If *value* is not already a number or sequence of numbers, convert it | |
| with `date2num`. | |
| The *unit* and *axis* arguments are not used. | |
| """ | |
| return date2num(value) | |
| def default_units(x, axis): | |
| """ | |
| Return the `~datetime.tzinfo` instance of *x* or of its first element, | |
| or None | |
| """ | |
| if isinstance(x, np.ndarray): | |
| x = x.ravel() | |
| try: | |
| x = cbook._safe_first_finite(x) | |
| except (TypeError, StopIteration): | |
| pass | |
| try: | |
| return x.tzinfo | |
| except AttributeError: | |
| pass | |
| return None | |
| class ConciseDateConverter(DateConverter): | |
| # docstring inherited | |
| def __init__(self, formats=None, zero_formats=None, offset_formats=None, | |
| show_offset=True, *, interval_multiples=True): | |
| self._formats = formats | |
| self._zero_formats = zero_formats | |
| self._offset_formats = offset_formats | |
| self._show_offset = show_offset | |
| self._interval_multiples = interval_multiples | |
| super().__init__() | |
| def axisinfo(self, unit, axis): | |
| # docstring inherited | |
| tz = unit | |
| majloc = AutoDateLocator(tz=tz, | |
| interval_multiples=self._interval_multiples) | |
| majfmt = ConciseDateFormatter(majloc, tz=tz, formats=self._formats, | |
| zero_formats=self._zero_formats, | |
| offset_formats=self._offset_formats, | |
| show_offset=self._show_offset) | |
| datemin = datetime.date(1970, 1, 1) | |
| datemax = datetime.date(1970, 1, 2) | |
| return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='', | |
| default_limits=(datemin, datemax)) | |
| class _SwitchableDateConverter: | |
| """ | |
| Helper converter-like object that generates and dispatches to | |
| temporary ConciseDateConverter or DateConverter instances based on | |
| :rc:`date.converter` and :rc:`date.interval_multiples`. | |
| """ | |
| def _get_converter(): | |
| converter_cls = { | |
| "concise": ConciseDateConverter, "auto": DateConverter}[ | |
| mpl.rcParams["date.converter"]] | |
| interval_multiples = mpl.rcParams["date.interval_multiples"] | |
| return converter_cls(interval_multiples=interval_multiples) | |
| def axisinfo(self, *args, **kwargs): | |
| return self._get_converter().axisinfo(*args, **kwargs) | |
| def default_units(self, *args, **kwargs): | |
| return self._get_converter().default_units(*args, **kwargs) | |
| def convert(self, *args, **kwargs): | |
| return self._get_converter().convert(*args, **kwargs) | |
| units.registry[np.datetime64] = \ | |
| units.registry[datetime.date] = \ | |
| units.registry[datetime.datetime] = \ | |
| _SwitchableDateConverter() | |
Xet Storage Details
- Size:
- 66.3 kB
- Xet hash:
- a4fe740aa1a4f24136841c532d47da9d2e89ed7630d866222b58250941bc9964
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.