如何确定 matplotlib 图例中的项目顺序?

我不得不重新排列图例中的物品,当我认为我不应该这样做的时候,我尝试:

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()

结果是屁股高过中间。我怎样才能得到正确的顺序?它不是由创造顺序决定的吗?

Code results in wrong legend item order

更新: 以下内容可用于强制执行订单。我认为这可能是最简单的方法,这似乎很尴尬。问题是什么决定了最初的顺序?

hh=[ht,h1,hb]
legend([ht,h1.patches[0],hb],[H.get_label() for H in hh])
99038 次浏览

The order is deterministic, but part of the private guts so can be changed at any time, see the code here which goes to here and eventually here. The children are the artists that have been added, hence the handle list is sorted by order they were added (this is a change in behavior with mpl34 or mpl35).

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.

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

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.

Figure after re-ordering labels

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])

A simple way to sort the labels based on another list goes like this: after you add all your plots and labels to the axes, do the following steps before displaying the label.

handles,labels = ax.get_legend_handles_labels()
sorted_legends= [x for _,x in sorted(zip(k,labels),reverse=True)]
#sort the labels based on the list k
#reverse=True sorts it in descending order
sorted_handles=[x for _,x in sorted(zip(k,handles),reverse=True)]
#to sort the colored handles
ax.legend(sorted_handles,sorted_legends,bbox_to_anchor=(1,0.5), loc='center left')
#display the legend on the side of your plot.

Example:

from matplotlib import pyplot as plt
import numpy as np




rollno=np.arange(1,11)
marks_math=np.random.randint(30,100,10)
marks_science=np.random.randint(30,100,10)
marks_english=np.random.randint(30,100,10)
print("Roll No. of the students: ",rollno)
print("Marks in Math: ",marks_math)
print("Marks in Science: ",marks_science)
print("Marks in English: ",marks_english)
average=[np.average(marks_math),np.average(marks_science),np.average(marks_english)] #storing the average of each subject in a list


fig1=plt.figure()
ax=fig1.add_subplot(1,1,1)
ax.set_xlabel("Roll No.")
ax.set_ylabel("Marks")
ax.plot(rollno,marks_math,c="red",label="marks in math, Mean="+str(average[0]))
ax.plot(rollno,marks_science,c="green",label="marks in science, Mean="+str(average[1]))
ax.plot(rollno,marks_english,c="blue",label="marks in english, Mean="+str(average[2]))
#ax.legend() # This would display the legend with red color first, green second and the blue at last


#but we want to sort the legend based on the average marks which must order the labels based on average sorted in decending order
handles,labels=ax.get_legend_handles_labels()
sorted_legends= [x for _,x in sorted(zip(average,labels),reverse=True)] #sort the labels based on the average which is on a list
sorted_handles=[x for _,x in sorted(zip(average,handles),reverse=True)] #sort the handles based on the average which is on a list
ax.legend(sorted_handles,sorted_legends,bbox_to_anchor=(1,0.5), loc='center left') #display the handles and the labels on the side
plt.show()
plt.close()

For a run that had the values like this:

Roll No. of the students:  [ 1  2  3  4  5  6  7  8  9 10]
Marks in Math:  [66 46 44 70 37 72 93 32 81 84]
Marks in Science:  [71 99 99 40 59 80 72 98 91 81]
Marks in English:  [46 64 74 33 86 49 84 92 67 35]
The average in each subject [62.5, 79.0, 63.0]

The labels would have come in the order red, green, and blue as they come in that order in the plot but we want to sort them based on the average which would give us an order green, blue and red.

Check this image

Playing off of Ian Hincks's answer, the changing of the order of the legend elements can be done in one line with a nested list comprehension. This avoids the need to name intermediate variables and reduces code duplication.

plt.legend(*(
[ x[i] for i in [2,1,0] ]
for x in plt.gca().get_legend_handles_labels()
), handletextpad=0.75, loc='best')

I threw in some additional arguments at the end to illustrate that the plt.legend() function does not need to be called separately for formatting and ordering of the elements.