Coverage for /usr/lib/python3/dist-packages/matplotlib/axis.py: 20%
1238 statements
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
« prev ^ index » next coverage.py v7.4.4, created at 2025-06-14 15:25 +0200
1"""
2Classes for the ticks and x- and y-axis.
3"""
5import datetime
6import functools
7import logging
8from numbers import Number
10import numpy as np
12import matplotlib as mpl
13from matplotlib import _api, cbook
14import matplotlib.artist as martist
15import matplotlib.colors as mcolors
16import matplotlib.lines as mlines
17import matplotlib.scale as mscale
18import matplotlib.text as mtext
19import matplotlib.ticker as mticker
20import matplotlib.transforms as mtransforms
21import matplotlib.units as munits
23_log = logging.getLogger(__name__)
25GRIDLINE_INTERPOLATION_STEPS = 180
27# This list is being used for compatibility with Axes.grid, which
28# allows all Line2D kwargs.
29_line_inspector = martist.ArtistInspector(mlines.Line2D)
30_line_param_names = _line_inspector.get_setters()
31_line_param_aliases = [list(d)[0] for d in _line_inspector.aliasd.values()]
32_gridline_param_names = ['grid_' + name
33 for name in _line_param_names + _line_param_aliases]
36class Tick(martist.Artist):
37 """
38 Abstract base class for the axis ticks, grid lines and labels.
40 Ticks mark a position on an Axis. They contain two lines as markers and
41 two labels; one each for the bottom and top positions (in case of an
42 `.XAxis`) or for the left and right positions (in case of a `.YAxis`).
44 Attributes
45 ----------
46 tick1line : `.Line2D`
47 The left/bottom tick marker.
48 tick2line : `.Line2D`
49 The right/top tick marker.
50 gridline : `.Line2D`
51 The grid line associated with the label position.
52 label1 : `.Text`
53 The left/bottom tick label.
54 label2 : `.Text`
55 The right/top tick label.
57 """
58 def __init__(
59 self, axes, loc, *,
60 size=None, # points
61 width=None,
62 color=None,
63 tickdir=None,
64 pad=None,
65 labelsize=None,
66 labelcolor=None,
67 zorder=None,
68 gridOn=None, # defaults to axes.grid depending on axes.grid.which
69 tick1On=True,
70 tick2On=True,
71 label1On=True,
72 label2On=False,
73 major=True,
74 labelrotation=0,
75 grid_color=None,
76 grid_linestyle=None,
77 grid_linewidth=None,
78 grid_alpha=None,
79 **kwargs, # Other Line2D kwargs applied to gridlines.
80 ):
81 """
82 bbox is the Bound2D bounding box in display coords of the Axes
83 loc is the tick location in data coords
84 size is the tick size in points
85 """
86 super().__init__()
88 if gridOn is None:
89 if major and (mpl.rcParams['axes.grid.which']
90 in ('both', 'major')):
91 gridOn = mpl.rcParams['axes.grid']
92 elif (not major) and (mpl.rcParams['axes.grid.which']
93 in ('both', 'minor')):
94 gridOn = mpl.rcParams['axes.grid']
95 else:
96 gridOn = False
98 self.set_figure(axes.figure)
99 self.axes = axes
101 self._loc = loc
102 self._major = major
104 name = self.__name__
105 major_minor = "major" if major else "minor"
107 if size is None:
108 size = mpl.rcParams[f"{name}.{major_minor}.size"]
109 self._size = size
111 if width is None:
112 width = mpl.rcParams[f"{name}.{major_minor}.width"]
113 self._width = width
115 if color is None:
116 color = mpl.rcParams[f"{name}.color"]
118 if pad is None:
119 pad = mpl.rcParams[f"{name}.{major_minor}.pad"]
120 self._base_pad = pad
122 if labelcolor is None:
123 labelcolor = mpl.rcParams[f"{name}.labelcolor"]
125 if labelcolor == 'inherit':
126 # inherit from tick color
127 labelcolor = mpl.rcParams[f"{name}.color"]
129 if labelsize is None:
130 labelsize = mpl.rcParams[f"{name}.labelsize"]
132 self._set_labelrotation(labelrotation)
134 if zorder is None:
135 if major:
136 zorder = mlines.Line2D.zorder + 0.01
137 else:
138 zorder = mlines.Line2D.zorder
139 self._zorder = zorder
141 if grid_color is None:
142 grid_color = mpl.rcParams["grid.color"]
143 if grid_linestyle is None:
144 grid_linestyle = mpl.rcParams["grid.linestyle"]
145 if grid_linewidth is None:
146 grid_linewidth = mpl.rcParams["grid.linewidth"]
147 if grid_alpha is None and not mcolors._has_alpha_channel(grid_color):
148 # alpha precedence: kwarg > color alpha > rcParams['grid.alpha']
149 # Note: only resolve to rcParams if the color does not have alpha
150 # otherwise `grid(color=(1, 1, 1, 0.5))` would work like
151 # grid(color=(1, 1, 1, 0.5), alpha=rcParams['grid.alpha'])
152 # so the that the rcParams default would override color alpha.
153 grid_alpha = mpl.rcParams["grid.alpha"]
154 grid_kw = {k[5:]: v for k, v in kwargs.items()}
156 self.tick1line = mlines.Line2D(
157 [], [],
158 color=color, linestyle="none", zorder=zorder, visible=tick1On,
159 markeredgecolor=color, markersize=size, markeredgewidth=width,
160 )
161 self.tick2line = mlines.Line2D(
162 [], [],
163 color=color, linestyle="none", zorder=zorder, visible=tick2On,
164 markeredgecolor=color, markersize=size, markeredgewidth=width,
165 )
166 self.gridline = mlines.Line2D(
167 [], [],
168 color=grid_color, alpha=grid_alpha, visible=gridOn,
169 linestyle=grid_linestyle, linewidth=grid_linewidth, marker="",
170 **grid_kw,
171 )
172 self.gridline.get_path()._interpolation_steps = \
173 GRIDLINE_INTERPOLATION_STEPS
174 self.label1 = mtext.Text(
175 np.nan, np.nan,
176 fontsize=labelsize, color=labelcolor, visible=label1On,
177 rotation=self._labelrotation[1])
178 self.label2 = mtext.Text(
179 np.nan, np.nan,
180 fontsize=labelsize, color=labelcolor, visible=label2On,
181 rotation=self._labelrotation[1])
183 self._apply_tickdir(tickdir)
185 for artist in [self.tick1line, self.tick2line, self.gridline,
186 self.label1, self.label2]:
187 self._set_artist_props(artist)
189 self.update_position(loc)
191 @property
192 @_api.deprecated("3.1", alternative="Tick.label1", removal="3.8")
193 def label(self):
194 return self.label1
196 def _set_labelrotation(self, labelrotation):
197 if isinstance(labelrotation, str):
198 mode = labelrotation
199 angle = 0
200 elif isinstance(labelrotation, (tuple, list)):
201 mode, angle = labelrotation
202 else:
203 mode = 'default'
204 angle = labelrotation
205 _api.check_in_list(['auto', 'default'], labelrotation=mode)
206 self._labelrotation = (mode, angle)
208 def _apply_tickdir(self, tickdir):
209 """Set tick direction. Valid values are 'out', 'in', 'inout'."""
210 # This method is responsible for updating `_pad`, and, in subclasses,
211 # for setting the tick{1,2}line markers as well. From the user
212 # perspective this should always be called though _apply_params, which
213 # further updates ticklabel positions using the new pads.
214 if tickdir is None:
215 tickdir = mpl.rcParams[f'{self.__name__}.direction']
216 _api.check_in_list(['in', 'out', 'inout'], tickdir=tickdir)
217 self._tickdir = tickdir
218 self._pad = self._base_pad + self.get_tick_padding()
220 @_api.deprecated("3.5", alternative="`.Axis.set_tick_params`")
221 def apply_tickdir(self, tickdir):
222 self._apply_tickdir(tickdir)
223 self.stale = True
225 def get_tickdir(self):
226 return self._tickdir
228 def get_tick_padding(self):
229 """Get the length of the tick outside of the Axes."""
230 padding = {
231 'in': 0.0,
232 'inout': 0.5,
233 'out': 1.0
234 }
235 return self._size * padding[self._tickdir]
237 def get_children(self):
238 children = [self.tick1line, self.tick2line,
239 self.gridline, self.label1, self.label2]
240 return children
242 def set_clip_path(self, clippath, transform=None):
243 # docstring inherited
244 super().set_clip_path(clippath, transform)
245 self.gridline.set_clip_path(clippath, transform)
246 self.stale = True
248 @_api.deprecated("3.6")
249 def get_pad_pixels(self):
250 return self.figure.dpi * self._base_pad / 72
252 def contains(self, mouseevent):
253 """
254 Test whether the mouse event occurred in the Tick marks.
256 This function always returns false. It is more useful to test if the
257 axis as a whole contains the mouse rather than the set of tick marks.
258 """
259 inside, info = self._default_contains(mouseevent)
260 if inside is not None:
261 return inside, info
262 return False, {}
264 def set_pad(self, val):
265 """
266 Set the tick label pad in points
268 Parameters
269 ----------
270 val : float
271 """
272 self._apply_params(pad=val)
273 self.stale = True
275 def get_pad(self):
276 """Get the value of the tick label pad in points."""
277 return self._base_pad
279 def _get_text1(self):
280 """Get the default Text 1 instance."""
282 def _get_text2(self):
283 """Get the default Text 2 instance."""
285 def _get_tick1line(self):
286 """Get the default `.Line2D` instance for tick1."""
288 def _get_tick2line(self):
289 """Get the default `.Line2D` instance for tick2."""
291 def _get_gridline(self):
292 """Get the default grid `.Line2D` instance for this tick."""
294 def get_loc(self):
295 """Return the tick location (data coords) as a scalar."""
296 return self._loc
298 @martist.allow_rasterization
299 def draw(self, renderer):
300 if not self.get_visible():
301 self.stale = False
302 return
303 renderer.open_group(self.__name__, gid=self.get_gid())
304 for artist in [self.gridline, self.tick1line, self.tick2line,
305 self.label1, self.label2]:
306 artist.draw(renderer)
307 renderer.close_group(self.__name__)
308 self.stale = False
310 def set_label1(self, s):
311 """
312 Set the label1 text.
314 Parameters
315 ----------
316 s : str
317 """
318 self.label1.set_text(s)
319 self.stale = True
321 set_label = set_label1
323 def set_label2(self, s):
324 """
325 Set the label2 text.
327 Parameters
328 ----------
329 s : str
330 """
331 self.label2.set_text(s)
332 self.stale = True
334 def set_url(self, url):
335 """
336 Set the url of label1 and label2.
338 Parameters
339 ----------
340 url : str
341 """
342 super().set_url(url)
343 self.label1.set_url(url)
344 self.label2.set_url(url)
345 self.stale = True
347 def _set_artist_props(self, a):
348 a.set_figure(self.figure)
350 def get_view_interval(self):
351 """
352 Return the view limits ``(min, max)`` of the axis the tick belongs to.
353 """
354 raise NotImplementedError('Derived must override')
356 def _apply_params(self, **kwargs):
357 for name, target in [("gridOn", self.gridline),
358 ("tick1On", self.tick1line),
359 ("tick2On", self.tick2line),
360 ("label1On", self.label1),
361 ("label2On", self.label2)]:
362 if name in kwargs:
363 target.set_visible(kwargs.pop(name))
364 if any(k in kwargs for k in ['size', 'width', 'pad', 'tickdir']):
365 self._size = kwargs.pop('size', self._size)
366 # Width could be handled outside this block, but it is
367 # convenient to leave it here.
368 self._width = kwargs.pop('width', self._width)
369 self._base_pad = kwargs.pop('pad', self._base_pad)
370 # _apply_tickdir uses _size and _base_pad to make _pad, and also
371 # sets the ticklines markers.
372 self._apply_tickdir(kwargs.pop('tickdir', self._tickdir))
373 for line in (self.tick1line, self.tick2line):
374 line.set_markersize(self._size)
375 line.set_markeredgewidth(self._width)
376 # _get_text1_transform uses _pad from _apply_tickdir.
377 trans = self._get_text1_transform()[0]
378 self.label1.set_transform(trans)
379 trans = self._get_text2_transform()[0]
380 self.label2.set_transform(trans)
381 tick_kw = {k: v for k, v in kwargs.items() if k in ['color', 'zorder']}
382 if 'color' in kwargs:
383 tick_kw['markeredgecolor'] = kwargs['color']
384 self.tick1line.set(**tick_kw)
385 self.tick2line.set(**tick_kw)
386 for k, v in tick_kw.items():
387 setattr(self, '_' + k, v)
389 if 'labelrotation' in kwargs:
390 self._set_labelrotation(kwargs.pop('labelrotation'))
391 self.label1.set(rotation=self._labelrotation[1])
392 self.label2.set(rotation=self._labelrotation[1])
394 label_kw = {k[5:]: v for k, v in kwargs.items()
395 if k in ['labelsize', 'labelcolor']}
396 self.label1.set(**label_kw)
397 self.label2.set(**label_kw)
399 grid_kw = {k[5:]: v for k, v in kwargs.items()
400 if k in _gridline_param_names}
401 self.gridline.set(**grid_kw)
403 def update_position(self, loc):
404 """Set the location of tick in data coords with scalar *loc*."""
405 raise NotImplementedError('Derived must override')
407 def _get_text1_transform(self):
408 raise NotImplementedError('Derived must override')
410 def _get_text2_transform(self):
411 raise NotImplementedError('Derived must override')
414class XTick(Tick):
415 """
416 Contains all the Artists needed to make an x tick - the tick line,
417 the label text and the grid line
418 """
419 __name__ = 'xtick'
421 def __init__(self, *args, **kwargs):
422 super().__init__(*args, **kwargs)
423 # x in data coords, y in axes coords
424 ax = self.axes
425 self.tick1line.set(
426 data=([0], [0]), transform=ax.get_xaxis_transform("tick1"))
427 self.tick2line.set(
428 data=([0], [1]), transform=ax.get_xaxis_transform("tick2"))
429 self.gridline.set(
430 data=([0, 0], [0, 1]), transform=ax.get_xaxis_transform("grid"))
431 # the y loc is 3 points below the min of y axis
432 trans, va, ha = self._get_text1_transform()
433 self.label1.set(
434 x=0, y=0,
435 verticalalignment=va, horizontalalignment=ha, transform=trans,
436 )
437 trans, va, ha = self._get_text2_transform()
438 self.label2.set(
439 x=0, y=1,
440 verticalalignment=va, horizontalalignment=ha, transform=trans,
441 )
443 def _get_text1_transform(self):
444 return self.axes.get_xaxis_text1_transform(self._pad)
446 def _get_text2_transform(self):
447 return self.axes.get_xaxis_text2_transform(self._pad)
449 def _apply_tickdir(self, tickdir):
450 # docstring inherited
451 super()._apply_tickdir(tickdir)
452 mark1, mark2 = {
453 'out': (mlines.TICKDOWN, mlines.TICKUP),
454 'in': (mlines.TICKUP, mlines.TICKDOWN),
455 'inout': ('|', '|'),
456 }[self._tickdir]
457 self.tick1line.set_marker(mark1)
458 self.tick2line.set_marker(mark2)
460 def update_position(self, loc):
461 """Set the location of tick in data coords with scalar *loc*."""
462 self.tick1line.set_xdata((loc,))
463 self.tick2line.set_xdata((loc,))
464 self.gridline.set_xdata((loc,))
465 self.label1.set_x(loc)
466 self.label2.set_x(loc)
467 self._loc = loc
468 self.stale = True
470 def get_view_interval(self):
471 # docstring inherited
472 return self.axes.viewLim.intervalx
475class YTick(Tick):
476 """
477 Contains all the Artists needed to make a Y tick - the tick line,
478 the label text and the grid line
479 """
480 __name__ = 'ytick'
482 def __init__(self, *args, **kwargs):
483 super().__init__(*args, **kwargs)
484 # x in axes coords, y in data coords
485 ax = self.axes
486 self.tick1line.set(
487 data=([0], [0]), transform=ax.get_yaxis_transform("tick1"))
488 self.tick2line.set(
489 data=([1], [0]), transform=ax.get_yaxis_transform("tick2"))
490 self.gridline.set(
491 data=([0, 1], [0, 0]), transform=ax.get_yaxis_transform("grid"))
492 # the y loc is 3 points below the min of y axis
493 trans, va, ha = self._get_text1_transform()
494 self.label1.set(
495 x=0, y=0,
496 verticalalignment=va, horizontalalignment=ha, transform=trans,
497 )
498 trans, va, ha = self._get_text2_transform()
499 self.label2.set(
500 x=1, y=0,
501 verticalalignment=va, horizontalalignment=ha, transform=trans,
502 )
504 def _get_text1_transform(self):
505 return self.axes.get_yaxis_text1_transform(self._pad)
507 def _get_text2_transform(self):
508 return self.axes.get_yaxis_text2_transform(self._pad)
510 def _apply_tickdir(self, tickdir):
511 # docstring inherited
512 super()._apply_tickdir(tickdir)
513 mark1, mark2 = {
514 'out': (mlines.TICKLEFT, mlines.TICKRIGHT),
515 'in': (mlines.TICKRIGHT, mlines.TICKLEFT),
516 'inout': ('_', '_'),
517 }[self._tickdir]
518 self.tick1line.set_marker(mark1)
519 self.tick2line.set_marker(mark2)
521 def update_position(self, loc):
522 """Set the location of tick in data coords with scalar *loc*."""
523 self.tick1line.set_ydata((loc,))
524 self.tick2line.set_ydata((loc,))
525 self.gridline.set_ydata((loc,))
526 self.label1.set_y(loc)
527 self.label2.set_y(loc)
528 self._loc = loc
529 self.stale = True
531 def get_view_interval(self):
532 # docstring inherited
533 return self.axes.viewLim.intervaly
536class Ticker:
537 """
538 A container for the objects defining tick position and format.
540 Attributes
541 ----------
542 locator : `matplotlib.ticker.Locator` subclass
543 Determines the positions of the ticks.
544 formatter : `matplotlib.ticker.Formatter` subclass
545 Determines the format of the tick labels.
546 """
548 def __init__(self):
549 self._locator = None
550 self._formatter = None
551 self._locator_is_default = True
552 self._formatter_is_default = True
554 @property
555 def locator(self):
556 return self._locator
558 @locator.setter
559 def locator(self, locator):
560 if not isinstance(locator, mticker.Locator):
561 raise TypeError('locator must be a subclass of '
562 'matplotlib.ticker.Locator')
563 self._locator = locator
565 @property
566 def formatter(self):
567 return self._formatter
569 @formatter.setter
570 def formatter(self, formatter):
571 if not isinstance(formatter, mticker.Formatter):
572 raise TypeError('formatter must be a subclass of '
573 'matplotlib.ticker.Formatter')
574 self._formatter = formatter
577class _LazyTickList:
578 """
579 A descriptor for lazy instantiation of tick lists.
581 See comment above definition of the ``majorTicks`` and ``minorTicks``
582 attributes.
583 """
585 def __init__(self, major):
586 self._major = major
588 def __get__(self, instance, cls):
589 if instance is None:
590 return self
591 else:
592 # instance._get_tick() can itself try to access the majorTicks
593 # attribute (e.g. in certain projection classes which override
594 # e.g. get_xaxis_text1_transform). In order to avoid infinite
595 # recursion, first set the majorTicks on the instance to an empty
596 # list, then create the tick and append it.
597 if self._major:
598 instance.majorTicks = []
599 tick = instance._get_tick(major=True)
600 instance.majorTicks.append(tick)
601 return instance.majorTicks
602 else:
603 instance.minorTicks = []
604 tick = instance._get_tick(major=False)
605 instance.minorTicks.append(tick)
606 return instance.minorTicks
609class Axis(martist.Artist):
610 """
611 Base class for `.XAxis` and `.YAxis`.
613 Attributes
614 ----------
615 isDefault_label : bool
617 axes : `matplotlib.axes.Axes`
618 The `~.axes.Axes` to which the Axis belongs.
619 major : `matplotlib.axis.Ticker`
620 Determines the major tick positions and their label format.
621 minor : `matplotlib.axis.Ticker`
622 Determines the minor tick positions and their label format.
623 callbacks : `matplotlib.cbook.CallbackRegistry`
625 label : `.Text`
626 The axis label.
627 labelpad : float
628 The distance between the axis label and the tick labels.
629 Defaults to :rc:`axes.labelpad` = 4.
630 offsetText : `.Text`
631 A `.Text` object containing the data offset of the ticks (if any).
632 pickradius : float
633 The acceptance radius for containment tests. See also `.Axis.contains`.
634 majorTicks : list of `.Tick`
635 The major ticks.
636 minorTicks : list of `.Tick`
637 The minor ticks.
638 """
639 OFFSETTEXTPAD = 3
640 # The class used in _get_tick() to create tick instances. Must either be
641 # overwritten in subclasses, or subclasses must reimplement _get_tick().
642 _tick_class = None
644 def __str__(self):
645 return "{}({},{})".format(
646 type(self).__name__, *self.axes.transAxes.transform((0, 0)))
648 @_api.make_keyword_only("3.6", name="pickradius")
649 def __init__(self, axes, pickradius=15):
650 """
651 Parameters
652 ----------
653 axes : `matplotlib.axes.Axes`
654 The `~.axes.Axes` to which the created Axis belongs.
655 pickradius : float
656 The acceptance radius for containment tests. See also
657 `.Axis.contains`.
658 """
659 super().__init__()
660 self._remove_overlapping_locs = True
662 self.set_figure(axes.figure)
664 self.isDefault_label = True
666 self.axes = axes
667 self.major = Ticker()
668 self.minor = Ticker()
669 self.callbacks = cbook.CallbackRegistry(
670 signals=["units", "units finalize"])
672 self._autolabelpos = True
674 self.label = mtext.Text(
675 np.nan, np.nan,
676 fontsize=mpl.rcParams['axes.labelsize'],
677 fontweight=mpl.rcParams['axes.labelweight'],
678 color=mpl.rcParams['axes.labelcolor'],
679 )
680 self._set_artist_props(self.label)
681 self.offsetText = mtext.Text(np.nan, np.nan)
682 self._set_artist_props(self.offsetText)
684 self.labelpad = mpl.rcParams['axes.labelpad']
686 self.pickradius = pickradius
688 # Initialize here for testing; later add API
689 self._major_tick_kw = dict()
690 self._minor_tick_kw = dict()
692 self.clear()
693 self._autoscale_on = True
695 @property
696 def isDefault_majloc(self):
697 return self.major._locator_is_default
699 @isDefault_majloc.setter
700 def isDefault_majloc(self, value):
701 self.major._locator_is_default = value
703 @property
704 def isDefault_majfmt(self):
705 return self.major._formatter_is_default
707 @isDefault_majfmt.setter
708 def isDefault_majfmt(self, value):
709 self.major._formatter_is_default = value
711 @property
712 def isDefault_minloc(self):
713 return self.minor._locator_is_default
715 @isDefault_minloc.setter
716 def isDefault_minloc(self, value):
717 self.minor._locator_is_default = value
719 @property
720 def isDefault_minfmt(self):
721 return self.minor._formatter_is_default
723 @isDefault_minfmt.setter
724 def isDefault_minfmt(self, value):
725 self.minor._formatter_is_default = value
727 # During initialization, Axis objects often create ticks that are later
728 # unused; this turns out to be a very slow step. Instead, use a custom
729 # descriptor to make the tick lists lazy and instantiate them as needed.
730 majorTicks = _LazyTickList(major=True)
731 minorTicks = _LazyTickList(major=False)
733 def get_remove_overlapping_locs(self):
734 return self._remove_overlapping_locs
736 def set_remove_overlapping_locs(self, val):
737 self._remove_overlapping_locs = bool(val)
739 remove_overlapping_locs = property(
740 get_remove_overlapping_locs, set_remove_overlapping_locs,
741 doc=('If minor ticker locations that overlap with major '
742 'ticker locations should be trimmed.'))
744 def set_label_coords(self, x, y, transform=None):
745 """
746 Set the coordinates of the label.
748 By default, the x coordinate of the y label and the y coordinate of the
749 x label are determined by the tick label bounding boxes, but this can
750 lead to poor alignment of multiple labels if there are multiple axes.
752 You can also specify the coordinate system of the label with the
753 transform. If None, the default coordinate system will be the axes
754 coordinate system: (0, 0) is bottom left, (0.5, 0.5) is center, etc.
755 """
756 self._autolabelpos = False
757 if transform is None:
758 transform = self.axes.transAxes
760 self.label.set_transform(transform)
761 self.label.set_position((x, y))
762 self.stale = True
764 def get_transform(self):
765 return self._scale.get_transform()
767 def get_scale(self):
768 """Return this Axis' scale (as a str)."""
769 return self._scale.name
771 def _set_scale(self, value, **kwargs):
772 if not isinstance(value, mscale.ScaleBase):
773 self._scale = mscale.scale_factory(value, self, **kwargs)
774 else:
775 self._scale = value
776 self._scale.set_default_locators_and_formatters(self)
778 self.isDefault_majloc = True
779 self.isDefault_minloc = True
780 self.isDefault_majfmt = True
781 self.isDefault_minfmt = True
783 # This method is directly wrapped by Axes.set_{x,y}scale.
784 def _set_axes_scale(self, value, **kwargs):
785 """
786 Set this Axis' scale.
788 Parameters
789 ----------
790 value : {"linear", "log", "symlog", "logit", ...} or `.ScaleBase`
791 The axis scale type to apply.
793 **kwargs
794 Different keyword arguments are accepted, depending on the scale.
795 See the respective class keyword arguments:
797 - `matplotlib.scale.LinearScale`
798 - `matplotlib.scale.LogScale`
799 - `matplotlib.scale.SymmetricalLogScale`
800 - `matplotlib.scale.LogitScale`
801 - `matplotlib.scale.FuncScale`
803 Notes
804 -----
805 By default, Matplotlib supports the above-mentioned scales.
806 Additionally, custom scales may be registered using
807 `matplotlib.scale.register_scale`. These scales can then also
808 be used here.
809 """
810 name, = [name for name, axis in self.axes._axis_map.items()
811 if axis is self] # The axis name.
812 old_default_lims = (self.get_major_locator()
813 .nonsingular(-np.inf, np.inf))
814 g = self.axes._shared_axes[name]
815 for ax in g.get_siblings(self.axes):
816 ax._axis_map[name]._set_scale(value, **kwargs)
817 ax._update_transScale()
818 ax.stale = True
819 new_default_lims = (self.get_major_locator()
820 .nonsingular(-np.inf, np.inf))
821 if old_default_lims != new_default_lims:
822 # Force autoscaling now, to take advantage of the scale locator's
823 # nonsingular() before it possibly gets swapped out by the user.
824 self.axes.autoscale_view(
825 **{f"scale{k}": k == name for k in self.axes._axis_names})
827 def limit_range_for_scale(self, vmin, vmax):
828 return self._scale.limit_range_for_scale(vmin, vmax, self.get_minpos())
830 def _get_autoscale_on(self):
831 """Return whether this Axis is autoscaled."""
832 return self._autoscale_on
834 def _set_autoscale_on(self, b):
835 """
836 Set whether this Axis is autoscaled when drawing or by
837 `.Axes.autoscale_view`.
839 Parameters
840 ----------
841 b : bool
842 """
843 self._autoscale_on = b
845 def get_children(self):
846 return [self.label, self.offsetText,
847 *self.get_major_ticks(), *self.get_minor_ticks()]
849 def _reset_major_tick_kw(self):
850 self._major_tick_kw.clear()
851 self._major_tick_kw['gridOn'] = (
852 mpl.rcParams['axes.grid'] and
853 mpl.rcParams['axes.grid.which'] in ('both', 'major'))
855 def _reset_minor_tick_kw(self):
856 self._minor_tick_kw.clear()
857 self._minor_tick_kw['gridOn'] = (
858 mpl.rcParams['axes.grid'] and
859 mpl.rcParams['axes.grid.which'] in ('both', 'minor'))
861 def clear(self):
862 """
863 Clear the axis.
865 This resets axis properties to their default values:
867 - the label
868 - the scale
869 - locators, formatters and ticks
870 - major and minor grid
871 - units
872 - registered callbacks
873 """
875 self.label.set_text('') # self.set_label_text would change isDefault_
877 self._set_scale('linear')
879 # Clear the callback registry for this axis, or it may "leak"
880 self.callbacks = cbook.CallbackRegistry(
881 signals=["units", "units finalize"])
883 # whether the grids are on
884 self._major_tick_kw['gridOn'] = (
885 mpl.rcParams['axes.grid'] and
886 mpl.rcParams['axes.grid.which'] in ('both', 'major'))
887 self._minor_tick_kw['gridOn'] = (
888 mpl.rcParams['axes.grid'] and
889 mpl.rcParams['axes.grid.which'] in ('both', 'minor'))
890 self.reset_ticks()
892 self.converter = None
893 self.units = None
894 self.set_units(None)
895 self.stale = True
897 def reset_ticks(self):
898 """
899 Re-initialize the major and minor Tick lists.
901 Each list starts with a single fresh Tick.
902 """
903 # Restore the lazy tick lists.
904 try:
905 del self.majorTicks
906 except AttributeError:
907 pass
908 try:
909 del self.minorTicks
910 except AttributeError:
911 pass
912 try:
913 self.set_clip_path(self.axes.patch)
914 except AttributeError:
915 pass
917 def set_tick_params(self, which='major', reset=False, **kwargs):
918 """
919 Set appearance parameters for ticks, ticklabels, and gridlines.
921 For documentation of keyword arguments, see
922 :meth:`matplotlib.axes.Axes.tick_params`.
923 """
924 _api.check_in_list(['major', 'minor', 'both'], which=which)
925 kwtrans = self._translate_tick_params(kwargs)
927 # the kwargs are stored in self._major/minor_tick_kw so that any
928 # future new ticks will automatically get them
929 if reset:
930 if which in ['major', 'both']:
931 self._reset_major_tick_kw()
932 self._major_tick_kw.update(kwtrans)
933 if which in ['minor', 'both']:
934 self._reset_minor_tick_kw()
935 self._minor_tick_kw.update(kwtrans)
936 self.reset_ticks()
937 else:
938 if which in ['major', 'both']:
939 self._major_tick_kw.update(kwtrans)
940 for tick in self.majorTicks:
941 tick._apply_params(**kwtrans)
942 if which in ['minor', 'both']:
943 self._minor_tick_kw.update(kwtrans)
944 for tick in self.minorTicks:
945 tick._apply_params(**kwtrans)
946 # labelOn and labelcolor also apply to the offset text.
947 if 'label1On' in kwtrans or 'label2On' in kwtrans:
948 self.offsetText.set_visible(
949 self._major_tick_kw.get('label1On', False)
950 or self._major_tick_kw.get('label2On', False))
951 if 'labelcolor' in kwtrans:
952 self.offsetText.set_color(kwtrans['labelcolor'])
954 self.stale = True
956 @staticmethod
957 def _translate_tick_params(kw):
958 """
959 Translate the kwargs supported by `.Axis.set_tick_params` to kwargs
960 supported by `.Tick._apply_params`.
962 In particular, this maps axis specific names like 'top', 'left'
963 to the generic tick1, tick2 logic of the axis. Additionally, there
964 are some other name translations.
966 Returns a new dict of translated kwargs.
968 Note: The input *kwargs* are currently modified, but that's ok for
969 the only caller.
970 """
971 # The following lists may be moved to a more accessible location.
972 allowed_keys = [
973 'size', 'width', 'color', 'tickdir', 'pad',
974 'labelsize', 'labelcolor', 'zorder', 'gridOn',
975 'tick1On', 'tick2On', 'label1On', 'label2On',
976 'length', 'direction', 'left', 'bottom', 'right', 'top',
977 'labelleft', 'labelbottom', 'labelright', 'labeltop',
978 'labelrotation',
979 *_gridline_param_names]
981 keymap = {
982 # tick_params key -> axis key
983 'length': 'size',
984 'direction': 'tickdir',
985 'rotation': 'labelrotation',
986 'left': 'tick1On',
987 'bottom': 'tick1On',
988 'right': 'tick2On',
989 'top': 'tick2On',
990 'labelleft': 'label1On',
991 'labelbottom': 'label1On',
992 'labelright': 'label2On',
993 'labeltop': 'label2On',
994 }
995 kwtrans = {newkey: kw.pop(oldkey)
996 for oldkey, newkey in keymap.items() if oldkey in kw}
997 if 'colors' in kw:
998 c = kw.pop('colors')
999 kwtrans['color'] = c
1000 kwtrans['labelcolor'] = c
1001 # Maybe move the checking up to the caller of this method.
1002 for key in kw:
1003 if key not in allowed_keys:
1004 raise ValueError(
1005 "keyword %s is not recognized; valid keywords are %s"
1006 % (key, allowed_keys))
1007 kwtrans.update(kw)
1008 return kwtrans
1010 def set_clip_path(self, clippath, transform=None):
1011 super().set_clip_path(clippath, transform)
1012 for child in self.majorTicks + self.minorTicks:
1013 child.set_clip_path(clippath, transform)
1014 self.stale = True
1016 def get_view_interval(self):
1017 """Return the ``(min, max)`` view limits of this axis."""
1018 raise NotImplementedError('Derived must override')
1020 def set_view_interval(self, vmin, vmax, ignore=False):
1021 """
1022 Set the axis view limits. This method is for internal use; Matplotlib
1023 users should typically use e.g. `~.Axes.set_xlim` or `~.Axes.set_ylim`.
1025 If *ignore* is False (the default), this method will never reduce the
1026 preexisting view limits, only expand them if *vmin* or *vmax* are not
1027 within them. Moreover, the order of *vmin* and *vmax* does not matter;
1028 the orientation of the axis will not change.
1030 If *ignore* is True, the view limits will be set exactly to ``(vmin,
1031 vmax)`` in that order.
1032 """
1033 raise NotImplementedError('Derived must override')
1035 def get_data_interval(self):
1036 """Return the ``(min, max)`` data limits of this axis."""
1037 raise NotImplementedError('Derived must override')
1039 def set_data_interval(self, vmin, vmax, ignore=False):
1040 """
1041 Set the axis data limits. This method is for internal use.
1043 If *ignore* is False (the default), this method will never reduce the
1044 preexisting data limits, only expand them if *vmin* or *vmax* are not
1045 within them. Moreover, the order of *vmin* and *vmax* does not matter;
1046 the orientation of the axis will not change.
1048 If *ignore* is True, the data limits will be set exactly to ``(vmin,
1049 vmax)`` in that order.
1050 """
1051 raise NotImplementedError('Derived must override')
1053 def get_inverted(self):
1054 """
1055 Return whether this Axis is oriented in the "inverse" direction.
1057 The "normal" direction is increasing to the right for the x-axis and to
1058 the top for the y-axis; the "inverse" direction is increasing to the
1059 left for the x-axis and to the bottom for the y-axis.
1060 """
1061 low, high = self.get_view_interval()
1062 return high < low
1064 def set_inverted(self, inverted):
1065 """
1066 Set whether this Axis is oriented in the "inverse" direction.
1068 The "normal" direction is increasing to the right for the x-axis and to
1069 the top for the y-axis; the "inverse" direction is increasing to the
1070 left for the x-axis and to the bottom for the y-axis.
1071 """
1072 a, b = self.get_view_interval()
1073 # cast to bool to avoid bad interaction between python 3.8 and np.bool_
1074 self._set_lim(*sorted((a, b), reverse=bool(inverted)), auto=None)
1076 def set_default_intervals(self):
1077 """
1078 Set the default limits for the axis data and view interval if they
1079 have not been not mutated yet.
1080 """
1081 # this is mainly in support of custom object plotting. For
1082 # example, if someone passes in a datetime object, we do not
1083 # know automagically how to set the default min/max of the
1084 # data and view limits. The unit conversion AxisInfo
1085 # interface provides a hook for custom types to register
1086 # default limits through the AxisInfo.default_limits
1087 # attribute, and the derived code below will check for that
1088 # and use it if it's available (else just use 0..1)
1090 def _set_lim(self, v0, v1, *, emit=True, auto):
1091 """
1092 Set view limits.
1094 This method is a helper for the Axes ``set_xlim``, ``set_ylim``, and
1095 ``set_zlim`` methods.
1097 Parameters
1098 ----------
1099 v0, v1 : float
1100 The view limits. (Passing *v0* as a (low, high) pair is not
1101 supported; normalization must occur in the Axes setters.)
1102 emit : bool, default: True
1103 Whether to notify observers of limit change.
1104 auto : bool or None, default: False
1105 Whether to turn on autoscaling of the x-axis. True turns on, False
1106 turns off, None leaves unchanged.
1107 """
1108 name, = [name for name, axis in self.axes._axis_map.items()
1109 if axis is self] # The axis name.
1111 self.axes._process_unit_info([(name, (v0, v1))], convert=False)
1112 v0 = self.axes._validate_converted_limits(v0, self.convert_units)
1113 v1 = self.axes._validate_converted_limits(v1, self.convert_units)
1115 if v0 is None or v1 is None:
1116 # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
1117 # so only grab the limits if we really need them.
1118 old0, old1 = self.get_view_interval()
1119 if v0 is None:
1120 v0 = old0
1121 if v1 is None:
1122 v1 = old1
1124 if self.get_scale() == 'log' and (v0 <= 0 or v1 <= 0):
1125 # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
1126 # so only grab the limits if we really need them.
1127 old0, old1 = self.get_view_interval()
1128 if v0 <= 0:
1129 _api.warn_external(f"Attempt to set non-positive {name}lim on "
1130 f"a log-scaled axis will be ignored.")
1131 v0 = old0
1132 if v1 <= 0:
1133 _api.warn_external(f"Attempt to set non-positive {name}lim on "
1134 f"a log-scaled axis will be ignored.")
1135 v1 = old1
1136 if v0 == v1:
1137 _api.warn_external(
1138 f"Attempting to set identical low and high {name}lims "
1139 f"makes transformation singular; automatically expanding.")
1140 reverse = bool(v0 > v1) # explicit cast needed for python3.8+np.bool_.
1141 v0, v1 = self.get_major_locator().nonsingular(v0, v1)
1142 v0, v1 = self.limit_range_for_scale(v0, v1)
1143 v0, v1 = sorted([v0, v1], reverse=bool(reverse))
1145 self.set_view_interval(v0, v1, ignore=True)
1146 # Mark viewlims as no longer stale without triggering an autoscale.
1147 for ax in self.axes._shared_axes[name].get_siblings(self.axes):
1148 ax._stale_viewlims[name] = False
1149 if auto is not None:
1150 self._set_autoscale_on(bool(auto))
1152 if emit:
1153 self.axes.callbacks.process(f"{name}lim_changed", self.axes)
1154 # Call all of the other axes that are shared with this one
1155 for other in self.axes._shared_axes[name].get_siblings(self.axes):
1156 if other is not self.axes:
1157 other._axis_map[name]._set_lim(
1158 v0, v1, emit=False, auto=auto)
1159 if other.figure != self.figure:
1160 other.figure.canvas.draw_idle()
1162 self.stale = True
1163 return v0, v1
1165 def _set_artist_props(self, a):
1166 if a is None:
1167 return
1168 a.set_figure(self.figure)
1170 @_api.deprecated("3.6")
1171 def get_ticklabel_extents(self, renderer):
1172 """Get the extents of the tick labels on either side of the axes."""
1173 ticks_to_draw = self._update_ticks()
1174 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
1175 if len(tlb1):
1176 bbox1 = mtransforms.Bbox.union(tlb1)
1177 else:
1178 bbox1 = mtransforms.Bbox.from_extents(0, 0, 0, 0)
1179 if len(tlb2):
1180 bbox2 = mtransforms.Bbox.union(tlb2)
1181 else:
1182 bbox2 = mtransforms.Bbox.from_extents(0, 0, 0, 0)
1183 return bbox1, bbox2
1185 def _update_ticks(self):
1186 """
1187 Update ticks (position and labels) using the current data interval of
1188 the axes. Return the list of ticks that will be drawn.
1189 """
1190 major_locs = self.get_majorticklocs()
1191 major_labels = self.major.formatter.format_ticks(major_locs)
1192 major_ticks = self.get_major_ticks(len(major_locs))
1193 self.major.formatter.set_locs(major_locs)
1194 for tick, loc, label in zip(major_ticks, major_locs, major_labels):
1195 tick.update_position(loc)
1196 tick.set_label1(label)
1197 tick.set_label2(label)
1198 minor_locs = self.get_minorticklocs()
1199 minor_labels = self.minor.formatter.format_ticks(minor_locs)
1200 minor_ticks = self.get_minor_ticks(len(minor_locs))
1201 self.minor.formatter.set_locs(minor_locs)
1202 for tick, loc, label in zip(minor_ticks, minor_locs, minor_labels):
1203 tick.update_position(loc)
1204 tick.set_label1(label)
1205 tick.set_label2(label)
1206 ticks = [*major_ticks, *minor_ticks]
1208 view_low, view_high = self.get_view_interval()
1209 if view_low > view_high:
1210 view_low, view_high = view_high, view_low
1212 interval_t = self.get_transform().transform([view_low, view_high])
1214 ticks_to_draw = []
1215 for tick in ticks:
1216 try:
1217 loc_t = self.get_transform().transform(tick.get_loc())
1218 except AssertionError:
1219 # transforms.transform doesn't allow masked values but
1220 # some scales might make them, so we need this try/except.
1221 pass
1222 else:
1223 if mtransforms._interval_contains_close(interval_t, loc_t):
1224 ticks_to_draw.append(tick)
1226 return ticks_to_draw
1228 def _get_ticklabel_bboxes(self, ticks, renderer=None):
1229 """Return lists of bboxes for ticks' label1's and label2's."""
1230 if renderer is None:
1231 renderer = self.figure._get_renderer()
1232 return ([tick.label1.get_window_extent(renderer)
1233 for tick in ticks if tick.label1.get_visible()],
1234 [tick.label2.get_window_extent(renderer)
1235 for tick in ticks if tick.label2.get_visible()])
1237 def get_tightbbox(self, renderer=None, *, for_layout_only=False):
1238 """
1239 Return a bounding box that encloses the axis. It only accounts
1240 tick labels, axis label, and offsetText.
1242 If *for_layout_only* is True, then the width of the label (if this
1243 is an x-axis) or the height of the label (if this is a y-axis) is
1244 collapsed to near zero. This allows tight/constrained_layout to ignore
1245 too-long labels when doing their layout.
1246 """
1247 if not self.get_visible():
1248 return
1249 if renderer is None:
1250 renderer = self.figure._get_renderer()
1251 ticks_to_draw = self._update_ticks()
1253 self._update_label_position(renderer)
1255 # go back to just this axis's tick labels
1256 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
1258 self._update_offset_text_position(tlb1, tlb2)
1259 self.offsetText.set_text(self.major.formatter.get_offset())
1261 bboxes = [
1262 *(a.get_window_extent(renderer)
1263 for a in [self.offsetText]
1264 if a.get_visible()),
1265 *tlb1, *tlb2,
1266 ]
1267 # take care of label
1268 if self.label.get_visible():
1269 bb = self.label.get_window_extent(renderer)
1270 # for constrained/tight_layout, we want to ignore the label's
1271 # width/height because the adjustments they make can't be improved.
1272 # this code collapses the relevant direction
1273 if for_layout_only:
1274 if self.axis_name == "x" and bb.width > 0:
1275 bb.x0 = (bb.x0 + bb.x1) / 2 - 0.5
1276 bb.x1 = bb.x0 + 1.0
1277 if self.axis_name == "y" and bb.height > 0:
1278 bb.y0 = (bb.y0 + bb.y1) / 2 - 0.5
1279 bb.y1 = bb.y0 + 1.0
1280 bboxes.append(bb)
1281 bboxes = [b for b in bboxes
1282 if 0 < b.width < np.inf and 0 < b.height < np.inf]
1283 if bboxes:
1284 return mtransforms.Bbox.union(bboxes)
1285 else:
1286 return None
1288 def get_tick_padding(self):
1289 values = []
1290 if len(self.majorTicks):
1291 values.append(self.majorTicks[0].get_tick_padding())
1292 if len(self.minorTicks):
1293 values.append(self.minorTicks[0].get_tick_padding())
1294 return max(values, default=0)
1296 @martist.allow_rasterization
1297 def draw(self, renderer, *args, **kwargs):
1298 # docstring inherited
1300 if not self.get_visible():
1301 return
1302 renderer.open_group(__name__, gid=self.get_gid())
1304 ticks_to_draw = self._update_ticks()
1305 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
1307 for tick in ticks_to_draw:
1308 tick.draw(renderer)
1310 # Scale up the axis label box to also find the neighbors, not just the
1311 # tick labels that actually overlap. We need a *copy* of the axis
1312 # label box because we don't want to scale the actual bbox.
1314 self._update_label_position(renderer)
1316 self.label.draw(renderer)
1318 self._update_offset_text_position(tlb1, tlb2)
1319 self.offsetText.set_text(self.major.formatter.get_offset())
1320 self.offsetText.draw(renderer)
1322 renderer.close_group(__name__)
1323 self.stale = False
1325 def get_gridlines(self):
1326 r"""Return this Axis' grid lines as a list of `.Line2D`\s."""
1327 ticks = self.get_major_ticks()
1328 return cbook.silent_list('Line2D gridline',
1329 [tick.gridline for tick in ticks])
1331 def get_label(self):
1332 """Return the axis label as a Text instance."""
1333 return self.label
1335 def get_offset_text(self):
1336 """Return the axis offsetText as a Text instance."""
1337 return self.offsetText
1339 def get_pickradius(self):
1340 """Return the depth of the axis used by the picker."""
1341 return self._pickradius
1343 def get_majorticklabels(self):
1344 """Return this Axis' major tick labels, as a list of `~.text.Text`."""
1345 self._update_ticks()
1346 ticks = self.get_major_ticks()
1347 labels1 = [tick.label1 for tick in ticks if tick.label1.get_visible()]
1348 labels2 = [tick.label2 for tick in ticks if tick.label2.get_visible()]
1349 return labels1 + labels2
1351 def get_minorticklabels(self):
1352 """Return this Axis' minor tick labels, as a list of `~.text.Text`."""
1353 self._update_ticks()
1354 ticks = self.get_minor_ticks()
1355 labels1 = [tick.label1 for tick in ticks if tick.label1.get_visible()]
1356 labels2 = [tick.label2 for tick in ticks if tick.label2.get_visible()]
1357 return labels1 + labels2
1359 def get_ticklabels(self, minor=False, which=None):
1360 """
1361 Get this Axis' tick labels.
1363 Parameters
1364 ----------
1365 minor : bool
1366 Whether to return the minor or the major ticklabels.
1368 which : None, ('minor', 'major', 'both')
1369 Overrides *minor*.
1371 Selects which ticklabels to return
1373 Returns
1374 -------
1375 list of `~matplotlib.text.Text`
1376 """
1377 if which is not None:
1378 if which == 'minor':
1379 return self.get_minorticklabels()
1380 elif which == 'major':
1381 return self.get_majorticklabels()
1382 elif which == 'both':
1383 return self.get_majorticklabels() + self.get_minorticklabels()
1384 else:
1385 _api.check_in_list(['major', 'minor', 'both'], which=which)
1386 if minor:
1387 return self.get_minorticklabels()
1388 return self.get_majorticklabels()
1390 def get_majorticklines(self):
1391 r"""Return this Axis' major tick lines as a list of `.Line2D`\s."""
1392 lines = []
1393 ticks = self.get_major_ticks()
1394 for tick in ticks:
1395 lines.append(tick.tick1line)
1396 lines.append(tick.tick2line)
1397 return cbook.silent_list('Line2D ticklines', lines)
1399 def get_minorticklines(self):
1400 r"""Return this Axis' minor tick lines as a list of `.Line2D`\s."""
1401 lines = []
1402 ticks = self.get_minor_ticks()
1403 for tick in ticks:
1404 lines.append(tick.tick1line)
1405 lines.append(tick.tick2line)
1406 return cbook.silent_list('Line2D ticklines', lines)
1408 def get_ticklines(self, minor=False):
1409 r"""Return this Axis' tick lines as a list of `.Line2D`\s."""
1410 if minor:
1411 return self.get_minorticklines()
1412 return self.get_majorticklines()
1414 def get_majorticklocs(self):
1415 """Return this Axis' major tick locations in data coordinates."""
1416 return self.major.locator()
1418 def get_minorticklocs(self):
1419 """Return this Axis' minor tick locations in data coordinates."""
1420 # Remove minor ticks duplicating major ticks.
1421 minor_locs = np.asarray(self.minor.locator())
1422 if self.remove_overlapping_locs:
1423 major_locs = self.major.locator()
1424 transform = self._scale.get_transform()
1425 tr_minor_locs = transform.transform(minor_locs)
1426 tr_major_locs = transform.transform(major_locs)
1427 lo, hi = sorted(transform.transform(self.get_view_interval()))
1428 # Use the transformed view limits as scale. 1e-5 is the default
1429 # rtol for np.isclose.
1430 tol = (hi - lo) * 1e-5
1431 mask = np.isclose(tr_minor_locs[:, None], tr_major_locs[None, :],
1432 atol=tol, rtol=0).any(axis=1)
1433 minor_locs = minor_locs[~mask]
1434 return minor_locs
1436 def get_ticklocs(self, *, minor=False):
1437 """
1438 Return this Axis' tick locations in data coordinates.
1440 The locations are not clipped to the current axis limits and hence
1441 may contain locations that are not visible in the output.
1443 Parameters
1444 ----------
1445 minor : bool, default: False
1446 True to return the minor tick directions,
1447 False to return the major tick directions.
1449 Returns
1450 -------
1451 numpy array of tick locations
1452 """
1453 return self.get_minorticklocs() if minor else self.get_majorticklocs()
1455 def get_ticks_direction(self, minor=False):
1456 """
1457 Get the tick directions as a numpy array
1459 Parameters
1460 ----------
1461 minor : bool, default: False
1462 True to return the minor tick directions,
1463 False to return the major tick directions.
1465 Returns
1466 -------
1467 numpy array of tick directions
1468 """
1469 if minor:
1470 return np.array(
1471 [tick._tickdir for tick in self.get_minor_ticks()])
1472 else:
1473 return np.array(
1474 [tick._tickdir for tick in self.get_major_ticks()])
1476 def _get_tick(self, major):
1477 """Return the default tick instance."""
1478 if self._tick_class is None:
1479 raise NotImplementedError(
1480 f"The Axis subclass {self.__class__.__name__} must define "
1481 "_tick_class or reimplement _get_tick()")
1482 tick_kw = self._major_tick_kw if major else self._minor_tick_kw
1483 return self._tick_class(self.axes, 0, major=major, **tick_kw)
1485 def _get_tick_label_size(self, axis_name):
1486 """
1487 Return the text size of tick labels for this Axis.
1489 This is a convenience function to avoid having to create a `Tick` in
1490 `.get_tick_space`, since it is expensive.
1491 """
1492 tick_kw = self._major_tick_kw
1493 size = tick_kw.get('labelsize',
1494 mpl.rcParams[f'{axis_name}tick.labelsize'])
1495 return mtext.FontProperties(size=size).get_size_in_points()
1497 def _copy_tick_props(self, src, dest):
1498 """Copy the properties from *src* tick to *dest* tick."""
1499 if src is None or dest is None:
1500 return
1501 dest.label1.update_from(src.label1)
1502 dest.label2.update_from(src.label2)
1503 dest.tick1line.update_from(src.tick1line)
1504 dest.tick2line.update_from(src.tick2line)
1505 dest.gridline.update_from(src.gridline)
1507 def get_label_text(self):
1508 """Get the text of the label."""
1509 return self.label.get_text()
1511 def get_major_locator(self):
1512 """Get the locator of the major ticker."""
1513 return self.major.locator
1515 def get_minor_locator(self):
1516 """Get the locator of the minor ticker."""
1517 return self.minor.locator
1519 def get_major_formatter(self):
1520 """Get the formatter of the major ticker."""
1521 return self.major.formatter
1523 def get_minor_formatter(self):
1524 """Get the formatter of the minor ticker."""
1525 return self.minor.formatter
1527 def get_major_ticks(self, numticks=None):
1528 r"""Return the list of major `.Tick`\s."""
1529 if numticks is None:
1530 numticks = len(self.get_majorticklocs())
1532 while len(self.majorTicks) < numticks:
1533 # Update the new tick label properties from the old.
1534 tick = self._get_tick(major=True)
1535 self.majorTicks.append(tick)
1536 self._copy_tick_props(self.majorTicks[0], tick)
1538 return self.majorTicks[:numticks]
1540 def get_minor_ticks(self, numticks=None):
1541 r"""Return the list of minor `.Tick`\s."""
1542 if numticks is None:
1543 numticks = len(self.get_minorticklocs())
1545 while len(self.minorTicks) < numticks:
1546 # Update the new tick label properties from the old.
1547 tick = self._get_tick(major=False)
1548 self.minorTicks.append(tick)
1549 self._copy_tick_props(self.minorTicks[0], tick)
1551 return self.minorTicks[:numticks]
1553 @_api.rename_parameter("3.5", "b", "visible")
1554 def grid(self, visible=None, which='major', **kwargs):
1555 """
1556 Configure the grid lines.
1558 Parameters
1559 ----------
1560 visible : bool or None
1561 Whether to show the grid lines. If any *kwargs* are supplied, it
1562 is assumed you want the grid on and *visible* will be set to True.
1564 If *visible* is *None* and there are no *kwargs*, this toggles the
1565 visibility of the lines.
1567 which : {'major', 'minor', 'both'}
1568 The grid lines to apply the changes on.
1570 **kwargs : `.Line2D` properties
1571 Define the line properties of the grid, e.g.::
1573 grid(color='r', linestyle='-', linewidth=2)
1574 """
1575 if kwargs:
1576 if visible is None:
1577 visible = True
1578 elif not visible: # something false-like but not None
1579 _api.warn_external('First parameter to grid() is false, '
1580 'but line properties are supplied. The '
1581 'grid will be enabled.')
1582 visible = True
1583 which = which.lower()
1584 _api.check_in_list(['major', 'minor', 'both'], which=which)
1585 gridkw = {f'grid_{name}': value for name, value in kwargs.items()}
1586 if which in ['minor', 'both']:
1587 gridkw['gridOn'] = (not self._minor_tick_kw['gridOn']
1588 if visible is None else visible)
1589 self.set_tick_params(which='minor', **gridkw)
1590 if which in ['major', 'both']:
1591 gridkw['gridOn'] = (not self._major_tick_kw['gridOn']
1592 if visible is None else visible)
1593 self.set_tick_params(which='major', **gridkw)
1594 self.stale = True
1596 def update_units(self, data):
1597 """
1598 Introspect *data* for units converter and update the
1599 ``axis.converter`` instance if necessary. Return *True*
1600 if *data* is registered for unit conversion.
1601 """
1602 converter = munits.registry.get_converter(data)
1603 if converter is None:
1604 return False
1606 neednew = self.converter != converter
1607 self.converter = converter
1608 default = self.converter.default_units(data, self)
1609 if default is not None and self.units is None:
1610 self.set_units(default)
1612 elif neednew:
1613 self._update_axisinfo()
1614 self.stale = True
1615 return True
1617 def _update_axisinfo(self):
1618 """
1619 Check the axis converter for the stored units to see if the
1620 axis info needs to be updated.
1621 """
1622 if self.converter is None:
1623 return
1625 info = self.converter.axisinfo(self.units, self)
1627 if info is None:
1628 return
1629 if info.majloc is not None and \
1630 self.major.locator != info.majloc and self.isDefault_majloc:
1631 self.set_major_locator(info.majloc)
1632 self.isDefault_majloc = True
1633 if info.minloc is not None and \
1634 self.minor.locator != info.minloc and self.isDefault_minloc:
1635 self.set_minor_locator(info.minloc)
1636 self.isDefault_minloc = True
1637 if info.majfmt is not None and \
1638 self.major.formatter != info.majfmt and self.isDefault_majfmt:
1639 self.set_major_formatter(info.majfmt)
1640 self.isDefault_majfmt = True
1641 if info.minfmt is not None and \
1642 self.minor.formatter != info.minfmt and self.isDefault_minfmt:
1643 self.set_minor_formatter(info.minfmt)
1644 self.isDefault_minfmt = True
1645 if info.label is not None and self.isDefault_label:
1646 self.set_label_text(info.label)
1647 self.isDefault_label = True
1649 self.set_default_intervals()
1651 def have_units(self):
1652 return self.converter is not None or self.units is not None
1654 def convert_units(self, x):
1655 # If x is natively supported by Matplotlib, doesn't need converting
1656 if munits._is_natively_supported(x):
1657 return x
1659 if self.converter is None:
1660 self.converter = munits.registry.get_converter(x)
1662 if self.converter is None:
1663 return x
1664 try:
1665 ret = self.converter.convert(x, self.units, self)
1666 except Exception as e:
1667 raise munits.ConversionError('Failed to convert value(s) to axis '
1668 f'units: {x!r}') from e
1669 return ret
1671 def set_units(self, u):
1672 """
1673 Set the units for axis.
1675 Parameters
1676 ----------
1677 u : units tag
1679 Notes
1680 -----
1681 The units of any shared axis will also be updated.
1682 """
1683 if u == self.units:
1684 return
1685 for name, axis in self.axes._axis_map.items():
1686 if self is axis:
1687 shared = [
1688 getattr(ax, f"{name}axis")
1689 for ax
1690 in self.axes._shared_axes[name].get_siblings(self.axes)]
1691 break
1692 else:
1693 shared = [self]
1694 for axis in shared:
1695 axis.units = u
1696 axis._update_axisinfo()
1697 axis.callbacks.process('units')
1698 axis.callbacks.process('units finalize')
1699 axis.stale = True
1701 def get_units(self):
1702 """Return the units for axis."""
1703 return self.units
1705 def set_label_text(self, label, fontdict=None, **kwargs):
1706 """
1707 Set the text value of the axis label.
1709 Parameters
1710 ----------
1711 label : str
1712 Text string.
1713 fontdict : dict
1714 Text properties.
1715 **kwargs
1716 Merged into fontdict.
1717 """
1718 self.isDefault_label = False
1719 self.label.set_text(label)
1720 if fontdict is not None:
1721 self.label.update(fontdict)
1722 self.label.update(kwargs)
1723 self.stale = True
1724 return self.label
1726 def set_major_formatter(self, formatter):
1727 """
1728 Set the formatter of the major ticker.
1730 In addition to a `~matplotlib.ticker.Formatter` instance,
1731 this also accepts a ``str`` or function.
1733 For a ``str`` a `~matplotlib.ticker.StrMethodFormatter` is used.
1734 The field used for the value must be labeled ``'x'`` and the field used
1735 for the position must be labeled ``'pos'``.
1736 See the `~matplotlib.ticker.StrMethodFormatter` documentation for
1737 more information.
1739 For a function, a `~matplotlib.ticker.FuncFormatter` is used.
1740 The function must take two inputs (a tick value ``x`` and a
1741 position ``pos``), and return a string containing the corresponding
1742 tick label.
1743 See the `~matplotlib.ticker.FuncFormatter` documentation for
1744 more information.
1746 Parameters
1747 ----------
1748 formatter : `~matplotlib.ticker.Formatter`, ``str``, or function
1749 """
1750 self._set_formatter(formatter, self.major)
1752 def set_minor_formatter(self, formatter):
1753 """
1754 Set the formatter of the minor ticker.
1756 In addition to a `~matplotlib.ticker.Formatter` instance,
1757 this also accepts a ``str`` or function.
1758 See `.Axis.set_major_formatter` for more information.
1760 Parameters
1761 ----------
1762 formatter : `~matplotlib.ticker.Formatter`, ``str``, or function
1763 """
1764 self._set_formatter(formatter, self.minor)
1766 def _set_formatter(self, formatter, level):
1767 if isinstance(formatter, str):
1768 formatter = mticker.StrMethodFormatter(formatter)
1769 # Don't allow any other TickHelper to avoid easy-to-make errors,
1770 # like using a Locator instead of a Formatter.
1771 elif (callable(formatter) and
1772 not isinstance(formatter, mticker.TickHelper)):
1773 formatter = mticker.FuncFormatter(formatter)
1774 else:
1775 _api.check_isinstance(mticker.Formatter, formatter=formatter)
1777 if (isinstance(formatter, mticker.FixedFormatter)
1778 and len(formatter.seq) > 0
1779 and not isinstance(level.locator, mticker.FixedLocator)):
1780 _api.warn_external('FixedFormatter should only be used together '
1781 'with FixedLocator')
1783 if level == self.major:
1784 self.isDefault_majfmt = False
1785 else:
1786 self.isDefault_minfmt = False
1788 level.formatter = formatter
1789 formatter.set_axis(self)
1790 self.stale = True
1792 def set_major_locator(self, locator):
1793 """
1794 Set the locator of the major ticker.
1796 Parameters
1797 ----------
1798 locator : `~matplotlib.ticker.Locator`
1799 """
1800 _api.check_isinstance(mticker.Locator, locator=locator)
1801 self.isDefault_majloc = False
1802 self.major.locator = locator
1803 if self.major.formatter:
1804 self.major.formatter._set_locator(locator)
1805 locator.set_axis(self)
1806 self.stale = True
1808 def set_minor_locator(self, locator):
1809 """
1810 Set the locator of the minor ticker.
1812 Parameters
1813 ----------
1814 locator : `~matplotlib.ticker.Locator`
1815 """
1816 _api.check_isinstance(mticker.Locator, locator=locator)
1817 self.isDefault_minloc = False
1818 self.minor.locator = locator
1819 if self.minor.formatter:
1820 self.minor.formatter._set_locator(locator)
1821 locator.set_axis(self)
1822 self.stale = True
1824 def set_pickradius(self, pickradius):
1825 """
1826 Set the depth of the axis used by the picker.
1828 Parameters
1829 ----------
1830 pickradius : float
1831 The acceptance radius for containment tests.
1832 See also `.Axis.contains`.
1833 """
1834 if not isinstance(pickradius, Number) or pickradius < 0:
1835 raise ValueError("pick radius should be a distance")
1836 self._pickradius = pickradius
1838 pickradius = property(
1839 get_pickradius, set_pickradius, doc="The acceptance radius for "
1840 "containment tests. See also `.Axis.contains`.")
1842 # Helper for set_ticklabels. Defining it here makes it picklable.
1843 @staticmethod
1844 def _format_with_dict(tickd, x, pos):
1845 return tickd.get(x, "")
1847 def set_ticklabels(self, ticklabels, *, minor=False, **kwargs):
1848 r"""
1849 [*Discouraged*] Set the text values of the tick labels.
1851 .. admonition:: Discouraged
1853 The use of this method is discouraged, because of the dependency
1854 on tick positions. In most cases, you'll want to use
1855 ``set_[x/y]ticks(positions, labels)`` instead.
1857 If you are using this method, you should always fix the tick
1858 positions before, e.g. by using `.Axis.set_ticks` or by explicitly
1859 setting a `~.ticker.FixedLocator`. Otherwise, ticks are free to
1860 move and the labels may end up in unexpected positions.
1862 Parameters
1863 ----------
1864 ticklabels : sequence of str or of `.Text`\s
1865 Texts for labeling each tick location in the sequence set by
1866 `.Axis.set_ticks`; the number of labels must match the number of
1867 locations.
1868 minor : bool
1869 If True, set minor ticks instead of major ticks.
1870 **kwargs
1871 Text properties.
1873 Returns
1874 -------
1875 list of `.Text`\s
1876 For each tick, includes ``tick.label1`` if it is visible, then
1877 ``tick.label2`` if it is visible, in that order.
1878 """
1879 try:
1880 ticklabels = [t.get_text() if hasattr(t, 'get_text') else t
1881 for t in ticklabels]
1882 except TypeError:
1883 raise TypeError(f"{ticklabels:=} must be a sequence") from None
1884 locator = (self.get_minor_locator() if minor
1885 else self.get_major_locator())
1886 if isinstance(locator, mticker.FixedLocator):
1887 # Passing [] as a list of ticklabels is often used as a way to
1888 # remove all tick labels, so only error for > 0 ticklabels
1889 if len(locator.locs) != len(ticklabels) and len(ticklabels) != 0:
1890 raise ValueError(
1891 "The number of FixedLocator locations"
1892 f" ({len(locator.locs)}), usually from a call to"
1893 " set_ticks, does not match"
1894 f" the number of ticklabels ({len(ticklabels)}).")
1895 tickd = {loc: lab for loc, lab in zip(locator.locs, ticklabels)}
1896 func = functools.partial(self._format_with_dict, tickd)
1897 formatter = mticker.FuncFormatter(func)
1898 else:
1899 formatter = mticker.FixedFormatter(ticklabels)
1901 if minor:
1902 self.set_minor_formatter(formatter)
1903 locs = self.get_minorticklocs()
1904 ticks = self.get_minor_ticks(len(locs))
1905 else:
1906 self.set_major_formatter(formatter)
1907 locs = self.get_majorticklocs()
1908 ticks = self.get_major_ticks(len(locs))
1910 ret = []
1911 for pos, (loc, tick) in enumerate(zip(locs, ticks)):
1912 tick.update_position(loc)
1913 tick_label = formatter(loc, pos)
1914 # deal with label1
1915 tick.label1.set_text(tick_label)
1916 tick.label1._internal_update(kwargs)
1917 # deal with label2
1918 tick.label2.set_text(tick_label)
1919 tick.label2._internal_update(kwargs)
1920 # only return visible tick labels
1921 if tick.label1.get_visible():
1922 ret.append(tick.label1)
1923 if tick.label2.get_visible():
1924 ret.append(tick.label2)
1926 self.stale = True
1927 return ret
1929 # Wrapper around set_ticklabels used to generate Axes.set_x/ytickabels; can
1930 # go away once the API of Axes.set_x/yticklabels becomes consistent.
1931 def _set_ticklabels(self, labels, *, fontdict=None, minor=False, **kwargs):
1932 """
1933 Set this Axis' labels with list of string labels.
1935 .. warning::
1936 This method should only be used after fixing the tick positions
1937 using `.Axis.set_ticks`. Otherwise, the labels may end up in
1938 unexpected positions.
1940 Parameters
1941 ----------
1942 labels : list of str
1943 The label texts.
1945 fontdict : dict, optional
1946 A dictionary controlling the appearance of the ticklabels.
1947 The default *fontdict* is::
1949 {'fontsize': rcParams['axes.titlesize'],
1950 'fontweight': rcParams['axes.titleweight'],
1951 'verticalalignment': 'baseline',
1952 'horizontalalignment': loc}
1954 minor : bool, default: False
1955 Whether to set the minor ticklabels rather than the major ones.
1957 Returns
1958 -------
1959 list of `.Text`
1960 The labels.
1962 Other Parameters
1963 ----------------
1964 **kwargs : `~.text.Text` properties.
1965 """
1966 if fontdict is not None:
1967 kwargs.update(fontdict)
1968 return self.set_ticklabels(labels, minor=minor, **kwargs)
1970 def _set_tick_locations(self, ticks, *, minor=False):
1971 # see docstring of set_ticks
1973 # XXX if the user changes units, the information will be lost here
1974 ticks = self.convert_units(ticks)
1975 for name, axis in self.axes._axis_map.items():
1976 if self is axis:
1977 shared = [
1978 getattr(ax, f"{name}axis")
1979 for ax
1980 in self.axes._shared_axes[name].get_siblings(self.axes)]
1981 break
1982 else:
1983 shared = [self]
1984 if len(ticks):
1985 for axis in shared:
1986 # set_view_interval maintains any preexisting inversion.
1987 axis.set_view_interval(min(ticks), max(ticks))
1988 self.axes.stale = True
1989 if minor:
1990 self.set_minor_locator(mticker.FixedLocator(ticks))
1991 return self.get_minor_ticks(len(ticks))
1992 else:
1993 self.set_major_locator(mticker.FixedLocator(ticks))
1994 return self.get_major_ticks(len(ticks))
1996 def set_ticks(self, ticks, labels=None, *, minor=False, **kwargs):
1997 """
1998 Set this Axis' tick locations and optionally labels.
2000 If necessary, the view limits of the Axis are expanded so that all
2001 given ticks are visible.
2003 Parameters
2004 ----------
2005 ticks : list of floats
2006 List of tick locations. The axis `.Locator` is replaced by a
2007 `~.ticker.FixedLocator`.
2009 Some tick formatters will not label arbitrary tick positions;
2010 e.g. log formatters only label decade ticks by default. In
2011 such a case you can set a formatter explicitly on the axis
2012 using `.Axis.set_major_formatter` or provide formatted
2013 *labels* yourself.
2014 labels : list of str, optional
2015 List of tick labels. If not set, the labels are generated with
2016 the axis tick `.Formatter`.
2017 minor : bool, default: False
2018 If ``False``, set the major ticks; if ``True``, the minor ticks.
2019 **kwargs
2020 `.Text` properties for the labels. These take effect only if you
2021 pass *labels*. In other cases, please use `~.Axes.tick_params`.
2023 Notes
2024 -----
2025 The mandatory expansion of the view limits is an intentional design
2026 choice to prevent the surprise of a non-visible tick. If you need
2027 other limits, you should set the limits explicitly after setting the
2028 ticks.
2029 """
2030 result = self._set_tick_locations(ticks, minor=minor)
2031 if labels is not None:
2032 self.set_ticklabels(labels, minor=minor, **kwargs)
2033 return result
2035 def _get_tick_boxes_siblings(self, renderer):
2036 """
2037 Get the bounding boxes for this `.axis` and its siblings
2038 as set by `.Figure.align_xlabels` or `.Figure.align_ylabels`.
2040 By default, it just gets bboxes for *self*.
2041 """
2042 # Get the Grouper keeping track of x or y label groups for this figure.
2043 axis_names = [
2044 name for name, axis in self.axes._axis_map.items()
2045 if name in self.figure._align_label_groups and axis is self]
2046 if len(axis_names) != 1:
2047 return [], []
2048 axis_name, = axis_names
2049 grouper = self.figure._align_label_groups[axis_name]
2050 bboxes = []
2051 bboxes2 = []
2052 # If we want to align labels from other Axes:
2053 for ax in grouper.get_siblings(self.axes):
2054 axis = getattr(ax, f"{axis_name}axis")
2055 ticks_to_draw = axis._update_ticks()
2056 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2057 bboxes.extend(tlb)
2058 bboxes2.extend(tlb2)
2059 return bboxes, bboxes2
2061 def _update_label_position(self, renderer):
2062 """
2063 Update the label position based on the bounding box enclosing
2064 all the ticklabels and axis spine.
2065 """
2066 raise NotImplementedError('Derived must override')
2068 def _update_offset_text_position(self, bboxes, bboxes2):
2069 """
2070 Update the offset text position based on the sequence of bounding
2071 boxes of all the ticklabels.
2072 """
2073 raise NotImplementedError('Derived must override')
2075 def axis_date(self, tz=None):
2076 """
2077 Set up axis ticks and labels to treat data along this Axis as dates.
2079 Parameters
2080 ----------
2081 tz : str or `datetime.tzinfo`, default: :rc:`timezone`
2082 The timezone used to create date labels.
2083 """
2084 # By providing a sample datetime instance with the desired timezone,
2085 # the registered converter can be selected, and the "units" attribute,
2086 # which is the timezone, can be set.
2087 if isinstance(tz, str):
2088 import dateutil.tz
2089 tz = dateutil.tz.gettz(tz)
2090 self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz))
2092 def get_tick_space(self):
2093 """Return the estimated number of ticks that can fit on the axis."""
2094 # Must be overridden in the subclass
2095 raise NotImplementedError()
2097 def _get_ticks_position(self):
2098 """
2099 Helper for `XAxis.get_ticks_position` and `YAxis.get_ticks_position`.
2101 Check the visibility of tick1line, label1, tick2line, and label2 on
2102 the first major and the first minor ticks, and return
2104 - 1 if only tick1line and label1 are visible (which corresponds to
2105 "bottom" for the x-axis and "left" for the y-axis);
2106 - 2 if only tick2line and label2 are visible (which corresponds to
2107 "top" for the x-axis and "right" for the y-axis);
2108 - "default" if only tick1line, tick2line and label1 are visible;
2109 - "unknown" otherwise.
2110 """
2111 major = self.majorTicks[0]
2112 minor = self.minorTicks[0]
2113 if all(tick.tick1line.get_visible()
2114 and not tick.tick2line.get_visible()
2115 and tick.label1.get_visible()
2116 and not tick.label2.get_visible()
2117 for tick in [major, minor]):
2118 return 1
2119 elif all(tick.tick2line.get_visible()
2120 and not tick.tick1line.get_visible()
2121 and tick.label2.get_visible()
2122 and not tick.label1.get_visible()
2123 for tick in [major, minor]):
2124 return 2
2125 elif all(tick.tick1line.get_visible()
2126 and tick.tick2line.get_visible()
2127 and tick.label1.get_visible()
2128 and not tick.label2.get_visible()
2129 for tick in [major, minor]):
2130 return "default"
2131 else:
2132 return "unknown"
2134 def get_label_position(self):
2135 """
2136 Return the label position (top or bottom)
2137 """
2138 return self.label_position
2140 def set_label_position(self, position):
2141 """
2142 Set the label position (top or bottom)
2144 Parameters
2145 ----------
2146 position : {'top', 'bottom'}
2147 """
2148 raise NotImplementedError()
2150 def get_minpos(self):
2151 raise NotImplementedError()
2154def _make_getset_interval(method_name, lim_name, attr_name):
2155 """
2156 Helper to generate ``get_{data,view}_interval`` and
2157 ``set_{data,view}_interval`` implementations.
2158 """
2160 def getter(self):
2161 # docstring inherited.
2162 return getattr(getattr(self.axes, lim_name), attr_name)
2164 def setter(self, vmin, vmax, ignore=False):
2165 # docstring inherited.
2166 if ignore:
2167 setattr(getattr(self.axes, lim_name), attr_name, (vmin, vmax))
2168 else:
2169 oldmin, oldmax = getter(self)
2170 if oldmin < oldmax:
2171 setter(self, min(vmin, vmax, oldmin), max(vmin, vmax, oldmax),
2172 ignore=True)
2173 else:
2174 setter(self, max(vmin, vmax, oldmin), min(vmin, vmax, oldmax),
2175 ignore=True)
2176 self.stale = True
2178 getter.__name__ = f"get_{method_name}_interval"
2179 setter.__name__ = f"set_{method_name}_interval"
2181 return getter, setter
2184class XAxis(Axis):
2185 __name__ = 'xaxis'
2186 axis_name = 'x' #: Read-only name identifying the axis.
2187 _tick_class = XTick
2189 def __init__(self, *args, **kwargs):
2190 super().__init__(*args, **kwargs)
2191 # x in axes coords, y in display coords (to be updated at draw time by
2192 # _update_label_positions and _update_offset_text_position).
2193 self.label.set(
2194 x=0.5, y=0,
2195 verticalalignment='top', horizontalalignment='center',
2196 transform=mtransforms.blended_transform_factory(
2197 self.axes.transAxes, mtransforms.IdentityTransform()),
2198 )
2199 self.label_position = 'bottom'
2200 self.offsetText.set(
2201 x=1, y=0,
2202 verticalalignment='top', horizontalalignment='right',
2203 transform=mtransforms.blended_transform_factory(
2204 self.axes.transAxes, mtransforms.IdentityTransform()),
2205 fontsize=mpl.rcParams['xtick.labelsize'],
2206 color=mpl.rcParams['xtick.color'],
2207 )
2208 self.offset_text_position = 'bottom'
2210 def contains(self, mouseevent):
2211 """Test whether the mouse event occurred in the x-axis."""
2212 inside, info = self._default_contains(mouseevent)
2213 if inside is not None:
2214 return inside, info
2216 x, y = mouseevent.x, mouseevent.y
2217 try:
2218 trans = self.axes.transAxes.inverted()
2219 xaxes, yaxes = trans.transform((x, y))
2220 except ValueError:
2221 return False, {}
2222 (l, b), (r, t) = self.axes.transAxes.transform([(0, 0), (1, 1)])
2223 inaxis = 0 <= xaxes <= 1 and (
2224 b - self._pickradius < y < b or
2225 t < y < t + self._pickradius)
2226 return inaxis, {}
2228 def set_label_position(self, position):
2229 """
2230 Set the label position (top or bottom)
2232 Parameters
2233 ----------
2234 position : {'top', 'bottom'}
2235 """
2236 self.label.set_verticalalignment(_api.check_getitem({
2237 'top': 'baseline', 'bottom': 'top',
2238 }, position=position))
2239 self.label_position = position
2240 self.stale = True
2242 def _update_label_position(self, renderer):
2243 """
2244 Update the label position based on the bounding box enclosing
2245 all the ticklabels and axis spine
2246 """
2247 if not self._autolabelpos:
2248 return
2250 # get bounding boxes for this axis and any siblings
2251 # that have been set by `fig.align_xlabels()`
2252 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2254 x, y = self.label.get_position()
2255 if self.label_position == 'bottom':
2256 try:
2257 spine = self.axes.spines['bottom']
2258 spinebbox = spine.get_window_extent()
2259 except KeyError:
2260 # use Axes if spine doesn't exist
2261 spinebbox = self.axes.bbox
2262 bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
2263 bottom = bbox.y0
2265 self.label.set_position(
2266 (x, bottom - self.labelpad * self.figure.dpi / 72)
2267 )
2268 else:
2269 try:
2270 spine = self.axes.spines['top']
2271 spinebbox = spine.get_window_extent()
2272 except KeyError:
2273 # use Axes if spine doesn't exist
2274 spinebbox = self.axes.bbox
2275 bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox])
2276 top = bbox.y1
2278 self.label.set_position(
2279 (x, top + self.labelpad * self.figure.dpi / 72)
2280 )
2282 def _update_offset_text_position(self, bboxes, bboxes2):
2283 """
2284 Update the offset_text position based on the sequence of bounding
2285 boxes of all the ticklabels
2286 """
2287 x, y = self.offsetText.get_position()
2288 if not hasattr(self, '_tick_position'):
2289 self._tick_position = 'bottom'
2290 if self._tick_position == 'bottom':
2291 if not len(bboxes):
2292 bottom = self.axes.bbox.ymin
2293 else:
2294 bbox = mtransforms.Bbox.union(bboxes)
2295 bottom = bbox.y0
2296 y = bottom - self.OFFSETTEXTPAD * self.figure.dpi / 72
2297 else:
2298 if not len(bboxes2):
2299 top = self.axes.bbox.ymax
2300 else:
2301 bbox = mtransforms.Bbox.union(bboxes2)
2302 top = bbox.y1
2303 y = top + self.OFFSETTEXTPAD * self.figure.dpi / 72
2304 self.offsetText.set_position((x, y))
2306 @_api.deprecated("3.6")
2307 def get_text_heights(self, renderer):
2308 """
2309 Return how much space should be reserved for text above and below the
2310 Axes, as a pair of floats.
2311 """
2312 bbox, bbox2 = self.get_ticklabel_extents(renderer)
2313 # MGDTODO: Need a better way to get the pad
2314 pad_pixels = self.majorTicks[0].get_pad_pixels()
2316 above = 0.0
2317 if bbox2.height:
2318 above += bbox2.height + pad_pixels
2319 below = 0.0
2320 if bbox.height:
2321 below += bbox.height + pad_pixels
2323 if self.get_label_position() == 'top':
2324 above += self.label.get_window_extent(renderer).height + pad_pixels
2325 else:
2326 below += self.label.get_window_extent(renderer).height + pad_pixels
2327 return above, below
2329 def set_ticks_position(self, position):
2330 """
2331 Set the ticks position.
2333 Parameters
2334 ----------
2335 position : {'top', 'bottom', 'both', 'default', 'none'}
2336 'both' sets the ticks to appear on both positions, but does not
2337 change the tick labels. 'default' resets the tick positions to
2338 the default: ticks on both positions, labels at bottom. 'none'
2339 can be used if you don't want any ticks. 'none' and 'both'
2340 affect only the ticks, not the labels.
2341 """
2342 _api.check_in_list(['top', 'bottom', 'both', 'default', 'none'],
2343 position=position)
2344 if position == 'top':
2345 self.set_tick_params(which='both', top=True, labeltop=True,
2346 bottom=False, labelbottom=False)
2347 self._tick_position = 'top'
2348 self.offsetText.set_verticalalignment('bottom')
2349 elif position == 'bottom':
2350 self.set_tick_params(which='both', top=False, labeltop=False,
2351 bottom=True, labelbottom=True)
2352 self._tick_position = 'bottom'
2353 self.offsetText.set_verticalalignment('top')
2354 elif position == 'both':
2355 self.set_tick_params(which='both', top=True,
2356 bottom=True)
2357 elif position == 'none':
2358 self.set_tick_params(which='both', top=False,
2359 bottom=False)
2360 elif position == 'default':
2361 self.set_tick_params(which='both', top=True, labeltop=False,
2362 bottom=True, labelbottom=True)
2363 self._tick_position = 'bottom'
2364 self.offsetText.set_verticalalignment('top')
2365 else:
2366 assert False, "unhandled parameter not caught by _check_in_list"
2367 self.stale = True
2369 def tick_top(self):
2370 """
2371 Move ticks and ticklabels (if present) to the top of the Axes.
2372 """
2373 label = True
2374 if 'label1On' in self._major_tick_kw:
2375 label = (self._major_tick_kw['label1On']
2376 or self._major_tick_kw['label2On'])
2377 self.set_ticks_position('top')
2378 # If labels were turned off before this was called, leave them off.
2379 self.set_tick_params(which='both', labeltop=label)
2381 def tick_bottom(self):
2382 """
2383 Move ticks and ticklabels (if present) to the bottom of the Axes.
2384 """
2385 label = True
2386 if 'label1On' in self._major_tick_kw:
2387 label = (self._major_tick_kw['label1On']
2388 or self._major_tick_kw['label2On'])
2389 self.set_ticks_position('bottom')
2390 # If labels were turned off before this was called, leave them off.
2391 self.set_tick_params(which='both', labelbottom=label)
2393 def get_ticks_position(self):
2394 """
2395 Return the ticks position ("top", "bottom", "default", or "unknown").
2396 """
2397 return {1: "bottom", 2: "top",
2398 "default": "default", "unknown": "unknown"}[
2399 self._get_ticks_position()]
2401 get_view_interval, set_view_interval = _make_getset_interval(
2402 "view", "viewLim", "intervalx")
2403 get_data_interval, set_data_interval = _make_getset_interval(
2404 "data", "dataLim", "intervalx")
2406 def get_minpos(self):
2407 return self.axes.dataLim.minposx
2409 def set_default_intervals(self):
2410 # docstring inherited
2411 # only change view if dataLim has not changed and user has
2412 # not changed the view:
2413 if (not self.axes.dataLim.mutatedx() and
2414 not self.axes.viewLim.mutatedx()):
2415 if self.converter is not None:
2416 info = self.converter.axisinfo(self.units, self)
2417 if info.default_limits is not None:
2418 xmin, xmax = self.convert_units(info.default_limits)
2419 self.axes.viewLim.intervalx = xmin, xmax
2420 self.stale = True
2422 def get_tick_space(self):
2423 ends = mtransforms.Bbox.unit().transformed(
2424 self.axes.transAxes - self.figure.dpi_scale_trans)
2425 length = ends.width * 72
2426 # There is a heuristic here that the aspect ratio of tick text
2427 # is no more than 3:1
2428 size = self._get_tick_label_size('x') * 3
2429 if size > 0:
2430 return int(np.floor(length / size))
2431 else:
2432 return 2**31 - 1
2435class YAxis(Axis):
2436 __name__ = 'yaxis'
2437 axis_name = 'y' #: Read-only name identifying the axis.
2438 _tick_class = YTick
2440 def __init__(self, *args, **kwargs):
2441 super().__init__(*args, **kwargs)
2442 # x in display coords, y in axes coords (to be updated at draw time by
2443 # _update_label_positions and _update_offset_text_position).
2444 self.label.set(
2445 x=0, y=0.5,
2446 verticalalignment='bottom', horizontalalignment='center',
2447 rotation='vertical', rotation_mode='anchor',
2448 transform=mtransforms.blended_transform_factory(
2449 mtransforms.IdentityTransform(), self.axes.transAxes),
2450 )
2451 self.label_position = 'left'
2452 # x in axes coords, y in display coords(!).
2453 self.offsetText.set(
2454 x=0, y=0.5,
2455 verticalalignment='baseline', horizontalalignment='left',
2456 transform=mtransforms.blended_transform_factory(
2457 self.axes.transAxes, mtransforms.IdentityTransform()),
2458 fontsize=mpl.rcParams['ytick.labelsize'],
2459 color=mpl.rcParams['ytick.color'],
2460 )
2461 self.offset_text_position = 'left'
2463 def contains(self, mouseevent):
2464 # docstring inherited
2465 inside, info = self._default_contains(mouseevent)
2466 if inside is not None:
2467 return inside, info
2469 x, y = mouseevent.x, mouseevent.y
2470 try:
2471 trans = self.axes.transAxes.inverted()
2472 xaxes, yaxes = trans.transform((x, y))
2473 except ValueError:
2474 return False, {}
2475 (l, b), (r, t) = self.axes.transAxes.transform([(0, 0), (1, 1)])
2476 inaxis = 0 <= yaxes <= 1 and (
2477 l - self._pickradius < x < l or
2478 r < x < r + self._pickradius)
2479 return inaxis, {}
2481 def set_label_position(self, position):
2482 """
2483 Set the label position (left or right)
2485 Parameters
2486 ----------
2487 position : {'left', 'right'}
2488 """
2489 self.label.set_rotation_mode('anchor')
2490 self.label.set_verticalalignment(_api.check_getitem({
2491 'left': 'bottom', 'right': 'top',
2492 }, position=position))
2493 self.label_position = position
2494 self.stale = True
2496 def _update_label_position(self, renderer):
2497 """
2498 Update the label position based on the bounding box enclosing
2499 all the ticklabels and axis spine
2500 """
2501 if not self._autolabelpos:
2502 return
2504 # get bounding boxes for this axis and any siblings
2505 # that have been set by `fig.align_ylabels()`
2506 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2507 x, y = self.label.get_position()
2508 if self.label_position == 'left':
2509 try:
2510 spine = self.axes.spines['left']
2511 spinebbox = spine.get_window_extent()
2512 except KeyError:
2513 # use Axes if spine doesn't exist
2514 spinebbox = self.axes.bbox
2515 bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
2516 left = bbox.x0
2517 self.label.set_position(
2518 (left - self.labelpad * self.figure.dpi / 72, y)
2519 )
2521 else:
2522 try:
2523 spine = self.axes.spines['right']
2524 spinebbox = spine.get_window_extent()
2525 except KeyError:
2526 # use Axes if spine doesn't exist
2527 spinebbox = self.axes.bbox
2529 bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox])
2530 right = bbox.x1
2531 self.label.set_position(
2532 (right + self.labelpad * self.figure.dpi / 72, y)
2533 )
2535 def _update_offset_text_position(self, bboxes, bboxes2):
2536 """
2537 Update the offset_text position based on the sequence of bounding
2538 boxes of all the ticklabels
2539 """
2540 x, _ = self.offsetText.get_position()
2541 if 'outline' in self.axes.spines:
2542 # Special case for colorbars:
2543 bbox = self.axes.spines['outline'].get_window_extent()
2544 else:
2545 bbox = self.axes.bbox
2546 top = bbox.ymax
2547 self.offsetText.set_position(
2548 (x, top + self.OFFSETTEXTPAD * self.figure.dpi / 72)
2549 )
2551 def set_offset_position(self, position):
2552 """
2553 Parameters
2554 ----------
2555 position : {'left', 'right'}
2556 """
2557 x, y = self.offsetText.get_position()
2558 x = _api.check_getitem({'left': 0, 'right': 1}, position=position)
2560 self.offsetText.set_ha(position)
2561 self.offsetText.set_position((x, y))
2562 self.stale = True
2564 @_api.deprecated("3.6")
2565 def get_text_widths(self, renderer):
2566 bbox, bbox2 = self.get_ticklabel_extents(renderer)
2567 # MGDTODO: Need a better way to get the pad
2568 pad_pixels = self.majorTicks[0].get_pad_pixels()
2570 left = 0.0
2571 if bbox.width:
2572 left += bbox.width + pad_pixels
2573 right = 0.0
2574 if bbox2.width:
2575 right += bbox2.width + pad_pixels
2577 if self.get_label_position() == 'left':
2578 left += self.label.get_window_extent(renderer).width + pad_pixels
2579 else:
2580 right += self.label.get_window_extent(renderer).width + pad_pixels
2581 return left, right
2583 def set_ticks_position(self, position):
2584 """
2585 Set the ticks position.
2587 Parameters
2588 ----------
2589 position : {'left', 'right', 'both', 'default', 'none'}
2590 'both' sets the ticks to appear on both positions, but does not
2591 change the tick labels. 'default' resets the tick positions to
2592 the default: ticks on both positions, labels at left. 'none'
2593 can be used if you don't want any ticks. 'none' and 'both'
2594 affect only the ticks, not the labels.
2595 """
2596 _api.check_in_list(['left', 'right', 'both', 'default', 'none'],
2597 position=position)
2598 if position == 'right':
2599 self.set_tick_params(which='both', right=True, labelright=True,
2600 left=False, labelleft=False)
2601 self.set_offset_position(position)
2602 elif position == 'left':
2603 self.set_tick_params(which='both', right=False, labelright=False,
2604 left=True, labelleft=True)
2605 self.set_offset_position(position)
2606 elif position == 'both':
2607 self.set_tick_params(which='both', right=True,
2608 left=True)
2609 elif position == 'none':
2610 self.set_tick_params(which='both', right=False,
2611 left=False)
2612 elif position == 'default':
2613 self.set_tick_params(which='both', right=True, labelright=False,
2614 left=True, labelleft=True)
2615 else:
2616 assert False, "unhandled parameter not caught by _check_in_list"
2617 self.stale = True
2619 def tick_right(self):
2620 """
2621 Move ticks and ticklabels (if present) to the right of the Axes.
2622 """
2623 label = True
2624 if 'label1On' in self._major_tick_kw:
2625 label = (self._major_tick_kw['label1On']
2626 or self._major_tick_kw['label2On'])
2627 self.set_ticks_position('right')
2628 # if labels were turned off before this was called
2629 # leave them off
2630 self.set_tick_params(which='both', labelright=label)
2632 def tick_left(self):
2633 """
2634 Move ticks and ticklabels (if present) to the left of the Axes.
2635 """
2636 label = True
2637 if 'label1On' in self._major_tick_kw:
2638 label = (self._major_tick_kw['label1On']
2639 or self._major_tick_kw['label2On'])
2640 self.set_ticks_position('left')
2641 # if labels were turned off before this was called
2642 # leave them off
2643 self.set_tick_params(which='both', labelleft=label)
2645 def get_ticks_position(self):
2646 """
2647 Return the ticks position ("left", "right", "default", or "unknown").
2648 """
2649 return {1: "left", 2: "right",
2650 "default": "default", "unknown": "unknown"}[
2651 self._get_ticks_position()]
2653 get_view_interval, set_view_interval = _make_getset_interval(
2654 "view", "viewLim", "intervaly")
2655 get_data_interval, set_data_interval = _make_getset_interval(
2656 "data", "dataLim", "intervaly")
2658 def get_minpos(self):
2659 return self.axes.dataLim.minposy
2661 def set_default_intervals(self):
2662 # docstring inherited
2663 # only change view if dataLim has not changed and user has
2664 # not changed the view:
2665 if (not self.axes.dataLim.mutatedy() and
2666 not self.axes.viewLim.mutatedy()):
2667 if self.converter is not None:
2668 info = self.converter.axisinfo(self.units, self)
2669 if info.default_limits is not None:
2670 ymin, ymax = self.convert_units(info.default_limits)
2671 self.axes.viewLim.intervaly = ymin, ymax
2672 self.stale = True
2674 def get_tick_space(self):
2675 ends = mtransforms.Bbox.unit().transformed(
2676 self.axes.transAxes - self.figure.dpi_scale_trans)
2677 length = ends.height * 72
2678 # Having a spacing of at least 2 just looks good.
2679 size = self._get_tick_label_size('y') * 2
2680 if size > 0:
2681 return int(np.floor(length / size))
2682 else:
2683 return 2**31 - 1