Skip to content

Commit c9468c1

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 a68f21f commit c9468c1

File tree

1 file changed

+142
-83
lines changed

1 file changed

+142
-83
lines changed

tutorials/text/annotations.py

+142-83
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

@@ -348,19 +388,64 @@
348388
ax.add_artist(box)
349389

350390
###############################################################################
351-
# As in the legend, the bbox_to_anchor argument can be set. Using the
352-
# HPacker and VPacker, you can have an arrangement(?) of artist as in the
353-
# legend (as a matter of fact, this is how the legend is created).
391+
# Another method of anchoring an artist relative to a parent axes or anchor
392+
# point is via the *bbox_to_anchor* argument of `.AnchoredOffsetbox`. This
393+
# artist can then be automatically positioned relative to another artist using
394+
# `.HPacker` and `.VPacker`. ::
395+
#
396+
# from matplotlib.offsetbox import (AnchoredOffsetbox, DrawingArea, HPacker,
397+
# TextArea)
398+
#
399+
# box1 = TextArea(" Test: ", textprops=dict(color="k"))
400+
# box2 = DrawingArea(60, 20, 0, 0)
401+
# # see gallery example for ellipse drawing code
402+
# box = HPacker(children=[box1, box2], align="center", pad=0, sep=5)
403+
# anchored_box = AnchoredOffsetbox(loc='lower left', child=box, pad=0.,
404+
# frameon=True, borderpad=0.,
405+
# bbox_to_anchor=(0., 1.02),
406+
# bbox_transform=ax.transAxes)
354407
#
355408
# .. figure:: ../../gallery/userdemo/images/sphx_glr_anchored_box04_001.png
356409
# :target: ../../gallery/userdemo/anchored_box04.html
357410
# :align: center
358411
#
359-
# Note that unlike the legend, the ``bbox_transform`` is set
360-
# to IdentityTransform by default.
412+
# Note that, unlike in `.Legend`, the ``bbox_transform`` is set to
413+
# IdentityTransform by default. The full example can be found at
414+
# :doc:`/gallery/userdemo/anchored_box04`.
361415
#
362-
# Coordinate systems for Annotations
363-
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
416+
# Defining custom box styles
417+
# ~~~~~~~~~~~~~~~~~~~~~~~~~~
418+
#
419+
# You can use a custom box style. The value for the ``boxstyle`` can be a
420+
# callable object in the following forms. ::
421+
#
422+
# def __call__(self, x0, y0, width, height, mutation_size,
423+
# aspect_ratio=1.):
424+
# '''
425+
# Given the location and size of the box, return the path of
426+
# the box around it.
427+
#
428+
# - *x0*, *y0*, *width*, *height* : location and size of the box
429+
# - *mutation_size* : a reference scale for the mutation.
430+
# - *aspect_ratio* : aspect-ratio for the mutation.
431+
# '''
432+
# path = ...
433+
# return path
434+
#
435+
# Here is a complete example.
436+
#
437+
# .. figure:: ../../gallery/userdemo/images/sphx_glr_custom_boxstyle01_001.png
438+
# :target: ../../gallery/userdemo/custom_boxstyle01.html
439+
# :align: center
440+
#
441+
# Similarly, you can define a custom `.ConnectionStyle` and a custom
442+
# `.ArrowStyle`. View the source code at `.patches` to learn how each class
443+
# is defined.
444+
#
445+
# .. _annotating_coordinate_systems:
446+
#
447+
# Coordinate systems for annotations
448+
# ----------------------------------
364449
#
365450
# Matplotlib Annotations support several types of coordinates. Some are
366451
# described in :ref:`annotations-tutorial`; more advanced options are
@@ -376,18 +461,22 @@
376461
# This allows annotating a point in another axes::
377462
#
378463
# fig, (ax1, ax2) = plt.subplots(1, 2)
379-
# ax2.annotate("Test", xy=(0.5, 0.5), xycoords=ax1.transData,
464+
# ax2.annotate("Test",
465+
# xy=(0.5, 0.5), xycoords=ax1.transData,
380466
# xytext=(0.5, 0.5), textcoords=ax2.transData,
381467
# arrowprops=dict(arrowstyle="->"))
382468
#
383469
# 2. An `.Artist` instance. The *xy* value (or *xytext*) is interpreted as a
384470
# fractional coordinate of the bbox (return value of *get_window_extent*) of
385-
# the artist::
471+
# the artist: ::
386472
#
387-
# an1 = ax.annotate("Test 1", xy=(0.5, 0.5), xycoords="data",
473+
# an1 = ax.annotate("Test 1",
474+
# xy=(0.5, 0.5), xycoords="data",
388475
# va="center", ha="center",
389476
# bbox=dict(boxstyle="round", fc="w"))
390-
# an2 = ax.annotate("Test 2", xy=(1, 0.5), xycoords=an1, # (1, 0.5) of the an1's bbox
477+
#
478+
# an2 = ax.annotate("Test 2",
479+
# xy=(1, 0.5), xycoords=an1, # (1, 0.5) of the an1's bbox
391480
# xytext=(30, 0), textcoords="offset points",
392481
# va="center", ha="left",
393482
# bbox=dict(boxstyle="round", fc="w"),
@@ -405,12 +494,14 @@
405494
# returns either a `.Transform` or a `.BboxBase`. The return value is then
406495
# handled as in (1), for transforms, or in (2), for bboxes. For example, ::
407496
#
408-
# an2 = ax.annotate("Test 2", xy=(1, 0.5), xycoords=an1,
497+
# an2 = ax.annotate("Test 2",
498+
# xy=(1, 0.5), xycoords=an1,
409499
# xytext=(30, 0), textcoords="offset points")
410500
#
411501
# is identical to::
412502
#
413-
# an2 = ax.annotate("Test 2", xy=(1, 0.5), xycoords=an1.get_window_extent,
503+
# an2 = ax.annotate("Test 2",
504+
# xy=(1, 0.5), xycoords=an1.get_window_extent,
414505
# xytext=(30, 0), textcoords="offset points")
415506
#
416507
# 4. A pair of coordinate specifications -- the first for the x-coordinate, and
@@ -434,18 +525,18 @@
434525
# :target: ../../gallery/userdemo/annotate_simple_coord03.html
435526
# :align: center
436527
#
437-
# You may take a look at this example
528+
# This example can be found at
438529
# :doc:`/gallery/text_labels_and_annotations/annotation_demo`.
439530
#
440531
# Using ConnectionPatch
441532
# ~~~~~~~~~~~~~~~~~~~~~
442533
#
443534
# ConnectionPatch is like an annotation without text. While `~.Axes.annotate`
444-
# is sufficient in most situations, ConnectionPatch is useful when you want to
535+
# is sufficient in most situations, `.ConnectionPatch` is useful when you want to
445536
# connect points in different axes. ::
446537
#
447538
# from matplotlib.patches import ConnectionPatch
448-
# xy = (0.2, 0.2)
539+
# xy = (0.3, 0.2)
449540
# con = ConnectionPatch(xyA=xy, coordsA=ax1.transData,
450541
# xyB=xy, coordsB=ax2.transData)
451542
# fig.add_artist(con)
@@ -462,9 +553,6 @@
462553
# and is also necessary if using :doc:`constrained_layout
463554
# </tutorials/intermediate/constrainedlayout_guide>` for positioning the axes.
464555
#
465-
# Advanced Topics
466-
# ---------------
467-
#
468556
# Zoom effect between Axes
469557
# ~~~~~~~~~~~~~~~~~~~~~~~~
470558
#
@@ -475,32 +563,3 @@
475563
# .. figure:: ../../gallery/subplots_axes_and_figures/images/sphx_glr_axes_zoom_effect_001.png
476564
# :target: ../../gallery/subplots_axes_and_figures/axes_zoom_effect.html
477565
# :align: center
478-
#
479-
# Define Custom BoxStyle
480-
# ~~~~~~~~~~~~~~~~~~~~~~
481-
#
482-
# You can use a custom box style. The value for the ``boxstyle`` can be a
483-
# callable object in the following forms.::
484-
#
485-
# def __call__(self, x0, y0, width, height, mutation_size,
486-
# aspect_ratio=1.):
487-
# '''
488-
# Given the location and size of the box, return the path of
489-
# the box around it.
490-
#
491-
# - *x0*, *y0*, *width*, *height* : location and size of the box
492-
# - *mutation_size* : a reference scale for the mutation.
493-
# - *aspect_ratio* : aspect-ratio for the mutation.
494-
# '''
495-
# path = ...
496-
# return path
497-
#
498-
# Here is a complete example.
499-
#
500-
# .. figure:: ../../gallery/userdemo/images/sphx_glr_custom_boxstyle01_001.png
501-
# :target: ../../gallery/userdemo/custom_boxstyle01.html
502-
# :align: center
503-
#
504-
# Similarly, you can define a custom ConnectionStyle and a custom ArrowStyle.
505-
# See the source code of ``lib/matplotlib/patches.py`` and check
506-
# how each style class is defined.

0 commit comments

Comments
 (0)