将 Matplotlib 颜色条大小设置为匹配图形
- 2024-12-27 08:47:00
- admin 原创
- 118
问题描述:
我无法让像这样的colorbar
图表的imshow
高度与图表的高度相同,除非事后使用 Photoshop。我该如何让高度匹配?
解决方案 1:
这种组合(以及接近这些组合的值)似乎“神奇地”起作用了,无论显示屏的尺寸如何,都能保持颜色条与图表的比例一致。
plt.colorbar(im,fraction=0.046, pad=0.04)
它也不需要共享轴,因为共享轴可能会使图变得不方形。
解决方案 2:
您可以使用 matplotlib AxisDivider轻松完成此操作。
链接页面中的示例无需使用子图即可工作:
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
import numpy as np
plt.figure()
ax = plt.gca()
im = ax.imshow(np.arange(100).reshape((10,10)))
# create an axes on the right side of ax. The width of cax will be 5%
# of ax and the padding between cax and ax will be fixed at 0.05 inch.
divider = make_axes_locatable(ax)
cax = divider.append_axes("right", size="5%", pad=0.05)
plt.colorbar(im, cax=cax)
解决方案 3:
我很感谢上述所有答案。但是,正如一些答案和评论指出的那样,该axes_grid1
模块无法处理 GeoAxes,而调整fraction
、pad
、shrink
和其他类似参数不一定能给出非常精确的顺序,这真的让我很困扰。我相信赋予colorbar
自己的axes
可能是解决所有提到的问题的更好解决方案。
代码
import matplotlib.pyplot as plt
import numpy as np
fig=plt.figure()
ax = plt.axes()
im = ax.imshow(np.arange(100).reshape((10,10)))
# Create an axes for colorbar. The position of the axes is calculated based on the position of ax.
# You can change 0.01 to adjust the distance between the main image and the colorbar.
# You can change 0.02 to adjust the width of the colorbar.
# This practice is universal for both subplots and GeoAxes.
cax = fig.add_axes([ax.get_position().x1+0.01,ax.get_position().y0,0.02,ax.get_position().height])
plt.colorbar(im, cax=cax) # Similar to fig.colorbar(im, cax = cax)
结果
后来我发现matplotlib.pyplot.colorbar
官方文档还提供了ax
选项,即为颜色条提供空间的现有轴。因此,它对于多个子图很有用,见下文。
代码
fig, ax = plt.subplots(2,1,figsize=(12,8)) # Caution, figsize will also influence positions.
im1 = ax[0].imshow(np.arange(100).reshape((10,10)), vmin = -100, vmax =100)
im2 = ax[1].imshow(np.arange(-100,0).reshape((10,10)), vmin = -100, vmax =100)
fig.colorbar(im1, ax=ax)
结果
同样,您也可以通过指定 cax 来实现类似的效果,从我的角度来看,这是一种更准确的方法。
代码
fig, ax = plt.subplots(2,1,figsize=(12,8))
im1 = ax[0].imshow(np.arange(100).reshape((10,10)), vmin = -100, vmax =100)
im2 = ax[1].imshow(np.arange(-100,0).reshape((10,10)), vmin = -100, vmax =100)
cax = fig.add_axes([ax[1].get_position().x1-0.25,ax[1].get_position().y0,0.02,ax[0].get_position().y1-ax[1].get_position().y0])
fig.colorbar(im1, cax=cax)
结果
解决方案 4:
@bogatron 已经给出了matplotlib 文档建议的答案,它产生了正确的高度,但它引入了另一个问题。现在颜色条的宽度(以及颜色条和图之间的空间)随着图的宽度而变化。换句话说,颜色条的纵横比不再固定。
为了获得正确的高度和给定的纵横比,您必须更深入地研究这个神秘的axes_grid1
模块。
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable, axes_size
import numpy as np
aspect = 20
pad_fraction = 0.5
ax = plt.gca()
im = ax.imshow(np.arange(200).reshape((20, 10)))
divider = make_axes_locatable(ax)
width = axes_size.AxesY(ax, aspect=1./aspect)
pad = axes_size.Fraction(pad_fraction, width)
cax = divider.append_axes("right", size=width, pad=pad)
plt.colorbar(im, cax=cax)
请注意,这指定了颜色条相对于绘图高度的宽度(与之前的图形宽度相反)。
现在可以将颜色条和绘图之间的间距指定为颜色条宽度的一小部分,在我看来,这比图形宽度的一小部分更有意义。
更新:
我创建了一个关于该主题的 IPython 笔记本,将上述代码打包成一个易于重复使用的函数:
import matplotlib.pyplot as plt
from mpl_toolkits import axes_grid1
def add_colorbar(im, aspect=20, pad_fraction=0.5, **kwargs):
"""Add a vertical color bar to an image plot."""
divider = axes_grid1.make_axes_locatable(im.axes)
width = axes_grid1.axes_size.AxesY(im.axes, aspect=1./aspect)
pad = axes_grid1.axes_size.Fraction(pad_fraction, width)
current_ax = plt.gca()
cax = divider.append_axes("right", size=width, pad=pad)
plt.sca(current_ax)
return im.axes.figure.colorbar(im, cax=cax, **kwargs)
可以这样使用:
im = plt.imshow(np.arange(200).reshape((20, 10)))
add_colorbar(im)
解决方案 5:
当您创建colorbar
尝试使用分数和/或收缩参数时。
摘自文件:
分数 0.15;用于颜色条的原始轴的分数
收缩 1.0;收缩颜色条的分数
解决方案 6:
以上所有解决方案都很好,但我最喜欢@Steve 和@bejota 的解决方案,因为它们不需要花哨的调用并且具有通用性。
我说的通用是指它适用于任何类型的轴,包括GeoAxes
。例如,你有用于映射的投影轴:
projection = cartopy.crs.UTM(zone='17N')
ax = plt.axes(projection=projection)
im = ax.imshow(np.arange(200).reshape((20, 10)))
呼吁
cax = divider.append_axes("right", size=width, pad=pad)
将会失败:KeyException: map_projection
因此,处理所有类型轴的颜色条大小的唯一通用方法是:
ax.colorbar(im, fraction=0.046, pad=0.04)
使用 0.035 到 0.046 之间的分数来获得最佳尺寸。但是,分数和 paddig 的值需要进行调整才能最适合您的图,并且会根据颜色条的方向是垂直还是水平而有所不同。
解决方案 7:
另一种方法是
shrink=0.7, aspect=20*0.7
shrink
缩放高度和宽度,但aspect
参数恢复原始宽度。默认纵横比为 20。0.7
是根据经验确定的。
解决方案 8:
axes_grid1.axes_divider
是此任务的规定方法(matplotlib 甚至有一个演示),但通过添加颜色条,它会使图像变小。如果您想保留原始图像大小,则以下提供了一种方法(基于Fei Yao 的回答)。
data = [(1,2,3,4,5),(4,5,6,7,8),(7,8,9,10,11)]
im = plt.imshow(data, cmap='RdBu')
l, b, w, h = plt.gca().get_position().bounds
cax = plt.gcf().add_axes([l + w + 0.03, b, 0.03, h])
plt.colorbar(im, cax=cax)
一个方便的函数包装器。
import matplotlib.pyplot as plt
def add_colorbar(im, width=None, pad=None, **kwargs):
l, b, w, h = im.axes.get_position().bounds # get boundaries
width = width or 0.1 * w # get width of the colorbar
pad = pad or width # get pad between im and cbar
fig = im.axes.figure # get figure of image
cax = fig.add_axes([l + w + pad, b, width, h]) # define cbar Axes
return fig.colorbar(im, cax=cax, **kwargs) # draw cbar
data = [(1,2,3,4,5),(4,5,6,7,8),(7,8,9,10,11)]
# an example usage
im = plt.imshow(data, cmap='RdBu')
add_colorbar(im)
解决方案 9:
我最近遇到了这个问题,我曾经ax.twinx()
解决过它。例如:
from matplotlib import pyplot as plt
# Some other code you've written
...
# Your data generation goes here
xdata = ...
ydata = ...
colordata = function(xdata, ydata)
# Your plotting stuff begins here
fig, ax = plt.subplots(1)
im = ax.scatterplot(xdata, ydata, c=colordata)
# Create a new axis which will be the parent for the colour bar
# Note that this solution is independent of the 'fig' object
ax2 = ax.twinx()
ax2.tick_params(which="both", right=False, labelright=False)
# Add the colour bar itself
plt.colorbar(im, ax=ax2)
# More of your code
...
plt.show()
我发现这在创建以 matplotlib 对象作为参数、在其上绘制并返回对象的函数时特别有用Axes
,因为我不需要传递必须从对象生成的单独轴figure
,或者传递figure
对象本身。
解决方案 10:
到目前为止提供的答案非常有用,但根据情况,它们可能存在缺点。例如,它们:
会占用轴空间(1、2、3),这在使用多个子图时会变得明显,
调整以找到最适合每个图的特定值(2 , 3)或
fig.tight_layout()
调用( 4 )后可能会导致难以预测的结果。
避免上述缺点的替代方法是通过以下方式添加插入/子轴ax.inset_axes
:
import matplotlib.pyplot as plt
import numpy as np
x = np.linspace(0, 1, 70)
y = np.linspace(0, 1, 100)
data = (np.stack(np.meshgrid(x, y)) ** 2).sum(0)
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
# specify lower-left corner, width and height of the colorbar,
# in coordinates relative to its parent axis
cax = axs[1].inset_axes((1.05, 0, 0.08, 1.0))
fig.colorbar(hm, cax=cax)
fig.suptitle(r"$f{Add~axis~with~relative~(axis)~coordinates}$", y=0.87)
fig.tight_layout()
通过这种替代方案,颜色条的坐标和大小是相对于其父轴的,从而确保无论父轴的形状或其他子图的排列如何,结果都是可预测的。结果:
这些是迄今为止获得最多赞同的答案所获得的结果(重现代码在最后)。
使用轴分隔符:
使用
fraction
+pad
参数
在图形坐标中添加新轴
使用
shrink
+aspect
参数
重现代码:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.axes_grid1 import make_axes_locatable
plt.close("all")
x = np.linspace(0, 1, 70)
y = np.linspace(0, 1, 100)
data = (np.stack(np.meshgrid(x, y)) ** 2).sum(0)
# using Axes divider
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
divider = make_axes_locatable(axs[1])
cax = divider.append_axes("right", size="5%", pad=0.05)
fig.colorbar(hm, cax=cax)
fig.suptitle(r"$f{Axes~divider}$" + "
steals axis space", y=0.92)
fig.tight_layout()
# using fraction + pad
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
fig.colorbar(hm, ax=axs[1], fraction=0.046, pad=0.04)
fig.suptitle(
r"$f{fraction+pad}$" + "
steals axis space + trial and error positioning", y=0.92
)
fig.tight_layout()
# adding axis in absolute (figure) coordinates
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
cax = fig.add_axes(
(
axs[1].get_position().x1 + 0.01,
axs[1].get_position().y0,
0.02,
axs[1].get_position().y1 - axs[1].get_position().y0,
),
)
fig.colorbar(hm, cax=cax)
fig.suptitle(
r"$f{Add~axis~with~Figure~coordinates}$"
+ "
Trial and error + hard to predict after tight_layout",
y=1.02,
)
fig.tight_layout()
# using shrink=0.7, aspect=20*0.7
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
fig.colorbar(hm, ax=axs[1], shrink=0.7, aspect=20 * 0.7)
fig.suptitle(
r"$f{shrink=0.7,~aspect=20*0.7}$" + "
steals axis space + trial and error",
y=0.92,
)
fig.tight_layout()
# adding axis in relative (axis) coordinates
fig, axs = plt.subplots(1, 3, figsize=(6, 3))
for ax in axs:
hm = ax.imshow(data)
cax = axs[1].inset_axes((1.05, 0, 0.08, 1.0))
fig.colorbar(hm, cax=cax)
fig.suptitle(r"$f{Add~axis~with~relative~(axis)~coordinates}$", y=0.87)
fig.tight_layout()
解决方案 11:
对于这些类型的图,我喜欢 的ImageGrid
API mpl_toolkits.axes_grid1
。它是为管理多个固定纵横比图而设计的,但对于单个图像来说也很好用。
from matplotlib import pyplot as plt
from mpl_toolkits.axes_grid1 import ImageGrid
fig = plt.figure()
plot = ImageGrid(fig, 111, (1, 1),
cbar_mode='single',
cbar_location='right',
cbar_size='3%',
cbar_pad='5%')
im = plot[0].imshow(np.random.randn(2**4, 2**6))
cbar = fig.colorbar(im, cax=plot.cbar_axes[0])
解决方案 12:
如果您不想声明另一组轴,我发现的最简单的解决方案是使用 figsize 调用来更改图形大小。
在上面的例子中,我将首先
fig = plt.figure(figsize = (12,6))
然后以不同的比例重新渲染,直到色条不再遮挡主线情节。
解决方案 13:
我发现的针对此问题的最佳解决方案已在本页中详细解释。
基本上,一旦你开始想象:
fig, myplot = plt.subplots((1,1), figsize = (12,10), layout = 'constrained')
提供论点:
layout = 'constrained'
对我有用。