Skip to content

Commit 7ed6bc2

Browse files
authored
Merge pull request #17022 from jklymak/enh-rcparams-concisedate-intervals
ENH: add rcParam for ConciseDate and interval_multiples
2 parents 1b12cd5 + a793bde commit 7ed6bc2

File tree

5 files changed

+147
-9
lines changed

5 files changed

+147
-9
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
New rcParams for dates: set converter and whether to use interval_multiples
2+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3+
4+
The new :rc:`date.converter` allows toggling between
5+
`matplotlib.dates.DateConverter` and `matplotlib.dates.ConciseDateConverter`
6+
using the strings 'auto' and 'concise' respectively.
7+
8+
The new :rc:`date.interval_multiples` allows toggling between the dates
9+
locator trying to pick ticks at set intervals (i.e. day 1 and 15 of the
10+
month), versus evenly spaced ticks that start where ever the
11+
timeseries starts:
12+
13+
.. plot::
14+
:include-source: True
15+
16+
import matplotlib.pyplot as plt
17+
import numpy as np
18+
19+
dates = np.arange('2001-01-10', '2001-05-23', dtype='datetime64[D]')
20+
y = np.sin(dates.astype(float) / 10)
21+
fig, axs = plt.subplots(nrows=2, constrained_layout=True)
22+
23+
plt.rcParams['date.converter'] = 'concise'
24+
plt.rcParams['date.interval_multiples'] = True
25+
ax = axs[0]
26+
ax.plot(dates, y)
27+
28+
plt.rcParams['date.converter'] = 'auto'
29+
plt.rcParams['date.interval_multiples'] = False
30+
ax = axs[1]
31+
ax.plot(dates, y)

lib/matplotlib/dates.py

