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