1"""
2================
3Annotating Plots
4================
5
6The following examples show how it is possible to annotate plots in matplotlib.
7This includes highlighting specific points of interest and using various
8visual tools to call attention to this point. For a more complete and in-depth
9description of the annotation and text tools in :mod:`matplotlib`, see the
10`tutorial on annotation <http://matplotlib.org/users/annotations.html>`_.
11"""
12
13import matplotlib.pyplot as plt
14from matplotlib.patches import Ellipse
15import numpy as np
16from matplotlib.text import OffsetFrom
17
18
19###############################################################################
20# Specifying text points and annotation points
21# --------------------------------------------
22#
23# You must specify an annotation point `xy=(x,y)` to annotate this point.
24# additionally, you may specify a text point `xytext=(x,y)` for the
25# location of the text for this annotation.  Optionally, you can
26# specify the coordinate system of `xy` and `xytext` with one of the
27# following strings for `xycoords` and `textcoords` (default is 'data')::
28#
29#   'figure points'   : points from the lower left corner of the figure
30#   'figure pixels'   : pixels from the lower left corner of the figure
31#   'figure fraction' : 0,0 is lower left of figure and 1,1 is upper, right
32#   'axes points'     : points from lower left corner of axes
33#   'axes pixels'     : pixels from lower left corner of axes
34#   'axes fraction'   : 0,0 is lower left of axes and 1,1 is upper right
35#   'offset points'   : Specify an offset (in points) from the xy value
36#   'offset pixels'   : Specify an offset (in pixels) from the xy value
37#   'data'            : use the axes data coordinate system
38#
39# Note: for physical coordinate systems (points or pixels) the origin is the
40# (bottom, left) of the figure or axes.
41#
42# Optionally, you can specify arrow properties which draws and arrow
43# from the text to the annotated point by giving a dictionary of arrow
44# properties
45#
46# Valid keys are::
47#
48#   width : the width of the arrow in points
49#   frac  : the fraction of the arrow length occupied by the head
50#   headwidth : the width of the base of the arrow head in points
51#   shrink : move the tip and base some percent away from the
52#            annotated point and text
53#   any key for matplotlib.patches.polygon  (e.g., facecolor)
54
55# Create our figure and data we'll use for plotting
56fig, ax = plt.subplots(figsize=(3, 3))
57
58t = np.arange(0.0, 5.0, 0.01)
59s = np.cos(2*np.pi*t)
60
61# Plot a line and add some simple annotations
62line, = ax.plot(t, s)
63ax.annotate('figure pixels',
64            xy=(10, 10), xycoords='figure pixels')
65ax.annotate('figure points',
66            xy=(80, 80), xycoords='figure points')
67ax.annotate('figure fraction',
68            xy=(.025, .975), xycoords='figure fraction',
69            horizontalalignment='left', verticalalignment='top',
70            fontsize=20)
71
72# The following examples show off how these arrows are drawn.
73
74ax.annotate('point offset from data',
75            xy=(2, 1), xycoords='data',
76            xytext=(-15, 25), textcoords='offset points',
77            arrowprops=dict(facecolor='black', shrink=0.05),
78            horizontalalignment='right', verticalalignment='bottom')
79
80ax.annotate('axes fraction',
81            xy=(3, 1), xycoords='data',
82            xytext=(0.8, 0.95), textcoords='axes fraction',
83            arrowprops=dict(facecolor='black', shrink=0.05),
84            horizontalalignment='right', verticalalignment='top')
85
86# You may also use negative points or pixels to specify from (right, top).
87# E.g., (-10, 10) is 10 points to the left of the right side of the axes and 10
88# points above the bottom
89
90ax.annotate('pixel offset from axes fraction',
91            xy=(1, 0), xycoords='axes fraction',
92            xytext=(-20, 20), textcoords='offset pixels',
93            horizontalalignment='right',
94            verticalalignment='bottom')
95
96ax.set(xlim=(-1, 5), ylim=(-3, 5))
97
98
99###############################################################################
100# Using multiple coordinate systems and axis types
101# ------------------------------------------------
102#
103# You can specify the xypoint and the xytext in different positions and
104# coordinate systems, and optionally turn on a connecting line and mark
105# the point with a marker.  Annotations work on polar axes too.
106#
107# In the example below, the xy point is in native coordinates (xycoords
108# defaults to 'data').  For a polar axes, this is in (theta, radius) space.
109# The text in the example is placed in the fractional figure coordinate system.
110# Text keyword args like horizontal and vertical alignment are respected.
111
112fig, ax = plt.subplots(subplot_kw=dict(projection='polar'), figsize=(3, 3))
113r = np.arange(0, 1, 0.001)
114theta = 2*2*np.pi*r
115line, = ax.plot(theta, r)
116
117ind = 800
118thisr, thistheta = r[ind], theta[ind]
119ax.plot([thistheta], [thisr], 'o')
120ax.annotate('a polar annotation',
121            xy=(thistheta, thisr),  # theta, radius
122            xytext=(0.05, 0.05),    # fraction, fraction
123            textcoords='figure fraction',
124            arrowprops=dict(facecolor='black', shrink=0.05),
125            horizontalalignment='left',
126            verticalalignment='bottom')
127
128# You can also use polar notation on a cartesian axes.  Here the native
129# coordinate system ('data') is cartesian, so you need to specify the
130# xycoords and textcoords as 'polar' if you want to use (theta, radius).
131
132el = Ellipse((0, 0), 10, 20, facecolor='r', alpha=0.5)
133
134fig, ax = plt.subplots(subplot_kw=dict(aspect='equal'))
135ax.add_artist(el)
136el.set_clip_box(ax.bbox)
137ax.annotate('the top',
138            xy=(np.pi/2., 10.),      # theta, radius
139            xytext=(np.pi/3, 20.),   # theta, radius
140            xycoords='polar',
141            textcoords='polar',
142            arrowprops=dict(facecolor='black', shrink=0.05),
143            horizontalalignment='left',
144            verticalalignment='bottom',
145            clip_on=True)  # clip to the axes bounding box
146
147ax.set(xlim=[-20, 20], ylim=[-20, 20])
148
149
150###############################################################################
151# Customizing arrow and bubble styles
152# -----------------------------------
153#
154# The arrow between xytext and the annotation point, as well as the bubble
155# that covers the annotation text, are highly customizable. Below are a few
156# parameter options as well as their resulting output.
157
158fig, ax = plt.subplots(figsize=(8, 5))
159
160t = np.arange(0.0, 5.0, 0.01)
161s = np.cos(2*np.pi*t)
162line, = ax.plot(t, s, lw=3)
163
164ax.annotate('straight',
165            xy=(0, 1), xycoords='data',
166            xytext=(-50, 30), textcoords='offset points',
167            arrowprops=dict(arrowstyle="->"))
168
169ax.annotate('arc3,\nrad 0.2',
170            xy=(0.5, -1), xycoords='data',
171            xytext=(-80, -60), textcoords='offset points',
172            arrowprops=dict(arrowstyle="->",
173                            connectionstyle="arc3,rad=.2"))
174
175ax.annotate('arc,\nangle 50',
176            xy=(1., 1), xycoords='data',
177            xytext=(-90, 50), textcoords='offset points',
178            arrowprops=dict(arrowstyle="->",
179                            connectionstyle="arc,angleA=0,armA=50,rad=10"))
180
181ax.annotate('arc,\narms',
182            xy=(1.5, -1), xycoords='data',
183            xytext=(-80, -60), textcoords='offset points',
184            arrowprops=dict(arrowstyle="->",
185                            connectionstyle="arc,angleA=0,armA=40,angleB=-90,armB=30,rad=7"))
186
187ax.annotate('angle,\nangle 90',
188            xy=(2., 1), xycoords='data',
189            xytext=(-70, 30), textcoords='offset points',
190            arrowprops=dict(arrowstyle="->",
191                            connectionstyle="angle,angleA=0,angleB=90,rad=10"))
192
193ax.annotate('angle3,\nangle -90',
194            xy=(2.5, -1), xycoords='data',
195            xytext=(-80, -60), textcoords='offset points',
196            arrowprops=dict(arrowstyle="->",
197                            connectionstyle="angle3,angleA=0,angleB=-90"))
198
199ax.annotate('angle,\nround',
200            xy=(3., 1), xycoords='data',
201            xytext=(-60, 30), textcoords='offset points',
202            bbox=dict(boxstyle="round", fc="0.8"),
203            arrowprops=dict(arrowstyle="->",
204                            connectionstyle="angle,angleA=0,angleB=90,rad=10"))
205
206ax.annotate('angle,\nround4',
207            xy=(3.5, -1), xycoords='data',
208            xytext=(-70, -80), textcoords='offset points',
209            size=20,
210            bbox=dict(boxstyle="round4,pad=.5", fc="0.8"),
211            arrowprops=dict(arrowstyle="->",
212                            connectionstyle="angle,angleA=0,angleB=-90,rad=10"))
213
214ax.annotate('angle,\nshrink',
215            xy=(4., 1), xycoords='data',
216            xytext=(-60, 30), textcoords='offset points',
217            bbox=dict(boxstyle="round", fc="0.8"),
218            arrowprops=dict(arrowstyle="->",
219                            shrinkA=0, shrinkB=10,
220                            connectionstyle="angle,angleA=0,angleB=90,rad=10"))
221
222# You can pass an empty string to get only annotation arrows rendered
223ann = ax.annotate('', xy=(4., 1.), xycoords='data',
224                  xytext=(4.5, -1), textcoords='data',
225                  arrowprops=dict(arrowstyle="<->",
226                                  connectionstyle="bar",
227                                  ec="k",
228                                  shrinkA=5, shrinkB=5))
229
230ax.set(xlim=(-1, 5), ylim=(-4, 3))
231
232# We'll create another figure so that it doesn't get too cluttered
233fig, ax = plt.subplots()
234
235el = Ellipse((2, -1), 0.5, 0.5)
236ax.add_patch(el)
237
238ax.annotate('$->$',
239            xy=(2., -1), xycoords='data',
240            xytext=(-150, -140), textcoords='offset points',
241            bbox=dict(boxstyle="round", fc="0.8"),
242            arrowprops=dict(arrowstyle="->",
243                            patchB=el,
244                            connectionstyle="angle,angleA=90,angleB=0,rad=10"))
245
246ax.annotate('arrow\nfancy',
247            xy=(2., -1), xycoords='data',
248            xytext=(-100, 60), textcoords='offset points',
249            size=20,
250            # bbox=dict(boxstyle="round", fc="0.8"),
251            arrowprops=dict(arrowstyle="fancy",
252                            fc="0.6", ec="none",
253                            patchB=el,
254                            connectionstyle="angle3,angleA=0,angleB=-90"))
255
256ax.annotate('arrow\nsimple',
257            xy=(2., -1), xycoords='data',
258            xytext=(100, 60), textcoords='offset points',
259            size=20,
260            # bbox=dict(boxstyle="round", fc="0.8"),
261            arrowprops=dict(arrowstyle="simple",
262                            fc="0.6", ec="none",
263                            patchB=el,
264                            connectionstyle="arc3,rad=0.3"))
265
266ax.annotate('wedge',
267            xy=(2., -1), xycoords='data',
268            xytext=(-100, -100), textcoords='offset points',
269            size=20,
270            # bbox=dict(boxstyle="round", fc="0.8"),
271            arrowprops=dict(arrowstyle="wedge,tail_width=0.7",
272                            fc="0.6", ec="none",
273                            patchB=el,
274                            connectionstyle="arc3,rad=-0.3"))
275
276ann = ax.annotate('bubble,\ncontours',
277                  xy=(2., -1), xycoords='data',
278                  xytext=(0, -70), textcoords='offset points',
279                  size=20,
280                  bbox=dict(boxstyle="round",
281                            fc=(1.0, 0.7, 0.7),
282                            ec=(1., .5, .5)),
283                  arrowprops=dict(arrowstyle="wedge,tail_width=1.",
284                                  fc=(1.0, 0.7, 0.7), ec=(1., .5, .5),
285                                  patchA=None,
286                                  patchB=el,
287                                  relpos=(0.2, 0.8),
288                                  connectionstyle="arc3,rad=-0.1"))
289
290ann = ax.annotate('bubble',
291                  xy=(2., -1), xycoords='data',
292                  xytext=(55, 0), textcoords='offset points',
293                  size=20, va="center",
294                  bbox=dict(boxstyle="round", fc=(1.0, 0.7, 0.7), ec="none"),
295                  arrowprops=dict(arrowstyle="wedge,tail_width=1.",
296                                  fc=(1.0, 0.7, 0.7), ec="none",
297                                  patchA=None,
298                                  patchB=el,
299                                  relpos=(0.2, 0.5)))
300
301ax.set(xlim=(-1, 5), ylim=(-5, 3))
302
303###############################################################################
304# More examples of coordinate systems
305# -----------------------------------
306#
307# Below we'll show a few more examples of coordinate systems and how the
308# location of annotations may be specified.
309
310fig, (ax1, ax2) = plt.subplots(1, 2)
311
312bbox_args = dict(boxstyle="round", fc="0.8")
313arrow_args = dict(arrowstyle="->")
314
315# Here we'll demonstrate the extents of the coordinate system and how
316# we place annotating text.
317
318ax1.annotate('figure fraction : 0, 0', xy=(0, 0), xycoords='figure fraction',
319             xytext=(20, 20), textcoords='offset points',
320             ha="left", va="bottom",
321             bbox=bbox_args,
322             arrowprops=arrow_args)
323
324ax1.annotate('figure fraction : 1, 1', xy=(1, 1), xycoords='figure fraction',
325             xytext=(-20, -20), textcoords='offset points',
326             ha="right", va="top",
327             bbox=bbox_args,
328             arrowprops=arrow_args)
329
330ax1.annotate('axes fraction : 0, 0', xy=(0, 0), xycoords='axes fraction',
331             xytext=(20, 20), textcoords='offset points',
332             ha="left", va="bottom",
333             bbox=bbox_args,
334             arrowprops=arrow_args)
335
336ax1.annotate('axes fraction : 1, 1', xy=(1, 1), xycoords='axes fraction',
337             xytext=(-20, -20), textcoords='offset points',
338             ha="right", va="top",
339             bbox=bbox_args,
340             arrowprops=arrow_args)
341
342# It is also possible to generate draggable annotations
343
344an1 = ax1.annotate('Drag me 1', xy=(.5, .7), xycoords='data',
345                   #xytext=(.5, .7), textcoords='data',
346                   ha="center", va="center",
347                   bbox=bbox_args,
348                   #arrowprops=arrow_args
349                   )
350
351an2 = ax1.annotate('Drag me 2', xy=(.5, .5), xycoords=an1,
352                   xytext=(.5, .3), textcoords='axes fraction',
353                   ha="center", va="center",
354                   bbox=bbox_args,
355                   arrowprops=dict(patchB=an1.get_bbox_patch(),
356                                   connectionstyle="arc3,rad=0.2",
357                                   **arrow_args))
358an1.draggable()
359an2.draggable()
360
361an3 = ax1.annotate('', xy=(.5, .5), xycoords=an2,
362                   xytext=(.5, .5), textcoords=an1,
363                   ha="center", va="center",
364                   bbox=bbox_args,
365                   arrowprops=dict(patchA=an1.get_bbox_patch(),
366                                   patchB=an2.get_bbox_patch(),
367                                   connectionstyle="arc3,rad=0.2",
368                                   **arrow_args))
369
370# Finally we'll show off some more complex annotation and placement
371
372text = ax2.annotate('xy=(0, 1)\nxycoords=("data", "axes fraction")',
373                    xy=(0, 1), xycoords=("data", 'axes fraction'),
374                    xytext=(0, -20), textcoords='offset points',
375                    ha="center", va="top",
376                    bbox=bbox_args,
377                    arrowprops=arrow_args)
378
379ax2.annotate('xy=(0.5, 0)\nxycoords=artist',
380             xy=(0.5, 0.), xycoords=text,
381             xytext=(0, -20), textcoords='offset points',
382             ha="center", va="top",
383             bbox=bbox_args,
384             arrowprops=arrow_args)
385
386ax2.annotate('xy=(0.8, 0.5)\nxycoords=ax1.transData',
387             xy=(0.8, 0.5), xycoords=ax1.transData,
388             xytext=(10, 10),
389             textcoords=OffsetFrom(ax2.bbox, (0, 0), "points"),
390             ha="left", va="bottom",
391             bbox=bbox_args,
392             arrowprops=arrow_args)
393
394ax2.set(xlim=[-2, 2], ylim=[-2, 2])
395plt.show()
396