如何将图例放在绘图外部
我有一系列20个图表(不是子图),想要在一个图形中展示。我希望图例(说明)能放在图形的外面。同时,我不想改变坐标轴,因为那样会让图形的大小变小。
- 我想把图例框放在绘图区域的外面(我希望图例在绘图区域的右侧)。
- 有没有办法把图例框里面的文字字体大小调小,这样图例框的大小也会变小?
18 个回答
放置图例(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
参数。你可以只提供 (x0, y0)
部分,这样就会创建一个零跨度的框,图例会根据 loc
参数的方向扩展。例如:
plt.legend(bbox_to_anchor=(1.04, 1), loc="upper left")
这会把图例放在坐标轴外面,使得图例的左上角位于坐标 (1.04, 1)
。
下面还有更多示例,展示了不同参数如 mode
和 ncols
之间的相互作用。
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
)的详细信息,可以参考 这个问题。mode="expand"
会让图例在四元组给定的边界框内水平扩展。如果想要垂直扩展的图例,可以查看 这个问题。
有时候,使用图形坐标而不是坐标轴坐标来指定边界框会更有用。这在上面的示例 l5
中展示了,使用 bbox_transform
参数把图例放在图形的左下角。
后处理
把图例放在坐标轴外面,常常会导致图例完全或部分超出图形的画布。
解决这个问题的方法有:
调整子图参数
可以调整子图参数,让坐标轴在图形中占用更少的空间(从而为图例留出更多空间),使用plt.subplots_adjust
。例如:plt.subplots_adjust(right=0.7)
这会在图形的右侧留出30%的空间,可以放置图例。
紧凑布局
使用plt.tight_layout
可以自动调整子图参数,使得图形中的元素紧贴图形边缘。遗憾的是,图例在这个自动调整中并没有被考虑,但我们可以提供一个矩形框,让整个子图区域(包括标签)都能适应。plt.tight_layout(rect=[0, 0, 0.75, 1])
使用
bbox_inches = "tight"
保存图形
在plt.savefig
中使用bbox_inches = "tight"
参数,可以保存图形,使画布上的所有元素(包括图例)都能适应保存的区域。如果需要,图形的大小会自动调整。plt.savefig("output.png", bbox_inches="tight")
自动调整子图参数
一种自动调整子图位置的方法,使得图例能适应画布 而不改变图形大小,可以参考这个答案:创建精确大小且无填充的图形(图例在坐标轴外)
以上讨论的情况比较:
替代方案
图形图例
可以使用图形的图例,而不是坐标轴的图例,使用 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()
在专用子图坐标轴内放置图例
使用 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()
这会生成一个在视觉上与上面的绘图非常相似的图:
我们也可以使用第一个坐标轴来放置图例,但使用图例坐标轴的 bbox_transform
,
ax.legend(bbox_to_anchor=(0, 0, 1, 1), bbox_transform=lax.transAxes)
lax.axis("off")
在这种方法中,我们不需要外部获取图例句柄,但需要指定 bbox_to_anchor
参数。
进一步阅读和说明:
- 可以参考 Matplotlib 的 图例指南,里面有一些关于图例的其他示例。
- 关于饼图图例放置的一些示例代码可以直接在这个问题的回答中找到:Python - 图例与饼图重叠
loc
参数可以使用数字而不是字符串,这样调用会更简短,但它们之间的映射不是很直观。这里是参考的映射:
有很多方法可以实现你想要的效果。除了Christian Alis和Navi已经提到的内容之外,你还可以使用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()
如果我们做同样的事情,但使用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()
同样地,可以让图例更横向一些,或者把它放在图的顶部(我还开启了圆角和简单的阴影效果):
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()
另外,也可以缩小当前图的宽度,把图例完全放在图的坐标轴外面(注意:如果你使用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()
同样的方式,缩小图的高度,把图例放在底部,横向排列:
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()
可以看看matplotlib的图例指南。你也可以查看plt.figlegend()
。
- 你可以通过设置
FontProperties
的set_size
来让图例的文字变小。 - 相关资源:
- 图例指南
matplotlib.legend
matplotlib.pyplot.legend
matplotlib.font_manager
set_size(self, size)
- 有效的字体大小有 xx-small、x-small、small、medium、large、x-large、xx-large、larger、smaller 和 None。
- Real Python: 使用 Matplotlib 绘图的指南
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')