如何将图例放在绘图区外

1597

我有一系列20个图表(不是子图),需要在一个单独的图中制作。我希望图例在框之外。同时,我不想改变坐标轴,因为图的大小会缩小。

  1. 我希望保持图例框在绘图区域之外(我想让图例在绘图区域右侧之外)。
  2. 是否有方法可以减小图例框内文本的字体大小,使得图例框的大小更小?

1
seaborn是matplotlib的高级API。从seaborn v0.11.2开始,有sns.move_legend,如将seaborn图例移动到不同位置所示。.move_legend可以传递所有.legend的参数,并且下面的所有答案都直接适用于seaborn轴级别的绘图(例如返回matplotlib Axes的绘图)。 - Trenton McKinney
18个回答

2391

有许多方法可以实现您想要的功能。除了Christian AlisNavi已经提到的方法,您还可以使用bbox_to_anchor关键字参数将图例部分放置在轴外部并/或减小字体大小。

在考虑减小字体大小之前(这可能会使事情变得非常难以阅读),请尝试在不同的位置放置图例:

因此,让我们从一个通用的示例开始:

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(10)

fig = plt.figure()
ax = plt.subplot(111)

for i in xrange(5):
    ax.plot(x, i * x, label='$y = %ix$' % i)

ax.legend()

plt.show()

alt text

如果我们做同样的事情,但使用bbox_to_anchor关键字参数,我们可以将图例稍微移动到轴边界之外:

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(10)

fig = plt.figure()
ax = plt.subplot(111)

for i in xrange(5):
    ax.plot(x, i * x, label='$y = %ix$' % i)

ax.legend(bbox_to_anchor=(1.1, 1.05))

plt.show()

Alt text

同样地,将图例变得更加水平和/或将其放置在图形顶部(我还打开了圆角和简单的阴影):

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(10)

fig = plt.figure()
ax = plt.subplot(111)

for i in xrange(5):
    line, = ax.plot(x, i * x, label='$y = %ix$'%i)

ax.legend(loc='upper center', bbox_to_anchor=(0.5, 1.05),
          ncol=3, fancybox=True, shadow=True)
plt.show()

alt text

或者,缩小当前图的宽度,并将图例完全放在图形轴的外部(注意:如果您使用 tight_layout(),则不要使用 ax.set_position()

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(10)

fig = plt.figure()
ax = plt.subplot(111)

for i in xrange(5):
    ax.plot(x, i * x, label='$y = %ix$'%i)

# Shrink current axis by 20%
box = ax.get_position()
ax.set_position([box.x0, box.y0, box.width * 0.8, box.height])

# Put a legend to the right of the current axis
ax.legend(loc='center left', bbox_to_anchor=(1, 0.5))

plt.show()

Alt text

同样地,垂直缩小图表,并在底部放置水平图例:

import matplotlib.pyplot as plt
import numpy as np

x = np.arange(10)

fig = plt.figure()
ax = plt.subplot(111)

for i in xrange(5):
    line, = ax.plot(x, i * x, label='$y = %ix$'%i)

# Shrink current axis's height by 10% on the bottom
box = ax.get_position()
ax.set_position([box.x0, box.y0 + box.height * 0.1,
                 box.width, box.height * 0.9])

# Put a legend below current axis
ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.05),
          fancybox=True, shadow=True, ncol=5)

plt.show()

Alt text

请看Matplotlib图例指南。您还可以查看plt.figlegend()

1487

