【发布时间】:2020-07-14 19:11:10
【问题描述】:
将 tight_layout 与 Cartopy 和多个子图一起使用时,结果不是紧凑的布局。示例:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import cartopy.crs as ccrs
gs = gridspec.GridSpec(2,2)
fig = plt.figure()
for k in range(0,4):
ax = plt.subplot(gs[k], projection = ccrs.PlateCarree())
ax.set_extent([0,50,0,90])
ax.coastlines()
gs.tight_layout(fig)
在每张地图之间产生一个差距很大的图形:
使用pyplot的tight_layout时也会出现同样的问题,不管我设置ax.set_extent()与否,结果都不如预期。示例:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
fig, ((ax1,ax2),(ax3,ax4)) = plt.subplots(2, 2,
subplot_kw=dict(projection=ccrs.PlateCarree()))
for ax in fig.axes:
ax.coastlines()
plt.tight_layout()
虽然我可以通过在tight_layout 上使用w_pad 和h_pad 参数来手动调整这两种情况,但我必须根据子图的数量和/或地图本身对每个数字进行手动调整。有没有办法在没有这些参数的情况下做到这一点,或者自动确定这些值应该是什么?或者自动设置图形大小?
使用 Linux 5.4.0-40-generic、Python 3.7.6 64 位、Cartopy 0.18.0、Matplotlib 3.2.2
编辑:
问题是如何使用 Cartopy 重现 tight_layout 函数,而无需根据我正在绘制的内容手动设置 w_pad 或 h_pad。
目前,我正在创建许多不同的图形,不断变化:
- 投影:有些是 PlateCarree,有些是 Polar Stereographic
- 地图范围:整个地图,PlateCarree 上减少的纬度/经度,Polar Stereographic 上减少的纬度
- 不同数量的行/列:1 到 7 行,1 到 5 列。
- 在第一列和最后一行添加纬度和经度刻度,每列添加标题,第一列添加
y_label,以及总标题。 - 根据图,在每行右侧添加一个颜色条。
最终结果应该是每个子图和不同图形之间具有一致的水平和垂直空间的图形,就像tight_layout 的预期结果一样。
上面所有的示例代码,遵循@swatchai 的回答:
import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
from cartopy.examples.waves import sample_data
from mpl_toolkits.axes_grid1.inset_locator import inset_axes
ar = 1.0 # initial aspect ratio for first trial
wi = 5 # width in inches
hi = wi * ar # height in inches
# set number of rows/columns
rows, cols = 2,2
# set projection
# proj = ccrs.PlateCarree()
proj = ccrs.NorthPolarStereo()
# set lon/lat extent
# extent = [0,50,0,90] # example for PlateCarree
# extent = [-180,180,-90,90] # complete map
extent = [-180,180,30,90] # example for PolarStereo
gs = gridspec.GridSpec(rows, cols)
# Set figsize using wi and hi
fig = plt.figure(figsize=(wi, hi))
for r in range(0,rows):
for c in range(0,cols):
ax = plt.subplot(gs[r,c], projection = proj)
ax.set_extent(extent,ccrs.PlateCarree())
ax.coastlines()
# Add sample data
x, y, z = sample_data((20, 40))
z = z * -1.5 * y
cf = ax.contourf(x, y, z, transform=ccrs.PlateCarree())
# Add colorbar
if c == cols -1:
axins = inset_axes(ax,
bbox_to_anchor=(1.05, 0., 1, 1),
width='5%',height='98%',loc='lower left',
bbox_transform=ax.transAxes,
borderpad=0)
cbar = fig.colorbar(cf, cax=axins,orientation='vertical')
# Gridlines, labels, titles
if r == 0:
ax.set_title('column title')
if c == 0:
ax.set_ylabel('y label')
ax.set_yticks([])
if proj == ccrs.PlateCarree():
gl = ax.gridlines(draw_labels=False, crs=ccrs.PlateCarree())
gl.xformatter, gl.yformatter = LONGITUDE_FORMATTER, LATITUDE_FORMATTER
if r == rows-1:
gl.xlabels_bottom = True
if c == 0:
gl.ylabels_left = True
ax.set_ylabel('y label',labelpad=35)
ax.set_yticks([])
elif proj == ccrs.NorthPolarStereo():
ax.gridlines()
plt.suptitle('A figure title')
# Do this to get updated positions/dimensions
plt.draw()
# # Get proper ratio here
# # Computed from the last `ax` (being the current)
xmin, xmax = ax.get_xbound()
ymin, ymax = ax.get_ybound()
y2x_ratio = (ymax-ymin)/(xmax-xmin) * rows/cols
# # Apply new h/w aspect ratio by changing h
# # Also possible to change w using set_figwidth()
fig.set_figheight(wi * y2x_ratio)
gs.tight_layout(fig)
plt.show()
上面生成的图表具有非常不同的水平/垂直间隙,具体取决于子图/投影的数量。标签、标题等有时也与整体数字不成比例(tight_layout 按预期工作时不会发生这种情况)。有些还旋转。例如,使用上面的代码和rows, cols = 3, 4、PlateCarree() 并绘制完整的地图,给出:
【问题讨论】:
-
我忘了提到我的代码中的
wi = 5是用户必须指定以容纳所有子图的整个图形的总宽度(以英寸为单位)。在你的最后一个情节中,5 显然太小了,wi = 17应该会产生更好的情节。请记住,您总是需要一些合理的尺寸才能开始。 -
@swatchai 我明白了。你是怎么想出 17 是这个特定情节的正确数字?试错?如果是,那么使用此代码并手动调整宽度与在
tight_layout上手动调整参数w_pad和h_pad有什么好处? -
在这种特殊情况下,任何相当大的值,比如 15-20 都足以容纳 4 列图(或每列 3+ 英寸)。大一点不疼。如果您不使用
inset_axes(),则步骤tight_layout()在大多数情况下会很好地调整整体尺寸。使用inset_axes()会破坏tight_layout()的功能,因此不允许使用它的一些选项,包括w_pad和h_pad。图形的所有尺寸(w 和 h)必须由用户指定(或跳过并使用默认值)。我只定义了width,而height是通过程序计算的。
标签: python-3.x matplotlib cartopy