При использовании tight_layout
с Картографией и несколькими подзаголовками результат не является плотным макетом. Пример:
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)
создает фигуру с большим промежутком между каждой картой:
The same issue occurs when using pyplot's tight_layout
, whether I set the ax.set_extent()
or not, the result is not as expected. Example:
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()
While I can adjust this manually on both cases by using w_pad
and h_pad
arguments on tight_layout
, I would have to do this manually for every figure depending on the amount of subplots and/or the map itself. Is there any way to do this without these arguments, or to automatically determine what these values should be? Or maybe automatically set the figure size?
Using Linux 5.4.0-40-generic, Python 3.7.6 64-bit, Cartopy 0.18.0, Matplotlib 3.2.2
Edit:
The question is how to reproduce the tight_layout
function using Cartopy without needing to manually set the w_pad
or h_pad
depending on what I'm plotting.
Currently, I'm creating many different figures, changing:
- Projections: some are PlateCarree, some are Polar Stereographic
- Map extent: entire map, reduced lat/lon on PlateCarree, reduced latitudes on Polar Stereographic
- Different amount of rows/cols: 1 to 7 rows, 1 to 5 columns.
- Adding latitudes and longitudes ticks on the first column and the last row, a title to each column, an
y_label
on the first column, and an overall title.
- Depending on the figure, adding a colorbar on the right of each row.
The end result should be figures that have consistent horizontal and vertical space between each subplot and the different figures, like the expected result of tight_layout
.
Example code with all the above, following @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()
The above produces graphs that have very different horizontal/vertical gaps depending on the amount of subplots/projections. The labels, titles, etc are also sometimes out of proportion for the overall figure (which doesn't happen when tight_layout
works as intended). Some are also rotated. E.g., using the above code with rows, cols = 3, 4
, PlateCarree()
and the complete map plotted, gives:
Большие промежутки по горизонтали / вертикали, поворотная ось