Matplotlib 中文用户指南 3.6 图例指南

图例指南

原文:Legend guide

译者:飞龙

协议:CC BY-NC-SA 4.0

此图例指南是legend()中可用文档的扩展 - 请在继续阅读本指南之前确保你熟悉该文档(见篇尾)的内容。

本指南使用一些常见术语,为了清楚起见,这些术语在此处进行说明:

图例条目

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

图例键

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

图例标签

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

图例句柄

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

控制图例条目

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

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

get_legend_handles_labels()函数返回轴域上存在的句柄/艺术家的列表,这些句柄/艺术家可以用于为结果图例生成条目 - 但值得注意的是,并非所有艺术家都可以添加到图例中, 这种情况下会创建『代理』(请参阅特地为添加到图例创建艺术家(也称为代理艺术家),来了解更多详细信息)。

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

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

在某些情况下,不可能设置句柄的标签,因此可以将标签列表传递给legend()

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

特地为添加到图例创建艺术家(也称为代理艺术家)

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

假设我们想创建一个图例,其中有一些数据表示为红色:

import matplotlib.patches as mpatches
import matplotlib.pyplot as plt

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

plt.show()

除了创建一个色块之外,有许多受支持的图例句柄,我们可以创建一个带有标记的线条:

import matplotlib.lines as mlines
import matplotlib.pyplot as plt

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

plt.show()

图例位置

图例的位置可以通过关键字参数loc指定。 详细信息请参阅legend()的文档。

bbox_to_anchor关键字可让用户手动控制图例布局。 例如,如果你希望轴域图例位于图像的右上角而不是轴域的边角,则只需指定角的位置以及该位置的坐标系:

plt.legend(bbox_to_anchor=(1, 1),
           bbox_transform=plt.gcf().transFigure)

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

import matplotlib.pyplot as plt


plt.subplot(211)
plt.plot([1,2,3], label="test1")
plt.plot([3,2,1], label="test2")
# 将图例放到这个子图上方,
# 扩展自身来完全利用提供的边界框。
plt.legend(bbox_to_anchor=(0., 1.02, 1., .102), loc=3,
           ncol=2, mode="expand", borderaxespad=0.)

plt.subplot(223)
plt.plot([1,2,3], label="test1")
plt.plot([3,2,1], label="test2")
# 将图例放到这个小型子图的右侧
plt.legend(bbox_to_anchor=(1.05, 1), loc=2, borderaxespad=0.)

plt.show()

相同轴域内的多个图例

有时,在多个图例之间分割图例条目会更加清晰。 虽然直觉上的做法可能是多次调用legend()函数,但你会发现轴域上只存在一个图例。 这样做是为了可以重复调用legend(),将图例更新为轴域上的最新句柄,因此要保留旧的图例实例,我们必须将它们手动添加到轴域中:

import matplotlib.pyplot as plt

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

# 为第一个线条创建图例
first_legend = plt.legend(handles=[line1], loc=1)

# 手动将图例添加到当前轴域
ax = plt.gca().add_artist(first_legend)

# 为第二个线条创建另一个图例
plt.legend(handles=[line2], loc=4)

plt.show()

图例处理器

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

  • 使用handler_map关键字中的值更新get_legend_handler_map()
  • 检查句柄是否在新创建的handler_map中。
  • 检查句柄的类型是否在新创建的handler_map中。
  • 检查句柄的mro中的任何类型是否在新创建的handler_map中。

处于完整性,这个逻辑大多在get_legend_handler()中实现。

所有这些灵活性意味着我们可以使用一些必要的钩子,为我们自己的图例键类型实现自定义处理器。

使用自定义处理器的最简单的例子是,实例化一个现有的HandlerBase子类。 为了简单起见,让我们选择matplotlib.legend_handler.HandlerLine2D,它接受numpoints参数(出于便利,注意numpointslegend()函数上的一个关键字)。 然后我们可以将实例的字典作为关键字handler_map传给legend

