33Various `~matplotlib.ticker.Locator` and `~matplotlib.ticker.Formatter`
44classes.
55"""
6+ import re
67import numpy as np
78import matplotlib .ticker as mticker
9+ import locale
810from fractions import Fraction
911from .internals import ic # noqa: F401
1012from .internals import _not_none
1618 'SimpleFormatter' ,
1719]
1820
19-
20- def _sanitize_label (string , zerotrim = False ):
21- """
22- Sanitize tick label strings.
23- """
24- if zerotrim and '.' in string :
25- string = string .rstrip ('0' ).rstrip ('.' )
26- string = string .replace ('-' , '\N{MINUS SIGN} ' )
27- if string == '\N{MINUS SIGN} 0' :
28- string = '0'
29- return string
21+ REGEX_ZERO = re .compile ('\\ A[-\N{MINUS SIGN} ]?0(.0*)?\\ Z' )
22+ REGEX_MINUS = re .compile ('\\ A[-\N{MINUS SIGN} ]\\ Z' )
23+ REGEX_MINUS_ZERO = re .compile ('\\ A[-\N{MINUS SIGN} ]0(.0*)?\\ Z' )
3024
3125
3226class AutoFormatter (mticker .ScalarFormatter ):
3327 """
34- The new default formatter, a simple wrapper around
35- `~matplotlib.ticker.ScalarFormatter`. Differs from
36- `~matplotlib.ticker.ScalarFormatter` in the following ways:
37-
38- 1. Trims trailing zeros if any exist.
39- 2. Allows user to specify *range* within which major tick marks
40- are labelled.
41- 3. Allows user to add arbitrary prefix or suffix to every
42- tick label string.
28+ The new default formatter. Differs from `~matplotlib.ticker.ScalarFormatter`
29+ in the following ways:
30+
31+ 1. Trims trailing decimal zeros by default.
32+ 2. Permits specifying *range* within which major tick marks are labeled.
33+ 3. Permits adding arbitrary prefix or suffix to every tick label string.
34+ 4. Permits adding "negative" and "positive" indicator.
4335 """
4436 def __init__ (
45- self , * args ,
37+ self ,
4638 zerotrim = None , tickrange = None ,
4739 prefix = None , suffix = None , negpos = None , ** kwargs
4840 ):
@@ -63,7 +55,7 @@ def __init__(
6355
6456 Other parameters
6557 ----------------
66- *args, * *kwargs
58+ **kwargs
6759 Passed to `~matplotlib.ticker.ScalarFormatter`.
6860
6961 Warning
@@ -75,7 +67,7 @@ def __init__(
7567 this behavior with a patch.
7668 """
7769 tickrange = tickrange or (- np .inf , np .inf )
78- super ().__init__ (* args , * *kwargs )
70+ super ().__init__ (** kwargs )
7971 from .config import rc
8072 zerotrim = _not_none (zerotrim , rc ['axes.formatter.zerotrim' ])
8173 self ._zerotrim = zerotrim
@@ -96,55 +88,135 @@ def __call__(self, x, pos=None):
9688 The position.
9789 """
9890 # Tick range limitation
99- eps = abs (x ) / 1000
100- tickrange = self ._tickrange
101- if (x + eps ) < tickrange [0 ] or (x - eps ) > tickrange [1 ]:
102- return '' # avoid some ticks
91+ if self ._outside_tick_range (x , self ._tickrange ):
92+ return ''
10393
10494 # Negative positive handling
105- if not self ._negpos or x == 0 :
106- tail = ''
107- elif x > 0 :
108- tail = self ._negpos [1 ]
109- else :
110- x *= - 1
111- tail = self ._negpos [0 ]
95+ x , tail = self ._neg_pos_format (x , self ._negpos )
11296
11397 # Default string formatting
11498 string = super ().__call__ (x , pos )
115- string = _sanitize_label (string , zerotrim = self ._zerotrim )
11699
100+ # Fix issue where non-zero string is formatted as zero
101+ string = self ._fix_small_number (x , string )
102+
103+ # Custom string formatting
104+ string = self ._minus_format (string )
105+ if self ._zerotrim :
106+ string = self ._trim_trailing_zeros (string , self .get_useLocale ())
107+
108+ # Prefix and suffix
109+ string = self ._add_prefix_suffix (string , self ._prefix , self ._suffix )
110+ string = string + tail # add negative-positive indicator
111+ return string
112+
113+ @staticmethod
114+ def _add_prefix_suffix (string , prefix = None , suffix = None ):
115+ """
116+ Add prefix and suffix to string.
117+ """
118+ sign = ''
119+ prefix = prefix or ''
120+ suffix = suffix or ''
121+ if string and REGEX_MINUS .match (string [0 ]):
122+ sign , string = string [0 ], string [1 :]
123+ return sign + prefix + string + suffix
124+
125+ @staticmethod
126+ def _fix_small_number (x , string , offset = 2 ):
127+ """
128+ Fix formatting for non-zero number that gets formatted as zero. The `offset`
129+ controls the offset from the true floating point precision at which we want
130+ to limit maximum precision of the string.
131+ """
117132 # Add just enough precision for small numbers. Default formatter is
118133 # only meant to be used for linear scales and cannot handle the wide
119134 # range of magnitudes in e.g. log scales. To correct this, we only
120135 # truncate if value is within one order of magnitude of the float
121136 # precision. Common issue is e.g. levels=plot.arange(-1, 1, 0.1).
122137 # This choice satisfies even 1000 additions of 0.1 to -100.
123- # Example code:
124- # def add(x, decimals=1, type_=np.float64):
125- # step = type_(10 ** -decimals)
126- # y = type_(x) + step
127- # if np.round(y, decimals) == 0:
128- # return y
129- # else:
130- # return add(y, decimals)
131- # num = abs(add(-200, 1, float))
132- # precision = abs(np.log10(num) // 1) - 1
133- # ('{:.%df}' % precision).format(num)
134- if string == '0' and x != 0 :
135- string = (
136- '{:.%df}' % min (
137- int (abs (np .log10 (abs (x )) // 1 )),
138- np .finfo (type (x )).precision - 1
139- )
140- ).format (x )
141- string = _sanitize_label (string , zerotrim = self ._zerotrim )
138+ match = REGEX_ZERO .match (string )
139+ decimal_point = AutoFormatter ._get_decimal_point ()
142140
143- # Prefix and suffix
144- sign = ''
145- if string and string [0 ] == '\N{MINUS SIGN} ' :
146- sign , string = string [0 ], string [1 :]
147- return sign + self ._prefix + string + self ._suffix + tail
141+ if match and x != 0 :
142+ # Get initial precision spit out by algorithm
143+ decimals , = match .groups ()
144+ if decimals :
145+ precision_init = len (decimals .lstrip (decimal_point ))
146+ else :
147+ precision_init = 0
148+
149+ # Format with precision below floating point error
150+ precision_true = int (abs (np .log10 (abs (x )) // 1 ))
151+ precision_max = np .finfo (type (x )).precision - offset
152+ precision = min (precision_true , precision_max )
153+ string = ('{:.%df}' % precision ).format (x )
154+
155+ # If number is zero after ignoring floating point error, generate
156+ # zero with precision matching original string.
157+ if REGEX_ZERO .match (string ):
158+ string = ('{:.%df}' % precision_init ).format (0 )
159+
160+ # Fix decimal point
161+ string = string .replace ('.' , decimal_point )
162+
163+ return string
164+
165+ @staticmethod
166+ def _get_decimal_point (use_locale = None ):
167+ """
168+ Get decimal point symbol for current locale (e.g. in Europe will be comma).
169+ """
170+ from .config import rc
171+ use_locale = _not_none (use_locale , rc ['axes.formatter.use_locale' ])
172+ if use_locale :
173+ return locale .localeconv ()['decimal_point' ]
174+ else :
175+ return '.'
176+
177+ @staticmethod
178+ def _minus_format (string ):
179+ """
180+ Format the minus sign and avoid "negative zero," e.g. ``-0.000``.
181+ """
182+ from .config import rc
183+ if rc ['axes.unicode_minus' ] and not rc ['text.usetex' ]:
184+ string = string .replace ('-' , '\N{MINUS SIGN} ' )
185+ if REGEX_MINUS_ZERO .match (string ):
186+ string = string [1 :]
187+ return string
188+
189+ @staticmethod
190+ def _neg_pos_format (x , negpos ):
191+ """
192+ Permit suffixes indicators for "negative" and "positive" numbers.
193+ """
194+ if not negpos or x == 0 :
195+ tail = ''
196+ elif x > 0 :
197+ tail = negpos [1 ]
198+ else :
199+ x *= - 1
200+ tail = negpos [0 ]
201+ return x , tail
202+
203+ @staticmethod
204+ def _outside_tick_range (x , tickrange ):
205+ """
206+ Return whether point is outside tick range up to some precision.
207+ """
208+ eps = abs (x ) / 1000
209+ return (x + eps ) < tickrange [0 ] or (x - eps ) > tickrange [1 ]
210+
211+ @staticmethod
212+ def _trim_trailing_zeros (string , use_locale = None ):
213+ """
214+ Sanitize tick label strings.
215+ """
216+ decimal_point = AutoFormatter ._get_decimal_point ()
217+ if decimal_point in string :
218+ string = string .rstrip ('0' ).rstrip (decimal_point )
219+ return string
148220
149221
150222def SigFigFormatter (sigfig = 1 , zerotrim = False ):
0 commit comments