How do you create a legend for a contour plot?
Asked Answered
T

5

46

I can't seem to find the answer anywhere! I found a discussion here, but trying this I get a TypeError: 'NoneType' object is not iterable:

>>> import numpy as np
>>> import matplotlib.pyplot as plt
>>> x, y = np.meshgrid(np.arange(10),np.arange(10))
>>> z = x + y
>>> cs = plt.contourf(x,y,z,levels=[2,3])
>>> cs.collections[0].set_label('test')
>>> plt.legend()
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/pyplot.py", line 2791, in legend
    ret =  gca().legend(*args, **kwargs)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/axes.py", line 4475, in legend
    self.legend_ = mlegend.Legend(self, handles, labels, **kwargs)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/legend.py", line 365, in __init__
    self._init_legend_box(handles, labels)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/legend.py", line 627, in _init_legend_box
    handlebox)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/legend_handler.py", line 110, in __call__
    handlebox.get_transform())
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/legend_handler.py", line 352, in create_artists
    width, height, fontsize)
  File "/opt/local/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/site-packages/matplotlib/legend_handler.py", line 307, in get_sizes
    size_max = max(orig_handle.get_sizes())*legend.markerscale**2
TypeError: 'NoneType' object is not iterable

EDIT: I'm looking for something like this:

kamland solar delta chi-squared map

Theine answered 7/5, 2012 at 23:3 Comment(4)
A legend on a contourf makes no sense to me. Do you mean colorbar?Mistassini
I've updated my question with an example of what I'm looking for.Theine
I guess what I'm looking for is a way to both label the contour lines from a contour() plot and label the filled regions from a contourf() plot.Theine
Also, I get the same error when trying to label a contour() plot.Theine
P
46

You can create proxy artists to make the legend:

import numpy as np
import matplotlib.pyplot as plt
x, y = np.meshgrid(np.arange(10),np.arange(10))
z = np.sqrt(x**2 + y**2)
cs = plt.contourf(x,y,z,levels=[2,3,4,6])

proxy = [plt.Rectangle((0,0),1,1,fc = pc.get_facecolor()[0]) 
    for pc in cs.collections]

plt.legend(proxy, ["range(2-3)", "range(3-4)", "range(4-6)"])
plt.show()

enter image description here

Problematic answered 8/5, 2012 at 1:0 Comment(2)
Matplotlib has also support for hatched contours, which you could include by changing the proxy to proxy = [pylab.Rectangle((0, 0), 1, 1, fc=pc.get_facecolor()[0], hatch=pc.get_hatch()) for pc in im.collections]Foreworn
Since the collections attribute was deprecated at Matplotlib v3.8, this should be updated with something like proxy = [plt.Rectangle((0, 0), 1, 1, fc=fc) for fc in cs.get_facecolors()]Harbin
C
61

You could also do it directly with the lines of the contour, without using proxy artists.

import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.mlab as mlab
import matplotlib.pyplot as plt

matplotlib.rcParams['xtick.direction'] = 'out'
matplotlib.rcParams['ytick.direction'] = 'out'

delta = 0.025
x = np.arange(-3.0, 3.0, delta)
y = np.arange(-2.0, 2.0, delta)
X, Y = np.meshgrid(x, y)
Z1 = mlab.bivariate_normal(X, Y, 1.0, 1.0, 0.0, 0.0)
Z2 = mlab.bivariate_normal(X, Y, 1.5, 0.5, 1, 1)
# difference of Gaussians
Z = 10.0 * (Z2 - Z1)



# Create a simple contour plot with labels using default colors.  The
# inline argument to clabel will control whether the labels are draw
# over the line segments of the contour, removing the lines beneath
# the label
plt.figure()
CS = plt.contour(X, Y, Z)
plt.clabel(CS, inline=1, fontsize=10)
plt.title('Simplest default with labels')

labels = ['line1', 'line2','line3','line4',
           'line5', 'line6']
for i in range(len(labels)):
    CS.collections[i].set_label(labels[i])

plt.legend(loc='upper left')

Will produce:

figure with legend and labels

