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

Skip to content

Commit ffe8914

Browse files
committed
ticker.EngFormatter: base upon ScalarFormatter
Allows us to use many order of magnitude and offset related routines from ScalarFormatter, and removes a bit usetex related duplicated code. Solves #28463.
1 parent 9c1e7b1 commit ffe8914

File tree

4 files changed

+101
-55
lines changed

4 files changed

+101
-55
lines changed
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
ticker.EngFormatter now computes offset by default
2+
--------------------------------------------------
3+
4+
``ticker.EngFormatter`` was modified to act very similar to
5+
``ticker.ScalarFormatter``, such that it computes the best offset of the axis
6+
data, and shows the offset with the known SI quantity prefixes. To disable this
7+
new behavior, simply pass ``useOffset=False`` when you instantiate it. If offsets
8+
are disabled, or if there is no particular offset that fits your axis data, the
9+
formatter will reside to the old behavior.

lib/matplotlib/tests/test_ticker.py

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1594,6 +1594,18 @@ def test_engformatter_usetex_useMathText():
15941594
assert x_tick_label_text == ['$0$', '$500$', '$1$ k']
15951595

15961596

1597+
def test_engformatter_useOffset():
1598+
fig, ax = plt.subplots()
1599+
offset = int(1e7)
1600+
ydata = range(offset, offset+5)
1601+
ax.plot(ydata)
1602+
ax.set_yticks(ydata)
1603+
ax.yaxis.set_major_formatter(mticker.EngFormatter(useOffset=True, unit="Hz"))
1604+
fig.canvas.draw()
1605+
y_tick_label_text = [labl.get_text() for labl in ax.get_yticklabels()]
1606+
assert y_tick_label_text == (np.array(ydata)-offset).astype(str).tolist()
1607+
1608+
15971609
class TestPercentFormatter:
15981610
percent_data = [
15991611
# Check explicitly set decimals over different intervals and values

lib/matplotlib/ticker.py

Lines changed: 77 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -1338,7 +1338,7 @@ def format_data_short(self, value):
13381338
return f"1-{1 - value:e}"
13391339

13401340

1341-
class EngFormatter(Formatter):
1341+
class EngFormatter(ScalarFormatter):
13421342
"""
13431343
Format axis values using engineering prefixes to represent powers
13441344
of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7.
@@ -1370,7 +1370,7 @@ class EngFormatter(Formatter):
13701370
}
13711371

13721372
def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
1373-
useMathText=None):
1373+
useMathText=None, useOffset=None):
13741374
r"""
13751375
Parameters
13761376
----------
@@ -1404,55 +1404,93 @@ def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
14041404
useMathText : bool, default: :rc:`axes.formatter.use_mathtext`
14051405
To enable/disable the use mathtext for rendering the numbers in
14061406
the formatter.
1407+
useOffset : bool or float, default: :rc:`axes.formatter.useoffset`
1408+
Whether to use offset notation. See `.set_useOffset`.
14071409
"""
14081410
self.unit = unit
14091411
self.places = places
14101412
self.sep = sep
1411-
self.set_usetex(usetex)
1412-
self.set_useMathText(useMathText)
1413-
1414-
def get_usetex(self):
1415-
return self._usetex
1416-
1417-
def set_usetex(self, val):
1418-
if val is None:
1419-
self._usetex = mpl.rcParams['text.usetex']
1420-
else:
1421-
self._usetex = val
1422-
1423-
usetex = property(fget=get_usetex, fset=set_usetex)
1424-
1425-
def get_useMathText(self):
1426-
return self._useMathText
1413+
super().__init__(
1414+
useOffset=useOffset,
1415+
useMathText=useMathText,
1416+
useLocale=False,
1417+
usetex=usetex,
1418+
)
14271419

1428-
def set_useMathText(self, val):
1429-
if val is None:
1430-
self._useMathText = mpl.rcParams['axes.formatter.use_mathtext']
1420+
def __call__(self, x, pos=None):
1421+
"""
1422+
Return the format for tick value *x* at position *pos*. If there is no
1423+
currently offset in the data, it returns the best engineering formatting
1424+
that fits the given argument, independently.
1425+
"""
1426+
if len(self.locs) == 0 or self.offset == 0:
1427+
return self.fix_minus(self.format_data(x))
14311428
else:
1432-
self._useMathText = val
1429+
xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
1430+
if abs(xp) < 1e-8:
1431+
xp = 0
1432+
return self._format_maybe_minus_and_locale(self.format, xp)
14331433

1434-
useMathText = property(fget=get_useMathText, fset=set_useMathText)
1434+
def set_locs(self, locs):
1435+
# docstring inherited
1436+
self.locs = locs
1437+
if len(self.locs) > 0:
1438+
if self._useOffset:
1439+
self._compute_offset()
1440+
self._set_order_of_magnitude()
1441+
# This is what's different from ScalarFormatter: We search among
1442+
# the engineers' standard orders of magnitudes (0, -3, 3, -6, 6,
1443+
# -9, 9 etc) the oom closest to our self.orderOfMagnitude. Then we
1444+
# set our self.orderOfMagnitude to it.
1445+
c = abs(self.orderOfMagnitude)
1446+
for sciOom in itertools.count(0, 3):
1447+
if c <= sciOom:
1448+
self.orderOfMagnitude = math.copysign(sciOom, self.orderOfMagnitude)
1449+
break
1450+
self._set_format()
14351451

1436-
def __call__(self, x, pos=None):
1437-
s = f"{self.format_eng(x)}{self.unit}"
1438-
# Remove the trailing separator when there is neither prefix nor unit
1439-
if self.sep and s.endswith(self.sep):
1440-
s = s[:-len(self.sep)]
1441-
return self.fix_minus(s)
1452+
# Simplify a bit ScalarFormatter.get_offset: We always want to use
1453+
# self.format_data. We insert here the surrounding $...$ here, if tex /
1454+
# mathtext is set.
1455+
def get_offset(self):
1456+
# docstring inherited
1457+
if len(self.locs) == 0:
1458+
return ''
1459+
if self.orderOfMagnitude or self.offset:
1460+
offsetStr = ''
1461+
sciNotStr = ''
1462+
if self.offset:
1463+
offsetStr = self.format_data(self.offset)
1464+
if self.offset > 0:
1465+
offsetStr = '+' + offsetStr
1466+
if self.orderOfMagnitude:
1467+
sciNotStr = self.format_data(10 ** self.orderOfMagnitude)
1468+
if self._useMathText or self._usetex:
1469+
if sciNotStr != '':
1470+
sciNotStr = r'\times%s' % sciNotStr
1471+
s = fr'${sciNotStr}{offsetStr}$'
1472+
else:
1473+
s = ''.join((sciNotStr, offsetStr))
1474+
return self.fix_minus(s)
1475+
return ''
14421476

14431477
def format_eng(self, num):
1478+
"""Alias to EngFormatter.format_data"""
1479+
return self.format_data(num)
1480+
1481+
def format_data(self, value):
14441482
"""
14451483
Format a number in engineering notation, appending a letter
14461484
representing the power of 1000 of the original number.
14471485
Some examples:
14481486
1449-
>>> format_eng(0) # for self.places = 0
1487+
>>> format_data(0) # for self.places = 0
14501488
'0'
14511489
1452-
>>> format_eng(1000000) # for self.places = 1
1490+
>>> format_data(1000000) # for self.places = 1
14531491
'1.0 M'
14541492
1455-
>>> format_eng(-1e-6) # for self.places = 2
1493+
>>> format_data(-1e-6) # for self.places = 2
14561494
'-1.00 \N{MICRO SIGN}'
14571495
"""
14581496
sign = 1
@@ -1482,13 +1520,15 @@ def format_eng(self, num):
14821520
mant /= 1000
14831521
pow10 += 3
14841522

1485-
prefix = self.ENG_PREFIXES[int(pow10)]
1523+
unitPrefix = self.ENG_PREFIXES[int(pow10)]
1524+
if self.unit or unitPrefix:
1525+
suffix = f"{self.sep}{unitPrefix}{self.unit}"
1526+
else:
1527+
suffix = ""
14861528
if self._usetex or self._useMathText:
1487-
formatted = f"${mant:{fmt}}${self.sep}{prefix}"
1529+
return rf"${mant:{fmt}}${suffix}"
14881530
else:
1489-
formatted = f"{mant:{fmt}}{self.sep}{prefix}"
1490-
1491-
return formatted
1531+
return rf"{mant:{fmt}}{suffix}"
14921532

14931533

14941534
class PercentFormatter(Formatter):

lib/matplotlib/ticker.pyi

Lines changed: 3 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -67,10 +67,6 @@ class ScalarFormatter(Formatter):
6767
usetex: bool | None = ...,
6868
) -> None: ...
6969
offset: float
70-
def get_usetex(self) -> bool: ...
71-
def set_usetex(self, val: bool | float) -> None: ...
72-
@property
73-
def usetex(self) -> bool: ...
7470
def get_useOffset(self) -> bool: ...
7571
def set_useOffset(self, val: bool | float) -> None: ...
7672
@property
@@ -130,7 +126,7 @@ class LogitFormatter(Formatter):
130126
def set_minor_number(self, minor_number: int) -> None: ...
131127
def format_data_short(self, value: float) -> str: ...
132128

133-
class EngFormatter(Formatter):
129+
class EngFormatter(ScalarFormatter):
134130
ENG_PREFIXES: dict[int, str]
135131
unit: str
136132
places: int | None
@@ -142,20 +138,9 @@ class EngFormatter(Formatter):
142138
sep: str = ...,
143139
*,
144140
usetex: bool | None = ...,
145-
useMathText: bool | None = ...
141+
useMathText: bool | None = ...,
142+
useOffset: bool | float | None = ...,
146143
) -> None: ...
147-
def get_usetex(self) -> bool: ...
148-
def set_usetex(self, val: bool | None) -> None: ...
149-
@property
150-
def usetex(self) -> bool: ...
151-
@usetex.setter
152-
def usetex(self, val: bool | None) -> None: ...
153-
def get_useMathText(self) -> bool: ...
154-
def set_useMathText(self, val: bool | None) -> None: ...
155-
@property
156-
def useMathText(self) -> bool: ...
157-
@useMathText.setter
158-
def useMathText(self, val: bool | None) -> None: ...
159144
def format_eng(self, num: float) -> str: ...
160145

161146
class PercentFormatter(Formatter):

0 commit comments

Comments
 (0)