Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ENH: add rcParam for ConciseDate and interval_multiples #17022

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions doc/users/next_whats_new/rcparams_dates.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
New rcParams for dates: set converter and whether to use interval_multiples
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The new :rc:`date.converter` allows toggling between
`matplotlib.dates.DateConverter` and `matplotlib.dates.ConciseDateConverter`
using the strings 'auto' and 'concise' respectively.

The new :rc:`date.interval_multiples` allows toggling between the dates
locator trying to pick ticks at set intervals (i.e. day 1 and 15 of the
month), versus evenly spaced ticks that start where ever the
timeseries starts:

.. plot::
:include-source: True

import matplotlib.pyplot as plt
import numpy as np

dates = np.arange('2001-01-10', '2001-05-23', dtype='datetime64[D]')
y = np.sin(dates.astype(float) / 10)
fig, axs = plt.subplots(nrows=2, constrained_layout=True)

plt.rcParams['date.converter'] = 'concise'
plt.rcParams['date.interval_multiples'] = True
ax = axs[0]
ax.plot(dates, y)

plt.rcParams['date.converter'] = 'auto'
plt.rcParams['date.interval_multiples'] = False
ax = axs[1]
ax.plot(dates, y)
58 changes: 50 additions & 8 deletions lib/matplotlib/dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -1834,8 +1834,11 @@ class DateConverter(units.ConversionInterface):
The 'unit' tag for such data is None or a tzinfo instance.
"""

@staticmethod
def axisinfo(unit, axis):
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*.

Expand All @@ -1844,7 +1847,8 @@ def axisinfo(unit, axis):
"""
tz = unit

majloc = AutoDateLocator(tz=tz)
majloc = AutoDateLocator(tz=tz,
interval_multiples=self._interval_multiples)
majfmt = AutoDateFormatter(majloc, tz=tz)
datemin = datetime.date(2000, 1, 1)
datemax = datetime.date(2010, 1, 1)
Expand Down Expand Up @@ -1886,17 +1890,19 @@ class ConciseDateConverter(DateConverter):
# docstring inherited

def __init__(self, formats=None, zero_formats=None, offset_formats=None,
show_offset=True):
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)
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,
Expand All @@ -1907,6 +1913,42 @@ def axisinfo(self, unit, axis):
default_limits=(datemin, datemax))


units.registry[np.datetime64] = DateConverter()
units.registry[datetime.date] = DateConverter()
units.registry[datetime.datetime] = DateConverter()
class _rcParam_helper:
"""
This helper class is so that we can set the converter for dates
via the validator for the rcParams `date.converter` and
`date.interval_multiples`. Never instatiated.
"""

conv_st = 'auto'
int_mult = True

@classmethod
def set_converter(cls, s):
"""Called by validator for rcParams date.converter"""
cls.conv_st = s
cls.register_converters()

@classmethod
def set_int_mult(cls, b):
"""Called by validator for rcParams date.interval_multiples"""
cls.int_mult = b
cls.register_converters()

@classmethod
def register_converters(cls):
"""
Helper to register the date converters when rcParams `date.converter`
and `date.interval_multiples` are changed. Called by the helpers
above.
"""
if cls.conv_st == 'concise':
converter = ConciseDateConverter
else:
converter = DateConverter

interval_multiples = cls.int_mult
convert = converter(interval_multiples=interval_multiples)
units.registry[np.datetime64] = convert
units.registry[datetime.date] = convert
units.registry[datetime.datetime] = convert
24 changes: 24 additions & 0 deletions lib/matplotlib/rcsetup.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import operator
import os
import re
import sys

import numpy as np

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


def _validate_date_converter(s):
s = validate_string(s)
mdates = sys.modules.get("matplotlib.dates")
if mdates:
mdates._rcParam_helper.set_converter(s)


def _validate_date_int_mult(s):
if s is None:
return
s = validate_bool(s)
# only do this if dates is already imported...
mdates = sys.modules.get("matplotlib.dates")
if mdates:
mdates._rcParam_helper.set_int_mult(s)


def _validate_tex_preamble(s):
if s is None or s == 'None':
cbook.warn_deprecated(
Expand Down Expand Up @@ -1271,13 +1289,19 @@ def _convert_validator_spec(key, conv):
"date.autoformatter.second": validate_string,
"date.autoformatter.microsecond": validate_string,

# 'auto', 'concise', 'auto-noninterval'
'date.converter': _validate_date_converter,
# for auto date locator, choose interval_multiples
'date.interval_multiples': _validate_date_int_mult,

# legend properties
"legend.fancybox": validate_bool,
"legend.loc": _ignorecase([
"best",
"upper right", "upper left", "lower left", "lower right", "right",
"center left", "center right", "lower center", "upper center",
"center"]),

# the number of points in the legend line
"legend.numpoints": validate_int,
# the number of points in the legend line for scatter
Expand Down
38 changes: 38 additions & 0 deletions lib/matplotlib/tests/test_dates.py
Original file line number Diff line number Diff line change
Expand Up @@ -958,3 +958,41 @@ def test_warn_notintervals():
mdates.date2num(dates[-1]))
with pytest.warns(UserWarning, match="AutoDateLocator was unable") as rec:
locs = locator()


def test_change_converter():
plt.rcParams['date.converter'] = 'concise'
dates = np.arange('2020-01-01', '2020-05-01', dtype='datetime64[D]')
fig, ax = plt.subplots()

ax.plot(dates, np.arange(len(dates)))
fig.canvas.draw()
assert ax.get_xticklabels()[0].get_text() == 'Jan'
assert ax.get_xticklabels()[1].get_text() == '15'

plt.rcParams['date.converter'] = 'auto'
fig, ax = plt.subplots()

ax.plot(dates, np.arange(len(dates)))
fig.canvas.draw()
assert ax.get_xticklabels()[0].get_text() == 'Jan 01 2020'
assert ax.get_xticklabels()[1].get_text() == 'Jan 15 2020'


def test_change_interval_multiples():
plt.rcParams['date.interval_multiples'] = False
dates = np.arange('2020-01-10', '2020-05-01', dtype='datetime64[D]')
fig, ax = plt.subplots()

ax.plot(dates, np.arange(len(dates)))
fig.canvas.draw()
assert ax.get_xticklabels()[0].get_text() == 'Jan 10 2020'
assert ax.get_xticklabels()[1].get_text() == 'Jan 24 2020'

plt.rcParams['date.interval_multiples'] = 'True'
fig, ax = plt.subplots()

ax.plot(dates, np.arange(len(dates)))
fig.canvas.draw()
assert ax.get_xticklabels()[0].get_text() == 'Jan 15 2020'
assert ax.get_xticklabels()[1].get_text() == 'Feb 01 2020'
5 changes: 4 additions & 1 deletion matplotlibrc.template
Original file line number Diff line number Diff line change
Expand Up @@ -442,10 +442,13 @@
#date.autoformatter.minute: %d %H:%M
#date.autoformatter.second: %H:%M:%S
#date.autoformatter.microsecond: %M:%S.%f

## The reference date for Matplotlib's internal date representation
## See https://matplotlib.org/examples/ticks_and_spines/date_precision_and_epochs.py
#date.epoch: 1970-01-01T00:00:00
## 'auto', 'concise':
#date.converter: auto
## For auto converter whether to use interval_multiples:
#date.interval_multiples: True

## ***************************************************************************
## * TICKS *
Expand Down