12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970 |
- """
- Matplotlib provides sophisticated date plotting capabilities, standing on the
- shoulders of python :mod:`datetime` and the add-on module :mod:`dateutil`.
- .. _date-format:
- Matplotlib date format
- ----------------------
- Matplotlib represents dates using floating point numbers specifying the number
- of days since 0001-01-01 UTC, plus 1. For example, 0001-01-01, 06:00 is 1.25,
- not 0.25. Values < 1, i.e. dates before 0001-01-01 UTC, are not supported.
- 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
- epoch2num
- num2epoch
- drange
- .. note::
- Like Python's 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, tickers and formatters are timezone aware.
- If no explicit timezone is provided, :rc:`timezone` is assumed. If you want to
- use a custom time zone, pass a `datetime.tzinfo` instance with the tz keyword
- argument to `num2date`, `~.Axes.plot_date`, and any custom date tickers or
- locators you create.
- 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 tickers
- ------------
- Most of the date tickers can locate single or multiple values. 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 tickers 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 `matplotlib.dates.rrulewrapper`.
- `.rrulewrapper` is a simple wrapper around dateutil_'s `dateutil.rrule` which
- allow almost arbitrary date tick specifications. See :doc:`rrule example
- </gallery/ticks_and_spines/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. E.g. 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
- ---------------
- 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.
- * `IndexDateFormatter`: date plots with implicit *x* indexing.
- """
- import datetime
- import functools
- import logging
- import math
- import re
- import time
- import warnings
- 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
- from matplotlib import rcParams
- import matplotlib.units as units
- import matplotlib.cbook as cbook
- import matplotlib.ticker as ticker
- __all__ = ('datestr2num', 'date2num', 'num2date', 'num2timedelta', 'drange',
- 'epoch2num', 'num2epoch', 'mx2num', 'DateFormatter',
- 'ConciseDateFormatter', 'IndexDateFormatter', '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',
- 'seconds', 'minutes', 'hours', 'weeks')
- _log = logging.getLogger(__name__)
- UTC = datetime.timezone.utc
- def _get_rc_timezone():
- """Retrieve the preferred timezone from the rcParams dictionary."""
- s = matplotlib.rcParams['timezone']
- if s == 'UTC':
- return UTC
- return dateutil.tz.gettz(s)
- """
- Time-related constants.
- """
- EPOCH_OFFSET = float(datetime.datetime(1970, 1, 1).toordinal())
- JULIAN_OFFSET = 1721424.5 # Julian date at 0001-01-01
- 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)
- def _to_ordinalf(dt):
- """
- Convert :mod:`datetime` or :mod:`date` to the Gregorian date as UTC float
- days, preserving hours, minutes, seconds and microseconds. Return value
- is a :func:`float`.
- """
- # Convert to UTC
- tzi = getattr(dt, 'tzinfo', None)
- if tzi is not None:
- dt = dt.astimezone(UTC)
- tzi = UTC
- base = float(dt.toordinal())
- # If it's sufficiently datetime-like, it will have a `date()` method
- cdate = getattr(dt, 'date', lambda: None)()
- if cdate is not None:
- # Get a datetime object at midnight UTC
- midnight_time = datetime.time(0, tzinfo=tzi)
- rdt = datetime.datetime.combine(cdate, midnight_time)
- # Append the seconds as a fraction of a day
- base += (dt - rdt).total_seconds() / SEC_PER_DAY
- return base
- # a version of _to_ordinalf that can operate on numpy arrays
- _to_ordinalf_np_vectorized = np.vectorize(_to_ordinalf)
- def _dt64_to_ordinalf(d):
- """
- Convert `numpy.datetime64` or an ndarray of those types to Gregorian
- date as UTC float. Roundoff is via float64 precision. Practically:
- microseconds for dates between 290301 BC, 294241 AD, milliseconds for
- larger dates (see `numpy.datetime64`). Nanoseconds aren't possible
- because we do times compared to ``0001-01-01T00:00:00`` (plus one day).
- """
- # the "extra" ensures that we at least allow the dynamic range out to
- # seconds. That should get out to +/-2e11 years.
- extra = (d - d.astype('datetime64[s]')).astype('timedelta64[ns]')
- t0 = np.datetime64('0001-01-01T00:00:00', 's')
- dt = (d.astype('datetime64[s]') - t0).astype(np.float64)
- dt += extra.astype(np.float64) / 1.0e9
- dt = dt / SEC_PER_DAY + 1.0
- NaT_int = np.datetime64('NaT').astype(np.int64)
- d_int = d.astype(np.int64)
- try:
- dt[d_int == NaT_int] = np.nan
- except TypeError:
- if d_int == NaT_int:
- dt = 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`.
- """
- if tz is None:
- tz = _get_rc_timezone()
- ix, remainder = divmod(x, 1)
- ix = int(ix)
- if ix < 1:
- raise ValueError('Cannot convert {} to a date. This often happens if '
- 'non-datetime values are passed to an axis that '
- 'expects datetime objects.'.format(ix))
- dt = datetime.datetime.fromordinal(ix).replace(tzinfo=UTC)
- # Since the input date *x* float is unable to preserve microsecond
- # precision of time representation in non-antique years, the
- # resulting datetime is rounded to the nearest multiple of
- # `musec_prec`. A value of 20 is appropriate for current dates.
- musec_prec = 20
- remainder_musec = int(round(remainder * MUSECONDS_PER_DAY / musec_prec)
- * musec_prec)
- # For people trying to plot with full microsecond precision, enable
- # an early-year workaround
- if x < 30 * 365:
- remainder_musec = int(round(remainder * MUSECONDS_PER_DAY))
- # add hours, minutes, seconds, microseconds
- dt += datetime.timedelta(microseconds=remainder_musec)
- return dt.astimezone(tz)
- # a version of _from_ordinalf that can operate on numpy arrays
- _from_ordinalf_np_vectorized = np.vectorize(_from_ordinalf)
- @cbook.deprecated(
- "3.1", alternative="time.strptime or dateutil.parser.parse or datestr2num")
- class strpdate2num:
- """
- Use this class to parse date strings to matplotlib datenums when
- you know the date format string of the date you are parsing.
- """
- def __init__(self, fmt):
- """
- Parameters
- ----------
- fmt : any valid strptime format
- """
- self.fmt = fmt
- def __call__(self, s):
- """
- Parameters
- ----------
- s : str
- Returns
- -------
- date2num float
- """
- return date2num(datetime.datetime(*time.strptime(s, self.fmt)[:6]))
- @cbook.deprecated(
- "3.1", alternative="time.strptime or dateutil.parser.parse or datestr2num")
- class bytespdate2num(strpdate2num):
- """
- Use this class to parse date strings to matplotlib datenums when
- you know the date format string of the date you are parsing. See
- :doc:`/gallery/misc/load_converter.py`.
- """
- def __init__(self, fmt, encoding='utf-8'):
- """
- Parameters
- ----------
- fmt : any valid strptime format
- encoding : str
- Encoding to use on byte input.
- """
- super().__init__(fmt)
- self.encoding = encoding
- def __call__(self, b):
- """
- Parameters
- ----------
- b : bytes
- Returns
- -------
- date2num float
- """
- s = b.decode(self.encoding)
- return super().__call__(s)
- # 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 :func:`dateutil.parser.parse`.
- Parameters
- ----------
- d : str or sequence of str
- The dates to convert.
- default : datetime instance, 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 = [dateutil.parser.parse(s, default=default) for s in 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 (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):
- 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):
- return _dt64_to_ordinalf(d)
- if not d.size:
- return d
- return _to_ordinalf_np_vectorized(d)
- def julian2num(j):
- """
- Convert a Julian date (or sequence) to a Matplotlib date (or sequence).
- Parameters
- ----------
- j : float or sequence of floats
- Julian date(s)
- Returns
- -------
- float or sequence of floats
- Matplotlib date(s)
- """
- return np.subtract(j, JULIAN_OFFSET) # Handles both scalar & nonscalar j.
- def num2julian(n):
- """
- Convert a Matplotlib date (or sequence) to a Julian date (or sequence).
- Parameters
- ----------
- n : float or sequence of floats
- Matplotlib date(s)
- Returns
- -------
- float or sequence of floats
- Julian date(s)
- """
- return np.add(n, JULIAN_OFFSET) # Handles both scalar & nonscalar j.
- 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 0001-01-01 00:00:00 UTC, plus one.
- tz : str, optional
- Timezone of *x* (defaults to rcparams ``timezone``).
- Returns
- -------
- `~datetime.datetime` or sequence of `~datetime.datetime`
- Dates are returned in timezone *tz*.
- If *x* is a sequence, a sequence of :class:`datetime` objects will
- be returned.
- 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 tz is None:
- tz = _get_rc_timezone()
- if not np.iterable(x):
- return _from_ordinalf(x, tz)
- else:
- x = np.asarray(x)
- if not x.size:
- return x
- return _from_ordinalf_np_vectorized(x, tz).tolist()
- def _ordinalf_to_timedelta(x):
- return datetime.timedelta(days=x)
- _ordinalf_to_timedelta_np_vectorized = np.vectorize(_ordinalf_to_timedelta)
- 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`]
- """
- if not np.iterable(x):
- return _ordinalf_to_timedelta(x)
- else:
- x = np.asarray(x)
- if not x.size:
- return x
- 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
- -------
- drange : `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 dend, just subtract one delta
- dinterval_end -= delta
- num -= 1
- f2 = date2num(dinterval_end) # new float-endpoint
- return np.linspace(f1, f2, num + 1)
- ## date tickers and formatters ###
- class DateFormatter(ticker.Formatter):
- """
- Format a tick (in days since the epoch) with a
- `~datetime.datetime.strftime` format string.
- """
- illegal_s = re.compile(r"((^|[^%])(%%)*%s)")
- def __init__(self, fmt, tz=None):
- """
- Parameters
- ----------
- fmt : str
- `~datetime.datetime.strftime` format string
- tz : `tzinfo`, default: :rc:`timezone`
- Ticks timezone.
- """
- if tz is None:
- tz = _get_rc_timezone()
- self.fmt = fmt
- self.tz = tz
- def __call__(self, x, pos=0):
- if x == 0:
- raise ValueError('DateFormatter found a value of x=0, which is '
- 'an illegal date; this usually occurs because '
- 'you have not informed the axis that it is '
- 'plotting dates, e.g., with ax.xaxis_date()')
- return num2date(x, self.tz).strftime(self.fmt)
- def set_tzinfo(self, tz):
- self.tz = tz
- class IndexDateFormatter(ticker.Formatter):
- """Use with `.IndexLocator` to cycle format strings by index."""
- def __init__(self, t, fmt, tz=None):
- """
- *t* is a sequence of dates (floating point days). *fmt* is a
- `~datetime.datetime.strftime` format string.
- """
- if tz is None:
- tz = _get_rc_timezone()
- self.t = t
- self.fmt = fmt
- self.tz = tz
- def __call__(self, x, pos=0):
- 'Return the label for time *x* at position *pos*'
- ind = int(round(x))
- if ind >= len(self.t) or ind <= 0:
- return ''
- return num2date(self.t[ind], self.tz).strftime(self.fmt)
- class ConciseDateFormatter(ticker.Formatter):
- """
- This class 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, optional
- Passed to `.dates.date2num`.
- 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 `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
- Whether to show the offset or not. Default is ``True``.
- Examples
- --------
- See :doc:`/gallery/ticks_and_spines/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):
- """
- 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 labled "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('offsetfmts 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
- def __call__(self, x, pos=None):
- formatter = DateFormatter(self.defaultfmt, self._tz)
- 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
- # 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):
- if len(np.unique(tickdate[:, level])) > 1:
- break
- # 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 self.show_offset:
- # set the offset string:
- self.offset_string = tickdatetime[-1].strftime(offsetfmts[level])
- 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):
- """
- This class attempts to figure out the best format to use. This is
- most useful when used with the `AutoDateLocator`.
- The AutoDateFormatter has a scale dictionary that maps the scale
- of the tick (the distance in days between one major tick) and a
- format string. The default looks like this::
- self.scaled = {
- DAYS_PER_YEAR: rcParams['date.autoformat.year'],
- DAYS_PER_MONTH: rcParams['date.autoformat.month'],
- 1.0: rcParams['date.autoformat.day'],
- 1. / HOURS_PER_DAY: rcParams['date.autoformat.hour'],
- 1. / (MINUTES_PER_DAY): rcParams['date.autoformat.minute'],
- 1. / (SEC_PER_DAY): rcParams['date.autoformat.second'],
- 1. / (MUSECONDS_PER_DAY): rcParams['date.autoformat.microsecond'],
- }
- The algorithm picks the key in the dictionary that is >= the
- current scale and uses that format string. You can customize this
- dictionary by doing::
- >>> locator = AutoDateLocator()
- >>> formatter = AutoDateFormatter(locator)
- >>> formatter.scaled[1/(24.*60.)] = '%M:%S' # only show min and sec
- A custom `.FuncFormatter` can also be used. 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
- >>> from matplotlib.ticker import FuncFormatter
- >>> formatter.scaled[1/(24.*60.)] = FuncFormatter(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'):
- """
- Autoformat the date labels. The default format is the one to use
- if none of the values in ``self.scaled`` are greater than the unit
- returned by ``locator._get_unit()``.
- """
- self._locator = locator
- self._tz = tz
- self.defaultfmt = defaultfmt
- self._formatter = DateFormatter(self.defaultfmt, tz)
- self.scaled = {DAYS_PER_YEAR: rcParams['date.autoformatter.year'],
- DAYS_PER_MONTH: rcParams['date.autoformatter.month'],
- 1.0: 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)
- result = self._formatter(x, pos)
- elif callable(fmt):
- result = fmt(x, pos)
- else:
- raise TypeError('Unexpected type passed to {0!r}.'.format(self))
- return result
- class rrulewrapper:
- def __init__(self, freq, tzinfo=None, **kwargs):
- kwargs['freq'] = freq
- self._base_tzinfo = tzinfo
- self._update_rrule(**kwargs)
- def set(self, **kwargs):
- self._construct.update(kwargs)
- self._update_rrule(**self._construct)
- def _update_rrule(self, **kwargs):
- tzinfo = self._base_tzinfo
- # rrule does not play nicely with time zones - 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):
- """
- *tz* is a :class:`tzinfo` instance.
- """
- if tz is None:
- tz = _get_rc_timezone()
- self.tz = tz
- def set_tzinfo(self, tz):
- """
- Set time zone info.
- """
- self.tz = 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
- if dmin < 1:
- raise ValueError('datalim minimum {} is less than 1 and '
- 'is an invalid Matplotlib date value. This often '
- 'happens if you pass a non-datetime '
- 'value to an axis that has datetime units'
- .format(dmin))
- return num2date(dmin, self.tz), num2date(dmax, self.tz)
- def viewlim_to_dt(self):
- """
- Converts the view interval to datetime objects.
- """
- vmin, vmax = self.axis.get_view_interval()
- if vmin > vmax:
- vmin, vmax = vmax, vmin
- if vmin < 1:
- raise ValueError('view limit minimum {} is less than 1 and '
- 'is an invalid Matplotlib date value. This '
- 'often happens if you pass a non-datetime '
- 'value to an axis that has datetime units'
- .format(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 2000-2010 as default.
- return (date2num(datetime.date(2000, 1, 1)),
- date2num(datetime.date(2010, 1, 1)))
- 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):
- DateLocator.__init__(self, 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):
- delta = relativedelta(vmax, vmin)
- # We need to cap at the endpoints of valid datetime
- try:
- start = vmin - delta
- except (ValueError, OverflowError):
- start = _from_ordinalf(1.0)
- try:
- stop = vmax + delta
- except (ValueError, OverflowError):
- # The magic number!
- stop = _from_ordinalf(3652059.9999999)
- self.rule.set(dtstart=start, until=stop)
- dates = self.rule.between(vmin, vmax, True)
- if len(dates) == 0:
- return date2num([vmin, vmax])
- return self.raise_if_exceeds(date2num(dates))
- def _get_unit(self):
- """
- Return how many days a unit of the locator is; used for
- intelligent autoscaling.
- """
- freq = self.rule._rrule._freq
- return self.get_unit_generic(freq)
- @staticmethod
- 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
- @cbook.deprecated("3.2")
- def autoscale(self):
- """
- Set the view limits to include the data range.
- """
- dmin, dmax = self.datalim_to_dt()
- delta = relativedelta(dmax, dmin)
- # We need to cap at the endpoints of valid datetime
- try:
- start = dmin - delta
- except ValueError:
- start = _from_ordinalf(1.0)
- try:
- stop = dmax + delta
- except ValueError:
- # The magic number!
- stop = _from_ordinalf(3652059.9999999)
- self.rule.set(dtstart=start, until=stop)
- dmin, dmax = self.datalim_to_dt()
- vmin = self.rule.before(dmin, True)
- if not vmin:
- vmin = dmin
- vmax = self.rule.after(dmax, True)
- if not vmax:
- vmax = dmax
- vmin = date2num(vmin)
- vmax = date2num(vmax)
- return self.nonsingular(vmin, vmax)
- 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 (a constant from dateutil.rrule) to
- multiples allowed for that ticking. The default looks like this::
- 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],
- 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],
- }
- 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.
- You can customize this dictionary by doing::
- 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 : `tzinfo`
- Ticks timezone.
- 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.
- """
- DateLocator.__init__(self, tz)
- self._locator = YearLocator(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, 21]
- self._byranges = [None, range(1, 13), range(1, 32),
- range(0, 24), range(0, 60), range(0, 60), None]
- def __call__(self):
- 'Return the locations of the ticks'
- 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.
- if not np.isfinite(vmin) or not np.isfinite(vmax):
- # Except if there is no data, then use 2000-2010 as default.
- return (date2num(datetime.date(2000, 1, 1)),
- date2num(datetime.date(2010, 1, 1)))
- 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 set_axis(self, axis):
- DateLocator.set_axis(self, axis)
- self._locator.set_axis(axis)
- def refresh(self):
- # docstring inherited
- dmin, dmax = self.viewlim_to_dt()
- self._locator = self.get_locator(dmin, dmax)
- def _get_unit(self):
- if self._freq in [MICROSECONDLY]:
- return 1. / MUSECONDS_PER_DAY
- else:
- return RRuleLocator.get_unit_generic(self._freq)
- @cbook.deprecated("3.2")
- def autoscale(self):
- 'Try to choose the view limits intelligently.'
- dmin, dmax = self.datalim_to_dt()
- self._locator = self.get_locator(dmin, dmax)
- return self._locator.autoscale()
- 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 an 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:
- # We went through the whole loop without breaking, default to
- # the last interval in the list and raise a warning
- cbook._warn_external(
- f"AutoDateLocator was unable to pick an appropriate "
- f"interval for this date range. It may be necessary to "
- f"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:
- raise ValueError('No sensible date limit could be found in the '
- 'AutoDateLocator.')
- 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, self.tz)
- else:
- locator = MicrosecondLocator(interval, tz=self.tz)
- if dmin.year > 20 and interval < 1000:
- cbook._warn_external(
- 'Plotting microsecond time intervals is not well '
- 'supported; please see the MicrosecondLocator '
- 'documentation for details.')
- locator.set_axis(self.axis)
- if self.axis is not None:
- locator.set_view_interval(*self.axis.get_view_interval())
- locator.set_data_interval(*self.axis.get_data_interval())
- return locator
- class YearLocator(DateLocator):
- """
- 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):
- """
- Mark years that are multiple of base on a given month and day
- (default jan 1).
- """
- DateLocator.__init__(self, tz)
- self.base = ticker._Edge_integer(base, 0)
- self.replaced = {'month': month,
- 'day': day,
- 'hour': 0,
- 'minute': 0,
- 'second': 0,
- }
- if not hasattr(tz, 'localize'):
- # if tz is pytz, we need to do this w/ the localize fcn,
- # otherwise datetime.replace works fine...
- self.replaced['tzinfo'] = tz
- 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):
- ymin = self.base.le(vmin.year) * self.base.step
- ymax = self.base.ge(vmax.year) * self.base.step
- vmin = vmin.replace(year=ymin, **self.replaced)
- if hasattr(self.tz, 'localize'):
- # look after pytz
- if not vmin.tzinfo:
- vmin = self.tz.localize(vmin, is_dst=True)
- ticks = [vmin]
- while True:
- dt = ticks[-1]
- if dt.year >= ymax:
- return date2num(ticks)
- year = dt.year + self.base.step
- dt = dt.replace(year=year, **self.replaced)
- if hasattr(self.tz, 'localize'):
- # look after pytz
- if not dt.tzinfo:
- dt = self.tz.localize(dt, is_dst=True)
- ticks.append(dt)
- @cbook.deprecated("3.2")
- def autoscale(self):
- """
- Set the view limits to include the data range.
- """
- dmin, dmax = self.datalim_to_dt()
- ymin = self.base.le(dmin.year)
- ymax = self.base.ge(dmax.year)
- vmin = dmin.replace(year=ymin, **self.replaced)
- vmin = vmin.astimezone(self.tz)
- vmax = dmax.replace(year=ymax, **self.replaced)
- vmax = vmax.astimezone(self.tz)
- vmin = date2num(vmin)
- vmax = date2num(vmax)
- return self.nonsingular(vmin, vmax)
- 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):
- """
- Mark every month in *bymonth*; *bymonth* can be an int or
- sequence. Default is ``range(1, 13)``, i.e. every month.
- *interval* is the interval between each iteration. For
- example, if ``interval=2``, mark every second occurrence.
- """
- if bymonth is None:
- bymonth = range(1, 13)
- elif isinstance(bymonth, np.ndarray):
- # This fixes a bug in dateutil <= 2.3 which prevents the use of
- # numpy arrays in (among other things) the bymonthday, byweekday
- # and bymonth parameters.
- bymonth = [x.item() for x in bymonth.astype(int)]
- rule = rrulewrapper(MONTHLY, bymonth=bymonth, bymonthday=bymonthday,
- interval=interval, **self.hms0d)
- RRuleLocator.__init__(self, rule, tz)
- class WeekdayLocator(RRuleLocator):
- """
- Make ticks on occurrences of each weekday.
- """
- def __init__(self, byweekday=1, interval=1, tz=None):
- """
- Mark every weekday in *byweekday*; *byweekday* can be a number or
- sequence.
- 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* specifies the number of weeks to skip. For example,
- ``interval=2`` plots every second week.
- """
- if isinstance(byweekday, np.ndarray):
- # This fixes a bug in dateutil <= 2.3 which prevents the use of
- # numpy arrays in (among other things) the bymonthday, byweekday
- # and bymonth parameters.
- [x.item() for x in byweekday.astype(int)]
- rule = rrulewrapper(DAILY, byweekday=byweekday,
- interval=interval, **self.hms0d)
- RRuleLocator.__init__(self, rule, 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):
- """
- Mark every day in *bymonthday*; *bymonthday* can be an int or sequence.
- Default is to tick every day of the month: ``bymonthday=range(1, 32)``.
- """
- 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)
- elif isinstance(bymonthday, np.ndarray):
- # This fixes a bug in dateutil <= 2.3 which prevents the use of
- # numpy arrays in (among other things) the bymonthday, byweekday
- # and bymonth parameters.
- bymonthday = [x.item() for x in bymonthday.astype(int)]
- rule = rrulewrapper(DAILY, bymonthday=bymonthday,
- interval=interval, **self.hms0d)
- RRuleLocator.__init__(self, rule, tz)
- class HourLocator(RRuleLocator):
- """
- Make ticks on occurrences of each hour.
- """
- def __init__(self, byhour=None, interval=1, tz=None):
- """
- Mark every hour in *byhour*; *byhour* can be an int or sequence.
- Default is to tick every hour: ``byhour=range(24)``
- *interval* is the interval between each iteration. For
- example, if ``interval=2``, mark every second occurrence.
- """
- if byhour is None:
- byhour = range(24)
- rule = rrulewrapper(HOURLY, byhour=byhour, interval=interval,
- byminute=0, bysecond=0)
- RRuleLocator.__init__(self, rule, tz)
- class MinuteLocator(RRuleLocator):
- """
- Make ticks on occurrences of each minute.
- """
- def __init__(self, byminute=None, interval=1, tz=None):
- """
- Mark every minute in *byminute*; *byminute* can be an int or
- sequence. Default is to tick every minute: ``byminute=range(60)``
- *interval* is the interval between each iteration. For
- example, if ``interval=2``, mark every second occurrence.
- """
- if byminute is None:
- byminute = range(60)
- rule = rrulewrapper(MINUTELY, byminute=byminute, interval=interval,
- bysecond=0)
- RRuleLocator.__init__(self, rule, tz)
- class SecondLocator(RRuleLocator):
- """
- Make ticks on occurrences of each second.
- """
- def __init__(self, bysecond=None, interval=1, tz=None):
- """
- Mark every second in *bysecond*; *bysecond* can be an int or
- sequence. Default is to tick every second: ``bysecond = range(60)``
- *interval* is the interval between each iteration. For
- example, if ``interval=2``, mark every second occurrence.
- """
- if bysecond is None:
- bysecond = range(60)
- rule = rrulewrapper(SECONDLY, bysecond=bysecond, interval=interval)
- RRuleLocator.__init__(self, rule, tz)
- class MicrosecondLocator(DateLocator):
- """
- Make ticks on regular intervals of one or more microsecond(s).
- .. note::
- Due to the floating point representation of time in days since
- 0001-01-01 UTC (plus 1), plotting data with microsecond time
- resolution does not work well with current dates.
- If you want 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, your only chance is to use very
- early years; using year 0001 is recommended.
- """
- def __init__(self, interval=1, tz=None):
- """
- *interval* is the interval between each iteration. For
- example, if ``interval=2``, mark every second microsecond.
- """
- self._interval = interval
- self._wrapped_locator = ticker.MultipleLocator(interval)
- self.tz = tz
- def set_axis(self, axis):
- self._wrapped_locator.set_axis(axis)
- return DateLocator.set_axis(self, axis)
- def set_view_interval(self, vmin, vmax):
- self._wrapped_locator.set_view_interval(vmin, vmax)
- return DateLocator.set_view_interval(self, vmin, vmax)
- 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):
- # 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
- def _get_unit(self):
- """
- Return how many days a unit of the locator is; used for
- intelligent autoscaling.
- """
- return 1. / MUSECONDS_PER_DAY
- def _get_interval(self):
- """
- Return the number of units for each tick.
- """
- return self._interval
- def epoch2num(e):
- """
- Convert an epoch or sequence of epochs to the new date format,
- that is days since 0001.
- """
- return EPOCH_OFFSET + np.asarray(e) / SEC_PER_DAY
- def num2epoch(d):
- """
- Convert days since 0001 to epoch. *d* can be a number or sequence.
- """
- return (np.asarray(d) - EPOCH_OFFSET) * SEC_PER_DAY
- @cbook.deprecated("3.2")
- def mx2num(mxdates):
- """
- Convert mx :class:`datetime` instance (or sequence of mx
- instances) to the new date format.
- """
- scalar = False
- if not np.iterable(mxdates):
- scalar = True
- mxdates = [mxdates]
- ret = epoch2num([m.ticks() for m in mxdates])
- if scalar:
- return ret[0]
- else:
- return ret
- def date_ticker_factory(span, tz=None, numticks=5):
- """
- Create a date locator with *numticks* (approx) and a date formatter
- for *span* in days. Return value is (locator, formatter).
- """
- if span == 0:
- span = 1 / HOURS_PER_DAY
- mins = span * MINUTES_PER_DAY
- hrs = span * HOURS_PER_DAY
- days = span
- wks = span / DAYS_PER_WEEK
- months = span / DAYS_PER_MONTH # Approx
- years = span / DAYS_PER_YEAR # Approx
- if years > numticks:
- locator = YearLocator(int(years / numticks), tz=tz) # define
- fmt = '%Y'
- elif months > numticks:
- locator = MonthLocator(tz=tz)
- fmt = '%b %Y'
- elif wks > numticks:
- locator = WeekdayLocator(tz=tz)
- fmt = '%a, %b %d'
- elif days > numticks:
- locator = DayLocator(interval=math.ceil(days / numticks), tz=tz)
- fmt = '%b %d'
- elif hrs > numticks:
- locator = HourLocator(interval=math.ceil(hrs / numticks), tz=tz)
- fmt = '%H:%M\n%b %d'
- elif mins > numticks:
- locator = MinuteLocator(interval=math.ceil(mins / numticks), tz=tz)
- fmt = '%H:%M:%S'
- else:
- locator = MinuteLocator(tz=tz)
- fmt = '%H:%M:%S'
- formatter = DateFormatter(fmt, tz=tz)
- return locator, formatter
- @cbook.deprecated("3.1")
- def seconds(s):
- """
- Return seconds as days.
- """
- return s / SEC_PER_DAY
- @cbook.deprecated("3.1")
- def minutes(m):
- """
- Return minutes as days.
- """
- return m / MINUTES_PER_DAY
- @cbook.deprecated("3.1")
- def hours(h):
- """
- Return hours as days.
- """
- return h / HOURS_PER_DAY
- @cbook.deprecated("3.1")
- def weeks(w):
- """
- Return weeks as days.
- """
- return w * DAYS_PER_WEEK
- 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 tzinfo instance.
- """
- @staticmethod
- def axisinfo(unit, axis):
- """
- Return the `~matplotlib.units.AxisInfo` for *unit*.
- *unit* is a tzinfo instance or None.
- The *axis* argument is required but not used.
- """
- tz = unit
- majloc = AutoDateLocator(tz=tz)
- majfmt = AutoDateFormatter(majloc, tz=tz)
- datemin = datetime.date(2000, 1, 1)
- datemax = datetime.date(2010, 1, 1)
- return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
- default_limits=(datemin, datemax))
- @staticmethod
- 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)
- @staticmethod
- def default_units(x, axis):
- """
- Return the tzinfo instance of *x* or of its first element, or None
- """
- if isinstance(x, np.ndarray):
- x = x.ravel()
- try:
- x = cbook.safe_first_element(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):
- self._formats = formats
- self._zero_formats = zero_formats
- self._offset_formats = offset_formats
- self._show_offset = show_offset
- super().__init__()
- def axisinfo(self, unit, axis):
- # docstring inherited
- tz = unit
- majloc = AutoDateLocator(tz=tz)
- 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(2000, 1, 1)
- datemax = datetime.date(2010, 1, 1)
- return units.AxisInfo(majloc=majloc, majfmt=majfmt, label='',
- default_limits=(datemin, datemax))
- units.registry[np.datetime64] = DateConverter()
- units.registry[datetime.date] = DateConverter()
- units.registry[datetime.datetime] = DateConverter()
|