写在前面
经常需要在一张图片上,同时绘制两个子图,一个是带有投影的空间分布图,另一个为不带投影的曲线图。
比如说EOF的空间模态以及PC序列。
在python中,如果在一张画布上同时绘制以上两个图,通常会使得两张图片的长宽比非常的不协调。虽然可以通过手动设置子图的大小来调整,但是如果画布的大小改变,则又需要进行细调,感觉非常笨重麻烦。
今天在读文献的时候,又看到这种配置的图片,索性花时间来实现让其聪明的解决。
问题复现
以下就一张画布上同时绘制两张子图的方法进行复现。首先,想要画出来是非常简单的,设置两个子图,一个带投影,另一个不带投影就行。
绘制的方法也是多种多样的:
导入相关package
import xarray as xr
import glob
import numpy as np
import os
import calendar
import matplotlib.pyplot as plt
import pandas as pd
import cartopy.crs as ccrs
import matplotlib.ticker as mticker
import matplotlib.ticker as ticker
import cmaps
import cartopy.feature as cfeature
from matplotlib.colors import LinearSegmentedColormap
from cartopy.mpl.ticker import LongitudeFormatter, LatitudeFormatter
import matplotlib.gridspec as gridspec
import warnings
warnings.filterwarnings("ignore", category=FutureWarning)
需要注意的是,以上库是我常用的直接粘贴过来的,实际上作为演示,只需要matplotlib相关的库即可
一般我常用的绘制多子图的方法主要以下三种或者说主要两种:
绘制方法1: add_gridspec
fig = plt.figure(figsize=(10, 8), dpi=200)
gs = fig.add_gridspec(2, 2, height_ratios=[1, 1], hspace=0.3)
ax1 = fig.add_subplot(gs[0, 0], projection=ccrs.PlateCarree())
ax2 = fig.add_subplot(gs[0, 1])
绘制方法2:add_subplot
fig = plt.figure(figsize=(10, 5), dpi=200)
ax1 = fig.add_subplot(121, projection=ccrs.PlateCarree())
ax2 = fig.add_subplot(122)
绘制方法3:add_axes
fig = plt.figure(figsize=(10, 8), dpi=200)
ax1 = fig.add_axes([0.05, 0.1, 0.6, 0.8], projection=ccrs.PlateCarree())
ax2 = fig.add_axes([0.67, 0.26, 0.15, 0.525])
其中,以上三种在遇到投影+非投影的子图配置时,都会出现下面这种情况,就是非投影子图会相比投影的子图更长,或者说对不齐。总是非常丑陋
在前两种方法中,试了各自参数也不好使。在之前,我都是换思路使用第三种绘制方法来解决这个问题,因为在第三种画法里,子图的长宽是自己给定的,基本上都是一步一步微调至两端对齐,也能调的肉眼上是还不错的。
plt.rcParams['font.size'] = 12
plt.rcParams['axes.linewidth'] = 1
plt.rcParams['axes.unicode_minus'] = False
plt.rcParams['xtick.direction'] = 'in'
plt.rcParams['ytick.direction'] = 'in'
plt.rcParams['xtick.minor.visible'] = True
plt.rcParams['ytick.minor.visible'] = True
plt.rcParams['figure.dpi'] = 200
plt.rcParams['savefig.dpi'] = 200
plt.rcParams['savefig.bbox'] = 'tight'
plt.rcParams['savefig.pad_inches'] = 0.1
plt.rcParams['font.family'] = 'Times New Roman'
# 创建图形和子图
fig = plt.figure(figsize=(14, 8), dpi=200) # 调整大小
ax1 = fig.add_axes([0.05, 0.1, 0.6, 0.8], projection=ccrs.PlateCarree())
ax2 = fig.add_axes([0.67, 0.26, 0.15, 0.525])
ds_clim.plot(ax=ax1, transform=ccrs.PlateCarree(), cmap='viridis',
levels=np.linspace(0, 20, 21),
extend='neither',
cbar_kwargs={'label': 'mm/day',
'orientation': 'horizontal',
'shrink': 0.9, 'aspect': 40, 'pad': 0.05},
add_colorbar=True, add_labels=False)
ax1.coastlines()
ax1.set_title('GPM Daily Precipitation Climatology (2000-2021)', pad=15)
ax1.set_xticks(np.arange(0, 361, 60), crs=ccrs.PlateCarree())
ax1.set_yticks(np.arange(-90, 91, 30), crs=ccrs.PlateCarree())
ax1.xaxis.set_major_formatter(LongitudeFormatter())
ax1.yaxis.set_major_formatter(LatitudeFormatter())
ax1.xaxis.set_major_locator(mticker.MultipleLocator(60))
ax1.xaxis.set_minor_locator(mticker.AutoMinorLocator(4))
ax1.hlines(0, -180, 180, color='k', linestyle='--', linewidth=1.5)
# 计算经向平均
meridional_mean = ds_clim.mean(dim='lon')
# 绘制经向平均图
ax2.plot(meridional_mean, meridional_mean['lat'])
ax2.set_ylim(-90, 90)
ax2.set_xlim(0, 8)
ax2.set_yticklabels([])
ax2.hlines(0, 0, 42, color='k', linestyle='--', linewidth=1)
plt.show()
plt.close()
但是,这种方法的麻烦在于,子图的比例是与figsize密切相关,当你想调整你的figsize的大小时,原本调整好的子图大小会失效,需要再次重新调整。
新的解决方法
下午在看文献时,终于下定决心来解决这个比较笨的方法,或者说让我时间不再浪费在调子图的办法。解决思路是这样的,其实很简单,我的需求无非就是让右边的子图的高度与左侧的一样,至于宽度无所谓。而在整张画布上,两个子图,实际上都是根据对应的坐标绘制的,这一点在add_axes()
中也可以印证。所以,我只需要知道在每一种figsize中的投影子图的坐标即可,那么我在绘制右边非投影子图时,只需要通过add_axes()
保证改子图的高度以及距离画布底部的范围是一致的即可。这样问题就变成如何返回投影子图的坐标,而这是非常容易的。只需要ax.get_position()
即可返回坐标。
于是以下是调整后的代码,在不同的figsize下,都能使得两个子图的高度完美对应:
fig = plt.figure(figsize=(12, 8), dpi=200)
plt.subplots_adjust(left=0.05, right=0.95, top=0.95, bottom=0.05)
ax1 = fig.add_subplot(121, projection=ccrs.PlateCarree(180))
position_ax1 = ax1.get_position() # 返回Bbox对象
left = position_ax1.x0
bottom = position_ax1.y0
width = position_ax1.width
height = position_ax1.height
print(f"左: {left}, 下: {bottom}, 宽: {width}, 高: {height}")
ax2 = fig.add_axes([left+0.42, bottom,width/5,height])
最终,只需要调整第二子图中fig.add_axes([left+0.42, bottom,width/5,height])
的第一个和第三个参数即可,大大的节省了时间。
以下是最终结果:
给我点在看的人
越来越好看