940 |
| -
| 941 |
| - @return: engineering formatted string |
| 953 | + `num` may be a numeric value or a string that can be converted |
| 954 | + to a numeric value with the `decimal.Decimal` constructor. |
942 | 955 | """
|
943 |
| - |
944 | 956 | dnum = decimal.Decimal(str(num))
|
945 | 957 |
|
946 | 958 | sign = 1
|
@@ -973,6 +985,90 @@ def format_eng(self, num):
|
973 | 985 | return formatted.strip()
|
974 | 986 |
|
975 | 987 |
|
| 988 | +class PercentFormatter(Formatter): |
| 989 | + """ |
| 990 | + Format numbers as a percentage. |
| 991 | +
|
| 992 | + How the number is converted into a percentage is determined by the |
| 993 | + `xmax` parameter. `xmax` is the data value that corresponds to 100%. |
| 994 | + Percentages are computed as ``x / xmax * 100``. So if the data is |
| 995 | + already scaled to be percentages, `xmax` will be 100. Another common |
| 996 | + situation is where `xmax` is 1.0. |
| 997 | +
|
| 998 | + `symbol` is a string which will be appended to the label. It may be |
| 999 | + `None` or empty to indicate that no symbol should be used. |
| 1000 | +
|
| 1001 | + `decimals` is the number of decimal places to place after the point. |
| 1002 | + If it is set to `None` (the default), the number will be computed |
| 1003 | + automatically. |
| 1004 | + """ |
| 1005 | + def __init__(self, xmax=100, decimals=None, symbol='%'): |
| 1006 | + self.xmax = xmax + 0.0 |
| 1007 | + self.decimals = decimals |
| 1008 | + self.symbol = symbol |
| 1009 | + |
| 1010 | + def __call__(self, x, pos=None): |
| 1011 | + """ |
| 1012 | + Formats the tick as a percentage with the appropriate scaling. |
| 1013 | + """ |
| 1014 | + ax_min, ax_max = self.axis.get_view_interval() |
| 1015 | + display_range = abs(ax_max - ax_min) |
| 1016 | + |
| 1017 | + return self.fix_minus(self.format_pct(x, display_range)) |
| 1018 | + |
| 1019 | + def format_pct(self, x, display_range): |
| 1020 | + """ |
| 1021 | + Formats the number as a percentage number with the correct |
| 1022 | + number of decimals and adds the percent symbol, if any. |
| 1023 | +
|
| 1024 | + If `self.decimals` is `None`, the number of digits after the |
| 1025 | + decimal point is set based on the `display_range` of the axis |
| 1026 | + as follows: |
| 1027 | +
|
| 1028 | + +---------------+----------+------------------------+ |
| 1029 | + | display_range | decimals | sample | |
| 1030 | + +---------------+----------+------------------------+ |
| 1031 | + | >50 | 0 | ``x = 34.5`` => 35% | |
| 1032 | + +---------------+----------+------------------------+ |
| 1033 | + | >5 | 1 | ``x = 34.5`` => 34.5% | |
| 1034 | + +---------------+----------+------------------------+ |
| 1035 | + | >0.5 | 2 | ``x = 34.5`` => 34.50% | |
| 1036 | + +---------------+----------+------------------------+ |
| 1037 | + | ... | ... | ... | |
| 1038 | + +---------------+----------+------------------------+ |
| 1039 | +
|
| 1040 | + This method will not be very good for tiny axis ranges or |
| 1041 | + extremely large ones. It assumes that the values on the chart |
| 1042 | + are percentages displayed on a reasonable scale. |
| 1043 | + """ |
| 1044 | + x = self.convert_to_pct(x) |
| 1045 | + if self.decimals is None: |
| 1046 | + # conversion works because display_range is a difference |
| 1047 | + scaled_range = self.convert_to_pct(display_range) |
| 1048 | + if scaled_range <= 0: |
| 1049 | + decimals = 0 |
| 1050 | + else: |
| 1051 | + # Luckily Python's built-in ceil rounds to +inf, not away from |
| 1052 | + # zero. This is very important since the equation for decimals |
| 1053 | + # starts out as `scaled_range > 0.5 * 10**(2 - decimals)` |
| 1054 | + # and ends up with `decimals > 2 - log10(2 * scaled_range)`. |
| 1055 | + decimals = math.ceil(2.0 - math.log10(2.0 * scaled_range)) |
| 1056 | + if decimals > 5: |
| 1057 | + decimals = 5 |
| 1058 | + elif decimals < 0: |
| 1059 | + decimals = 0 |
| 1060 | + else: |
| 1061 | + decimals = self.decimals |
| 1062 | + s = '{x:0.{decimals}f}'.format(x=x, decimals=int(decimals)) |
| 1063 | + |
| 1064 | + if self.symbol: |
| 1065 | + return s + self.symbol |
| 1066 | + return s |
| 1067 | + |
| 1068 | + def convert_to_pct(self, x): |
| 1069 | + return 100.0 * (x / self.xmax) |
| 1070 | + |
| 1071 | + |
976 | 1072 | class Locator(TickHelper):
|
977 | 1073 | """
|
978 | 1074 | Determine the tick locations;
|
@@ -2055,13 +2151,3 @@ def get_locator(self, d):
|
2055 | 2151 | locator = MultipleLocator(ticksize)
|
2056 | 2152 |
|
2057 | 2153 | return locator
|
2058 |
| - |
2059 |
| - |
2060 |
| -__all__ = ('TickHelper', 'Formatter', 'FixedFormatter', |
2061 |
| - 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter', |
2062 |
| - 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter', |
2063 |
| - 'LogFormatterExponent', 'LogFormatterMathtext', 'Locator', |
2064 |
| - 'IndexLocator', 'FixedLocator', 'NullLocator', |
2065 |
| - 'LinearLocator', 'LogLocator', 'AutoLocator', |
2066 |
| - 'MultipleLocator',
3E9D
'MaxNLocator', 'AutoMinorLocator', |
2067 |
| - 'SymmetricalLogLocator') |
|
0 commit comments