Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit de1cb4a

Browse files
committed
Support usetex in date Formatters.
Fixes matplotlib#2294.
1 parent 535941c commit de1cb4a

File tree

3 files changed

+119
-7
lines changed

3 files changed

+119
-7
lines changed
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
Date formatters now respect *usetex* rcParam
2+
--------------------------------------------
3+
4+
The `.AutoDateFormatter` and `.ConciseDateFormatter` now respect
5+
:rc:`text.usetex`, and will thus use fonts consistent with TeX rendering of the
6+
default (non-date) formatter. TeX rendering may also be enabled/disabled by
7+
passing the *usetex* parameter when creating the formatter instance.
8+
9+
In the following plot, both the x-axis (dates) and y-axis (numbers) now use the
10+
same (TeX) font:
11+
12+
.. plot::
13+
14+
from datetime import datetime, timedelta
15+
from matplotlib.dates import ConciseDateFormatter
16+
17+
plt.rc('text', usetex=True)
18+
19+
t0 = datetime(1968, 8, 1)
20+
ts = [t0 + i * timedelta(days=1) for i in range(10)]
21+
22+
fig, ax = plt.subplots()
23+
ax.plot(ts, range(10))
24+
ax.xaxis.set_major_formatter(ConciseDateFormatter(ax.xaxis.get_major_locator()))
25+
ax.set_xlabel('Date')
26+
ax.set_ylabel('Value')

lib/matplotlib/dates.py

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -583,6 +583,12 @@ def drange(dstart, dend, delta):
583583
f2 = date2num(dinterval_end) # new float-endpoint
584584
return np.linspace(f1, f2, num + 1)
585585

586+
587+
def _wrap_in_tex(text):
588+
# Braces ensure dashes are not spaced like binary operators.
589+
return '$\\mathdefault{' + text.replace('-', '{-}') + '}$'
590+
591+
586592
## date tickers and formatters ###
587593

588594

@@ -597,22 +603,28 @@ class DateFormatter(ticker.Formatter):
597603
def illegal_s(self):
598604
return re.compile(r"((^|[^%])(%%)*%s)")
599605

600-
def __init__(self, fmt, tz=None):
606+
def __init__(self, fmt, tz=None, *, usetex=None):
601607
"""
602608
Parameters
603609
----------
604610
fmt : str
605611
`~datetime.datetime.strftime` format string
606612
tz : `datetime.tzinfo`, default: :rc:`timezone`
607613
Ticks timezone.
614+
usetex : bool, default: :rc:`text.usetex`
615+
To enable/disable the use of TeX's math mode for rendering the
616+
results of the formatter.
608617
"""
609618
if tz is None:
610619
tz = _get_rc_timezone()
611620
self.fmt = fmt
612621
self.tz = tz
622+
self._usetex = (usetex if usetex is not None else
623+
mpl.rcParams['text.usetex'])
613624

614625
def __call__(self, x, pos=0):
615-
return num2date(x, self.tz).strftime(self.fmt)
626+
result = num2date(x, self.tz).strftime(self.fmt)
627+
return _wrap_in_tex(result) if self._usetex else result
616628

617629
def set_tzinfo(self, tz):
618630
self.tz = tz
@@ -685,6 +697,10 @@ class ConciseDateFormatter(ticker.Formatter):
685697
show_offset : bool, default: True
686698
Whether to show the offset or not.
687699
700+
usetex : bool, default: :rc:`text.usetex`
701+
To enable/disable the use of TeX's math mode for rendering the results
702+
of the formatter.
703+
688704
Examples
689705
--------
690706
See :doc:`/gallery/ticks_and_spines/date_concise_formatter`
@@ -713,7 +729,7 @@ class ConciseDateFormatter(ticker.Formatter):
713729
"""
714730

715731
def __init__(self, locator, tz=None, formats=None, offset_formats=None,
716-
zero_formats=None, show_offset=True):
732+
zero_formats=None, show_offset=True, *, usetex=True):
717733
"""
718734
Autoformat the date labels. The default format is used to form an
719735
initial string, and then redundant elements are removed.
@@ -768,9 +784,12 @@ def __init__(self, locator, tz=None, formats=None, offset_formats=None,
768784
'%Y-%b-%d %H:%M']
769785
self.offset_string = ''
770786
self.show_offset = show_offset
787+
self._usetex = (usetex if usetex is not None else
788+
mpl.rcParams['text.usetex'])
771789

772790
def __call__(self, x, pos=None):
773-
formatter = DateFormatter(self.defaultfmt, self._tz)
791+
formatter = DateFormatter(self.defaultfmt, self._tz,
792+
usetex=self._usetex)
774793
return formatter(x, pos=pos)
775794

776795
def format_ticks(self, values):
@@ -837,8 +856,13 @@ def format_ticks(self, values):
837856
if self.show_offset:
838857
# set the offset string:
839858
self.offset_string = tickdatetime[-1].strftime(offsetfmts[level])
859+
if self._usetex:
860+
self.offset_string = _wrap_in_tex(self.offset_string)
840861

841-
return labels
862+
if self._usetex:
863+
return [_wrap_in_tex(l) for l in labels]
864+
else:
865+
return labels
842866

843867
def get_offset(self):
844868
return self.offset_string
@@ -903,7 +927,8 @@ class AutoDateFormatter(ticker.Formatter):
903927
# Or more simply, perhaps just a format string for each
904928
# possibility...
905929

