Coverage for /usr/lib/python3/dist-packages/matplotlib/axes/_base.py: 55%
1691 statements
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
« prev ^ index » next coverage.py v7.9.1, created at 2025-06-14 15:55 +0200
1from collections.abc import MutableSequence
2from contextlib import ExitStack
3import functools
4import inspect
5import itertools
6import logging
7from numbers import Real
8from operator import attrgetter
9import types
11import numpy as np
13import matplotlib as mpl
14from matplotlib import _api, cbook, _docstring, offsetbox
15import matplotlib.artist as martist
16import matplotlib.axis as maxis
17from matplotlib.cbook import _OrderedSet, _check_1d, index_of
18import matplotlib.collections as mcoll
19import matplotlib.colors as mcolors
20import matplotlib.font_manager as font_manager
21import matplotlib.image as mimage
22import matplotlib.lines as mlines
23import matplotlib.patches as mpatches
24from matplotlib.rcsetup import cycler, validate_axisbelow
25import matplotlib.spines as mspines
26import matplotlib.table as mtable
27import matplotlib.text as mtext
28import matplotlib.ticker as mticker
29import matplotlib.transforms as mtransforms
31_log = logging.getLogger(__name__)
34class _axis_method_wrapper:
35 """
36 Helper to generate Axes methods wrapping Axis methods.
38 After ::
40 get_foo = _axis_method_wrapper("xaxis", "get_bar")
42 (in the body of a class) ``get_foo`` is a method that forwards it arguments
43 to the ``get_bar`` method of the ``xaxis`` attribute, and gets its
44 signature and docstring from ``Axis.get_bar``.
46 The docstring of ``get_foo`` is built by replacing "this Axis" by "the
47 {attr_name}" (i.e., "the xaxis", "the yaxis") in the wrapped method's
48 dedented docstring; additional replacements can by given in *doc_sub*.
49 """
51 def __init__(self, attr_name, method_name, *, doc_sub=None):
52 self.attr_name = attr_name
53 self.method_name = method_name
54 # Immediately put the docstring in ``self.__doc__`` so that docstring
55 # manipulations within the class body work as expected.
56 doc = inspect.getdoc(getattr(maxis.Axis, method_name))
57 self._missing_subs = []
58 if doc:
59 doc_sub = {"this Axis": f"the {self.attr_name}", **(doc_sub or {})}
60 for k, v in doc_sub.items():
61 if k not in doc: # Delay raising error until we know qualname.
62 self._missing_subs.append(k)
63 doc = doc.replace(k, v)
64 self.__doc__ = doc
66 def __set_name__(self, owner, name):
67 # This is called at the end of the class body as
68 # ``self.__set_name__(cls, name_under_which_self_is_assigned)``; we
69 # rely on that to give the wrapper the correct __name__/__qualname__.
70 get_method = attrgetter(f"{self.attr_name}.{self.method_name}")
72 def wrapper(self, *args, **kwargs):
73 return get_method(self)(*args, **kwargs)
75 wrapper.__module__ = owner.__module__
76 wrapper.__name__ = name
77 wrapper.__qualname__ = f"{owner.__qualname__}.{name}"
78 wrapper.__doc__ = self.__doc__
79 # Manually copy the signature instead of using functools.wraps because
80 # displaying the Axis method source when asking for the Axes method
81 # source would be confusing.
82 wrapper.__signature__ = inspect.signature(
83 getattr(maxis.Axis, self.method_name))
85 if self._missing_subs:
86 raise ValueError(
87 "The definition of {} expected that the docstring of Axis.{} "
88 "contains {!r} as substrings".format(
89 wrapper.__qualname__, self.method_name,
90 ", ".join(map(repr, self._missing_subs))))
92 setattr(owner, name, wrapper)
95class _TransformedBoundsLocator:
96 """
97 Axes locator for `.Axes.inset_axes` and similarly positioned Axes.
99 The locator is a callable object used in `.Axes.set_aspect` to compute the
100 Axes location depending on the renderer.
101 """
103 def __init__(self, bounds, transform):
104 """
105 *bounds* (a ``[l, b, w, h]`` rectangle) and *transform* together
106 specify the position of the inset Axes.
107 """
108 self._bounds = bounds
109 self._transform = transform
111 def __call__(self, ax, renderer):
112 # Subtracting transSubfigure will typically rely on inverted(),
113 # freezing the transform; thus, this needs to be delayed until draw
114 # time as transSubfigure may otherwise change after this is evaluated.
115 return mtransforms.TransformedBbox(
116 mtransforms.Bbox.from_bounds(*self._bounds),
117 self._transform - ax.figure.transSubfigure)
120def _process_plot_format(fmt, *, ambiguous_fmt_datakey=False):
121 """
122 Convert a MATLAB style color/line style format string to a (*linestyle*,
123 *marker*, *color*) tuple.
125 Example format strings include:
127 * 'ko': black circles
128 * '.b': blue dots
129 * 'r--': red dashed lines
130 * 'C2--': the third color in the color cycle, dashed lines
132 The format is absolute in the sense that if a linestyle or marker is not
133 defined in *fmt*, there is no line or marker. This is expressed by
134 returning 'None' for the respective quantity.
136 See Also
137 --------
138 matplotlib.Line2D.lineStyles, matplotlib.colors.cnames
139 All possible styles and color format strings.
140 """
142 linestyle = None
143 marker = None
144 color = None
146 # Is fmt just a colorspec?
147 try:
148 color = mcolors.to_rgba(fmt)
150 # We need to differentiate grayscale '1.0' from tri_down marker '1'
151 try:
152 fmtint = str(int(fmt))
153 except ValueError:
154 return linestyle, marker, color # Yes
155 else:
156 if fmt != fmtint:
157 # user definitely doesn't want tri_down marker
158 return linestyle, marker, color # Yes
159 else:
160 # ignore converted color
161 color = None
162 except ValueError:
163 pass # No, not just a color.
165 errfmt = ("{!r} is neither a data key nor a valid format string ({})"
166 if ambiguous_fmt_datakey else
167 "{!r} is not a valid format string ({})")
169 i = 0
170 while i < len(fmt):
171 c = fmt[i]
172 if fmt[i:i+2] in mlines.lineStyles: # First, the two-char styles.
173 if linestyle is not None:
174 raise ValueError(errfmt.format(fmt, "two linestyle symbols"))
175 linestyle = fmt[i:i+2]
176 i += 2
177 elif c in mlines.lineStyles:
178 if linestyle is not None:
179 raise ValueError(errfmt.format(fmt, "two linestyle symbols"))
180 linestyle = c
181 i += 1
182 elif c in mlines.lineMarkers:
183 if marker is not None:
184 raise ValueError(errfmt.format(fmt, "two marker symbols"))
185 marker = c
186 i += 1
187 elif c in mcolors.get_named_colors_mapping():
188 if color is not None:
189 raise ValueError(errfmt.format(fmt, "two color symbols"))
190 color = c
191 i += 1
192 elif c == 'C' and i < len(fmt) - 1:
193 color_cycle_number = int(fmt[i + 1])
194 color = mcolors.to_rgba("C{}".format(color_cycle_number))
195 i += 2
196 else:
197 raise ValueError(
198 errfmt.format(fmt, f"unrecognized character {c!r}"))
200 if linestyle is None and marker is None:
201 linestyle = mpl.rcParams['lines.linestyle']
202 if linestyle is None:
203 linestyle = 'None'
204 if marker is None:
205 marker = 'None'
207 return linestyle, marker, color
210class _process_plot_var_args:
211 """
212 Process variable length arguments to `~.Axes.plot`, to support ::
214 plot(t, s)
215 plot(t1, s1, t2, s2)
216 plot(t1, s1, 'ko', t2, s2)
217 plot(t1, s1, 'ko', t2, s2, 'r--', t3, e3)
219 an arbitrary number of *x*, *y*, *fmt* are allowed
220 """
221 def __init__(self, axes, command='plot'):
222 self.axes = axes
223 self.command = command
224 self.set_prop_cycle(None)
226 def __getstate__(self):
227 # note: it is not possible to pickle a generator (and thus a cycler).
228 return {'axes': self.axes, 'command': self.command}
230 def __setstate__(self, state):
231 self.__dict__ = state.copy()
232 self.set_prop_cycle(None)
234 def set_prop_cycle(self, cycler):
235 if cycler is None:
236 cycler = mpl.rcParams['axes.prop_cycle']
237 self.prop_cycler = itertools.cycle(cycler)
238 self._prop_keys = cycler.keys # This should make a copy
240 def __call__(self, *args, data=None, **kwargs):
241 self.axes._process_unit_info(kwargs=kwargs)
243 for pos_only in "xy":
244 if pos_only in kwargs:
245 raise TypeError("{} got an unexpected keyword argument {!r}"
246 .format(self.command, pos_only))
248 if not args:
249 return
251 if data is None: # Process dict views
252 args = [cbook.sanitize_sequence(a) for a in args]
253 else: # Process the 'data' kwarg.
254 replaced = [mpl._replacer(data, arg) for arg in args]
255 if len(args) == 1:
256 label_namer_idx = 0
257 elif len(args) == 2: # Can be x, y or y, c.
258 # Figure out what the second argument is.
259 # 1) If the second argument cannot be a format shorthand, the
260 # second argument is the label_namer.
261 # 2) Otherwise (it could have been a format shorthand),
262 # a) if we did perform a substitution, emit a warning, and
263 # use it as label_namer.
264 # b) otherwise, it is indeed a format shorthand; use the
265 # first argument as label_namer.
266 try:
267 _process_plot_format(args[1])
268 except ValueError: # case 1)
269 label_namer_idx = 1
270 else:
271 if replaced[1] is not args[1]: # case 2a)
272 _api.warn_external(
273 f"Second argument {args[1]!r} is ambiguous: could "
274 f"be a format string but is in 'data'; using as "
275 f"data. If it was intended as data, set the "
276 f"format string to an empty string to suppress "
277 f"this warning. If it was intended as a format "
278 f"string, explicitly pass the x-values as well. "
279 f"Alternatively, rename the entry in 'data'.",
280 RuntimeWarning)
281 label_namer_idx = 1
282 else: # case 2b)
283 label_namer_idx = 0
284 elif len(args) == 3:
285 label_namer_idx = 1
286 else:
287 raise ValueError(
288 "Using arbitrary long args with data is not supported due "
289 "to ambiguity of arguments; use multiple plotting calls "
290 "instead")
291 if kwargs.get("label") is None:
292 kwargs["label"] = mpl._label_from_arg(
293 replaced[label_namer_idx], args[label_namer_idx])
294 args = replaced
295 ambiguous_fmt_datakey = data is not None and len(args) == 2
297 if len(args) >= 4 and not cbook.is_scalar_or_string(
298 kwargs.get("label")):
299 raise ValueError("plot() with multiple groups of data (i.e., "
300 "pairs of x and y) does not support multiple "
301 "labels")
303 # Repeatedly grab (x, y) or (x, y, format) from the front of args and
304 # massage them into arguments to plot() or fill().
306 while args:
307 this, args = args[:2], args[2:]
308 if args and isinstance(args[0], str):
309 this += args[0],
310 args = args[1:]
311 yield from self._plot_args(
312 this, kwargs, ambiguous_fmt_datakey=ambiguous_fmt_datakey)
314 def get_next_color(self):
315 """Return the next color in the cycle."""
316 if 'color' not in self._prop_keys:
317 return 'k'
318 return next(self.prop_cycler)['color']
320 def _getdefaults(self, ignore, kw):
321 """
322 If some keys in the property cycle (excluding those in the set
323 *ignore*) are absent or set to None in the dict *kw*, return a copy
324 of the next entry in the property cycle, excluding keys in *ignore*.
325 Otherwise, don't advance the property cycle, and return an empty dict.
326 """
327 prop_keys = self._prop_keys - ignore
328 if any(kw.get(k, None) is None for k in prop_keys):
329 # Need to copy this dictionary or else the next time around
330 # in the cycle, the dictionary could be missing entries.
331 default_dict = next(self.prop_cycler).copy()
332 for p in ignore:
333 default_dict.pop(p, None)
334 else:
335 default_dict = {}
336 return default_dict
338 def _setdefaults(self, defaults, kw):
339 """
340 Add to the dict *kw* the entries in the dict *default* that are absent
341 or set to None in *kw*.
342 """
343 for k in defaults:
344 if kw.get(k, None) is None:
345 kw[k] = defaults[k]
347 def _makeline(self, x, y, kw, kwargs):
348 kw = {**kw, **kwargs} # Don't modify the original kw.
349 default_dict = self._getdefaults(set(), kw)
350 self._setdefaults(default_dict, kw)
351 seg = mlines.Line2D(x, y, **kw)
352 return seg, kw
354 def _makefill(self, x, y, kw, kwargs):
355 # Polygon doesn't directly support unitized inputs.
356 x = self.axes.convert_xunits(x)
357 y = self.axes.convert_yunits(y)
359 kw = kw.copy() # Don't modify the original kw.
360 kwargs = kwargs.copy()
362 # Ignore 'marker'-related properties as they aren't Polygon
363 # properties, but they are Line2D properties, and so they are
364 # likely to appear in the default cycler construction.
365 # This is done here to the defaults dictionary as opposed to the
366 # other two dictionaries because we do want to capture when a
367 # *user* explicitly specifies a marker which should be an error.
368 # We also want to prevent advancing the cycler if there are no
369 # defaults needed after ignoring the given properties.
370 ignores = {'marker', 'markersize', 'markeredgecolor',
371 'markerfacecolor', 'markeredgewidth'}
372 # Also ignore anything provided by *kwargs*.
373 for k, v in kwargs.items():
374 if v is not None:
375 ignores.add(k)
377 # Only using the first dictionary to use as basis
378 # for getting defaults for back-compat reasons.
379 # Doing it with both seems to mess things up in
380 # various places (probably due to logic bugs elsewhere).
381 default_dict = self._getdefaults(ignores, kw)
382 self._setdefaults(default_dict, kw)
384 # Looks like we don't want "color" to be interpreted to
385 # mean both facecolor and edgecolor for some reason.
386 # So the "kw" dictionary is thrown out, and only its
387 # 'color' value is kept and translated as a 'facecolor'.
388 # This design should probably be revisited as it increases
389 # complexity.
390 facecolor = kw.get('color', None)
392 # Throw out 'color' as it is now handled as a facecolor
393 default_dict.pop('color', None)
395 # To get other properties set from the cycler
396 # modify the kwargs dictionary.
397 self._setdefaults(default_dict, kwargs)
399 seg = mpatches.Polygon(np.column_stack((x, y)),
400 facecolor=facecolor,
401 fill=kwargs.get('fill', True),
402 closed=kw['closed'])
403 seg.set(**kwargs)
404 return seg, kwargs
406 def _plot_args(self, tup, kwargs, *,
407 return_kwargs=False, ambiguous_fmt_datakey=False):
408 """
409 Process the arguments of ``plot([x], y, [fmt], **kwargs)`` calls.
411 This processes a single set of ([x], y, [fmt]) parameters; i.e. for
412 ``plot(x, y, x2, y2)`` it will be called twice. Once for (x, y) and
413 once for (x2, y2).
415 x and y may be 2D and thus can still represent multiple datasets.
417 For multiple datasets, if the keyword argument *label* is a list, this
418 will unpack the list and assign the individual labels to the datasets.
420 Parameters
421 ----------
422 tup : tuple
423 A tuple of the positional parameters. This can be one of
425 - (y,)
426 - (x, y)
427 - (y, fmt)
428 - (x, y, fmt)
430 kwargs : dict
431 The keyword arguments passed to ``plot()``.
433 return_kwargs : bool
434 Whether to also return the effective keyword arguments after label
435 unpacking as well.
437 ambiguous_fmt_datakey : bool
438 Whether the format string in *tup* could also have been a
439 misspelled data key.
441 Returns
442 -------
443 result
444 If *return_kwargs* is false, a list of Artists representing the
445 dataset(s).
446 If *return_kwargs* is true, a list of (Artist, effective_kwargs)
447 representing the dataset(s). See *return_kwargs*.
448 The Artist is either `.Line2D` (if called from ``plot()``) or
449 `.Polygon` otherwise.
450 """
451 if len(tup) > 1 and isinstance(tup[-1], str):
452 # xy is tup with fmt stripped (could still be (y,) only)
453 *xy, fmt = tup
454 linestyle, marker, color = _process_plot_format(
455 fmt, ambiguous_fmt_datakey=ambiguous_fmt_datakey)
456 elif len(tup) == 3:
457 raise ValueError('third arg must be a format string')
458 else:
459 xy = tup
460 linestyle, marker, color = None, None, None
462 # Don't allow any None value; these would be up-converted to one
463 # element array of None which causes problems downstream.
464 if any(v is None for v in tup):
465 raise ValueError("x, y, and format string must not be None")
467 kw = {}
468 for prop_name, val in zip(('linestyle', 'marker', 'color'),
469 (linestyle, marker, color)):
470 if val is not None:
471 # check for conflicts between fmt and kwargs
472 if (fmt.lower() != 'none'
473 and prop_name in kwargs
474 and val != 'None'):
475 # Technically ``plot(x, y, 'o', ls='--')`` is a conflict
476 # because 'o' implicitly unsets the linestyle
477 # (linestyle='None').
478 # We'll gracefully not warn in this case because an
479 # explicit set via kwargs can be seen as intention to
480 # override an implicit unset.
481 # Note: We don't val.lower() != 'none' because val is not
482 # necessarily a string (can be a tuple for colors). This
483 # is safe, because *val* comes from _process_plot_format()
484 # which only returns 'None'.
485 _api.warn_external(
486 f"{prop_name} is redundantly defined by the "
487 f"'{prop_name}' keyword argument and the fmt string "
488 f'"{fmt}" (-> {prop_name}={val!r}). The keyword '
489 f"argument will take precedence.")
490 kw[prop_name] = val
492 if len(xy) == 2:
493 x = _check_1d(xy[0])
494 y = _check_1d(xy[1])
495 else:
496 x, y = index_of(xy[-1])
498 if self.axes.xaxis is not None:
499 self.axes.xaxis.update_units(x)
500 if self.axes.yaxis is not None:
501 self.axes.yaxis.update_units(y)
503 if x.shape[0] != y.shape[0]:
504 raise ValueError(f"x and y must have same first dimension, but "
505 f"have shapes {x.shape} and {y.shape}")
506 if x.ndim > 2 or y.ndim > 2:
507 raise ValueError(f"x and y can be no greater than 2D, but have "
508 f"shapes {x.shape} and {y.shape}")
509 if x.ndim == 1:
510 x = x[:, np.newaxis]
511 if y.ndim == 1:
512 y = y[:, np.newaxis]
514 if self.command == 'plot':
515 make_artist = self._makeline
516 else:
517 kw['closed'] = kwargs.get('closed', True)
518 make_artist = self._makefill
520 ncx, ncy = x.shape[1], y.shape[1]
521 if ncx > 1 and ncy > 1 and ncx != ncy:
522 raise ValueError(f"x has {ncx} columns but y has {ncy} columns")
523 if ncx == 0 or ncy == 0:
524 return []
526 label = kwargs.get('label')
527 n_datasets = max(ncx, ncy)
528 if n_datasets > 1 and not cbook.is_scalar_or_string(label):
529 if len(label) != n_datasets:
530 raise ValueError(f"label must be scalar or have the same "
531 f"length as the input data, but found "
532 f"{len(label)} for {n_datasets} datasets.")
533 labels = label
534 else:
535 labels = [label] * n_datasets
537 result = (make_artist(x[:, j % ncx], y[:, j % ncy], kw,
538 {**kwargs, 'label': label})
539 for j, label in enumerate(labels))
541 if return_kwargs:
542 return list(result)
543 else:
544 return [l[0] for l in result]
547@_api.define_aliases({"facecolor": ["fc"]})
548class _AxesBase(martist.Artist):
549 name = "rectilinear"
551 # axis names are the prefixes for the attributes that contain the
552 # respective axis; e.g. 'x' <-> self.xaxis, containing an XAxis.
553 # Note that PolarAxes uses these attributes as well, so that we have
554 # 'x' <-> self.xaxis, containing a ThetaAxis. In particular we do not
555 # have 'theta' in _axis_names.
556 # In practice, this is ('x', 'y') for all 2D Axes and ('x', 'y', 'z')
557 # for Axes3D.
558 _axis_names = ("x", "y")
559 _shared_axes = {name: cbook.Grouper() for name in _axis_names}
560 _twinned_axes = cbook.Grouper()
562 _subclass_uses_cla = False
564 @property
565 def _axis_map(self):
566 """A mapping of axis names, e.g. 'x', to `Axis` instances."""
567 return {name: getattr(self, f"{name}axis")
568 for name in self._axis_names}
570 def __str__(self):
571 return "{0}({1[0]:g},{1[1]:g};{1[2]:g}x{1[3]:g})".format(
572 type(self).__name__, self._position.bounds)
574 def __init__(self, fig, rect,
575 *,
576 facecolor=None, # defaults to rc axes.facecolor
577 frameon=True,
578 sharex=None, # use Axes instance's xaxis info
579 sharey=None, # use Axes instance's yaxis info
580 label='',
581 xscale=None,
582 yscale=None,
583 box_aspect=None,
584 **kwargs
585 ):
586 """
587 Build an Axes in a figure.
589 Parameters
590 ----------
591 fig : `~matplotlib.figure.Figure`
592 The Axes is built in the `.Figure` *fig*.
594 rect : tuple (left, bottom, width, height).
595 The Axes is built in the rectangle *rect*. *rect* is in
596 `.Figure` coordinates.
598 sharex, sharey : `~.axes.Axes`, optional
599 The x or y `~.matplotlib.axis` is shared with the x or
600 y axis in the input `~.axes.Axes`.
602 frameon : bool, default: True
603 Whether the Axes frame is visible.
605 box_aspect : float, optional
606 Set a fixed aspect for the Axes box, i.e. the ratio of height to
607 width. See `~.axes.Axes.set_box_aspect` for details.
609 **kwargs
610 Other optional keyword arguments:
612 %(Axes:kwdoc)s
614 Returns
615 -------
616 `~.axes.Axes`
617 The new `~.axes.Axes` object.
618 """
620 super().__init__()
621 if isinstance(rect, mtransforms.Bbox):
622 self._position = rect
623 else:
624 self._position = mtransforms.Bbox.from_bounds(*rect)
625 if self._position.width < 0 or self._position.height < 0:
626 raise ValueError('Width and height specified must be non-negative')
627 self._originalPosition = self._position.frozen()
628 self.axes = self
629 self._aspect = 'auto'
630 self._adjustable = 'box'
631 self._anchor = 'C'
632 self._stale_viewlims = {name: False for name in self._axis_names}
633 self._sharex = sharex
634 self._sharey = sharey
635 self.set_label(label)
636 self.set_figure(fig)
637 self.set_box_aspect(box_aspect)
638 self._axes_locator = None # Optionally set via update(kwargs).
639 # placeholder for any colorbars added that use this Axes.
640 # (see colorbar.py):
641 self._colorbars = []
642 self.spines = mspines.Spines.from_dict(self._gen_axes_spines())
644 # this call may differ for non-sep axes, e.g., polar
645 self._init_axis()
646 if facecolor is None:
647 facecolor = mpl.rcParams['axes.facecolor']
648 self._facecolor = facecolor
649 self._frameon = frameon
650 self.set_axisbelow(mpl.rcParams['axes.axisbelow'])
652 self._rasterization_zorder = None
653 self.clear()
655 # funcs used to format x and y - fall back on major formatters
656 self.fmt_xdata = None
657 self.fmt_ydata = None
659 self.set_navigate(True)
660 self.set_navigate_mode(None)
662 if xscale:
663 self.set_xscale(xscale)
664 if yscale:
665 self.set_yscale(yscale)
667 self._internal_update(kwargs)
669 for name, axis in self._axis_map.items():
670 axis.callbacks._connect_picklable(
671 'units', self._unit_change_handler(name))
673 rcParams = mpl.rcParams
674 self.tick_params(
675 top=rcParams['xtick.top'] and rcParams['xtick.minor.top'],
676 bottom=rcParams['xtick.bottom'] and rcParams['xtick.minor.bottom'],
677 labeltop=(rcParams['xtick.labeltop'] and
678 rcParams['xtick.minor.top']),
679 labelbottom=(rcParams['xtick.labelbottom'] and
680 rcParams['xtick.minor.bottom']),
681 left=rcParams['ytick.left'] and rcParams['ytick.minor.left'],
682 right=rcParams['ytick.right'] and rcParams['ytick.minor.right'],
683 labelleft=(rcParams['ytick.labelleft'] and
684 rcParams['ytick.minor.left']),
685 labelright=(rcParams['ytick.labelright'] and
686 rcParams['ytick.minor.right']),
687 which='minor')
689 self.tick_params(
690 top=rcParams['xtick.top'] and rcParams['xtick.major.top'],
691 bottom=rcParams['xtick.bottom'] and rcParams['xtick.major.bottom'],
692 labeltop=(rcParams['xtick.labeltop'] and
693 rcParams['xtick.major.top']),
694 labelbottom=(rcParams['xtick.labelbottom'] and
695 rcParams['xtick.major.bottom']),
696 left=rcParams['ytick.left'] and rcParams['ytick.major.left'],
697 right=rcParams['ytick.right'] and rcParams['ytick.major.right'],
698 labelleft=(rcParams['ytick.labelleft'] and
699 rcParams['ytick.major.left']),
700 labelright=(rcParams['ytick.labelright'] and
701 rcParams['ytick.major.right']),
702 which='major')
704 def __init_subclass__(cls, **kwargs):
705 parent_uses_cla = super(cls, cls)._subclass_uses_cla
706 if 'cla' in cls.__dict__:
707 _api.warn_deprecated(
708 '3.6',
709 pending=True,
710 message=f'Overriding `Axes.cla` in {cls.__qualname__} is '
711 'pending deprecation in %(since)s and will be fully '
712 'deprecated in favor of `Axes.clear` in the future. '
713 'Please report '
714 f'this to the {cls.__module__!r} author.')
715 cls._subclass_uses_cla = 'cla' in cls.__dict__ or parent_uses_cla
716 super().__init_subclass__(**kwargs)
718 def __getstate__(self):
719 state = super().__getstate__()
720 # Prune the sharing & twinning info to only contain the current group.
721 state["_shared_axes"] = {
722 name: self._shared_axes[name].get_siblings(self)
723 for name in self._axis_names if self in self._shared_axes[name]}
724 state["_twinned_axes"] = (self._twinned_axes.get_siblings(self)
725 if self in self._twinned_axes else None)
726 return state
728 def __setstate__(self, state):
729 # Merge the grouping info back into the global groupers.
730 shared_axes = state.pop("_shared_axes")
731 for name, shared_siblings in shared_axes.items():
732 self._shared_axes[name].join(*shared_siblings)
733 twinned_siblings = state.pop("_twinned_axes")
734 if twinned_siblings:
735 self._twinned_axes.join(*twinned_siblings)
736 self.__dict__ = state
737 self._stale = True
739 def __repr__(self):
740 fields = []
741 if self.get_label():
742 fields += [f"label={self.get_label()!r}"]
743 if hasattr(self, "get_title"):
744 titles = {}
745 for k in ["left", "center", "right"]:
746 title = self.get_title(loc=k)
747 if title:
748 titles[k] = title
749 if titles:
750 fields += [f"title={titles}"]
751 for name, axis in self._axis_map.items():
752 if axis.get_label() and axis.get_label().get_text():
753 fields += [f"{name}label={axis.get_label().get_text()!r}"]
754 return f"<{self.__class__.__name__}: " + ", ".join(fields) + ">"
756 @_api.delete_parameter("3.6", "args")
757 @_api.delete_parameter("3.6", "kwargs")
758 def get_window_extent(self, renderer=None, *args, **kwargs):
759 """
760 Return the Axes bounding box in display space; *args* and *kwargs*
761 are empty.
763 This bounding box does not include the spines, ticks, ticklabels,
764 or other labels. For a bounding box including these elements use
765 `~matplotlib.axes.Axes.get_tightbbox`.
767 See Also
768 --------
769 matplotlib.axes.Axes.get_tightbbox
770 matplotlib.axis.Axis.get_tightbbox
771 matplotlib.spines.Spine.get_window_extent
772 """
773 return self.bbox
775 def _init_axis(self):
776 # This is moved out of __init__ because non-separable axes don't use it
777 self.xaxis = maxis.XAxis(self)
778 self.spines.bottom.register_axis(self.xaxis)
779 self.spines.top.register_axis(self.xaxis)
780 self.yaxis = maxis.YAxis(self)
781 self.spines.left.register_axis(self.yaxis)
782 self.spines.right.register_axis(self.yaxis)
783 self._update_transScale()
785 def set_figure(self, fig):
786 # docstring inherited
787 super().set_figure(fig)
789 self.bbox = mtransforms.TransformedBbox(self._position,
790 fig.transSubfigure)
791 # these will be updated later as data is added
792 self.dataLim = mtransforms.Bbox.null()
793 self._viewLim = mtransforms.Bbox.unit()
794 self.transScale = mtransforms.TransformWrapper(
795 mtransforms.IdentityTransform())
797 self._set_lim_and_transforms()
799 def _unstale_viewLim(self):
800 # We should arrange to store this information once per share-group
801 # instead of on every axis.
802 need_scale = {
803 name: any(ax._stale_viewlims[name]
804 for ax in self._shared_axes[name].get_siblings(self))
805 for name in self._axis_names}
806 if any(need_scale.values()):
807 for name in need_scale:
808 for ax in self._shared_axes[name].get_siblings(self):
809 ax._stale_viewlims[name] = False
810 self.autoscale_view(**{f"scale{name}": scale
811 for name, scale in need_scale.items()})
813 @property
814 def viewLim(self):
815 self._unstale_viewLim()
816 return self._viewLim
818 def _request_autoscale_view(self, axis="all", tight=None):
819 """
820 Mark a single axis, or all of them, as stale wrt. autoscaling.
822 No computation is performed until the next autoscaling; thus, separate
823 calls to control individual axises incur negligible performance cost.
825 Parameters
826 ----------
827 axis : str, default: "all"
828 Either an element of ``self._axis_names``, or "all".
829 tight : bool or None, default: None
830 """
831 axis_names = _api.check_getitem(
832 {**{k: [k] for k in self._axis_names}, "all": self._axis_names},
833 axis=axis)
834 for name in axis_names:
835 self._stale_viewlims[name] = True
836 if tight is not None:
837 self._tight = tight
839 def _set_lim_and_transforms(self):
840 """
841 Set the *_xaxis_transform*, *_yaxis_transform*, *transScale*,
842 *transData*, *transLimits* and *transAxes* transformations.
844 .. note::
846 This method is primarily used by rectilinear projections of the
847 `~matplotlib.axes.Axes` class, and is meant to be overridden by
848 new kinds of projection Axes that need different transformations
849 and limits. (See `~matplotlib.projections.polar.PolarAxes` for an
850 example.)
851 """
852 self.transAxes = mtransforms.BboxTransformTo(self.bbox)
854 # Transforms the x and y axis separately by a scale factor.
855 # It is assumed that this part will have non-linear components
856 # (e.g., for a log scale).
857 self.transScale = mtransforms.TransformWrapper(
858 mtransforms.IdentityTransform())
860 # An affine transformation on the data, generally to limit the
861 # range of the axes
862 self.transLimits = mtransforms.BboxTransformFrom(
863 mtransforms.TransformedBbox(self._viewLim, self.transScale))
865 # The parentheses are important for efficiency here -- they
866 # group the last two (which are usually affines) separately
867 # from the first (which, with log-scaling can be non-affine).
868 self.transData = self.transScale + (self.transLimits + self.transAxes)
870 self._xaxis_transform = mtransforms.blended_transform_factory(
871 self.transData, self.transAxes)
872 self._yaxis_transform = mtransforms.blended_transform_factory(
873 self.transAxes, self.transData)
875 def get_xaxis_transform(self, which='grid'):
876 """
877 Get the transformation used for drawing x-axis labels, ticks
878 and gridlines. The x-direction is in data coordinates and the
879 y-direction is in axis coordinates.
881 .. note::
883 This transformation is primarily used by the
884 `~matplotlib.axis.Axis` class, and is meant to be
885 overridden by new kinds of projections that may need to
886 place axis elements in different locations.
887 """
888 if which == 'grid':
889 return self._xaxis_transform
890 elif which == 'tick1':
891 # for cartesian projection, this is bottom spine
892 return self.spines.bottom.get_spine_transform()
893 elif which == 'tick2':
894 # for cartesian projection, this is top spine
895 return self.spines.top.get_spine_transform()
896 else:
897 raise ValueError(f'unknown value for which: {which!r}')
899 def get_xaxis_text1_transform(self, pad_points):
900 """
901 Returns
902 -------
903 transform : Transform
904 The transform used for drawing x-axis labels, which will add
905 *pad_points* of padding (in points) between the axis and the label.
906 The x-direction is in data coordinates and the y-direction is in
907 axis coordinates
908 valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
909 The text vertical alignment.
910 halign : {'center', 'left', 'right'}
911 The text horizontal alignment.
913 Notes
914 -----
915 This transformation is primarily used by the `~matplotlib.axis.Axis`
916 class, and is meant to be overridden by new kinds of projections that
917 may need to place axis elements in different locations.
918 """
919 labels_align = mpl.rcParams["xtick.alignment"]
920 return (self.get_xaxis_transform(which='tick1') +
921 mtransforms.ScaledTranslation(0, -1 * pad_points / 72,
922 self.figure.dpi_scale_trans),
923 "top", labels_align)
925 def get_xaxis_text2_transform(self, pad_points):
926 """
927 Returns
928 -------
929 transform : Transform
930 The transform used for drawing secondary x-axis labels, which will
931 add *pad_points* of padding (in points) between the axis and the
932 label. The x-direction is in data coordinates and the y-direction
933 is in axis coordinates
934 valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
935 The text vertical alignment.
936 halign : {'center', 'left', 'right'}
937 The text horizontal alignment.
939 Notes
940 -----
941 This transformation is primarily used by the `~matplotlib.axis.Axis`
942 class, and is meant to be overridden by new kinds of projections that
943 may need to place axis elements in different locations.
944 """
945 labels_align = mpl.rcParams["xtick.alignment"]
946 return (self.get_xaxis_transform(which='tick2') +
947 mtransforms.ScaledTranslation(0, pad_points / 72,
948 self.figure.dpi_scale_trans),
949 "bottom", labels_align)
951 def get_yaxis_transform(self, which='grid'):
952 """
953 Get the transformation used for drawing y-axis labels, ticks
954 and gridlines. The x-direction is in axis coordinates and the
955 y-direction is in data coordinates.
957 .. note::
959 This transformation is primarily used by the
960 `~matplotlib.axis.Axis` class, and is meant to be
961 overridden by new kinds of projections that may need to
962 place axis elements in different locations.
963 """
964 if which == 'grid':
965 return self._yaxis_transform
966 elif which == 'tick1':
967 # for cartesian projection, this is bottom spine
968 return self.spines.left.get_spine_transform()
969 elif which == 'tick2':
970 # for cartesian projection, this is top spine
971 return self.spines.right.get_spine_transform()
972 else:
973 raise ValueError(f'unknown value for which: {which!r}')
975 def get_yaxis_text1_transform(self, pad_points):
976 """
977 Returns
978 -------
979 transform : Transform
980 The transform used for drawing y-axis labels, which will add
981 *pad_points* of padding (in points) between the axis and the label.
982 The x-direction is in axis coordinates and the y-direction is in
983 data coordinates
984 valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
985 The text vertical alignment.
986 halign : {'center', 'left', 'right'}
987 The text horizontal alignment.
989 Notes
990 -----
991 This transformation is primarily used by the `~matplotlib.axis.Axis`
992 class, and is meant to be overridden by new kinds of projections that
993 may need to place axis elements in different locations.
994 """
995 labels_align = mpl.rcParams["ytick.alignment"]
996 return (self.get_yaxis_transform(which='tick1') +
997 mtransforms.ScaledTranslation(-1 * pad_points / 72, 0,
998 self.figure.dpi_scale_trans),
999 labels_align, "right")
1001 def get_yaxis_text2_transform(self, pad_points):
1002 """
1003 Returns
1004 -------
1005 transform : Transform
1006 The transform used for drawing secondart y-axis labels, which will
1007 add *pad_points* of padding (in points) between the axis and the
1008 label. The x-direction is in axis coordinates and the y-direction
1009 is in data coordinates
1010 valign : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
1011 The text vertical alignment.
1012 halign : {'center', 'left', 'right'}
1013 The text horizontal alignment.
1015 Notes
1016 -----
1017 This transformation is primarily used by the `~matplotlib.axis.Axis`
1018 class, and is meant to be overridden by new kinds of projections that
1019 may need to place axis elements in different locations.
1020 """
1021 labels_align = mpl.rcParams["ytick.alignment"]
1022 return (self.get_yaxis_transform(which='tick2') +
1023 mtransforms.ScaledTranslation(pad_points / 72, 0,
1024 self.figure.dpi_scale_trans),
1025 labels_align, "left")
1027 def _update_transScale(self):
1028 self.transScale.set(
1029 mtransforms.blended_transform_factory(
1030 self.xaxis.get_transform(), self.yaxis.get_transform()))
1032 def get_position(self, original=False):
1033 """
1034 Return the position of the Axes within the figure as a `.Bbox`.
1036 Parameters
1037 ----------
1038 original : bool
1039 If ``True``, return the original position. Otherwise return the
1040 active position. For an explanation of the positions see
1041 `.set_position`.
1043 Returns
1044 -------
1045 `.Bbox`
1047 """
1048 if original:
1049 return self._originalPosition.frozen()
1050 else:
1051 locator = self.get_axes_locator()
1052 if not locator:
1053 self.apply_aspect()
1054 return self._position.frozen()
1056 def set_position(self, pos, which='both'):
1057 """
1058 Set the Axes position.
1060 Axes have two position attributes. The 'original' position is the
1061 position allocated for the Axes. The 'active' position is the
1062 position the Axes is actually drawn at. These positions are usually
1063 the same unless a fixed aspect is set to the Axes. See
1064 `.Axes.set_aspect` for details.
1066 Parameters
1067 ----------
1068 pos : [left, bottom, width, height] or `~matplotlib.transforms.Bbox`
1069 The new position of the Axes in `.Figure` coordinates.
1071 which : {'both', 'active', 'original'}, default: 'both'
1072 Determines which position variables to change.
1074 See Also
1075 --------
1076 matplotlib.transforms.Bbox.from_bounds
1077 matplotlib.transforms.Bbox.from_extents
1078 """
1079 self._set_position(pos, which=which)
1080 # because this is being called externally to the library we
1081 # don't let it be in the layout.
1082 self.set_in_layout(False)
1084 def _set_position(self, pos, which='both'):
1085 """
1086 Private version of set_position.
1088 Call this internally to get the same functionality of `set_position`,
1089 but not to take the axis out of the constrained_layout hierarchy.
1090 """
1091 if not isinstance(pos, mtransforms.BboxBase):
1092 pos = mtransforms.Bbox.from_bounds(*pos)
1093 for ax in self._twinned_axes.get_siblings(self):
1094 if which in ('both', 'active'):
1095 ax._position.set(pos)
1096 if which in ('both', 'original'):
1097 ax._originalPosition.set(pos)
1098 self.stale = True
1100 def reset_position(self):
1101 """
1102 Reset the active position to the original position.
1104 This undoes changes to the active position (as defined in
1105 `.set_position`) which may have been performed to satisfy fixed-aspect
1106 constraints.
1107 """
1108 for ax in self._twinned_axes.get_siblings(self):
1109 pos = ax.get_position(original=True)
1110 ax.set_position(pos, which='active')
1112 def set_axes_locator(self, locator):
1113 """
1114 Set the Axes locator.
1116 Parameters
1117 ----------
1118 locator : Callable[[Axes, Renderer], Bbox]
1119 """
1120 self._axes_locator = locator
1121 self.stale = True
1123 def get_axes_locator(self):
1124 """
1125 Return the axes_locator.
1126 """
1127 return self._axes_locator
1129 def _set_artist_props(self, a):
1130 """Set the boilerplate props for artists added to Axes."""
1131 a.set_figure(self.figure)
1132 if not a.is_transform_set():
1133 a.set_transform(self.transData)
1135 a.axes = self
1136 if a.get_mouseover():
1137 self._mouseover_set.add(a)
1139 def _gen_axes_patch(self):
1140 """
1141 Returns
1142 -------
1143 Patch
1144 The patch used to draw the background of the Axes. It is also used
1145 as the clipping path for any data elements on the Axes.
1147 In the standard Axes, this is a rectangle, but in other projections
1148 it may not be.
1150 Notes
1151 -----
1152 Intended to be overridden by new projection types.
1153 """
1154 return mpatches.Rectangle((0.0, 0.0), 1.0, 1.0)
1156 def _gen_axes_spines(self, locations=None, offset=0.0, units='inches'):
1157 """
1158 Returns
1159 -------
1160 dict
1161 Mapping of spine names to `.Line2D` or `.Patch` instances that are
1162 used to draw Axes spines.
1164 In the standard Axes, spines are single line segments, but in other
1165 projections they may not be.
1167 Notes
1168 -----
1169 Intended to be overridden by new projection types.
1170 """
1171 return {side: mspines.Spine.linear_spine(self, side)
1172 for side in ['left', 'right', 'bottom', 'top']}
1174 def sharex(self, other):
1175 """
1176 Share the x-axis with *other*.
1178 This is equivalent to passing ``sharex=other`` when constructing the
1179 Axes, and cannot be used if the x-axis is already being shared with
1180 another Axes.
1181 """
1182 _api.check_isinstance(_AxesBase, other=other)
1183 if self._sharex is not None and other is not self._sharex:
1184 raise ValueError("x-axis is already shared")
1185 self._shared_axes["x"].join(self, other)
1186 self._sharex = other
1187 self.xaxis.major = other.xaxis.major # Ticker instances holding
1188 self.xaxis.minor = other.xaxis.minor # locator and formatter.
1189 x0, x1 = other.get_xlim()
1190 self.set_xlim(x0, x1, emit=False, auto=other.get_autoscalex_on())
1191 self.xaxis._scale = other.xaxis._scale
1193 def sharey(self, other):
1194 """
1195 Share the y-axis with *other*.
1197 This is equivalent to passing ``sharey=other`` when constructing the
1198 Axes, and cannot be used if the y-axis is already being shared with
1199 another Axes.
1200 """
1201 _api.check_isinstance(_AxesBase, other=other)
1202 if self._sharey is not None and other is not self._sharey:
1203 raise ValueError("y-axis is already shared")
1204 self._shared_axes["y"].join(self, other)
1205 self._sharey = other
1206 self.yaxis.major = other.yaxis.major # Ticker instances holding
1207 self.yaxis.minor = other.yaxis.minor # locator and formatter.
1208 y0, y1 = other.get_ylim()
1209 self.set_ylim(y0, y1, emit=False, auto=other.get_autoscaley_on())
1210 self.yaxis._scale = other.yaxis._scale
1212 def __clear(self):
1213 """Clear the Axes."""
1214 # The actual implementation of clear() as long as clear() has to be
1215 # an adapter delegating to the correct implementation.
1216 # The implementation can move back into clear() when the
1217 # deprecation on cla() subclassing expires.
1219 # stash the current visibility state
1220 if hasattr(self, 'patch'):
1221 patch_visible = self.patch.get_visible()
1222 else:
1223 patch_visible = True
1225 xaxis_visible = self.xaxis.get_visible()
1226 yaxis_visible = self.yaxis.get_visible()
1228 for axis in self._axis_map.values():
1229 axis.clear() # Also resets the scale to linear.
1230 for spine in self.spines.values():
1231 spine.clear()
1233 self.ignore_existing_data_limits = True
1234 self.callbacks = cbook.CallbackRegistry(
1235 signals=["xlim_changed", "ylim_changed", "zlim_changed"])
1237 for name, axis in self._axis_map.items():
1238 share = getattr(self, f"_share{name}")
1239 if share is not None:
1240 getattr(self, f"share{name}")(share)
1241 else:
1242 axis._set_scale("linear")
1243 axis._set_lim(0, 1, auto=True)
1245 # update the minor locator for x and y axis based on rcParams
1246 if mpl.rcParams['xtick.minor.visible']:
1247 self.xaxis.set_minor_locator(mticker.AutoMinorLocator())
1248 if mpl.rcParams['ytick.minor.visible']:
1249 self.yaxis.set_minor_locator(mticker.AutoMinorLocator())
1251 self._xmargin = mpl.rcParams['axes.xmargin']
1252 self._ymargin = mpl.rcParams['axes.ymargin']
1253 self._tight = None
1254 self._use_sticky_edges = True
1255 self._update_transScale() # needed?
1257 self._get_lines = _process_plot_var_args(self)
1258 self._get_patches_for_fill = _process_plot_var_args(self, 'fill')
1260 self._gridOn = mpl.rcParams['axes.grid']
1261 self._children = []
1262 self._mouseover_set = _OrderedSet()
1263 self.child_axes = []
1264 self._current_image = None # strictly for pyplot via _sci, _gci
1265 self._projection_init = None # strictly for pyplot.subplot
1266 self.legend_ = None
1267 self.containers = []
1269 self.grid(False) # Disable grid on init to use rcParameter
1270 self.grid(self._gridOn, which=mpl.rcParams['axes.grid.which'],
1271 axis=mpl.rcParams['axes.grid.axis'])
1272 props = font_manager.FontProperties(
1273 size=mpl.rcParams['axes.titlesize'],
1274 weight=mpl.rcParams['axes.titleweight'])
1276 y = mpl.rcParams['axes.titley']
1277 if y is None:
1278 y = 1.0
1279 self._autotitlepos = True
1280 else:
1281 self._autotitlepos = False
1283 self.title = mtext.Text(
1284 x=0.5, y=y, text='',
1285 fontproperties=props,
1286 verticalalignment='baseline',
1287 horizontalalignment='center',
1288 )
1289 self._left_title = mtext.Text(
1290 x=0.0, y=y, text='',
1291 fontproperties=props.copy(),
1292 verticalalignment='baseline',
1293 horizontalalignment='left', )
1294 self._right_title = mtext.Text(
1295 x=1.0, y=y, text='',
1296 fontproperties=props.copy(),
1297 verticalalignment='baseline',
1298 horizontalalignment='right',
1299 )
1300 title_offset_points = mpl.rcParams['axes.titlepad']
1301 # refactor this out so it can be called in ax.set_title if
1302 # pad argument used...
1303 self._set_title_offset_trans(title_offset_points)
1305 for _title in (self.title, self._left_title, self._right_title):
1306 self._set_artist_props(_title)
1308 # The patch draws the background of the Axes. We want this to be below
1309 # the other artists. We use the frame to draw the edges so we are
1310 # setting the edgecolor to None.
1311 self.patch = self._gen_axes_patch()
1312 self.patch.set_figure(self.figure)
1313 self.patch.set_facecolor(self._facecolor)
1314 self.patch.set_edgecolor('none')
1315 self.patch.set_linewidth(0)
1316 self.patch.set_transform(self.transAxes)
1318 self.set_axis_on()
1320 self.xaxis.set_clip_path(self.patch)
1321 self.yaxis.set_clip_path(self.patch)
1323 self._shared_axes["x"].clean()
1324 self._shared_axes["y"].clean()
1325 if self._sharex is not None:
1326 self.xaxis.set_visible(xaxis_visible)
1327 self.patch.set_visible(patch_visible)
1328 if self._sharey is not None:
1329 self.yaxis.set_visible(yaxis_visible)
1330 self.patch.set_visible(patch_visible)
1332 self.stale = True
1334 def clear(self):
1335 """Clear the Axes."""
1336 # Act as an alias, or as the superclass implementation depending on the
1337 # subclass implementation.
1338 if self._subclass_uses_cla:
1339 self.cla()
1340 else:
1341 self.__clear()
1343 def cla(self):
1344 """Clear the Axes."""
1345 # Act as an alias, or as the superclass implementation depending on the
1346 # subclass implementation.
1347 if self._subclass_uses_cla:
1348 self.__clear()
1349 else:
1350 self.clear()
1352 class ArtistList(MutableSequence):
1353 """
1354 A sublist of Axes children based on their type.
1356 The type-specific children sublists will become immutable in
1357 Matplotlib 3.7. Then, these artist lists will likely be replaced by
1358 tuples. Use as if this is a tuple already.
1360 This class exists only for the transition period to warn on the
1361 deprecated modification of artist lists.
1362 """
1363 def __init__(self, axes, prop_name, add_name,
1364 valid_types=None, invalid_types=None):
1365 """
1366 Parameters
1367 ----------
1368 axes : .axes.Axes
1369 The Axes from which this sublist will pull the children
1370 Artists.
1371 prop_name : str
1372 The property name used to access this sublist from the Axes;
1373 used to generate deprecation warnings.
1374 add_name : str
1375 The method name used to add Artists of this sublist's type to
1376 the Axes; used to generate deprecation warnings.
1377 valid_types : list of type, optional
1378 A list of types that determine which children will be returned
1379 by this sublist. If specified, then the Artists in the sublist
1380 must be instances of any of these types. If unspecified, then
1381 any type of Artist is valid (unless limited by
1382 *invalid_types*.)
1383 invalid_types : tuple, optional
1384 A list of types that determine which children will *not* be
1385 returned by this sublist. If specified, then Artists in the
1386 sublist will never be an instance of these types. Otherwise, no
1387 types will be excluded.
1388 """
1389 self._axes = axes
1390 self._prop_name = prop_name
1391 self._add_name = add_name
1392 self._type_check = lambda artist: (
1393 (not valid_types or isinstance(artist, valid_types)) and
1394 (not invalid_types or not isinstance(artist, invalid_types))
1395 )
1397 def __repr__(self):
1398 return f'<Axes.ArtistList of {len(self)} {self._prop_name}>'
1400 def __len__(self):
1401 return sum(self._type_check(artist)
1402 for artist in self._axes._children)
1404 def __iter__(self):
1405 for artist in list(self._axes._children):
1406 if self._type_check(artist):
1407 yield artist
1409 def __getitem__(self, key):
1410 return [artist
1411 for artist in self._axes._children
1412 if self._type_check(artist)][key]
1414 def __add__(self, other):
1415 if isinstance(other, (list, _AxesBase.ArtistList)):
1416 return [*self, *other]
1417 return NotImplemented
1419 def __radd__(self, other):
1420 if isinstance(other, list):
1421 return other + list(self)
1422 return NotImplemented
1424 def insert(self, index, item):
1425 _api.warn_deprecated(
1426 '3.5',
1427 name=f'modification of the Axes.{self._prop_name}',
1428 obj_type='property',
1429 alternative=f'Axes.{self._add_name}')
1430 try:
1431 index = self._axes._children.index(self[index])
1432 except IndexError:
1433 index = None
1434 getattr(self._axes, self._add_name)(item)
1435 if index is not None:
1436 # Move new item to the specified index, if there's something to
1437 # put it before.
1438 self._axes._children[index:index] = self._axes._children[-1:]
1439 del self._axes._children[-1]
1441 def __setitem__(self, key, item):
1442 _api.warn_deprecated(
1443 '3.5',
1444 name=f'modification of the Axes.{self._prop_name}',
1445 obj_type='property',
1446 alternative=f'Artist.remove() and Axes.f{self._add_name}')
1447 del self[key]
1448 if isinstance(key, slice):
1449 key = key.start
1450 if not np.iterable(item):
1451 self.insert(key, item)
1452 return
1454 try:
1455 index = self._axes._children.index(self[key])
1456 except IndexError:
1457 index = None
1458 for i, artist in enumerate(item):
1459 getattr(self._axes, self._add_name)(artist)
1460 if index is not None:
1461 # Move new items to the specified index, if there's something
1462 # to put it before.
1463 i = -(i + 1)
1464 self._axes._children[index:index] = self._axes._children[i:]
1465 del self._axes._children[i:]
1467 def __delitem__(self, key):
1468 _api.warn_deprecated(
1469 '3.5',
1470 name=f'modification of the Axes.{self._prop_name}',
1471 obj_type='property',
1472 alternative='Artist.remove()')
1473 if isinstance(key, slice):
1474 for artist in self[key]:
1475 artist.remove()
1476 else:
1477 self[key].remove()
1479 @property
1480 def artists(self):
1481 return self.ArtistList(self, 'artists', 'add_artist', invalid_types=(
1482 mcoll.Collection, mimage.AxesImage, mlines.Line2D, mpatches.Patch,
1483 mtable.Table, mtext.Text))
1485 @property
1486 def collections(self):
1487 return self.ArtistList(self, 'collections', 'add_collection',
1488 valid_types=mcoll.Collection)
1490 @property
1491 def images(self):
1492 return self.ArtistList(self, 'images', 'add_image',
1493 valid_types=mimage.AxesImage)
1495 @property
1496 def lines(self):
1497 return self.ArtistList(self, 'lines', 'add_line',
1498 valid_types=mlines.Line2D)
1500 @property
1501 def patches(self):
1502 return self.ArtistList(self, 'patches', 'add_patch',
1503 valid_types=mpatches.Patch)
1505 @property
1506 def tables(self):
1507 return self.ArtistList(self, 'tables', 'add_table',
1508 valid_types=mtable.Table)
1510 @property
1511 def texts(self):
1512 return self.ArtistList(self, 'texts', 'add_artist',
1513 valid_types=mtext.Text)
1515 def get_facecolor(self):
1516 """Get the facecolor of the Axes."""
1517 return self.patch.get_facecolor()
1519 def set_facecolor(self, color):
1520 """
1521 Set the facecolor of the Axes.
1523 Parameters
1524 ----------
1525 color : color
1526 """
1527 self._facecolor = color
1528 self.stale = True
1529 return self.patch.set_facecolor(color)
1531 def _set_title_offset_trans(self, title_offset_points):
1532 """
1533 Set the offset for the title either from :rc:`axes.titlepad`
1534 or from set_title kwarg ``pad``.
1535 """
1536 self.titleOffsetTrans = mtransforms.ScaledTranslation(
1537 0.0, title_offset_points / 72,
1538 self.figure.dpi_scale_trans)
1539 for _title in (self.title, self._left_title, self._right_title):
1540 _title.set_transform(self.transAxes + self.titleOffsetTrans)
1541 _title.set_clip_box(None)
1543 def set_prop_cycle(self, *args, **kwargs):
1544 """
1545 Set the property cycle of the Axes.
1547 The property cycle controls the style properties such as color,
1548 marker and linestyle of future plot commands. The style properties
1549 of data already added to the Axes are not modified.
1551 Call signatures::
1553 set_prop_cycle(cycler)
1554 set_prop_cycle(label=values[, label2=values2[, ...]])
1555 set_prop_cycle(label, values)
1557 Form 1 sets given `~cycler.Cycler` object.
1559 Form 2 creates a `~cycler.Cycler` which cycles over one or more
1560 properties simultaneously and set it as the property cycle of the
1561 Axes. If multiple properties are given, their value lists must have
1562 the same length. This is just a shortcut for explicitly creating a
1563 cycler and passing it to the function, i.e. it's short for
1564 ``set_prop_cycle(cycler(label=values label2=values2, ...))``.
1566 Form 3 creates a `~cycler.Cycler` for a single property and set it
1567 as the property cycle of the Axes. This form exists for compatibility
1568 with the original `cycler.cycler` interface. Its use is discouraged
1569 in favor of the kwarg form, i.e. ``set_prop_cycle(label=values)``.
1571 Parameters
1572 ----------
1573 cycler : Cycler
1574 Set the given Cycler. *None* resets to the cycle defined by the
1575 current style.
1577 label : str
1578 The property key. Must be a valid `.Artist` property.
1579 For example, 'color' or 'linestyle'. Aliases are allowed,
1580 such as 'c' for 'color' and 'lw' for 'linewidth'.
1582 values : iterable
1583 Finite-length iterable of the property values. These values
1584 are validated and will raise a ValueError if invalid.
1586 See Also
1587 --------
1588 matplotlib.rcsetup.cycler
1589 Convenience function for creating validated cyclers for properties.
1590 cycler.cycler
1591 The original function for creating unvalidated cyclers.
1593 Examples
1594 --------
1595 Setting the property cycle for a single property:
1597 >>> ax.set_prop_cycle(color=['red', 'green', 'blue'])
1599 Setting the property cycle for simultaneously cycling over multiple
1600 properties (e.g. red circle, green plus, blue cross):
1602 >>> ax.set_prop_cycle(color=['red', 'green', 'blue'],
1603 ... marker=['o', '+', 'x'])
1605 """
1606 if args and kwargs:
1607 raise TypeError("Cannot supply both positional and keyword "
1608 "arguments to this method.")
1609 # Can't do `args == (None,)` as that crashes cycler.
1610 if len(args) == 1 and args[0] is None:
1611 prop_cycle = None
1612 else:
1613 prop_cycle = cycler(*args, **kwargs)
1614 self._get_lines.set_prop_cycle(prop_cycle)
1615 self._get_patches_for_fill.set_prop_cycle(prop_cycle)
1617 def get_aspect(self):
1618 """
1619 Return the aspect ratio of the axes scaling.
1621 This is either "auto" or a float giving the ratio of y/x-scale.
1622 """
1623 return self._aspect
1625 def set_aspect(self, aspect, adjustable=None, anchor=None, share=False):
1626 """
1627 Set the aspect ratio of the axes scaling, i.e. y/x-scale.
1629 Parameters
1630 ----------
1631 aspect : {'auto', 'equal'} or float
1632 Possible values:
1634 - 'auto': fill the position rectangle with data.
1635 - 'equal': same as ``aspect=1``, i.e. same scaling for x and y.
1636 - *float*: The displayed size of 1 unit in y-data coordinates will
1637 be *aspect* times the displayed size of 1 unit in x-data
1638 coordinates; e.g. for ``aspect=2`` a square in data coordinates
1639 will be rendered with a height of twice its width.
1641 adjustable : None or {'box', 'datalim'}, optional
1642 If not ``None``, this defines which parameter will be adjusted to
1643 meet the required aspect. See `.set_adjustable` for further
1644 details.
1646 anchor : None or str or (float, float), optional
1647 If not ``None``, this defines where the Axes will be drawn if there
1648 is extra space due to aspect constraints. The most common way to
1649 to specify the anchor are abbreviations of cardinal directions:
1651 ===== =====================
1652 value description
1653 ===== =====================
1654 'C' centered
1655 'SW' lower left corner
1656 'S' middle of bottom edge
1657 'SE' lower right corner
1658 etc.
1659 ===== =====================
1661 See `~.Axes.set_anchor` for further details.
1663 share : bool, default: False
1664 If ``True``, apply the settings to all shared Axes.
1666 See Also
1667 --------
1668 matplotlib.axes.Axes.set_adjustable
1669 Set how the Axes adjusts to achieve the required aspect ratio.
1670 matplotlib.axes.Axes.set_anchor
1671 Set the position in case of extra space.
1672 """
1673 if cbook._str_equal(aspect, 'equal'):
1674 aspect = 1
1675 if not cbook._str_equal(aspect, 'auto'):
1676 aspect = float(aspect) # raise ValueError if necessary
1678 if share:
1679 axes = {sibling for name in self._axis_names
1680 for sibling in self._shared_axes[name].get_siblings(self)}
1681 else:
1682 axes = [self]
1684 for ax in axes:
1685 ax._aspect = aspect
1687 if adjustable is None:
1688 adjustable = self._adjustable
1689 self.set_adjustable(adjustable, share=share) # Handle sharing.
1691 if anchor is not None:
1692 self.set_anchor(anchor, share=share)
1693 self.stale = True
1695 def get_adjustable(self):
1696 """
1697 Return whether the Axes will adjust its physical dimension ('box') or
1698 its data limits ('datalim') to achieve the desired aspect ratio.
1700 See Also
1701 --------
1702 matplotlib.axes.Axes.set_adjustable
1703 Set how the Axes adjusts to achieve the required aspect ratio.
1704 matplotlib.axes.Axes.set_aspect
1705 For a description of aspect handling.
1706 """
1707 return self._adjustable
1709 def set_adjustable(self, adjustable, share=False):
1710 """
1711 Set how the Axes adjusts to achieve the required aspect ratio.
1713 Parameters
1714 ----------
1715 adjustable : {'box', 'datalim'}
1716 If 'box', change the physical dimensions of the Axes.
1717 If 'datalim', change the ``x`` or ``y`` data limits.
1719 share : bool, default: False
1720 If ``True``, apply the settings to all shared Axes.
1722 See Also
1723 --------
1724 matplotlib.axes.Axes.set_aspect
1725 For a description of aspect handling.
1727 Notes
1728 -----
1729 Shared Axes (of which twinned Axes are a special case)
1730 impose restrictions on how aspect ratios can be imposed.
1731 For twinned Axes, use 'datalim'. For Axes that share both
1732 x and y, use 'box'. Otherwise, either 'datalim' or 'box'
1733 may be used. These limitations are partly a requirement
1734 to avoid over-specification, and partly a result of the
1735 particular implementation we are currently using, in
1736 which the adjustments for aspect ratios are done sequentially
1737 and independently on each Axes as it is drawn.
1738 """
1739 _api.check_in_list(["box", "datalim"], adjustable=adjustable)
1740 if share:
1741 axs = {sibling for name in self._axis_names
1742 for sibling in self._shared_axes[name].get_siblings(self)}
1743 else:
1744 axs = [self]
1745 if (adjustable == "datalim"
1746 and any(getattr(ax.get_data_ratio, "__func__", None)
1747 != _AxesBase.get_data_ratio
1748 for ax in axs)):
1749 # Limits adjustment by apply_aspect assumes that the axes' aspect
1750 # ratio can be computed from the data limits and scales.
1751 raise ValueError("Cannot set Axes adjustable to 'datalim' for "
1752 "Axes which override 'get_data_ratio'")
1753 for ax in axs:
1754 ax._adjustable = adjustable
1755 self.stale = True
1757 def get_box_aspect(self):
1758 """
1759 Return the Axes box aspect, i.e. the ratio of height to width.
1761 The box aspect is ``None`` (i.e. chosen depending on the available
1762 figure space) unless explicitly specified.
1764 See Also
1765 --------
1766 matplotlib.axes.Axes.set_box_aspect
1767 for a description of box aspect.
1768 matplotlib.axes.Axes.set_aspect
1769 for a description of aspect handling.
1770 """
1771 return self._box_aspect
1773 def set_box_aspect(self, aspect=None):
1774 """
1775 Set the Axes box aspect, i.e. the ratio of height to width.
1777 This defines the aspect of the Axes in figure space and is not to be
1778 confused with the data aspect (see `~.Axes.set_aspect`).
1780 Parameters
1781 ----------
1782 aspect : float or None
1783 Changes the physical dimensions of the Axes, such that the ratio
1784 of the Axes height to the Axes width in physical units is equal to
1785 *aspect*. Defining a box aspect will change the *adjustable*
1786 property to 'datalim' (see `~.Axes.set_adjustable`).
1788 *None* will disable a fixed box aspect so that height and width
1789 of the Axes are chosen independently.
1791 See Also
1792 --------
1793 matplotlib.axes.Axes.set_aspect
1794 for a description of aspect handling.
1795 """
1796 axs = {*self._twinned_axes.get_siblings(self),
1797 *self._twinned_axes.get_siblings(self)}
1799 if aspect is not None:
1800 aspect = float(aspect)
1801 # when box_aspect is set to other than ´None`,
1802 # adjustable must be "datalim"
1803 for ax in axs:
1804 ax.set_adjustable("datalim")
1806 for ax in axs:
1807 ax._box_aspect = aspect
1808 ax.stale = True
1810 def get_anchor(self):
1811 """
1812 Get the anchor location.
1814 See Also
1815 --------
1816 matplotlib.axes.Axes.set_anchor
1817 for a description of the anchor.
1818 matplotlib.axes.Axes.set_aspect
1819 for a description of aspect handling.
1820 """
1821 return self._anchor
1823 def set_anchor(self, anchor, share=False):
1824 """
1825 Define the anchor location.
1827 The actual drawing area (active position) of the Axes may be smaller
1828 than the Bbox (original position) when a fixed aspect is required. The
1829 anchor defines where the drawing area will be located within the
1830 available space.
1832 Parameters
1833 ----------
1834 anchor : (float, float) or {'C', 'SW', 'S', 'SE', 'E', 'NE', ...}
1835 Either an (*x*, *y*) pair of relative coordinates (0 is left or
1836 bottom, 1 is right or top), 'C' (center), or a cardinal direction
1837 ('SW', southwest, is bottom left, etc.). str inputs are shorthands
1838 for (*x*, *y*) coordinates, as shown in the following table::
1840 .. code-block:: none
1842 +-----------------+-----------------+-----------------+
1843 | 'NW' (0.0, 1.0) | 'N' (0.5, 1.0) | 'NE' (1.0, 1.0) |
1844 +-----------------+-----------------+-----------------+
1845 | 'W' (0.0, 0.5) | 'C' (0.5, 0.5) | 'E' (1.0, 0.5) |
1846 +-----------------+-----------------+-----------------+
1847 | 'SW' (0.0, 0.0) | 'S' (0.5, 0.0) | 'SE' (1.0, 0.0) |
1848 +-----------------+-----------------+-----------------+
1850 share : bool, default: False
1851 If ``True``, apply the settings to all shared Axes.
1853 See Also
1854 --------
1855 matplotlib.axes.Axes.set_aspect
1856 for a description of aspect handling.
1857 """
1858 if not (anchor in mtransforms.Bbox.coefs or len(anchor) == 2):
1859 raise ValueError('argument must be among %s' %
1860 ', '.join(mtransforms.Bbox.coefs))
1861 if share:
1862 axes = {sibling for name in self._axis_names
1863 for sibling in self._shared_axes[name].get_siblings(self)}
1864 else:
1865 axes = [self]
1866 for ax in axes:
1867 ax._anchor = anchor
1869 self.stale = True
1871 def get_data_ratio(self):
1872 """
1873 Return the aspect ratio of the scaled data.
1875 Notes
1876 -----
1877 This method is intended to be overridden by new projection types.
1878 """
1879 txmin, txmax = self.xaxis.get_transform().transform(self.get_xbound())
1880 tymin, tymax = self.yaxis.get_transform().transform(self.get_ybound())
1881 xsize = max(abs(txmax - txmin), 1e-30)
1882 ysize = max(abs(tymax - tymin), 1e-30)
1883 return ysize / xsize
1885 def apply_aspect(self, position=None):
1886 """
1887 Adjust the Axes for a specified data aspect ratio.
1889 Depending on `.get_adjustable` this will modify either the
1890 Axes box (position) or the view limits. In the former case,
1891 `~matplotlib.axes.Axes.get_anchor` will affect the position.
1893 Parameters
1894 ----------
1895 position : None or .Bbox
1896 If not ``None``, this defines the position of the
1897 Axes within the figure as a Bbox. See `~.Axes.get_position`
1898 for further details.
1900 Notes
1901 -----
1902 This is called automatically when each Axes is drawn. You may need
1903 to call it yourself if you need to update the Axes position and/or
1904 view limits before the Figure is drawn.
1906 See Also
1907 --------
1908 matplotlib.axes.Axes.set_aspect
1909 For a description of aspect ratio handling.
1910 matplotlib.axes.Axes.set_adjustable
1911 Set how the Axes adjusts to achieve the required aspect ratio.
1912 matplotlib.axes.Axes.set_anchor
1913 Set the position in case of extra space.
1914 """
1915 if position is None:
1916 position = self.get_position(original=True)
1918 aspect = self.get_aspect()
1920 if aspect == 'auto' and self._box_aspect is None:
1921 self._set_position(position, which='active')
1922 return
1924 trans = self.get_figure().transSubfigure
1925 bb = mtransforms.Bbox.unit().transformed(trans)
1926 # this is the physical aspect of the panel (or figure):
1927 fig_aspect = bb.height / bb.width
1929 if self._adjustable == 'box':
1930 if self in self._twinned_axes:
1931 raise RuntimeError("Adjustable 'box' is not allowed in a "
1932 "twinned Axes; use 'datalim' instead")
1933 box_aspect = aspect * self.get_data_ratio()
1934 pb = position.frozen()
1935 pb1 = pb.shrunk_to_aspect(box_aspect, pb, fig_aspect)
1936 self._set_position(pb1.anchored(self.get_anchor(), pb), 'active')
1937 return
1939 # The following is only seen if self._adjustable == 'datalim'
1940 if self._box_aspect is not None:
1941 pb = position.frozen()
1942 pb1 = pb.shrunk_to_aspect(self._box_aspect, pb, fig_aspect)
1943 self._set_position(pb1.anchored(self.get_anchor(), pb), 'active')
1944 if aspect == "auto":
1945 return
1947 # reset active to original in case it had been changed by prior use
1948 # of 'box'
1949 if self._box_aspect is None:
1950 self._set_position(position, which='active')
1951 else:
1952 position = pb1.anchored(self.get_anchor(), pb)
1954 x_trf = self.xaxis.get_transform()
1955 y_trf = self.yaxis.get_transform()
1956 xmin, xmax = x_trf.transform(self.get_xbound())
1957 ymin, ymax = y_trf.transform(self.get_ybound())
1958 xsize = max(abs(xmax - xmin), 1e-30)
1959 ysize = max(abs(ymax - ymin), 1e-30)
1961 box_aspect = fig_aspect * (position.height / position.width)
1962 data_ratio = box_aspect / aspect
1964 y_expander = data_ratio * xsize / ysize - 1
1965 # If y_expander > 0, the dy/dx viewLim ratio needs to increase
1966 if abs(y_expander) < 0.005:
1967 return
1969 dL = self.dataLim
1970 x0, x1 = x_trf.transform(dL.intervalx)
1971 y0, y1 = y_trf.transform(dL.intervaly)
1972 xr = 1.05 * (x1 - x0)
1973 yr = 1.05 * (y1 - y0)
1975 xmarg = xsize - xr
1976 ymarg = ysize - yr
1977 Ysize = data_ratio * xsize
1978 Xsize = ysize / data_ratio
1979 Xmarg = Xsize - xr
1980 Ymarg = Ysize - yr
1981 # Setting these targets to, e.g., 0.05*xr does not seem to help.
1982 xm = 0
1983 ym = 0
1985 shared_x = self in self._shared_axes["x"]
1986 shared_y = self in self._shared_axes["y"]
1988 if shared_x and shared_y:
1989 raise RuntimeError("set_aspect(..., adjustable='datalim') or "
1990 "axis('equal') are not allowed when both axes "
1991 "are shared. Try set_aspect(..., "
1992 "adjustable='box').")
1994 # If y is shared, then we are only allowed to change x, etc.
1995 if shared_y:
1996 adjust_y = False
1997 else:
1998 if xmarg > xm and ymarg > ym:
1999 adjy = ((Ymarg > 0 and y_expander < 0) or
2000 (Xmarg < 0 and y_expander > 0))
2001 else:
2002 adjy = y_expander > 0
2003 adjust_y = shared_x or adjy # (Ymarg > xmarg)
2005 if adjust_y:
2006 yc = 0.5 * (ymin + ymax)
2007 y0 = yc - Ysize / 2.0
2008 y1 = yc + Ysize / 2.0
2009 self.set_ybound(y_trf.inverted().transform([y0, y1]))
2010 else:
2011 xc = 0.5 * (xmin + xmax)
2012 x0 = xc - Xsize / 2.0
2013 x1 = xc + Xsize / 2.0
2014 self.set_xbound(x_trf.inverted().transform([x0, x1]))
2016 def axis(self, *args, emit=True, **kwargs):
2017 """
2018 Convenience method to get or set some axis properties.
2020 Call signatures::
2022 xmin, xmax, ymin, ymax = axis()
2023 xmin, xmax, ymin, ymax = axis([xmin, xmax, ymin, ymax])
2024 xmin, xmax, ymin, ymax = axis(option)
2025 xmin, xmax, ymin, ymax = axis(**kwargs)
2027 Parameters
2028 ----------
2029 xmin, xmax, ymin, ymax : float, optional
2030 The axis limits to be set. This can also be achieved using ::
2032 ax.set(xlim=(xmin, xmax), ylim=(ymin, ymax))
2034 option : bool or str
2035 If a bool, turns axis lines and labels on or off. If a string,
2036 possible values are:
2038 ======== ==========================================================
2039 Value Description
2040 ======== ==========================================================
2041 'on' Turn on axis lines and labels. Same as ``True``.
2042 'off' Turn off axis lines and labels. Same as ``False``.
2043 'equal' Set equal scaling (i.e., make circles circular) by
2044 changing axis limits. This is the same as
2045 ``ax.set_aspect('equal', adjustable='datalim')``.
2046 Explicit data limits may not be respected in this case.
2047 'scaled' Set equal scaling (i.e., make circles circular) by
2048 changing dimensions of the plot box. This is the same as
2049 ``ax.set_aspect('equal', adjustable='box', anchor='C')``.
2050 Additionally, further autoscaling will be disabled.
2051 'tight' Set limits just large enough to show all data, then
2052 disable further autoscaling.
2053 'auto' Automatic scaling (fill plot box with data).
2054 'image' 'scaled' with axis limits equal to data limits.
2055 'square' Square plot; similar to 'scaled', but initially forcing
2056 ``xmax-xmin == ymax-ymin``.
2057 ======== ==========================================================
2059 emit : bool, default: True
2060 Whether observers are notified of the axis limit change.
2061 This option is passed on to `~.Axes.set_xlim` and
2062 `~.Axes.set_ylim`.
2064 Returns
2065 -------
2066 xmin, xmax, ymin, ymax : float
2067 The axis limits.
2069 See Also
2070 --------
2071 matplotlib.axes.Axes.set_xlim
2072 matplotlib.axes.Axes.set_ylim
2073 """
2074 if len(args) > 1:
2075 raise TypeError("axis() takes 0 or 1 positional arguments but "
2076 f"{len(args)} were given")
2077 elif len(args) == 1 and isinstance(args[0], (str, bool)):
2078 s = args[0]
2079 if s is True:
2080 s = 'on'
2081 if s is False:
2082 s = 'off'
2083 s = s.lower()
2084 if s == 'on':
2085 self.set_axis_on()
2086 elif s == 'off':
2087 self.set_axis_off()
2088 elif s in ('equal', 'tight', 'scaled', 'auto', 'image', 'square'):
2089 self.set_autoscale_on(True)
2090 self.set_aspect('auto')
2091 self.autoscale_view(tight=False)
2092 if s == 'equal':
2093 self.set_aspect('equal', adjustable='datalim')
2094 elif s == 'scaled':
2095 self.set_aspect('equal', adjustable='box', anchor='C')
2096 self.set_autoscale_on(False) # Req. by Mark Bakker
2097 elif s == 'tight':
2098 self.autoscale_view(tight=True)
2099 self.set_autoscale_on(False)
2100 elif s == 'image':
2101 self.autoscale_view(tight=True)
2102 self.set_autoscale_on(False)
2103 self.set_aspect('equal', adjustable='box', anchor='C')
2104 elif s == 'square':
2105 self.set_aspect('equal', adjustable='box', anchor='C')
2106 self.set_autoscale_on(False)
2107 xlim = self.get_xlim()
2108 ylim = self.get_ylim()
2109 edge_size = max(np.diff(xlim), np.diff(ylim))[0]
2110 self.set_xlim([xlim[0], xlim[0] + edge_size],
2111 emit=emit, auto=False)
2112 self.set_ylim([ylim[0], ylim[0] + edge_size],
2113 emit=emit, auto=False)
2114 else:
2115 raise ValueError(f"Unrecognized string {s!r} to axis; "
2116 "try 'on' or 'off'")
2117 else:
2118 if len(args) == 1:
2119 limits = args[0]
2120 try:
2121 xmin, xmax, ymin, ymax = limits
2122 except (TypeError, ValueError) as err:
2123 raise TypeError('the first argument to axis() must be an '
2124 'iterable of the form '
2125 '[xmin, xmax, ymin, ymax]') from err
2126 else:
2127 xmin = kwargs.pop('xmin', None)
2128 xmax = kwargs.pop('xmax', None)
2129 ymin = kwargs.pop('ymin', None)
2130 ymax = kwargs.pop('ymax', None)
2131 xauto = (None # Keep autoscale state as is.
2132 if xmin is None and xmax is None
2133 else False) # Turn off autoscale.
2134 yauto = (None
2135 if ymin is None and ymax is None
2136 else False)
2137 self.set_xlim(xmin, xmax, emit=emit, auto=xauto)
2138 self.set_ylim(ymin, ymax, emit=emit, auto=yauto)
2139 if kwargs:
2140 raise TypeError(f"axis() got an unexpected keyword argument "
2141 f"'{next(iter(kwargs))}'")
2142 return (*self.get_xlim(), *self.get_ylim())
2144 def get_legend(self):
2145 """Return the `.Legend` instance, or None if no legend is defined."""
2146 return self.legend_
2148 def get_images(self):
2149 r"""Return a list of `.AxesImage`\s contained by the Axes."""
2150 return cbook.silent_list('AxesImage', self.images)
2152 def get_lines(self):
2153 """Return a list of lines contained by the Axes."""
2154 return cbook.silent_list('Line2D', self.lines)
2156 def get_xaxis(self):
2157 """
2158 [*Discouraged*] Return the XAxis instance.
2160 .. admonition:: Discouraged
2162 The use of this function is discouraged. You should instead
2163 directly access the attribute ``ax.xaxis``.
2164 """
2165 return self.xaxis
2167 def get_yaxis(self):
2168 """
2169 [*Discouraged*] Return the YAxis instance.
2171 .. admonition:: Discouraged
2173 The use of this function is discouraged. You should instead
2174 directly access the attribute ``ax.yaxis``.
2175 """
2176 return self.yaxis
2178 get_xgridlines = _axis_method_wrapper("xaxis", "get_gridlines")
2179 get_xticklines = _axis_method_wrapper("xaxis", "get_ticklines")
2180 get_ygridlines = _axis_method_wrapper("yaxis", "get_gridlines")
2181 get_yticklines = _axis_method_wrapper("yaxis", "get_ticklines")
2183 # Adding and tracking artists
2185 def _sci(self, im):
2186 """
2187 Set the current image.
2189 This image will be the target of colormap functions like
2190 ``pyplot.viridis``, and other functions such as `~.pyplot.clim`. The
2191 current image is an attribute of the current Axes.
2192 """
2193 _api.check_isinstance(
2194 (mpl.contour.ContourSet, mcoll.Collection, mimage.AxesImage),
2195 im=im)
2196 if isinstance(im, mpl.contour.ContourSet):
2197 if im.collections[0] not in self._children:
2198 raise ValueError("ContourSet must be in current Axes")
2199 elif im not in self._children:
2200 raise ValueError("Argument must be an image, collection, or "
2201 "ContourSet in this Axes")
2202 self._current_image = im
2204 def _gci(self):
2205 """Helper for `~matplotlib.pyplot.gci`; do not use elsewhere."""
2206 return self._current_image
2208 def has_data(self):
2209 """
2210 Return whether any artists have been added to the Axes.
2212 This should not be used to determine whether the *dataLim*
2213 need to be updated, and may not actually be useful for
2214 anything.
2215 """
2216 return any(isinstance(a, (mcoll.Collection, mimage.AxesImage,
2217 mlines.Line2D, mpatches.Patch))
2218 for a in self._children)
2220 def _deprecate_noninstance(self, _name, _types, **kwargs):
2221 """
2222 For each *key, value* pair in *kwargs*, check that *value* is an
2223 instance of one of *_types*; if not, raise an appropriate deprecation.
2224 """
2225 for key, value in kwargs.items():
2226 if not isinstance(value, _types):
2227 _api.warn_deprecated(
2228 '3.5', name=_name,
2229 message=f'Passing argument *{key}* of unexpected type '
2230 f'{type(value).__qualname__} to %(name)s which only '
2231 f'accepts {_types} is deprecated since %(since)s and will '
2232 'become an error %(removal)s.')
2234 def add_artist(self, a):
2235 """
2236 Add an `.Artist` to the Axes; return the artist.
2238 Use `add_artist` only for artists for which there is no dedicated
2239 "add" method; and if necessary, use a method such as `update_datalim`
2240 to manually update the dataLim if the artist is to be included in
2241 autoscaling.
2243 If no ``transform`` has been specified when creating the artist (e.g.
2244 ``artist.get_transform() == None``) then the transform is set to
2245 ``ax.transData``.
2246 """
2247 a.axes = self
2248 self._children.append(a)
2249 a._remove_method = self._children.remove
2250 self._set_artist_props(a)
2251 a.set_clip_path(self.patch)
2252 self.stale = True
2253 return a
2255 def add_child_axes(self, ax):
2256 """
2257 Add an `.AxesBase` to the Axes' children; return the child Axes.
2259 This is the lowlevel version. See `.axes.Axes.inset_axes`.
2260 """
2262 # normally Axes have themselves as the Axes, but these need to have
2263 # their parent...
2264 # Need to bypass the getter...
2265 ax._axes = self
2266 ax.stale_callback = martist._stale_axes_callback
2268 self.child_axes.append(ax)
2269 ax._remove_method = self.child_axes.remove
2270 self.stale = True
2271 return ax
2273 def add_collection(self, collection, autolim=True):
2274 """
2275 Add a `.Collection` to the Axes; return the collection.
2276 """
2277 self._deprecate_noninstance('add_collection', mcoll.Collection,
2278 collection=collection)
2279 label = collection.get_label()
2280 if not label:
2281 collection.set_label(f'_child{len(self._children)}')
2282 self._children.append(collection)
2283 collection._remove_method = self._children.remove
2284 self._set_artist_props(collection)
2286 if collection.get_clip_path() is None:
2287 collection.set_clip_path(self.patch)
2289 if autolim:
2290 # Make sure viewLim is not stale (mostly to match
2291 # pre-lazy-autoscale behavior, which is not really better).
2292 self._unstale_viewLim()
2293 datalim = collection.get_datalim(self.transData)
2294 points = datalim.get_points()
2295 if not np.isinf(datalim.minpos).all():
2296 # By definition, if minpos (minimum positive value) is set
2297 # (i.e., non-inf), then min(points) <= minpos <= max(points),
2298 # and minpos would be superfluous. However, we add minpos to
2299 # the call so that self.dataLim will update its own minpos.
2300 # This ensures that log scales see the correct minimum.
2301 points = np.concatenate([points, [datalim.minpos]])
2302 self.update_datalim(points)
2304 self.stale = True
2305 return collection
2307 def add_image(self, image):
2308 """
2309 Add an `.AxesImage` to the Axes; return the image.
2310 """
2311 self._deprecate_noninstance('add_image', mimage.AxesImage, image=image)
2312 self._set_artist_props(image)
2313 if not image.get_label():
2314 image.set_label(f'_child{len(self._children)}')
2315 self._children.append(image)
2316 image._remove_method = self._children.remove
2317 self.stale = True
2318 return image
2320 def _update_image_limits(self, image):
2321 xmin, xmax, ymin, ymax = image.get_extent()
2322 self.axes.update_datalim(((xmin, ymin), (xmax, ymax)))
2324 def add_line(self, line):
2325 """
2326 Add a `.Line2D` to the Axes; return the line.
2327 """
2328 self._deprecate_noninstance('add_line', mlines.Line2D, line=line)
2329 self._set_artist_props(line)
2330 if line.get_clip_path() is None:
2331 line.set_clip_path(self.patch)
2333 self._update_line_limits(line)
2334 if not line.get_label():
2335 line.set_label(f'_child{len(self._children)}')
2336 self._children.append(line)
2337 line._remove_method = self._children.remove
2338 self.stale = True
2339 return line
2341 def _add_text(self, txt):
2342 """
2343 Add a `.Text` to the Axes; return the text.
2344 """
2345 self._deprecate_noninstance('_add_text', mtext.Text, txt=txt)
2346 self._set_artist_props(txt)
2347 self._children.append(txt)
2348 txt._remove_method = self._children.remove
2349 self.stale = True
2350 return txt
2352 def _update_line_limits(self, line):
2353 """
2354 Figures out the data limit of the given line, updating self.dataLim.
2355 """
2356 path = line.get_path()
2357 if path.vertices.size == 0:
2358 return
2360 line_trf = line.get_transform()
2362 if line_trf == self.transData:
2363 data_path = path
2364 elif any(line_trf.contains_branch_seperately(self.transData)):
2365 # Compute the transform from line coordinates to data coordinates.
2366 trf_to_data = line_trf - self.transData
2367 # If transData is affine we can use the cached non-affine component
2368 # of line's path (since the non-affine part of line_trf is
2369 # entirely encapsulated in trf_to_data).
2370 if self.transData.is_affine:
2371 line_trans_path = line._get_transformed_path()
2372 na_path, _ = line_trans_path.get_transformed_path_and_affine()
2373 data_path = trf_to_data.transform_path_affine(na_path)
2374 else:
2375 data_path = trf_to_data.transform_path(path)
2376 else:
2377 # For backwards compatibility we update the dataLim with the
2378 # coordinate range of the given path, even though the coordinate
2379 # systems are completely different. This may occur in situations
2380 # such as when ax.transAxes is passed through for absolute
2381 # positioning.
2382 data_path = path
2384 if not data_path.vertices.size:
2385 return
2387 updatex, updatey = line_trf.contains_branch_seperately(self.transData)
2388 if self.name != "rectilinear":
2389 # This block is mostly intended to handle axvline in polar plots,
2390 # for which updatey would otherwise be True.
2391 if updatex and line_trf == self.get_yaxis_transform():
2392 updatex = False
2393 if updatey and line_trf == self.get_xaxis_transform():
2394 updatey = False
2395 self.dataLim.update_from_path(data_path,
2396 self.ignore_existing_data_limits,
2397 updatex=updatex, updatey=updatey)
2398 self.ignore_existing_data_limits = False
2400 def add_patch(self, p):
2401 """
2402 Add a `.Patch` to the Axes; return the patch.
2403 """
2404 self._deprecate_noninstance('add_patch', mpatches.Patch, p=p)
2405 self._set_artist_props(p)
2406 if p.get_clip_path() is None:
2407 p.set_clip_path(self.patch)
2408 self._update_patch_limits(p)
2409 self._children.append(p)
2410 p._remove_method = self._children.remove
2411 return p
2413 def _update_patch_limits(self, patch):
2414 """Update the data limits for the given patch."""
2415 # hist can add zero height Rectangles, which is useful to keep
2416 # the bins, counts and patches lined up, but it throws off log
2417 # scaling. We'll ignore rects with zero height or width in
2418 # the auto-scaling
2420 # cannot check for '==0' since unitized data may not compare to zero
2421 # issue #2150 - we update the limits if patch has non zero width
2422 # or height.
2423 if (isinstance(patch, mpatches.Rectangle) and
2424 ((not patch.get_width()) and (not patch.get_height()))):
2425 return
2426 p = patch.get_path()
2427 # Get all vertices on the path
2428 # Loop through each segment to get extrema for Bezier curve sections
2429 vertices = []
2430 for curve, code in p.iter_bezier(simplify=False):
2431 # Get distance along the curve of any extrema
2432 _, dzeros = curve.axis_aligned_extrema()
2433 # Calculate vertices of start, end and any extrema in between
2434 vertices.append(curve([0, *dzeros, 1]))
2436 if len(vertices):
2437 vertices = np.row_stack(vertices)
2439 patch_trf = patch.get_transform()
2440 updatex, updatey = patch_trf.contains_branch_seperately(self.transData)
2441 if not (updatex or updatey):
2442 return
2443 if self.name != "rectilinear":
2444 # As in _update_line_limits, but for axvspan.
2445 if updatex and patch_trf == self.get_yaxis_transform():
2446 updatex = False
2447 if updatey and patch_trf == self.get_xaxis_transform():
2448 updatey = False
2449 trf_to_data = patch_trf - self.transData
2450 xys = trf_to_data.transform(vertices)
2451 self.update_datalim(xys, updatex=updatex, updatey=updatey)
2453 def add_table(self, tab):
2454 """
2455 Add a `.Table` to the Axes; return the table.
2456 """
2457 self._deprecate_noninstance('add_table', mtable.Table, tab=tab)
2458 self._set_artist_props(tab)
2459 self._children.append(tab)
2460 tab.set_clip_path(self.patch)
2461 tab._remove_method = self._children.remove
2462 return tab
2464 def add_container(self, container):
2465 """
2466 Add a `.Container` to the Axes' containers; return the container.
2467 """
2468 label = container.get_label()
2469 if not label:
2470 container.set_label('_container%d' % len(self.containers))
2471 self.containers.append(container)
2472 container._remove_method = self.containers.remove
2473 return container
2475 def _unit_change_handler(self, axis_name, event=None):
2476 """
2477 Process axis units changes: requests updates to data and view limits.
2478 """
2479 if event is None: # Allow connecting `self._unit_change_handler(name)`
2480 return functools.partial(
2481 self._unit_change_handler, axis_name, event=object())
2482 _api.check_in_list(self._axis_map, axis_name=axis_name)
2483 for line in self.lines:
2484 line.recache_always()
2485 self.relim()
2486 self._request_autoscale_view(axis_name)
2488 def relim(self, visible_only=False):
2489 """
2490 Recompute the data limits based on current artists.
2492 At present, `.Collection` instances are not supported.
2494 Parameters
2495 ----------
2496 visible_only : bool, default: False
2497 Whether to exclude invisible artists.
2498 """
2499 # Collections are deliberately not supported (yet); see
2500 # the TODO note in artists.py.
2501 self.dataLim.ignore(True)
2502 self.dataLim.set_points(mtransforms.Bbox.null().get_points())
2503 self.ignore_existing_data_limits = True
2505 for artist in self._children:
2506 if not visible_only or artist.get_visible():
2507 if isinstance(artist, mlines.Line2D):
2508 self._update_line_limits(artist)
2509 elif isinstance(artist, mpatches.Patch):
2510 self._update_patch_limits(artist)
2511 elif isinstance(artist, mimage.AxesImage):
2512 self._update_image_limits(artist)
2514 def update_datalim(self, xys, updatex=True, updatey=True):
2515 """
2516 Extend the `~.Axes.dataLim` Bbox to include the given points.
2518 If no data is set currently, the Bbox will ignore its limits and set
2519 the bound to be the bounds of the xydata (*xys*). Otherwise, it will
2520 compute the bounds of the union of its current data and the data in
2521 *xys*.
2523 Parameters
2524 ----------
2525 xys : 2D array-like
2526 The points to include in the data limits Bbox. This can be either
2527 a list of (x, y) tuples or a Nx2 array.
2529 updatex, updatey : bool, default: True
2530 Whether to update the x/y limits.
2531 """
2532 xys = np.asarray(xys)
2533 if not np.any(np.isfinite(xys)):
2534 return
2535 self.dataLim.update_from_data_xy(xys, self.ignore_existing_data_limits,
2536 updatex=updatex, updatey=updatey)
2537 self.ignore_existing_data_limits = False
2539 def _process_unit_info(self, datasets=None, kwargs=None, *, convert=True):
2540 """
2541 Set axis units based on *datasets* and *kwargs*, and optionally apply
2542 unit conversions to *datasets*.
2544 Parameters
2545 ----------
2546 datasets : list
2547 List of (axis_name, dataset) pairs (where the axis name is defined
2548 as in `._axis_map`). Individual datasets can also be None
2549 (which gets passed through).
2550 kwargs : dict
2551 Other parameters from which unit info (i.e., the *xunits*,
2552 *yunits*, *zunits* (for 3D Axes), *runits* and *thetaunits* (for
2553 polar) entries) is popped, if present. Note that this dict is
2554 mutated in-place!
2555 convert : bool, default: True
2556 Whether to return the original datasets or the converted ones.
2558 Returns
2559 -------
2560 list
2561 Either the original datasets if *convert* is False, or the
2562 converted ones if *convert* is True (the default).
2563 """
2564 # The API makes datasets a list of pairs rather than an axis_name to
2565 # dataset mapping because it is sometimes necessary to process multiple
2566 # datasets for a single axis, and concatenating them may be tricky
2567 # (e.g. if some are scalars, etc.).
2568 datasets = datasets or []
2569 kwargs = kwargs or {}
2570 axis_map = self._axis_map
2571 for axis_name, data in datasets:
2572 try:
2573 axis = axis_map[axis_name]
2574 except KeyError:
2575 raise ValueError(f"Invalid axis name: {axis_name!r}") from None
2576 # Update from data if axis is already set but no unit is set yet.
2577 if axis is not None and data is not None and not axis.have_units():
2578 axis.update_units(data)
2579 for axis_name, axis in axis_map.items():
2580 # Return if no axis is set.
2581 if axis is None:
2582 continue
2583 # Check for units in the kwargs, and if present update axis.
2584 units = kwargs.pop(f"{axis_name}units", axis.units)
2585 if self.name == "polar":
2586 # Special case: polar supports "thetaunits"/"runits".
2587 polar_units = {"x": "thetaunits", "y": "runits"}
2588 units = kwargs.pop(polar_units[axis_name], units)
2589 if units != axis.units and units is not None:
2590 axis.set_units(units)
2591 # If the units being set imply a different converter,
2592 # we need to update again.
2593 for dataset_axis_name, data in datasets:
2594 if dataset_axis_name == axis_name and data is not None:
2595 axis.update_units(data)
2596 return [axis_map[axis_name].convert_units(data)
2597 if convert and data is not None else data
2598 for axis_name, data in datasets]
2600 def in_axes(self, mouseevent):
2601 """
2602 Return whether the given event (in display coords) is in the Axes.
2603 """
2604 return self.patch.contains(mouseevent)[0]
2606 get_autoscalex_on = _axis_method_wrapper("xaxis", "_get_autoscale_on")
2607 get_autoscaley_on = _axis_method_wrapper("yaxis", "_get_autoscale_on")
2608 set_autoscalex_on = _axis_method_wrapper("xaxis", "_set_autoscale_on")
2609 set_autoscaley_on = _axis_method_wrapper("yaxis", "_set_autoscale_on")
2611 def get_autoscale_on(self):
2612 """Return True if each axis is autoscaled, False otherwise."""
2613 return all(axis._get_autoscale_on()
2614 for axis in self._axis_map.values())
2616 def set_autoscale_on(self, b):
2617 """
2618 Set whether autoscaling is applied to each axis on the next draw or
2619 call to `.Axes.autoscale_view`.
2621 Parameters
2622 ----------
2623 b : bool
2624 """
2625 for axis in self._axis_map.values():
2626 axis._set_autoscale_on(b)
2628 @property
2629 def use_sticky_edges(self):
2630 """
2631 When autoscaling, whether to obey all `Artist.sticky_edges`.
2633 Default is ``True``.
2635 Setting this to ``False`` ensures that the specified margins
2636 will be applied, even if the plot includes an image, for
2637 example, which would otherwise force a view limit to coincide
2638 with its data limit.
2640 The changing this property does not change the plot until
2641 `autoscale` or `autoscale_view` is called.
2642 """
2643 return self._use_sticky_edges
2645 @use_sticky_edges.setter
2646 def use_sticky_edges(self, b):
2647 self._use_sticky_edges = bool(b)
2648 # No effect until next autoscaling, which will mark the Axes as stale.
2650 def set_xmargin(self, m):
2651 """
2652 Set padding of X data limits prior to autoscaling.
2654 *m* times the data interval will be added to each end of that interval
2655 before it is used in autoscaling. If *m* is negative, this will clip
2656 the data range instead of expanding it.
2658 For example, if your data is in the range [0, 2], a margin of 0.1 will
2659 result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range
2660 of [0.2, 1.8].
2662 Parameters
2663 ----------
2664 m : float greater than -0.5
2665 """
2666 if m <= -0.5:
2667 raise ValueError("margin must be greater than -0.5")
2668 self._xmargin = m
2669 self._request_autoscale_view("x")
2670 self.stale = True
2672 def set_ymargin(self, m):
2673 """
2674 Set padding of Y data limits prior to autoscaling.
2676 *m* times the data interval will be added to each end of that interval
2677 before it is used in autoscaling. If *m* is negative, this will clip
2678 the data range instead of expanding it.
2680 For example, if your data is in the range [0, 2], a margin of 0.1 will
2681 result in a range [-0.2, 2.2]; a margin of -0.1 will result in a range
2682 of [0.2, 1.8].
2684 Parameters
2685 ----------
2686 m : float greater than -0.5
2687 """
2688 if m <= -0.5:
2689 raise ValueError("margin must be greater than -0.5")
2690 self._ymargin = m
2691 self._request_autoscale_view("y")
2692 self.stale = True
2694 def margins(self, *margins, x=None, y=None, tight=True):
2695 """
2696 Set or retrieve autoscaling margins.
2698 The padding added to each limit of the Axes is the *margin*
2699 times the data interval. All input parameters must be floats
2700 within the range [0, 1]. Passing both positional and keyword
2701 arguments is invalid and will raise a TypeError. If no
2702 arguments (positional or otherwise) are provided, the current
2703 margins will remain in place and simply be returned.
2705 Specifying any margin changes only the autoscaling; for example,
2706 if *xmargin* is not None, then *xmargin* times the X data
2707 interval will be added to each end of that interval before
2708 it is used in autoscaling.
2710 Parameters
2711 ----------
2712 *margins : float, optional
2713 If a single positional argument is provided, it specifies
2714 both margins of the x-axis and y-axis limits. If two
2715 positional arguments are provided, they will be interpreted
2716 as *xmargin*, *ymargin*. If setting the margin on a single
2717 axis is desired, use the keyword arguments described below.
2719 x, y : float, optional
2720 Specific margin values for the x-axis and y-axis,
2721 respectively. These cannot be used with positional
2722 arguments, but can be used individually to alter on e.g.,
2723 only the y-axis.
2725 tight : bool or None, default: True
2726 The *tight* parameter is passed to `~.axes.Axes.autoscale_view`,
2727 which is executed after a margin is changed; the default
2728 here is *True*, on the assumption that when margins are
2729 specified, no additional padding to match tick marks is
2730 usually desired. Setting *tight* to *None* preserves
2731 the previous setting.
2733 Returns
2734 -------
2735 xmargin, ymargin : float
2737 Notes
2738 -----
2739 If a previously used Axes method such as :meth:`pcolor` has set
2740 :attr:`use_sticky_edges` to `True`, only the limits not set by
2741 the "sticky artists" will be modified. To force all of the
2742 margins to be set, set :attr:`use_sticky_edges` to `False`
2743 before calling :meth:`margins`.
2744 """
2746 if margins and (x is not None or y is not None):
2747 raise TypeError('Cannot pass both positional and keyword '
2748 'arguments for x and/or y.')
2749 elif len(margins) == 1:
2750 x = y = margins[0]
2751 elif len(margins) == 2:
2752 x, y = margins
2753 elif margins:
2754 raise TypeError('Must pass a single positional argument for all '
2755 'margins, or one for each margin (x, y).')
2757 if x is None and y is None:
2758 if tight is not True:
2759 _api.warn_external(f'ignoring tight={tight!r} in get mode')
2760 return self._xmargin, self._ymargin
2762 if tight is not None:
2763 self._tight = tight
2764 if x is not None:
2765 self.set_xmargin(x)
2766 if y is not None:
2767 self.set_ymargin(y)
2769 def set_rasterization_zorder(self, z):
2770 """
2771 Set the zorder threshold for rasterization for vector graphics output.
2773 All artists with a zorder below the given value will be rasterized if
2774 they support rasterization.
2776 This setting is ignored for pixel-based output.
2778 See also :doc:`/gallery/misc/rasterization_demo`.
2780 Parameters
2781 ----------
2782 z : float or None
2783 The zorder below which artists are rasterized.
2784 If ``None`` rasterization based on zorder is deactivated.
2785 """
2786 self._rasterization_zorder = z
2787 self.stale = True
2789 def get_rasterization_zorder(self):
2790 """Return the zorder value below which artists will be rasterized."""
2791 return self._rasterization_zorder
2793 def autoscale(self, enable=True, axis='both', tight=None):
2794 """
2795 Autoscale the axis view to the data (toggle).
2797 Convenience method for simple axis view autoscaling.
2798 It turns autoscaling on or off, and then,
2799 if autoscaling for either axis is on, it performs
2800 the autoscaling on the specified axis or Axes.
2802 Parameters
2803 ----------
2804 enable : bool or None, default: True
2805 True turns autoscaling on, False turns it off.
2806 None leaves the autoscaling state unchanged.
2807 axis : {'both', 'x', 'y'}, default: 'both'
2808 The axis on which to operate. (For 3D Axes, *axis* can also be set
2809 to 'z', and 'both' refers to all three axes.)
2810 tight : bool or None, default: None
2811 If True, first set the margins to zero. Then, this argument is
2812 forwarded to `~.axes.Axes.autoscale_view` (regardless of
2813 its value); see the description of its behavior there.
2814 """
2815 if enable is None:
2816 scalex = True
2817 scaley = True
2818 else:
2819 if axis in ['x', 'both']:
2820 self.set_autoscalex_on(bool(enable))
2821 scalex = self.get_autoscalex_on()
2822 else:
2823 scalex = False
2824 if axis in ['y', 'both']:
2825 self.set_autoscaley_on(bool(enable))
2826 scaley = self.get_autoscaley_on()
2827 else:
2828 scaley = False
2829 if tight and scalex:
2830 self._xmargin = 0
2831 if tight and scaley:
2832 self._ymargin = 0
2833 if scalex:
2834 self._request_autoscale_view("x", tight=tight)
2835 if scaley:
2836 self._request_autoscale_view("y", tight=tight)
2838 def autoscale_view(self, tight=None, scalex=True, scaley=True):
2839 """
2840 Autoscale the view limits using the data limits.
2842 Parameters
2843 ----------
2844 tight : bool or None
2845 If *True*, only expand the axis limits using the margins. Note
2846 that unlike for `autoscale`, ``tight=True`` does *not* set the
2847 margins to zero.
2849 If *False* and :rc:`axes.autolimit_mode` is 'round_numbers', then
2850 after expansion by the margins, further expand the axis limits
2851 using the axis major locator.
2853 If None (the default), reuse the value set in the previous call to
2854 `autoscale_view` (the initial value is False, but the default style
2855 sets :rc:`axes.autolimit_mode` to 'data', in which case this
2856 behaves like True).
2858 scalex : bool, default: True
2859 Whether to autoscale the x axis.
2861 scaley : bool, default: True
2862 Whether to autoscale the y axis.
2864 Notes
2865 -----
2866 The autoscaling preserves any preexisting axis direction reversal.
2868 The data limits are not updated automatically when artist data are
2869 changed after the artist has been added to an Axes instance. In that
2870 case, use :meth:`matplotlib.axes.Axes.relim` prior to calling
2871 autoscale_view.
2873 If the views of the Axes are fixed, e.g. via `set_xlim`, they will
2874 not be changed by autoscale_view().
2875 See :meth:`matplotlib.axes.Axes.autoscale` for an alternative.
2876 """
2877 if tight is not None:
2878 self._tight = bool(tight)
2880 x_stickies = y_stickies = np.array([])
2881 if self.use_sticky_edges:
2882 # Only iterate over Axes and artists if needed. The check for
2883 # ``hasattr(ax, "_children")`` is necessary because this can be
2884 # called very early in the Axes init process (e.g., for twin Axes)
2885 # when these attributes don't even exist yet, in which case
2886 # `get_children` would raise an AttributeError.
2887 if self._xmargin and scalex and self.get_autoscalex_on():
2888 x_stickies = np.sort(np.concatenate([
2889 artist.sticky_edges.x
2890 for ax in self._shared_axes["x"].get_siblings(self)
2891 if hasattr(ax, "_children")
2892 for artist in ax.get_children()]))
2893 if self._ymargin and scaley and self.get_autoscaley_on():
2894 y_stickies = np.sort(np.concatenate([
2895 artist.sticky_edges.y
2896 for ax in self._shared_axes["y"].get_siblings(self)
2897 if hasattr(ax, "_children")
2898 for artist in ax.get_children()]))
2899 if self.get_xscale() == 'log':
2900 x_stickies = x_stickies[x_stickies > 0]
2901 if self.get_yscale() == 'log':
2902 y_stickies = y_stickies[y_stickies > 0]
2904 def handle_single_axis(
2905 scale, shared_axes, name, axis, margin, stickies, set_bound):
2907 if not (scale and axis._get_autoscale_on()):
2908 return # nothing to do...
2910 shared = shared_axes.get_siblings(self)
2911 # Base autoscaling on finite data limits when there is at least one
2912 # finite data limit among all the shared_axes and intervals.
2913 values = [val for ax in shared
2914 for val in getattr(ax.dataLim, f"interval{name}")
2915 if np.isfinite(val)]
2916 if values:
2917 x0, x1 = (min(values), max(values))
2918 elif getattr(self._viewLim, f"mutated{name}")():
2919 # No data, but explicit viewLims already set:
2920 # in mutatedx or mutatedy.
2921 return
2922 else:
2923 x0, x1 = (-np.inf, np.inf)
2924 # If x0 and x1 are nonfinite, get default limits from the locator.
2925 locator = axis.get_major_locator()
2926 x0, x1 = locator.nonsingular(x0, x1)
2927 # Find the minimum minpos for use in the margin calculation.
2928 minimum_minpos = min(
2929 getattr(ax.dataLim, f"minpos{name}") for ax in shared)
2931 # Prevent margin addition from crossing a sticky value. A small
2932 # tolerance must be added due to floating point issues with
2933 # streamplot; it is defined relative to x0, x1, x1-x0 but has
2934 # no absolute term (e.g. "+1e-8") to avoid issues when working with
2935 # datasets where all values are tiny (less than 1e-8).
2936 tol = 1e-5 * max(abs(x0), abs(x1), abs(x1 - x0))
2937 # Index of largest element < x0 + tol, if any.
2938 i0 = stickies.searchsorted(x0 + tol) - 1
2939 x0bound = stickies[i0] if i0 != -1 else None
2940 # Index of smallest element > x1 - tol, if any.
2941 i1 = stickies.searchsorted(x1 - tol)
2942 x1bound = stickies[i1] if i1 != len(stickies) else None
2944 # Add the margin in figure space and then transform back, to handle
2945 # non-linear scales.
2946 transform = axis.get_transform()
2947 inverse_trans = transform.inverted()
2948 x0, x1 = axis._scale.limit_range_for_scale(x0, x1, minimum_minpos)
2949 x0t, x1t = transform.transform([x0, x1])
2950 delta = (x1t - x0t) * margin
2951 if not np.isfinite(delta):
2952 delta = 0 # If a bound isn't finite, set margin to zero.
2953 x0, x1 = inverse_trans.transform([x0t - delta, x1t + delta])
2955 # Apply sticky bounds.
2956 if x0bound is not None:
2957 x0 = max(x0, x0bound)
2958 if x1bound is not None:
2959 x1 = min(x1, x1bound)
2961 if not self._tight:
2962 x0, x1 = locator.view_limits(x0, x1)
2963 set_bound(x0, x1)
2964 # End of definition of internal function 'handle_single_axis'.
2966 handle_single_axis(
2967 scalex, self._shared_axes["x"], 'x', self.xaxis, self._xmargin,
2968 x_stickies, self.set_xbound)
2969 handle_single_axis(
2970 scaley, self._shared_axes["y"], 'y', self.yaxis, self._ymargin,
2971 y_stickies, self.set_ybound)
2973 def _update_title_position(self, renderer):
2974 """
2975 Update the title position based on the bounding box enclosing
2976 all the ticklabels and x-axis spine and xlabel...
2977 """
2978 if self._autotitlepos is not None and not self._autotitlepos:
2979 _log.debug('title position was updated manually, not adjusting')
2980 return
2982 titles = (self.title, self._left_title, self._right_title)
2984 # Need to check all our twins too, and all the children as well.
2985 axs = self._twinned_axes.get_siblings(self) + self.child_axes
2986 for ax in self.child_axes: # Child positions must be updated first.
2987 locator = ax.get_axes_locator()
2988 ax.apply_aspect(locator(self, renderer) if locator else None)
2990 for title in titles:
2991 x, _ = title.get_position()
2992 # need to start again in case of window resizing
2993 title.set_position((x, 1.0))
2994 top = -np.inf
2995 for ax in axs:
2996 bb = None
2997 if (ax.xaxis.get_ticks_position() in ['top', 'unknown']
2998 or ax.xaxis.get_label_position() == 'top'):
2999 bb = ax.xaxis.get_tightbbox(renderer)
3000 if bb is None:
3001 if 'outline' in ax.spines:
3002 # Special case for colorbars:
3003 bb = ax.spines['outline'].get_window_extent()
3004 else:
3005 bb = ax.get_window_extent(renderer)
3006 top = max(top, bb.ymax)
3007 if title.get_text():
3008 ax.yaxis.get_tightbbox(renderer) # update offsetText
3009 if ax.yaxis.offsetText.get_text():
3010 bb = ax.yaxis.offsetText.get_tightbbox(renderer)
3011 if bb.intersection(title.get_tightbbox(renderer), bb):
3012 top = bb.ymax
3013 if top < 0:
3014 # the top of Axes is not even on the figure, so don't try and
3015 # automatically place it.
3016 _log.debug('top of Axes not in the figure, so title not moved')
3017 return
3018 if title.get_window_extent(renderer).ymin < top:
3019 _, y = self.transAxes.inverted().transform((0, top))
3020 title.set_position((x, y))
3021 # empirically, this doesn't always get the min to top,
3022 # so we need to adjust again.
3023 if title.get_window_extent(renderer).ymin < top:
3024 _, y = self.transAxes.inverted().transform(
3025 (0., 2 * top - title.get_window_extent(renderer).ymin))
3026 title.set_position((x, y))
3028 ymax = max(title.get_position()[1] for title in titles)
3029 for title in titles:
3030 # now line up all the titles at the highest baseline.
3031 x, _ = title.get_position()
3032 title.set_position((x, ymax))
3034 # Drawing
3035 @martist.allow_rasterization
3036 def draw(self, renderer):
3037 # docstring inherited
3038 if renderer is None:
3039 raise RuntimeError('No renderer defined')
3040 if not self.get_visible():
3041 return
3042 self._unstale_viewLim()
3044 renderer.open_group('axes', gid=self.get_gid())
3046 # prevent triggering call backs during the draw process
3047 self._stale = True
3049 # loop over self and child Axes...
3050 locator = self.get_axes_locator()
3051 self.apply_aspect(locator(self, renderer) if locator else None)
3053 artists = self.get_children()
3054 artists.remove(self.patch)
3056 # the frame draws the edges around the Axes patch -- we
3057 # decouple these so the patch can be in the background and the
3058 # frame in the foreground. Do this before drawing the axis
3059 # objects so that the spine has the opportunity to update them.
3060 if not (self.axison and self._frameon):
3061 for spine in self.spines.values():
3062 artists.remove(spine)
3064 self._update_title_position(renderer)
3066 if not self.axison:
3067 for _axis in self._axis_map.values():
3068 artists.remove(_axis)
3070 if not self.figure.canvas.is_saving():
3071 artists = [
3072 a for a in artists
3073 if not a.get_animated() or isinstance(a, mimage.AxesImage)]
3074 artists = sorted(artists, key=attrgetter('zorder'))
3076 # rasterize artists with negative zorder
3077 # if the minimum zorder is negative, start rasterization
3078 rasterization_zorder = self._rasterization_zorder
3080 if (rasterization_zorder is not None and
3081 artists and artists[0].zorder < rasterization_zorder):
3082 renderer.start_rasterizing()
3083 artists_rasterized = [a for a in artists
3084 if a.zorder < rasterization_zorder]
3085 artists = [a for a in artists
3086 if a.zorder >= rasterization_zorder]
3087 else:
3088 artists_rasterized = []
3090 # the patch draws the background rectangle -- the frame below
3091 # will draw the edges
3092 if self.axison and self._frameon:
3093 self.patch.draw(renderer)
3095 if artists_rasterized:
3096 for a in artists_rasterized:
3097 a.draw(renderer)
3098 renderer.stop_rasterizing()
3100 mimage._draw_list_compositing_images(
3101 renderer, self, artists, self.figure.suppressComposite)
3103 renderer.close_group('axes')
3104 self.stale = False
3106 def draw_artist(self, a):
3107 """
3108 Efficiently redraw a single artist.
3109 """
3110 a.draw(self.figure.canvas.get_renderer())
3112 def redraw_in_frame(self):
3113 """
3114 Efficiently redraw Axes data, but not axis ticks, labels, etc.
3115 """
3116 with ExitStack() as stack:
3117 for artist in [*self._axis_map.values(),
3118 self.title, self._left_title, self._right_title]:
3119 stack.enter_context(artist._cm_set(visible=False))
3120 self.draw(self.figure.canvas.get_renderer())
3122 @_api.deprecated("3.6", alternative="Axes.figure.canvas.get_renderer()")
3123 def get_renderer_cache(self):
3124 return self.figure.canvas.get_renderer()
3126 # Axes rectangle characteristics
3128 def get_frame_on(self):
3129 """Get whether the Axes rectangle patch is drawn."""
3130 return self._frameon
3132 def set_frame_on(self, b):
3133 """
3134 Set whether the Axes rectangle patch is drawn.
3136 Parameters
3137 ----------
3138 b : bool
3139 """
3140 self._frameon = b
3141 self.stale = True
3143 def get_axisbelow(self):
3144 """
3145 Get whether axis ticks and gridlines are above or below most artists.
3147 Returns
3148 -------
3149 bool or 'line'
3151 See Also
3152 --------
3153 set_axisbelow
3154 """
3155 return self._axisbelow
3157 def set_axisbelow(self, b):
3158 """
3159 Set whether axis ticks and gridlines are above or below most artists.
3161 This controls the zorder of the ticks and gridlines. For more
3162 information on the zorder see :doc:`/gallery/misc/zorder_demo`.
3164 Parameters
3165 ----------
3166 b : bool or 'line'
3167 Possible values:
3169 - *True* (zorder = 0.5): Ticks and gridlines are below all Artists.
3170 - 'line' (zorder = 1.5): Ticks and gridlines are above patches
3171 (e.g. rectangles, with default zorder = 1) but still below lines
3172 and markers (with their default zorder = 2).
3173 - *False* (zorder = 2.5): Ticks and gridlines are above patches
3174 and lines / markers.
3176 See Also
3177 --------
3178 get_axisbelow
3179 """
3180 # Check that b is True, False or 'line'
3181 self._axisbelow = axisbelow = validate_axisbelow(b)
3182 zorder = {
3183 True: 0.5,
3184 'line': 1.5,
3185 False: 2.5,
3186 }[axisbelow]
3187 for axis in self._axis_map.values():
3188 axis.set_zorder(zorder)
3189 self.stale = True
3191 @_docstring.dedent_interpd
3192 @_api.rename_parameter("3.5", "b", "visible")
3193 def grid(self, visible=None, which='major', axis='both', **kwargs):
3194 """
3195 Configure the grid lines.
3197 Parameters
3198 ----------
3199 visible : bool or None, optional
3200 Whether to show the grid lines. If any *kwargs* are supplied, it
3201 is assumed you want the grid on and *visible* will be set to True.
3203 If *visible* is *None* and there are no *kwargs*, this toggles the
3204 visibility of the lines.
3206 which : {'major', 'minor', 'both'}, optional
3207 The grid lines to apply the changes on.
3209 axis : {'both', 'x', 'y'}, optional
3210 The axis to apply the changes on.
3212 **kwargs : `.Line2D` properties
3213 Define the line properties of the grid, e.g.::
3215 grid(color='r', linestyle='-', linewidth=2)
3217 Valid keyword arguments are:
3219 %(Line2D:kwdoc)s
3221 Notes
3222 -----
3223 The axis is drawn as a unit, so the effective zorder for drawing the
3224 grid is determined by the zorder of each axis, not by the zorder of the
3225 `.Line2D` objects comprising the grid. Therefore, to set grid zorder,
3226 use `.set_axisbelow` or, for more control, call the
3227 `~.Artist.set_zorder` method of each axis.
3228 """
3229 _api.check_in_list(['x', 'y', 'both'], axis=axis)
3230 if axis in ['x', 'both']:
3231 self.xaxis.grid(visible, which=which, **kwargs)
3232 if axis in ['y', 'both']:
3233 self.yaxis.grid(visible, which=which, **kwargs)
3235 def ticklabel_format(self, *, axis='both', style='', scilimits=None,
3236 useOffset=None, useLocale=None, useMathText=None):
3237 r"""
3238 Configure the `.ScalarFormatter` used by default for linear Axes.
3240 If a parameter is not set, the corresponding property of the formatter
3241 is left unchanged.
3243 Parameters
3244 ----------
3245 axis : {'x', 'y', 'both'}, default: 'both'
3246 The axis to configure. Only major ticks are affected.
3248 style : {'sci', 'scientific', 'plain'}
3249 Whether to use scientific notation.
3250 The formatter default is to use scientific notation.
3252 scilimits : pair of ints (m, n)
3253 Scientific notation is used only for numbers outside the range
3254 10\ :sup:`m` to 10\ :sup:`n` (and only if the formatter is
3255 configured to use scientific notation at all). Use (0, 0) to
3256 include all numbers. Use (m, m) where m != 0 to fix the order of
3257 magnitude to 10\ :sup:`m`.
3258 The formatter default is :rc:`axes.formatter.limits`.
3260 useOffset : bool or float
3261 If True, the offset is calculated as needed.
3262 If False, no offset is used.
3263 If a numeric value, it sets the offset.
3264 The formatter default is :rc:`axes.formatter.useoffset`.
3266 useLocale : bool
3267 Whether to format the number using the current locale or using the
3268 C (English) locale. This affects e.g. the decimal separator. The
3269 formatter default is :rc:`axes.formatter.use_locale`.
3271 useMathText : bool
3272 Render the offset and scientific notation in mathtext.
3273 The formatter default is :rc:`axes.formatter.use_mathtext`.
3275 Raises
3276 ------
3277 AttributeError
3278 If the current formatter is not a `.ScalarFormatter`.
3279 """
3280 style = style.lower()
3281 axis = axis.lower()
3282 if scilimits is not None:
3283 try:
3284 m, n = scilimits
3285 m + n + 1 # check that both are numbers
3286 except (ValueError, TypeError) as err:
3287 raise ValueError("scilimits must be a sequence of 2 integers"
3288 ) from err
3289 STYLES = {'sci': True, 'scientific': True, 'plain': False, '': None}
3290 is_sci_style = _api.check_getitem(STYLES, style=style)
3291 axis_map = {**{k: [v] for k, v in self._axis_map.items()},
3292 'both': list(self._axis_map.values())}
3293 axises = _api.check_getitem(axis_map, axis=axis)
3294 try:
3295 for axis in axises:
3296 if is_sci_style is not None:
3297 axis.major.formatter.set_scientific(is_sci_style)
3298 if scilimits is not None:
3299 axis.major.formatter.set_powerlimits(scilimits)
3300 if useOffset is not None:
3301 axis.major.formatter.set_useOffset(useOffset)
3302 if useLocale is not None:
3303 axis.major.formatter.set_useLocale(useLocale)
3304 if useMathText is not None:
3305 axis.major.formatter.set_useMathText(useMathText)
3306 except AttributeError as err:
3307 raise AttributeError(
3308 "This method only works with the ScalarFormatter") from err
3310 def locator_params(self, axis='both', tight=None, **kwargs):
3311 """
3312 Control behavior of major tick locators.
3314 Because the locator is involved in autoscaling, `~.Axes.autoscale_view`
3315 is called automatically after the parameters are changed.
3317 Parameters
3318 ----------
3319 axis : {'both', 'x', 'y'}, default: 'both'
3320 The axis on which to operate. (For 3D Axes, *axis* can also be
3321 set to 'z', and 'both' refers to all three axes.)
3322 tight : bool or None, optional
3323 Parameter passed to `~.Axes.autoscale_view`.
3324 Default is None, for no change.
3326 Other Parameters
3327 ----------------
3328 **kwargs
3329 Remaining keyword arguments are passed to directly to the
3330 ``set_params()`` method of the locator. Supported keywords depend
3331 on the type of the locator. See for example
3332 `~.ticker.MaxNLocator.set_params` for the `.ticker.MaxNLocator`
3333 used by default for linear.
3335 Examples
3336 --------
3337 When plotting small subplots, one might want to reduce the maximum
3338 number of ticks and use tight bounds, for example::
3340 ax.locator_params(tight=True, nbins=4)
3342 """
3343 _api.check_in_list([*self._axis_names, "both"], axis=axis)
3344 for name in self._axis_names:
3345 if axis in [name, "both"]:
3346 loc = self._axis_map[name].get_major_locator()
3347 loc.set_params(**kwargs)
3348 self._request_autoscale_view(name, tight=tight)
3349 self.stale = True
3351 def tick_params(self, axis='both', **kwargs):
3352 """
3353 Change the appearance of ticks, tick labels, and gridlines.
3355 Tick properties that are not explicitly set using the keyword
3356 arguments remain unchanged unless *reset* is True.
3358 Parameters
3359 ----------
3360 axis : {'x', 'y', 'both'}, default: 'both'
3361 The axis to which the parameters are applied.
3362 which : {'major', 'minor', 'both'}, default: 'major'
3363 The group of ticks to which the parameters are applied.
3364 reset : bool, default: False
3365 Whether to reset the ticks to defaults before updating them.
3367 Other Parameters
3368 ----------------
3369 direction : {'in', 'out', 'inout'}
3370 Puts ticks inside the Axes, outside the Axes, or both.
3371 length : float
3372 Tick length in points.
3373 width : float
3374 Tick width in points.
3375 color : color
3376 Tick color.
3377 pad : float
3378 Distance in points between tick and label.
3379 labelsize : float or str
3380 Tick label font size in points or as a string (e.g., 'large').
3381 labelcolor : color
3382 Tick label color.
3383 colors : color
3384 Tick color and label color.
3385 zorder : float
3386 Tick and label zorder.
3387 bottom, top, left, right : bool
3388 Whether to draw the respective ticks.
3389 labelbottom, labeltop, labelleft, labelright : bool
3390 Whether to draw the respective tick labels.
3391 labelrotation : float
3392 Tick label rotation
3393 grid_color : color
3394 Gridline color.
3395 grid_alpha : float
3396 Transparency of gridlines: 0 (transparent) to 1 (opaque).
3397 grid_linewidth : float
3398 Width of gridlines in points.
3399 grid_linestyle : str
3400 Any valid `.Line2D` line style spec.
3402 Examples
3403 --------
3404 ::
3406 ax.tick_params(direction='out', length=6, width=2, colors='r',
3407 grid_color='r', grid_alpha=0.5)
3409 This will make all major ticks be red, pointing out of the box,
3410 and with dimensions 6 points by 2 points. Tick labels will
3411 also be red. Gridlines will be red and translucent.
3413 """
3414 _api.check_in_list(['x', 'y', 'both'], axis=axis)
3415 if axis in ['x', 'both']:
3416 xkw = dict(kwargs)
3417 xkw.pop('left', None)
3418 xkw.pop('right', None)
3419 xkw.pop('labelleft', None)
3420 xkw.pop('labelright', None)
3421 self.xaxis.set_tick_params(**xkw)
3422 if axis in ['y', 'both']:
3423 ykw = dict(kwargs)
3424 ykw.pop('top', None)
3425 ykw.pop('bottom', None)
3426 ykw.pop('labeltop', None)
3427 ykw.pop('labelbottom', None)
3428 self.yaxis.set_tick_params(**ykw)
3430 def set_axis_off(self):
3431 """
3432 Turn the x- and y-axis off.
3434 This affects the axis lines, ticks, ticklabels, grid and axis labels.
3435 """
3436 self.axison = False
3437 self.stale = True
3439 def set_axis_on(self):
3440 """
3441 Turn the x- and y-axis on.
3443 This affects the axis lines, ticks, ticklabels, grid and axis labels.
3444 """
3445 self.axison = True
3446 self.stale = True
3448 # data limits, ticks, tick labels, and formatting
3450 def get_xlabel(self):
3451 """
3452 Get the xlabel text string.
3453 """
3454 label = self.xaxis.get_label()
3455 return label.get_text()
3457 def set_xlabel(self, xlabel, fontdict=None, labelpad=None, *,
3458 loc=None, **kwargs):
3459 """
3460 Set the label for the x-axis.
3462 Parameters
3463 ----------
3464 xlabel : str
3465 The label text.
3467 labelpad : float, default: :rc:`axes.labelpad`
3468 Spacing in points from the Axes bounding box including ticks
3469 and tick labels. If None, the previous value is left as is.
3471 loc : {'left', 'center', 'right'}, default: :rc:`xaxis.labellocation`
3472 The label position. This is a high-level alternative for passing
3473 parameters *x* and *horizontalalignment*.
3475 Other Parameters
3476 ----------------
3477 **kwargs : `.Text` properties
3478 `.Text` properties control the appearance of the label.
3480 See Also
3481 --------
3482 text : Documents the properties supported by `.Text`.
3483 """
3484 if labelpad is not None:
3485 self.xaxis.labelpad = labelpad
3486 protected_kw = ['x', 'horizontalalignment', 'ha']
3487 if {*kwargs} & {*protected_kw}:
3488 if loc is not None:
3489 raise TypeError(f"Specifying 'loc' is disallowed when any of "
3490 f"its corresponding low level keyword "
3491 f"arguments ({protected_kw}) are also "
3492 f"supplied")
3494 else:
3495 loc = (loc if loc is not None
3496 else mpl.rcParams['xaxis.labellocation'])
3497 _api.check_in_list(('left', 'center', 'right'), loc=loc)
3499 x = {
3500 'left': 0,
3501 'center': 0.5,
3502 'right': 1,
3503 }[loc]
3504 kwargs.update(x=x, horizontalalignment=loc)
3506 return self.xaxis.set_label_text(xlabel, fontdict, **kwargs)
3508 def invert_xaxis(self):
3509 """
3510 Invert the x-axis.
3512 See Also
3513 --------
3514 xaxis_inverted
3515 get_xlim, set_xlim
3516 get_xbound, set_xbound
3517 """
3518 self.xaxis.set_inverted(not self.xaxis.get_inverted())
3520 xaxis_inverted = _axis_method_wrapper("xaxis", "get_inverted")
3522 def get_xbound(self):
3523 """
3524 Return the lower and upper x-axis bounds, in increasing order.
3526 See Also
3527 --------
3528 set_xbound
3529 get_xlim, set_xlim
3530 invert_xaxis, xaxis_inverted
3531 """
3532 left, right = self.get_xlim()
3533 if left < right:
3534 return left, right
3535 else:
3536 return right, left
3538 def set_xbound(self, lower=None, upper=None):
3539 """
3540 Set the lower and upper numerical bounds of the x-axis.
3542 This method will honor axis inversion regardless of parameter order.
3543 It will not change the autoscaling setting (`.get_autoscalex_on()`).
3545 Parameters
3546 ----------
3547 lower, upper : float or None
3548 The lower and upper bounds. If *None*, the respective axis bound
3549 is not modified.
3551 See Also
3552 --------
3553 get_xbound
3554 get_xlim, set_xlim
3555 invert_xaxis, xaxis_inverted
3556 """
3557 if upper is None and np.iterable(lower):
3558 lower, upper = lower
3560 old_lower, old_upper = self.get_xbound()
3561 if lower is None:
3562 lower = old_lower
3563 if upper is None:
3564 upper = old_upper
3566 self.set_xlim(sorted((lower, upper),
3567 reverse=bool(self.xaxis_inverted())),
3568 auto=None)
3570 def get_xlim(self):
3571 """
3572 Return the x-axis view limits.
3574 Returns
3575 -------
3576 left, right : (float, float)
3577 The current x-axis limits in data coordinates.
3579 See Also
3580 --------
3581 .Axes.set_xlim
3582 set_xbound, get_xbound
3583 invert_xaxis, xaxis_inverted
3585 Notes
3586 -----
3587 The x-axis may be inverted, in which case the *left* value will
3588 be greater than the *right* value.
3589 """
3590 return tuple(self.viewLim.intervalx)
3592 def _validate_converted_limits(self, limit, convert):
3593 """
3594 Raise ValueError if converted limits are non-finite.
3596 Note that this function also accepts None as a limit argument.
3598 Returns
3599 -------
3600 The limit value after call to convert(), or None if limit is None.
3601 """
3602 if limit is not None:
3603 converted_limit = convert(limit)
3604 if (isinstance(converted_limit, Real)
3605 and not np.isfinite(converted_limit)):
3606 raise ValueError("Axis limits cannot be NaN or Inf")
3607 return converted_limit
3609 @_api.make_keyword_only("3.6", "emit")
3610 def set_xlim(self, left=None, right=None, emit=True, auto=False,
3611 *, xmin=None, xmax=None):
3612 """
3613 Set the x-axis view limits.
3615 Parameters
3616 ----------
3617 left : float, optional
3618 The left xlim in data coordinates. Passing *None* leaves the
3619 limit unchanged.
3621 The left and right xlims may also be passed as the tuple
3622 (*left*, *right*) as the first positional argument (or as
3623 the *left* keyword argument).
3625 .. ACCEPTS: (bottom: float, top: float)
3627 right : float, optional
3628 The right xlim in data coordinates. Passing *None* leaves the
3629 limit unchanged.
3631 emit : bool, default: True
3632 Whether to notify observers of limit change.
3634 auto : bool or None, default: False
3635 Whether to turn on autoscaling of the x-axis. True turns on,
3636 False turns off, None leaves unchanged.
3638 xmin, xmax : float, optional
3639 They are equivalent to left and right respectively, and it is an
3640 error to pass both *xmin* and *left* or *xmax* and *right*.
3642 Returns
3643 -------
3644 left, right : (float, float)
3645 The new x-axis limits in data coordinates.
3647 See Also
3648 --------
3649 get_xlim
3650 set_xbound, get_xbound
3651 invert_xaxis, xaxis_inverted
3653 Notes
3654 -----
3655 The *left* value may be greater than the *right* value, in which
3656 case the x-axis values will decrease from left to right.
3658 Examples
3659 --------
3660 >>> set_xlim(left, right)
3661 >>> set_xlim((left, right))
3662 >>> left, right = set_xlim(left, right)
3664 One limit may be left unchanged.
3666 >>> set_xlim(right=right_lim)
3668 Limits may be passed in reverse order to flip the direction of
3669 the x-axis. For example, suppose *x* represents the number of
3670 years before present. The x-axis limits might be set like the
3671 following so 5000 years ago is on the left of the plot and the
3672 present is on the right.
3674 >>> set_xlim(5000, 0)
3675 """
3676 if right is None and np.iterable(left):
3677 left, right = left
3678 if xmin is not None:
3679 if left is not None:
3680 raise TypeError("Cannot pass both 'left' and 'xmin'")
3681 left = xmin
3682 if xmax is not None:
3683 if right is not None:
3684 raise TypeError("Cannot pass both 'right' and 'xmax'")
3685 right = xmax
3686 return self.xaxis._set_lim(left, right, emit=emit, auto=auto)
3688 get_xscale = _axis_method_wrapper("xaxis", "get_scale")
3689 set_xscale = _axis_method_wrapper("xaxis", "_set_axes_scale")
3690 get_xticks = _axis_method_wrapper("xaxis", "get_ticklocs")
3691 set_xticks = _axis_method_wrapper("xaxis", "set_ticks")
3692 get_xmajorticklabels = _axis_method_wrapper("xaxis", "get_majorticklabels")
3693 get_xminorticklabels = _axis_method_wrapper("xaxis", "get_minorticklabels")
3694 get_xticklabels = _axis_method_wrapper("xaxis", "get_ticklabels")
3695 set_xticklabels = _axis_method_wrapper(
3696 "xaxis", "_set_ticklabels",
3697 doc_sub={"Axis.set_ticks": "Axes.set_xticks"})
3699 def get_ylabel(self):
3700 """
3701 Get the ylabel text string.
3702 """
3703 label = self.yaxis.get_label()
3704 return label.get_text()
3706 def set_ylabel(self, ylabel, fontdict=None, labelpad=None, *,
3707 loc=None, **kwargs):
3708 """
3709 Set the label for the y-axis.
3711 Parameters
3712 ----------
3713 ylabel : str
3714 The label text.
3716 labelpad : float, default: :rc:`axes.labelpad`
3717 Spacing in points from the Axes bounding box including ticks
3718 and tick labels. If None, the previous value is left as is.
3720 loc : {'bottom', 'center', 'top'}, default: :rc:`yaxis.labellocation`
3721 The label position. This is a high-level alternative for passing
3722 parameters *y* and *horizontalalignment*.
3724 Other Parameters
3725 ----------------
3726 **kwargs : `.Text` properties
3727 `.Text` properties control the appearance of the label.
3729 See Also
3730 --------
3731 text : Documents the properties supported by `.Text`.
3732 """
3733 if labelpad is not None:
3734 self.yaxis.labelpad = labelpad
3735 protected_kw = ['y', 'horizontalalignment', 'ha']
3736 if {*kwargs} & {*protected_kw}:
3737 if loc is not None:
3738 raise TypeError(f"Specifying 'loc' is disallowed when any of "
3739 f"its corresponding low level keyword "
3740 f"arguments ({protected_kw}) are also "
3741 f"supplied")
3743 else:
3744 loc = (loc if loc is not None
3745 else mpl.rcParams['yaxis.labellocation'])
3746 _api.check_in_list(('bottom', 'center', 'top'), loc=loc)
3748 y, ha = {
3749 'bottom': (0, 'left'),
3750 'center': (0.5, 'center'),
3751 'top': (1, 'right')
3752 }[loc]
3753 kwargs.update(y=y, horizontalalignment=ha)
3755 return self.yaxis.set_label_text(ylabel, fontdict, **kwargs)
3757 def invert_yaxis(self):
3758 """
3759 Invert the y-axis.
3761 See Also
3762 --------
3763 yaxis_inverted
3764 get_ylim, set_ylim
3765 get_ybound, set_ybound
3766 """
3767 self.yaxis.set_inverted(not self.yaxis.get_inverted())
3769 yaxis_inverted = _axis_method_wrapper("yaxis", "get_inverted")
3771 def get_ybound(self):
3772 """
3773 Return the lower and upper y-axis bounds, in increasing order.
3775 See Also
3776 --------
3777 set_ybound
3778 get_ylim, set_ylim
3779 invert_yaxis, yaxis_inverted
3780 """
3781 bottom, top = self.get_ylim()
3782 if bottom < top:
3783 return bottom, top
3784 else:
3785 return top, bottom
3787 def set_ybound(self, lower=None, upper=None):
3788 """
3789 Set the lower and upper numerical bounds of the y-axis.
3791 This method will honor axis inversion regardless of parameter order.
3792 It will not change the autoscaling setting (`.get_autoscaley_on()`).
3794 Parameters
3795 ----------
3796 lower, upper : float or None
3797 The lower and upper bounds. If *None*, the respective axis bound
3798 is not modified.
3800 See Also
3801 --------
3802 get_ybound
3803 get_ylim, set_ylim
3804 invert_yaxis, yaxis_inverted
3805 """
3806 if upper is None and np.iterable(lower):
3807 lower, upper = lower
3809 old_lower, old_upper = self.get_ybound()
3810 if lower is None:
3811 lower = old_lower
3812 if upper is None:
3813 upper = old_upper
3815 self.set_ylim(sorted((lower, upper),
3816 reverse=bool(self.yaxis_inverted())),
3817 auto=None)
3819 def get_ylim(self):
3820 """
3821 Return the y-axis view limits.
3823 Returns
3824 -------
3825 bottom, top : (float, float)
3826 The current y-axis limits in data coordinates.
3828 See Also
3829 --------
3830 .Axes.set_ylim
3831 set_ybound, get_ybound
3832 invert_yaxis, yaxis_inverted
3834 Notes
3835 -----
3836 The y-axis may be inverted, in which case the *bottom* value
3837 will be greater than the *top* value.
3838 """
3839 return tuple(self.viewLim.intervaly)
3841 @_api.make_keyword_only("3.6", "emit")
3842 def set_ylim(self, bottom=None, top=None, emit=True, auto=False,
3843 *, ymin=None, ymax=None):
3844 """
3845 Set the y-axis view limits.
3847 Parameters
3848 ----------
3849 bottom : float, optional
3850 The bottom ylim in data coordinates. Passing *None* leaves the
3851 limit unchanged.
3853 The bottom and top ylims may also be passed as the tuple
3854 (*bottom*, *top*) as the first positional argument (or as
3855 the *bottom* keyword argument).
3857 .. ACCEPTS: (bottom: float, top: float)
3859 top : float, optional
3860 The top ylim in data coordinates. Passing *None* leaves the
3861 limit unchanged.
3863 emit : bool, default: True
3864 Whether to notify observers of limit change.
3866 auto : bool or None, default: False
3867 Whether to turn on autoscaling of the y-axis. *True* turns on,
3868 *False* turns off, *None* leaves unchanged.
3870 ymin, ymax : float, optional
3871 They are equivalent to bottom and top respectively, and it is an
3872 error to pass both *ymin* and *bottom* or *ymax* and *top*.
3874 Returns
3875 -------
3876 bottom, top : (float, float)
3877 The new y-axis limits in data coordinates.
3879 See Also
3880 --------
3881 get_ylim
3882 set_ybound, get_ybound
3883 invert_yaxis, yaxis_inverted
3885 Notes
3886 -----
3887 The *bottom* value may be greater than the *top* value, in which
3888 case the y-axis values will decrease from *bottom* to *top*.
3890 Examples
3891 --------
3892 >>> set_ylim(bottom, top)
3893 >>> set_ylim((bottom, top))
3894 >>> bottom, top = set_ylim(bottom, top)
3896 One limit may be left unchanged.
3898 >>> set_ylim(top=top_lim)
3900 Limits may be passed in reverse order to flip the direction of
3901 the y-axis. For example, suppose ``y`` represents depth of the
3902 ocean in m. The y-axis limits might be set like the following
3903 so 5000 m depth is at the bottom of the plot and the surface,
3904 0 m, is at the top.
3906 >>> set_ylim(5000, 0)
3907 """
3908 if top is None and np.iterable(bottom):
3909 bottom, top = bottom
3910 if ymin is not None:
3911 if bottom is not None:
3912 raise TypeError("Cannot pass both 'bottom' and 'ymin'")
3913 bottom = ymin
3914 if ymax is not None:
3915 if top is not None:
3916 raise TypeError("Cannot pass both 'top' and 'ymax'")
3917 top = ymax
3918 return self.yaxis._set_lim(bottom, top, emit=emit, auto=auto)
3920 get_yscale = _axis_method_wrapper("yaxis", "get_scale")
3921 set_yscale = _axis_method_wrapper("yaxis", "_set_axes_scale")
3922 get_yticks = _axis_method_wrapper("yaxis", "get_ticklocs")
3923 set_yticks = _axis_method_wrapper("yaxis", "set_ticks")
3924 get_ymajorticklabels = _axis_method_wrapper("yaxis", "get_majorticklabels")
3925 get_yminorticklabels = _axis_method_wrapper("yaxis", "get_minorticklabels")
3926 get_yticklabels = _axis_method_wrapper("yaxis", "get_ticklabels")
3927 set_yticklabels = _axis_method_wrapper(
3928 "yaxis", "_set_ticklabels",
3929 doc_sub={"Axis.set_ticks": "Axes.set_yticks"})
3931 xaxis_date = _axis_method_wrapper("xaxis", "axis_date")
3932 yaxis_date = _axis_method_wrapper("yaxis", "axis_date")
3934 def format_xdata(self, x):
3935 """
3936 Return *x* formatted as an x-value.
3938 This function will use the `.fmt_xdata` attribute if it is not None,
3939 else will fall back on the xaxis major formatter.
3940 """
3941 return (self.fmt_xdata if self.fmt_xdata is not None
3942 else self.xaxis.get_major_formatter().format_data_short)(x)
3944 def format_ydata(self, y):
3945 """
3946 Return *y* formatted as an y-value.
3948 This function will use the `.fmt_ydata` attribute if it is not None,
3949 else will fall back on the yaxis major formatter.
3950 """
3951 return (self.fmt_ydata if self.fmt_ydata is not None
3952 else self.yaxis.get_major_formatter().format_data_short)(y)
3954 def format_coord(self, x, y):
3955 """Return a format string formatting the *x*, *y* coordinates."""
3956 return "x={} y={}".format(
3957 "???" if x is None else self.format_xdata(x),
3958 "???" if y is None else self.format_ydata(y),
3959 )
3961 def minorticks_on(self):
3962 """
3963 Display minor ticks on the Axes.
3965 Displaying minor ticks may reduce performance; you may turn them off
3966 using `minorticks_off()` if drawing speed is a problem.
3967 """
3968 for ax in (self.xaxis, self.yaxis):
3969 scale = ax.get_scale()
3970 if scale == 'log':
3971 s = ax._scale
3972 ax.set_minor_locator(mticker.LogLocator(s.base, s.subs))
3973 elif scale == 'symlog':
3974 s = ax._scale
3975 ax.set_minor_locator(
3976 mticker.SymmetricalLogLocator(s._transform, s.subs))
3977 else:
3978 ax.set_minor_locator(mticker.AutoMinorLocator())
3980 def minorticks_off(self):
3981 """Remove minor ticks from the Axes."""
3982 self.xaxis.set_minor_locator(mticker.NullLocator())
3983 self.yaxis.set_minor_locator(mticker.NullLocator())
3985 # Interactive manipulation
3987 def can_zoom(self):
3988 """
3989 Return whether this Axes supports the zoom box button functionality.
3990 """
3991 return True
3993 def can_pan(self):
3994 """
3995 Return whether this Axes supports any pan/zoom button functionality.
3996 """
3997 return True
3999 def get_navigate(self):
4000 """
4001 Get whether the Axes responds to navigation commands.
4002 """
4003 return self._navigate
4005 def set_navigate(self, b):
4006 """
4007 Set whether the Axes responds to navigation toolbar commands.
4009 Parameters
4010 ----------
4011 b : bool
4012 """
4013 self._navigate = b
4015 def get_navigate_mode(self):
4016 """
4017 Get the navigation toolbar button status: 'PAN', 'ZOOM', or None.
4018 """
4019 return self._navigate_mode
4021 def set_navigate_mode(self, b):
4022 """
4023 Set the navigation toolbar button status.
4025 .. warning::
4026 This is not a user-API function.
4028 """
4029 self._navigate_mode = b
4031 def _get_view(self):
4032 """
4033 Save information required to reproduce the current view.
4035 Called before a view is changed, such as during a pan or zoom
4036 initiated by the user. You may return any information you deem
4037 necessary to describe the view.
4039 .. note::
4041 Intended to be overridden by new projection types, but if not, the
4042 default implementation saves the view limits. You *must* implement
4043 :meth:`_set_view` if you implement this method.
4044 """
4045 xmin, xmax = self.get_xlim()
4046 ymin, ymax = self.get_ylim()
4047 return xmin, xmax, ymin, ymax
4049 def _set_view(self, view):
4050 """
4051 Apply a previously saved view.
4053 Called when restoring a view, such as with the navigation buttons.
4055 .. note::
4057 Intended to be overridden by new projection types, but if not, the
4058 default implementation restores the view limits. You *must*
4059 implement :meth:`_get_view` if you implement this method.
4060 """
4061 xmin, xmax, ymin, ymax = view
4062 self.set_xlim((xmin, xmax))
4063 self.set_ylim((ymin, ymax))
4065 def _prepare_view_from_bbox(self, bbox, direction='in',
4066 mode=None, twinx=False, twiny=False):
4067 """
4068 Helper function to prepare the new bounds from a bbox.
4070 This helper function returns the new x and y bounds from the zoom
4071 bbox. This a convenience method to abstract the bbox logic
4072 out of the base setter.
4073 """
4074 if len(bbox) == 3:
4075 xp, yp, scl = bbox # Zooming code
4076 if scl == 0: # Should not happen
4077 scl = 1.
4078 if scl > 1:
4079 direction = 'in'
4080 else:
4081 direction = 'out'
4082 scl = 1/scl
4083 # get the limits of the axes
4084 (xmin, ymin), (xmax, ymax) = self.transData.transform(
4085 np.transpose([self.get_xlim(), self.get_ylim()]))
4086 # set the range
4087 xwidth = xmax - xmin
4088 ywidth = ymax - ymin
4089 xcen = (xmax + xmin)*.5
4090 ycen = (ymax + ymin)*.5
4091 xzc = (xp*(scl - 1) + xcen)/scl
4092 yzc = (yp*(scl - 1) + ycen)/scl
4093 bbox = [xzc - xwidth/2./scl, yzc - ywidth/2./scl,
4094 xzc + xwidth/2./scl, yzc + ywidth/2./scl]
4095 elif len(bbox) != 4:
4096 # should be len 3 or 4 but nothing else
4097 _api.warn_external(
4098 "Warning in _set_view_from_bbox: bounding box is not a tuple "
4099 "of length 3 or 4. Ignoring the view change.")
4100 return
4102 # Original limits.
4103 xmin0, xmax0 = self.get_xbound()
4104 ymin0, ymax0 = self.get_ybound()
4105 # The zoom box in screen coords.
4106 startx, starty, stopx, stopy = bbox
4107 # Convert to data coords.
4108 (startx, starty), (stopx, stopy) = self.transData.inverted().transform(
4109 [(startx, starty), (stopx, stopy)])
4110 # Clip to axes limits.
4111 xmin, xmax = np.clip(sorted([startx, stopx]), xmin0, xmax0)
4112 ymin, ymax = np.clip(sorted([starty, stopy]), ymin0, ymax0)
4113 # Don't double-zoom twinned axes or if zooming only the other axis.
4114 if twinx or mode == "y":
4115 xmin, xmax = xmin0, xmax0
4116 if twiny or mode == "x":
4117 ymin, ymax = ymin0, ymax0
4119 if direction == "in":
4120 new_xbound = xmin, xmax
4121 new_ybound = ymin, ymax
4123 elif direction == "out":
4124 x_trf = self.xaxis.get_transform()
4125 sxmin0, sxmax0, sxmin, sxmax = x_trf.transform(
4126 [xmin0, xmax0, xmin, xmax]) # To screen space.
4127 factor = (sxmax0 - sxmin0) / (sxmax - sxmin) # Unzoom factor.
4128 # Move original bounds away by
4129 # (factor) x (distance between unzoom box and Axes bbox).
4130 sxmin1 = sxmin0 - factor * (sxmin - sxmin0)
4131 sxmax1 = sxmax0 + factor * (sxmax0 - sxmax)
4132 # And back to data space.
4133 new_xbound = x_trf.inverted().transform([sxmin1, sxmax1])
4135 y_trf = self.yaxis.get_transform()
4136 symin0, symax0, symin, symax = y_trf.transform(
4137 [ymin0, ymax0, ymin, ymax])
4138 factor = (symax0 - symin0) / (symax - symin)
4139 symin1 = symin0 - factor * (symin - symin0)
4140 symax1 = symax0 + factor * (symax0 - symax)
4141 new_ybound = y_trf.inverted().transform([symin1, symax1])
4143 return new_xbound, new_ybound
4145 def _set_view_from_bbox(self, bbox, direction='in',
4146 mode=None, twinx=False, twiny=False):
4147 """
4148 Update view from a selection bbox.
4150 .. note::
4152 Intended to be overridden by new projection types, but if not, the
4153 default implementation sets the view limits to the bbox directly.
4155 Parameters
4156 ----------
4157 bbox : 4-tuple or 3 tuple
4158 * If bbox is a 4 tuple, it is the selected bounding box limits,
4159 in *display* coordinates.
4160 * If bbox is a 3 tuple, it is an (xp, yp, scl) triple, where
4161 (xp, yp) is the center of zooming and scl the scale factor to
4162 zoom by.
4164 direction : str
4165 The direction to apply the bounding box.
4166 * `'in'` - The bounding box describes the view directly, i.e.,
4167 it zooms in.
4168 * `'out'` - The bounding box describes the size to make the
4169 existing view, i.e., it zooms out.
4171 mode : str or None
4172 The selection mode, whether to apply the bounding box in only the
4173 `'x'` direction, `'y'` direction or both (`None`).
4175 twinx : bool
4176 Whether this axis is twinned in the *x*-direction.
4178 twiny : bool
4179 Whether this axis is twinned in the *y*-direction.
4180 """
4181 new_xbound, new_ybound = self._prepare_view_from_bbox(
4182 bbox, direction=direction, mode=mode, twinx=twinx, twiny=twiny)
4183 if not twinx and mode != "y":
4184 self.set_xbound(new_xbound)
4185 self.set_autoscalex_on(False)
4186 if not twiny and mode != "x":
4187 self.set_ybound(new_ybound)
4188 self.set_autoscaley_on(False)
4190 def start_pan(self, x, y, button):
4191 """
4192 Called when a pan operation has started.
4194 Parameters
4195 ----------
4196 x, y : float
4197 The mouse coordinates in display coords.
4198 button : `.MouseButton`
4199 The pressed mouse button.
4201 Notes
4202 -----
4203 This is intended to be overridden by new projection types.
4204 """
4205 self._pan_start = types.SimpleNamespace(
4206 lim=self.viewLim.frozen(),
4207 trans=self.transData.frozen(),
4208 trans_inverse=self.transData.inverted().frozen(),
4209 bbox=self.bbox.frozen(),
4210 x=x,
4211 y=y)
4213 def end_pan(self):
4214 """
4215 Called when a pan operation completes (when the mouse button is up.)
4217 Notes
4218 -----
4219 This is intended to be overridden by new projection types.
4220 """
4221 del self._pan_start
4223 def _get_pan_points(self, button, key, x, y):
4224 """
4225 Helper function to return the new points after a pan.
4227 This helper function returns the points on the axis after a pan has
4228 occurred. This is a convenience method to abstract the pan logic
4229 out of the base setter.
4230 """
4231 def format_deltas(key, dx, dy):
4232 if key == 'control':
4233 if abs(dx) > abs(dy):
4234 dy = dx
4235 else:
4236 dx = dy
4237 elif key == 'x':
4238 dy = 0
4239 elif key == 'y':
4240 dx = 0
4241 elif key == 'shift':
4242 if 2 * abs(dx) < abs(dy):
4243 dx = 0
4244 elif 2 * abs(dy) < abs(dx):
4245 dy = 0
4246 elif abs(dx) > abs(dy):
4247 dy = dy / abs(dy) * abs(dx)
4248 else:
4249 dx = dx / abs(dx) * abs(dy)
4250 return dx, dy
4252 p = self._pan_start
4253 dx = x - p.x
4254 dy = y - p.y
4255 if dx == dy == 0:
4256 return
4257 if button == 1:
4258 dx, dy = format_deltas(key, dx, dy)
4259 result = p.bbox.translated(-dx, -dy).transformed(p.trans_inverse)
4260 elif button == 3:
4261 try:
4262 dx = -dx / self.bbox.width
4263 dy = -dy / self.bbox.height
4264 dx, dy = format_deltas(key, dx, dy)
4265 if self.get_aspect() != 'auto':
4266 dx = dy = 0.5 * (dx + dy)
4267 alpha = np.power(10.0, (dx, dy))
4268 start = np.array([p.x, p.y])
4269 oldpoints = p.lim.transformed(p.trans)
4270 newpoints = start + alpha * (oldpoints - start)
4271 result = (mtransforms.Bbox(newpoints)
4272 .transformed(p.trans_inverse))
4273 except OverflowError:
4274 _api.warn_external('Overflow while panning')
4275 return
4276 else:
4277 return
4279 valid = np.isfinite(result.transformed(p.trans))
4280 points = result.get_points().astype(object)
4281 # Just ignore invalid limits (typically, underflow in log-scale).
4282 points[~valid] = None
4283 return points
4285 def drag_pan(self, button, key, x, y):
4286 """
4287 Called when the mouse moves during a pan operation.
4289 Parameters
4290 ----------
4291 button : `.MouseButton`
4292 The pressed mouse button.
4293 key : str or None
4294 The pressed key, if any.
4295 x, y : float
4296 The mouse coordinates in display coords.
4298 Notes
4299 -----
4300 This is intended to be overridden by new projection types.
4301 """
4302 points = self._get_pan_points(button, key, x, y)
4303 if points is not None:
4304 self.set_xlim(points[:, 0])
4305 self.set_ylim(points[:, 1])
4307 def get_children(self):
4308 # docstring inherited.
4309 return [
4310 *self._children,
4311 *self.spines.values(),
4312 *self._axis_map.values(),
4313 self.title, self._left_title, self._right_title,
4314 *self.child_axes,
4315 *([self.legend_] if self.legend_ is not None else []),
4316 self.patch,
4317 ]
4319 def contains(self, mouseevent):
4320 # docstring inherited.
4321 inside, info = self._default_contains(mouseevent)
4322 if inside is not None:
4323 return inside, info
4324 return self.patch.contains(mouseevent)
4326 def contains_point(self, point):
4327 """
4328 Return whether *point* (pair of pixel coordinates) is inside the Axes
4329 patch.
4330 """
4331 return self.patch.contains_point(point, radius=1.0)
4333 def get_default_bbox_extra_artists(self):
4334 """
4335 Return a default list of artists that are used for the bounding box
4336 calculation.
4338 Artists are excluded either by not being visible or
4339 ``artist.set_in_layout(False)``.
4340 """
4342 artists = self.get_children()
4344 for axis in self._axis_map.values():
4345 # axis tight bboxes are calculated separately inside
4346 # Axes.get_tightbbox() using for_layout_only=True
4347 artists.remove(axis)
4348 if not (self.axison and self._frameon):
4349 # don't do bbox on spines if frame not on.
4350 for spine in self.spines.values():
4351 artists.remove(spine)
4353 artists.remove(self.title)
4354 artists.remove(self._left_title)
4355 artists.remove(self._right_title)
4357 # always include types that do not internally implement clipping
4358 # to Axes. may have clip_on set to True and clip_box equivalent
4359 # to ax.bbox but then ignore these properties during draws.
4360 noclip = (_AxesBase, maxis.Axis,
4361 offsetbox.AnnotationBbox, offsetbox.OffsetBox)
4362 return [a for a in artists if a.get_visible() and a.get_in_layout()
4363 and (isinstance(a, noclip) or not a._fully_clipped_to_axes())]
4365 def get_tightbbox(self, renderer=None, call_axes_locator=True,
4366 bbox_extra_artists=None, *, for_layout_only=False):
4367 """
4368 Return the tight bounding box of the Axes, including axis and their
4369 decorators (xlabel, title, etc).
4371 Artists that have ``artist.set_in_layout(False)`` are not included
4372 in the bbox.
4374 Parameters
4375 ----------
4376 renderer : `.RendererBase` subclass
4377 renderer that will be used to draw the figures (i.e.
4378 ``fig.canvas.get_renderer()``)
4380 bbox_extra_artists : list of `.Artist` or ``None``
4381 List of artists to include in the tight bounding box. If
4382 ``None`` (default), then all artist children of the Axes are
4383 included in the tight bounding box.
4385 call_axes_locator : bool, default: True
4386 If *call_axes_locator* is ``False``, it does not call the
4387 ``_axes_locator`` attribute, which is necessary to get the correct
4388 bounding box. ``call_axes_locator=False`` can be used if the
4389 caller is only interested in the relative size of the tightbbox
4390 compared to the Axes bbox.
4392 for_layout_only : default: False
4393 The bounding box will *not* include the x-extent of the title and
4394 the xlabel, or the y-extent of the ylabel.
4396 Returns
4397 -------
4398 `.BboxBase`
4399 Bounding box in figure pixel coordinates.
4401 See Also
4402 --------
4403 matplotlib.axes.Axes.get_window_extent
4404 matplotlib.axis.Axis.get_tightbbox
4405 matplotlib.spines.Spine.get_window_extent
4406 """
4408 bb = []
4409 if renderer is None:
4410 renderer = self.figure._get_renderer()
4412 if not self.get_visible():
4413 return None
4415 locator = self.get_axes_locator()
4416 self.apply_aspect(
4417 locator(self, renderer) if locator and call_axes_locator else None)
4419 for axis in self._axis_map.values():
4420 if self.axison and axis.get_visible():
4421 ba = martist._get_tightbbox_for_layout_only(axis, renderer)
4422 if ba:
4423 bb.append(ba)
4424 self._update_title_position(renderer)
4425 axbbox = self.get_window_extent(renderer)
4426 bb.append(axbbox)
4428 for title in [self.title, self._left_title, self._right_title]:
4429 if title.get_visible():
4430 bt = title.get_window_extent(renderer)
4431 if for_layout_only and bt.width > 0:
4432 # make the title bbox 1 pixel wide so its width
4433 # is not accounted for in bbox calculations in
4434 # tight/constrained_layout
4435 bt.x0 = (bt.x0 + bt.x1) / 2 - 0.5
4436 bt.x1 = bt.x0 + 1.0
4437 bb.append(bt)
4439 bbox_artists = bbox_extra_artists
4440 if bbox_artists is None:
4441 bbox_artists = self.get_default_bbox_extra_artists()
4443 for a in bbox_artists:
4444 bbox = a.get_tightbbox(renderer)
4445 if (bbox is not None
4446 and 0 < bbox.width < np.inf
4447 and 0 < bbox.height < np.inf):
4448 bb.append(bbox)
4449 return mtransforms.Bbox.union(
4450 [b for b in bb if b.width != 0 or b.height != 0])
4452 def _make_twin_axes(self, *args, **kwargs):
4453 """Make a twinx Axes of self. This is used for twinx and twiny."""
4454 # Typically, SubplotBase._make_twin_axes is called instead of this.
4455 if 'sharex' in kwargs and 'sharey' in kwargs:
4456 raise ValueError("Twinned Axes may share only one axis")
4457 ax2 = self.figure.add_axes(
4458 self.get_position(True), *args, **kwargs,
4459 axes_locator=_TransformedBoundsLocator(
4460 [0, 0, 1, 1], self.transAxes))
4461 self.set_adjustable('datalim')
4462 ax2.set_adjustable('datalim')
4463 self._twinned_axes.join(self, ax2)
4464 return ax2
4466 def twinx(self):
4467 """
4468 Create a twin Axes sharing the xaxis.
4470 Create a new Axes with an invisible x-axis and an independent
4471 y-axis positioned opposite to the original one (i.e. at right). The
4472 x-axis autoscale setting will be inherited from the original
4473 Axes. To ensure that the tick marks of both y-axes align, see
4474 `~matplotlib.ticker.LinearLocator`.
4476 Returns
4477 -------
4478 Axes
4479 The newly created Axes instance
4481 Notes
4482 -----
4483 For those who are 'picking' artists while using twinx, pick
4484 events are only called for the artists in the top-most Axes.
4485 """
4486 ax2 = self._make_twin_axes(sharex=self)
4487 ax2.yaxis.tick_right()
4488 ax2.yaxis.set_label_position('right')
4489 ax2.yaxis.set_offset_position('right')
4490 ax2.set_autoscalex_on(self.get_autoscalex_on())
4491 self.yaxis.tick_left()
4492 ax2.xaxis.set_visible(False)
4493 ax2.patch.set_visible(False)
4494 return ax2
4496 def twiny(self):
4497 """
4498 Create a twin Axes sharing the yaxis.
4500 Create a new Axes with an invisible y-axis and an independent
4501 x-axis positioned opposite to the original one (i.e. at top). The
4502 y-axis autoscale setting will be inherited from the original Axes.
4503 To ensure that the tick marks of both x-axes align, see
4504 `~matplotlib.ticker.LinearLocator`.
4506 Returns
4507 -------
4508 Axes
4509 The newly created Axes instance
4511 Notes
4512 -----
4513 For those who are 'picking' artists while using twiny, pick
4514 events are only called for the artists in the top-most Axes.
4515 """
4516 ax2 = self._make_twin_axes(sharey=self)
4517 ax2.xaxis.tick_top()
4518 ax2.xaxis.set_label_position('top')
4519 ax2.set_autoscaley_on(self.get_autoscaley_on())
4520 self.xaxis.tick_bottom()
4521 ax2.yaxis.set_visible(False)
4522 ax2.patch.set_visible(False)
4523 return ax2
4525 def get_shared_x_axes(self):
4526 """Return an immutable view on the shared x-axes Grouper."""
4527 return cbook.GrouperView(self._shared_axes["x"])
4529 def get_shared_y_axes(self):
4530 """Return an immutable view on the shared y-axes Grouper."""
4531 return cbook.GrouperView(self._shared_axes["y"])