123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860 |
- from collections import namedtuple
- import contextlib
- from functools import cache, wraps
- import inspect
- from inspect import Signature, Parameter
- import logging
- from numbers import Number, Real
- import re
- import warnings
- import numpy as np
- import matplotlib as mpl
- from . import _api, cbook
- from .colors import BoundaryNorm
- from .cm import ScalarMappable
- 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 artist.figure and
- artist.figure.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.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 self.figure:
- if not _ax_flag:
- self.figure.stale = True
- self.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 re-use 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 self.figure is not None
- and event.canvas is not self.figure.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.
- """
- inside, info = self._default_contains(mouseevent)
- if inside is not None:
- return inside, info
- _log.warning("%r needs 'contains' method", self.__class__.__name__)
- return False, {}
- def pickable(self):
- """
- Return whether the artist is pickable.
- See Also
- --------
- set_picker, get_picker, pick
- """
- return self.figure 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
- --------
- set_picker, get_picker, 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.figure.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 (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 `.set_picker`.
- See Also
- --------
- set_picker, pickable, 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):
- """Return the `.Figure` instance the artist belongs to."""
- return self.figure
- def set_figure(self, fig):
- """
- Set the `.Figure` instance the artist belongs to.
- Parameters
- ----------
- fig : `~matplotlib.figure.Figure`
- """
- # if this is a no-op just return
- if self.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.figure is not None:
- raise RuntimeError("Can not put single artist in "
- "more than one figure")
- self.figure = fig
- if self.figure and self.figure is not self:
- self.pchanged()
- self.stale = True
- 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 : scalar 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 scalar 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)`` and the property 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))
- 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 = sum([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 isinstance(self, ScalarMappable):
- # This block logically belongs to ScalarMappable, but can't be
- # implemented in it because most ScalarMappable subclasses inherit
- # from Artist first and from ScalarMappable second, so
- # Artist.format_cursor_data would always have precedence over
- # ScalarMappable.format_cursor_data.
- n = self.cmap.N
- if np.ma.getmask(data):
- return "[]"
- normed = self.norm(data)
- if np.isfinite(normed):
- if isinstance(self.norm, BoundaryNorm):
- # not an invertible normalization mapping
- cur_idx = np.argmin(np.abs(self.norm.boundaries - data))
- neigh_idx = max(0, cur_idx - 1)
- # use max diff to prevent delta == 0
- delta = np.diff(
- self.norm.boundaries[neigh_idx:cur_idx + 2]
- ).max()
- else:
- # Midpoints of neighboring color intervals.
- neighbors = self.norm.inverse(
- (int(normed * n) + np.array([0, 1])) / n)
- delta = abs(neighbors - data).max()
- g_sig_digits = cbook._g_sig_digits(data, delta)
- else:
- g_sig_digits = 3 # Consistent with default below.
- return f"[{data:-#.{g_sig_digits}g}]"
- 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)
- 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(' or %s' % x for x 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()
|