Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/ticker.py : 18%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Tick locating and formatting
3============================
5This module contains classes to support completely configurable tick
6locating and formatting. Although the locators know nothing about major
7or minor ticks, they are used by the Axis class to support major and
8minor tick locating and formatting. Generic tick locators and
9formatters are provided, as well as domain specific custom ones.
11Default Formatter
12-----------------
14The default formatter identifies when the x-data being plotted is a
15small range on top of a large offset. To reduce the chances that the
16ticklabels overlap, the ticks are labeled as deltas from a fixed offset.
17For example::
19 ax.plot(np.arange(2000, 2010), range(10))
21will have tick of 0-9 with an offset of +2e3. If this is not desired
22turn off the use of the offset on the default formatter::
24 ax.get_xaxis().get_major_formatter().set_useOffset(False)
26set the rcParam ``axes.formatter.useoffset=False`` to turn it off
27globally, or set a different formatter.
29Tick locating
30-------------
32The Locator class is the base class for all tick locators. The locators
33handle autoscaling of the view limits based on the data limits, and the
34choosing of tick locations. A useful semi-automatic tick locator is
35`MultipleLocator`. It is initialized with a base, e.g., 10, and it picks
36axis limits and ticks that are multiples of that base.
38The Locator subclasses defined here are
40:class:`AutoLocator`
41 `MaxNLocator` with simple defaults. This is the default tick locator for
42 most plotting.
44:class:`MaxNLocator`
45 Finds up to a max number of intervals with ticks at nice locations.
47:class:`LinearLocator`
48 Space ticks evenly from min to max.
50:class:`LogLocator`
51 Space ticks logarithmically from min to max.
53:class:`MultipleLocator`
54 Ticks and range are a multiple of base; either integer or float.
56:class:`FixedLocator`
57 Tick locations are fixed.
59:class:`IndexLocator`
60 Locator for index plots (e.g., where ``x = range(len(y))``).
62:class:`NullLocator`
63 No ticks.
65:class:`SymmetricalLogLocator`
66 Locator for use with with the symlog norm; works like `LogLocator` for the
67 part outside of the threshold and adds 0 if inside the limits.
69:class:`LogitLocator`
70 Locator for logit scaling.
72:class:`OldAutoLocator`
73 Choose a `MultipleLocator` and dynamically reassign it for intelligent
74 ticking during navigation.
76:class:`AutoMinorLocator`
77 Locator for minor ticks when the axis is linear and the
78 major ticks are uniformly spaced. Subdivides the major
79 tick interval into a specified number of minor intervals,
80 defaulting to 4 or 5 depending on the major interval.
83There are a number of locators specialized for date locations - see
84the `dates` module.
86You can define your own locator by deriving from Locator. You must
87override the ``__call__`` method, which returns a sequence of locations,
88and you will probably want to override the autoscale method to set the
89view limits from the data limits.
91If you want to override the default locator, use one of the above or a custom
92locator and pass it to the x or y axis instance. The relevant methods are::
94 ax.xaxis.set_major_locator(xmajor_locator)
95 ax.xaxis.set_minor_locator(xminor_locator)
96 ax.yaxis.set_major_locator(ymajor_locator)
97 ax.yaxis.set_minor_locator(yminor_locator)
99The default minor locator is `NullLocator`, i.e., no minor ticks on by default.
101Tick formatting
102---------------
104Tick formatting is controlled by classes derived from Formatter. The formatter
105operates on a single tick value and returns a string to the axis.
107:class:`NullFormatter`
108 No labels on the ticks.
110:class:`IndexFormatter`
111 Set the strings from a list of labels.
113:class:`FixedFormatter`
114 Set the strings manually for the labels.
116:class:`FuncFormatter`
117 User defined function sets the labels.
119:class:`StrMethodFormatter`
120 Use string `format` method.
122:class:`FormatStrFormatter`
123 Use an old-style sprintf format string.
125:class:`ScalarFormatter`
126 Default formatter for scalars: autopick the format string.
128:class:`LogFormatter`
129 Formatter for log axes.
131:class:`LogFormatterExponent`
132 Format values for log axis using ``exponent = log_base(value)``.
134:class:`LogFormatterMathtext`
135 Format values for log axis using ``exponent = log_base(value)``
136 using Math text.
138:class:`LogFormatterSciNotation`
139 Format values for log axis using scientific notation.
141:class:`LogitFormatter`
142 Probability formatter.
144:class:`EngFormatter`
145 Format labels in engineering notation
147:class:`PercentFormatter`
148 Format labels as a percentage
150You can derive your own formatter from the Formatter base class by
151simply overriding the ``__call__`` method. The formatter class has
152access to the axis view and data limits.
154To control the major and minor tick label formats, use one of the
155following methods::
157 ax.xaxis.set_major_formatter(xmajor_formatter)
158 ax.xaxis.set_minor_formatter(xminor_formatter)
159 ax.yaxis.set_major_formatter(ymajor_formatter)
160 ax.yaxis.set_minor_formatter(yminor_formatter)
162See :doc:`/gallery/ticks_and_spines/major_minor_demo` for an
163example of setting major and minor ticks. See the :mod:`matplotlib.dates`
164module for more information and examples of using date locators and formatters.
165"""
167import itertools
168import logging
169import locale
170import math
171import numpy as np
172from matplotlib import rcParams
173from matplotlib import cbook
174from matplotlib import transforms as mtransforms
176_log = logging.getLogger(__name__)
178__all__ = ('TickHelper', 'Formatter', 'FixedFormatter',
179 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter',
180 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter',
181 'LogFormatterExponent', 'LogFormatterMathtext',
182 'IndexFormatter', 'LogFormatterSciNotation',
183 'LogitFormatter', 'EngFormatter', 'PercentFormatter',
184 'OldScalarFormatter',
185 'Locator', 'IndexLocator', 'FixedLocator', 'NullLocator',
186 'LinearLocator', 'LogLocator', 'AutoLocator',
187 'MultipleLocator', 'MaxNLocator', 'AutoMinorLocator',
188 'SymmetricalLogLocator', 'LogitLocator', 'OldAutoLocator')
191class _DummyAxis:
192 def __init__(self, minpos=0):
193 self.dataLim = mtransforms.Bbox.unit()
194 self.viewLim = mtransforms.Bbox.unit()
195 self._minpos = minpos
197 def get_view_interval(self):
198 return self.viewLim.intervalx
200 def set_view_interval(self, vmin, vmax):
201 self.viewLim.intervalx = vmin, vmax
203 def get_minpos(self):
204 return self._minpos
206 def get_data_interval(self):
207 return self.dataLim.intervalx
209 def set_data_interval(self, vmin, vmax):
210 self.dataLim.intervalx = vmin, vmax
212 def get_tick_space(self):
213 # Just use the long-standing default of nbins==9
214 return 9
217class TickHelper:
218 axis = None
220 def set_axis(self, axis):
221 self.axis = axis
223 def create_dummy_axis(self, **kwargs):
224 if self.axis is None:
225 self.axis = _DummyAxis(**kwargs)
227 def set_view_interval(self, vmin, vmax):
228 self.axis.set_view_interval(vmin, vmax)
230 def set_data_interval(self, vmin, vmax):
231 self.axis.set_data_interval(vmin, vmax)
233 def set_bounds(self, vmin, vmax):
234 self.set_view_interval(vmin, vmax)
235 self.set_data_interval(vmin, vmax)
238class Formatter(TickHelper):
239 """
240 Create a string based on a tick value and location.
241 """
242 # some classes want to see all the locs to help format
243 # individual ones
244 locs = []
246 def __call__(self, x, pos=None):
247 """
248 Return the format for tick value *x* at position pos.
249 ``pos=None`` indicates an unspecified location.
250 """
251 raise NotImplementedError('Derived must override')
253 def format_ticks(self, values):
254 """Return the tick labels for all the ticks at once."""
255 self.set_locs(values)
256 return [self(value, i) for i, value in enumerate(values)]
258 def format_data(self, value):
259 """
260 Returns the full string representation of the value with the
261 position unspecified.
262 """
263 return self.__call__(value)
265 def format_data_short(self, value):
266 """
267 Return a short string version of the tick value.
269 Defaults to the position-independent long value.
270 """
271 return self.format_data(value)
273 def get_offset(self):
274 return ''
276 def set_locs(self, locs):
277 self.locs = locs
279 def fix_minus(self, s):
280 """
281 Some classes may want to replace a hyphen for minus with the
282 proper unicode symbol (U+2212) for typographical correctness.
283 The default is to not replace it.
285 Note, if you use this method, e.g., in :meth:`format_data` or
286 call, you probably don't want to use it for
287 :meth:`format_data_short` since the toolbar uses this for
288 interactive coord reporting and I doubt we can expect GUIs
289 across platforms will handle the unicode correctly. So for
290 now the classes that override :meth:`fix_minus` should have an
291 explicit :meth:`format_data_short` method
292 """
293 return s
295 def _set_locator(self, locator):
296 """Subclasses may want to override this to set a locator."""
297 pass
300class IndexFormatter(Formatter):
301 """
302 Format the position x to the nearest i-th label where ``i = int(x + 0.5)``.
303 Positions where ``i < 0`` or ``i > len(list)`` have no tick labels.
305 Parameters
306 ----------
307 labels : list
308 List of labels.
309 """
310 def __init__(self, labels):
311 self.labels = labels
312 self.n = len(labels)
314 def __call__(self, x, pos=None):
315 """
316 Return the format for tick value *x* at position pos.
318 The position is ignored and the value is rounded to the nearest
319 integer, which is used to look up the label.
320 """
321 i = int(x + 0.5)
322 if i < 0 or i >= self.n:
323 return ''
324 else:
325 return self.labels[i]
328class NullFormatter(Formatter):
329 """
330 Always return the empty string.
331 """
332 def __call__(self, x, pos=None):
333 """
334 Returns an empty string for all inputs.
335 """
336 return ''
339class FixedFormatter(Formatter):
340 """
341 Return fixed strings for tick labels based only on position, not value.
342 """
343 def __init__(self, seq):
344 """
345 Set the sequence of strings that will be used for labels.
346 """
347 self.seq = seq
348 self.offset_string = ''
350 def __call__(self, x, pos=None):
351 """
352 Returns the label that matches the position regardless of the
353 value.
355 For positions ``pos < len(seq)``, return ``seq[i]`` regardless of
356 *x*. Otherwise return empty string. ``seq`` is the sequence of
357 strings that this object was initialized with.
358 """
359 if pos is None or pos >= len(self.seq):
360 return ''
361 else:
362 return self.seq[pos]
364 def get_offset(self):
365 return self.offset_string
367 def set_offset_string(self, ofs):
368 self.offset_string = ofs
371class FuncFormatter(Formatter):
372 """
373 Use a user-defined function for formatting.
375 The function should take in two inputs (a tick value ``x`` and a
376 position ``pos``), and return a string containing the corresponding
377 tick label.
378 """
379 def __init__(self, func):
380 self.func = func
382 def __call__(self, x, pos=None):
383 """
384 Return the value of the user defined function.
386 *x* and *pos* are passed through as-is.
387 """
388 return self.func(x, pos)
391class FormatStrFormatter(Formatter):
392 """
393 Use an old-style ('%' operator) format string to format the tick.
395 The format string should have a single variable format (%) in it.
396 It will be applied to the value (not the position) of the tick.
397 """
398 def __init__(self, fmt):
399 self.fmt = fmt
401 def __call__(self, x, pos=None):
402 """
403 Return the formatted label string.
405 Only the value *x* is formatted. The position is ignored.
406 """
407 return self.fmt % x
410class StrMethodFormatter(Formatter):
411 """
412 Use a new-style format string (as used by `str.format()`)
413 to format the tick.
415 The field used for the value must be labeled *x* and the field used
416 for the position must be labeled *pos*.
417 """
418 def __init__(self, fmt):
419 self.fmt = fmt
421 def __call__(self, x, pos=None):
422 """
423 Return the formatted label string.
425 *x* and *pos* are passed to `str.format` as keyword arguments
426 with those exact names.
427 """
428 return self.fmt.format(x=x, pos=pos)
431class OldScalarFormatter(Formatter):
432 """
433 Tick location is a plain old number.
434 """
436 def __call__(self, x, pos=None):
437 """
438 Return the format for tick val *x* based on the width of the axis.
440 The position *pos* is ignored.
441 """
442 xmin, xmax = self.axis.get_view_interval()
443 # If the number is not too big and it's an int, format it as an int.
444 if abs(x) < 1e4 and x == int(x):
445 return '%d' % x
446 d = abs(xmax - xmin)
447 fmt = ('%1.3e' if d < 1e-2 else
448 '%1.3f' if d <= 1 else
449 '%1.2f' if d <= 10 else
450 '%1.1f' if d <= 1e5 else
451 '%1.1e')
452 s = fmt % x
453 tup = s.split('e')
454 if len(tup) == 2:
455 mantissa = tup[0].rstrip('0').rstrip('.')
456 sign = tup[1][0].replace('+', '')
457 exponent = tup[1][1:].lstrip('0')
458 s = '%se%s%s' % (mantissa, sign, exponent)
459 else:
460 s = s.rstrip('0').rstrip('.')
461 return s
463 @cbook.deprecated("3.1")
464 def pprint_val(self, x, d):
465 """
466 Formats the value *x* based on the size of the axis range *d*.
467 """
468 # If the number is not too big and it's an int, format it as an int.
469 if abs(x) < 1e4 and x == int(x):
470 return '%d' % x
472 if d < 1e-2:
473 fmt = '%1.3e'
474 elif d < 1e-1:
475 fmt = '%1.3f'
476 elif d > 1e5:
477 fmt = '%1.1e'
478 elif d > 10:
479 fmt = '%1.1f'
480 elif d > 1:
481 fmt = '%1.2f'
482 else:
483 fmt = '%1.3f'
484 s = fmt % x
485 tup = s.split('e')
486 if len(tup) == 2:
487 mantissa = tup[0].rstrip('0').rstrip('.')
488 sign = tup[1][0].replace('+', '')
489 exponent = tup[1][1:].lstrip('0')
490 s = '%se%s%s' % (mantissa, sign, exponent)
491 else:
492 s = s.rstrip('0').rstrip('.')
493 return s
496class ScalarFormatter(Formatter):
497 """
498 Format tick values as a number.
500 Tick value is interpreted as a plain old number. If
501 ``useOffset==True`` and the data range is much smaller than the data
502 average, then an offset will be determined such that the tick labels
503 are meaningful. Scientific notation is used for ``data < 10^-n`` or
504 ``data >= 10^m``, where ``n`` and ``m`` are the power limits set
505 using ``set_powerlimits((n, m))``. The defaults for these are
506 controlled by the ``axes.formatter.limits`` rc parameter.
507 """
508 def __init__(self, useOffset=None, useMathText=None, useLocale=None):
509 # useOffset allows plotting small data ranges with large offsets: for
510 # example: [1+1e-9, 1+2e-9, 1+3e-9] useMathText will render the offset
511 # and scientific notation in mathtext
513 if useOffset is None:
514 useOffset = rcParams['axes.formatter.useoffset']
515 self._offset_threshold = rcParams['axes.formatter.offset_threshold']
516 self.set_useOffset(useOffset)
517 self._usetex = rcParams['text.usetex']
518 if useMathText is None:
519 useMathText = rcParams['axes.formatter.use_mathtext']
520 self.set_useMathText(useMathText)
521 self.orderOfMagnitude = 0
522 self.format = ''
523 self._scientific = True
524 self._powerlimits = rcParams['axes.formatter.limits']
525 if useLocale is None:
526 useLocale = rcParams['axes.formatter.use_locale']
527 self._useLocale = useLocale
529 def get_useOffset(self):
530 return self._useOffset
532 def set_useOffset(self, val):
533 if val in [True, False]:
534 self.offset = 0
535 self._useOffset = val
536 else:
537 self._useOffset = False
538 self.offset = val
540 useOffset = property(fget=get_useOffset, fset=set_useOffset)
542 def get_useLocale(self):
543 return self._useLocale
545 def set_useLocale(self, val):
546 if val is None:
547 self._useLocale = rcParams['axes.formatter.use_locale']
548 else:
549 self._useLocale = val
551 useLocale = property(fget=get_useLocale, fset=set_useLocale)
553 def get_useMathText(self):
554 return self._useMathText
556 def set_useMathText(self, val):
557 if val is None:
558 self._useMathText = rcParams['axes.formatter.use_mathtext']
559 else:
560 self._useMathText = val
562 useMathText = property(fget=get_useMathText, fset=set_useMathText)
564 def fix_minus(self, s):
565 """
566 Replace hyphens with a unicode minus.
567 """
568 if rcParams['text.usetex'] or not rcParams['axes.unicode_minus']:
569 return s
570 else:
571 return s.replace('-', '\N{MINUS SIGN}')
573 def __call__(self, x, pos=None):
574 """
575 Return the format for tick value *x* at position *pos*.
576 """
577 if len(self.locs) == 0:
578 return ''
579 else:
580 xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
581 if np.abs(xp) < 1e-8:
582 xp = 0
583 if self._useLocale:
584 s = locale.format_string(self.format, (xp,))
585 else:
586 s = self.format % xp
587 return self.fix_minus(s)
589 def set_scientific(self, b):
590 """
591 Turn scientific notation on or off.
593 See Also
594 --------
595 ScalarFormatter.set_powerlimits
596 """
597 self._scientific = bool(b)
599 def set_powerlimits(self, lims):
600 """
601 Sets size thresholds for scientific notation.
603 Parameters
604 ----------
605 lims : (min_exp, max_exp)
606 A tuple containing the powers of 10 that determine the switchover
607 threshold. Numbers below ``10**min_exp`` and above ``10**max_exp``
608 will be displayed in scientific notation.
610 For example, ``formatter.set_powerlimits((-3, 4))`` sets the
611 pre-2007 default in which scientific notation is used for
612 numbers less than 1e-3 or greater than 1e4.
614 See Also
615 --------
616 ScalarFormatter.set_scientific
617 """
618 if len(lims) != 2:
619 raise ValueError("'lims' must be a sequence of length 2")
620 self._powerlimits = lims
622 def format_data_short(self, value):
623 """
624 Return a short formatted string representation of a number.
625 """
626 if self._useLocale:
627 return locale.format_string('%-12g', (value,))
628 elif isinstance(value, np.ma.MaskedArray) and value.mask:
629 return ''
630 else:
631 return '%-12g' % value
633 def format_data(self, value):
634 """
635 Return a formatted string representation of a number.
636 """
637 if self._useLocale:
638 s = locale.format_string('%1.10e', (value,))
639 else:
640 s = '%1.10e' % value
641 s = self._formatSciNotation(s)
642 return self.fix_minus(s)
644 def get_offset(self):
645 """
646 Return scientific notation, plus offset.
647 """
648 if len(self.locs) == 0:
649 return ''
650 s = ''
651 if self.orderOfMagnitude or self.offset:
652 offsetStr = ''
653 sciNotStr = ''
654 if self.offset:
655 offsetStr = self.format_data(self.offset)
656 if self.offset > 0:
657 offsetStr = '+' + offsetStr
658 if self.orderOfMagnitude:
659 if self._usetex or self._useMathText:
660 sciNotStr = self.format_data(10 ** self.orderOfMagnitude)
661 else:
662 sciNotStr = '1e%d' % self.orderOfMagnitude
663 if self._useMathText or self._usetex:
664 if sciNotStr != '':
665 sciNotStr = r'\times\mathdefault{%s}' % sciNotStr
666 s = r'$%s\mathdefault{%s}$' % (sciNotStr, offsetStr)
667 else:
668 s = ''.join((sciNotStr, offsetStr))
670 return self.fix_minus(s)
672 def set_locs(self, locs):
673 """
674 Set the locations of the ticks.
675 """
676 self.locs = locs
677 if len(self.locs) > 0:
678 if self._useOffset:
679 self._compute_offset()
680 self._set_order_of_magnitude()
681 self._set_format()
683 def _compute_offset(self):
684 locs = self.locs
685 # Restrict to visible ticks.
686 vmin, vmax = sorted(self.axis.get_view_interval())
687 locs = np.asarray(locs)
688 locs = locs[(vmin <= locs) & (locs <= vmax)]
689 if not len(locs):
690 self.offset = 0
691 return
692 lmin, lmax = locs.min(), locs.max()
693 # Only use offset if there are at least two ticks and every tick has
694 # the same sign.
695 if lmin == lmax or lmin <= 0 <= lmax:
696 self.offset = 0
697 return
698 # min, max comparing absolute values (we want division to round towards
699 # zero so we work on absolute values).
700 abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))])
701 sign = math.copysign(1, lmin)
702 # What is the smallest power of ten such that abs_min and abs_max are
703 # equal up to that precision?
704 # Note: Internally using oom instead of 10 ** oom avoids some numerical
705 # accuracy issues.
706 oom_max = np.ceil(math.log10(abs_max))
707 oom = 1 + next(oom for oom in itertools.count(oom_max, -1)
708 if abs_min // 10 ** oom != abs_max // 10 ** oom)
709 if (abs_max - abs_min) / 10 ** oom <= 1e-2:
710 # Handle the case of straddling a multiple of a large power of ten
711 # (relative to the span).
712 # What is the smallest power of ten such that abs_min and abs_max
713 # are no more than 1 apart at that precision?
714 oom = 1 + next(oom for oom in itertools.count(oom_max, -1)
715 if abs_max // 10 ** oom - abs_min // 10 ** oom > 1)
716 # Only use offset if it saves at least _offset_threshold digits.
717 n = self._offset_threshold - 1
718 self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom
719 if abs_max // 10 ** oom >= 10**n
720 else 0)
722 def _set_order_of_magnitude(self):
723 # if scientific notation is to be used, find the appropriate exponent
724 # if using an numerical offset, find the exponent after applying the
725 # offset. When lower power limit = upper <> 0, use provided exponent.
726 if not self._scientific:
727 self.orderOfMagnitude = 0
728 return
729 if self._powerlimits[0] == self._powerlimits[1] != 0:
730 # fixed scaling when lower power limit = upper <> 0.
731 self.orderOfMagnitude = self._powerlimits[0]
732 return
733 # restrict to visible ticks
734 vmin, vmax = sorted(self.axis.get_view_interval())
735 locs = np.asarray(self.locs)
736 locs = locs[(vmin <= locs) & (locs <= vmax)]
737 locs = np.abs(locs)
738 if not len(locs):
739 self.orderOfMagnitude = 0
740 return
741 if self.offset:
742 oom = math.floor(math.log10(vmax - vmin))
743 else:
744 if locs[0] > locs[-1]:
745 val = locs[0]
746 else:
747 val = locs[-1]
748 if val == 0:
749 oom = 0
750 else:
751 oom = math.floor(math.log10(val))
752 if oom <= self._powerlimits[0]:
753 self.orderOfMagnitude = oom
754 elif oom >= self._powerlimits[1]:
755 self.orderOfMagnitude = oom
756 else:
757 self.orderOfMagnitude = 0
759 def _set_format(self):
760 # set the format string to format all the ticklabels
761 if len(self.locs) < 2:
762 # Temporarily augment the locations with the axis end points.
763 _locs = [*self.locs, *self.axis.get_view_interval()]
764 else:
765 _locs = self.locs
766 locs = (np.asarray(_locs) - self.offset) / 10. ** self.orderOfMagnitude
767 loc_range = np.ptp(locs)
768 # Curvilinear coordinates can yield two identical points.
769 if loc_range == 0:
770 loc_range = np.max(np.abs(locs))
771 # Both points might be zero.
772 if loc_range == 0:
773 loc_range = 1
774 if len(self.locs) < 2:
775 # We needed the end points only for the loc_range calculation.
776 locs = locs[:-2]
777 loc_range_oom = int(math.floor(math.log10(loc_range)))
778 # first estimate:
779 sigfigs = max(0, 3 - loc_range_oom)
780 # refined estimate:
781 thresh = 1e-3 * 10 ** loc_range_oom
782 while sigfigs >= 0:
783 if np.abs(locs - np.round(locs, decimals=sigfigs)).max() < thresh:
784 sigfigs -= 1
785 else:
786 break
787 sigfigs += 1
788 self.format = '%1.' + str(sigfigs) + 'f'
789 if self._usetex or self._useMathText:
790 self.format = r'$\mathdefault{%s}$' % self.format
792 @cbook.deprecated("3.1")
793 def pprint_val(self, x):
794 xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
795 if np.abs(xp) < 1e-8:
796 xp = 0
797 if self._useLocale:
798 return locale.format_string(self.format, (xp,))
799 else:
800 return self.format % xp
802 def _formatSciNotation(self, s):
803 # transform 1e+004 into 1e4, for example
804 if self._useLocale:
805 decimal_point = locale.localeconv()['decimal_point']
806 positive_sign = locale.localeconv()['positive_sign']
807 else:
808 decimal_point = '.'
809 positive_sign = '+'
810 tup = s.split('e')
811 try:
812 significand = tup[0].rstrip('0').rstrip(decimal_point)
813 sign = tup[1][0].replace(positive_sign, '')
814 exponent = tup[1][1:].lstrip('0')
815 if self._useMathText or self._usetex:
816 if significand == '1' and exponent != '':
817 # reformat 1x10^y as 10^y
818 significand = ''
819 if exponent:
820 exponent = '10^{%s%s}' % (sign, exponent)
821 if significand and exponent:
822 return r'%s{\times}%s' % (significand, exponent)
823 else:
824 return r'%s%s' % (significand, exponent)
825 else:
826 s = ('%se%s%s' % (significand, sign, exponent)).rstrip('e')
827 return s
828 except IndexError:
829 return s
832class LogFormatter(Formatter):
833 """
834 Base class for formatting ticks on a log or symlog scale.
836 It may be instantiated directly, or subclassed.
838 Parameters
839 ----------
840 base : float, optional, default: 10.
841 Base of the logarithm used in all calculations.
843 labelOnlyBase : bool, optional, default: False
844 If True, label ticks only at integer powers of base.
845 This is normally True for major ticks and False for
846 minor ticks.
848 minor_thresholds : (subset, all), optional, default: (1, 0.4)
849 If labelOnlyBase is False, these two numbers control
850 the labeling of ticks that are not at integer powers of
851 base; normally these are the minor ticks. The controlling
852 parameter is the log of the axis data range. In the typical
853 case where base is 10 it is the number of decades spanned
854 by the axis, so we can call it 'numdec'. If ``numdec <= all``,
855 all minor ticks will be labeled. If ``all < numdec <= subset``,
856 then only a subset of minor ticks will be labeled, so as to
857 avoid crowding. If ``numdec > subset`` then no minor ticks will
858 be labeled.
860 linthresh : None or float, optional, default: None
861 If a symmetric log scale is in use, its ``linthresh``
862 parameter must be supplied here.
864 Notes
865 -----
866 The `set_locs` method must be called to enable the subsetting
867 logic controlled by the ``minor_thresholds`` parameter.
869 In some cases such as the colorbar, there is no distinction between
870 major and minor ticks; the tick locations might be set manually,
871 or by a locator that puts ticks at integer powers of base and
872 at intermediate locations. For this situation, disable the
873 minor_thresholds logic by using ``minor_thresholds=(np.inf, np.inf)``,
874 so that all ticks will be labeled.
876 To disable labeling of minor ticks when 'labelOnlyBase' is False,
877 use ``minor_thresholds=(0, 0)``. This is the default for the
878 "classic" style.
880 Examples
881 --------
882 To label a subset of minor ticks when the view limits span up
883 to 2 decades, and all of the ticks when zoomed in to 0.5 decades
884 or less, use ``minor_thresholds=(2, 0.5)``.
886 To label all minor ticks when the view limits span up to 1.5
887 decades, use ``minor_thresholds=(1.5, 1.5)``.
889 """
890 def __init__(self, base=10.0, labelOnlyBase=False,
891 minor_thresholds=None,
892 linthresh=None):
894 self._base = float(base)
895 self.labelOnlyBase = labelOnlyBase
896 if minor_thresholds is None:
897 if rcParams['_internal.classic_mode']:
898 minor_thresholds = (0, 0)
899 else:
900 minor_thresholds = (1, 0.4)
901 self.minor_thresholds = minor_thresholds
902 self._sublabels = None
903 self._linthresh = linthresh
905 def base(self, base):
906 """
907 Change the *base* for labeling.
909 .. warning::
910 Should always match the base used for :class:`LogLocator`
912 """
913 self._base = base
915 def label_minor(self, labelOnlyBase):
916 """
917 Switch minor tick labeling on or off.
919 Parameters
920 ----------
921 labelOnlyBase : bool
922 If True, label ticks only at integer powers of base.
924 """
925 self.labelOnlyBase = labelOnlyBase
927 def set_locs(self, locs=None):
928 """
929 Use axis view limits to control which ticks are labeled.
931 The *locs* parameter is ignored in the present algorithm.
933 """
934 if np.isinf(self.minor_thresholds[0]):
935 self._sublabels = None
936 return
938 # Handle symlog case:
939 linthresh = self._linthresh
940 if linthresh is None:
941 try:
942 linthresh = self.axis.get_transform().linthresh
943 except AttributeError:
944 pass
946 vmin, vmax = self.axis.get_view_interval()
947 if vmin > vmax:
948 vmin, vmax = vmax, vmin
950 if linthresh is None and vmin <= 0:
951 # It's probably a colorbar with
952 # a format kwarg setting a LogFormatter in the manner
953 # that worked with 1.5.x, but that doesn't work now.
954 self._sublabels = {1} # label powers of base
955 return
957 b = self._base
958 if linthresh is not None: # symlog
959 # Only compute the number of decades in the logarithmic part of the
960 # axis
961 numdec = 0
962 if vmin < -linthresh:
963 rhs = min(vmax, -linthresh)
964 numdec += math.log(vmin / rhs) / math.log(b)
965 if vmax > linthresh:
966 lhs = max(vmin, linthresh)
967 numdec += math.log(vmax / lhs) / math.log(b)
968 else:
969 vmin = math.log(vmin) / math.log(b)
970 vmax = math.log(vmax) / math.log(b)
971 numdec = abs(vmax - vmin)
973 if numdec > self.minor_thresholds[0]:
974 # Label only bases
975 self._sublabels = {1}
976 elif numdec > self.minor_thresholds[1]:
977 # Add labels between bases at log-spaced coefficients;
978 # include base powers in case the locations include
979 # "major" and "minor" points, as in colorbar.
980 c = np.logspace(0, 1, int(b)//2 + 1, base=b)
981 self._sublabels = set(np.round(c))
982 # For base 10, this yields (1, 2, 3, 4, 6, 10).
983 else:
984 # Label all integer multiples of base**n.
985 self._sublabels = set(np.arange(1, b + 1))
987 def _num_to_string(self, x, vmin, vmax):
988 if x > 10000:
989 s = '%1.0e' % x
990 elif x < 1:
991 s = '%1.0e' % x
992 else:
993 s = self._pprint_val(x, vmax - vmin)
994 return s
996 def __call__(self, x, pos=None):
997 """
998 Return the format for tick val *x*.
999 """
1000 if x == 0.0: # Symlog
1001 return '0'
1003 x = abs(x)
1004 b = self._base
1005 # only label the decades
1006 fx = math.log(x) / math.log(b)
1007 is_x_decade = is_close_to_int(fx)
1008 exponent = round(fx) if is_x_decade else np.floor(fx)
1009 coeff = round(x / b ** exponent)
1011 if self.labelOnlyBase and not is_x_decade:
1012 return ''
1013 if self._sublabels is not None and coeff not in self._sublabels:
1014 return ''
1016 vmin, vmax = self.axis.get_view_interval()
1017 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
1018 s = self._num_to_string(x, vmin, vmax)
1019 return self.fix_minus(s)
1021 def format_data(self, value):
1022 b = self.labelOnlyBase
1023 self.labelOnlyBase = False
1024 value = cbook.strip_math(self.__call__(value))
1025 self.labelOnlyBase = b
1026 return value
1028 def format_data_short(self, value):
1029 """
1030 Return a short formatted string representation of a number.
1031 """
1032 return '%-12g' % value
1034 @cbook.deprecated("3.1")
1035 def pprint_val(self, *args, **kwargs):
1036 return self._pprint_val(*args, **kwargs)
1038 def _pprint_val(self, x, d):
1039 # If the number is not too big and it's an int, format it as an int.
1040 if abs(x) < 1e4 and x == int(x):
1041 return '%d' % x
1042 fmt = ('%1.3e' if d < 1e-2 else
1043 '%1.3f' if d <= 1 else
1044 '%1.2f' if d <= 10 else
1045 '%1.1f' if d <= 1e5 else
1046 '%1.1e')
1047 s = fmt % x
1048 tup = s.split('e')
1049 if len(tup) == 2:
1050 mantissa = tup[0].rstrip('0').rstrip('.')
1051 exponent = int(tup[1])
1052 if exponent:
1053 s = '%se%d' % (mantissa, exponent)
1054 else:
1055 s = mantissa
1056 else:
1057 s = s.rstrip('0').rstrip('.')
1058 return s
1061class LogFormatterExponent(LogFormatter):
1062 """
1063 Format values for log axis using ``exponent = log_base(value)``.
1064 """
1065 def _num_to_string(self, x, vmin, vmax):
1066 fx = math.log(x) / math.log(self._base)
1067 if abs(fx) > 10000:
1068 s = '%1.0g' % fx
1069 elif abs(fx) < 1:
1070 s = '%1.0g' % fx
1071 else:
1072 fd = math.log(vmax - vmin) / math.log(self._base)
1073 s = self._pprint_val(fx, fd)
1074 return s
1077class LogFormatterMathtext(LogFormatter):
1078 """
1079 Format values for log axis using ``exponent = log_base(value)``.
1080 """
1082 def _non_decade_format(self, sign_string, base, fx, usetex):
1083 'Return string for non-decade locations'
1084 return r'$\mathdefault{%s%s^{%.2f}}$' % (sign_string, base, fx)
1086 def __call__(self, x, pos=None):
1087 """
1088 Return the format for tick value *x*.
1090 The position *pos* is ignored.
1091 """
1092 usetex = rcParams['text.usetex']
1093 min_exp = rcParams['axes.formatter.min_exponent']
1095 if x == 0: # Symlog
1096 return r'$\mathdefault{0}$'
1098 sign_string = '-' if x < 0 else ''
1099 x = abs(x)
1100 b = self._base
1102 # only label the decades
1103 fx = math.log(x) / math.log(b)
1104 is_x_decade = is_close_to_int(fx)
1105 exponent = round(fx) if is_x_decade else np.floor(fx)
1106 coeff = round(x / b ** exponent)
1107 if is_x_decade:
1108 fx = round(fx)
1110 if self.labelOnlyBase and not is_x_decade:
1111 return ''
1112 if self._sublabels is not None and coeff not in self._sublabels:
1113 return ''
1115 # use string formatting of the base if it is not an integer
1116 if b % 1 == 0.0:
1117 base = '%d' % b
1118 else:
1119 base = '%s' % b
1121 if abs(fx) < min_exp:
1122 return r'$\mathdefault{%s%g}$' % (sign_string, x)
1123 elif not is_x_decade:
1124 return self._non_decade_format(sign_string, base, fx, usetex)
1125 else:
1126 return r'$\mathdefault{%s%s^{%d}}$' % (sign_string, base, fx)
1129class LogFormatterSciNotation(LogFormatterMathtext):
1130 """
1131 Format values following scientific notation in a logarithmic axis.
1132 """
1134 def _non_decade_format(self, sign_string, base, fx, usetex):
1135 'Return string for non-decade locations'
1136 b = float(base)
1137 exponent = math.floor(fx)
1138 coeff = b ** fx / b ** exponent
1139 if is_close_to_int(coeff):
1140 coeff = round(coeff)
1141 return r'$\mathdefault{%s%g\times%s^{%d}}$' \
1142 % (sign_string, coeff, base, exponent)
1145class LogitFormatter(Formatter):
1146 """
1147 Probability formatter (using Math text).
1148 """
1150 def __init__(
1151 self,
1152 *,
1153 use_overline=False,
1154 one_half=r"\frac{1}{2}",
1155 minor=False,
1156 minor_threshold=25,
1157 minor_number=6,
1158 ):
1159 r"""
1160 Parameters
1161 ----------
1162 use_overline : bool, default: False
1163 If x > 1/2, with x = 1-v, indicate if x should be displayed as
1164 $\overline{v}$. The default is to display $1-v$.
1166 one_half : str, default: r"\frac{1}{2}"
1167 The string used to represent 1/2.
1169 minor : bool, default: False
1170 Indicate if the formatter is formatting minor ticks or not.
1171 Basically minor ticks are not labelled, except when only few ticks
1172 are provided, ticks with most space with neighbor ticks are
1173 labelled. See other parameters to change the default behavior.
1175 minor_threshold : int, default: 25
1176 Maximum number of locs for labelling some minor ticks. This
1177 parameter have no effect if minor is False.
1179 minor_number : int, default: 6
1180 Number of ticks which are labelled when the number of ticks is
1181 below the threshold.
1182 """
1183 self._use_overline = use_overline
1184 self._one_half = one_half
1185 self._minor = minor
1186 self._labelled = set()
1187 self._minor_threshold = minor_threshold
1188 self._minor_number = minor_number
1190 def use_overline(self, use_overline):
1191 r"""
1192 Switch display mode with overline for labelling p>1/2.
1194 Parameters
1195 ----------
1196 use_overline : bool, default: False
1197 If x > 1/2, with x = 1-v, indicate if x should be displayed as
1198 $\overline{v}$. The default is to display $1-v$.
1199 """
1200 self._use_overline = use_overline
1202 def set_one_half(self, one_half):
1203 r"""
1204 Set the way one half is displayed.
1206 one_half : str, default: r"\frac{1}{2}"
1207 The string used to represent 1/2.
1208 """
1209 self._one_half = one_half
1211 def set_minor_threshold(self, minor_threshold):
1212 """
1213 Set the threshold for labelling minors ticks.
1215 Parameters
1216 ----------
1217 minor_threshold : int
1218 Maximum number of locations for labelling some minor ticks. This
1219 parameter have no effect if minor is False.
1220 """
1221 self._minor_threshold = minor_threshold
1223 def set_minor_number(self, minor_number):
1224 """
1225 Set the number of minor ticks to label when some minor ticks are
1226 labelled.
1228 Parameters
1229 ----------
1230 minor_number : int
1231 Number of ticks which are labelled when the number of ticks is
1232 below the threshold.
1233 """
1234 self._minor_number = minor_number
1236 def set_locs(self, locs):
1237 self.locs = np.array(locs)
1238 self._labelled.clear()
1240 if not self._minor:
1241 return None
1242 if all(
1243 is_decade(x, rtol=1e-7)
1244 or is_decade(1 - x, rtol=1e-7)
1245 or (is_close_to_int(2 * x) and int(np.round(2 * x)) == 1)
1246 for x in locs
1247 ):
1248 # minor ticks are subsample from ideal, so no label
1249 return None
1250 if len(locs) < self._minor_threshold:
1251 if len(locs) < self._minor_number:
1252 self._labelled.update(locs)
1253 else:
1254 # we do not have a lot of minor ticks, so only few decades are
1255 # displayed, then we choose some (spaced) minor ticks to label.
1256 # Only minor ticks are known, we assume it is sufficient to
1257 # choice which ticks are displayed.
1258 # For each ticks we compute the distance between the ticks and
1259 # the previous, and between the ticks and the next one. Ticks
1260 # with smallest minimum are chosen. As tiebreak, the ticks
1261 # with smallest sum is chosen.
1262 diff = np.diff(-np.log(1 / self.locs - 1))
1263 space_pessimistic = np.minimum(
1264 np.concatenate(((np.inf,), diff)),
1265 np.concatenate((diff, (np.inf,))),
1266 )
1267 space_sum = (
1268 np.concatenate(((0,), diff))
1269 + np.concatenate((diff, (0,)))
1270 )
1271 good_minor = sorted(
1272 range(len(self.locs)),
1273 key=lambda i: (space_pessimistic[i], space_sum[i]),
1274 )[-self._minor_number:]
1275 self._labelled.update(locs[i] for i in good_minor)
1277 def _format_value(self, x, locs, sci_notation=True):
1278 if sci_notation:
1279 exponent = math.floor(np.log10(x))
1280 min_precision = 0
1281 else:
1282 exponent = 0
1283 min_precision = 1
1284 value = x * 10 ** (-exponent)
1285 if len(locs) < 2:
1286 precision = min_precision
1287 else:
1288 diff = np.sort(np.abs(locs - x))[1]
1289 precision = -np.log10(diff) + exponent
1290 precision = (
1291 int(np.round(precision))
1292 if is_close_to_int(precision)
1293 else math.ceil(precision)
1294 )
1295 if precision < min_precision:
1296 precision = min_precision
1297 mantissa = r"%.*f" % (precision, value)
1298 if not sci_notation:
1299 return mantissa
1300 s = r"%s\cdot10^{%d}" % (mantissa, exponent)
1301 return s
1303 def _one_minus(self, s):
1304 if self._use_overline:
1305 return r"\overline{%s}" % s
1306 else:
1307 return "1-{}".format(s)
1309 def __call__(self, x, pos=None):
1310 if self._minor and x not in self._labelled:
1311 return ""
1312 if x <= 0 or x >= 1:
1313 return ""
1314 if is_close_to_int(2 * x) and round(2 * x) == 1:
1315 s = self._one_half
1316 elif x < 0.5 and is_decade(x, rtol=1e-7):
1317 exponent = round(np.log10(x))
1318 s = "10^{%d}" % exponent
1319 elif x > 0.5 and is_decade(1 - x, rtol=1e-7):
1320 exponent = round(np.log10(1 - x))
1321 s = self._one_minus("10^{%d}" % exponent)
1322 elif x < 0.1:
1323 s = self._format_value(x, self.locs)
1324 elif x > 0.9:
1325 s = self._one_minus(self._format_value(1-x, 1-self.locs))
1326 else:
1327 s = self._format_value(x, self.locs, sci_notation=False)
1328 return r"$\mathdefault{%s}$" % s
1330 def format_data_short(self, value):
1331 """
1332 Return a short formatted string representation of a number.
1333 """
1334 # thresholds choosen for use scienfic notation if and only if exponent
1335 # is less or equal than -2.
1336 if value < 0.1:
1337 return "{:e}".format(value)
1338 if value < 0.9:
1339 return "{:f}".format(value)
1340 return "1-{:e}".format(1 - value)
1343class EngFormatter(Formatter):
1344 """
1345 Formats axis values using engineering prefixes to represent powers
1346 of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7.
1347 """
1349 # The SI engineering prefixes
1350 ENG_PREFIXES = {
1351 -24: "y",
1352 -21: "z",
1353 -18: "a",
1354 -15: "f",
1355 -12: "p",
1356 -9: "n",
1357 -6: "\N{MICRO SIGN}",
1358 -3: "m",
1359 0: "",
1360 3: "k",
1361 6: "M",
1362 9: "G",
1363 12: "T",
1364 15: "P",
1365 18: "E",
1366 21: "Z",
1367 24: "Y"
1368 }
1370 def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
1371 useMathText=None):
1372 r"""
1373 Parameters
1374 ----------
1375 unit : str (default: "")
1376 Unit symbol to use, suitable for use with single-letter
1377 representations of powers of 1000. For example, 'Hz' or 'm'.
1379 places : int (default: None)
1380 Precision with which to display the number, specified in
1381 digits after the decimal point (there will be between one
1382 and three digits before the decimal point). If it is None,
1383 the formatting falls back to the floating point format '%g',
1384 which displays up to 6 *significant* digits, i.e. the equivalent
1385 value for *places* varies between 0 and 5 (inclusive).
1387 sep : str (default: " ")
1388 Separator used between the value and the prefix/unit. For
1389 example, one get '3.14 mV' if ``sep`` is " " (default) and
1390 '3.14mV' if ``sep`` is "". Besides the default behavior, some
1391 other useful options may be:
1393 * ``sep=""`` to append directly the prefix/unit to the value;
1394 * ``sep="\N{THIN SPACE}"`` (``U+2009``);
1395 * ``sep="\N{NARROW NO-BREAK SPACE}"`` (``U+202F``);
1396 * ``sep="\N{NO-BREAK SPACE}"`` (``U+00A0``).
1398 usetex : bool (default: None)
1399 To enable/disable the use of TeX's math mode for rendering the
1400 numbers in the formatter.
1402 useMathText : bool (default: None)
1403 To enable/disable the use mathtext for rendering the numbers in
1404 the formatter.
1405 """
1406 self.unit = unit
1407 self.places = places
1408 self.sep = sep
1409 self.set_usetex(usetex)
1410 self.set_useMathText(useMathText)
1412 def get_usetex(self):
1413 return self._usetex
1415 def set_usetex(self, val):
1416 if val is None:
1417 self._usetex = rcParams['text.usetex']
1418 else:
1419 self._usetex = val
1421 usetex = property(fget=get_usetex, fset=set_usetex)
1423 def get_useMathText(self):
1424 return self._useMathText
1426 def set_useMathText(self, val):
1427 if val is None:
1428 self._useMathText = rcParams['axes.formatter.use_mathtext']
1429 else:
1430 self._useMathText = val
1432 useMathText = property(fget=get_useMathText, fset=set_useMathText)
1434 def fix_minus(self, s):
1435 """
1436 Replace hyphens with a unicode minus.
1437 """
1438 return ScalarFormatter.fix_minus(self, s)
1440 def __call__(self, x, pos=None):
1441 s = "%s%s" % (self.format_eng(x), self.unit)
1442 # Remove the trailing separator when there is neither prefix nor unit
1443 if self.sep and s.endswith(self.sep):
1444 s = s[:-len(self.sep)]
1445 return self.fix_minus(s)
1447 def format_eng(self, num):
1448 """
1449 Formats a number in engineering notation, appending a letter
1450 representing the power of 1000 of the original number.
1451 Some examples:
1453 >>> format_eng(0) # for self.places = 0
1454 '0'
1456 >>> format_eng(1000000) # for self.places = 1
1457 '1.0 M'
1459 >>> format_eng("-1e-6") # for self.places = 2
1460 '-1.00 \N{MICRO SIGN}'
1461 """
1462 sign = 1
1463 fmt = "g" if self.places is None else ".{:d}f".format(self.places)
1465 if num < 0:
1466 sign = -1
1467 num = -num
1469 if num != 0:
1470 pow10 = int(math.floor(math.log10(num) / 3) * 3)
1471 else:
1472 pow10 = 0
1473 # Force num to zero, to avoid inconsistencies like
1474 # format_eng(-0) = "0" and format_eng(0.0) = "0"
1475 # but format_eng(-0.0) = "-0.0"
1476 num = 0.0
1478 pow10 = np.clip(pow10, min(self.ENG_PREFIXES), max(self.ENG_PREFIXES))
1480 mant = sign * num / (10.0 ** pow10)
1481 # Taking care of the cases like 999.9..., which may be rounded to 1000
1482 # instead of 1 k. Beware of the corner case of values that are beyond
1483 # the range of SI prefixes (i.e. > 'Y').
1484 if (abs(float(format(mant, fmt))) >= 1000
1485 and pow10 < max(self.ENG_PREFIXES)):
1486 mant /= 1000
1487 pow10 += 3
1489 prefix = self.ENG_PREFIXES[int(pow10)]
1490 if self._usetex or self._useMathText:
1491 formatted = "${mant:{fmt}}${sep}{prefix}".format(
1492 mant=mant, sep=self.sep, prefix=prefix, fmt=fmt)
1493 else:
1494 formatted = "{mant:{fmt}}{sep}{prefix}".format(
1495 mant=mant, sep=self.sep, prefix=prefix, fmt=fmt)
1497 return formatted
1500class PercentFormatter(Formatter):
1501 """
1502 Format numbers as a percentage.
1504 Parameters
1505 ----------
1506 xmax : float
1507 Determines how the number is converted into a percentage.
1508 *xmax* is the data value that corresponds to 100%.
1509 Percentages are computed as ``x / xmax * 100``. So if the data is
1510 already scaled to be percentages, *xmax* will be 100. Another common
1511 situation is where *xmax* is 1.0.
1513 decimals : None or int
1514 The number of decimal places to place after the point.
1515 If *None* (the default), the number will be computed automatically.
1517 symbol : str or None
1518 A string that will be appended to the label. It may be
1519 *None* or empty to indicate that no symbol should be used. LaTeX
1520 special characters are escaped in *symbol* whenever latex mode is
1521 enabled, unless *is_latex* is *True*.
1523 is_latex : bool
1524 If *False*, reserved LaTeX characters in *symbol* will be escaped.
1525 """
1526 def __init__(self, xmax=100, decimals=None, symbol='%', is_latex=False):
1527 self.xmax = xmax + 0.0
1528 self.decimals = decimals
1529 self._symbol = symbol
1530 self._is_latex = is_latex
1532 def __call__(self, x, pos=None):
1533 """
1534 Formats the tick as a percentage with the appropriate scaling.
1535 """
1536 ax_min, ax_max = self.axis.get_view_interval()
1537 display_range = abs(ax_max - ax_min)
1539 return self.fix_minus(self.format_pct(x, display_range))
1541 def format_pct(self, x, display_range):
1542 """
1543 Formats the number as a percentage number with the correct
1544 number of decimals and adds the percent symbol, if any.
1546 If `self.decimals` is `None`, the number of digits after the
1547 decimal point is set based on the `display_range` of the axis
1548 as follows:
1550 +---------------+----------+------------------------+
1551 | display_range | decimals | sample |
1552 +---------------+----------+------------------------+
1553 | >50 | 0 | ``x = 34.5`` => 35% |
1554 +---------------+----------+------------------------+
1555 | >5 | 1 | ``x = 34.5`` => 34.5% |
1556 +---------------+----------+------------------------+
1557 | >0.5 | 2 | ``x = 34.5`` => 34.50% |
1558 +---------------+----------+------------------------+
1559 | ... | ... | ... |
1560 +---------------+----------+------------------------+
1562 This method will not be very good for tiny axis ranges or
1563 extremely large ones. It assumes that the values on the chart
1564 are percentages displayed on a reasonable scale.
1565 """
1566 x = self.convert_to_pct(x)
1567 if self.decimals is None:
1568 # conversion works because display_range is a difference
1569 scaled_range = self.convert_to_pct(display_range)
1570 if scaled_range <= 0:
1571 decimals = 0
1572 else:
1573 # Luckily Python's built-in ceil rounds to +inf, not away from
1574 # zero. This is very important since the equation for decimals
1575 # starts out as `scaled_range > 0.5 * 10**(2 - decimals)`
1576 # and ends up with `decimals > 2 - log10(2 * scaled_range)`.
1577 decimals = math.ceil(2.0 - math.log10(2.0 * scaled_range))
1578 if decimals > 5:
1579 decimals = 5
1580 elif decimals < 0:
1581 decimals = 0
1582 else:
1583 decimals = self.decimals
1584 s = '{x:0.{decimals}f}'.format(x=x, decimals=int(decimals))
1586 return s + self.symbol
1588 def convert_to_pct(self, x):
1589 return 100.0 * (x / self.xmax)
1591 @property
1592 def symbol(self):
1593 r"""
1594 The configured percent symbol as a string.
1596 If LaTeX is enabled via :rc:`text.usetex`, the special characters
1597 ``{'#', '$', '%', '&', '~', '_', '^', '\', '{', '}'}`` are
1598 automatically escaped in the string.
1599 """
1600 symbol = self._symbol
1601 if not symbol:
1602 symbol = ''
1603 elif rcParams['text.usetex'] and not self._is_latex:
1604 # Source: http://www.personal.ceu.hu/tex/specchar.htm
1605 # Backslash must be first for this to work correctly since
1606 # it keeps getting added in
1607 for spec in r'\#$%&~_^{}':
1608 symbol = symbol.replace(spec, '\\' + spec)
1609 return symbol
1611 @symbol.setter
1612 def symbol(self, symbol):
1613 self._symbol = symbol
1616class Locator(TickHelper):
1617 """
1618 Determine the tick locations;
1620 Note that the same locator should not be used across multiple
1621 `~matplotlib.axis.Axis` because the locator stores references to the Axis
1622 data and view limits.
1623 """
1625 # Some automatic tick locators can generate so many ticks they
1626 # kill the machine when you try and render them.
1627 # This parameter is set to cause locators to raise an error if too
1628 # many ticks are generated.
1629 MAXTICKS = 1000
1631 def tick_values(self, vmin, vmax):
1632 """
1633 Return the values of the located ticks given **vmin** and **vmax**.
1635 .. note::
1636 To get tick locations with the vmin and vmax values defined
1637 automatically for the associated :attr:`axis` simply call
1638 the Locator instance::
1640 >>> print(type(loc))
1641 <type 'Locator'>
1642 >>> print(loc())
1643 [1, 2, 3, 4]
1645 """
1646 raise NotImplementedError('Derived must override')
1648 def set_params(self, **kwargs):
1649 """
1650 Do nothing, and raise a warning. Any locator class not supporting the
1651 set_params() function will call this.
1652 """
1653 cbook._warn_external(
1654 "'set_params()' not defined for locator of type " +
1655 str(type(self)))
1657 def __call__(self):
1658 """Return the locations of the ticks."""
1659 # note: some locators return data limits, other return view limits,
1660 # hence there is no *one* interface to call self.tick_values.
1661 raise NotImplementedError('Derived must override')
1663 def raise_if_exceeds(self, locs):
1664 """
1665 Log at WARNING level if *locs* is longer than `Locator.MAXTICKS`.
1667 This is intended to be called immediately before returning *locs* from
1668 ``__call__`` to inform users in case their Locator returns a huge
1669 number of ticks, causing Matplotlib to run out of memory.
1671 The "strange" name of this method dates back to when it would raise an
1672 exception instead of emitting a log.
1673 """
1674 if len(locs) >= self.MAXTICKS:
1675 _log.warning(
1676 "Locator attempting to generate %s ticks ([%s, ..., %s]), "
1677 "which exceeds Locator.MAXTICKS (%s).",
1678 len(locs), locs[0], locs[-1], self.MAXTICKS)
1679 return locs
1681 def nonsingular(self, v0, v1):
1682 """
1683 Adjust a range as needed to avoid singularities.
1685 This method gets called during autoscaling, with ``(v0, v1)`` set to
1686 the data limits on the axes if the axes contains any data, or
1687 ``(-inf, +inf)`` if not.
1689 - If ``v0 == v1`` (possibly up to some floating point slop), this
1690 method returns an expanded interval around this value.
1691 - If ``(v0, v1) == (-inf, +inf)``, this method returns appropriate
1692 default view limits.
1693 - Otherwise, ``(v0, v1)`` is returned without modification.
1694 """
1695 return mtransforms.nonsingular(v0, v1, expander=.05)
1697 def view_limits(self, vmin, vmax):
1698 """
1699 Select a scale for the range from vmin to vmax.
1701 Subclasses should override this method to change locator behaviour.
1702 """
1703 return mtransforms.nonsingular(vmin, vmax)
1705 @cbook.deprecated("3.2")
1706 def autoscale(self):
1707 """Autoscale the view limits."""
1708 return self.view_limits(*self.axis.get_view_interval())
1710 def pan(self, numsteps):
1711 """Pan numticks (can be positive or negative)"""
1712 ticks = self()
1713 numticks = len(ticks)
1715 vmin, vmax = self.axis.get_view_interval()
1716 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
1717 if numticks > 2:
1718 step = numsteps * abs(ticks[0] - ticks[1])
1719 else:
1720 d = abs(vmax - vmin)
1721 step = numsteps * d / 6.
1723 vmin += step
1724 vmax += step
1725 self.axis.set_view_interval(vmin, vmax, ignore=True)
1727 def zoom(self, direction):
1728 "Zoom in/out on axis; if direction is >0 zoom in, else zoom out"
1730 vmin, vmax = self.axis.get_view_interval()
1731 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
1732 interval = abs(vmax - vmin)
1733 step = 0.1 * interval * direction
1734 self.axis.set_view_interval(vmin + step, vmax - step, ignore=True)
1736 def refresh(self):
1737 """Refresh internal information based on current limits."""
1738 pass
1741class IndexLocator(Locator):
1742 """
1743 Place a tick on every multiple of some base number of points
1744 plotted, e.g., on every 5th point. It is assumed that you are doing
1745 index plotting; i.e., the axis is 0, len(data). This is mainly
1746 useful for x ticks.
1747 """
1748 def __init__(self, base, offset):
1749 """Place ticks every *base* data point, starting at *offset*."""
1750 self._base = base
1751 self.offset = offset
1753 def set_params(self, base=None, offset=None):
1754 """Set parameters within this locator"""
1755 if base is not None:
1756 self._base = base
1757 if offset is not None:
1758 self.offset = offset
1760 def __call__(self):
1761 """Return the locations of the ticks"""
1762 dmin, dmax = self.axis.get_data_interval()
1763 return self.tick_values(dmin, dmax)
1765 def tick_values(self, vmin, vmax):
1766 return self.raise_if_exceeds(
1767 np.arange(vmin + self.offset, vmax + 1, self._base))
1770class FixedLocator(Locator):
1771 """
1772 Tick locations are fixed. If nbins is not None,
1773 the array of possible positions will be subsampled to
1774 keep the number of ticks <= nbins +1.
1775 The subsampling will be done so as to include the smallest
1776 absolute value; for example, if zero is included in the
1777 array of possibilities, then it is guaranteed to be one of
1778 the chosen ticks.
1779 """
1781 def __init__(self, locs, nbins=None):
1782 self.locs = np.asarray(locs)
1783 self.nbins = max(nbins, 2) if nbins is not None else None
1785 def set_params(self, nbins=None):
1786 """Set parameters within this locator."""
1787 if nbins is not None:
1788 self.nbins = nbins
1790 def __call__(self):
1791 return self.tick_values(None, None)
1793 def tick_values(self, vmin, vmax):
1794 """"
1795 Return the locations of the ticks.
1797 .. note::
1799 Because the values are fixed, vmin and vmax are not used in this
1800 method.
1802 """
1803 if self.nbins is None:
1804 return self.locs
1805 step = max(int(np.ceil(len(self.locs) / self.nbins)), 1)
1806 ticks = self.locs[::step]
1807 for i in range(1, step):
1808 ticks1 = self.locs[i::step]
1809 if np.abs(ticks1).min() < np.abs(ticks).min():
1810 ticks = ticks1
1811 return self.raise_if_exceeds(ticks)
1814class NullLocator(Locator):
1815 """
1816 No ticks
1817 """
1819 def __call__(self):
1820 return self.tick_values(None, None)
1822 def tick_values(self, vmin, vmax):
1823 """"
1824 Return the locations of the ticks.
1826 .. note::
1828 Because the values are Null, vmin and vmax are not used in this
1829 method.
1830 """
1831 return []
1834class LinearLocator(Locator):
1835 """
1836 Determine the tick locations
1838 The first time this function is called it will try to set the
1839 number of ticks to make a nice tick partitioning. Thereafter the
1840 number of ticks will be fixed so that interactive navigation will
1841 be nice
1843 """
1844 def __init__(self, numticks=None, presets=None):
1845 """
1846 Use presets to set locs based on lom. A dict mapping vmin, vmax->locs
1847 """
1848 self.numticks = numticks
1849 if presets is None:
1850 self.presets = {}
1851 else:
1852 self.presets = presets
1854 @property
1855 def numticks(self):
1856 # Old hard-coded default.
1857 return self._numticks if self._numticks is not None else 11
1859 @numticks.setter
1860 def numticks(self, numticks):
1861 self._numticks = numticks
1863 def set_params(self, numticks=None, presets=None):
1864 """Set parameters within this locator."""
1865 if presets is not None:
1866 self.presets = presets
1867 if numticks is not None:
1868 self.numticks = numticks
1870 def __call__(self):
1871 'Return the locations of the ticks'
1872 vmin, vmax = self.axis.get_view_interval()
1873 return self.tick_values(vmin, vmax)
1875 def tick_values(self, vmin, vmax):
1876 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
1877 if vmax < vmin:
1878 vmin, vmax = vmax, vmin
1880 if (vmin, vmax) in self.presets:
1881 return self.presets[(vmin, vmax)]
1883 if self.numticks == 0:
1884 return []
1885 ticklocs = np.linspace(vmin, vmax, self.numticks)
1887 return self.raise_if_exceeds(ticklocs)
1889 def view_limits(self, vmin, vmax):
1890 'Try to choose the view limits intelligently'
1892 if vmax < vmin:
1893 vmin, vmax = vmax, vmin
1895 if vmin == vmax:
1896 vmin -= 1
1897 vmax += 1
1899 if rcParams['axes.autolimit_mode'] == 'round_numbers':
1900 exponent, remainder = divmod(
1901 math.log10(vmax - vmin), math.log10(max(self.numticks - 1, 1)))
1902 exponent -= (remainder < .5)
1903 scale = max(self.numticks - 1, 1) ** (-exponent)
1904 vmin = math.floor(scale * vmin) / scale
1905 vmax = math.ceil(scale * vmax) / scale
1907 return mtransforms.nonsingular(vmin, vmax)
1910class MultipleLocator(Locator):
1911 """
1912 Set a tick on each integer multiple of a base within the view interval.
1913 """
1915 def __init__(self, base=1.0):
1916 self._edge = _Edge_integer(base, 0)
1918 def set_params(self, base):
1919 """Set parameters within this locator."""
1920 if base is not None:
1921 self._edge = _Edge_integer(base, 0)
1923 def __call__(self):
1924 'Return the locations of the ticks'
1925 vmin, vmax = self.axis.get_view_interval()
1926 return self.tick_values(vmin, vmax)
1928 def tick_values(self, vmin, vmax):
1929 if vmax < vmin:
1930 vmin, vmax = vmax, vmin
1931 step = self._edge.step
1932 vmin = self._edge.ge(vmin) * step
1933 n = (vmax - vmin + 0.001 * step) // step
1934 locs = vmin - step + np.arange(n + 3) * step
1935 return self.raise_if_exceeds(locs)
1937 def view_limits(self, dmin, dmax):
1938 """
1939 Set the view limits to the nearest multiples of base that
1940 contain the data.
1941 """
1942 if rcParams['axes.autolimit_mode'] == 'round_numbers':
1943 vmin = self._edge.le(dmin) * self._edge.step
1944 vmax = self._edge.ge(dmax) * self._edge.step
1945 if vmin == vmax:
1946 vmin -= 1
1947 vmax += 1
1948 else:
1949 vmin = dmin
1950 vmax = dmax
1952 return mtransforms.nonsingular(vmin, vmax)
1955def scale_range(vmin, vmax, n=1, threshold=100):
1956 dv = abs(vmax - vmin) # > 0 as nonsingular is called before.
1957 meanv = (vmax + vmin) / 2
1958 if abs(meanv) / dv < threshold:
1959 offset = 0
1960 else:
1961 offset = math.copysign(10 ** (math.log10(abs(meanv)) // 1), meanv)
1962 scale = 10 ** (math.log10(dv / n) // 1)
1963 return scale, offset
1966class _Edge_integer:
1967 """
1968 Helper for MaxNLocator, MultipleLocator, etc.
1970 Take floating point precision limitations into account when calculating
1971 tick locations as integer multiples of a step.
1972 """
1973 def __init__(self, step, offset):
1974 """
1975 *step* is a positive floating-point interval between ticks.
1976 *offset* is the offset subtracted from the data limits
1977 prior to calculating tick locations.
1978 """
1979 if step <= 0:
1980 raise ValueError("'step' must be positive")
1981 self.step = step
1982 self._offset = abs(offset)
1984 def closeto(self, ms, edge):
1985 # Allow more slop when the offset is large compared to the step.
1986 if self._offset > 0:
1987 digits = np.log10(self._offset / self.step)
1988 tol = max(1e-10, 10 ** (digits - 12))
1989 tol = min(0.4999, tol)
1990 else:
1991 tol = 1e-10
1992 return abs(ms - edge) < tol
1994 def le(self, x):
1995 'Return the largest n: n*step <= x.'
1996 d, m = divmod(x, self.step)
1997 if self.closeto(m / self.step, 1):
1998 return (d + 1)
1999 return d
2001 def ge(self, x):
2002 'Return the smallest n: n*step >= x.'
2003 d, m = divmod(x, self.step)
2004 if self.closeto(m / self.step, 0):
2005 return d
2006 return (d + 1)
2009class MaxNLocator(Locator):
2010 """
2011 Select no more than N intervals at nice locations.
2012 """
2013 default_params = dict(nbins=10,
2014 steps=None,
2015 integer=False,
2016 symmetric=False,
2017 prune=None,
2018 min_n_ticks=2)
2020 def __init__(self, *args, **kwargs):
2021 """
2022 Parameters
2023 ----------
2024 nbins : int or 'auto', optional, default: 10
2025 Maximum number of intervals; one less than max number of
2026 ticks. If the string `'auto'`, the number of bins will be
2027 automatically determined based on the length of the axis.
2029 steps : array-like, optional
2030 Sequence of nice numbers starting with 1 and ending with 10;
2031 e.g., [1, 2, 4, 5, 10], where the values are acceptable
2032 tick multiples. i.e. for the example, 20, 40, 60 would be
2033 an acceptable set of ticks, as would 0.4, 0.6, 0.8, because
2034 they are multiples of 2. However, 30, 60, 90 would not
2035 be allowed because 3 does not appear in the list of steps.
2037 integer : bool, optional, default: False
2038 If True, ticks will take only integer values, provided
2039 at least `min_n_ticks` integers are found within the
2040 view limits.
2042 symmetric : bool, optional, default: False
2043 If True, autoscaling will result in a range symmetric about zero.
2045 prune : {'lower', 'upper', 'both', None}, optional, default: None
2046 Remove edge ticks -- useful for stacked or ganged plots where
2047 the upper tick of one axes overlaps with the lower tick of the
2048 axes above it, primarily when :rc:`axes.autolimit_mode` is
2049 ``'round_numbers'``. If ``prune=='lower'``, the smallest tick will
2050 be removed. If ``prune == 'upper'``, the largest tick will be
2051 removed. If ``prune == 'both'``, the largest and smallest ticks
2052 will be removed. If ``prune == None``, no ticks will be removed.
2054 min_n_ticks : int, optional, default: 2
2055 Relax *nbins* and *integer* constraints if necessary to obtain
2056 this minimum number of ticks.
2058 """
2059 if args:
2060 if 'nbins' in kwargs:
2061 cbook.deprecated("3.1",
2062 message='Calling MaxNLocator with positional '
2063 'and keyword parameter *nbins* is '
2064 'considered an error and will fail '
2065 'in future versions of matplotlib.')
2066 kwargs['nbins'] = args[0]
2067 if len(args) > 1:
2068 raise ValueError(
2069 "Keywords are required for all arguments except 'nbins'")
2070 self.set_params(**{**self.default_params, **kwargs})
2072 @staticmethod
2073 def _validate_steps(steps):
2074 if not np.iterable(steps):
2075 raise ValueError('steps argument must be an increasing sequence '
2076 'of numbers between 1 and 10 inclusive')
2077 steps = np.asarray(steps)
2078 if np.any(np.diff(steps) <= 0) or steps[-1] > 10 or steps[0] < 1:
2079 raise ValueError('steps argument must be an increasing sequence '
2080 'of numbers between 1 and 10 inclusive')
2081 if steps[0] != 1:
2082 steps = np.hstack((1, steps))
2083 if steps[-1] != 10:
2084 steps = np.hstack((steps, 10))
2085 return steps
2087 @staticmethod
2088 def _staircase(steps):
2089 # Make an extended staircase within which the needed
2090 # step will be found. This is probably much larger
2091 # than necessary.
2092 flights = (0.1 * steps[:-1], steps, 10 * steps[1])
2093 return np.hstack(flights)
2095 def set_params(self, **kwargs):
2096 """
2097 Set parameters for this locator.
2099 Parameters
2100 ----------
2101 nbins : int or 'auto', optional
2102 see `.MaxNLocator`
2103 steps : array-like, optional
2104 see `.MaxNLocator`
2105 integer : bool, optional
2106 see `.MaxNLocator`
2107 symmetric : bool, optional
2108 see `.MaxNLocator`
2109 prune : {'lower', 'upper', 'both', None}, optional
2110 see `.MaxNLocator`
2111 min_n_ticks : int, optional
2112 see `.MaxNLocator`
2113 """
2114 if 'nbins' in kwargs:
2115 self._nbins = kwargs.pop('nbins')
2116 if self._nbins != 'auto':
2117 self._nbins = int(self._nbins)
2118 if 'symmetric' in kwargs:
2119 self._symmetric = kwargs.pop('symmetric')
2120 if 'prune' in kwargs:
2121 prune = kwargs.pop('prune')
2122 cbook._check_in_list(['upper', 'lower', 'both', None], prune=prune)
2123 self._prune = prune
2124 if 'min_n_ticks' in kwargs:
2125 self._min_n_ticks = max(1, kwargs.pop('min_n_ticks'))
2126 if 'steps' in kwargs:
2127 steps = kwargs.pop('steps')
2128 if steps is None:
2129 self._steps = np.array([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10])
2130 else:
2131 self._steps = self._validate_steps(steps)
2132 self._extended_steps = self._staircase(self._steps)
2133 if 'integer' in kwargs:
2134 self._integer = kwargs.pop('integer')
2135 if kwargs:
2136 key, _ = kwargs.popitem()
2137 cbook.warn_deprecated("3.1",
2138 message="MaxNLocator.set_params got an "
2139 f"unexpected parameter: {key}")
2141 def _raw_ticks(self, vmin, vmax):
2142 """
2143 Generate a list of tick locations including the range *vmin* to
2144 *vmax*. In some applications, one or both of the end locations
2145 will not be needed, in which case they are trimmed off
2146 elsewhere.
2147 """
2148 if self._nbins == 'auto':
2149 if self.axis is not None:
2150 nbins = np.clip(self.axis.get_tick_space(),
2151 max(1, self._min_n_ticks - 1), 9)
2152 else:
2153 nbins = 9
2154 else:
2155 nbins = self._nbins
2157 scale, offset = scale_range(vmin, vmax, nbins)
2158 _vmin = vmin - offset
2159 _vmax = vmax - offset
2160 raw_step = (_vmax - _vmin) / nbins
2161 steps = self._extended_steps * scale
2162 if self._integer:
2163 # For steps > 1, keep only integer values.
2164 igood = (steps < 1) | (np.abs(steps - np.round(steps)) < 0.001)
2165 steps = steps[igood]
2167 istep = np.nonzero(steps >= raw_step)[0][0]
2169 # Classic round_numbers mode may require a larger step.
2170 if rcParams['axes.autolimit_mode'] == 'round_numbers':
2171 for istep in range(istep, len(steps)):
2172 step = steps[istep]
2173 best_vmin = (_vmin // step) * step
2174 best_vmax = best_vmin + step * nbins
2175 if best_vmax >= _vmax:
2176 break
2178 # This is an upper limit; move to smaller steps if necessary.
2179 for istep in reversed(range(istep + 1)):
2180 step = steps[istep]
2182 if (self._integer and
2183 np.floor(_vmax) - np.ceil(_vmin) >= self._min_n_ticks - 1):
2184 step = max(1, step)
2185 best_vmin = (_vmin // step) * step
2187 # Find tick locations spanning the vmin-vmax range, taking into
2188 # account degradation of precision when there is a large offset.
2189 # The edge ticks beyond vmin and/or vmax are needed for the
2190 # "round_numbers" autolimit mode.
2191 edge = _Edge_integer(step, offset)
2192 low = edge.le(_vmin - best_vmin)
2193 high = edge.ge(_vmax - best_vmin)
2194 ticks = np.arange(low, high + 1) * step + best_vmin
2195 # Count only the ticks that will be displayed.
2196 nticks = ((ticks <= _vmax) & (ticks >= _vmin)).sum()
2197 if nticks >= self._min_n_ticks:
2198 break
2199 return ticks + offset
2201 def __call__(self):
2202 vmin, vmax = self.axis.get_view_interval()
2203 return self.tick_values(vmin, vmax)
2205 def tick_values(self, vmin, vmax):
2206 if self._symmetric:
2207 vmax = max(abs(vmin), abs(vmax))
2208 vmin = -vmax
2209 vmin, vmax = mtransforms.nonsingular(
2210 vmin, vmax, expander=1e-13, tiny=1e-14)
2211 locs = self._raw_ticks(vmin, vmax)
2213 prune = self._prune
2214 if prune == 'lower':
2215 locs = locs[1:]
2216 elif prune == 'upper':
2217 locs = locs[:-1]
2218 elif prune == 'both':
2219 locs = locs[1:-1]
2220 return self.raise_if_exceeds(locs)
2222 def view_limits(self, dmin, dmax):
2223 if self._symmetric:
2224 dmax = max(abs(dmin), abs(dmax))
2225 dmin = -dmax
2227 dmin, dmax = mtransforms.nonsingular(
2228 dmin, dmax, expander=1e-12, tiny=1e-13)
2230 if rcParams['axes.autolimit_mode'] == 'round_numbers':
2231 return self._raw_ticks(dmin, dmax)[[0, -1]]
2232 else:
2233 return dmin, dmax
2236@cbook.deprecated("3.1")
2237def decade_down(x, base=10):
2238 """Floor x to the nearest lower decade."""
2239 if x == 0.0:
2240 return -base
2241 lx = np.floor(np.log(x) / np.log(base))
2242 return base ** lx
2245@cbook.deprecated("3.1")
2246def decade_up(x, base=10):
2247 """Ceil x to the nearest higher decade."""
2248 if x == 0.0:
2249 return base
2250 lx = np.ceil(np.log(x) / np.log(base))
2251 return base ** lx
2254def is_decade(x, base=10, *, rtol=1e-10):
2255 if not np.isfinite(x):
2256 return False
2257 if x == 0.0:
2258 return True
2259 lx = np.log(np.abs(x)) / np.log(base)
2260 return is_close_to_int(lx, atol=rtol)
2263def _decade_less_equal(x, base):
2264 """
2265 Return the largest integer power of *base* that's less or equal to *x*.
2267 If *x* is negative, the exponent will be *greater*.
2268 """
2269 return (x if x == 0 else
2270 -_decade_greater_equal(-x, base) if x < 0 else
2271 base ** np.floor(np.log(x) / np.log(base)))
2274def _decade_greater_equal(x, base):
2275 """
2276 Return the smallest integer power of *base* that's greater or equal to *x*.
2278 If *x* is negative, the exponent will be *smaller*.
2279 """
2280 return (x if x == 0 else
2281 -_decade_less_equal(-x, base) if x < 0 else
2282 base ** np.ceil(np.log(x) / np.log(base)))
2285def _decade_less(x, base):
2286 """
2287 Return the largest integer power of *base* that's less than *x*.
2289 If *x* is negative, the exponent will be *greater*.
2290 """
2291 if x < 0:
2292 return -_decade_greater(-x, base)
2293 less = _decade_less_equal(x, base)
2294 if less == x:
2295 less /= base
2296 return less
2299def _decade_greater(x, base):
2300 """
2301 Return the smallest integer power of *base* that's greater than *x*.
2303 If *x* is negative, the exponent will be *smaller*.
2304 """
2305 if x < 0:
2306 return -_decade_less(-x, base)
2307 greater = _decade_greater_equal(x, base)
2308 if greater == x:
2309 greater *= base
2310 return greater
2313def is_close_to_int(x, *, atol=1e-10):
2314 return abs(x - np.round(x)) < atol
2317class LogLocator(Locator):
2318 """
2319 Determine the tick locations for log axes
2320 """
2322 def __init__(self, base=10.0, subs=(1.0,), numdecs=4, numticks=None):
2323 """
2324 Place ticks on the locations : subs[j] * base**i
2326 Parameters
2327 ----------
2328 subs : None, str, or sequence of float, optional, default (1.0,)
2329 Gives the multiples of integer powers of the base at which
2330 to place ticks. The default places ticks only at
2331 integer powers of the base.
2332 The permitted string values are ``'auto'`` and ``'all'``,
2333 both of which use an algorithm based on the axis view
2334 limits to determine whether and how to put ticks between
2335 integer powers of the base. With ``'auto'``, ticks are
2336 placed only between integer powers; with ``'all'``, the
2337 integer powers are included. A value of None is
2338 equivalent to ``'auto'``.
2340 """
2341 if numticks is None:
2342 if rcParams['_internal.classic_mode']:
2343 numticks = 15
2344 else:
2345 numticks = 'auto'
2346 self.base(base)
2347 self.subs(subs)
2348 self.numdecs = numdecs
2349 self.numticks = numticks
2351 def set_params(self, base=None, subs=None, numdecs=None, numticks=None):
2352 """Set parameters within this locator."""
2353 if base is not None:
2354 self.base(base)
2355 if subs is not None:
2356 self.subs(subs)
2357 if numdecs is not None:
2358 self.numdecs = numdecs
2359 if numticks is not None:
2360 self.numticks = numticks
2362 # FIXME: these base and subs functions are contrary to our
2363 # usual and desired API.
2365 def base(self, base):
2366 """Set the log base (major tick every ``base**i``, i integer)."""
2367 self._base = float(base)
2369 def subs(self, subs):
2370 """
2371 Set the minor ticks for the log scaling every ``base**i*subs[j]``.
2372 """
2373 if subs is None: # consistency with previous bad API
2374 self._subs = 'auto'
2375 elif isinstance(subs, str):
2376 cbook._check_in_list(('all', 'auto'), subs=subs)
2377 self._subs = subs
2378 else:
2379 try:
2380 self._subs = np.asarray(subs, dtype=float)
2381 except ValueError as e:
2382 raise ValueError("subs must be None, 'all', 'auto' or "
2383 "a sequence of floats, not "
2384 "{}.".format(subs)) from e
2385 if self._subs.ndim != 1:
2386 raise ValueError("A sequence passed to subs must be "
2387 "1-dimensional, not "
2388 "{}-dimensional.".format(self._subs.ndim))
2390 def __call__(self):
2391 'Return the locations of the ticks'
2392 vmin, vmax = self.axis.get_view_interval()
2393 return self.tick_values(vmin, vmax)
2395 def tick_values(self, vmin, vmax):
2396 if self.numticks == 'auto':
2397 if self.axis is not None:
2398 numticks = np.clip(self.axis.get_tick_space(), 2, 9)
2399 else:
2400 numticks = 9
2401 else:
2402 numticks = self.numticks
2404 b = self._base
2405 # dummy axis has no axes attribute
2406 if hasattr(self.axis, 'axes') and self.axis.axes.name == 'polar':
2407 vmax = math.ceil(math.log(vmax) / math.log(b))
2408 decades = np.arange(vmax - self.numdecs, vmax)
2409 ticklocs = b ** decades
2411 return ticklocs
2413 if vmin <= 0.0:
2414 if self.axis is not None:
2415 vmin = self.axis.get_minpos()
2417 if vmin <= 0.0 or not np.isfinite(vmin):
2418 raise ValueError(
2419 "Data has no positive values, and therefore can not be "
2420 "log-scaled.")
2422 _log.debug('vmin %s vmax %s', vmin, vmax)
2424 if vmax < vmin:
2425 vmin, vmax = vmax, vmin
2426 log_vmin = math.log(vmin) / math.log(b)
2427 log_vmax = math.log(vmax) / math.log(b)
2429 numdec = math.floor(log_vmax) - math.ceil(log_vmin)
2431 if isinstance(self._subs, str):
2432 _first = 2.0 if self._subs == 'auto' else 1.0
2433 if numdec > 10 or b < 3:
2434 if self._subs == 'auto':
2435 return np.array([]) # no minor or major ticks
2436 else:
2437 subs = np.array([1.0]) # major ticks
2438 else:
2439 subs = np.arange(_first, b)
2440 else:
2441 subs = self._subs
2443 # Get decades between major ticks.
2444 stride = (max(math.ceil(numdec / (numticks - 1)), 1)
2445 if rcParams['_internal.classic_mode'] else
2446 (numdec + 1) // numticks + 1)
2448 # Does subs include anything other than 1? Essentially a hack to know
2449 # whether we're a major or a minor locator.
2450 have_subs = len(subs) > 1 or (len(subs) == 1 and subs[0] != 1.0)
2452 decades = np.arange(math.floor(log_vmin) - stride,
2453 math.ceil(log_vmax) + 2 * stride, stride)
2455 if hasattr(self, '_transform'):
2456 ticklocs = self._transform.inverted().transform(decades)
2457 if have_subs:
2458 if stride == 1:
2459 ticklocs = np.ravel(np.outer(subs, ticklocs))
2460 else:
2461 # No ticklocs if we have >1 decade between major ticks.
2462 ticklocs = np.array([])
2463 else:
2464 if have_subs:
2465 if stride == 1:
2466 ticklocs = np.concatenate(
2467 [subs * decade_start for decade_start in b ** decades])
2468 else:
2469 ticklocs = np.array([])
2470 else:
2471 ticklocs = b ** decades
2473 _log.debug('ticklocs %r', ticklocs)
2474 if (len(subs) > 1
2475 and stride == 1
2476 and ((vmin <= ticklocs) & (ticklocs <= vmax)).sum() <= 1):
2477 # If we're a minor locator *that expects at least two ticks per
2478 # decade* and the major locator stride is 1 and there's no more
2479 # than one minor tick, switch to AutoLocator.
2480 return AutoLocator().tick_values(vmin, vmax)
2481 else:
2482 return self.raise_if_exceeds(ticklocs)
2484 def view_limits(self, vmin, vmax):
2485 'Try to choose the view limits intelligently'
2486 b = self._base
2488 vmin, vmax = self.nonsingular(vmin, vmax)
2490 if self.axis.axes.name == 'polar':
2491 vmax = math.ceil(math.log(vmax) / math.log(b))
2492 vmin = b ** (vmax - self.numdecs)
2494 if rcParams['axes.autolimit_mode'] == 'round_numbers':
2495 vmin = _decade_less_equal(vmin, self._base)
2496 vmax = _decade_greater_equal(vmax, self._base)
2498 return vmin, vmax
2500 def nonsingular(self, vmin, vmax):
2501 if vmin > vmax:
2502 vmin, vmax = vmax, vmin
2503 if not np.isfinite(vmin) or not np.isfinite(vmax):
2504 vmin, vmax = 1, 10 # Initial range, no data plotted yet.
2505 elif vmax <= 0:
2506 cbook._warn_external(
2507 "Data has no positive values, and therefore cannot be "
2508 "log-scaled.")
2509 vmin, vmax = 1, 10
2510 else:
2511 minpos = self.axis.get_minpos()
2512 if not np.isfinite(minpos):
2513 minpos = 1e-300 # This should never take effect.
2514 if vmin <= 0:
2515 vmin = minpos
2516 if vmin == vmax:
2517 vmin = _decade_less(vmin, self._base)
2518 vmax = _decade_greater(vmax, self._base)
2519 return vmin, vmax
2522class SymmetricalLogLocator(Locator):
2523 """
2524 Determine the tick locations for symmetric log axes
2525 """
2527 def __init__(self, transform=None, subs=None, linthresh=None, base=None):
2528 """Place ticks on the locations ``base**i*subs[j]``."""
2529 if transform is not None:
2530 self._base = transform.base
2531 self._linthresh = transform.linthresh
2532 elif linthresh is not None and base is not None:
2533 self._base = base
2534 self._linthresh = linthresh
2535 else:
2536 raise ValueError("Either transform, or both linthresh "
2537 "and base, must be provided.")
2538 if subs is None:
2539 self._subs = [1.0]
2540 else:
2541 self._subs = subs
2542 self.numticks = 15
2544 def set_params(self, subs=None, numticks=None):
2545 """Set parameters within this locator."""
2546 if numticks is not None:
2547 self.numticks = numticks
2548 if subs is not None:
2549 self._subs = subs
2551 def __call__(self):
2552 """Return the locations of the ticks."""
2553 # Note, these are untransformed coordinates
2554 vmin, vmax = self.axis.get_view_interval()
2555 return self.tick_values(vmin, vmax)
2557 def tick_values(self, vmin, vmax):
2558 base = self._base
2559 linthresh = self._linthresh
2561 if vmax < vmin:
2562 vmin, vmax = vmax, vmin
2564 # The domain is divided into three sections, only some of
2565 # which may actually be present.
2566 #
2567 # <======== -t ==0== t ========>
2568 # aaaaaaaaa bbbbb ccccccccc
2569 #
2570 # a) and c) will have ticks at integral log positions. The
2571 # number of ticks needs to be reduced if there are more
2572 # than self.numticks of them.
2573 #
2574 # b) has a tick at 0 and only 0 (we assume t is a small
2575 # number, and the linear segment is just an implementation
2576 # detail and not interesting.)
2577 #
2578 # We could also add ticks at t, but that seems to usually be
2579 # uninteresting.
2580 #
2581 # "simple" mode is when the range falls entirely within (-t,
2582 # t) -- it should just display (vmin, 0, vmax)
2583 if -linthresh < vmin < vmax < linthresh:
2584 # only the linear range is present
2585 return [vmin, vmax]
2587 # Lower log range is present
2588 has_a = (vmin < -linthresh)
2589 # Upper log range is present
2590 has_c = (vmax > linthresh)
2592 # Check if linear range is present
2593 has_b = (has_a and vmax > -linthresh) or (has_c and vmin < linthresh)
2595 def get_log_range(lo, hi):
2596 lo = np.floor(np.log(lo) / np.log(base))
2597 hi = np.ceil(np.log(hi) / np.log(base))
2598 return lo, hi
2600 # Calculate all the ranges, so we can determine striding
2601 a_lo, a_hi = (0, 0)
2602 if has_a:
2603 a_upper_lim = min(-linthresh, vmax)
2604 a_lo, a_hi = get_log_range(np.abs(a_upper_lim), np.abs(vmin) + 1)
2606 c_lo, c_hi = (0, 0)
2607 if has_c:
2608 c_lower_lim = max(linthresh, vmin)
2609 c_lo, c_hi = get_log_range(c_lower_lim, vmax + 1)
2611 # Calculate the total number of integer exponents in a and c ranges
2612 total_ticks = (a_hi - a_lo) + (c_hi - c_lo)
2613 if has_b:
2614 total_ticks += 1
2615 stride = max(total_ticks // (self.numticks - 1), 1)
2617 decades = []
2618 if has_a:
2619 decades.extend(-1 * (base ** (np.arange(a_lo, a_hi,
2620 stride)[::-1])))
2622 if has_b:
2623 decades.append(0.0)
2625 if has_c:
2626 decades.extend(base ** (np.arange(c_lo, c_hi, stride)))
2628 # Add the subticks if requested
2629 if self._subs is None:
2630 subs = np.arange(2.0, base)
2631 else:
2632 subs = np.asarray(self._subs)
2634 if len(subs) > 1 or subs[0] != 1.0:
2635 ticklocs = []
2636 for decade in decades:
2637 if decade == 0:
2638 ticklocs.append(decade)
2639 else:
2640 ticklocs.extend(subs * decade)
2641 else:
2642 ticklocs = decades
2644 return self.raise_if_exceeds(np.array(ticklocs))
2646 def view_limits(self, vmin, vmax):
2647 'Try to choose the view limits intelligently'
2648 b = self._base
2649 if vmax < vmin:
2650 vmin, vmax = vmax, vmin
2652 if rcParams['axes.autolimit_mode'] == 'round_numbers':
2653 vmin = _decade_less_equal(vmin, b)
2654 vmax = _decade_greater_equal(vmax, b)
2655 if vmin == vmax:
2656 vmin = _decade_less(vmin, b)
2657 vmax = _decade_greater(vmax, b)
2659 result = mtransforms.nonsingular(vmin, vmax)
2660 return result
2663class LogitLocator(MaxNLocator):
2664 """
2665 Determine the tick locations for logit axes
2666 """
2668 def __init__(self, minor=False, *, nbins="auto"):
2669 """
2670 Place ticks on the logit locations
2672 Parameters
2673 ----------
2674 nbins : int or 'auto', optional
2675 Number of ticks. Only used if minor is False.
2676 minor : bool, default: False
2677 Indicate if this locator is for minor ticks or not.
2678 """
2680 self._minor = minor
2681 MaxNLocator.__init__(self, nbins=nbins, steps=[1, 2, 5, 10])
2683 def set_params(self, minor=None, **kwargs):
2684 """Set parameters within this locator."""
2685 if minor is not None:
2686 self._minor = minor
2687 MaxNLocator.set_params(self, **kwargs)
2689 @property
2690 def minor(self):
2691 return self._minor
2693 @minor.setter
2694 def minor(self, value):
2695 self.set_params(minor=value)
2697 def tick_values(self, vmin, vmax):
2698 # dummy axis has no axes attribute
2699 if hasattr(self.axis, "axes") and self.axis.axes.name == "polar":
2700 raise NotImplementedError("Polar axis cannot be logit scaled yet")
2702 if self._nbins == "auto":
2703 if self.axis is not None:
2704 nbins = self.axis.get_tick_space()
2705 if nbins < 2:
2706 nbins = 2
2707 else:
2708 nbins = 9
2709 else:
2710 nbins = self._nbins
2712 # We define ideal ticks with their index:
2713 # linscale: ... 1e-3 1e-2 1e-1 1/2 1-1e-1 1-1e-2 1-1e-3 ...
2714 # b-scale : ... -3 -2 -1 0 1 2 3 ...
2715 def ideal_ticks(x):
2716 return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 1 / 2
2718 vmin, vmax = self.nonsingular(vmin, vmax)
2719 binf = int(
2720 np.floor(np.log10(vmin))
2721 if vmin < 0.5
2722 else 0
2723 if vmin < 0.9
2724 else -np.ceil(np.log10(1 - vmin))
2725 )
2726 bsup = int(
2727 np.ceil(np.log10(vmax))
2728 if vmax <= 0.5
2729 else 1
2730 if vmax <= 0.9
2731 else -np.floor(np.log10(1 - vmax))
2732 )
2733 numideal = bsup - binf - 1
2734 if numideal >= 2:
2735 # have 2 or more wanted ideal ticks, so use them as major ticks
2736 if numideal > nbins:
2737 # to many ideal ticks, subsampling ideals for major ticks, and
2738 # take others for minor ticks
2739 subsampling_factor = math.ceil(numideal / nbins)
2740 if self._minor:
2741 ticklocs = [
2742 ideal_ticks(b)
2743 for b in range(binf, bsup + 1)
2744 if (b % subsampling_factor) != 0
2745 ]
2746 else:
2747 ticklocs = [
2748 ideal_ticks(b)
2749 for b in range(binf, bsup + 1)
2750 if (b % subsampling_factor) == 0
2751 ]
2752 return self.raise_if_exceeds(np.array(ticklocs))
2753 if self._minor:
2754 ticklocs = []
2755 for b in range(binf, bsup):
2756 if b < -1:
2757 ticklocs.extend(np.arange(2, 10) * 10 ** b)
2758 elif b == -1:
2759 ticklocs.extend(np.arange(2, 5) / 10)
2760 elif b == 0:
2761 ticklocs.extend(np.arange(6, 9) / 10)
2762 else:
2763 ticklocs.extend(
2764 1 - np.arange(2, 10)[::-1] * 10 ** (-b - 1)
2765 )
2766 return self.raise_if_exceeds(np.array(ticklocs))
2767 ticklocs = [ideal_ticks(b) for b in range(binf, bsup + 1)]
2768 return self.raise_if_exceeds(np.array(ticklocs))
2769 # the scale is zoomed so same ticks as linear scale can be used
2770 if self._minor:
2771 return []
2772 return MaxNLocator.tick_values(self, vmin, vmax)
2774 def nonsingular(self, vmin, vmax):
2775 standard_minpos = 1e-7
2776 initial_range = (standard_minpos, 1 - standard_minpos)
2777 if vmin > vmax:
2778 vmin, vmax = vmax, vmin
2779 if not np.isfinite(vmin) or not np.isfinite(vmax):
2780 vmin, vmax = initial_range # Initial range, no data plotted yet.
2781 elif vmax <= 0 or vmin >= 1:
2782 # vmax <= 0 occurs when all values are negative
2783 # vmin >= 1 occurs when all values are greater than one
2784 cbook._warn_external(
2785 "Data has no values between 0 and 1, and therefore cannot be "
2786 "logit-scaled."
2787 )
2788 vmin, vmax = initial_range
2789 else:
2790 minpos = (
2791 self.axis.get_minpos()
2792 if self.axis is not None
2793 else standard_minpos
2794 )
2795 if not np.isfinite(minpos):
2796 minpos = standard_minpos # This should never take effect.
2797 if vmin <= 0:
2798 vmin = minpos
2799 # NOTE: for vmax, we should query a property similar to get_minpos,
2800 # but related to the maximal, less-than-one data point.
2801 # Unfortunately, Bbox._minpos is defined very deep in the BBox and
2802 # updated with data, so for now we use 1 - minpos as a substitute.
2803 if vmax >= 1:
2804 vmax = 1 - minpos
2805 if vmin == vmax:
2806 vmin, vmax = 0.1 * vmin, 1 - 0.1 * vmin
2808 return vmin, vmax
2811class AutoLocator(MaxNLocator):
2812 """
2813 Dynamically find major tick positions. This is actually a subclass
2814 of `~matplotlib.ticker.MaxNLocator`, with parameters *nbins = 'auto'*
2815 and *steps = [1, 2, 2.5, 5, 10]*.
2816 """
2817 def __init__(self):
2818 """
2819 To know the values of the non-public parameters, please have a
2820 look to the defaults of `~matplotlib.ticker.MaxNLocator`.
2821 """
2822 if rcParams['_internal.classic_mode']:
2823 nbins = 9
2824 steps = [1, 2, 5, 10]
2825 else:
2826 nbins = 'auto'
2827 steps = [1, 2, 2.5, 5, 10]
2828 MaxNLocator.__init__(self, nbins=nbins, steps=steps)
2831class AutoMinorLocator(Locator):
2832 """
2833 Dynamically find minor tick positions based on the positions of
2834 major ticks. The scale must be linear with major ticks evenly spaced.
2835 """
2836 def __init__(self, n=None):
2837 """
2838 *n* is the number of subdivisions of the interval between
2839 major ticks; e.g., n=2 will place a single minor tick midway
2840 between major ticks.
2842 If *n* is omitted or None, it will be set to 5 or 4.
2843 """
2844 self.ndivs = n
2846 def __call__(self):
2847 'Return the locations of the ticks'
2848 if self.axis.get_scale() == 'log':
2849 cbook._warn_external('AutoMinorLocator does not work with '
2850 'logarithmic scale')
2851 return []
2853 majorlocs = self.axis.get_majorticklocs()
2854 try:
2855 majorstep = majorlocs[1] - majorlocs[0]
2856 except IndexError:
2857 # Need at least two major ticks to find minor tick locations
2858 # TODO: Figure out a way to still be able to display minor
2859 # ticks without two major ticks visible. For now, just display
2860 # no ticks at all.
2861 return []
2863 if self.ndivs is None:
2865 majorstep_no_exponent = 10 ** (np.log10(majorstep) % 1)
2867 if np.isclose(majorstep_no_exponent, [1.0, 2.5, 5.0, 10.0]).any():
2868 ndivs = 5
2869 else:
2870 ndivs = 4
2871 else:
2872 ndivs = self.ndivs
2874 minorstep = majorstep / ndivs
2876 vmin, vmax = self.axis.get_view_interval()
2877 if vmin > vmax:
2878 vmin, vmax = vmax, vmin
2880 t0 = majorlocs[0]
2881 tmin = ((vmin - t0) // minorstep + 1) * minorstep
2882 tmax = ((vmax - t0) // minorstep + 1) * minorstep
2883 locs = np.arange(tmin, tmax, minorstep) + t0
2885 return self.raise_if_exceeds(locs)
2887 def tick_values(self, vmin, vmax):
2888 raise NotImplementedError('Cannot get tick locations for a '
2889 '%s type.' % type(self))
2892class OldAutoLocator(Locator):
2893 """
2894 On autoscale this class picks the best MultipleLocator to set the
2895 view limits and the tick locs.
2897 """
2898 def __init__(self):
2899 self._locator = LinearLocator()
2901 def __call__(self):
2902 'Return the locations of the ticks'
2903 self.refresh()
2904 return self.raise_if_exceeds(self._locator())
2906 def tick_values(self, vmin, vmax):
2907 raise NotImplementedError('Cannot get tick locations for a '
2908 '%s type.' % type(self))
2910 def refresh(self):
2911 # docstring inherited
2912 vmin, vmax = self.axis.get_view_interval()
2913 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
2914 d = abs(vmax - vmin)
2915 self._locator = self.get_locator(d)
2917 def view_limits(self, vmin, vmax):
2918 'Try to choose the view limits intelligently'
2920 d = abs(vmax - vmin)
2921 self._locator = self.get_locator(d)
2922 return self._locator.view_limits(vmin, vmax)
2924 def get_locator(self, d):
2925 """Pick the best locator based on a distance *d*."""
2926 d = abs(d)
2927 if d <= 0:
2928 locator = MultipleLocator(0.2)
2929 else:
2931 try:
2932 ld = math.log10(d)
2933 except OverflowError:
2934 raise RuntimeError('AutoLocator illegal data interval range')
2936 fld = math.floor(ld)
2937 base = 10 ** fld
2939 #if ld==fld: base = 10**(fld-1)
2940 #else: base = 10**fld
2942 if d >= 5 * base:
2943 ticksize = base
2944 elif d >= 2 * base:
2945 ticksize = base / 2.0
2946 else:
2947 ticksize = base / 5.0
2948 locator = MultipleLocator(ticksize)
2950 return locator