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.
		
		
		
		
		
			
		
			
				
	
	
		
			2036 lines
		
	
	
		
			69 KiB
		
	
	
	
		
			Python
		
	
			
		
		
	
	
			2036 lines
		
	
	
		
			69 KiB
		
	
	
	
		
			Python
		
	
"""
 | 
						|
Classes for including text in a figure.
 | 
						|
"""
 | 
						|
 | 
						|
import functools
 | 
						|
import logging
 | 
						|
import math
 | 
						|
from numbers import Real
 | 
						|
import weakref
 | 
						|
 | 
						|
import numpy as np
 | 
						|
 | 
						|
import matplotlib as mpl
 | 
						|
from . import _api, artist, cbook, _docstring
 | 
						|
from .artist import Artist
 | 
						|
from .font_manager import FontProperties
 | 
						|
from .patches import FancyArrowPatch, FancyBboxPatch, Rectangle
 | 
						|
from .textpath import TextPath, TextToPath  # noqa # Logically located here
 | 
						|
from .transforms import (
 | 
						|
    Affine2D, Bbox, BboxBase, BboxTransformTo, IdentityTransform, Transform)
 | 
						|
 | 
						|
 | 
						|
_log = logging.getLogger(__name__)
 | 
						|
 | 
						|
 | 
						|
def _get_textbox(text, renderer):
 | 
						|
    """
 | 
						|
    Calculate the bounding box of the text.
 | 
						|
 | 
						|
    The bbox position takes text rotation into account, but the width and
 | 
						|
    height are those of the unrotated box (unlike `.Text.get_window_extent`).
 | 
						|
    """
 | 
						|
    # TODO : This function may move into the Text class as a method. As a
 | 
						|
    # matter of fact, the information from the _get_textbox function
 | 
						|
    # should be available during the Text._get_layout() call, which is
 | 
						|
    # called within the _get_textbox. So, it would better to move this
 | 
						|
    # function as a method with some refactoring of _get_layout method.
 | 
						|
 | 
						|
    projected_xs = []
 | 
						|
    projected_ys = []
 | 
						|
 | 
						|
    theta = np.deg2rad(text.get_rotation())
 | 
						|
    tr = Affine2D().rotate(-theta)
 | 
						|
 | 
						|
    _, parts, d = text._get_layout(renderer)
 | 
						|
 | 
						|
    for t, wh, x, y in parts:
 | 
						|
        w, h = wh
 | 
						|
 | 
						|
        xt1, yt1 = tr.transform((x, y))
 | 
						|
        yt1 -= d
 | 
						|
        xt2, yt2 = xt1 + w, yt1 + h
 | 
						|
 | 
						|
        projected_xs.extend([xt1, xt2])
 | 
						|
        projected_ys.extend([yt1, yt2])
 | 
						|
 | 
						|
    xt_box, yt_box = min(projected_xs), min(projected_ys)
 | 
						|
    w_box, h_box = max(projected_xs) - xt_box, max(projected_ys) - yt_box
 | 
						|
 | 
						|
    x_box, y_box = Affine2D().rotate(theta).transform((xt_box, yt_box))
 | 
						|
 | 
						|
    return x_box, y_box, w_box, h_box
 | 
						|
 | 
						|
 | 
						|
def _get_text_metrics_with_cache(renderer, text, fontprop, ismath, dpi):
 | 
						|
    """Call ``renderer.get_text_width_height_descent``, caching the results."""
 | 
						|
    # Cached based on a copy of fontprop so that later in-place mutations of
 | 
						|
    # the passed-in argument do not mess up the cache.
 | 
						|
    return _get_text_metrics_with_cache_impl(
 | 
						|
        weakref.ref(renderer), text, fontprop.copy(), ismath, dpi)
 | 
						|
 | 
						|
 | 
						|
@functools.lru_cache(4096)
 | 
						|
def _get_text_metrics_with_cache_impl(
 | 
						|
        renderer_ref, text, fontprop, ismath, dpi):
 | 
						|
    # dpi is unused, but participates in cache invalidation (via the renderer).
 | 
						|
    return renderer_ref().get_text_width_height_descent(text, fontprop, ismath)
 | 
						|
 | 
						|
 | 
						|
@_docstring.interpd
 | 
						|
@_api.define_aliases({
 | 
						|
    "color": ["c"],
 | 
						|
    "fontproperties": ["font", "font_properties"],
 | 
						|
    "fontfamily": ["family"],
 | 
						|
    "fontname": ["name"],
 | 
						|
    "fontsize": ["size"],
 | 
						|
    "fontstretch": ["stretch"],
 | 
						|
    "fontstyle": ["style"],
 | 
						|
    "fontvariant": ["variant"],
 | 
						|
    "fontweight": ["weight"],
 | 
						|
    "horizontalalignment": ["ha"],
 | 
						|
    "verticalalignment": ["va"],
 | 
						|
    "multialignment": ["ma"],
 | 
						|
})
 | 
						|