放置图例(bbox_to_anchor

使用loc参数在坐标轴的边界框内定位图例,例如plt.legend中的loc="upper right"将图例放置在边界框的右上角。默认情况下,边界框从坐标轴的 (0, 0)(1, 1) 扩展(或以边界框符号表示为 (x0, y0, width, height) = (0, 0, 1, 1))。

要将图例放置在坐标轴边界框之外,可以指定一个元组(x0, y0),表示图例左下角的坐标轴坐标。

plt.legend(loc=(1.04, 0))

一种更灵活的方法是手动指定图例应放置的边界框,使用bbox_to_anchor参数。可以仅提供bbox的(x0, y0)部分,这将创建一个零跨度框,图例将在由loc参数给出的方向上扩展。例如,
plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left")
将图例放置在轴外,以使图例的左上角在轴坐标中的位置为(1.04, 1)
下面给出了更多示例,还演示了不同参数之间的相互作用,如modencols

Enter image description here

l1 = plt.legend(bbox_to_anchor=(1.04, 1), borderaxespad=0)
l2 = plt.legend(bbox_to_anchor=(1.04, 0), loc="lower left", borderaxespad=0)
l3 = plt.legend(bbox_to_anchor=(1.04, 0.5), loc="center left", borderaxespad=0)
l4 = plt.legend(bbox_to_anchor=(0, 1.02, 1, 0.2), loc="lower left",
                mode="expand", borderaxespad=0, ncol=3)
l5 = plt.legend(bbox_to_anchor=(1, 0), loc="lower right",
                bbox_transform=fig.transFigure, ncol=3)
l6 = plt.legend(bbox_to_anchor=(0.4, 0.8), loc="upper right")

如何解释 bbox_to_anchor 的四元组参数,例如 l4 中的参数,请参见 this question。当 mode="expand" 时,将图例在四元组指定的边界框内水平展开。要进行垂直展开,请参见 this question

有时候,以图形坐标而不是轴坐标指定边界框可能很有用。这在上面的示例 l5 中展示,其中使用了 bbox_transform 参数将图例放置在图形左下角。

后处理

将图例放置在轴外部通常会导致不希望出现的情况,即它完全或部分超出图形画布。

解决此问题的方法有:

  • Adjust the subplot parameters
    One can adjust the subplot parameters such, that the axes take less space inside the figure (and thereby leave more space to the legend) by using plt.subplots_adjust. E.g.,

    plt.subplots_adjust(right=0.7)
    

    leaves 30% space on the right-hand side of the figure, where one could place the legend.

  • Tight layout
    Using plt.tight_layout Allows to automatically adjust the subplot parameters such that the elements in the figure sit tight against the figure edges. Unfortunately, the legend is not taken into account in this automatism, but we can supply a rectangle box that the whole subplots area (including labels) will fit into.

    plt.tight_layout(rect=[0, 0, 0.75, 1])
    
  • Saving the figure with bbox_inches = "tight"
    The argument bbox_inches = "tight" to plt.savefig can be used to save the figure such that all artist on the canvas (including the legend) are fit into the saved area. If needed, the figure size is automatically adjusted.

    plt.savefig("output.png", bbox_inches="tight")
    
  • Automatically adjusting the subplot parameters
    A way to automatically adjust the subplot position such that the legend fits inside the canvas without changing the figure size can be found in this answer: Creating figure with exact size and no padding (and legend outside the axes)

上述案例的比较:

Enter image description here

替代方案

图例说明

可以使用图例来替代坐标轴,matplotlib.figure.Figure.legend。这在Matplotlib 2.1或更高版本中变得特别有用,不需要任何特殊参数。

fig.legend(loc=7)

为所有不同轴中的艺术家创建图例。使用loc参数放置图例,类似于在坐标轴内部放置图例,但是参考整个图形 - 因此它将自动位于轴外部。剩下的就是调整子图,以便图例和轴之间没有重叠。这里上面提到的点“调整子图参数”会有所帮助。以下是一个示例:
import numpy as np
import matplotlib.pyplot as plt

x = np.linspace(0, 2*np.pi)
colors = ["#7aa0c4", "#ca82e1", "#8bcd50", "#e18882"]
fig, axes = plt.subplots(ncols=2)
for i in range(4):
    axes[i//2].plot(x, np.sin(x+i), color=colors[i], label="y=sin(x + {})".format(i))

fig.legend(loc=7)
fig.tight_layout()
fig.subplots_adjust(right=0.75)
plt.show()

Enter image description here

专属子图中的图例

除了使用bbox_to_anchor之外,还可以将图例放置在其专用子图轴(lax)中。 由于图例子图应该比图形小,因此我们可以在轴创建时使用gridspec_kw={"width_ratios":[4, 1]}。 我们可以隐藏轴lax.axis("off"),但仍然放置一个图例。需要通过h, l = ax.get_legend_handles_labels()从真实的图形中获取图例处理程序和标签,然后可以将它们提供给lax子图中的图例lax.legend(h, l)。下面是一个完整的示例。

import matplotlib.pyplot as plt
plt.rcParams["figure.figsize"] = 6, 2

fig, (ax, lax) = plt.subplots(ncols=2, gridspec_kw={"width_ratios":[4, 1]})
ax.plot(x, y, label="y=sin(x)")
....

h, l = ax.get_legend_handles_labels()
lax.legend(h, l, borderaxespad=0)
lax.axis("off")

plt.tight_layout()
plt.show()

这将生成一个图表,与上面的图表在视觉上非常相似:

Enter image description here

我们也可以使用第一个坐标轴来放置图例,但是使用图例坐标轴的bbox_transform
ax.legend(bbox_to_anchor=(0, 0, 1, 1), bbox_transform=lax.transAxes)
lax.axis("off")

在这种方法中,我们不需要从外部获取图例句柄,但我们需要指定bbox_to_anchor参数。

进一步阅读和注释:

  • 考虑使用Matplotlib 图例指南,其中包含一些其他想要在图例中执行的操作示例。
  • 有关放置饼图图例的示例代码直接可以在回答此问题的答案中找到:Python - 图例与饼图重叠
  • loc参数可以采用数字而不是字符串,这使得调用更短,但它们之间没有很直观的映射。以下是参考映射:

Enter image description here


200

像这样,在plot()之后调用legend()

# Matplotlib
plt.plot(...)
plt.legend(loc='center left', bbox_to_anchor=(1, 0.5))

# Pandas
df.myCol.plot().legend(loc='center left', bbox_to_anchor=(1, 0.5))

结果可能看起来像这样:

这里输入图片描述


4
当将相同的参数传递给matplotlib.pyplot.legend时,它会起作用。 - kidmose
14
这是否会割掉其他人传说中的文字? - user1717828
1
调用“tight_layout()”可以解决我遇到的截断单词问题。 - Will

195
import matplotlib.pyplot as plt
from matplotlib.font_manager import FontProperties

fontP = FontProperties()
fontP.set_size('xx-small')

p1, = plt.plot([1, 2, 3], label='Line 1')
p2, = plt.plot([3, 2, 1], label='Line 2')
plt.legend(handles=[p1, p2], title='title', bbox_to_anchor=(1.05, 1), loc='upper left', prop=fontP)

在此输入图片描述

  • fontsize='xx-small' 也可以使用,无需导入FontProperties
plt.legend(handles=[p1, p2], title='title', bbox_to_anchor=(1.05, 1), loc='upper left', fontsize='xx-small')

106

要将图例放在绘图区域外部,请使用legend()locbbox_to_anchor关键字。例如,以下代码将把图例放置在绘图区域的右侧:

legend(loc="upper left", bbox_to_anchor=(1,1))

更多信息请参见图例指南


13
我喜欢这个功能的实现,但是当我尝试保存该图时(而不手动调整窗口大小,因为我不想每次都这样做),图例总是被裁剪掉了。你有什么建议可以修复这个问题吗? - astromax
3
@astromax 我不确定,但或许可以尝试调用 plt.tight_layout() 函数? - Christian Alis

91

简短回答:您可以使用bbox_to_anchor+ bbox_extra_artists+ bbox_inches ='tight'


更详细的回答: 您可以使用bbox_to_anchor手动指定图例框的位置,正如其他人在回答中提到的那样。

然而,通常的问题是图例框被裁剪了,例如:

import matplotlib.pyplot as plt

# data 
all_x = [10,20,30]
all_y = [[1,3], [1.5,2.9],[3,2]]

# Plot
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(all_x, all_y)

# Add legend, title and axis labels
lgd = ax.legend( [ 'Lag ' + str(lag) for lag in all_x], loc='center right', bbox_to_anchor=(1.3, 0.5))
ax.set_title('Title')
ax.set_xlabel('x label')
ax.set_ylabel('y label')

fig.savefig('image_output.png', dpi=300, format='png')

在此输入图像描述

为了防止图例框被裁剪,当您保存图形时,可以使用参数bbox_extra_artistsbbox_inches要求savefig在保存的图像中包含裁剪的元素:

fig.savefig('image_output.png', bbox_extra_artists=(lgd,), bbox_inches='tight')

示例(只更改了最后一行以向fig.savefig()添加2个参数):

import matplotlib.pyplot as plt

# data 
all_x = [10,20,30]
all_y = [[1,3], [1.5,2.9],[3,2]]

# Plot
fig = plt.figure(1)
ax = fig.add_subplot(111)
ax.plot(all_x, all_y)

# Add legend, title and axis labels
lgd = ax.legend( [ 'Lag ' + str(lag) for lag in all_x], loc='center right', bbox_to_anchor=(1.3, 0.5))
ax.set_title('Title')
ax.set_xlabel('x label')
ax.set_ylabel('y label')    

fig.savefig('image_output.png', dpi=300, format='png', bbox_extra_artists=(lgd,), bbox_inches='tight')

enter image description here

我希望Matplotlib可以像Matlab一样,原生支持图例框的外部位置:

figure
x = 0:.2:12;
plot(x,besselj(1,x),x,besselj(2,x),x,besselj(3,x));
hleg = legend('First','Second','Third',...
              'Location','NorthEastOutside')
% Make the text of the legend italic and color it brown
set(hleg,'FontAngle','italic','TextColor',[.3,.2,.1])

输入图像描述


7
谢谢,但实际上即使没有bbox_extra_artist,bbox_inches='tight'对我来说已经完美地起作用了。 - avtomaton
1
@avtomaton 谢谢,好的,知道了。你使用的matplotlib版本是哪个? - Franck Dernoncourt
1
@FranckDernoncourt python3,matplotlib版本1.5.3 - avtomaton

70

除了这里的所有优秀答案,更新版本的matplotlibpylab可以自动决定在哪里放置图例而不干扰绘图,如果可能的话。

pylab.legend(loc='best')

如果可能的话,这将自动将图例放置在数据远离的地方!

比较使用 loc='best' 的效果

但是,如果没有可以放置图例而不重叠数据的位置,则需要尝试其他答案;使用 loc="best" 永远不会将图例放在绘图区外部


2
谢谢您指出这一点!我几年前就在寻找它,但没有找到,这确实让我的生活更轻松了。 - Edgar H
5
这个选项有帮助,但并没有回答问题,所以我投了反对票。据我所知,最好的做法是将图例放在图表内部。 - Tommy
3
@Tommy:在原帖的评论部分中(现在好像已经没有了),明确说明了OP想要图例不遮盖图表数据,他认为只有将其放在图表外面才能实现。你可以在mefathy、Mateo Sanchez、Bastiaan和radtek的答案中看到这一点。OP 问了X,但他想要Y - dotancohen
1
实际上不是这样的。他/她特别要求图例在图形外部。这也是问题的名称:“如何将图例放在图形外部”。 - durbachit
5
这并不保证图例不会遮盖数据。只是绘制了一个非常密集的图 -- 没有放置图例的地方。例如,尝试以下代码...from numpy import arange, sin, pi import matplotlib.pyplot as plt t = arange(0.0, 100.0, 0.01) fig = plt.figure(1) ax1 = fig.add_subplot(211) ax1.scatter(t, sin(2*pi*t),label='test') ax1.grid(True) # ax1.set_ylim((-2, 2)) ax1.set_ylabel('1 Hz') ax1.set_title('一个正弦波或两个') for label in ax1.get_xticklabels(): label.set_color('r') plt.legend(loc='best') plt.show() - adam.r

57

简短回答:将可拖动功能应用于图例,然后交互地将其移动到任何位置:

ax.legend().draggable()

长篇回答: 如果你更喜欢手动交互地放置图例,而不是通过编程的方式,你可以切换图例的可拖动模式,这样你就可以将其拖至任何位置。请参考下面的示例:

import matplotlib.pylab as plt
import numpy as np
#define the figure and get an axes instance
fig = plt.figure()
ax = fig.add_subplot(111)
#plot the data
x = np.arange(-5, 6)
ax.plot(x, x*x, label='y = x^2')
ax.plot(x, x*x*x, label='y = x^3')
ax.legend().draggable()
plt.show()

1
我不确定我完全理解这个。我如何使用Python 3.6和Jupyter Notebook将图例“拖动”到任何我想要的位置? - veg2020

22

Matplotlib 3.7新特性

现在,图例可以直接接受“外部”位置,例如loc='outside right upper'

只需确保布局已受到限制,然后在loc字符串前加上“outside”即可:

import matplotlib.pyplot as plt
import numpy as np

fig, ax = plt.subplots(layout='constrained')
#                      --------------------

x = np.linspace(-np.pi, np.pi)
ax.plot(x, x, label='$f(x) = x$')
ax.plot(x, np.sin(x), label='$f(x) = sin(x)$')
ax.plot(x, np.cos(x), label='$f(x) = cos(x)$')

fig.legend(loc='outside right upper')
#               -------

plt.show()

多个小图在新的“外部”位置也能正常工作:

fig, (ax1, ax2) = plt.subplots(1, 2, layout='constrained')
#                                    --------------------

x = np.linspace(-np.pi, np.pi)
ax1.plot(x, x,         '-',  label='$f(x) = x$')
ax1.plot(x, np.sin(x), '--', label='$f(x) = sin(x)$')
ax2.plot(x, np.cos(x), ':',  label='$f(x) = cos(x)$')

fig.legend(loc='outside right center')
#               -------

当然,“外部”位置是预设的,如果您需要更精细的定位,请使用旧答案。但是标准位置应适用于大多数情况:

注:此处涉及IT技术相关内容,可能需要具备一定专业知识才能更好地理解。

locs = [
    'outside upper left', 'outside upper center', 'outside upper right',
    'outside center right', 'upper center left',
    'outside lower right', 'outside lower center', 'outside lower left',
]
for loc in locs:
    fig.legend(loc=loc, title=loc)

locs = [
    'outside right upper', 'outside right lower',
    'outside left lower', 'outside left upper',
]
for loc in locs:
    fig.legend(loc=loc, title=loc)


1
太棒了!谢谢! - undefined
不幸的是,这对于ax不起作用(例如ax.legend(loc="outside lower left", ncols=3))。 - undefined

21

Matplotlib的更新版本使得在图外部放置图例变得更加容易。我是用Matplotlib3.1.1版本制作了这个示例。

用户可以通过将一个二元组坐标传递给loc参数来将图例放置在边框框中的任何位置。唯一需要注意的是你需要运行plt.tight_layout()以便让matplotlib重新计算绘图的尺寸,从而使图例可见:

import matplotlib.pyplot as plt

plt.plot([0, 1], [0, 1], label="Label 1")
plt.plot([0, 1], [0, 2], label='Label 2')

plt.legend(loc=(1.05, 0.5))
plt.tight_layout()

这导致了以下的图表:

图例在图外的图表

参考文献:


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接