Python Matplotlib 2个子批次,1个颜色条

Python Matplotlib 2个子批次,1个颜色条,python,matplotlib,subplot,colorbar,Python,Matplotlib,Subplot,Colorbar,我花了太长时间研究如何在Matplotlib中使两个子图共享同一y轴,并在两个子图之间共享一个颜色条 发生的事情是,当我在subplot1或subplot2中调用colorbar函数时,它会自动缩放绘图,这样colorbar和绘图将适合“subplot”边界框,导致两个并排绘图的大小完全不同 为了解决这个问题,我尝试创建第三个子图,然后对其进行黑客攻击,使其仅呈现一个色条,而不呈现任何绘图。 唯一的问题是,现在两幅图的高度和宽度都不均匀,我不知道如何使它看起来好 这是我的密码: from __f

我花了太长时间研究如何在Matplotlib中使两个子图共享同一y轴,并在两个子图之间共享一个颜色条

发生的事情是,当我在subplot1或subplot2中调用colorbar函数时,它会自动缩放绘图,这样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()

只需将颜色条放置在其自身的轴上,并使用子图_调整为其留出空间

举个简单的例子:

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_轴更容易,并提供更好的结果。它还提供了定制颜色条位置的可能性。 还要注意共享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的代码。 发件人:

斧头

无|将从中窃取新色条轴空间的父轴对象。如果给出了轴列表,它们都将调整大小,以便为颜色条轴留出空间


abevieiramota使用轴列表的解决方案非常有效,直到您只使用一行图像,如注释中所指出的。为figsize使用合理的纵横比会有所帮助,但还远远不够完美。例如:

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()
fig.colorbar(im, ax=axes.ravel().tolist(), shrink=0.75)
提供收缩参数,该参数是颜色条轴大小的比例因子。它确实需要一些手动尝试和错误。例如:

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()
fig.colorbar(im, ax=axes.ravel().tolist(), shrink=0.75)

此解决方案不需要手动调整轴位置或颜色栏大小,可用于多行和单行布局,也可用于紧密的布局。它使用matplotlib中的ImageGrid从a改编而来


作为一个偶然发现这条线索的初学者,我想添加一个python for Dummie,它是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()

更不用说蟒蛇了,像我这样的疯子更容易看到这里到底发生了什么

正如在其他答案中指出的那样,这个想法通常是为色条定义一个要驻留的轴。这样做的方式多种多样;一个尚未提及的问题是,在使用plt.subplot创建子地块时直接指定颜色条轴。其优点是,轴位置不需要手动设置,并且在所有具有自动纵横比的情况下,颜色条的高度将与子批次的高度完全相同。即使在许多使用图像的情况下,结果也会令人满意,如下所示

使用plt.subplot时,使用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_toolkits.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()

我注意到几乎所有发布的解决方案都涉及ax.imshowim。。。并且没有对多个子图形的颜色栏显示的颜色进行规格化。im-mappable来自上一个实例,但是如果多个im-s的值不同呢?我假设这些可映射对象的处理方式与等高线集和曲面集的处理方式相同。我有一个例子,使用下面的3d曲面图为2x2子地块创建两个色条,每行一个色条。虽然这个问题明确要求不同的安排,但我认为这个例子有助于澄清一些事情。我还没有找到一种使用plt的方法。子图。。。然而不幸的是,由于3D轴

如果我能以更好的方式定位颜色条就好了。。。也许有更好的方法可以做到这一点,但至少不难遵循

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的优秀答案,您可以获得紧密布局与约束布局的平衡。如果使用imshow而不是pcolormesh,由于imshow强加的高宽比为1:1,您仍然会获得较大的水平间隙


这个话题已经被很好地讨论过了,但我仍然想以一种稍微不同的理念提出另一种方法< /p> 这是一个有点复杂的设置,但它允许在我看来更灵活一点。例如,可以使用每个子图/色条的各自比率:

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需要共享,而不仅仅是使用最后一个

