Numerical & Scientific Computing with Python: Legends and Annotations

## Matplotlib Tutorial, Adding Legends and Annotations

If we look at the line graphs of our previous examples, we realize, that we have to look into our code to understand what kind of function is depicted. This information should be available in the diagram for convenience. Legends are used for this purpose. The word stems from Latin and meand "to be read". So it "has to be read" to understand the graph.

Before legends have been used in mathematical graphs, they have been used in maps. Legends - as they are found in maps - describe the pictorial language or symbology of the map. Legends are used in line graphs to explain the function or the values underlying the different lines of the graph.

We will demonstrate in the following simple example how we can place a legend on a graph. A legend contains one or more entries. Every entry consists of a key and a label.

The pyplot function

legend(*args, **kwargs)


places a legend on the axes.

All we have to do to create a legend for lines, which already exist on the axes, is to simply call the function "legend" with an iterable of strings, one for each legend item. For example:

The easiest way to create a line graph in Matplitlib.

# next line only needed if working with "ipython notebook":
%matplotlib inline
import numpy as np
import matplotlib.pyplot as plt
ax = plt.gca()
ax.plot([1, 2, 3, 4])
ax.legend(['A simple line'])

This gets us the following result:
<matplotlib.legend.Legend at 0x7fa12108bc18>

If we add a label to the plot function, the value will be used as the label in the legend command. The only argument the legend function will still need is the location argument "loc":

If we add a label to the plot function, the values will be used in the legend command:

import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(0, 25, 1000)
y1 = np.sin(x)
y2 = np.cos(x)
plt.plot(x, y1, '-b', label='sine')
plt.plot(x, y2, '-r', label='cosine')
plt.legend(loc='upper left')
plt.ylim(-1.5, 2.0)
plt.show()

import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(0, 25, 1000)
F1 = np.sin(0.5 * X)
F2 = 3 * np.cos(0.8*X)
plt.plot(X, F1, label="$sin(0.5 * x)$")
plt.plot(X, F2, label="$3 sin(x)$")
plt.legend(loc='upper right')

The above code returned the following:
<matplotlib.legend.Legend at 0x7fa120f17898>

In many cases we don't know what the result may look like before you plot it. It could be for example, that the legend will overshadow an important part of the lines. If you don't know what the data may look like, it may be best to use 'best' as the argument for loc. Matplotlib will automatically try to find the best possible location for the legend:

import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(0, 25, 1000)
F1 = np.sin(0.5 * X)
F2 = 3 * np.cos(0.8*X)
plt.plot(X, F1, label="$sin(0.5 * x)$")
plt.plot(X, F2, label="$3 sin(x)$")
plt.legend(loc='best')

The previous Python code returned the following output:
<matplotlib.legend.Legend at 0x7fa120db1cf8>

We can see in the following two examples, that

loc='best'
can work very well:

import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-2 * np.pi, 2 * np.pi, 70, endpoint=True)
F1 = np.sin(0.5*X)
F2 = -3 * np.cos(0.8*X)
plt.xticks( [-6.28, -3.14, 3.14, 6.28],
[r'$-2\pi$', r'$-\pi$', r'$+\pi$', r'$+2\pi$'])
plt.yticks([-3, -1, 0, +1, 3])
plt.plot(X, F1, label="$sin(0.5x)$")
plt.plot(X, F2, label="$-3 cos(0.8x)$")
plt.legend(loc='best')
plt.show()

import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-2 * np.pi, 2 * np.pi, 70, endpoint=True)
F1 = np.sin(0.5*X)
F2 = 3 * np.cos(0.8*X)
plt.xticks( [-6.28, -3.14, 3.14, 6.28],
[r'$-2\pi$', r'$-\pi$', r'$+\pi$', r'$+2\pi$'])
plt.yticks([-3, -1, 0, +1, 3])
plt.plot(X, F1, label="$sin(0.5x)$")
plt.plot(X, F2, label="$3 cos(0.8x)$")
plt.legend(loc='best')
plt.show()


### Annotations

Of course, the sinus function has "boring" and interesting values. Let's assume that you are especially interested in the value of $3 * sin(3 * pi/4)$.

import numpy as np
print(3 * np.sin(3 * np.pi/4))

2.12132034356


The numerical result doesn't look special, but if we do a symbolic calculation for the above expression we get $\frac{3}{\sqrt{2}}$. Now we want to label this point on the graph. We can do this with the annotate function. We want to annotate our graph with this point.

import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-2 * np.pi, 3 * np.pi, 70, endpoint=True)
F1 = np.sin(X)
F2 = 3 * np.sin(X)
ax = plt.gca()
plt.xticks( [-6.28, -3.14, 3.14, 6.28],
[r'$-2\pi$', r'$-\pi$', r'$+\pi$', r'$+2\pi$'])
plt.yticks([-3, -1, 0, +1, 3])
x = 3 * np.pi / 4
plt.scatter([x,],[3 * np.sin(x),], 50, color ='blue')
plt.annotate(r'$(3\sin(\frac{3\pi}{4}),\frac{3}{\sqrt{2}})$',
xy=(x, 3 * np.sin(x)),
xycoords='data',
xytext=(+20, +20),
textcoords='offset points',
fontsize=16,
arrowprops=dict(facecolor='blue'))
plt.plot(X, F1, label="$sin(x)$")
plt.plot(X, F2, label="$3 sin(x)$")
plt.legend(loc='lower left')
plt.show()


We have to provide some informations to the parameters of annotate, we have used in our previous example.

Parameter Meaning
xy coordinates of the arrow tip
xytext coordinates of the text location

The xy and the xytext locations of our example are in data coordinates. There are other coordinate systems available we can choose. The coordinate system of xy and xytext can be specified string values assigned to xycoords and textcoords. The default value is 'data':

String Value 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 axes
axes pixels pixels from lower left corner of axes
axes fraction 0,0 is lower left of axes and 1,1 is upper right
data use the axes data coordinate system

Additionally, we can also specify the properties of the arrow. To do so, we have to provide a dictionary of arrow properties to the parameter arrowprops:

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 from the annotated point and text
**kwargs any key for matplotlib.patches.Polygon, e.g., facecolor

In the following example, we will change the appearance of the arrow of our previous example:

import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-2 * np.pi, 3 * np.pi, 70, endpoint=True)
F1 = np.sin(X)
F2 = 3 * np.sin(X)
ax = plt.gca()
plt.xticks( [-6.28, -3.14, 3.14, 6.28],
[r'$-2\pi$', r'$-\pi$', r'$+\pi$', r'$+2\pi$'])
plt.yticks([-3, -1, 0, +1, 3])
x = 3 * np.pi / 4
plt.scatter([x,],[3 * np.sin(x),], 50, color ='blue')
plt.annotate(r'$(3\sin(\frac{3\pi}{4}),\frac{3}{\sqrt{2}})$',
xy=(x, 3 * np.sin(x)),
xycoords='data',
xytext=(+20, +20),
textcoords='offset points',
fontsize=16,
plt.plot(X, F1, label="$sin(x)$")
plt.plot(X, F2, label="$3 sin(x)$")