Python 3.x 使用Cartopy进行紧凑布局而不传递w_pad参数?

Python 3.x 使用Cartopy进行紧凑布局而不传递w_pad参数?,python-3.x,matplotlib,cartopy,Python 3.x,Matplotlib,Cartopy,当对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 = cc

当对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)
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()
生成每个地图之间有较大间距的图形:

使用pyplot的
紧密布局时也会出现同样的问题,无论我是否设置了
ax.set\u extent()
,结果都不符合预期。例如:

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)
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()

虽然我可以在这两种情况下通过使用
w_pad
h_pad
参数对
tight_layout
进行手动调整,但我必须根据子地块的数量和/或地图本身对每个地物进行手动调整。有没有办法在没有这些参数的情况下实现这一点,或者自动确定这些值应该是什么?或者自动设置数字大小

使用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

目前,我正在创造许多不同的数字,改变:

  • 投影:有些是平板投影,有些是极赤平投影
  • 地图范围:整个地图,平台上的纬度/经度减小,极赤平极射图上的纬度减小
  • 行数/列数不同:1到7行,1到5列
  • 在第一列和最后一行添加纬度和经度标记、每列的标题、第一列的
    y_标签
    、以及总标题
  • 根据图形,在每行的右侧添加一个颜色栏
最终结果应该是在每个子批次和不同图形之间具有一致的水平和垂直空间的图形,就像
紧密布局的预期结果一样

包含上述所有内容的示例代码,如下@swatchai-answer:

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()
根据子地块/投影的数量,上面生成的图形具有非常不同的水平/垂直间隙。标签、标题等有时也会与整个图形不成比例(当
紧凑布局
按预期工作时不会发生这种情况)。有些还可以旋转。例如,将上述代码与
行一起使用,cols=3,4
PlateCarree()
和绘制的完整地图,给出:


要获得更好的绘图,必须声明
figsize
具有适当的宽度和高度,以使高宽比与子地块大小匹配。可以在代码中以编程方式计算比率,然后将其作为更新值应用,并根据需要获得绘图

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import cartopy.crs as ccrs

# use figsize=(w,h) of proper values can give good result
# the aspect ratio, h/w is important
# default is around 1, is good in general, but not in your case

ar = 1.0  # initial aspect ratio for first trial
wi = 5    # width in inches
hi = wi * ar  # height in inches

gs = gridspec.GridSpec(2,2)

# Set figsize using wi and hi
fig = plt.figure(figsize=(wi, hi))  

for k in range(0,4):
    ax = plt.subplot(gs[k], projection = ccrs.PlateCarree())  
    # any of these projections are OK to use
    # PlateCarree() Robinson() Mercator() TransverseMercator() Orthographic()
    ax.set_extent([0,50,0,90])
    ax.coastlines()
    ax.gridlines()

# Do this to get updated positions/dimensions   
plt.draw() 

# Get proper ratio here
xmin, xmax = ax.get_xbound()
ymin, ymax = ax.get_ybound()
y2x_ratio = (ymax-ymin)/(xmax-xmin)

# This also gives same ratio
#x1, x2 = ax.get_xlim()
#y1, y2 = ax.get_ylim()
#y2x_ratio = (y2-y1)/(x2-x1)

print("y2x_ratio: "+ str(y2x_ratio)) 
#1.8258 with Robinson; 1.8 PlateCarree; 3.37 Mercator; 1.549 TransverseMercator

# 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()
输出曲线图:

编辑1

最初的答案集中在2x2(行x列)的数组上。对于其他阵列,必须更改纵横比的计算以考虑它。下面是可运行代码和示例图的更新版本

import matplotlib.pyplot as plt
import matplotlib.gridspec as gridspec
import cartopy.crs as ccrs

# use figsize=(w,h) of proper values can give good result
# the aspect ratio, h/w is important
# default is around 1, is good in general cases

ar = 1.0  # initial aspect ratio for first trial
wi = 5    # width of the whole figure in inches, ...
# set it wide enough to cover all columns of sub-plots

hi = wi * ar  # height in inches

# set number of rows/columns
rows, cols = 4,3

gs = gridspec.GridSpec(rows, cols)

# Set figsize using wi and hi
fig = plt.figure(figsize=(wi, hi))

for k in range(0, rows*cols):
    ax = plt.subplot(gs[k], projection = ccrs.Robinson())  
    # any of these projections are OK to use
    # PlateCarree() Robinson() Mercator() Orthographic()
    #ax.set_extent([0,50,0,90])
    ax.coastlines()
    ax.gridlines()

# 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()
输出曲线图:


我很感激这个答案,但当我绘制整个地图时(没有
ax.set\u extent
),我似乎无法做到这一点。当使用更多子批次(例如4行x 3列)时,这一点尤其明显。我还尝试添加一个
figwidth(…)
x2x\u ratio=1/y2x\u ratio
以及变量
wi
的其他组合。这在使用
ax.set\u extent
和不同数量的子地块的情况下很有帮助,但在绘制整个地图时似乎还不够。我很感谢更新。虽然这样做效果更好,但仍会根据投影、地图范围和子地块的数量留下不同的水平/垂直间距。我似乎也找不到一个好的
fig.set_figwidth(…)
,因为改变它也会改变垂直间隙。也许我错过了什么?否则,将其应用于每个绘图似乎比仅使用自定义的
w_pad
h_pad
@lanadaquenada请在问题中明确说明您的所有要求更困难。像这样的自定义绘图有很多细节需要处理。我编辑了这个问题,希望它现在足够清晰。我忘了在我的代码中提到
wi=5
是整个图形的总宽度(以英寸为单位),用户必须指定以容纳所有子绘图。在上一个绘图中,5显然太小,
wi=17
应该可以生成更好的绘图。记住,你总是需要一些合理的尺寸来开始。你是如何得出17是这个特定情节的正确数字的?试错?如果是,那么使用此代码并手动调整宽度与手动调整
紧密布局上的参数
w_pad
h_pad
相比会有什么好处?在这种特殊情况下,任何合理的大值,例如15-20都足以容纳4列绘图(或每列3+英寸)。大一点也不疼。如果不使用
插入轴()
,步骤
紧密布局()
将在大多数情况下很好地调整总体尺寸。使用
插入轴()
会中断
紧密布局()的功能,因此不允许使用其某些选项,包括
w\u pad
h\u pad
。图形的所有尺寸(w和h)必须由用户指定(或跳过并使用默认值)。我只定义了
宽度
,而
高度
是通过编程计算的。