import matplotlib.pyplot as plt
from matplotlib.legend_handler import HandlerLine2D

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

plt.legend(handler_map={line1: HandlerLine2D(numpoints=4)})

如你所见,Line 1现在有 4 个标记点,Line 2有两个(默认值)。 尝试上面的代码,只需将字典的键从line1更改为type(line)。 注意现在两个Line2D`实例都拥有了 4 个标记。

除了用于复杂的绘图类型的处理器,如误差条,茎叶图和直方图,默认的handler_map有一个特殊的元组处理器(HandlerTuple),它简单地在顶部一一绘制给定元组中每个项目的句柄。 以下示例演示如何将两个图例的键相互叠加:

import matplotlib.pyplot as plt
from numpy.random import randn

z = randn(10)

red_dot, = plt.plot(z, "ro", markersize=15)
# 将白色十字放置在一些数据上
white_cross, = plt.plot(z[:5], "w+", markeredgewidth=3, markersize=15)

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

实现自定义图例处理器

可以实现自定义处理器,将任何句柄转换为图例的键(句柄不必要是matplotlibartist)。 处理器必须实现legend_artist方法,该方法为要使用的图例返回单个艺术家。 有关legend_artist的详细信息,请参阅legend_artist()

import matplotlib.pyplot as plt
import matplotlib.patches as mpatches

class AnyObject(object):
    pass

class AnyObjectHandler(object):
    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

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

或者,如果我们想要接受全局的AnyObject实例,而不想一直手动设置handler_map关键字,我们可以注册新的处理器:

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

虽然这里的功能十分清楚,请记住,有很多已实现的处理器,你想实现的目标可能易于使用现有的类实现。 例如,要生成椭圆的图例键,而不是矩形键:

from matplotlib.legend_handler import HandlerPatch
import matplotlib.pyplot as plt
import matplotlib.patches as mpatches


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)
plt.gca().add_patch(c)

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

使用图例的现有示例

这里是一个不太详尽的示例列表,涉及以各种方式使用的图例:

matplotlib.pyplot.legend(*args, **kwargs) 文档

在轴域上放置一个图例。

为了为轴域上已经存在的线条(例如通过绘图)制作图例,只需使用字符串的可迭代对象(每个图例条目对应一个字符串)调用此函数。 例如:

ax.plot([1, 2, 3])
ax.legend(['A simple line'])

但是,为了使『标签』和图例元素实例保持一致,最好在艺术家创建时指定标签,或者通过调用艺术家的set_label()方法:

line, = ax.plot([1, 2, 3], label='Inline label')
# 通过调用该方法覆写标签
line.set_label('Label via method')
ax.legend()

通过定义以下划线开头的标签,可以从图例元素自动选择中排除特定线条。 这对于所有艺术家都是默认的,因此不带任何参数调用legend(),并且没有手动设置标签会导致没有绘制图例。

为了完全控制哪些艺术家拥有图例条目,可以传递拥有图例的艺术家的可迭代对象,然后是相应图例标签的可迭代对象:

legend((line1, line2, line3), ('label1', 'label2', 'label3'))

参数

loc:整数、字符串或者浮点偶对,默认为'upper right'

图例的位置。 可能的代码是:

位置字符串 位置代码
'best' 0
'upper right' 1
'upper left' 2
'lower left' 3
'lower right' 4
'right' 5
'center left' 6
'center right' 7
'lower center' 8
'upper center' 9
'center' 10

或者,可以是一个二元组,提供图例的距离左下角的x, y坐标(在这种情况下,bbox_to_anchor将被忽略)。

bbox_to_anchormatplotlib.transforms.BboxBase示例或者浮点元组。

bbox_transform坐标(默认轴域坐标)中为图例指定任意位置。

例如,要将图例的右上角放在轴域中心,可以使用以下关键字:

loc='upper right', bbox_to_anchor=(0.5, 0.5)

ncol:整数。

图例的列数,默认为 1。

propNonematplotlib.font_manager.FontProperties或者字典。

图例的字体属性,如果为None(默认),会使用当前的matplotlib.rcParams

fontsize:整数、浮点或者{‘xx-small’, ‘x-small’, ‘small’, ‘medium’, ‘large’, ‘x-large’, ‘xx-large’}

控制图例的字体大小。 如果值为数字,则大小将为绝对字体大小(以磅为单位)。 字符串值相对于当前默认字体大小。 此参数仅在未指定prop的情况下使用。

numpointsNone或者整数。

为线条/matplotlib.lines.Line2D创建图例条目时,图例中的标记点数。 默认值为None,它将从legend.numpoints rcParam中获取值。

scatterpointsNone或者整数。

为散点图/matplotlib.collections.PathCollection创建图例条目时,图例中的标记点数。 默认值为None,它将从legend.scatterpoints rcParam中获取值。

scatteryoffsets:浮点的可迭代对象。

为散点图图例条目创建的标记的垂直偏移量(相对于字体大小)。 0.0 是在图例文本的底部,1.0 是在顶部。 为了将所有标记绘制在相同的高度,请设置为[0.5]。 默认值为[0.375,0.5,0.3125]

markerscaleNone、整数或者浮点。

图例标记对于原始绘制的标记的相对大小。 默认值为None,它将从legend.markerscale rcParam中获取值。

markerfirst: [ True | False ]

如果为True,则图例标记位于图例标签的左侧,如果为False,图例标记位于图例标签的右侧。

frameonNone或布尔值

控制是否应在图例周围绘制框架。 默认值为None,它将从legend.frameon rcParam中获取值。

fancyboxNone或布尔值

控制是否应在构成图例背景的FancyBboxPatch周围启用圆边。 默认值为None,它将从legend.fancybox rcParam中获取值。

shadowNone或布尔值

控制是否在图例后面画一个阴影。 默认值为None,它将从legend.shadow rcParam中获取值。

framealphaNone或浮点

控制图例框架的 Alpha 透明度。 默认值为None,它将从legend.framealpha rcParam中获取值。

mode{"expand", None}

如果mode设置为"expand",图例将水平扩展来填充轴域区域(如果定义图例的大小,则为bbox_to_anchor)。

bbox_transformNone或者matplotlib.transforms.Transform

边界框的变换(bbox_to_anchor)。 对于None值(默认),将使用AxestransAxes变换。

title:字符串或者None

图例的标题,默认没有标题(None)。

borderpad:浮点或None

图例边框的内边距。 以字体大小为单位度量。 默认值为None,它将从legend.borderpad rcParam中获取值。

labelspacing:浮点或None

图例条目之间的垂直间距。 以字体大小为单位度量。 默认值为None,它将从legend.labelspacing rcParam中获取值。

handlelength:浮点或None

图例句柄的长度。 以字体大小为单位度量。 默认值为None,它将从legend.handlelength rcParam取值。

handletextpad:浮点或None

图例句柄和文本之间的间距。 以字体大小为单位度量。 默认值为None,它将从legend.handletextpad rcParam中获取值。

borderaxespad:浮点或None

轴和图例边框之间的间距。 以字体大小为单位度量。 默认值为None,它将从legend.borderaxespad rcParam中获取值。

columnspacing:浮点或None

列间距。以字体大小为单位度量。 默认值为None,它将从legend.columnspacing rcParam中获取值。

handler_map:字典或None

自定义字典,用于将实例或类型映射到图例处理器。 这个handler_map会更新在matplotlib.legend.Legend.get_legend_handler_map()中获得的默认处理器字典。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,029评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,395评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,570评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,535评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,650评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,850评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,006评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,747评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,207评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,536评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,683评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,342评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,964评论 3 315
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,772评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,004评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,401评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,566评论 2 349

推荐阅读更多精彩内容