我花了太长时间研究如何在Matplotlib中让两个子图共享相同的y轴,并在两者之间共享一个颜色条。
发生的事情是,当我在subplot1或subplot2中调用colorbar()函数时,它会自动缩放图形,以便颜色条加上图形将适合'subplot'边界框,导致两个并排的图形具有两个非常不同的大小。
为了解决这个问题,我试着创建了第三个子图,然后我把它黑了,只渲染一个颜色条。
唯一的问题是,现在两个地块的高度和宽度不均匀,我不知道如何让它看起来还好。
这是我的代码:
from __future__ import division
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import patches
from matplotlib.ticker import NullFormatter
# SIS Functions
TE = 1 # Einstein radius
g1 = lambda x,y: (TE/2) * (y**2-x**2)/((x**2+y**2)**(3/2))
g2 = lambda x,y: -1*TE*x*y / ((x**2+y**2)**(3/2))
kappa = lambda x,y: TE / (2*np.sqrt(x**2+y**2))
coords = np.linspace(-2,2,400)
X,Y = np.meshgrid(coords,coords)
g1out = g1(X,Y)
g2out = g2(X,Y)
kappaout = kappa(X,Y)
for i in range(len(coords)):
for j in range(len(coords)):
if np.sqrt(coords[i]**2+coords[j]**2) <= TE:
g1out[i][j]=0
g2out[i][j]=0
fig = plt.figure()
fig.subplots_adjust(wspace=0,hspace=0)
# subplot number 1
ax1 = fig.add_subplot(1,2,1,aspect='equal',xlim=[-2,2],ylim=[-2,2])
plt.title(r"$\gamma_{1}$",fontsize="18")
plt.xlabel(r"x ($\theta_{E}$)",fontsize="15")
plt.ylabel(r"y ($\theta_{E}$)",rotation='horizontal',fontsize="15")
plt.xticks([-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5])
plt.xticks([-2.0,-1.5,-1.0,-0.5,0,0.5,1.0,1.5])
plt.imshow(g1out,extent=(-2,2,-2,2))
plt.axhline(y=0,linewidth=2,color='k',linestyle="--")
plt.axvline(x=0,linewidth=2,color='k',linestyle="--")
e1 = patches.Ellipse((0,0),2,2,color='white')
ax1.add_patch(e1)
# subplot number 2
ax2 = fig.add_subplot(1,2,2,sharey=ax1,xlim=[-2,2],ylim=[-2,2])
plt.title(r"$\gamma_{2}$",fontsize="18")
plt.xlabel(r"x ($\theta_{E}$)",fontsize="15")
ax2.yaxis.set_major_formatter( NullFormatter() )
plt.axhline(y=0,linewidth=2,color='k',linestyle="--")
plt.axvline(x=0,linewidth=2,color='k',linestyle="--")
plt.imshow(g2out,extent=(-2,2,-2,2))
e2 = patches.Ellipse((0,0),2,2,color='white')
ax2.add_patch(e2)
# subplot for colorbar
ax3 = fig.add_subplot(1,1,1)
ax3.axis('off')
cbar = plt.colorbar(ax=ax2)
plt.show()
As pointed out in other answers, the idea is usually to define an axes for the colorbar to reside in. There are various ways of doing so; one that hasn't been mentionned yet would be to directly specify the colorbar axes at subplot creation with plt.subplots(). The advantage is that the axes position does not need to be manually set and in all cases with automatic aspect the colorbar will be exactly the same height as the subplots. Even in many cases where images are used the result will be satisfying as shown below.
当使用plt.subplots()时,使用gridspec_kw参数可以使色条轴比其他轴小得多。
fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(5.5,3),
gridspec_kw={"width_ratios":[1,1, 0.05]})
例子:
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(5.5,3),
gridspec_kw={"width_ratios":[1,1, 0.05]})
fig.subplots_adjust(wspace=0.3)
im = ax.imshow(np.random.rand(11,8), vmin=0, vmax=1)
im2 = ax2.imshow(np.random.rand(11,8), vmin=0, vmax=1)
ax.set_ylabel("y label")
fig.colorbar(im, cax=cax)
plt.show()
如果图的方面是自动缩放的,或者图像由于它们在宽度方向上的方面而收缩(如上所示),那么这种方法工作得很好。然而,如果图像是宽的,然后是高的,结果将是如下所示,这可能是不希望看到的。
将颜色条高度固定到子图高度的解决方案是使用mpl_toolkit .axes_grid1.inset_locator。InsetPosition用于设置相对于图像子图轴的色条轴。
import matplotlib.pyplot as plt
import numpy as np; np.random.seed(1)
from mpl_toolkits.axes_grid1.inset_locator import InsetPosition
fig, (ax, ax2, cax) = plt.subplots(ncols=3,figsize=(7,3),
gridspec_kw={"width_ratios":[1,1, 0.05]})
fig.subplots_adjust(wspace=0.3)
im = ax.imshow(np.random.rand(11,16), vmin=0, vmax=1)
im2 = ax2.imshow(np.random.rand(11,16), vmin=0, vmax=1)
ax.set_ylabel("y label")
ip = InsetPosition(ax2, [1.05,0,0.05,1])
cax.set_axes_locator(ip)
fig.colorbar(im, cax=cax, ax=[ax,ax2])
plt.show()
I noticed that almost every solution posted involved ax.imshow(im, ...) and did not normalize the colors displayed to the colorbar for the multiple subfigures. The im mappable is taken from the last instance, but what if the values of the multiple im-s are different? (I'm assuming these mappables are treated in the same way that the contour-sets and surface-sets are treated.) I have an example using a 3d surface plot below that creates two colorbars for a 2x2 subplot (one colorbar per one row). Although the question asks explicitly for a different arrangement, I think the example helps clarify some things. I haven't found a way to do this using plt.subplots(...) yet because of the 3D axes unfortunately.
如果我能以更好的方式定位颜色条就好了……(可能有更好的方法来做到这一点,但至少它应该不会太难遵循。)
import matplotlib
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
cmap = 'plasma'
ncontours = 5
def get_data(row, col):
""" get X, Y, Z, and plot number of subplot
Z > 0 for top row, Z < 0 for bottom row """
if row == 0:
x = np.linspace(1, 10, 10, dtype=int)
X, Y = np.meshgrid(x, x)
Z = np.sqrt(X**2 + Y**2)
if col == 0:
pnum = 1
else:
pnum = 2
elif row == 1:
x = np.linspace(1, 10, 10, dtype=int)
X, Y = np.meshgrid(x, x)
Z = -np.sqrt(X**2 + Y**2)
if col == 0:
pnum = 3
else:
pnum = 4
print("\nPNUM: {}, Zmin = {}, Zmax = {}\n".format(pnum, np.min(Z), np.max(Z)))
return X, Y, Z, pnum
fig = plt.figure()
nrows, ncols = 2, 2
zz = []
axes = []
for row in range(nrows):
for col in range(ncols):
X, Y, Z, pnum = get_data(row, col)
ax = fig.add_subplot(nrows, ncols, pnum, projection='3d')
ax.set_title('row = {}, col = {}'.format(row, col))
fhandle = ax.plot_surface(X, Y, Z, cmap=cmap)
zz.append(Z)
axes.append(ax)
## get full range of Z data as flat list for top and bottom rows
zz_top = zz[0].reshape(-1).tolist() + zz[1].reshape(-1).tolist()
zz_btm = zz[2].reshape(-1).tolist() + zz[3].reshape(-1).tolist()
## get top and bottom axes
ax_top = [axes[0], axes[1]]
ax_btm = [axes[2], axes[3]]
## normalize colors to minimum and maximum values of dataset
norm_top = matplotlib.colors.Normalize(vmin=min(zz_top), vmax=max(zz_top))
norm_btm = matplotlib.colors.Normalize(vmin=min(zz_btm), vmax=max(zz_btm))
cmap = cm.get_cmap(cmap, ncontours) # number of colors on colorbar
mtop = cm.ScalarMappable(cmap=cmap, norm=norm_top)
mbtm = cm.ScalarMappable(cmap=cmap, norm=norm_btm)
for m in (mtop, mbtm):
m.set_array([])
# ## create cax to draw colorbar in
# cax_top = fig.add_axes([0.9, 0.55, 0.05, 0.4])
# cax_btm = fig.add_axes([0.9, 0.05, 0.05, 0.4])
cbar_top = fig.colorbar(mtop, ax=ax_top, orientation='vertical', shrink=0.75, pad=0.2) #, cax=cax_top)
cbar_top.set_ticks(np.linspace(min(zz_top), max(zz_top), ncontours))
cbar_btm = fig.colorbar(mbtm, ax=ax_btm, orientation='vertical', shrink=0.75, pad=0.2) #, cax=cax_btm)
cbar_btm.set_ticks(np.linspace(min(zz_btm), max(zz_btm), ncontours))
plt.show()
plt.close(fig)
## orientation of colorbar = 'horizontal' if done by column