+50-8
Original file line numberDiff line numberDiff line change
@@ -1834,8 +1834,11 @@ class DateConverter(units.ConversionInterface):
18341834
The 'unit' tag for such data is None or a tzinfo instance.
18351835
"""
18361836

1837-
@staticmethod
1838-
def axisinfo(unit, axis):
1837+
def __init__(self, *, interval_multiples=True):
1838+
self._interval_multiples = interval_multiples
1839+
super().__init__()
1840+
1841+
def axisinfo(self, unit, axis):
18391842
"""
18401843
Return the `~matplotlib.units.AxisInfo` for *unit*.
18411844
@@ -1844,7 +1847,8 @@ def axisinfo(unit, axis):
18441847
"""
18451848
tz = unit
18461849

1847-
majloc = AutoDateLocator(tz=tz)
1850+
majloc = AutoDateLocator(tz=tz,
1851+
interval_multiples=self._interval_multiples)
18481852
majfmt = AutoDateFormatter(majloc, tz=tz)
18491853
datemin = datetime.date(2000, 1, 1)
18501854
datemax = datetime.date(2010, 1, 1)
@@ -1886,17 +1890,19 @@ class ConciseDateConverter(DateConverter):
18861890
# docstring inherited
18871891

18881892
def __init__(self, formats=None, zero_formats=None, offset_formats=None,
1889-
show_offset=True):
1893+
show_offset=True, *, interval_multiples=True):
18901894
self._formats = formats
18911895
self._zero_formats = zero_formats
18921896
self._offset_formats = offset_formats
18931897
self._show_offset = show_offset
1898+
self._interval_multiples = interval_multiples
18941899
super().__init__()
18951900

18961901
def axisinfo(self, unit, axis):
18971902
# docstring inherited
18981903
tz = unit
1899-
majloc = AutoDateLocator(tz=tz)
1904+
majloc = AutoDateLocator(tz=tz,
1905+
interval_multiples=self._interval_multiples)
19001906
majfmt = ConciseDateFormatter(majloc, tz=tz, formats=self._formats,
19011907
zero_formats=self._zero_formats,
19021908
offset_formats=self._offset_formats,
@@ -1907,6 +1913,42 @@ def axisinfo(self, unit, axis):
19071913
default_limits=(datemin, datemax))
19081914

19091915

1910-
units.registry[np.datetime64] = DateConverter()
1911-
units.registry[datetime.date] = DateConverter()
1912-
units.registry[datetime.datetime] = DateConverter()
1916+
class _rcParam_helper:
1917+
"""
1918+
This helper class is so that we can set the converter for dates
1919+
via the validator for the rcParams `date.converter` and
1920+
`date.interval_multiples`. Never instatiated.
1921+
"""
1922+
1923+
conv_st = 'auto'
1924+
int_mult = True
1925+
1926+
@classmethod
1927+
def set_converter(cls, s):
1928+
"""Called by validator for rcParams date.converter"""
1929+
cls.conv_st = s
1930+
cls.register_converters()
1931+
1932+
@classmethod
1933+
def set_int_mult(cls, b):
1934+
"""Called by validator for rcParams date.interval_multiples"""
1935+
cls.int_mult = b
1936+
cls.register_converters()
1937+
1938+
@classmethod
1939+
def register_converters(cls):
1940+
"""
1941+
Helper to register the date converters when rcParams `date.converter`
1942+
and `date.interval_multiples` are changed. Called by the helpers
1943+
above.
1944+
"""
1945+
if cls.conv_st == 'concise':
1946+
converter = ConciseDateConverter
1947+
else:
1948+
converter = DateConverter
1949+
1950+
interval_multiples = cls.int_mult
1951+
convert = converter(interval_multiples=interval_multiples)
1952+
units.registry[np.datetime64] = convert
1953+
units.registry[datetime.date] = convert
1954+
units.registry[datetime.datetime] = convert

lib/matplotlib/rcsetup.py

+24
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import operator
2121
import os
2222
import re
23+
import sys
2324

2425
import numpy as np
2526

@@ -174,6 +175,23 @@ def validate_bool_maybe_none(b):
174175
raise ValueError('Could not convert "%s" to bool' % b)
175176

176177

178+
def _validate_date_converter(s):
179+
s = validate_string(s)
180+
mdates = sys.modules.get("matplotlib.dates")
181+
if mdates:
182+
mdates._rcParam_helper.set_converter(s)
183+
184+
185+
def _validate_date_int_mult(s):
186+
if s is None:
187+
return
188+
s = validate_bool(s)
189+
# only do this if dates is already imported...
190+
mdates = sys.modules.get("matplotlib.dates")
191+
if mdates:
192+
mdates._rcParam_helper.set_int_mult(s)
193+
194+
177195
def _validate_tex_preamble(s):
178196
if s is None or s == 'None':
179197
cbook.warn_deprecated(
@@ -1271,13 +1289,19 @@ def _convert_validator_spec(key, conv):
12711289
"date.autoformatter.second": validate_string,
12721290
"date.autoformatter.microsecond": validate_string,
12731291

1292+
# 'auto', 'concise', 'auto-noninterval'
1293+
'date.converter': _validate_date_converter,
1294+
# for auto date locator, choose interval_multiples
1295+
'date.interval_multiples': _validate_date_int_mult,
1296+
12741297
# legend properties
12751298
"legend.fancybox": validate_bool,
12761299
"legend.loc": _ignorecase([
12771300
"best",
12781301
"upper right", "upper left", "lower left", "lower right", "right",
12791302
"center left", "center right", "lower center", "upper center",
12801303
"center"]),
1304+
12811305
# the number of points in the legend line
12821306
"legend.numpoints": validate_int,
12831307
# the number of points in the legend line for scatter

lib/matplotlib/tests/test_dates.py

+38
Original file line numberDiff line numberDiff line change
@@ -958,3 +958,41 @@ def test_warn_notintervals():
958958
mdates.date2num(dates[-1]))
959959
with pytest.warns(UserWarning, match="AutoDateLocator was unable") as rec:
960960
locs = locator()
961+
962+
963+
def test_change_converter():
964+
plt.rcParams['date.converter'] = 'concise'
965+
dates = np.arange('2020-01-01', '2020-05-01', dtype='datetime64[D]')
966+
fig, ax = plt.subplots()
967+
968+
ax.plot(dates, np.arange(len(dates)))
969+
fig.canvas.draw()
970+
assert ax.get_xticklabels()[0].get_text() == 'Jan'
971+
assert ax.get_xticklabels()[1].get_text() == '15'
972+
973+
plt.rcParams['date.converter'] = 'auto'
974+
fig, ax = plt.subplots()
975+
976+
ax.plot(dates, np.arange(len(dates)))
977+
fig.canvas.draw()
978+
assert ax.get_xticklabels()[0].get_text() == 'Jan 01 2020'
979+
assert ax.get_xticklabels()[1].get_text() == 'Jan 15 2020'
980+
981+
982+
def test_change_interval_multiples():
983+
plt.rcParams['date.interval_multiples'] = False
984+
dates = np.arange('2020-01-10', '2020-05-01', dtype='datetime64[D]')
985+
fig, ax = plt.subplots()
986+
987+
ax.plot(dates, np.arange(len(dates)))
988+
fig.canvas.draw()
989+
assert ax.get_xticklabels()[0].get_text() == 'Jan 10 2020'
990+
assert ax.get_xticklabels()[1].get_text() == 'Jan 24 2020'
991+
992+
plt.rcParams['date.interval_multiples'] = 'True'
993+
fig, ax = plt.subplots()
994+
995+
ax.plot(dates, np.arange(len(dates)))
996+
fig.canvas.draw()
997+
assert ax.get_xticklabels()[0].get_text() == 'Jan 15 2020'
998+
assert ax.get_xticklabels()[1].get_text() == 'Feb 01 2020'

matplotlibrc.template

+4-1
Original file line numberDiff line numberDiff line change
@@ -442,10 +442,13 @@
442442
#date.autoformatter.minute: %d %H:%M
443443
#date.autoformatter.second: %H:%M:%S
444444
#date.autoformatter.microsecond: %M:%S.%f
445-
446445
## The reference date for Matplotlib's internal date representation
447446
## See https://matplotlib.org/examples/ticks_and_spines/date_precision_and_epochs.py
448447
#date.epoch: 1970-01-01T00:00:00
448+
## 'auto', 'concise':
449+
#date.converter: auto
450+
## For auto converter whether to use interval_multiples:
451+
#date.interval_multiples: True
449452

450453
## ***************************************************************************
451454
## * TICKS *

0 commit comments

Comments
 (0)