You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			1614 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			1614 lines
		
	
	
		
			53 KiB
		
	
	
	
		
			Python
		
	
r"""
 | 
						|
Container classes for `.Artist`\s.
 | 
						|
 | 
						|
`OffsetBox`
 | 
						|
    The base of all container artists defined in this module.
 | 
						|
 | 
						|
`AnchoredOffsetbox`, `AnchoredText`
 | 
						|
    Anchor and align an arbitrary `.Artist` or a text relative to the parent
 | 
						|
    axes or a specific anchor point.
 | 
						|
 | 
						|
`DrawingArea`
 | 
						|
    A container with fixed width and height. Children have a fixed position
 | 
						|
    inside the container and may be clipped.
 | 
						|
 | 
						|
`HPacker`, `VPacker`
 | 
						|
    Containers for layouting their children vertically or horizontally.
 | 
						|
 | 
						|
`PaddedBox`
 | 
						|
    A container to add a padding around an `.Artist`.
 | 
						|
 | 
						|
`TextArea`
 | 
						|
    Contains a single `.Text` instance.
 | 
						|
"""
 | 
						|
 | 
						|
import functools
 | 
						|
 | 
						|
import numpy as np
 | 
						|
 | 
						|
import matplotlib as mpl
 | 
						|
from matplotlib import _api, _docstring
 | 
						|
import matplotlib.artist as martist
 | 
						|
import matplotlib.path as mpath
 | 
						|
import matplotlib.text as mtext
 | 
						|
import matplotlib.transforms as mtransforms
 | 
						|
from matplotlib.font_manager import FontProperties
 | 
						|
from matplotlib.image import BboxImage
 | 
						|
from matplotlib.patches import (
 | 
						|
    FancyBboxPatch, FancyArrowPatch, bbox_artist as mbbox_artist)
 | 
						|
from matplotlib.transforms import Bbox, BboxBase, TransformedBbox
 | 
						|
 | 
						|
 | 
						|
DEBUG = False
 | 
						|
 | 
						|
 | 
						|
def _compat_get_offset(meth):
 | 
						|
    """
 | 
						|
    Decorator for the get_offset method of OffsetBox and subclasses, that
 | 
						|
    allows supporting both the new signature (self, bbox, renderer) and the old
 | 
						|
    signature (self, width, height, xdescent, ydescent, renderer).
 | 
						|
    """
 | 
						|
    sigs = [lambda self, width, height, xdescent, ydescent, renderer: locals(),
 | 
						|
            lambda self, bbox, renderer: locals()]
 | 
						|
 | 
						|
    @functools.wraps(meth)
 | 
						|
    def get_offset(self, *args, **kwargs):
 | 
						|
        params = _api.select_matching_signature(sigs, self, *args, **kwargs)
 | 
						|
        bbox = (params["bbox"] if "bbox" in params else
 | 
						|
                Bbox.from_bounds(-params["xdescent"], -params["ydescent"],
 | 
						|
                                 params["width"], params["height"]))
 | 
						|
        return meth(params["self"], bbox, params["renderer"])
 | 
						|
    return get_offset
 | 
						|
 | 
						|
 | 
						|
# for debugging use
 | 
						|
def _bbox_artist(*args, **kwargs):
 | 
						|
    if DEBUG:
 | 
						|
        mbbox_artist(*args, **kwargs)
 | 
						|
 | 
						|
 | 
						|
def _get_packed_offsets(widths, total, sep, mode="fixed"):
 | 
						|
    r"""
 | 
						|
    Pack boxes specified by their *widths*.
 | 
						|
 | 
						|
    For simplicity of the description, the terminology used here assumes a
 | 
						|
    horizontal layout, but the function works equally for a vertical layout.
 | 
						|
 | 
						|
    There are three packing *mode*\s:
 | 
						|
 | 
						|
    - 'fixed': The elements are packed tight to the left with a spacing of
 | 
						|
      *sep* in between. If *total* is *None* the returned total will be the
 | 
						|
      right edge of the last box. A non-*None* total will be passed unchecked
 | 
						|
      to the output. In particular this means that right edge of the last
 | 
						|
      box may be further to the right than the returned total.
 | 
						|
 | 
						|
    - 'expand': Distribute the boxes with equal spacing so that the left edge
 | 
						|
      of the first box is at 0, and the right edge of the last box is at
 | 
						|
      *total*. The parameter *sep* is ignored in this mode. A total of *None*
 | 
						|
      is accepted and considered equal to 1. The total is returned unchanged
 | 
						|
      (except for the conversion *None* to 1). If the total is smaller than
 | 
						|
      the sum of the widths, the laid out boxes will overlap.
 | 
						|
 | 
						|
    - 'equal': If *total* is given, the total space is divided in N equal
 | 
						|
      ranges and each box is left-aligned within its subspace.
 | 
						|
      Otherwise (*total* is *None*), *sep* must be provided and each box is
 | 
						|
      left-aligned in its subspace of width ``(max(widths) + sep)``. The
 | 
						|
      total width is then calculated to be ``N * (max(widths) + sep)``.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    widths : list of float
 | 
						|
        Widths of boxes to be packed.
 | 
						|
    total : float or None
 | 
						|
        Intended total length. *None* if not used.
 | 
						|
    sep : float or None
 | 
						|
        Spacing between boxes.
 | 
						|
    mode : {'fixed', 'expand', 'equal'}
 | 
						|
        The packing mode.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    total : float
 | 
						|
        The total width needed to accommodate the laid out boxes.
 | 
						|
    offsets : array of float
 | 
						|
        The left offsets of the boxes.
 | 
						|
    """
 | 
						|
    _api.check_in_list(["fixed", "expand", "equal"], mode=mode)
 | 
						|
 | 
						|
    if mode == "fixed":
 | 
						|
        offsets_ = np.cumsum([0] + [w + sep for w in widths])
 | 
						|
        offsets = offsets_[:-1]
 | 
						|
        if total is None:
 | 
						|
            total = offsets_[-1] - sep
 | 
						|
        return total, offsets
 | 
						|
 | 
						|
    elif mode == "expand":
 | 
						|
        # This is a bit of a hack to avoid a TypeError when *total*
 | 
						|
        # is None and used in conjugation with tight layout.
 | 
						|
        if total is None:
 | 
						|
            total = 1
 | 
						|
        if len(widths) > 1:
 | 
						|
            sep = (total - sum(widths)) / (len(widths) - 1)
 | 
						|
        else:
 | 
						|
            sep = 0
 | 
						|
        offsets_ = np.cumsum([0] + [w + sep for w in widths])
 | 
						|
        offsets = offsets_[:-1]
 | 
						|
        return total, offsets
 | 
						|
 | 
						|
    elif mode == "equal":
 | 
						|
        maxh = max(widths)
 | 
						|
        if total is None:
 | 
						|
            if sep is None:
 | 
						|
                raise ValueError("total and sep cannot both be None when "
 | 
						|
                                 "using layout mode 'equal'")
 | 
						|
            total = (maxh + sep) * len(widths)
 | 
						|
        else:
 | 
						|
            sep = total / len(widths) - maxh
 | 
						|
        offsets = (maxh + sep) * np.arange(len(widths))
 | 
						|
        return total, offsets
 | 
						|
 | 
						|
 | 
						|
def _get_aligned_offsets(yspans, height, align="baseline"):
 | 
						|
    """
 | 
						|
    Align boxes each specified by their ``(y0, y1)`` spans.
 | 
						|
 | 
						|
    For simplicity of the description, the terminology used here assumes a
 | 
						|
    horizontal layout (i.e., vertical alignment), but the function works
 | 
						|
    equally for a vertical layout.
 | 
						|
 | 
						|
    Parameters
 | 
						|
    ----------
 | 
						|
    yspans
 | 
						|
        List of (y0, y1) spans of boxes to be aligned.
 | 
						|
    height : float or None
 | 
						|
        Intended total height. If None, the maximum of the heights
 | 
						|
        (``y1 - y0``) in *yspans* is used.
 | 
						|
    align : {'baseline', 'left', 'top', 'right', 'bottom', 'center'}
 | 
						|
        The alignment anchor of the boxes.
 | 
						|
 | 
						|
    Returns
 | 
						|
    -------
 | 
						|
    (y0, y1)
 | 
						|
        y range spanned by the packing.  If a *height* was originally passed
 | 
						|
        in, then for all alignments other than "baseline", a span of ``(0,
 | 
						|
        height)`` is used without checking that it is actually large enough).
 | 
						|
    descent
 | 
						|
        The descent of the packing.
 | 
						|
    offsets
 | 
						|
        The bottom offsets of the boxes.
 | 
						|
    """
 | 
						|
 | 
						|
    _api.check_in_list(
 | 
						|
        ["baseline", "left", "top", "right", "bottom", "center"], align=align)
 | 
						|
    if height is None:
 | 
						|
        height = max(y1 - y0 for y0, y1 in yspans)
 | 
						|
 | 
						|
    if align == "baseline":
 | 
						|
        yspan = (min(y0 for y0, y1 in yspans), max(y1 for y0, y1 in yspans))
 | 
						|
        offsets = [0] * len(yspans)
 | 
						|
    elif align in ["left", "bottom"]:
 | 
						|
        yspan = (0, height)
 | 
						|
        offsets = [-y0 for y0, y1 in yspans]
 | 
						|
    elif align in ["right", "top"]:
 | 
						|
        yspan = (0, height)
 | 
						|
        offsets = [height - y1 for y0, y1 in yspans]
 | 
						|
    elif align == "center":
 | 
						|
        yspan = (0, height)
 | 
						|
        offsets = [(height - (y1 - y0)) * .5 - y0 for y0, y1 in yspans]
 | 
						|
 | 
						|
    return yspan, offsets
 | 
						|
 | 
						|
 | 
						|
