图例指南#

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

本指南使用了一些常见术语,为了清晰起见,这里对其进行了说明

图例条目#

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

图例键#

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

图例标签#

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

图例句柄#

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

控制图例条目#

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

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

The get_legend_handles_labels() 函数返回 Axes 上存在的句柄/艺术家列表,这些列表可用于生成结果图例的条目 - 值得注意的是,并非所有艺术家都可以添加到图例中,此时将不得不创建一个“代理”(请参阅 Creating artists specifically for adding to the legend (aka. Proxy artists) 了解更多详细信息)。

注意

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

为了完全控制添加到图例的内容,通常将适当的句柄直接传递给 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提供了极大的控制力来手动放置图例。例如,如果您希望坐标轴图例位于图形的右上角而不是坐标轴的角落,只需指定角落的位置和该位置的坐标系

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

图形图例#

有时,将图例相对于(子)图形而不是单个坐标轴放置更有意义。通过使用受约束的布局并在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()函数,但您会发现坐标轴上始终只存在一个图例。这样做是为了能够重复调用legend()以将图例更新为坐标轴上的最新句柄。要保留旧的图例实例,我们必须将它们手动添加到坐标轴

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 子类之一。为了简单起见,让我们选择 legend_handler.HandlerLine2D,它接受一个 *numpoints* 参数(numpoints 也是 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 分钟 4.804 秒)

由 Sphinx-Gallery 生成的画廊