Skip to content

Commit bf764c2

Browse files
story645jklymaktimhoffmQuLogic
committed
added offset section and restructured annotations tutorial and reworked
AnchoredOffsetbox section also minor consistency and linking tweaks throughout Co-authored-by: Jody Klymak <jklymak@gmail.com> Co-authored-by: Tim Hoffmann <2836374+timhoffm@users.noreply.github.com> Co-authored-by: Elliott Sales de Andrade <quantum.analyst@gmail.com>
1 parent cada8fb commit bf764c2

File tree

1 file changed

+141
-82
lines changed

1 file changed

+141
-82
lines changed

tutorials/text/annotations.py

Lines changed: 141 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -47,22 +47,46 @@
4747
# 'data' use the axes data coordinate system
4848
# ================== ========================================================
4949
#
50-
# For example to place the text coordinates in fractional axes
51-
# coordinates, one could do::
50+
# The following strings are also valid arguments for *textcoords*
5251
#
53-
# ax.annotate('local max', xy=(3, 1), xycoords='data',
54-
# xytext=(0.8, 0.95), textcoords='axes fraction',
55-
# arrowprops=dict(facecolor='black', shrink=0.05),
56-
# horizontalalignment='right', verticalalignment='top',
57-
# )
52+
# ================== ========================================================
53+
# argument coordinate system
54+
# ================== ========================================================
55+
# 'offset points' offset (in points) from the xy value
56+
# 'offset pixels' offset (in pixels) from the xy value
57+
# ================== ========================================================
5858
#
5959
# For physical coordinate systems (points or pixels) the origin is the
60-
# bottom-left of the figure or axes.
60+
# bottom-left of the figure or axes. Points are
61+
# `typographic points <https://en.wikipedia.org/wiki/Point_(typography)>`_
62+
# meaning that they are a physical unit measuring 1/72 of an inch. Points and
63+
# pixels are discussed in further detail in :ref:`transforms-fig-scale-dpi`.
6164
#
62-
# Optionally, you can enable drawing of an arrow from the text to the annotated
63-
# point by giving a dictionary of arrow properties in the optional keyword
64-
# argument *arrowprops*.
65+
# .. _annotation-data:
66+
#
67+
# Annotating data
68+
# ~~~~~~~~~~~~~~~
69+
#
70+
# For example to place the text coordinates in fractional axes
71+
# coordinates, one could do :
72+
73+
fig, ax = plt.subplots()
74+
ax.scatter(3, 1, s=20)
75+
ax.annotate('local max',
76+
xy=(3, 1), xycoords='data',
77+
xytext=(0.8, 0.95), textcoords='axes fraction',
78+
horizontalalignment='right', verticalalignment='top')
79+
80+
###################################################################
81+
#
82+
# .. _annotation-with-arrow:
83+
#
84+
# Annotating with arrows
85+
# ~~~~~~~~~~~~~~~~~~~~~~
6586
#
87+
# You can enable drawing of an arrow from the text to the annotated point
88+
# by giving a dictionary of arrow properties in the optional keyword
89+
# argument *arrowprops*.
6690
#
6791
# ==================== =====================================================
6892
# *arrowprops* key description
@@ -77,10 +101,9 @@
77101
# e.g., ``facecolor``
78102
# ==================== =====================================================
79103
#
80-
#
81-
# In the example below, the *xy* point is in native coordinates
82-
# (*xycoords* defaults to 'data'). For a polar axes, this is in
83-
# (theta, radius) space. The text in this example is placed in the
104+
# In the example below, the *xy* point is in the data coordinate system
105+
# since *xycoords* defaults to 'data'. For a polar axes, this is in
106+
# (theta, radius) space. The text in this example is placed in the
84107
# fractional figure coordinate system. :class:`matplotlib.text.Text`
85108
# keyword arguments like *horizontalalignment*, *verticalalignment* and
86109
# *fontsize* are passed from `~matplotlib.axes.Axes.annotate` to the
@@ -94,18 +117,38 @@
94117
# annotations, including fancy arrows, see :ref:`plotting-guide-annotation`
95118
# and :doc:`/gallery/text_labels_and_annotations/annotation_demo`.
96119
#
120+
# .. _annotations-offset-text:
97121
#
98-
# Do not proceed unless you have already read :ref:`annotations-tutorial`,
99-
# :func:`~matplotlib.pyplot.text` and :func:`~matplotlib.pyplot.annotate`!
100-
#
122+
# Placing text annotations relative to data
123+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
124+
# Annotations can be positioned at a relative offset to the *xy* input to
125+
# annotation by setting the *textcoords* keyword argument to 'offset points' or
126+
# 'offset pixels'.
127+
128+
fig, ax = plt.subplots()
129+
x = [1, 3, 5, 7, 9]
130+
y = [2, 4, 6, 8, 10]
131+
annotations = ["A", "B", "C", "D", "E"]
132+
ax.scatter(x, y, s=20)
133+
134+
for xi, yi, text in zip(x, y, annotations):
135+
ax.annotate(text,
136+
xy=(xi, yi), xycoords='data',
137+
xytext=(1.5, 1.5), textcoords='offset points')
138+
139+
###############################################################################
140+
# The annotations are offset 1.5 points (1.5*1/72 inches) from the *xy* values.
101141
#
102142
# .. _plotting-guide-annotation:
103143
#
104-
# Advanced Annotations
105-
# --------------------
144+
# Advanced annotation
145+
# -------------------
106146
#
107-
# Annotating with Text with Box
108-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
147+
# We recommend reading :ref:`annotations-tutorial`, :func:`~matplotlib.pyplot.text`
148+
# and :func:`~matplotlib.pyplot.annotate` before reading this section.
149+
#
150+
# Annotating with boxed text
151+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
109152
#
110153
# Let's start with a simple example.
111154
#
@@ -116,9 +159,9 @@
116159
# `~.Axes.text` takes a *bbox* keyword argument, which draws a box around the
117160
# text::
118161
#
119-
# t = ax.text(
120-
# 0, 0, "Direction", ha="center", va="center", rotation=45, size=15,
121-
# bbox=dict(boxstyle="rarrow,pad=0.3", fc="cyan", ec="b", lw=2))
162+
# t = ax.text(0, 0, "Direction",
163+
# ha="center", va="center", rotation=45, size=15,
164+
# bbox=dict(boxstyle="rarrow,pad=0.3", fc="cyan", ec="b", lw=2))
122165
#
123166
# The patch object associated with the text can be accessed by::
124167
#
@@ -155,17 +198,16 @@
155198
# name with separating comma (this form can be used as "boxstyle" value
156199
# of bbox argument when initializing the text instance) ::
157200
#
158-
# bb.set_boxstyle("rarrow,pad=0.6")
201+
# bb.set_boxstyle("rarrow, pad=0.6")
159202
#
160-
# Annotating with Arrow
161-
# ~~~~~~~~~~~~~~~~~~~~~
203+
# Customizing annotation arrows
204+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
162205
#
163206
# `~.Axes.annotate` draws an arrow connecting two points in an Axes::
164207
#
165208
# ax.annotate("Annotation",
166209
# xy=(x1, y1), xycoords='data',
167-
# xytext=(x2, y2), textcoords='offset points',
168-
# )
210+
# xytext=(x2, y2), textcoords='offset points')
169211
#
170212
# This annotates a point at *xy* in the given coordinate (*xycoords*)
171213
# with the text at *xytext* given in *textcoords*. Often, the
@@ -180,9 +222,7 @@
180222
# ax.annotate("",
181223
# xy=(0.2, 0.2), xycoords='data',
182224
# xytext=(0.8, 0.8), textcoords='data',
183-
# arrowprops=dict(arrowstyle="->",
184-
# connectionstyle="arc3"),
185-
# )
225+
# arrowprops=dict(arrowstyle="->", connectionstyle="arc3"))
186226
#
187227
# .. figure:: ../../gallery/userdemo/images/sphx_glr_annotate_simple01_001.png
188228
# :target: ../../gallery/userdemo/annotate_simple01.html
@@ -292,8 +332,8 @@
292332
from matplotlib.offsetbox import AnchoredText
293333

