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