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