图例指南#

本图例指南扩展了 legend 文档字符串 - 请在继续阅读本指南之前先阅读它。

本指南使用了一些常见术语,在此处记录以确保清晰:

图例条目#

图例由一个或多个图例条目组成。一个条目由一个键和一个标签组成。

图例键#

每个图例标签左侧的彩色/图案标记。

图例标签#

描述由键表示的句柄的文本。

图例句柄#

用于在图例中生成适当条目的原始对象。

控制图例条目#

调用 legend() 时不带参数会自动获取图例句柄及其关联的标签。此功能等同于:

handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, labels)

函数 get_legend_handles_labels() 返回一个存在于 Axes 上的句柄/艺术家的列表,这些句柄/艺术家可以用来生成结果图例的条目——值得注意的是,并非所有艺术家都可以添加到图例中,此时需要创建一个“代理”(详见 创建专门用于添加到图例的艺术家(即代理艺术家))。

备注

标签为空字符串或标签以下划线“_”开头的艺术家将被忽略。

为了完全控制添加到图例中的内容,通常直接将适当的句柄传递给 legend():

fig, ax = plt.subplots()
line_up, = ax.plot([1, 2, 3], label='Line 2')
line_down, = ax.plot([3, 2, 1], label='Line 1')
ax.legend(handles=[line_up, line_down])

重命名图例条目#

当标签不能直接设置在句柄上时,它们可以直接传递给 Axes.legend:

fig, ax = plt.subplots()
line_up, = ax.plot([1, 2, 3], label='Line 2')
line_down, = ax.plot([3, 2, 1], label='Line 1')
ax.legend([line_up, line_down], ['Line Up', 'Line Down'])

如果句柄不能直接访问,例如在使用某些 第三方包 时,可以通过 Axes.get_legend_handles_labels 访问它们。这里我们使用字典来重命名现有的标签:

my_map = {'Line Up':'Up', 'Line Down':'Down'}

handles, labels = ax.get_legend_handles_labels()
ax.legend(handles, [my_map[l] for l in labels])

创建专门用于添加到图例的艺术家(即代理艺术家)#

并非所有句柄都能自动转换为图例条目,因此通常需要创建一个 可以 的艺术家。图例句柄不必存在于图形或轴上即可使用。

假设我们想要创建一个图例,该图例为某些用红色表示的数据提供一个条目:

import matplotlib.pyplot as plt

import matplotlib.patches as mpatches

fig, ax = plt.subplots()
red_patch = mpatches.Patch(color='red', label='The red data')
ax.legend(handles=[red_patch])

plt.show()
legend guide

有许多支持的图例句柄。我们可以创建带有标记的线条,而不是创建一个颜色块:

import matplotlib.lines as mlines

fig, ax = plt.subplots()
blue_line = mlines.Line2D([], [], color='blue', marker='*',
                          markersize=15, label='Blue stars')
ax.legend(handles=[blue_line])

plt.show()
legend guide

图例位置#

图例的位置可以通过关键字参数 loc 来指定。更多详情请参阅 legend() 的文档。

bbox_to_anchor 关键字提供了对手动图例放置的极大控制。例如,如果你想将 Axes 图例放置在图的右上角而不是 Axes 的角落,只需指定该角落的位置和该位置的坐标系:

ax.legend(bbox_to_anchor=(1, 1),
          bbox_transform=fig.transFigure)

更多自定义图例位置的示例:

fig, ax_dict = plt.subplot_mosaic([['top', 'top'], ['bottom', 'BLANK']],
                                  empty_sentinel="BLANK")
ax_dict['top'].plot([1, 2, 3], label="test1")
ax_dict['top'].plot([3, 2, 1], label="test2")
# Place a legend above this subplot, expanding itself to
# fully use the given bounding box.
ax_dict['top'].legend(bbox_to_anchor=(0., 1.02, 1., .102), loc='lower left',
                      ncols=2, mode="expand", borderaxespad=0.)

