Note

Go to the endto download the full example code.

Annotations#

Annotations are graphical elements, often pieces of text, that explain, addcontext to, or otherwise highlight some portion of the visualized data.annotate supports a number of coordinate systems for flexiblypositioning data and annotations relative to each other and a variety ofoptions of for styling the text. Axes.annotate also provides an optional arrowfrom the text to the data and this arrow can be styled in various ways.text can also be used for simple text annotation, but does notprovide as much flexibility in positioning and styling asannotate.

Basic annotation#

In an annotation, there are two points to consider: the location of the databeing annotatedxy and the location of the annotation textxytext. Bothof these arguments are(x,y) tuples:

importmatplotlib.pyplotaspltimportnumpyasnpfig,ax=plt.subplots(figsize=(3,3))t=np.arange(0.0,5.0,0.01)s=np.cos(2*np.pi*t)line,=ax.plot(t,s,lw=2)ax.annotate('local max',xy=(2,1),xytext=(3,1.5),arrowprops=dict(facecolor='black',shrink=0.05))ax.set_ylim(-2,2)
annotations

In this example, both thexy (arrow tip) andxytext locations(text location) are in data coordinates. There are a variety of othercoordinate systems one can choose -- you can specify the coordinatesystem ofxy andxytext with one of the following strings forxycoords andtextcoords (default is 'data')

argument

coordinate system

'figure points'

points from the lower left corner of the figure

'figure pixels'

pixels from the lower left corner of the figure

'figure fraction'

(0, 0) is lower left of figure and (1, 1) is upper right

'axes points'

points from lower left corner of the Axes

'axes pixels'

pixels from lower left corner of the Axes

'axes fraction'

(0, 0) is lower left of Axes and (1, 1) is upper right

'data'

use the axes data coordinate system

The following strings are also valid arguments fortextcoords

argument

coordinate system

'offset points'

offset (in points) from the xy value

'offset pixels'

offset (in pixels) from the xy value

For physical coordinate systems (points or pixels) the origin is thebottom-left of the figure or Axes. Points aretypographic pointsmeaning that they are a physical unit measuring 1/72 of an inch. Points andpixels are discussed in further detail inPlotting in physical coordinates.

Annotating data#

This example places the text coordinates in fractional axes coordinates:

fig,ax=plt.subplots(figsize=(3,3))t=np.arange(0.0,5.0,0.01)s=np.cos(2*np.pi*t)line,=ax.plot(t,s,lw=2)ax.annotate('local max',xy=(2,1),xycoords='data',xytext=(0.01,.99),textcoords='axes fraction',va='top',ha='left',arrowprops=dict(facecolor='black',shrink=0.05))ax.set_ylim(-2,2)
annotations

Annotating an Artist#

Annotations can be positioned relative to anArtist instance by passingthat Artist in asxycoords. Thenxy is interpreted as a fraction of theArtist's bounding box.

importmatplotlib.patchesasmpatchesfig,ax=plt.subplots(figsize=(3,3))arr=mpatches.FancyArrowPatch((1.25,1.5),(1.75,1.5),arrowstyle='->,head_width=.15',mutation_scale=20)ax.add_patch(arr)ax.annotate("label",(.5,.5),xycoords=arr,ha='center',va='bottom')ax.set(xlim=(1,2),ylim=(1,2))
annotations

Here the annotation is placed at position (.5,.5) relative to the arrow'slower left corner and is vertically and horizontally at that position.Vertically, the bottom aligns to that reference point so that the labelis above the line. For an example of chaining annotation Artists, see theArtist section ofCoordinate systems for annotations.

Annotating with arrows#

You can enable drawing of an arrow from the text to the annotated pointby giving a dictionary of arrow properties in the optional keywordargumentarrowprops.

arrowprops key

description

width

the width of the arrow in points

frac

the fraction of the arrow length occupied by the head

headwidth

the width of the base of the arrow head in points

shrink

move the tip and base some percent away fromthe annotated point and text

**kwargs

any key formatplotlib.patches.Polygon,e.g.,facecolor

In the example below, thexy point is in the data coordinate systemsincexycoords defaults to 'data'. For a polar Axes, this is in(theta, radius) space. The text in this example is placed in thefractional figure coordinate system.matplotlib.text.Textkeyword arguments likehorizontalalignment,verticalalignment andfontsize are passed fromannotate to theText instance.