However, you might also want to look into annotations for your own need. In my opinion it will give you a more fine grained control on where and what you write on the image, here is the same example with some annotation:

### better with annotation, more flexible
plt.figure(2)
CS = plt.contour(X, Y, Z)
plt.clabel(CS, inline=1, fontsize=10)
plt.title('Simplest default with labels')

plt.annotate('some text here',(1.4,1.6))
plt.annotate('some text there',(-2,-1.5))

Figure with annotations

Caty answered 8/5, 2012 at 7:5 Comment(2)
I wanted to add, you can use the values of the contours with for i, label in enumerate(CS.cvalues): CS.collections[i].set_label(label)Etymology
set_label() does not render correctly with matplotlib 3.5.2. Use solutions below or legend_elements().Gariepy
P
46

You can create proxy artists to make the legend:

import numpy as np
import matplotlib.pyplot as plt
x, y = np.meshgrid(np.arange(10),np.arange(10))
z = np.sqrt(x**2 + y**2)
cs = plt.contourf(x,y,z,levels=[2,3,4,6])

proxy = [plt.Rectangle((0,0),1,1,fc = pc.get_facecolor()[0]) 
    for pc in cs.collections]

plt.legend(proxy, ["range(2-3)", "range(3-4)", "range(4-6)"])
plt.show()

enter image description here

Problematic answered 8/5, 2012 at 1:0 Comment(2)
Matplotlib has also support for hatched contours, which you could include by changing the proxy to proxy = [pylab.Rectangle((0, 0), 1, 1, fc=pc.get_facecolor()[0], hatch=pc.get_hatch()) for pc in im.collections]Foreworn
Since the collections attribute was deprecated at Matplotlib v3.8, this should be updated with something like proxy = [plt.Rectangle((0, 0), 1, 1, fc=fc) for fc in cs.get_facecolors()]Harbin
A
4

Adding to this answer to make it less manual:

import numpy as np
import matplotlib.pyplot as plt
x, y = np.meshgrid(np.arange(10),np.arange(10))
z = np.sqrt(x**2 + y**2)
levels=[2,3,4,6]
cs = plt.contourf(x,y,z,levels=levels)

proxy = [plt.Rectangle((0,0),1,1,fc = pc.get_facecolor()[0]) 
    for pc in cs.collections]

plt.legend(proxy, [f"{lower:2.1f} - {upper:2.1f}" for lower, upper in zip(levels[:-1], levels[1:])])
plt.show()

Contour plot

Alister answered 3/6, 2022 at 7:16 Comment(1)
Since the collections attribute was deprecated at Matplotlib v3.8, this should be updated with something like proxy = [plt.Rectangle((0, 0), 1, 1, fc=fc) for fc in cs.get_facecolors()]Harbin
H
2

Another option is to use the legend_elements method, which will work for both filled and unfilled contours:

import numpy as np
import matplotlib.pyplot as plt

x, y = np.meshgrid(np.arange(10),np.arange(10))
z = np.sqrt(x**2 + y**2)

fig, ax = plt.subplots()

cs_filled = ax.contourf(x, y, z, levels=[2, 3, 4, 6])
handles_filled, labels = cs_filled.legend_elements()

cs_unfilled = ax.contour(x, y, np.flip(z, axis=1), levels=[3, 4, 6])
handles_unfilled, labels = cs_unfilled.legend_elements()

ax.legend(handles_filled + handles_unfilled,
          ["range(2-3)", "range(3-4)", "range(4-6)", "3", "4", "6"],
          ncols=2)

plt.show()

enter image description here

Harbin answered 19/1, 2024 at 12:11 Comment(0)
A
1

I had a similar question but needed to go a bit beyond HYRY's answer. To make a package user friendly I wanted ax.legend() to work without requiring users to pass any handles, which can be achieved by passing the label on to the proxy

proxy = plt.Rectangle((0, 0), 1, 1, fc='red', label='some label')

and then adding the proxy to the axis' patches:

ax.patches += [proxy]

(do ax = plt.gca() to get the current axis)

This is described in more detail in this answer.

Albarran answered 14/7, 2019 at 20:16 Comment(0)

© 2022 - 2025 — McMap. All rights reserved.