class OffsetBox(martist.Artist):
 | 
						|
    """
 | 
						|
    A simple container artist.
 | 
						|
 | 
						|
    The child artists are meant to be drawn at a relative position to its
 | 
						|
    parent.
 | 
						|
 | 
						|
    Being an artist itself, all parameters are passed on to `.Artist`.
 | 
						|
    """
 | 
						|
    def __init__(self, *args, **kwargs):
 | 
						|
        super().__init__(*args)
 | 
						|
        self._internal_update(kwargs)
 | 
						|
        # Clipping has not been implemented in the OffsetBox family, so
 | 
						|
        # disable the clip flag for consistency. It can always be turned back
 | 
						|
        # on to zero effect.
 | 
						|
        self.set_clip_on(False)
 | 
						|
        self._children = []
 | 
						|
        self._offset = (0, 0)
 | 
						|
 | 
						|
    def set_figure(self, fig):
 | 
						|
        """
 | 
						|
        Set the `.Figure` for the `.OffsetBox` and all its children.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        fig : `~matplotlib.figure.Figure`
 | 
						|
        """
 | 
						|
        super().set_figure(fig)
 | 
						|
        for c in self.get_children():
 | 
						|
            c.set_figure(fig)
 | 
						|
 | 
						|
    @martist.Artist.axes.setter
 | 
						|
    def axes(self, ax):
 | 
						|
        # TODO deal with this better
 | 
						|
        martist.Artist.axes.fset(self, ax)
 | 
						|
        for c in self.get_children():
 | 
						|
            if c is not None:
 | 
						|
                c.axes = ax
 | 
						|
 | 
						|
    def contains(self, mouseevent):
 | 
						|
        """
 | 
						|
        Delegate the mouse event contains-check to the children.
 | 
						|
 | 
						|
        As a container, the `.OffsetBox` does not respond itself to
 | 
						|
        mouseevents.
 | 
						|
 | 
						|
        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.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .Artist.contains
 | 
						|
        """
 | 
						|
        if self._different_canvas(mouseevent):
 | 
						|
            return False, {}
 | 
						|
        for c in self.get_children():
 | 
						|
            a, b = c.contains(mouseevent)
 | 
						|
            if a:
 | 
						|
                return a, b
 | 
						|
        return False, {}
 | 
						|
 | 
						|
    def set_offset(self, xy):
 | 
						|
        """
 | 
						|
        Set the offset.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        xy : (float, float) or callable
 | 
						|
            The (x, y) coordinates of the offset in display units. These can
 | 
						|
            either be given explicitly as a tuple (x, y), or by providing a
 | 
						|
            function that converts the extent into the offset. This function
 | 
						|
            must have the signature::
 | 
						|
 | 
						|
                def offset(width, height, xdescent, ydescent, renderer) \
 | 
						|
-> (float, float)
 | 
						|
        """
 | 
						|
        self._offset = xy
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    @_compat_get_offset
 | 
						|
    def get_offset(self, bbox, renderer):
 | 
						|
        """
 | 
						|
        Return the offset as a tuple (x, y).
 | 
						|
 | 
						|
        The extent parameters have to be provided to handle the case where the
 | 
						|
        offset is dynamically determined by a callable (see
 | 
						|
        `~.OffsetBox.set_offset`).
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        bbox : `.Bbox`
 | 
						|
        renderer : `.RendererBase` subclass
 | 
						|
        """
 | 
						|
        return (
 | 
						|
            self._offset(bbox.width, bbox.height, -bbox.x0, -bbox.y0, renderer)
 | 
						|
            if callable(self._offset)
 | 
						|
            else self._offset)
 | 
						|
 | 
						|
    def set_width(self, width):
 | 
						|
        """
 | 
						|
        Set the width of the box.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        width : float
 | 
						|
        """
 | 
						|
        self.width = width
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_height(self, height):
 | 
						|
        """
 | 
						|
        Set the height of the box.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        height : float
 | 
						|
        """
 | 
						|
        self.height = height
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_visible_children(self):
 | 
						|
        r"""Return a list of the visible child `.Artist`\s."""
 | 
						|
        return [c for c in self._children if c.get_visible()]
 | 
						|
 | 
						|
    def get_children(self):
 | 
						|
        r"""Return a list of the child `.Artist`\s."""
 | 
						|
        return self._children
 | 
						|
 | 
						|
    def _get_bbox_and_child_offsets(self, renderer):
 | 
						|
        """
 | 
						|
        Return the bbox of the offsetbox and the child offsets.
 | 
						|
 | 
						|
        The bbox should satisfy ``x0 <= x1 and y0 <= y1``.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        renderer : `.RendererBase` subclass
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        bbox
 | 
						|
        list of (xoffset, yoffset) pairs
 | 
						|
        """
 | 
						|
        raise NotImplementedError(
 | 
						|
            "get_bbox_and_offsets must be overridden in derived classes")
 | 
						|
 | 
						|
    def get_bbox(self, renderer):
 | 
						|
        """Return the bbox of the offsetbox, ignoring parent offsets."""
 | 
						|
        bbox, offsets = self._get_bbox_and_child_offsets(renderer)
 | 
						|
        return bbox
 | 
						|
 | 
						|
    def get_window_extent(self, renderer=None):
 | 
						|
        # docstring inherited
 | 
						|
        if renderer is None:
 | 
						|
            renderer = self.get_figure(root=True)._get_renderer()
 | 
						|
        bbox = self.get_bbox(renderer)
 | 
						|
        try:  # Some subclasses redefine get_offset to take no args.
 | 
						|
            px, py = self.get_offset(bbox, renderer)
 | 
						|
        except TypeError:
 | 
						|
            px, py = self.get_offset()
 | 
						|
        return bbox.translated(px, py)
 | 
						|
 | 
						|
    def draw(self, renderer):
 | 
						|
        """
 | 
						|
        Update the location of children if necessary and draw them
 | 
						|
        to the given *renderer*.
 | 
						|
        """
 | 
						|
        bbox, offsets = self._get_bbox_and_child_offsets(renderer)
 | 
						|
        px, py = self.get_offset(bbox, renderer)
 | 
						|
        for c, (ox, oy) in zip(self.get_visible_children(), offsets):
 | 
						|
            c.set_offset((px + ox, py + oy))
 | 
						|
            c.draw(renderer)
 | 
						|
        _bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
 | 
						|
        self.stale = False
 | 
						|
 | 
						|
 | 
						|
class PackerBase(OffsetBox):
 | 
						|
    def __init__(self, pad=0., sep=0., width=None, height=None,
 | 
						|
                 align="baseline", mode="fixed", children=None):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        pad : float, default: 0.0
 | 
						|
            The boundary padding in points.
 | 
						|
 | 
						|
        sep : float, default: 0.0
 | 
						|
            The spacing between items in points.
 | 
						|
 | 
						|
        width, height : float, optional
 | 
						|
            Width and height of the container box in pixels, calculated if
 | 
						|
            *None*.
 | 
						|
 | 
						|
        align : {'top', 'bottom', 'left', 'right', 'center', 'baseline'}, \
 | 
						|
