1"""
2Classes for including text in a figure.
3"""
4
5import contextlib
6import logging
7import math
8import weakref
9
10import numpy as np
11
12import matplotlib as mpl
13from . import _api, artist, cbook, docstring
14from .artist import Artist
15from .font_manager import FontProperties
16from .patches import FancyArrowPatch, FancyBboxPatch, Rectangle
17from .textpath import TextPath  # Unused, but imported by others.
18from .transforms import (
19    Affine2D, Bbox, BboxBase, BboxTransformTo, IdentityTransform, Transform)
20
21
22_log = logging.getLogger(__name__)
23
24
25@contextlib.contextmanager
26def _wrap_text(textobj):
27    """Temporarily inserts newlines if the wrap option is enabled."""
28    if textobj.get_wrap():
29        old_text = textobj.get_text()
30        try:
31            textobj.set_text(textobj._get_wrapped_text())
32            yield textobj
33        finally:
34            textobj.set_text(old_text)
35    else:
36        yield textobj
37
38
39# Extracted from Text's method to serve as a function
40def get_rotation(rotation):
41    """
42    Return *rotation* normalized to an angle between 0 and 360 degrees.
43
44    Parameters
45    ----------
46    rotation : float or {None, 'horizontal', 'vertical'}
47        Rotation angle in degrees. *None* and 'horizontal' equal 0,
48        'vertical' equals 90.
49
50    Returns
51    -------
52    float
53    """
54    try:
55        return float(rotation) % 360
56    except (ValueError, TypeError) as err:
57        if cbook._str_equal(rotation, 'horizontal') or rotation is None:
58            return 0.
59        elif cbook._str_equal(rotation, 'vertical'):
60            return 90.
61        else:
62            raise ValueError("rotation is {!r}; expected either 'horizontal', "
63                             "'vertical', numeric value, or None"
64                             .format(rotation)) from err
65
66
67def _get_textbox(text, renderer):
68    """
69    Calculate the bounding box of the text.
70
71    The bbox position takes text rotation into account, but the width and
72    height are those of the unrotated box (unlike `.Text.get_window_extent`).
73    """
74    # TODO : This function may move into the Text class as a method. As a
75    # matter of fact, the information from the _get_textbox function
76    # should be available during the Text._get_layout() call, which is
77    # called within the _get_textbox. So, it would better to move this
78    # function as a method with some refactoring of _get_layout method.
79
80    projected_xs = []
81    projected_ys = []
82
83    theta = np.deg2rad(text.get_rotation())
84    tr = Affine2D().rotate(-theta)
85
86    _, parts, d = text._get_layout(renderer)
87
88    for t, wh, x, y in parts:
89        w, h = wh
90
91        xt1, yt1 = tr.transform((x, y))
92        yt1 -= d
93        xt2, yt2 = xt1 + w, yt1 + h
94
95        projected_xs.extend([xt1, xt2])
96        projected_ys.extend([yt1, yt2])
97
98    xt_box, yt_box = min(projected_xs), min(projected_ys)
99    w_box, h_box = max(projected_xs) - xt_box, max(projected_ys) - yt_box
100
101    x_box, y_box = Affine2D().rotate(theta).transform((xt_box, yt_box))
102
103    return x_box, y_box, w_box, h_box
104
105
106@cbook._define_aliases({
107    "color": ["c"],
108    "fontfamily": ["family"],
109    "fontproperties": ["font", "font_properties"],
110    "horizontalalignment": ["ha"],
111    "multialignment": ["ma"],
112    "fontname": ["name"],
113    "fontsize": ["size"],
114    "fontstretch": ["stretch"],
115    "fontstyle": ["style"],
116    "fontvariant": ["variant"],
117    "verticalalignment": ["va"],
118    "fontweight": ["weight"],
119})
120class Text(Artist):
121    """Handle storing and drawing of text in window or data coordinates."""
122
123    zorder = 3
124    _cached = cbook.maxdict(50)
125
126    def __repr__(self):
127        return "Text(%s, %s, %s)" % (self._x, self._y, repr(self._text))
128
129    def __init__(self,
130                 x=0, y=0, text='',
131                 color=None,           # defaults to rc params
132                 verticalalignment='baseline',
133                 horizontalalignment='left',
134                 multialignment=None,
135                 fontproperties=None,  # defaults to FontProperties()
136                 rotation=None,
137                 linespacing=None,
138                 rotation_mode=None,
139                 usetex=None,          # defaults to rcParams['text.usetex']
140                 wrap=False,
141                 transform_rotates_text=False,
142                 **kwargs
143                 ):
144        """
145        Create a `.Text` instance at *x*, *y* with string *text*.
146
147        Valid keyword arguments are:
148
149        %(Text_kwdoc)s
150        """
151        super().__init__()
152        self._x, self._y = x, y
153        self._text = ''
154        self.set_text(text)
155        self.set_color(
156            color if color is not None else mpl.rcParams["text.color"])
157        self.set_fontproperties(fontproperties)
158        self.set_usetex(usetex)
159        self.set_wrap(wrap)
160        self.set_verticalalignment(verticalalignment)
161        self.set_horizontalalignment(horizontalalignment)
162        self._multialignment = multialignment
163        self._rotation = rotation
164        self._transform_rotates_text = transform_rotates_text
165        self._bbox_patch = None  # a FancyBboxPatch instance
166        self._renderer = None
167        if linespacing is None:
168            linespacing = 1.2   # Maybe use rcParam later.
169        self._linespacing = linespacing
170        self.set_rotation_mode(rotation_mode)
171        self.update(kwargs)
172
173    def update(self, kwargs):
174        # docstring inherited
175        # make a copy so we do not mutate user input!
176        kwargs = dict(kwargs)
177        sentinel = object()  # bbox can be None, so use another sentinel.
178        # Update fontproperties first, as it has lowest priority.
179        fontproperties = kwargs.pop("fontproperties", sentinel)
180        if fontproperties is not sentinel:
181            self.set_fontproperties(fontproperties)
182        # Update bbox last, as it depends on font properties.
183        bbox = kwargs.pop("bbox", sentinel)
184        super().update(kwargs)
185        if bbox is not sentinel:
186            self.set_bbox(bbox)
187
188    def __getstate__(self):
189        d = super().__getstate__()
190        # remove the cached _renderer (if it exists)
191        d['_renderer'] = None
192        return d
193
194    def contains(self, mouseevent):
195        """
196        Return whether the mouse event occurred inside the axis-aligned
197        bounding-box of the text.
198        """
199        inside, info = self._default_contains(mouseevent)
200        if inside is not None:
201            return inside, info
202
203        if not self.get_visible() or self._renderer is None:
204            return False, {}
205
206        # Explicitly use Text.get_window_extent(self) and not
207        # self.get_window_extent() so that Annotation.contains does not
208        # accidentally cover the entire annotation bounding box.
209        bbox = Text.get_window_extent(self)
210        inside = (bbox.x0 <= mouseevent.x <= bbox.x1
211                  and bbox.y0 <= mouseevent.y <= bbox.y1)
212
213        cattr = {}
214        # if the text has a surrounding patch, also check containment for it,
215        # and merge the results with the results for the text.
216        if self._bbox_patch:
217            patch_inside, patch_cattr = self._bbox_patch.contains(mouseevent)
218            inside = inside or patch_inside
219            cattr["bbox_patch"] = patch_cattr
220
221        return inside, cattr
222
223    def _get_xy_display(self):
224        """
225        Get the (possibly unit converted) transformed x, y in display coords.
226        """
227        x, y = self.get_unitless_position()
228        return self.get_transform().transform((x, y))
229
230    def _get_multialignment(self):
231        if self._multialignment is not None:
232            return self._multialignment
233        else:
234            return self._horizontalalignment
235
236    def get_rotation(self):
237        """Return the text angle in degrees between 0 and 360."""
238        if self.get_transform_rotates_text():
239            angle = get_rotation(self._rotation)
240            x, y = self.get_unitless_position()
241            angles = [angle, ]
242            pts = [[x, y]]
243            return self.get_transform().transform_angles(angles, pts).item(0)
244        else:
245            return get_rotation(self._rotation)  # string_or_number -> number
246
247    def get_transform_rotates_text(self):
248        """
249        Return whether rotations of the transform affect the text direction.
250        """
251        return self._transform_rotates_text
252
253    def set_rotation_mode(self, m):
254        """
255        Set text rotation mode.
256
257        Parameters
258        ----------
259        m : {None, 'default', 'anchor'}
260            If ``None`` or ``"default"``, the text will be first rotated, then
261            aligned according to their horizontal and vertical alignments.  If
262            ``"anchor"``, then alignment occurs before rotation.
263        """
264        _api.check_in_list(["anchor", "default", None], rotation_mode=m)
265        self._rotation_mode = m
266        self.stale = True
267
268    def get_rotation_mode(self):
269        """Return the text rotation mode."""
270        return self._rotation_mode
271
272    def update_from(self, other):
273        # docstring inherited
274        super().update_from(other)
275        self._color = other._color
276        self._multialignment = other._multialignment
277        self._verticalalignment = other._verticalalignment
278        self._horizontalalignment = other._horizontalalignment
279        self._fontproperties = other._fontproperties.copy()
280        self._usetex = other._usetex
281        self._rotation = other._rotation
282        self._transform_rotates_text = other._transform_rotates_text
283        self._picker = other._picker
284        self._linespacing = other._linespacing
285        self.stale = True
286
287    def _get_layout(self, renderer):
288        """
289        Return the extent (bbox) of the text together with
290        multiple-alignment information. Note that it returns an extent
291        of a rotated text when necessary.
292        """
293        key = self.get_prop_tup(renderer=renderer)
294        if key in self._cached:
295            return self._cached[key]
296
297        thisx, thisy = 0.0, 0.0
298        lines = self.get_text().split("\n")  # Ensures lines is not empty.
299
300        ws = []
301        hs = []
302        xs = []
303        ys = []
304
305        # Full vertical extent of font, including ascenders and descenders:
306        _, lp_h, lp_d = renderer.get_text_width_height_descent(
307            "lp", self._fontproperties,
308            ismath="TeX" if self.get_usetex() else False)
309        min_dy = (lp_h - lp_d) * self._linespacing
310
311        for i, line in enumerate(lines):
312            clean_line, ismath = self._preprocess_math(line)
313            if clean_line:
314                w, h, d = renderer.get_text_width_height_descent(
315                    clean_line, self._fontproperties, ismath=ismath)
316            else:
317                w = h = d = 0
318
319            # For multiline text, increase the line spacing when the text
320            # net-height (excluding baseline) is larger than that of a "l"
321            # (e.g., use of superscripts), which seems what TeX does.
322            h = max(h, lp_h)
323            d = max(d, lp_d)
324
325            ws.append(w)
326            hs.append(h)
327
328            # Metrics of the last line that are needed later:
329            baseline = (h - d) - thisy
330
331            if i == 0:
332                # position at baseline
333                thisy = -(h - d)
334            else:
335                # put baseline a good distance from bottom of previous line
336                thisy -= max(min_dy, (h - d) * self._linespacing)
337
338            xs.append(thisx)  # == 0.
339            ys.append(thisy)
340
341            thisy -= d
342
343        # Metrics of the last line that are needed later:
344        descent = d
345
346        # Bounding box definition:
347        width = max(ws)
348        xmin = 0
349        xmax = width
350        ymax = 0
351        ymin = ys[-1] - descent  # baseline of last line minus its descent
352        height = ymax - ymin
353
354        # get the rotation matrix
355        M = Affine2D().rotate_deg(self.get_rotation())
356
357        # now offset the individual text lines within the box
358        malign = self._get_multialignment()
359        if malign == 'left':
360            offset_layout = [(x, y) for x, y in zip(xs, ys)]
361        elif malign == 'center':
362            offset_layout = [(x + width / 2 - w / 2, y)
363                             for x, y, w in zip(xs, ys, ws)]
364        elif malign == 'right':
365            offset_layout = [(x + width - w, y)
366                             for x, y, w in zip(xs, ys, ws)]
367
368        # the corners of the unrotated bounding box
369        corners_horiz = np.array(
370            [(xmin, ymin), (xmin, ymax), (xmax, ymax), (xmax, ymin)])
371
372        # now rotate the bbox
373        corners_rotated = M.transform(corners_horiz)
374        # compute the bounds of the rotated box
375        xmin = corners_rotated[:, 0].min()
376        xmax = corners_rotated[:, 0].max()
377        ymin = corners_rotated[:, 1].min()
378        ymax = corners_rotated[:, 1].max()
379        width = xmax - xmin
380        height = ymax - ymin
381
382        # Now move the box to the target position offset the display
383        # bbox by alignment
384        halign = self._horizontalalignment
385        valign = self._verticalalignment
386
387        rotation_mode = self.get_rotation_mode()
388        if rotation_mode != "anchor":
389            # compute the text location in display coords and the offsets
390            # necessary to align the bbox with that location
391            if halign == 'center':
392                offsetx = (xmin + xmax) / 2
393            elif halign == 'right':
394                offsetx = xmax
395            else:
396                offsetx = xmin
397
398            if valign == 'center':
399                offsety = (ymin + ymax) / 2
400            elif valign == 'top':
401                offsety = ymax
402            elif valign == 'baseline':
403                offsety = ymin + descent
404            elif valign == 'center_baseline':
405                offsety = ymin + height - baseline / 2.0
406            else:
407                offsety = ymin
408        else:
409            xmin1, ymin1 = corners_horiz[0]
410            xmax1, ymax1 = corners_horiz[2]
411
412            if halign == 'center':
413                offsetx = (xmin1 + xmax1) / 2.0
414            elif halign == 'right':
415                offsetx = xmax1
416            else:
417                offsetx = xmin1
418
419            if valign == 'center':
420                offsety = (ymin1 + ymax1) / 2.0
421            elif valign == 'top':
422                offsety = ymax1
423            elif valign == 'baseline':
424                offsety = ymax1 - baseline
425            elif valign == 'center_baseline':
426                offsety = ymax1 - baseline / 2.0
427            else:
428                offsety = ymin1
429
430            offsetx, offsety = M.transform((offsetx, offsety))
431
432        xmin -= offsetx
433        ymin -= offsety
434
435        bbox = Bbox.from_bounds(xmin, ymin, width, height)
436
437        # now rotate the positions around the first (x, y) position
438        xys = M.transform(offset_layout) - (offsetx, offsety)
439
440        ret = bbox, list(zip(lines, zip(ws, hs), *xys.T)), descent
441        self._cached[key] = ret
442        return ret
443
444    def set_bbox(self, rectprops):
445        """
446        Draw a bounding box around self.
447
448        Parameters
449        ----------
450        rectprops : dict with properties for `.patches.FancyBboxPatch`
451             The default boxstyle is 'square'. The mutation
452             scale of the `.patches.FancyBboxPatch` is set to the fontsize.
453
454        Examples
455        --------
456        ::
457
458            t.set_bbox(dict(facecolor='red', alpha=0.5))
459        """
460
461        if rectprops is not None:
462            props = rectprops.copy()
463            boxstyle = props.pop("boxstyle", None)
464            pad = props.pop("pad", None)
465            if boxstyle is None:
466                boxstyle = "square"
467                if pad is None:
468                    pad = 4  # points
469                pad /= self.get_size()  # to fraction of font size
470            else:
471                if pad is None:
472                    pad = 0.3
473            # boxstyle could be a callable or a string
474            if isinstance(boxstyle, str) and "pad" not in boxstyle:
475                boxstyle += ",pad=%0.2f" % pad
476            self._bbox_patch = FancyBboxPatch(
477                (0, 0), 1, 1,
478                boxstyle=boxstyle, transform=IdentityTransform(), **props)
479        else:
480            self._bbox_patch = None
481
482        self._update_clip_properties()
483
484    def get_bbox_patch(self):
485        """
486        Return the bbox Patch, or None if the `.patches.FancyBboxPatch`
487        is not made.
488        """
489        return self._bbox_patch
490
491    def update_bbox_position_size(self, renderer):
492        """
493        Update the location and the size of the bbox.
494
495        This method should be used when the position and size of the bbox needs
496        to be updated before actually drawing the bbox.
497        """
498        if self._bbox_patch:
499            # don't use self.get_unitless_position here, which refers to text
500            # position in Text:
501            posx = float(self.convert_xunits(self._x))
502            posy = float(self.convert_yunits(self._y))
503            posx, posy = self.get_transform().transform((posx, posy))
504
505            x_box, y_box, w_box, h_box = _get_textbox(self, renderer)
506            self._bbox_patch.set_bounds(0., 0., w_box, h_box)
507            self._bbox_patch.set_transform(
508                Affine2D()
509                .rotate_deg(self.get_rotation())
510                .translate(posx + x_box, posy + y_box))
511            fontsize_in_pixel = renderer.points_to_pixels(self.get_size())
512            self._bbox_patch.set_mutation_scale(fontsize_in_pixel)
513
514    def _update_clip_properties(self):
515        clipprops = dict(clip_box=self.clipbox,
516                         clip_path=self._clippath,
517                         clip_on=self._clipon)
518        if self._bbox_patch:
519            self._bbox_patch.update(clipprops)
520
521    def set_clip_box(self, clipbox):
522        # docstring inherited.
523        super().set_clip_box(clipbox)
524        self._update_clip_properties()
525
526    def set_clip_path(self, path, transform=None):
527        # docstring inherited.
528        super().set_clip_path(path, transform)
529        self._update_clip_properties()
530
531    def set_clip_on(self, b):
532        # docstring inherited.
533        super().set_clip_on(b)
534        self._update_clip_properties()
535
536    def get_wrap(self):
537        """Return whether the text can be wrapped."""
538        return self._wrap
539
540    def set_wrap(self, wrap):
541        """
542        Set whether the text can be wrapped.
543
544        Parameters
545        ----------
546        wrap : bool
547
548        Notes
549        -----
550        Wrapping does not work together with
551        ``savefig(..., bbox_inches='tight')`` (which is also used internally
552        by ``%matplotlib inline`` in IPython/Jupyter). The 'tight' setting
553        rescales the canvas to accommodate all content and happens before
554        wrapping.
555        """
556        self._wrap = wrap
557
558    def _get_wrap_line_width(self):
559        """
560        Return the maximum line width for wrapping text based on the current
561        orientation.
562        """
563        x0, y0 = self.get_transform().transform(self.get_position())
564        figure_box = self.get_figure().get_window_extent()
565
566        # Calculate available width based on text alignment
567        alignment = self.get_horizontalalignment()
568        self.set_rotation_mode('anchor')
569        rotation = self.get_rotation()
570
571        left = self._get_dist_to_box(rotation, x0, y0, figure_box)
572        right = self._get_dist_to_box(
573            (180 + rotation) % 360, x0, y0, figure_box)
574
575        if alignment == 'left':
576            line_width = left
577        elif alignment == 'right':
578            line_width = right
579        else:
580            line_width = 2 * min(left, right)
581
582        return line_width
583
584    def _get_dist_to_box(self, rotation, x0, y0, figure_box):
585        """
586        Return the distance from the given points to the boundaries of a
587        rotated box, in pixels.
588        """
589        if rotation > 270:
590            quad = rotation - 270
591            h1 = y0 / math.cos(math.radians(quad))
592            h2 = (figure_box.x1 - x0) / math.cos(math.radians(90 - quad))
593        elif rotation > 180:
594            quad = rotation - 180
595            h1 = x0 / math.cos(math.radians(quad))
596            h2 = y0 / math.cos(math.radians(90 - quad))
597        elif rotation > 90:
598            quad = rotation - 90
599            h1 = (figure_box.y1 - y0) / math.cos(math.radians(quad))
600            h2 = x0 / math.cos(math.radians(90 - quad))
601        else:
602            h1 = (figure_box.x1 - x0) / math.cos(math.radians(rotation))
603            h2 = (figure_box.y1 - y0) / math.cos(math.radians(90 - rotation))
604
605        return min(h1, h2)
606
607    def _get_rendered_text_width(self, text):
608        """
609        Return the width of a given text string, in pixels.
610        """
611        w, h, d = self._renderer.get_text_width_height_descent(
612            text,
613            self.get_fontproperties(),
614            False)
615        return math.ceil(w)
616
617    def _get_wrapped_text(self):
618        """
619        Return a copy of the text with new lines added, so that
620        the text is wrapped relative to the parent figure.
621        """
622        # Not fit to handle breaking up latex syntax correctly, so
623        # ignore latex for now.
624        if self.get_usetex():
625            return self.get_text()
626
627        # Build the line incrementally, for a more accurate measure of length
628        line_width = self._get_wrap_line_width()
629        wrapped_lines = []
630
631        # New lines in the user's text force a split
632        unwrapped_lines = self.get_text().split('\n')
633
634        # Now wrap each individual unwrapped line
635        for unwrapped_line in unwrapped_lines:
636
637            sub_words = unwrapped_line.split(' ')
638            # Remove items from sub_words as we go, so stop when empty
639            while len(sub_words) > 0:
640                if len(sub_words) == 1:
641                    # Only one word, so just add it to the end
642                    wrapped_lines.append(sub_words.pop(0))
643                    continue
644
645                for i in range(2, len(sub_words) + 1):
646                    # Get width of all words up to and including here
647                    line = ' '.join(sub_words[:i])
648                    current_width = self._get_rendered_text_width(line)
649
650                    # If all these words are too wide, append all not including
651                    # last word
652                    if current_width > line_width:
653                        wrapped_lines.append(' '.join(sub_words[:i - 1]))
654                        sub_words = sub_words[i - 1:]
655                        break
656
657                    # Otherwise if all words fit in the width, append them all
658                    elif i == len(sub_words):
659                        wrapped_lines.append(' '.join(sub_words[:i]))
660                        sub_words = []
661                        break
662
663        return '\n'.join(wrapped_lines)
664
665    @artist.allow_rasterization
666    def draw(self, renderer):
667        # docstring inherited
668
669        if renderer is not None:
670            self._renderer = renderer
671        if not self.get_visible():
672            return
673        if self.get_text() == '':
674            return
675
676        renderer.open_group('text', self.get_gid())
677
678        with _wrap_text(self) as textobj:
679            bbox, info, descent = textobj._get_layout(renderer)
680            trans = textobj.get_transform()
681
682            # don't use textobj.get_position here, which refers to text
683            # position in Text:
684            posx = float(textobj.convert_xunits(textobj._x))
685            posy = float(textobj.convert_yunits(textobj._y))
686            posx, posy = trans.transform((posx, posy))
687            if not np.isfinite(posx) or not np.isfinite(posy):
688                _log.warning("posx and posy should be finite values")
689                return
690            canvasw, canvash = renderer.get_canvas_width_height()
691
692            # Update the location and size of the bbox
693            # (`.patches.FancyBboxPatch`), and draw it.
694            if textobj._bbox_patch:
695                self.update_bbox_position_size(renderer)
696                self._bbox_patch.draw(renderer)
697
698            gc = renderer.new_gc()
699            gc.set_foreground(textobj.get_color())
700            gc.set_alpha(textobj.get_alpha())
701            gc.set_url(textobj._url)
702            textobj._set_gc_clip(gc)
703
704            angle = textobj.get_rotation()
705
706            for line, wh, x, y in info:
707
708                mtext = textobj if len(info) == 1 else None
709                x = x + posx
710                y = y + posy
711                if renderer.flipy():
712                    y = canvash - y
713                clean_line, ismath = textobj._preprocess_math(line)
714
715                if textobj.get_path_effects():
716                    from matplotlib.patheffects import PathEffectRenderer
717                    textrenderer = PathEffectRenderer(
718                        textobj.get_path_effects(), renderer)
719                else:
720                    textrenderer = renderer
721
722                if textobj.get_usetex():
723                    textrenderer.draw_tex(gc, x, y, clean_line,
724                                          textobj._fontproperties, angle,
725                                          mtext=mtext)
726                else:
727                    textrenderer.draw_text(gc, x, y, clean_line,
728                                           textobj._fontproperties, angle,
729                                           ismath=ismath, mtext=mtext)
730
731        gc.restore()
732        renderer.close_group('text')
733        self.stale = False
734
735    def get_color(self):
736        """Return the color of the text."""
737        return self._color
738
739    def get_fontproperties(self):
740        """Return the `.font_manager.FontProperties`."""
741        return self._fontproperties
742
743    def get_fontfamily(self):
744        """
745        Return the list of font families used for font lookup.
746
747        See Also
748        --------
749        .font_manager.FontProperties.get_family
750        """
751        return self._fontproperties.get_family()
752
753    def get_fontname(self):
754        """
755        Return the font name as a string.
756
757        See Also
758        --------
759        .font_manager.FontProperties.get_name
760        """
761        return self._fontproperties.get_name()
762
763    def get_fontstyle(self):
764        """
765        Return the font style as a string.
766
767        See Also
768        --------
769        .font_manager.FontProperties.get_style
770        """
771        return self._fontproperties.get_style()
772
773    def get_fontsize(self):
774        """
775        Return the font size as an integer.
776
777        See Also
778        --------
779        .font_manager.FontProperties.get_size_in_points
780        """
781        return self._fontproperties.get_size_in_points()
782
783    def get_fontvariant(self):
784        """
785        Return the font variant as a string.
786
787        See Also
788        --------
789        .font_manager.FontProperties.get_variant
790        """
791        return self._fontproperties.get_variant()
792
793    def get_fontweight(self):
794        """
795        Return the font weight as a string or a number.
796
797        See Also
798        --------
799        .font_manager.FontProperties.get_weight
800        """
801        return self._fontproperties.get_weight()
802
803    def get_stretch(self):
804        """
805        Return the font stretch as a string or a number.
806
807        See Also
808        --------
809        .font_manager.FontProperties.get_stretch
810        """
811        return self._fontproperties.get_stretch()
812
813    def get_horizontalalignment(self):
814        """
815        Return the horizontal alignment as a string.  Will be one of
816        'left', 'center' or 'right'.
817        """
818        return self._horizontalalignment
819
820    def get_unitless_position(self):
821        """Return the (x, y) unitless position of the text."""
822        # This will get the position with all unit information stripped away.
823        # This is here for convenience since it is done in several locations.
824        x = float(self.convert_xunits(self._x))
825        y = float(self.convert_yunits(self._y))
826        return x, y
827
828    def get_position(self):
829        """Return the (x, y) position of the text."""
830        # This should return the same data (possible unitized) as was
831        # specified with 'set_x' and 'set_y'.
832        return self._x, self._y
833
834    def get_prop_tup(self, renderer=None):
835        """
836        Return a hashable tuple of properties.
837
838        Not intended to be human readable, but useful for backends who
839        want to cache derived information about text (e.g., layouts) and
840        need to know if the text has changed.
841        """
842        x, y = self.get_unitless_position()
843        renderer = renderer or self._renderer
844        return (x, y, self.get_text(), self._color,
845                self._verticalalignment, self._horizontalalignment,
846                hash(self._fontproperties),
847                self._rotation, self._rotation_mode,
848                self._transform_rotates_text,
849                self.figure.dpi, weakref.ref(renderer),
850                self._linespacing
851                )
852
853    def get_text(self):
854        """Return the text string."""
855        return self._text
856
857    def get_verticalalignment(self):
858        """
859        Return the vertical alignment as a string.  Will be one of
860        'top', 'center', 'bottom', 'baseline' or 'center_baseline'.
861        """
862        return self._verticalalignment
863
864    def get_window_extent(self, renderer=None, dpi=None):
865        """
866        Return the `.Bbox` bounding the text, in display units.
867
868        In addition to being used internally, this is useful for specifying
869        clickable regions in a png file on a web page.
870
871        Parameters
872        ----------
873        renderer : Renderer, optional
874            A renderer is needed to compute the bounding box.  If the artist
875            has already been drawn, the renderer is cached; thus, it is only
876            necessary to pass this argument when calling `get_window_extent`
877            before the first `draw`.  In practice, it is usually easier to
878            trigger a draw first (e.g. by saving the figure).
879
880        dpi : float, optional
881            The dpi value for computing the bbox, defaults to
882            ``self.figure.dpi`` (*not* the renderer dpi); should be set e.g. if
883            to match regions with a figure saved with a custom dpi value.
884        """
885        #return _unit_box
886        if not self.get_visible():
887            return Bbox.unit()
888        if dpi is None:
889            dpi = self.figure.dpi
890        if self.get_text() == '':
891            with cbook._setattr_cm(self.figure, dpi=dpi):
892                tx, ty = self._get_xy_display()
893                return Bbox.from_bounds(tx, ty, 0, 0)
894
895        if renderer is not None:
896            self._renderer = renderer
897        if self._renderer is None:
898            self._renderer = self.figure._cachedRenderer
899        if self._renderer is None:
900            raise RuntimeError('Cannot get window extent w/o renderer')
901
902        with cbook._setattr_cm(self.figure, dpi=dpi):
903            bbox, info, descent = self._get_layout(self._renderer)
904            x, y = self.get_unitless_position()
905            x, y = self.get_transform().transform((x, y))
906            bbox = bbox.translated(x, y)
907            return bbox
908
909    def set_backgroundcolor(self, color):
910        """
911        Set the background color of the text by updating the bbox.
912
913        Parameters
914        ----------
915        color : color
916
917        See Also
918        --------
919        .set_bbox : To change the position of the bounding box
920        """
921        if self._bbox_patch is None:
922            self.set_bbox(dict(facecolor=color, edgecolor=color))
923        else:
924            self._bbox_patch.update(dict(facecolor=color))
925
926        self._update_clip_properties()
927        self.stale = True
928
929    def set_color(self, color):
930        """
931        Set the foreground color of the text
932
933        Parameters
934        ----------
935        color : color
936        """
937        # "auto" is only supported by axisartist, but we can just let it error
938        # out at draw time for simplicity.
939        if not cbook._str_equal(color, "auto"):
940            mpl.colors._check_color_like(color=color)
941        # Make sure it is hashable, or get_prop_tup will fail.
942        try:
943            hash(color)
944        except TypeError:
945            color = tuple(color)
946        self._color = color
947        self.stale = True
948
949    def set_horizontalalignment(self, align):
950        """
951        Set the horizontal alignment to one of
952
953        Parameters
954        ----------
955        align : {'center', 'right', 'left'}
956        """
957        _api.check_in_list(['center', 'right', 'left'], align=align)
958        self._horizontalalignment = align
959        self.stale = True
960
961    def set_multialignment(self, align):
962        """
963        Set the text alignment for multiline texts.
964
965        The layout of the bounding box of all the lines is determined by the
966        horizontalalignment and verticalalignment properties. This property
967        controls the alignment of the text lines within that box.
968
969        Parameters
970        ----------
971        align : {'left', 'right', 'center'}
972        """
973        _api.check_in_list(['center', 'right', 'left'], align=align)
974        self._multialignment = align
975        self.stale = True
976
977    def set_linespacing(self, spacing):
978        """
979        Set the line spacing as a multiple of the font size.
980
981        The default line spacing is 1.2.
982
983        Parameters
984        ----------
985        spacing : float (multiple of font size)
986        """
987        self._linespacing = spacing
988        self.stale = True
989
990    def set_fontfamily(self, fontname):
991        """
992        Set the font family.  May be either a single string, or a list of
993        strings in decreasing priority.  Each string may be either a real font
994        name or a generic font class name.  If the latter, the specific font
995        names will be looked up in the corresponding rcParams.
996
997        If a `Text` instance is constructed with ``fontfamily=None``, then the
998        font is set to :rc:`font.family`, and the
999        same is done when `set_fontfamily()` is called on an existing
1000        `Text` instance.
1001
1002        Parameters
1003        ----------
1004        fontname : {FONTNAME, 'serif', 'sans-serif', 'cursive', 'fantasy', \
1005'monospace'}
1006
1007        See Also
1008        --------
1009        .font_manager.FontProperties.set_family
1010        """
1011        self._fontproperties.set_family(fontname)
1012        self.stale = True
1013
1014    def set_fontvariant(self, variant):
1015        """
1016        Set the font variant.
1017
1018        Parameters
1019        ----------
1020        variant : {'normal', 'small-caps'}
1021
1022        See Also
1023        --------
1024        .font_manager.FontProperties.set_variant
1025        """
1026        self._fontproperties.set_variant(variant)
1027        self.stale = True
1028
1029    def set_fontstyle(self, fontstyle):
1030        """
1031        Set the font style.
1032
1033        Parameters
1034        ----------
1035        fontstyle : {'normal', 'italic', 'oblique'}
1036
1037        See Also
1038        --------
1039        .font_manager.FontProperties.set_style
1040        """
1041        self._fontproperties.set_style(fontstyle)
1042        self.stale = True
1043
1044    def set_fontsize(self, fontsize):
1045        """
1046        Set the font size.
1047
1048        Parameters
1049        ----------
1050        fontsize : float or {'xx-small', 'x-small', 'small', 'medium', \
1051'large', 'x-large', 'xx-large'}
1052            If float, the fontsize in points. The string values denote sizes
1053            relative to the default font size.
1054
1055        See Also
1056        --------
1057        .font_manager.FontProperties.set_size
1058        """
1059        self._fontproperties.set_size(fontsize)
1060        self.stale = True
1061
1062    def get_math_fontfamily(self):
1063        """
1064        Return the font family name for math text rendered by Matplotlib.
1065
1066        The default value is :rc:`mathtext.fontset`.
1067
1068        See Also
1069        --------
1070        set_math_fontfamily
1071        """
1072        return self._fontproperties.get_math_fontfamily()
1073
1074    def set_math_fontfamily(self, fontfamily):
1075        """
1076        Set the font family for math text rendered by Matplotlib.
1077
1078        This does only affect Matplotlib's own math renderer. It has no effect
1079        when rendering with TeX (``usetex=True``).
1080
1081        Parameters
1082        ----------
1083        fontfamily : str
1084            The name of the font family.
1085
1086            Available font families are defined in the
1087            :ref:`matplotlibrc.template file
1088            <customizing-with-matplotlibrc-files>`.
1089
1090        See Also
1091        --------
1092        get_math_fontfamily
1093        """
1094        self._fontproperties.set_math_fontfamily(fontfamily)
1095
1096    def set_fontweight(self, weight):
1097        """
1098        Set the font weight.
1099
1100        Parameters
1101        ----------
1102        weight : {a numeric value in range 0-1000, 'ultralight', 'light', \
1103'normal', 'regular', 'book', 'medium', 'roman', 'semibold', 'demibold', \
1104'demi', 'bold', 'heavy', 'extra bold', 'black'}
1105
1106        See Also
1107        --------
1108        .font_manager.FontProperties.set_weight
1109        """
1110        self._fontproperties.set_weight(weight)
1111        self.stale = True
1112
1113    def set_fontstretch(self, stretch):
1114        """
1115        Set the font stretch (horizontal condensation or expansion).
1116
1117        Parameters
1118        ----------
1119        stretch : {a numeric value in range 0-1000, 'ultra-condensed', \
1120'extra-condensed', 'condensed', 'semi-condensed', 'normal', 'semi-expanded', \
1121'expanded', 'extra-expanded', 'ultra-expanded'}
1122
1123        See Also
1124        --------
1125        .font_manager.FontProperties.set_stretch
1126        """
1127        self._fontproperties.set_stretch(stretch)
1128        self.stale = True
1129
1130    def set_position(self, xy):
1131        """
1132        Set the (*x*, *y*) position of the text.
1133
1134        Parameters
1135        ----------
1136        xy : (float, float)
1137        """
1138        self.set_x(xy[0])
1139        self.set_y(xy[1])
1140
1141    def set_x(self, x):
1142        """
1143        Set the *x* position of the text.
1144
1145        Parameters
1146        ----------
1147        x : float
1148        """
1149        self._x = x
1150        self.stale = True
1151
1152    def set_y(self, y):
1153        """
1154        Set the *y* position of the text.
1155
1156        Parameters
1157        ----------
1158        y : float
1159        """
1160        self._y = y
1161        self.stale = True
1162
1163    def set_rotation(self, s):
1164        """
1165        Set the rotation of the text.
1166
1167        Parameters
1168        ----------
1169        s : float or {'vertical', 'horizontal'}
1170            The rotation angle in degrees in mathematically positive direction
1171            (counterclockwise). 'horizontal' equals 0, 'vertical' equals 90.
1172        """
1173        self._rotation = s
1174        self.stale = True
1175
1176    def set_transform_rotates_text(self, t):
1177        """
1178        Whether rotations of the transform affect the text direction.
1179
1180        Parameters
1181        ----------
1182        t : bool
1183        """
1184        self._transform_rotates_text = t
1185        self.stale = True
1186
1187    def set_verticalalignment(self, align):
1188        """
1189        Set the vertical alignment.
1190
1191        Parameters
1192        ----------
1193        align : {'center', 'top', 'bottom', 'baseline', 'center_baseline'}
1194        """
1195        _api.check_in_list(
1196            ['top', 'bottom', 'center', 'baseline', 'center_baseline'],
1197            align=align)
1198        self._verticalalignment = align
1199        self.stale = True
1200
1201    def set_text(self, s):
1202        r"""
1203        Set the text string *s*.
1204
1205        It may contain newlines (``\n``) or math in LaTeX syntax.
1206
1207        Parameters
1208        ----------
1209        s : object
1210            Any object gets converted to its `str` representation, except for
1211            ``None`` which is converted to an empty string.
1212        """
1213        if s is None:
1214            s = ''
1215        if s != self._text:
1216            self._text = str(s)
1217            self.stale = True
1218
1219    def _preprocess_math(self, s):
1220        """
1221        Return the string *s* after mathtext preprocessing, and the kind of
1222        mathtext support needed.
1223
1224        - If *self* is configured to use TeX, return *s* unchanged except that
1225          a single space gets escaped, and the flag "TeX".
1226        - Otherwise, if *s* is mathtext (has an even number of unescaped dollar
1227          signs), return *s* and the flag True.
1228        - Otherwise, return *s* with dollar signs unescaped, and the flag
1229          False.
1230        """
1231        if self.get_usetex():
1232            if s == " ":
1233                s = r"\ "
1234            return s, "TeX"
1235        elif cbook.is_math_text(s):
1236            return s, True
1237        else:
1238            return s.replace(r"\$", "$"), False
1239
1240    def set_fontproperties(self, fp):
1241        """
1242        Set the font properties that control the text.
1243
1244        Parameters
1245        ----------
1246        fp : `.font_manager.FontProperties` or `str` or `pathlib.Path`
1247            If a `str`, it is interpreted as a fontconfig pattern parsed by
1248            `.FontProperties`.  If a `pathlib.Path`, it is interpreted as the
1249            absolute path to a font file.
1250        """
1251        self._fontproperties = FontProperties._from_any(fp).copy()
1252        self.stale = True
1253
1254    def set_usetex(self, usetex):
1255        """
1256        Parameters
1257        ----------
1258        usetex : bool or None
1259            Whether to render using TeX, ``None`` means to use
1260            :rc:`text.usetex`.
1261        """
1262        if usetex is None:
1263            self._usetex = mpl.rcParams['text.usetex']
1264        else:
1265            self._usetex = bool(usetex)
1266        self.stale = True
1267
1268    def get_usetex(self):
1269        """Return whether this `Text` object uses TeX for rendering."""
1270        return self._usetex
1271
1272    def set_fontname(self, fontname):
1273        """
1274        Alias for `set_family`.
1275
1276        One-way alias only: the getter differs.
1277
1278        Parameters
1279        ----------
1280        fontname : {FONTNAME, 'serif', 'sans-serif', 'cursive', 'fantasy', \
1281'monospace'}
1282
1283        See Also
1284        --------
1285        .font_manager.FontProperties.set_family
1286
1287        """
1288        return self.set_family(fontname)
1289
1290
1291docstring.interpd.update(Text_kwdoc=artist.kwdoc(Text))
1292docstring.dedent_interpd(Text.__init__)
1293
1294
1295class OffsetFrom:
1296    """Callable helper class for working with `Annotation`."""
1297
1298    def __init__(self, artist, ref_coord, unit="points"):
1299        """
1300        Parameters
1301        ----------
1302        artist : `.Artist` or `.BboxBase` or `.Transform`
1303            The object to compute the offset from.
1304
1305        ref_coord : (float, float)
1306            If *artist* is an `.Artist` or `.BboxBase`, this values is
1307            the location to of the offset origin in fractions of the
1308            *artist* bounding box.
1309
1310            If *artist* is a transform, the offset origin is the
1311            transform applied to this value.
1312
1313        unit : {'points, 'pixels'}, default: 'points'
1314            The screen units to use (pixels or points) for the offset input.
1315        """
1316        self._artist = artist
1317        self._ref_coord = ref_coord
1318        self.set_unit(unit)
1319
1320    def set_unit(self, unit):
1321        """
1322        Set the unit for input to the transform used by ``__call__``.
1323
1324        Parameters
1325        ----------
1326        unit : {'points', 'pixels'}
1327        """
1328        _api.check_in_list(["points", "pixels"], unit=unit)
1329        self._unit = unit
1330
1331    def get_unit(self):
1332        """Return the unit for input to the transform used by ``__call__``."""
1333        return self._unit
1334
1335    def _get_scale(self, renderer):
1336        unit = self.get_unit()
1337        if unit == "pixels":
1338            return 1.
1339        else:
1340            return renderer.points_to_pixels(1.)
1341
1342    def __call__(self, renderer):
1343        """
1344        Return the offset transform.
1345
1346        Parameters
1347        ----------
1348        renderer : `RendererBase`
1349            The renderer to use to compute the offset
1350
1351        Returns
1352        -------
1353        `Transform`
1354            Maps (x, y) in pixel or point units to screen units
1355            relative to the given artist.
1356        """
1357        if isinstance(self._artist, Artist):
1358            bbox = self._artist.get_window_extent(renderer)
1359            xf, yf = self._ref_coord
1360            x = bbox.x0 + bbox.width * xf
1361            y = bbox.y0 + bbox.height * yf
1362        elif isinstance(self._artist, BboxBase):
1363            bbox = self._artist
1364            xf, yf = self._ref_coord
1365            x = bbox.x0 + bbox.width * xf
1366            y = bbox.y0 + bbox.height * yf
1367        elif isinstance(self._artist, Transform):
1368            x, y = self._artist.transform(self._ref_coord)
1369        else:
1370            raise RuntimeError("unknown type")
1371
1372        sc = self._get_scale(renderer)
1373        tr = Affine2D().scale(sc).translate(x, y)
1374
1375        return tr
1376
1377
1378class _AnnotationBase:
1379    def __init__(self,
1380                 xy,
1381                 xycoords='data',
1382                 annotation_clip=None):
1383
1384        self.xy = xy
1385        self.xycoords = xycoords
1386        self.set_annotation_clip(annotation_clip)
1387
1388        self._draggable = None
1389
1390    def _get_xy(self, renderer, x, y, s):
1391        if isinstance(s, tuple):
1392            s1, s2 = s
1393        else:
1394            s1, s2 = s, s
1395        if s1 == 'data':
1396            x = float(self.convert_xunits(x))
1397        if s2 == 'data':
1398            y = float(self.convert_yunits(y))
1399        return self._get_xy_transform(renderer, s).transform((x, y))
1400
1401    def _get_xy_transform(self, renderer, s):
1402
1403        if isinstance(s, tuple):
1404            s1, s2 = s
1405            from matplotlib.transforms import blended_transform_factory
1406            tr1 = self._get_xy_transform(renderer, s1)
1407            tr2 = self._get_xy_transform(renderer, s2)
1408            tr = blended_transform_factory(tr1, tr2)
1409            return tr
1410        elif callable(s):
1411            tr = s(renderer)
1412            if isinstance(tr, BboxBase):
1413                return BboxTransformTo(tr)
1414            elif isinstance(tr, Transform):
1415                return tr
1416            else:
1417                raise RuntimeError("unknown return type ...")
1418        elif isinstance(s, Artist):
1419            bbox = s.get_window_extent(renderer)
1420            return BboxTransformTo(bbox)
1421        elif isinstance(s, BboxBase):
1422            return BboxTransformTo(s)
1423        elif isinstance(s, Transform):
1424            return s
1425        elif not isinstance(s, str):
1426            raise RuntimeError("unknown coordinate type : %s" % s)
1427
1428        if s == 'data':
1429            return self.axes.transData
1430        elif s == 'polar':
1431            from matplotlib.projections import PolarAxes
1432            tr = PolarAxes.PolarTransform()
1433            trans = tr + self.axes.transData
1434            return trans
1435
1436        s_ = s.split()
1437        if len(s_) != 2:
1438            raise ValueError("%s is not a recognized coordinate" % s)
1439
1440        bbox0, xy0 = None, None
1441
1442        bbox_name, unit = s_
1443        # if unit is offset-like
1444        if bbox_name == "figure":
1445            bbox0 = self.figure.figbbox
1446        elif bbox_name == "subfigure":
1447            bbox0 = self.figure.bbox
1448        elif bbox_name == "axes":
1449            bbox0 = self.axes.bbox
1450        # elif bbox_name == "bbox":
1451        #     if bbox is None:
1452        #         raise RuntimeError("bbox is specified as a coordinate but "
1453        #                            "never set")
1454        #     bbox0 = self._get_bbox(renderer, bbox)
1455
1456        if bbox0 is not None:
1457            xy0 = bbox0.p0
1458        elif bbox_name == "offset":
1459            xy0 = self._get_ref_xy(renderer)
1460
1461        if xy0 is not None:
1462            # reference x, y in display coordinate
1463            ref_x, ref_y = xy0
1464            if unit == "points":
1465                # dots per points
1466                dpp = self.figure.get_dpi() / 72.
1467                tr = Affine2D().scale(dpp)
1468            elif unit == "pixels":
1469                tr = Affine2D()
1470            elif unit == "fontsize":
1471                fontsize = self.get_size()
1472                dpp = fontsize * self.figure.get_dpi() / 72.
1473                tr = Affine2D().scale(dpp)
1474            elif unit == "fraction":
1475                w, h = bbox0.size
1476                tr = Affine2D().scale(w, h)
1477            else:
1478                raise ValueError("%s is not a recognized coordinate" % s)
1479
1480            return tr.translate(ref_x, ref_y)
1481
1482        else:
1483            raise ValueError("%s is not a recognized coordinate" % s)
1484
1485    def _get_ref_xy(self, renderer):
1486        """
1487        Return x, y (in display coordinates) that is to be used for a reference
1488        of any offset coordinate.
1489        """
1490        return self._get_xy(renderer, *self.xy, self.xycoords)
1491
1492    # def _get_bbox(self, renderer):
1493    #     if hasattr(bbox, "bounds"):
1494    #         return bbox
1495    #     elif hasattr(bbox, "get_window_extent"):
1496    #         bbox = bbox.get_window_extent()
1497    #         return bbox
1498    #     else:
1499    #         raise ValueError("A bbox instance is expected but got %s" %
1500    #                          str(bbox))
1501
1502    def set_annotation_clip(self, b):
1503        """
1504        Set the annotation's clipping behavior.
1505
1506        Parameters
1507        ----------
1508        b : bool or None
1509            - True: the annotation will only be drawn when ``self.xy`` is
1510              inside the axes.
1511            - False: the annotation will always be drawn regardless of its
1512              position.
1513            - None: the ``self.xy`` will be checked only if *xycoords* is
1514              "data".
1515        """
1516        self._annotation_clip = b
1517
1518    def get_annotation_clip(self):
1519        """
1520        Return the annotation's clipping behavior.
1521
1522        See `set_annotation_clip` for the meaning of return values.
1523        """
1524        return self._annotation_clip
1525
1526    def _get_position_xy(self, renderer):
1527        """Return the pixel position of the annotated point."""
1528        x, y = self.xy
1529        return self._get_xy(renderer, x, y, self.xycoords)
1530
1531    def _check_xy(self, renderer):
1532        """Check whether the annotation at *xy_pixel* should be drawn."""
1533        b = self.get_annotation_clip()
1534        if b or (b is None and self.xycoords == "data"):
1535            # check if self.xy is inside the axes.
1536            xy_pixel = self._get_position_xy(renderer)
1537            return self.axes.contains_point(xy_pixel)
1538        return True
1539
1540    def draggable(self, state=None, use_blit=False):
1541        """
1542        Set whether the annotation is draggable with the mouse.
1543
1544        Parameters
1545        ----------
1546        state : bool or None
1547            - True or False: set the draggability.
1548            - None: toggle the draggability.
1549
1550        Returns
1551        -------
1552        DraggableAnnotation or None
1553            If the annotation is draggable, the corresponding
1554            `.DraggableAnnotation` helper is returned.
1555        """
1556        from matplotlib.offsetbox import DraggableAnnotation
1557        is_draggable = self._draggable is not None
1558
1559        # if state is None we'll toggle
1560        if state is None:
1561            state = not is_draggable
1562
1563        if state:
1564            if self._draggable is None:
1565                self._draggable = DraggableAnnotation(self, use_blit)
1566        else:
1567            if self._draggable is not None:
1568                self._draggable.disconnect()
1569            self._draggable = None
1570
1571        return self._draggable
1572
1573
1574class Annotation(Text, _AnnotationBase):
1575    """
1576    An `.Annotation` is a `.Text` that can refer to a specific position *xy*.
1577    Optionally an arrow pointing from the text to *xy* can be drawn.
1578
1579    Attributes
1580    ----------
1581    xy
1582        The annotated position.
1583    xycoords
1584        The coordinate system for *xy*.
1585    arrow_patch
1586        A `.FancyArrowPatch` to point from *xytext* to *xy*.
1587    """
1588
1589    def __str__(self):
1590        return "Annotation(%g, %g, %r)" % (self.xy[0], self.xy[1], self._text)
1591
1592    def __init__(self, text, xy,
1593                 xytext=None,
1594                 xycoords='data',
1595                 textcoords=None,
1596                 arrowprops=None,
1597                 annotation_clip=None,
1598                 **kwargs):
1599        """
1600        Annotate the point *xy* with text *text*.
1601
1602        In the simplest form, the text is placed at *xy*.
1603
1604        Optionally, the text can be displayed in another position *xytext*.
1605        An arrow pointing from the text to the annotated point *xy* can then
1606        be added by defining *arrowprops*.
1607
1608        Parameters
1609        ----------
1610        text : str
1611            The text of the annotation.
1612
1613        xy : (float, float)
1614            The point *(x, y)* to annotate. The coordinate system is determined
1615            by *xycoords*.
1616
1617        xytext : (float, float), default: *xy*
1618            The position *(x, y)* to place the text at. The coordinate system
1619            is determined by *textcoords*.
1620
1621        xycoords : str or `.Artist` or `.Transform` or callable or \
1622(float, float), default: 'data'
1623
1624            The coordinate system that *xy* is given in. The following types
1625            of values are supported:
1626
1627            - One of the following strings:
1628
1629              ==================== ============================================
1630              Value                Description
1631              ==================== ============================================
1632              'figure points'      Points from the lower left of the figure
1633              'figure pixels'      Pixels from the lower left of the figure
1634              'figure fraction'    Fraction of figure from lower left
1635              'subfigure points'   Points from the lower left of the subfigure
1636              'subfigure pixels'   Pixels from the lower left of the subfigure
1637              'subfigure fraction' Fraction of subfigure from lower left
1638              'axes points'        Points from lower left corner of axes
1639              'axes pixels'        Pixels from lower left corner of axes
1640              'axes fraction'      Fraction of axes from lower left
1641              'data'               Use the coordinate system of the object
1642                                   being annotated (default)
1643              'polar'              *(theta, r)* if not native 'data'
1644                                   coordinates
1645              ==================== ============================================
1646
1647              Note that 'subfigure pixels' and 'figure pixels' are the same
1648              for the parent figure, so users who want code that is usable in
1649              a subfigure can use 'subfigure pixels'.
1650
1651            - An `.Artist`: *xy* is interpreted as a fraction of the artist's
1652              `~matplotlib.transforms.Bbox`. E.g. *(0, 0)* would be the lower
1653              left corner of the bounding box and *(0.5, 1)* would be the
1654              center top of the bounding box.
1655
1656            - A `.Transform` to transform *xy* to screen coordinates.
1657
1658            - A function with one of the following signatures::
1659
1660                def transform(renderer) -> Bbox
1661                def transform(renderer) -> Transform
1662
1663              where *renderer* is a `.RendererBase` subclass.
1664
1665              The result of the function is interpreted like the `.Artist` and
1666              `.Transform` cases above.
1667
1668            - A tuple *(xcoords, ycoords)* specifying separate coordinate
1669              systems for *x* and *y*. *xcoords* and *ycoords* must each be
1670              of one of the above described types.
1671
1672            See :ref:`plotting-guide-annotation` for more details.
1673
1674        textcoords : str or `.Artist` or `.Transform` or callable or \
1675(float, float), default: value of *xycoords*
1676            The coordinate system that *xytext* is given in.
1677
1678            All *xycoords* values are valid as well as the following
1679            strings:
1680
1681            =================   =========================================
1682            Value               Description
1683            =================   =========================================
1684            'offset points'     Offset (in points) from the *xy* value
1685            'offset pixels'     Offset (in pixels) from the *xy* value
1686            =================   =========================================
1687
1688        arrowprops : dict, optional
1689            The properties used to draw a `.FancyArrowPatch` arrow between the
1690            positions *xy* and *xytext*. Note that the edge of the arrow
1691            pointing to *xytext* will be centered on the text itself and may
1692            not point directly to the coordinates given in *xytext*.
1693
1694            If *arrowprops* does not contain the key 'arrowstyle' the
1695            allowed keys are:
1696
1697            ==========   ======================================================
1698            Key          Description
1699            ==========   ======================================================
1700            width        The width of the arrow in points
1701            headwidth    The width of the base of the arrow head in points
1702            headlength   The length of the arrow head in points
1703            shrink       Fraction of total length to shrink from both ends
1704            ?            Any key to :class:`matplotlib.patches.FancyArrowPatch`
1705            ==========   ======================================================
1706
1707            If *arrowprops* contains the key 'arrowstyle' the
1708            above keys are forbidden.  The allowed values of
1709            ``'arrowstyle'`` are:
1710
1711            ============   =============================================
1712            Name           Attrs
1713            ============   =============================================
1714            ``'-'``        None
1715            ``'->'``       head_length=0.4,head_width=0.2
1716            ``'-['``       widthB=1.0,lengthB=0.2,angleB=None
1717            ``'|-|'``      widthA=1.0,widthB=1.0
1718            ``'-|>'``      head_length=0.4,head_width=0.2
1719            ``'<-'``       head_length=0.4,head_width=0.2
1720            ``'<->'``      head_length=0.4,head_width=0.2
1721            ``'<|-'``      head_length=0.4,head_width=0.2
1722            ``'<|-|>'``    head_length=0.4,head_width=0.2
1723            ``'fancy'``    head_length=0.4,head_width=0.4,tail_width=0.4
1724            ``'simple'``   head_length=0.5,head_width=0.5,tail_width=0.2
1725            ``'wedge'``    tail_width=0.3,shrink_factor=0.5
1726            ============   =============================================
1727
1728            Valid keys for `~matplotlib.patches.FancyArrowPatch` are:
1729
1730            ===============  ==================================================
1731            Key              Description
1732            ===============  ==================================================
1733            arrowstyle       the arrow style
1734            connectionstyle  the connection style
1735            relpos           default is (0.5, 0.5)
1736            patchA           default is bounding box of the text
1737            patchB           default is None
1738            shrinkA          default is 2 points
1739            shrinkB          default is 2 points
1740            mutation_scale   default is text size (in points)
1741            mutation_aspect  default is 1.
1742            ?                any key for :class:`matplotlib.patches.PathPatch`
1743            ===============  ==================================================
1744
1745            Defaults to None, i.e. no arrow is drawn.
1746
1747        annotation_clip : bool or None, default: None
1748            Whether to draw the annotation when the annotation point *xy* is
1749            outside the axes area.
1750
1751            - If *True*, the annotation will only be drawn when *xy* is
1752              within the axes.
1753            - If *False*, the annotation will always be drawn.
1754            - If *None*, the annotation will only be drawn when *xy* is
1755              within the axes and *xycoords* is 'data'.
1756
1757        **kwargs
1758            Additional kwargs are passed to `~matplotlib.text.Text`.
1759
1760        Returns
1761        -------
1762        `.Annotation`
1763
1764        See Also
1765        --------
1766        :ref:`plotting-guide-annotation`
1767
1768        """
1769        _AnnotationBase.__init__(self,
1770                                 xy,
1771                                 xycoords=xycoords,
1772                                 annotation_clip=annotation_clip)
1773        # warn about wonky input data
1774        if (xytext is None and
1775                textcoords is not None and
1776                textcoords != xycoords):
1777            _api.warn_external("You have used the `textcoords` kwarg, but "
1778                               "not the `xytext` kwarg.  This can lead to "
1779                               "surprising results.")
1780
1781        # clean up textcoords and assign default
1782        if textcoords is None:
1783            textcoords = self.xycoords
1784        self._textcoords = textcoords
1785
1786        # cleanup xytext defaults
1787        if xytext is None:
1788            xytext = self.xy
1789        x, y = xytext
1790
1791        self.arrowprops = arrowprops
1792        if arrowprops is not None:
1793            arrowprops = arrowprops.copy()
1794            if "arrowstyle" in arrowprops:
1795                self._arrow_relpos = arrowprops.pop("relpos", (0.5, 0.5))
1796            else:
1797                # modified YAArrow API to be used with FancyArrowPatch
1798                for key in [
1799                        'width', 'headwidth', 'headlength', 'shrink', 'frac']:
1800                    arrowprops.pop(key, None)
1801            self.arrow_patch = FancyArrowPatch((0, 0), (1, 1), **arrowprops)
1802        else:
1803            self.arrow_patch = None
1804
1805        # Must come last, as some kwargs may be propagated to arrow_patch.
1806        Text.__init__(self, x, y, text, **kwargs)
1807
1808    def contains(self, event):
1809        inside, info = self._default_contains(event)
1810        if inside is not None:
1811            return inside, info
1812        contains, tinfo = Text.contains(self, event)
1813        if self.arrow_patch is not None:
1814            in_patch, _ = self.arrow_patch.contains(event)
1815            contains = contains or in_patch
1816        return contains, tinfo
1817
1818    @property
1819    def xycoords(self):
1820        return self._xycoords
1821
1822    @xycoords.setter
1823    def xycoords(self, xycoords):
1824        def is_offset(s):
1825            return isinstance(s, str) and s.startswith("offset")
1826
1827        if (isinstance(xycoords, tuple) and any(map(is_offset, xycoords))
1828                or is_offset(xycoords)):
1829            raise ValueError("xycoords cannot be an offset coordinate")
1830        self._xycoords = xycoords
1831
1832    @property
1833    def xyann(self):
1834        """
1835        The text position.
1836
1837        See also *xytext* in `.Annotation`.
1838        """
1839        return self.get_position()
1840
1841    @xyann.setter
1842    def xyann(self, xytext):
1843        self.set_position(xytext)
1844
1845    def get_anncoords(self):
1846        """
1847        Return the coordinate system to use for `.Annotation.xyann`.
1848
1849        See also *xycoords* in `.Annotation`.
1850        """
1851        return self._textcoords
1852
1853    def set_anncoords(self, coords):
1854        """
1855        Set the coordinate system to use for `.Annotation.xyann`.
1856
1857        See also *xycoords* in `.Annotation`.
1858        """
1859        self._textcoords = coords
1860
1861    anncoords = property(get_anncoords, set_anncoords, doc="""
1862        The coordinate system to use for `.Annotation.xyann`.""")
1863
1864    def set_figure(self, fig):
1865        # docstring inherited
1866        if self.arrow_patch is not None:
1867            self.arrow_patch.set_figure(fig)
1868        Artist.set_figure(self, fig)
1869
1870    def update_positions(self, renderer):
1871        """
1872        Update the pixel positions of the annotation text and the arrow patch.
1873        """
1874        x1, y1 = self._get_position_xy(renderer)  # Annotated position.
1875        # generate transformation,
1876        self.set_transform(self._get_xy_transform(renderer, self.anncoords))
1877
1878        if self.arrowprops is None:
1879            return
1880
1881        bbox = Text.get_window_extent(self, renderer)
1882
1883        d = self.arrowprops.copy()
1884        ms = d.pop("mutation_scale", self.get_size())
1885        self.arrow_patch.set_mutation_scale(ms)
1886
1887        if "arrowstyle" not in d:
1888            # Approximately simulate the YAArrow.
1889            # Pop its kwargs:
1890            shrink = d.pop('shrink', 0.0)
1891            width = d.pop('width', 4)
1892            headwidth = d.pop('headwidth', 12)
1893            # Ignore frac--it is useless.
1894            frac = d.pop('frac', None)
1895            if frac is not None:
1896                _api.warn_external(
1897                    "'frac' option in 'arrowprops' is no longer supported;"
1898                    " use 'headlength' to set the head length in points.")
1899            headlength = d.pop('headlength', 12)
1900
1901            # NB: ms is in pts
1902            stylekw = dict(head_length=headlength / ms,
1903                           head_width=headwidth / ms,
1904                           tail_width=width / ms)
1905
1906            self.arrow_patch.set_arrowstyle('simple', **stylekw)
1907
1908            # using YAArrow style:
1909            # pick the corner of the text bbox closest to annotated point.
1910            xpos = [(bbox.x0, 0), ((bbox.x0 + bbox.x1) / 2, 0.5), (bbox.x1, 1)]
1911            ypos = [(bbox.y0, 0), ((bbox.y0 + bbox.y1) / 2, 0.5), (bbox.y1, 1)]
1912            x, relposx = min(xpos, key=lambda v: abs(v[0] - x1))
1913            y, relposy = min(ypos, key=lambda v: abs(v[0] - y1))
1914            self._arrow_relpos = (relposx, relposy)
1915            r = np.hypot(y - y1, x - x1)
1916            shrink_pts = shrink * r / renderer.points_to_pixels(1)
1917            self.arrow_patch.shrinkA = self.arrow_patch.shrinkB = shrink_pts
1918
1919        # adjust the starting point of the arrow relative to the textbox.
1920        # TODO : Rotation needs to be accounted.
1921        relposx, relposy = self._arrow_relpos
1922        x0 = bbox.x0 + bbox.width * relposx
1923        y0 = bbox.y0 + bbox.height * relposy
1924
1925        # The arrow will be drawn from (x0, y0) to (x1, y1). It will be first
1926        # clipped by patchA and patchB.  Then it will be shrunk by shrinkA and
1927        # shrinkB (in points).  If patch A is not set, self.bbox_patch is used.
1928        self.arrow_patch.set_positions((x0, y0), (x1, y1))
1929
1930        if "patchA" in d:
1931            self.arrow_patch.set_patchA(d.pop("patchA"))
1932        else:
1933            if self._bbox_patch:
1934                self.arrow_patch.set_patchA(self._bbox_patch)
1935            else:
1936                if self.get_text() == "":
1937                    self.arrow_patch.set_patchA(None)
1938                    return
1939                pad = renderer.points_to_pixels(4)
1940                r = Rectangle(xy=(bbox.x0 - pad / 2, bbox.y0 - pad / 2),
1941                              width=bbox.width + pad, height=bbox.height + pad,
1942                              transform=IdentityTransform(), clip_on=False)
1943                self.arrow_patch.set_patchA(r)
1944
1945    @artist.allow_rasterization
1946    def draw(self, renderer):
1947        # docstring inherited
1948        if renderer is not None:
1949            self._renderer = renderer
1950        if not self.get_visible() or not self._check_xy(renderer):
1951            return
1952        # Update text positions before `Text.draw` would, so that the
1953        # FancyArrowPatch is correctly positioned.
1954        self.update_positions(renderer)
1955        self.update_bbox_position_size(renderer)
1956        if self.arrow_patch is not None:   # FancyArrowPatch
1957            if self.arrow_patch.figure is None and self.figure is not None:
1958                self.arrow_patch.figure = self.figure
1959            self.arrow_patch.draw(renderer)
1960        # Draw text, including FancyBboxPatch, after FancyArrowPatch.
1961        # Otherwise, a wedge arrowstyle can land partly on top of the Bbox.
1962        Text.draw(self, renderer)
1963
1964    def get_window_extent(self, renderer=None):
1965        """
1966        Return the `.Bbox` bounding the text and arrow, in display units.
1967
1968        Parameters
1969        ----------
1970        renderer : Renderer, optional
1971            A renderer is needed to compute the bounding box.  If the artist
1972            has already been drawn, the renderer is cached; thus, it is only
1973            necessary to pass this argument when calling `get_window_extent`
1974            before the first `draw`.  In practice, it is usually easier to
1975            trigger a draw first (e.g. by saving the figure).
1976        """
1977        # This block is the same as in Text.get_window_extent, but we need to
1978        # set the renderer before calling update_positions().
1979        if not self.get_visible() or not self._check_xy(renderer):
1980            return Bbox.unit()
1981        if renderer is not None:
1982            self._renderer = renderer
1983        if self._renderer is None:
1984            self._renderer = self.figure._cachedRenderer
1985        if self._renderer is None:
1986            raise RuntimeError('Cannot get window extent w/o renderer')
1987
1988        self.update_positions(self._renderer)
1989
1990        text_bbox = Text.get_window_extent(self)
1991        bboxes = [text_bbox]
1992
1993        if self.arrow_patch is not None:
1994            bboxes.append(self.arrow_patch.get_window_extent())
1995
1996        return Bbox.union(bboxes)
1997
1998    def get_tightbbox(self, renderer):
1999        # docstring inherited
2000        if not self._check_xy(renderer):
2001            return Bbox.null()
2002        return super().get_tightbbox(renderer)
2003
2004
2005docstring.interpd.update(Annotation=Annotation.__init__.__doc__)
2006