我如何为许多子情节制作一个图例?

我绘制了相同类型的信息,但针对不同的国家,使用Matplotlib绘制了多个子图。也就是说,我在一个3x3网格上有9个图,所有的线都是相同的(当然,每条线的值不同)。

然而,我还没有弄清楚如何将一个图例(因为所有九个子图都有相同的线条)放在图形上一次。

我怎么做呢?

281770 次浏览

figlegend可能就是你要找的:matplotlib.pyplot.figlegend

一个例子是图图例演示

另一个例子:

plt.figlegend(lines, labels, loc = 'lower center', ncol=5, labelspacing=0.)

或者:

fig.legend(lines, labels, loc = (0.5, 0), ncol=5)

你只需要在循环之外请求一次图例。

例如,在这种情况下,我有4个子情节,具有相同的线,和一个图例。

from matplotlib.pyplot import *


ficheiros = ['120318.nc', '120319.nc', '120320.nc', '120321.nc']


fig = figure()
fig.suptitle('concentration profile analysis')


for a in range(len(ficheiros)):
# dados is here defined
level = dados.variables['level'][:]


ax = fig.add_subplot(2,2,a+1)
xticks(range(8), ['0h','3h','6h','9h','12h','15h','18h','21h'])
ax.set_xlabel('time (hours)')
ax.set_ylabel('CONC ($\mu g. m^{-3}$)')


for index in range(len(level)):
conc = dados.variables['CONC'][4:12,index] * 1e9
ax.plot(conc,label=str(level[index])+'m')


dados.close()


ax.legend(bbox_to_anchor=(1.05, 0), loc='lower left', borderaxespad=0.)
# it will place the legend on the outer right-hand side of the last axes


show()

对于在具有多个轴的figure中自动定位单个图例,就像使用subplots()获得的那样,以下解决方案非常有效:

plt.legend(lines, labels, loc = 'lower center', bbox_to_anchor = (0, -0.1, 1, 1),
bbox_transform = plt.gcf().transFigure)

使用bbox_to_anchorbbox_transform=plt.gcf().transFigure,你定义了一个新的边界框,其大小与你的__abc2相同,作为loc的引用。使用(0, -0.1, 1, 1)将这个边界框略微向下移动,以防止图例被放置在其他艺术家之上。

OBS:使用这个解决方案你使用fig.set_size_inches()之前你使用fig.tight_layout()

还有一个很好的函数get_legend_handles_labels(),你可以在最后一个轴上调用(如果你遍历它们),它会收集你从label=参数中需要的所有东西:

handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')

这个答案是在图例位置上对user707650的回答的补充。

我第一次尝试user707650的解决方案失败了,因为图例和副图的标题重叠。

事实上,重叠是由fig.tight_layout()引起的,它在不考虑图形图例的情况下改变了子图的布局。然而,fig.tight_layout()是必要的。

为了避免重叠,我们可以通过fig.tight_layout(rect=(0,0,1,0.9))告诉fig.tight_layout()为图形的图例留出空格。

tight_layout()参数的描述

使用Matplotlib 2.2.2,可以使用gridspec特性来实现这一点。

在下面的例子中,目标是以2x2的方式排列四个子情节,并在底部显示图例。在底部创建一个“人造”轴,将图例放置在固定的位置。“人造”轴然后关闭,所以只有传说显示。结果:

Some plot produced by Matplotlib

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec


# Gridspec demo
fig = plt.figure()
fig.set_size_inches(8, 9)
fig.set_dpi(100)


rows   = 17 # The larger the number here, the smaller the spacing around the legend
start1 = 0
end1   = int((rows-1)/2)
start2 = end1
end2   = int(rows-1)


gspec = gridspec.GridSpec(ncols=4, nrows=rows)


axes = []
axes.append(fig.add_subplot(gspec[start1:end1, 0:2]))
axes.append(fig.add_subplot(gspec[start2:end2, 0:2]))
axes.append(fig.add_subplot(gspec[start1:end1, 2:4]))
axes.append(fig.add_subplot(gspec[start2:end2, 2:4]))
axes.append(fig.add_subplot(gspec[end2, 0:4]))


line, = axes[0].plot([0, 1], [0, 1], 'b')         # Add some data
axes[-1].legend((line,), ('Test',), loc='center') # Create legend on bottommost axis
axes[-1].set_axis_off()                           # Don't show the bottom-most axis


fig.tight_layout()
plt.show()

