From 3aa65a3bec20b81e8e7dcb22d4594a75bf195fb0 Mon Sep 17 00:00:00 2001 From: Antony Lee Date: Sun, 30 Jan 2022 12:19:57 +0100 Subject: [PATCH] Revert datetime usetex ticklabels to use default tex font. with `{\fontfamily{\familydefault}\selectfont ...}`, instead of using math and selectively escaping parts of the string. Note that this is only possible now that tex strings are passed "all at once" to the tex process rather than "one line at a time", because if breaking at `"\n"` as previously done, then the braces of the tex command above would become unbalanced (and lines other than the first would not see the `\selectfont`). The main difference in rendering is that hyphens (e.g. in YY-MM-DD) are now rendered as plain hyphens rather than minus signs, but some googling suggests that this is in fact correct (see e.g. ctan datetime2 or isodate packages). Also, month names are now rendered with serif, but that seems more consistent with day and years which are also serifed (and which were the original source of all these issues). See also the script below, which reproduces the various issues raised over the years: ```python from datetime import datetime, timedelta from matplotlib.dates import ConciseDateFormatter, DateFormatter import matplotlib.pyplot as plt import numpy as np plt.rcdefaults(); plt.rcParams['text.usetex'] = True fig, axs = plt.subplots(4, constrained_layout=True, figsize=(12, 4)) t0 = datetime.now() ts = [t0 + i * timedelta(days=1) for i in range(10)] axs[0].plot(ts, range(10)) axs[1].plot(ts, range(10)) axs[1].xaxis.set_major_formatter(ConciseDateFormatter(axs[1].xaxis.get_major_locator())) ts = [t0 + i * timedelta(seconds=6) for i in range(100)] axs[2].plot(ts, range(100)) axs[3].xaxis.set_major_formatter(DateFormatter('%d/%m\n%Y')) plt.show() ``` --- lib/matplotlib/dates.py | 12 +-------- lib/matplotlib/tests/test_dates.py | 40 ++++++++++++++---------------- 2 files changed, 20 insertions(+), 32 deletions(-) diff --git a/lib/matplotlib/dates.py b/lib/matplotlib/dates.py index 69a04bb672b1..ad883130067f 100644 --- a/lib/matplotlib/dates.py +++ b/lib/matplotlib/dates.py @@ -173,7 +173,6 @@ import functools import logging import math -import re from dateutil.rrule import (rrule, MO, TU, WE, TH, FR, SA, SU, YEARLY, MONTHLY, WEEKLY, DAILY, HOURLY, MINUTELY, @@ -605,16 +604,7 @@ def drange(dstart, dend, delta): 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 + return r"{\fontfamily{\familydefault}\selectfont " + text + "}" ## date tickers and formatters ### diff --git a/lib/matplotlib/tests/test_dates.py b/lib/matplotlib/tests/test_dates.py index a606b8b0cf47..a78c39c560f7 100644 --- a/lib/matplotlib/tests/test_dates.py +++ b/lib/matplotlib/tests/test_dates.py @@ -322,13 +322,13 @@ def callable_formatting_function(dates, _): @pytest.mark.parametrize('delta, expected', [ (datetime.timedelta(weeks=52 * 200), - [r'$\mathdefault{%d}$' % year for year in range(1990, 2171, 20)]), + range(1990, 2171, 20)), (datetime.timedelta(days=30), - [r'$\mathdefault{1990{-}01{-}%02d}$' % day for day in range(1, 32, 3)]), + ['1990-01-%02d' % day for day in range(1, 32, 3)]), (datetime.timedelta(hours=20), - [r'$\mathdefault{01{-}01\;%02d}$' % hour for hour in range(0, 21, 2)]), + ['01-01 %02d' % hour for hour in range(0, 21, 2)]), (datetime.timedelta(minutes=10), - [r'$\mathdefault{01\;00{:}%02d}$' % minu for minu in range(0, 11)]), + ['01 00:%02d' % minu for minu in range(0, 11)]), ]) def test_date_formatter_usetex(delta, expected): style.use("default") @@ -341,7 +341,8 @@ def test_date_formatter_usetex(delta, expected): locator.axis.set_view_interval(mdates.date2num(d1), mdates.date2num(d2)) formatter = mdates.AutoDateFormatter(locator, usetex=True) - assert [formatter(loc) for loc in locator()] == expected + assert [formatter(loc) for loc in locator()] == [ + r'{\fontfamily{\familydefault}\selectfont %s}' % s for s in expected] def test_drange(): @@ -636,24 +637,14 @@ def test_offset_changes(): @pytest.mark.parametrize('t_delta, expected', [ (datetime.timedelta(weeks=52 * 200), - ['$\\mathdefault{%d}$' % (t, ) for t in range(1980, 2201, 20)]), + range(1980, 2201, 20)), (datetime.timedelta(days=40), - ['Jan', '$\\mathdefault{05}$', '$\\mathdefault{09}$', - '$\\mathdefault{13}$', '$\\mathdefault{17}$', '$\\mathdefault{21}$', - '$\\mathdefault{25}$', '$\\mathdefault{29}$', 'Feb', - '$\\mathdefault{05}$', '$\\mathdefault{09}$']), + ['Jan', '05', '09', '13', '17', '21', '25', '29', 'Feb', '05', '09']), (datetime.timedelta(hours=40), - ['Jan$\\mathdefault{{-}01}$', '$\\mathdefault{04{:}00}$', - '$\\mathdefault{08{:}00}$', '$\\mathdefault{12{:}00}$', - '$\\mathdefault{16{:}00}$', '$\\mathdefault{20{:}00}$', - 'Jan$\\mathdefault{{-}02}$', '$\\mathdefault{04{:}00}$', - '$\\mathdefault{08{:}00}$', '$\\mathdefault{12{:}00}$', - '$\\mathdefault{16{:}00}$']), + ['Jan-01', '04:00', '08:00', '12:00', '16:00', '20:00', + 'Jan-02', '04:00', '08:00', '12:00', '16:00']), (datetime.timedelta(seconds=2), - ['$\\mathdefault{59.5}$', '$\\mathdefault{00{:}00}$', - '$\\mathdefault{00.5}$', '$\\mathdefault{01.0}$', - '$\\mathdefault{01.5}$', '$\\mathdefault{02.0}$', - '$\\mathdefault{02.5}$']), + ['59.5', '00:00', '00.5', '01.0', '01.5', '02.0', '02.5']), ]) def test_concise_formatter_usetex(t_delta, expected): d1 = datetime.datetime(1997, 1, 1) @@ -664,7 +655,8 @@ def test_concise_formatter_usetex(t_delta, expected): locator.axis.set_view_interval(mdates.date2num(d1), mdates.date2num(d2)) formatter = mdates.ConciseDateFormatter(locator, usetex=True) - assert formatter.format_ticks(locator()) == expected + assert formatter.format_ticks(locator()) == [ + r'{\fontfamily{\familydefault}\selectfont %s}' % s for s in expected] def test_concise_formatter_formats(): @@ -1325,3 +1317,9 @@ def test_concise_formatter_call(): def test_date_ticker_factory(span, expected_locator): locator, _ = mdates.date_ticker_factory(span) assert isinstance(locator, expected_locator) + + +def test_usetex_newline(): + fig, ax = plt.subplots() + ax.xaxis.set_major_formatter(mdates.DateFormatter('%d/%m\n%Y')) + fig.canvas.draw()