相对于地理轴(cartopy)正确放置颜色条
Posted
技术标签:
【中文标题】相对于地理轴(cartopy)正确放置颜色条【英文标题】:Correct placement of colorbar relative to geo axes (cartopy) 【发布时间】:2015-07-13 20:24:41 【问题描述】:使用 Cartopy,我想完全控制我的颜色条的去向。通常我通过获取当前坐标轴位置作为基础,然后为颜色条创建新坐标轴来做到这一点。这适用于标准 matplotlib 轴,但在使用 Cartopy 和 geo_axes 时效果不佳,因为这会扭曲轴。
所以,我的问题是:如何获得我的 geo_axes 的确切位置?
这是一个基于 Cartopy 文档http://scitools.org.uk/cartopy/docs/latest/matplotlib/advanced_plotting.html 的代码示例:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import os
from netCDF4 import Dataset as netcdf_dataset
from cartopy import config
def main():
fname = os.path.join(config["repo_data_dir"],
'netcdf', 'HadISST1_SST_update.nc'
)
dataset = netcdf_dataset(fname)
sst = dataset.variables['sst'][0, :, :]
lats = dataset.variables['lat'][:]
lons = dataset.variables['lon'][:]
#my preferred way of creating plots (even if it is only one plot)
ef, ax = plt.subplots(1,1,figsize=(10,5),subplot_kw='projection': ccrs.PlateCarree())
ef.subplots_adjust(hspace=0,wspace=0,top=0.925,left=0.1)
#get size and extent of axes:
axpos = ax.get_position()
pos_x = axpos.x0+axpos.width + 0.01# + 0.25*axpos.width
pos_y = axpos.y0
cax_width = 0.04
cax_height = axpos.height
#create new axes where the colorbar should go.
#it should be next to the original axes and have the same height!
pos_cax = ef.add_axes([pos_x,pos_y,cax_width,cax_height])
im = ax.contourf(lons, lats, sst, 60, transform=ccrs.PlateCarree())
ax.coastlines()
plt.colorbar(im, cax=pos_cax)
ax.coastlines(resolution='110m')
ax.gridlines()
ax.set_extent([-20, 60, 33, 63])
#when using this line the positioning of the colorbar is correct,
#but the image gets distorted.
#when omitting this line, the positioning of the colorbar is wrong,
#but the image is well represented (not distorted).
ax.set_aspect('auto', adjustable=None)
plt.savefig('sst_aspect.png')
plt.close()
if __name__ == '__main__': main()
使用“set_aspect”时的结果图:
结果图,省略“set_aspect”时:
基本上,我想获得第一个数字(正确放置的颜色条),但不使用“set_aspect”。 我想这应该可以通过一些转换来实现,但到目前为止我还没有找到解决方案。
谢谢!
【问题讨论】:
【参考方案1】:请记住,mpl_toolkits.axes_grid1
不是 matplotlib 中经过最佳测试的部分,我们可以使用它的功能来实现您想要的。
我们可以使用mpl_toolkits
文档中给出的Example,但是axes_class
需要明确设置,它必须设置为axes_class=plt.Axes
,否则它会尝试创建一个GeoAxes
作为颜色条
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.axes_grid1 import make_axes_locatable
def sample_data_3d(shape):
"""Returns `lons`, `lats`, and fake `data`
adapted from:
http://scitools.org.uk/cartopy/docs/v0.15/examples/axes_grid_basic.html
"""
nlons, nlats = shape
lats = np.linspace(-np.pi / 2, np.pi / 2, nlats)
lons = np.linspace(0, 2 * np.pi, nlons)
lons, lats = np.meshgrid(lons, lats)
wave = 0.75 * (np.sin(2 * lats) ** 8) * np.cos(4 * lons)
mean = 0.5 * np.cos(2 * lats) * ((np.sin(2 * lats)) ** 2 + 2)
lats = np.rad2deg(lats)
lons = np.rad2deg(lons)
data = wave + mean
return lons, lats, data
# get data
lons, lats, data = sample_data_3d((180, 90))
# set up the plot
proj = ccrs.PlateCarree()
f, ax = plt.subplots(1, 1, subplot_kw=dict(projection=proj))
h = ax.pcolormesh(lons, lats, data, transform=proj, cmap='RdBu')
ax.coastlines()
# following https://matplotlib.org/2.0.2/mpl_toolkits/axes_grid/users/overview.html#colorbar-whose-height-or-width-in-sync-with-the-master-axes
# we need to set axes_class=plt.Axes, else it attempts to create
# a GeoAxes as colorbar
divider = make_axes_locatable(ax)
ax_cb = divider.new_horizontal(size="5%", pad=0.1, axes_class=plt.Axes)
f.add_axes(ax_cb)
plt.colorbar(h, cax=ax_cb)
还要注意使用来自mpl_toolkits.axes_grid1
的AxesGrid
的cartopy example。
【讨论】:
此解决方案中的颜色条已正确保存在 png 文件中。【参考方案2】:好问题!感谢您提供代码和图片,它使问题更容易理解,并且更容易快速迭代可能的解决方案。
这里的问题本质上是一个 matplotlib 问题。 Cartopy 调用ax.set_aspect('equal')
,因为这是投影定义的笛卡尔单位的一部分。
Matplotlib 的相等纵横比功能会调整坐标轴的大小以匹配 x 和 y 限制,而不是更改限制以适应坐标区矩形。正是由于这个原因,轴不会填满图中分配给它的空间。如果您以交互方式调整图形大小,您将看到轴占用的空间量取决于您调整图形大小的方面。
识别坐标区位置的最简单方法是使用您已经使用过的ax.get_position()
方法。然而,正如我们现在所知,这个“位置”会随着图形的大小而变化。因此,一种解决方案是在每次调整图形大小时重新计算颜色条的位置。
matplotlib event machinery 有一个“resize_event”,每次调整图形大小时都会触发该事件。如果我们将此机制用于您的颜色条,我们的事件可能类似于:
def resize_colobar(event):
# Tell matplotlib to re-draw everything, so that we can get
# the correct location from get_position.
plt.draw()
posn = ax.get_position()
colorbar_ax.set_position([posn.x0 + posn.width + 0.01, posn.y0,
0.04, axpos.height])
fig.canvas.mpl_connect('resize_event', resize_colobar)
因此,如果我们将其与 cartopy 以及您最初的问题联系起来,现在可以根据地理轴的位置调整颜色条的大小。执行此操作的完整代码可能如下所示:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import os
from netCDF4 import Dataset as netcdf_dataset
from cartopy import config
fname = os.path.join(config["repo_data_dir"],
'netcdf', 'HadISST1_SST_update.nc'
)
dataset = netcdf_dataset(fname)
sst = dataset.variables['sst'][0, :, :]
lats = dataset.variables['lat'][:]
lons = dataset.variables['lon'][:]
fig, ax = plt.subplots(1, 1, figsize=(10,5),
subplot_kw='projection': ccrs.PlateCarree())
# Add the colorbar axes anywhere in the figure. Its position will be
# re-calculated at each figure resize.
cbar_ax = fig.add_axes([0, 0, 0.1, 0.1])
fig.subplots_adjust(hspace=0, wspace=0, top=0.925, left=0.1)
sst_contour = ax.contourf(lons, lats, sst, 60, transform=ccrs.PlateCarree())
def resize_colobar(event):
plt.draw()
posn = ax.get_position()
cbar_ax.set_position([posn.x0 + posn.width + 0.01, posn.y0,
0.04, posn.height])
fig.canvas.mpl_connect('resize_event', resize_colobar)
ax.coastlines()
plt.colorbar(sst_contour, cax=cbar_ax)
ax.gridlines()
ax.set_extent([-20, 60, 33, 63])
plt.show()
【讨论】:
很好的答案!我自己永远不会找到这个解决方案! 如果可以的话,也许是一个简短的附带问题:是否可以将 resize_colorbar 的定义放在其他地方并将 ax 和 cbar_ax 作为参数传递(以便此函数可重用)? 更新:虽然 plt.show() 提供了正确的结果,但 plt.savefig('sst.png') 没有(颜色条保持在 [0,0,0.1,0.1] 的原始位置) .试图将后端更改为 matplotlib.use('Agg'),但这无济于事。知道如何让它与 savefig 一起使用吗? 问题2:未经测试,但请尝试在保存前调用resize_colorbar(None)
。我怀疑保存时没有触发resize_event。
这里是solution for Q1。这不应该全部内置到cartopy
中吗?这样 GeoAxes
上的 ax.colorbar()
会生成具有正确扩展名的 cbar_ax
并负责更新?以上是关于相对于地理轴(cartopy)正确放置颜色条的主要内容,如果未能解决你的问题,请参考以下文章
如何使用 cartopy 将显示坐标转换为地理坐标(纬度、经度)?
R语言使用car包的scatterplotMatrix函数绘制散点图矩阵并添加线性和loess拟合线在主对角线上放置箱图密度或直方图在图像边缘添加轴须图rug可以基于分组变量绘制散点图矩阵