906-
def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
930+
def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d', *,
931+
usetex=None):
907932
"""
908933
Autoformat the date labels.
909934
@@ -918,12 +943,20 @@ def __init__(self, locator, tz=None, defaultfmt='%Y-%m-%d'):
918943
defaultfmt : str
919944
The default format to use if none of the values in ``self.scaled``
920945
are greater than the unit returned by ``locator._get_unit()``.
946+
947+
usetex : bool, default: :rc:`text.usetex`
948+
To enable/disable the use of TeX's math mode for rendering the
949+
results of the formatter. If any entries in ``self.scaled`` are set
950+
as functions, then it is up to the customized function to enable or
951+
disable TeX's math mode itself.
921952
"""
922953
self._locator = locator
923954
self._tz = tz
924955
self.defaultfmt = defaultfmt
925956
self._formatter = DateFormatter(self.defaultfmt, tz)
926957
rcParams = mpl.rcParams
958+
self._usetex = (usetex if usetex is not None else
959+
mpl.rcParams['text.usetex'])
927960
self.scaled = {
928961
DAYS_PER_YEAR: rcParams['date.autoformatter.year'],
929962
DAYS_PER_MONTH: rcParams['date.autoformatter.month'],
@@ -948,7 +981,7 @@ def __call__(self, x, pos=None):
948981
self.defaultfmt)
949982

950983
if isinstance(fmt, str):
951-
self._formatter = DateFormatter(fmt, self._tz)
984+
self._formatter = DateFormatter(fmt, self._tz, usetex=self._usetex)
952985
result = self._formatter(x, pos)
953986
elif callable(fmt):
954987
result = fmt(x, pos)

lib/matplotlib/tests/test_dates.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -266,6 +266,26 @@ def callable_formatting_function(dates, _):
266266
assert formatter([datetime.datetime(2014, 12, 25)]) == ['25-12//2014']
267267

268268

269+
@pytest.mark.parametrize('delta, expected', [
270+
(datetime.timedelta(weeks=52 * 200),
271+
[r'$\mathdefault{%d}$' % (year,) for year in range(1990, 2171, 20)]),
272+
(datetime.timedelta(days=30),
273+
[r'$\mathdefault{Jan %02d 1990}$' % (day,) for day in range(1, 32, 3)]),
274+
(datetime.timedelta(hours=20),
275+
[r'$\mathdefault{%02d:00:00}$' % (hour,) for hour in range(0, 21, 2)]),
276+
])
277+
def test_date_formatter_usetex(delta, expected):
278+
d1 = datetime.datetime(1990, 1, 1)
279+
d2 = d1 + delta
280+
281+
locator = mdates.AutoDateLocator(interval_multiples=False)
282+
locator.create_dummy_axis()
283+
locator.set_view_interval(mdates.date2num(d1), mdates.date2num(d2))
284+
285+
formatter = mdates.AutoDateFormatter(locator, usetex=True)
286+
assert [formatter(loc) for loc in locator()] == expected
287+
288+
269289
def test_drange():
270290
"""
271291
This test should check if drange works as expected, and if all the
@@ -503,6 +523,39 @@ def _create_auto_date_locator(date1, date2):
503523
assert strings == expected
504524

505525

526+
@pytest.mark.parametrize('t_delta, expected', [
527+
(datetime.timedelta(weeks=52 * 200),
528+
['$\\mathdefault{%d}$' % (t, ) for t in range(1980, 2201, 20)]),
529+
(datetime.timedelta(days=40),
530+
['$\\mathdefault{Jan}$', '$\\mathdefault{05}$', '$\\mathdefault{09}$',
531+
'$\\mathdefault{13}$', '$\\mathdefault{17}$', '$\\mathdefault{21}$',
532+
'$\\mathdefault{25}$', '$\\mathdefault{29}$', '$\\mathdefault{Feb}$',
533+
'$\\mathdefault{05}$', '$\\mathdefault{09}$']),
534+
(datetime.timedelta(hours=40),
535+
['$\\mathdefault{Jan{-}01}$', '$\\mathdefault{04:00}$',
536+
'$\\mathdefault{08:00}$', '$\\mathdefault{12:00}$',
537+
'$\\mathdefault{16:00}$', '$\\mathdefault{20:00}$',
538+
'$\\mathdefault{Jan{-}02}$', '$\\mathdefault{04:00}$',
539+
'$\\mathdefault{08:00}$', '$\\mathdefault{12:00}$',
540+
'$\\mathdefault{16:00}$']),
541+
(datetime.timedelta(seconds=2),
542+
['$\\mathdefault{59.5}$', '$\\mathdefault{00:00}$',
543+
'$\\mathdefault{00.5}$', '$\\mathdefault{01.0}$',
544+
'$\\mathdefault{01.5}$', '$\\mathdefault{02.0}$',
545+
'$\\mathdefault{02.5}$']),
546+
])
547+
def test_concise_formatter_usetex(t_delta, expected):
548+
d1 = datetime.datetime(1997, 1, 1)
549+
d2 = d1 + t_delta
550+
551+
locator = mdates.AutoDateLocator(interval_multiples=True)
552+
locator.create_dummy_axis()
553+
locator.set_view_interval(mdates.date2num(d1), mdates.date2num(d2))
554+
555+
formatter = mdates.ConciseDateFormatter(locator, usetex=True)
556+
assert formatter.format_ticks(locator()) == expected
557+
558+
506559
def test_concise_formatter_formats():
507560
formats = ['%Y', '%m/%Y', 'day: %d',
508561
'%H hr %M min', '%H hr %M min', '%S.%f sec']

0 commit comments

Comments
 (0)