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...
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
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? ;-)