Matplotlib使用tight_layout时处理长图例导致子图额外水平空间的高效解决方案
Matplotlib使用tight_layout时处理长图例导致子图额外水平空间的高效解决方案
我在用Matplotlib绘制4×2网格的子图时遇到了个问题:matplotlib.pyplot.tight_layout本来可以自动把子图、图例和文本标签都适配到图里,方便保存成PNG。但图例特别长的时候,tight_layout会给某些子图加上多余的水平空间,看起来很不协调。
我想找个高效的办法避免这个问题,subplots_adjust看起来能用,但调参数太靠试错了,希望能找到用tight_layout就能自动解决的快捷方案。下面是我的最小复现代码:
import numpy as np import matplotlib.pyplot as plt t = np.linspace(-5,5,100) x1 = np.sin(t) x2 = np.cos(t) x3 = np.sin(2*t) x4 = np.cos(2*t) x5 = 2*np.sin(t) x6 = 2*np.cos(t) x7 = np.sin(0.5*t) x8 = np.cos(0.5*t) fig, ax = plt.subplots(nrows=4, ncols=2, figsize=(10, 7)) for r in range(4): for c in range(2): ax[r,c].plot(t,x1,label='preliminary 1') ax[r,c].plot(t,x2,label='preliminary 2') ax[r,c].plot(t,x3,label='trial 1, result 1') ax[r,c].plot(t,x4,label='trial 1, result 2') ax[r,c].plot(t,x5,label='trial 1, result 6') ax[r,c].plot(t,x6,label='trial 4, result 1') ax[r,c].plot(t,x7,label='trial 12, result 2') ax[r,c].plot(t,x8,label='trial 15, result 2') ax[0,1].legend(loc='best', bbox_to_anchor = (0.3, -1.1, 1.2, 2)) plt.tight_layout() plt.savefig('myfig.png') plt.show()

针对这个问题,我整理了几个高效的解决方案,你可以根据自己的需求选择:
解决方案一:改用constrained_layout(最省心)
constrained_layout是Matplotlib后续推出的布局工具,比tight_layout的算法更智能,能自动识别并处理图例、颜色条这类超出子图范围的元素,不用手动调整参数。你只需要在创建子图时加上这个参数即可:
import numpy as np import matplotlib.pyplot as plt t = np.linspace(-5,5,100) x1 = np.sin(t) x2 = np.cos(t) x3 = np.sin(2*t) x4 = np.cos(2*t) x5 = 2*np.sin(t) x6 = 2*np.cos(t) x7 = np.sin(0.5*t) x8 = np.cos(0.5*t) # 创建子图时启用constrained_layout fig, ax = plt.subplots(nrows=4, ncols=2, figsize=(10, 7), constrained_layout=True) for r in range(4): for c in range(2): ax[r,c].plot(t,x1,label='preliminary 1') ax[r,c].plot(t,x2,label='preliminary 2') ax[r,c].plot(t,x3,label='trial 1, result 1') ax[r,c].plot(t,x4,label='trial 1, result 2') ax[r,c].plot(t,x5,label='trial 1, result 6') ax[r,c].plot(t,x6,label='trial 4, result 1') ax[r,c].plot(t,x7,label='trial 12, result 2') ax[r,c].plot(t,x8,label='trial 15, result 2') # 调整图例位置为子图正下方,分多列显示更紧凑 ax[0,1].legend(loc='upper center', bbox_to_anchor=(0.5, -0.3), ncol=4) plt.savefig('myfig.png') plt.show()
解决方案二:创建全局图例(适合子图图例一致的场景)
如果所有子图的图例内容都相同,完全可以只创建一个全局图例,放在整个画布的底部,这样就不会干扰子图的布局空间,整体也更整洁:
import numpy as np import matplotlib.pyplot as plt t = np.linspace(-5,5,100) x1 = np.sin(t) x2 = np.cos(t) x3 = np.sin(2*t) x4 = np.cos(2*t) x5 = 2*np.sin(t) x6 = 2*np.cos(t) x7 = np.sin(0.5*t) x8 = np.cos(0.5*t) fig, ax = plt.subplots(nrows=4, ncols=2, figsize=(10, 7)) # 先绘制所有曲线,从第一个子图获取图例的句柄和标签 lines, labels = ax[0,0].get_legend_handles_labels() for r in range(4): for c in range(2): ax[r,c].plot(t,x1) ax[r,c].plot(t,x2) ax[r,c].plot(t,x3) ax[r,c].plot(t,x4) ax[r,c].plot(t,x5) ax[r,c].plot(t,x6) ax[r,c].plot(t,x7) ax[r,c].plot(t,x8) # 创建全局图例,放在画布底部中央,分4列显示 fig.legend(lines, labels, loc='lower center', ncol=4, bbox_to_anchor=(0.5, -0.05)) # 用tight_layout的rect参数给底部图例留出空间 plt.tight_layout(rect=[0, 0.1, 1, 1]) plt.savefig('myfig.png') plt.show()
解决方案三:手动让tight_layout识别图例
如果你必须坚持使用tight_layout,可以把图例对象作为额外元素传给它,让布局算法计算时把图例的空间也考虑进去,避免出现多余的空白:
import numpy as np import matplotlib.pyplot as plt t = np.linspace(-5,5,100) x1 = np.sin(t) x2 = np.cos(t) x3 = np.sin(2*t) x4 = np.cos(2*t) x5 = 2*np.sin(t) x6 = 2*np.cos(t) x7 = np.sin(0.5*t) x8 = np.cos(0.5*t) fig, ax = plt.subplots(nrows=4, ncols=2, figsize=(10, 7)) for r in range(4): for c in range(2): ax[r,c].plot(t,x1,label='preliminary 1') ax[r,c].plot(t,x2,label='preliminary 2') ax[r,c].plot(t,x3,label='trial 1, result 1') ax[r,c].plot(t,x4,label='trial 1, result 2') ax[r,c].plot(t,x5,label='trial 1, result 6') ax[r,c].plot(t,x6,label='trial 4, result 1') ax[r,c].plot(t,x7,label='trial 12, result 2') ax[r,c].plot(t,x8,label='trial 15, result 2') # 保存图例对象 leg = ax[0,1].legend(loc='best', bbox_to_anchor=(0.3, -1.1, 1.2, 2)) # 将图例传入tight_layout的bbox_extra_artists参数 plt.tight_layout(bbox_extra_artists=[leg]) plt.savefig('myfig.png') plt.show()
备注:内容来源于stack exchange,提问作者Medulla Oblongata
相关产品推荐
相关产品推荐

