我花了太长时间研究如何在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()

只需将颜色条放在它自己的轴上,并使用subplots_adjust为它腾出空间。

举个简单的例子:

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=2, ncols=2)
for ax in axes.flat:
    im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)

fig.subplots_adjust(right=0.8)
cbar_ax = fig.add_axes([0.85, 0.15, 0.05, 0.7])
fig.colorbar(im, cax=cbar_ax)

plt.show()

请注意,即使值的范围是由vmin和vmax设置的,颜色范围也将由最后绘制的图像(产生im的图像)设置。例如,如果另一个图具有更高的最大值,则比im的最大值更高的点将以统一的颜色显示。


使用make_axes甚至更简单,并且可以得到更好的结果。它还提供了自定义颜色条位置的可能性。 还要注意共享x轴和y轴的子图选项。

import numpy as np
import matplotlib.pyplot as plt
import matplotlib as mpl

fig, axes = plt.subplots(nrows=2, ncols=2, sharex=True, sharey=True)
for ax in axes.flat:
    im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)

cax,kw = mpl.colorbar.make_axes([ax for ax in axes.flat])
plt.colorbar(im, cax=cax, **kw)

plt.show()


您可以使用带有坐标轴列表的figure.colorbar()的axparameter简化Joe Kington的代码。 从文档中可以看到:

斧头 没有|父轴对象,其中新的色条轴的空间将被窃取。如果给出了坐标轴列表,它们将全部调整大小,以便为颜色条坐标轴腾出空间。

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=2, ncols=2)
for ax in axes.flat:
    im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)

fig.colorbar(im, ax=axes.ravel().tolist())

plt.show()


abevieiramota使用坐标轴列表的解决方案非常有效,直到你只使用一行图像,正如评论中指出的那样。使用一个合理的长宽比来显示图像大小是有帮助的,但还远远不够完美。例如:

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=1, ncols=3, figsize=(9.75, 3))
for ax in axes.flat:
    im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)

fig.colorbar(im, ax=axes.ravel().tolist())

plt.show()

colorbar函数提供了收缩参数,这是一个颜色条轴大小的缩放因子。这确实需要一些手工试验和错误。例如:

fig.colorbar(im, ax=axes.ravel().tolist(), shrink=0.75)


这个解决方案不需要手动调整轴位置或颜色条大小,适用于多行和单行布局,并适用于tight_layout()。它改编自一个图库示例,使用了matplotlib的AxesGrid工具箱中的ImageGrid。

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid

# Set up figure and image grid
fig = plt.figure(figsize=(9.75, 3))

grid = ImageGrid(fig, 111,          # as in plt.subplot(111)
                 nrows_ncols=(1,3),
                 axes_pad=0.15,
                 share_all=True,
                 cbar_location="right",
                 cbar_mode="single",
                 cbar_size="7%",
                 cbar_pad=0.15,
                 )

# Add data to image grid
for ax in grid:
    im = ax.imshow(np.random.random((10,10)), vmin=0, vmax=1)

# Colorbar
ax.cax.colorbar(im)
ax.cax.toggle_label(True)

#plt.tight_layout()    # Works, but may still require rect paramater to keep colorbar labels visible
plt.show()


作为一个无意中发现这条线程的初学者,我想添加一个python for dummies对abevieiramota非常简洁的回答的改编(因为我的水平是,我必须查找“ravel”来了解他们的代码在做什么):

import numpy as np
import matplotlib.pyplot as plt

fig, ((ax1,ax2,ax3),(ax4,ax5,ax6)) = plt.subplots(2,3)

axlist = [ax1,ax2,ax3,ax4,ax5,ax6]

first = ax1.imshow(np.random.random((10,10)), vmin=0, vmax=1)
third = ax3.imshow(np.random.random((12,12)), vmin=0, vmax=1)

fig.colorbar(first, ax=axlist)

plt.show()

更少的python,更容易让像我这样的新手看到这里到底发生了什么。


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

为了补充@abevieiramota的精彩答案,你可以用constrained_layout得到等价的tight_layout。如果你使用imshow而不是pcolormesh,你仍然会得到很大的水平间隙,因为imshow施加了1:1的纵横比。

