1"""
2Classes for the ticks and x and y axis
3"""
4from __future__ import (absolute_import, division, print_function,
5                        unicode_literals)
6
7import six
8
9import logging
10
11from matplotlib import rcParams
12import matplotlib.artist as artist
13from matplotlib.artist import allow_rasterization
14import matplotlib.cbook as cbook
15from matplotlib.cbook import _string_to_bool
16import matplotlib.font_manager as font_manager
17import matplotlib.lines as mlines
18import matplotlib.patches as mpatches
19import matplotlib.scale as mscale
20import matplotlib.text as mtext
21import matplotlib.ticker as mticker
22import matplotlib.transforms as mtransforms
23import matplotlib.units as munits
24import numpy as np
25import warnings
26
27_log = logging.getLogger(__name__)
28
29GRIDLINE_INTERPOLATION_STEPS = 180
30
31# This list is being used for compatibility with Axes.grid, which
32# allows all Line2D kwargs.
33_line_AI = artist.ArtistInspector(mlines.Line2D)
34_line_param_names = _line_AI.get_setters()
35_line_param_aliases = [list(d.keys())[0] for d in _line_AI.aliasd.values()]
36_gridline_param_names = ['grid_' + name
37                         for name in _line_param_names + _line_param_aliases]
38
39
40class Tick(artist.Artist):
41    """
42    Abstract base class for the axis ticks, grid lines and labels
43
44    1 refers to the bottom of the plot for xticks and the left for yticks
45    2 refers to the top of the plot for xticks and the right for yticks
46
47    Attributes
48    ----------
49    tick1line : Line2D
50
51    tick2line : Line2D
52
53    gridline : Line2D
54
55    label1 : Text
56
57    label2 : Text
58
59    gridOn : bool
60        Determines whether to draw the tickline.
61
62    tick1On : bool
63        Determines whether to draw the first tickline.
64
65    tick2On : bool
66        Determines whether to draw the second tickline.
67
68    label1On : bool
69        Determines whether to draw the first tick label.
70
71    label2On : bool
72        Determines whether to draw the second tick label.
73    """
74    def __init__(self, axes, loc, label,
75                 size=None,  # points
76                 width=None,
77                 color=None,
78                 tickdir=None,
79                 pad=None,
80                 labelsize=None,
81                 labelcolor=None,
82                 zorder=None,
83                 gridOn=None,  # defaults to axes.grid depending on
84                               # axes.grid.which
85                 tick1On=True,
86                 tick2On=True,
87                 label1On=True,
88                 label2On=False,
89                 major=True,
90                 labelrotation=0,
91                 grid_color=None,
92                 grid_linestyle=None,
93                 grid_linewidth=None,
94                 grid_alpha=None,
95                 **kw  # Other Line2D kwargs applied to gridlines.
96                 ):
97        """
98        bbox is the Bound2D bounding box in display coords of the Axes
99        loc is the tick location in data coords
100        size is the tick size in points
101        """
102        artist.Artist.__init__(self)
103
104        if gridOn is None:
105            if major and (rcParams['axes.grid.which'] in ('both', 'major')):
106                gridOn = rcParams['axes.grid']
107            elif (not major) and (rcParams['axes.grid.which']
108                                  in ('both', 'minor')):
109                gridOn = rcParams['axes.grid']
110            else:
111                gridOn = False
112
113        self.set_figure(axes.figure)
114        self.axes = axes
115
116        name = self.__name__.lower()
117        self._name = name
118
119        self._loc = loc
120
121        if size is None:
122            if major:
123                size = rcParams['%s.major.size' % name]
124            else:
125                size = rcParams['%s.minor.size' % name]
126        self._size = size
127
128        if width is None:
129            if major:
130                width = rcParams['%s.major.width' % name]
131            else:
132                width = rcParams['%s.minor.width' % name]
133        self._width = width
134
135        if color is None:
136            color = rcParams['%s.color' % name]
137        self._color = color
138
139        if pad is None:
140            if major:
141                pad = rcParams['%s.major.pad' % name]
142            else:
143                pad = rcParams['%s.minor.pad' % name]
144        self._base_pad = pad
145
146        if labelcolor is None:
147            labelcolor = rcParams['%s.color' % name]
148        self._labelcolor = labelcolor
149
150        if labelsize is None:
151            labelsize = rcParams['%s.labelsize' % name]
152        self._labelsize = labelsize
153
154        self._set_labelrotation(labelrotation)
155
156        if zorder is None:
157            if major:
158                zorder = mlines.Line2D.zorder + 0.01
159            else:
160                zorder = mlines.Line2D.zorder
161        self._zorder = zorder
162
163        self._grid_color = (rcParams['grid.color']
164                            if grid_color is None else grid_color)
165        self._grid_linestyle = (rcParams['grid.linestyle']
166                                if grid_linestyle is None else grid_linestyle)
167        self._grid_linewidth = (rcParams['grid.linewidth']
168                                if grid_linewidth is None else grid_linewidth)
169        self._grid_alpha = (rcParams['grid.alpha']
170                            if grid_alpha is None else grid_alpha)
171
172        self._grid_kw = {k[5:]: v for k, v in kw.items()}
173
174        self.apply_tickdir(tickdir)
175
176        self.tick1line = self._get_tick1line()
177        self.tick2line = self._get_tick2line()
178        self.gridline = self._get_gridline()
179
180        self.label1 = self._get_text1()
181        self.label = self.label1  # legacy name
182        self.label2 = self._get_text2()
183
184        self.gridOn = gridOn
185        self.tick1On = tick1On
186        self.tick2On = tick2On
187        self.label1On = label1On
188        self.label2On = label2On
189
190        self.update_position(loc)
191
192    def _set_labelrotation(self, labelrotation):
193        if isinstance(labelrotation, six.string_types):
194            mode = labelrotation
195            angle = 0
196        elif isinstance(labelrotation, (tuple, list)):
197            mode, angle = labelrotation
198        else:
199            mode = 'default'
200            angle = labelrotation
201        if mode not in ('auto', 'default'):
202            raise ValueError("Label rotation mode must be 'default' or "
203                             "'auto', not '{}'.".format(mode))
204        self._labelrotation = (mode, angle)
205
206    def apply_tickdir(self, tickdir):
207        """
208        Calculate self._pad and self._tickmarkers
209        """
210        pass
211
212    def get_tickdir(self):
213        return self._tickdir
214
215    def get_tick_padding(self):
216        """
217        Get the length of the tick outside of the axes.
218        """
219        padding = {
220            'in': 0.0,
221            'inout': 0.5,
222            'out': 1.0
223        }
224        return self._size * padding[self._tickdir]
225
226    def get_children(self):
227        children = [self.tick1line, self.tick2line,
228                    self.gridline, self.label1, self.label2]
229        return children
230
231    def set_clip_path(self, clippath, transform=None):
232        artist.Artist.set_clip_path(self, clippath, transform)
233        self.gridline.set_clip_path(clippath, transform)
234        self.stale = True
235
236    set_clip_path.__doc__ = artist.Artist.set_clip_path.__doc__
237
238    def get_pad_pixels(self):
239        return self.figure.dpi * self._base_pad / 72.0
240
241    def contains(self, mouseevent):
242        """
243        Test whether the mouse event occurred in the Tick marks.
244
245        This function always returns false.  It is more useful to test if the
246        axis as a whole contains the mouse rather than the set of tick marks.
247        """
248        if callable(self._contains):
249            return self._contains(self, mouseevent)
250        return False, {}
251
252    def set_pad(self, val):
253        """
254        Set the tick label pad in points
255
256        ACCEPTS: float
257        """
258        self._apply_params(pad=val)
259        self.stale = True
260
261    def get_pad(self):
262        'Get the value of the tick label pad in points'
263        return self._base_pad
264
265    def _get_text1(self):
266        'Get the default Text 1 instance'
267        pass
268
269    def _get_text2(self):
270        'Get the default Text 2 instance'
271        pass
272
273    def _get_tick1line(self):
274        'Get the default line2D instance for tick1'
275        pass
276
277    def _get_tick2line(self):
278        'Get the default line2D instance for tick2'
279        pass
280
281    def _get_gridline(self):
282        'Get the default grid Line2d instance for this tick'
283        pass
284
285    def get_loc(self):
286        'Return the tick location (data coords) as a scalar'
287        return self._loc
288
289    @allow_rasterization
290    def draw(self, renderer):
291        if not self.get_visible():
292            self.stale = False
293            return
294
295        renderer.open_group(self.__name__)
296        if self.gridOn:
297            self.gridline.draw(renderer)
298        if self.tick1On:
299            self.tick1line.draw(renderer)
300        if self.tick2On:
301            self.tick2line.draw(renderer)
302
303        if self.label1On:
304            self.label1.draw(renderer)
305        if self.label2On:
306            self.label2.draw(renderer)
307        renderer.close_group(self.__name__)
308
309        self.stale = False
310
311    def set_label1(self, s):
312        """
313        Set the text of ticklabel
314
315        ACCEPTS: str
316        """
317        self.label1.set_text(s)
318        self.stale = True
319
320    set_label = set_label1
321
322    def set_label2(self, s):
323        """
324        Set the text of ticklabel2
325
326        ACCEPTS: str
327        """
328        self.label2.set_text(s)
329        self.stale = True
330
331    def _set_artist_props(self, a):
332        a.set_figure(self.figure)
333
334    def get_view_interval(self):
335        'return the view Interval instance for the axis this tick is ticking'
336        raise NotImplementedError('Derived must override')
337
338    def _apply_params(self, **kw):
339        switchkw = ['gridOn', 'tick1On', 'tick2On', 'label1On', 'label2On']
340        switches = [k for k in kw if k in switchkw]
341        for k in switches:
342            setattr(self, k, kw.pop(k))
343        newmarker = [k for k in kw if k in ['size', 'width', 'pad', 'tickdir']]
344        if newmarker:
345            self._size = kw.pop('size', self._size)
346            # Width could be handled outside this block, but it is
347            # convenient to leave it here.
348            self._width = kw.pop('width', self._width)
349            self._base_pad = kw.pop('pad', self._base_pad)
350            # apply_tickdir uses _size and _base_pad to make _pad,
351            # and also makes _tickmarkers.
352            self.apply_tickdir(kw.pop('tickdir', self._tickdir))
353            self.tick1line.set_marker(self._tickmarkers[0])
354            self.tick2line.set_marker(self._tickmarkers[1])
355            for line in (self.tick1line, self.tick2line):
356                line.set_markersize(self._size)
357                line.set_markeredgewidth(self._width)
358            # _get_text1_transform uses _pad from apply_tickdir.
359            trans = self._get_text1_transform()[0]
360            self.label1.set_transform(trans)
361            trans = self._get_text2_transform()[0]
362            self.label2.set_transform(trans)
363        tick_kw = {k: v for k, v in six.iteritems(kw)
364                   if k in ['color', 'zorder']}
365        if tick_kw:
366            self.tick1line.set(**tick_kw)
367            self.tick2line.set(**tick_kw)
368            for k, v in six.iteritems(tick_kw):
369                setattr(self, '_' + k, v)
370
371        if 'labelrotation' in kw:
372            self._set_labelrotation(kw.pop('labelrotation'))
373            self.label1.set(rotation=self._labelrotation[1])
374            self.label2.set(rotation=self._labelrotation[1])
375
376        label_list = [k for k in six.iteritems(kw)
377                      if k[0] in ['labelsize', 'labelcolor']]
378        if label_list:
379            label_kw = {k[5:]: v for k, v in label_list}
380            self.label1.set(**label_kw)
381            self.label2.set(**label_kw)
382            for k, v in six.iteritems(label_kw):
383                # for labelsize the text objects covert str ('small')
384                # -> points. grab the integer from the `Text` object
385                # instead of saving the string representation
386                v = getattr(self.label1, 'get_' + k)()
387                setattr(self, '_label' + k, v)
388
389        grid_list = [k for k in six.iteritems(kw)
390                     if k[0] in _gridline_param_names]
391        if grid_list:
392            grid_kw = {k[5:]: v for k, v in grid_list}
393            self.gridline.set(**grid_kw)
394            for k, v in six.iteritems(grid_kw):
395                setattr(self, '_grid_' + k, v)
396
397    def update_position(self, loc):
398        'Set the location of tick in data coords with scalar *loc*'
399        raise NotImplementedError('Derived must override')
400
401    def _get_text1_transform(self):
402        raise NotImplementedError('Derived must override')
403
404    def _get_text2_transform(self):
405        raise NotImplementedError('Derived must override')
406
407
408class XTick(Tick):
409    """
410    Contains all the Artists needed to make an x tick - the tick line,
411    the label text and the grid line
412    """
413    __name__ = 'xtick'
414
415    def _get_text1_transform(self):
416        return self.axes.get_xaxis_text1_transform(self._pad)
417
418    def _get_text2_transform(self):
419        return self.axes.get_xaxis_text2_transform(self._pad)
420
421    def apply_tickdir(self, tickdir):
422        if tickdir is None:
423            tickdir = rcParams['%s.direction' % self._name]
424        self._tickdir = tickdir
425
426        if self._tickdir == 'in':
427            self._tickmarkers = (mlines.TICKUP, mlines.TICKDOWN)
428        elif self._tickdir == 'inout':
429            self._tickmarkers = ('|', '|')
430        else:
431            self._tickmarkers = (mlines.TICKDOWN, mlines.TICKUP)
432        self._pad = self._base_pad + self.get_tick_padding()
433        self.stale = True
434
435    def _get_text1(self):
436        'Get the default Text instance'
437        # the y loc is 3 points below the min of y axis
438        # get the affine as an a,b,c,d,tx,ty list
439        # x in data coords, y in axes coords
440        trans, vert, horiz = self._get_text1_transform()
441        t = mtext.Text(
442            x=0, y=0,
443            fontproperties=font_manager.FontProperties(size=self._labelsize),
444            color=self._labelcolor,
445            verticalalignment=vert,
446            horizontalalignment=horiz,
447            )
448        t.set_transform(trans)
449        self._set_artist_props(t)
450        return t
451
452    def _get_text2(self):
453
454        'Get the default Text 2 instance'
455        # x in data coords, y in axes coords
456        trans, vert, horiz = self._get_text2_transform()
457        t = mtext.Text(
458            x=0, y=1,
459            fontproperties=font_manager.FontProperties(size=self._labelsize),
460            color=self._labelcolor,
461            verticalalignment=vert,
462            horizontalalignment=horiz,
463            )
464        t.set_transform(trans)
465        self._set_artist_props(t)
466        return t
467
468    def _get_tick1line(self):
469        'Get the default line2D instance'
470        # x in data coords, y in axes coords
471        l = mlines.Line2D(xdata=(0,), ydata=(0,), color=self._color,
472                          linestyle='None', marker=self._tickmarkers[0],
473                          markersize=self._size,
474                          markeredgewidth=self._width, zorder=self._zorder)
475        l.set_transform(self.axes.get_xaxis_transform(which='tick1'))
476        self._set_artist_props(l)
477        return l
478
479    def _get_tick2line(self):
480        'Get the default line2D instance'
481        # x in data coords, y in axes coords
482        l = mlines.Line2D(xdata=(0,), ydata=(1,),
483                          color=self._color,
484                          linestyle='None',
485                          marker=self._tickmarkers[1],
486                          markersize=self._size,
487                          markeredgewidth=self._width,
488                          zorder=self._zorder)
489
490        l.set_transform(self.axes.get_xaxis_transform(which='tick2'))
491        self._set_artist_props(l)
492        return l
493
494    def _get_gridline(self):
495        'Get the default line2D instance'
496        # x in data coords, y in axes coords
497        l = mlines.Line2D(xdata=(0.0, 0.0), ydata=(0, 1.0),
498                          color=self._grid_color,
499                          linestyle=self._grid_linestyle,
500                          linewidth=self._grid_linewidth,
501                          alpha=self._grid_alpha,
502                          markersize=0,
503                          **self._grid_kw)
504        l.set_transform(self.axes.get_xaxis_transform(which='grid'))
505        l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS
506        self._set_artist_props(l)
507
508        return l
509
510    def update_position(self, loc):
511        'Set the location of tick in data coords with scalar *loc*'
512        if self.tick1On:
513            self.tick1line.set_xdata((loc,))
514        if self.tick2On:
515            self.tick2line.set_xdata((loc,))
516        if self.gridOn:
517            self.gridline.set_xdata((loc,))
518        if self.label1On:
519            self.label1.set_x(loc)
520        if self.label2On:
521            self.label2.set_x(loc)
522
523        self._loc = loc
524        self.stale = True
525
526    def get_view_interval(self):
527        'return the Interval instance for this axis view limits'
528        return self.axes.viewLim.intervalx
529
530
531class YTick(Tick):
532    """
533    Contains all the Artists needed to make a Y tick - the tick line,
534    the label text and the grid line
535    """
536    __name__ = 'ytick'
537
538    def _get_text1_transform(self):
539        return self.axes.get_yaxis_text1_transform(self._pad)
540
541    def _get_text2_transform(self):
542        return self.axes.get_yaxis_text2_transform(self._pad)
543
544    def apply_tickdir(self, tickdir):
545        if tickdir is None:
546            tickdir = rcParams['%s.direction' % self._name]
547        self._tickdir = tickdir
548
549        if self._tickdir == 'in':
550            self._tickmarkers = (mlines.TICKRIGHT, mlines.TICKLEFT)
551        elif self._tickdir == 'inout':
552            self._tickmarkers = ('_', '_')
553        else:
554            self._tickmarkers = (mlines.TICKLEFT, mlines.TICKRIGHT)
555        self._pad = self._base_pad + self.get_tick_padding()
556        self.stale = True
557
558    # how far from the y axis line the right of the ticklabel are
559    def _get_text1(self):
560        'Get the default Text instance'
561        # x in axes coords, y in data coords
562        trans, vert, horiz = self._get_text1_transform()
563        t = mtext.Text(
564            x=0, y=0,
565            fontproperties=font_manager.FontProperties(size=self._labelsize),
566            color=self._labelcolor,
567            verticalalignment=vert,
568            horizontalalignment=horiz,
569            )
570        t.set_transform(trans)
571        self._set_artist_props(t)
572        return t
573
574    def _get_text2(self):
575        'Get the default Text instance'
576        # x in axes coords, y in data coords
577        trans, vert, horiz = self._get_text2_transform()
578        t = mtext.Text(
579            x=1, y=0,
580            fontproperties=font_manager.FontProperties(size=self._labelsize),
581            color=self._labelcolor,
582            verticalalignment=vert,
583            horizontalalignment=horiz,
584            )
585        t.set_transform(trans)
586        self._set_artist_props(t)
587        return t
588
589    def _get_tick1line(self):
590        'Get the default line2D instance'
591        # x in axes coords, y in data coords
592
593        l = mlines.Line2D((0,), (0,),
594                          color=self._color,
595                          marker=self._tickmarkers[0],
596                          linestyle='None',
597                          markersize=self._size,
598                          markeredgewidth=self._width,
599                          zorder=self._zorder)
600        l.set_transform(self.axes.get_yaxis_transform(which='tick1'))
601        self._set_artist_props(l)
602        return l
603
604    def _get_tick2line(self):
605        'Get the default line2D instance'
606        # x in axes coords, y in data coords
607        l = mlines.Line2D((1,), (0,),
608                          color=self._color,
609                          marker=self._tickmarkers[1],
610                          linestyle='None',
611                          markersize=self._size,
612                          markeredgewidth=self._width,
613                          zorder=self._zorder)
614        l.set_transform(self.axes.get_yaxis_transform(which='tick2'))
615        self._set_artist_props(l)
616        return l
617
618    def _get_gridline(self):
619        'Get the default line2D instance'
620        # x in axes coords, y in data coords
621        l = mlines.Line2D(xdata=(0, 1), ydata=(0, 0),
622                          color=self._grid_color,
623                          linestyle=self._grid_linestyle,
624                          linewidth=self._grid_linewidth,
625                          alpha=self._grid_alpha,
626                          markersize=0,
627                          **self._grid_kw)
628        l.set_transform(self.axes.get_yaxis_transform(which='grid'))
629        l.get_path()._interpolation_steps = GRIDLINE_INTERPOLATION_STEPS
630        self._set_artist_props(l)
631        return l
632
633    def update_position(self, loc):
634        'Set the location of tick in data coords with scalar *loc*'
635        if self.tick1On:
636            self.tick1line.set_ydata((loc,))
637        if self.tick2On:
638            self.tick2line.set_ydata((loc,))
639        if self.gridOn:
640            self.gridline.set_ydata((loc,))
641        if self.label1On:
642            self.label1.set_y(loc)
643        if self.label2On:
644            self.label2.set_y(loc)
645
646        self._loc = loc
647        self.stale = True
648
649    def get_view_interval(self):
650        'return the Interval instance for this axis view limits'
651        return self.axes.viewLim.intervaly
652
653
654class Ticker(object):
655    locator = None
656    formatter = None
657
658
659class _LazyTickList(object):
660    """
661    A descriptor for lazy instantiation of tick lists.
662
663    See comment above definition of the ``majorTicks`` and ``minorTicks``
664    attributes.
665    """
666
667    def __init__(self, major):
668        self._major = major
669
670    def __get__(self, instance, cls):
671        if instance is None:
672            return self
673        else:
674            # instance._get_tick() can itself try to access the majorTicks
675            # attribute (e.g. in certain projection classes which override
676            # e.g. get_xaxis_text1_transform).  In order to avoid infinite
677            # recursion, first set the majorTicks on the instance to an empty
678            # list, then create the tick and append it.
679            if self._major:
680                instance.majorTicks = []
681                tick = instance._get_tick(major=True)
682                instance.majorTicks.append(tick)
683                return instance.majorTicks
684            else:
685                instance.minorTicks = []
686                tick = instance._get_tick(major=False)
687                instance.minorTicks.append(tick)
688                return instance.minorTicks
689
690
691class Axis(artist.Artist):
692    """
693    Public attributes
694
695    * :attr:`axes.transData` - transform data coords to display coords
696    * :attr:`axes.transAxes` - transform axis coords to display coords
697    * :attr:`labelpad` - number of points between the axis and its label
698    """
699    OFFSETTEXTPAD = 3
700
701    def __str__(self):
702        return self.__class__.__name__ \
703            + "(%f,%f)" % tuple(self.axes.transAxes.transform_point((0, 0)))
704
705    def __init__(self, axes, pickradius=15):
706        """
707        Init the axis with the parent Axes instance
708        """
709        artist.Artist.__init__(self)
710        self.set_figure(axes.figure)
711
712        self.isDefault_label = True
713
714        self.axes = axes
715        self.major = Ticker()
716        self.minor = Ticker()
717        self.callbacks = cbook.CallbackRegistry()
718
719        self._autolabelpos = True
720        self._smart_bounds = False
721
722        self.label = self._get_label()
723        self.labelpad = rcParams['axes.labelpad']
724        self.offsetText = self._get_offset_text()
725
726        self.pickradius = pickradius
727
728        # Initialize here for testing; later add API
729        self._major_tick_kw = dict()
730        self._minor_tick_kw = dict()
731
732        self.cla()
733        self._set_scale('linear')
734
735    # During initialization, Axis objects often create ticks that are later
736    # unused; this turns out to be a very slow step.  Instead, use a custom
737    # descriptor to make the tick lists lazy and instantiate them as needed.
738    majorTicks = _LazyTickList(major=True)
739    minorTicks = _LazyTickList(major=False)
740
741    def set_label_coords(self, x, y, transform=None):
742        """
743        Set the coordinates of the label.  By default, the x
744        coordinate of the y label is determined by the tick label
745        bounding boxes, but this can lead to poor alignment of
746        multiple ylabels if there are multiple axes.  Ditto for the y
747        coordinate of the x label.
748
749        You can also specify the coordinate system of the label with
750        the transform.  If None, the default coordinate system will be
751        the axes coordinate system (0,0) is (left,bottom), (0.5, 0.5)
752        is middle, etc
753
754        """
755
756        self._autolabelpos = False
757        if transform is None:
758            transform = self.axes.transAxes
759
760        self.label.set_transform(transform)
761        self.label.set_position((x, y))
762        self.stale = True
763
764    def get_transform(self):
765        return self._scale.get_transform()
766
767    def get_scale(self):
768        return self._scale.name
769
770    def _set_scale(self, value, **kwargs):
771        self._scale = mscale.scale_factory(value, self, **kwargs)
772        self._scale.set_default_locators_and_formatters(self)
773
774        self.isDefault_majloc = True
775        self.isDefault_minloc = True
776        self.isDefault_majfmt = True
777        self.isDefault_minfmt = True
778
779    def limit_range_for_scale(self, vmin, vmax):
780        return self._scale.limit_range_for_scale(vmin, vmax, self.get_minpos())
781
782    @property
783    @cbook.deprecated("2.2.0")
784    def unit_data(self):
785        return self.units
786
787    @unit_data.setter
788    @cbook.deprecated("2.2.0")
789    def unit_data(self, unit_data):
790        self.set_units(unit_data)
791
792    def get_children(self):
793        children = [self.label, self.offsetText]
794        majorticks = self.get_major_ticks()
795        minorticks = self.get_minor_ticks()
796
797        children.extend(majorticks)
798        children.extend(minorticks)
799        return children
800
801    def cla(self):
802        'clear the current axis'
803
804        self.label.set_text('')  # self.set_label_text would change isDefault_
805
806        self._set_scale('linear')
807
808        # Clear the callback registry for this axis, or it may "leak"
809        self.callbacks = cbook.CallbackRegistry()
810
811        # whether the grids are on
812        self._gridOnMajor = (rcParams['axes.grid'] and
813                             rcParams['axes.grid.which'] in ('both', 'major'))
814        self._gridOnMinor = (rcParams['axes.grid'] and
815                             rcParams['axes.grid.which'] in ('both', 'minor'))
816
817        self.reset_ticks()
818
819        self.converter = None
820        self.units = None
821        self.set_units(None)
822        self.stale = True
823
824    def reset_ticks(self):
825        """
826        Re-initialize the major and minor Tick lists.
827
828        Each list starts with a single fresh Tick.
829        """
830        # Restore the lazy tick lists.
831        try:
832            del self.majorTicks
833        except AttributeError:
834            pass
835        try:
836            del self.minorTicks
837        except AttributeError:
838            pass
839        try:
840            self.set_clip_path(self.axes.patch)
841        except AttributeError:
842            pass
843
844    def set_tick_params(self, which='major', reset=False, **kw):
845        """
846        Set appearance parameters for ticks, ticklabels, and gridlines.
847
848        For documentation of keyword arguments, see
849        :meth:`matplotlib.axes.Axes.tick_params`.
850        """
851        dicts = []
852        if which == 'major' or which == 'both':
853            dicts.append(self._major_tick_kw)
854        if which == 'minor' or which == 'both':
855            dicts.append(self._minor_tick_kw)
856        kwtrans = self._translate_tick_kw(kw, to_init_kw=True)
857        for d in dicts:
858            if reset:
859                d.clear()
860            d.update(kwtrans)
861
862        if reset:
863            self.reset_ticks()
864        else:
865            if which == 'major' or which == 'both':
866                for tick in self.majorTicks:
867                    tick._apply_params(**self._major_tick_kw)
868            if which == 'minor' or which == 'both':
869                for tick in self.minorTicks:
870                    tick._apply_params(**self._minor_tick_kw)
871            if 'labelcolor' in kwtrans:
872                self.offsetText.set_color(kwtrans['labelcolor'])
873        self.stale = True
874
875    @staticmethod
876    def _translate_tick_kw(kw, to_init_kw=True):
877        # The following lists may be moved to a more
878        # accessible location.
879        kwkeys0 = ['size', 'width', 'color', 'tickdir', 'pad',
880                   'labelsize', 'labelcolor', 'zorder', 'gridOn',
881                   'tick1On', 'tick2On', 'label1On', 'label2On']
882        kwkeys1 = ['length', 'direction', 'left', 'bottom', 'right', 'top',
883                   'labelleft', 'labelbottom', 'labelright', 'labeltop',
884                   'labelrotation']
885        kwkeys2 = _gridline_param_names
886        kwkeys = kwkeys0 + kwkeys1 + kwkeys2
887        kwtrans = dict()
888        if to_init_kw:
889            if 'length' in kw:
890                kwtrans['size'] = kw.pop('length')
891            if 'direction' in kw:
892                kwtrans['tickdir'] = kw.pop('direction')
893            if 'rotation' in kw:
894                kwtrans['labelrotation'] = kw.pop('rotation')
895            if 'left' in kw:
896                kwtrans['tick1On'] = _string_to_bool(kw.pop('left'))
897            if 'bottom' in kw:
898                kwtrans['tick1On'] = _string_to_bool(kw.pop('bottom'))
899            if 'right' in kw:
900                kwtrans['tick2On'] = _string_to_bool(kw.pop('right'))
901            if 'top' in kw:
902                kwtrans['tick2On'] = _string_to_bool(kw.pop('top'))
903
904            if 'labelleft' in kw:
905                kwtrans['label1On'] = _string_to_bool(kw.pop('labelleft'))
906            if 'labelbottom' in kw:
907                kwtrans['label1On'] = _string_to_bool(kw.pop('labelbottom'))
908            if 'labelright' in kw:
909                kwtrans['label2On'] = _string_to_bool(kw.pop('labelright'))
910            if 'labeltop' in kw:
911                kwtrans['label2On'] = _string_to_bool(kw.pop('labeltop'))
912            if 'colors' in kw:
913                c = kw.pop('colors')
914                kwtrans['color'] = c
915                kwtrans['labelcolor'] = c
916            # Maybe move the checking up to the caller of this method.
917            for key in kw:
918                if key not in kwkeys:
919                    raise ValueError(
920                        "keyword %s is not recognized; valid keywords are %s"
921                        % (key, kwkeys))
922            kwtrans.update(kw)
923        else:
924            raise NotImplementedError("Inverse translation is deferred")
925        return kwtrans
926
927    def set_clip_path(self, clippath, transform=None):
928        artist.Artist.set_clip_path(self, clippath, transform)
929        for child in self.majorTicks + self.minorTicks:
930            child.set_clip_path(clippath, transform)
931        self.stale = True
932
933    def get_view_interval(self):
934        'return the Interval instance for this axis view limits'
935        raise NotImplementedError('Derived must override')
936
937    def set_view_interval(self, vmin, vmax, ignore=False):
938        raise NotImplementedError('Derived must override')
939
940    def get_data_interval(self):
941        'return the Interval instance for this axis data limits'
942        raise NotImplementedError('Derived must override')
943
944    def set_data_interval(self):
945        '''set the axis data limits'''
946        raise NotImplementedError('Derived must override')
947
948    def set_default_intervals(self):
949        '''set the default limits for the axis data and view interval if they
950        are not mutated'''
951
952        # this is mainly in support of custom object plotting.  For
953        # example, if someone passes in a datetime object, we do not
954        # know automagically how to set the default min/max of the
955        # data and view limits.  The unit conversion AxisInfo
956        # interface provides a hook for custom types to register
957        # default limits through the AxisInfo.default_limits
958        # attribute, and the derived code below will check for that
959        # and use it if is available (else just use 0..1)
960        pass
961
962    def _set_artist_props(self, a):
963        if a is None:
964            return
965        a.set_figure(self.figure)
966
967    def iter_ticks(self):
968        """
969        Iterate through all of the major and minor ticks.
970        """
971        majorLocs = self.major.locator()
972        majorTicks = self.get_major_ticks(len(majorLocs))
973        self.major.formatter.set_locs(majorLocs)
974        majorLabels = [self.major.formatter(val, i)
975                       for i, val in enumerate(majorLocs)]
976
977        minorLocs = self.minor.locator()
978        minorTicks = self.get_minor_ticks(len(minorLocs))
979        self.minor.formatter.set_locs(minorLocs)
980        minorLabels = [self.minor.formatter(val, i)
981                       for i, val in enumerate(minorLocs)]
982
983        major_minor = [
984            (majorTicks, majorLocs, majorLabels),
985            (minorTicks, minorLocs, minorLabels)]
986
987        for group in major_minor:
988            for tick in zip(*group):
989                yield tick
990
991    def get_ticklabel_extents(self, renderer):
992        """
993        Get the extents of the tick labels on either side
994        of the axes.
995        """
996
997        ticks_to_draw = self._update_ticks(renderer)
998        ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
999                                                                renderer)
1000
1001        if len(ticklabelBoxes):
1002            bbox = mtransforms.Bbox.union(ticklabelBoxes)
1003        else:
1004            bbox = mtransforms.Bbox.from_extents(0, 0, 0, 0)
1005        if len(ticklabelBoxes2):
1006            bbox2 = mtransforms.Bbox.union(ticklabelBoxes2)
1007        else:
1008            bbox2 = mtransforms.Bbox.from_extents(0, 0, 0, 0)
1009        return bbox, bbox2
1010
1011    def set_smart_bounds(self, value):
1012        """set the axis to have smart bounds"""
1013        self._smart_bounds = value
1014        self.stale = True
1015
1016    def get_smart_bounds(self):
1017        """get whether the axis has smart bounds"""
1018        return self._smart_bounds
1019
1020    def _update_ticks(self, renderer):
1021        """
1022        Update ticks (position and labels) using the current data
1023        interval of the axes. Returns a list of ticks that will be
1024        drawn.
1025        """
1026
1027        interval = self.get_view_interval()
1028        tick_tups = list(self.iter_ticks())  # iter_ticks calls the locator
1029        if self._smart_bounds and tick_tups:
1030            # handle inverted limits
1031            view_low, view_high = sorted(interval)
1032            data_low, data_high = sorted(self.get_data_interval())
1033            locs = np.sort([ti[1] for ti in tick_tups])
1034            if data_low <= view_low:
1035                # data extends beyond view, take view as limit
1036                ilow = view_low
1037            else:
1038                # data stops within view, take best tick
1039                good_locs = locs[locs <= data_low]
1040                if len(good_locs):
1041                    # last tick prior or equal to first data point
1042                    ilow = good_locs[-1]
1043                else:
1044                    # No ticks (why not?), take first tick
1045                    ilow = locs[0]
1046            if data_high >= view_high:
1047                # data extends beyond view, take view as limit
1048                ihigh = view_high
1049            else:
1050                # data stops within view, take best tick
1051                good_locs = locs[locs >= data_high]
1052                if len(good_locs):
1053                    # first tick after or equal to last data point
1054                    ihigh = good_locs[0]
1055                else:
1056                    # No ticks (why not?), take last tick
1057                    ihigh = locs[-1]
1058            tick_tups = [ti for ti in tick_tups if ilow <= ti[1] <= ihigh]
1059
1060        # so that we don't lose ticks on the end, expand out the interval ever
1061        # so slightly.  The "ever so slightly" is defined to be the width of a
1062        # half of a pixel.  We don't want to draw a tick that even one pixel
1063        # outside of the defined axis interval.
1064        if interval[0] <= interval[1]:
1065            interval_expanded = interval
1066        else:
1067            interval_expanded = interval[1], interval[0]
1068
1069        if hasattr(self, '_get_pixel_distance_along_axis'):
1070            # normally, one does not want to catch all exceptions that
1071            # could possibly happen, but it is not clear exactly what
1072            # exceptions might arise from a user's projection (their
1073            # rendition of the Axis object).  So, we catch all, with
1074            # the idea that one would rather potentially lose a tick
1075            # from one side of the axis or another, rather than see a
1076            # stack trace.
1077            # We also catch users warnings here. These are the result of
1078            # invalid numpy calculations that may be the result of out of
1079            # bounds on axis with finite allowed intervals such as geo
1080            # projections i.e. Mollweide.
1081            with np.errstate(invalid='ignore'):
1082                try:
1083                    ds1 = self._get_pixel_distance_along_axis(
1084                        interval_expanded[0], -0.5)
1085                except:
1086                    warnings.warn("Unable to find pixel distance along axis "
1087                                  "for interval padding of ticks; assuming no "
1088                                  "interval padding needed.")
1089                    ds1 = 0.0
1090                if np.isnan(ds1):
1091                    ds1 = 0.0
1092                try:
1093                    ds2 = self._get_pixel_distance_along_axis(
1094                        interval_expanded[1], +0.5)
1095                except:
1096                    warnings.warn("Unable to find pixel distance along axis "
1097                                  "for interval padding of ticks; assuming no "
1098                                  "interval padding needed.")
1099                    ds2 = 0.0
1100                if np.isnan(ds2):
1101                    ds2 = 0.0
1102            interval_expanded = (interval_expanded[0] - ds1,
1103                                 interval_expanded[1] + ds2)
1104
1105        ticks_to_draw = []
1106        for tick, loc, label in tick_tups:
1107            if tick is None:
1108                continue
1109            # NB: always update labels and position to avoid issues like #9397
1110            tick.update_position(loc)
1111            tick.set_label1(label)
1112            tick.set_label2(label)
1113            if not mtransforms.interval_contains(interval_expanded, loc):
1114                continue
1115            ticks_to_draw.append(tick)
1116
1117        return ticks_to_draw
1118
1119    def _get_tick_bboxes(self, ticks, renderer):
1120        """
1121        Given the list of ticks, return two lists of bboxes. One for
1122        tick lable1's and another for tick label2's.
1123        """
1124
1125        ticklabelBoxes = []
1126        ticklabelBoxes2 = []
1127
1128        for tick in ticks:
1129            if tick.label1On and tick.label1.get_visible():
1130                extent = tick.label1.get_window_extent(renderer)
1131                ticklabelBoxes.append(extent)
1132            if tick.label2On and tick.label2.get_visible():
1133                extent = tick.label2.get_window_extent(renderer)
1134                ticklabelBoxes2.append(extent)
1135        return ticklabelBoxes, ticklabelBoxes2
1136
1137    def get_tightbbox(self, renderer):
1138        """
1139        Return a bounding box that encloses the axis. It only accounts
1140        tick labels, axis label, and offsetText.
1141        """
1142        if not self.get_visible():
1143            return
1144
1145        ticks_to_draw = self._update_ticks(renderer)
1146
1147        self._update_label_position(renderer)
1148
1149        # go back to just this axis's tick labels
1150        ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(
1151                    ticks_to_draw, renderer)
1152
1153        self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2)
1154        self.offsetText.set_text(self.major.formatter.get_offset())
1155
1156        bb = []
1157
1158        for a in [self.label, self.offsetText]:
1159            if a.get_visible():
1160                bb.append(a.get_window_extent(renderer))
1161
1162        bb.extend(ticklabelBoxes)
1163        bb.extend(ticklabelBoxes2)
1164
1165        bb = [b for b in bb if b.width != 0 or b.height != 0]
1166        if bb:
1167            _bbox = mtransforms.Bbox.union(bb)
1168            return _bbox
1169        else:
1170            return None
1171
1172    def get_tick_padding(self):
1173        values = []
1174        if len(self.majorTicks):
1175            values.append(self.majorTicks[0].get_tick_padding())
1176        if len(self.minorTicks):
1177            values.append(self.minorTicks[0].get_tick_padding())
1178        if len(values):
1179            return max(values)
1180        return 0.0
1181
1182    @allow_rasterization
1183    def draw(self, renderer, *args, **kwargs):
1184        'Draw the axis lines, grid lines, tick lines and labels'
1185
1186        if not self.get_visible():
1187            return
1188        renderer.open_group(__name__)
1189
1190        ticks_to_draw = self._update_ticks(renderer)
1191        ticklabelBoxes, ticklabelBoxes2 = self._get_tick_bboxes(ticks_to_draw,
1192                                                                renderer)
1193
1194        for tick in ticks_to_draw:
1195            tick.draw(renderer)
1196
1197        # scale up the axis label box to also find the neighbors, not
1198        # just the tick labels that actually overlap note we need a
1199        # *copy* of the axis label box because we don't wan't to scale
1200        # the actual bbox
1201
1202        self._update_label_position(renderer)
1203
1204        self.label.draw(renderer)
1205
1206        self._update_offset_text_position(ticklabelBoxes, ticklabelBoxes2)
1207        self.offsetText.set_text(self.major.formatter.get_offset())
1208        self.offsetText.draw(renderer)
1209
1210        if 0:  # draw the bounding boxes around the text for debug
1211            for tick in self.majorTicks:
1212                label = tick.label1
1213                mpatches.bbox_artist(label, renderer)
1214            mpatches.bbox_artist(self.label, renderer)
1215
1216        renderer.close_group(__name__)
1217        self.stale = False
1218
1219    def _get_label(self):
1220        raise NotImplementedError('Derived must override')
1221
1222    def _get_offset_text(self):
1223        raise NotImplementedError('Derived must override')
1224
1225    def get_gridlines(self):
1226        'Return the grid lines as a list of Line2D instance'
1227        ticks = self.get_major_ticks()
1228        return cbook.silent_list('Line2D gridline',
1229                                 [tick.gridline for tick in ticks])
1230
1231    def get_label(self):
1232        'Return the axis label as a Text instance'
1233        return self.label
1234
1235    def get_offset_text(self):
1236        'Return the axis offsetText as a Text instance'
1237        return self.offsetText
1238
1239    def get_pickradius(self):
1240        'Return the depth of the axis used by the picker'
1241        return self.pickradius
1242
1243    def get_majorticklabels(self):
1244        'Return a list of Text instances for the major ticklabels'
1245        ticks = self.get_major_ticks()
1246        labels1 = [tick.label1 for tick in ticks if tick.label1On]
1247        labels2 = [tick.label2 for tick in ticks if tick.label2On]
1248        return cbook.silent_list('Text major ticklabel', labels1 + labels2)
1249
1250    def get_minorticklabels(self):
1251        'Return a list of Text instances for the minor ticklabels'
1252        ticks = self.get_minor_ticks()
1253        labels1 = [tick.label1 for tick in ticks if tick.label1On]
1254        labels2 = [tick.label2 for tick in ticks if tick.label2On]
1255        return cbook.silent_list('Text minor ticklabel', labels1 + labels2)
1256
1257    def get_ticklabels(self, minor=False, which=None):
1258        """
1259        Get the tick labels as a list of :class:`~matplotlib.text.Text`
1260        instances.
1261
1262        Parameters
1263        ----------
1264        minor : bool
1265           If True return the minor ticklabels,
1266           else return the major ticklabels
1267
1268        which : None, ('minor', 'major', 'both')
1269           Overrides `minor`.
1270
1271           Selects which ticklabels to return
1272
1273        Returns
1274        -------
1275        ret : list
1276           List of :class:`~matplotlib.text.Text` instances.
1277        """
1278
1279        if which is not None:
1280            if which == 'minor':
1281                return self.get_minorticklabels()
1282            elif which == 'major':
1283                return self.get_majorticklabels()
1284            elif which == 'both':
1285                return self.get_majorticklabels() + self.get_minorticklabels()
1286            else:
1287                raise ValueError("`which` must be one of ('minor', 'major', "
1288                                 "'both') not " + str(which))
1289        if minor:
1290            return self.get_minorticklabels()
1291        return self.get_majorticklabels()
1292
1293    def get_majorticklines(self):
1294        'Return the major tick lines as a list of Line2D instances'
1295        lines = []
1296        ticks = self.get_major_ticks()
1297        for tick in ticks:
1298            lines.append(tick.tick1line)
1299            lines.append(tick.tick2line)
1300        return cbook.silent_list('Line2D ticklines', lines)
1301
1302    def get_minorticklines(self):
1303        'Return the minor tick lines as a list of Line2D instances'
1304        lines = []
1305        ticks = self.get_minor_ticks()
1306        for tick in ticks:
1307            lines.append(tick.tick1line)
1308            lines.append(tick.tick2line)
1309        return cbook.silent_list('Line2D ticklines', lines)
1310
1311    def get_ticklines(self, minor=False):
1312        'Return the tick lines as a list of Line2D instances'
1313        if minor:
1314            return self.get_minorticklines()
1315        return self.get_majorticklines()
1316
1317    def get_majorticklocs(self):
1318        "Get the major tick locations in data coordinates as a numpy array"
1319        return self.major.locator()
1320
1321    def get_minorticklocs(self):
1322        "Get the minor tick locations in data coordinates as a numpy array"
1323        return self.minor.locator()
1324
1325    def get_ticklocs(self, minor=False):
1326        "Get the tick locations in data coordinates as a numpy array"
1327        if minor:
1328            return self.minor.locator()
1329        return self.major.locator()
1330
1331    def get_ticks_direction(self, minor=False):
1332        """
1333        Get the tick directions as a numpy array
1334
1335        Parameters
1336        ----------
1337        minor : boolean
1338            True to return the minor tick directions,
1339            False to return the major tick directions,
1340            Default is False
1341
1342        Returns
1343        -------
1344        numpy array of tick directions
1345        """
1346        if minor:
1347            return np.array(
1348                [tick._tickdir for tick in self.get_minor_ticks()])
1349        else:
1350            return np.array(
1351                [tick._tickdir for tick in self.get_major_ticks()])
1352
1353    def _get_tick(self, major):
1354        'return the default tick instance'
1355        raise NotImplementedError('derived must override')
1356
1357    def _copy_tick_props(self, src, dest):
1358        'Copy the props from src tick to dest tick'
1359        if src is None or dest is None:
1360            return
1361        dest.label1.update_from(src.label1)
1362        dest.label2.update_from(src.label2)
1363
1364        dest.tick1line.update_from(src.tick1line)
1365        dest.tick2line.update_from(src.tick2line)
1366        dest.gridline.update_from(src.gridline)
1367
1368        dest.tick1On = src.tick1On
1369        dest.tick2On = src.tick2On
1370        dest.label1On = src.label1On
1371        dest.label2On = src.label2On
1372
1373    def get_label_text(self):
1374        'Get the text of the label'
1375        return self.label.get_text()
1376
1377    def get_major_locator(self):
1378        'Get the locator of the major ticker'
1379        return self.major.locator
1380
1381    def get_minor_locator(self):
1382        'Get the locator of the minor ticker'
1383        return self.minor.locator
1384
1385    def get_major_formatter(self):
1386        'Get the formatter of the major ticker'
1387        return self.major.formatter
1388
1389    def get_minor_formatter(self):
1390        'Get the formatter of the minor ticker'
1391        return self.minor.formatter
1392
1393    def get_major_ticks(self, numticks=None):
1394        'get the tick instances; grow as necessary'
1395        if numticks is None:
1396            numticks = len(self.get_major_locator()())
1397
1398        while len(self.majorTicks) < numticks:
1399            # update the new tick label properties from the old
1400            tick = self._get_tick(major=True)
1401            self.majorTicks.append(tick)
1402            if self._gridOnMajor:
1403                tick.gridOn = True
1404            self._copy_tick_props(self.majorTicks[0], tick)
1405
1406        return self.majorTicks[:numticks]
1407
1408    def get_minor_ticks(self, numticks=None):
1409        'get the minor tick instances; grow as necessary'
1410        if numticks is None:
1411            numticks = len(self.get_minor_locator()())
1412
1413        while len(self.minorTicks) < numticks:
1414            # update the new tick label properties from the old
1415            tick = self._get_tick(major=False)
1416            self.minorTicks.append(tick)
1417            if self._gridOnMinor:
1418                tick.gridOn = True
1419            self._copy_tick_props(self.minorTicks[0], tick)
1420
1421        return self.minorTicks[:numticks]
1422
1423    def grid(self, b=None, which='major', **kwargs):
1424        """
1425        Set the axis grid on or off; b is a boolean. Use *which* =
1426        'major' | 'minor' | 'both' to set the grid for major or minor ticks.
1427
1428        If *b* is *None* and len(kwargs)==0, toggle the grid state.  If
1429        *kwargs* are supplied, it is assumed you want the grid on and *b*
1430        will be set to True.
1431
1432        *kwargs* are used to set the line properties of the grids, e.g.,
1433
1434          xax.grid(color='r', linestyle='-', linewidth=2)
1435        """
1436        if len(kwargs):
1437            b = True
1438        which = which.lower()
1439        gridkw = {'grid_' + item[0]: item[1] for item in kwargs.items()}
1440        if which in ['minor', 'both']:
1441            if b is None:
1442                self._gridOnMinor = not self._gridOnMinor
1443            else:
1444                self._gridOnMinor = b
1445            self.set_tick_params(which='minor', gridOn=self._gridOnMinor,
1446                                 **gridkw)
1447        if which in ['major', 'both']:
1448            if b is None:
1449                self._gridOnMajor = not self._gridOnMajor
1450            else:
1451                self._gridOnMajor = b
1452            self.set_tick_params(which='major', gridOn=self._gridOnMajor,
1453                                 **gridkw)
1454        self.stale = True
1455
1456    def update_units(self, data):
1457        """
1458        introspect *data* for units converter and update the
1459        axis.converter instance if necessary. Return *True*
1460        if *data* is registered for unit conversion.
1461        """
1462
1463        converter = munits.registry.get_converter(data)
1464        if converter is None:
1465            return False
1466
1467        neednew = self.converter != converter
1468        self.converter = converter
1469        default = self.converter.default_units(data, self)
1470        if default is not None and self.units is None:
1471            self.set_units(default)
1472
1473        if neednew:
1474            self._update_axisinfo()
1475        self.stale = True
1476        return True
1477
1478    def _update_axisinfo(self):
1479        """
1480        check the axis converter for the stored units to see if the
1481        axis info needs to be updated
1482        """
1483        if self.converter is None:
1484            return
1485
1486        info = self.converter.axisinfo(self.units, self)
1487
1488        if info is None:
1489            return
1490        if info.majloc is not None and \
1491           self.major.locator != info.majloc and self.isDefault_majloc:
1492            self.set_major_locator(info.majloc)
1493            self.isDefault_majloc = True
1494        if info.minloc is not None and \
1495           self.minor.locator != info.minloc and self.isDefault_minloc:
1496            self.set_minor_locator(info.minloc)
1497            self.isDefault_minloc = True
1498        if info.majfmt is not None and \
1499           self.major.formatter != info.majfmt and self.isDefault_majfmt:
1500            self.set_major_formatter(info.majfmt)
1501            self.isDefault_majfmt = True
1502        if info.minfmt is not None and \
1503           self.minor.formatter != info.minfmt and self.isDefault_minfmt:
1504            self.set_minor_formatter(info.minfmt)
1505            self.isDefault_minfmt = True
1506        if info.label is not None and self.isDefault_label:
1507            self.set_label_text(info.label)
1508            self.isDefault_label = True
1509
1510        self.set_default_intervals()
1511
1512    def have_units(self):
1513        return self.converter is not None or self.units is not None
1514
1515    def convert_units(self, x):
1516        # If x is already a number, doesn't need converting
1517        if munits.ConversionInterface.is_numlike(x):
1518            return x
1519
1520        if self.converter is None:
1521            self.converter = munits.registry.get_converter(x)
1522
1523        if self.converter is None:
1524            return x
1525
1526        ret = self.converter.convert(x, self.units, self)
1527        return ret
1528
1529    def set_units(self, u):
1530        """
1531        set the units for axis
1532
1533        ACCEPTS: a units tag
1534        """
1535        pchanged = False
1536        if u is None:
1537            self.units = None
1538            pchanged = True
1539        else:
1540            if u != self.units:
1541                self.units = u
1542                pchanged = True
1543        if pchanged:
1544            self._update_axisinfo()
1545            self.callbacks.process('units')
1546            self.callbacks.process('units finalize')
1547        self.stale = True
1548
1549    def get_units(self):
1550        'return the units for axis'
1551        return self.units
1552
1553    def set_label_text(self, label, fontdict=None, **kwargs):
1554        """  Sets the text value of the axis label
1555
1556        ACCEPTS: A string value for the label
1557        """
1558        self.isDefault_label = False
1559        self.label.set_text(label)
1560        if fontdict is not None:
1561            self.label.update(fontdict)
1562        self.label.update(kwargs)
1563        self.stale = True
1564        return self.label
1565
1566    def set_major_formatter(self, formatter):
1567        """
1568        Set the formatter of the major ticker
1569
1570        ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance
1571        """
1572        self.isDefault_majfmt = False
1573        self.major.formatter = formatter
1574        formatter.set_axis(self)
1575        self.stale = True
1576
1577    def set_minor_formatter(self, formatter):
1578        """
1579        Set the formatter of the minor ticker
1580
1581        ACCEPTS: A :class:`~matplotlib.ticker.Formatter` instance
1582        """
1583        self.isDefault_minfmt = False
1584        self.minor.formatter = formatter
1585        formatter.set_axis(self)
1586        self.stale = True
1587
1588    def set_major_locator(self, locator):
1589        """
1590        Set the locator of the major ticker
1591
1592        ACCEPTS: a :class:`~matplotlib.ticker.Locator` instance
1593        """
1594        self.isDefault_majloc = False
1595        self.major.locator = locator
1596        locator.set_axis(self)
1597        self.stale = True
1598
1599    def set_minor_locator(self, locator):
1600        """
1601        Set the locator of the minor ticker
1602
1603        ACCEPTS: a :class:`~matplotlib.ticker.Locator` instance
1604        """
1605        self.isDefault_minloc = False
1606        self.minor.locator = locator
1607        locator.set_axis(self)
1608        self.stale = True
1609
1610    def set_pickradius(self, pickradius):
1611        """
1612        Set the depth of the axis used by the picker
1613
1614        ACCEPTS: a distance in points
1615        """
1616        self.pickradius = pickradius
1617
1618    def set_ticklabels(self, ticklabels, *args, **kwargs):
1619        """
1620        Set the text values of the tick labels. Return a list of Text
1621        instances.  Use *kwarg* *minor=True* to select minor ticks.
1622        All other kwargs are used to update the text object properties.
1623        As for get_ticklabels, label1 (left or bottom) is
1624        affected for a given tick only if its label1On attribute
1625        is True, and similarly for label2.  The list of returned
1626        label text objects consists of all such label1 objects followed
1627        by all such label2 objects.
1628
1629        The input *ticklabels* is assumed to match the set of
1630        tick locations, regardless of the state of label1On and
1631        label2On.
1632
1633        ACCEPTS: sequence of strings or Text objects
1634        """
1635        get_labels = []
1636        for t in ticklabels:
1637            # try calling get_text() to check whether it is Text object
1638            # if it is Text, get label content
1639            try:
1640                get_labels.append(t.get_text())
1641            # otherwise add the label to the list directly
1642            except AttributeError:
1643                get_labels.append(t)
1644        # replace the ticklabels list with the processed one
1645        ticklabels = get_labels
1646
1647        minor = kwargs.pop('minor', False)
1648        if minor:
1649            self.set_minor_formatter(mticker.FixedFormatter(ticklabels))
1650            ticks = self.get_minor_ticks()
1651        else:
1652            self.set_major_formatter(mticker.FixedFormatter(ticklabels))
1653            ticks = self.get_major_ticks()
1654        ret = []
1655        for tick_label, tick in zip(ticklabels, ticks):
1656            # deal with label1
1657            tick.label1.set_text(tick_label)
1658            tick.label1.update(kwargs)
1659            # deal with label2
1660            tick.label2.set_text(tick_label)
1661            tick.label2.update(kwargs)
1662            # only return visible tick labels
1663            if tick.label1On:
1664                ret.append(tick.label1)
1665            if tick.label2On:
1666                ret.append(tick.label2)
1667
1668        self.stale = True
1669        return ret
1670
1671    def set_ticks(self, ticks, minor=False):
1672        """
1673        Set the locations of the tick marks from sequence ticks
1674
1675        ACCEPTS: sequence of floats
1676        """
1677        # XXX if the user changes units, the information will be lost here
1678        ticks = self.convert_units(ticks)
1679        if len(ticks) > 1:
1680            xleft, xright = self.get_view_interval()
1681            if xright > xleft:
1682                self.set_view_interval(min(ticks), max(ticks))
1683            else:
1684                self.set_view_interval(max(ticks), min(ticks))
1685        if minor:
1686            self.set_minor_locator(mticker.FixedLocator(ticks))
1687            return self.get_minor_ticks(len(ticks))
1688        else:
1689            self.set_major_locator(mticker.FixedLocator(ticks))
1690            return self.get_major_ticks(len(ticks))
1691
1692    def _get_tick_boxes_siblings(self, xdir, renderer):
1693        """
1694        Get the bounding boxes for this `.axis` and its siblings
1695        as set by `.Figure.align_xlabels` or  `.Figure.align_ylablels`.
1696
1697        By default it just gets bboxes for self.
1698        """
1699        raise NotImplementedError('Derived must override')
1700
1701    def _update_label_position(self, renderer):
1702        """
1703        Update the label position based on the bounding box enclosing
1704        all the ticklabels and axis spine
1705        """
1706        raise NotImplementedError('Derived must override')
1707
1708    def _update_offset_text_position(self, bboxes, bboxes2):
1709        """
1710        Update the label position based on the sequence of bounding
1711        boxes of all the ticklabels
1712        """
1713        raise NotImplementedError('Derived must override')
1714
1715    def pan(self, numsteps):
1716        'Pan *numsteps* (can be positive or negative)'
1717        self.major.locator.pan(numsteps)
1718
1719    def zoom(self, direction):
1720        "Zoom in/out on axis; if *direction* is >0 zoom in, else zoom out"
1721        self.major.locator.zoom(direction)
1722
1723    def axis_date(self, tz=None):
1724        """
1725        Sets up x-axis ticks and labels that treat the x data as dates.
1726        *tz* is a :class:`tzinfo` instance or a timezone string.
1727        This timezone is used to create date labels.
1728        """
1729        # By providing a sample datetime instance with the desired
1730        # timezone, the registered converter can be selected,
1731        # and the "units" attribute, which is the timezone, can
1732        # be set.
1733        import datetime
1734        if isinstance(tz, six.string_types):
1735            import pytz
1736            tz = pytz.timezone(tz)
1737        self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz))
1738
1739    def get_tick_space(self):
1740        """
1741        Return the estimated number of ticks that can fit on the axis.
1742        """
1743        # Must be overridden in the subclass
1744        raise NotImplementedError()
1745
1746    def get_label_position(self):
1747        """
1748        Return the label position (top or bottom)
1749        """
1750        return self.label_position
1751
1752    def set_label_position(self, position):
1753        """
1754        Set the label position (top or bottom)
1755
1756        ACCEPTS: [ 'top' | 'bottom' ]
1757        """
1758        raise NotImplementedError()
1759
1760    def get_minpos(self):
1761        raise NotImplementedError()
1762
1763
1764class XAxis(Axis):
1765    __name__ = 'xaxis'
1766    axis_name = 'x'
1767
1768    def contains(self, mouseevent):
1769        """Test whether the mouse event occurred in the x axis.
1770        """
1771        if callable(self._contains):
1772            return self._contains(self, mouseevent)
1773
1774        x, y = mouseevent.x, mouseevent.y
1775        try:
1776            trans = self.axes.transAxes.inverted()
1777            xaxes, yaxes = trans.transform_point((x, y))
1778        except ValueError:
1779            return False, {}
1780        l, b = self.axes.transAxes.transform_point((0, 0))
1781        r, t = self.axes.transAxes.transform_point((1, 1))
1782        inaxis = xaxes >= 0 and xaxes <= 1 and (
1783            (y < b and y > b - self.pickradius) or
1784            (y > t and y < t + self.pickradius))
1785        return inaxis, {}
1786
1787    def _get_tick(self, major):
1788        if major:
1789            tick_kw = self._major_tick_kw
1790        else:
1791            tick_kw = self._minor_tick_kw
1792        return XTick(self.axes, 0, '', major=major, **tick_kw)
1793
1794    def _get_label(self):
1795        # x in axes coords, y in display coords (to be updated at draw
1796        # time by _update_label_positions)
1797        label = mtext.Text(x=0.5, y=0,
1798                           fontproperties=font_manager.FontProperties(
1799                               size=rcParams['axes.labelsize'],
1800                               weight=rcParams['axes.labelweight']),
1801                           color=rcParams['axes.labelcolor'],
1802                           verticalalignment='top',
1803                           horizontalalignment='center')
1804
1805        label.set_transform(mtransforms.blended_transform_factory(
1806            self.axes.transAxes, mtransforms.IdentityTransform()))
1807
1808        self._set_artist_props(label)
1809        self.label_position = 'bottom'
1810        return label
1811
1812    def _get_offset_text(self):
1813        # x in axes coords, y in display coords (to be updated at draw time)
1814        offsetText = mtext.Text(x=1, y=0,
1815                                fontproperties=font_manager.FontProperties(
1816                                    size=rcParams['xtick.labelsize']),
1817                                color=rcParams['xtick.color'],
1818                                verticalalignment='top',
1819                                horizontalalignment='right')
1820        offsetText.set_transform(mtransforms.blended_transform_factory(
1821            self.axes.transAxes, mtransforms.IdentityTransform())
1822        )
1823        self._set_artist_props(offsetText)
1824        self.offset_text_position = 'bottom'
1825        return offsetText
1826
1827    def _get_pixel_distance_along_axis(self, where, perturb):
1828        """
1829        Returns the amount, in data coordinates, that a single pixel
1830        corresponds to in the locality given by "where", which is also given
1831        in data coordinates, and is an x coordinate. "perturb" is the amount
1832        to perturb the pixel.  Usually +0.5 or -0.5.
1833
1834        Implementing this routine for an axis is optional; if present, it will
1835        ensure that no ticks are lost due to round-off at the extreme ends of
1836        an axis.
1837        """
1838
1839        # Note that this routine does not work for a polar axis, because of
1840        # the 1e-10 below.  To do things correctly, we need to use rmax
1841        # instead of 1e-10 for a polar axis.  But since we do not have that
1842        # kind of information at this point, we just don't try to pad anything
1843        # for the theta axis of a polar plot.
1844        if self.axes.name == 'polar':
1845            return 0.0
1846
1847        #
1848        # first figure out the pixel location of the "where" point.  We use
1849        # 1e-10 for the y point, so that we remain compatible with log axes.
1850
1851        # transformation from data coords to display coords
1852        trans = self.axes.transData
1853        # transformation from display coords to data coords
1854        transinv = trans.inverted()
1855        pix = trans.transform_point((where, 1e-10))
1856        # perturb the pixel
1857        ptp = transinv.transform_point((pix[0] + perturb, pix[1]))
1858        dx = abs(ptp[0] - where)
1859
1860        return dx
1861
1862    def set_label_position(self, position):
1863        """
1864        Set the label position (top or bottom)
1865
1866        ACCEPTS: [ 'top' | 'bottom' ]
1867        """
1868        if position == 'top':
1869            self.label.set_verticalalignment('baseline')
1870        elif position == 'bottom':
1871            self.label.set_verticalalignment('top')
1872        else:
1873            raise ValueError("Position accepts only 'top' or 'bottom'")
1874        self.label_position = position
1875        self.stale = True
1876
1877    def _get_tick_boxes_siblings(self, renderer):
1878        """
1879        Get the bounding boxes for this `.axis` and its siblings
1880        as set by `.Figure.align_xlabels` or  `.Figure.align_ylablels`.
1881
1882        By default it just gets bboxes for self.
1883        """
1884        bboxes = []
1885        bboxes2 = []
1886        # get the Grouper that keeps track of x-label groups for this figure
1887        grp = self.figure._align_xlabel_grp
1888        # if we want to align labels from other axes:
1889        for nn, axx in enumerate(grp.get_siblings(self.axes)):
1890            ticks_to_draw = axx.xaxis._update_ticks(renderer)
1891            tlb, tlb2 = axx.xaxis._get_tick_bboxes(ticks_to_draw, renderer)
1892            bboxes.extend(tlb)
1893            bboxes2.extend(tlb2)
1894        return bboxes, bboxes2
1895
1896    def _update_label_position(self, renderer):
1897        """
1898        Update the label position based on the bounding box enclosing
1899        all the ticklabels and axis spine
1900        """
1901        if not self._autolabelpos:
1902            return
1903
1904        # get bounding boxes for this axis and any siblings
1905        # that have been set by `fig.align_xlabels()`
1906        bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
1907
1908        x, y = self.label.get_position()
1909        if self.label_position == 'bottom':
1910            try:
1911                spine = self.axes.spines['bottom']
1912                spinebbox = spine.get_transform().transform_path(
1913                    spine.get_path()).get_extents()
1914            except KeyError:
1915                # use axes if spine doesn't exist
1916                spinebbox = self.axes.bbox
1917            bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
1918            bottom = bbox.y0
1919
1920            self.label.set_position(
1921                (x, bottom - self.labelpad * self.figure.dpi / 72.0)
1922            )
1923
1924        else:
1925            try:
1926                spine = self.axes.spines['top']
1927                spinebbox = spine.get_transform().transform_path(
1928                    spine.get_path()).get_extents()
1929            except KeyError:
1930                # use axes if spine doesn't exist
1931                spinebbox = self.axes.bbox
1932            bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox])
1933            top = bbox.y1
1934
1935            self.label.set_position(
1936                (x, top + self.labelpad * self.figure.dpi / 72.0)
1937            )
1938
1939    def _update_offset_text_position(self, bboxes, bboxes2):
1940        """
1941        Update the offset_text position based on the sequence of bounding
1942        boxes of all the ticklabels
1943        """
1944        x, y = self.offsetText.get_position()
1945        if not len(bboxes):
1946            bottom = self.axes.bbox.ymin
1947        else:
1948            bbox = mtransforms.Bbox.union(bboxes)
1949            bottom = bbox.y0
1950        self.offsetText.set_position(
1951            (x, bottom - self.OFFSETTEXTPAD * self.figure.dpi / 72.0)
1952        )
1953
1954    def get_text_heights(self, renderer):
1955        """
1956        Returns the amount of space one should reserve for text
1957        above and below the axes.  Returns a tuple (above, below)
1958        """
1959        bbox, bbox2 = self.get_ticklabel_extents(renderer)
1960        # MGDTODO: Need a better way to get the pad
1961        padPixels = self.majorTicks[0].get_pad_pixels()
1962
1963        above = 0.0
1964        if bbox2.height:
1965            above += bbox2.height + padPixels
1966        below = 0.0
1967        if bbox.height:
1968            below += bbox.height + padPixels
1969
1970        if self.get_label_position() == 'top':
1971            above += self.label.get_window_extent(renderer).height + padPixels
1972        else:
1973            below += self.label.get_window_extent(renderer).height + padPixels
1974        return above, below
1975
1976    def set_ticks_position(self, position):
1977        """
1978        Set the ticks position (top, bottom, both, default or none)
1979        both sets the ticks to appear on both positions, but does not
1980        change the tick labels.  'default' resets the tick positions to
1981        the default: ticks on both positions, labels at bottom.  'none'
1982        can be used if you don't want any ticks. 'none' and 'both'
1983        affect only the ticks, not the labels.
1984
1985        ACCEPTS: [ 'top' | 'bottom' | 'both' | 'default' | 'none' ]
1986        """
1987        if position == 'top':
1988            self.set_tick_params(which='both', top=True, labeltop=True,
1989                                 bottom=False, labelbottom=False)
1990        elif position == 'bottom':
1991            self.set_tick_params(which='both', top=False, labeltop=False,
1992                                 bottom=True, labelbottom=True)
1993        elif position == 'both':
1994            self.set_tick_params(which='both', top=True,
1995                                 bottom=True)
1996        elif position == 'none':
1997            self.set_tick_params(which='both', top=False,
1998                                 bottom=False)
1999        elif position == 'default':
2000            self.set_tick_params(which='both', top=True, labeltop=False,
2001                                 bottom=True, labelbottom=True)
2002        else:
2003            raise ValueError("invalid position: %s" % position)
2004        self.stale = True
2005
2006    def tick_top(self):
2007        """
2008        Move ticks and ticklabels (if present) to the top of the axes.
2009        """
2010        label = True
2011        if 'label1On' in self._major_tick_kw:
2012            label = (self._major_tick_kw['label1On']
2013                     or self._major_tick_kw['label2On'])
2014        self.set_ticks_position('top')
2015        # if labels were turned off before this was called
2016        # leave them off
2017        self.set_tick_params(which='both', labeltop=label)
2018
2019    def tick_bottom(self):
2020        """
2021        Move ticks and ticklabels (if present) to the bottom of the axes.
2022        """
2023        label = True
2024        if 'label1On' in self._major_tick_kw:
2025            label = (self._major_tick_kw['label1On']
2026                     or self._major_tick_kw['label2On'])
2027        self.set_ticks_position('bottom')
2028        # if labels were turned off before this was called
2029        # leave them off
2030        self.set_tick_params(which='both', labelbottom=label)
2031
2032    def get_ticks_position(self):
2033        """
2034        Return the ticks position (top, bottom, default or unknown)
2035        """
2036        majt = self.majorTicks[0]
2037        mT = self.minorTicks[0]
2038
2039        majorTop = ((not majt.tick1On) and majt.tick2On and
2040                    (not majt.label1On) and majt.label2On)
2041        minorTop = ((not mT.tick1On) and mT.tick2On and
2042                    (not mT.label1On) and mT.label2On)
2043        if majorTop and minorTop:
2044            return 'top'
2045
2046        MajorBottom = (majt.tick1On and (not majt.tick2On) and
2047                       majt.label1On and (not majt.label2On))
2048        MinorBottom = (mT.tick1On and (not mT.tick2On) and
2049                       mT.label1On and (not mT.label2On))
2050        if MajorBottom and MinorBottom:
2051            return 'bottom'
2052
2053        majorDefault = (majt.tick1On and majt.tick2On and
2054                        majt.label1On and (not majt.label2On))
2055        minorDefault = (mT.tick1On and mT.tick2On and
2056                        mT.label1On and (not mT.label2On))
2057        if majorDefault and minorDefault:
2058            return 'default'
2059
2060        return 'unknown'
2061
2062    def get_view_interval(self):
2063        'return the Interval instance for this axis view limits'
2064        return self.axes.viewLim.intervalx
2065
2066    def set_view_interval(self, vmin, vmax, ignore=False):
2067        """
2068        If *ignore* is *False*, the order of vmin, vmax
2069        does not matter; the original axis orientation will
2070        be preserved. In addition, the view limits can be
2071        expanded, but will not be reduced.  This method is
2072        for mpl internal use; for normal use, see
2073        :meth:`~matplotlib.axes.Axes.set_xlim`.
2074
2075        """
2076        if ignore:
2077            self.axes.viewLim.intervalx = vmin, vmax
2078        else:
2079            Vmin, Vmax = self.get_view_interval()
2080            if Vmin < Vmax:
2081                self.axes.viewLim.intervalx = (min(vmin, vmax, Vmin),
2082                                               max(vmin, vmax, Vmax))
2083            else:
2084                self.axes.viewLim.intervalx = (max(vmin, vmax, Vmin),
2085                                               min(vmin, vmax, Vmax))
2086
2087    def get_minpos(self):
2088        return self.axes.dataLim.minposx
2089
2090    def get_data_interval(self):
2091        'return the Interval instance for this axis data limits'
2092        return self.axes.dataLim.intervalx
2093
2094    def set_data_interval(self, vmin, vmax, ignore=False):
2095        'set the axis data limits'
2096        if ignore:
2097            self.axes.dataLim.intervalx = vmin, vmax
2098        else:
2099            Vmin, Vmax = self.get_data_interval()
2100            self.axes.dataLim.intervalx = min(vmin, Vmin), max(vmax, Vmax)
2101        self.stale = True
2102
2103    def set_default_intervals(self):
2104        'set the default limits for the axis interval if they are not mutated'
2105        xmin, xmax = 0., 1.
2106        dataMutated = self.axes.dataLim.mutatedx()
2107        viewMutated = self.axes.viewLim.mutatedx()
2108        if not dataMutated or not viewMutated:
2109            if self.converter is not None:
2110                info = self.converter.axisinfo(self.units, self)
2111                if info.default_limits is not None:
2112                    valmin, valmax = info.default_limits
2113                    xmin = self.converter.convert(valmin, self.units, self)
2114                    xmax = self.converter.convert(valmax, self.units, self)
2115            if not dataMutated:
2116                self.axes.dataLim.intervalx = xmin, xmax
2117            if not viewMutated:
2118                self.axes.viewLim.intervalx = xmin, xmax
2119        self.stale = True
2120
2121    def get_tick_space(self):
2122        ends = self.axes.transAxes.transform([[0, 0], [1, 0]])
2123        length = ((ends[1][0] - ends[0][0]) / self.axes.figure.dpi) * 72.0
2124        tick = self._get_tick(True)
2125        # There is a heuristic here that the aspect ratio of tick text
2126        # is no more than 3:1
2127        size = tick.label1.get_size() * 3
2128        if size > 0:
2129            return int(np.floor(length / size))
2130        else:
2131            return 2**31 - 1
2132
2133
2134class YAxis(Axis):
2135    __name__ = 'yaxis'
2136    axis_name = 'y'
2137
2138    def contains(self, mouseevent):
2139        """Test whether the mouse event occurred in the y axis.
2140
2141        Returns *True* | *False*
2142        """
2143        if callable(self._contains):
2144            return self._contains(self, mouseevent)
2145
2146        x, y = mouseevent.x, mouseevent.y
2147        try:
2148            trans = self.axes.transAxes.inverted()
2149            xaxes, yaxes = trans.transform_point((x, y))
2150        except ValueError:
2151            return False, {}
2152        l, b = self.axes.transAxes.transform_point((0, 0))
2153        r, t = self.axes.transAxes.transform_point((1, 1))
2154        inaxis = yaxes >= 0 and yaxes <= 1 and (
2155            (x < l and x > l - self.pickradius) or
2156            (x > r and x < r + self.pickradius))
2157        return inaxis, {}
2158
2159    def _get_tick(self, major):
2160        if major:
2161            tick_kw = self._major_tick_kw
2162        else:
2163            tick_kw = self._minor_tick_kw
2164        return YTick(self.axes, 0, '', major=major, **tick_kw)
2165
2166    def _get_label(self):
2167        # x in display coords (updated by _update_label_position)
2168        # y in axes coords
2169        label = mtext.Text(x=0, y=0.5,
2170                           # todo: get the label position
2171                           fontproperties=font_manager.FontProperties(
2172                               size=rcParams['axes.labelsize'],
2173                               weight=rcParams['axes.labelweight']),
2174                           color=rcParams['axes.labelcolor'],
2175                           verticalalignment='bottom',
2176                           horizontalalignment='center',
2177                           rotation='vertical',
2178                           rotation_mode='anchor')
2179        label.set_transform(mtransforms.blended_transform_factory(
2180            mtransforms.IdentityTransform(), self.axes.transAxes))
2181
2182        self._set_artist_props(label)
2183        self.label_position = 'left'
2184        return label
2185
2186    def _get_offset_text(self):
2187        # x in display coords, y in axes coords (to be updated at draw time)
2188        offsetText = mtext.Text(x=0, y=0.5,
2189                                fontproperties=font_manager.FontProperties(
2190                                    size=rcParams['ytick.labelsize']
2191                                ),
2192                                color=rcParams['ytick.color'],
2193                                verticalalignment='baseline',
2194                                horizontalalignment='left')
2195        offsetText.set_transform(mtransforms.blended_transform_factory(
2196            self.axes.transAxes, mtransforms.IdentityTransform())
2197        )
2198        self._set_artist_props(offsetText)
2199        self.offset_text_position = 'left'
2200        return offsetText
2201
2202    def _get_pixel_distance_along_axis(self, where, perturb):
2203        """
2204        Returns the amount, in data coordinates, that a single pixel
2205        corresponds to in the locality given by *where*, which is also given
2206        in data coordinates, and is a y coordinate.
2207
2208        *perturb* is the amount to perturb the pixel.  Usually +0.5 or -0.5.
2209
2210        Implementing this routine for an axis is optional; if present, it will
2211        ensure that no ticks are lost due to round-off at the extreme ends of
2212        an axis.
2213        """
2214
2215        #
2216        # first figure out the pixel location of the "where" point.  We use
2217        # 1e-10 for the x point, so that we remain compatible with log axes.
2218
2219        # transformation from data coords to display coords
2220        trans = self.axes.transData
2221        # transformation from display coords to data coords
2222        transinv = trans.inverted()
2223        pix = trans.transform_point((1e-10, where))
2224        # perturb the pixel
2225        ptp = transinv.transform_point((pix[0], pix[1] + perturb))
2226        dy = abs(ptp[1] - where)
2227        return dy
2228
2229    def set_label_position(self, position):
2230        """
2231        Set the label position (left or right)
2232
2233        ACCEPTS: [ 'left' | 'right' ]
2234        """
2235        self.label.set_rotation_mode('anchor')
2236        self.label.set_horizontalalignment('center')
2237        if position == 'left':
2238            self.label.set_verticalalignment('bottom')
2239        elif position == 'right':
2240            self.label.set_verticalalignment('top')
2241        else:
2242            raise ValueError("Position accepts only 'left' or 'right'")
2243        self.label_position = position
2244        self.stale = True
2245
2246    def _get_tick_boxes_siblings(self, renderer):
2247        """
2248        Get the bounding boxes for this `.axis` and its siblings
2249        as set by `.Figure.align_xlabels` or  `.Figure.align_ylablels`.
2250
2251        By default it just gets bboxes for self.
2252        """
2253        bboxes = []
2254        bboxes2 = []
2255        # get the Grouper that keeps track of y-label groups for this figure
2256        grp = self.figure._align_ylabel_grp
2257        # if we want to align labels from other axes:
2258        for axx in grp.get_siblings(self.axes):
2259            ticks_to_draw = axx.yaxis._update_ticks(renderer)
2260            tlb, tlb2 = axx.yaxis._get_tick_bboxes(ticks_to_draw, renderer)
2261            bboxes.extend(tlb)
2262            bboxes2.extend(tlb2)
2263        return bboxes, bboxes2
2264
2265    def _update_label_position(self, renderer):
2266        """
2267        Update the label position based on the bounding box enclosing
2268        all the ticklabels and axis spine
2269        """
2270        if not self._autolabelpos:
2271            return
2272
2273        # get bounding boxes for this axis and any siblings
2274        # that have been set by `fig.align_ylabels()`
2275        bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2276
2277        x, y = self.label.get_position()
2278        if self.label_position == 'left':
2279            try:
2280                spine = self.axes.spines['left']
2281                spinebbox = spine.get_transform().transform_path(
2282                    spine.get_path()).get_extents()
2283            except KeyError:
2284                # use axes if spine doesn't exist
2285                spinebbox = self.axes.bbox
2286            bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
2287            left = bbox.x0
2288            self.label.set_position(
2289                (left - self.labelpad * self.figure.dpi / 72.0, y)
2290            )
2291
2292        else:
2293            try:
2294                spine = self.axes.spines['right']
2295                spinebbox = spine.get_transform().transform_path(
2296                    spine.get_path()).get_extents()
2297            except KeyError:
2298                # use axes if spine doesn't exist
2299                spinebbox = self.axes.bbox
2300            bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox])
2301            right = bbox.x1
2302
2303            self.label.set_position(
2304                (right + self.labelpad * self.figure.dpi / 72.0, y)
2305            )
2306
2307    def _update_offset_text_position(self, bboxes, bboxes2):
2308        """
2309        Update the offset_text position based on the sequence of bounding
2310        boxes of all the ticklabels
2311        """
2312        x, y = self.offsetText.get_position()
2313        top = self.axes.bbox.ymax
2314        self.offsetText.set_position(
2315            (x, top + self.OFFSETTEXTPAD * self.figure.dpi / 72.0)
2316        )
2317
2318    def set_offset_position(self, position):
2319        """
2320        .. ACCEPTS: [ 'left' | 'right' ]
2321        """
2322        x, y = self.offsetText.get_position()
2323        if position == 'left':
2324            x = 0
2325        elif position == 'right':
2326            x = 1
2327        else:
2328            raise ValueError("Position accepts only [ 'left' | 'right' ]")
2329
2330        self.offsetText.set_ha(position)
2331        self.offsetText.set_position((x, y))
2332        self.stale = True
2333
2334    def get_text_widths(self, renderer):
2335        bbox, bbox2 = self.get_ticklabel_extents(renderer)
2336        # MGDTODO: Need a better way to get the pad
2337        padPixels = self.majorTicks[0].get_pad_pixels()
2338
2339        left = 0.0
2340        if bbox.width:
2341            left += bbox.width + padPixels
2342        right = 0.0
2343        if bbox2.width:
2344            right += bbox2.width + padPixels
2345
2346        if self.get_label_position() == 'left':
2347            left += self.label.get_window_extent(renderer).width + padPixels
2348        else:
2349            right += self.label.get_window_extent(renderer).width + padPixels
2350        return left, right
2351
2352    def set_ticks_position(self, position):
2353        """
2354        Set the ticks position (left, right, both, default or none)
2355        'both' sets the ticks to appear on both positions, but does not
2356        change the tick labels.  'default' resets the tick positions to
2357        the default: ticks on both positions, labels at left.  'none'
2358        can be used if you don't want any ticks. 'none' and 'both'
2359        affect only the ticks, not the labels.
2360
2361        ACCEPTS: [ 'left' | 'right' | 'both' | 'default' | 'none' ]
2362        """
2363        if position == 'right':
2364            self.set_tick_params(which='both', right=True, labelright=True,
2365                                 left=False, labelleft=False)
2366            self.set_offset_position(position)
2367        elif position == 'left':
2368            self.set_tick_params(which='both', right=False, labelright=False,
2369                                 left=True, labelleft=True)
2370            self.set_offset_position(position)
2371        elif position == 'both':
2372            self.set_tick_params(which='both', right=True,
2373                                 left=True)
2374        elif position == 'none':
2375            self.set_tick_params(which='both', right=False,
2376                                 left=False)
2377        elif position == 'default':
2378            self.set_tick_params(which='both', right=True, labelright=False,
2379                                 left=True, labelleft=True)
2380        else:
2381            raise ValueError("invalid position: %s" % position)
2382        self.stale = True
2383
2384    def tick_right(self):
2385        """
2386        Move ticks and ticklabels (if present) to the right of the axes.
2387        """
2388        label = True
2389        if 'label1On' in self._major_tick_kw:
2390            label = (self._major_tick_kw['label1On']
2391                     or self._major_tick_kw['label2On'])
2392        self.set_ticks_position('right')
2393        # if labels were turned off before this was called
2394        # leave them off
2395        self.set_tick_params(which='both', labelright=label)
2396
2397    def tick_left(self):
2398        """
2399        Move ticks and ticklabels (if present) to the left of the axes.
2400        """
2401        label = True
2402        if 'label1On' in self._major_tick_kw:
2403            label = (self._major_tick_kw['label1On']
2404                     or self._major_tick_kw['label2On'])
2405        self.set_ticks_position('left')
2406        # if labels were turned off before this was called
2407        # leave them off
2408        self.set_tick_params(which='both', labelleft=label)
2409
2410    def get_ticks_position(self):
2411        """
2412        Return the ticks position (left, right, both or unknown)
2413        """
2414        majt = self.majorTicks[0]
2415        mT = self.minorTicks[0]
2416
2417        majorRight = ((not majt.tick1On) and majt.tick2On and
2418                      (not majt.label1On) and majt.label2On)
2419        minorRight = ((not mT.tick1On) and mT.tick2On and
2420                      (not mT.label1On) and mT.label2On)
2421        if majorRight and minorRight:
2422            return 'right'
2423
2424        majorLeft = (majt.tick1On and (not majt.tick2On) and
2425                     majt.label1On and (not majt.label2On))
2426        minorLeft = (mT.tick1On and (not mT.tick2On) and
2427                     mT.label1On and (not mT.label2On))
2428        if majorLeft and minorLeft:
2429            return 'left'
2430
2431        majorDefault = (majt.tick1On and majt.tick2On and
2432                        majt.label1On and (not majt.label2On))
2433        minorDefault = (mT.tick1On and mT.tick2On and
2434                        mT.label1On and (not mT.label2On))
2435        if majorDefault and minorDefault:
2436            return 'default'
2437
2438        return 'unknown'
2439
2440    def get_view_interval(self):
2441        'return the Interval instance for this axis view limits'
2442        return self.axes.viewLim.intervaly
2443
2444    def set_view_interval(self, vmin, vmax, ignore=False):
2445        """
2446        If *ignore* is *False*, the order of vmin, vmax
2447        does not matter; the original axis orientation will
2448        be preserved. In addition, the view limits can be
2449        expanded, but will not be reduced.  This method is
2450        for mpl internal use; for normal use, see
2451        :meth:`~matplotlib.axes.Axes.set_ylim`.
2452
2453        """
2454        if ignore:
2455            self.axes.viewLim.intervaly = vmin, vmax
2456        else:
2457            Vmin, Vmax = self.get_view_interval()
2458            if Vmin < Vmax:
2459                self.axes.viewLim.intervaly = (min(vmin, vmax, Vmin),
2460                                               max(vmin, vmax, Vmax))
2461            else:
2462                self.axes.viewLim.intervaly = (max(vmin, vmax, Vmin),
2463                                               min(vmin, vmax, Vmax))
2464        self.stale = True
2465
2466    def get_minpos(self):
2467        return self.axes.dataLim.minposy
2468
2469    def get_data_interval(self):
2470        'return the Interval instance for this axis data limits'
2471        return self.axes.dataLim.intervaly
2472
2473    def set_data_interval(self, vmin, vmax, ignore=False):
2474        'set the axis data limits'
2475        if ignore:
2476            self.axes.dataLim.intervaly = vmin, vmax
2477        else:
2478            Vmin, Vmax = self.get_data_interval()
2479            self.axes.dataLim.intervaly = min(vmin, Vmin), max(vmax, Vmax)
2480        self.stale = True
2481
2482    def set_default_intervals(self):
2483        'set the default limits for the axis interval if they are not mutated'
2484        ymin, ymax = 0., 1.
2485        dataMutated = self.axes.dataLim.mutatedy()
2486        viewMutated = self.axes.viewLim.mutatedy()
2487        if not dataMutated or not viewMutated:
2488            if self.converter is not None:
2489                info = self.converter.axisinfo(self.units, self)
2490                if info.default_limits is not None:
2491                    valmin, valmax = info.default_limits
2492                    ymin = self.converter.convert(valmin, self.units, self)
2493                    ymax = self.converter.convert(valmax, self.units, self)
2494            if not dataMutated:
2495                self.axes.dataLim.intervaly = ymin, ymax
2496            if not viewMutated:
2497                self.axes.viewLim.intervaly = ymin, ymax
2498        self.stale = True
2499
2500    def get_tick_space(self):
2501        ends = self.axes.transAxes.transform([[0, 0], [0, 1]])
2502        length = ((ends[1][1] - ends[0][1]) / self.axes.figure.dpi) * 72.0
2503        tick = self._get_tick(True)
2504        # Having a spacing of at least 2 just looks good.
2505        size = tick.label1.get_size() * 2.0
2506        if size > 0:
2507            return int(np.floor(length / size))
2508        else:
2509            return 2**31 - 1
2510