ax_dict['bottom'].plot([1, 2, 3], label="test1")
ax_dict['bottom'].plot([3, 2, 1], label="test2")
# Place a legend to the right of this smaller subplot.
ax_dict['bottom'].legend(bbox_to_anchor=(1.05, 1),
                         loc='upper left', borderaxespad=0.)
legend guide

图例#

有时将图例相对于(子)图放置比相对于单个坐标轴更有意义。通过使用 constrained layout 并在 loc 关键字参数的开头指定 "outside",图例将绘制在(子)图的坐标轴之外。

fig, axs = plt.subplot_mosaic([['left', 'right']], layout='constrained')

axs['left'].plot([1, 2, 3], label="test1")
axs['left'].plot([3, 2, 1], label="test2")

axs['right'].plot([1, 2, 3], 'C2', label="test3")
axs['right'].plot([3, 2, 1], 'C3', label="test4")
# Place a legend to the right of this smaller subplot.
fig.legend(loc='outside upper right')
legend guide

这与普通的 loc 关键字接受的语法略有不同,其中“outside right upper”与“outside upper right”不同。

ucl = ['upper', 'center', 'lower']
lcr = ['left', 'center', 'right']
fig, ax = plt.subplots(figsize=(6, 4), layout='constrained', facecolor='0.7')

ax.plot([1, 2], [1, 2], label='TEST')
# Place a legend to the right of this smaller subplot.
for loc in [
        'outside upper left',
        'outside upper center',
        'outside upper right',
        'outside lower left',
        'outside lower center',
        'outside lower right']:
    fig.legend(loc=loc, title=loc)

fig, ax = plt.subplots(figsize=(6, 4), layout='constrained', facecolor='0.7')
ax.plot([1, 2], [1, 2], label='test')

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

同一坐标轴上的多个图例#

有时将图例条目拆分到多个图例中会更清晰。虽然直觉上可能会多次调用 legend() 函数来实现这一点,但你会发现 Axes 上始终只有一个图例。这样做是为了可以反复调用 legend() 来更新图例以包含 Axes 上的最新句柄。要保留旧的图例实例,我们必须手动将它们添加到 Axes 中:

fig, ax = plt.subplots()
line1, = ax.plot([1, 2, 3], label="Line 1", linestyle='--')
line2, = ax.plot([3, 2, 1], label="Line 2", linewidth=4)

# Create a legend for the first line.
first_legend = ax.legend(handles=[line1], loc='upper right')

# Add the legend manually to the Axes.
ax.add_artist(first_legend)

# Create another legend for the second line.
ax.legend(handles=[line2], loc='lower right')

plt.show()
legend guide

图例处理程序#

为了创建图例条目,句柄作为参数传递给适当的 HandlerBase 子类。处理子类的选择由以下规则决定:

  1. 使用 handler_map 关键字中的值更新 get_legend_handler_map()

  2. 检查 handle 是否在新创建的 handler_map 中。

  3. 检查 handle 的类型是否在新创建的 handler_map 中。

  4. 检查 handle 的 mro 中的任何类型是否在新创建的 handler_map 中。

为了完整性,这个逻辑主要在 get_legend_handler() 中实现。

所有这些灵活性意味着我们有必要实现自定义处理程序来处理我们自己的图例键类型。

使用自定义处理程序的最简单示例是实例化现有的 legend_handler.HandlerBase 子类之一。为了简单起见,我们选择接受 numpoints 参数的 legend_handler.HandlerLine2D`(numpoints 也是 :func:`legend 函数中的一个关键字,以方便使用)。然后,我们可以将实例到处理程序的映射作为关键字传递给 legend。

from matplotlib.legend_handler import HandlerLine2D

fig, ax = plt.subplots()
line1, = ax.plot([3, 2, 1], marker='o', label='Line 1')
line2, = ax.plot([1, 2, 3], marker='o', label='Line 2')

ax.legend(handler_map={line1: HandlerLine2D(numpoints=4)}, handlelength=4)
legend guide