import numpy as np
import matplotlib.pyplot as plt

fig, axes = plt.subplots(nrows=2, ncols=2, constrained_layout=True)
for ax in axes.flat:
    im = ax.pcolormesh(np.random.random((10,10)), vmin=0, vmax=1)

fig.colorbar(im, ax=axes.flat)
plt.show()


这个主题已经被很好地涵盖了,但我仍然想提出另一种略有不同的哲学方法。

它的设置有点复杂,但它允许(在我看来)更多的灵活性。例如,我们可以处理每个子图/色条的比例:

import matplotlib.pyplot as plt
import numpy as np
from matplotlib.gridspec import GridSpec

# Define number of rows and columns you want in your figure
nrow = 2
ncol = 3

# Make a new figure
fig = plt.figure(constrained_layout=True)

# Design your figure properties
widths = [3,4,5,1]
gs = GridSpec(nrow, ncol + 1, figure=fig, width_ratios=widths)

# Fill your figure with desired plots
axes = []
for i in range(nrow):
    for j in range(ncol):
        axes.append(fig.add_subplot(gs[i, j]))
        im = axes[-1].pcolormesh(np.random.random((10,10)))

# Shared colorbar    
axes.append(fig.add_subplot(gs[:, ncol]))
fig.colorbar(im, cax=axes[-1])

plt.show()


共享色图和色条

这是针对更复杂的情况,其中值不只是在0和1之间;cmap需要共享,而不是仅仅使用最后一个cmap。

import numpy as np
from matplotlib.colors import Normalize
import matplotlib.pyplot as plt
import matplotlib.cm as cm
fig, axes = plt.subplots(nrows=2, ncols=2)
cmap=cm.get_cmap('viridis')
normalizer=Normalize(0,4)
im=cm.ScalarMappable(norm=normalizer)
for i,ax in enumerate(axes.flat):
    ax.imshow(i+np.random.random((10,10)),cmap=cmap,norm=normalizer)
    ax.set_title(str(i))
fig.colorbar(im, ax=axes.ravel().tolist())
plt.show()


matplotlib 3.4.0新增功能

共享色条现在可以使用子图实现:

新的图。子图和图。Add_subfigure allow…只属于每个子图形的本地化图形艺术家(例如,色条和标题)。

matplotlib库包括如何绘制子图的演示。

下面是一个有2个子图的最小示例,每个子图都有一个共享的色条:

fig = plt.figure(constrained_layout=True)
(subfig_l, subfig_r) = fig.subfigures(nrows=1, ncols=2)

axes_l = subfig_l.subplots(nrows=1, ncols=2, sharey=True)
for ax in axes_l:
    im = ax.imshow(np.random.random((10, 10)), vmin=0, vmax=1)

# shared colorbar for left subfigure
subfig_l.colorbar(im, ax=axes_l, location='bottom')

axes_r = subfig_r.subplots(nrows=3, ncols=1, sharex=True)
for ax in axes_r:
    mesh = ax.pcolormesh(np.random.randn(30, 30), vmin=-2.5, vmax=2.5)

# shared colorbar for right subfigure
subfig_r.colorbar(mesh, ax=axes_r)

上面的答案很好,但大多数都使用fig.colobar()方法应用于fig对象。这个例子展示了如何使用plt.colobar()函数,直接应用到pyplot:

def shared_colorbar_example():
    fig, axs = plt.subplots(nrows=3, ncols=3)
    for ax in axs.flat:
        plt.sca(ax)
        color = np.random.random((10))
        plt.scatter(range(10), range(10), c=color, cmap='viridis', vmin=0, vmax=1)
    plt.colorbar(ax=axs.ravel().tolist(), shrink=0.6)
    plt.show()

shared_colorbar_example()

因为上面的大多数答案都演示了在2D矩阵上的使用,所以我使用了一个简单的散点图。收缩关键字是可选的,可以调整颜色条的大小。

如果没有指定vmin和vmax,该方法将自动分析所有子图,以获得颜色条上使用的最小值和最大值。当使用fig.colorbar(im)时,上述方法只扫描作为颜色条最小和最大值参数的图像。

结果: