Quantcast
Viewing all articles
Browse latest Browse all 11

Answer by gboffi for How do I make a single legend for many subplots?

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.
enter image description here

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.
enter image description here


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


Viewing all articles
Browse latest Browse all 11

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>