Coverage for /home/martinb/.local/share/virtualenvs/camcops/lib/python3.6/site-packages/matplotlib/artist.py : 30%

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