Skip to content
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
9 changes: 9 additions & 0 deletions doc/api/next_api_changes/behavior/32281-CAZM.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
``EngFormatter.format_data`` honors ``axes.unicode_minus``
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

`.EngFormatter.format_data` and `.EngFormatter.format_eng` now honor
:rc:`axes.unicode_minus`, like `.ScalarFormatter.format_data`. With the default
rcParams, negative values are returned with a Unicode minus sign (U+2212)
instead of an ASCII hyphen (U+002D), as are negative exponents of values beyond
the range of SI prefixes. Tick labels and the offset text already used
the Unicode minus, so plots are unchanged.
17 changes: 17 additions & 0 deletions doc/release/next_whats_new/engformatter_locale.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
``EngFormatter`` now respects ``axes.formatter.use_locale``
-----------------------------------------------------------

`~matplotlib.ticker.EngFormatter` now formats the decimal separator according to
the current locale, like `~matplotlib.ticker.ScalarFormatter` already did. This
is controlled by the :rc:`axes.formatter.use_locale` rcParam, or by the new
*useLocale* keyword argument:

.. code-block:: python

import locale
from matplotlib.ticker import EngFormatter

locale.setlocale(locale.LC_ALL, "de_DE.UTF-8")

ax.yaxis.set_major_formatter(EngFormatter(places=2, useLocale=True))
# tick labels now read "555,10 m" instead of "555.10 m"
40 changes: 40 additions & 0 deletions lib/matplotlib/tests/test_ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1571,6 +1571,7 @@ def test_params(self, unicode_minus, input, expected):
)
for _formatter, _exp_output in zip(formatters, exp_outputs):
assert _formatter(input) == _exp_output
assert _formatter.format_data(input) == _exp_output

# Case 1: unit=UNIT and sep=' ' (default).
# Append a unit symbol to the reference case.
Expand Down Expand Up @@ -1810,6 +1811,45 @@ def test_locale_comma():
pytest.skip(skip_msg)


def _impl_engformatter_locale():
try:
locale.setlocale(locale.LC_ALL, 'de_DE.UTF-8')
except locale.Error:
print('SKIP: Locale de_DE.UTF-8 is not supported on this machine')
return
# EngFormatter is a ScalarFormatter subclass, so it must honour the
# axes.formatter.use_locale rcParam like its parent does.
with mpl.rc_context(rc={'axes.formatter.use_locale': True}):
fmt = mticker.EngFormatter(places=2)
assert fmt.get_useLocale()
assert fmt.format_data(0.5551) == '555,10 m'
# An explicit keyword argument overrides the rcParam.
fmt = mticker.EngFormatter(places=2, useLocale=False)
assert not fmt.get_useLocale()
assert fmt.format_data(1234.5) == '1.23 k'
fmt = mticker.EngFormatter(places=2, useLocale=True)
assert fmt.get_useLocale()
assert fmt.format_data(1234.5) == '1,23 k'
# The inherited setter also reaches the engineering formatting path.
fmt = mticker.EngFormatter(places=2, useLocale=False)
fmt.set_useLocale(True)
assert fmt.format_data(1234.5) == '1,23 k'
# Separators are escaped for mathtext, as ScalarFormatter does.
fmt = mticker.EngFormatter(places=2, useLocale=True, useMathText=True)
assert fmt.format_data(0.5551) == '$555{,}10$ m'


def test_engformatter_locale():
proc = mpl.testing.subprocess_run_helper(
_impl_engformatter_locale, timeout=60, extra_env={'MPLBACKEND': 'Agg'})
skip_msg = next((line[len('SKIP:'):].strip()
for line in proc.stdout.splitlines()
if line.startswith('SKIP:')),
'')
if skip_msg:
pytest.skip(skip_msg)


def test_majformatter_type():
fig, ax = plt.subplots()
with pytest.raises(TypeError):
Expand Down
20 changes: 15 additions & 5 deletions lib/matplotlib/ticker.py
Original file line number Diff line number Diff line change
Expand Up @@ -1433,7 +1433,7 @@ class EngFormatter(ScalarFormatter):
}

def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
useMathText=None, useOffset=False):
useMathText=None, useOffset=False, useLocale=None):
r"""
Parameters
----------
Expand Down Expand Up @@ -1476,14 +1476,20 @@ def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
3 digits. See also `.set_useOffset`.

.. versionadded:: 3.10

useLocale : bool, default: :rc:`axes.formatter.use_locale`.
Whether to use locale settings for decimal sign and positive sign.
See `.set_useLocale`.

.. versionadded:: 3.12
"""
self.unit = unit
self.places = places
self.sep = sep
super().__init__(
useOffset=useOffset,
useMathText=useMathText,
useLocale=False,
useLocale=useLocale,
usetex=usetex,
)

Expand Down Expand Up @@ -1564,7 +1570,10 @@ def format_data(self, value):
'1.0 M'

>>> format_data(-1e-6) # for self.places = 2
'-1.00 \N{MICRO SIGN}'
'\N{MINUS SIGN}1.00 \N{MICRO SIGN}'

.. versionchanged:: 3.12
The result now honors :rc:`axes.unicode_minus`.
"""
sign = 1
fmt = "g" if self.places is None else f".{self.places:d}f"
Expand Down Expand Up @@ -1598,10 +1607,11 @@ def format_data(self, value):
suffix = f"{self.sep}{unit_prefix}{self.unit}"
else:
suffix = ""
mant_str = self._format_maybe_minus_and_locale(f"%{fmt}", mant)
if self._usetex or self._useMathText:
return f"${mant:{fmt}}${suffix}"
return f"${mant_str}${suffix}"
else:
return f"{mant:{fmt}}{suffix}"
return f"{mant_str}{suffix}"


class PercentFormatter(Formatter):
Expand Down
1 change: 1 addition & 0 deletions lib/matplotlib/ticker.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,7 @@ class EngFormatter(ScalarFormatter):
usetex: bool | None = ...,
useMathText: bool | None = ...,
useOffset: bool | float | None = ...,
useLocale: bool | None = ...,
) -> None: ...
def format_eng(self, num: float) -> str: ...

Expand Down
Loading