default: 'baseline'
 | 
						|
            Alignment of boxes.
 | 
						|
 | 
						|
        mode : {'fixed', 'expand', 'equal'}, default: 'fixed'
 | 
						|
            The packing mode.
 | 
						|
 | 
						|
            - 'fixed' packs the given `.Artist`\\s tight with *sep* spacing.
 | 
						|
            - 'expand' uses the maximal available space to distribute the
 | 
						|
              artists with equal spacing in between.
 | 
						|
            - 'equal': Each artist an equal fraction of the available space
 | 
						|
              and is left-aligned (or top-aligned) therein.
 | 
						|
 | 
						|
        children : list of `.Artist`
 | 
						|
            The artists to pack.
 | 
						|
 | 
						|
        Notes
 | 
						|
        -----
 | 
						|
        *pad* and *sep* are in points and will be scaled with the renderer
 | 
						|
        dpi, while *width* and *height* are in pixels.
 | 
						|
        """
 | 
						|
        super().__init__()
 | 
						|
        self.height = height
 | 
						|
        self.width = width
 | 
						|
        self.sep = sep
 | 
						|
        self.pad = pad
 | 
						|
        self.mode = mode
 | 
						|
        self.align = align
 | 
						|
        self._children = children
 | 
						|
 | 
						|
 | 
						|
class VPacker(PackerBase):
 | 
						|
    """
 | 
						|
    VPacker packs its children vertically, automatically adjusting their
 | 
						|
    relative positions at draw time.
 | 
						|
 | 
						|
    .. code-block:: none
 | 
						|
 | 
						|
       +---------+
 | 
						|
       | Child 1 |
 | 
						|
       | Child 2 |
 | 
						|
       | Child 3 |
 | 
						|
       +---------+
 | 
						|
    """
 | 
						|
 | 
						|
    def _get_bbox_and_child_offsets(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        dpicor = renderer.points_to_pixels(1.)
 | 
						|
        pad = self.pad * dpicor
 | 
						|
        sep = self.sep * dpicor
 | 
						|
 | 
						|
        if self.width is not None:
 | 
						|
            for c in self.get_visible_children():
 | 
						|
                if isinstance(c, PackerBase) and c.mode == "expand":
 | 
						|
                    c.set_width(self.width)
 | 
						|
 | 
						|
        bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
 | 
						|
        (x0, x1), xoffsets = _get_aligned_offsets(
 | 
						|
            [bbox.intervalx for bbox in bboxes], self.width, self.align)
 | 
						|
        height, yoffsets = _get_packed_offsets(
 | 
						|
            [bbox.height for bbox in bboxes], self.height, sep, self.mode)
 | 
						|
 | 
						|
        yoffsets = height - (yoffsets + [bbox.y1 for bbox in bboxes])
 | 
						|
        ydescent = yoffsets[0]
 | 
						|
        yoffsets = yoffsets - ydescent
 | 
						|
 | 
						|
        return (
 | 
						|
            Bbox.from_bounds(x0, -ydescent, x1 - x0, height).padded(pad),
 | 
						|
            [*zip(xoffsets, yoffsets)])
 | 
						|
 | 
						|
 | 
						|
class HPacker(PackerBase):
 | 
						|
    """
 | 
						|
    HPacker packs its children horizontally, automatically adjusting their
 | 
						|
    relative positions at draw time.
 | 
						|
 | 
						|
    .. code-block:: none
 | 
						|
 | 
						|
       +-------------------------------+
 | 
						|
       | Child 1    Child 2    Child 3 |
 | 
						|
       +-------------------------------+
 | 
						|
    """
 | 
						|
 | 
						|
    def _get_bbox_and_child_offsets(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        dpicor = renderer.points_to_pixels(1.)
 | 
						|
        pad = self.pad * dpicor
 | 
						|
        sep = self.sep * dpicor
 | 
						|
 | 
						|
        bboxes = [c.get_bbox(renderer) for c in self.get_visible_children()]
 | 
						|
        if not bboxes:
 | 
						|
            return Bbox.from_bounds(0, 0, 0, 0).padded(pad), []
 | 
						|
 | 
						|
        (y0, y1), yoffsets = _get_aligned_offsets(
 | 
						|
            [bbox.intervaly for bbox in bboxes], self.height, self.align)
 | 
						|
        width, xoffsets = _get_packed_offsets(
 | 
						|
            [bbox.width for bbox in bboxes], self.width, sep, self.mode)
 | 
						|
 | 
						|
        x0 = bboxes[0].x0
 | 
						|
        xoffsets -= ([bbox.x0 for bbox in bboxes] - x0)
 | 
						|
 | 
						|
        return (Bbox.from_bounds(x0, y0, width, y1 - y0).padded(pad),
 | 
						|
                [*zip(xoffsets, yoffsets)])
 | 
						|
 | 
						|
 | 
						|
class PaddedBox(OffsetBox):
 | 
						|
    """
 | 
						|
    A container to add a padding around an `.Artist`.
 | 
						|
 | 
						|
    The `.PaddedBox` contains a `.FancyBboxPatch` that is used to visualize
 | 
						|
    it when rendering.
 | 
						|
 | 
						|
    .. code-block:: none
 | 
						|
 | 
						|
       +----------------------------+
 | 
						|
       |                            |
 | 
						|
       |                            |
 | 
						|
       |                            |
 | 
						|
       | <--pad--> Artist           |
 | 
						|
       |             ^              |
 | 
						|
       |            pad             |
 | 
						|
       |             v              |
 | 
						|
       +----------------------------+
 | 
						|
 | 
						|
    Attributes
 | 
						|
    ----------
 | 
						|
    pad : float
 | 
						|
        The padding in points.
 | 
						|
    patch : `.FancyBboxPatch`
 | 
						|
        When *draw_frame* is True, this `.FancyBboxPatch` is made visible and
 | 
						|
        creates a border around the box.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, child, pad=0., *, draw_frame=False, patch_attrs=None):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        child : `~matplotlib.artist.Artist`
 | 
						|
            The contained `.Artist`.
 | 
						|
        pad : float, default: 0.0
 | 
						|
            The padding in points. This will be scaled with the renderer dpi.
 | 
						|
            In contrast, *width* and *height* are in *pixels* and thus not
 | 
						|
            scaled.
 | 
						|
        draw_frame : bool
 | 
						|
            Whether to draw the contained `.FancyBboxPatch`.
 | 
						|
        patch_attrs : dict or None
 | 
						|
            Additional parameters passed to the contained `.FancyBboxPatch`.
 | 
						|
        """
 | 
						|
        super().__init__()
 | 
						|
        self.pad = pad
 | 
						|
        self._children = [child]
 | 
						|
        self.patch = FancyBboxPatch(
 | 
						|
            xy=(0.0, 0.0), width=1., height=1.,
 | 
						|
            facecolor='w', edgecolor='k',
 | 
						|
            mutation_scale=1,  # self.prop.get_size_in_points(),
 | 
						|
            snap=True,
 | 
						|
            visible=draw_frame,
 | 
						|
            boxstyle="square,pad=0",
 | 
						|
        )
 | 
						|
        if patch_attrs is not None:
 | 
						|
            self.patch.update(patch_attrs)
 | 
						|
 | 
						|
    def _get_bbox_and_child_offsets(self, renderer):
 | 
						|
        # docstring inherited.
 | 
						|
        pad = self.pad * renderer.points_to_pixels(1.)
 | 
						|
        return (self._children[0].get_bbox(renderer).padded(pad), [(0, 0)])
 | 
						|
 | 
						|
    def draw(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        bbox, offsets = self._get_bbox_and_child_offsets(renderer)
 | 
						|
        px, py = self.get_offset(bbox, renderer)
 | 
						|
        for c, (ox, oy) in zip(self.get_visible_children(), offsets):
 | 
						|
            c.set_offset((px + ox, py + oy))
 | 
						|
 | 
						|
        self.draw_frame(renderer)
 | 
						|
 | 
						|
        for c in self.get_visible_children():
 | 
						|
            c.draw(renderer)
 | 
						|
 | 
						|
        self.stale = False
 | 
						|
 | 
						|
    def update_frame(self, bbox, fontsize=None):
 | 
						|
        self.patch.set_bounds(bbox.bounds)
 | 
						|
        if fontsize:
 | 
						|
            self.patch.set_mutation_scale(fontsize)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def draw_frame(self, renderer):
 | 
						|
        # update the location and size of the legend
 | 
						|
        self.update_frame(self.get_window_extent(renderer))
 | 
						|
        self.patch.draw(renderer)
 | 
						|
 | 
						|
 | 
						|
class DrawingArea(OffsetBox):
 | 
						|
    """
 | 
						|
    The DrawingArea can contain any Artist as a child. The DrawingArea
 | 
						|
    has a fixed width and height. The position of children relative to
 | 
						|
    the parent is fixed. The children can be clipped at the
 | 
						|
    boundaries of the parent.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, width, height, xdescent=0., ydescent=0., clip=False):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        width, height : float
 | 
						|
            Width and height of the container box.
 | 
						|
        xdescent, ydescent : float
 | 
						|
            Descent of the box in x- and y-direction.
 | 
						|
        clip : bool
 | 
						|
            Whether to clip the children to the box.
 | 
						|
        """
 | 
						|
        super().__init__()
 | 
						|
        self.width = width
 | 
						|
        self.height = height
 | 
						|
        self.xdescent = xdescent
 | 
						|
        self.ydescent = ydescent
 | 
						|
        self._clip_children = clip
 | 
						|
        self.offset_transform = mtransforms.Affine2D()
 | 
						|
        self.dpi_transform = mtransforms.Affine2D()
 | 
						|
 | 
						|
    @property
 | 
						|
    def clip_children(self):
 | 
						|
        """
 | 
						|
        If the children of this DrawingArea should be clipped
 | 
						|
        by DrawingArea bounding box.
 | 
						|
        """
 | 
						|
        return self._clip_children
 | 
						|
 | 
						|
    @clip_children.setter
 | 
						|
    def clip_children(self, val):
 | 
						|
        self._clip_children = bool(val)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_transform(self):
 | 
						|
        """
 | 
						|
        Return the `~matplotlib.transforms.Transform` applied to the children.
 | 
						|
        """
 | 
						|
        return self.dpi_transform + self.offset_transform
 | 
						|
 | 
						|
    def set_transform(self, t):
 | 
						|
        """
 | 
						|
        set_transform is ignored.
 | 
						|
        """
 | 
						|
 | 
						|
    def set_offset(self, xy):
 | 
						|
        """
 | 
						|
        Set the offset of the container.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        xy : (float, float)
 | 
						|
            The (x, y) coordinates of the offset in display units.
 | 
						|
        """
 | 
						|
        self._offset = xy
 | 
						|
        self.offset_transform.clear()
 | 
						|
        self.offset_transform.translate(xy[0], xy[1])
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_offset(self):
 | 
						|
        """Return offset of the container."""
 | 
						|
        return self._offset
 | 
						|
 | 
						|
    def get_bbox(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        dpi_cor = renderer.points_to_pixels(1.)
 | 
						|
        return Bbox.from_bounds(
 | 
						|
            -self.xdescent * dpi_cor, -self.ydescent * dpi_cor,
 | 
						|
            self.width * dpi_cor, self.height * dpi_cor)
 | 
						|
 | 
						|
    def add_artist(self, a):
 | 
						|
        """Add an `.Artist` to the container box."""
 | 
						|
        self._children.append(a)
 | 
						|
        if not a.is_transform_set():
 | 
						|
            a.set_transform(self.get_transform())
 | 
						|
        if self.axes is not None:
 | 
						|
            a.axes = self.axes
 | 
						|
        fig = self.get_figure(root=False)
 | 
						|
        if fig is not None:
 | 
						|
            a.set_figure(fig)
 | 
						|
 | 
						|
    def draw(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
 | 
						|
        dpi_cor = renderer.points_to_pixels(1.)
 | 
						|
        self.dpi_transform.clear()
 | 
						|
        self.dpi_transform.scale(dpi_cor)
 | 
						|
 | 
						|
        # At this point the DrawingArea has a transform
 | 
						|
        # to the display space so the path created is
 | 
						|
        # good for clipping children
 | 
						|
        tpath = mtransforms.TransformedPath(
 | 
						|
            mpath.Path([[0, 0], [0, self.height],
 | 
						|
                        [self.width, self.height],
 | 
						|
                        [self.width, 0]]),
 | 
						|
            self.get_transform())
 | 
						|
        for c in self._children:
 | 
						|
            if self._clip_children and not (c.clipbox or c._clippath):
 | 
						|
                c.set_clip_path(tpath)
 | 
						|
            c.draw(renderer)
 | 
						|
 | 
						|
        _bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
 | 
						|
        self.stale = False
 | 
						|
 | 
						|
 | 
						|
class TextArea(OffsetBox):
 | 
						|
    """
 | 
						|
    The TextArea is a container artist for a single Text instance.
 | 
						|
 | 
						|
    The text is placed at (0, 0) with baseline+left alignment, by default. The
 | 
						|
    width and height of the TextArea instance is the width and height of its
 | 
						|
    child text.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, s,
 | 
						|
                 *,
 | 
						|
                 textprops=None,
 | 
						|
                 multilinebaseline=False,
 | 
						|
                 ):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        s : str
 | 
						|
            The text to be displayed.
 | 
						|
        textprops : dict, default: {}
 | 
						|
            Dictionary of keyword parameters to be passed to the `.Text`
 | 
						|
            instance in the TextArea.
 | 
						|
        multilinebaseline : bool, default: False
 | 
						|
            Whether the baseline for multiline text is adjusted so that it
 | 
						|
            is (approximately) center-aligned with single-line text.
 | 
						|
        """
 | 
						|
        if textprops is None:
 | 
						|
            textprops = {}
 | 
						|
        self._text = mtext.Text(0, 0, s, **textprops)
 | 
						|
        super().__init__()
 | 
						|
        self._children = [self._text]
 | 
						|
        self.offset_transform = mtransforms.Affine2D()
 | 
						|
        self._baseline_transform = mtransforms.Affine2D()
 | 
						|
        self._text.set_transform(self.offset_transform +
 | 
						|
                                 self._baseline_transform)
 | 
						|
        self._multilinebaseline = multilinebaseline
 | 
						|
 | 
						|
    def set_text(self, s):
 | 
						|
        """Set the text of this area as a string."""
 | 
						|
        self._text.set_text(s)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_text(self):
 | 
						|
        """Return the string representation of this area's text."""
 | 
						|
        return self._text.get_text()
 | 
						|
 | 
						|
    def set_multilinebaseline(self, t):
 | 
						|
        """
 | 
						|
        Set multilinebaseline.
 | 
						|
 | 
						|
        If True, the baseline for multiline text is adjusted so that it is
 | 
						|
        (approximately) center-aligned with single-line text.  This is used
 | 
						|
        e.g. by the legend implementation so that single-line labels are
 | 
						|
        baseline-aligned, but multiline labels are "center"-aligned with them.
 | 
						|
        """
 | 
						|
        self._multilinebaseline = t
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_multilinebaseline(self):
 | 
						|
        """
 | 
						|
        Get multilinebaseline.
 | 
						|
        """
 | 
						|
        return self._multilinebaseline
 | 
						|
 | 
						|
    def set_transform(self, t):
 | 
						|
        """
 | 
						|
        set_transform is ignored.
 | 
						|
        """
 | 
						|
 | 
						|
    def set_offset(self, xy):
 | 
						|
        """
 | 
						|
        Set the offset of the container.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        xy : (float, float)
 | 
						|
            The (x, y) coordinates of the offset in display units.
 | 
						|
        """
 | 
						|
        self._offset = xy
 | 
						|
        self.offset_transform.clear()
 | 
						|
        self.offset_transform.translate(xy[0], xy[1])
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_offset(self):
 | 
						|
        """Return offset of the container."""
 | 
						|
        return self._offset
 | 
						|
 | 
						|
    def get_bbox(self, renderer):
 | 
						|
        _, h_, d_ = mtext._get_text_metrics_with_cache(
 | 
						|
            renderer, "lp", self._text._fontproperties,
 | 
						|
            ismath="TeX" if self._text.get_usetex() else False,
 | 
						|
            dpi=self.get_figure(root=True).dpi)
 | 
						|
 | 
						|
        bbox, info, yd = self._text._get_layout(renderer)
 | 
						|
        w, h = bbox.size
 | 
						|
 | 
						|
        self._baseline_transform.clear()
 | 
						|
 | 
						|
        if len(info) > 1 and self._multilinebaseline:
 | 
						|
            yd_new = 0.5 * h - 0.5 * (h_ - d_)
 | 
						|
            self._baseline_transform.translate(0, yd - yd_new)
 | 
						|
            yd = yd_new
 | 
						|
        else:  # single line
 | 
						|
            h_d = max(h_ - d_, h - yd)
 | 
						|
            h = h_d + yd
 | 
						|
 | 
						|
        ha = self._text.get_horizontalalignment()
 | 
						|
        x0 = {"left": 0, "center": -w / 2, "right": -w}[ha]
 | 
						|
 | 
						|
        return Bbox.from_bounds(x0, -yd, w, h)
 | 
						|
 | 
						|
    def draw(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        self._text.draw(renderer)
 | 
						|
        _bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
 | 
						|
        self.stale = False
 | 
						|
 | 
						|
 | 
						|
class AuxTransformBox(OffsetBox):
 | 
						|
    """
 | 
						|
    An OffsetBox with an auxiliary transform.
 | 
						|
 | 
						|
    All child artists are first transformed with *aux_transform*, then
 | 
						|
    translated with an offset (the same for all children) so the bounding
 | 
						|
    box of the children matches the drawn box.  (In other words, adding an
 | 
						|
    arbitrary translation to *aux_transform* has no effect as it will be
 | 
						|
    cancelled out by the later offsetting.)
 | 
						|
 | 
						|
    `AuxTransformBox` is similar to `.DrawingArea`, except that the extent of
 | 
						|
    the box is not predetermined but calculated from the window extent of its
 | 
						|
    children, and the extent of the children will be calculated in the
 | 
						|
    transformed coordinate.
 | 
						|
    """
 | 
						|
    def __init__(self, aux_transform):
 | 
						|
        self.aux_transform = aux_transform
 | 
						|
        super().__init__()
 | 
						|
        self.offset_transform = mtransforms.Affine2D()
 | 
						|
        # ref_offset_transform makes offset_transform always relative to the
 | 
						|
        # lower-left corner of the bbox of its children.
 | 
						|
        self.ref_offset_transform = mtransforms.Affine2D()
 | 
						|
 | 
						|
    def add_artist(self, a):
 | 
						|
        """Add an `.Artist` to the container box."""
 | 
						|
        self._children.append(a)
 | 
						|
        a.set_transform(self.get_transform())
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_transform(self):
 | 
						|
        """Return the `.Transform` applied to the children."""
 | 
						|
        return (self.aux_transform
 | 
						|
                + self.ref_offset_transform
 | 
						|
                + self.offset_transform)
 | 
						|
 | 
						|
    def set_transform(self, t):
 | 
						|
        """
 | 
						|
        set_transform is ignored.
 | 
						|
        """
 | 
						|
 | 
						|
    def set_offset(self, xy):
 | 
						|
        """
 | 
						|
        Set the offset of the container.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        xy : (float, float)
 | 
						|
            The (x, y) coordinates of the offset in display units.
 | 
						|
        """
 | 
						|
        self._offset = xy
 | 
						|
        self.offset_transform.clear()
 | 
						|
        self.offset_transform.translate(xy[0], xy[1])
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_offset(self):
 | 
						|
        """Return offset of the container."""
 | 
						|
        return self._offset
 | 
						|
 | 
						|
    def get_bbox(self, renderer):
 | 
						|
        # clear the offset transforms
 | 
						|
        _off = self.offset_transform.get_matrix()  # to be restored later
 | 
						|
        self.ref_offset_transform.clear()
 | 
						|
        self.offset_transform.clear()
 | 
						|
        # calculate the extent
 | 
						|
        bboxes = [c.get_window_extent(renderer) for c in self._children]
 | 
						|
        ub = Bbox.union(bboxes)
 | 
						|
        # adjust ref_offset_transform
 | 
						|
        self.ref_offset_transform.translate(-ub.x0, -ub.y0)
 | 
						|
        # restore offset transform
 | 
						|
        self.offset_transform.set_matrix(_off)
 | 
						|
        return Bbox.from_bounds(0, 0, ub.width, ub.height)
 | 
						|
 | 
						|
    def draw(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        for c in self._children:
 | 
						|
            c.draw(renderer)
 | 
						|
        _bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
 | 
						|
        self.stale = False
 | 
						|
 | 
						|
 | 
						|
class AnchoredOffsetbox(OffsetBox):
 | 
						|
    """
 | 
						|
    An OffsetBox placed according to location *loc*.
 | 
						|
 | 
						|
    AnchoredOffsetbox has a single child.  When multiple children are needed,
 | 
						|
    use an extra OffsetBox to enclose them.  By default, the offset box is
 | 
						|
    anchored against its parent Axes. You may explicitly specify the
 | 
						|
    *bbox_to_anchor*.
 | 
						|
    """
 | 
						|
    zorder = 5  # zorder of the legend
 | 
						|
 | 
						|
    # Location codes
 | 
						|
    codes = {'upper right': 1,
 | 
						|
             'upper left': 2,
 | 
						|
             'lower left': 3,
 | 
						|
             'lower right': 4,
 | 
						|
             'right': 5,
 | 
						|
             'center left': 6,
 | 
						|
             'center right': 7,
 | 
						|
             'lower center': 8,
 | 
						|
             'upper center': 9,
 | 
						|
             'center': 10,
 | 
						|
             }
 | 
						|
 | 
						|
    def __init__(self, loc, *,
 | 
						|
                 pad=0.4, borderpad=0.5,
 | 
						|
                 child=None, prop=None, frameon=True,
 | 
						|
                 bbox_to_anchor=None,
 | 
						|
                 bbox_transform=None,
 | 
						|
                 **kwargs):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        loc : str
 | 
						|
            The box location.  Valid locations are
 | 
						|
            'upper left', 'upper center', 'upper right',
 | 
						|
            'center left', 'center', 'center right',
 | 
						|
            'lower left', 'lower center', 'lower right'.
 | 
						|
            For backward compatibility, numeric values are accepted as well.
 | 
						|
            See the parameter *loc* of `.Legend` for details.
 | 
						|
        pad : float, default: 0.4
 | 
						|
            Padding around the child as fraction of the fontsize.
 | 
						|
        borderpad : float, default: 0.5
 | 
						|
            Padding between the offsetbox frame and the *bbox_to_anchor*.
 | 
						|
        child : `.OffsetBox`
 | 
						|
            The box that will be anchored.
 | 
						|
        prop : `.FontProperties`
 | 
						|
            This is only used as a reference for paddings. If not given,
 | 
						|
            :rc:`legend.fontsize` is used.
 | 
						|
        frameon : bool
 | 
						|
            Whether to draw a frame around the box.
 | 
						|
        bbox_to_anchor : `.BboxBase`, 2-tuple, or 4-tuple of floats
 | 
						|
            Box that is used to position the legend in conjunction with *loc*.
 | 
						|
        bbox_transform : None or :class:`matplotlib.transforms.Transform`
 | 
						|
            The transform for the bounding box (*bbox_to_anchor*).
 | 
						|
        **kwargs
 | 
						|
            All other parameters are passed on to `.OffsetBox`.
 | 
						|
 | 
						|
        Notes
 | 
						|
        -----
 | 
						|
        See `.Legend` for a detailed description of the anchoring mechanism.
 | 
						|
        """
 | 
						|
        super().__init__(**kwargs)
 | 
						|
 | 
						|
        self.set_bbox_to_anchor(bbox_to_anchor, bbox_transform)
 | 
						|
        self.set_child(child)
 | 
						|
 | 
						|
        if isinstance(loc, str):
 | 
						|
            loc = _api.check_getitem(self.codes, loc=loc)
 | 
						|
 | 
						|
        self.loc = loc
 | 
						|
        self.borderpad = borderpad
 | 
						|
        self.pad = pad
 | 
						|
 | 
						|
        if prop is None:
 | 
						|
            self.prop = FontProperties(size=mpl.rcParams["legend.fontsize"])
 | 
						|
        else:
 | 
						|
            self.prop = FontProperties._from_any(prop)
 | 
						|
            if isinstance(prop, dict) and "size" not in prop:
 | 
						|
                self.prop.set_size(mpl.rcParams["legend.fontsize"])
 | 
						|
 | 
						|
        self.patch = FancyBboxPatch(
 | 
						|
            xy=(0.0, 0.0), width=1., height=1.,
 | 
						|
            facecolor='w', edgecolor='k',
 | 
						|
            mutation_scale=self.prop.get_size_in_points(),
 | 
						|
            snap=True,
 | 
						|
            visible=frameon,
 | 
						|
            boxstyle="square,pad=0",
 | 
						|
        )
 | 
						|
 | 
						|
    def set_child(self, child):
 | 
						|
        """Set the child to be anchored."""
 | 
						|
        self._child = child
 | 
						|
        if child is not None:
 | 
						|
            child.axes = self.axes
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_child(self):
 | 
						|
        """Return the child."""
 | 
						|
        return self._child
 | 
						|
 | 
						|
    def get_children(self):
 | 
						|
        """Return the list of children."""
 | 
						|
        return [self._child]
 | 
						|
 | 
						|
    def get_bbox(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
 | 
						|
        pad = self.pad * fontsize
 | 
						|
        return self.get_child().get_bbox(renderer).padded(pad)
 | 
						|
 | 
						|
    def get_bbox_to_anchor(self):
 | 
						|
        """Return the bbox that the box is anchored to."""
 | 
						|
        if self._bbox_to_anchor is None:
 | 
						|
            return self.axes.bbox
 | 
						|
        else:
 | 
						|
            transform = self._bbox_to_anchor_transform
 | 
						|
            if transform is None:
 | 
						|
                return self._bbox_to_anchor
 | 
						|
            else:
 | 
						|
                return TransformedBbox(self._bbox_to_anchor, transform)
 | 
						|
 | 
						|
    def set_bbox_to_anchor(self, bbox, transform=None):
 | 
						|
        """
 | 
						|
        Set the bbox that the box is anchored to.
 | 
						|
 | 
						|
        *bbox* can be a Bbox instance, a list of [left, bottom, width,
 | 
						|
        height], or a list of [left, bottom] where the width and
 | 
						|
        height will be assumed to be zero. The bbox will be
 | 
						|
        transformed to display coordinate by the given transform.
 | 
						|
        """
 | 
						|
        if bbox is None or isinstance(bbox, BboxBase):
 | 
						|
            self._bbox_to_anchor = bbox
 | 
						|
        else:
 | 
						|
            try:
 | 
						|
                l = len(bbox)
 | 
						|
            except TypeError as err:
 | 
						|
                raise ValueError(f"Invalid bbox: {bbox}") from err
 | 
						|
 | 
						|
            if l == 2:
 | 
						|
                bbox = [bbox[0], bbox[1], 0, 0]
 | 
						|
 | 
						|
            self._bbox_to_anchor = Bbox.from_bounds(*bbox)
 | 
						|
 | 
						|
        self._bbox_to_anchor_transform = transform
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    @_compat_get_offset
 | 
						|
    def get_offset(self, bbox, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        pad = (self.borderpad
 | 
						|
               * renderer.points_to_pixels(self.prop.get_size_in_points()))
 | 
						|
        bbox_to_anchor = self.get_bbox_to_anchor()
 | 
						|
        x0, y0 = _get_anchored_bbox(
 | 
						|
            self.loc, Bbox.from_bounds(0, 0, bbox.width, bbox.height),
 | 
						|
            bbox_to_anchor, pad)
 | 
						|
        return x0 - bbox.x0, y0 - bbox.y0
 | 
						|
 | 
						|
    def update_frame(self, bbox, fontsize=None):
 | 
						|
        self.patch.set_bounds(bbox.bounds)
 | 
						|
        if fontsize:
 | 
						|
            self.patch.set_mutation_scale(fontsize)
 | 
						|
 | 
						|
    def draw(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        if not self.get_visible():
 | 
						|
            return
 | 
						|
 | 
						|
        # update the location and size of the legend
 | 
						|
        bbox = self.get_window_extent(renderer)
 | 
						|
        fontsize = renderer.points_to_pixels(self.prop.get_size_in_points())
 | 
						|
        self.update_frame(bbox, fontsize)
 | 
						|
        self.patch.draw(renderer)
 | 
						|
 | 
						|
        px, py = self.get_offset(self.get_bbox(renderer), renderer)
 | 
						|
        self.get_child().set_offset((px, py))
 | 
						|
        self.get_child().draw(renderer)
 | 
						|
        self.stale = False
 | 
						|
 | 
						|
 | 
						|
def _get_anchored_bbox(loc, bbox, parentbbox, borderpad):
 | 
						|
    """
 | 
						|
    Return the (x, y) position of the *bbox* anchored at the *parentbbox* with
 | 
						|
    the *loc* code with the *borderpad*.
 | 
						|
    """
 | 
						|
    # This is only called internally and *loc* should already have been
 | 
						|
    # validated.  If 0 (None), we just let ``bbox.anchored`` raise.
 | 
						|
    c = [None, "NE", "NW", "SW", "SE", "E", "W", "E", "S", "N", "C"][loc]
 | 
						|
    container = parentbbox.padded(-borderpad)
 | 
						|
    return bbox.anchored(c, container=container).p0
 | 
						|
 | 
						|
 | 
						|
class AnchoredText(AnchoredOffsetbox):
 | 
						|
    """
 | 
						|
    AnchoredOffsetbox with Text.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, s, loc, *, pad=0.4, borderpad=0.5, prop=None, **kwargs):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        s : str
 | 
						|
            Text.
 | 
						|
 | 
						|
        loc : str
 | 
						|
            Location code. See `AnchoredOffsetbox`.
 | 
						|
 | 
						|
        pad : float, default: 0.4
 | 
						|
            Padding around the text as fraction of the fontsize.
 | 
						|
 | 
						|
        borderpad : float, default: 0.5
 | 
						|
            Spacing between the offsetbox frame and the *bbox_to_anchor*.
 | 
						|
 | 
						|
        prop : dict, optional
 | 
						|
            Dictionary of keyword parameters to be passed to the
 | 
						|
            `~matplotlib.text.Text` instance contained inside AnchoredText.
 | 
						|
 | 
						|
        **kwargs
 | 
						|
            All other parameters are passed to `AnchoredOffsetbox`.
 | 
						|
        """
 | 
						|
 | 
						|
        if prop is None:
 | 
						|
            prop = {}
 | 
						|
        badkwargs = {'va', 'verticalalignment'}
 | 
						|
        if badkwargs & set(prop):
 | 
						|
            raise ValueError(
 | 
						|
                'Mixing verticalalignment with AnchoredText is not supported.')
 | 
						|
 | 
						|
        self.txt = TextArea(s, textprops=prop)
 | 
						|
        fp = self.txt._text.get_fontproperties()
 | 
						|
        super().__init__(
 | 
						|
            loc, pad=pad, borderpad=borderpad, child=self.txt, prop=fp,
 | 
						|
            **kwargs)
 | 
						|
 | 
						|
 | 
						|
class OffsetImage(OffsetBox):
 | 
						|
 | 
						|
    def __init__(self, arr, *,
 | 
						|
                 zoom=1,
 | 
						|
                 cmap=None,
 | 
						|
                 norm=None,
 | 
						|
                 interpolation=None,
 | 
						|
                 origin=None,
 | 
						|
                 filternorm=True,
 | 
						|
                 filterrad=4.0,
 | 
						|
                 resample=False,
 | 
						|
                 dpi_cor=True,
 | 
						|
                 **kwargs
 | 
						|
                 ):
 | 
						|
 | 
						|
        super().__init__()
 | 
						|
        self._dpi_cor = dpi_cor
 | 
						|
 | 
						|
        self.image = BboxImage(bbox=self.get_window_extent,
 | 
						|
                               cmap=cmap,
 | 
						|
                               norm=norm,
 | 
						|
                               interpolation=interpolation,
 | 
						|
                               origin=origin,
 | 
						|
                               filternorm=filternorm,
 | 
						|
                               filterrad=filterrad,
 | 
						|
                               resample=resample,
 | 
						|
                               **kwargs
 | 
						|
                               )
 | 
						|
 | 
						|
        self._children = [self.image]
 | 
						|
 | 
						|
        self.set_zoom(zoom)
 | 
						|
        self.set_data(arr)
 | 
						|
 | 
						|
    def set_data(self, arr):
 | 
						|
        self._data = np.asarray(arr)
 | 
						|
        self.image.set_data(self._data)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_data(self):
 | 
						|
        return self._data
 | 
						|
 | 
						|
    def set_zoom(self, zoom):
 | 
						|
        self._zoom = zoom
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_zoom(self):
 | 
						|
        return self._zoom
 | 
						|
 | 
						|
    def get_offset(self):
 | 
						|
        """Return offset of the container."""
 | 
						|
        return self._offset
 | 
						|
 | 
						|
    def get_children(self):
 | 
						|
        return [self.image]
 | 
						|
 | 
						|
    def get_bbox(self, renderer):
 | 
						|
        dpi_cor = renderer.points_to_pixels(1.) if self._dpi_cor else 1.
 | 
						|
        zoom = self.get_zoom()
 | 
						|
        data = self.get_data()
 | 
						|
        ny, nx = data.shape[:2]
 | 
						|
        w, h = dpi_cor * nx * zoom, dpi_cor * ny * zoom
 | 
						|
        return Bbox.from_bounds(0, 0, w, h)
 | 
						|
 | 
						|
    def draw(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        self.image.draw(renderer)
 | 
						|
        # bbox_artist(self, renderer, fill=False, props=dict(pad=0.))
 | 
						|
        self.stale = False
 | 
						|
 | 
						|
 | 
						|
class AnnotationBbox(martist.Artist, mtext._AnnotationBase):
 | 
						|
    """
 | 
						|
    Container for an `OffsetBox` referring to a specific position *xy*.
 | 
						|
 | 
						|
    Optionally an arrow pointing from the offsetbox to *xy* can be drawn.
 | 
						|
 | 
						|
    This is like `.Annotation`, but with `OffsetBox` instead of `.Text`.
 | 
						|
    """
 | 
						|
 | 
						|
    zorder = 3
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return f"AnnotationBbox({self.xy[0]:g},{self.xy[1]:g})"
 | 
						|
 | 
						|
    @_docstring.interpd
 | 
						|
    def __init__(self, offsetbox, xy, xybox=None, xycoords='data', boxcoords=None, *,
 | 
						|
                 frameon=True, pad=0.4,  # FancyBboxPatch boxstyle.
 | 
						|
                 annotation_clip=None,
 | 
						|
                 box_alignment=(0.5, 0.5),
 | 
						|
                 bboxprops=None,
 | 
						|
                 arrowprops=None,
 | 
						|
                 fontsize=None,
 | 
						|
                 **kwargs):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        offsetbox : `OffsetBox`
 | 
						|
 | 
						|
        xy : (float, float)
 | 
						|
            The point *(x, y)* to annotate. The coordinate system is determined
 | 
						|
            by *xycoords*.
 | 
						|
 | 
						|
        xybox : (float, float), default: *xy*
 | 
						|
            The position *(x, y)* to place the text at. The coordinate system
 | 
						|
            is determined by *boxcoords*.
 | 
						|
 | 
						|
        xycoords : single or two-tuple of str or `.Artist` or `.Transform` or \
 | 
						|
callable, default: 'data'
 | 
						|
            The coordinate system that *xy* is given in. See the parameter
 | 
						|
            *xycoords* in `.Annotation` for a detailed description.
 | 
						|
 | 
						|
        boxcoords : single or two-tuple of str or `.Artist` or `.Transform` \
 | 
						|
or callable, default: value of *xycoords*
 | 
						|
            The coordinate system that *xybox* is given in. See the parameter
 | 
						|
            *textcoords* in `.Annotation` for a detailed description.
 | 
						|
 | 
						|
        frameon : bool, default: True
 | 
						|
            By default, the text is surrounded by a white `.FancyBboxPatch`
 | 
						|
            (accessible as the ``patch`` attribute of the `.AnnotationBbox`).
 | 
						|
            If *frameon* is set to False, this patch is made invisible.
 | 
						|
 | 
						|
        annotation_clip: bool or None, default: None
 | 
						|
            Whether to clip (i.e. not draw) the annotation when the annotation
 | 
						|
            point *xy* is outside the Axes area.
 | 
						|
 | 
						|
            - If *True*, the annotation will be clipped when *xy* is outside
 | 
						|
              the Axes.
 | 
						|
            - If *False*, the annotation will always be drawn.
 | 
						|
            - If *None*, the annotation will be clipped when *xy* is outside
 | 
						|
              the Axes and *xycoords* is 'data'.
 | 
						|
 | 
						|
        pad : float, default: 0.4
 | 
						|
            Padding around the offsetbox.
 | 
						|
 | 
						|
        box_alignment : (float, float)
 | 
						|
            A tuple of two floats for a vertical and horizontal alignment of
 | 
						|
            the offset box w.r.t. the *boxcoords*.
 | 
						|
            The lower-left corner is (0, 0) and upper-right corner is (1, 1).
 | 
						|
 | 
						|
        bboxprops : dict, optional
 | 
						|
            A dictionary of properties to set for the annotation bounding box,
 | 
						|
            for example *boxstyle* and *alpha*.  See `.FancyBboxPatch` for
 | 
						|
            details.
 | 
						|
 | 
						|
        arrowprops: dict, optional
 | 
						|
            Arrow properties, see `.Annotation` for description.
 | 
						|
 | 
						|
        fontsize: float or str, optional
 | 
						|
            Translated to points and passed as *mutation_scale* into
 | 
						|
            `.FancyBboxPatch` to scale attributes of the box style (e.g. pad
 | 
						|
            or rounding_size).  The name is chosen in analogy to `.Text` where
 | 
						|
            *fontsize* defines the mutation scale as well.  If not given,
 | 
						|
            :rc:`legend.fontsize` is used.  See `.Text.set_fontsize` for valid
 | 
						|
            values.
 | 
						|
 | 
						|
        **kwargs
 | 
						|
            Other `AnnotationBbox` properties.  See `.AnnotationBbox.set` for
 | 
						|
            a list.
 | 
						|
        """
 | 
						|
 | 
						|
        martist.Artist.__init__(self)
 | 
						|
        mtext._AnnotationBase.__init__(
 | 
						|
            self, xy, xycoords=xycoords, annotation_clip=annotation_clip)
 | 
						|
 | 
						|
        self.offsetbox = offsetbox
 | 
						|
        self.arrowprops = arrowprops.copy() if arrowprops is not None else None
 | 
						|
        self.set_fontsize(fontsize)
 | 
						|
        self.xybox = xybox if xybox is not None else xy
 | 
						|
        self.boxcoords = boxcoords if boxcoords is not None else xycoords
 | 
						|
        self._box_alignment = box_alignment
 | 
						|
 | 
						|
        if arrowprops is not None:
 | 
						|
            self._arrow_relpos = self.arrowprops.pop("relpos", (0.5, 0.5))
 | 
						|
            self.arrow_patch = FancyArrowPatch((0, 0), (1, 1),
 | 
						|
                                               **self.arrowprops)
 | 
						|
        else:
 | 
						|
            self._arrow_relpos = None
 | 
						|
            self.arrow_patch = None
 | 
						|
 | 
						|
        self.patch = FancyBboxPatch(  # frame
 | 
						|
            xy=(0.0, 0.0), width=1., height=1.,
 | 
						|
            facecolor='w', edgecolor='k',
 | 
						|
            mutation_scale=self.prop.get_size_in_points(),
 | 
						|
            snap=True,
 | 
						|
            visible=frameon,
 | 
						|
        )
 | 
						|
        self.patch.set_boxstyle("square", pad=pad)
 | 
						|
        if bboxprops:
 | 
						|
            self.patch.set(**bboxprops)
 | 
						|
 | 
						|
        self._internal_update(kwargs)
 | 
						|
 | 
						|
    @property
 | 
						|
    def xyann(self):
 | 
						|
        return self.xybox
 | 
						|
 | 
						|
    @xyann.setter
 | 
						|
    def xyann(self, xyann):
 | 
						|
        self.xybox = xyann
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    @property
 | 
						|
    def anncoords(self):
 | 
						|
        return self.boxcoords
 | 
						|
 | 
						|
    @anncoords.setter
 | 
						|
    def anncoords(self, coords):
 | 
						|
        self.boxcoords = coords
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def contains(self, mouseevent):
 | 
						|
        if self._different_canvas(mouseevent):
 | 
						|
            return False, {}
 | 
						|
        if not self._check_xy(None):
 | 
						|
            return False, {}
 | 
						|
        return self.offsetbox.contains(mouseevent)
 | 
						|
        # self.arrow_patch is currently not checked as this can be a line - JJ
 | 
						|
 | 
						|
    def get_children(self):
 | 
						|
        children = [self.offsetbox, self.patch]
 | 
						|
        if self.arrow_patch:
 | 
						|
            children.append(self.arrow_patch)
 | 
						|
        return children
 | 
						|
 | 
						|
    def set_figure(self, fig):
 | 
						|
        if self.arrow_patch is not None:
 | 
						|
            self.arrow_patch.set_figure(fig)
 | 
						|
        self.offsetbox.set_figure(fig)
 | 
						|
        martist.Artist.set_figure(self, fig)
 | 
						|
 | 
						|
    def set_fontsize(self, s=None):
 | 
						|
        """
 | 
						|
        Set the fontsize in points.
 | 
						|
 | 
						|
        If *s* is not given, reset to :rc:`legend.fontsize`.
 | 
						|
        """
 | 
						|
        if s is None:
 | 
						|
            s = mpl.rcParams["legend.fontsize"]
 | 
						|
 | 
						|
        self.prop = FontProperties(size=s)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_fontsize(self):
 | 
						|
        """Return the fontsize in points."""
 | 
						|
        return self.prop.get_size_in_points()
 | 
						|
 | 
						|
    def get_window_extent(self, renderer=None):
 | 
						|
        # docstring inherited
 | 
						|
        if renderer is None:
 | 
						|
            renderer = self.get_figure(root=True)._get_renderer()
 | 
						|
        self.update_positions(renderer)
 | 
						|
        return Bbox.union([child.get_window_extent(renderer)
 | 
						|
                           for child in self.get_children()])
 | 
						|
 | 
						|
    def get_tightbbox(self, renderer=None):
 | 
						|
        # docstring inherited
 | 
						|
        if renderer is None:
 | 
						|
            renderer = self.get_figure(root=True)._get_renderer()
 | 
						|
        self.update_positions(renderer)
 | 
						|
        return Bbox.union([child.get_tightbbox(renderer)
 | 
						|
                           for child in self.get_children()])
 | 
						|
 | 
						|
    def update_positions(self, renderer):
 | 
						|
        """Update pixel positions for the annotated point, the text, and the arrow."""
 | 
						|
 | 
						|
        ox0, oy0 = self._get_xy(renderer, self.xybox, self.boxcoords)
 | 
						|
        bbox = self.offsetbox.get_bbox(renderer)
 | 
						|
        fw, fh = self._box_alignment
 | 
						|
        self.offsetbox.set_offset(
 | 
						|
            (ox0 - fw*bbox.width - bbox.x0, oy0 - fh*bbox.height - bbox.y0))
 | 
						|
 | 
						|
        bbox = self.offsetbox.get_window_extent(renderer)
 | 
						|
        self.patch.set_bounds(bbox.bounds)
 | 
						|
 | 
						|
        mutation_scale = renderer.points_to_pixels(self.get_fontsize())
 | 
						|
        self.patch.set_mutation_scale(mutation_scale)
 | 
						|
 | 
						|
        if self.arrowprops:
 | 
						|
            # Use FancyArrowPatch if self.arrowprops has "arrowstyle" key.
 | 
						|
 | 
						|
            # Adjust the starting point of the arrow relative to the textbox.
 | 
						|
            # TODO: Rotation needs to be accounted.
 | 
						|
            arrow_begin = bbox.p0 + bbox.size * self._arrow_relpos
 | 
						|
            arrow_end = self._get_position_xy(renderer)
 | 
						|
            # The arrow (from arrow_begin to arrow_end) will be first clipped
 | 
						|
            # by patchA and patchB, then shrunk by shrinkA and shrinkB (in
 | 
						|
            # points).  If patch A is not set, self.bbox_patch is used.
 | 
						|
            self.arrow_patch.set_positions(arrow_begin, arrow_end)
 | 
						|
 | 
						|
            if "mutation_scale" in self.arrowprops:
 | 
						|
                mutation_scale = renderer.points_to_pixels(
 | 
						|
                    self.arrowprops["mutation_scale"])
 | 
						|
                # Else, use fontsize-based mutation_scale defined above.
 | 
						|
            self.arrow_patch.set_mutation_scale(mutation_scale)
 | 
						|
 | 
						|
            patchA = self.arrowprops.get("patchA", self.patch)
 | 
						|
            self.arrow_patch.set_patchA(patchA)
 | 
						|
 | 
						|
    def draw(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        if not self.get_visible() or not self._check_xy(renderer):
 | 
						|
            return
 | 
						|
        renderer.open_group(self.__class__.__name__, gid=self.get_gid())
 | 
						|
        self.update_positions(renderer)
 | 
						|
        if self.arrow_patch is not None:
 | 
						|
            if (self.arrow_patch.get_figure(root=False) is None and
 | 
						|
                    (fig := self.get_figure(root=False)) is not None):
 | 
						|
                self.arrow_patch.set_figure(fig)
 | 
						|
            self.arrow_patch.draw(renderer)
 | 
						|
        self.patch.draw(renderer)
 | 
						|
        self.offsetbox.draw(renderer)
 | 
						|
        renderer.close_group(self.__class__.__name__)
 | 
						|
        self.stale = False
 | 
						|
 | 
						|
 | 
						|
class DraggableBase:
 | 
						|
    """
 | 
						|
    Helper base class for a draggable artist (legend, offsetbox).
 | 
						|
 | 
						|
    Derived classes must override the following methods::
 | 
						|
 | 
						|
        def save_offset(self):
 | 
						|
            '''
 | 
						|
            Called when the object is picked for dragging; should save the
 | 
						|
            reference position of the artist.
 | 
						|
            '''
 | 
						|
 | 
						|
        def update_offset(self, dx, dy):
 | 
						|
            '''
 | 
						|
            Called during the dragging; (*dx*, *dy*) is the pixel offset from
 | 
						|
            the point where the mouse drag started.
 | 
						|
            '''
 | 
						|
 | 
						|
    Optionally, you may override the following method::
 | 
						|
 | 
						|
        def finalize_offset(self):
 | 
						|
            '''Called when the mouse is released.'''
 | 
						|
 | 
						|
    In the current implementation of `.DraggableLegend` and
 | 
						|
    `DraggableAnnotation`, `update_offset` places the artists in display
 | 
						|
    coordinates, and `finalize_offset` recalculates their position in axes
 | 
						|
    coordinate and set a relevant attribute.
 | 
						|
    """
 | 
						|
 | 
						|
    def __init__(self, ref_artist, use_blit=False):
 | 
						|
        self.ref_artist = ref_artist
 | 
						|
        if not ref_artist.pickable():
 | 
						|
            ref_artist.set_picker(self._picker)
 | 
						|
        self.got_artist = False
 | 
						|
        self._use_blit = use_blit and self.canvas.supports_blit
 | 
						|
        callbacks = self.canvas.callbacks
 | 
						|
        self._disconnectors = [
 | 
						|
            functools.partial(
 | 
						|
                callbacks.disconnect, callbacks._connect_picklable(name, func))
 | 
						|
            for name, func in [
 | 
						|
                ("pick_event", self.on_pick),
 | 
						|
                ("button_release_event", self.on_release),
 | 
						|
                ("motion_notify_event", self.on_motion),
 | 
						|
            ]
 | 
						|
        ]
 | 
						|
 | 
						|
    @staticmethod
 | 
						|
    def _picker(artist, mouseevent):
 | 
						|
        # A custom picker to prevent dragging on mouse scroll events
 | 
						|
        if mouseevent.name == "scroll_event":
 | 
						|
            return False, {}
 | 
						|
        return artist.contains(mouseevent)
 | 
						|
 | 
						|
    # A property, not an attribute, to maintain picklability.
 | 
						|
    canvas = property(lambda self: self.ref_artist.get_figure(root=True).canvas)
 | 
						|
    cids = property(lambda self: [
 | 
						|
        disconnect.args[0] for disconnect in self._disconnectors[:2]])
 | 
						|
 | 
						|
    def on_motion(self, evt):
 | 
						|
        if self._check_still_parented() and self.got_artist:
 | 
						|
            dx = evt.x - self.mouse_x
 | 
						|
            dy = evt.y - self.mouse_y
 | 
						|
            self.update_offset(dx, dy)
 | 
						|
            if self._use_blit:
 | 
						|
                self.canvas.restore_region(self.background)
 | 
						|
                self.ref_artist.draw(
 | 
						|
                    self.ref_artist.get_figure(root=True)._get_renderer())
 | 
						|
                self.canvas.blit()
 | 
						|
            else:
 | 
						|
                self.canvas.draw()
 | 
						|
 | 
						|
    def on_pick(self, evt):
 | 
						|
        if self._check_still_parented():
 | 
						|
            if evt.artist == self.ref_artist:
 | 
						|
                self.mouse_x = evt.mouseevent.x
 | 
						|
                self.mouse_y = evt.mouseevent.y
 | 
						|
                self.save_offset()
 | 
						|
                self.got_artist = True
 | 
						|
            if self.got_artist and self._use_blit:
 | 
						|
                self.ref_artist.set_animated(True)
 | 
						|
                self.canvas.draw()
 | 
						|
                fig = self.ref_artist.get_figure(root=False)
 | 
						|
                self.background = self.canvas.copy_from_bbox(fig.bbox)
 | 
						|
                self.ref_artist.draw(fig._get_renderer())
 | 
						|
                self.canvas.blit()
 | 
						|
 | 
						|
    def on_release(self, event):
 | 
						|
        if self._check_still_parented() and self.got_artist:
 | 
						|
            self.finalize_offset()
 | 
						|
            self.got_artist = False
 | 
						|
            if self._use_blit:
 | 
						|
                self.canvas.restore_region(self.background)
 | 
						|
                self.ref_artist.draw(self.ref_artist.figure._get_renderer())
 | 
						|
                self.canvas.blit()
 | 
						|
                self.ref_artist.set_animated(False)
 | 
						|
 | 
						|
    def _check_still_parented(self):
 | 
						|
        if self.ref_artist.get_figure(root=False) is None:
 | 
						|
            self.disconnect()
 | 
						|
            return False
 | 
						|
        else:
 | 
						|
            return True
 | 
						|
 | 
						|
    def disconnect(self):
 | 
						|
        """Disconnect the callbacks."""
 | 
						|
        for disconnector in self._disconnectors:
 | 
						|
            disconnector()
 | 
						|
 | 
						|
    def save_offset(self):
 | 
						|
        pass
 | 
						|
 | 
						|
    def update_offset(self, dx, dy):
 | 
						|
        pass
 | 
						|
 | 
						|
    def finalize_offset(self):
 | 
						|
        pass
 | 
						|
 | 
						|
 | 
						|
class DraggableOffsetBox(DraggableBase):
 | 
						|
    def __init__(self, ref_artist, offsetbox, use_blit=False):
 | 
						|
        super().__init__(ref_artist, use_blit=use_blit)
 | 
						|
        self.offsetbox = offsetbox
 | 
						|
 | 
						|
    def save_offset(self):
 | 
						|
        offsetbox = self.offsetbox
 | 
						|
        renderer = offsetbox.get_figure(root=True)._get_renderer()
 | 
						|
        offset = offsetbox.get_offset(offsetbox.get_bbox(renderer), renderer)
 | 
						|
        self.offsetbox_x, self.offsetbox_y = offset
 | 
						|
        self.offsetbox.set_offset(offset)
 | 
						|
 | 
						|
    def update_offset(self, dx, dy):
 | 
						|
        loc_in_canvas = self.offsetbox_x + dx, self.offsetbox_y + dy
 | 
						|
        self.offsetbox.set_offset(loc_in_canvas)
 | 
						|
 | 
						|
    def get_loc_in_canvas(self):
 | 
						|
        offsetbox = self.offsetbox
 | 
						|
        renderer = offsetbox.get_figure(root=True)._get_renderer()
 | 
						|
        bbox = offsetbox.get_bbox(renderer)
 | 
						|
        ox, oy = offsetbox._offset
 | 
						|
        loc_in_canvas = (ox + bbox.x0, oy + bbox.y0)
 | 
						|
        return loc_in_canvas
 | 
						|
 | 
						|
 | 
						|
class DraggableAnnotation(DraggableBase):
 | 
						|
    def __init__(self, annotation, use_blit=False):
 | 
						|
        super().__init__(annotation, use_blit=use_blit)
 | 
						|
        self.annotation = annotation
 | 
						|
 | 
						|
    def save_offset(self):
 | 
						|
        ann = self.annotation
 | 
						|
        self.ox, self.oy = ann.get_transform().transform(ann.xyann)
 | 
						|
 | 
						|
    def update_offset(self, dx, dy):
 | 
						|
        ann = self.annotation
 | 
						|
        ann.xyann = ann.get_transform().inverted().transform(
 | 
						|
            (self.ox + dx, self.oy + dy))
 |