将numpy作为np导入 从matplotlib.colors导入规格化 将matplotlib.pyplot作为plt导入 将matplotlib.cm导入为cm 图,轴=plt。子批次行=2,ncols=2 cmap=cm.获得“绿色” 规格化器=规格化0,4 im=cm.标度armappalenorm=normalizer 对于i,轴中的ax.flat: ax.imshowi+np.random.random10,10,cmap=cmap,norm=normalizer ax.set_titlestri fig.colorbarim,ax=axes.ravel.tolist 节目

ImageGrid在这方面也非常有用。如果您需要使用紧密布局,您将希望在子图之后执行所有操作,在紧密布局之后进行调整,然后手动调整子图的坐标,并添加轴。如何为我已经拥有的两个不同散点图创建一个颜色条?我在上面尝试过,但我不知道如何用适当的变量替换im。假设我的散点图是plot1=pylib.scatterx,y,z和plot2=pylib.scattera,b,c这对其他人来说可能是显而易见的,但我想指出,为了使色条能够准确地表示所有图中的颜色,vmin和vmax参数是至关重要的。它们控制每个子批次的颜色范围。如果你有真实数据,你可能需要先通过这个来找到最小值和最大值。如果绘图的值范围不同,颜色条范围将只显示最后一个绘图的范围,对吗?有什么建议吗?这个解决方案在这里运行得很好,似乎是最简单的一个。如果将nrows更改为1,两个绘图都比colorbar更清晰。那么,如何解决这个问题呢?遗憾的是,它不适合紧凑的布局,但仍然是一个很好的解决方案。请记住。。。我喜欢这个解决方案!蒂娜·克瑟·齐阿伦斯!这个答案的关键部分是fig.colorbarim,ax=axes.ravel.tolist。如果省略ax=axes.ravel.tolist,则颜色栏将放置在一个子图中。如果子图不是正方形,则此方法不起作用。如果更改nrows=1,则颜色栏将再次大于子绘图。matplotlib的默认值是什么?看起来很棒@TomCho要设置标签,可以在实例化颜色栏时抓住它的句柄,如:thecb=ax.cax.colorbarim。然后你可以做cb.set_label_textfoo如何更改颜色映射?@Sigur我相信你现在已经知道了,但是对于其他人,你可以在声明im时更改cmap:im=ax.imshowdata,vmin=0,vmax=1,cmap='your_cmap\u here'Hi!我真的很喜欢这个答案。更新matplotlib后,我收到以下警告:“MatPlotLibDeprecatitionWarning:mpl_toolkits.axes_grid1.colorbar模块在matplotlib 3.2中已被弃用,将在两个次要版本后删除。使用matplotlib.colorbar。”但是,我现在不知道如何替换这些行:ax.cax.colorbarim ax.cax.toggle_labelTrue@all为了防止有人感兴趣,我找到了一个解决方案:将ax.cax.colorbarim行替换为ax.cax.cla matplotlib.colorbar.Colorbarax.cax,im。当然,matplotlib.colorbar必须在开始时导入。如果有人想要抑制颜色栏的标签,请使用ax.cax.toggle_labelFalse并添加ax.cax.tick_paramssize=0。我不确定是否允许我在此处询问此问题,但是否有方法使用ax=fig.add_子图来实现此解决方案?我这样问是因为我不知道如何将它用于basemap。@lanadaquenada是的,这是可能的,但在这种情况下,您需要提供一个GridSpec来添加_子图。如果来自多个IM的值不同,它们不应该使用相同的颜色栏,因此原始问题不会真正适用这很好,但是为了使用除viridis之外的颜色映射,您需要将cmap=cmap添加到ScalarMapable的创建中。它应该读为im=cm.ScalarMappablenorm=normalizer,cmap=cmap对于我来说这段代码抛出,TypeError:您必须首先为可映射设置_数组
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()