1from collections import namedtuple
2import contextlib
3from functools import cache, wraps
4import inspect
5from inspect import Signature, Parameter
6import logging
7from numbers import Number, Real
8import re
9import warnings
10
11import numpy as np
12
13import matplotlib as mpl
14from . import _api, cbook
15from .colors import BoundaryNorm
16from .cm import ScalarMappable
17from .path import Path
18from .transforms import (BboxBase, Bbox, IdentityTransform, Transform, TransformedBbox,
19 TransformedPatchPath, TransformedPath)
20
21_log = logging.getLogger(__name__)
22
23
24def _prevent_rasterization(draw):
25 # We assume that by default artists are not allowed to rasterize (unless
26 # its draw method is explicitly decorated). If it is being drawn after a
27 # rasterized artist and it has reached a raster_depth of 0, we stop
28 # rasterization so that it does not affect the behavior of normal artist
29 # (e.g., change in dpi).
30
31 @wraps(draw)
32 def draw_wrapper(artist, renderer, *args, **kwargs):
33 if renderer._raster_depth == 0 and renderer._rasterizing:
34 # Only stop when we are not in a rasterized parent
35 # and something has been rasterized since last stop.
36 renderer.stop_rasterizing()
37 renderer._rasterizing = False
38
39 return draw(artist, renderer, *args, **kwargs)
40
41 draw_wrapper._supports_rasterization = False
42 return draw_wrapper
43
44
45def allow_rasterization(draw):
46 """
47 Decorator for Artist.draw method. Provides routines
48 that run before and after the draw call. The before and after functions
49 are useful for changing artist-dependent renderer attributes or making
50 other setup function calls, such as starting and flushing a mixed-mode
51 renderer.
52 """
53
54 @wraps(draw)
55 def draw_wrapper(artist, renderer):
56 try:
57 if artist.get_rasterized():
58 if renderer._raster_depth == 0 and not renderer._rasterizing:
59 renderer.start_rasterizing()
60 renderer._rasterizing = True
61 renderer._raster_depth += 1
62 else:
63 if renderer._raster_depth == 0 and renderer._rasterizing:
64 # Only stop when we are not in a rasterized parent
65 # and something has be rasterized since last stop
66 renderer.stop_rasterizing()
67 renderer._rasterizing = False
68
69 if artist.get_agg_filter() is not None:
70 renderer.start_filter()
71
72 return draw(artist, renderer)
73 finally:
74 if artist.get_agg_filter() is not None:
75 renderer.stop_filter(artist.get_agg_filter())
76 if artist.get_rasterized():
77 renderer._raster_depth -= 1
78 if (renderer._rasterizing and artist.figure and
79 artist.figure.suppressComposite):
80 # restart rasterizing to prevent merging
81 renderer.stop_rasterizing()
82 renderer.start_rasterizing()
83
84 draw_wrapper._supports_rasterization = True
85 return draw_wrapper
86
87
88def _finalize_rasterization(draw):
89 """
90 Decorator for Artist.draw method. Needed on the outermost artist, i.e.
91 Figure, to finish up if the render is still in rasterized mode.
92 """
93 @wraps(draw)
94 def draw_wrapper(artist, renderer, *args, **kwargs):
95 result = draw(artist, renderer, *args, **kwargs)
96 if renderer._rasterizing:
97 renderer.stop_rasterizing()
98 renderer._rasterizing = False
99 return result
100 return draw_wrapper
101
102
103def _stale_axes_callback(self, val):
104 if self.axes:
105 self.axes.stale = val
106
107
108_XYPair = namedtuple("_XYPair", "x y")
109
110
111class _Unset:
112 def __repr__(self):
113 return "<UNSET>"
114_UNSET = _Unset()
115
116
117class Artist:
118 """
119 Abstract base class for objects that render into a FigureCanvas.
120
121 Typically, all visible elements in a figure are subclasses of Artist.
122 """
123
124 zorder = 0
125
126 def __init_subclass__(cls):
127
128 # Decorate draw() method so that all artists are able to stop
129 # rastrization when necessary. If the artist's draw method is already
130 # decorated (has a `_supports_rasterization` attribute), it won't be
131 # decorated.
132
133 if not hasattr(cls.draw, "_supports_rasterization"):
134 cls.draw = _prevent_rasterization(cls.draw)
135
136 # Inject custom set() methods into the subclass with signature and
137 # docstring based on the subclasses' properties.
138
139 if not hasattr(cls.set, '_autogenerated_signature'):
140 # Don't overwrite cls.set if the subclass or one of its parents
141 # has defined a set method set itself.
142 # If there was no explicit definition, cls.set is inherited from
143 # the hierarchy of auto-generated set methods, which hold the
144 # flag _autogenerated_signature.
145 return
146
147 cls.set = lambda self, **kwargs: Artist.set(self, **kwargs)
148 cls.set.__name__ = "set"
149 cls.set.__qualname__ = f"{cls.__qualname__}.set"
150 cls._update_set_signature_and_docstring()
151
152 _PROPERTIES_EXCLUDED_FROM_SET = [
153 'navigate_mode', # not a user-facing function
154 'figure', # changing the figure is such a profound operation
155 # that we don't want this in set()
156 '3d_properties', # cannot be used as a keyword due to leading digit
157 ]
158
159 @classmethod
160 def _update_set_signature_and_docstring(cls):
161 """
162 Update the signature of the set function to list all properties
163 as keyword arguments.
164
165 Property aliases are not listed in the signature for brevity, but
166 are still accepted as keyword arguments.
167 """
168 cls.set.__signature__ = Signature(
169 [Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
170 *[Parameter(prop, Parameter.KEYWORD_ONLY, default=_UNSET)
171 for prop in ArtistInspector(cls).get_setters()
172 if prop not in Artist._PROPERTIES_EXCLUDED_FROM_SET]])
173 cls.set._autogenerated_signature = True
174
175 cls.set.__doc__ = (
176 "Set multiple properties at once.\n\n"
177 "Supported properties are\n\n"
178 + kwdoc(cls))
179
180 def __init__(self):
181 self._stale = True
182 self.stale_callback = None
183 self._axes = None
184 self.figure = None
185
186 self._transform = None
187 self._transformSet = False
188 self._visible = True
189 self._animated = False
190 self._alpha = None
191 self.clipbox = None
192 self._clippath = None
193 self._clipon = True
194 self._label = ''
195 self._picker = None
196 self._rasterized = False
197 self._agg_filter = None
198 # Normally, artist classes need to be queried for mouseover info if and
199 # only if they override get_cursor_data.
200 self._mouseover = type(self).get_cursor_data != Artist.get_cursor_data
201 self._callbacks = cbook.CallbackRegistry(signals=["pchanged"])
202 try:
203 self.axes = None
204 except AttributeError:
205 # Handle self.axes as a read-only property, as in Figure.
206 pass
207 self._remove_method = None
208 self._url = None
209 self._gid = None
210 self._snap = None
211 self._sketch = mpl.rcParams['path.sketch']
212 self._path_effects = mpl.rcParams['path.effects']
213 self._sticky_edges = _XYPair([], [])
214 self._in_layout = True
215
216 def __getstate__(self):
217 d = self.__dict__.copy()
218 d['stale_callback'] = None
219 return d
220
221 def remove(self):
222 """
223 Remove the artist from the figure if possible.
224
225 The effect will not be visible until the figure is redrawn, e.g.,
226 with `.FigureCanvasBase.draw_idle`. Call `~.axes.Axes.relim` to
227 update the Axes limits if desired.
228
229 Note: `~.axes.Axes.relim` will not see collections even if the
230 collection was added to the Axes with *autolim* = True.
231
232 Note: there is no support for removing the artist's legend entry.
233 """
234
235 # There is no method to set the callback. Instead, the parent should
236 # set the _remove_method attribute directly. This would be a
237 # protected attribute if Python supported that sort of thing. The
238 # callback has one parameter, which is the child to be removed.
239 if self._remove_method is not None:
240 self._remove_method(self)
241 # clear stale callback
242 self.stale_callback = None
243 _ax_flag = False
244 if hasattr(self, 'axes') and self.axes:
245 # remove from the mouse hit list
246 self.axes._mouseover_set.discard(self)
247 self.axes.stale = True
248 self.axes = None # decouple the artist from the Axes
249 _ax_flag = True
250
251 if self.figure:
252 if not _ax_flag:
253 self.figure.stale = True
254 self.figure = None
255
256 else:
257 raise NotImplementedError('cannot remove artist')
258 # TODO: the fix for the collections relim problem is to move the
259 # limits calculation into the artist itself, including the property of
260 # whether or not the artist should affect the limits. Then there will
261 # be no distinction between axes.add_line, axes.add_patch, etc.
262 # TODO: add legend support
263
264 def have_units(self):
265 """Return whether units are set on any axis."""
266 ax = self.axes
267 return ax and any(axis.have_units() for axis in ax._axis_map.values())
268
269 def convert_xunits(self, x):
270 """
271 Convert *x* using the unit type of the xaxis.
272
273 If the artist is not contained in an Axes or if the xaxis does not
274 have units, *x* itself is returned.
275 """
276 ax = getattr(self, 'axes', None)
277 if ax is None or ax.xaxis is None:
278 return x
279 return ax.xaxis.convert_units(x)
280
281 def convert_yunits(self, y):
282 """
283 Convert *y* using the unit type of the yaxis.
284
285 If the artist is not contained in an Axes or if the yaxis does not
286 have units, *y* itself is returned.
287 """
288 ax = getattr(self, 'axes', None)
289 if ax is None or ax.yaxis is None:
290 return y
291 return ax.yaxis.convert_units(y)
292
293 @property
294 def axes(self):
295 """The `~.axes.Axes` instance the artist resides in, or *None*."""
296 return self._axes
297
298 @axes.setter
299 def axes(self, new_axes):
300 if (new_axes is not None and self._axes is not None
301 and new_axes != self._axes):
302 raise ValueError("Can not reset the Axes. You are probably trying to reuse "
303 "an artist in more than one Axes which is not supported")
304 self._axes = new_axes
305 if new_axes is not None and new_axes is not self:
306 self.stale_callback = _stale_axes_callback
307
308 @property
309 def stale(self):
310 """
311 Whether the artist is 'stale' and needs to be re-drawn for the output
312 to match the internal state of the artist.
313 """
314 return self._stale
315
316 @stale.setter
317 def stale(self, val):
318 self._stale = val
319
320 # if the artist is animated it does not take normal part in the
321 # draw stack and is not expected to be drawn as part of the normal
322 # draw loop (when not saving) so do not propagate this change
323 if self._animated:
324 return
325
326 if val and self.stale_callback is not None:
327 self.stale_callback(self, val)
328
329 def get_window_extent(self, renderer=None):
330 """
331 Get the artist's bounding box in display space.
332
333 The bounding box' width and height are nonnegative.
334
335 Subclasses should override for inclusion in the bounding box
336 "tight" calculation. Default is to return an empty bounding
337 box at 0, 0.
338
339 Be careful when using this function, the results will not update
340 if the artist window extent of the artist changes. The extent
341 can change due to any changes in the transform stack, such as
342 changing the Axes limits, the figure size, or the canvas used
343 (as is done when saving a figure). This can lead to unexpected
344 behavior where interactive figures will look fine on the screen,
345 but will save incorrectly.
346 """
347 return Bbox([[0, 0], [0, 0]])
348
349 def get_tightbbox(self, renderer=None):
350 """
351 Like `.Artist.get_window_extent`, but includes any clipping.
352
353 Parameters
354 ----------
355 renderer : `~matplotlib.backend_bases.RendererBase` subclass, optional
356 renderer that will be used to draw the figures (i.e.
357 ``fig.canvas.get_renderer()``)
358
359 Returns
360 -------
361 `.Bbox` or None
362 The enclosing bounding box (in figure pixel coordinates).
363 Returns None if clipping results in no intersection.
364 """
365 bbox = self.get_window_extent(renderer)
366 if self.get_clip_on():
367 clip_box = self.get_clip_box()
368 if clip_box is not None:
369 bbox = Bbox.intersection(bbox, clip_box)
370 clip_path = self.get_clip_path()
371 if clip_path is not None and bbox is not None:
372 clip_path = clip_path.get_fully_transformed_path()
373 bbox = Bbox.intersection(bbox, clip_path.get_extents())
374 return bbox
375
376 def add_callback(self, func):
377 """
378 Add a callback function that will be called whenever one of the
379 `.Artist`'s properties changes.
380
381 Parameters
382 ----------
383 func : callable
384 The callback function. It must have the signature::
385
386 def func(artist: Artist) -> Any
387
388 where *artist* is the calling `.Artist`. Return values may exist
389 but are ignored.
390
391 Returns
392 -------
393 int
394 The observer id associated with the callback. This id can be
395 used for removing the callback with `.remove_callback` later.
396
397 See Also
398 --------
399 remove_callback
400 """
401 # Wrapping func in a lambda ensures it can be connected multiple times
402 # and never gets weakref-gc'ed.
403 return self._callbacks.connect("pchanged", lambda: func(self))
404
405 def remove_callback(self, oid):
406 """
407 Remove a callback based on its observer id.
408
409 See Also
410 --------
411 add_callback
412 """
413 self._callbacks.disconnect(oid)
414
415 def pchanged(self):
416 """
417 Call all of the registered callbacks.
418
419 This function is triggered internally when a property is changed.
420
421 See Also
422 --------
423 add_callback
424 remove_callback
425 """
426 self._callbacks.process("pchanged")
427
428 def is_transform_set(self):
429 """
430 Return whether the Artist has an explicitly set transform.
431
432 This is *True* after `.set_transform` has been called.
433 """
434 return self._transformSet
435
436 def set_transform(self, t):
437 """
438 Set the artist transform.
439
440 Parameters
441 ----------
442 t : `~matplotlib.transforms.Transform`
443 """
444 self._transform = t
445 self._transformSet = True
446 self.pchanged()
447 self.stale = True
448
449 def get_transform(self):
450 """Return the `.Transform` instance used by this artist."""
451 if self._transform is None:
452 self._transform = IdentityTransform()
453 elif (not isinstance(self._transform, Transform)
454 and hasattr(self._transform, '_as_mpl_transform')):
455 self._transform = self._transform._as_mpl_transform(self.axes)
456 return self._transform
457
458 def get_children(self):
459 r"""Return a list of the child `.Artist`\s of this `.Artist`."""
460 return []
461
462 def _different_canvas(self, event):
463 """
464 Check whether an *event* occurred on a canvas other that this artist's canvas.
465
466 If this method returns True, the event definitely occurred on a different
467 canvas; if it returns False, either it occurred on the same canvas, or we may
468 not have enough information to know.
469
470 Subclasses should start their definition of `contains` as follows::
471
472 if self._different_canvas(mouseevent):
473 return False, {}
474 # subclass-specific implementation follows
475 """
476 return (getattr(event, "canvas", None) is not None and self.figure is not None
477 and event.canvas is not self.figure.canvas)
478
479 def contains(self, mouseevent):
480 """
481 Test whether the artist contains the mouse event.
482
483 Parameters
484 ----------
485 mouseevent : `~matplotlib.backend_bases.MouseEvent`
486
487 Returns
488 -------
489 contains : bool
490 Whether any values are within the radius.
491 details : dict
492 An artist-specific dictionary of details of the event context,
493 such as which points are contained in the pick radius. See the
494 individual Artist subclasses for details.
495 """
496 _log.warning("%r needs 'contains' method", self.__class__.__name__)
497 return False, {}
498
499 def pickable(self):
500 """
501 Return whether the artist is pickable.
502
503 See Also
504 --------
505 .Artist.set_picker, .Artist.get_picker, .Artist.pick
506 """
507 return self.figure is not None and self._picker is not None
508
509 def pick(self, mouseevent):
510 """
511 Process a pick event.
512
513 Each child artist will fire a pick event if *mouseevent* is over
514 the artist and the artist has picker set.
515
516 See Also
517 --------
518 .Artist.set_picker, .Artist.get_picker, .Artist.pickable
519 """
520 from .backend_bases import PickEvent # Circular import.
521 # Pick self
522 if self.pickable():
523 picker = self.get_picker()
524 if callable(picker):
525 inside, prop = picker(self, mouseevent)
526 else:
527 inside, prop = self.contains(mouseevent)
528 if inside:
529 PickEvent("pick_event", self.figure.canvas,
530 mouseevent, self, **prop)._process()
531
532 # Pick children
533 for a in self.get_children():
534 # make sure the event happened in the same Axes
535 ax = getattr(a, 'axes', None)
536 if (isinstance(a, mpl.figure.SubFigure)
537 or mouseevent.inaxes is None or ax is None
538 or mouseevent.inaxes == ax):
539 # we need to check if mouseevent.inaxes is None
540 # because some objects associated with an Axes (e.g., a
541 # tick label) can be outside the bounding box of the
542 # Axes and inaxes will be None
543 # also check that ax is None so that it traverse objects
544 # which do not have an axes property but children might
545 a.pick(mouseevent)
546
547 def set_picker(self, picker):
548 """
549 Define the picking behavior of the artist.
550
551 Parameters
552 ----------
553 picker : None or bool or float or callable
554 This can be one of the following:
555
556 - *None*: Picking is disabled for this artist (default).
557
558 - A boolean: If *True* then picking will be enabled and the
559 artist will fire a pick event if the mouse event is over
560 the artist.
561
562 - A float: If picker is a number it is interpreted as an
563 epsilon tolerance in points and the artist will fire
564 off an event if its data is within epsilon of the mouse
565 event. For some artists like lines and patch collections,
566 the artist may provide additional data to the pick event
567 that is generated, e.g., the indices of the data within
568 epsilon of the pick event
569
570 - A function: If picker is callable, it is a user supplied
571 function which determines whether the artist is hit by the
572 mouse event::
573
574 hit, props = picker(artist, mouseevent)
575
576 to determine the hit test. if the mouse event is over the
577 artist, return *hit=True* and props is a dictionary of
578 properties you want added to the PickEvent attributes.
579 """
580 self._picker = picker
581
582 def get_picker(self):
583 """
584 Return the picking behavior of the artist.
585
586 The possible values are described in `.Artist.set_picker`.
587
588 See Also
589 --------
590 .Artist.set_picker, .Artist.pickable, .Artist.pick
591 """
592 return self._picker
593
594 def get_url(self):
595 """Return the url."""
596 return self._url
597
598 def set_url(self, url):
599 """
600 Set the url for the artist.
601
602 Parameters
603 ----------
604 url : str
605 """
606 self._url = url
607
608 def get_gid(self):
609 """Return the group id."""
610 return self._gid
611
612 def set_gid(self, gid):
613 """
614 Set the (group) id for the artist.
615
616 Parameters
617 ----------
618 gid : str
619 """
620 self._gid = gid
621
622 def get_snap(self):
623 """
624 Return the snap setting.
625
626 See `.set_snap` for details.
627 """
628 if mpl.rcParams['path.snap']:
629 return self._snap
630 else:
631 return False
632
633 def set_snap(self, snap):
634 """
635 Set the snapping behavior.
636
637 Snapping aligns positions with the pixel grid, which results in
638 clearer images. For example, if a black line of 1px width was
639 defined at a position in between two pixels, the resulting image
640 would contain the interpolated value of that line in the pixel grid,
641 which would be a grey value on both adjacent pixel positions. In
642 contrast, snapping will move the line to the nearest integer pixel
643 value, so that the resulting image will really contain a 1px wide
644 black line.
645
646 Snapping is currently only supported by the Agg and MacOSX backends.
647
648 Parameters
649 ----------
650 snap : bool or None
651 Possible values:
652
653 - *True*: Snap vertices to the nearest pixel center.
654 - *False*: Do not modify vertex positions.
655 - *None*: (auto) If the path contains only rectilinear line
656 segments, round to the nearest pixel center.
657 """
658 self._snap = snap
659 self.stale = True
660
661 def get_sketch_params(self):
662 """
663 Return the sketch parameters for the artist.
664
665 Returns
666 -------
667 tuple or None
668
669 A 3-tuple with the following elements:
670
671 - *scale*: The amplitude of the wiggle perpendicular to the
672 source line.
673 - *length*: The length of the wiggle along the line.
674 - *randomness*: The scale factor by which the length is
675 shrunken or expanded.
676
677 Returns *None* if no sketch parameters were set.
678 """
679 return self._sketch
680
681 def set_sketch_params(self, scale=None, length=None, randomness=None):
682 """
683 Set the sketch parameters.
684
685 Parameters
686 ----------
687 scale : float, optional
688 The amplitude of the wiggle perpendicular to the source
689 line, in pixels. If scale is `None`, or not provided, no
690 sketch filter will be provided.
691 length : float, optional
692 The length of the wiggle along the line, in pixels
693 (default 128.0)
694 randomness : float, optional
695 The scale factor by which the length is shrunken or
696 expanded (default 16.0)
697
698 The PGF backend uses this argument as an RNG seed and not as
699 described above. Using the same seed yields the same random shape.
700
701 .. ACCEPTS: (scale: float, length: float, randomness: float)
702 """
703 if scale is None:
704 self._sketch = None
705 else:
706 self._sketch = (scale, length or 128.0, randomness or 16.0)
707 self.stale = True
708
709 def set_path_effects(self, path_effects):
710 """
711 Set the path effects.
712
713 Parameters
714 ----------
715 path_effects : list of `.AbstractPathEffect`
716 """
717 self._path_effects = path_effects
718 self.stale = True
719
720 def get_path_effects(self):
721 return self._path_effects
722
723 def get_figure(self):
724 """Return the `.Figure` instance the artist belongs to."""
725 return self.figure
726
727 def set_figure(self, fig):
728 """
729 Set the `.Figure` instance the artist belongs to.
730
731 Parameters
732 ----------
733 fig : `~matplotlib.figure.Figure`
734 """
735 # if this is a no-op just return
736 if self.figure is fig:
737 return
738 # if we currently have a figure (the case of both `self.figure`
739 # and *fig* being none is taken care of above) we then user is
740 # trying to change the figure an artist is associated with which
741 # is not allowed for the same reason as adding the same instance
742 # to more than one Axes
743 if self.figure is not None:
744 raise RuntimeError("Can not put single artist in "
745 "more than one figure")
746 self.figure = fig
747 if self.figure and self.figure is not self:
748 self.pchanged()
749 self.stale = True
750
751 def set_clip_box(self, clipbox):
752 """
753 Set the artist's clip `.Bbox`.
754
755 Parameters
756 ----------
757 clipbox : `~matplotlib.transforms.BboxBase` or None
758 Will typically be created from a `.TransformedBbox`. For instance,
759 ``TransformedBbox(Bbox([[0, 0], [1, 1]]), ax.transAxes)`` is the default
760 clipping for an artist added to an Axes.
761
762 """
763 _api.check_isinstance((BboxBase, None), clipbox=clipbox)
764 if clipbox != self.clipbox:
765 self.clipbox = clipbox
766 self.pchanged()
767 self.stale = True
768
769 def set_clip_path(self, path, transform=None):
770 """
771 Set the artist's clip path.
772
773 Parameters
774 ----------
775 path : `~matplotlib.patches.Patch` or `.Path` or `.TransformedPath` or None
776 The clip path. If given a `.Path`, *transform* must be provided as
777 well. If *None*, a previously set clip path is removed.
778 transform : `~matplotlib.transforms.Transform`, optional
779 Only used if *path* is a `.Path`, in which case the given `.Path`
780 is converted to a `.TransformedPath` using *transform*.
781
782 Notes
783 -----
784 For efficiency, if *path* is a `.Rectangle` this method will set the
785 clipping box to the corresponding rectangle and set the clipping path
786 to ``None``.
787
788 For technical reasons (support of `~.Artist.set`), a tuple
789 (*path*, *transform*) is also accepted as a single positional
790 parameter.
791
792 .. ACCEPTS: Patch or (Path, Transform) or None
793 """
794 from matplotlib.patches import Patch, Rectangle
795
796 success = False
797 if transform is None:
798 if isinstance(path, Rectangle):
799 self.clipbox = TransformedBbox(Bbox.unit(),
800 path.get_transform())
801 self._clippath = None
802 success = True
803 elif isinstance(path, Patch):
804 self._clippath = TransformedPatchPath(path)
805 success = True
806 elif isinstance(path, tuple):
807 path, transform = path
808
809 if path is None:
810 self._clippath = None
811 success = True
812 elif isinstance(path, Path):
813 self._clippath = TransformedPath(path, transform)
814 success = True
815 elif isinstance(path, TransformedPatchPath):
816 self._clippath = path
817 success = True
818 elif isinstance(path, TransformedPath):
819 self._clippath = path
820 success = True
821
822 if not success:
823 raise TypeError(
824 "Invalid arguments to set_clip_path, of type "
825 f"{type(path).__name__} and {type(transform).__name__}")
826 # This may result in the callbacks being hit twice, but guarantees they
827 # will be hit at least once.
828 self.pchanged()
829 self.stale = True
830
831 def get_alpha(self):
832 """
833 Return the alpha value used for blending - not supported on all
834 backends.
835 """
836 return self._alpha
837
838 def get_visible(self):
839 """Return the visibility."""
840 return self._visible
841
842 def get_animated(self):
843 """Return whether the artist is animated."""
844 return self._animated
845
846 def get_in_layout(self):
847 """
848 Return boolean flag, ``True`` if artist is included in layout
849 calculations.
850
851 E.g. :ref:`constrainedlayout_guide`,
852 `.Figure.tight_layout()`, and
853 ``fig.savefig(fname, bbox_inches='tight')``.
854 """
855 return self._in_layout
856
857 def _fully_clipped_to_axes(self):
858 """
859 Return a boolean flag, ``True`` if the artist is clipped to the Axes
860 and can thus be skipped in layout calculations. Requires `get_clip_on`
861 is True, one of `clip_box` or `clip_path` is set, ``clip_box.extents``
862 is equivalent to ``ax.bbox.extents`` (if set), and ``clip_path._patch``
863 is equivalent to ``ax.patch`` (if set).
864 """
865 # Note that ``clip_path.get_fully_transformed_path().get_extents()``
866 # cannot be directly compared to ``axes.bbox.extents`` because the
867 # extents may be undefined (i.e. equivalent to ``Bbox.null()``)
868 # before the associated artist is drawn, and this method is meant
869 # to determine whether ``axes.get_tightbbox()`` may bypass drawing
870 clip_box = self.get_clip_box()
871 clip_path = self.get_clip_path()
872 return (self.axes is not None
873 and self.get_clip_on()
874 and (clip_box is not None or clip_path is not None)
875 and (clip_box is None
876 or np.all(clip_box.extents == self.axes.bbox.extents))
877 and (clip_path is None
878 or isinstance(clip_path, TransformedPatchPath)
879 and clip_path._patch is self.axes.patch))
880
881 def get_clip_on(self):
882 """Return whether the artist uses clipping."""
883 return self._clipon
884
885 def get_clip_box(self):
886 """Return the clipbox."""
887 return self.clipbox
888
889 def get_clip_path(self):
890 """Return the clip path."""
891 return self._clippath
892
893 def get_transformed_clip_path_and_affine(self):
894 """
895 Return the clip path with the non-affine part of its
896 transformation applied, and the remaining affine part of its
897 transformation.
898 """
899 if self._clippath is not None:
900 return self._clippath.get_transformed_path_and_affine()
901 return None, None
902
903 def set_clip_on(self, b):
904 """
905 Set whether the artist uses clipping.
906
907 When False, artists will be visible outside the Axes which
908 can lead to unexpected results.
909
910 Parameters
911 ----------
912 b : bool
913 """
914 self._clipon = b
915 # This may result in the callbacks being hit twice, but ensures they
916 # are hit at least once
917 self.pchanged()
918 self.stale = True
919
920 def _set_gc_clip(self, gc):
921 """Set the clip properly for the gc."""
922 if self._clipon:
923 if self.clipbox is not None:
924 gc.set_clip_rectangle(self.clipbox)
925 gc.set_clip_path(self._clippath)
926 else:
927 gc.set_clip_rectangle(None)
928 gc.set_clip_path(None)
929
930 def get_rasterized(self):
931 """Return whether the artist is to be rasterized."""
932 return self._rasterized
933
934 def set_rasterized(self, rasterized):
935 """
936 Force rasterized (bitmap) drawing for vector graphics output.
937
938 Rasterized drawing is not supported by all artists. If you try to
939 enable this on an artist that does not support it, the command has no
940 effect and a warning will be issued.
941
942 This setting is ignored for pixel-based output.
943
944 See also :doc:`/gallery/misc/rasterization_demo`.
945
946 Parameters
947 ----------
948 rasterized : bool
949 """
950 supports_rasterization = getattr(self.draw,
951 "_supports_rasterization", False)
952 if rasterized and not supports_rasterization:
953 _api.warn_external(f"Rasterization of '{self}' will be ignored")
954
955 self._rasterized = rasterized
956
957 def get_agg_filter(self):
958 """Return filter function to be used for agg filter."""
959 return self._agg_filter
960
961 def set_agg_filter(self, filter_func):
962 """
963 Set the agg filter.
964
965 Parameters
966 ----------
967 filter_func : callable
968 A filter function, which takes a (m, n, depth) float array
969 and a dpi value, and returns a (m, n, depth) array and two
970 offsets from the bottom left corner of the image
971
972 .. ACCEPTS: a filter function, which takes a (m, n, 3) float array
973 and a dpi value, and returns a (m, n, 3) array and two offsets
974 from the bottom left corner of the image
975 """
976 self._agg_filter = filter_func
977 self.stale = True
978
979 def draw(self, renderer):
980 """
981 Draw the Artist (and its children) using the given renderer.
982
983 This has no effect if the artist is not visible (`.Artist.get_visible`
984 returns False).
985
986 Parameters
987 ----------
988 renderer : `~matplotlib.backend_bases.RendererBase` subclass.
989
990 Notes
991 -----
992 This method is overridden in the Artist subclasses.
993 """
994 if not self.get_visible():
995 return
996 self.stale = False
997
998 def set_alpha(self, alpha):
999 """
1000 Set the alpha value used for blending - not supported on all backends.
1001
1002 Parameters
1003 ----------
1004 alpha : scalar or None
1005 *alpha* must be within the 0-1 range, inclusive.
1006 """
1007 if alpha is not None and not isinstance(alpha, Real):
1008 raise TypeError(
1009 f'alpha must be numeric or None, not {type(alpha)}')
1010 if alpha is not None and not (0 <= alpha <= 1):
1011 raise ValueError(f'alpha ({alpha}) is outside 0-1 range')
1012 if alpha != self._alpha:
1013 self._alpha = alpha
1014 self.pchanged()
1015 self.stale = True
1016
1017 def _set_alpha_for_array(self, alpha):
1018 """
1019 Set the alpha value used for blending - not supported on all backends.
1020
1021 Parameters
1022 ----------
1023 alpha : array-like or scalar or None
1024 All values must be within the 0-1 range, inclusive.
1025 Masked values and nans are not supported.
1026 """
1027 if isinstance(alpha, str):
1028 raise TypeError("alpha must be numeric or None, not a string")
1029 if not np.iterable(alpha):
1030 Artist.set_alpha(self, alpha)
1031 return
1032 alpha = np.asarray(alpha)
1033 if not (0 <= alpha.min() and alpha.max() <= 1):
1034 raise ValueError('alpha must be between 0 and 1, inclusive, '
1035 f'but min is {alpha.min()}, max is {alpha.max()}')
1036 self._alpha = alpha
1037 self.pchanged()
1038 self.stale = True
1039
1040 def set_visible(self, b):
1041 """
1042 Set the artist's visibility.
1043
1044 Parameters
1045 ----------
1046 b : bool
1047 """
1048 if b != self._visible:
1049 self._visible = b
1050 self.pchanged()
1051 self.stale = True
1052
1053 def set_animated(self, b):
1054 """
1055 Set whether the artist is intended to be used in an animation.
1056
1057 If True, the artist is excluded from regular drawing of the figure.
1058 You have to call `.Figure.draw_artist` / `.Axes.draw_artist`
1059 explicitly on the artist. This approach is used to speed up animations
1060 using blitting.
1061
1062 See also `matplotlib.animation` and
1063 :ref:`blitting`.
1064
1065 Parameters
1066 ----------
1067 b : bool
1068 """
1069 if self._animated != b:
1070 self._animated = b
1071 self.pchanged()
1072
1073 def set_in_layout(self, in_layout):
1074 """
1075 Set if artist is to be included in layout calculations,
1076 E.g. :ref:`constrainedlayout_guide`,
1077 `.Figure.tight_layout()`, and
1078 ``fig.savefig(fname, bbox_inches='tight')``.
1079
1080 Parameters
1081 ----------
1082 in_layout : bool
1083 """
1084 self._in_layout = in_layout
1085
1086 def get_label(self):
1087 """Return the label used for this artist in the legend."""
1088 return self._label
1089
1090 def set_label(self, s):
1091 """
1092 Set a label that will be displayed in the legend.
1093
1094 Parameters
1095 ----------
1096 s : object
1097 *s* will be converted to a string by calling `str`.
1098 """
1099 label = str(s) if s is not None else None
1100 if label != self._label:
1101 self._label = label
1102 self.pchanged()
1103 self.stale = True
1104
1105 def get_zorder(self):
1106 """Return the artist's zorder."""
1107 return self.zorder
1108
1109 def set_zorder(self, level):
1110 """
1111 Set the zorder for the artist. Artists with lower zorder
1112 values are drawn first.
1113
1114 Parameters
1115 ----------
1116 level : float
1117 """
1118 if level is None:
1119 level = self.__class__.zorder
1120 if level != self.zorder:
1121 self.zorder = level
1122 self.pchanged()
1123 self.stale = True
1124
1125 @property
1126 def sticky_edges(self):
1127 """
1128 ``x`` and ``y`` sticky edge lists for autoscaling.
1129
1130 When performing autoscaling, if a data limit coincides with a value in
1131 the corresponding sticky_edges list, then no margin will be added--the
1132 view limit "sticks" to the edge. A typical use case is histograms,
1133 where one usually expects no margin on the bottom edge (0) of the
1134 histogram.
1135
1136 Moreover, margin expansion "bumps" against sticky edges and cannot
1137 cross them. For example, if the upper data limit is 1.0, the upper
1138 view limit computed by simple margin application is 1.2, but there is a
1139 sticky edge at 1.1, then the actual upper view limit will be 1.1.
1140
1141 This attribute cannot be assigned to; however, the ``x`` and ``y``
1142 lists can be modified in place as needed.
1143
1144 Examples
1145 --------
1146 >>> artist.sticky_edges.x[:] = (xmin, xmax)
1147 >>> artist.sticky_edges.y[:] = (ymin, ymax)
1148
1149 """
1150 return self._sticky_edges
1151
1152 def update_from(self, other):
1153 """Copy properties from *other* to *self*."""
1154 self._transform = other._transform
1155 self._transformSet = other._transformSet
1156 self._visible = other._visible
1157 self._alpha = other._alpha
1158 self.clipbox = other.clipbox
1159 self._clipon = other._clipon
1160 self._clippath = other._clippath
1161 self._label = other._label
1162 self._sketch = other._sketch
1163 self._path_effects = other._path_effects
1164 self.sticky_edges.x[:] = other.sticky_edges.x.copy()
1165 self.sticky_edges.y[:] = other.sticky_edges.y.copy()
1166 self.pchanged()
1167 self.stale = True
1168
1169 def properties(self):
1170 """Return a dictionary of all the properties of the artist."""
1171 return ArtistInspector(self).properties()
1172
1173 def _update_props(self, props, errfmt):
1174 """
1175 Helper for `.Artist.set` and `.Artist.update`.
1176
1177 *errfmt* is used to generate error messages for invalid property
1178 names; it gets formatted with ``type(self)`` and the property name.
1179 """
1180 ret = []
1181 with cbook._setattr_cm(self, eventson=False):
1182 for k, v in props.items():
1183 # Allow attributes we want to be able to update through
1184 # art.update, art.set, setp.
1185 if k == "axes":
1186 ret.append(setattr(self, k, v))
1187 else:
1188 func = getattr(self, f"set_{k}", None)
1189 if not callable(func):
1190 raise AttributeError(
1191 errfmt.format(cls=type(self), prop_name=k))
1192 ret.append(func(v))
1193 if ret:
1194 self.pchanged()
1195 self.stale = True
1196 return ret
1197
1198 def update(self, props):
1199 """
1200 Update this artist's properties from the dict *props*.
1201
1202 Parameters
1203 ----------
1204 props : dict
1205 """
1206 return self._update_props(
1207 props, "{cls.__name__!r} object has no property {prop_name!r}")
1208
1209 def _internal_update(self, kwargs):
1210 """
1211 Update artist properties without prenormalizing them, but generating
1212 errors as if calling `set`.
1213
1214 The lack of prenormalization is to maintain backcompatibility.
1215 """
1216 return self._update_props(
1217 kwargs, "{cls.__name__}.set() got an unexpected keyword argument "
1218 "{prop_name!r}")
1219
1220 def set(self, **kwargs):
1221 # docstring and signature are auto-generated via
1222 # Artist._update_set_signature_and_docstring() at the end of the
1223 # module.
1224 return self._internal_update(cbook.normalize_kwargs(kwargs, self))
1225
1226 @contextlib.contextmanager
1227 def _cm_set(self, **kwargs):
1228 """
1229 `.Artist.set` context-manager that restores original values at exit.
1230 """
1231 orig_vals = {k: getattr(self, f"get_{k}")() for k in kwargs}
1232 try:
1233 self.set(**kwargs)
1234 yield
1235 finally:
1236 self.set(**orig_vals)
1237
1238 def findobj(self, match=None, include_self=True):
1239 """
1240 Find artist objects.
1241
1242 Recursively find all `.Artist` instances contained in the artist.
1243
1244 Parameters
1245 ----------
1246 match
1247 A filter criterion for the matches. This can be
1248
1249 - *None*: Return all objects contained in artist.
1250 - A function with signature ``def match(artist: Artist) -> bool``.
1251 The result will only contain artists for which the function
1252 returns *True*.
1253 - A class instance: e.g., `.Line2D`. The result will only contain
1254 artists of this class or its subclasses (``isinstance`` check).
1255
1256 include_self : bool
1257 Include *self* in the list to be checked for a match.
1258
1259 Returns
1260 -------
1261 list of `.Artist`
1262
1263 """
1264 if match is None: # always return True
1265 def matchfunc(x):
1266 return True
1267 elif isinstance(match, type) and issubclass(match, Artist):
1268 def matchfunc(x):
1269 return isinstance(x, match)
1270 elif callable(match):
1271 matchfunc = match
1272 else:
1273 raise ValueError('match must be None, a matplotlib.artist.Artist '
1274 'subclass, or a callable')
1275
1276 artists = sum([c.findobj(matchfunc) for c in self.get_children()], [])
1277 if include_self and matchfunc(self):
1278 artists.append(self)
1279 return artists
1280
1281 def get_cursor_data(self, event):
1282 """
1283 Return the cursor data for a given event.
1284
1285 .. note::
1286 This method is intended to be overridden by artist subclasses.
1287 As an end-user of Matplotlib you will most likely not call this
1288 method yourself.
1289
1290 Cursor data can be used by Artists to provide additional context
1291 information for a given event. The default implementation just returns
1292 *None*.
1293
1294 Subclasses can override the method and return arbitrary data. However,
1295 when doing so, they must ensure that `.format_cursor_data` can convert
1296 the data to a string representation.
1297
1298 The only current use case is displaying the z-value of an `.AxesImage`
1299 in the status bar of a plot window, while moving the mouse.
1300
1301 Parameters
1302 ----------
1303 event : `~matplotlib.backend_bases.MouseEvent`
1304
1305 See Also
1306 --------
1307 format_cursor_data
1308
1309 """
1310 return None
1311
1312 def format_cursor_data(self, data):
1313 """
1314 Return a string representation of *data*.
1315
1316 .. note::
1317 This method is intended to be overridden by artist subclasses.
1318 As an end-user of Matplotlib you will most likely not call this
1319 method yourself.
1320
1321 The default implementation converts ints and floats and arrays of ints
1322 and floats into a comma-separated string enclosed in square brackets,
1323 unless the artist has an associated colorbar, in which case scalar
1324 values are formatted using the colorbar's formatter.
1325
1326 See Also
1327 --------
1328 get_cursor_data
1329 """
1330 if np.ndim(data) == 0 and isinstance(self, ScalarMappable):
1331 # This block logically belongs to ScalarMappable, but can't be
1332 # implemented in it because most ScalarMappable subclasses inherit
1333 # from Artist first and from ScalarMappable second, so
1334 # Artist.format_cursor_data would always have precedence over
1335 # ScalarMappable.format_cursor_data.
1336 n = self.cmap.N
1337 if np.ma.getmask(data):
1338 return "[]"
1339 normed = self.norm(data)
1340 if np.isfinite(normed):
1341 if isinstance(self.norm, BoundaryNorm):
1342 # not an invertible normalization mapping
1343 cur_idx = np.argmin(np.abs(self.norm.boundaries - data))
1344 neigh_idx = max(0, cur_idx - 1)
1345 # use max diff to prevent delta == 0
1346 delta = np.diff(
1347 self.norm.boundaries[neigh_idx:cur_idx + 2]
1348 ).max()
1349 elif self.norm.vmin == self.norm.vmax:
1350 # singular norms, use delta of 10% of only value
1351 delta = np.abs(self.norm.vmin * .1)
1352 else:
1353 # Midpoints of neighboring color intervals.
1354 neighbors = self.norm.inverse(
1355 (int(normed * n) + np.array([0, 1])) / n)
1356 delta = abs(neighbors - data).max()
1357 g_sig_digits = cbook._g_sig_digits(data, delta)
1358 else:
1359 g_sig_digits = 3 # Consistent with default below.
1360 return f"[{data:-#.{g_sig_digits}g}]"
1361 else:
1362 try:
1363 data[0]
1364 except (TypeError, IndexError):
1365 data = [data]
1366 data_str = ', '.join(f'{item:0.3g}' for item in data
1367 if isinstance(item, Number))
1368 return "[" + data_str + "]"
1369
1370 def get_mouseover(self):
1371 """
1372 Return whether this artist is queried for custom context information
1373 when the mouse cursor moves over it.
1374 """
1375 return self._mouseover
1376
1377 def set_mouseover(self, mouseover):
1378 """
1379 Set whether this artist is queried for custom context information when
1380 the mouse cursor moves over it.
1381
1382 Parameters
1383 ----------
1384 mouseover : bool
1385
1386 See Also
1387 --------
1388 get_cursor_data
1389 .ToolCursorPosition
1390 .NavigationToolbar2
1391 """
1392 self._mouseover = bool(mouseover)
1393 ax = self.axes
1394 if ax:
1395 if self._mouseover:
1396 ax._mouseover_set.add(self)
1397 else:
1398 ax._mouseover_set.discard(self)
1399
1400 mouseover = property(get_mouseover, set_mouseover) # backcompat.
1401
1402
1403def _get_tightbbox_for_layout_only(obj, *args, **kwargs):
1404 """
1405 Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
1406 *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
1407 when encountering third-party subclasses that do not support it.
1408 """
1409 try:
1410 return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
1411 except TypeError:
1412 return obj.get_tightbbox(*args, **kwargs)
1413
1414
1415class ArtistInspector:
1416 """
1417 A helper class to inspect an `~matplotlib.artist.Artist` and return
1418 information about its settable properties and their current values.
1419 """
1420
1421 def __init__(self, o):
1422 r"""
1423 Initialize the artist inspector with an `Artist` or an iterable of
1424 `Artist`\s. If an iterable is used, we assume it is a homogeneous
1425 sequence (all `Artist`\s are of the same type) and it is your
1426 responsibility to make sure this is so.
1427 """
1428 if not isinstance(o, Artist):
1429 if np.iterable(o):
1430 o = list(o)
1431 if len(o):
1432 o = o[0]
1433
1434 self.oorig = o
1435 if not isinstance(o, type):
1436 o = type(o)
1437 self.o = o
1438
1439 self.aliasd = self.get_aliases()
1440
1441 def get_aliases(self):
1442 """
1443 Get a dict mapping property fullnames to sets of aliases for each alias
1444 in the :class:`~matplotlib.artist.ArtistInspector`.
1445
1446 e.g., for lines::
1447
1448 {'markerfacecolor': {'mfc'},
1449 'linewidth' : {'lw'},
1450 }
1451 """
1452 names = [name for name in dir(self.o)
1453 if name.startswith(('set_', 'get_'))
1454 and callable(getattr(self.o, name))]
1455 aliases = {}
1456 for name in names:
1457 func = getattr(self.o, name)
1458 if not self.is_alias(func):
1459 continue
1460 propname = re.search(f"`({name[:4]}.*)`", # get_.*/set_.*
1461 inspect.getdoc(func)).group(1)
1462 aliases.setdefault(propname[4:], set()).add(name[4:])
1463 return aliases
1464
1465 _get_valid_values_regex = re.compile(
1466 r"\n\s*(?:\.\.\s+)?ACCEPTS:\s*((?:.|\n)*?)(?:$|(?:\n\n))"
1467 )
1468
1469 def get_valid_values(self, attr):
1470 """
1471 Get the legal arguments for the setter associated with *attr*.
1472
1473 This is done by querying the docstring of the setter for a line that
1474 begins with "ACCEPTS:" or ".. ACCEPTS:", and then by looking for a
1475 numpydoc-style documentation for the setter's first argument.
1476 """
1477
1478 name = 'set_%s' % attr
1479 if not hasattr(self.o, name):
1480 raise AttributeError(f'{self.o} has no function {name}')
1481 func = getattr(self.o, name)
1482
1483 if hasattr(func, '_kwarg_doc'):
1484 return func._kwarg_doc
1485
1486 docstring = inspect.getdoc(func)
1487 if docstring is None:
1488 return 'unknown'
1489
1490 if docstring.startswith('Alias for '):
1491 return None
1492
1493 match = self._get_valid_values_regex.search(docstring)
1494 if match is not None:
1495 return re.sub("\n *", " ", match.group(1))
1496
1497 # Much faster than list(inspect.signature(func).parameters)[1],
1498 # although barely relevant wrt. matplotlib's total import time.
1499 param_name = func.__code__.co_varnames[1]
1500 # We could set the presence * based on whether the parameter is a
1501 # varargs (it can't be a varkwargs) but it's not really worth it.
1502 match = re.search(fr"(?m)^ *\*?{param_name} : (.+)", docstring)
1503 if match:
1504 return match.group(1)
1505
1506 return 'unknown'
1507
1508 def _replace_path(self, source_class):
1509 """
1510 Changes the full path to the public API path that is used
1511 in sphinx. This is needed for links to work.
1512 """
1513 replace_dict = {'_base._AxesBase': 'Axes',
1514 '_axes.Axes': 'Axes'}
1515 for key, value in replace_dict.items():
1516 source_class = source_class.replace(key, value)
1517 return source_class
1518
1519 def get_setters(self):
1520 """
1521 Get the attribute strings with setters for object.
1522
1523 For example, for a line, return ``['markerfacecolor', 'linewidth',
1524 ....]``.
1525 """
1526 setters = []
1527 for name in dir(self.o):
1528 if not name.startswith('set_'):
1529 continue
1530 func = getattr(self.o, name)
1531 if (not callable(func)
1532 or self.number_of_parameters(func) < 2
1533 or self.is_alias(func)):
1534 continue
1535 setters.append(name[4:])
1536 return setters
1537
1538 @staticmethod
1539 @cache
1540 def number_of_parameters(func):
1541 """Return number of parameters of the callable *func*."""
1542 return len(inspect.signature(func).parameters)
1543
1544 @staticmethod
1545 @cache
1546 def is_alias(method):
1547 """
1548 Return whether the object *method* is an alias for another method.
1549 """
1550
1551 ds = inspect.getdoc(method)
1552 if ds is None:
1553 return False
1554
1555 return ds.startswith('Alias for ')
1556
1557 def aliased_name(self, s):
1558 """
1559 Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME'.
1560
1561 For example, for the line markerfacecolor property, which has an
1562 alias, return 'markerfacecolor or mfc' and for the transform
1563 property, which does not, return 'transform'.
1564 """
1565 aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, [])))
1566 return s + aliases
1567
1568 _NOT_LINKABLE = {
1569 # A set of property setter methods that are not available in our
1570 # current docs. This is a workaround used to prevent trying to link
1571 # these setters which would lead to "target reference not found"
1572 # warnings during doc build.
1573 'matplotlib.image._ImageBase.set_alpha',
1574 'matplotlib.image._ImageBase.set_array',
1575 'matplotlib.image._ImageBase.set_data',
1576 'matplotlib.image._ImageBase.set_filternorm',
1577 'matplotlib.image._ImageBase.set_filterrad',
1578 'matplotlib.image._ImageBase.set_interpolation',
1579 'matplotlib.image._ImageBase.set_interpolation_stage',
1580 'matplotlib.image._ImageBase.set_resample',
1581 'matplotlib.text._AnnotationBase.set_annotation_clip',
1582 }
1583
1584 def aliased_name_rest(self, s, target):
1585 """
1586 Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME',
1587 formatted for reST.
1588
1589 For example, for the line markerfacecolor property, which has an
1590 alias, return 'markerfacecolor or mfc' and for the transform
1591 property, which does not, return 'transform'.
1592 """
1593 # workaround to prevent "reference target not found"
1594 if target in self._NOT_LINKABLE:
1595 return f'``{s}``'
1596
1597 aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, [])))
1598 return f':meth:`{s} <{target}>`{aliases}'
1599
1600 def pprint_setters(self, prop=None, leadingspace=2):
1601 """
1602 If *prop* is *None*, return a list of strings of all settable
1603 properties and their valid values.
1604
1605 If *prop* is not *None*, it is a valid property name and that
1606 property will be returned as a string of property : valid
1607 values.
1608 """
1609 if leadingspace:
1610 pad = ' ' * leadingspace
1611 else:
1612 pad = ''
1613 if prop is not None:
1614 accepts = self.get_valid_values(prop)
1615 return f'{pad}{prop}: {accepts}'
1616
1617 lines = []
1618 for prop in sorted(self.get_setters()):
1619 accepts = self.get_valid_values(prop)
1620 name = self.aliased_name(prop)
1621 lines.append(f'{pad}{name}: {accepts}')
1622 return lines
1623
1624 def pprint_setters_rest(self, prop=None, leadingspace=4):
1625 """
1626 If *prop* is *None*, return a list of reST-formatted strings of all
1627 settable properties and their valid values.
1628
1629 If *prop* is not *None*, it is a valid property name and that
1630 property will be returned as a string of "property : valid"
1631 values.
1632 """
1633 if leadingspace:
1634 pad = ' ' * leadingspace
1635 else:
1636 pad = ''
1637 if prop is not None:
1638 accepts = self.get_valid_values(prop)
1639 return f'{pad}{prop}: {accepts}'
1640
1641 prop_and_qualnames = []
1642 for prop in sorted(self.get_setters()):
1643 # Find the parent method which actually provides the docstring.
1644 for cls in self.o.__mro__:
1645 method = getattr(cls, f"set_{prop}", None)
1646 if method and method.__doc__ is not None:
1647 break
1648 else: # No docstring available.
1649 method = getattr(self.o, f"set_{prop}")
1650 prop_and_qualnames.append(
1651 (prop, f"{method.__module__}.{method.__qualname__}"))
1652
1653 names = [self.aliased_name_rest(prop, target)
1654 .replace('_base._AxesBase', 'Axes')
1655 .replace('_axes.Axes', 'Axes')
1656 for prop, target in prop_and_qualnames]
1657 accepts = [self.get_valid_values(prop)
1658 for prop, _ in prop_and_qualnames]
1659
1660 col0_len = max(len(n) for n in names)
1661 col1_len = max(len(a) for a in accepts)
1662 table_formatstr = pad + ' ' + '=' * col0_len + ' ' + '=' * col1_len
1663
1664 return [
1665 '',
1666 pad + '.. table::',
1667 pad + ' :class: property-table',
1668 '',
1669 table_formatstr,
1670 pad + ' ' + 'Property'.ljust(col0_len)
1671 + ' ' + 'Description'.ljust(col1_len),
1672 table_formatstr,
1673 *[pad + ' ' + n.ljust(col0_len) + ' ' + a.ljust(col1_len)
1674 for n, a in zip(names, accepts)],
1675 table_formatstr,
1676 '',
1677 ]
1678
1679 def properties(self):
1680 """Return a dictionary mapping property name -> value."""
1681 o = self.oorig
1682 getters = [name for name in dir(o)
1683 if name.startswith('get_') and callable(getattr(o, name))]
1684 getters.sort()
1685 d = {}
1686 for name in getters:
1687 func = getattr(o, name)
1688 if self.is_alias(func):
1689 continue
1690 try:
1691 with warnings.catch_warnings():
1692 warnings.simplefilter('ignore')
1693 val = func()
1694 except Exception:
1695 continue
1696 else:
1697 d[name[4:]] = val
1698 return d
1699
1700 def pprint_getters(self):
1701 """Return the getters and actual values as list of strings."""
1702 lines = []
1703 for name, val in sorted(self.properties().items()):
1704 if getattr(val, 'shape', ()) != () and len(val) > 6:
1705 s = str(val[:6]) + '...'
1706 else:
1707 s = str(val)
1708 s = s.replace('\n', ' ')
1709 if len(s) > 50:
1710 s = s[:50] + '...'
1711 name = self.aliased_name(name)
1712 lines.append(f' {name} = {s}')
1713 return lines
1714
1715
1716def getp(obj, property=None):
1717 """
1718 Return the value of an `.Artist`'s *property*, or print all of them.
1719
1720 Parameters
1721 ----------
1722 obj : `~matplotlib.artist.Artist`
1723 The queried artist; e.g., a `.Line2D`, a `.Text`, or an `~.axes.Axes`.
1724
1725 property : str or None, default: None
1726 If *property* is 'somename', this function returns
1727 ``obj.get_somename()``.
1728
1729 If it's None (or unset), it *prints* all gettable properties from
1730 *obj*. Many properties have aliases for shorter typing, e.g. 'lw' is
1731 an alias for 'linewidth'. In the output, aliases and full property
1732 names will be listed as:
1733
1734 property or alias = value
1735
1736 e.g.:
1737
1738 linewidth or lw = 2
1739
1740 See Also
1741 --------
1742 setp
1743 """
1744 if property is None:
1745 insp = ArtistInspector(obj)
1746 ret = insp.pprint_getters()
1747 print('\n'.join(ret))
1748 return
1749 return getattr(obj, 'get_' + property)()
1750
1751# alias
1752get = getp
1753
1754
1755def setp(obj, *args, file=None, **kwargs):
1756 """
1757 Set one or more properties on an `.Artist`, or list allowed values.
1758
1759 Parameters
1760 ----------
1761 obj : `~matplotlib.artist.Artist` or list of `.Artist`
1762 The artist(s) whose properties are being set or queried. When setting
1763 properties, all artists are affected; when querying the allowed values,
1764 only the first instance in the sequence is queried.
1765
1766 For example, two lines can be made thicker and red with a single call:
1767
1768 >>> x = arange(0, 1, 0.01)
1769 >>> lines = plot(x, sin(2*pi*x), x, sin(4*pi*x))
1770 >>> setp(lines, linewidth=2, color='r')
1771
1772 file : file-like, default: `sys.stdout`
1773 Where `setp` writes its output when asked to list allowed values.
1774
1775 >>> with open('output.log') as file:
1776 ... setp(line, file=file)
1777
1778 The default, ``None``, means `sys.stdout`.
1779
1780 *args, **kwargs
1781 The properties to set. The following combinations are supported:
1782
1783 - Set the linestyle of a line to be dashed:
1784
1785 >>> line, = plot([1, 2, 3])
1786 >>> setp(line, linestyle='--')
1787
1788 - Set multiple properties at once:
1789
1790 >>> setp(line, linewidth=2, color='r')
1791
1792 - List allowed values for a line's linestyle:
1793
1794 >>> setp(line, 'linestyle')
1795 linestyle: {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
1796
1797 - List all properties that can be set, and their allowed values:
1798
1799 >>> setp(line)
1800 agg_filter: a filter function, ...
1801 [long output listing omitted]
1802
1803 `setp` also supports MATLAB style string/value pairs. For example, the
1804 following are equivalent:
1805
1806 >>> setp(lines, 'linewidth', 2, 'color', 'r') # MATLAB style
1807 >>> setp(lines, linewidth=2, color='r') # Python style
1808
1809 See Also
1810 --------
1811 getp
1812 """
1813
1814 if isinstance(obj, Artist):
1815 objs = [obj]
1816 else:
1817 objs = list(cbook.flatten(obj))
1818
1819 if not objs:
1820 return
1821
1822 insp = ArtistInspector(objs[0])
1823
1824 if not kwargs and len(args) < 2:
1825 if args:
1826 print(insp.pprint_setters(prop=args[0]), file=file)
1827 else:
1828 print('\n'.join(insp.pprint_setters()), file=file)
1829 return
1830
1831 if len(args) % 2:
1832 raise ValueError('The set args must be string, value pairs')
1833
1834 funcvals = dict(zip(args[::2], args[1::2]))
1835 ret = [o.update(funcvals) for o in objs] + [o.set(**kwargs) for o in objs]
1836 return list(cbook.flatten(ret))
1837
1838
1839def kwdoc(artist):
1840 r"""
1841 Inspect an `~matplotlib.artist.Artist` class (using `.ArtistInspector`) and
1842 return information about its settable properties and their current values.
1843
1844 Parameters
1845 ----------
1846 artist : `~matplotlib.artist.Artist` or an iterable of `Artist`\s
1847
1848 Returns
1849 -------
1850 str
1851 The settable properties of *artist*, as plain text if
1852 :rc:`docstring.hardcopy` is False and as a rst table (intended for
1853 use in Sphinx) if it is True.
1854 """
1855 ai = ArtistInspector(artist)
1856 return ('\n'.join(ai.pprint_setters_rest(leadingspace=4))
1857 if mpl.rcParams['docstring.hardcopy'] else
1858 'Properties:\n' + '\n'.join(ai.pprint_setters(leadingspace=4)))
1859
1860# We defer this to the end of them module, because it needs ArtistInspector
1861# to be defined.
1862Artist._update_set_signature_and_docstring()