问题:如何使用matplotlib为多个子图制作一个图例?

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

但是,我还没有想出如何仅在图上放置一个图例(因为所有9个子图都有相同的行)。

我怎么做?

I am plotting the same type of information, but for different countries, with multiple subplots with matplotlib. That is, I have 9 plots on a 3×3 grid, all with the same for lines (of course, different values per line).

However, I have not figured out how to put a single legend (since all 9 subplots have the same lines) on the figure just once.

How do I do that?


回答 0

get_legend_handles_labels()您还可以在最后一个轴上调用一个不错的函数(如果对其进行迭代),该label=函数将从参数中收集所需的所有信息:

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

There is also a nice function get_legend_handles_labels() you can call on the last axis (if you iterate over them) that would collect everything you need from label= arguments:

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

回答 1

figlegend可能就是您要寻找的东西:http ://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.figlegend

此处的示例:http : //matplotlib.org/examples/pylab_examples/figlegend_demo.html

另一个例子:

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

要么:

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

figlegend may be what you’re looking for: http://matplotlib.org/api/pyplot_api.html#matplotlib.pyplot.figlegend

Example here: http://matplotlib.org/examples/pylab_examples/figlegend_demo.html

Another example:

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

or:

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

回答 2

为了自动将单个图例定位在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()

For the automatic positioning of a single legend in a figure with many axes, like those obtained with subplots(), the following solution works really well:

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

With bbox_to_anchor and bbox_transform=plt.gcf().transFigure you are defining a new bounding box of the size of your figureto be a reference for loc. Using (0,-0.1,1,1) moves this bouding box slightly downwards to prevent the legend to be placed over other artists.

OBS: use this solution AFTER you use fig.set_size_inches() and BEFORE you use fig.tight_layout()


回答 3

您只需要在循环外询问一次图例。

例如,在这种情况下,我有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()

You just have to ask for the legend once, outside of your loop.

For example, in this case I have 4 subplots, with the same lines, and a single legend.

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

回答 4

我注意到没有答案显示带有单个图例的图像,该图例引用了不同子图中的许多曲线,因此我必须向您展示一个……让您感到好奇……

在此处输入图片说明

现在,您想看一下代码,不是吗?

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

值得解释-为此,我将棘手的部分封装在一个函数中,仅用4行代码,但注释严重

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

    # 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和效率高吗?;-)

I have noticed that no answer display an image with a single legend referencing many curves in different subplots, so I have to show you one… to make you curious…

enter image description here

Now, you want to look at the code, don’t you?

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

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 — to this aim I have encapsulated the tricky part in a function, just 4 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])
    
    # 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 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? ;-)


Important Update

If you want to stick with the official Matplotlib API my answer above is perfect, really.

On the other hand, 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), ...)

A complete explanation can be found in the source code of Axes.get_legend_handles_labels in .../matplotlib/axes/_axes.py


回答 5

虽然游戏时间比较晚,但我将在此处提供另一种解决方案,因为这仍然是显示在Google上的第一个链接之一。使用matplotlib 2.2.2,可以使用gridspec功能来实现。在下面的示例中,目标是以2×2的方式排列四个子图,图例显示在底部。在底部创建一个“人造”轴,以将图例放置在固定位置。然后关闭“人造”轴,因此仅显示图例。结果:https : //i.stack.imgur.com/5LUWM.png

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 bottommost axis

fig.tight_layout()
plt.show()

While rather late to the game, I’ll give another solution here as this is still one of the first links to show up on google. Using matplotlib 2.2.2, this can be achieved using the gridspec feature. In the example below the aim is to have four subplots arranged in a 2×2 fashion with the legend shown at the bottom. A ‘faux’ axis is created at the bottom to place the legend in a fixed spot. The ‘faux’ axis is then turned off so only the legend shows. Result: https://i.stack.imgur.com/5LUWM.png.

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 bottommost axis

fig.tight_layout()
plt.show()

回答 6

如果您将子图与条形图一起使用,则每个条形具有不同的颜色。自己动手制作艺术品可能会更快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

if you are using subplots with bar charts, with different colour for each bar. it may be faster to create the artefacts yourself using mpatches

Say you have four bars with different colours as r m c k you can set the legend as follows

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

回答 7

此答案是@Evert在图例位置的补充。

由于图例和子图标题的重叠,我对@Evert解决方案的首次尝试失败。

实际上,重叠是由引起的fig.tight_layout(),这会更改子图的布局而无需考虑图形图例。但是,这fig.tight_layout()是必需的。

为了避免重叠,我们可以通过告知fig.tight_layout()为人物的图例保留空格fig.tight_layout(rect=(0,0,1,0.9))

.tight_layout()参数的描述

This answer is a complement to @Evert’s on the legend position.

My first try on @Evert’s solution failed due to overlaps of the legend and the subplot’s title.

In fact, the overlaps are caused by fig.tight_layout(), which changes the subplots’ layout without considering the figure legend. However, fig.tight_layout() is necessary.

In order to avoid the overlaps, we can tell fig.tight_layout() to leave spaces for the figure’s legend by fig.tight_layout(rect=(0,0,1,0.9)).

Description of tight_layout() parameters.


回答 8

要建立在@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,
)

To build on top of @gboffi’s and Ben Usman’s answer:

In a situation where one has different lines in different subplots with the same color and label, one can do something along the lines of

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

声明:本站所有文章,如无特殊说明或标注,均为本站原创发布。任何个人或组织,在未征得本站同意时,禁止复制、盗用、采集、发布本站内容到任何网站、书籍等各类媒体平台。如若本站内容侵犯了原著者的合法权益,可联系我们进行处理。