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