控制Matplotlib图例框的宽度以适应换行

4

我正在尝试在 Matplotlib(Python 2.7)中固定图例框的宽度。这是我正在使用的简单代码示例:

import numpy as np
import pylab as plt
fig = plt.figure(figsize=(8.5, 8.5))
ax = fig.add_axes([0.0882, 0.0588, 0.8882, 0.9118])

x = np.array([0,1,2,33])
y = np.array([0.1,2,4,8])
z = np.array([0,1,3.7,7])
t = np.array([0.5,1,12,41])
v = np.array([0.9,7,24,54])
a = np.array([0.2,11,17,61])
q = np.array([0.4,17,15,80])
r = np.array([0.9,3.7,18,44])
s = np.array([0.2,10,19,31])

y1 = y+1
z1 = z+1
t1 = t+1
v1 = v+1
a1 = a+1
q1 = q+1
r1 = r+1
s1 = s+1

ax.plot(x,y,label='y')
ax.plot(x,z,label='z')
ax.plot(x,t,label='t')
ax.plot(x,v,label='v')
ax.plot(x,a,label='a')
ax.plot(x,y1,label='y1')
ax.plot(x,z1,label='z1')
ax.plot(x,t1,label='t1')
ax.plot(x,v1,label='v1')
ax.plot(x,a1,label='a1')
ax.plot(x,q1,label='q1')
ax.plot(x,r1,label='r1')
ax.plot(x,s1,label='s1')

lg = ax.legend(loc='upper center', bbox_to_anchor=(0.5, -0.05), ncol=10)

fig.savefig('A_test_position.png', facecolor='white', dpi = 300, bbox_inches='tight')

如果我只保留前三个 ax.plot() 语句,我得到的结果是 this。但当我包含所有 ax.plot() 语句时,输出变成了 this

问题: 我需要让图例框:

  1. 从x轴0开始
  2. 在x轴上以最大值结束(在这种情况下,最大值为35)
  3. 在达到x轴上的最大值后换行

当条目较少时(例如,第一个图),我希望图例居中。目前我的代码已经正确实现了这一点,所以我不需要改变这种行为。但是,当有很多条目需要换行时,我需要改变这种行为。

如何在Matplotlib中实现?是否有一种方法来指定图例框的大小?

1个回答

4
一个解决方案是通过迭代ncol参数,直到图例的宽度大于轴的宽度。然后,使用找到的最佳ncol值和mode='expand'选项使图例扩展到整个轴的宽度。如果所有条目都可以适合单行中,则保留您的“居中布局”,而不会将图例扩展到轴的宽度。
以下是一个代码示例,演示了如何完成此操作:
ncol = 1
lgwidth_old = 0.
renderer = fig.canvas.get_renderer()
while True:
    lg = ax.legend(loc='upper center', bbox_to_anchor=(0., -0.05, 1., 0.),
                   borderaxespad=0, ncol=ncol)
    fig.canvas.draw()
    lgbbox = lg.get_window_extent(renderer).inverse_transformed(ax.transAxes)

    if lgwidth_old == lgbbox.width:
        # All the entries fit within a single row. Keep the legend
        # as is and break the loop.
        break

    if lgbbox.width < 1:
        # The width of the legend is still smaller than that of the axe.
        # Continue iterating.
        ncol += 1
        lgwidth_old = lgbbox.width
    else: 
        # The width of the legend is larger than that of the axe.
        # Backtrack ncol, plot the legend so it span the entire width of
        # the axe, and break the loop.
        ncol -= 1
        lg = ax.legend(loc='upper center', bbox_to_anchor=(0., -0.05, 1., 0.),
                       borderaxespad=0, ncol=ncol, mode='expand')         
        break

    if ncol > 100:
        print('Number max of iteration reached. Something is wrong.')
        break 

这将在您的代码中添加以下内容:

图片描述


这个可以运行。但是我不明白为什么要用while循环。是否可以改成这样:for ncol in range(1,100): - edesz
@WR 是的,如果您喜欢这种编码风格,可以使用 for 循环来完成此操作。应该会得到相同的结果。我只是更喜欢在这些类型的问题中使用 while 循环,因为开始时迭代次数是不确定的。 - Jean-Sébastien
什么决定了迭代的次数?例如,我的图中有13个符号。这是否意味着for ncol in range(1,13): - edesz
我们无法提前确定迭代次数,否则就没有必要迭代ncol了。循环的结束由条件break控制。在您上面的评论中给出的示例中,13将是最大可能的迭代次数。但我不明白您这样做会得到什么好处。每次都必须显式指定图例中的条目数。最好使用while循环或具有大范围的for循环,并让条件break完成其工作以结束循环。 - Jean-Sébastien

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接