TL;DR
lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]fig.legend(lines, labels)
I have noticed that none of the other answers displays an image with a single legend referencing many curves in different subplots, so I have to show you one... to make you curious...
Image may be NSFW.
Clik here to view.
Now, if I've teased you enough, here it is the code
from numpy import linspaceimport matplotlib.pyplot as plt# each Axes has a brand new prop_cycle, so to have differently# colored curves in different Axes, we need our own prop_cycle# Note: we CALL the axes.prop_cycle to get an itertoools.cyclecolor_cycle = plt.rcParams['axes.prop_cycle']()# I need some curves to plotx = linspace(0, 1, 51)functs = [x*(1-x), x**2*(1-x), 0.25-x*(1-x), 0.25-x**2*(1-x)] labels = ['$x-x²$', '$x²-x³$','$\\frac{1}{4} - (x-x²)$', '$\\frac{1}{4} - (x²-x³)$']# the plot, fig, (a1,a2) = plt.subplots(2)for ax, f, l, cc in zip((a1,a1,a2,a2), functs, labels, color_cycle): ax.plot(x, f, label=l, **cc) ax.set_aspect(2) # superfluos, but nice# So far, nothing special except the managed prop_cycle. Now the trick:lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]# Finally, the legend (that maybe you'll customize differently)fig.legend(lines, labels, loc='upper center', ncol=4)plt.show()
If you want to stick with the official Matplotlib API, this isperfect, otherwise see note no.1 below (there is a privatemethod...)
The two lines
lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]
deserve an explanation, see note 2 below.
I tried the method proposed by the most up-voted and accepted answer,
# fig.legend(lines, labels, loc='upper center', ncol=4) fig.legend(*a2.get_legend_handles_labels(), loc='upper center', ncol=4)
and this is what I've got
Image may be NSFW.
Clik here to view.
Note 1
If you don't mind using a private method of the matplotlib.legend
module ... it's really much much much easier
from matplotlib.legend import _get_legend_handles_labels...fig.legend(*_get_legend_handles_and_labels(fig.axes), ...)
Note 2
I have encapsulated the two tricky lines in a function, just four lines of code, but heavily commented
def fig_legend(fig, **kwdargs): # Generate a sequence of tuples, each contains # - a list of handles (lohand) and # - a list of labels (lolbl) tuples_lohand_lolbl = (ax.get_legend_handles_labels() for ax in fig.axes) # E.g., a figure with two axes, ax0 with two curves, ax1 with one curve # yields: ([ax0h0, ax0h1], [ax0l0, ax0l1]) and ([ax1h0], [ax1l0]) # The legend needs a list of handles and a list of labels, # so our first step is to transpose our data, # generating two tuples of lists of homogeneous stuff(tolohs), i.e., # we yield ([ax0h0, ax0h1], [ax1h0]) and ([ax0l0, ax0l1], [ax1l0]) tolohs = zip(*tuples_lohand_lolbl) # Finally, we need to concatenate the individual lists in the two # lists of lists: [ax0h0, ax0h1, ax1h0] and [ax0l0, ax0l1, ax1l0] # a possible solution is to sum the sublists - we use unpacking handles, labels = (sum(list_of_lists, []) for list_of_lists in tolohs) # Call fig.legend with the keyword arguments, return the legend object return fig.legend(handles, labels, **kwdargs)
I recognize that sum(list_of_lists, [])
is a really inefficient method to flatten a list of lists, but ① I love its compactness, ② usually is a few curves in a few subplots and ③ Matplotlib and efficiency? ;-)