如你所见,“Line 1”现在有4个标记点,而“Line 2”有2个(默认值)。我们还通过``handlelength``关键字增加了手柄的长度,以适应更大的图例条目。尝试上面的代码,只需将地图的键从``line1``更改为``type(line1)``。注意现在两个`.Line2D`实例都获得了4个标记点。

除了处理诸如误差条、茎图和直方图等复杂绘图类型的处理程序外,默认的 handler_map 还有一个特殊的 tuple 处理程序(legend_handler.HandlerTuple),它简单地将给定元组中每个项目的句柄相互叠加。以下示例演示了将两个图例键相互叠加:

from numpy.random import randn

z = randn(10)

fig, ax = plt.subplots()
red_dot, = ax.plot(z, "ro", markersize=15)
# Put a white cross over some of the data.
white_cross, = ax.plot(z[:5], "w+", markeredgewidth=3, markersize=15)

ax.legend([red_dot, (red_dot, white_cross)], ["Attr A", "Attr A+B"])
legend guide

legend_handler.HandlerTuple 类也可以用于将多个图例键分配给同一个条目:

from matplotlib.legend_handler import HandlerLine2D, HandlerTuple

fig, ax = plt.subplots()
p1, = ax.plot([1, 2.5, 3], 'r-d')
p2, = ax.plot([3, 2, 1], 'k-o')

l = ax.legend([(p1, p2)], ['Two keys'], numpoints=1,
              handler_map={tuple: HandlerTuple(ndivide=None)})
legend guide

实现自定义图例处理程序#

可以实现一个自定义处理器,将任何句柄转换为图例键(句柄不一定需要是 matplotlib 艺术家)。该处理器必须实现一个 legend_artist 方法,该方法返回图例使用的单个艺术家。legend_artist 的必要签名在 legend_artist 中记录。

import matplotlib.patches as mpatches


class AnyObject:
    pass


class AnyObjectHandler:
    def legend_artist(self, legend, orig_handle, fontsize, handlebox):
        x0, y0 = handlebox.xdescent, handlebox.ydescent
        width, height = handlebox.width, handlebox.height
        patch = mpatches.Rectangle([x0, y0], width, height, facecolor='red',
                                   edgecolor='black', hatch='xx', lw=3,
                                   transform=handlebox.get_transform())
        handlebox.add_artist(patch)
        return patch

fig, ax = plt.subplots()

ax.legend([AnyObject()], ['My first handler'],
          handler_map={AnyObject: AnyObjectHandler()})
legend guide

或者,如果我们希望全局接受 AnyObject 实例而不需要每次手动设置 handler_map 关键字,我们可以使用以下方式注册新处理程序::

from matplotlib.legend import Legend
Legend.update_default_handler_map({AnyObject: AnyObjectHandler()})

虽然这里的功能很强大,但请记住已经有许多处理程序被实现,你想要实现的功能可能已经可以通过现有的类轻松实现。例如,要生成椭圆形的图例键,而不是矩形的:

from matplotlib.legend_handler import HandlerPatch


class HandlerEllipse(HandlerPatch):
    def create_artists(self, legend, orig_handle,
                       xdescent, ydescent, width, height, fontsize, trans):
        center = 0.5 * width - 0.5 * xdescent, 0.5 * height - 0.5 * ydescent
        p = mpatches.Ellipse(xy=center, width=width + xdescent,
                             height=height + ydescent)
        self.update_prop(p, orig_handle, legend)
        p.set_transform(trans)
        return [p]


c = mpatches.Circle((0.5, 0.5), 0.25, facecolor="green",
                    edgecolor="red", linewidth=3)

fig, ax = plt.subplots()

ax.add_patch(c)
ax.legend([c], ["An ellipse, not a rectangle"],
          handler_map={mpatches.Circle: HandlerEllipse()})
legend guide

脚本的总运行时间: (0 分钟 1.510 秒)

由 Sphinx-Gallery 生成的图库