Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork7.9k
Description
Discussing with some of my colleagues who are using other plotting softwares than Matplotlib, I noticed that their formatting of the engineering notation is a bit different from the one implemented inticker.EngFormatter
. Some softwares append directly the prefix/unit to the tick value (e.g. "1.23µs", vs "1.23 µs" with Matplotlib). I am not sure it is really OK from the (English) typography point of view but I guess they are doing this due to the rather limited ticklablel space.
I wonder how interesting it may be to add such a possibility to theEngFormatter
in Matplotlib. As some users may prefer "1.23µs" over "1.23 µs", I would say it's worth adding it toEngFormatter
.
If it is added toEngFormatter
, I guess the major pitfall would be the default values… IMO, the current behavior of Matplotlib is the best one whenEngFormatter
is instantiated with a unit. However, when it is instantatied without unit (unit=""
), I wouldn't be categorical about the fact that "1.23 µ" is better than "1.23µ". So I don't really know if one should use by default a space separator between the value and the prefix/unit, or not…
I wrote a small demonstration of what could be easily done with theEngFormatter
class (keeping the current Matplotlib behavior as the default one). It is > 100 lines because I directly copy-pasted the source code ofticker.EngFormatter
. I've put the changes between<ENH>
and<\ENH>
tags. NB: the code includes a bug fix similar to PR#6014 .
from __future__importdivision,print_function,unicode_literalsimportdecimalimportmathimportnumpyasnpfrommatplotlib.tickerimportFormatter# Proposed "enhancement" of the EngFormatterclassEnhancedEngFormatter(Formatter):""" Formats axis values using engineering prefixes to represent powers of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7. """# the unicode for -6 is the greek letter mu# commeted here due to bug in pep8# (https://github.com/jcrocholl/pep8/issues/271)# The SI engineering prefixesENG_PREFIXES= {-24:"y",-21:"z",-18:"a",-15:"f",-12:"p",-9:"n",-6:"\u03bc",-3:"m",0:"",3:"k",6:"M",9:"G",12:"T",15:"P",18:"E",21:"Z",24:"Y" }def__init__(self,unit="",places=None,space_sep=True):""" Parameters ---------- unit: str (default: "") Unit symbol to use. places: int (default: None) Number of digits after the decimal point. If it is None, falls back to the floating point format '%g'. space_sep: boolean (default: True) If True, a (single) space is used between the value and the prefix/unit, else the prefix/unit is directly appended to the value. """self.unit=unitself.places=places# <ENH>ifspace_sepisTrue:self.sep=" "# 1 spaceelse:self.sep=""# no space# <\ENH>def__call__(self,x,pos=None):s="%s%s"% (self.format_eng(x),self.unit)returnself.fix_minus(s)defformat_eng(self,num):""" Formats a number in engineering notation, appending a letter representing the power of 1000 of the original number. Some examples: >>> format_eng(0) # for self.places = 0 '0' >>> format_eng(1000000) # for self.places = 1 '1.0 M' >>> format_eng("-1e-6") # for self.places = 2 u'-1.00\u03bc' @param num: the value to represent @type num: either a numeric value or a string that can be converted to a numeric value (as per decimal.Decimal constructor) @return: engineering formatted string """dnum=decimal.Decimal(str(num))sign=1ifdnum<0:sign=-1dnum=-dnumifdnum!=0:pow10=decimal.Decimal(int(math.floor(dnum.log10()/3)*3))else:pow10=decimal.Decimal(0)pow10=pow10.min(max(self.ENG_PREFIXES.keys()))pow10=pow10.max(min(self.ENG_PREFIXES.keys()))prefix=self.ENG_PREFIXES[int(pow10)]mant=sign*dnum/ (10**pow10)# <ENH>ifself.placesisNone:format_str="%g{sep:s}%s".format(sep=self.sep)elifself.places==0:format_str="%i{sep:s}%s".format(sep=self.sep)elifself.places>0:format_str="%.{p:i}f{sep:s}%s".format(p=self.places,sep=self.sep)# <\ENH>formatted=format_str% (mant,prefix)formatted=formatted.strip()if (self.unit!="")and (prefix==self.ENG_PREFIXES[0]):# <ENH>formatted=formatted+self.sep# <\ENH>returnformatted# DEMOdefdemo_formatter(**kwargs):""" Print the strings produced by the EnhancedEngFormatter for a list of arbitrary test values. """TEST_VALUES= [1.23456789e-6,0.1,1,999.9,1001]unit=kwargs.get('unit',"")space_sep=kwargs.get('space_sep',True)formatter=EnhancedEngFormatter(**kwargs)print("\n[Case: unit='{u:s}' & space_sep={s}]".format(u=unit,s=space_sep))print(*["{tst};".format(tst=formatter(value))forvalueinTEST_VALUES])if__name__=='__main__':""" Matplotlib current behavior (w/ space separator) """demo_formatter(unit="s",space_sep=True)# >> 1.23457 μs; 100 ms; 1 s; 999.9 s; 1.001 ks;demo_formatter(unit="",space_sep=True)# >> 1.23457 μ; 100 m; 1; 999.9; 1.001 k;""" New possibility (w/o space separator) """demo_formatter(unit="s",space_sep=False)# >> 1.23457μs; 100ms; 1s; 999.9s; 1.001ks;demo_formatter(unit="",space_sep=False)# >> 1.23457μ; 100m; 1; 999.9; 1.001k;