diff --git a/doc/users/next_whats_new/formatter_unicode_minus.rst b/doc/users/next_whats_new/formatter_unicode_minus.rst new file mode 100644 index 000000000000..1b12b216240e --- /dev/null +++ b/doc/users/next_whats_new/formatter_unicode_minus.rst @@ -0,0 +1,4 @@ +``StrMethodFormatter`` now respects ``axes.unicode_minus`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +When formatting negative values, `.StrMethodFormatter` will now use unicode +minus signs if :rc:`axes.unicode_minus` is set. diff --git a/lib/matplotlib/tests/test_ticker.py b/lib/matplotlib/tests/test_ticker.py index 6d363a48aec8..12eafba9ea2b 100644 --- a/lib/matplotlib/tests/test_ticker.py +++ b/lib/matplotlib/tests/test_ticker.py @@ -1434,14 +1434,21 @@ def test_basic(self): class TestStrMethodFormatter: test_data = [ - ('{x:05d}', (2,), '00002'), - ('{x:03d}-{pos:02d}', (2, 1), '002-01'), + ('{x:05d}', (2,), False, '00002'), + ('{x:05d}', (2,), True, '00002'), + ('{x:05d}', (-2,), False, '-0002'), + ('{x:05d}', (-2,), True, '\N{MINUS SIGN}0002'), + ('{x:03d}-{pos:02d}', (2, 1), False, '002-01'), + ('{x:03d}-{pos:02d}', (2, 1), True, '002-01'), + ('{x:03d}-{pos:02d}', (-2, 1), False, '-02-01'), + ('{x:03d}-{pos:02d}', (-2, 1), True, '\N{MINUS SIGN}02-01'), ] - @pytest.mark.parametrize('format, input, expected', test_data) - def test_basic(self, format, input, expected): - fmt = mticker.StrMethodFormatter(format) - assert fmt(*input) == expected + @pytest.mark.parametrize('format, input, unicode_minus, expected', test_data) + def test_basic(self, format, input, unicode_minus, expected): + with mpl.rc_context({"axes.unicode_minus": unicode_minus}): + fmt = mticker.StrMethodFormatter(format) + assert fmt(*input) == expected class TestEngFormatter: diff --git a/lib/matplotlib/ticker.py b/lib/matplotlib/ticker.py index 50a505da154c..10ac8cad6ebc 100644 --- a/lib/matplotlib/ticker.py +++ b/lib/matplotlib/ticker.py @@ -135,6 +135,7 @@ import locale import math from numbers import Integral +import string import numpy as np @@ -353,6 +354,18 @@ def __call__(self, x, pos=None): return self.fmt % x +class _UnicodeMinusFormat(string.Formatter): + """ + A specialized string formatter so that `.StrMethodFormatter` respects + :rc:`axes.unicode_minus`. This implementation relies on the fact that the + format string is only ever called with kwargs *x* and *pos*, so it blindly + replaces dashes by unicode minuses without further checking. + """ + + def format_field(self, value, format_spec): + return Formatter.fix_minus(super().format_field(value, format_spec)) + + class StrMethodFormatter(Formatter): """ Use a new-style format string (as used by `str.format`) to format the tick. @@ -360,9 +373,8 @@ class StrMethodFormatter(Formatter): The field used for the tick value must be labeled *x* and the field used for the tick position must be labeled *pos*. - Negative numeric values (e.g., -1) will use a dash, not a Unicode minus; - use mathtext to get a Unicode minus by wrapping the format specifier with $ - (e.g. "${x}$"). + The formatter will respect :rc:`axes.unicode_minus` when formatting + negative numeric values. It is typically unnecessary to explicitly construct `.StrMethodFormatter` objects, as `~.Axis.set_major_formatter` directly accepts the format string @@ -379,7 +391,7 @@ def __call__(self, x, pos=None): *x* and *pos* are passed to `str.format` as keyword arguments with those exact names. """ - return self.fmt.format(x=x, pos=pos) + return _UnicodeMinusFormat().format(self.fmt, x=x, pos=pos) class ScalarFormatter(Formatter):