在Matplotlib中灵活生成图例。
本图例指南是legend():https://matplotlib.org/stable/api/_as_gen/matplotlib.pyplot.legend.html#matplotlib.pyplot.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])
完整代码:
·
·
·
·
·
·
·
ipython%matplotlib tkimport matplotlib.pyplot as pltfig, 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])
在某些情况下,无法设置句柄的标签,因此可以将标签列表传递给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'])
完整代码:
·
·
·
·
·
·
·
ipython%matplotlib tkimport matplotlib.pyplot as pltfig, 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'])
get_legend_handles_labels():
https://matplotlib.org/stable/api/_as_gen/matplotlib.axes.Axes.get_legend_handles_labels.html#matplotlib.axes.Axes.get_legend_handles_labels
创建专门用于添加到图例的艺术家(又名代理艺术家):
https://matplotlib.org/stable/tutorials/intermediate/legend_guide.html#proxy-legend-handles
创建专门用于添加到图例的艺术家(又名代理艺术家)
并非所有句柄都可以自动转换为图例条目,因此通常需要创建一个可以转换的艺术家。创建的图例句柄不必存在于图表(Figure)或图表域(Axes)容器中:
假设我们想要创建一个图例,其中包含一些数据的条目,这些数据用红色表示:
·
·
·
·
·
·
·
·
import matplotlib.patches as mpatchesimport matplotlib.pyplot as plt
fig, ax = plt.subplots()red_patch = mpatches.Patch(color='red', label='The red data')ax.legend(handles=[red_patch])
plt.show()
有很多受支持的图例句柄。我们可以创建一条带有标记的线,而不是创建一块颜色:
·
·
·
·
·
·
·
·
·
import matplotlib.lines as mlinesimport matplotlib.pyplot as plt
fig, ax = plt.subplots()blue_line = mlines.Line2D([], [], color='blue', marker='*', markersize=15, label='Blue stars')ax.legend(handles=[blue_line])
plt.show()
图例位置
图例的位置可以由关键字参数loc指定。有关详细信息,请参阅legend()上的文档。
bbox_to_anchor关键字为图例手动放置提供了很大程度的控制。例如,如果希望图表域(axes)图例位于图表(Figure)的右上角而不是图表域(axes)的角,只需指定图例的哪个角和该角所处的位置的坐标系:
·
·
ax.legend(bbox_to_anchor=(1, 1), bbox_transform=fig.transFigure)
完整代码:
·
·
·
·
·
·
·
·
·
·
import matplotlib.lines as mlinesimport matplotlib.pyplot as pltfig, ax = plt.subplots()blue_line = mlines.Line2D([], [], color='blue', marker='*', markersize=15, label='Blue stars')ax.legend(handles=[blue_line], bbox_to_anchor=(1, 1), bbox_transform=fig.transFigure)
plt.show()
自定义图例放置的更多示例:
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
import matplotlib.pyplot as pltfig, 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', ncol=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.)
plt.show()
同一图表域(Axes)上的多个图例
有时,将图例条目拆分为多个图例更为清晰。虽然这样做的直觉方法可能是多次调用legend()函数,但你会发现图表域(Axes)上还是只存在一个图例。这样就可以重复调用legend()来将图例更新为图表域(Axes)上的最新句柄图例。要保留旧图例实例,我们必须手动将它们添加到图表域(Axes)中:
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
import matplotlib.pyplot as pltfig, 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()
图例处理者
为了创建图例条目,句柄作为参数提供给适当的HandlerBase子类。处理者子类的选择由以下规则决定:
1、使用handler_map关键字中的值更新get_legend_handler_map()。
2、检查句柄是否在新创建的handler_map中。
3、检查句柄类型是否在新创建的handler_map中。
4、检查句柄的mro中是否有任何类型在新创建的handler_map中。
为了完整起见,这个逻辑主要在get_legend_handler()中实现。
所有这些灵活性,意味着我们有必要用钩子(hooks)来为自己类型的图例键实现自定义处理者。
使用自定义处理者的最简单示例是实例化一个现有的legend_handler.HandlerBase子类。为了简单起见,就选择legend_handler.HandlerLine2D,它接受numpoints参数(为了方便,numpoints也是legend()函数上的关键字)。然后,我们可以将实例到处理者的映射作为关键字传递给图例。
·
·
·
·
·
·
·
·
import matplotlib.pyplot as pltfrom 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)})
如上图,“Line 1”有4个标记点,其中“Line 2”有1个(默认值)。尝试以上代码,只需将地图的键从line1更改为type(line1)。即发现,两个Line2D实例都获得了4个标记。
·
·
·
·
·
·
·
·
import matplotlib.pyplot as pltfrom 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={type(line1): HandlerLine2D(numpoints=4)})
除了用于复杂绘图类型(如误差条形图、针头图和直方图)的处理者之外,默认的handler_map还有一个特殊的元组处理者(legend_handler.HandlerTuple),它按给定元组中的每个句柄的顺序,一个接一个地绘制在前一个的上面而成。以下示例演示了将两个图例键叠加在一起:
·
·
·
·
·
·
·
·
·
·
·
import matplotlib.pyplot as pltfrom numpy.random import randnnp.random.seed(19680801) # seed the random number generator.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_handler.HandlerTuple类也可用于将多个图例键分配给同一条目:
·
·
·
·
·
·
·
·
·
·
import matplotlib.pyplot as pltfrom matplotlib.legend_handler import 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)})fig.show()
HandlerBase:
https://matplotlib.org/stable/api/legend_handler_api.html#matplotlib.legend_handler.HandlerBase
get_legend_handler_map():
https://matplotlib.org/stable/api/legend_api.html#matplotlib.legend.Legend.get_legend_handler_map
get_legend_handler():
https://matplotlib.org/stable/api/legend_api.html#matplotlib.legend.Legend.get_legend_handler
legend_handler.HandlerBase:
https://matplotlib.org/stable/api/legend_handler_api.html#matplotlib.legend_handler.HandlerBase
legend_handler.HandlerLine2D:
https://matplotlib.org/stable/api/legend_handler_api.html#matplotlib.legend_handler.HandlerLine2D
Line2D:
https://matplotlib.org/stable/api/_as_gen/matplotlib.lines.Line2D.html#matplotlib.lines.Line2D
legend_handler.HandlerTuple:
https://matplotlib.org/stable/api/legend_handler_api.html#matplotlib.legend_handler.HandlerTuple
实现自定义图例处理者
可以实现自定义处理者将任何句柄转换为图例键(句柄不一定需要是matplotlib艺术家)。处理者必须实现legend_artist方法,该方法返回一个艺术家供图例使用。legend_artist中讲述了legend_artist方法所需的签名(参数)。
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
import matplotlib.pyplot as pltimport 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()})fig.show()
或者,如果我们想全局接受AnyObject实例,而不需要一直手动设置handler_map关键字,我们可以使用以下方式注册新的处理者:
·
·
from matplotlib.legend import LegendLegend.update_default_handler_map({AnyObject: AnyObjectHandler()})
完整代码:
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
import matplotlib.pyplot as pltimport 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()
from matplotlib.legend import LegendLegend.update_default_handler_map({AnyObject: AnyObjectHandler()})
ax.legend([AnyObject()], ['My first default handler'])fig.show()
虽然这里的功能很清楚,但请记住,系统已经实现了很多处理者,你想要实现的功能可能已经可以通过现有的类轻松实现。例如,要生成椭圆图例键,而不是矩形图例键:
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
·
import matplotlib.pyplot as pltimport matplotlib.patches as mpatchesfrom 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()})fig.show()
legend_artist:
https://matplotlib.org/stable/api/legend_handler_api.html#matplotlib.legend_handler.HandlerBase.legend_artist