@@ -407,6 +407,11 @@ class ScalarFormatter(Formatter):
407
407
useLocale : bool, default: :rc:`axes.formatter.use_locale`.
408
408
Whether to use locale settings for decimal sign and positive sign.
409
409
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
410
415
411
416
Notes
412
417
-----
@@ -444,20 +449,29 @@ class ScalarFormatter(Formatter):
444
449
445
450
"""
446
451
447
- def __init__ (self , useOffset = None , useMathText = None , useLocale = None ):
452
+ def __init__ (self , useOffset = None , useMathText = None , useLocale = None , * ,
453
+ usetex = None ):
448
454
if useOffset is None :
449
455
useOffset = mpl .rcParams ['axes.formatter.useoffset' ]
450
456
self ._offset_threshold = \
451
457
mpl .rcParams ['axes.formatter.offset_threshold' ]
452
458
self .set_useOffset (useOffset )
453
- self ._usetex = mpl . rcParams [ 'text. usetex' ]
459
+ self .set_usetex ( usetex )
454
460
self .set_useMathText (useMathText )
455
461
self .orderOfMagnitude = 0
456
462
self .format = ''
457
463
self ._scientific = True
458
464
self ._powerlimits = mpl .rcParams ['axes.formatter.limits' ]
459
465
self .set_useLocale (useLocale )
460
466
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
+
461
475
def get_useOffset (self ):
462
476
"""
463
477
Return whether automatic mode for offset notation is active.
@@ -1324,7 +1338,7 @@ def format_data_short(self, value):
1324
1338
return f"1-{ 1 - value :e} "
1325
1339
1326
1340
1327
- class EngFormatter (Formatter ):
1341
+ class EngFormatter (ScalarFormatter ):
1328
1342
"""
1329
1343
Format axis values using engineering prefixes to represent powers
1330
1344
of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7.
@@ -1356,7 +1370,7 @@ class EngFormatter(Formatter):
1356
1370
}
1357
1371
1358
1372
def __init__ (self , unit = "" , places = None , sep = " " , * , usetex = None ,
1359
- useMathText = None ):
1373
+ useMathText = None , useOffset = False ):
1360
1374
r"""
1361
1375
Parameters
1362
1376
----------
@@ -1390,76 +1404,124 @@ def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
1390
1404
useMathText : bool, default: :rc:`axes.formatter.use_mathtext`
1391
1405
To enable/disable the use mathtext for rendering the numbers in
1392
1406
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
1393
1416
"""
1394
1417
self .unit = unit
1395
1418
self .places = places
1396
1419
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
+ )
1410
1426
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*.
1413
1430
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 ))
1417
1436
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 )
1419
1441
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 ()
1421
1461
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 ''
1428
1486
1429
1487
def format_eng (self , num ):
1488
+ """Alias to EngFormatter.format_data"""
1489
+ return self .format_data (num )
1490
+
1491
+ def format_data (self , value ):
1430
1492
"""
1431
1493
Format a number in engineering notation, appending a letter
1432
1494
representing the power of 1000 of the original number.
1433
1495
Some examples:
1434
1496
1435
- >>> format_eng (0) # for self.places = 0
1497
+ >>> format_data (0) # for self.places = 0
1436
1498
'0'
1437
1499
1438
- >>> format_eng (1000000) # for self.places = 1
1500
+ >>> format_data (1000000) # for self.places = 1
1439
1501
'1.0 M'
1440
1502
1441
- >>> format_eng (-1e-6) # for self.places = 2
1503
+ >>> format_data (-1e-6) # for self.places = 2
1442
1504
'-1.00 \N{MICRO SIGN} '
1443
1505
"""
1444
1506
sign = 1
1445
1507
fmt = "g" if self .places is None else f".{ self .places :d} f"
1446
1508
1447
- if num < 0 :
1509
+ if value < 0 :
1448
1510
sign = - 1
1449
- num = - num
1511
+ value = - value
1450
1512
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 )
1453
1515
else :
1454
1516
pow10 = 0
1455
- # Force num to zero, to avoid inconsistencies like
1517
+ # Force value to zero, to avoid inconsistencies like
1456
1518
# format_eng(-0) = "0" and format_eng(0.0) = "0"
1457
1519
# but format_eng(-0.0) = "-0.0"
1458
- num = 0.0
1520
+ value = 0.0
1459
1521
1460
1522
pow10 = np .clip (pow10 , min (self .ENG_PREFIXES ), max (self .ENG_PREFIXES ))
1461
1523
1462
- mant = sign * num / (10.0 ** pow10 )
1524
+ mant = sign * value / (10.0 ** pow10 )
1463
1525
# Taking care of the cases like 999.9..., which may be rounded to 1000
1464
1526
# instead of 1 k. Beware of the corner case of values that are beyond
1465
1527
# the range of SI prefixes (i.e. > 'Y').
@@ -1468,13 +1530,15 @@ def format_eng(self, num):
1468
1530
mant /= 1000
1469
1531
pow10 += 3
1470
1532
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 = ""
1472
1538
if self ._usetex or self ._useMathText :
1473
- formatted = f"${ mant :{fmt }} ${ self . sep } { prefix } "
1539
+ return f"${ mant :{fmt }} ${ suffix } "
1474
1540
else :
1475
- formatted = f"{ mant :{fmt }} { self .sep } { prefix } "
1476
-
1477
- return formatted
1541
+ return f"{ mant :{fmt }} { suffix } "
1478
1542
1479
1543
1480
1544
class PercentFormatter (Formatter ):
0 commit comments