How is order of items in matplotlib legend determined?
I am having to reorder items in a legend, when I don't think I should have to. I try:
from pylab import *
clf()
ax=gca()
ht=ax.add_patch(Rectangle((1,1),1,1,color='r',label='Top',alpha=.1))
h1=ax.bar(1,2,label='Middle')
hb=ax.add_patch(Rectangle((1,1),1,1,color='k',label='Bottom',alpha=.11))
legend()
show()
and end up with Bottom above Middle. How can I get the right order? Is it not determined by creation order?
Update: The following can be used to force the order. I think this may be the simplest way to do it, and that seems awkward. The question is what determines the original order?
hh=[ht,h1,hb]
legend([ht,h1.patches[0],hb],[H.get_label() for H in hh])
Here's a quick snippet to sort the entries in a legend. It assumes that you've already added your plot elements with a label, for example, something like
ax.plot(..., label='label1')
ax.plot(..., label='label2')
and then the main bit:
handles, labels = ax.get_legend_handles_labels()
# sort both labels and handles by labels
labels, handles = zip(*sorted(zip(labels, handles), key=lambda t: t[0]))
ax.legend(handles, labels)
This is just a simple adaptation from the code listed at http://matplotlib.org/users/legend_guide.html
A slight variation on some other aswers. The list order
should have the same length as the number of legend items, and specifies the new order manually.
handles, labels = plt.gca().get_legend_handles_labels()
order = [0,2,1]
plt.legend([handles[idx] for idx in order],[labels[idx] for idx in order])
The order is deterministic, but part of the private guts so can be changed at any time, see the code here (the self.*
elements are lists of the artists that have been added, hence the handle list is sorted first by type, second by order they were added).
If you want to explicitly control the order of the elements in your legend then assemble a list of handlers and labels like you did in the your edit.
The following function makes control of legend order easy and readable.
You can specify the order you want by label. It will find the legend handles and labels, drop duplicate labels, and sort or partially sort them according to your given list (order
). So you use it like this:
reorderLegend(ax,['Top', 'Middle', 'Bottom'])
Details are below.
# Returns tuple of handles, labels for axis ax, after reordering them to conform to the label order `order`, and if unique is True, after removing entries with duplicate labels.
def reorderLegend(ax=None,order=None,unique=False):
if ax is None: ax=plt.gca()
handles, labels = ax.get_legend_handles_labels()
labels, handles = zip(*sorted(zip(labels, handles), key=lambda t: t[0])) # sort both labels and handles by labels
if order is not None: # Sort according to a given list (not necessarily complete)
keys=dict(zip(order,range(len(order))))
labels, handles = zip(*sorted(zip(labels, handles), key=lambda t,keys=keys: keys.get(t[0],np.inf)))
if unique: labels, handles= zip(*unique_everseen(zip(labels,handles), key = labels)) # Keep only the first of each handle
ax.legend(handles, labels)
return(handles, labels)
def unique_everseen(seq, key=None):
seen = set()
seen_add = seen.add
return [x for x,k in zip(seq,key) if not (k in seen or seen_add(k))]
The function in updated form is in cpblUtilities.mathgraph
at https://gitlab.com/cpbl/cpblUtilities/blob/master/mathgraph.py
Usage is thus like this:
fig, ax = plt.subplots(1)
ax.add_patch(Rectangle((1,1),1,1,color='r',label='Top',alpha=.1))
ax.bar(1,2,label='Middle')
ax.add_patch(Rectangle((.8,.5),1,1,color='k',label='Bottom',alpha=.1))
legend()
reorderLegend(ax,['Top', 'Middle', 'Bottom'])
show()
The optional unique
argument makes sure to drop duplicate plot objects which have the same label.