Matplotlib内嵌轴(inset axes)导出截断及连线优化问题求助
Matplotlib 3.0.3外部放大子图问题解决
问题背景
使用Matplotlib 3.0.3,通过inset axes对imshow绘图的指定区域放大并放置在主图外部,测试代码如下:
import matplotlib.pyplot as plt import numpy as np fig, ax = plt.subplots(figsize=(6,6)) Z2 = np.random.rand(512, 512) ax.imshow(Z2, interpolation='gaussian', cmap = 'RdBu', origin='lower') ax.tick_params(axis='both', bottom=False, top=False, right=False, left=False, labelbottom=False, labelleft=False, labeltop=False, labelright=False) # 左下角外部子图 axins_1 = ax.inset_axes([0, -1, 1, 1]) axins_1.imshow(Z2, interpolation="gaussian", cmap = 'RdBu', origin='lower') axins_1.tick_params(axis='both', bottom=False, top=False, right=False, left=False, labelbottom=False, labelleft=False, labeltop=False, labelright=False) axins_1.set_xlim(100, 150) axins_1.set_ylim(85, 135) ax.indicate_inset_zoom(axins_1, edgecolor='0') # 右下角外部子图 axins_2 = ax.inset_axes([1, -1, 1, 1]) axins_2.imshow(Z2, interpolation="gaussian", cmap = 'RdBu', origin='lower') axins_2.tick_params(axis='both', bottom=False, top=False, right=False, left=False, labelbottom=False, labelleft=False, labeltop=False, labelright=False) axins_2.set_xlim(400, 450) axins_2.set_ylim(200, 250) ax.indicate_inset_zoom(axins_2, edgecolor='0') # 右上角外部子图 axins_3 = ax.inset_axes([1, 0, 1, 1]) axins_3.imshow(Z2, interpolation="gaussian", cmap = 'RdBu', origin='lower') axins_3.tick_params(axis='both', bottom=False, top=False, right=False, left=False, labelbottom=False, labelleft=False, labeltop=False, labelright=False) axins_3.set_xlim(400, 450) axins_3.set_ylim(400, 450) ax.indicate_inset_zoom(axins_3, edgecolor='0') plt.show()
当前存在两个问题:
- 保存绘图时,外部放大的子图被明显截断;
- 主图放大区域与对应子图的连线交叉,希望修改连线为箭头/标记,或改用独立子图方案并关联区域。
问题1:修复子图保存截断问题
原因是外部inset axes超出了主图默认的布局范围,保存时未被纳入画布。可通过两种方式解决:
方法1:调整布局并指定保存参数
在plt.show()前添加布局调整代码,保存时使用bbox_inches='tight'自动包含所有元素:
# 调整主图周围的空白空间,容纳外部子图 fig.subplots_adjust(bottom=0.25, right=0.75, top=0.75) # 保存图片,确保所有元素被包含 plt.savefig('output.png', bbox_inches='tight', dpi=150)
subplots_adjust的参数根据外部子图的位置调整,bottom给下方子图留空间,right给右侧子图留空间;bbox_inches='tight'会自动扩展画布边界,包含所有可见的绘图元素。
方法2:手动设置画布边界
如果需要更精确的控制,可以手动计算并设置画布的bbox范围:
# 获取所有axes的边界 bbox = fig.get_tightbbox(fig.canvas.get_renderer()) # 保存时使用计算出的bbox plt.savefig('output.png', bbox_inches=bbox.transformed(fig.dpi_scale_trans.inverted()), dpi=150)
问题2:修改连线或改用独立子图
方案1:替换默认连线为箭头
Matplotlib 3.0.3的indicate_inset_zoom不支持直接生成箭头,可手动绘制主图边框和箭头来替代:
以axins_1为例,替换ax.indicate_inset_zoom(axins_1, edgecolor='0')为以下代码:
# 绘制主图上的放大区域边框 x1, x2 = 100, 150 y1, y2 = 85, 135 rect = plt.Rectangle((x1, y1), x2-x1, y2-y1, fill=False, edgecolor='black', lw=1) ax.add_patch(rect) # 获取主图区域中心和inset axes的位置 rect_center = ((x1+x2)/2, (y1+y2)/2) inset_pos = axins_1.get_position() # 绘制箭头从主图区域指向子图 ax.annotate('', xy=(inset_pos.x0 + inset_pos.width/2, inset_pos.y1), # 子图顶部中心 xytext=rect_center, # 主图区域中心 arrowprops=dict(arrowstyle='->', color='black', lw=1))
此方法可完全自定义箭头样式、位置,避免连线交叉。
方案2:改用独立子图关联区域
如果希望更灵活的子图布局,可使用fig.add_axes()创建独立子图,再绘制关联标记:
示例代码片段:
# 创建主图 fig, ax = plt.subplots(figsize=(6,6)) ax.imshow(Z2, interpolation='gaussian', cmap='RdBu', origin='lower') ax.tick_params(axis='both', bottom=False, top=False, right=False, left=False, labelbottom=False, labelleft=False, labeltop=False, labelright=False) # 创建左下角独立子图(位置参数:左, 下, 宽, 高,单位为画布比例) axins_1 = fig.add_axes([0.05, 0.05, 0.3, 0.3]) axins_1.imshow(Z2, interpolation='gaussian', cmap='RdBu', origin='lower') axins_1.set_xlim(100, 150) axins_1.set_ylim(85, 135) axins_1.tick_params(axis='both', bottom=False, top=False, right=False, left=False, labelbottom=False, labelleft=False, labeltop=False, labelright=False) # 绘制主图边框和关联箭头 x1, x2 = 100, 150 y1, y2 = 85, 135 rect = plt.Rectangle((x1, y1), x2-x1, y2-y1, fill=False, edgecolor='black', lw=1) ax.add_patch(rect) # 箭头从主图区域底部指向子图顶部 ax.annotate('', xy=(axins_1.get_position().x0 + axins_1.get_position().width/2, axins_1.get_position().y1), xytext=((x1+x2)/2, y1), arrowprops=dict(arrowstyle='->', color='black', lw=1))
独立子图的位置更易控制,也不会出现inset axes的布局截断问题。
内容的提问来源于stack exchange,提问作者Matthew Cassell
相关产品推荐
相关产品推荐

