如何为所有子图设置一个颜色条
- 2024-11-27 10:43:00
- admin 原创
- 193
问题描述:
我花了很长时间研究如何让两个子图在 Matplotlib 中共享同一个 y 轴,并且两者之间共享一个颜色条。
发生的情况是,当我在或中调用该colorbar()
函数时,它会自动缩放图,使得颜色条加上图能够适合“子图”边界框,从而导致两个并排的图具有两个非常不同的大小。subplot1
`subplot2`
为了解决这个问题,我尝试创建第三个子图,然后对其进行修改,使其只显示颜色条,不渲染任何图。唯一的问题是,现在两个图的高度和宽度不一致,我不知道如何让它看起来正常。
这是我的代码:
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 ($ heta_{E}$)",fontsize="15")
plt.ylabel(r"y ($ heta_{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 ($ heta_{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()
解决方案 1:
只需将颜色条放置在其自己的轴上并为其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()
im
请注意,即使值的范围由vmin
和设定,颜色范围也将由最后绘制的图像(产生 )设定vmax
。如果另一个图具有更高的最大值,则具有高于 最大值的值的点im
将以统一的颜色显示。
解决方案 2:
您可以使用带有轴列表的ax
参数来简化 Joe Kington 的代码。摘自文档:figure.colorbar()
斧头
无 | 父轴对象,新颜色条轴的空间将从中被窃取。如果给出了轴列表,它们将全部调整大小以腾出空间给颜色条轴。
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()
解决方案 3:
此解决方案不需要手动调整轴位置或颜色条大小,适用于多行和单行布局,并适用于tight_layout()
。它改编自图库示例,使用ImageGrid
来自 matplotlib 的AxesGrid 工具箱。
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()
解决方案 4:
使用起来make_axes
更加简单,而且效果更好。它还提供了自定义颜色条定位的可能性。还请注意subplots
共享 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()
解决方案 5:
共享色彩图和颜色条
这是针对更复杂的情况,其中值不仅仅介于 0 和 1 之间;需要共享 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()
解决方案 6:
作为一名偶然发现这个帖子的初学者,我想添加一个针对abevieiramota非常简洁的答案的 python-for-dummies 改编版(因为我处于必须查找“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 风格,对于像我这样的新手来说更容易看到这里到底发生了什么。
解决方案 7:
matplotlib 3.4.0 中的新功能
现在可以使用子图实现共享颜色条:
新的
Figure.subfigures
并Figure.add_subfigure
允许...仅与每个子图相关的本地化图形艺术家(例如,彩条和字幕)。
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)
解决方案 8:
正如其他答案所指出的那样,通常的想法是定义一个轴来放置颜色条。有多种方法可以做到这一点;其中一种尚未提及的方法是在创建子图时直接指定颜色条轴plt.subplots()
。优点是轴的位置不需要手动设置,并且在所有情况下,使用自动纵横比,颜色条的高度都将与子图完全相同。即使在许多使用图像的情况下,结果也会令人满意,如下所示。
当使用时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_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()
解决方案 9:
正如评论中指出的那样,使用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()
colorbar函数提供了一个shrink
参数,它是 colorbar 轴大小的缩放因子。它确实需要一些手动反复试验。例如:
fig.colorbar(im, ax=axes.ravel().tolist(), shrink=0.75)
解决方案 10:
除了@abevieiramota 的出色回答之外,您还可以获得与 constrained_layout 等价的 tight_layout。如果您使用imshow
而不是 ,您仍会得到较大的水平间隙pcolormesh
,因为 强加了 1:1 的纵横比imshow
。
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()
解决方案 11:
我注意到,几乎每个发布的解决方案都涉及ax.imshow(im, ...)
并且没有对多个子图的颜色条显示的颜色进行标准化。im
可映射图取自最后一个实例,但如果多个im
-s 的值不同怎么办?(我假设这些可映射图的处理方式与轮廓集和表面集的处理方式相同。)我有一个使用下面 3d 表面图的示例,它为 2x2 子图创建两个颜色条(每行一个颜色条)。虽然问题明确要求不同的安排,但我认为这个例子有助于澄清一些事情。plt.subplots(...)
不幸的是,由于 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("
PNUM: {}, Zmin = {}, Zmax = {}
".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
解决方案 12:
这个主题已经被很好地涵盖了,但我仍然想从稍微不同的哲学角度提出另一种方法。
设置起来有点复杂,但(在我看来)它提供了更多的灵活性。例如,你可以调整每个子图/颜色条的相应比例:
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()
解决方案 13:
上面的答案很棒,但大多数答案都使用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()
由于上面的大多数答案都演示了在二维矩阵上的用法,因此我使用了一个简单的散点图。shrink
关键字是可选的,用于调整颜色条的大小。
如果未指定vmin
和,vmax
则此方法将自动分析所有子图,以找出颜色条上要使用的最小值和最大值。上述方法在使用fig.colorbar(im)
扫描时,仅扫描作为参数传递的图像,以获取颜色条的最小值和最大值。
结果:
- 2024年20款好用的项目管理软件推荐,项目管理提效的20个工具和技巧
- 2024年开源项目管理软件有哪些?推荐5款好用的项目管理工具
- 2024年常用的项目管理软件有哪些?推荐这10款国内外好用的项目管理工具
- 项目管理软件有哪些?推荐7款超好用的项目管理工具
- 项目管理软件有哪些最好用?推荐6款好用的项目管理工具
- 项目管理软件哪个最好用?盘点推荐5款好用的项目管理工具
- 项目管理软件排行榜:2024年项目经理必备5款开源项目管理软件汇总
- 项目管理软件有哪些,盘点推荐国内外超好用的7款项目管理工具
- 项目管理必备:盘点2024年13款好用的项目管理软件
- 2024项目管理软件排行榜(10类常用的项目管理工具全推荐)