@@ -407,6 +407,11 @@ class ScalarFormatter(Formatter):
407407 useLocale : bool, default: :rc:`axes.formatter.use_locale`.
408408 Whether to use locale settings for decimal sign and positive sign.
409409 See `.set_useLocale`.
410+ usetex : bool, default: :rc:`text.usetex`
411+ To enable/disable the use of TeX's math mode for rendering the
412+ numbers in the formatter.
413+
414+ .. versionadded:: 3.10
410415
411416 Notes
412417 -----
@@ -444,20 +449,29 @@ class ScalarFormatter(Formatter):
444449
445450 """
446451
447- def __init__ (self , useOffset = None , useMathText = None , useLocale = None ):
452+ def __init__ (self , useOffset = None , useMathText = None , useLocale = None , * ,
453+ usetex = None ):
448454 if useOffset is None :
449455 useOffset = mpl .rcParams ['axes.formatter.useoffset' ]
450456 self ._offset_threshold = \
451457 mpl .rcParams ['axes.formatter.offset_threshold' ]
452458 self .set_useOffset (useOffset )
453- self ._usetex = mpl . rcParams [ 'text. usetex' ]
459+ self .set_usetex ( usetex )
454460 self .set_useMathText (useMathText )
455461 self .orderOfMagnitude = 0
456462 self .format = ''
457463 self ._scientific = True
458464 self ._powerlimits = mpl .rcParams ['axes.formatter.limits' ]
459465 self .set_useLocale (useLocale )
460466
467+ def get_usetex (self ):
468+ return self ._usetex
469+
470+ def set_usetex (self , val ):
471+ self ._usetex = mpl ._val_or_rc (val , 'text.usetex' )
472+
473+ usetex = property (fget = get_usetex , fset = set_usetex )
474+
461475 def get_useOffset (self ):
462476 """
463477 Return whether automatic mode for offset notation is active.
@@ -1324,7 +1338,7 @@ def format_data_short(self, value):
13241338 return f"1-{ 1 - value :e} "
13251339
13261340
1327- class EngFormatter (Formatter ):
1341+ class EngFormatter (ScalarFormatter ):
13281342 """
13291343 Format axis values using engineering prefixes to represent powers
13301344 of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7.
@@ -1356,7 +1370,7 @@ class EngFormatter(Formatter):
13561370 }
13571371
13581372 def __init__ (self , unit = "" , places = None , sep = " " , * , usetex = None ,
1359- useMathText = None ):
1373+ useMathText = None , useOffset = False ):
13601374 r"""
13611375 Parameters
13621376 ----------
@@ -1390,76 +1404,124 @@ def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
13901404 useMathText : bool, default: :rc:`axes.formatter.use_mathtext`
13911405 To enable/disable the use mathtext for rendering the numbers in
13921406 the formatter.
1407+ useOffset : bool or float, default: False
1408+ Whether to use offset notation with :math:`10^{3*N}` based prefixes.
1409+ This features allows showing an offset with standard SI order of
1410+ magnitude prefix near the axis. Offset is computed similarly to
1411+ how `ScalarFormatter` computes it internally, but here you are
1412+ guaranteed to get an offset which will make the tick labels exceed
1413+ 3 digits. See also `.set_useOffset`.
1414+
1415+ .. versionadded:: 3.10
13931416 """
13941417 self .unit = unit
13951418 self .places = places
13961419 self .sep = sep
1397- self .set_usetex (usetex )
1398- self .set_useMathText (useMathText )
1399-
1400- def get_usetex (self ):
1401- return self ._usetex
1402-
1403- def set_usetex (self , val ):
1404- if val is None :
1405- self ._usetex = mpl .rcParams ['text.usetex' ]
1406- else :
1407- self ._usetex = val
1408-
1409- usetex = property (fget = get_usetex , fset = set_usetex )
1420+ super ().__init__ (
1421+ useOffset = useOffset ,
1422+ useMathText = useMathText ,
1423+ useLocale = False ,
1424+ usetex = usetex ,
1425+ )
14101426
1411- def get_useMathText (self ):
1412- return self ._useMathText
1427+ def __call__ (self , x , pos = None ):
1428+ """
1429+ Return the format for tick value *x* at position *pos*.
14131430
1414- def set_useMathText (self , val ):
1415- if val is None :
1416- self ._useMathText = mpl .rcParams ['axes.formatter.use_mathtext' ]
1431+ If there is no currently offset in the data, it returns the best
1432+ engineering formatting that fits the given argument, independently.
1433+ """
1434+ if len (self .locs ) == 0 or self .offset == 0 :
1435+ return self .fix_minus (self .format_data (x ))
14171436 else :
1418- self ._useMathText = val
1437+ xp = (x - self .offset ) / (10. ** self .orderOfMagnitude )
1438+ if abs (xp ) < 1e-8 :
1439+ xp = 0
1440+ return self ._format_maybe_minus_and_locale (self .format , xp )
14191441
1420- useMathText = property (fget = get_useMathText , fset = set_useMathText )
1442+ def set_locs (self , locs ):
1443+ # docstring inherited
1444+ self .locs = locs
1445+ if len (self .locs ) > 0 :
1446+ vmin , vmax = sorted (self .axis .get_view_interval ())
1447+ if self ._useOffset :
1448+ self ._compute_offset ()
1449+ if self .offset != 0 :
1450+ # We don't want to use the offset computed by
1451+ # self._compute_offset because it rounds the offset unaware
1452+ # of our engineering prefixes preference, and this can
1453+ # cause ticks with 4+ digits to appear. These ticks are
1454+ # slightly less readable, so if offset is justified
1455+ # (decided by self._compute_offset) we set it to better
1456+ # value:
1457+ self .offset = round ((vmin + vmax )/ 2 , 3 )
1458+ # Use log1000 to use engineers' oom standards
1459+ self .orderOfMagnitude = math .floor (math .log (vmax - vmin , 1000 ))* 3
1460+ self ._set_format ()
14211461
1422- def __call__ (self , x , pos = None ):
1423- s = f"{ self .format_eng (x )} { self .unit } "
1424- # Remove the trailing separator when there is neither prefix nor unit
1425- if self .sep and s .endswith (self .sep ):
1426- s = s [:- len (self .sep )]
1427- return self .fix_minus (s )
1462+ # Simplify a bit ScalarFormatter.get_offset: We always want to use
1463+ # self.format_data. Also we want to return a non-empty string only if there
1464+ # is an offset, no matter what is self.orderOfMagnitude. If there _is_ an
1465+ # offset, self.orderOfMagnitude is consulted. This behavior is verified
1466+ # in `test_ticker.py`.
1467+ def get_offset (self ):
1468+ # docstring inherited
1469+ if len (self .locs ) == 0 :
1470+ return ''
1471+ if self .offset :
1472+ offsetStr = ''
1473+ if self .offset :
1474+ offsetStr = self .format_data (self .offset )
1475+ if self .offset > 0 :
1476+ offsetStr = '+' + offsetStr
1477+ sciNotStr = self .format_data (10 ** self .orderOfMagnitude )
1478+ if self ._useMathText or self ._usetex :
1479+ if sciNotStr != '' :
1480+ sciNotStr = r'\times%s' % sciNotStr
1481+ s = f'${ sciNotStr } { offsetStr } $'
1482+ else :
1483+ s = sciNotStr + offsetStr
1484+ return self .fix_minus (s )
1485+ return ''
14281486
14291487 def format_eng (self , num ):
1488+ """Alias to EngFormatter.format_data"""
1489+ return self .format_data (num )
1490+
1491+ def format_data (self , value ):
14301492 """
14311493 Format a number in engineering notation, appending a letter
14321494 representing the power of 1000 of the original number.
14331495 Some examples:
14341496
1435- >>> format_eng (0) # for self.places = 0
1497+ >>> format_data (0) # for self.places = 0
14361498 '0'
14371499
1438- >>> format_eng (1000000) # for self.places = 1
1500+ >>> format_data (1000000) # for self.places = 1
14391501 '1.0 M'
14401502
1441- >>> format_eng (-1e-6) # for self.places = 2
1503+ >>> format_data (-1e-6) # for self.places = 2
14421504 '-1.00 \N{MICRO SIGN} '
14431505 """
14441506 sign = 1
14451507 fmt = "g" if self .places is None else f".{ self .places :d} f"
14461508
1447- if num < 0 :
1509+ if value < 0 :
14481510 sign = - 1
1449- num = - num
1511+ value = - value
14501512
1451- if num != 0 :
1452- pow10 = int (math .floor (math .log10 (num ) / 3 ) * 3 )
1513+ if value != 0 :
1514+ pow10 = int (math .floor (math .log10 (value ) / 3 ) * 3 )
14531515 else :
14541516 pow10 = 0
1455- # Force num to zero, to avoid inconsistencies like
1517+ # Force value to zero, to avoid inconsistencies like
14561518 # format_eng(-0) = "0" and format_eng(0.0) = "0"
14571519 # but format_eng(-0.0) = "-0.0"
1458- num = 0.0
1520+ value = 0.0
14591521
14601522 pow10 = np .clip (pow10 , min (self .ENG_PREFIXES ), max (self .ENG_PREFIXES ))
14611523
1462- mant = sign * num / (10.0 ** pow10 )
1524+ mant = sign * value / (10.0 ** pow10 )
14631525 # Taking care of the cases like 999.9..., which may be rounded to 1000
14641526 # instead of 1 k. Beware of the corner case of values that are beyond
14651527 # the range of SI prefixes (i.e. > 'Y').
@@ -1468,13 +1530,15 @@ def format_eng(self, num):
14681530 mant /= 1000
14691531 pow10 += 3
14701532
1471- prefix = self .ENG_PREFIXES [int (pow10 )]
1533+ unit_prefix = self .ENG_PREFIXES [int (pow10 )]
1534+ if self .unit or unit_prefix :
1535+ suffix = f"{ self .sep } { unit_prefix } { self .unit } "
1536+ else :
1537+ suffix = ""
14721538 if self ._usetex or self ._useMathText :
1473- formatted = f"${ mant :{fmt }} ${ self . sep } { prefix } "
1539+ return f"${ mant :{fmt }} ${ suffix } "
14741540 else :
1475- formatted = f"{ mant :{fmt }} { self .sep } { prefix } "
1476-
1477- return formatted
1541+ return f"{ mant :{fmt }} { suffix } "
14781542
14791543
14801544class PercentFormatter (Formatter ):
0 commit comments