Coverage for /usr/lib/python3/dist-packages/matplotlib/ticker.py: 19%
1246 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
1"""
2Tick locating and formatting
3============================
5This module contains classes for configuring tick locating and formatting.
6Generic tick locators and formatters are provided, as well as domain specific
7custom ones.
9Although the locators know nothing about major or minor ticks, they are used
10by the Axis class to support major and minor tick locating and formatting.
12Tick locating
13-------------
15The Locator class is the base class for all tick locators. The locators
16handle autoscaling of the view limits based on the data limits, and the
17choosing of tick locations. A useful semi-automatic tick locator is
18`MultipleLocator`. It is initialized with a base, e.g., 10, and it picks
19axis limits and ticks that are multiples of that base.
21The Locator subclasses defined here are:
23======================= =======================================================
24`AutoLocator` `MaxNLocator` with simple defaults. This is the default
25 tick locator for most plotting.
26`MaxNLocator` Finds up to a max number of intervals with ticks at
27 nice locations.
28`LinearLocator` Space ticks evenly from min to max.
29`LogLocator` Space ticks logarithmically from min to max.
30`MultipleLocator` Ticks and range are a multiple of base; either integer
31 or float.
32`FixedLocator` Tick locations are fixed.
33`IndexLocator` Locator for index plots (e.g., where
34 ``x = range(len(y))``).
35`NullLocator` No ticks.
36`SymmetricalLogLocator` Locator for use with the symlog norm; works like
37 `LogLocator` for the part outside of the threshold and
38 adds 0 if inside the limits.
39`AsinhLocator` Locator for use with the asinh norm, attempting to
40 space ticks approximately uniformly.
41`LogitLocator` Locator for logit scaling.
42`AutoMinorLocator` Locator for minor ticks when the axis is linear and the
43 major ticks are uniformly spaced. Subdivides the major
44 tick interval into a specified number of minor
45 intervals, defaulting to 4 or 5 depending on the major
46 interval.
47======================= =======================================================
49There are a number of locators specialized for date locations - see
50the :mod:`.dates` module.
52You can define your own locator by deriving from Locator. You must
53override the ``__call__`` method, which returns a sequence of locations,
54and you will probably want to override the autoscale method to set the
55view limits from the data limits.
57If you want to override the default locator, use one of the above or a custom
58locator and pass it to the x- or y-axis instance. The relevant methods are::
60 ax.xaxis.set_major_locator(xmajor_locator)
61 ax.xaxis.set_minor_locator(xminor_locator)
62 ax.yaxis.set_major_locator(ymajor_locator)
63 ax.yaxis.set_minor_locator(yminor_locator)
65The default minor locator is `NullLocator`, i.e., no minor ticks on by default.
67.. note::
68 `Locator` instances should not be used with more than one
69 `~matplotlib.axis.Axis` or `~matplotlib.axes.Axes`. So instead of::
71 locator = MultipleLocator(5)
72 ax.xaxis.set_major_locator(locator)
73 ax2.xaxis.set_major_locator(locator)
75 do the following instead::
77 ax.xaxis.set_major_locator(MultipleLocator(5))
78 ax2.xaxis.set_major_locator(MultipleLocator(5))
80Tick formatting
81---------------
83Tick formatting is controlled by classes derived from Formatter. The formatter
84operates on a single tick value and returns a string to the axis.
86========================= =====================================================
87`NullFormatter` No labels on the ticks.
88`FixedFormatter` Set the strings manually for the labels.
89`FuncFormatter` User defined function sets the labels.
90`StrMethodFormatter` Use string `format` method.
91`FormatStrFormatter` Use an old-style sprintf format string.
92`ScalarFormatter` Default formatter for scalars: autopick the format
93 string.
94`LogFormatter` Formatter for log axes.
95`LogFormatterExponent` Format values for log axis using
96 ``exponent = log_base(value)``.
97`LogFormatterMathtext` Format values for log axis using
98 ``exponent = log_base(value)`` using Math text.
99`LogFormatterSciNotation` Format values for log axis using scientific notation.
100`LogitFormatter` Probability formatter.
101`EngFormatter` Format labels in engineering notation.
102`PercentFormatter` Format labels as a percentage.
103========================= =====================================================
105You can derive your own formatter from the Formatter base class by
106simply overriding the ``__call__`` method. The formatter class has
107access to the axis view and data limits.
109To control the major and minor tick label formats, use one of the
110following methods::
112 ax.xaxis.set_major_formatter(xmajor_formatter)
113 ax.xaxis.set_minor_formatter(xminor_formatter)
114 ax.yaxis.set_major_formatter(ymajor_formatter)
115 ax.yaxis.set_minor_formatter(yminor_formatter)
117In addition to a `.Formatter` instance, `~.Axis.set_major_formatter` and
118`~.Axis.set_minor_formatter` also accept a ``str`` or function. ``str`` input
119will be internally replaced with an autogenerated `.StrMethodFormatter` with
120the input ``str``. For function input, a `.FuncFormatter` with the input
121function will be generated and used.
123See :doc:`/gallery/ticks/major_minor_demo` for an example of setting major
124and minor ticks. See the :mod:`matplotlib.dates` module for more information
125and examples of using date locators and formatters.
126"""
128import itertools
129import logging
130import locale
131import math
132from numbers import Integral
134import numpy as np
136import matplotlib as mpl
137from matplotlib import _api, cbook
138from matplotlib import transforms as mtransforms
140_log = logging.getLogger(__name__)
142__all__ = ('TickHelper', 'Formatter', 'FixedFormatter',
143 'NullFormatter', 'FuncFormatter', 'FormatStrFormatter',
144 'StrMethodFormatter', 'ScalarFormatter', 'LogFormatter',
145 'LogFormatterExponent', 'LogFormatterMathtext',
146 'LogFormatterSciNotation',
147 'LogitFormatter', 'EngFormatter', 'PercentFormatter',
148 'Locator', 'IndexLocator', 'FixedLocator', 'NullLocator',
149 'LinearLocator', 'LogLocator', 'AutoLocator',
150 'MultipleLocator', 'MaxNLocator', 'AutoMinorLocator',
151 'SymmetricalLogLocator', 'AsinhLocator', 'LogitLocator')
154class _DummyAxis:
155 __name__ = "dummy"
157 # Once the deprecation elapses, replace dataLim and viewLim by plain
158 # _view_interval and _data_interval private tuples.
159 dataLim = _api.deprecate_privatize_attribute(
160 "3.6", alternative="get_data_interval() and set_data_interval()")
161 viewLim = _api.deprecate_privatize_attribute(
162 "3.6", alternative="get_view_interval() and set_view_interval()")
164 def __init__(self, minpos=0):
165 self._dataLim = mtransforms.Bbox.unit()
166 self._viewLim = mtransforms.Bbox.unit()
167 self._minpos = minpos
169 def get_view_interval(self):
170 return self._viewLim.intervalx
172 def set_view_interval(self, vmin, vmax):
173 self._viewLim.intervalx = vmin, vmax
175 def get_minpos(self):
176 return self._minpos
178 def get_data_interval(self):
179 return self._dataLim.intervalx
181 def set_data_interval(self, vmin, vmax):
182 self._dataLim.intervalx = vmin, vmax
184 def get_tick_space(self):
185 # Just use the long-standing default of nbins==9
186 return 9
189class TickHelper:
190 axis = None
192 def set_axis(self, axis):
193 self.axis = axis
195 def create_dummy_axis(self, **kwargs):
196 if self.axis is None:
197 self.axis = _DummyAxis(**kwargs)
199 @_api.deprecated("3.5", alternative="`.Axis.set_view_interval`")
200 def set_view_interval(self, vmin, vmax):
201 self.axis.set_view_interval(vmin, vmax)
203 @_api.deprecated("3.5", alternative="`.Axis.set_data_interval`")
204 def set_data_interval(self, vmin, vmax):
205 self.axis.set_data_interval(vmin, vmax)
207 @_api.deprecated(
208 "3.5",
209 alternative="`.Axis.set_view_interval` and `.Axis.set_data_interval`")
210 def set_bounds(self, vmin, vmax):
211 self.set_view_interval(vmin, vmax)
212 self.set_data_interval(vmin, vmax)
215class Formatter(TickHelper):
216 """
217 Create a string based on a tick value and location.
218 """
219 # some classes want to see all the locs to help format
220 # individual ones
221 locs = []
223 def __call__(self, x, pos=None):
224 """
225 Return the format for tick value *x* at position pos.
226 ``pos=None`` indicates an unspecified location.
227 """
228 raise NotImplementedError('Derived must override')
230 def format_ticks(self, values):
231 """Return the tick labels for all the ticks at once."""
232 self.set_locs(values)
233 return [self(value, i) for i, value in enumerate(values)]
235 def format_data(self, value):
236 """
237 Return the full string representation of the value with the
238 position unspecified.
239 """
240 return self.__call__(value)
242 def format_data_short(self, value):
243 """
244 Return a short string version of the tick value.
246 Defaults to the position-independent long value.
247 """
248 return self.format_data(value)
250 def get_offset(self):
251 return ''
253 def set_locs(self, locs):
254 """
255 Set the locations of the ticks.
257 This method is called before computing the tick labels because some
258 formatters need to know all tick locations to do so.
259 """
260 self.locs = locs
262 @staticmethod
263 def fix_minus(s):
264 """
265 Some classes may want to replace a hyphen for minus with the proper
266 Unicode symbol (U+2212) for typographical correctness. This is a
267 helper method to perform such a replacement when it is enabled via
268 :rc:`axes.unicode_minus`.
269 """
270 return (s.replace('-', '\N{MINUS SIGN}')
271 if mpl.rcParams['axes.unicode_minus']
272 else s)
274 def _set_locator(self, locator):
275 """Subclasses may want to override this to set a locator."""
276 pass
279class NullFormatter(Formatter):
280 """Always return the empty string."""
282 def __call__(self, x, pos=None):
283 # docstring inherited
284 return ''
287class FixedFormatter(Formatter):
288 """
289 Return fixed strings for tick labels based only on position, not value.
291 .. note::
292 `.FixedFormatter` should only be used together with `.FixedLocator`.
293 Otherwise, the labels may end up in unexpected positions.
294 """
296 def __init__(self, seq):
297 """Set the sequence *seq* of strings that will be used for labels."""
298 self.seq = seq
299 self.offset_string = ''
301 def __call__(self, x, pos=None):
302 """
303 Return the label that matches the position, regardless of the value.
305 For positions ``pos < len(seq)``, return ``seq[i]`` regardless of
306 *x*. Otherwise return empty string. ``seq`` is the sequence of
307 strings that this object was initialized with.
308 """
309 if pos is None or pos >= len(self.seq):
310 return ''
311 else:
312 return self.seq[pos]
314 def get_offset(self):
315 return self.offset_string
317 def set_offset_string(self, ofs):
318 self.offset_string = ofs
321class FuncFormatter(Formatter):
322 """
323 Use a user-defined function for formatting.
325 The function should take in two inputs (a tick value ``x`` and a
326 position ``pos``), and return a string containing the corresponding
327 tick label.
328 """
330 def __init__(self, func):
331 self.func = func
332 self.offset_string = ""
334 def __call__(self, x, pos=None):
335 """
336 Return the value of the user defined function.
338 *x* and *pos* are passed through as-is.
339 """
340 return self.func(x, pos)
342 def get_offset(self):
343 return self.offset_string
345 def set_offset_string(self, ofs):
346 self.offset_string = ofs
349class FormatStrFormatter(Formatter):
350 """
351 Use an old-style ('%' operator) format string to format the tick.
353 The format string should have a single variable format (%) in it.
354 It will be applied to the value (not the position) of the tick.
356 Negative numeric values will use a dash, not a Unicode minus; use mathtext
357 to get a Unicode minus by wrapping the format specifier with $ (e.g.
358 "$%g$").
359 """
360 def __init__(self, fmt):
361 self.fmt = fmt
363 def __call__(self, x, pos=None):
364 """
365 Return the formatted label string.
367 Only the value *x* is formatted. The position is ignored.
368 """
369 return self.fmt % x
372class StrMethodFormatter(Formatter):
373 """
374 Use a new-style format string (as used by `str.format`) to format the tick.
376 The field used for the tick value must be labeled *x* and the field used
377 for the tick position must be labeled *pos*.
378 """
379 def __init__(self, fmt):
380 self.fmt = fmt
382 def __call__(self, x, pos=None):
383 """
384 Return the formatted label string.
386 *x* and *pos* are passed to `str.format` as keyword arguments
387 with those exact names.
388 """
389 return self.fmt.format(x=x, pos=pos)
392class ScalarFormatter(Formatter):
393 """
394 Format tick values as a number.
396 Parameters
397 ----------
398 useOffset : bool or float, default: :rc:`axes.formatter.useoffset`
399 Whether to use offset notation. See `.set_useOffset`.
400 useMathText : bool, default: :rc:`axes.formatter.use_mathtext`
401 Whether to use fancy math formatting. See `.set_useMathText`.
402 useLocale : bool, default: :rc:`axes.formatter.use_locale`.
403 Whether to use locale settings for decimal sign and positive sign.
404 See `.set_useLocale`.
406 Notes
407 -----
408 In addition to the parameters above, the formatting of scientific vs.
409 floating point representation can be configured via `.set_scientific`
410 and `.set_powerlimits`).
412 **Offset notation and scientific notation**
414 Offset notation and scientific notation look quite similar at first sight.
415 Both split some information from the formatted tick values and display it
416 at the end of the axis.
418 - The scientific notation splits up the order of magnitude, i.e. a
419 multiplicative scaling factor, e.g. ``1e6``.
421 - The offset notation separates an additive constant, e.g. ``+1e6``. The
422 offset notation label is always prefixed with a ``+`` or ``-`` sign
423 and is thus distinguishable from the order of magnitude label.
425 The following plot with x limits ``1_000_000`` to ``1_000_010`` illustrates
426 the different formatting. Note the labels at the right edge of the x axis.
428 .. plot::
430 lim = (1_000_000, 1_000_010)
432 fig, (ax1, ax2, ax3) = plt.subplots(3, 1, gridspec_kw={'hspace': 2})
433 ax1.set(title='offset_notation', xlim=lim)
434 ax2.set(title='scientific notation', xlim=lim)
435 ax2.xaxis.get_major_formatter().set_useOffset(False)
436 ax3.set(title='floating point notation', xlim=lim)
437 ax3.xaxis.get_major_formatter().set_useOffset(False)
438 ax3.xaxis.get_major_formatter().set_scientific(False)
440 """
442 def __init__(self, useOffset=None, useMathText=None, useLocale=None):
443 if useOffset is None:
444 useOffset = mpl.rcParams['axes.formatter.useoffset']
445 self._offset_threshold = \
446 mpl.rcParams['axes.formatter.offset_threshold']
447 self.set_useOffset(useOffset)
448 self._usetex = mpl.rcParams['text.usetex']
449 self.set_useMathText(useMathText)
450 self.orderOfMagnitude = 0
451 self.format = ''
452 self._scientific = True
453 self._powerlimits = mpl.rcParams['axes.formatter.limits']
454 self.set_useLocale(useLocale)
456 def get_useOffset(self):
457 """
458 Return whether automatic mode for offset notation is active.
460 This returns True if ``set_useOffset(True)``; it returns False if an
461 explicit offset was set, e.g. ``set_useOffset(1000)``.
463 See Also
464 --------
465 ScalarFormatter.set_useOffset
466 """
467 return self._useOffset
469 def set_useOffset(self, val):
470 """
471 Set whether to use offset notation.
473 When formatting a set numbers whose value is large compared to their
474 range, the formatter can separate an additive constant. This can
475 shorten the formatted numbers so that they are less likely to overlap
476 when drawn on an axis.
478 Parameters
479 ----------
480 val : bool or float
481 - If False, do not use offset notation.
482 - If True (=automatic mode), use offset notation if it can make
483 the residual numbers significantly shorter. The exact behavior
484 is controlled by :rc:`axes.formatter.offset_threshold`.
485 - If a number, force an offset of the given value.
487 Examples
488 --------
489 With active offset notation, the values
491 ``100_000, 100_002, 100_004, 100_006, 100_008``
493 will be formatted as ``0, 2, 4, 6, 8`` plus an offset ``+1e5``, which
494 is written to the edge of the axis.
495 """
496 if val in [True, False]:
497 self.offset = 0
498 self._useOffset = val
499 else:
500 self._useOffset = False
501 self.offset = val
503 useOffset = property(fget=get_useOffset, fset=set_useOffset)
505 def get_useLocale(self):
506 """
507 Return whether locale settings are used for formatting.
509 See Also
510 --------
511 ScalarFormatter.set_useLocale
512 """
513 return self._useLocale
515 def set_useLocale(self, val):
516 """
517 Set whether to use locale settings for decimal sign and positive sign.
519 Parameters
520 ----------
521 val : bool or None
522 *None* resets to :rc:`axes.formatter.use_locale`.
523 """
524 if val is None:
525 self._useLocale = mpl.rcParams['axes.formatter.use_locale']
526 else:
527 self._useLocale = val
529 useLocale = property(fget=get_useLocale, fset=set_useLocale)
531 def _format_maybe_minus_and_locale(self, fmt, arg):
532 """
533 Format *arg* with *fmt*, applying Unicode minus and locale if desired.
534 """
535 return self.fix_minus(locale.format_string(fmt, (arg,), True)
536 if self._useLocale else fmt % arg)
538 def get_useMathText(self):
539 """
540 Return whether to use fancy math formatting.
542 See Also
543 --------
544 ScalarFormatter.set_useMathText
545 """
546 return self._useMathText
548 def set_useMathText(self, val):
549 r"""
550 Set whether to use fancy math formatting.
552 If active, scientific notation is formatted as :math:`1.2 \times 10^3`.
554 Parameters
555 ----------
556 val : bool or None
557 *None* resets to :rc:`axes.formatter.use_mathtext`.
558 """
559 if val is None:
560 self._useMathText = mpl.rcParams['axes.formatter.use_mathtext']
561 if self._useMathText is False:
562 try:
563 from matplotlib import font_manager
564 ufont = font_manager.findfont(
565 font_manager.FontProperties(
566 mpl.rcParams["font.family"]
567 ),
568 fallback_to_default=False,
569 )
570 except ValueError:
571 ufont = None
573 if ufont == str(cbook._get_data_path("fonts/ttf/cmr10.ttf")):
574 _api.warn_external(
575 "cmr10 font should ideally be used with "
576 "mathtext, set axes.formatter.use_mathtext to True"
577 )
578 else:
579 self._useMathText = val
581 useMathText = property(fget=get_useMathText, fset=set_useMathText)
583 def __call__(self, x, pos=None):
584 """
585 Return the format for tick value *x* at position *pos*.
586 """
587 if len(self.locs) == 0:
588 return ''
589 else:
590 xp = (x - self.offset) / (10. ** self.orderOfMagnitude)
591 if abs(xp) < 1e-8:
592 xp = 0
593 return self._format_maybe_minus_and_locale(self.format, xp)
595 def set_scientific(self, b):
596 """
597 Turn scientific notation on or off.
599 See Also
600 --------
601 ScalarFormatter.set_powerlimits
602 """
603 self._scientific = bool(b)
605 def set_powerlimits(self, lims):
606 r"""
607 Set size thresholds for scientific notation.
609 Parameters
610 ----------
611 lims : (int, int)
612 A tuple *(min_exp, max_exp)* containing the powers of 10 that
613 determine the switchover threshold. For a number representable as
614 :math:`a \times 10^\mathrm{exp}` with :math:`1 <= |a| < 10`,
615 scientific notation will be used if ``exp <= min_exp`` or
616 ``exp >= max_exp``.
618 The default limits are controlled by :rc:`axes.formatter.limits`.
620 In particular numbers with *exp* equal to the thresholds are
621 written in scientific notation.
623 Typically, *min_exp* will be negative and *max_exp* will be
624 positive.
626 For example, ``formatter.set_powerlimits((-3, 4))`` will provide
627 the following formatting:
628 :math:`1 \times 10^{-3}, 9.9 \times 10^{-3}, 0.01,`
629 :math:`9999, 1 \times 10^4`.
631 See Also
632 --------
633 ScalarFormatter.set_scientific
634 """
635 if len(lims) != 2:
636 raise ValueError("'lims' must be a sequence of length 2")
637 self._powerlimits = lims
639 def format_data_short(self, value):
640 # docstring inherited
641 if isinstance(value, np.ma.MaskedArray) and value.mask:
642 return ""
643 if isinstance(value, Integral):
644 fmt = "%d"
645 else:
646 if getattr(self.axis, "__name__", "") in ["xaxis", "yaxis"]:
647 if self.axis.__name__ == "xaxis":
648 axis_trf = self.axis.axes.get_xaxis_transform()
649 axis_inv_trf = axis_trf.inverted()
650 screen_xy = axis_trf.transform((value, 0))
651 neighbor_values = axis_inv_trf.transform(
652 screen_xy + [[-1, 0], [+1, 0]])[:, 0]
653 else: # yaxis:
654 axis_trf = self.axis.axes.get_yaxis_transform()
655 axis_inv_trf = axis_trf.inverted()
656 screen_xy = axis_trf.transform((0, value))
657 neighbor_values = axis_inv_trf.transform(
658 screen_xy + [[0, -1], [0, +1]])[:, 1]
659 delta = abs(neighbor_values - value).max()
660 else:
661 # Rough approximation: no more than 1e4 divisions.
662 a, b = self.axis.get_view_interval()
663 delta = (b - a) / 1e4
664 fmt = "%-#.{}g".format(cbook._g_sig_digits(value, delta))
665 return self._format_maybe_minus_and_locale(fmt, value)
667 def format_data(self, value):
668 # docstring inherited
669 e = math.floor(math.log10(abs(value)))
670 s = round(value / 10**e, 10)
671 exponent = self._format_maybe_minus_and_locale("%d", e)
672 significand = self._format_maybe_minus_and_locale(
673 "%d" if s % 1 == 0 else "%1.10g", s)
674 if e == 0:
675 return significand
676 elif self._useMathText or self._usetex:
677 exponent = "10^{%s}" % exponent
678 return (exponent if s == 1 # reformat 1x10^y as 10^y
679 else rf"{significand} \times {exponent}")
680 else:
681 return f"{significand}e{exponent}"
683 def get_offset(self):
684 """
685 Return scientific notation, plus offset.
686 """
687 if len(self.locs) == 0:
688 return ''
689 s = ''
690 if self.orderOfMagnitude or self.offset:
691 offsetStr = ''
692 sciNotStr = ''
693 if self.offset:
694 offsetStr = self.format_data(self.offset)
695 if self.offset > 0:
696 offsetStr = '+' + offsetStr
697 if self.orderOfMagnitude:
698 if self._usetex or self._useMathText:
699 sciNotStr = self.format_data(10 ** self.orderOfMagnitude)
700 else:
701 sciNotStr = '1e%d' % self.orderOfMagnitude
702 if self._useMathText or self._usetex:
703 if sciNotStr != '':
704 sciNotStr = r'\times\mathdefault{%s}' % sciNotStr
705 s = r'$%s\mathdefault{%s}$' % (sciNotStr, offsetStr)
706 else:
707 s = ''.join((sciNotStr, offsetStr))
709 return self.fix_minus(s)
711 def set_locs(self, locs):
712 # docstring inherited
713 self.locs = locs
714 if len(self.locs) > 0:
715 if self._useOffset:
716 self._compute_offset()
717 self._set_order_of_magnitude()
718 self._set_format()
720 def _compute_offset(self):
721 locs = self.locs
722 # Restrict to visible ticks.
723 vmin, vmax = sorted(self.axis.get_view_interval())
724 locs = np.asarray(locs)
725 locs = locs[(vmin <= locs) & (locs <= vmax)]
726 if not len(locs):
727 self.offset = 0
728 return
729 lmin, lmax = locs.min(), locs.max()
730 # Only use offset if there are at least two ticks and every tick has
731 # the same sign.
732 if lmin == lmax or lmin <= 0 <= lmax:
733 self.offset = 0
734 return
735 # min, max comparing absolute values (we want division to round towards
736 # zero so we work on absolute values).
737 abs_min, abs_max = sorted([abs(float(lmin)), abs(float(lmax))])
738 sign = math.copysign(1, lmin)
739 # What is the smallest power of ten such that abs_min and abs_max are
740 # equal up to that precision?
741 # Note: Internally using oom instead of 10 ** oom avoids some numerical
742 # accuracy issues.
743 oom_max = np.ceil(math.log10(abs_max))
744 oom = 1 + next(oom for oom in itertools.count(oom_max, -1)
745 if abs_min // 10 ** oom != abs_max // 10 ** oom)
746 if (abs_max - abs_min) / 10 ** oom <= 1e-2:
747 # Handle the case of straddling a multiple of a large power of ten
748 # (relative to the span).
749 # What is the smallest power of ten such that abs_min and abs_max
750 # are no more than 1 apart at that precision?
751 oom = 1 + next(oom for oom in itertools.count(oom_max, -1)
752 if abs_max // 10 ** oom - abs_min // 10 ** oom > 1)
753 # Only use offset if it saves at least _offset_threshold digits.
754 n = self._offset_threshold - 1
755 self.offset = (sign * (abs_max // 10 ** oom) * 10 ** oom
756 if abs_max // 10 ** oom >= 10**n
757 else 0)
759 def _set_order_of_magnitude(self):
760 # if scientific notation is to be used, find the appropriate exponent
761 # if using a numerical offset, find the exponent after applying the
762 # offset. When lower power limit = upper <> 0, use provided exponent.
763 if not self._scientific:
764 self.orderOfMagnitude = 0
765 return
766 if self._powerlimits[0] == self._powerlimits[1] != 0:
767 # fixed scaling when lower power limit = upper <> 0.
768 self.orderOfMagnitude = self._powerlimits[0]
769 return
770 # restrict to visible ticks
771 vmin, vmax = sorted(self.axis.get_view_interval())
772 locs = np.asarray(self.locs)
773 locs = locs[(vmin <= locs) & (locs <= vmax)]
774 locs = np.abs(locs)
775 if not len(locs):
776 self.orderOfMagnitude = 0
777 return
778 if self.offset:
779 oom = math.floor(math.log10(vmax - vmin))
780 else:
781 val = locs.max()
782 if val == 0:
783 oom = 0
784 else:
785 oom = math.floor(math.log10(val))
786 if oom <= self._powerlimits[0]:
787 self.orderOfMagnitude = oom
788 elif oom >= self._powerlimits[1]:
789 self.orderOfMagnitude = oom
790 else:
791 self.orderOfMagnitude = 0
793 def _set_format(self):
794 # set the format string to format all the ticklabels
795 if len(self.locs) < 2:
796 # Temporarily augment the locations with the axis end points.
797 _locs = [*self.locs, *self.axis.get_view_interval()]
798 else:
799 _locs = self.locs
800 locs = (np.asarray(_locs) - self.offset) / 10. ** self.orderOfMagnitude
801 loc_range = np.ptp(locs)
802 # Curvilinear coordinates can yield two identical points.
803 if loc_range == 0:
804 loc_range = np.max(np.abs(locs))
805 # Both points might be zero.
806 if loc_range == 0:
807 loc_range = 1
808 if len(self.locs) < 2:
809 # We needed the end points only for the loc_range calculation.
810 locs = locs[:-2]
811 loc_range_oom = int(math.floor(math.log10(loc_range)))
812 # first estimate:
813 sigfigs = max(0, 3 - loc_range_oom)
814 # refined estimate:
815 thresh = 1e-3 * 10 ** loc_range_oom
816 while sigfigs >= 0:
817 if np.abs(locs - np.round(locs, decimals=sigfigs)).max() < thresh:
818 sigfigs -= 1
819 else:
820 break
821 sigfigs += 1
822 self.format = '%1.' + str(sigfigs) + 'f'
823 if self._usetex or self._useMathText:
824 self.format = r'$\mathdefault{%s}$' % self.format
827class LogFormatter(Formatter):
828 """
829 Base class for formatting ticks on a log or symlog scale.
831 It may be instantiated directly, or subclassed.
833 Parameters
834 ----------
835 base : float, default: 10.
836 Base of the logarithm used in all calculations.
838 labelOnlyBase : bool, default: False
839 If True, label ticks only at integer powers of base.
840 This is normally True for major ticks and False for
841 minor ticks.
843 minor_thresholds : (subset, all), default: (1, 0.4)
844 If labelOnlyBase is False, these two numbers control
845 the labeling of ticks that are not at integer powers of
846 base; normally these are the minor ticks. The controlling
847 parameter is the log of the axis data range. In the typical
848 case where base is 10 it is the number of decades spanned
849 by the axis, so we can call it 'numdec'. If ``numdec <= all``,
850 all minor ticks will be labeled. If ``all < numdec <= subset``,
851 then only a subset of minor ticks will be labeled, so as to
852 avoid crowding. If ``numdec > subset`` then no minor ticks will
853 be labeled.
855 linthresh : None or float, default: None
856 If a symmetric log scale is in use, its ``linthresh``
857 parameter must be supplied here.
859 Notes
860 -----
861 The `set_locs` method must be called to enable the subsetting
862 logic controlled by the ``minor_thresholds`` parameter.
864 In some cases such as the colorbar, there is no distinction between
865 major and minor ticks; the tick locations might be set manually,
866 or by a locator that puts ticks at integer powers of base and
867 at intermediate locations. For this situation, disable the
868 minor_thresholds logic by using ``minor_thresholds=(np.inf, np.inf)``,
869 so that all ticks will be labeled.
871 To disable labeling of minor ticks when 'labelOnlyBase' is False,
872 use ``minor_thresholds=(0, 0)``. This is the default for the
873 "classic" style.
875 Examples
876 --------
877 To label a subset of minor ticks when the view limits span up
878 to 2 decades, and all of the ticks when zoomed in to 0.5 decades
879 or less, use ``minor_thresholds=(2, 0.5)``.
881 To label all minor ticks when the view limits span up to 1.5
882 decades, use ``minor_thresholds=(1.5, 1.5)``.
883 """
885 def __init__(self, base=10.0, labelOnlyBase=False,
886 minor_thresholds=None,
887 linthresh=None):
889 self.set_base(base)
890 self.set_label_minor(labelOnlyBase)
891 if minor_thresholds is None:
892 if mpl.rcParams['_internal.classic_mode']:
893 minor_thresholds = (0, 0)
894 else:
895 minor_thresholds = (1, 0.4)
896 self.minor_thresholds = minor_thresholds
897 self._sublabels = None
898 self._linthresh = linthresh
900 @_api.deprecated("3.6", alternative='set_base()')
901 def base(self, base):
902 """
903 Change the *base* for labeling.
905 .. warning::
906 Should always match the base used for :class:`LogLocator`
907 """
908 self.set_base(base)
910 def set_base(self, base):
911 """
912 Change the *base* for labeling.
914 .. warning::
915 Should always match the base used for :class:`LogLocator`
916 """
917 self._base = float(base)
919 @_api.deprecated("3.6", alternative='set_label_minor()')
920 def label_minor(self, labelOnlyBase):
921 """
922 Switch minor tick labeling on or off.
924 Parameters
925 ----------
926 labelOnlyBase : bool
927 If True, label ticks only at integer powers of base.
928 """
929 self.set_label_minor(labelOnlyBase)
931 def set_label_minor(self, labelOnlyBase):
932 """
933 Switch minor tick labeling on or off.
935 Parameters
936 ----------
937 labelOnlyBase : bool
938 If True, label ticks only at integer powers of base.
939 """
940 self.labelOnlyBase = labelOnlyBase
942 def set_locs(self, locs=None):
943 """
944 Use axis view limits to control which ticks are labeled.
946 The *locs* parameter is ignored in the present algorithm.
947 """
948 if np.isinf(self.minor_thresholds[0]):
949 self._sublabels = None
950 return
952 # Handle symlog case:
953 linthresh = self._linthresh
954 if linthresh is None:
955 try:
956 linthresh = self.axis.get_transform().linthresh
957 except AttributeError:
958 pass
960 vmin, vmax = self.axis.get_view_interval()
961 if vmin > vmax:
962 vmin, vmax = vmax, vmin
964 if linthresh is None and vmin <= 0:
965 # It's probably a colorbar with
966 # a format kwarg setting a LogFormatter in the manner
967 # that worked with 1.5.x, but that doesn't work now.
968 self._sublabels = {1} # label powers of base
969 return
971 b = self._base
972 if linthresh is not None: # symlog
973 # Only compute the number of decades in the logarithmic part of the
974 # axis
975 numdec = 0
976 if vmin < -linthresh:
977 rhs = min(vmax, -linthresh)
978 numdec += math.log(vmin / rhs) / math.log(b)
979 if vmax > linthresh:
980 lhs = max(vmin, linthresh)
981 numdec += math.log(vmax / lhs) / math.log(b)
982 else:
983 vmin = math.log(vmin) / math.log(b)
984 vmax = math.log(vmax) / math.log(b)
985 numdec = abs(vmax - vmin)
987 if numdec > self.minor_thresholds[0]:
988 # Label only bases
989 self._sublabels = {1}
990 elif numdec > self.minor_thresholds[1]:
991 # Add labels between bases at log-spaced coefficients;
992 # include base powers in case the locations include
993 # "major" and "minor" points, as in colorbar.
994 c = np.geomspace(1, b, int(b)//2 + 1)
995 self._sublabels = set(np.round(c))
996 # For base 10, this yields (1, 2, 3, 4, 6, 10).
997 else:
998 # Label all integer multiples of base**n.
999 self._sublabels = set(np.arange(1, b + 1))
1001 def _num_to_string(self, x, vmin, vmax):
1002 if x > 10000:
1003 s = '%1.0e' % x
1004 elif x < 1:
1005 s = '%1.0e' % x
1006 else:
1007 s = self._pprint_val(x, vmax - vmin)
1008 return s
1010 def __call__(self, x, pos=None):
1011 # docstring inherited
1012 if x == 0.0: # Symlog
1013 return '0'
1015 x = abs(x)
1016 b = self._base
1017 # only label the decades
1018 fx = math.log(x) / math.log(b)
1019 is_x_decade = _is_close_to_int(fx)
1020 exponent = round(fx) if is_x_decade else np.floor(fx)
1021 coeff = round(b ** (fx - exponent))
1023 if self.labelOnlyBase and not is_x_decade:
1024 return ''
1025 if self._sublabels is not None and coeff not in self._sublabels:
1026 return ''
1028 vmin, vmax = self.axis.get_view_interval()
1029 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
1030 s = self._num_to_string(x, vmin, vmax)
1031 return self.fix_minus(s)
1033 def format_data(self, value):
1034 with cbook._setattr_cm(self, labelOnlyBase=False):
1035 return cbook.strip_math(self.__call__(value))
1037 def format_data_short(self, value):
1038 # docstring inherited
1039 return '%-12g' % value
1041 def _pprint_val(self, x, d):
1042 # If the number is not too big and it's an int, format it as an int.
1043 if abs(x) < 1e4 and x == int(x):
1044 return '%d' % x
1045 fmt = ('%1.3e' if d < 1e-2 else
1046 '%1.3f' if d <= 1 else
1047 '%1.2f' if d <= 10 else
1048 '%1.1f' if d <= 1e5 else
1049 '%1.1e')
1050 s = fmt % x
1051 tup = s.split('e')
1052 if len(tup) == 2:
1053 mantissa = tup[0].rstrip('0').rstrip('.')
1054 exponent = int(tup[1])
1055 if exponent:
1056 s = '%se%d' % (mantissa, exponent)
1057 else:
1058 s = mantissa
1059 else:
1060 s = s.rstrip('0').rstrip('.')
1061 return s
1064class LogFormatterExponent(LogFormatter):
1065 """
1066 Format values for log axis using ``exponent = log_base(value)``.
1067 """
1068 def _num_to_string(self, x, vmin, vmax):
1069 fx = math.log(x) / math.log(self._base)
1070 if abs(fx) > 10000:
1071 s = '%1.0g' % fx
1072 elif abs(fx) < 1:
1073 s = '%1.0g' % fx
1074 else:
1075 fd = math.log(vmax - vmin) / math.log(self._base)
1076 s = self._pprint_val(fx, fd)
1077 return s
1080class LogFormatterMathtext(LogFormatter):
1081 """
1082 Format values for log axis using ``exponent = log_base(value)``.
1083 """
1085 def _non_decade_format(self, sign_string, base, fx, usetex):
1086 """Return string for non-decade locations."""
1087 return r'$\mathdefault{%s%s^{%.2f}}$' % (sign_string, base, fx)
1089 def __call__(self, x, pos=None):
1090 # docstring inherited
1091 usetex = mpl.rcParams['text.usetex']
1092 min_exp = mpl.rcParams['axes.formatter.min_exponent']
1094 if x == 0: # Symlog
1095 return r'$\mathdefault{0}$'
1097 sign_string = '-' if x < 0 else ''
1098 x = abs(x)
1099 b = self._base
1101 # only label the decades
1102 fx = math.log(x) / math.log(b)
1103 is_x_decade = _is_close_to_int(fx)
1104 exponent = round(fx) if is_x_decade else np.floor(fx)
1105 coeff = round(b ** (fx - exponent))
1106 if is_x_decade:
1107 fx = round(fx)
1109 if self.labelOnlyBase and not is_x_decade:
1110 return ''
1111 if self._sublabels is not None and coeff not in self._sublabels:
1112 return ''
1114 # use string formatting of the base if it is not an integer
1115 if b % 1 == 0.0:
1116 base = '%d' % b
1117 else:
1118 base = '%s' % b
1120 if abs(fx) < min_exp:
1121 return r'$\mathdefault{%s%g}$' % (sign_string, x)
1122 elif not is_x_decade:
1123 return self._non_decade_format(sign_string, base, fx, usetex)
1124 else:
1125 return r'$\mathdefault{%s%s^{%d}}$' % (sign_string, base, fx)
1128class LogFormatterSciNotation(LogFormatterMathtext):
1129 """
1130 Format values following scientific notation in a logarithmic axis.
1131 """
1133 def _non_decade_format(self, sign_string, base, fx, usetex):
1134 """Return string for non-decade locations."""
1135 b = float(base)
1136 exponent = math.floor(fx)
1137 coeff = b ** (fx - exponent)
1138 if _is_close_to_int(coeff):
1139 coeff = round(coeff)
1140 return r'$\mathdefault{%s%g\times%s^{%d}}$' \
1141 % (sign_string, coeff, base, exponent)
1144class LogitFormatter(Formatter):
1145 """
1146 Probability formatter (using Math text).
1147 """
1149 def __init__(
1150 self,
1151 *,
1152 use_overline=False,
1153 one_half=r"\frac{1}{2}",
1154 minor=False,
1155 minor_threshold=25,
1156 minor_number=6,
1157 ):
1158 r"""
1159 Parameters
1160 ----------
1161 use_overline : bool, default: False
1162 If x > 1/2, with x = 1-v, indicate if x should be displayed as
1163 $\overline{v}$. The default is to display $1-v$.
1165 one_half : str, default: r"\frac{1}{2}"
1166 The string used to represent 1/2.
1168 minor : bool, default: False
1169 Indicate if the formatter is formatting minor ticks or not.
1170 Basically minor ticks are not labelled, except when only few ticks
1171 are provided, ticks with most space with neighbor ticks are
1172 labelled. See other parameters to change the default behavior.
1174 minor_threshold : int, default: 25
1175 Maximum number of locs for labelling some minor ticks. This
1176 parameter have no effect if minor is False.
1178 minor_number : int, default: 6
1179 Number of ticks which are labelled when the number of ticks is
1180 below the threshold.
1181 """
1182 self._use_overline = use_overline
1183 self._one_half = one_half
1184 self._minor = minor
1185 self._labelled = set()
1186 self._minor_threshold = minor_threshold
1187 self._minor_number = minor_number
1189 def use_overline(self, use_overline):
1190 r"""
1191 Switch display mode with overline for labelling p>1/2.
1193 Parameters
1194 ----------
1195 use_overline : bool, default: False
1196 If x > 1/2, with x = 1-v, indicate if x should be displayed as
1197 $\overline{v}$. The default is to display $1-v$.
1198 """
1199 self._use_overline = use_overline
1201 def set_one_half(self, one_half):
1202 r"""
1203 Set the way one half is displayed.
1205 one_half : str, default: r"\frac{1}{2}"
1206 The string used to represent 1/2.
1207 """
1208 self._one_half = one_half
1210 def set_minor_threshold(self, minor_threshold):
1211 """
1212 Set the threshold for labelling minors ticks.
1214 Parameters
1215 ----------
1216 minor_threshold : int
1217 Maximum number of locations for labelling some minor ticks. This
1218 parameter have no effect if minor is False.
1219 """
1220 self._minor_threshold = minor_threshold
1222 def set_minor_number(self, minor_number):
1223 """
1224 Set the number of minor ticks to label when some minor ticks are
1225 labelled.
1227 Parameters
1228 ----------
1229 minor_number : int
1230 Number of ticks which are labelled when the number of ticks is
1231 below the threshold.
1232 """
1233 self._minor_number = minor_number
1235 def set_locs(self, locs):
1236 self.locs = np.array(locs)
1237 self._labelled.clear()
1239 if not self._minor:
1240 return None
1241 if all(
1242 _is_decade(x, rtol=1e-7)
1243 or _is_decade(1 - x, rtol=1e-7)
1244 or (_is_close_to_int(2 * x) and
1245 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(math.log10(x))
1318 s = "10^{%d}" % exponent
1319 elif x > 0.5 and _is_decade(1 - x, rtol=1e-7):
1320 exponent = round(math.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 # docstring inherited
1332 # Thresholds chosen to use scientific notation iff exponent <= -2.
1333 if value < 0.1:
1334 return "{:e}".format(value)
1335 if value < 0.9:
1336 return "{:f}".format(value)
1337 return "1-{:e}".format(1 - value)
1340class EngFormatter(Formatter):
1341 """
1342 Format axis values using engineering prefixes to represent powers
1343 of 1000, plus a specified unit, e.g., 10 MHz instead of 1e7.
1344 """
1346 # The SI engineering prefixes
1347 ENG_PREFIXES = {
1348 -24: "y",
1349 -21: "z",
1350 -18: "a",
1351 -15: "f",
1352 -12: "p",
1353 -9: "n",
1354 -6: "\N{MICRO SIGN}",
1355 -3: "m",
1356 0: "",
1357 3: "k",
1358 6: "M",
1359 9: "G",
1360 12: "T",
1361 15: "P",
1362 18: "E",
1363 21: "Z",
1364 24: "Y"
1365 }
1367 def __init__(self, unit="", places=None, sep=" ", *, usetex=None,
1368 useMathText=None):
1369 r"""
1370 Parameters
1371 ----------
1372 unit : str, default: ""
1373 Unit symbol to use, suitable for use with single-letter
1374 representations of powers of 1000. For example, 'Hz' or 'm'.
1376 places : int, default: None
1377 Precision with which to display the number, specified in
1378 digits after the decimal point (there will be between one
1379 and three digits before the decimal point). If it is None,
1380 the formatting falls back to the floating point format '%g',
1381 which displays up to 6 *significant* digits, i.e. the equivalent
1382 value for *places* varies between 0 and 5 (inclusive).
1384 sep : str, default: " "
1385 Separator used between the value and the prefix/unit. For
1386 example, one get '3.14 mV' if ``sep`` is " " (default) and
1387 '3.14mV' if ``sep`` is "". Besides the default behavior, some
1388 other useful options may be:
1390 * ``sep=""`` to append directly the prefix/unit to the value;
1391 * ``sep="\N{THIN SPACE}"`` (``U+2009``);
1392 * ``sep="\N{NARROW NO-BREAK SPACE}"`` (``U+202F``);
1393 * ``sep="\N{NO-BREAK SPACE}"`` (``U+00A0``).
1395 usetex : bool, default: :rc:`text.usetex`
1396 To enable/disable the use of TeX's math mode for rendering the
1397 numbers in the formatter.
1399 useMathText : bool, default: :rc:`axes.formatter.use_mathtext`
1400 To enable/disable the use mathtext for rendering the numbers in
1401 the formatter.
1402 """
1403 self.unit = unit
1404 self.places = places
1405 self.sep = sep
1406 self.set_usetex(usetex)
1407 self.set_useMathText(useMathText)
1409 def get_usetex(self):
1410 return self._usetex
1412 def set_usetex(self, val):
1413 if val is None:
1414 self._usetex = mpl.rcParams['text.usetex']
1415 else:
1416 self._usetex = val
1418 usetex = property(fget=get_usetex, fset=set_usetex)
1420 def get_useMathText(self):
1421 return self._useMathText
1423 def set_useMathText(self, val):
1424 if val is None:
1425 self._useMathText = mpl.rcParams['axes.formatter.use_mathtext']
1426 else:
1427 self._useMathText = val
1429 useMathText = property(fget=get_useMathText, fset=set_useMathText)
1431 def __call__(self, x, pos=None):
1432 s = "%s%s" % (self.format_eng(x), self.unit)
1433 # Remove the trailing separator when there is neither prefix nor unit
1434 if self.sep and s.endswith(self.sep):
1435 s = s[:-len(self.sep)]
1436 return self.fix_minus(s)
1438 def format_eng(self, num):
1439 """
1440 Format a number in engineering notation, appending a letter
1441 representing the power of 1000 of the original number.
1442 Some examples:
1444 >>> format_eng(0) # for self.places = 0
1445 '0'
1447 >>> format_eng(1000000) # for self.places = 1
1448 '1.0 M'
1450 >>> format_eng("-1e-6") # for self.places = 2
1451 '-1.00 \N{MICRO SIGN}'
1452 """
1453 sign = 1
1454 fmt = "g" if self.places is None else ".{:d}f".format(self.places)
1456 if num < 0:
1457 sign = -1
1458 num = -num
1460 if num != 0:
1461 pow10 = int(math.floor(math.log10(num) / 3) * 3)
1462 else:
1463 pow10 = 0
1464 # Force num to zero, to avoid inconsistencies like
1465 # format_eng(-0) = "0" and format_eng(0.0) = "0"
1466 # but format_eng(-0.0) = "-0.0"
1467 num = 0.0
1469 pow10 = np.clip(pow10, min(self.ENG_PREFIXES), max(self.ENG_PREFIXES))
1471 mant = sign * num / (10.0 ** pow10)
1472 # Taking care of the cases like 999.9..., which may be rounded to 1000
1473 # instead of 1 k. Beware of the corner case of values that are beyond
1474 # the range of SI prefixes (i.e. > 'Y').
1475 if (abs(float(format(mant, fmt))) >= 1000
1476 and pow10 < max(self.ENG_PREFIXES)):
1477 mant /= 1000
1478 pow10 += 3
1480 prefix = self.ENG_PREFIXES[int(pow10)]
1481 if self._usetex or self._useMathText:
1482 formatted = "${mant:{fmt}}${sep}{prefix}".format(
1483 mant=mant, sep=self.sep, prefix=prefix, fmt=fmt)
1484 else:
1485 formatted = "{mant:{fmt}}{sep}{prefix}".format(
1486 mant=mant, sep=self.sep, prefix=prefix, fmt=fmt)
1488 return formatted
1491class PercentFormatter(Formatter):
1492 """
1493 Format numbers as a percentage.
1495 Parameters
1496 ----------
1497 xmax : float
1498 Determines how the number is converted into a percentage.
1499 *xmax* is the data value that corresponds to 100%.
1500 Percentages are computed as ``x / xmax * 100``. So if the data is
1501 already scaled to be percentages, *xmax* will be 100. Another common
1502 situation is where *xmax* is 1.0.
1504 decimals : None or int
1505 The number of decimal places to place after the point.
1506 If *None* (the default), the number will be computed automatically.
1508 symbol : str or None
1509 A string that will be appended to the label. It may be
1510 *None* or empty to indicate that no symbol should be used. LaTeX
1511 special characters are escaped in *symbol* whenever latex mode is
1512 enabled, unless *is_latex* is *True*.
1514 is_latex : bool
1515 If *False*, reserved LaTeX characters in *symbol* will be escaped.
1516 """
1517 def __init__(self, xmax=100, decimals=None, symbol='%', is_latex=False):
1518 self.xmax = xmax + 0.0
1519 self.decimals = decimals
1520 self._symbol = symbol
1521 self._is_latex = is_latex
1523 def __call__(self, x, pos=None):
1524 """Format the tick as a percentage with the appropriate scaling."""
1525 ax_min, ax_max = self.axis.get_view_interval()
1526 display_range = abs(ax_max - ax_min)
1527 return self.fix_minus(self.format_pct(x, display_range))
1529 def format_pct(self, x, display_range):
1530 """
1531 Format the number as a percentage number with the correct
1532 number of decimals and adds the percent symbol, if any.
1534 If ``self.decimals`` is `None`, the number of digits after the
1535 decimal point is set based on the *display_range* of the axis
1536 as follows:
1538 +---------------+----------+------------------------+
1539 | display_range | decimals | sample |
1540 +---------------+----------+------------------------+
1541 | >50 | 0 | ``x = 34.5`` => 35% |
1542 +---------------+----------+------------------------+
1543 | >5 | 1 | ``x = 34.5`` => 34.5% |
1544 +---------------+----------+------------------------+
1545 | >0.5 | 2 | ``x = 34.5`` => 34.50% |
1546 +---------------+----------+------------------------+
1547 | ... | ... | ... |
1548 +---------------+----------+------------------------+
1550 This method will not be very good for tiny axis ranges or
1551 extremely large ones. It assumes that the values on the chart
1552 are percentages displayed on a reasonable scale.
1553 """
1554 x = self.convert_to_pct(x)
1555 if self.decimals is None:
1556 # conversion works because display_range is a difference
1557 scaled_range = self.convert_to_pct(display_range)
1558 if scaled_range <= 0:
1559 decimals = 0
1560 else:
1561 # Luckily Python's built-in ceil rounds to +inf, not away from
1562 # zero. This is very important since the equation for decimals
1563 # starts out as `scaled_range > 0.5 * 10**(2 - decimals)`
1564 # and ends up with `decimals > 2 - log10(2 * scaled_range)`.
1565 decimals = math.ceil(2.0 - math.log10(2.0 * scaled_range))
1566 if decimals > 5:
1567 decimals = 5
1568 elif decimals < 0:
1569 decimals = 0
1570 else:
1571 decimals = self.decimals
1572 s = '{x:0.{decimals}f}'.format(x=x, decimals=int(decimals))
1574 return s + self.symbol
1576 def convert_to_pct(self, x):
1577 return 100.0 * (x / self.xmax)
1579 @property
1580 def symbol(self):
1581 r"""
1582 The configured percent symbol as a string.
1584 If LaTeX is enabled via :rc:`text.usetex`, the special characters
1585 ``{'#', '$', '%', '&', '~', '_', '^', '\', '{', '}'}`` are
1586 automatically escaped in the string.
1587 """
1588 symbol = self._symbol
1589 if not symbol:
1590 symbol = ''
1591 elif mpl.rcParams['text.usetex'] and not self._is_latex:
1592 # Source: http://www.personal.ceu.hu/tex/specchar.htm
1593 # Backslash must be first for this to work correctly since
1594 # it keeps getting added in
1595 for spec in r'\#$%&~_^{}':
1596 symbol = symbol.replace(spec, '\\' + spec)
1597 return symbol
1599 @symbol.setter
1600 def symbol(self, symbol):
1601 self._symbol = symbol
1604class Locator(TickHelper):
1605 """
1606 Determine the tick locations;
1608 Note that the same locator should not be used across multiple
1609 `~matplotlib.axis.Axis` because the locator stores references to the Axis
1610 data and view limits.
1611 """
1613 # Some automatic tick locators can generate so many ticks they
1614 # kill the machine when you try and render them.
1615 # This parameter is set to cause locators to raise an error if too
1616 # many ticks are generated.
1617 MAXTICKS = 1000
1619 def tick_values(self, vmin, vmax):
1620 """
1621 Return the values of the located ticks given **vmin** and **vmax**.
1623 .. note::
1624 To get tick locations with the vmin and vmax values defined
1625 automatically for the associated ``axis`` simply call
1626 the Locator instance::
1628 >>> print(type(loc))
1629 <type 'Locator'>
1630 >>> print(loc())
1631 [1, 2, 3, 4]
1633 """
1634 raise NotImplementedError('Derived must override')
1636 def set_params(self, **kwargs):
1637 """
1638 Do nothing, and raise a warning. Any locator class not supporting the
1639 set_params() function will call this.
1640 """
1641 _api.warn_external(
1642 "'set_params()' not defined for locator of type " +
1643 str(type(self)))
1645 def __call__(self):
1646 """Return the locations of the ticks."""
1647 # note: some locators return data limits, other return view limits,
1648 # hence there is no *one* interface to call self.tick_values.
1649 raise NotImplementedError('Derived must override')
1651 def raise_if_exceeds(self, locs):
1652 """
1653 Log at WARNING level if *locs* is longer than `Locator.MAXTICKS`.
1655 This is intended to be called immediately before returning *locs* from
1656 ``__call__`` to inform users in case their Locator returns a huge
1657 number of ticks, causing Matplotlib to run out of memory.
1659 The "strange" name of this method dates back to when it would raise an
1660 exception instead of emitting a log.
1661 """
1662 if len(locs) >= self.MAXTICKS:
1663 _log.warning(
1664 "Locator attempting to generate %s ticks ([%s, ..., %s]), "
1665 "which exceeds Locator.MAXTICKS (%s).",
1666 len(locs), locs[0], locs[-1], self.MAXTICKS)
1667 return locs
1669 def nonsingular(self, v0, v1):
1670 """
1671 Adjust a range as needed to avoid singularities.
1673 This method gets called during autoscaling, with ``(v0, v1)`` set to
1674 the data limits on the axes if the axes contains any data, or
1675 ``(-inf, +inf)`` if not.
1677 - If ``v0 == v1`` (possibly up to some floating point slop), this
1678 method returns an expanded interval around this value.
1679 - If ``(v0, v1) == (-inf, +inf)``, this method returns appropriate
1680 default view limits.
1681 - Otherwise, ``(v0, v1)`` is returned without modification.
1682 """
1683 return mtransforms.nonsingular(v0, v1, expander=.05)
1685 def view_limits(self, vmin, vmax):
1686 """
1687 Select a scale for the range from vmin to vmax.
1689 Subclasses should override this method to change locator behaviour.
1690 """
1691 return mtransforms.nonsingular(vmin, vmax)
1694class IndexLocator(Locator):
1695 """
1696 Place a tick on every multiple of some base number of points
1697 plotted, e.g., on every 5th point. It is assumed that you are doing
1698 index plotting; i.e., the axis is 0, len(data). This is mainly
1699 useful for x ticks.
1700 """
1701 def __init__(self, base, offset):
1702 """Place ticks every *base* data point, starting at *offset*."""
1703 self._base = base
1704 self.offset = offset
1706 def set_params(self, base=None, offset=None):
1707 """Set parameters within this locator"""
1708 if base is not None:
1709 self._base = base
1710 if offset is not None:
1711 self.offset = offset
1713 def __call__(self):
1714 """Return the locations of the ticks"""
1715 dmin, dmax = self.axis.get_data_interval()
1716 return self.tick_values(dmin, dmax)
1718 def tick_values(self, vmin, vmax):
1719 return self.raise_if_exceeds(
1720 np.arange(vmin + self.offset, vmax + 1, self._base))
1723class FixedLocator(Locator):
1724 """
1725 Tick locations are fixed. If nbins is not None,
1726 the array of possible positions will be subsampled to
1727 keep the number of ticks <= nbins +1.
1728 The subsampling will be done so as to include the smallest
1729 absolute value; for example, if zero is included in the
1730 array of possibilities, then it is guaranteed to be one of
1731 the chosen ticks.
1732 """
1734 def __init__(self, locs, nbins=None):
1735 self.locs = np.asarray(locs)
1736 self.nbins = max(nbins, 2) if nbins is not None else None
1738 def set_params(self, nbins=None):
1739 """Set parameters within this locator."""
1740 if nbins is not None:
1741 self.nbins = nbins
1743 def __call__(self):
1744 return self.tick_values(None, None)
1746 def tick_values(self, vmin, vmax):
1747 """
1748 Return the locations of the ticks.
1750 .. note::
1752 Because the values are fixed, vmin and vmax are not used in this
1753 method.
1755 """
1756 if self.nbins is None:
1757 return self.locs
1758 step = max(int(np.ceil(len(self.locs) / self.nbins)), 1)
1759 ticks = self.locs[::step]
1760 for i in range(1, step):
1761 ticks1 = self.locs[i::step]
1762 if np.abs(ticks1).min() < np.abs(ticks).min():
1763 ticks = ticks1
1764 return self.raise_if_exceeds(ticks)
1767class NullLocator(Locator):
1768 """
1769 No ticks
1770 """
1772 def __call__(self):
1773 return self.tick_values(None, None)
1775 def tick_values(self, vmin, vmax):
1776 """
1777 Return the locations of the ticks.
1779 .. note::
1781 Because the values are Null, vmin and vmax are not used in this
1782 method.
1783 """
1784 return []
1787class LinearLocator(Locator):
1788 """
1789 Determine the tick locations
1791 The first time this function is called it will try to set the
1792 number of ticks to make a nice tick partitioning. Thereafter the
1793 number of ticks will be fixed so that interactive navigation will
1794 be nice
1796 """
1797 def __init__(self, numticks=None, presets=None):
1798 """
1799 Use presets to set locs based on lom. A dict mapping vmin, vmax->locs
1800 """
1801 self.numticks = numticks
1802 if presets is None:
1803 self.presets = {}
1804 else:
1805 self.presets = presets
1807 @property
1808 def numticks(self):
1809 # Old hard-coded default.
1810 return self._numticks if self._numticks is not None else 11
1812 @numticks.setter
1813 def numticks(self, numticks):
1814 self._numticks = numticks
1816 def set_params(self, numticks=None, presets=None):
1817 """Set parameters within this locator."""
1818 if presets is not None:
1819 self.presets = presets
1820 if numticks is not None:
1821 self.numticks = numticks
1823 def __call__(self):
1824 """Return the locations of the ticks."""
1825 vmin, vmax = self.axis.get_view_interval()
1826 return self.tick_values(vmin, vmax)
1828 def tick_values(self, vmin, vmax):
1829 vmin, vmax = mtransforms.nonsingular(vmin, vmax, expander=0.05)
1830 if vmax < vmin:
1831 vmin, vmax = vmax, vmin
1833 if (vmin, vmax) in self.presets:
1834 return self.presets[(vmin, vmax)]
1836 if self.numticks == 0:
1837 return []
1838 ticklocs = np.linspace(vmin, vmax, self.numticks)
1840 return self.raise_if_exceeds(ticklocs)
1842 def view_limits(self, vmin, vmax):
1843 """Try to choose the view limits intelligently."""
1845 if vmax < vmin:
1846 vmin, vmax = vmax, vmin
1848 if vmin == vmax:
1849 vmin -= 1
1850 vmax += 1
1852 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
1853 exponent, remainder = divmod(
1854 math.log10(vmax - vmin), math.log10(max(self.numticks - 1, 1)))
1855 exponent -= (remainder < .5)
1856 scale = max(self.numticks - 1, 1) ** (-exponent)
1857 vmin = math.floor(scale * vmin) / scale
1858 vmax = math.ceil(scale * vmax) / scale
1860 return mtransforms.nonsingular(vmin, vmax)
1863class MultipleLocator(Locator):
1864 """
1865 Set a tick on each integer multiple of a base within the view interval.
1866 """
1868 def __init__(self, base=1.0):
1869 self._edge = _Edge_integer(base, 0)
1871 def set_params(self, base):
1872 """Set parameters within this locator."""
1873 if base is not None:
1874 self._edge = _Edge_integer(base, 0)
1876 def __call__(self):
1877 """Return the locations of the ticks."""
1878 vmin, vmax = self.axis.get_view_interval()
1879 return self.tick_values(vmin, vmax)
1881 def tick_values(self, vmin, vmax):
1882 if vmax < vmin:
1883 vmin, vmax = vmax, vmin
1884 step = self._edge.step
1885 vmin = self._edge.ge(vmin) * step
1886 n = (vmax - vmin + 0.001 * step) // step
1887 locs = vmin - step + np.arange(n + 3) * step
1888 return self.raise_if_exceeds(locs)
1890 def view_limits(self, dmin, dmax):
1891 """
1892 Set the view limits to the nearest multiples of base that
1893 contain the data.
1894 """
1895 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
1896 vmin = self._edge.le(dmin) * self._edge.step
1897 vmax = self._edge.ge(dmax) * self._edge.step
1898 if vmin == vmax:
1899 vmin -= 1
1900 vmax += 1
1901 else:
1902 vmin = dmin
1903 vmax = dmax
1905 return mtransforms.nonsingular(vmin, vmax)
1908def scale_range(vmin, vmax, n=1, threshold=100):
1909 dv = abs(vmax - vmin) # > 0 as nonsingular is called before.
1910 meanv = (vmax + vmin) / 2
1911 if abs(meanv) / dv < threshold:
1912 offset = 0
1913 else:
1914 offset = math.copysign(10 ** (math.log10(abs(meanv)) // 1), meanv)
1915 scale = 10 ** (math.log10(dv / n) // 1)
1916 return scale, offset
1919class _Edge_integer:
1920 """
1921 Helper for MaxNLocator, MultipleLocator, etc.
1923 Take floating point precision limitations into account when calculating
1924 tick locations as integer multiples of a step.
1925 """
1926 def __init__(self, step, offset):
1927 """
1928 *step* is a positive floating-point interval between ticks.
1929 *offset* is the offset subtracted from the data limits
1930 prior to calculating tick locations.
1931 """
1932 if step <= 0:
1933 raise ValueError("'step' must be positive")
1934 self.step = step
1935 self._offset = abs(offset)
1937 def closeto(self, ms, edge):
1938 # Allow more slop when the offset is large compared to the step.
1939 if self._offset > 0:
1940 digits = np.log10(self._offset / self.step)
1941 tol = max(1e-10, 10 ** (digits - 12))
1942 tol = min(0.4999, tol)
1943 else:
1944 tol = 1e-10
1945 return abs(ms - edge) < tol
1947 def le(self, x):
1948 """Return the largest n: n*step <= x."""
1949 d, m = divmod(x, self.step)
1950 if self.closeto(m / self.step, 1):
1951 return d + 1
1952 return d
1954 def ge(self, x):
1955 """Return the smallest n: n*step >= x."""
1956 d, m = divmod(x, self.step)
1957 if self.closeto(m / self.step, 0):
1958 return d
1959 return d + 1
1962class MaxNLocator(Locator):
1963 """
1964 Find nice tick locations with no more than N being within the view limits.
1965 Locations beyond the limits are added to support autoscaling.
1966 """
1967 default_params = dict(nbins=10,
1968 steps=None,
1969 integer=False,
1970 symmetric=False,
1971 prune=None,
1972 min_n_ticks=2)
1974 def __init__(self, nbins=None, **kwargs):
1975 """
1976 Parameters
1977 ----------
1978 nbins : int or 'auto', default: 10
1979 Maximum number of intervals; one less than max number of
1980 ticks. If the string 'auto', the number of bins will be
1981 automatically determined based on the length of the axis.
1983 steps : array-like, optional
1984 Sequence of nice numbers starting with 1 and ending with 10;
1985 e.g., [1, 2, 4, 5, 10], where the values are acceptable
1986 tick multiples. i.e. for the example, 20, 40, 60 would be
1987 an acceptable set of ticks, as would 0.4, 0.6, 0.8, because
1988 they are multiples of 2. However, 30, 60, 90 would not
1989 be allowed because 3 does not appear in the list of steps.
1991 integer : bool, default: False
1992 If True, ticks will take only integer values, provided at least
1993 *min_n_ticks* integers are found within the view limits.
1995 symmetric : bool, default: False
1996 If True, autoscaling will result in a range symmetric about zero.
1998 prune : {'lower', 'upper', 'both', None}, default: None
1999 Remove edge ticks -- useful for stacked or ganged plots where
2000 the upper tick of one axes overlaps with the lower tick of the
2001 axes above it, primarily when :rc:`axes.autolimit_mode` is
2002 ``'round_numbers'``. If ``prune=='lower'``, the smallest tick will
2003 be removed. If ``prune == 'upper'``, the largest tick will be
2004 removed. If ``prune == 'both'``, the largest and smallest ticks
2005 will be removed. If *prune* is *None*, no ticks will be removed.
2007 min_n_ticks : int, default: 2
2008 Relax *nbins* and *integer* constraints if necessary to obtain
2009 this minimum number of ticks.
2010 """
2011 if nbins is not None:
2012 kwargs['nbins'] = nbins
2013 self.set_params(**{**self.default_params, **kwargs})
2015 @staticmethod
2016 def _validate_steps(steps):
2017 if not np.iterable(steps):
2018 raise ValueError('steps argument must be an increasing sequence '
2019 'of numbers between 1 and 10 inclusive')
2020 steps = np.asarray(steps)
2021 if np.any(np.diff(steps) <= 0) or steps[-1] > 10 or steps[0] < 1:
2022 raise ValueError('steps argument must be an increasing sequence '
2023 'of numbers between 1 and 10 inclusive')
2024 if steps[0] != 1:
2025 steps = np.concatenate([[1], steps])
2026 if steps[-1] != 10:
2027 steps = np.concatenate([steps, [10]])
2028 return steps
2030 @staticmethod
2031 def _staircase(steps):
2032 # Make an extended staircase within which the needed step will be
2033 # found. This is probably much larger than necessary.
2034 return np.concatenate([0.1 * steps[:-1], steps, [10 * steps[1]]])
2036 def set_params(self, **kwargs):
2037 """
2038 Set parameters for this locator.
2040 Parameters
2041 ----------
2042 nbins : int or 'auto', optional
2043 see `.MaxNLocator`
2044 steps : array-like, optional
2045 see `.MaxNLocator`
2046 integer : bool, optional
2047 see `.MaxNLocator`
2048 symmetric : bool, optional
2049 see `.MaxNLocator`
2050 prune : {'lower', 'upper', 'both', None}, optional
2051 see `.MaxNLocator`
2052 min_n_ticks : int, optional
2053 see `.MaxNLocator`
2054 """
2055 if 'nbins' in kwargs:
2056 self._nbins = kwargs.pop('nbins')
2057 if self._nbins != 'auto':
2058 self._nbins = int(self._nbins)
2059 if 'symmetric' in kwargs:
2060 self._symmetric = kwargs.pop('symmetric')
2061 if 'prune' in kwargs:
2062 prune = kwargs.pop('prune')
2063 _api.check_in_list(['upper', 'lower', 'both', None], prune=prune)
2064 self._prune = prune
2065 if 'min_n_ticks' in kwargs:
2066 self._min_n_ticks = max(1, kwargs.pop('min_n_ticks'))
2067 if 'steps' in kwargs:
2068 steps = kwargs.pop('steps')
2069 if steps is None:
2070 self._steps = np.array([1, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10])
2071 else:
2072 self._steps = self._validate_steps(steps)
2073 self._extended_steps = self._staircase(self._steps)
2074 if 'integer' in kwargs:
2075 self._integer = kwargs.pop('integer')
2076 if kwargs:
2077 key, _ = kwargs.popitem()
2078 raise TypeError(
2079 f"set_params() got an unexpected keyword argument '{key}'")
2081 def _raw_ticks(self, vmin, vmax):
2082 """
2083 Generate a list of tick locations including the range *vmin* to
2084 *vmax*. In some applications, one or both of the end locations
2085 will not be needed, in which case they are trimmed off
2086 elsewhere.
2087 """
2088 if self._nbins == 'auto':
2089 if self.axis is not None:
2090 nbins = np.clip(self.axis.get_tick_space(),
2091 max(1, self._min_n_ticks - 1), 9)
2092 else:
2093 nbins = 9
2094 else:
2095 nbins = self._nbins
2097 scale, offset = scale_range(vmin, vmax, nbins)
2098 _vmin = vmin - offset
2099 _vmax = vmax - offset
2100 raw_step = (_vmax - _vmin) / nbins
2101 steps = self._extended_steps * scale
2102 if self._integer:
2103 # For steps > 1, keep only integer values.
2104 igood = (steps < 1) | (np.abs(steps - np.round(steps)) < 0.001)
2105 steps = steps[igood]
2107 istep = np.nonzero(steps >= raw_step)[0][0]
2109 # Classic round_numbers mode may require a larger step.
2110 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
2111 for istep in range(istep, len(steps)):
2112 step = steps[istep]
2113 best_vmin = (_vmin // step) * step
2114 best_vmax = best_vmin + step * nbins
2115 if best_vmax >= _vmax:
2116 break
2118 # This is an upper limit; move to smaller steps if necessary.
2119 for istep in reversed(range(istep + 1)):
2120 step = steps[istep]
2122 if (self._integer and
2123 np.floor(_vmax) - np.ceil(_vmin) >= self._min_n_ticks - 1):
2124 step = max(1, step)
2125 best_vmin = (_vmin // step) * step
2127 # Find tick locations spanning the vmin-vmax range, taking into
2128 # account degradation of precision when there is a large offset.
2129 # The edge ticks beyond vmin and/or vmax are needed for the
2130 # "round_numbers" autolimit mode.
2131 edge = _Edge_integer(step, offset)
2132 low = edge.le(_vmin - best_vmin)
2133 high = edge.ge(_vmax - best_vmin)
2134 ticks = np.arange(low, high + 1) * step + best_vmin
2135 # Count only the ticks that will be displayed.
2136 nticks = ((ticks <= _vmax) & (ticks >= _vmin)).sum()
2137 if nticks >= self._min_n_ticks:
2138 break
2139 return ticks + offset
2141 def __call__(self):
2142 vmin, vmax = self.axis.get_view_interval()
2143 return self.tick_values(vmin, vmax)
2145 def tick_values(self, vmin, vmax):
2146 if self._symmetric:
2147 vmax = max(abs(vmin), abs(vmax))
2148 vmin = -vmax
2149 vmin, vmax = mtransforms.nonsingular(
2150 vmin, vmax, expander=1e-13, tiny=1e-14)
2151 locs = self._raw_ticks(vmin, vmax)
2153 prune = self._prune
2154 if prune == 'lower':
2155 locs = locs[1:]
2156 elif prune == 'upper':
2157 locs = locs[:-1]
2158 elif prune == 'both':
2159 locs = locs[1:-1]
2160 return self.raise_if_exceeds(locs)
2162 def view_limits(self, dmin, dmax):
2163 if self._symmetric:
2164 dmax = max(abs(dmin), abs(dmax))
2165 dmin = -dmax
2167 dmin, dmax = mtransforms.nonsingular(
2168 dmin, dmax, expander=1e-12, tiny=1e-13)
2170 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
2171 return self._raw_ticks(dmin, dmax)[[0, -1]]
2172 else:
2173 return dmin, dmax
2176@_api.deprecated("3.6")
2177def is_decade(x, base=10, *, rtol=1e-10):
2178 if not np.isfinite(x):
2179 return False
2180 if x == 0.0:
2181 return True
2182 lx = np.log(abs(x)) / np.log(base)
2183 return is_close_to_int(lx, atol=rtol)
2186def _is_decade(x, *, base=10, rtol=None):
2187 """Return True if *x* is an integer power of *base*."""
2188 if not np.isfinite(x):
2189 return False
2190 if x == 0.0:
2191 return True
2192 lx = np.log(abs(x)) / np.log(base)
2193 if rtol is None:
2194 return np.isclose(lx, np.round(lx))
2195 else:
2196 return np.isclose(lx, np.round(lx), rtol=rtol)
2199def _decade_less_equal(x, base):
2200 """
2201 Return the largest integer power of *base* that's less or equal to *x*.
2203 If *x* is negative, the exponent will be *greater*.
2204 """
2205 return (x if x == 0 else
2206 -_decade_greater_equal(-x, base) if x < 0 else
2207 base ** np.floor(np.log(x) / np.log(base)))
2210def _decade_greater_equal(x, base):
2211 """
2212 Return the smallest integer power of *base* that's greater or equal to *x*.
2214 If *x* is negative, the exponent will be *smaller*.
2215 """
2216 return (x if x == 0 else
2217 -_decade_less_equal(-x, base) if x < 0 else
2218 base ** np.ceil(np.log(x) / np.log(base)))
2221def _decade_less(x, base):
2222 """
2223 Return the largest integer power of *base* that's less than *x*.
2225 If *x* is negative, the exponent will be *greater*.
2226 """
2227 if x < 0:
2228 return -_decade_greater(-x, base)
2229 less = _decade_less_equal(x, base)
2230 if less == x:
2231 less /= base
2232 return less
2235def _decade_greater(x, base):
2236 """
2237 Return the smallest integer power of *base* that's greater than *x*.
2239 If *x* is negative, the exponent will be *smaller*.
2240 """
2241 if x < 0:
2242 return -_decade_less(-x, base)
2243 greater = _decade_greater_equal(x, base)
2244 if greater == x:
2245 greater *= base
2246 return greater
2249@_api.deprecated("3.6")
2250def is_close_to_int(x, *, atol=1e-10):
2251 return abs(x - np.round(x)) < atol
2254def _is_close_to_int(x):
2255 return math.isclose(x, round(x))
2258class LogLocator(Locator):
2259 """
2260 Determine the tick locations for log axes
2261 """
2263 def __init__(self, base=10.0, subs=(1.0,), numdecs=4, numticks=None):
2264 """
2265 Place ticks at values ``subs[j] * base**n``.
2267 Parameters
2268 ----------
2269 base : float, default: 10.0
2270 The base of the log used, so major ticks are placed at
2271 ``base**n``, where ``n`` is an integer.
2272 subs : None or {'auto', 'all'} or sequence of float, default: (1.0,)
2273 Gives the multiples of integer powers of the base at which
2274 to place ticks. The default of ``(1.0, )`` places ticks only at
2275 integer powers of the base.
2276 Permitted string values are ``'auto'`` and ``'all'``.
2277 Both of these use an algorithm based on the axis view
2278 limits to determine whether and how to put ticks between
2279 integer powers of the base. With ``'auto'``, ticks are
2280 placed only between integer powers; with ``'all'``, the
2281 integer powers are included. A value of None is
2282 equivalent to ``'auto'``.
2283 numticks : None or int, default: None
2284 The maximum number of ticks to allow on a given axis. The default
2285 of ``None`` will try to choose intelligently as long as this
2286 Locator has already been assigned to an axis using
2287 `~.axis.Axis.get_tick_space`, but otherwise falls back to 9.
2288 """
2289 if numticks is None:
2290 if mpl.rcParams['_internal.classic_mode']:
2291 numticks = 15
2292 else:
2293 numticks = 'auto'
2294 self._base = float(base)
2295 self._set_subs(subs)
2296 self.numdecs = numdecs
2297 self.numticks = numticks
2299 def set_params(self, base=None, subs=None, numdecs=None, numticks=None):
2300 """Set parameters within this locator."""
2301 if base is not None:
2302 self._base = float(base)
2303 if subs is not None:
2304 self._set_subs(subs)
2305 if numdecs is not None:
2306 self.numdecs = numdecs
2307 if numticks is not None:
2308 self.numticks = numticks
2310 @_api.deprecated("3.6", alternative='set_params(base=...)')
2311 def base(self, base):
2312 """Set the log base (major tick every ``base**i``, i integer)."""
2313 self._base = float(base)
2315 @_api.deprecated("3.6", alternative='set_params(subs=...)')
2316 def subs(self, subs):
2317 """
2318 Set the minor ticks for the log scaling every ``base**i*subs[j]``.
2319 """
2320 self._set_subs(subs)
2322 def _set_subs(self, subs):
2323 """
2324 Set the minor ticks for the log scaling every ``base**i*subs[j]``.
2325 """
2326 if subs is None: # consistency with previous bad API
2327 self._subs = 'auto'
2328 elif isinstance(subs, str):
2329 _api.check_in_list(('all', 'auto'), subs=subs)
2330 self._subs = subs
2331 else:
2332 try:
2333 self._subs = np.asarray(subs, dtype=float)
2334 except ValueError as e:
2335 raise ValueError("subs must be None, 'all', 'auto' or "
2336 "a sequence of floats, not "
2337 "{}.".format(subs)) from e
2338 if self._subs.ndim != 1:
2339 raise ValueError("A sequence passed to subs must be "
2340 "1-dimensional, not "
2341 "{}-dimensional.".format(self._subs.ndim))
2343 def __call__(self):
2344 """Return the locations of the ticks."""
2345 vmin, vmax = self.axis.get_view_interval()
2346 return self.tick_values(vmin, vmax)
2348 def tick_values(self, vmin, vmax):
2349 if self.numticks == 'auto':
2350 if self.axis is not None:
2351 numticks = np.clip(self.axis.get_tick_space(), 2, 9)
2352 else:
2353 numticks = 9
2354 else:
2355 numticks = self.numticks
2357 b = self._base
2358 # dummy axis has no axes attribute
2359 if hasattr(self.axis, 'axes') and self.axis.axes.name == 'polar':
2360 vmax = math.ceil(math.log(vmax) / math.log(b))
2361 decades = np.arange(vmax - self.numdecs, vmax)
2362 ticklocs = b ** decades
2364 return ticklocs
2366 if vmin <= 0.0:
2367 if self.axis is not None:
2368 vmin = self.axis.get_minpos()
2370 if vmin <= 0.0 or not np.isfinite(vmin):
2371 raise ValueError(
2372 "Data has no positive values, and therefore can not be "
2373 "log-scaled.")
2375 _log.debug('vmin %s vmax %s', vmin, vmax)
2377 if vmax < vmin:
2378 vmin, vmax = vmax, vmin
2379 log_vmin = math.log(vmin) / math.log(b)
2380 log_vmax = math.log(vmax) / math.log(b)
2382 numdec = math.floor(log_vmax) - math.ceil(log_vmin)
2384 if isinstance(self._subs, str):
2385 _first = 2.0 if self._subs == 'auto' else 1.0
2386 if numdec > 10 or b < 3:
2387 if self._subs == 'auto':
2388 return np.array([]) # no minor or major ticks
2389 else:
2390 subs = np.array([1.0]) # major ticks
2391 else:
2392 subs = np.arange(_first, b)
2393 else:
2394 subs = self._subs
2396 # Get decades between major ticks.
2397 stride = (max(math.ceil(numdec / (numticks - 1)), 1)
2398 if mpl.rcParams['_internal.classic_mode'] else
2399 (numdec + 1) // numticks + 1)
2401 # if we have decided that the stride is as big or bigger than
2402 # the range, clip the stride back to the available range - 1
2403 # with a floor of 1. This prevents getting axis with only 1 tick
2404 # visible.
2405 if stride >= numdec:
2406 stride = max(1, numdec - 1)
2408 # Does subs include anything other than 1? Essentially a hack to know
2409 # whether we're a major or a minor locator.
2410 have_subs = len(subs) > 1 or (len(subs) == 1 and subs[0] != 1.0)
2412 decades = np.arange(math.floor(log_vmin) - stride,
2413 math.ceil(log_vmax) + 2 * stride, stride)
2415 if hasattr(self, '_transform'):
2416 ticklocs = self._transform.inverted().transform(decades)
2417 if have_subs:
2418 if stride == 1:
2419 ticklocs = np.ravel(np.outer(subs, ticklocs))
2420 else:
2421 # No ticklocs if we have >1 decade between major ticks.
2422 ticklocs = np.array([])
2423 else:
2424 if have_subs:
2425 if stride == 1:
2426 ticklocs = np.concatenate(
2427 [subs * decade_start for decade_start in b ** decades])
2428 else:
2429 ticklocs = np.array([])
2430 else:
2431 ticklocs = b ** decades
2433 _log.debug('ticklocs %r', ticklocs)
2434 if (len(subs) > 1
2435 and stride == 1
2436 and ((vmin <= ticklocs) & (ticklocs <= vmax)).sum() <= 1):
2437 # If we're a minor locator *that expects at least two ticks per
2438 # decade* and the major locator stride is 1 and there's no more
2439 # than one minor tick, switch to AutoLocator.
2440 return AutoLocator().tick_values(vmin, vmax)
2441 else:
2442 return self.raise_if_exceeds(ticklocs)
2444 def view_limits(self, vmin, vmax):
2445 """Try to choose the view limits intelligently."""
2446 b = self._base
2448 vmin, vmax = self.nonsingular(vmin, vmax)
2450 if self.axis.axes.name == 'polar':
2451 vmax = math.ceil(math.log(vmax) / math.log(b))
2452 vmin = b ** (vmax - self.numdecs)
2454 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
2455 vmin = _decade_less_equal(vmin, self._base)
2456 vmax = _decade_greater_equal(vmax, self._base)
2458 return vmin, vmax
2460 def nonsingular(self, vmin, vmax):
2461 if vmin > vmax:
2462 vmin, vmax = vmax, vmin
2463 if not np.isfinite(vmin) or not np.isfinite(vmax):
2464 vmin, vmax = 1, 10 # Initial range, no data plotted yet.
2465 elif vmax <= 0:
2466 _api.warn_external(
2467 "Data has no positive values, and therefore cannot be "
2468 "log-scaled.")
2469 vmin, vmax = 1, 10
2470 else:
2471 minpos = self.axis.get_minpos()
2472 if not np.isfinite(minpos):
2473 minpos = 1e-300 # This should never take effect.
2474 if vmin <= 0:
2475 vmin = minpos
2476 if vmin == vmax:
2477 vmin = _decade_less(vmin, self._base)
2478 vmax = _decade_greater(vmax, self._base)
2479 return vmin, vmax
2482class SymmetricalLogLocator(Locator):
2483 """
2484 Determine the tick locations for symmetric log axes.
2485 """
2487 def __init__(self, transform=None, subs=None, linthresh=None, base=None):
2488 """
2489 Parameters
2490 ----------
2491 transform : `~.scale.SymmetricalLogTransform`, optional
2492 If set, defines the *base* and *linthresh* of the symlog transform.
2493 base, linthresh : float, optional
2494 The *base* and *linthresh* of the symlog transform, as documented
2495 for `.SymmetricalLogScale`. These parameters are only used if
2496 *transform* is not set.
2497 subs : sequence of float, default: [1]
2498 The multiples of integer powers of the base where ticks are placed,
2499 i.e., ticks are placed at
2500 ``[sub * base**i for i in ... for sub in subs]``.
2502 Notes
2503 -----
2504 Either *transform*, or both *base* and *linthresh*, must be given.
2505 """
2506 if transform is not None:
2507 self._base = transform.base
2508 self._linthresh = transform.linthresh
2509 elif linthresh is not None and base is not None:
2510 self._base = base
2511 self._linthresh = linthresh
2512 else:
2513 raise ValueError("Either transform, or both linthresh "
2514 "and base, must be provided.")
2515 if subs is None:
2516 self._subs = [1.0]
2517 else:
2518 self._subs = subs
2519 self.numticks = 15
2521 def set_params(self, subs=None, numticks=None):
2522 """Set parameters within this locator."""
2523 if numticks is not None:
2524 self.numticks = numticks
2525 if subs is not None:
2526 self._subs = subs
2528 def __call__(self):
2529 """Return the locations of the ticks."""
2530 # Note, these are untransformed coordinates
2531 vmin, vmax = self.axis.get_view_interval()
2532 return self.tick_values(vmin, vmax)
2534 def tick_values(self, vmin, vmax):
2535 base = self._base
2536 linthresh = self._linthresh
2538 if vmax < vmin:
2539 vmin, vmax = vmax, vmin
2541 # The domain is divided into three sections, only some of
2542 # which may actually be present.
2543 #
2544 # <======== -t ==0== t ========>
2545 # aaaaaaaaa bbbbb ccccccccc
2546 #
2547 # a) and c) will have ticks at integral log positions. The
2548 # number of ticks needs to be reduced if there are more
2549 # than self.numticks of them.
2550 #
2551 # b) has a tick at 0 and only 0 (we assume t is a small
2552 # number, and the linear segment is just an implementation
2553 # detail and not interesting.)
2554 #
2555 # We could also add ticks at t, but that seems to usually be
2556 # uninteresting.
2557 #
2558 # "simple" mode is when the range falls entirely within (-t,
2559 # t) -- it should just display (vmin, 0, vmax)
2560 if -linthresh < vmin < vmax < linthresh:
2561 # only the linear range is present
2562 return [vmin, vmax]
2564 # Lower log range is present
2565 has_a = (vmin < -linthresh)
2566 # Upper log range is present
2567 has_c = (vmax > linthresh)
2569 # Check if linear range is present
2570 has_b = (has_a and vmax > -linthresh) or (has_c and vmin < linthresh)
2572 def get_log_range(lo, hi):
2573 lo = np.floor(np.log(lo) / np.log(base))
2574 hi = np.ceil(np.log(hi) / np.log(base))
2575 return lo, hi
2577 # Calculate all the ranges, so we can determine striding
2578 a_lo, a_hi = (0, 0)
2579 if has_a:
2580 a_upper_lim = min(-linthresh, vmax)
2581 a_lo, a_hi = get_log_range(abs(a_upper_lim), abs(vmin) + 1)
2583 c_lo, c_hi = (0, 0)
2584 if has_c:
2585 c_lower_lim = max(linthresh, vmin)
2586 c_lo, c_hi = get_log_range(c_lower_lim, vmax + 1)
2588 # Calculate the total number of integer exponents in a and c ranges
2589 total_ticks = (a_hi - a_lo) + (c_hi - c_lo)
2590 if has_b:
2591 total_ticks += 1
2592 stride = max(total_ticks // (self.numticks - 1), 1)
2594 decades = []
2595 if has_a:
2596 decades.extend(-1 * (base ** (np.arange(a_lo, a_hi,
2597 stride)[::-1])))
2599 if has_b:
2600 decades.append(0.0)
2602 if has_c:
2603 decades.extend(base ** (np.arange(c_lo, c_hi, stride)))
2605 # Add the subticks if requested
2606 if self._subs is None:
2607 subs = np.arange(2.0, base)
2608 else:
2609 subs = np.asarray(self._subs)
2611 if len(subs) > 1 or subs[0] != 1.0:
2612 ticklocs = []
2613 for decade in decades:
2614 if decade == 0:
2615 ticklocs.append(decade)
2616 else:
2617 ticklocs.extend(subs * decade)
2618 else:
2619 ticklocs = decades
2621 return self.raise_if_exceeds(np.array(ticklocs))
2623 def view_limits(self, vmin, vmax):
2624 """Try to choose the view limits intelligently."""
2625 b = self._base
2626 if vmax < vmin:
2627 vmin, vmax = vmax, vmin
2629 if mpl.rcParams['axes.autolimit_mode'] == 'round_numbers':
2630 vmin = _decade_less_equal(vmin, b)
2631 vmax = _decade_greater_equal(vmax, b)
2632 if vmin == vmax:
2633 vmin = _decade_less(vmin, b)
2634 vmax = _decade_greater(vmax, b)
2636 result = mtransforms.nonsingular(vmin, vmax)
2637 return result
2640class AsinhLocator(Locator):
2641 """
2642 An axis tick locator specialized for the inverse-sinh scale
2644 This is very unlikely to have any use beyond
2645 the `~.scale.AsinhScale` class.
2647 .. note::
2649 This API is provisional and may be revised in the future
2650 based on early user feedback.
2651 """
2652 def __init__(self, linear_width, numticks=11, symthresh=0.2,
2653 base=10, subs=None):
2654 """
2655 Parameters
2656 ----------
2657 linear_width : float
2658 The scale parameter defining the extent
2659 of the quasi-linear region.
2660 numticks : int, default: 11
2661 The approximate number of major ticks that will fit
2662 along the entire axis
2663 symthresh : float, default: 0.2
2664 The fractional threshold beneath which data which covers
2665 a range that is approximately symmetric about zero
2666 will have ticks that are exactly symmetric.
2667 base : int, default: 10
2668 The number base used for rounding tick locations
2669 on a logarithmic scale. If this is less than one,
2670 then rounding is to the nearest integer multiple
2671 of powers of ten.
2672 subs : tuple, default: None
2673 Multiples of the number base, typically used
2674 for the minor ticks, e.g. (2, 5) when base=10.
2675 """
2676 super().__init__()
2677 self.linear_width = linear_width
2678 self.numticks = numticks
2679 self.symthresh = symthresh
2680 self.base = base
2681 self.subs = subs
2683 def set_params(self, numticks=None, symthresh=None,
2684 base=None, subs=None):
2685 """Set parameters within this locator."""
2686 if numticks is not None:
2687 self.numticks = numticks
2688 if symthresh is not None:
2689 self.symthresh = symthresh
2690 if base is not None:
2691 self.base = base
2692 if subs is not None:
2693 self.subs = subs if len(subs) > 0 else None
2695 def __call__(self):
2696 vmin, vmax = self.axis.get_view_interval()
2697 if (vmin * vmax) < 0 and abs(1 + vmax / vmin) < self.symthresh:
2698 # Data-range appears to be almost symmetric, so round up:
2699 bound = max(abs(vmin), abs(vmax))
2700 return self.tick_values(-bound, bound)
2701 else:
2702 return self.tick_values(vmin, vmax)
2704 def tick_values(self, vmin, vmax):
2705 # Construct a set of "on-screen" locations
2706 # that are uniformly spaced:
2707 ymin, ymax = self.linear_width * np.arcsinh(np.array([vmin, vmax])
2708 / self.linear_width)
2709 ys = np.linspace(ymin, ymax, self.numticks)
2710 zero_dev = np.abs(ys / (ymax - ymin))
2711 if (ymin * ymax) < 0:
2712 # Ensure that the zero tick-mark is included,
2713 # if the axis straddles zero
2714 ys = np.hstack([ys[(zero_dev > 0.5 / self.numticks)], 0.0])
2716 # Transform the "on-screen" grid to the data space:
2717 xs = self.linear_width * np.sinh(ys / self.linear_width)
2718 zero_xs = (ys == 0)
2720 # Round the data-space values to be intuitive base-n numbers,
2721 # keeping track of positive and negative values separately,
2722 # but giving careful treatment to the zero value:
2723 if self.base > 1:
2724 log_base = math.log(self.base)
2725 powers = (
2726 np.where(zero_xs, 0, np.sign(xs)) *
2727 np.power(self.base,
2728 np.where(zero_xs, 0.0,
2729 np.floor(np.log(np.abs(xs) + zero_xs*1e-6)
2730 / log_base)))
2731 )
2732 if self.subs:
2733 qs = np.outer(powers, self.subs).flatten()
2734 else:
2735 qs = powers
2736 else:
2737 powers = (
2738 np.where(xs >= 0, 1, -1) *
2739 np.power(10, np.where(zero_xs, 0.0,
2740 np.floor(np.log10(np.abs(xs)
2741 + zero_xs*1e-6))))
2742 )
2743 qs = powers * np.round(xs / powers)
2744 ticks = np.array(sorted(set(qs)))
2746 if len(ticks) >= 2:
2747 return ticks
2748 else:
2749 return np.linspace(vmin, vmax, self.numticks)
2752class LogitLocator(MaxNLocator):
2753 """
2754 Determine the tick locations for logit axes
2755 """
2757 def __init__(self, minor=False, *, nbins="auto"):
2758 """
2759 Place ticks on the logit locations
2761 Parameters
2762 ----------
2763 nbins : int or 'auto', optional
2764 Number of ticks. Only used if minor is False.
2765 minor : bool, default: False
2766 Indicate if this locator is for minor ticks or not.
2767 """
2769 self._minor = minor
2770 super().__init__(nbins=nbins, steps=[1, 2, 5, 10])
2772 def set_params(self, minor=None, **kwargs):
2773 """Set parameters within this locator."""
2774 if minor is not None:
2775 self._minor = minor
2776 super().set_params(**kwargs)
2778 @property
2779 def minor(self):
2780 return self._minor
2782 @minor.setter
2783 def minor(self, value):
2784 self.set_params(minor=value)
2786 def tick_values(self, vmin, vmax):
2787 # dummy axis has no axes attribute
2788 if hasattr(self.axis, "axes") and self.axis.axes.name == "polar":
2789 raise NotImplementedError("Polar axis cannot be logit scaled yet")
2791 if self._nbins == "auto":
2792 if self.axis is not None:
2793 nbins = self.axis.get_tick_space()
2794 if nbins < 2:
2795 nbins = 2
2796 else:
2797 nbins = 9
2798 else:
2799 nbins = self._nbins
2801 # We define ideal ticks with their index:
2802 # linscale: ... 1e-3 1e-2 1e-1 1/2 1-1e-1 1-1e-2 1-1e-3 ...
2803 # b-scale : ... -3 -2 -1 0 1 2 3 ...
2804 def ideal_ticks(x):
2805 return 10 ** x if x < 0 else 1 - (10 ** (-x)) if x > 0 else 1 / 2
2807 vmin, vmax = self.nonsingular(vmin, vmax)
2808 binf = int(
2809 np.floor(np.log10(vmin))
2810 if vmin < 0.5
2811 else 0
2812 if vmin < 0.9
2813 else -np.ceil(np.log10(1 - vmin))
2814 )
2815 bsup = int(
2816 np.ceil(np.log10(vmax))
2817 if vmax <= 0.5
2818 else 1
2819 if vmax <= 0.9
2820 else -np.floor(np.log10(1 - vmax))
2821 )
2822 numideal = bsup - binf - 1
2823 if numideal >= 2:
2824 # have 2 or more wanted ideal ticks, so use them as major ticks
2825 if numideal > nbins:
2826 # to many ideal ticks, subsampling ideals for major ticks, and
2827 # take others for minor ticks
2828 subsampling_factor = math.ceil(numideal / nbins)
2829 if self._minor:
2830 ticklocs = [
2831 ideal_ticks(b)
2832 for b in range(binf, bsup + 1)
2833 if (b % subsampling_factor) != 0
2834 ]
2835 else:
2836 ticklocs = [
2837 ideal_ticks(b)
2838 for b in range(binf, bsup + 1)
2839 if (b % subsampling_factor) == 0
2840 ]
2841 return self.raise_if_exceeds(np.array(ticklocs))
2842 if self._minor:
2843 ticklocs = []
2844 for b in range(binf, bsup):
2845 if b < -1:
2846 ticklocs.extend(np.arange(2, 10) * 10 ** b)
2847 elif b == -1:
2848 ticklocs.extend(np.arange(2, 5) / 10)
2849 elif b == 0:
2850 ticklocs.extend(np.arange(6, 9) / 10)
2851 else:
2852 ticklocs.extend(
2853 1 - np.arange(2, 10)[::-1] * 10 ** (-b - 1)
2854 )
2855 return self.raise_if_exceeds(np.array(ticklocs))
2856 ticklocs = [ideal_ticks(b) for b in range(binf, bsup + 1)]
2857 return self.raise_if_exceeds(np.array(ticklocs))
2858 # the scale is zoomed so same ticks as linear scale can be used
2859 if self._minor:
2860 return []
2861 return super().tick_values(vmin, vmax)
2863 def nonsingular(self, vmin, vmax):
2864 standard_minpos = 1e-7
2865 initial_range = (standard_minpos, 1 - standard_minpos)
2866 if vmin > vmax:
2867 vmin, vmax = vmax, vmin
2868 if not np.isfinite(vmin) or not np.isfinite(vmax):
2869 vmin, vmax = initial_range # Initial range, no data plotted yet.
2870 elif vmax <= 0 or vmin >= 1:
2871 # vmax <= 0 occurs when all values are negative
2872 # vmin >= 1 occurs when all values are greater than one
2873 _api.warn_external(
2874 "Data has no values between 0 and 1, and therefore cannot be "
2875 "logit-scaled."
2876 )
2877 vmin, vmax = initial_range
2878 else:
2879 minpos = (
2880 self.axis.get_minpos()
2881 if self.axis is not None
2882 else standard_minpos
2883 )
2884 if not np.isfinite(minpos):
2885 minpos = standard_minpos # This should never take effect.
2886 if vmin <= 0:
2887 vmin = minpos
2888 # NOTE: for vmax, we should query a property similar to get_minpos,
2889 # but related to the maximal, less-than-one data point.
2890 # Unfortunately, Bbox._minpos is defined very deep in the BBox and
2891 # updated with data, so for now we use 1 - minpos as a substitute.
2892 if vmax >= 1:
2893 vmax = 1 - minpos
2894 if vmin == vmax:
2895 vmin, vmax = 0.1 * vmin, 1 - 0.1 * vmin
2897 return vmin, vmax
2900class AutoLocator(MaxNLocator):
2901 """
2902 Dynamically find major tick positions. This is actually a subclass
2903 of `~matplotlib.ticker.MaxNLocator`, with parameters *nbins = 'auto'*
2904 and *steps = [1, 2, 2.5, 5, 10]*.
2905 """
2906 def __init__(self):
2907 """
2908 To know the values of the non-public parameters, please have a
2909 look to the defaults of `~matplotlib.ticker.MaxNLocator`.
2910 """
2911 if mpl.rcParams['_internal.classic_mode']:
2912 nbins = 9
2913 steps = [1, 2, 5, 10]
2914 else:
2915 nbins = 'auto'
2916 steps = [1, 2, 2.5, 5, 10]
2917 super().__init__(nbins=nbins, steps=steps)
2920class AutoMinorLocator(Locator):
2921 """
2922 Dynamically find minor tick positions based on the positions of
2923 major ticks. The scale must be linear with major ticks evenly spaced.
2924 """
2925 def __init__(self, n=None):
2926 """
2927 *n* is the number of subdivisions of the interval between
2928 major ticks; e.g., n=2 will place a single minor tick midway
2929 between major ticks.
2931 If *n* is omitted or None, it will be set to 5 or 4.
2932 """
2933 self.ndivs = n
2935 def __call__(self):
2936 """Return the locations of the ticks."""
2937 if self.axis.get_scale() == 'log':
2938 _api.warn_external('AutoMinorLocator does not work with '
2939 'logarithmic scale')
2940 return []
2942 majorlocs = self.axis.get_majorticklocs()
2943 try:
2944 majorstep = majorlocs[1] - majorlocs[0]
2945 except IndexError:
2946 # Need at least two major ticks to find minor tick locations
2947 # TODO: Figure out a way to still be able to display minor
2948 # ticks without two major ticks visible. For now, just display
2949 # no ticks at all.
2950 return []
2952 if self.ndivs is None:
2954 majorstep_no_exponent = 10 ** (np.log10(majorstep) % 1)
2956 if np.isclose(majorstep_no_exponent, [1.0, 2.5, 5.0, 10.0]).any():
2957 ndivs = 5
2958 else:
2959 ndivs = 4
2960 else:
2961 ndivs = self.ndivs
2963 minorstep = majorstep / ndivs
2965 vmin, vmax = self.axis.get_view_interval()
2966 if vmin > vmax:
2967 vmin, vmax = vmax, vmin
2969 t0 = majorlocs[0]
2970 tmin = ((vmin - t0) // minorstep + 1) * minorstep
2971 tmax = ((vmax - t0) // minorstep + 1) * minorstep
2972 locs = np.arange(tmin, tmax, minorstep) + t0
2974 return self.raise_if_exceeds(locs)
2976 def tick_values(self, vmin, vmax):
2977 raise NotImplementedError('Cannot get tick locations for a '
2978 '%s type.' % type(self))