1715 lines
56 KiB
Python
1715 lines
56 KiB
Python
|
from collections import OrderedDict, namedtuple
|
||
|
from functools import wraps
|
||
|
import inspect
|
||
|
import logging
|
||
|
from numbers import Number
|
||
|
import re
|
||
|
import warnings
|
||
|
|
||
|
import numpy as np
|
||
|
|
||
|
import matplotlib as mpl
|
||
|
from . import _api, cbook, docstring
|
||
|
from .path import Path
|
||
|
from .transforms import (Bbox, IdentityTransform, Transform, TransformedBbox,
|
||
|
TransformedPatchPath, TransformedPath)
|
||
|
|
||
|
_log = logging.getLogger(__name__)
|
||
|
|
||
|
|
||
|
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.
|
||
|
"""
|
||
|
|
||
|
# Axes has a second (deprecated) argument inframe for its draw method.
|
||
|
# args and kwargs are deprecated, but we don't wrap this in
|
||
|
# _api.delete_parameter for performance; the relevant deprecation
|
||
|
# warning will be emitted by the inner draw() call.
|
||
|
@wraps(draw)
|
||
|
def draw_wrapper(artist, renderer, *args, **kwargs):
|
||
|
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, *args, **kwargs)
|
||
|
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 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__(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._contains = 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()
|
||
|
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()
|
||
|
# remove the unpicklable remove method, this will get re-added on load
|
||
|
# (by the axes) if the artist lives on an axes.
|
||
|
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)
|
||
|
# mark the axes as stale
|
||
|
self.axes.stale = True
|
||
|
# decouple the artist from the axes
|
||
|
self.axes = None
|
||
|
_ax_flag = True
|
||
|
|
||
|
if self.figure:
|
||
|
self.figure = None
|
||
|
if not _ax_flag:
|
||
|
self.figure = True
|
||
|
|
||
|
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._get_axis_list())
|
||
|
|
||
|
def convert_xunits(self, x):
|
||
|
"""
|
||
|
Convert *x* using the unit type of the xaxis.
|
||
|
|
||
|
If the artist is not in 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 in 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.get_animated():
|
||
|
return
|
||
|
|
||
|
if val and self.stale_callback is not None:
|
||
|
self.stale_callback(self, val)
|
||
|
|
||
|
def get_window_extent(self, renderer):
|
||
|
"""
|
||
|
Get the axes 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_clipping_extent_bbox(self):
|
||
|
"""
|
||
|
Return a bbox with the extents of the intersection of the clip_path
|
||
|
and clip_box for this artist, or None if both of these are
|
||
|
None, or ``get_clip_on`` is False.
|
||
|
"""
|
||
|
bbox = None
|
||
|
if self.get_clip_on():
|
||
|
clip_box = self.get_clip_box()
|
||
|
if clip_box is not None:
|
||
|
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 get_tightbbox(self, renderer):
|
||
|
"""
|
||
|
Like `.Artist.get_window_extent`, but includes any clipping.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
renderer : `.RendererBase` subclass
|
||
|
renderer that will be used to draw the figures (i.e.
|
||
|
``fig.canvas.get_renderer()``)
|
||
|
|
||
|
Returns
|
||
|
-------
|
||
|
`.Bbox`
|
||
|
The enclosing bounding box (in figure pixel coordinates).
|
||
|
"""
|
||
|
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 : `.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 _default_contains(self, mouseevent, figure=None):
|
||
|
"""
|
||
|
Base impl. for checking whether a mouseevent happened in an artist.
|
||
|
|
||
|
1. If the artist defines a custom checker, use it (deprecated).
|
||
|
2. If the artist figure is known and the event did not occur in that
|
||
|
figure (by checking its ``canvas`` attribute), reject it.
|
||
|
3. Otherwise, return `None, {}`, indicating that the subclass'
|
||
|
implementation should be used.
|
||
|
|
||
|
Subclasses should start their definition of `contains` as follows:
|
||
|
|
||
|
inside, info = self._default_contains(mouseevent)
|
||
|
if inside is not None:
|
||
|
return inside, info
|
||
|
# subclass-specific implementation follows
|
||
|
|
||
|
The *figure* kwarg is provided for the implementation of
|
||
|
`.Figure.contains`.
|
||
|
"""
|
||
|
if callable(self._contains):
|
||
|
return self._contains(self, mouseevent)
|
||
|
if figure is not None and mouseevent.canvas is not figure.canvas:
|
||
|
return False, {}
|
||
|
return None, {}
|
||
|
|
||
|
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, {}
|
||
|
|
||
|
@_api.deprecated("3.3", alternative="set_picker")
|
||
|
def set_contains(self, picker):
|
||
|
"""
|
||
|
Define a custom contains test for the artist.
|
||
|
|
||
|
The provided callable replaces the default `.contains` method
|
||
|
of the artist.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
picker : callable
|
||
|
A custom picker function to evaluate if an event is within the
|
||
|
artist. The function must have the signature::
|
||
|
|
||
|
def contains(artist: Artist, event: MouseEvent) -> bool, dict
|
||
|
|
||
|
that returns:
|
||
|
|
||
|
- a bool indicating if the event is within the artist
|
||
|
- a dict of additional information. The dict should at least
|
||
|
return the same information as the default ``contains()``
|
||
|
implementation of the respective artist, but may provide
|
||
|
additional information.
|
||
|
"""
|
||
|
if not callable(picker):
|
||
|
raise TypeError("picker is not a callable")
|
||
|
self._contains = picker
|
||
|
|
||
|
@_api.deprecated("3.3", alternative="get_picker")
|
||
|
def get_contains(self):
|
||
|
"""
|
||
|
Return the custom contains function of the artist if set, or *None*.
|
||
|
|
||
|
See Also
|
||
|
--------
|
||
|
set_contains
|
||
|
"""
|
||
|
return self._contains
|
||
|
|
||
|
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
|
||
|
"""
|
||
|
# 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:
|
||
|
self.figure.canvas.pick_event(mouseevent, self, **prop)
|
||
|
|
||
|
# 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 no 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)
|
||
|
|
||
|
.. 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 : `.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 : `.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 : `.Bbox`
|
||
|
"""
|
||
|
self.clipbox = clipbox
|
||
|
self.pchanged()
|
||
|
self.stale = True
|
||
|
|
||
|
def set_clip_path(self, path, transform=None):
|
||
|
"""
|
||
|
Set the artist's clip path.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
path : `.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 {} and {}"
|
||
|
.format(type(path).__name__, 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. :doc:`/tutorials/intermediate/constrainedlayout_guide`,
|
||
|
`.Figure.tight_layout()`, and
|
||
|
``fig.savefig(fname, bbox_inches='tight')``.
|
||
|
"""
|
||
|
return self._in_layout
|
||
|
|
||
|
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 of 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
|
||
|
"""
|
||
|
if rasterized and not hasattr(self.draw, "_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, 3) float array and a dpi
|
||
|
value, and returns a (m, n, 3) array.
|
||
|
|
||
|
.. ACCEPTS: a filter function, which takes a (m, n, 3) float array
|
||
|
and a dpi value, and returns a (m, n, 3) array
|
||
|
"""
|
||
|
self._agg_filter = filter_func
|
||
|
self.stale = True
|
||
|
|
||
|
@_api.delete_parameter("3.3", "args")
|
||
|
@_api.delete_parameter("3.3", "kwargs")
|
||
|
def draw(self, renderer, *args, **kwargs):
|
||
|
"""
|
||
|
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 : `.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, Number):
|
||
|
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')
|
||
|
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
|
||
|
"""
|
||
|
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 appoach is used to speed up animations
|
||
|
using blitting.
|
||
|
|
||
|
See also `matplotlib.animation` and
|
||
|
:doc:`/tutorials/advanced/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. :doc:`/tutorials/intermediate/constrainedlayout_guide`,
|
||
|
`.Figure.tight_layout()`, and
|
||
|
``fig.savefig(fname, bbox_inches='tight')``.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
in_layout : bool
|
||
|
"""
|
||
|
self._in_layout = in_layout
|
||
|
|
||
|
def update(self, props):
|
||
|
"""
|
||
|
Update this artist's properties from the dict *props*.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
props : dict
|
||
|
"""
|
||
|
ret = []
|
||
|
with cbook._setattr_cm(self, eventson=False):
|
||
|
for k, v in props.items():
|
||
|
if k != k.lower():
|
||
|
_api.warn_deprecated(
|
||
|
"3.3", message="Case-insensitive properties were "
|
||
|
"deprecated in %(since)s and support will be removed "
|
||
|
"%(removal)s")
|
||
|
k = k.lower()
|
||
|
# White list 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(f"{type(self).__name__!r} object "
|
||
|
f"has no property {k!r}")
|
||
|
ret.append(func(v))
|
||
|
if ret:
|
||
|
self.pchanged()
|
||
|
self.stale = True
|
||
|
return ret
|
||
|
|
||
|
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`.
|
||
|
"""
|
||
|
if s is not None:
|
||
|
self._label = str(s)
|
||
|
else:
|
||
|
self._label = None
|
||
|
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
|
||
|
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.
|
||
|
|
||
|
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 set(self, **kwargs):
|
||
|
"""A property batch setter. Pass *kwargs* to set properties."""
|
||
|
kwargs = cbook.normalize_kwargs(kwargs, self)
|
||
|
move_color_to_start = False
|
||
|
if "color" in kwargs:
|
||
|
keys = [*kwargs]
|
||
|
i_color = keys.index("color")
|
||
|
props = ["edgecolor", "facecolor"]
|
||
|
if any(tp.__module__ == "matplotlib.collections"
|
||
|
and tp.__name__ == "Collection"
|
||
|
for tp in type(self).__mro__):
|
||
|
props.append("alpha")
|
||
|
for other in props:
|
||
|
if other not in keys:
|
||
|
continue
|
||
|
i_other = keys.index(other)
|
||
|
if i_other < i_color:
|
||
|
move_color_to_start = True
|
||
|
_api.warn_deprecated(
|
||
|
"3.3", message=f"You have passed the {other!r} kwarg "
|
||
|
"before the 'color' kwarg. Artist.set() currently "
|
||
|
"reorders the properties to apply 'color' first, but "
|
||
|
"this is deprecated since %(since)s and will be "
|
||
|
"removed %(removal)s; please pass 'color' first "
|
||
|
"instead.")
|
||
|
if move_color_to_start:
|
||
|
kwargs = {"color": kwargs.pop("color"), **kwargs}
|
||
|
return self.update(kwargs)
|
||
|
|
||
|
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.
|
||
|
|
||
|
See Also
|
||
|
--------
|
||
|
get_cursor_data
|
||
|
"""
|
||
|
try:
|
||
|
data[0]
|
||
|
except (TypeError, IndexError):
|
||
|
data = [data]
|
||
|
data_str = ', '.join('{:0.3g}'.format(item) for item in data
|
||
|
if isinstance(item, Number))
|
||
|
return "[" + data_str + "]"
|
||
|
|
||
|
@property
|
||
|
def mouseover(self):
|
||
|
"""
|
||
|
If this property is set to *True*, the artist will be queried for
|
||
|
custom context information when the mouse cursor moves over it.
|
||
|
|
||
|
See also :meth:`get_cursor_data`, :class:`.ToolCursorPosition` and
|
||
|
:class:`.NavigationToolbar2`.
|
||
|
"""
|
||
|
return self._mouseover
|
||
|
|
||
|
@mouseover.setter
|
||
|
def mouseover(self, val):
|
||
|
val = bool(val)
|
||
|
self._mouseover = val
|
||
|
ax = self.axes
|
||
|
if ax:
|
||
|
if val:
|
||
|
ax._mouseover_set.add(self)
|
||
|
else:
|
||
|
ax._mouseover_set.discard(self)
|
||
|
|
||
|
|
||
|
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("`({}.*)`".format(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('%s has no function %s' % (self.o, 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 the it.
|
||
|
match = re.search(r"(?m)^ *\*?{} : (.+)".format(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 len(inspect.signature(func).parameters) < 2
|
||
|
or self.is_alias(func)):
|
||
|
continue
|
||
|
setters.append(name[4:])
|
||
|
return setters
|
||
|
|
||
|
def is_alias(self, o):
|
||
|
"""Return whether method object *o* is an alias for another method."""
|
||
|
ds = inspect.getdoc(o)
|
||
|
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'.
|
||
|
|
||
|
e.g., 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
|
||
|
|
||
|
def aliased_name_rest(self, s, target):
|
||
|
"""
|
||
|
Return 'PROPNAME or alias' if *s* has an alias, else return 'PROPNAME',
|
||
|
formatted for reST.
|
||
|
|
||
|
e.g., 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 ':meth:`%s <%s>`%s' % (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 '%s%s: %s' % (pad, prop, accepts)
|
||
|
|
||
|
lines = []
|
||
|
for prop in sorted(self.get_setters()):
|
||
|
accepts = self.get_valid_values(prop)
|
||
|
name = self.aliased_name(prop)
|
||
|
lines.append('%s%s: %s' % (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 '%s%s: %s' % (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(' %s = %s' % (name, s))
|
||
|
return lines
|
||
|
|
||
|
|
||
|
def getp(obj, property=None):
|
||
|
"""
|
||
|
Return the value of an `.Artist`'s *property*, or print all of them.
|
||
|
|
||
|
Parameters
|
||
|
----------
|
||
|
obj : `.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 is is 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 : `.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')
|
||
|
|
||
|
# put args into ordereddict to maintain order
|
||
|
funcvals = OrderedDict((k, v) for k, v in 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)))
|
||
|
|
||
|
|
||
|
docstring.interpd.update(Artist_kwdoc=kwdoc(Artist))
|