Coverage for /pythoncovmergedfiles/medio/medio/usr/local/lib/python3.9/dist-packages/matplotlib/axis.py: 20%
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
Shortcuts on this page
r m x toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1"""
2Classes for the ticks and x- and y-axis.
3"""
5import datetime
6import functools
7import logging
8from numbers import Real
9import warnings
11import numpy as np
13import matplotlib as mpl
14from matplotlib import _api, cbook
15import matplotlib.artist as martist
16import matplotlib.colors as mcolors
17import matplotlib.lines as mlines
18import matplotlib.scale as mscale
19import matplotlib.text as mtext
20import matplotlib.ticker as mticker
21import matplotlib.transforms as mtransforms
22import matplotlib.units as munits
24_log = logging.getLogger(__name__)
26GRIDLINE_INTERPOLATION_STEPS = 180
28# This list is being used for compatibility with Axes.grid, which
29# allows all Line2D kwargs.
30_line_inspector = martist.ArtistInspector(mlines.Line2D)
31_line_param_names = _line_inspector.get_setters()
32_line_param_aliases = [list(d)[0] for d in _line_inspector.aliasd.values()]
33_gridline_param_names = ['grid_' + name
34 for name in _line_param_names + _line_param_aliases]
37class Tick(martist.Artist):
38 """
39 Abstract base class for the axis ticks, grid lines and labels.
41 Ticks mark a position on an Axis. They contain two lines as markers and
42 two labels; one each for the bottom and top positions (in case of an
43 `.XAxis`) or for the left and right positions (in case of a `.YAxis`).
45 Attributes
46 ----------
47 tick1line : `~matplotlib.lines.Line2D`
48 The left/bottom tick marker.
49 tick2line : `~matplotlib.lines.Line2D`
50 The right/top tick marker.
51 gridline : `~matplotlib.lines.Line2D`
52 The grid line associated with the label position.
53 label1 : `~matplotlib.text.Text`
54 The left/bottom tick label.
55 label2 : `~matplotlib.text.Text`
56 The right/top tick label.
58 """
59 def __init__(
60 self, axes, loc, *,
61 size=None, # points
62 width=None,
63 color=None,
64 tickdir=None,
65 pad=None,
66 labelsize=None,
67 labelcolor=None,
68 labelfontfamily=None,
69 zorder=None,
70 gridOn=None, # defaults to axes.grid depending on axes.grid.which
71 tick1On=True,
72 tick2On=True,
73 label1On=True,
74 label2On=False,
75 major=True,
76 labelrotation=0,
77 grid_color=None,
78 grid_linestyle=None,
79 grid_linewidth=None,
80 grid_alpha=None,
81 **kwargs, # Other Line2D kwargs applied to gridlines.
82 ):
83 """
84 bbox is the Bound2D bounding box in display coords of the Axes
85 loc is the tick location in data coords
86 size is the tick size in points
87 """
88 super().__init__()
90 if gridOn is None:
91 which = mpl.rcParams['axes.grid.which']
92 if major and (which in ('both', 'major')):
93 gridOn = mpl.rcParams['axes.grid']
94 elif (not major) and (which in ('both', 'minor')):
95 gridOn = mpl.rcParams['axes.grid']
96 else:
97 gridOn = False
99 self.set_figure(axes.figure)
100 self.axes = axes
102 self._loc = loc
103 self._major = major
105 name = self.__name__
106 major_minor = "major" if major else "minor"
108 if size is None:
109 size = mpl.rcParams[f"{name}.{major_minor}.size"]
110 self._size = size
112 if width is None:
113 width = mpl.rcParams[f"{name}.{major_minor}.width"]
114 self._width = width
116 if color is None:
117 color = mpl.rcParams[f"{name}.color"]
119 if pad is None:
120 pad = mpl.rcParams[f"{name}.{major_minor}.pad"]
121 self._base_pad = pad
123 if labelcolor is None:
124 labelcolor = mpl.rcParams[f"{name}.labelcolor"]
126 if cbook._str_equal(labelcolor, 'inherit'):
127 # inherit from tick color
128 labelcolor = mpl.rcParams[f"{name}.color"]
130 if labelsize is None:
131 labelsize = mpl.rcParams[f"{name}.labelsize"]
133 self._set_labelrotation(labelrotation)
135 if zorder is None:
136 if major:
137 zorder = mlines.Line2D.zorder + 0.01
138 else:
139 zorder = mlines.Line2D.zorder
140 self._zorder = zorder
142 grid_color = mpl._val_or_rc(grid_color, "grid.color")
143 grid_linestyle = mpl._val_or_rc(grid_linestyle, "grid.linestyle")
144 grid_linewidth = mpl._val_or_rc(grid_linewidth, "grid.linewidth")
145 if grid_alpha is None and not mcolors._has_alpha_channel(grid_color):
146 # alpha precedence: kwarg > color alpha > rcParams['grid.alpha']
147 # Note: only resolve to rcParams if the color does not have alpha
148 # otherwise `grid(color=(1, 1, 1, 0.5))` would work like
149 # grid(color=(1, 1, 1, 0.5), alpha=rcParams['grid.alpha'])
150 # so the that the rcParams default would override color alpha.
151 grid_alpha = mpl.rcParams["grid.alpha"]
152 grid_kw = {k[5:]: v for k, v in kwargs.items()}
154 self.tick1line = mlines.Line2D(
155 [], [],
156 color=color, linestyle="none", zorder=zorder, visible=tick1On,
157 markeredgecolor=color, markersize=size, markeredgewidth=width,
158 )
159 self.tick2line = mlines.Line2D(
160 [], [],
161 color=color, linestyle="none", zorder=zorder, visible=tick2On,
162 markeredgecolor=color, markersize=size, markeredgewidth=width,
163 )
164 self.gridline = mlines.Line2D(
165 [], [],
166 color=grid_color, alpha=grid_alpha, visible=gridOn,
167 linestyle=grid_linestyle, linewidth=grid_linewidth, marker="",
168 **grid_kw,
169 )
170 self.gridline.get_path()._interpolation_steps = \
171 GRIDLINE_INTERPOLATION_STEPS
172 self.label1 = mtext.Text(
173 np.nan, np.nan,
174 fontsize=labelsize, color=labelcolor, visible=label1On,
175 fontfamily=labelfontfamily, rotation=self._labelrotation[1])
176 self.label2 = mtext.Text(
177 np.nan, np.nan,
178 fontsize=labelsize, color=labelcolor, visible=label2On,
179 fontfamily=labelfontfamily, rotation=self._labelrotation[1])
181 self._apply_tickdir(tickdir)
183 for artist in [self.tick1line, self.tick2line, self.gridline,
184 self.label1, self.label2]:
185 self._set_artist_props(artist)
187 self.update_position(loc)
189 def _set_labelrotation(self, labelrotation):
190 if isinstance(labelrotation, str):
191 mode = labelrotation
192 angle = 0
193 elif isinstance(labelrotation, (tuple, list)):
194 mode, angle = labelrotation
195 else:
196 mode = 'default'
197 angle = labelrotation
198 _api.check_in_list(['auto', 'default'], labelrotation=mode)
199 self._labelrotation = (mode, angle)
201 @property
202 def _pad(self):
203 return self._base_pad + self.get_tick_padding()
205 def _apply_tickdir(self, tickdir):
206 """Set tick direction. Valid values are 'out', 'in', 'inout'."""
207 # This method is responsible for verifying input and, in subclasses, for setting
208 # the tick{1,2}line markers. From the user perspective this should always be
209 # called through _apply_params, which further updates ticklabel positions using
210 # the new pads.
211 if tickdir is None:
212 tickdir = mpl.rcParams[f'{self.__name__}.direction']
213 else:
214 _api.check_in_list(['in', 'out', 'inout'], tickdir=tickdir)
215 self._tickdir = tickdir
217 def get_tickdir(self):
218 return self._tickdir
220 def get_tick_padding(self):
221 """Get the length of the tick outside of the Axes."""
222 padding = {
223 'in': 0.0,
224 'inout': 0.5,
225 'out': 1.0
226 }
227 return self._size * padding[self._tickdir]
229 def get_children(self):
230 children = [self.tick1line, self.tick2line,
231 self.gridline, self.label1, self.label2]
232 return children
234 @_api.rename_parameter("3.8", "clippath", "path")
235 def set_clip_path(self, path, transform=None):
236 # docstring inherited
237 super().set_clip_path(path, transform)
238 self.gridline.set_clip_path(path, transform)
239 self.stale = True
241 def contains(self, mouseevent):
242 """
243 Test whether the mouse event occurred in the Tick marks.
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 return False, {}
250 def set_pad(self, val):
251 """
252 Set the tick label pad in points
254 Parameters
255 ----------
256 val : float
257 """
258 self._apply_params(pad=val)
259 self.stale = True
261 def get_pad(self):
262 """Get the value of the tick label pad in points."""
263 return self._base_pad
265 def get_loc(self):
266 """Return the tick location (data coords) as a scalar."""
267 return self._loc
269 @martist.allow_rasterization
270 def draw(self, renderer):
271 if not self.get_visible():
272 self.stale = False
273 return
274 renderer.open_group(self.__name__, gid=self.get_gid())
275 for artist in [self.gridline, self.tick1line, self.tick2line,
276 self.label1, self.label2]:
277 artist.draw(renderer)
278 renderer.close_group(self.__name__)
279 self.stale = False
281 @_api.deprecated("3.8")
282 def set_label1(self, s):
283 """
284 Set the label1 text.
286 Parameters
287 ----------
288 s : str
289 """
290 self.label1.set_text(s)
291 self.stale = True
293 set_label = set_label1
295 @_api.deprecated("3.8")
296 def set_label2(self, s):
297 """
298 Set the label2 text.
300 Parameters
301 ----------
302 s : str
303 """
304 self.label2.set_text(s)
305 self.stale = True
307 def set_url(self, url):
308 """
309 Set the url of label1 and label2.
311 Parameters
312 ----------
313 url : str
314 """
315 super().set_url(url)
316 self.label1.set_url(url)
317 self.label2.set_url(url)
318 self.stale = True
320 def _set_artist_props(self, a):
321 a.set_figure(self.figure)
323 def get_view_interval(self):
324 """
325 Return the view limits ``(min, max)`` of the axis the tick belongs to.
326 """
327 raise NotImplementedError('Derived must override')
329 def _apply_params(self, **kwargs):
330 for name, target in [("gridOn", self.gridline),
331 ("tick1On", self.tick1line),
332 ("tick2On", self.tick2line),
333 ("label1On", self.label1),
334 ("label2On", self.label2)]:
335 if name in kwargs:
336 target.set_visible(kwargs.pop(name))
337 if any(k in kwargs for k in ['size', 'width', 'pad', 'tickdir']):
338 self._size = kwargs.pop('size', self._size)
339 # Width could be handled outside this block, but it is
340 # convenient to leave it here.
341 self._width = kwargs.pop('width', self._width)
342 self._base_pad = kwargs.pop('pad', self._base_pad)
343 # _apply_tickdir uses _size and _base_pad to make _pad, and also
344 # sets the ticklines markers.
345 self._apply_tickdir(kwargs.pop('tickdir', self._tickdir))
346 for line in (self.tick1line, self.tick2line):
347 line.set_markersize(self._size)
348 line.set_markeredgewidth(self._width)
349 # _get_text1_transform uses _pad from _apply_tickdir.
350 trans = self._get_text1_transform()[0]
351 self.label1.set_transform(trans)
352 trans = self._get_text2_transform()[0]
353 self.label2.set_transform(trans)
354 tick_kw = {k: v for k, v in kwargs.items() if k in ['color', 'zorder']}
355 if 'color' in kwargs:
356 tick_kw['markeredgecolor'] = kwargs['color']
357 self.tick1line.set(**tick_kw)
358 self.tick2line.set(**tick_kw)
359 for k, v in tick_kw.items():
360 setattr(self, '_' + k, v)
362 if 'labelrotation' in kwargs:
363 self._set_labelrotation(kwargs.pop('labelrotation'))
364 self.label1.set(rotation=self._labelrotation[1])
365 self.label2.set(rotation=self._labelrotation[1])
367 label_kw = {k[5:]: v for k, v in kwargs.items()
368 if k in ['labelsize', 'labelcolor', 'labelfontfamily']}
369 self.label1.set(**label_kw)
370 self.label2.set(**label_kw)
372 grid_kw = {k[5:]: v for k, v in kwargs.items()
373 if k in _gridline_param_names}
374 self.gridline.set(**grid_kw)
376 def update_position(self, loc):
377 """Set the location of tick in data coords with scalar *loc*."""
378 raise NotImplementedError('Derived must override')
380 def _get_text1_transform(self):
381 raise NotImplementedError('Derived must override')
383 def _get_text2_transform(self):
384 raise NotImplementedError('Derived must override')
387class XTick(Tick):
388 """
389 Contains all the Artists needed to make an x tick - the tick line,
390 the label text and the grid line
391 """
392 __name__ = 'xtick'
394 def __init__(self, *args, **kwargs):
395 super().__init__(*args, **kwargs)
396 # x in data coords, y in axes coords
397 ax = self.axes
398 self.tick1line.set(
399 data=([0], [0]), transform=ax.get_xaxis_transform("tick1"))
400 self.tick2line.set(
401 data=([0], [1]), transform=ax.get_xaxis_transform("tick2"))
402 self.gridline.set(
403 data=([0, 0], [0, 1]), transform=ax.get_xaxis_transform("grid"))
404 # the y loc is 3 points below the min of y axis
405 trans, va, ha = self._get_text1_transform()
406 self.label1.set(
407 x=0, y=0,
408 verticalalignment=va, horizontalalignment=ha, transform=trans,
409 )
410 trans, va, ha = self._get_text2_transform()
411 self.label2.set(
412 x=0, y=1,
413 verticalalignment=va, horizontalalignment=ha, transform=trans,
414 )
416 def _get_text1_transform(self):
417 return self.axes.get_xaxis_text1_transform(self._pad)
419 def _get_text2_transform(self):
420 return self.axes.get_xaxis_text2_transform(self._pad)
422 def _apply_tickdir(self, tickdir):
423 # docstring inherited
424 super()._apply_tickdir(tickdir)
425 mark1, mark2 = {
426 'out': (mlines.TICKDOWN, mlines.TICKUP),
427 'in': (mlines.TICKUP, mlines.TICKDOWN),
428 'inout': ('|', '|'),
429 }[self._tickdir]
430 self.tick1line.set_marker(mark1)
431 self.tick2line.set_marker(mark2)
433 def update_position(self, loc):
434 """Set the location of tick in data coords with scalar *loc*."""
435 self.tick1line.set_xdata((loc,))
436 self.tick2line.set_xdata((loc,))
437 self.gridline.set_xdata((loc,))
438 self.label1.set_x(loc)
439 self.label2.set_x(loc)
440 self._loc = loc
441 self.stale = True
443 def get_view_interval(self):
444 # docstring inherited
445 return self.axes.viewLim.intervalx
448class YTick(Tick):
449 """
450 Contains all the Artists needed to make a Y tick - the tick line,
451 the label text and the grid line
452 """
453 __name__ = 'ytick'
455 def __init__(self, *args, **kwargs):
456 super().__init__(*args, **kwargs)
457 # x in axes coords, y in data coords
458 ax = self.axes
459 self.tick1line.set(
460 data=([0], [0]), transform=ax.get_yaxis_transform("tick1"))
461 self.tick2line.set(
462 data=([1], [0]), transform=ax.get_yaxis_transform("tick2"))
463 self.gridline.set(
464 data=([0, 1], [0, 0]), transform=ax.get_yaxis_transform("grid"))
465 # the y loc is 3 points below the min of y axis
466 trans, va, ha = self._get_text1_transform()
467 self.label1.set(
468 x=0, y=0,
469 verticalalignment=va, horizontalalignment=ha, transform=trans,
470 )
471 trans, va, ha = self._get_text2_transform()
472 self.label2.set(
473 x=1, y=0,
474 verticalalignment=va, horizontalalignment=ha, transform=trans,
475 )
477 def _get_text1_transform(self):
478 return self.axes.get_yaxis_text1_transform(self._pad)
480 def _get_text2_transform(self):
481 return self.axes.get_yaxis_text2_transform(self._pad)
483 def _apply_tickdir(self, tickdir):
484 # docstring inherited
485 super()._apply_tickdir(tickdir)
486 mark1, mark2 = {
487 'out': (mlines.TICKLEFT, mlines.TICKRIGHT),
488 'in': (mlines.TICKRIGHT, mlines.TICKLEFT),
489 'inout': ('_', '_'),
490 }[self._tickdir]
491 self.tick1line.set_marker(mark1)
492 self.tick2line.set_marker(mark2)
494 def update_position(self, loc):
495 """Set the location of tick in data coords with scalar *loc*."""
496 self.tick1line.set_ydata((loc,))
497 self.tick2line.set_ydata((loc,))
498 self.gridline.set_ydata((loc,))
499 self.label1.set_y(loc)
500 self.label2.set_y(loc)
501 self._loc = loc
502 self.stale = True
504 def get_view_interval(self):
505 # docstring inherited
506 return self.axes.viewLim.intervaly
509class Ticker:
510 """
511 A container for the objects defining tick position and format.
513 Attributes
514 ----------
515 locator : `~matplotlib.ticker.Locator` subclass
516 Determines the positions of the ticks.
517 formatter : `~matplotlib.ticker.Formatter` subclass
518 Determines the format of the tick labels.
519 """
521 def __init__(self):
522 self._locator = None
523 self._formatter = None
524 self._locator_is_default = True
525 self._formatter_is_default = True
527 @property
528 def locator(self):
529 return self._locator
531 @locator.setter
532 def locator(self, locator):
533 if not isinstance(locator, mticker.Locator):
534 raise TypeError('locator must be a subclass of '
535 'matplotlib.ticker.Locator')
536 self._locator = locator
538 @property
539 def formatter(self):
540 return self._formatter
542 @formatter.setter
543 def formatter(self, formatter):
544 if not isinstance(formatter, mticker.Formatter):
545 raise TypeError('formatter must be a subclass of '
546 'matplotlib.ticker.Formatter')
547 self._formatter = formatter
550class _LazyTickList:
551 """
552 A descriptor for lazy instantiation of tick lists.
554 See comment above definition of the ``majorTicks`` and ``minorTicks``
555 attributes.
556 """
558 def __init__(self, major):
559 self._major = major
561 def __get__(self, instance, owner):
562 if instance is None:
563 return self
564 else:
565 # instance._get_tick() can itself try to access the majorTicks
566 # attribute (e.g. in certain projection classes which override
567 # e.g. get_xaxis_text1_transform). In order to avoid infinite
568 # recursion, first set the majorTicks on the instance to an empty
569 # list, then create the tick and append it.
570 if self._major:
571 instance.majorTicks = []
572 tick = instance._get_tick(major=True)
573 instance.majorTicks.append(tick)
574 return instance.majorTicks
575 else:
576 instance.minorTicks = []
577 tick = instance._get_tick(major=False)
578 instance.minorTicks.append(tick)
579 return instance.minorTicks
582class Axis(martist.Artist):
583 """
584 Base class for `.XAxis` and `.YAxis`.
586 Attributes
587 ----------
588 isDefault_label : bool
590 axes : `~matplotlib.axes.Axes`
591 The `~.axes.Axes` to which the Axis belongs.
592 major : `~matplotlib.axis.Ticker`
593 Determines the major tick positions and their label format.
594 minor : `~matplotlib.axis.Ticker`
595 Determines the minor tick positions and their label format.
596 callbacks : `~matplotlib.cbook.CallbackRegistry`
598 label : `~matplotlib.text.Text`
599 The axis label.
600 labelpad : float
601 The distance between the axis label and the tick labels.
602 Defaults to :rc:`axes.labelpad` = 4.
603 offsetText : `~matplotlib.text.Text`
604 A `.Text` object containing the data offset of the ticks (if any).
605 pickradius : float
606 The acceptance radius for containment tests. See also `.Axis.contains`.
607 majorTicks : list of `.Tick`
608 The major ticks.
610 .. warning::
612 Ticks are not guaranteed to be persistent. Various operations
613 can create, delete and modify the Tick instances. There is an
614 imminent risk that changes to individual ticks will not
615 survive if you work on the figure further (including also
616 panning/zooming on a displayed figure).
618 Working on the individual ticks is a method of last resort.
619 Use `.set_tick_params` instead if possible.
621 minorTicks : list of `.Tick`
622 The minor ticks.
623 """
624 OFFSETTEXTPAD = 3
625 # The class used in _get_tick() to create tick instances. Must either be
626 # overwritten in subclasses, or subclasses must reimplement _get_tick().
627 _tick_class = None
629 def __str__(self):
630 return "{}({},{})".format(
631 type(self).__name__, *self.axes.transAxes.transform((0, 0)))
633 def __init__(self, axes, *, pickradius=15, clear=True):
634 """
635 Parameters
636 ----------
637 axes : `~matplotlib.axes.Axes`
638 The `~.axes.Axes` to which the created Axis belongs.
639 pickradius : float
640 The acceptance radius for containment tests. See also
641 `.Axis.contains`.
642 clear : bool, default: True
643 Whether to clear the Axis on creation. This is not required, e.g., when
644 creating an Axis as part of an Axes, as ``Axes.clear`` will call
645 ``Axis.clear``.
646 .. versionadded:: 3.8
647 """
648 super().__init__()
649 self._remove_overlapping_locs = True
651 self.set_figure(axes.figure)
653 self.isDefault_label = True
655 self.axes = axes
656 self.major = Ticker()
657 self.minor = Ticker()
658 self.callbacks = cbook.CallbackRegistry(signals=["units"])
660 self._autolabelpos = True
662 self.label = mtext.Text(
663 np.nan, np.nan,
664 fontsize=mpl.rcParams['axes.labelsize'],
665 fontweight=mpl.rcParams['axes.labelweight'],
666 color=mpl.rcParams['axes.labelcolor'],
667 )
668 self._set_artist_props(self.label)
669 self.offsetText = mtext.Text(np.nan, np.nan)
670 self._set_artist_props(self.offsetText)
672 self.labelpad = mpl.rcParams['axes.labelpad']
674 self.pickradius = pickradius
676 # Initialize here for testing; later add API
677 self._major_tick_kw = dict()
678 self._minor_tick_kw = dict()
680 if clear:
681 self.clear()
682 else:
683 self.converter = None
684 self.units = None
686 self._autoscale_on = True
688 @property
689 def isDefault_majloc(self):
690 return self.major._locator_is_default
692 @isDefault_majloc.setter
693 def isDefault_majloc(self, value):
694 self.major._locator_is_default = value
696 @property
697 def isDefault_majfmt(self):
698 return self.major._formatter_is_default
700 @isDefault_majfmt.setter
701 def isDefault_majfmt(self, value):
702 self.major._formatter_is_default = value
704 @property
705 def isDefault_minloc(self):
706 return self.minor._locator_is_default
708 @isDefault_minloc.setter
709 def isDefault_minloc(self, value):
710 self.minor._locator_is_default = value
712 @property
713 def isDefault_minfmt(self):
714 return self.minor._formatter_is_default
716 @isDefault_minfmt.setter
717 def isDefault_minfmt(self, value):
718 self.minor._formatter_is_default = value
720 def _get_shared_axes(self):
721 """Return Grouper of shared Axes for current axis."""
722 return self.axes._shared_axes[
723 self._get_axis_name()].get_siblings(self.axes)
725 def _get_shared_axis(self):
726 """Return list of shared axis for current axis."""
727 name = self._get_axis_name()
728 return [ax._axis_map[name] for ax in self._get_shared_axes()]
730 def _get_axis_name(self):
731 """Return the axis name."""
732 return [name for name, axis in self.axes._axis_map.items()
733 if axis is self][0]
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)
741 def get_remove_overlapping_locs(self):
742 return self._remove_overlapping_locs
744 def set_remove_overlapping_locs(self, val):
745 self._remove_overlapping_locs = bool(val)
747 remove_overlapping_locs = property(
748 get_remove_overlapping_locs, set_remove_overlapping_locs,
749 doc=('If minor ticker locations that overlap with major '
750 'ticker locations should be trimmed.'))
752 def set_label_coords(self, x, y, transform=None):
753 """
754 Set the coordinates of the label.
756 By default, the x coordinate of the y label and the y coordinate of the
757 x label are determined by the tick label bounding boxes, but this can
758 lead to poor alignment of multiple labels if there are multiple Axes.
760 You can also specify the coordinate system of the label with the
761 transform. If None, the default coordinate system will be the axes
762 coordinate system: (0, 0) is bottom left, (0.5, 0.5) is center, etc.
763 """
764 self._autolabelpos = False
765 if transform is None:
766 transform = self.axes.transAxes
768 self.label.set_transform(transform)
769 self.label.set_position((x, y))
770 self.stale = True
772 def get_transform(self):
773 """Return the transform used in the Axis' scale"""
774 return self._scale.get_transform()
776 def get_scale(self):
777 """Return this Axis' scale (as a str)."""
778 return self._scale.name
780 def _set_scale(self, value, **kwargs):
781 if not isinstance(value, mscale.ScaleBase):
782 self._scale = mscale.scale_factory(value, self, **kwargs)
783 else:
784 self._scale = value
785 self._scale.set_default_locators_and_formatters(self)
787 self.isDefault_majloc = True
788 self.isDefault_minloc = True
789 self.isDefault_majfmt = True
790 self.isDefault_minfmt = True
792 # This method is directly wrapped by Axes.set_{x,y}scale.
793 def _set_axes_scale(self, value, **kwargs):
794 """
795 Set this Axis' scale.
797 Parameters
798 ----------
799 value : {"linear", "log", "symlog", "logit", ...} or `.ScaleBase`
800 The axis scale type to apply.
802 **kwargs
803 Different keyword arguments are accepted, depending on the scale.
804 See the respective class keyword arguments:
806 - `matplotlib.scale.LinearScale`
807 - `matplotlib.scale.LogScale`
808 - `matplotlib.scale.SymmetricalLogScale`
809 - `matplotlib.scale.LogitScale`
810 - `matplotlib.scale.FuncScale`
811 - `matplotlib.scale.AsinhScale`
813 Notes
814 -----
815 By default, Matplotlib supports the above-mentioned scales.
816 Additionally, custom scales may be registered using
817 `matplotlib.scale.register_scale`. These scales can then also
818 be used here.
819 """
820 name = self._get_axis_name()
821 old_default_lims = (self.get_major_locator()
822 .nonsingular(-np.inf, np.inf))
823 for ax in self._get_shared_axes():
824 ax._axis_map[name]._set_scale(value, **kwargs)
825 ax._update_transScale()
826 ax.stale = True
827 new_default_lims = (self.get_major_locator()
828 .nonsingular(-np.inf, np.inf))
829 if old_default_lims != new_default_lims:
830 # Force autoscaling now, to take advantage of the scale locator's
831 # nonsingular() before it possibly gets swapped out by the user.
832 self.axes.autoscale_view(
833 **{f"scale{k}": k == name for k in self.axes._axis_names})
835 def limit_range_for_scale(self, vmin, vmax):
836 return self._scale.limit_range_for_scale(vmin, vmax, self.get_minpos())
838 def _get_autoscale_on(self):
839 """Return whether this Axis is autoscaled."""
840 return self._autoscale_on
842 def _set_autoscale_on(self, b):
843 """
844 Set whether this Axis is autoscaled when drawing or by
845 `.Axes.autoscale_view`. If b is None, then the value is not changed.
847 Parameters
848 ----------
849 b : bool
850 """
851 if b is not None:
852 self._autoscale_on = b
854 def get_children(self):
855 return [self.label, self.offsetText,
856 *self.get_major_ticks(), *self.get_minor_ticks()]
858 def _reset_major_tick_kw(self):
859 self._major_tick_kw.clear()
860 self._major_tick_kw['gridOn'] = (
861 mpl.rcParams['axes.grid'] and
862 mpl.rcParams['axes.grid.which'] in ('both', 'major'))
864 def _reset_minor_tick_kw(self):
865 self._minor_tick_kw.clear()
866 self._minor_tick_kw['gridOn'] = (
867 mpl.rcParams['axes.grid'] and
868 mpl.rcParams['axes.grid.which'] in ('both', 'minor'))
870 def clear(self):
871 """
872 Clear the axis.
874 This resets axis properties to their default values:
876 - the label
877 - the scale
878 - locators, formatters and ticks
879 - major and minor grid
880 - units
881 - registered callbacks
882 """
883 self.label._reset_visual_defaults()
884 # The above resets the label formatting using text rcParams,
885 # so we then update the formatting using axes rcParams
886 self.label.set_color(mpl.rcParams['axes.labelcolor'])
887 self.label.set_fontsize(mpl.rcParams['axes.labelsize'])
888 self.label.set_fontweight(mpl.rcParams['axes.labelweight'])
889 self.offsetText._reset_visual_defaults()
890 self.labelpad = mpl.rcParams['axes.labelpad']
892 self._init()
894 self._set_scale('linear')
896 # Clear the callback registry for this axis, or it may "leak"
897 self.callbacks = cbook.CallbackRegistry(signals=["units"])
899 # whether the grids are on
900 self._major_tick_kw['gridOn'] = (
901 mpl.rcParams['axes.grid'] and
902 mpl.rcParams['axes.grid.which'] in ('both', 'major'))
903 self._minor_tick_kw['gridOn'] = (
904 mpl.rcParams['axes.grid'] and
905 mpl.rcParams['axes.grid.which'] in ('both', 'minor'))
906 self.reset_ticks()
908 self.converter = None
909 self.units = None
910 self.stale = True
912 def reset_ticks(self):
913 """
914 Re-initialize the major and minor Tick lists.
916 Each list starts with a single fresh Tick.
917 """
918 # Restore the lazy tick lists.
919 try:
920 del self.majorTicks
921 except AttributeError:
922 pass
923 try:
924 del self.minorTicks
925 except AttributeError:
926 pass
927 try:
928 self.set_clip_path(self.axes.patch)
929 except AttributeError:
930 pass
932 def minorticks_on(self):
933 """
934 Display default minor ticks on the Axis, depending on the scale
935 (`~.axis.Axis.get_scale`).
937 Scales use specific minor locators:
939 - log: `~.LogLocator`
940 - symlog: `~.SymmetricalLogLocator`
941 - asinh: `~.AsinhLocator`
942 - logit: `~.LogitLocator`
943 - default: `~.AutoMinorLocator`
945 Displaying minor ticks may reduce performance; you may turn them off
946 using `minorticks_off()` if drawing speed is a problem.
947 """
948 scale = self.get_scale()
949 if scale == 'log':
950 s = self._scale
951 self.set_minor_locator(mticker.LogLocator(s.base, s.subs))
952 elif scale == 'symlog':
953 s = self._scale
954 self.set_minor_locator(
955 mticker.SymmetricalLogLocator(s._transform, s.subs))
956 elif scale == 'asinh':
957 s = self._scale
958 self.set_minor_locator(
959 mticker.AsinhLocator(s.linear_width, base=s._base,
960 subs=s._subs))
961 elif scale == 'logit':
962 self.set_minor_locator(mticker.LogitLocator(minor=True))
963 else:
964 self.set_minor_locator(mticker.AutoMinorLocator())
966 def minorticks_off(self):
967 """Remove minor ticks from the Axis."""
968 self.set_minor_locator(mticker.NullLocator())
970 def set_tick_params(self, which='major', reset=False, **kwargs):
971 """
972 Set appearance parameters for ticks, ticklabels, and gridlines.
974 For documentation of keyword arguments, see
975 :meth:`matplotlib.axes.Axes.tick_params`.
977 See Also
978 --------
979 .Axis.get_tick_params
980 View the current style settings for ticks, ticklabels, and
981 gridlines.
982 """
983 _api.check_in_list(['major', 'minor', 'both'], which=which)
984 kwtrans = self._translate_tick_params(kwargs)
986 # the kwargs are stored in self._major/minor_tick_kw so that any
987 # future new ticks will automatically get them
988 if reset:
989 if which in ['major', 'both']:
990 self._reset_major_tick_kw()
991 self._major_tick_kw.update(kwtrans)
992 if which in ['minor', 'both']:
993 self._reset_minor_tick_kw()
994 self._minor_tick_kw.update(kwtrans)
995 self.reset_ticks()
996 else:
997 if which in ['major', 'both']:
998 self._major_tick_kw.update(kwtrans)
999 for tick in self.majorTicks:
1000 tick._apply_params(**kwtrans)
1001 if which in ['minor', 'both']:
1002 self._minor_tick_kw.update(kwtrans)
1003 for tick in self.minorTicks:
1004 tick._apply_params(**kwtrans)
1005 # labelOn and labelcolor also apply to the offset text.
1006 if 'label1On' in kwtrans or 'label2On' in kwtrans:
1007 self.offsetText.set_visible(
1008 self._major_tick_kw.get('label1On', False)
1009 or self._major_tick_kw.get('label2On', False))
1010 if 'labelcolor' in kwtrans:
1011 self.offsetText.set_color(kwtrans['labelcolor'])
1013 self.stale = True
1015 def get_tick_params(self, which='major'):
1016 """
1017 Get appearance parameters for ticks, ticklabels, and gridlines.
1019 .. versionadded:: 3.7
1021 Parameters
1022 ----------
1023 which : {'major', 'minor'}, default: 'major'
1024 The group of ticks for which the parameters are retrieved.
1026 Returns
1027 -------
1028 dict
1029 Properties for styling tick elements added to the axis.
1031 Notes
1032 -----
1033 This method returns the appearance parameters for styling *new*
1034 elements added to this axis and may be different from the values
1035 on current elements if they were modified directly by the user
1036 (e.g., via ``set_*`` methods on individual tick objects).
1038 Examples
1039 --------
1040 ::
1042 >>> ax.yaxis.set_tick_params(labelsize=30, labelcolor='red',
1043 ... direction='out', which='major')
1044 >>> ax.yaxis.get_tick_params(which='major')
1045 {'direction': 'out',
1046 'left': True,
1047 'right': False,
1048 'labelleft': True,
1049 'labelright': False,
1050 'gridOn': False,
1051 'labelsize': 30,
1052 'labelcolor': 'red'}
1053 >>> ax.yaxis.get_tick_params(which='minor')
1054 {'left': True,
1055 'right': False,
1056 'labelleft': True,
1057 'labelright': False,
1058 'gridOn': False}
1061 """
1062 _api.check_in_list(['major', 'minor'], which=which)
1063 if which == 'major':
1064 return self._translate_tick_params(
1065 self._major_tick_kw, reverse=True
1066 )
1067 return self._translate_tick_params(self._minor_tick_kw, reverse=True)
1069 @staticmethod
1070 def _translate_tick_params(kw, reverse=False):
1071 """
1072 Translate the kwargs supported by `.Axis.set_tick_params` to kwargs
1073 supported by `.Tick._apply_params`.
1075 In particular, this maps axis specific names like 'top', 'left'
1076 to the generic tick1, tick2 logic of the axis. Additionally, there
1077 are some other name translations.
1079 Returns a new dict of translated kwargs.
1081 Note: Use reverse=True to translate from those supported by
1082 `.Tick._apply_params` back to those supported by
1083 `.Axis.set_tick_params`.
1084 """
1085 kw_ = {**kw}
1087 # The following lists may be moved to a more accessible location.
1088 allowed_keys = [
1089 'size', 'width', 'color', 'tickdir', 'pad',
1090 'labelsize', 'labelcolor', 'labelfontfamily', 'zorder', 'gridOn',
1091 'tick1On', 'tick2On', 'label1On', 'label2On',
1092 'length', 'direction', 'left', 'bottom', 'right', 'top',
1093 'labelleft', 'labelbottom', 'labelright', 'labeltop',
1094 'labelrotation',
1095 *_gridline_param_names]
1097 keymap = {
1098 # tick_params key -> axis key
1099 'length': 'size',
1100 'direction': 'tickdir',
1101 'rotation': 'labelrotation',
1102 'left': 'tick1On',
1103 'bottom': 'tick1On',
1104 'right': 'tick2On',
1105 'top': 'tick2On',
1106 'labelleft': 'label1On',
1107 'labelbottom': 'label1On',
1108 'labelright': 'label2On',
1109 'labeltop': 'label2On',
1110 }
1111 if reverse:
1112 kwtrans = {
1113 oldkey: kw_.pop(newkey)
1114 for oldkey, newkey in keymap.items() if newkey in kw_
1115 }
1116 else:
1117 kwtrans = {
1118 newkey: kw_.pop(oldkey)
1119 for oldkey, newkey in keymap.items() if oldkey in kw_
1120 }
1121 if 'colors' in kw_:
1122 c = kw_.pop('colors')
1123 kwtrans['color'] = c
1124 kwtrans['labelcolor'] = c
1125 # Maybe move the checking up to the caller of this method.
1126 for key in kw_:
1127 if key not in allowed_keys:
1128 raise ValueError(
1129 "keyword %s is not recognized; valid keywords are %s"
1130 % (key, allowed_keys))
1131 kwtrans.update(kw_)
1132 return kwtrans
1134 @_api.rename_parameter("3.8", "clippath", "path")
1135 def set_clip_path(self, path, transform=None):
1136 super().set_clip_path(path, transform)
1137 for child in self.majorTicks + self.minorTicks:
1138 child.set_clip_path(path, transform)
1139 self.stale = True
1141 def get_view_interval(self):
1142 """Return the ``(min, max)`` view limits of this axis."""
1143 raise NotImplementedError('Derived must override')
1145 def set_view_interval(self, vmin, vmax, ignore=False):
1146 """
1147 Set the axis view limits. This method is for internal use; Matplotlib
1148 users should typically use e.g. `~.Axes.set_xlim` or `~.Axes.set_ylim`.
1150 If *ignore* is False (the default), this method will never reduce the
1151 preexisting view limits, only expand them if *vmin* or *vmax* are not
1152 within them. Moreover, the order of *vmin* and *vmax* does not matter;
1153 the orientation of the axis will not change.
1155 If *ignore* is True, the view limits will be set exactly to ``(vmin,
1156 vmax)`` in that order.
1157 """
1158 raise NotImplementedError('Derived must override')
1160 def get_data_interval(self):
1161 """Return the ``(min, max)`` data limits of this axis."""
1162 raise NotImplementedError('Derived must override')
1164 def set_data_interval(self, vmin, vmax, ignore=False):
1165 """
1166 Set the axis data limits. This method is for internal use.
1168 If *ignore* is False (the default), this method will never reduce the
1169 preexisting data limits, only expand them if *vmin* or *vmax* are not
1170 within them. Moreover, the order of *vmin* and *vmax* does not matter;
1171 the orientation of the axis will not change.
1173 If *ignore* is True, the data limits will be set exactly to ``(vmin,
1174 vmax)`` in that order.
1175 """
1176 raise NotImplementedError('Derived must override')
1178 def get_inverted(self):
1179 """
1180 Return whether this Axis is oriented in the "inverse" direction.
1182 The "normal" direction is increasing to the right for the x-axis and to
1183 the top for the y-axis; the "inverse" direction is increasing to the
1184 left for the x-axis and to the bottom for the y-axis.
1185 """
1186 low, high = self.get_view_interval()
1187 return high < low
1189 def set_inverted(self, inverted):
1190 """
1191 Set whether this Axis is oriented in the "inverse" direction.
1193 The "normal" direction is increasing to the right for the x-axis and to
1194 the top for the y-axis; the "inverse" direction is increasing to the
1195 left for the x-axis and to the bottom for the y-axis.
1196 """
1197 a, b = self.get_view_interval()
1198 # cast to bool to avoid bad interaction between python 3.8 and np.bool_
1199 self._set_lim(*sorted((a, b), reverse=bool(inverted)), auto=None)
1201 def set_default_intervals(self):
1202 """
1203 Set the default limits for the axis data and view interval if they
1204 have not been not mutated yet.
1205 """
1206 # this is mainly in support of custom object plotting. For
1207 # example, if someone passes in a datetime object, we do not
1208 # know automagically how to set the default min/max of the
1209 # data and view limits. The unit conversion AxisInfo
1210 # interface provides a hook for custom types to register
1211 # default limits through the AxisInfo.default_limits
1212 # attribute, and the derived code below will check for that
1213 # and use it if it's available (else just use 0..1)
1215 def _set_lim(self, v0, v1, *, emit=True, auto):
1216 """
1217 Set view limits.
1219 This method is a helper for the Axes ``set_xlim``, ``set_ylim``, and
1220 ``set_zlim`` methods.
1222 Parameters
1223 ----------
1224 v0, v1 : float
1225 The view limits. (Passing *v0* as a (low, high) pair is not
1226 supported; normalization must occur in the Axes setters.)
1227 emit : bool, default: True
1228 Whether to notify observers of limit change.
1229 auto : bool or None, default: False
1230 Whether to turn on autoscaling of the x-axis. True turns on, False
1231 turns off, None leaves unchanged.
1232 """
1233 name = self._get_axis_name()
1235 self.axes._process_unit_info([(name, (v0, v1))], convert=False)
1236 v0 = self.axes._validate_converted_limits(v0, self.convert_units)
1237 v1 = self.axes._validate_converted_limits(v1, self.convert_units)
1239 if v0 is None or v1 is None:
1240 # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
1241 # so only grab the limits if we really need them.
1242 old0, old1 = self.get_view_interval()
1243 if v0 is None:
1244 v0 = old0
1245 if v1 is None:
1246 v1 = old1
1248 if self.get_scale() == 'log' and (v0 <= 0 or v1 <= 0):
1249 # Axes init calls set_xlim(0, 1) before get_xlim() can be called,
1250 # so only grab the limits if we really need them.
1251 old0, old1 = self.get_view_interval()
1252 if v0 <= 0:
1253 _api.warn_external(f"Attempt to set non-positive {name}lim on "
1254 f"a log-scaled axis will be ignored.")
1255 v0 = old0
1256 if v1 <= 0:
1257 _api.warn_external(f"Attempt to set non-positive {name}lim on "
1258 f"a log-scaled axis will be ignored.")
1259 v1 = old1
1260 if v0 == v1:
1261 _api.warn_external(
1262 f"Attempting to set identical low and high {name}lims "
1263 f"makes transformation singular; automatically expanding.")
1264 reverse = bool(v0 > v1) # explicit cast needed for python3.8+np.bool_.
1265 v0, v1 = self.get_major_locator().nonsingular(v0, v1)
1266 v0, v1 = self.limit_range_for_scale(v0, v1)
1267 v0, v1 = sorted([v0, v1], reverse=bool(reverse))
1269 self.set_view_interval(v0, v1, ignore=True)
1270 # Mark viewlims as no longer stale without triggering an autoscale.
1271 for ax in self._get_shared_axes():
1272 ax._stale_viewlims[name] = False
1273 self._set_autoscale_on(auto)
1275 if emit:
1276 self.axes.callbacks.process(f"{name}lim_changed", self.axes)
1277 # Call all of the other Axes that are shared with this one
1278 for other in self._get_shared_axes():
1279 if other is self.axes:
1280 continue
1281 other._axis_map[name]._set_lim(v0, v1, emit=False, auto=auto)
1282 if emit:
1283 other.callbacks.process(f"{name}lim_changed", other)
1284 if other.figure != self.figure:
1285 other.figure.canvas.draw_idle()
1287 self.stale = True
1288 return v0, v1
1290 def _set_artist_props(self, a):
1291 if a is None:
1292 return
1293 a.set_figure(self.figure)
1295 def _update_ticks(self):
1296 """
1297 Update ticks (position and labels) using the current data interval of
1298 the axes. Return the list of ticks that will be drawn.
1299 """
1300 major_locs = self.get_majorticklocs()
1301 major_labels = self.major.formatter.format_ticks(major_locs)
1302 major_ticks = self.get_major_ticks(len(major_locs))
1303 for tick, loc, label in zip(major_ticks, major_locs, major_labels):
1304 tick.update_position(loc)
1305 tick.label1.set_text(label)
1306 tick.label2.set_text(label)
1307 minor_locs = self.get_minorticklocs()
1308 minor_labels = self.minor.formatter.format_ticks(minor_locs)
1309 minor_ticks = self.get_minor_ticks(len(minor_locs))
1310 for tick, loc, label in zip(minor_ticks, minor_locs, minor_labels):
1311 tick.update_position(loc)
1312 tick.label1.set_text(label)
1313 tick.label2.set_text(label)
1314 ticks = [*major_ticks, *minor_ticks]
1316 view_low, view_high = self.get_view_interval()
1317 if view_low > view_high:
1318 view_low, view_high = view_high, view_low
1320 if (hasattr(self, "axes") and self.axes.name == '3d'
1321 and mpl.rcParams['axes3d.automargin']):
1322 # In mpl3.8, the margin was 1/48. Due to the change in automargin
1323 # behavior in mpl3.9, we need to adjust this to compensate for a
1324 # zoom factor of 2/48, giving us a 23/24 modifier. So the new
1325 # margin is 0.019965277777777776 = 1/48*23/24.
1326 margin = 0.019965277777777776
1327 delta = view_high - view_low
1328 view_high = view_high - delta * margin
1329 view_low = view_low + delta * margin
1331 interval_t = self.get_transform().transform([view_low, view_high])
1333 ticks_to_draw = []
1334 for tick in ticks:
1335 try:
1336 loc_t = self.get_transform().transform(tick.get_loc())
1337 except AssertionError:
1338 # transforms.transform doesn't allow masked values but
1339 # some scales might make them, so we need this try/except.
1340 pass
1341 else:
1342 if mtransforms._interval_contains_close(interval_t, loc_t):
1343 ticks_to_draw.append(tick)
1345 return ticks_to_draw
1347 def _get_ticklabel_bboxes(self, ticks, renderer=None):
1348 """Return lists of bboxes for ticks' label1's and label2's."""
1349 if renderer is None:
1350 renderer = self.figure._get_renderer()
1351 return ([tick.label1.get_window_extent(renderer)
1352 for tick in ticks if tick.label1.get_visible()],
1353 [tick.label2.get_window_extent(renderer)
1354 for tick in ticks if tick.label2.get_visible()])
1356 def get_tightbbox(self, renderer=None, *, for_layout_only=False):
1357 """
1358 Return a bounding box that encloses the axis. It only accounts
1359 tick labels, axis label, and offsetText.
1361 If *for_layout_only* is True, then the width of the label (if this
1362 is an x-axis) or the height of the label (if this is a y-axis) is
1363 collapsed to near zero. This allows tight/constrained_layout to ignore
1364 too-long labels when doing their layout.
1365 """
1366 if not self.get_visible():
1367 return
1368 if renderer is None:
1369 renderer = self.figure._get_renderer()
1370 ticks_to_draw = self._update_ticks()
1372 self._update_label_position(renderer)
1374 # go back to just this axis's tick labels
1375 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
1377 self._update_offset_text_position(tlb1, tlb2)
1378 self.offsetText.set_text(self.major.formatter.get_offset())
1380 bboxes = [
1381 *(a.get_window_extent(renderer)
1382 for a in [self.offsetText]
1383 if a.get_visible()),
1384 *tlb1, *tlb2,
1385 ]
1386 # take care of label
1387 if self.label.get_visible():
1388 bb = self.label.get_window_extent(renderer)
1389 # for constrained/tight_layout, we want to ignore the label's
1390 # width/height because the adjustments they make can't be improved.
1391 # this code collapses the relevant direction
1392 if for_layout_only:
1393 if self.axis_name == "x" and bb.width > 0:
1394 bb.x0 = (bb.x0 + bb.x1) / 2 - 0.5
1395 bb.x1 = bb.x0 + 1.0
1396 if self.axis_name == "y" and bb.height > 0:
1397 bb.y0 = (bb.y0 + bb.y1) / 2 - 0.5
1398 bb.y1 = bb.y0 + 1.0
1399 bboxes.append(bb)
1400 bboxes = [b for b in bboxes
1401 if 0 < b.width < np.inf and 0 < b.height < np.inf]
1402 if bboxes:
1403 return mtransforms.Bbox.union(bboxes)
1404 else:
1405 return None
1407 def get_tick_padding(self):
1408 values = []
1409 if len(self.majorTicks):
1410 values.append(self.majorTicks[0].get_tick_padding())
1411 if len(self.minorTicks):
1412 values.append(self.minorTicks[0].get_tick_padding())
1413 return max(values, default=0)
1415 @martist.allow_rasterization
1416 def draw(self, renderer):
1417 # docstring inherited
1419 if not self.get_visible():
1420 return
1421 renderer.open_group(__name__, gid=self.get_gid())
1423 ticks_to_draw = self._update_ticks()
1424 tlb1, tlb2 = self._get_ticklabel_bboxes(ticks_to_draw, renderer)
1426 for tick in ticks_to_draw:
1427 tick.draw(renderer)
1429 # Shift label away from axes to avoid overlapping ticklabels.
1430 self._update_label_position(renderer)
1431 self.label.draw(renderer)
1433 self._update_offset_text_position(tlb1, tlb2)
1434 self.offsetText.set_text(self.major.formatter.get_offset())
1435 self.offsetText.draw(renderer)
1437 renderer.close_group(__name__)
1438 self.stale = False
1440 def get_gridlines(self):
1441 r"""Return this Axis' grid lines as a list of `.Line2D`\s."""
1442 ticks = self.get_major_ticks()
1443 return cbook.silent_list('Line2D gridline',
1444 [tick.gridline for tick in ticks])
1446 def get_label(self):
1447 """Return the axis label as a Text instance."""
1448 return self.label
1450 def get_offset_text(self):
1451 """Return the axis offsetText as a Text instance."""
1452 return self.offsetText
1454 def get_pickradius(self):
1455 """Return the depth of the axis used by the picker."""
1456 return self._pickradius
1458 def get_majorticklabels(self):
1459 """Return this Axis' major tick labels, as a list of `~.text.Text`."""
1460 self._update_ticks()
1461 ticks = self.get_major_ticks()
1462 labels1 = [tick.label1 for tick in ticks if tick.label1.get_visible()]
1463 labels2 = [tick.label2 for tick in ticks if tick.label2.get_visible()]
1464 return labels1 + labels2
1466 def get_minorticklabels(self):
1467 """Return this Axis' minor tick labels, as a list of `~.text.Text`."""
1468 self._update_ticks()
1469 ticks = self.get_minor_ticks()
1470 labels1 = [tick.label1 for tick in ticks if tick.label1.get_visible()]
1471 labels2 = [tick.label2 for tick in ticks if tick.label2.get_visible()]
1472 return labels1 + labels2
1474 def get_ticklabels(self, minor=False, which=None):
1475 """
1476 Get this Axis' tick labels.
1478 Parameters
1479 ----------
1480 minor : bool
1481 Whether to return the minor or the major ticklabels.
1483 which : None, ('minor', 'major', 'both')
1484 Overrides *minor*.
1486 Selects which ticklabels to return
1488 Returns
1489 -------
1490 list of `~matplotlib.text.Text`
1491 """
1492 if which is not None:
1493 if which == 'minor':
1494 return self.get_minorticklabels()
1495 elif which == 'major':
1496 return self.get_majorticklabels()
1497 elif which == 'both':
1498 return self.get_majorticklabels() + self.get_minorticklabels()
1499 else:
1500 _api.check_in_list(['major', 'minor', 'both'], which=which)
1501 if minor:
1502 return self.get_minorticklabels()
1503 return self.get_majorticklabels()
1505 def get_majorticklines(self):
1506 r"""Return this Axis' major tick lines as a list of `.Line2D`\s."""
1507 lines = []
1508 ticks = self.get_major_ticks()
1509 for tick in ticks:
1510 lines.append(tick.tick1line)
1511 lines.append(tick.tick2line)
1512 return cbook.silent_list('Line2D ticklines', lines)
1514 def get_minorticklines(self):
1515 r"""Return this Axis' minor tick lines as a list of `.Line2D`\s."""
1516 lines = []
1517 ticks = self.get_minor_ticks()
1518 for tick in ticks:
1519 lines.append(tick.tick1line)
1520 lines.append(tick.tick2line)
1521 return cbook.silent_list('Line2D ticklines', lines)
1523 def get_ticklines(self, minor=False):
1524 r"""Return this Axis' tick lines as a list of `.Line2D`\s."""
1525 if minor:
1526 return self.get_minorticklines()
1527 return self.get_majorticklines()
1529 def get_majorticklocs(self):
1530 """Return this Axis' major tick locations in data coordinates."""
1531 return self.major.locator()
1533 def get_minorticklocs(self):
1534 """Return this Axis' minor tick locations in data coordinates."""
1535 # Remove minor ticks duplicating major ticks.
1536 minor_locs = np.asarray(self.minor.locator())
1537 if self.remove_overlapping_locs:
1538 major_locs = self.major.locator()
1539 transform = self._scale.get_transform()
1540 tr_minor_locs = transform.transform(minor_locs)
1541 tr_major_locs = transform.transform(major_locs)
1542 lo, hi = sorted(transform.transform(self.get_view_interval()))
1543 # Use the transformed view limits as scale. 1e-5 is the default
1544 # rtol for np.isclose.
1545 tol = (hi - lo) * 1e-5
1546 mask = np.isclose(tr_minor_locs[:, None], tr_major_locs[None, :],
1547 atol=tol, rtol=0).any(axis=1)
1548 minor_locs = minor_locs[~mask]
1549 return minor_locs
1551 def get_ticklocs(self, *, minor=False):
1552 """
1553 Return this Axis' tick locations in data coordinates.
1555 The locations are not clipped to the current axis limits and hence
1556 may contain locations that are not visible in the output.
1558 Parameters
1559 ----------
1560 minor : bool, default: False
1561 True to return the minor tick directions,
1562 False to return the major tick directions.
1564 Returns
1565 -------
1566 array of tick locations
1567 """
1568 return self.get_minorticklocs() if minor else self.get_majorticklocs()
1570 def get_ticks_direction(self, minor=False):
1571 """
1572 Return an array of this Axis' tick directions.
1574 Parameters
1575 ----------
1576 minor : bool, default: False
1577 True to return the minor tick directions,
1578 False to return the major tick directions.
1580 Returns
1581 -------
1582 array of tick directions
1583 """
1584 if minor:
1585 return np.array(
1586 [tick._tickdir for tick in self.get_minor_ticks()])
1587 else:
1588 return np.array(
1589 [tick._tickdir for tick in self.get_major_ticks()])
1591 def _get_tick(self, major):
1592 """Return the default tick instance."""
1593 if self._tick_class is None:
1594 raise NotImplementedError(
1595 f"The Axis subclass {self.__class__.__name__} must define "
1596 "_tick_class or reimplement _get_tick()")
1597 tick_kw = self._major_tick_kw if major else self._minor_tick_kw
1598 return self._tick_class(self.axes, 0, major=major, **tick_kw)
1600 def _get_tick_label_size(self, axis_name):
1601 """
1602 Return the text size of tick labels for this Axis.
1604 This is a convenience function to avoid having to create a `Tick` in
1605 `.get_tick_space`, since it is expensive.
1606 """
1607 tick_kw = self._major_tick_kw
1608 size = tick_kw.get('labelsize',
1609 mpl.rcParams[f'{axis_name}tick.labelsize'])
1610 return mtext.FontProperties(size=size).get_size_in_points()
1612 def _copy_tick_props(self, src, dest):
1613 """Copy the properties from *src* tick to *dest* tick."""
1614 if src is None or dest is None:
1615 return
1616 dest.label1.update_from(src.label1)
1617 dest.label2.update_from(src.label2)
1618 dest.tick1line.update_from(src.tick1line)
1619 dest.tick2line.update_from(src.tick2line)
1620 dest.gridline.update_from(src.gridline)
1621 dest.update_from(src)
1622 dest._loc = src._loc
1623 dest._size = src._size
1624 dest._width = src._width
1625 dest._base_pad = src._base_pad
1626 dest._labelrotation = src._labelrotation
1627 dest._zorder = src._zorder
1628 dest._tickdir = src._tickdir
1630 def get_label_text(self):
1631 """Get the text of the label."""
1632 return self.label.get_text()
1634 def get_major_locator(self):
1635 """Get the locator of the major ticker."""
1636 return self.major.locator
1638 def get_minor_locator(self):
1639 """Get the locator of the minor ticker."""
1640 return self.minor.locator
1642 def get_major_formatter(self):
1643 """Get the formatter of the major ticker."""
1644 return self.major.formatter
1646 def get_minor_formatter(self):
1647 """Get the formatter of the minor ticker."""
1648 return self.minor.formatter
1650 def get_major_ticks(self, numticks=None):
1651 r"""
1652 Return the list of major `.Tick`\s.
1654 .. warning::
1656 Ticks are not guaranteed to be persistent. Various operations
1657 can create, delete and modify the Tick instances. There is an
1658 imminent risk that changes to individual ticks will not
1659 survive if you work on the figure further (including also
1660 panning/zooming on a displayed figure).
1662 Working on the individual ticks is a method of last resort.
1663 Use `.set_tick_params` instead if possible.
1664 """
1665 if numticks is None:
1666 numticks = len(self.get_majorticklocs())
1668 while len(self.majorTicks) < numticks:
1669 # Update the new tick label properties from the old.
1670 tick = self._get_tick(major=True)
1671 self.majorTicks.append(tick)
1672 self._copy_tick_props(self.majorTicks[0], tick)
1674 return self.majorTicks[:numticks]
1676 def get_minor_ticks(self, numticks=None):
1677 r"""
1678 Return the list of minor `.Tick`\s.
1680 .. warning::
1682 Ticks are not guaranteed to be persistent. Various operations
1683 can create, delete and modify the Tick instances. There is an
1684 imminent risk that changes to individual ticks will not
1685 survive if you work on the figure further (including also
1686 panning/zooming on a displayed figure).
1688 Working on the individual ticks is a method of last resort.
1689 Use `.set_tick_params` instead if possible.
1690 """
1691 if numticks is None:
1692 numticks = len(self.get_minorticklocs())
1694 while len(self.minorTicks) < numticks:
1695 # Update the new tick label properties from the old.
1696 tick = self._get_tick(major=False)
1697 self.minorTicks.append(tick)
1698 self._copy_tick_props(self.minorTicks[0], tick)
1700 return self.minorTicks[:numticks]
1702 def grid(self, visible=None, which='major', **kwargs):
1703 """
1704 Configure the grid lines.
1706 Parameters
1707 ----------
1708 visible : bool or None
1709 Whether to show the grid lines. If any *kwargs* are supplied, it
1710 is assumed you want the grid on and *visible* will be set to True.
1712 If *visible* is *None* and there are no *kwargs*, this toggles the
1713 visibility of the lines.
1715 which : {'major', 'minor', 'both'}
1716 The grid lines to apply the changes on.
1718 **kwargs : `~matplotlib.lines.Line2D` properties
1719 Define the line properties of the grid, e.g.::
1721 grid(color='r', linestyle='-', linewidth=2)
1722 """
1723 if kwargs:
1724 if visible is None:
1725 visible = True
1726 elif not visible: # something false-like but not None
1727 _api.warn_external('First parameter to grid() is false, '
1728 'but line properties are supplied. The '
1729 'grid will be enabled.')
1730 visible = True
1731 which = which.lower()
1732 _api.check_in_list(['major', 'minor', 'both'], which=which)
1733 gridkw = {f'grid_{name}': value for name, value in kwargs.items()}
1734 if which in ['minor', 'both']:
1735 gridkw['gridOn'] = (not self._minor_tick_kw['gridOn']
1736 if visible is None else visible)
1737 self.set_tick_params(which='minor', **gridkw)
1738 if which in ['major', 'both']:
1739 gridkw['gridOn'] = (not self._major_tick_kw['gridOn']
1740 if visible is None else visible)
1741 self.set_tick_params(which='major', **gridkw)
1742 self.stale = True
1744 def update_units(self, data):
1745 """
1746 Introspect *data* for units converter and update the
1747 ``axis.converter`` instance if necessary. Return *True*
1748 if *data* is registered for unit conversion.
1749 """
1750 converter = munits.registry.get_converter(data)
1751 if converter is None:
1752 return False
1754 neednew = self.converter != converter
1755 self.converter = converter
1756 default = self.converter.default_units(data, self)
1757 if default is not None and self.units is None:
1758 self.set_units(default)
1760 elif neednew:
1761 self._update_axisinfo()
1762 self.stale = True
1763 return True
1765 def _update_axisinfo(self):
1766 """
1767 Check the axis converter for the stored units to see if the
1768 axis info needs to be updated.
1769 """
1770 if self.converter is None:
1771 return
1773 info = self.converter.axisinfo(self.units, self)
1775 if info is None:
1776 return
1777 if info.majloc is not None and \
1778 self.major.locator != info.majloc and self.isDefault_majloc:
1779 self.set_major_locator(info.majloc)
1780 self.isDefault_majloc = True
1781 if info.minloc is not None and \
1782 self.minor.locator != info.minloc and self.isDefault_minloc:
1783 self.set_minor_locator(info.minloc)
1784 self.isDefault_minloc = True
1785 if info.majfmt is not None and \
1786 self.major.formatter != info.majfmt and self.isDefault_majfmt:
1787 self.set_major_formatter(info.majfmt)
1788 self.isDefault_majfmt = True
1789 if info.minfmt is not None and \
1790 self.minor.formatter != info.minfmt and self.isDefault_minfmt:
1791 self.set_minor_formatter(info.minfmt)
1792 self.isDefault_minfmt = True
1793 if info.label is not None and self.isDefault_label:
1794 self.set_label_text(info.label)
1795 self.isDefault_label = True
1797 self.set_default_intervals()
1799 def have_units(self):
1800 return self.converter is not None or self.units is not None
1802 def convert_units(self, x):
1803 # If x is natively supported by Matplotlib, doesn't need converting
1804 if munits._is_natively_supported(x):
1805 return x
1807 if self.converter is None:
1808 self.converter = munits.registry.get_converter(x)
1810 if self.converter is None:
1811 return x
1812 try:
1813 ret = self.converter.convert(x, self.units, self)
1814 except Exception as e:
1815 raise munits.ConversionError('Failed to convert value(s) to axis '
1816 f'units: {x!r}') from e
1817 return ret
1819 def set_units(self, u):
1820 """
1821 Set the units for axis.
1823 Parameters
1824 ----------
1825 u : units tag
1827 Notes
1828 -----
1829 The units of any shared axis will also be updated.
1830 """
1831 if u == self.units:
1832 return
1833 for axis in self._get_shared_axis():
1834 axis.units = u
1835 axis._update_axisinfo()
1836 axis.callbacks.process('units')
1837 axis.stale = True
1839 def get_units(self):
1840 """Return the units for axis."""
1841 return self.units
1843 def set_label_text(self, label, fontdict=None, **kwargs):
1844 """
1845 Set the text value of the axis label.
1847 Parameters
1848 ----------
1849 label : str
1850 Text string.
1851 fontdict : dict
1852 Text properties.
1854 .. admonition:: Discouraged
1856 The use of *fontdict* is discouraged. Parameters should be passed as
1857 individual keyword arguments or using dictionary-unpacking
1858 ``set_label_text(..., **fontdict)``.
1860 **kwargs
1861 Merged into fontdict.
1862 """
1863 self.isDefault_label = False
1864 self.label.set_text(label)
1865 if fontdict is not None:
1866 self.label.update(fontdict)
1867 self.label.update(kwargs)
1868 self.stale = True
1869 return self.label
1871 def set_major_formatter(self, formatter):
1872 """
1873 Set the formatter of the major ticker.
1875 In addition to a `~matplotlib.ticker.Formatter` instance,
1876 this also accepts a ``str`` or function.
1878 For a ``str`` a `~matplotlib.ticker.StrMethodFormatter` is used.
1879 The field used for the value must be labeled ``'x'`` and the field used
1880 for the position must be labeled ``'pos'``.
1881 See the `~matplotlib.ticker.StrMethodFormatter` documentation for
1882 more information.
1884 For a function, a `~matplotlib.ticker.FuncFormatter` is used.
1885 The function must take two inputs (a tick value ``x`` and a
1886 position ``pos``), and return a string containing the corresponding
1887 tick label.
1888 See the `~matplotlib.ticker.FuncFormatter` documentation for
1889 more information.
1891 Parameters
1892 ----------
1893 formatter : `~matplotlib.ticker.Formatter`, ``str``, or function
1894 """
1895 self._set_formatter(formatter, self.major)
1897 def set_minor_formatter(self, formatter):
1898 """
1899 Set the formatter of the minor ticker.
1901 In addition to a `~matplotlib.ticker.Formatter` instance,
1902 this also accepts a ``str`` or function.
1903 See `.Axis.set_major_formatter` for more information.
1905 Parameters
1906 ----------
1907 formatter : `~matplotlib.ticker.Formatter`, ``str``, or function
1908 """
1909 self._set_formatter(formatter, self.minor)
1911 def _set_formatter(self, formatter, level):
1912 if isinstance(formatter, str):
1913 formatter = mticker.StrMethodFormatter(formatter)
1914 # Don't allow any other TickHelper to avoid easy-to-make errors,
1915 # like using a Locator instead of a Formatter.
1916 elif (callable(formatter) and
1917 not isinstance(formatter, mticker.TickHelper)):
1918 formatter = mticker.FuncFormatter(formatter)
1919 else:
1920 _api.check_isinstance(mticker.Formatter, formatter=formatter)
1922 if (isinstance(formatter, mticker.FixedFormatter)
1923 and len(formatter.seq) > 0
1924 and not isinstance(level.locator, mticker.FixedLocator)):
1925 _api.warn_external('FixedFormatter should only be used together '
1926 'with FixedLocator')
1928 if level == self.major:
1929 self.isDefault_majfmt = False
1930 else:
1931 self.isDefault_minfmt = False
1933 level.formatter = formatter
1934 formatter.set_axis(self)
1935 self.stale = True
1937 def set_major_locator(self, locator):
1938 """
1939 Set the locator of the major ticker.
1941 Parameters
1942 ----------
1943 locator : `~matplotlib.ticker.Locator`
1944 """
1945 _api.check_isinstance(mticker.Locator, locator=locator)
1946 self.isDefault_majloc = False
1947 self.major.locator = locator
1948 if self.major.formatter:
1949 self.major.formatter._set_locator(locator)
1950 locator.set_axis(self)
1951 self.stale = True
1953 def set_minor_locator(self, locator):
1954 """
1955 Set the locator of the minor ticker.
1957 Parameters
1958 ----------
1959 locator : `~matplotlib.ticker.Locator`
1960 """
1961 _api.check_isinstance(mticker.Locator, locator=locator)
1962 self.isDefault_minloc = False
1963 self.minor.locator = locator
1964 if self.minor.formatter:
1965 self.minor.formatter._set_locator(locator)
1966 locator.set_axis(self)
1967 self.stale = True
1969 def set_pickradius(self, pickradius):
1970 """
1971 Set the depth of the axis used by the picker.
1973 Parameters
1974 ----------
1975 pickradius : float
1976 The acceptance radius for containment tests.
1977 See also `.Axis.contains`.
1978 """
1979 if not isinstance(pickradius, Real) or pickradius < 0:
1980 raise ValueError("pick radius should be a distance")
1981 self._pickradius = pickradius
1983 pickradius = property(
1984 get_pickradius, set_pickradius, doc="The acceptance radius for "
1985 "containment tests. See also `.Axis.contains`.")
1987 # Helper for set_ticklabels. Defining it here makes it picklable.
1988 @staticmethod
1989 def _format_with_dict(tickd, x, pos):
1990 return tickd.get(x, "")
1992 def set_ticklabels(self, labels, *, minor=False, fontdict=None, **kwargs):
1993 r"""
1994 [*Discouraged*] Set this Axis' tick labels with list of string labels.
1996 .. admonition:: Discouraged
1998 The use of this method is discouraged, because of the dependency on
1999 tick positions. In most cases, you'll want to use
2000 ``Axes.set_[x/y/z]ticks(positions, labels)`` or ``Axis.set_ticks``
2001 instead.
2003 If you are using this method, you should always fix the tick
2004 positions before, e.g. by using `.Axis.set_ticks` or by explicitly
2005 setting a `~.ticker.FixedLocator`. Otherwise, ticks are free to
2006 move and the labels may end up in unexpected positions.
2008 Parameters
2009 ----------
2010 labels : sequence of str or of `.Text`\s
2011 Texts for labeling each tick location in the sequence set by
2012 `.Axis.set_ticks`; the number of labels must match the number of locations.
2013 The labels are used as is, via a `.FixedFormatter` (without further
2014 formatting).
2016 minor : bool
2017 If True, set minor ticks instead of major ticks.
2019 fontdict : dict, optional
2021 .. admonition:: Discouraged
2023 The use of *fontdict* is discouraged. Parameters should be passed as
2024 individual keyword arguments or using dictionary-unpacking
2025 ``set_ticklabels(..., **fontdict)``.
2027 A dictionary controlling the appearance of the ticklabels.
2028 The default *fontdict* is::
2030 {'fontsize': rcParams['axes.titlesize'],
2031 'fontweight': rcParams['axes.titleweight'],
2032 'verticalalignment': 'baseline',
2033 'horizontalalignment': loc}
2035 **kwargs
2036 Text properties.
2038 .. warning::
2040 This only sets the properties of the current ticks, which is
2041 only sufficient for static plots.
2043 Ticks are not guaranteed to be persistent. Various operations
2044 can create, delete and modify the Tick instances. There is an
2045 imminent risk that these settings can get lost if you work on
2046 the figure further (including also panning/zooming on a
2047 displayed figure).
2049 Use `.set_tick_params` instead if possible.
2051 Returns
2052 -------
2053 list of `.Text`\s
2054 For each tick, includes ``tick.label1`` if it is visible, then
2055 ``tick.label2`` if it is visible, in that order.
2056 """
2057 try:
2058 labels = [t.get_text() if hasattr(t, 'get_text') else t
2059 for t in labels]
2060 except TypeError:
2061 raise TypeError(f"{labels:=} must be a sequence") from None
2062 locator = (self.get_minor_locator() if minor
2063 else self.get_major_locator())
2064 if not labels:
2065 # eg labels=[]:
2066 formatter = mticker.NullFormatter()
2067 elif isinstance(locator, mticker.FixedLocator):
2068 # Passing [] as a list of labels is often used as a way to
2069 # remove all tick labels, so only error for > 0 labels
2070 if len(locator.locs) != len(labels) and len(labels) != 0:
2071 raise ValueError(
2072 "The number of FixedLocator locations"
2073 f" ({len(locator.locs)}), usually from a call to"
2074 " set_ticks, does not match"
2075 f" the number of labels ({len(labels)}).")
2076 tickd = {loc: lab for loc, lab in zip(locator.locs, labels)}
2077 func = functools.partial(self._format_with_dict, tickd)
2078 formatter = mticker.FuncFormatter(func)
2079 else:
2080 _api.warn_external(
2081 "set_ticklabels() should only be used with a fixed number of "
2082 "ticks, i.e. after set_ticks() or using a FixedLocator.")
2083 formatter = mticker.FixedFormatter(labels)
2085 with warnings.catch_warnings():
2086 warnings.filterwarnings(
2087 "ignore",
2088 message="FixedFormatter should only be used together with FixedLocator")
2089 if minor:
2090 self.set_minor_formatter(formatter)
2091 locs = self.get_minorticklocs()
2092 ticks = self.get_minor_ticks(len(locs))
2093 else:
2094 self.set_major_formatter(formatter)
2095 locs = self.get_majorticklocs()
2096 ticks = self.get_major_ticks(len(locs))
2098 ret = []
2099 if fontdict is not None:
2100 kwargs.update(fontdict)
2101 for pos, (loc, tick) in enumerate(zip(locs, ticks)):
2102 tick.update_position(loc)
2103 tick_label = formatter(loc, pos)
2104 # deal with label1
2105 tick.label1.set_text(tick_label)
2106 tick.label1._internal_update(kwargs)
2107 # deal with label2
2108 tick.label2.set_text(tick_label)
2109 tick.label2._internal_update(kwargs)
2110 # only return visible tick labels
2111 if tick.label1.get_visible():
2112 ret.append(tick.label1)
2113 if tick.label2.get_visible():
2114 ret.append(tick.label2)
2116 self.stale = True
2117 return ret
2119 def _set_tick_locations(self, ticks, *, minor=False):
2120 # see docstring of set_ticks
2122 # XXX if the user changes units, the information will be lost here
2123 ticks = self.convert_units(ticks)
2124 locator = mticker.FixedLocator(ticks) # validate ticks early.
2125 if len(ticks):
2126 for axis in self._get_shared_axis():
2127 # set_view_interval maintains any preexisting inversion.
2128 axis.set_view_interval(min(ticks), max(ticks))
2129 self.axes.stale = True
2130 if minor:
2131 self.set_minor_locator(locator)
2132 return self.get_minor_ticks(len(ticks))
2133 else:
2134 self.set_major_locator(locator)
2135 return self.get_major_ticks(len(ticks))
2137 def set_ticks(self, ticks, labels=None, *, minor=False, **kwargs):
2138 """
2139 Set this Axis' tick locations and optionally tick labels.
2141 If necessary, the view limits of the Axis are expanded so that all
2142 given ticks are visible.
2144 Parameters
2145 ----------
2146 ticks : 1D array-like
2147 Array of tick locations (either floats or in axis units). The axis
2148 `.Locator` is replaced by a `~.ticker.FixedLocator`.
2150 Pass an empty list (``set_ticks([])``) to remove all ticks.
2152 Some tick formatters will not label arbitrary tick positions;
2153 e.g. log formatters only label decade ticks by default. In
2154 such a case you can set a formatter explicitly on the axis
2155 using `.Axis.set_major_formatter` or provide formatted
2156 *labels* yourself.
2158 labels : list of str, optional
2159 Tick labels for each location in *ticks*; must have the same length as
2160 *ticks*. If set, the labels are used as is, via a `.FixedFormatter`.
2161 If not set, the labels are generated using the axis tick `.Formatter`.
2163 minor : bool, default: False
2164 If ``False``, set only the major ticks; if ``True``, only the minor ticks.
2166 **kwargs
2167 `.Text` properties for the labels. Using these is only allowed if
2168 you pass *labels*. In other cases, please use `~.Axes.tick_params`.
2170 Notes
2171 -----
2172 The mandatory expansion of the view limits is an intentional design
2173 choice to prevent the surprise of a non-visible tick. If you need
2174 other limits, you should set the limits explicitly after setting the
2175 ticks.
2176 """
2177 if labels is None and kwargs:
2178 first_key = next(iter(kwargs))
2179 raise ValueError(
2180 f"Incorrect use of keyword argument {first_key!r}. Keyword arguments "
2181 "other than 'minor' modify the text labels and can only be used if "
2182 "'labels' are passed as well.")
2183 result = self._set_tick_locations(ticks, minor=minor)
2184 if labels is not None:
2185 self.set_ticklabels(labels, minor=minor, **kwargs)
2186 return result
2188 def _get_tick_boxes_siblings(self, renderer):
2189 """
2190 Get the bounding boxes for this `.axis` and its siblings
2191 as set by `.Figure.align_xlabels` or `.Figure.align_ylabels`.
2193 By default, it just gets bboxes for *self*.
2194 """
2195 # Get the Grouper keeping track of x or y label groups for this figure.
2196 name = self._get_axis_name()
2197 if name not in self.figure._align_label_groups:
2198 return [], []
2199 grouper = self.figure._align_label_groups[name]
2200 bboxes = []
2201 bboxes2 = []
2202 # If we want to align labels from other Axes:
2203 for ax in grouper.get_siblings(self.axes):
2204 axis = ax._axis_map[name]
2205 ticks_to_draw = axis._update_ticks()
2206 tlb, tlb2 = axis._get_ticklabel_bboxes(ticks_to_draw, renderer)
2207 bboxes.extend(tlb)
2208 bboxes2.extend(tlb2)
2209 return bboxes, bboxes2
2211 def _update_label_position(self, renderer):
2212 """
2213 Update the label position based on the bounding box enclosing
2214 all the ticklabels and axis spine.
2215 """
2216 raise NotImplementedError('Derived must override')
2218 def _update_offset_text_position(self, bboxes, bboxes2):
2219 """
2220 Update the offset text position based on the sequence of bounding
2221 boxes of all the ticklabels.
2222 """
2223 raise NotImplementedError('Derived must override')
2225 def axis_date(self, tz=None):
2226 """
2227 Set up axis ticks and labels to treat data along this Axis as dates.
2229 Parameters
2230 ----------
2231 tz : str or `datetime.tzinfo`, default: :rc:`timezone`
2232 The timezone used to create date labels.
2233 """
2234 # By providing a sample datetime instance with the desired timezone,
2235 # the registered converter can be selected, and the "units" attribute,
2236 # which is the timezone, can be set.
2237 if isinstance(tz, str):
2238 import dateutil.tz
2239 tz = dateutil.tz.gettz(tz)
2240 self.update_units(datetime.datetime(2009, 1, 1, 0, 0, 0, 0, tz))
2242 def get_tick_space(self):
2243 """Return the estimated number of ticks that can fit on the axis."""
2244 # Must be overridden in the subclass
2245 raise NotImplementedError()
2247 def _get_ticks_position(self):
2248 """
2249 Helper for `XAxis.get_ticks_position` and `YAxis.get_ticks_position`.
2251 Check the visibility of tick1line, label1, tick2line, and label2 on
2252 the first major and the first minor ticks, and return
2254 - 1 if only tick1line and label1 are visible (which corresponds to
2255 "bottom" for the x-axis and "left" for the y-axis);
2256 - 2 if only tick2line and label2 are visible (which corresponds to
2257 "top" for the x-axis and "right" for the y-axis);
2258 - "default" if only tick1line, tick2line and label1 are visible;
2259 - "unknown" otherwise.
2260 """
2261 major = self.majorTicks[0]
2262 minor = self.minorTicks[0]
2263 if all(tick.tick1line.get_visible()
2264 and not tick.tick2line.get_visible()
2265 and tick.label1.get_visible()
2266 and not tick.label2.get_visible()
2267 for tick in [major, minor]):
2268 return 1
2269 elif all(tick.tick2line.get_visible()
2270 and not tick.tick1line.get_visible()
2271 and tick.label2.get_visible()
2272 and not tick.label1.get_visible()
2273 for tick in [major, minor]):
2274 return 2
2275 elif all(tick.tick1line.get_visible()
2276 and tick.tick2line.get_visible()
2277 and tick.label1.get_visible()
2278 and not tick.label2.get_visible()
2279 for tick in [major, minor]):
2280 return "default"
2281 else:
2282 return "unknown"
2284 def get_label_position(self):
2285 """
2286 Return the label position (top or bottom)
2287 """
2288 return self.label_position
2290 def set_label_position(self, position):
2291 """
2292 Set the label position (top or bottom)
2294 Parameters
2295 ----------
2296 position : {'top', 'bottom'}
2297 """
2298 raise NotImplementedError()
2300 def get_minpos(self):
2301 raise NotImplementedError()
2304def _make_getset_interval(method_name, lim_name, attr_name):
2305 """
2306 Helper to generate ``get_{data,view}_interval`` and
2307 ``set_{data,view}_interval`` implementations.
2308 """
2310 def getter(self):
2311 # docstring inherited.
2312 return getattr(getattr(self.axes, lim_name), attr_name)
2314 def setter(self, vmin, vmax, ignore=False):
2315 # docstring inherited.
2316 if ignore:
2317 setattr(getattr(self.axes, lim_name), attr_name, (vmin, vmax))
2318 else:
2319 oldmin, oldmax = getter(self)
2320 if oldmin < oldmax:
2321 setter(self, min(vmin, vmax, oldmin), max(vmin, vmax, oldmax),
2322 ignore=True)
2323 else:
2324 setter(self, max(vmin, vmax, oldmin), min(vmin, vmax, oldmax),
2325 ignore=True)
2326 self.stale = True
2328 getter.__name__ = f"get_{method_name}_interval"
2329 setter.__name__ = f"set_{method_name}_interval"
2331 return getter, setter
2334class XAxis(Axis):
2335 __name__ = 'xaxis'
2336 axis_name = 'x' #: Read-only name identifying the axis.
2337 _tick_class = XTick
2339 def __init__(self, *args, **kwargs):
2340 super().__init__(*args, **kwargs)
2341 self._init()
2343 def _init(self):
2344 """
2345 Initialize the label and offsetText instance values and
2346 `label_position` / `offset_text_position`.
2347 """
2348 # x in axes coords, y in display coords (to be updated at draw time by
2349 # _update_label_positions and _update_offset_text_position).
2350 self.label.set(
2351 x=0.5, y=0,
2352 verticalalignment='top', horizontalalignment='center',
2353 transform=mtransforms.blended_transform_factory(
2354 self.axes.transAxes, mtransforms.IdentityTransform()),
2355 )
2356 self.label_position = 'bottom'
2358 if mpl.rcParams['xtick.labelcolor'] == 'inherit':
2359 tick_color = mpl.rcParams['xtick.color']
2360 else:
2361 tick_color = mpl.rcParams['xtick.labelcolor']
2363 self.offsetText.set(
2364 x=1, y=0,
2365 verticalalignment='top', horizontalalignment='right',
2366 transform=mtransforms.blended_transform_factory(
2367 self.axes.transAxes, mtransforms.IdentityTransform()),
2368 fontsize=mpl.rcParams['xtick.labelsize'],
2369 color=tick_color
2370 )
2371 self.offset_text_position = 'bottom'
2373 def contains(self, mouseevent):
2374 """Test whether the mouse event occurred in the x-axis."""
2375 if self._different_canvas(mouseevent):
2376 return False, {}
2377 x, y = mouseevent.x, mouseevent.y
2378 try:
2379 trans = self.axes.transAxes.inverted()
2380 xaxes, yaxes = trans.transform((x, y))
2381 except ValueError:
2382 return False, {}
2383 (l, b), (r, t) = self.axes.transAxes.transform([(0, 0), (1, 1)])
2384 inaxis = 0 <= xaxes <= 1 and (
2385 b - self._pickradius < y < b or
2386 t < y < t + self._pickradius)
2387 return inaxis, {}
2389 def set_label_position(self, position):
2390 """
2391 Set the label position (top or bottom)
2393 Parameters
2394 ----------
2395 position : {'top', 'bottom'}
2396 """
2397 self.label.set_verticalalignment(_api.check_getitem({
2398 'top': 'baseline', 'bottom': 'top',
2399 }, position=position))
2400 self.label_position = position
2401 self.stale = True
2403 def _update_label_position(self, renderer):
2404 """
2405 Update the label position based on the bounding box enclosing
2406 all the ticklabels and axis spine
2407 """
2408 if not self._autolabelpos:
2409 return
2411 # get bounding boxes for this axis and any siblings
2412 # that have been set by `fig.align_xlabels()`
2413 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2415 x, y = self.label.get_position()
2416 if self.label_position == 'bottom':
2417 try:
2418 spine = self.axes.spines['bottom']
2419 spinebbox = spine.get_window_extent()
2420 except KeyError:
2421 # use Axes if spine doesn't exist
2422 spinebbox = self.axes.bbox
2423 bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
2424 bottom = bbox.y0
2426 self.label.set_position(
2427 (x, bottom - self.labelpad * self.figure.dpi / 72)
2428 )
2429 else:
2430 try:
2431 spine = self.axes.spines['top']
2432 spinebbox = spine.get_window_extent()
2433 except KeyError:
2434 # use Axes if spine doesn't exist
2435 spinebbox = self.axes.bbox
2436 bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox])
2437 top = bbox.y1
2439 self.label.set_position(
2440 (x, top + self.labelpad * self.figure.dpi / 72)
2441 )
2443 def _update_offset_text_position(self, bboxes, bboxes2):
2444 """
2445 Update the offset_text position based on the sequence of bounding
2446 boxes of all the ticklabels
2447 """
2448 x, y = self.offsetText.get_position()
2449 if not hasattr(self, '_tick_position'):
2450 self._tick_position = 'bottom'
2451 if self._tick_position == 'bottom':
2452 if not len(bboxes):
2453 bottom = self.axes.bbox.ymin
2454 else:
2455 bbox = mtransforms.Bbox.union(bboxes)
2456 bottom = bbox.y0
2457 y = bottom - self.OFFSETTEXTPAD * self.figure.dpi / 72
2458 else:
2459 if not len(bboxes2):
2460 top = self.axes.bbox.ymax
2461 else:
2462 bbox = mtransforms.Bbox.union(bboxes2)
2463 top = bbox.y1
2464 y = top + self.OFFSETTEXTPAD * self.figure.dpi / 72
2465 self.offsetText.set_position((x, y))
2467 def set_ticks_position(self, position):
2468 """
2469 Set the ticks position.
2471 Parameters
2472 ----------
2473 position : {'top', 'bottom', 'both', 'default', 'none'}
2474 'both' sets the ticks to appear on both positions, but does not
2475 change the tick labels. 'default' resets the tick positions to
2476 the default: ticks on both positions, labels at bottom. 'none'
2477 can be used if you don't want any ticks. 'none' and 'both'
2478 affect only the ticks, not the labels.
2479 """
2480 if position == 'top':
2481 self.set_tick_params(which='both', top=True, labeltop=True,
2482 bottom=False, labelbottom=False)
2483 self._tick_position = 'top'
2484 self.offsetText.set_verticalalignment('bottom')
2485 elif position == 'bottom':
2486 self.set_tick_params(which='both', top=False, labeltop=False,
2487 bottom=True, labelbottom=True)
2488 self._tick_position = 'bottom'
2489 self.offsetText.set_verticalalignment('top')
2490 elif position == 'both':
2491 self.set_tick_params(which='both', top=True,
2492 bottom=True)
2493 elif position == 'none':
2494 self.set_tick_params(which='both', top=False,
2495 bottom=False)
2496 elif position == 'default':
2497 self.set_tick_params(which='both', top=True, labeltop=False,
2498 bottom=True, labelbottom=True)
2499 self._tick_position = 'bottom'
2500 self.offsetText.set_verticalalignment('top')
2501 else:
2502 _api.check_in_list(['top', 'bottom', 'both', 'default', 'none'],
2503 position=position)
2504 self.stale = True
2506 def tick_top(self):
2507 """
2508 Move ticks and ticklabels (if present) to the top of the Axes.
2509 """
2510 label = True
2511 if 'label1On' in self._major_tick_kw:
2512 label = (self._major_tick_kw['label1On']
2513 or self._major_tick_kw['label2On'])
2514 self.set_ticks_position('top')
2515 # If labels were turned off before this was called, leave them off.
2516 self.set_tick_params(which='both', labeltop=label)
2518 def tick_bottom(self):
2519 """
2520 Move ticks and ticklabels (if present) to the bottom of the Axes.
2521 """
2522 label = True
2523 if 'label1On' in self._major_tick_kw:
2524 label = (self._major_tick_kw['label1On']
2525 or self._major_tick_kw['label2On'])
2526 self.set_ticks_position('bottom')
2527 # If labels were turned off before this was called, leave them off.
2528 self.set_tick_params(which='both', labelbottom=label)
2530 def get_ticks_position(self):
2531 """
2532 Return the ticks position ("top", "bottom", "default", or "unknown").
2533 """
2534 return {1: "bottom", 2: "top",
2535 "default": "default", "unknown": "unknown"}[
2536 self._get_ticks_position()]
2538 get_view_interval, set_view_interval = _make_getset_interval(
2539 "view", "viewLim", "intervalx")
2540 get_data_interval, set_data_interval = _make_getset_interval(
2541 "data", "dataLim", "intervalx")
2543 def get_minpos(self):
2544 return self.axes.dataLim.minposx
2546 def set_default_intervals(self):
2547 # docstring inherited
2548 # only change view if dataLim has not changed and user has
2549 # not changed the view:
2550 if (not self.axes.dataLim.mutatedx() and
2551 not self.axes.viewLim.mutatedx()):
2552 if self.converter is not None:
2553 info = self.converter.axisinfo(self.units, self)
2554 if info.default_limits is not None:
2555 xmin, xmax = self.convert_units(info.default_limits)
2556 self.axes.viewLim.intervalx = xmin, xmax
2557 self.stale = True
2559 def get_tick_space(self):
2560 ends = mtransforms.Bbox.unit().transformed(
2561 self.axes.transAxes - self.figure.dpi_scale_trans)
2562 length = ends.width * 72
2563 # There is a heuristic here that the aspect ratio of tick text
2564 # is no more than 3:1
2565 size = self._get_tick_label_size('x') * 3
2566 if size > 0:
2567 return int(np.floor(length / size))
2568 else:
2569 return 2**31 - 1
2572class YAxis(Axis):
2573 __name__ = 'yaxis'
2574 axis_name = 'y' #: Read-only name identifying the axis.
2575 _tick_class = YTick
2577 def __init__(self, *args, **kwargs):
2578 super().__init__(*args, **kwargs)
2579 self._init()
2581 def _init(self):
2582 """
2583 Initialize the label and offsetText instance values and
2584 `label_position` / `offset_text_position`.
2585 """
2586 # x in display coords, y in axes coords (to be updated at draw time by
2587 # _update_label_positions and _update_offset_text_position).
2588 self.label.set(
2589 x=0, y=0.5,
2590 verticalalignment='bottom', horizontalalignment='center',
2591 rotation='vertical', rotation_mode='anchor',
2592 transform=mtransforms.blended_transform_factory(
2593 mtransforms.IdentityTransform(), self.axes.transAxes),
2594 )
2595 self.label_position = 'left'
2597 if mpl.rcParams['ytick.labelcolor'] == 'inherit':
2598 tick_color = mpl.rcParams['ytick.color']
2599 else:
2600 tick_color = mpl.rcParams['ytick.labelcolor']
2602 # x in axes coords, y in display coords(!).
2603 self.offsetText.set(
2604 x=0, y=0.5,
2605 verticalalignment='baseline', horizontalalignment='left',
2606 transform=mtransforms.blended_transform_factory(
2607 self.axes.transAxes, mtransforms.IdentityTransform()),
2608 fontsize=mpl.rcParams['ytick.labelsize'],
2609 color=tick_color
2610 )
2611 self.offset_text_position = 'left'
2613 def contains(self, mouseevent):
2614 # docstring inherited
2615 if self._different_canvas(mouseevent):
2616 return False, {}
2617 x, y = mouseevent.x, mouseevent.y
2618 try:
2619 trans = self.axes.transAxes.inverted()
2620 xaxes, yaxes = trans.transform((x, y))
2621 except ValueError:
2622 return False, {}
2623 (l, b), (r, t) = self.axes.transAxes.transform([(0, 0), (1, 1)])
2624 inaxis = 0 <= yaxes <= 1 and (
2625 l - self._pickradius < x < l or
2626 r < x < r + self._pickradius)
2627 return inaxis, {}
2629 def set_label_position(self, position):
2630 """
2631 Set the label position (left or right)
2633 Parameters
2634 ----------
2635 position : {'left', 'right'}
2636 """
2637 self.label.set_rotation_mode('anchor')
2638 self.label.set_verticalalignment(_api.check_getitem({
2639 'left': 'bottom', 'right': 'top',
2640 }, position=position))
2641 self.label_position = position
2642 self.stale = True
2644 def _update_label_position(self, renderer):
2645 """
2646 Update the label position based on the bounding box enclosing
2647 all the ticklabels and axis spine
2648 """
2649 if not self._autolabelpos:
2650 return
2652 # get bounding boxes for this axis and any siblings
2653 # that have been set by `fig.align_ylabels()`
2654 bboxes, bboxes2 = self._get_tick_boxes_siblings(renderer=renderer)
2655 x, y = self.label.get_position()
2656 if self.label_position == 'left':
2657 try:
2658 spine = self.axes.spines['left']
2659 spinebbox = spine.get_window_extent()
2660 except KeyError:
2661 # use Axes if spine doesn't exist
2662 spinebbox = self.axes.bbox
2663 bbox = mtransforms.Bbox.union(bboxes + [spinebbox])
2664 left = bbox.x0
2665 self.label.set_position(
2666 (left - self.labelpad * self.figure.dpi / 72, y)
2667 )
2669 else:
2670 try:
2671 spine = self.axes.spines['right']
2672 spinebbox = spine.get_window_extent()
2673 except KeyError:
2674 # use Axes if spine doesn't exist
2675 spinebbox = self.axes.bbox
2677 bbox = mtransforms.Bbox.union(bboxes2 + [spinebbox])
2678 right = bbox.x1
2679 self.label.set_position(
2680 (right + self.labelpad * self.figure.dpi / 72, y)
2681 )
2683 def _update_offset_text_position(self, bboxes, bboxes2):
2684 """
2685 Update the offset_text position based on the sequence of bounding
2686 boxes of all the ticklabels
2687 """
2688 x, _ = self.offsetText.get_position()
2689 if 'outline' in self.axes.spines:
2690 # Special case for colorbars:
2691 bbox = self.axes.spines['outline'].get_window_extent()
2692 else:
2693 bbox = self.axes.bbox
2694 top = bbox.ymax
2695 self.offsetText.set_position(
2696 (x, top + self.OFFSETTEXTPAD * self.figure.dpi / 72)
2697 )
2699 def set_offset_position(self, position):
2700 """
2701 Parameters
2702 ----------
2703 position : {'left', 'right'}
2704 """
2705 x, y = self.offsetText.get_position()
2706 x = _api.check_getitem({'left': 0, 'right': 1}, position=position)
2708 self.offsetText.set_ha(position)
2709 self.offsetText.set_position((x, y))
2710 self.stale = True
2712 def set_ticks_position(self, position):
2713 """
2714 Set the ticks position.
2716 Parameters
2717 ----------
2718 position : {'left', 'right', 'both', 'default', 'none'}
2719 'both' sets the ticks to appear on both positions, but does not
2720 change the tick labels. 'default' resets the tick positions to
2721 the default: ticks on both positions, labels at left. 'none'
2722 can be used if you don't want any ticks. 'none' and 'both'
2723 affect only the ticks, not the labels.
2724 """
2725 if position == 'right':
2726 self.set_tick_params(which='both', right=True, labelright=True,
2727 left=False, labelleft=False)
2728 self.set_offset_position(position)
2729 elif position == 'left':
2730 self.set_tick_params(which='both', right=False, labelright=False,
2731 left=True, labelleft=True)
2732 self.set_offset_position(position)
2733 elif position == 'both':
2734 self.set_tick_params(which='both', right=True,
2735 left=True)
2736 elif position == 'none':
2737 self.set_tick_params(which='both', right=False,
2738 left=False)
2739 elif position == 'default':
2740 self.set_tick_params(which='both', right=True, labelright=False,
2741 left=True, labelleft=True)
2742 else:
2743 _api.check_in_list(['left', 'right', 'both', 'default', 'none'],
2744 position=position)
2745 self.stale = True
2747 def tick_right(self):
2748 """
2749 Move ticks and ticklabels (if present) to the right of the Axes.
2750 """
2751 label = True
2752 if 'label1On' in self._major_tick_kw:
2753 label = (self._major_tick_kw['label1On']
2754 or self._major_tick_kw['label2On'])
2755 self.set_ticks_position('right')
2756 # if labels were turned off before this was called
2757 # leave them off
2758 self.set_tick_params(which='both', labelright=label)
2760 def tick_left(self):
2761 """
2762 Move ticks and ticklabels (if present) to the left of the Axes.
2763 """
2764 label = True
2765 if 'label1On' in self._major_tick_kw:
2766 label = (self._major_tick_kw['label1On']
2767 or self._major_tick_kw['label2On'])
2768 self.set_ticks_position('left')
2769 # if labels were turned off before this was called
2770 # leave them off
2771 self.set_tick_params(which='both', labelleft=label)
2773 def get_ticks_position(self):
2774 """
2775 Return the ticks position ("left", "right", "default", or "unknown").
2776 """
2777 return {1: "left", 2: "right",
2778 "default": "default", "unknown": "unknown"}[
2779 self._get_ticks_position()]
2781 get_view_interval, set_view_interval = _make_getset_interval(
2782 "view", "viewLim", "intervaly")
2783 get_data_interval, set_data_interval = _make_getset_interval(
2784 "data", "dataLim", "intervaly")
2786 def get_minpos(self):
2787 return self.axes.dataLim.minposy
2789 def set_default_intervals(self):
2790 # docstring inherited
2791 # only change view if dataLim has not changed and user has
2792 # not changed the view:
2793 if (not self.axes.dataLim.mutatedy() and
2794 not self.axes.viewLim.mutatedy()):
2795 if self.converter is not None:
2796 info = self.converter.axisinfo(self.units, self)
2797 if info.default_limits is not None:
2798 ymin, ymax = self.convert_units(info.default_limits)
2799 self.axes.viewLim.intervaly = ymin, ymax
2800 self.stale = True
2802 def get_tick_space(self):
2803 ends = mtransforms.Bbox.unit().transformed(
2804 self.axes.transAxes - self.figure.dpi_scale_trans)
2805 length = ends.height * 72
2806 # Having a spacing of at least 2 just looks good.
2807 size = self._get_tick_label_size('y') * 2
2808 if size > 0:
2809 return int(np.floor(length / size))
2810 else:
2811 return 2**31 - 1