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