class Text(Artist):
 | 
						|
    """Handle storing and drawing of text in window or data coordinates."""
 | 
						|
 | 
						|
    zorder = 3
 | 
						|
    _charsize_cache = dict()
 | 
						|
 | 
						|
    def __repr__(self):
 | 
						|
        return f"Text({self._x}, {self._y}, {self._text!r})"
 | 
						|
 | 
						|
    def __init__(self,
 | 
						|
                 x=0, y=0, text='', *,
 | 
						|
                 color=None,           # defaults to rc params
 | 
						|
                 verticalalignment='baseline',
 | 
						|
                 horizontalalignment='left',
 | 
						|
                 multialignment=None,
 | 
						|
                 fontproperties=None,  # defaults to FontProperties()
 | 
						|
                 rotation=None,
 | 
						|
                 linespacing=None,
 | 
						|
                 rotation_mode=None,
 | 
						|
                 usetex=None,          # defaults to rcParams['text.usetex']
 | 
						|
                 wrap=False,
 | 
						|
                 transform_rotates_text=False,
 | 
						|
                 parse_math=None,    # defaults to rcParams['text.parse_math']
 | 
						|
                 antialiased=None,  # defaults to rcParams['text.antialiased']
 | 
						|
                 **kwargs
 | 
						|
                 ):
 | 
						|
        """
 | 
						|
        Create a `.Text` instance at *x*, *y* with string *text*.
 | 
						|
 | 
						|
        The text is aligned relative to the anchor point (*x*, *y*) according
 | 
						|
        to ``horizontalalignment`` (default: 'left') and ``verticalalignment``
 | 
						|
        (default: 'baseline'). See also
 | 
						|
        :doc:`/gallery/text_labels_and_annotations/text_alignment`.
 | 
						|
 | 
						|
        While Text accepts the 'label' keyword argument, by default it is not
 | 
						|
        added to the handles of a legend.
 | 
						|
 | 
						|
        Valid keyword arguments are:
 | 
						|
 | 
						|
        %(Text:kwdoc)s
 | 
						|
        """
 | 
						|
        super().__init__()
 | 
						|
        self._x, self._y = x, y
 | 
						|
        self._text = ''
 | 
						|
        self._reset_visual_defaults(
 | 
						|
            text=text,
 | 
						|
            color=color,
 | 
						|
            fontproperties=fontproperties,
 | 
						|
            usetex=usetex,
 | 
						|
            parse_math=parse_math,
 | 
						|
            wrap=wrap,
 | 
						|
            verticalalignment=verticalalignment,
 | 
						|
            horizontalalignment=horizontalalignment,
 | 
						|
            multialignment=multialignment,
 | 
						|
            rotation=rotation,
 | 
						|
            transform_rotates_text=transform_rotates_text,
 | 
						|
            linespacing=linespacing,
 | 
						|
            rotation_mode=rotation_mode,
 | 
						|
            antialiased=antialiased
 | 
						|
        )
 | 
						|
        self.update(kwargs)
 | 
						|
 | 
						|
    def _reset_visual_defaults(
 | 
						|
        self,
 | 
						|
        text='',
 | 
						|
        color=None,
 | 
						|
        fontproperties=None,
 | 
						|
        usetex=None,
 | 
						|
        parse_math=None,
 | 
						|
        wrap=False,
 | 
						|
        verticalalignment='baseline',
 | 
						|
        horizontalalignment='left',
 | 
						|
        multialignment=None,
 | 
						|
        rotation=None,
 | 
						|
        transform_rotates_text=False,
 | 
						|
        linespacing=None,
 | 
						|
        rotation_mode=None,
 | 
						|
        antialiased=None
 | 
						|
    ):
 | 
						|
        self.set_text(text)
 | 
						|
        self.set_color(mpl._val_or_rc(color, "text.color"))
 | 
						|
        self.set_fontproperties(fontproperties)
 | 
						|
        self.set_usetex(usetex)
 | 
						|
        self.set_parse_math(mpl._val_or_rc(parse_math, 'text.parse_math'))
 | 
						|
        self.set_wrap(wrap)
 | 
						|
        self.set_verticalalignment(verticalalignment)
 | 
						|
        self.set_horizontalalignment(horizontalalignment)
 | 
						|
        self._multialignment = multialignment
 | 
						|
        self.set_rotation(rotation)
 | 
						|
        self._transform_rotates_text = transform_rotates_text
 | 
						|
        self._bbox_patch = None  # a FancyBboxPatch instance
 | 
						|
        self._renderer = None
 | 
						|
        if linespacing is None:
 | 
						|
            linespacing = 1.2  # Maybe use rcParam later.
 | 
						|
        self.set_linespacing(linespacing)
 | 
						|
        self.set_rotation_mode(rotation_mode)
 | 
						|
        self.set_antialiased(antialiased if antialiased is not None else
 | 
						|
                             mpl.rcParams['text.antialiased'])
 | 
						|
 | 
						|
    def update(self, kwargs):
 | 
						|
        # docstring inherited
 | 
						|
        ret = []
 | 
						|
        kwargs = cbook.normalize_kwargs(kwargs, Text)
 | 
						|
        sentinel = object()  # bbox can be None, so use another sentinel.
 | 
						|
        # Update fontproperties first, as it has lowest priority.
 | 
						|
        fontproperties = kwargs.pop("fontproperties", sentinel)
 | 
						|
        if fontproperties is not sentinel:
 | 
						|
            ret.append(self.set_fontproperties(fontproperties))
 | 
						|
        # Update bbox last, as it depends on font properties.
 | 
						|
        bbox = kwargs.pop("bbox", sentinel)
 | 
						|
        ret.extend(super().update(kwargs))
 | 
						|
        if bbox is not sentinel:
 | 
						|
            ret.append(self.set_bbox(bbox))
 | 
						|
        return ret
 | 
						|
 | 
						|
    def __getstate__(self):
 | 
						|
        d = super().__getstate__()
 | 
						|
        # remove the cached _renderer (if it exists)
 | 
						|
        d['_renderer'] = None
 | 
						|
        return d
 | 
						|
 | 
						|
    def contains(self, mouseevent):
 | 
						|
        """
 | 
						|
        Return whether the mouse event occurred inside the axis-aligned
 | 
						|
        bounding-box of the text.
 | 
						|
        """
 | 
						|
        if (self._different_canvas(mouseevent) or not self.get_visible()
 | 
						|
                or self._renderer is None):
 | 
						|
            return False, {}
 | 
						|
        # Explicitly use Text.get_window_extent(self) and not
 | 
						|
        # self.get_window_extent() so that Annotation.contains does not
 | 
						|
        # accidentally cover the entire annotation bounding box.
 | 
						|
        bbox = Text.get_window_extent(self)
 | 
						|
        inside = (bbox.x0 <= mouseevent.x <= bbox.x1
 | 
						|
                  and bbox.y0 <= mouseevent.y <= bbox.y1)
 | 
						|
        cattr = {}
 | 
						|
        # if the text has a surrounding patch, also check containment for it,
 | 
						|
        # and merge the results with the results for the text.
 | 
						|
        if self._bbox_patch:
 | 
						|
            patch_inside, patch_cattr = self._bbox_patch.contains(mouseevent)
 | 
						|
            inside = inside or patch_inside
 | 
						|
            cattr["bbox_patch"] = patch_cattr
 | 
						|
        return inside, cattr
 | 
						|
 | 
						|
    def _get_xy_display(self):
 | 
						|
        """
 | 
						|
        Get the (possibly unit converted) transformed x, y in display coords.
 | 
						|
        """
 | 
						|
        x, y = self.get_unitless_position()
 | 
						|
        return self.get_transform().transform((x, y))
 | 
						|
 | 
						|
    def _get_multialignment(self):
 | 
						|
        if self._multialignment is not None:
 | 
						|
            return self._multialignment
 | 
						|
        else:
 | 
						|
            return self._horizontalalignment
 | 
						|
 | 
						|
    def _char_index_at(self, x):
 | 
						|
        """
 | 
						|
        Calculate the index closest to the coordinate x in display space.
 | 
						|
 | 
						|
        The position of text[index] is assumed to be the sum of the widths
 | 
						|
        of all preceding characters text[:index].
 | 
						|
 | 
						|
        This works only on single line texts.
 | 
						|
        """
 | 
						|
        if not self._text:
 | 
						|
            return 0
 | 
						|
 | 
						|
        text = self._text
 | 
						|
 | 
						|
        fontproperties = str(self._fontproperties)
 | 
						|
        if fontproperties not in Text._charsize_cache:
 | 
						|
            Text._charsize_cache[fontproperties] = dict()
 | 
						|
 | 
						|
        charsize_cache = Text._charsize_cache[fontproperties]
 | 
						|
        for char in set(text):
 | 
						|
            if char not in charsize_cache:
 | 
						|
                self.set_text(char)
 | 
						|
                bb = self.get_window_extent()
 | 
						|
                charsize_cache[char] = bb.x1 - bb.x0
 | 
						|
 | 
						|
        self.set_text(text)
 | 
						|
        bb = self.get_window_extent()
 | 
						|
 | 
						|
        size_accum = np.cumsum([0] + [charsize_cache[x] for x in text])
 | 
						|
        std_x = x - bb.x0
 | 
						|
        return (np.abs(size_accum - std_x)).argmin()
 | 
						|
 | 
						|
    def get_rotation(self):
 | 
						|
        """Return the text angle in degrees between 0 and 360."""
 | 
						|
        if self.get_transform_rotates_text():
 | 
						|
            return self.get_transform().transform_angles(
 | 
						|
                [self._rotation], [self.get_unitless_position()]).item(0)
 | 
						|
        else:
 | 
						|
            return self._rotation
 | 
						|
 | 
						|
    def get_transform_rotates_text(self):
 | 
						|
        """
 | 
						|
        Return whether rotations of the transform affect the text direction.
 | 
						|
        """
 | 
						|
        return self._transform_rotates_text
 | 
						|
 | 
						|
    def set_rotation_mode(self, m):
 | 
						|
        """
 | 
						|
        Set text rotation mode.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        m : {None, 'default', 'anchor'}
 | 
						|
            If ``"default"``, the text will be first rotated, then aligned according
 | 
						|
            to their horizontal and vertical alignments.  If ``"anchor"``, then
 | 
						|
            alignment occurs before rotation. Passing ``None`` will set the rotation
 | 
						|
            mode to ``"default"``.
 | 
						|
        """
 | 
						|
        if m is None:
 | 
						|
            m = "default"
 | 
						|
        else:
 | 
						|
            _api.check_in_list(("anchor", "default"), rotation_mode=m)
 | 
						|
        self._rotation_mode = m
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_rotation_mode(self):
 | 
						|
        """Return the text rotation mode."""
 | 
						|
        return self._rotation_mode
 | 
						|
 | 
						|
    def set_antialiased(self, antialiased):
 | 
						|
        """
 | 
						|
        Set whether to use antialiased rendering.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        antialiased : bool
 | 
						|
 | 
						|
        Notes
 | 
						|
        -----
 | 
						|
        Antialiasing will be determined by :rc:`text.antialiased`
 | 
						|
        and the parameter *antialiased* will have no effect if the text contains
 | 
						|
        math expressions.
 | 
						|
        """
 | 
						|
        self._antialiased = antialiased
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_antialiased(self):
 | 
						|
        """Return whether antialiased rendering is used."""
 | 
						|
        return self._antialiased
 | 
						|
 | 
						|
    def update_from(self, other):
 | 
						|
        # docstring inherited
 | 
						|
        super().update_from(other)
 | 
						|
        self._color = other._color
 | 
						|
        self._multialignment = other._multialignment
 | 
						|
        self._verticalalignment = other._verticalalignment
 | 
						|
        self._horizontalalignment = other._horizontalalignment
 | 
						|
        self._fontproperties = other._fontproperties.copy()
 | 
						|
        self._usetex = other._usetex
 | 
						|
        self._rotation = other._rotation
 | 
						|
        self._transform_rotates_text = other._transform_rotates_text
 | 
						|
        self._picker = other._picker
 | 
						|
        self._linespacing = other._linespacing
 | 
						|
        self._antialiased = other._antialiased
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def _get_layout(self, renderer):
 | 
						|
        """
 | 
						|
        Return the extent (bbox) of the text together with
 | 
						|
        multiple-alignment information. Note that it returns an extent
 | 
						|
        of a rotated text when necessary.
 | 
						|
        """
 | 
						|
        thisx, thisy = 0.0, 0.0
 | 
						|
        lines = self._get_wrapped_text().split("\n")  # Ensures lines is not empty.
 | 
						|
 | 
						|
        ws = []
 | 
						|
        hs = []
 | 
						|
        xs = []
 | 
						|
        ys = []
 | 
						|
 | 
						|
        # Full vertical extent of font, including ascenders and descenders:
 | 
						|
        _, lp_h, lp_d = _get_text_metrics_with_cache(
 | 
						|
            renderer, "lp", self._fontproperties,
 | 
						|
            ismath="TeX" if self.get_usetex() else False,
 | 
						|
            dpi=self.get_figure(root=True).dpi)
 | 
						|
        min_dy = (lp_h - lp_d) * self._linespacing
 | 
						|
 | 
						|
        for i, line in enumerate(lines):
 | 
						|
            clean_line, ismath = self._preprocess_math(line)
 | 
						|
            if clean_line:
 | 
						|
                w, h, d = _get_text_metrics_with_cache(
 | 
						|
                    renderer, clean_line, self._fontproperties,
 | 
						|
                    ismath=ismath, dpi=self.get_figure(root=True).dpi)
 | 
						|
            else:
 | 
						|
                w = h = d = 0
 | 
						|
 | 
						|
            # For multiline text, increase the line spacing when the text
 | 
						|
            # net-height (excluding baseline) is larger than that of a "l"
 | 
						|
            # (e.g., use of superscripts), which seems what TeX does.
 | 
						|
            h = max(h, lp_h)
 | 
						|
            d = max(d, lp_d)
 | 
						|
 | 
						|
            ws.append(w)
 | 
						|
            hs.append(h)
 | 
						|
 | 
						|
            # Metrics of the last line that are needed later:
 | 
						|
            baseline = (h - d) - thisy
 | 
						|
 | 
						|
            if i == 0:
 | 
						|
                # position at baseline
 | 
						|
                thisy = -(h - d)
 | 
						|
            else:
 | 
						|
                # put baseline a good distance from bottom of previous line
 | 
						|
                thisy -= max(min_dy, (h - d) * self._linespacing)
 | 
						|
 | 
						|
            xs.append(thisx)  # == 0.
 | 
						|
            ys.append(thisy)
 | 
						|
 | 
						|
            thisy -= d
 | 
						|
 | 
						|
        # Metrics of the last line that are needed later:
 | 
						|
        descent = d
 | 
						|
 | 
						|
        # Bounding box definition:
 | 
						|
        width = max(ws)
 | 
						|
        xmin = 0
 | 
						|
        xmax = width
 | 
						|
        ymax = 0
 | 
						|
        ymin = ys[-1] - descent  # baseline of last line minus its descent
 | 
						|
 | 
						|
        # get the rotation matrix
 | 
						|
        M = Affine2D().rotate_deg(self.get_rotation())
 | 
						|
 | 
						|
        # now offset the individual text lines within the box
 | 
						|
        malign = self._get_multialignment()
 | 
						|
        if malign == 'left':
 | 
						|
            offset_layout = [(x, y) for x, y in zip(xs, ys)]
 | 
						|
        elif malign == 'center':
 | 
						|
            offset_layout = [(x + width / 2 - w / 2, y)
 | 
						|
                             for x, y, w in zip(xs, ys, ws)]
 | 
						|
        elif malign == 'right':
 | 
						|
            offset_layout = [(x + width - w, y)
 | 
						|
                             for x, y, w in zip(xs, ys, ws)]
 | 
						|
 | 
						|
        # the corners of the unrotated bounding box
 | 
						|
        corners_horiz = np.array(
 | 
						|
            [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)])
 | 
						|
 | 
						|
        # now rotate the bbox
 | 
						|
        corners_rotated = M.transform(corners_horiz)
 | 
						|
        # compute the bounds of the rotated box
 | 
						|
        xmin = corners_rotated[:, 0].min()
 | 
						|
        xmax = corners_rotated[:, 0].max()
 | 
						|
        ymin = corners_rotated[:, 1].min()
 | 
						|
        ymax = corners_rotated[:, 1].max()
 | 
						|
        width = xmax - xmin
 | 
						|
        height = ymax - ymin
 | 
						|
 | 
						|
        # Now move the box to the target position offset the display
 | 
						|
        # bbox by alignment
 | 
						|
        halign = self._horizontalalignment
 | 
						|
        valign = self._verticalalignment
 | 
						|
 | 
						|
        rotation_mode = self.get_rotation_mode()
 | 
						|
        if rotation_mode != "anchor":
 | 
						|
            # compute the text location in display coords and the offsets
 | 
						|
            # necessary to align the bbox with that location
 | 
						|
            if halign == 'center':
 | 
						|
                offsetx = (xmin + xmax) / 2
 | 
						|
            elif halign == 'right':
 | 
						|
                offsetx = xmax
 | 
						|
            else:
 | 
						|
                offsetx = xmin
 | 
						|
 | 
						|
            if valign == 'center':
 | 
						|
                offsety = (ymin + ymax) / 2
 | 
						|
            elif valign == 'top':
 | 
						|
                offsety = ymax
 | 
						|
            elif valign == 'baseline':
 | 
						|
                offsety = ymin + descent
 | 
						|
            elif valign == 'center_baseline':
 | 
						|
                offsety = ymin + height - baseline / 2.0
 | 
						|
            else:
 | 
						|
                offsety = ymin
 | 
						|
        else:
 | 
						|
            xmin1, ymin1 = corners_horiz[0]
 | 
						|
            xmax1, ymax1 = corners_horiz[2]
 | 
						|
 | 
						|
            if halign == 'center':
 | 
						|
                offsetx = (xmin1 + xmax1) / 2.0
 | 
						|
            elif halign == 'right':
 | 
						|
                offsetx = xmax1
 | 
						|
            else:
 | 
						|
                offsetx = xmin1
 | 
						|
 | 
						|
            if valign == 'center':
 | 
						|
                offsety = (ymin1 + ymax1) / 2.0
 | 
						|
            elif valign == 'top':
 | 
						|
                offsety = ymax1
 | 
						|
            elif valign == 'baseline':
 | 
						|
                offsety = ymax1 - baseline
 | 
						|
            elif valign == 'center_baseline':
 | 
						|
                offsety = ymax1 - baseline / 2.0
 | 
						|
            else:
 | 
						|
                offsety = ymin1
 | 
						|
 | 
						|
            offsetx, offsety = M.transform((offsetx, offsety))
 | 
						|
 | 
						|
        xmin -= offsetx
 | 
						|
        ymin -= offsety
 | 
						|
 | 
						|
        bbox = Bbox.from_bounds(xmin, ymin, width, height)
 | 
						|
 | 
						|
        # now rotate the positions around the first (x, y) position
 | 
						|
        xys = M.transform(offset_layout) - (offsetx, offsety)
 | 
						|
 | 
						|
        return bbox, list(zip(lines, zip(ws, hs), *xys.T)), descent
 | 
						|
 | 
						|
    def set_bbox(self, rectprops):
 | 
						|
        """
 | 
						|
        Draw a bounding box around self.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        rectprops : dict with properties for `.patches.FancyBboxPatch`
 | 
						|
             The default boxstyle is 'square'. The mutation
 | 
						|
             scale of the `.patches.FancyBboxPatch` is set to the fontsize.
 | 
						|
 | 
						|
        Examples
 | 
						|
        --------
 | 
						|
        ::
 | 
						|
 | 
						|
            t.set_bbox(dict(facecolor='red', alpha=0.5))
 | 
						|
        """
 | 
						|
 | 
						|
        if rectprops is not None:
 | 
						|
            props = rectprops.copy()
 | 
						|
            boxstyle = props.pop("boxstyle", None)
 | 
						|
            pad = props.pop("pad", None)
 | 
						|
            if boxstyle is None:
 | 
						|
                boxstyle = "square"
 | 
						|
                if pad is None:
 | 
						|
                    pad = 4  # points
 | 
						|
                pad /= self.get_size()  # to fraction of font size
 | 
						|
            else:
 | 
						|
                if pad is None:
 | 
						|
                    pad = 0.3
 | 
						|
            # boxstyle could be a callable or a string
 | 
						|
            if isinstance(boxstyle, str) and "pad" not in boxstyle:
 | 
						|
                boxstyle += ",pad=%0.2f" % pad
 | 
						|
            self._bbox_patch = FancyBboxPatch(
 | 
						|
                (0, 0), 1, 1,
 | 
						|
                boxstyle=boxstyle, transform=IdentityTransform(), **props)
 | 
						|
        else:
 | 
						|
            self._bbox_patch = None
 | 
						|
 | 
						|
        self._update_clip_properties()
 | 
						|
 | 
						|
    def get_bbox_patch(self):
 | 
						|
        """
 | 
						|
        Return the bbox Patch, or None if the `.patches.FancyBboxPatch`
 | 
						|
        is not made.
 | 
						|
        """
 | 
						|
        return self._bbox_patch
 | 
						|
 | 
						|
    def update_bbox_position_size(self, renderer):
 | 
						|
        """
 | 
						|
        Update the location and the size of the bbox.
 | 
						|
 | 
						|
        This method should be used when the position and size of the bbox needs
 | 
						|
        to be updated before actually drawing the bbox.
 | 
						|
        """
 | 
						|
        if self._bbox_patch:
 | 
						|
            # don't use self.get_unitless_position here, which refers to text
 | 
						|
            # position in Text:
 | 
						|
            posx = float(self.convert_xunits(self._x))
 | 
						|
            posy = float(self.convert_yunits(self._y))
 | 
						|
            posx, posy = self.get_transform().transform((posx, posy))
 | 
						|
 | 
						|
            x_box, y_box, w_box, h_box = _get_textbox(self, renderer)
 | 
						|
            self._bbox_patch.set_bounds(0., 0., w_box, h_box)
 | 
						|
            self._bbox_patch.set_transform(
 | 
						|
                Affine2D()
 | 
						|
                .rotate_deg(self.get_rotation())
 | 
						|
                .translate(posx + x_box, posy + y_box))
 | 
						|
            fontsize_in_pixel = renderer.points_to_pixels(self.get_size())
 | 
						|
            self._bbox_patch.set_mutation_scale(fontsize_in_pixel)
 | 
						|
 | 
						|
    def _update_clip_properties(self):
 | 
						|
        if self._bbox_patch:
 | 
						|
            clipprops = dict(clip_box=self.clipbox,
 | 
						|
                             clip_path=self._clippath,
 | 
						|
                             clip_on=self._clipon)
 | 
						|
            self._bbox_patch.update(clipprops)
 | 
						|
 | 
						|
    def set_clip_box(self, clipbox):
 | 
						|
        # docstring inherited.
 | 
						|
        super().set_clip_box(clipbox)
 | 
						|
        self._update_clip_properties()
 | 
						|
 | 
						|
    def set_clip_path(self, path, transform=None):
 | 
						|
        # docstring inherited.
 | 
						|
        super().set_clip_path(path, transform)
 | 
						|
        self._update_clip_properties()
 | 
						|
 | 
						|
    def set_clip_on(self, b):
 | 
						|
        # docstring inherited.
 | 
						|
        super().set_clip_on(b)
 | 
						|
        self._update_clip_properties()
 | 
						|
 | 
						|
    def get_wrap(self):
 | 
						|
        """Return whether the text can be wrapped."""
 | 
						|
        return self._wrap
 | 
						|
 | 
						|
    def set_wrap(self, wrap):
 | 
						|
        """
 | 
						|
        Set whether the text can be wrapped.
 | 
						|
 | 
						|
        Wrapping makes sure the text is confined to the (sub)figure box. It
 | 
						|
        does not take into account any other artists.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        wrap : bool
 | 
						|
 | 
						|
        Notes
 | 
						|
        -----
 | 
						|
        Wrapping does not work together with
 | 
						|
        ``savefig(..., bbox_inches='tight')`` (which is also used internally
 | 
						|
        by ``%matplotlib inline`` in IPython/Jupyter). The 'tight' setting
 | 
						|
        rescales the canvas to accommodate all content and happens before
 | 
						|
        wrapping.
 | 
						|
        """
 | 
						|
        self._wrap = wrap
 | 
						|
 | 
						|
    def _get_wrap_line_width(self):
 | 
						|
        """
 | 
						|
        Return the maximum line width for wrapping text based on the current
 | 
						|
        orientation.
 | 
						|
        """
 | 
						|
        x0, y0 = self.get_transform().transform(self.get_position())
 | 
						|
        figure_box = self.get_figure().get_window_extent()
 | 
						|
 | 
						|
        # Calculate available width based on text alignment
 | 
						|
        alignment = self.get_horizontalalignment()
 | 
						|
        self.set_rotation_mode('anchor')
 | 
						|
        rotation = self.get_rotation()
 | 
						|
 | 
						|
        left = self._get_dist_to_box(rotation, x0, y0, figure_box)
 | 
						|
        right = self._get_dist_to_box(
 | 
						|
            (180 + rotation) % 360, x0, y0, figure_box)
 | 
						|
 | 
						|
        if alignment == 'left':
 | 
						|
            line_width = left
 | 
						|
        elif alignment == 'right':
 | 
						|
            line_width = right
 | 
						|
        else:
 | 
						|
            line_width = 2 * min(left, right)
 | 
						|
 | 
						|
        return line_width
 | 
						|
 | 
						|
    def _get_dist_to_box(self, rotation, x0, y0, figure_box):
 | 
						|
        """
 | 
						|
        Return the distance from the given points to the boundaries of a
 | 
						|
        rotated box, in pixels.
 | 
						|
        """
 | 
						|
        if rotation > 270:
 | 
						|
            quad = rotation - 270
 | 
						|
            h1 = (y0 - figure_box.y0) / math.cos(math.radians(quad))
 | 
						|
            h2 = (figure_box.x1 - x0) / math.cos(math.radians(90 - quad))
 | 
						|
        elif rotation > 180:
 | 
						|
            quad = rotation - 180
 | 
						|
            h1 = (x0 - figure_box.x0) / math.cos(math.radians(quad))
 | 
						|
            h2 = (y0 - figure_box.y0) / math.cos(math.radians(90 - quad))
 | 
						|
        elif rotation > 90:
 | 
						|
            quad = rotation - 90
 | 
						|
            h1 = (figure_box.y1 - y0) / math.cos(math.radians(quad))
 | 
						|
            h2 = (x0 - figure_box.x0) / math.cos(math.radians(90 - quad))
 | 
						|
        else:
 | 
						|
            h1 = (figure_box.x1 - x0) / math.cos(math.radians(rotation))
 | 
						|
            h2 = (figure_box.y1 - y0) / math.cos(math.radians(90 - rotation))
 | 
						|
 | 
						|
        return min(h1, h2)
 | 
						|
 | 
						|
    def _get_rendered_text_width(self, text):
 | 
						|
        """
 | 
						|
        Return the width of a given text string, in pixels.
 | 
						|
        """
 | 
						|
 | 
						|
        w, h, d = _get_text_metrics_with_cache(
 | 
						|
            self._renderer, text, self.get_fontproperties(),
 | 
						|
            cbook.is_math_text(text),
 | 
						|
            self.get_figure(root=True).dpi)
 | 
						|
        return math.ceil(w)
 | 
						|
 | 
						|
    def _get_wrapped_text(self):
 | 
						|
        """
 | 
						|
        Return a copy of the text string with new lines added so that the text
 | 
						|
        is wrapped relative to the parent figure (if `get_wrap` is True).
 | 
						|
        """
 | 
						|
        if not self.get_wrap():
 | 
						|
            return self.get_text()
 | 
						|
 | 
						|
        # Not fit to handle breaking up latex syntax correctly, so
 | 
						|
        # ignore latex for now.
 | 
						|
        if self.get_usetex():
 | 
						|
            return self.get_text()
 | 
						|
 | 
						|
        # Build the line incrementally, for a more accurate measure of length
 | 
						|
        line_width = self._get_wrap_line_width()
 | 
						|
        wrapped_lines = []
 | 
						|
 | 
						|
        # New lines in the user's text force a split
 | 
						|
        unwrapped_lines = self.get_text().split('\n')
 | 
						|
 | 
						|
        # Now wrap each individual unwrapped line
 | 
						|
        for unwrapped_line in unwrapped_lines:
 | 
						|
 | 
						|
            sub_words = unwrapped_line.split(' ')
 | 
						|
            # Remove items from sub_words as we go, so stop when empty
 | 
						|
            while len(sub_words) > 0:
 | 
						|
                if len(sub_words) == 1:
 | 
						|
                    # Only one word, so just add it to the end
 | 
						|
                    wrapped_lines.append(sub_words.pop(0))
 | 
						|
                    continue
 | 
						|
 | 
						|
                for i in range(2, len(sub_words) + 1):
 | 
						|
                    # Get width of all words up to and including here
 | 
						|
                    line = ' '.join(sub_words[:i])
 | 
						|
                    current_width = self._get_rendered_text_width(line)
 | 
						|
 | 
						|
                    # If all these words are too wide, append all not including
 | 
						|
                    # last word
 | 
						|
                    if current_width > line_width:
 | 
						|
                        wrapped_lines.append(' '.join(sub_words[:i - 1]))
 | 
						|
                        sub_words = sub_words[i - 1:]
 | 
						|
                        break
 | 
						|
 | 
						|
                    # Otherwise if all words fit in the width, append them all
 | 
						|
                    elif i == len(sub_words):
 | 
						|
                        wrapped_lines.append(' '.join(sub_words[:i]))
 | 
						|
                        sub_words = []
 | 
						|
                        break
 | 
						|
 | 
						|
        return '\n'.join(wrapped_lines)
 | 
						|
 | 
						|
    @artist.allow_rasterization
 | 
						|
    def draw(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
 | 
						|
        if renderer is not None:
 | 
						|
            self._renderer = renderer
 | 
						|
        if not self.get_visible():
 | 
						|
            return
 | 
						|
        if self.get_text() == '':
 | 
						|
            return
 | 
						|
 | 
						|
        renderer.open_group('text', self.get_gid())
 | 
						|
 | 
						|
        with self._cm_set(text=self._get_wrapped_text()):
 | 
						|
            bbox, info, descent = self._get_layout(renderer)
 | 
						|
            trans = self.get_transform()
 | 
						|
 | 
						|
            # don't use self.get_position here, which refers to text
 | 
						|
            # position in Text:
 | 
						|
            x, y = self._x, self._y
 | 
						|
            if np.ma.is_masked(x):
 | 
						|
                x = np.nan
 | 
						|
            if np.ma.is_masked(y):
 | 
						|
                y = np.nan
 | 
						|
            posx = float(self.convert_xunits(x))
 | 
						|
            posy = float(self.convert_yunits(y))
 | 
						|
            posx, posy = trans.transform((posx, posy))
 | 
						|
            if np.isnan(posx) or np.isnan(posy):
 | 
						|
                return  # don't throw a warning here
 | 
						|
            if not np.isfinite(posx) or not np.isfinite(posy):
 | 
						|
                _log.warning("posx and posy should be finite values")
 | 
						|
                return
 | 
						|
            canvasw, canvash = renderer.get_canvas_width_height()
 | 
						|
 | 
						|
            # Update the location and size of the bbox
 | 
						|
            # (`.patches.FancyBboxPatch`), and draw it.
 | 
						|
            if self._bbox_patch:
 | 
						|
                self.update_bbox_position_size(renderer)
 | 
						|
                self._bbox_patch.draw(renderer)
 | 
						|
 | 
						|
            gc = renderer.new_gc()
 | 
						|
            gc.set_foreground(self.get_color())
 | 
						|
            gc.set_alpha(self.get_alpha())
 | 
						|
            gc.set_url(self._url)
 | 
						|
            gc.set_antialiased(self._antialiased)
 | 
						|
            self._set_gc_clip(gc)
 | 
						|
 | 
						|
            angle = self.get_rotation()
 | 
						|
 | 
						|
            for line, wh, x, y in info:
 | 
						|
 | 
						|
                mtext = self if len(info) == 1 else None
 | 
						|
                x = x + posx
 | 
						|
                y = y + posy
 | 
						|
                if renderer.flipy():
 | 
						|
                    y = canvash - y
 | 
						|
                clean_line, ismath = self._preprocess_math(line)
 | 
						|
 | 
						|
                if self.get_path_effects():
 | 
						|
                    from matplotlib.patheffects import PathEffectRenderer
 | 
						|
                    textrenderer = PathEffectRenderer(
 | 
						|
                        self.get_path_effects(), renderer)
 | 
						|
                else:
 | 
						|
                    textrenderer = renderer
 | 
						|
 | 
						|
                if self.get_usetex():
 | 
						|
                    textrenderer.draw_tex(gc, x, y, clean_line,
 | 
						|
                                          self._fontproperties, angle,
 | 
						|
                                          mtext=mtext)
 | 
						|
                else:
 | 
						|
                    textrenderer.draw_text(gc, x, y, clean_line,
 | 
						|
                                           self._fontproperties, angle,
 | 
						|
                                           ismath=ismath, mtext=mtext)
 | 
						|
 | 
						|
        gc.restore()
 | 
						|
        renderer.close_group('text')
 | 
						|
        self.stale = False
 | 
						|
 | 
						|
    def get_color(self):
 | 
						|
        """Return the color of the text."""
 | 
						|
        return self._color
 | 
						|
 | 
						|
    def get_fontproperties(self):
 | 
						|
        """Return the `.font_manager.FontProperties`."""
 | 
						|
        return self._fontproperties
 | 
						|
 | 
						|
    def get_fontfamily(self):
 | 
						|
        """
 | 
						|
        Return the list of font families used for font lookup.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.get_family
 | 
						|
        """
 | 
						|
        return self._fontproperties.get_family()
 | 
						|
 | 
						|
    def get_fontname(self):
 | 
						|
        """
 | 
						|
        Return the font name as a string.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.get_name
 | 
						|
        """
 | 
						|
        return self._fontproperties.get_name()
 | 
						|
 | 
						|
    def get_fontstyle(self):
 | 
						|
        """
 | 
						|
        Return the font style as a string.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.get_style
 | 
						|
        """
 | 
						|
        return self._fontproperties.get_style()
 | 
						|
 | 
						|
    def get_fontsize(self):
 | 
						|
        """
 | 
						|
        Return the font size as an integer.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.get_size_in_points
 | 
						|
        """
 | 
						|
        return self._fontproperties.get_size_in_points()
 | 
						|
 | 
						|
    def get_fontvariant(self):
 | 
						|
        """
 | 
						|
        Return the font variant as a string.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.get_variant
 | 
						|
        """
 | 
						|
        return self._fontproperties.get_variant()
 | 
						|
 | 
						|
    def get_fontweight(self):
 | 
						|
        """
 | 
						|
        Return the font weight as a string or a number.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.get_weight
 | 
						|
        """
 | 
						|
        return self._fontproperties.get_weight()
 | 
						|
 | 
						|
    def get_stretch(self):
 | 
						|
        """
 | 
						|
        Return the font stretch as a string or a number.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.get_stretch
 | 
						|
        """
 | 
						|
        return self._fontproperties.get_stretch()
 | 
						|
 | 
						|
    def get_horizontalalignment(self):
 | 
						|
        """
 | 
						|
        Return the horizontal alignment as a string.  Will be one of
 | 
						|
        'left', 'center' or 'right'.
 | 
						|
        """
 | 
						|
        return self._horizontalalignment
 | 
						|
 | 
						|
    def get_unitless_position(self):
 | 
						|
        """Return the (x, y) unitless position of the text."""
 | 
						|
        # This will get the position with all unit information stripped away.
 | 
						|
        # This is here for convenience since it is done in several locations.
 | 
						|
        x = float(self.convert_xunits(self._x))
 | 
						|
        y = float(self.convert_yunits(self._y))
 | 
						|
        return x, y
 | 
						|
 | 
						|
    def get_position(self):
 | 
						|
        """Return the (x, y) position of the text."""
 | 
						|
        # This should return the same data (possible unitized) as was
 | 
						|
        # specified with 'set_x' and 'set_y'.
 | 
						|
        return self._x, self._y
 | 
						|
 | 
						|
    def get_text(self):
 | 
						|
        """Return the text string."""
 | 
						|
        return self._text
 | 
						|
 | 
						|
    def get_verticalalignment(self):
 | 
						|
        """
 | 
						|
        Return the vertical alignment as a string.  Will be one of
 | 
						|
        'top', 'center', 'bottom', 'baseline' or 'center_baseline'.
 | 
						|
        """
 | 
						|
        return self._verticalalignment
 | 
						|
 | 
						|
    def get_window_extent(self, renderer=None, dpi=None):
 | 
						|
        """
 | 
						|
        Return the `.Bbox` bounding the text, in display units.
 | 
						|
 | 
						|
        In addition to being used internally, this is useful for specifying
 | 
						|
        clickable regions in a png file on a web page.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        renderer : Renderer, optional
 | 
						|
            A renderer is needed to compute the bounding box.  If the artist
 | 
						|
            has already been drawn, the renderer is cached; thus, it is only
 | 
						|
            necessary to pass this argument when calling `get_window_extent`
 | 
						|
            before the first draw.  In practice, it is usually easier to
 | 
						|
            trigger a draw first, e.g. by calling
 | 
						|
            `~.Figure.draw_without_rendering` or ``plt.show()``.
 | 
						|
 | 
						|
        dpi : float, optional
 | 
						|
            The dpi value for computing the bbox, defaults to
 | 
						|
            ``self.get_figure(root=True).dpi`` (*not* the renderer dpi); should be set
 | 
						|
            e.g. if to match regions with a figure saved with a custom dpi value.
 | 
						|
        """
 | 
						|
        if not self.get_visible():
 | 
						|
            return Bbox.unit()
 | 
						|
 | 
						|
        fig = self.get_figure(root=True)
 | 
						|
        if dpi is None:
 | 
						|
            dpi = fig.dpi
 | 
						|
        if self.get_text() == '':
 | 
						|
            with cbook._setattr_cm(fig, dpi=dpi):
 | 
						|
                tx, ty = self._get_xy_display()
 | 
						|
                return Bbox.from_bounds(tx, ty, 0, 0)
 | 
						|
 | 
						|
        if renderer is not None:
 | 
						|
            self._renderer = renderer
 | 
						|
        if self._renderer is None:
 | 
						|
            self._renderer = fig._get_renderer()
 | 
						|
        if self._renderer is None:
 | 
						|
            raise RuntimeError(
 | 
						|
                "Cannot get window extent of text w/o renderer. You likely "
 | 
						|
                "want to call 'figure.draw_without_rendering()' first.")
 | 
						|
 | 
						|
        with cbook._setattr_cm(fig, dpi=dpi):
 | 
						|
            bbox, info, descent = self._get_layout(self._renderer)
 | 
						|
            x, y = self.get_unitless_position()
 | 
						|
            x, y = self.get_transform().transform((x, y))
 | 
						|
            bbox = bbox.translated(x, y)
 | 
						|
            return bbox
 | 
						|
 | 
						|
    def set_backgroundcolor(self, color):
 | 
						|
        """
 | 
						|
        Set the background color of the text by updating the bbox.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        color : :mpltype:`color`
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .set_bbox : To change the position of the bounding box
 | 
						|
        """
 | 
						|
        if self._bbox_patch is None:
 | 
						|
            self.set_bbox(dict(facecolor=color, edgecolor=color))
 | 
						|
        else:
 | 
						|
            self._bbox_patch.update(dict(facecolor=color))
 | 
						|
 | 
						|
        self._update_clip_properties()
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_color(self, color):
 | 
						|
        """
 | 
						|
        Set the foreground color of the text
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        color : :mpltype:`color`
 | 
						|
        """
 | 
						|
        # "auto" is only supported by axisartist, but we can just let it error
 | 
						|
        # out at draw time for simplicity.
 | 
						|
        if not cbook._str_equal(color, "auto"):
 | 
						|
            mpl.colors._check_color_like(color=color)
 | 
						|
        self._color = color
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_horizontalalignment(self, align):
 | 
						|
        """
 | 
						|
        Set the horizontal alignment relative to the anchor point.
 | 
						|
 | 
						|
        See also :doc:`/gallery/text_labels_and_annotations/text_alignment`.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        align : {'left', 'center', 'right'}
 | 
						|
        """
 | 
						|
        _api.check_in_list(['center', 'right', 'left'], align=align)
 | 
						|
        self._horizontalalignment = align
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_multialignment(self, align):
 | 
						|
        """
 | 
						|
        Set the text alignment for multiline texts.
 | 
						|
 | 
						|
        The layout of the bounding box of all the lines is determined by the
 | 
						|
        horizontalalignment and verticalalignment properties. This property
 | 
						|
        controls the alignment of the text lines within that box.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        align : {'left', 'right', 'center'}
 | 
						|
        """
 | 
						|
        _api.check_in_list(['center', 'right', 'left'], align=align)
 | 
						|
        self._multialignment = align
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_linespacing(self, spacing):
 | 
						|
        """
 | 
						|
        Set the line spacing as a multiple of the font size.
 | 
						|
 | 
						|
        The default line spacing is 1.2.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        spacing : float (multiple of font size)
 | 
						|
        """
 | 
						|
        _api.check_isinstance(Real, spacing=spacing)
 | 
						|
        self._linespacing = spacing
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_fontfamily(self, fontname):
 | 
						|
        """
 | 
						|
        Set the font family.  Can be either a single string, or a list of
 | 
						|
        strings in decreasing priority.  Each string may be either a real font
 | 
						|
        name or a generic font class name.  If the latter, the specific font
 | 
						|
        names will be looked up in the corresponding rcParams.
 | 
						|
 | 
						|
        If a `Text` instance is constructed with ``fontfamily=None``, then the
 | 
						|
        font is set to :rc:`font.family`, and the
 | 
						|
        same is done when `set_fontfamily()` is called on an existing
 | 
						|
        `Text` instance.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        fontname : {FONTNAME, 'serif', 'sans-serif', 'cursive', 'fantasy', \
 | 
						|
'monospace'}
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.set_family
 | 
						|
        """
 | 
						|
        self._fontproperties.set_family(fontname)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_fontvariant(self, variant):
 | 
						|
        """
 | 
						|
        Set the font variant.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        variant : {'normal', 'small-caps'}
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.set_variant
 | 
						|
        """
 | 
						|
        self._fontproperties.set_variant(variant)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_fontstyle(self, fontstyle):
 | 
						|
        """
 | 
						|
        Set the font style.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        fontstyle : {'normal', 'italic', 'oblique'}
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.set_style
 | 
						|
        """
 | 
						|
        self._fontproperties.set_style(fontstyle)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_fontsize(self, fontsize):
 | 
						|
        """
 | 
						|
        Set the font size.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        fontsize : float or {'xx-small', 'x-small', 'small', 'medium', \
 | 
						|
'large', 'x-large', 'xx-large'}
 | 
						|
            If a float, the fontsize in points. The string values denote sizes
 | 
						|
            relative to the default font size.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.set_size
 | 
						|
        """
 | 
						|
        self._fontproperties.set_size(fontsize)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_math_fontfamily(self):
 | 
						|
        """
 | 
						|
        Return the font family name for math text rendered by Matplotlib.
 | 
						|
 | 
						|
        The default value is :rc:`mathtext.fontset`.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        set_math_fontfamily
 | 
						|
        """
 | 
						|
        return self._fontproperties.get_math_fontfamily()
 | 
						|
 | 
						|
    def set_math_fontfamily(self, fontfamily):
 | 
						|
        """
 | 
						|
        Set the font family for math text rendered by Matplotlib.
 | 
						|
 | 
						|
        This does only affect Matplotlib's own math renderer. It has no effect
 | 
						|
        when rendering with TeX (``usetex=True``).
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        fontfamily : str
 | 
						|
            The name of the font family.
 | 
						|
 | 
						|
            Available font families are defined in the
 | 
						|
            :ref:`default matplotlibrc file
 | 
						|
            <customizing-with-matplotlibrc-files>`.
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        get_math_fontfamily
 | 
						|
        """
 | 
						|
        self._fontproperties.set_math_fontfamily(fontfamily)
 | 
						|
 | 
						|
    def set_fontweight(self, weight):
 | 
						|
        """
 | 
						|
        Set the font weight.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        weight : {a numeric value in range 0-1000, 'ultralight', 'light', \
 | 
						|
'normal', 'regular', 'book', 'medium', 'roman', 'semibold', 'demibold', \
 | 
						|
'demi', 'bold', 'heavy', 'extra bold', 'black'}
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.set_weight
 | 
						|
        """
 | 
						|
        self._fontproperties.set_weight(weight)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_fontstretch(self, stretch):
 | 
						|
        """
 | 
						|
        Set the font stretch (horizontal condensation or expansion).
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        stretch : {a numeric value in range 0-1000, 'ultra-condensed', \
 | 
						|
'extra-condensed', 'condensed', 'semi-condensed', 'normal', 'semi-expanded', \
 | 
						|
'expanded', 'extra-expanded', 'ultra-expanded'}
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.set_stretch
 | 
						|
        """
 | 
						|
        self._fontproperties.set_stretch(stretch)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_position(self, xy):
 | 
						|
        """
 | 
						|
        Set the (*x*, *y*) position of the text.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        xy : (float, float)
 | 
						|
        """
 | 
						|
        self.set_x(xy[0])
 | 
						|
        self.set_y(xy[1])
 | 
						|
 | 
						|
    def set_x(self, x):
 | 
						|
        """
 | 
						|
        Set the *x* position of the text.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        x : float
 | 
						|
        """
 | 
						|
        self._x = x
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_y(self, y):
 | 
						|
        """
 | 
						|
        Set the *y* position of the text.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        y : float
 | 
						|
        """
 | 
						|
        self._y = y
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_rotation(self, s):
 | 
						|
        """
 | 
						|
        Set the rotation of the text.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        s : float or {'vertical', 'horizontal'}
 | 
						|
            The rotation angle in degrees in mathematically positive direction
 | 
						|
            (counterclockwise). 'horizontal' equals 0, 'vertical' equals 90.
 | 
						|
        """
 | 
						|
        if isinstance(s, Real):
 | 
						|
            self._rotation = float(s) % 360
 | 
						|
        elif cbook._str_equal(s, 'horizontal') or s is None:
 | 
						|
            self._rotation = 0.
 | 
						|
        elif cbook._str_equal(s, 'vertical'):
 | 
						|
            self._rotation = 90.
 | 
						|
        else:
 | 
						|
            raise ValueError("rotation must be 'vertical', 'horizontal' or "
 | 
						|
                             f"a number, not {s}")
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_transform_rotates_text(self, t):
 | 
						|
        """
 | 
						|
        Whether rotations of the transform affect the text direction.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        t : bool
 | 
						|
        """
 | 
						|
        self._transform_rotates_text = t
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_verticalalignment(self, align):
 | 
						|
        """
 | 
						|
        Set the vertical alignment relative to the anchor point.
 | 
						|
 | 
						|
        See also :doc:`/gallery/text_labels_and_annotations/text_alignment`.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        align : {'baseline', 'bottom', 'center', 'center_baseline', 'top'}
 | 
						|
        """
 | 
						|
        _api.check_in_list(
 | 
						|
            ['top', 'bottom', 'center', 'baseline', 'center_baseline'],
 | 
						|
            align=align)
 | 
						|
        self._verticalalignment = align
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def set_text(self, s):
 | 
						|
        r"""
 | 
						|
        Set the text string *s*.
 | 
						|
 | 
						|
        It may contain newlines (``\n``) or math in LaTeX syntax.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        s : object
 | 
						|
            Any object gets converted to its `str` representation, except for
 | 
						|
            ``None`` which is converted to an empty string.
 | 
						|
        """
 | 
						|
        s = '' if s is None else str(s)
 | 
						|
        if s != self._text:
 | 
						|
            self._text = s
 | 
						|
            self.stale = True
 | 
						|
 | 
						|
    def _preprocess_math(self, s):
 | 
						|
        """
 | 
						|
        Return the string *s* after mathtext preprocessing, and the kind of
 | 
						|
        mathtext support needed.
 | 
						|
 | 
						|
        - If *self* is configured to use TeX, return *s* unchanged except that
 | 
						|
          a single space gets escaped, and the flag "TeX".
 | 
						|
        - Otherwise, if *s* is mathtext (has an even number of unescaped dollar
 | 
						|
          signs) and ``parse_math`` is not set to False, return *s* and the
 | 
						|
          flag True.
 | 
						|
        - Otherwise, return *s* with dollar signs unescaped, and the flag
 | 
						|
          False.
 | 
						|
        """
 | 
						|
        if self.get_usetex():
 | 
						|
            if s == " ":
 | 
						|
                s = r"\ "
 | 
						|
            return s, "TeX"
 | 
						|
        elif not self.get_parse_math():
 | 
						|
            return s, False
 | 
						|
        elif cbook.is_math_text(s):
 | 
						|
            return s, True
 | 
						|
        else:
 | 
						|
            return s.replace(r"\$", "$"), False
 | 
						|
 | 
						|
    def set_fontproperties(self, fp):
 | 
						|
        """
 | 
						|
        Set the font properties that control the text.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        fp : `.font_manager.FontProperties` or `str` or `pathlib.Path`
 | 
						|
            If a `str`, it is interpreted as a fontconfig pattern parsed by
 | 
						|
            `.FontProperties`.  If a `pathlib.Path`, it is interpreted as the
 | 
						|
            absolute path to a font file.
 | 
						|
        """
 | 
						|
        self._fontproperties = FontProperties._from_any(fp).copy()
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    @_docstring.kwarg_doc("bool, default: :rc:`text.usetex`")
 | 
						|
    def set_usetex(self, usetex):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        usetex : bool or None
 | 
						|
            Whether to render using TeX, ``None`` means to use
 | 
						|
            :rc:`text.usetex`.
 | 
						|
        """
 | 
						|
        if usetex is None:
 | 
						|
            self._usetex = mpl.rcParams['text.usetex']
 | 
						|
        else:
 | 
						|
            self._usetex = bool(usetex)
 | 
						|
        self.stale = True
 | 
						|
 | 
						|
    def get_usetex(self):
 | 
						|
        """Return whether this `Text` object uses TeX for rendering."""
 | 
						|
        return self._usetex
 | 
						|
 | 
						|
    def set_parse_math(self, parse_math):
 | 
						|
        """
 | 
						|
        Override switch to disable any mathtext parsing for this `Text`.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        parse_math : bool
 | 
						|
            If False, this `Text` will never use mathtext.  If True, mathtext
 | 
						|
            will be used if there is an even number of unescaped dollar signs.
 | 
						|
        """
 | 
						|
        self._parse_math = bool(parse_math)
 | 
						|
 | 
						|
    def get_parse_math(self):
 | 
						|
        """Return whether mathtext parsing is considered for this `Text`."""
 | 
						|
        return self._parse_math
 | 
						|
 | 
						|
    def set_fontname(self, fontname):
 | 
						|
        """
 | 
						|
        Alias for `set_fontfamily`.
 | 
						|
 | 
						|
        One-way alias only: the getter differs.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        fontname : {FONTNAME, 'serif', 'sans-serif', 'cursive', 'fantasy', \
 | 
						|
'monospace'}
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        .font_manager.FontProperties.set_family
 | 
						|
 | 
						|
        """
 | 
						|
        self.set_fontfamily(fontname)
 | 
						|
 | 
						|
 | 
						|
class OffsetFrom:
 | 
						|
    """Callable helper class for working with `Annotation`."""
 | 
						|
 | 
						|
    def __init__(self, artist, ref_coord, unit="points"):
 | 
						|
        """
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        artist : `~matplotlib.artist.Artist` or `.BboxBase` or `.Transform`
 | 
						|
            The object to compute the offset from.
 | 
						|
 | 
						|
        ref_coord : (float, float)
 | 
						|
            If *artist* is an `.Artist` or `.BboxBase`, this values is
 | 
						|
            the location to of the offset origin in fractions of the
 | 
						|
            *artist* bounding box.
 | 
						|
 | 
						|
            If *artist* is a transform, the offset origin is the
 | 
						|
            transform applied to this value.
 | 
						|
 | 
						|
        unit : {'points, 'pixels'}, default: 'points'
 | 
						|
            The screen units to use (pixels or points) for the offset input.
 | 
						|
        """
 | 
						|
        self._artist = artist
 | 
						|
        x, y = ref_coord  # Make copy when ref_coord is an array (and check the shape).
 | 
						|
        self._ref_coord = x, y
 | 
						|
        self.set_unit(unit)
 | 
						|
 | 
						|
    def set_unit(self, unit):
 | 
						|
        """
 | 
						|
        Set the unit for input to the transform used by ``__call__``.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        unit : {'points', 'pixels'}
 | 
						|
        """
 | 
						|
        _api.check_in_list(["points", "pixels"], unit=unit)
 | 
						|
        self._unit = unit
 | 
						|
 | 
						|
    def get_unit(self):
 | 
						|
        """Return the unit for input to the transform used by ``__call__``."""
 | 
						|
        return self._unit
 | 
						|
 | 
						|
    def __call__(self, renderer):
 | 
						|
        """
 | 
						|
        Return the offset transform.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        renderer : `RendererBase`
 | 
						|
            The renderer to use to compute the offset
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        `Transform`
 | 
						|
            Maps (x, y) in pixel or point units to screen units
 | 
						|
            relative to the given artist.
 | 
						|
        """
 | 
						|
        if isinstance(self._artist, Artist):
 | 
						|
            bbox = self._artist.get_window_extent(renderer)
 | 
						|
            xf, yf = self._ref_coord
 | 
						|
            x = bbox.x0 + bbox.width * xf
 | 
						|
            y = bbox.y0 + bbox.height * yf
 | 
						|
        elif isinstance(self._artist, BboxBase):
 | 
						|
            bbox = self._artist
 | 
						|
            xf, yf = self._ref_coord
 | 
						|
            x = bbox.x0 + bbox.width * xf
 | 
						|
            y = bbox.y0 + bbox.height * yf
 | 
						|
        elif isinstance(self._artist, Transform):
 | 
						|
            x, y = self._artist.transform(self._ref_coord)
 | 
						|
        else:
 | 
						|
            _api.check_isinstance((Artist, BboxBase, Transform), artist=self._artist)
 | 
						|
        scale = 1 if self._unit == "pixels" else renderer.points_to_pixels(1)
 | 
						|
        return Affine2D().scale(scale).translate(x, y)
 | 
						|
 | 
						|
 | 
						|
class _AnnotationBase:
 | 
						|
    def __init__(self,
 | 
						|
                 xy,
 | 
						|
                 xycoords='data',
 | 
						|
                 annotation_clip=None):
 | 
						|
 | 
						|
        x, y = xy  # Make copy when xy is an array (and check the shape).
 | 
						|
        self.xy = x, y
 | 
						|
        self.xycoords = xycoords
 | 
						|
        self.set_annotation_clip(annotation_clip)
 | 
						|
 | 
						|
        self._draggable = None
 | 
						|
 | 
						|
    def _get_xy(self, renderer, xy, coords):
 | 
						|
        x, y = xy
 | 
						|
        xcoord, ycoord = coords if isinstance(coords, tuple) else (coords, coords)
 | 
						|
        if xcoord == 'data':
 | 
						|
            x = float(self.convert_xunits(x))
 | 
						|
        if ycoord == 'data':
 | 
						|
            y = float(self.convert_yunits(y))
 | 
						|
        return self._get_xy_transform(renderer, coords).transform((x, y))
 | 
						|
 | 
						|
    def _get_xy_transform(self, renderer, coords):
 | 
						|
 | 
						|
        if isinstance(coords, tuple):
 | 
						|
            xcoord, ycoord = coords
 | 
						|
            from matplotlib.transforms import blended_transform_factory
 | 
						|
            tr1 = self._get_xy_transform(renderer, xcoord)
 | 
						|
            tr2 = self._get_xy_transform(renderer, ycoord)
 | 
						|
            return blended_transform_factory(tr1, tr2)
 | 
						|
        elif callable(coords):
 | 
						|
            tr = coords(renderer)
 | 
						|
            if isinstance(tr, BboxBase):
 | 
						|
                return BboxTransformTo(tr)
 | 
						|
            elif isinstance(tr, Transform):
 | 
						|
                return tr
 | 
						|
            else:
 | 
						|
                raise TypeError(
 | 
						|
                    f"xycoords callable must return a BboxBase or Transform, not a "
 | 
						|
                    f"{type(tr).__name__}")
 | 
						|
        elif isinstance(coords, Artist):
 | 
						|
            bbox = coords.get_window_extent(renderer)
 | 
						|
            return BboxTransformTo(bbox)
 | 
						|
        elif isinstance(coords, BboxBase):
 | 
						|
            return BboxTransformTo(coords)
 | 
						|
        elif isinstance(coords, Transform):
 | 
						|
            return coords
 | 
						|
        elif not isinstance(coords, str):
 | 
						|
            raise TypeError(
 | 
						|
                f"'xycoords' must be an instance of str, tuple[str, str], Artist, "
 | 
						|
                f"Transform, or Callable, not a {type(coords).__name__}")
 | 
						|
 | 
						|
        if coords == 'data':
 | 
						|
            return self.axes.transData
 | 
						|
        elif coords == 'polar':
 | 
						|
            from matplotlib.projections import PolarAxes
 | 
						|
            tr = PolarAxes.PolarTransform(apply_theta_transforms=False)
 | 
						|
            trans = tr + self.axes.transData
 | 
						|
            return trans
 | 
						|
 | 
						|
        try:
 | 
						|
            bbox_name, unit = coords.split()
 | 
						|
        except ValueError:  # i.e. len(coords.split()) != 2.
 | 
						|
            raise ValueError(f"{coords!r} is not a valid coordinate") from None
 | 
						|
 | 
						|
        bbox0, xy0 = None, None
 | 
						|
 | 
						|
        # if unit is offset-like
 | 
						|
        if bbox_name == "figure":
 | 
						|
            bbox0 = self.get_figure(root=False).figbbox
 | 
						|
        elif bbox_name == "subfigure":
 | 
						|
            bbox0 = self.get_figure(root=False).bbox
 | 
						|
        elif bbox_name == "axes":
 | 
						|
            bbox0 = self.axes.bbox
 | 
						|
 | 
						|
        # reference x, y in display coordinate
 | 
						|
        if bbox0 is not None:
 | 
						|
            xy0 = bbox0.p0
 | 
						|
        elif bbox_name == "offset":
 | 
						|
            xy0 = self._get_position_xy(renderer)
 | 
						|
        else:
 | 
						|
            raise ValueError(f"{coords!r} is not a valid coordinate")
 | 
						|
 | 
						|
        if unit == "points":
 | 
						|
            tr = Affine2D().scale(
 | 
						|
                self.get_figure(root=True).dpi / 72)  # dpi/72 dots per point
 | 
						|
        elif unit == "pixels":
 | 
						|
            tr = Affine2D()
 | 
						|
        elif unit == "fontsize":
 | 
						|
            tr = Affine2D().scale(
 | 
						|
                self.get_size() * self.get_figure(root=True).dpi / 72)
 | 
						|
        elif unit == "fraction":
 | 
						|
            tr = Affine2D().scale(*bbox0.size)
 | 
						|
        else:
 | 
						|
            raise ValueError(f"{unit!r} is not a recognized unit")
 | 
						|
 | 
						|
        return tr.translate(*xy0)
 | 
						|
 | 
						|
    def set_annotation_clip(self, b):
 | 
						|
        """
 | 
						|
        Set the annotation's clipping behavior.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        b : bool or None
 | 
						|
            - True: The annotation will be clipped when ``self.xy`` is
 | 
						|
              outside the Axes.
 | 
						|
            - False: The annotation will always be drawn.
 | 
						|
            - None: The annotation will be clipped when ``self.xy`` is
 | 
						|
              outside the Axes and ``self.xycoords == "data"``.
 | 
						|
        """
 | 
						|
        self._annotation_clip = b
 | 
						|
 | 
						|
    def get_annotation_clip(self):
 | 
						|
        """
 | 
						|
        Return the annotation's clipping behavior.
 | 
						|
 | 
						|
        See `set_annotation_clip` for the meaning of return values.
 | 
						|
        """
 | 
						|
        return self._annotation_clip
 | 
						|
 | 
						|
    def _get_position_xy(self, renderer):
 | 
						|
        """Return the pixel position of the annotated point."""
 | 
						|
        return self._get_xy(renderer, self.xy, self.xycoords)
 | 
						|
 | 
						|
    def _check_xy(self, renderer=None):
 | 
						|
        """Check whether the annotation at *xy_pixel* should be drawn."""
 | 
						|
        if renderer is None:
 | 
						|
            renderer = self.get_figure(root=True)._get_renderer()
 | 
						|
        b = self.get_annotation_clip()
 | 
						|
        if b or (b is None and self.xycoords == "data"):
 | 
						|
            # check if self.xy is inside the Axes.
 | 
						|
            xy_pixel = self._get_position_xy(renderer)
 | 
						|
            return self.axes.contains_point(xy_pixel)
 | 
						|
        return True
 | 
						|
 | 
						|
    def draggable(self, state=None, use_blit=False):
 | 
						|
        """
 | 
						|
        Set whether the annotation is draggable with the mouse.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        state : bool or None
 | 
						|
            - True or False: set the draggability.
 | 
						|
            - None: toggle the draggability.
 | 
						|
        use_blit : bool, default: False
 | 
						|
            Use blitting for faster image composition. For details see
 | 
						|
            :ref:`func-animation`.
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        DraggableAnnotation or None
 | 
						|
            If the annotation is draggable, the corresponding
 | 
						|
            `.DraggableAnnotation` helper is returned.
 | 
						|
        """
 | 
						|
        from matplotlib.offsetbox import DraggableAnnotation
 | 
						|
        is_draggable = self._draggable is not None
 | 
						|
 | 
						|
        # if state is None we'll toggle
 | 
						|
        if state is None:
 | 
						|
            state = not is_draggable
 | 
						|
 | 
						|
        if state:
 | 
						|
            if self._draggable is None:
 | 
						|
                self._draggable = DraggableAnnotation(self, use_blit)
 | 
						|
        else:
 | 
						|
            if self._draggable is not None:
 | 
						|
                self._draggable.disconnect()
 | 
						|
            self._draggable = None
 | 
						|
 | 
						|
        return self._draggable
 | 
						|
 | 
						|
 | 
						|
class Annotation(Text, _AnnotationBase):
 | 
						|
    """
 | 
						|
    An `.Annotation` is a `.Text` that can refer to a specific position *xy*.
 | 
						|
    Optionally an arrow pointing from the text to *xy* can be drawn.
 | 
						|
 | 
						|
    Attributes
 | 
						|
    ----------
 | 
						|
    xy
 | 
						|
        The annotated position.
 | 
						|
    xycoords
 | 
						|
        The coordinate system for *xy*.
 | 
						|
    arrow_patch
 | 
						|
        A `.FancyArrowPatch` to point from *xytext* to *xy*.
 | 
						|
    """
 | 
						|
 | 
						|
    def __str__(self):
 | 
						|
        return f"Annotation({self.xy[0]:g}, {self.xy[1]:g}, {self._text!r})"
 | 
						|
 | 
						|
    def __init__(self, text, xy,
 | 
						|
                 xytext=None,
 | 
						|
                 xycoords='data',
 | 
						|
                 textcoords=None,
 | 
						|
                 arrowprops=None,
 | 
						|
                 annotation_clip=None,
 | 
						|
                 **kwargs):
 | 
						|
        """
 | 
						|
        Annotate the point *xy* with text *text*.
 | 
						|
 | 
						|
        In the simplest form, the text is placed at *xy*.
 | 
						|
 | 
						|
        Optionally, the text can be displayed in another position *xytext*.
 | 
						|
        An arrow pointing from the text to the annotated point *xy* can then
 | 
						|
        be added by defining *arrowprops*.
 | 
						|
 | 
						|
        Parameters
 | 
						|
        ----------
 | 
						|
        text : str
 | 
						|
            The text of the annotation.
 | 
						|
 | 
						|
        xy : (float, float)
 | 
						|
            The point *(x, y)* to annotate. The coordinate system is determined
 | 
						|
            by *xycoords*.
 | 
						|
 | 
						|
        xytext : (float, float), default: *xy*
 | 
						|
            The position *(x, y)* to place the text at. The coordinate system
 | 
						|
            is determined by *textcoords*.
 | 
						|
 | 
						|
        xycoords : single or two-tuple of str or `.Artist` or `.Transform` or \
 | 
						|
callable, default: 'data'
 | 
						|
 | 
						|
            The coordinate system that *xy* is given in. The following types
 | 
						|
            of values are supported:
 | 
						|
 | 
						|
            - One of the following strings:
 | 
						|
 | 
						|
              ==================== ============================================
 | 
						|
              Value                Description
 | 
						|
              ==================== ============================================
 | 
						|
              'figure points'      Points from the lower left of the figure
 | 
						|
              'figure pixels'      Pixels from the lower left of the figure
 | 
						|
              'figure fraction'    Fraction of figure from lower left
 | 
						|
              'subfigure points'   Points from the lower left of the subfigure
 | 
						|
              'subfigure pixels'   Pixels from the lower left of the subfigure
 | 
						|
              'subfigure fraction' Fraction of subfigure from lower left
 | 
						|
              'axes points'        Points from lower left corner of the Axes
 | 
						|
              'axes pixels'        Pixels from lower left corner of the Axes
 | 
						|
              'axes fraction'      Fraction of Axes from lower left
 | 
						|
              'data'               Use the coordinate system of the object
 | 
						|
                                   being annotated (default)
 | 
						|
              'polar'              *(theta, r)* if not native 'data'
 | 
						|
                                   coordinates
 | 
						|
              ==================== ============================================
 | 
						|
 | 
						|
              Note that 'subfigure pixels' and 'figure pixels' are the same
 | 
						|
              for the parent figure, so users who want code that is usable in
 | 
						|
              a subfigure can use 'subfigure pixels'.
 | 
						|
 | 
						|
            - An `.Artist`: *xy* is interpreted as a fraction of the artist's
 | 
						|
              `~matplotlib.transforms.Bbox`. E.g. *(0, 0)* would be the lower
 | 
						|
              left corner of the bounding box and *(0.5, 1)* would be the
 | 
						|
              center top of the bounding box.
 | 
						|
 | 
						|
            - A `.Transform` to transform *xy* to screen coordinates.
 | 
						|
 | 
						|
            - A function with one of the following signatures::
 | 
						|
 | 
						|
                def transform(renderer) -> Bbox
 | 
						|
                def transform(renderer) -> Transform
 | 
						|
 | 
						|
              where *renderer* is a `.RendererBase` subclass.
 | 
						|
 | 
						|
              The result of the function is interpreted like the `.Artist` and
 | 
						|
              `.Transform` cases above.
 | 
						|
 | 
						|
            - A tuple *(xcoords, ycoords)* specifying separate coordinate
 | 
						|
              systems for *x* and *y*. *xcoords* and *ycoords* must each be
 | 
						|
              of one of the above described types.
 | 
						|
 | 
						|
            See :ref:`plotting-guide-annotation` for more details.
 | 
						|
 | 
						|
        textcoords : single or two-tuple of str or `.Artist` or `.Transform` \
 | 
						|
or callable, default: value of *xycoords*
 | 
						|
            The coordinate system that *xytext* is given in.
 | 
						|
 | 
						|
            All *xycoords* values are valid as well as the following strings:
 | 
						|
 | 
						|
            =================   =================================================
 | 
						|
            Value               Description
 | 
						|
            =================   =================================================
 | 
						|
            'offset points'     Offset, in points, from the *xy* value
 | 
						|
            'offset pixels'     Offset, in pixels, from the *xy* value
 | 
						|
            'offset fontsize'   Offset, relative to fontsize, from the *xy* value
 | 
						|
            =================   =================================================
 | 
						|
 | 
						|
        arrowprops : dict, optional
 | 
						|
            The properties used to draw a `.FancyArrowPatch` arrow between the
 | 
						|
            positions *xy* and *xytext*.  Defaults to None, i.e. no arrow is
 | 
						|
            drawn.
 | 
						|
 | 
						|
            For historical reasons there are two different ways to specify
 | 
						|
            arrows, "simple" and "fancy":
 | 
						|
 | 
						|
            **Simple arrow:**
 | 
						|
 | 
						|
            If *arrowprops* does not contain the key 'arrowstyle' the
 | 
						|
            allowed keys are:
 | 
						|
 | 
						|
            ==========  =================================================
 | 
						|
            Key         Description
 | 
						|
            ==========  =================================================
 | 
						|
            width       The width of the arrow in points
 | 
						|
            headwidth   The width of the base of the arrow head in points
 | 
						|
            headlength  The length of the arrow head in points
 | 
						|
            shrink      Fraction of total length to shrink from both ends
 | 
						|
            ?           Any `.FancyArrowPatch` property
 | 
						|
            ==========  =================================================
 | 
						|
 | 
						|
            The arrow is attached to the edge of the text box, the exact
 | 
						|
            position (corners or centers) depending on where it's pointing to.
 | 
						|
 | 
						|
            **Fancy arrow:**
 | 
						|
 | 
						|
            This is used if 'arrowstyle' is provided in the *arrowprops*.
 | 
						|
 | 
						|
            Valid keys are the following `.FancyArrowPatch` parameters:
 | 
						|
 | 
						|
            ===============  ===================================
 | 
						|
            Key              Description
 | 
						|
            ===============  ===================================
 | 
						|
            arrowstyle       The arrow style
 | 
						|
            connectionstyle  The connection style
 | 
						|
            relpos           See below; default is (0.5, 0.5)
 | 
						|
            patchA           Default is bounding box of the text
 | 
						|
            patchB           Default is None
 | 
						|
            shrinkA          In points. Default is 2 points
 | 
						|
            shrinkB          In points. Default is 2 points
 | 
						|
            mutation_scale   Default is text size (in points)
 | 
						|
            mutation_aspect  Default is 1
 | 
						|
            ?                Any `.FancyArrowPatch` property
 | 
						|
            ===============  ===================================
 | 
						|
 | 
						|
            The exact starting point position of the arrow is defined by
 | 
						|
            *relpos*. It's a tuple of relative coordinates of the text box,
 | 
						|
            where (0, 0) is the lower left corner and (1, 1) is the upper
 | 
						|
            right corner. Values <0 and >1 are supported and specify points
 | 
						|
            outside the text box. By default (0.5, 0.5), so the starting point
 | 
						|
            is centered in the text box.
 | 
						|
 | 
						|
        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'.
 | 
						|
 | 
						|
        **kwargs
 | 
						|
            Additional kwargs are passed to `.Text`.
 | 
						|
 | 
						|
        Returns
 | 
						|
        -------
 | 
						|
        `.Annotation`
 | 
						|
 | 
						|
        See Also
 | 
						|
        --------
 | 
						|
        :ref:`annotations`
 | 
						|
 | 
						|
        """
 | 
						|
        _AnnotationBase.__init__(self,
 | 
						|
                                 xy,
 | 
						|
                                 xycoords=xycoords,
 | 
						|
                                 annotation_clip=annotation_clip)
 | 
						|
        # warn about wonky input data
 | 
						|
        if (xytext is None and
 | 
						|
                textcoords is not None and
 | 
						|
                textcoords != xycoords):
 | 
						|
            _api.warn_external("You have used the `textcoords` kwarg, but "
 | 
						|
                               "not the `xytext` kwarg.  This can lead to "
 | 
						|
                               "surprising results.")
 | 
						|
 | 
						|
        # clean up textcoords and assign default
 | 
						|
        if textcoords is None:
 | 
						|
            textcoords = self.xycoords
 | 
						|
        self._textcoords = textcoords
 | 
						|
 | 
						|
        # cleanup xytext defaults
 | 
						|
        if xytext is None:
 | 
						|
            xytext = self.xy
 | 
						|
        x, y = xytext
 | 
						|
 | 
						|
        self.arrowprops = arrowprops
 | 
						|
        if arrowprops is not None:
 | 
						|
            arrowprops = arrowprops.copy()
 | 
						|
            if "arrowstyle" in arrowprops:
 | 
						|
                self._arrow_relpos = arrowprops.pop("relpos", (0.5, 0.5))
 | 
						|
            else:
 | 
						|
                # modified YAArrow API to be used with FancyArrowPatch
 | 
						|
                for key in ['width', 'headwidth', 'headlength', 'shrink']:
 | 
						|
                    arrowprops.pop(key, None)
 | 
						|
            self.arrow_patch = FancyArrowPatch((0, 0), (1, 1), **arrowprops)
 | 
						|
        else:
 | 
						|
            self.arrow_patch = None
 | 
						|
 | 
						|
        # Must come last, as some kwargs may be propagated to arrow_patch.
 | 
						|
        Text.__init__(self, x, y, text, **kwargs)
 | 
						|
 | 
						|
    def contains(self, mouseevent):
 | 
						|
        if self._different_canvas(mouseevent):
 | 
						|
            return False, {}
 | 
						|
        contains, tinfo = Text.contains(self, mouseevent)
 | 
						|
        if self.arrow_patch is not None:
 | 
						|
            in_patch, _ = self.arrow_patch.contains(mouseevent)
 | 
						|
            contains = contains or in_patch
 | 
						|
        return contains, tinfo
 | 
						|
 | 
						|
    @property
 | 
						|
    def xycoords(self):
 | 
						|
        return self._xycoords
 | 
						|
 | 
						|
    @xycoords.setter
 | 
						|
    def xycoords(self, xycoords):
 | 
						|
        def is_offset(s):
 | 
						|
            return isinstance(s, str) and s.startswith("offset")
 | 
						|
 | 
						|
        if (isinstance(xycoords, tuple) and any(map(is_offset, xycoords))
 | 
						|
                or is_offset(xycoords)):
 | 
						|
            raise ValueError("xycoords cannot be an offset coordinate")
 | 
						|
        self._xycoords = xycoords
 | 
						|
 | 
						|
    @property
 | 
						|
    def xyann(self):
 | 
						|
        """
 | 
						|
        The text position.
 | 
						|
 | 
						|
        See also *xytext* in `.Annotation`.
 | 
						|
        """
 | 
						|
        return self.get_position()
 | 
						|
 | 
						|
    @xyann.setter
 | 
						|
    def xyann(self, xytext):
 | 
						|
        self.set_position(xytext)
 | 
						|
 | 
						|
    def get_anncoords(self):
 | 
						|
        """
 | 
						|
        Return the coordinate system to use for `.Annotation.xyann`.
 | 
						|
 | 
						|
        See also *xycoords* in `.Annotation`.
 | 
						|
        """
 | 
						|
        return self._textcoords
 | 
						|
 | 
						|
    def set_anncoords(self, coords):
 | 
						|
        """
 | 
						|
        Set the coordinate system to use for `.Annotation.xyann`.
 | 
						|
 | 
						|
        See also *xycoords* in `.Annotation`.
 | 
						|
        """
 | 
						|
        self._textcoords = coords
 | 
						|
 | 
						|
    anncoords = property(get_anncoords, set_anncoords, doc="""
 | 
						|
        The coordinate system to use for `.Annotation.xyann`.""")
 | 
						|
 | 
						|
    def set_figure(self, fig):
 | 
						|
        # docstring inherited
 | 
						|
        if self.arrow_patch is not None:
 | 
						|
            self.arrow_patch.set_figure(fig)
 | 
						|
        Artist.set_figure(self, fig)
 | 
						|
 | 
						|
    def update_positions(self, renderer):
 | 
						|
        """
 | 
						|
        Update the pixel positions of the annotation text and the arrow patch.
 | 
						|
        """
 | 
						|
        # generate transformation
 | 
						|
        self.set_transform(self._get_xy_transform(renderer, self.anncoords))
 | 
						|
 | 
						|
        arrowprops = self.arrowprops
 | 
						|
        if arrowprops is None:
 | 
						|
            return
 | 
						|
 | 
						|
        bbox = Text.get_window_extent(self, renderer)
 | 
						|
 | 
						|
        arrow_end = x1, y1 = self._get_position_xy(renderer)  # Annotated pos.
 | 
						|
 | 
						|
        ms = arrowprops.get("mutation_scale", self.get_size())
 | 
						|
        self.arrow_patch.set_mutation_scale(ms)
 | 
						|
 | 
						|
        if "arrowstyle" not in arrowprops:
 | 
						|
            # Approximately simulate the YAArrow.
 | 
						|
            shrink = arrowprops.get('shrink', 0.0)
 | 
						|
            width = arrowprops.get('width', 4)
 | 
						|
            headwidth = arrowprops.get('headwidth', 12)
 | 
						|
            headlength = arrowprops.get('headlength', 12)
 | 
						|
 | 
						|
            # NB: ms is in pts
 | 
						|
            stylekw = dict(head_length=headlength / ms,
 | 
						|
                           head_width=headwidth / ms,
 | 
						|
                           tail_width=width / ms)
 | 
						|
 | 
						|
            self.arrow_patch.set_arrowstyle('simple', **stylekw)
 | 
						|
 | 
						|
            # using YAArrow style:
 | 
						|
            # pick the corner of the text bbox closest to annotated point.
 | 
						|
            xpos = [(bbox.x0, 0), ((bbox.x0 + bbox.x1) / 2, 0.5), (bbox.x1, 1)]
 | 
						|
            ypos = [(bbox.y0, 0), ((bbox.y0 + bbox.y1) / 2, 0.5), (bbox.y1, 1)]
 | 
						|
            x, relposx = min(xpos, key=lambda v: abs(v[0] - x1))
 | 
						|
            y, relposy = min(ypos, key=lambda v: abs(v[0] - y1))
 | 
						|
            self._arrow_relpos = (relposx, relposy)
 | 
						|
            r = np.hypot(y - y1, x - x1)
 | 
						|
            shrink_pts = shrink * r / renderer.points_to_pixels(1)
 | 
						|
            self.arrow_patch.shrinkA = self.arrow_patch.shrinkB = shrink_pts
 | 
						|
 | 
						|
        # 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
 | 
						|
        # The arrow is drawn from arrow_begin to arrow_end.  It will be first
 | 
						|
        # clipped by patchA and patchB.  Then it will be shrunk by shrinkA and
 | 
						|
        # shrinkB (in points).  If patchA is not set, self.bbox_patch is used.
 | 
						|
        self.arrow_patch.set_positions(arrow_begin, arrow_end)
 | 
						|
 | 
						|
        if "patchA" in arrowprops:
 | 
						|
            patchA = arrowprops["patchA"]
 | 
						|
        elif self._bbox_patch:
 | 
						|
            patchA = self._bbox_patch
 | 
						|
        elif self.get_text() == "":
 | 
						|
            patchA = None
 | 
						|
        else:
 | 
						|
            pad = renderer.points_to_pixels(4)
 | 
						|
            patchA = Rectangle(
 | 
						|
                xy=(bbox.x0 - pad / 2, bbox.y0 - pad / 2),
 | 
						|
                width=bbox.width + pad, height=bbox.height + pad,
 | 
						|
                transform=IdentityTransform(), clip_on=False)
 | 
						|
        self.arrow_patch.set_patchA(patchA)
 | 
						|
 | 
						|
    @artist.allow_rasterization
 | 
						|
    def draw(self, renderer):
 | 
						|
        # docstring inherited
 | 
						|
        if renderer is not None:
 | 
						|
            self._renderer = renderer
 | 
						|
        if not self.get_visible() or not self._check_xy(renderer):
 | 
						|
            return
 | 
						|
        # Update text positions before `Text.draw` would, so that the
 | 
						|
        # FancyArrowPatch is correctly positioned.
 | 
						|
        self.update_positions(renderer)
 | 
						|
        self.update_bbox_position_size(renderer)
 | 
						|
        if self.arrow_patch is not None:  # FancyArrowPatch
 | 
						|
            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)
 | 
						|
        # Draw text, including FancyBboxPatch, after FancyArrowPatch.
 | 
						|
        # Otherwise, a wedge arrowstyle can land partly on top of the Bbox.
 | 
						|
        Text.draw(self, renderer)
 | 
						|
 | 
						|
    def get_window_extent(self, renderer=None):
 | 
						|
        # docstring inherited
 | 
						|
        # This block is the same as in Text.get_window_extent, but we need to
 | 
						|
        # set the renderer before calling update_positions().
 | 
						|
        if not self.get_visible() or not self._check_xy(renderer):
 | 
						|
            return Bbox.unit()
 | 
						|
        if renderer is not None:
 | 
						|
            self._renderer = renderer
 | 
						|
        if self._renderer is None:
 | 
						|
            self._renderer = self.get_figure(root=True)._get_renderer()
 | 
						|
        if self._renderer is None:
 | 
						|
            raise RuntimeError('Cannot get window extent without renderer')
 | 
						|
 | 
						|
        self.update_positions(self._renderer)
 | 
						|
 | 
						|
        text_bbox = Text.get_window_extent(self)
 | 
						|
        bboxes = [text_bbox]
 | 
						|
 | 
						|
        if self.arrow_patch is not None:
 | 
						|
            bboxes.append(self.arrow_patch.get_window_extent())
 | 
						|
 | 
						|
        return Bbox.union(bboxes)
 | 
						|
 | 
						|
    def get_tightbbox(self, renderer=None):
 | 
						|
        # docstring inherited
 | 
						|
        if not self._check_xy(renderer):
 | 
						|
            return Bbox.null()
 | 
						|
        return super().get_tightbbox(renderer)
 | 
						|
 | 
						|
 | 
						|
_docstring.interpd.register(Annotation=Annotation.__init__.__doc__)
 |