| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855 |
- from collections import namedtuple
- import contextlib
- from functools import cache, reduce, wraps
- import inspect
- from inspect import Signature, Parameter
- import logging
- from numbers import Number, Real
- import operator
- import re
- import warnings
- import numpy as np
- import matplotlib as mpl
- from . import _api, cbook
- from .path import Path
- from .transforms import (BboxBase, Bbox, IdentityTransform, Transform, TransformedBbox,
- TransformedPatchPath, TransformedPath)
- _log = logging.getLogger(__name__)
- def _prevent_rasterization(draw):
- # We assume that by default artists are not allowed to rasterize (unless
- # its draw method is explicitly decorated). If it is being drawn after a
- # rasterized artist and it has reached a raster_depth of 0, we stop
- # rasterization so that it does not affect the behavior of normal artist
- # (e.g., change in dpi).
- @wraps(draw)
- def draw_wrapper(artist, renderer, *args, **kwargs):
- if renderer._raster_depth == 0 and renderer._rasterizing:
- # Only stop when we are not in a rasterized parent
- # and something has been rasterized since last stop.
- renderer.stop_rasterizing()
- renderer._rasterizing = False
- return draw(artist, renderer, *args, **kwargs)
- draw_wrapper._supports_rasterization = False
- return draw_wrapper
- def allow_rasterization(draw):
- """
- Decorator for Artist.draw method. Provides routines
- that run before and after the draw call. The before and after functions
- are useful for changing artist-dependent renderer attributes or making
- other setup function calls, such as starting and flushing a mixed-mode
- renderer.
- """
- @wraps(draw)
- def draw_wrapper(artist, renderer):
- try:
- if artist.get_rasterized():
- if renderer._raster_depth == 0 and not renderer._rasterizing:
- renderer.start_rasterizing()
- renderer._rasterizing = True
- renderer._raster_depth += 1
- else:
- if renderer._raster_depth == 0 and renderer._rasterizing:
- # Only stop when we are not in a rasterized parent
- # and something has be rasterized since last stop
- renderer.stop_rasterizing()
- renderer._rasterizing = False
- if artist.get_agg_filter() is not None:
- renderer.start_filter()
- return draw(artist, renderer)
- finally:
- if artist.get_agg_filter() is not None:
- renderer.stop_filter(artist.get_agg_filter())
- if artist.get_rasterized():
- renderer._raster_depth -= 1
- if (renderer._rasterizing and (fig := artist.get_figure(root=True)) and
- fig.suppressComposite):
- # restart rasterizing to prevent merging
- renderer.stop_rasterizing()
- renderer.start_rasterizing()
- draw_wrapper._supports_rasterization = True
- return draw_wrapper
- def _finalize_rasterization(draw):
- """
- Decorator for Artist.draw method. Needed on the outermost artist, i.e.
- Figure, to finish up if the render is still in rasterized mode.
- """
- @wraps(draw)
- def draw_wrapper(artist, renderer, *args, **kwargs):
- result = draw(artist, renderer, *args, **kwargs)
- if renderer._rasterizing:
- renderer.stop_rasterizing()
- renderer._rasterizing = False
- return result
- return draw_wrapper
- def _stale_axes_callback(self, val):
- if self.axes:
- self.axes.stale = val
- _XYPair = namedtuple("_XYPair", "x y")
- class _Unset:
- def __repr__(self):
- return "<UNSET>"
- _UNSET = _Unset()
- class Artist:
- """
- Abstract base class for objects that render into a FigureCanvas.
- Typically, all visible elements in a figure are subclasses of Artist.
- """
- zorder = 0
- def __init_subclass__(cls):
- # Decorate draw() method so that all artists are able to stop
- # rastrization when necessary. If the artist's draw method is already
- # decorated (has a `_supports_rasterization` attribute), it won't be
- # decorated.
- if not hasattr(cls.draw, "_supports_rasterization"):
- cls.draw = _prevent_rasterization(cls.draw)
- # Inject custom set() methods into the subclass with signature and
- # docstring based on the subclasses' properties.
- if not hasattr(cls.set, '_autogenerated_signature'):
- # Don't overwrite cls.set if the subclass or one of its parents
- # has defined a set method set itself.
- # If there was no explicit definition, cls.set is inherited from
- # the hierarchy of auto-generated set methods, which hold the
- # flag _autogenerated_signature.
- return
- cls.set = lambda self, **kwargs: Artist.set(self, **kwargs)
- cls.set.__name__ = "set"
- cls.set.__qualname__ = f"{cls.__qualname__}.set"
- cls._update_set_signature_and_docstring()
- _PROPERTIES_EXCLUDED_FROM_SET = [
- 'navigate_mode', # not a user-facing function
- 'figure', # changing the figure is such a profound operation
- # that we don't want this in set()
- '3d_properties', # cannot be used as a keyword due to leading digit
- ]
- @classmethod
- def _update_set_signature_and_docstring(cls):
- """
- Update the signature of the set function to list all properties
- as keyword arguments.
- Property aliases are not listed in the signature for brevity, but
- are still accepted as keyword arguments.
- """
- cls.set.__signature__ = Signature(
- [Parameter("self", Parameter.POSITIONAL_OR_KEYWORD),
- *[Parameter(prop, Parameter.KEYWORD_ONLY, default=_UNSET)
- for prop in ArtistInspector(cls).get_setters()
- if prop not in Artist._PROPERTIES_EXCLUDED_FROM_SET]])
- cls.set._autogenerated_signature = True
- cls.set.__doc__ = (
- "Set multiple properties at once.\n\n"
- "Supported properties are\n\n"
- + kwdoc(cls))
- def __init__(self):
- self._stale = True
- self.stale_callback = None
- self._axes = None
- self._parent_figure = None
- self._transform = None
- self._transformSet = False
- self._visible = True
- self._animated = False
- self._alpha = None
- self.clipbox = None
- self._clippath = None
- self._clipon = True
- self._label = ''
- self._picker = None
- self._rasterized = False
- self._agg_filter = None
- # Normally, artist classes need to be queried for mouseover info if and
- # only if they override get_cursor_data.
- self._mouseover = type(self).get_cursor_data != Artist.get_cursor_data
- self._callbacks = cbook.CallbackRegistry(signals=["pchanged"])
- try:
- self.axes = None
- except AttributeError:
- # Handle self.axes as a read-only property, as in Figure.
- pass
- self._remove_method = None
- self._url = None
- self._gid = None
- self._snap = None
- self._sketch = mpl.rcParams['path.sketch']
- self._path_effects = mpl.rcParams['path.effects']
- self._sticky_edges = _XYPair([], [])
- self._in_layout = True
- def __getstate__(self):
- d = self.__dict__.copy()
- d['stale_callback'] = None
- return d
- def remove(self):
- """
- Remove the artist from the figure if possible.
- The effect will not be visible until the figure is redrawn, e.g.,
- with `.FigureCanvasBase.draw_idle`. Call `~.axes.Axes.relim` to
- update the Axes limits if desired.
- Note: `~.axes.Axes.relim` will not see collections even if the
- collection was added to the Axes with *autolim* = True.
- Note: there is no support for removing the artist's legend entry.
- """
- # There is no method to set the callback. Instead, the parent should
- # set the _remove_method attribute directly. This would be a
- # protected attribute if Python supported that sort of thing. The
- # callback has one parameter, which is the child to be removed.
- if self._remove_method is not None:
- self._remove_method(self)
- # clear stale callback
- self.stale_callback = None
- _ax_flag = False
- if hasattr(self, 'axes') and self.axes:
- # remove from the mouse hit list
- self.axes._mouseover_set.discard(self)
- self.axes.stale = True
- self.axes = None # decouple the artist from the Axes
- _ax_flag = True
- if (fig := self.get_figure(root=False)) is not None:
- if not _ax_flag:
- fig.stale = True
- self._parent_figure = None
- else:
- raise NotImplementedError('cannot remove artist')
- # TODO: the fix for the collections relim problem is to move the
- # limits calculation into the artist itself, including the property of
- # whether or not the artist should affect the limits. Then there will
- # be no distinction between axes.add_line, axes.add_patch, etc.
- # TODO: add legend support
- def have_units(self):
- """Return whether units are set on any axis."""
- ax = self.axes
- return ax and any(axis.have_units() for axis in ax._axis_map.values())
- def convert_xunits(self, x):
- """
- Convert *x* using the unit type of the xaxis.
- If the artist is not contained in an Axes or if the xaxis does not
- have units, *x* itself is returned.
- """
- ax = getattr(self, 'axes', None)
- if ax is None or ax.xaxis is None:
- return x
- return ax.xaxis.convert_units(x)
- def convert_yunits(self, y):
- """
- Convert *y* using the unit type of the yaxis.
- If the artist is not contained in an Axes or if the yaxis does not
- have units, *y* itself is returned.
- """
- ax = getattr(self, 'axes', None)
- if ax is None or ax.yaxis is None:
- return y
- return ax.yaxis.convert_units(y)
- @property
- def axes(self):
- """The `~.axes.Axes` instance the artist resides in, or *None*."""
- return self._axes
- @axes.setter
- def axes(self, new_axes):
- if (new_axes is not None and self._axes is not None
- and new_axes != self._axes):
- raise ValueError("Can not reset the Axes. You are probably trying to reuse "
- "an artist in more than one Axes which is not supported")
- self._axes = new_axes
- if new_axes is not None and new_axes is not self:
- self.stale_callback = _stale_axes_callback
- @property
- def stale(self):
- """
- Whether the artist is 'stale' and needs to be re-drawn for the output
- to match the internal state of the artist.
- """
- return self._stale
- @stale.setter
- def stale(self, val):
- self._stale = val
- # if the artist is animated it does not take normal part in the
- # draw stack and is not expected to be drawn as part of the normal
- # draw loop (when not saving) so do not propagate this change
- if self._animated:
- return
- if val and self.stale_callback is not None:
- self.stale_callback(self, val)
- def get_window_extent(self, renderer=None):
- """
- Get the artist's bounding box in display space.
- The bounding box' width and height are nonnegative.
- Subclasses should override for inclusion in the bounding box
- "tight" calculation. Default is to return an empty bounding
- box at 0, 0.
- Be careful when using this function, the results will not update
- if the artist window extent of the artist changes. The extent
- can change due to any changes in the transform stack, such as
- changing the Axes limits, the figure size, or the canvas used
- (as is done when saving a figure). This can lead to unexpected
- behavior where interactive figures will look fine on the screen,
- but will save incorrectly.
- """
- return Bbox([[0, 0], [0, 0]])
- def get_tightbbox(self, renderer=None):
- """
- Like `.Artist.get_window_extent`, but includes any clipping.
- Parameters
- ----------
- renderer : `~matplotlib.backend_bases.RendererBase` subclass, optional
- renderer that will be used to draw the figures (i.e.
- ``fig.canvas.get_renderer()``)
- Returns
- -------
- `.Bbox` or None
- The enclosing bounding box (in figure pixel coordinates).
- Returns None if clipping results in no intersection.
- """
- bbox = self.get_window_extent(renderer)
- if self.get_clip_on():
- clip_box = self.get_clip_box()
- if clip_box is not None:
- bbox = Bbox.intersection(bbox, clip_box)
- clip_path = self.get_clip_path()
- if clip_path is not None and bbox is not None:
- clip_path = clip_path.get_fully_transformed_path()
- bbox = Bbox.intersection(bbox, clip_path.get_extents())
- return bbox
- def add_callback(self, func):
- """
- Add a callback function that will be called whenever one of the
- `.Artist`'s properties changes.
- Parameters
- ----------
- func : callable
- The callback function. It must have the signature::
- def func(artist: Artist) -> Any
- where *artist* is the calling `.Artist`. Return values may exist
- but are ignored.
- Returns
- -------
- int
- The observer id associated with the callback. This id can be
- used for removing the callback with `.remove_callback` later.
- See Also
- --------
- remove_callback
- """
- # Wrapping func in a lambda ensures it can be connected multiple times
- # and never gets weakref-gc'ed.
- return self._callbacks.connect("pchanged", lambda: func(self))
- def remove_callback(self, oid):
- """
- Remove a callback based on its observer id.
- See Also
- --------
- add_callback
- """
- self._callbacks.disconnect(oid)
- def pchanged(self):
- """
- Call all of the registered callbacks.
- This function is triggered internally when a property is changed.
- See Also
- --------
- add_callback
- remove_callback
- """
- self._callbacks.process("pchanged")
- def is_transform_set(self):
- """
- Return whether the Artist has an explicitly set transform.
- This is *True* after `.set_transform` has been called.
- """
- return self._transformSet
- def set_transform(self, t):
- """
- Set the artist transform.
- Parameters
- ----------
- t : `~matplotlib.transforms.Transform`
- """
- self._transform = t
- self._transformSet = True
- self.pchanged()
- self.stale = True
- def get_transform(self):
- """Return the `.Transform` instance used by this artist."""
- if self._transform is None:
- self._transform = IdentityTransform()
- elif (not isinstance(self._transform, Transform)
- and hasattr(self._transform, '_as_mpl_transform')):
- self._transform = self._transform._as_mpl_transform(self.axes)
- return self._transform
- def get_children(self):
- r"""Return a list of the child `.Artist`\s of this `.Artist`."""
- return []
- def _different_canvas(self, event):
- """
- Check whether an *event* occurred on a canvas other that this artist's canvas.
- If this method returns True, the event definitely occurred on a different
- canvas; if it returns False, either it occurred on the same canvas, or we may
- not have enough information to know.
- Subclasses should start their definition of `contains` as follows::
- if self._different_canvas(mouseevent):
- return False, {}
- # subclass-specific implementation follows
- """
- return (getattr(event, "canvas", None) is not None
- and (fig := self.get_figure(root=True)) is not None
- and event.canvas is not fig.canvas)
- def contains(self, mouseevent):
- """
- Test whether the artist contains the mouse event.
- Parameters
- ----------
- mouseevent : `~matplotlib.backend_bases.MouseEvent`
- Returns
- -------
- contains : bool
- Whether any values are within the radius.
- details : dict
- An artist-specific dictionary of details of the event context,
- such as which points are contained in the pick radius. See the
- individual Artist subclasses for details.
- """
- _log.warning("%r needs 'contains' method", self.__class__.__name__)
- return False, {}
- def pickable(self):
- """
- Return whether the artist is pickable.
- See Also
- --------
- .Artist.set_picker, .Artist.get_picker, .Artist.pick
- """
- return self.get_figure(root=False) is not None and self._picker is not None
- def pick(self, mouseevent):
- """
- Process a pick event.
- Each child artist will fire a pick event if *mouseevent* is over
- the artist and the artist has picker set.
- See Also
- --------
- .Artist.set_picker, .Artist.get_picker, .Artist.pickable
- """
- from .backend_bases import PickEvent # Circular import.
- # Pick self
- if self.pickable():
- picker = self.get_picker()
- if callable(picker):
- inside, prop = picker(self, mouseevent)
- else:
- inside, prop = self.contains(mouseevent)
- if inside:
- PickEvent("pick_event", self.get_figure(root=True).canvas,
- mouseevent, self, **prop)._process()
- # Pick children
- for a in self.get_children():
- # make sure the event happened in the same Axes
- ax = getattr(a, 'axes', None)
- if (isinstance(a, mpl.figure.SubFigure)
- or mouseevent.inaxes is None or ax is None
- or mouseevent.inaxes == ax):
- # we need to check if mouseevent.inaxes is None
- # because some objects associated with an Axes (e.g., a
- # tick label) can be outside the bounding box of the
- # Axes and inaxes will be None
- # also check that ax is None so that it traverse objects
- # which do not have an axes property but children might
- a.pick(mouseevent)
- def set_picker(self, picker):
- """
- Define the picking behavior of the artist.
- Parameters
- ----------
- picker : None or bool or float or callable
- This can be one of the following:
- - *None*: Picking is disabled for this artist (default).
- - A boolean: If *True* then picking will be enabled and the
- artist will fire a pick event if the mouse event is over
- the artist.
- - A float: If picker is a number it is interpreted as an
- epsilon tolerance in points and the artist will fire
- off an event if its data is within epsilon of the mouse
- event. For some artists like lines and patch collections,
- the artist may provide additional data to the pick event
- that is generated, e.g., the indices of the data within
- epsilon of the pick event
- - A function: If picker is callable, it is a user supplied
- function which determines whether the artist is hit by the
- mouse event::
- hit, props = picker(artist, mouseevent)
- to determine the hit test. if the mouse event is over the
- artist, return *hit=True* and props is a dictionary of
- properties you want added to the PickEvent attributes.
- """
- self._picker = picker
- def get_picker(self):
- """
- Return the picking behavior of the artist.
- The possible values are described in `.Artist.set_picker`.
- See Also
- --------
- .Artist.set_picker, .Artist.pickable, .Artist.pick
- """
- return self._picker
- def get_url(self):
- """Return the url."""
- return self._url
- def set_url(self, url):
- """
- Set the url for the artist.
- Parameters
- ----------
- url : str
- """
- self._url = url
- def get_gid(self):
- """Return the group id."""
- return self._gid
- def set_gid(self, gid):
- """
- Set the (group) id for the artist.
- Parameters
- ----------
- gid : str
- """
- self._gid = gid
- def get_snap(self):
- """
- Return the snap setting.
- See `.set_snap` for details.
- """
- if mpl.rcParams['path.snap']:
- return self._snap
- else:
- return False
- def set_snap(self, snap):
- """
- Set the snapping behavior.
- Snapping aligns positions with the pixel grid, which results in
- clearer images. For example, if a black line of 1px width was
- defined at a position in between two pixels, the resulting image
- would contain the interpolated value of that line in the pixel grid,
- which would be a grey value on both adjacent pixel positions. In
- contrast, snapping will move the line to the nearest integer pixel
- value, so that the resulting image will really contain a 1px wide
- black line.
- Snapping is currently only supported by the Agg and MacOSX backends.
- Parameters
- ----------
- snap : bool or None
- Possible values:
- - *True*: Snap vertices to the nearest pixel center.
- - *False*: Do not modify vertex positions.
- - *None*: (auto) If the path contains only rectilinear line
- segments, round to the nearest pixel center.
- """
- self._snap = snap
- self.stale = True
- def get_sketch_params(self):
- """
- Return the sketch parameters for the artist.
- Returns
- -------
- tuple or None
- A 3-tuple with the following elements:
- - *scale*: The amplitude of the wiggle perpendicular to the
- source line.
- - *length*: The length of the wiggle along the line.
- - *randomness*: The scale factor by which the length is
- shrunken or expanded.
- Returns *None* if no sketch parameters were set.
- """
- return self._sketch
- def set_sketch_params(self, scale=None, length=None, randomness=None):
- """
- Set the sketch parameters.
- Parameters
- ----------
- scale : float, optional
- The amplitude of the wiggle perpendicular to the source
- line, in pixels. If scale is `None`, or not provided, no
- sketch filter will be provided.
- length : float, optional
- The length of the wiggle along the line, in pixels
- (default 128.0)
- randomness : float, optional
- The scale factor by which the length is shrunken or
- expanded (default 16.0)
- The PGF backend uses this argument as an RNG seed and not as
- described above. Using the same seed yields the same random shape.
- .. ACCEPTS: (scale: float, length: float, randomness: float)
- """
- if scale is None:
- self._sketch = None
- else:
- self._sketch = (scale, length or 128.0, randomness or 16.0)
- self.stale = True
- def set_path_effects(self, path_effects):
- """
- Set the path effects.
- Parameters
- ----------
- path_effects : list of `.AbstractPathEffect`
- """
- self._path_effects = path_effects
- self.stale = True
- def get_path_effects(self):
- return self._path_effects
- def get_figure(self, root=False):
- """
- Return the `.Figure` or `.SubFigure` instance the artist belongs to.
- Parameters
- ----------
- root : bool, default=False
- If False, return the (Sub)Figure this artist is on. If True,
- return the root Figure for a nested tree of SubFigures.
- """
- if root and self._parent_figure is not None:
- return self._parent_figure.get_figure(root=True)
- return self._parent_figure
- def set_figure(self, fig):
- """
- Set the `.Figure` or `.SubFigure` instance the artist belongs to.
- Parameters
- ----------
- fig : `~matplotlib.figure.Figure` or `~matplotlib.figure.SubFigure`
- """
- # if this is a no-op just return
- if self._parent_figure is fig:
- return
- # if we currently have a figure (the case of both `self.figure`
- # and *fig* being none is taken care of above) we then user is
- # trying to change the figure an artist is associated with which
- # is not allowed for the same reason as adding the same instance
- # to more than one Axes
- if self._parent_figure is not None:
- raise RuntimeError("Can not put single artist in "
- "more than one figure")
- self._parent_figure = fig
- if self._parent_figure and self._parent_figure is not self:
- self.pchanged()
- self.stale = True
- figure = property(get_figure, set_figure,
- doc=("The (Sub)Figure that the artist is on. For more "
- "control, use the `get_figure` method."))
- def set_clip_box(self, clipbox):
- """
- Set the artist's clip `.Bbox`.
- Parameters
- ----------
- clipbox : `~matplotlib.transforms.BboxBase` or None
- Will typically be created from a `.TransformedBbox`. For instance,
- ``TransformedBbox(Bbox([[0, 0], [1, 1]]), ax.transAxes)`` is the default
- clipping for an artist added to an Axes.
- """
- _api.check_isinstance((BboxBase, None), clipbox=clipbox)
- if clipbox != self.clipbox:
- self.clipbox = clipbox
- self.pchanged()
- self.stale = True
- def set_clip_path(self, path, transform=None):
- """
- Set the artist's clip path.
- Parameters
- ----------
- path : `~matplotlib.patches.Patch` or `.Path` or `.TransformedPath` or None
- The clip path. If given a `.Path`, *transform* must be provided as
- well. If *None*, a previously set clip path is removed.
- transform : `~matplotlib.transforms.Transform`, optional
- Only used if *path* is a `.Path`, in which case the given `.Path`
- is converted to a `.TransformedPath` using *transform*.
- Notes
- -----
- For efficiency, if *path* is a `.Rectangle` this method will set the
- clipping box to the corresponding rectangle and set the clipping path
- to ``None``.
- For technical reasons (support of `~.Artist.set`), a tuple
- (*path*, *transform*) is also accepted as a single positional
- parameter.
- .. ACCEPTS: Patch or (Path, Transform) or None
- """
- from matplotlib.patches import Patch, Rectangle
- success = False
- if transform is None:
- if isinstance(path, Rectangle):
- self.clipbox = TransformedBbox(Bbox.unit(),
- path.get_transform())
- self._clippath = None
- success = True
- elif isinstance(path, Patch):
- self._clippath = TransformedPatchPath(path)
- success = True
- elif isinstance(path, tuple):
- path, transform = path
- if path is None:
- self._clippath = None
- success = True
- elif isinstance(path, Path):
- self._clippath = TransformedPath(path, transform)
- success = True
- elif isinstance(path, TransformedPatchPath):
- self._clippath = path
- success = True
- elif isinstance(path, TransformedPath):
- self._clippath = path
- success = True
- if not success:
- raise TypeError(
- "Invalid arguments to set_clip_path, of type "
- f"{type(path).__name__} and {type(transform).__name__}")
- # This may result in the callbacks being hit twice, but guarantees they
- # will be hit at least once.
- self.pchanged()
- self.stale = True
- def get_alpha(self):
- """
- Return the alpha value used for blending - not supported on all
- backends.
- """
- return self._alpha
- def get_visible(self):
- """Return the visibility."""
- return self._visible
- def get_animated(self):
- """Return whether the artist is animated."""
- return self._animated
- def get_in_layout(self):
- """
- Return boolean flag, ``True`` if artist is included in layout
- calculations.
- E.g. :ref:`constrainedlayout_guide`,
- `.Figure.tight_layout()`, and
- ``fig.savefig(fname, bbox_inches='tight')``.
- """
- return self._in_layout
- def _fully_clipped_to_axes(self):
- """
- Return a boolean flag, ``True`` if the artist is clipped to the Axes
- and can thus be skipped in layout calculations. Requires `get_clip_on`
- is True, one of `clip_box` or `clip_path` is set, ``clip_box.extents``
- is equivalent to ``ax.bbox.extents`` (if set), and ``clip_path._patch``
- is equivalent to ``ax.patch`` (if set).
- """
- # Note that ``clip_path.get_fully_transformed_path().get_extents()``
- # cannot be directly compared to ``axes.bbox.extents`` because the
- # extents may be undefined (i.e. equivalent to ``Bbox.null()``)
- # before the associated artist is drawn, and this method is meant
- # to determine whether ``axes.get_tightbbox()`` may bypass drawing
- clip_box = self.get_clip_box()
- clip_path = self.get_clip_path()
- return (self.axes is not None
- and self.get_clip_on()
- and (clip_box is not None or clip_path is not None)
- and (clip_box is None
- or np.all(clip_box.extents == self.axes.bbox.extents))
- and (clip_path is None
- or isinstance(clip_path, TransformedPatchPath)
- and clip_path._patch is self.axes.patch))
- def get_clip_on(self):
- """Return whether the artist uses clipping."""
- return self._clipon
- def get_clip_box(self):
- """Return the clipbox."""
- return self.clipbox
- def get_clip_path(self):
- """Return the clip path."""
- return self._clippath
- def get_transformed_clip_path_and_affine(self):
- """
- Return the clip path with the non-affine part of its
- transformation applied, and the remaining affine part of its
- transformation.
- """
- if self._clippath is not None:
- return self._clippath.get_transformed_path_and_affine()
- return None, None
- def set_clip_on(self, b):
- """
- Set whether the artist uses clipping.
- When False, artists will be visible outside the Axes which
- can lead to unexpected results.
- Parameters
- ----------
- b : bool
- """
- self._clipon = b
- # This may result in the callbacks being hit twice, but ensures they
- # are hit at least once
- self.pchanged()
- self.stale = True
- def _set_gc_clip(self, gc):
- """Set the clip properly for the gc."""
- if self._clipon:
- if self.clipbox is not None:
- gc.set_clip_rectangle(self.clipbox)
- gc.set_clip_path(self._clippath)
- else:
- gc.set_clip_rectangle(None)
- gc.set_clip_path(None)
- def get_rasterized(self):
- """Return whether the artist is to be rasterized."""
- return self._rasterized
- def set_rasterized(self, rasterized):
- """
- Force rasterized (bitmap) drawing for vector graphics output.
- Rasterized drawing is not supported by all artists. If you try to
- enable this on an artist that does not support it, the command has no
- effect and a warning will be issued.
- This setting is ignored for pixel-based output.
- See also :doc:`/gallery/misc/rasterization_demo`.
- Parameters
- ----------
- rasterized : bool
- """
- supports_rasterization = getattr(self.draw,
- "_supports_rasterization", False)
- if rasterized and not supports_rasterization:
- _api.warn_external(f"Rasterization of '{self}' will be ignored")
- self._rasterized = rasterized
- def get_agg_filter(self):
- """Return filter function to be used for agg filter."""
- return self._agg_filter
- def set_agg_filter(self, filter_func):
- """
- Set the agg filter.
- Parameters
- ----------
- filter_func : callable
- A filter function, which takes a (m, n, depth) float array
- and a dpi value, and returns a (m, n, depth) array and two
- offsets from the bottom left corner of the image
- .. ACCEPTS: a filter function, which takes a (m, n, 3) float array
- and a dpi value, and returns a (m, n, 3) array and two offsets
- from the bottom left corner of the image
- """
- self._agg_filter = filter_func
- self.stale = True
- def draw(self, renderer):
- """
- Draw the Artist (and its children) using the given renderer.
- This has no effect if the artist is not visible (`.Artist.get_visible`
- returns False).
- Parameters
- ----------
- renderer : `~matplotlib.backend_bases.RendererBase` subclass.
- Notes
- -----
- This method is overridden in the Artist subclasses.
- """
- if not self.get_visible():
- return
- self.stale = False
- def set_alpha(self, alpha):
- """
- Set the alpha value used for blending - not supported on all backends.
- Parameters
- ----------
- alpha : float or None
- *alpha* must be within the 0-1 range, inclusive.
- """
- if alpha is not None and not isinstance(alpha, Real):
- raise TypeError(
- f'alpha must be numeric or None, not {type(alpha)}')
- if alpha is not None and not (0 <= alpha <= 1):
- raise ValueError(f'alpha ({alpha}) is outside 0-1 range')
- if alpha != self._alpha:
- self._alpha = alpha
- self.pchanged()
- self.stale = True
- def _set_alpha_for_array(self, alpha):
- """
- Set the alpha value used for blending - not supported on all backends.
- Parameters
- ----------
- alpha : array-like or float or None
- All values must be within the 0-1 range, inclusive.
- Masked values and nans are not supported.
- """
- if isinstance(alpha, str):
- raise TypeError("alpha must be numeric or None, not a string")
- if not np.iterable(alpha):
- Artist.set_alpha(self, alpha)
- return
- alpha = np.asarray(alpha)
- if not (0 <= alpha.min() and alpha.max() <= 1):
- raise ValueError('alpha must be between 0 and 1, inclusive, '
- f'but min is {alpha.min()}, max is {alpha.max()}')
- self._alpha = alpha
- self.pchanged()
- self.stale = True
- def set_visible(self, b):
- """
- Set the artist's visibility.
- Parameters
- ----------
- b : bool
- """
- if b != self._visible:
- self._visible = b
- self.pchanged()
- self.stale = True
- def set_animated(self, b):
- """
- Set whether the artist is intended to be used in an animation.
- If True, the artist is excluded from regular drawing of the figure.
- You have to call `.Figure.draw_artist` / `.Axes.draw_artist`
- explicitly on the artist. This approach is used to speed up animations
- using blitting.
- See also `matplotlib.animation` and
- :ref:`blitting`.
- Parameters
- ----------
- b : bool
- """
- if self._animated != b:
- self._animated = b
- self.pchanged()
- def set_in_layout(self, in_layout):
- """
- Set if artist is to be included in layout calculations,
- E.g. :ref:`constrainedlayout_guide`,
- `.Figure.tight_layout()`, and
- ``fig.savefig(fname, bbox_inches='tight')``.
- Parameters
- ----------
- in_layout : bool
- """
- self._in_layout = in_layout
- def get_label(self):
- """Return the label used for this artist in the legend."""
- return self._label
- def set_label(self, s):
- """
- Set a label that will be displayed in the legend.
- Parameters
- ----------
- s : object
- *s* will be converted to a string by calling `str`.
- """
- label = str(s) if s is not None else None
- if label != self._label:
- self._label = label
- self.pchanged()
- self.stale = True
- def get_zorder(self):
- """Return the artist's zorder."""
- return self.zorder
- def set_zorder(self, level):
- """
- Set the zorder for the artist. Artists with lower zorder
- values are drawn first.
- Parameters
- ----------
- level : float
- """
- if level is None:
- level = self.__class__.zorder
- if level != self.zorder:
- self.zorder = level
- self.pchanged()
- self.stale = True
- @property
- def sticky_edges(self):
- """
- ``x`` and ``y`` sticky edge lists for autoscaling.
- When performing autoscaling, if a data limit coincides with a value in
- the corresponding sticky_edges list, then no margin will be added--the
- view limit "sticks" to the edge. A typical use case is histograms,
- where one usually expects no margin on the bottom edge (0) of the
- histogram.
- Moreover, margin expansion "bumps" against sticky edges and cannot
- cross them. For example, if the upper data limit is 1.0, the upper
- view limit computed by simple margin application is 1.2, but there is a
- sticky edge at 1.1, then the actual upper view limit will be 1.1.
- This attribute cannot be assigned to; however, the ``x`` and ``y``
- lists can be modified in place as needed.
- Examples
- --------
- >>> artist.sticky_edges.x[:] = (xmin, xmax)
- >>> artist.sticky_edges.y[:] = (ymin, ymax)
- """
- return self._sticky_edges
- def update_from(self, other):
- """Copy properties from *other* to *self*."""
- self._transform = other._transform
- self._transformSet = other._transformSet
- self._visible = other._visible
- self._alpha = other._alpha
- self.clipbox = other.clipbox
- self._clipon = other._clipon
- self._clippath = other._clippath
- self._label = other._label
- self._sketch = other._sketch
- self._path_effects = other._path_effects
- self.sticky_edges.x[:] = other.sticky_edges.x.copy()
- self.sticky_edges.y[:] = other.sticky_edges.y.copy()
- self.pchanged()
- self.stale = True
- def properties(self):
- """Return a dictionary of all the properties of the artist."""
- return ArtistInspector(self).properties()
- def _update_props(self, props, errfmt):
- """
- Helper for `.Artist.set` and `.Artist.update`.
- *errfmt* is used to generate error messages for invalid property
- names; it gets formatted with ``type(self)`` for "{cls}" and the
- property name for "{prop_name}".
- """
- ret = []
- with cbook._setattr_cm(self, eventson=False):
- for k, v in props.items():
- # Allow attributes we want to be able to update through
- # art.update, art.set, setp.
- if k == "axes":
- ret.append(setattr(self, k, v))
- else:
- func = getattr(self, f"set_{k}", None)
- if not callable(func):
- raise AttributeError(
- errfmt.format(cls=type(self), prop_name=k),
- name=k)
- ret.append(func(v))
- if ret:
- self.pchanged()
- self.stale = True
- return ret
- def update(self, props):
- """
- Update this artist's properties from the dict *props*.
- Parameters
- ----------
- props : dict
- """
- return self._update_props(
- props, "{cls.__name__!r} object has no property {prop_name!r}")
- def _internal_update(self, kwargs):
- """
- Update artist properties without prenormalizing them, but generating
- errors as if calling `set`.
- The lack of prenormalization is to maintain backcompatibility.
- """
- return self._update_props(
- kwargs, "{cls.__name__}.set() got an unexpected keyword argument "
- "{prop_name!r}")
- def set(self, **kwargs):
- # docstring and signature are auto-generated via
- # Artist._update_set_signature_and_docstring() at the end of the
- # module.
- return self._internal_update(cbook.normalize_kwargs(kwargs, self))
- @contextlib.contextmanager
- def _cm_set(self, **kwargs):
- """
- `.Artist.set` context-manager that restores original values at exit.
- """
- orig_vals = {k: getattr(self, f"get_{k}")() for k in kwargs}
- try:
- self.set(**kwargs)
- yield
- finally:
- self.set(**orig_vals)
- def findobj(self, match=None, include_self=True):
- """
- Find artist objects.
- Recursively find all `.Artist` instances contained in the artist.
- Parameters
- ----------
- match
- A filter criterion for the matches. This can be
- - *None*: Return all objects contained in artist.
- - A function with signature ``def match(artist: Artist) -> bool``.
- The result will only contain artists for which the function
- returns *True*.
- - A class instance: e.g., `.Line2D`. The result will only contain
- artists of this class or its subclasses (``isinstance`` check).
- include_self : bool
- Include *self* in the list to be checked for a match.
- Returns
- -------
- list of `.Artist`
- """
- if match is None: # always return True
- def matchfunc(x):
- return True
- elif isinstance(match, type) and issubclass(match, Artist):
- def matchfunc(x):
- return isinstance(x, match)
- elif callable(match):
- matchfunc = match
- else:
- raise ValueError('match must be None, a matplotlib.artist.Artist '
- 'subclass, or a callable')
- artists = reduce(operator.iadd,
- [c.findobj(matchfunc) for c in self.get_children()], [])
- if include_self and matchfunc(self):
- artists.append(self)
- return artists
- def get_cursor_data(self, event):
- """
- Return the cursor data for a given event.
- .. note::
- This method is intended to be overridden by artist subclasses.
- As an end-user of Matplotlib you will most likely not call this
- method yourself.
- Cursor data can be used by Artists to provide additional context
- information for a given event. The default implementation just returns
- *None*.
- Subclasses can override the method and return arbitrary data. However,
- when doing so, they must ensure that `.format_cursor_data` can convert
- the data to a string representation.
- The only current use case is displaying the z-value of an `.AxesImage`
- in the status bar of a plot window, while moving the mouse.
- Parameters
- ----------
- event : `~matplotlib.backend_bases.MouseEvent`
- See Also
- --------
- format_cursor_data
- """
- return None
- def format_cursor_data(self, data):
- """
- Return a string representation of *data*.
- .. note::
- This method is intended to be overridden by artist subclasses.
- As an end-user of Matplotlib you will most likely not call this
- method yourself.
- The default implementation converts ints and floats and arrays of ints
- and floats into a comma-separated string enclosed in square brackets,
- unless the artist has an associated colorbar, in which case scalar
- values are formatted using the colorbar's formatter.
- See Also
- --------
- get_cursor_data
- """
- if np.ndim(data) == 0 and hasattr(self, "_format_cursor_data_override"):
- # workaround for ScalarMappable to be able to define its own
- # format_cursor_data(). See ScalarMappable._format_cursor_data_override
- # for details.
- return self._format_cursor_data_override(data)
- else:
- try:
- data[0]
- except (TypeError, IndexError):
- data = [data]
- data_str = ', '.join(f'{item:0.3g}' for item in data
- if isinstance(item, Number))
- return "[" + data_str + "]"
- def get_mouseover(self):
- """
- Return whether this artist is queried for custom context information
- when the mouse cursor moves over it.
- """
- return self._mouseover
- def set_mouseover(self, mouseover):
- """
- Set whether this artist is queried for custom context information when
- the mouse cursor moves over it.
- Parameters
- ----------
- mouseover : bool
- See Also
- --------
- get_cursor_data
- .ToolCursorPosition
- .NavigationToolbar2
- """
- self._mouseover = bool(mouseover)
- ax = self.axes
- if ax:
- if self._mouseover:
- ax._mouseover_set.add(self)
- else:
- ax._mouseover_set.discard(self)
- mouseover = property(get_mouseover, set_mouseover) # backcompat.
- def _get_tightbbox_for_layout_only(obj, *args, **kwargs):
- """
- Matplotlib's `.Axes.get_tightbbox` and `.Axis.get_tightbbox` support a
- *for_layout_only* kwarg; this helper tries to use the kwarg but skips it
- when encountering third-party subclasses that do not support it.
- """
- try:
- return obj.get_tightbbox(*args, **{**kwargs, "for_layout_only": True})
- except TypeError:
- return obj.get_tightbbox(*args, **kwargs)
- class ArtistInspector:
- """
- A helper class to inspect an `~matplotlib.artist.Artist` and return
- information about its settable properties and their current values.
- """
- def __init__(self, o):
- r"""
- Initialize the artist inspector with an `Artist` or an iterable of
- `Artist`\s. If an iterable is used, we assume it is a homogeneous
- sequence (all `Artist`\s are of the same type) and it is your
- responsibility to make sure this is so.
- """
- if not isinstance(o, Artist):
- if np.iterable(o):
- o = list(o)
- if len(o):
- o = o[0]
- self.oorig = o
- if not isinstance(o, type):
- o = type(o)
- self.o = o
- self.aliasd = self.get_aliases()
- def get_aliases(self):
- """
- Get a dict mapping property fullnames to sets of aliases for each alias
- in the :class:`~matplotlib.artist.ArtistInspector`.
- e.g., for lines::
- {'markerfacecolor': {'mfc'},
- 'linewidth' : {'lw'},
- }
- """
- names = [name for name in dir(self.o)
- if name.startswith(('set_', 'get_'))
- and callable(getattr(self.o, name))]
- aliases = {}
- for name in names:
- func = getattr(self.o, name)
- if not self.is_alias(func):
- continue
- propname = re.search(f"`({name[:4]}.*)`", # get_.*/set_.*
- inspect.getdoc(func)).group(1)
- aliases.setdefault(propname[4:], set()).add(name[4:])
- return aliases
- _get_valid_values_regex = re.compile(
- r"\n\s*(?:\.\.\s+)?ACCEPTS:\s*((?:.|\n)*?)(?:$|(?:\n\n))"
- )
- def get_valid_values(self, attr):
- """
- Get the legal arguments for the setter associated with *attr*.
- This is done by querying the docstring of the setter for a line that
- begins with "ACCEPTS:" or ".. ACCEPTS:", and then by looking for a
- numpydoc-style documentation for the setter's first argument.
- """
- name = 'set_%s' % attr
- if not hasattr(self.o, name):
- raise AttributeError(f'{self.o} has no function {name}')
- func = getattr(self.o, name)
- if hasattr(func, '_kwarg_doc'):
- return func._kwarg_doc
- docstring = inspect.getdoc(func)
- if docstring is None:
- return 'unknown'
- if docstring.startswith('Alias for '):
- return None
- match = self._get_valid_values_regex.search(docstring)
- if match is not None:
- return re.sub("\n *", " ", match.group(1))
- # Much faster than list(inspect.signature(func).parameters)[1],
- # although barely relevant wrt. matplotlib's total import time.
- param_name = func.__code__.co_varnames[1]
- # We could set the presence * based on whether the parameter is a
- # varargs (it can't be a varkwargs) but it's not really worth it.
- match = re.search(fr"(?m)^ *\*?{param_name} : (.+)", docstring)
- if match:
- return match.group(1)
- return 'unknown'
- def _replace_path(self, source_class):
- """
- Changes the full path to the public API path that is used
- in sphinx. This is needed for links to work.
- """
- replace_dict = {'_base._AxesBase': 'Axes',
- '_axes.Axes': 'Axes'}
- for key, value in replace_dict.items():
- source_class = source_class.replace(key, value)
- return source_class
- def get_setters(self):
- """
- Get the attribute strings with setters for object.
- For example, for a line, return ``['markerfacecolor', 'linewidth',
- ....]``.
- """
- setters = []
- for name in dir(self.o):
- if not name.startswith('set_'):
- continue
- func = getattr(self.o, name)
- if (not callable(func)
- or self.number_of_parameters(func) < 2
- or self.is_alias(func)):
- continue
- setters.append(name[4:])
- return setters
- @staticmethod
- @cache
- def number_of_parameters(func):
- """Return number of parameters of the callable *func*."""
- return len(inspect.signature(func).parameters)
- @staticmethod
- @cache
- def is_alias(method):
- """
- Return whether the object *method* is an alias for another method.
- """
- ds = inspect.getdoc(method)
- if ds is None:
- return False
- return ds.startswith('Alias for ')
- def aliased_name(self, s):
- """
- Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME'.
- For example, for the line markerfacecolor property, which has an
- alias, return 'markerfacecolor or mfc' and for the transform
- property, which does not, return 'transform'.
- """
- aliases = ''.join(' or %s' % x for x in sorted(self.aliasd.get(s, [])))
- return s + aliases
- _NOT_LINKABLE = {
- # A set of property setter methods that are not available in our
- # current docs. This is a workaround used to prevent trying to link
- # these setters which would lead to "target reference not found"
- # warnings during doc build.
- 'matplotlib.image._ImageBase.set_alpha',
- 'matplotlib.image._ImageBase.set_array',
- 'matplotlib.image._ImageBase.set_data',
- 'matplotlib.image._ImageBase.set_filternorm',
- 'matplotlib.image._ImageBase.set_filterrad',
- 'matplotlib.image._ImageBase.set_interpolation',
- 'matplotlib.image._ImageBase.set_interpolation_stage',
- 'matplotlib.image._ImageBase.set_resample',
- 'matplotlib.text._AnnotationBase.set_annotation_clip',
- }
- def aliased_name_rest(self, s, target):
- """
- Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME',
- formatted for reST.
- For example, for the line markerfacecolor property, which has an
- alias, return 'markerfacecolor or mfc' and for the transform
- property, which does not, return 'transform'.
- """
- # workaround to prevent "reference target not found"
- if target in self._NOT_LINKABLE:
- return f'``{s}``'
- aliases = ''.join(
- f' or :meth:`{a} <{target}>`' for a in sorted(self.aliasd.get(s, [])))
- return f':meth:`{s} <{target}>`{aliases}'
- def pprint_setters(self, prop=None, leadingspace=2):
- """
- If *prop* is *None*, return a list of strings of all settable
- properties and their valid values.
- If *prop* is not *None*, it is a valid property name and that
- property will be returned as a string of property : valid
- values.
- """
- if leadingspace:
- pad = ' ' * leadingspace
- else:
- pad = ''
- if prop is not None:
- accepts = self.get_valid_values(prop)
- return f'{pad}{prop}: {accepts}'
- lines = []
- for prop in sorted(self.get_setters()):
- accepts = self.get_valid_values(prop)
- name = self.aliased_name(prop)
- lines.append(f'{pad}{name}: {accepts}')
- return lines
- def pprint_setters_rest(self, prop=None, leadingspace=4):
- """
- If *prop* is *None*, return a list of reST-formatted strings of all
- settable properties and their valid values.
- If *prop* is not *None*, it is a valid property name and that
- property will be returned as a string of "property : valid"
- values.
- """
- if leadingspace:
- pad = ' ' * leadingspace
- else:
- pad = ''
- if prop is not None:
- accepts = self.get_valid_values(prop)
- return f'{pad}{prop}: {accepts}'
- prop_and_qualnames = []
- for prop in sorted(self.get_setters()):
- # Find the parent method which actually provides the docstring.
- for cls in self.o.__mro__:
- method = getattr(cls, f"set_{prop}", None)
- if method and method.__doc__ is not None:
- break
- else: # No docstring available.
- method = getattr(self.o, f"set_{prop}")
- prop_and_qualnames.append(
- (prop, f"{method.__module__}.{method.__qualname__}"))
- names = [self.aliased_name_rest(prop, target)
- .replace('_base._AxesBase', 'Axes')
- .replace('_axes.Axes', 'Axes')
- for prop, target in prop_and_qualnames]
- accepts = [self.get_valid_values(prop)
- for prop, _ in prop_and_qualnames]
- col0_len = max(len(n) for n in names)
- col1_len = max(len(a) for a in accepts)
- table_formatstr = pad + ' ' + '=' * col0_len + ' ' + '=' * col1_len
- return [
- '',
- pad + '.. table::',
- pad + ' :class: property-table',
- '',
- table_formatstr,
- pad + ' ' + 'Property'.ljust(col0_len)
- + ' ' + 'Description'.ljust(col1_len),
- table_formatstr,
- *[pad + ' ' + n.ljust(col0_len) + ' ' + a.ljust(col1_len)
- for n, a in zip(names, accepts)],
- table_formatstr,
- '',
- ]
- def properties(self):
- """Return a dictionary mapping property name -> value."""
- o = self.oorig
- getters = [name for name in dir(o)
- if name.startswith('get_') and callable(getattr(o, name))]
- getters.sort()
- d = {}
- for name in getters:
- func = getattr(o, name)
- if self.is_alias(func):
- continue
- try:
- with warnings.catch_warnings():
- warnings.simplefilter('ignore')
- val = func()
- except Exception:
- continue
- else:
- d[name[4:]] = val
- return d
- def pprint_getters(self):
- """Return the getters and actual values as list of strings."""
- lines = []
- for name, val in sorted(self.properties().items()):
- if getattr(val, 'shape', ()) != () and len(val) > 6:
- s = str(val[:6]) + '...'
- else:
- s = str(val)
- s = s.replace('\n', ' ')
- if len(s) > 50:
- s = s[:50] + '...'
- name = self.aliased_name(name)
- lines.append(f' {name} = {s}')
- return lines
- def getp(obj, property=None):
- """
- Return the value of an `.Artist`'s *property*, or print all of them.
- Parameters
- ----------
- obj : `~matplotlib.artist.Artist`
- The queried artist; e.g., a `.Line2D`, a `.Text`, or an `~.axes.Axes`.
- property : str or None, default: None
- If *property* is 'somename', this function returns
- ``obj.get_somename()``.
- If it's None (or unset), it *prints* all gettable properties from
- *obj*. Many properties have aliases for shorter typing, e.g. 'lw' is
- an alias for 'linewidth'. In the output, aliases and full property
- names will be listed as:
- property or alias = value
- e.g.:
- linewidth or lw = 2
- See Also
- --------
- setp
- """
- if property is None:
- insp = ArtistInspector(obj)
- ret = insp.pprint_getters()
- print('\n'.join(ret))
- return
- return getattr(obj, 'get_' + property)()
- # alias
- get = getp
- def setp(obj, *args, file=None, **kwargs):
- """
- Set one or more properties on an `.Artist`, or list allowed values.
- Parameters
- ----------
- obj : `~matplotlib.artist.Artist` or list of `.Artist`
- The artist(s) whose properties are being set or queried. When setting
- properties, all artists are affected; when querying the allowed values,
- only the first instance in the sequence is queried.
- For example, two lines can be made thicker and red with a single call:
- >>> x = arange(0, 1, 0.01)
- >>> lines = plot(x, sin(2*pi*x), x, sin(4*pi*x))
- >>> setp(lines, linewidth=2, color='r')
- file : file-like, default: `sys.stdout`
- Where `setp` writes its output when asked to list allowed values.
- >>> with open('output.log') as file:
- ... setp(line, file=file)
- The default, ``None``, means `sys.stdout`.
- *args, **kwargs
- The properties to set. The following combinations are supported:
- - Set the linestyle of a line to be dashed:
- >>> line, = plot([1, 2, 3])
- >>> setp(line, linestyle='--')
- - Set multiple properties at once:
- >>> setp(line, linewidth=2, color='r')
- - List allowed values for a line's linestyle:
- >>> setp(line, 'linestyle')
- linestyle: {'-', '--', '-.', ':', '', (offset, on-off-seq), ...}
- - List all properties that can be set, and their allowed values:
- >>> setp(line)
- agg_filter: a filter function, ...
- [long output listing omitted]
- `setp` also supports MATLAB style string/value pairs. For example, the
- following are equivalent:
- >>> setp(lines, 'linewidth', 2, 'color', 'r') # MATLAB style
- >>> setp(lines, linewidth=2, color='r') # Python style
- See Also
- --------
- getp
- """
- if isinstance(obj, Artist):
- objs = [obj]
- else:
- objs = list(cbook.flatten(obj))
- if not objs:
- return
- insp = ArtistInspector(objs[0])
- if not kwargs and len(args) < 2:
- if args:
- print(insp.pprint_setters(prop=args[0]), file=file)
- else:
- print('\n'.join(insp.pprint_setters()), file=file)
- return
- if len(args) % 2:
- raise ValueError('The set args must be string, value pairs')
- funcvals = dict(zip(args[::2], args[1::2]))
- ret = [o.update(funcvals) for o in objs] + [o.set(**kwargs) for o in objs]
- return list(cbook.flatten(ret))
- def kwdoc(artist):
- r"""
- Inspect an `~matplotlib.artist.Artist` class (using `.ArtistInspector`) and
- return information about its settable properties and their current values.
- Parameters
- ----------
- artist : `~matplotlib.artist.Artist` or an iterable of `Artist`\s
- Returns
- -------
- str
- The settable properties of *artist*, as plain text if
- :rc:`docstring.hardcopy` is False and as a rst table (intended for
- use in Sphinx) if it is True.
- """
- ai = ArtistInspector(artist)
- return ('\n'.join(ai.pprint_setters_rest(leadingspace=4))
- if mpl.rcParams['docstring.hardcopy'] else
- 'Properties:\n' + '\n'.join(ai.pprint_setters(leadingspace=4)))
- # We defer this to the end of them module, because it needs ArtistInspector
- # to be defined.
- Artist._update_set_signature_and_docstring()
|