@@ -1340,7 +1340,7 @@ def format_data_short(self, value):
1340
1340
return f"1-{ 1 - value :e} "
1341
1341
1342
1342
1343
- class EngFormatter (Formatter ):
1343
+ class EngFormatter (ScalarFormatter ):
1344
1344
"""
1345
1345
Format axis values using engineering prefixes to represent powers
1346
1346
of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7.
@@ -1372,7 +1372,7 @@ class EngFormatter(Formatter):
1372
1372
}
1373
1373
1374
1374
def __init__ (self , unit = "" , places = None , sep = " " , * , usetex = None ,
1375
- useMathText = None ):
1375
+ useMathText = None , useOffset = False ):
1376
1376
r"""
1377
1377
Parameters
1378
1378
----------
@@ -1406,76 +1406,123 @@ def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
1406
1406
useMathText : bool, default: :rc:`axes.formatter.use_mathtext`
1407
1407
To enable/disable the use mathtext for rendering the numbers in
1408
1408
the formatter.
1409
+ useOffset : bool or float, default: False
1410
+ Whether to use offset notation with :math:`10^{3*N}` based prefixes.
1411
+ This features allows showing an offset with standard SI order of
1412
+ magnitude prefix near the axis. Offset is computed similarly to
1413
+ how `ScalarFormatter` computes it internally, but here you are
1414
+ guaranteed to get an offset which will make the tick labels exceed
1415
+ 3 digits. See also `.set_useOffset`.
1416
+
1417
+ .. versionadded:: 3.10
1409
1418
"""
1410
1419
self .unit = unit
1411
1420
self .places = places
1412
1421
self .sep = sep
1413
- self .set_usetex (usetex )
1414
- self .set_useMathText (useMathText )
1415
-
1416
- def get_usetex (self ):
1417
- return self ._usetex
1418
-
1419
- def set_usetex (self , val ):
1420
- if val is None :
1421
- self ._usetex = mpl .rcParams ['text.usetex' ]
1422
- else :
1423
- self ._usetex = val
1424
-
1425
- usetex = property (fget = get_usetex , fset = set_usetex )
1426
-
1427
- def get_useMathText (self ):
1428
- return self ._useMathText
1422
+ super ().__init__ (
1423
+ useOffset = useOffset ,
1424
+ useMathText = useMathText ,
1425
+ useLocale = False ,
1426
+ usetex = usetex ,
1427
+ )
1429
1428
1430
- def set_useMathText (self , val ):
1431
- if val is None :
1432
- self ._useMathText = mpl .rcParams ['axes.formatter.use_mathtext' ]
1429
+ def __call__ (self , x , pos = None ):
1430
+ """
1431
+ Return the format for tick value *x* at position *pos*. If there is no
1432
+ currently offset in the data, it returns the best engineering formatting
1433
+ that fits the given argument, independently.
1434
+ """
1435
+ if len (self .locs ) == 0 or self .offset == 0 :
1436
+ return self .fix_minus (self .format_data (x ))
1433
1437
else :
1434
- self ._useMathText = val
1438
+ xp = (x - self .offset ) / (10. ** self .orderOfMagnitude )
1439
+ if abs (xp ) < 1e-8 :
1440
+ xp = 0
1441
+ return self ._format_maybe_minus_and_locale (self .format , xp )
1435
1442
1436
- useMathText = property (fget = get_useMathText , fset = set_useMathText )
1443
+ def set_locs (self , locs ):
1444
+ # docstring inherited
1445
+ self .locs = locs
1446
+ if len (self .locs ) > 0 :
1447
+ vmin , vmax = sorted (self .axis .get_view_interval ())
1448
+ if self ._useOffset :
1449
+ self ._compute_offset ()
1450
+ if self .offset != 0 :
1451
+ # We don't want to use the offset computed by
1452
+ # self._compute_offset because it rounds the offset unaware
1453
+ # of our engineering prefixes preference, and this can
1454
+ # cause ticks with 4+ digits to appear. These ticks are
1455
+ # slightly less readable, so if offset is justified
1456
+ # (decided by self._compute_offset) we set it to better
1457
+ # value:
1458
+ self .offset = round ((vmin + vmax )/ 2 , 3 )
1459
+ # Use log1000 to use engineers' oom standards
1460
+ self .orderOfMagnitude = math .floor (math .log (vmax - vmin , 1000 ))* 3
1461
+ self ._set_format ()
1437
1462
1438
- def __call__ (self , x , pos = None ):
1439
- s = f"{ self .format_eng (x )} { self .unit } "
1440
- # Remove the trailing separator when there is neither prefix nor unit
1441
- if self .sep and s .endswith (self .sep ):
1442
- s = s [:- len (self .sep )]
1443
- return self .fix_minus (s )
1463
+ # Simplify a bit ScalarFormatter.get_offset: We always want to use
1464
+ # self.format_data. Also we want to return a non-empty string only if there
1465
+ # is an offset, no matter what is self.orderOfMagnitude. if there is an
1466
+ # offset OTH, self.orderOfMagnitude is consulted. This behavior is verified
1467
+ # in `test_ticker.py`.
1468
+ def get_offset (self ):
1469
+ # docstring inherited
1470
+ if len (self .locs ) == 0 :
1471
+ return ''
1472
+ if self .offset :
1473
+ offsetStr = ''
1474
+ if self .offset :
1475
+ offsetStr = self .format_data (self .offset )
1476
+ if self .offset > 0 :
1477
+ offsetStr = '+' + offsetStr
1478
+ sciNotStr = self .format_data (10 ** self .orderOfMagnitude )
1479
+ if self ._useMathText or self ._usetex :
1480
+ if sciNotStr != '' :
1481
+ sciNotStr = r'\times%s' % sciNotStr
1482
+ s = fr'${ sciNotStr } { offsetStr } $'
1483
+ else :
1484
+ s = '' .join ((sciNotStr , offsetStr ))
1485
+ return self .fix_minus (s )
1486
+ return ''
1444
1487
1445
1488
def format_eng (self , num ):
1489
+ """Alias to EngFormatter.format_data"""
1490
+ return self .format_data (num )
1491
+
1492
+ def format_data (self , value ):
1446
1493
"""
1447
1494
Format a number in engineering notation, appending a letter
1448
1495
representing the power of 1000 of the original number.
1449
1496
Some examples:
1450
1497
1451
- >>> format_eng (0) # for self.places = 0
1498
+ >>> format_data (0) # for self.places = 0
1452
1499
'0'
1453
1500
1454
- >>> format_eng (1000000) # for self.places = 1
1501
+ >>> format_data (1000000) # for self.places = 1
1455
1502
'1.0 M'
1456
1503
1457
- >>> format_eng (-1e-6) # for self.places = 2
1504
+ >>> format_data (-1e-6) # for self.places = 2
1458
1505
'-1.00 \N{MICRO SIGN} '
1459
1506
"""
1460
1507
sign = 1
1461
1508
fmt = "g" if self .places is None else f".{ self .places :d} f"
1462
1509
1463
- if num < 0 :
1510
+ if value < 0 :
1464
1511
sign = - 1
1465
- num = - num
1512
+ value = - value
1466
1513
1467
- if num != 0 :
1468
- pow10 = int (math .floor (math .log10 (num ) / 3 ) * 3 )
1514
+ if value != 0 :
1515
+ pow10 = int (math .floor (math .log10 (value ) / 3 ) * 3 )
1469
1516
else :
1470
1517
pow10 = 0
1471
- # Force num to zero, to avoid inconsistencies like
1518
+ # Force value to zero, to avoid inconsistencies like
1472
1519
# format_eng(-0) = "0" and format_eng(0.0) = "0"
1473
1520
# but format_eng(-0.0) = "-0.0"
1474
- num = 0.0
1521
+ value = 0.0
1475
1522
1476
1523
pow10 = np .clip (pow10 , min (self .ENG_PREFIXES ), max (self .ENG_PREFIXES ))
1477
1524
1478
- mant = sign * num / (10.0 ** pow10 )
1525
+ mant = sign * value / (10.0 ** pow10 )
1479
1526
# Taking care of the cases like 999.9..., which may be rounded to 1000
1480
1527
# instead of 1 k. Beware of the corner case of values that are beyond
1481
1528
# the range of SI prefixes (i.e. > 'Y').
@@ -1484,13 +1531,15 @@ def format_eng(self, num):
1484
1531
mant /= 1000
1485
1532
pow10 += 3
1486
1533
1487
- prefix = self .ENG_PREFIXES [int (pow10 )]
1534
+ unitPrefix = self .ENG_PREFIXES [int (pow10 )]
1535
+ if self .unit or unitPrefix :
1536
+ suffix = f"{ self .sep } { unitPrefix } { self .unit } "
1537
+ else :
1538
+ suffix = ""
1488
1539
if self ._usetex or self ._useMathText :
1489
- formatted = f "${ mant :{fmt }} ${ self . sep } { prefix } "
1540
+ return rf "${ mant :{fmt }} ${ suffix } "
1490
1541
else :
1491
- formatted = f"{ mant :{fmt }} { self .sep } { prefix } "
1492
-
1493
- return formatted
1542
+ return rf"{ mant :{fmt }} { suffix } "
1494
1543
1495
1544
1496
1545
class PercentFormatter (Formatter ):
0 commit comments