fig=plt.figure()ax=fig.add_subplot(projection='polar')r=np.arange(0,1,0.001)theta=2*2*np.pi*rline,=ax.plot(theta,r,color='#ee8d18',lw=3)ind=800thisr,thistheta=r[ind],theta[ind]ax.plot([thistheta],[thisr],'o')ax.annotate('a polar annotation',xy=(thistheta,thisr),# theta, radiusxytext=(0.05,0.05),# fraction, fractiontextcoords='figure fraction',arrowprops=dict(facecolor='black',shrink=0.05),horizontalalignment='left',verticalalignment='bottom')
annotations

For more on plotting with arrows, seeCustomizing annotation arrows

Placing text annotations relative to data#

Annotations can be positioned at a relative offset to thexy input toannotation by setting thetextcoords keyword argument to'offsetpoints'or'offsetpixels'.

fig,ax=plt.subplots(figsize=(3,3))x=[1,3,5,7,9]y=[2,4,6,8,10]annotations=["A","B","C","D","E"]ax.scatter(x,y,s=20)forxi,yi,textinzip(x,y,annotations):ax.annotate(text,xy=(xi,yi),xycoords='data',xytext=(1.5,1.5),textcoords='offset points')
annotations

The annotations are offset 1.5 points (1.5*1/72 inches) from thexy values.

Advanced annotation#

We recommend readingBasic annotation,text()andannotate() before reading this section.

Annotating with boxed text#

text takes abbox keyword argument, which draws a box around thetext:

fig,ax=plt.subplots(figsize=(5,5))t=ax.text(0.5,0.5,"Direction",ha="center",va="center",rotation=45,size=15,bbox=dict(boxstyle="rarrow,pad=0.3",fc="lightblue",ec="steelblue",lw=2))
annotations

The arguments are the name of the box style with its attributes askeyword arguments. Currently, following box styles are implemented:

Class

Name

Attrs

Circle

circle

pad=0.3

DArrow

darrow

pad=0.3

Ellipse

ellipse

pad=0.3

LArrow

larrow

pad=0.3

RArrow

rarrow

pad=0.3

Round

round

pad=0.3,rounding_size=None

Round4

round4

pad=0.3,rounding_size=None

Roundtooth

roundtooth

pad=0.3,tooth_size=None

Sawtooth

sawtooth

pad=0.3,tooth_size=None

Square

square

pad=0.3

../../../_images/sphx_glr_fancybox_demo_001.png

The patch object (box) associated with the text can be accessed using:

bb=t.get_bbox_patch()

The return value is aFancyBboxPatch; patch properties(facecolor, edgewidth, etc.) can be accessed and modified as usual.FancyBboxPatch.set_boxstyle sets the box shape:

bb.set_boxstyle("rarrow",pad=0.6)

The attribute arguments can also be specified within the stylename with separating comma:

bb.set_boxstyle("rarrow, pad=0.6")

Defining custom box styles#

Custom box styles can be implemented as a function that takes arguments specifyingboth a rectangular box and the amount of "mutation", and returns the "mutated" path.The specific signature is the one ofcustom_box_style below.

Here, we return a new path which adds an "arrow" shape on the left of the box.

The custom box style can then be used by passingbbox=dict(boxstyle=custom_box_style,...) toAxes.text.

frommatplotlib.pathimportPathdefcustom_box_style(x0,y0,width,height,mutation_size):"""    Given the location and size of the box, return the path of the box around it.    Rotation is automatically taken care of.    Parameters    ----------    x0, y0, width, height : float       Box location and size.    mutation_size : float        Mutation reference scale, typically the text font size.    """# paddingmypad=0.3pad=mutation_size*mypad# width and height with padding added.width=width+2*padheight=height+2*pad# boundary of the padded boxx0,y0=x0-pad,y0-padx1,y1=x0+width,y0+height# return the new pathreturnPath([(x0,y0),(x1,y0),(x1,y1),(x0,y1),(x0-pad,(y0+y1)/2),(x0,y0),(x0,y0)],closed=True)fig,ax=plt.subplots(figsize=(3,3))ax.text(0.5,0.5,"Test",size=30,va="center",ha="center",rotation=30,bbox=dict(boxstyle=custom_box_style,alpha=0.2))
annotations

Likewise, custom box styles can be implemented as classes that implement__call__.

The classes can then be registered into theBoxStyle._style_list dict,which allows specifying the box style as a string,bbox=dict(boxstyle="registered_name,param=value,...",...).Note that this registration relies on internal APIs and is therefore notofficially supported.

frommatplotlib.patchesimportBoxStyleclassMyStyle:"""A simple box."""def__init__(self,pad=0.3):"""        The arguments must be floats and have default values.        Parameters        ----------        pad : float            amount of padding        """self.pad=padsuper().__init__()def__call__(self,x0,y0,width,height,mutation_size):"""        Given the location and size of the box, return the path of the box around it.        Rotation is automatically taken care of.        Parameters        ----------        x0, y0, width, height : float            Box location and size.        mutation_size : float            Reference scale for the mutation, typically the text font size.        """# paddingpad=mutation_size*self.pad# width and height with padding addedwidth=width+2*padheight=height+2*pad# boundary of the padded boxx0,y0=x0-pad,y0-padx1,y1=x0+width,y0+height# return the new pathreturnPath([(x0,y0),(x1,y0),(x1,y1),(x0,y1),(x0-pad,(y0+y1)/2),(x0,y0),(x0,y0)],closed=True)BoxStyle._style_list["angled"]=MyStyle# Register the custom style.fig,ax=plt.subplots(figsize=(3,3))ax.text(0.5,0.5,"Test",size=30,va="center",ha="center",rotation=30,bbox=dict(boxstyle="angled,pad=0.5",alpha=0.2))delBoxStyle._style_list["angled"]# Unregister it.
annotations

Similarly, you can define a customConnectionStyle and a customArrowStyle. Viewthe source code atpatches to learn how each class is defined.

Customizing annotation arrows#

An arrow connectingxy toxytext can be optionally drawn byspecifying thearrowprops argument. To draw only an arrow, useempty string as the first argument:

fig,ax=plt.subplots(figsize=(3,3))ax.annotate("",xy=(0.2,0.2),xycoords='data',xytext=(0.8,0.8),textcoords='data',arrowprops=dict(arrowstyle="->",connectionstyle="arc3"))
annotations

The arrow is drawn as follows:

  1. A path connecting the two points is created, as specified by theconnectionstyle parameter.

  2. The path is clipped to avoid patchespatchA andpatchB, if these areset.

  3. The path is further shrunk byshrinkA andshrinkB (in pixels).

  4. The path is transmuted to an arrow patch, as specified by thearrowstyleparameter.

(2x.png,png)

The creation of the connecting path between two points is controlled byconnectionstyle key and the following styles are available:

Name

Attrs

angle

angleA=90,angleB=0,rad=0.0

angle3

angleA=90,angleB=0

arc

angleA=0,angleB=0,armA=None,armB=None,rad=0.0

arc3

rad=0.0

bar

armA=0.0,armB=0.0,fraction=0.3,angle=None

Note that "3" inangle3 andarc3 is meant to indicate that theresulting path is a quadratic spline segment (three controlpoints). As will be discussed below, some arrow style options can onlybe used when the connecting path is a quadratic spline.

The behavior of each connection style is (limitedly) demonstrated in theexample below. (Warning: The behavior of thebar style is currently notwell-defined and may be changed in the future).

(Sourcecode,2x.png,png)

Connection styles for annotations

The connecting path (after clipping and shrinking) is then mutated toan arrow patch, according to the givenarrowstyle:

Name

Attrs

-

None

->

head_length=0.4,head_width=0.2

-[

widthB=1.0,lengthB=0.2,angleB=None

|-|

widthA=1.0,widthB=1.0

-|>

head_length=0.4,head_width=0.2

<-

head_length=0.4,head_width=0.2

<->

head_length=0.4,head_width=0.2

<|-

head_length=0.4,head_width=0.2

<|-|>

head_length=0.4,head_width=0.2

fancy

head_length=0.4,head_width=0.4,tail_width=0.4

simple

head_length=0.5,head_width=0.5,tail_width=0.2

wedge

tail_width=0.3,shrink_factor=0.5

../../../_images/sphx_glr_fancyarrow_demo_001.png

Some arrowstyles only work with connection styles that generate aquadratic-spline segment. They arefancy,simple, andwedge.For these arrow styles, you must use the "angle3" or "arc3" connectionstyle.

If the annotation string is given, the patch is set to the bbox patchof the text by default.

fig,ax=plt.subplots(figsize=(3,3))ax.annotate("Test",xy=(0.2,0.2),xycoords='data',xytext=(0.8,0.8),textcoords='data',size=20,va="center",ha="center",arrowprops=dict(arrowstyle="simple",connectionstyle="arc3,rad=-0.2"))
annotations

As withtext, a box around the text can be drawn using thebboxargument.

fig,ax=plt.subplots(figsize=(3,3))ann=ax.annotate("Test",xy=(0.2,0.2),xycoords='data',xytext=(0.8,0.8),textcoords='data',size=20,va="center",ha="center",bbox=dict(boxstyle="round4",fc="w"),arrowprops=dict(arrowstyle="-|>",connectionstyle="arc3,rad=-0.2",fc="w"))
annotations

By default, the starting point is set to the center of the textextent. This can be adjusted withrelpos key value. The valuesare normalized to the extent of the text. For example, (0, 0) meanslower-left corner and (1, 1) means top-right.

fig,ax=plt.subplots(figsize=(3,3))ann=ax.annotate("Test",xy=(0.2,0.2),xycoords='data',xytext=(0.8,0.8),textcoords='data',size=20,va="center",ha="center",bbox=dict(boxstyle="round4",fc="w"),arrowprops=dict(arrowstyle="-|>",connectionstyle="arc3,rad=0.2",relpos=(0.,0.),fc="w"))ann=ax.annotate("Test",xy=(0.2,0.2),xycoords='data',xytext=(0.8,0.8),textcoords='data',size=20,va="center",ha="center",bbox=dict(boxstyle="round4",fc="w"),arrowprops=dict(arrowstyle="-|>",connectionstyle="arc3,rad=-0.2",relpos=(1.,0.),fc="w"))
annotations

Placing Artist at anchored Axes locations#

There are classes of artists that can be placed at an anchoredlocation in the Axes. A common example is the legend. This typeof artist can be created by using theOffsetBox class. A fewpredefined classes are available inmatplotlib.offsetbox and inmpl_toolkits.axes_grid1.anchored_artists.

frommatplotlib.offsetboximportAnchoredTextfig,ax=plt.subplots(figsize=(3,3))at=AnchoredText("Figure 1a",prop=dict(size=15),frameon=True,loc='upper left')at.patch.set_boxstyle("round,pad=0.,rounding_size=0.2")ax.add_artist(at)
annotations

Theloc keyword has same meaning as in the legend command.

A simple application is when the size of the artist (or collection ofartists) is known in pixel size during the time of creation. Forexample, If you want to draw a circle with fixed size of 20 pixel x 20pixel (radius = 10 pixel), you can utilizeAnchoredDrawingArea. The instanceis created with a size of the drawing area (in pixels), and arbitrary artistscan be added to the drawing area. Note that the extents of the artists that areadded to the drawing area are not related to the placement of the drawingarea itself. Only the initial size matters.

The artists that are added to the drawing area should not have atransform set (it will be overridden) and the dimensions of thoseartists are interpreted as a pixel coordinate, i.e., the radius of thecircles in above example are 10 pixels and 5 pixels, respectively.

frommatplotlib.patchesimportCirclefrommpl_toolkits.axes_grid1.anchored_artistsimportAnchoredDrawingAreafig,ax=plt.subplots(figsize=(3,3))ada=AnchoredDrawingArea(40,20,0,0,loc='upper right',pad=0.,frameon=False)p1=Circle((10,10),10)ada.drawing_area.add_artist(p1)p2=Circle((30,10),5,fc="r")ada.drawing_area.add_artist(p2)ax.add_artist(ada)
annotations

Sometimes, you want your artists to scale with the data coordinate (orcoordinates other than canvas pixels). You can useAnchoredAuxTransformBox class.This is similar toAnchoredDrawingArea except thatthe extent of the artist is determined during the drawing time respecting thespecified transform.

The ellipse in the example below will have width and heightcorresponding to 0.1 and 0.4 in data coordinates and will beautomatically scaled when the view limits of the Axes change.

frommatplotlib.patchesimportEllipsefrommpl_toolkits.axes_grid1.anchored_artistsimportAnchoredAuxTransformBoxfig,ax=plt.subplots(figsize=(3,3))box=AnchoredAuxTransformBox(ax.transData,loc='upper left')el=Ellipse((0,0),width=0.1,height=0.4,angle=30)# in data coordinates!box.drawing_area.add_artist(el)ax.add_artist(box)
annotations

Another method of anchoring an artist relative to a parent Axes or anchorpoint is via thebbox_to_anchor argument ofAnchoredOffsetbox. Thisartist can then be automatically positioned relative to another artist usingHPacker andVPacker:

frommatplotlib.offsetboximport(AnchoredOffsetbox,DrawingArea,HPacker,TextArea)fig,ax=plt.subplots(figsize=(3,3))box1=TextArea(" Test: ",textprops=dict(color="k"))box2=DrawingArea(60,20,0,0)el1=Ellipse((10,10),width=16,height=5,angle=30,fc="r")el2=Ellipse((30,10),width=16,height=5,angle=170,fc="g")el3=Ellipse((50,10),width=16,height=5,angle=230,fc="b")box2.add_artist(el1)box2.add_artist(el2)box2.add_artist(el3)box=HPacker(children=[box1,box2],align="center",pad=0,sep=5)anchored_box=AnchoredOffsetbox(loc='lower left',child=box,pad=0.,frameon=True,bbox_to_anchor=(0.,1.02),bbox_transform=ax.transAxes,borderpad=0.,)ax.add_artist(anchored_box)fig.subplots_adjust(top=0.8)
annotations

Note that, unlike inLegend, thebbox_transform is set toIdentityTransform by default

Coordinate systems for annotations#

Matplotlib Annotations support several types of coordinate systems. Theexamples inBasic annotation used thedata coordinate system;Some others more advanced options are:

Transform instance#

Transforms map coordinates into different coordinate systems, usually thedisplay coordinate system. SeeTransformations Tutorial for a detailedexplanation. Here Transform objects are used to identify the coordinatesystem of the corresponding points. For example, theAxes.transAxestransform positions the annotation relative to the Axes coordinates; thereforeusing it is identical to setting the coordinate system to "axes fraction":

fig,(ax1,ax2)=plt.subplots(nrows=1,ncols=2,figsize=(6,3))ax1.annotate("Test",xy=(0.2,0.2),xycoords=ax1.transAxes)ax2.annotate("Test",xy=(0.2,0.2),xycoords="axes fraction")
annotations

Another commonly usedTransform instance isAxes.transData. Thistransform is the coordinate system of the data plotted in the Axes. In thisexample, it is used to draw an arrow between related data points in twoAxes. We have passed an empty text because in this case, the annotationconnects data points.

x=np.linspace(-1,1)fig,(ax1,ax2)=plt.subplots(nrows=1,ncols=2,figsize=(6,3))ax1.plot(x,-x**3)ax2.plot(x,-3*x**2)ax2.annotate("",xy=(0,0),xycoords=ax1.transData,xytext=(0,0),textcoords=ax2.transData,arrowprops=dict(arrowstyle="<->"))
annotations

Artist instance#

Thexy value (orxytext) is interpreted as a fractional coordinate of thebounding box (bbox) of the artist:

fig,ax=plt.subplots(nrows=1,ncols=1,figsize=(3,3))an1=ax.annotate("Test 1",xy=(0.5,0.5),xycoords="data",va="center",ha="center",bbox=dict(boxstyle="round",fc="w"))an2=ax.annotate("Test 2",xy=(1,0.5),xycoords=an1,# (1, 0.5) of an1's bboxxytext=(30,0),textcoords="offset points",va="center",ha="left",bbox=dict(boxstyle="round",fc="w"),arrowprops=dict(arrowstyle="->"))
annotations

Note that you must ensure that the extent of the coordinate artist (an1 inthis example) is determined beforean2 gets drawn. Usually, this meansthatan2 needs to be drawn afteran1. The base class for all boundingboxes isBboxBase

Callable that returnsTransform ofBboxBase#

A callable object that takes the renderer instance as single argument, andreturns either aTransform or aBboxBase. For example, the returnvalue ofArtist.get_window_extent is a bbox, so this method is identicalto (2) passing in the artist:

fig,ax=plt.subplots(nrows=1,ncols=1,figsize=(3,3))an1=ax.annotate("Test 1",xy=(0.5,0.5),xycoords="data",va="center",ha="center",bbox=dict(boxstyle="round",fc="w"))an2=ax.annotate("Test 2",xy=(1,0.5),xycoords=an1.get_window_extent,xytext=(30,0),textcoords="offset points",va="center",ha="left",bbox=dict(boxstyle="round",fc="w"),arrowprops=dict(arrowstyle="->"))
annotations

Artist.get_window_extent is the bounding box of the Axes object and istherefore identical to setting the coordinate system to axes fraction:

fig,(ax1,ax2)=plt.subplots(nrows=1,ncols=2,figsize=(6,3))an1=ax1.annotate("Test1",xy=(0.5,0.5),xycoords="axes fraction")an2=ax2.annotate("Test 2",xy=(0.5,0.5),xycoords=ax2.get_window_extent)
annotations

Blended coordinate specification#

A blended pair of coordinate specifications -- the first for thex-coordinate, and the second is for the y-coordinate. For example, x=0.5 isin data coordinates, and y=1 is in normalized axes coordinates:

fig,ax=plt.subplots(figsize=(3,3))ax.annotate("Test",xy=(0.5,1),xycoords=("data","axes fraction"))ax.axvline(x=.5,color='lightgray')ax.set(xlim=(0,2),ylim=(1,2))
annotations

Any of the supported coordinate systems can be used in a blendedspecification. For example, the text "Anchored to 1 & 2" is positionedrelative to the twoText Artists:

fig,ax=plt.subplots(figsize=(3,3))t1=ax.text(0.05,.05,"Text 1",va='bottom',ha='left')t2=ax.text(0.90,.90,"Text 2",ha='right')t3=ax.annotate("Anchored to 1 & 2",xy=(0,0),xycoords=(t1,t2),va='bottom',color='tab:orange',)
annotations

text.OffsetFrom#

Sometimes, you want your annotation with some "offset points", not from theannotated point but from some other point or artist.text.OffsetFrom isa helper for such cases.

frommatplotlib.textimportOffsetFromfig,ax=plt.subplots(figsize=(3,3))an1=ax.annotate("Test 1",xy=(0.5,0.5),xycoords="data",va="center",ha="center",bbox=dict(boxstyle="round",fc="w"))offset_from=OffsetFrom(an1,(0.5,0))an2=ax.annotate("Test 2",xy=(0.1,0.1),xycoords="data",xytext=(0,-10),textcoords=offset_from,# xytext is offset points from "xy=(0.5, 0), xycoords=an1"va="top",ha="center",bbox=dict(boxstyle="round",fc="w"),arrowprops=dict(arrowstyle="->"))
annotations

Non-text annotations#

Using ConnectionPatch#

ConnectionPatch is like an annotation without text. Whileannotateis sufficient in most situations,ConnectionPatch is useful when you wantto connect points in different Axes. For example, here we connect the pointxy in the data coordinates ofax1 to pointxy in the data coordinatesofax2:

frommatplotlib.patchesimportConnectionPatchfig,(ax1,ax2)=plt.subplots(nrows=1,ncols=2,figsize=(6,3))xy=(0.3,0.2)con=ConnectionPatch(xyA=xy,coordsA=ax1.transData,xyB=xy,coordsB=ax2.transData)fig.add_artist(con)
annotations

Here, we added theConnectionPatch to thefigure(withadd_artist) rather than to either Axes. This ensures thatthe ConnectionPatch artist is drawn on top of both Axes, and is also necessarywhen usingconstrained_layoutfor positioning the Axes.

Zoom effect between Axes#

mpl_toolkits.axes_grid1.inset_locator defines some patch classes useful forinterconnecting two Axes.

../../../_images/sphx_glr_axes_zoom_effect_001.png

The code for this figure is atAxes zoom effect andfamiliarity withTransformations Tutorialis recommended.

Total running time of the script: (0 minutes 4.953 seconds)

Gallery generated by Sphinx-Gallery