如何为多个子图制作一个图例?
- 2025-01-07 08:44:00
- admin 原创
- 107
问题描述:
我正在用 Matplotlib 绘制相同类型的信息,但针对不同的国家/地区,并绘制多个子图。也就是说,我在 3x3 网格上有九个图,所有图的线条都相同(当然,每条线的值不同)。
但是,我还没有弄清楚如何将单个图例(因为所有九个子图都有相同的线条)只放在图形上一次。
我该如何做呢?
解决方案 1:
您还可以在最后一个轴上调用一个很好的函数get_legend_handles_labels()
(如果您对它们进行迭代),它将从label=
参数中收集您需要的所有内容:
handles, labels = ax.get_legend_handles_labels()
fig.legend(handles, labels, loc='upper center')
如果pyplot
正在使用接口而不是Axes
接口,请使用:
handles, labels = plt.gca().get_legend_handles_labels()
要从子图中删除图例,请参阅删除 matplotlib 图形上的图例。
要合并twinx
图例,请参阅使用 twinx() 的次轴:如何添加到图例。
解决方案 2:
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)
解决方案 3:
总结
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)
我注意到其他答案都没有显示带有单个图例的图像,该图例引用不同子图中的多条曲线,所以我必须向您展示一个......让您好奇......
现在,如果我够逗你了,下面是代码
from numpy import linspace
import 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.cycle
color_cycle = plt.rcParams['axes.prop_cycle']()
# I need some curves to plot
x = 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()
如果您想坚持使用官方的 Matplotlib API,那么这是完美的,否则请参阅下面的注释 1(有一个私有
方法......)这两条线
lines_labels = [ax.get_legend_handles_labels() for ax in fig.axes]
lines, labels = [sum(lol, []) for lol in zip(*lines_labels)]
值得解释,请参阅下文注释2。
我尝试了最受欢迎和接受的答案提出的方法,
# fig.legend(lines, labels, loc='upper center', ncol=4)
fig.legend(*a2.get_legend_handles_labels(),
loc='upper center', ncol=4)
这就是我得到的
注 1
如果您不介意使用matplotlib.legend
模块的私有方法...这真的要容易得多
from matplotlib.legend import _get_legend_handles_labels
...
fig.legend(*_get_legend_handles_and_labels(fig.axes), ...)
注 2
我把这两行棘手的代码封装在一个函数中,只有四行代码,但注释了很多
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)
我认识到这sum(list_of_lists, [])
是一种非常低效的展平列表列表的方法,但是 ① 我喜欢它的紧凑性;② 通常是几个子图中的几条曲线;③ Matplotlib 和效率?;-)
解决方案 4:
对于具有多个轴的 中的单个图例的自动定位figure
,就像使用 获得的图例一样subplots()
,以下解决方案非常有效:
plt.legend(lines, labels, loc = 'lower center', bbox_to_anchor = (0, -0.1, 1, 1),
bbox_transform = plt.gcf().transFigure)
使用bbox_to_anchor
和bbox_transform=plt.gcf().transFigure
,您将定义一个大小为 的新边界框figure
作为 的参考loc
。使用(0, -0.1, 1, 1)
将此边界框略微向下移动,以防止图例被放置在其他艺术家之上。
OBS:使用后fig.set_size_inches()
和使用前使用此解决方案fig.tight_layout()
解决方案 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()
解决方案 6:
在gboffi 和 Ben Usman 的答案的基础上进行构建:
如果在不同的子图中存在具有相同颜色和标签的不同线条,则可以执行以下操作:
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,
)
解决方案 7:
如果您使用条形图的子图,并且每个条形使用不同的颜色,则使用 自己创建工件可能会更快mpatches
。
假设您有四个不同颜色的条形图,分别为r
、m
、c
和k
,您可以按如下方式设置图例:
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
解决方案 8:
使用 Matplotlib 2.2.2,可以使用 gridspec 功能实现这一点。
在下面的示例中,目标是将四个子图以 2x2 的方式排列,并在底部显示图例。在底部创建一个“假”轴,以将图例放置在固定位置。然后关闭“假”轴,只显示图例。结果:
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()
解决方案 9:
该答案是对用户707650关于图例位置的答案的补充。
我第一次尝试用户 707650 的解决方案由于图例和子图标题重叠而失败。
事实上,重叠是由 引起的fig.tight_layout()
,它改变了子图的布局,而没有考虑图例。但是,fig.tight_layout()
是必要的。
为了避免重叠,我们可以fig.tight_layout()
通过 来为图形的图例留出空间fig.tight_layout(rect=(0,0,1,0.9))
。
tight_layout() 参数的描述。
解决方案 10:
在我的编码历程中,所有先前的答案都超出了我的理解范围,所以我只添加了另一个称为补丁的 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 Notebook中的同一个单元格中)。
结果
(我把这些名字改成了我自己的传奇。)