如果你正在使用柱状图的子图,每个柱状图都有不同的颜色,那么使用mpatches自己创建工件可能会更快。

假设你有四个不同颜色的条,分别是rmck,你可以这样设置图例:

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt
labels = ['Red Bar', 'Magenta Bar', 'Cyan Bar', 'Black Bar']




#####################################
# Insert code for the subplots here #
#####################################




# Now, create an artist for each color
red_patch = mpatches.Patch(facecolor='r', edgecolor='#000000') # This will create a red bar with black borders, you can leave out edgecolor if you do not want the borders
black_patch = mpatches.Patch(facecolor='k', edgecolor='#000000')
magenta_patch = mpatches.Patch(facecolor='m', edgecolor='#000000')
cyan_patch = mpatches.Patch(facecolor='c', edgecolor='#000000')
fig.legend(handles = [red_patch, magenta_patch, cyan_patch, black_patch], labels=labels,
loc="center right",
borderaxespad=0.1)
plt.subplots_adjust(right=0.85) # Adjust the subplot to the right for the legend

博士TL;

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)

我注意到,没有一个答案显示的是一个图例在不同的子图中引用许多曲线的图像,所以我必须给你看一个…为了让你好奇……

Enter image description here

现在,你想要来查看代码,不是吗?

from numpy import linspace
import matplotlib.pyplot as plt


# Calling the axes.prop_cycle returns an itertoools.cycle


color_cycle = plt.rcParams['axes.prop_cycle']()


# I need some curves to plot


x = linspace(0, 1, 51)
f1 = x*(1-x)   ; lab1 = 'x - x x'
f2 = 0.25-f1   ; lab2 = '1/4 - x + x x'
f3 = x*x*(1-x) ; lab3 = 'x x - x x x'
f4 = 0.25-f3   ; lab4 = '1/4 - x x + x x x'


# Let's plot our curves (note the use of color cycle, otherwise the curves colors in
# The two subplots will be repeated and a single legend becomes difficult to read)
fig, (a13, a24) = plt.subplots(2)


a13.plot(x, f1, label=lab1, **next(color_cycle))
a13.plot(x, f3, label=lab3, **next(color_cycle))
a24.plot(x, f2, label=lab2, **next(color_cycle))
a24.plot(x, f4, label=lab4, **next(color_cycle))


# So far so good. 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, we invoke the legend (that you probably would like to customize...)


fig.legend(lines, labels)
plt.show()

两条线

lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]

值得解释一下——为了达到这个目的,我把棘手的部分封装在一个函数中,只有四行代码,但是严重注释了

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)

PS:我认识到sum(list_of_lists, [])是一个非常低效的方法来扁平化一个列表的列表,但是①我喜欢它的紧凑性,②通常是几个子图中的几个曲线和③Matplotlib和效率?;-)

如果您想坚持使用官方的Matplotlib API,这是非常完美的。

另一方面,如果你不介意使用matplotlib.legend模块的私有方法…这真的非常非常简单

from matplotlib.legend import _get_legend_handles_labels
...


fig.legend(*_get_legend_handles_and_labels(fig.axes), ...)

完整的解释可以在.../matplotlib/axes/_axes.pyAxes.get_legend_handles_labels的源代码中找到

构建在gboffi和 本·乌斯曼的回答之上:

如果在不同的子图中有不同的线,但颜色和标签相同,你可以这样做:

labels_handles = {
label: handle for ax in fig.axes for handle, label in zip(*ax.get_legend_handles_labels())
}


fig.legend(
labels_handles.values(),
labels_handles.keys(),
loc = "upper center",
bbox_to_anchor = (0.5, 0),
bbox_transform = plt.gcf().transFigure,
)

所有之前的答案都超出了我的理解,在我的编码旅程的这个状态下,所以我只是添加了另一个Matplotlib方面,称为补丁:

import matplotlib.patches as mpatches


first_leg = mpatches.Patch(color='red', label='1st plot')
second_leg = mpatches.Patch(color='blue', label='2nd plot')
thrid_leg = mpatches.Patch(color='green', label='3rd plot')
plt.legend(handles=[first_leg ,second_leg ,thrid_leg ])

补丁方面把我需要的所有数据放在我的最终图(这是一个线状图,在Jupyter笔记本的同一个单元格中组合了三个不同的线状图)。

结果

(我更改了我自己命名的图例的名称。)

I changed the names form what I named my own