Matplotlib 如何调整图例中两列的行对齐方式
在使用Matplotlib绘图的过程中,经常需要加入图例来说明不同曲线或图形的含义,而图例的布局方式也是需要注意的。如果图例的条目过多,有时需要将图例布局成两列,而这时候就会遇到一些行对齐的问题。本文将介绍如何使用Matplotlib来调整图例的行对齐方式,实现两列图例中的行对齐。
阅读更多:Matplotlib 教程
绘制带图例的图形
在介绍行对齐问题之前,我们先来看一个简单的例子,如何在Matplotlib中绘制带图例的图形。
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 5, 0.1)
y1 = np.sin(x)
y2 = np.cos(x)
y3 = np.tan(x)
fig, ax = plt.subplots()
ax.plot(x, y1, label='sin(x)')
ax.plot(x, y2, label='cos(x)')
ax.plot(x, y3, label='tan(x)')
ax.legend(loc='upper right')
plt.show()
这段代码会生成一个包含正弦、余弦和正切函数的折线图,并在图形的右上方加入一个图例。其中,ax.plot()
函数用于绘制曲线,ax.legend()
函数用于添加图例,loc='upper right'
用于设置图例的位置。
从图中可以看到,图例中包含了三条曲线的标签和颜色,使得读者可以更好地理解这幅图形。
Matplotlib图例行对齐问题
在某些情况下,一张图上需要同时显示多条曲线的标签和颜色,而这时候图例就不可避免地会出现行对齐的问题。比如,我们将前面的例子中的三条曲线的数量增加到10条,并将图例布局成两列:
import numpy as np
import matplotlib.pyplot as plt
x = np.arange(0, 5, 0.1)
y = np.zeros((10, len(x)))
for i in range(10):
y[i] = np.sin(x + i/2)*np.exp(i/20)
fig, ax = plt.subplots()
for i in range(10):
ax.plot(x, y[i], label=f'sin(x+{i/2})exp({i/20})')
ax.legend(loc='upper right', ncol=2)
plt.show()
这段代码会生成一个包含10条曲线的折线图,并在图形的右上方加入一个图例,图例布局成两列。ncol=2
用于设置图例的列数。
从图中可以看到,虽然图例已经按照两列布局,但是每列的行之间并不对齐,导致图例看起来比较杂乱。这时候就需要对图例的布局进行调整,使得两列的行在相同的高度上对齐,这样才能更好地展示图例中的信息。
调整图例行对齐方式
Matplotlib提供了多种方法来调整图例的行对齐方式,可以使用下面介绍的一些方法来实现在两列图例中的行对齐。
使用legend_handler
Matplotlib中的legend_handler可以用来处理图例的布局,其中最常用的是HandlerTuple、HandlerLine2D和HandlerPatch。当我们需要自定义图例的layout时,常用HandlerTuple来实现对图例条目的处理。
我们需要先定义一个新的handler类MyHandler,它继承自HandlerTuple,并重写其中的create_artists()方法来实现对图例条目的处理。具体代码如下:
class MyHandler(HandlerTuple):
def __init__(self, **kwargs):
HandlerTuple.__init__(self, **kwargs)
def create_artists(self, legend, ori_handle, xdescent, ydescent,
width, height, fontsize, trans):
handlers = []
for handle in ori_handle:
handler = HandlerLine2D(numpoints=1)
artist = handler.create_artists(legend, handle,
xdescent, ydescent,
width, height, fontsize, trans)
handlers.extend(artist)
return handlers
这个handler类的主要目的是将一列中不同的图例条目转换为一个tuple,然后方便布局。然后我们就可以在绘图时调用这个handler来添加图例,并指定布局方式,例如:
fig, ax = plt.subplots()
for i in range(10):
ax.plot(x, y[i], label=f'sin(x+{i/2})exp({i/20})')
leg = ax.legend(handler_map={MyHandler: HandlerTuple(ndivide=None)}, ncol=2, fontsize=8)
for legobj in leg.legendHandles:
legobj.set_linewidth(2.0)
plt.show()
这段代码中,我们使用handler_map
参数来指定对图例的处理方式为MyHandler
,然后可以设置ncol
和fontsize
参数来调整图例布局和字体大小。
从图中可以看到,在图例的布局中,两列的行已经对齐了,并且每一行内的图例条目也被分组展示。这种方法可以用于处理行对齐的问题,同时还可以对图例条目进行自由的定制。
使用legend_grid
另一种常用的处理图例布局的方法是使用legend_grid。这个方法的主要思想是将图例条目分别放置在一个网格中,然后按照相同的行高进行对齐。指定legend_grid的方式通常是在legend()函数中使用参数mode='expand'
和ncol=N
,并将返回的legend对象转化为legend_grid对象,之后通过legend_grid对象的属性调整行高和列宽,最后再将legend_grid对象转化为legend对象。
import matplotlib.gridspec as gridspec
fig, ax = plt.subplots()
for i in range(10):
ax.plot(x, y[i], label=f'sin(x+{i/2})exp({i/20})')
leg = ax.legend(ncol=2, fontsize=8, mode='expand')
plt.draw()
renderer = fig.canvas.get_renderer()
bbox = leg.get_window_extent(renderer)
border = 0.05
width = bbox.width/(2+border)
height = width/2
gs = gridspec.GridSpecFromSubplotSpec(2, 2, subplot_spec=ax.set_position([0.1, 0.2, width*2, height*5]), wspace=0.05, hspace=0.05)
axes = []
for i in range(10):
row = i // 4
col = i % 2
ax_legend = plt.Subplot(fig, gs[row, col])
ax_legend.set_axis_off()
ax_legend.plot([0, 1], [1, 1], lw=2, color=f'C{i}')
ax_legend.text(0.5, 0.5, f'sin(x+{i/2})exp({i/20})', ha='center', va='center', fontsize=6, color=f'C{i}')
fig.add_subplot(ax_legend)
axes.append(ax_legend)
leg2 = legend_grid(axes, ncol=2, position='center', fontsize=8, border=False, columnspacing=0.2)
plt.show()
这段代码首先绘制图形,然后使用legend()
函数来添加图例,并将返回的legend
对象转化为legend_grid
对象,并使用get_window_extent()
来获取图例组件的边界框。接下来按照一定的比例计算每个网格的宽度和高度,并创建一个网格gs
来存放每个网格,并将其添加到图形中。然后在每个网格中创建一个新的subplot对象,并在其中绘制每个图例条目。最后将这些图例条目放置到一个新的legend_grid
中,并调整其布局和字体等属性。
从图中可以看到,在使用legend_grid的方式下,两列的行也已经对齐了,并且每一行内的图例条目都被分组展示。这种方法相比于使用handler来处理图例,需要添加更多的代码,但是可以更加精确地控制图例的布局。
总结
Matplotlib提供了多种方法来调整图例的行对齐方式,包括使用legend_handler和legend_grid等方法。在使用这些方法的时候,需要根据实际情况选择合适的参数并进行调整,才能得到更好的图形效果。