在 matplotlib 中精确控制子图位置

Nat*_*iel 0 python matplotlib

我目前正在为一篇论文制作一个图形,它看起来像这样:

在此处输入图片说明

以上非常接近我想要的样子,但我有一种强烈的感觉,我没有以“正确的方式”做这件事,因为它的制作真的很繁琐,而且我的代码充满了各种魔法我手动微调定位的数字。因此我的问题是,产生这样的情节的正确方法是什么?

以下是该图难以制作的重要特征:

  • 三个子图的纵横比由数据固定,但图像的分辨率并不相同。

  • 我希望所有三个地块都占据人物的全部高度

  • 我希望 (a) 和 (b) 靠得很近,因为它们共享 y 轴,而 (c) 离得更远

  • 理想情况下,我希望顶部颜色条的顶部与三个图像的顶部完全匹配,并且与底部颜色条的底部类似。(实际上它们并没有完全对齐,因为我通过猜测数字并重新编译图像来做到这一点。)

在生成此图时,我首先尝试使用GridSpec,但我无法控制三个主要子图之间的相对间距。然后我尝试了 ImageGrid,它是AxisGrid 工具包的一部分,但三个图像之间的不同分辨率导致其行为异常。深入研究 AxesGrid,我能够使用append_axes函数,但我仍然必须手动定位三个颜色条。(我手动创建了颜色条。)

我宁愿不发布我现有的代码,因为它是一个可怕的黑客和神奇数字的集合。相反,我的问题是,在 MatPlotLib 中是否有任何方法可以指定图形的逻辑布局(即上面项目符号的内容)并自动为我计算布局?

Imp*_*est 6

这是一个可能的解决方案。您将从图形宽度(这在准备论文时有意义)开始,并使用图形的各个方面、子图和边距之间的一些任意间距来计算您的方式。这些公式类似于我在这个答案中使用的公式。而不平等的方面则由GridSpecwidth_ratios论点处理。然后你最终得到一个图形高度,使得子图的高度相等。

所以你不能避免输入一些数字,但它们不是“魔法”。所有这些都与可访问的数量有关,例如图形大小的分数或平均子图大小的分数。由于系统是封闭的,改变任何数字只会产生不同的图形高度,但不会破坏布局。

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import numpy as np; np.random.seed(42)

imgs = []
shapes = [(550,200), ( 550,205), (1100,274) ]
for shape in shapes:
    imgs.append(np.random.random(shape))

# calculate inverse aspect(width/height) for all images
inva = np.array([ img.shape[1]/float(img.shape[0]) for img in imgs])
# set width of empty column used to stretch layout
emptycol = 0.02
r = np.array([inva[0],inva[1], emptycol, inva[2], 3*emptycol, emptycol])
# set a figure width in inch
figw = 8
# border, can be set independently of all other quantities
left = 0.1; right=1-left
bottom=0.1; top=1-bottom
# wspace (=average relative space between subplots)
wspace = 0.1
#calculate scale
s = figw*(right-left)/(len(r)+(len(r)-1)*wspace) 
# mean aspect
masp = len(r)/np.sum(r)
#calculate figheight
figh = s*masp/float(top-bottom)


gs = gridspec.GridSpec(3,len(r), width_ratios=r)

fig = plt.figure(figsize=(figw,figh))
plt.subplots_adjust(left, bottom, right, top, wspace)

ax1 = plt.subplot(gs[:,0])
ax2 = plt.subplot(gs[:,1])
ax2.set_yticks([])

ax3 = plt.subplot(gs[:,3])
ax3.yaxis.tick_right()
ax3.yaxis.set_label_position("right")

cax1 = plt.subplot(gs[0,5])
cax2 = plt.subplot(gs[1,5])
cax3 = plt.subplot(gs[2,5])


im1 = ax1.imshow(imgs[0], cmap="viridis")
im2 = ax2.imshow(imgs[1], cmap="plasma")
im3 = ax3.imshow(imgs[2], cmap="RdBu")

fig.colorbar(im1, ax=ax1, cax=cax1)
fig.colorbar(im2, ax=ax2, cax=cax2)
fig.colorbar(im3, ax=ax3, cax=cax3)

ax1.set_title("image title")
ax1.set_xlabel("xlabel")
ax1.set_ylabel("ylabel")

plt.show()
Run Code Online (Sandbox Code Playgroud)

在此处输入图片说明