294334
fig, ax = plt.subplots()
295-
at = AnchoredText(
296-
"Figure 1a", prop=dict(size=15), frameon=True, loc='upper left')
335+
at = AnchoredText("Figure 1a",
336+
prop=dict(size=15), frameon=True, loc='upper left')
297337
at.patch.set_boxstyle("round,pad=0.,rounding_size=0.2")
298338
ax.add_artist(at)
299339

@@ -350,19 +390,64 @@
350390
ax.add_artist(box)
351391

352392
###############################################################################
353-
# As in the legend, the bbox_to_anchor argument can be set. Using the
354-
# HPacker and VPacker, you can have an arrangement(?) of artist as in the
355-
# legend (as a matter of fact, this is how the legend is created).
393+
# Another method of anchoring an artist relative to a parent axes or anchor
394+
# point is via the *bbox_to_anchor* argument of `.AnchoredOffsetbox`. This
395+
# artist can then be automatically positioned relative to another artist using
396+
# `.HPacker` and `.VPacker`. ::
397+
#
398+
# from matplotlib.offsetbox import (AnchoredOffsetbox, DrawingArea, HPacker,
399+
# TextArea)
400+
#
401+
# box1 = TextArea(" Test: ", textprops=dict(color="k"))
402+
# box2 = DrawingArea(60, 20, 0, 0)
403+
# # see gallery example for ellipse drawing code
404+
# box = HPacker(children=[box1, box2], align="center", pad=0, sep=5)
405+
# anchored_box = AnchoredOffsetbox(loc='lower left', child=box, pad=0.,
406+
# frameon=True, borderpad=0.,
407+
# bbox_to_anchor=(0., 1.02),
408+
# bbox_transform=ax.transAxes)
356409
#
357410
# .. figure:: ../../gallery/userdemo/images/sphx_glr_anchored_box04_001.png
358411
# :target: ../../gallery/userdemo/anchored_box04.html
359412
# :align: center
360413
#
361-
# Note that unlike the legend, the ``bbox_transform`` is set
362-
# to IdentityTransform by default.
414+
# Note that, unlike in `.Legend`, the ``bbox_transform`` is set to
415+
# IdentityTransform by default. The full example can be found at
416+
# :doc:`/gallery/userdemo/anchored_box04`.
363417
#
364-
# Coordinate systems for Annotations
365-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
418+
# Defining custom box styles
419+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
420+
#
421+
# You can use a custom box style. The value for the ``boxstyle`` can be a
422+
# callable object in the following forms. ::
423+
#
424+
# def __call__(self, x0, y0, width, height, mutation_size,
425+
# aspect_ratio=1.):
426+
# '''
427+
# Given the location and size of the box, return the path of
428+
# the box around it.
429+
#
430+
# - *x0*, *y0*, *width*, *height* : location and size of the box
431+
# - *mutation_size* : a reference scale for the mutation.
432+
# - *aspect_ratio* : aspect-ratio for the mutation.
433+
# '''
434+
# path = ...
435+
# return path
436+
#
437+
# Here is a complete example.
438+
#
439+
# .. figure:: ../../gallery/userdemo/images/sphx_glr_custom_boxstyle01_001.png
440+
# :target: ../../gallery/userdemo/custom_boxstyle01.html
441+
# :align: center
442+
#
443+
# Similarly, you can define a custom `.ConnectionStyle` and a custom
444+
# `.ArrowStyle`. View the source code at `.patches` to learn how each class
445+
# is defined.
446+
#
447+
# .. _annotating_coordinate_systems:
448+
#
449+
# Coordinate systems for annotations
450+
# ----------------------------------
366451
#
367452
# Matplotlib Annotations support several types of coordinates. Some are
368453
# described in :ref:`annotations-tutorial`; more advanced options are
@@ -378,18 +463,22 @@
378463
# This allows annotating a point in another axes::
379464
#
380465
# fig, (ax1, ax2) = plt.subplots(1, 2)
381-
# ax2.annotate("Test", xy=(0.5, 0.5), xycoords=ax1.transData,
466+
# ax2.annotate("Test",
467+
# xy=(0.5, 0.5), xycoords=ax1.transData,
382468
# xytext=(0.5, 0.5), textcoords=ax2.transData,
383469
# arrowprops=dict(arrowstyle="->"))
384470
#
385471
# 2. An `.Artist` instance. The *xy* value (or *xytext*) is interpreted as a
386472
# fractional coordinate of the bbox (return value of *get_window_extent*) of
387-
# the artist::
473+
# the artist: ::
388474
#
389-
# an1 = ax.annotate("Test 1", xy=(0.5, 0.5), xycoords="data",
475+
# an1 = ax.annotate("Test 1",
476+
# xy=(0.5, 0.5), xycoords="data",
390477
# va="center", ha="center",
391478
# bbox=dict(boxstyle="round", fc="w"))
392-
# an2 = ax.annotate("Test 2", xy=(1, 0.5), xycoords=an1, # (1, 0.5) of the an1's bbox
479+
#
480+
# an2 = ax.annotate("Test 2",
481+
# xy=(1, 0.5), xycoords=an1, # (1, 0.5) of the an1's bbox
393482
# xytext=(30, 0), textcoords="offset points",
394483
# va="center", ha="left",
395484
# bbox=dict(boxstyle="round", fc="w"),
@@ -407,12 +496,14 @@
407496
# returns either a `.Transform` or a `.BboxBase`. The return value is then
408497
# handled as in (1), for transforms, or in (2), for bboxes. For example, ::
409498
#
410-
# an2 = ax.annotate("Test 2", xy=(1, 0.5), xycoords=an1,
499+
# an2 = ax.annotate("Test 2",
500+
# xy=(1, 0.5), xycoords=an1,
411501
# xytext=(30, 0), textcoords="offset points")
412502
#
413503
# is identical to::
414504
#
415-
# an2 = ax.annotate("Test 2", xy=(1, 0.5), xycoords=an1.get_window_extent,
505+
# an2 = ax.annotate("Test 2",
506+
# xy=(1, 0.5), xycoords=an1.get_window_extent,
416507
# xytext=(30, 0), textcoords="offset points")
417508
#
418509
# 4. A pair of coordinate specifications -- the first for the x-coordinate, and
@@ -436,7 +527,7 @@
436527
# :target: ../../gallery/userdemo/annotate_simple_coord03.html
437528
# :align: center
438529
#
439-
# You may take a look at this example
530+
# This example can be found at
440531
# :doc:`/gallery/text_labels_and_annotations/annotation_demo`.
441532
#
442533
# Using ConnectionPatch
@@ -447,7 +538,7 @@
447538
# connect points in different axes. ::
448539
#
449540
# from matplotlib.patches import ConnectionPatch
450-
# xy = (0.2, 0.2)
541+
# xy = (0.3, 0.2)
451542
# con = ConnectionPatch(xyA=xy, coordsA=ax1.transData,
452543
# xyB=xy, coordsB=ax2.transData)
453544
# fig.add_artist(con)
@@ -464,9 +555,6 @@
464555
# and is also necessary if using :doc:`constrained_layout
465556
# </tutorials/intermediate/constrainedlayout_guide>` for positioning the axes.
466557
#
467-
# Advanced Topics
468-
# ---------------
469-
#
470558
# Zoom effect between Axes
471559
# ~~~~~~~~~~~~~~~~~~~~~~~~
472560
#
@@ -477,32 +565,3 @@
477565
# .. figure:: ../../gallery/subplots_axes_and_figures/images/sphx_glr_axes_zoom_effect_001.png
478566
# :target: ../../gallery/subplots_axes_and_figures/axes_zoom_effect.html
479567
# :align: center
480-
#
481-
# Define Custom BoxStyle
482-
# ~~~~~~~~~~~~~~~~~~~~~~
483-
#
484-
# You can use a custom box style. The value for the ``boxstyle`` can be a
485-
# callable object in the following forms.::
486-
#
487-
# def __call__(self, x0, y0, width, height, mutation_size,
488-
# aspect_ratio=1.):
489-
# '''
490-
# Given the location and size of the box, return the path of
491-
# the box around it.
492-
#
493-
# - *x0*, *y0*, *width*, *height* : location and size of the box
494-
# - *mutation_size* : a reference scale for the mutation.
495-
# - *aspect_ratio* : aspect-ratio for the mutation.
496-
# '''
497-
# path = ...
498-
# return path
499-
#
500-
# Here is a complete example.
501-
#
502-
# .. figure:: ../../gallery/userdemo/images/sphx_glr_custom_boxstyle01_001.png
503-
# :target: ../../gallery/userdemo/custom_boxstyle01.html
504-
# :align: center
505-
#
506-
# Similarly, you can define a custom ConnectionStyle and a custom ArrowStyle.
507-
# See the source code of ``lib/matplotlib/patches.py`` and check
508-
# how each style class is defined.

0 commit comments

Comments
 (0)