Too much white space in figure including multiple projected maps (matplotlib and cartopy)

I initially posted this issue in the ARCCSS slack, but I am reposting here in case this may be useful to someone else:

Hello everyone! I have a bit of an issue when plotting projected maps in a grid. I am using CMIP6 outputs from various models and I want to present all results in the same figure. All maps are plotted using matplotlib and cartopy , but for some unknown reason, there is an impressive amount of white space between maps and I cannot understand what the problem is (see figure below).
I have used gridspec and subplots , but they both have the same issue. I have tried changing the aspect of each map, the space between each map, applying a constrained and tight layout, adjust the figure size, remove the colorbars (once of which is shared between two columns), but nothing seems to solve the issue.
Below is the latest attempt at getting rid of the white space:

fig, axs = plt.subplots(len(models), len(decadeName), subplot_kw = {'projection': ccrs.Robinson(), 'aspect': 0.75}, gridspec_kw = {'wspace': 0.0, 'hspace': 0.1}, layout = 'constrained',
figsize = (15, 11.25))
for i, mod in enumerate(models):
  x = data[mod].sel(month = month_plot)
  for j, dec in enumerate(x.decade.values):
    #Add land and coastlines
    axs[i, j].add_feature(land_50m)
    axs[i, j].coastlines(resolution = '50m')
      if j < 2:
      p1 = x.sel(decade = dec).plot.pcolormesh('lon', 'lat', ax = axs[i, j], transform = ccrs.PlateCarree(),
      cmap = cm.cm.speed, vmin = min_intpp, vmax = max_intpp*0.9, add_colorbar = False,
      norm = mcolors.PowerNorm(gamma = 0.5))
    else:
      p2 = x.sel(decade = dec).plot.pcolormesh('lon', 'lat', ax = axs[i, j], transform = ccrs.PlateCarree(),
      cmap = mymap_diff, norm = divnorm_diff, add_colorbar = False)
    axs[i, j].set_title('')
    
cb = plt.colorbar(p1, ax = axs[i, 0:2], use_gridspec = True, orientation = 'horizontal',
                  shrink = 0.75, aspect = 30,
                  label = 'Primary Organic Carbon Production (mol m-2 s-1)')

cb_dif = plt.colorbar(p2, ax = axs[i, -1], use_gridspec = True, orientation = 'horizontal',
                      shrink = 0.9, label = 'Difference (mol m-2 s-1)')

fig.suptitle(f'Mean {month_plot} values', y = 1.0)

A solution that was offered was to change the figure size, but this has not helped.

Processing: test.png…

1 Like

I’ll try to summarize here the discussion from Slack so that it’s documented for future reference.

Changing the figsize actually did the job in the end. For a configuration with 10x3 subplot panels one should have a figsize that has the equivalent ratio…

Here’s a demonstration below. The only difference for the two cases is the figsize.

import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cft

land_50m = cft.NaturalEarthFeature('physical', 'land', '50m',
                                   edgecolor='black', facecolor='papayawhip', linewidth=0.5)

fig, axs = plt.subplots(10, 3,
                        subplot_kw = {'projection': ccrs.Robinson(),
                                      'aspect': 0.75},
                                      gridspec_kw = {'wspace': 0.0, 'hspace': 0.1},
                                      layout = 'constrained',
                        figsize = (15, 11.25))

for i in np.arange(10):
    for j in np.arange(3):
        axs[i, j].add_feature(land_50m)
        axs[i, j].coastlines(resolution = '50m')

fig.suptitle("Denisse's attempt", y = 1.1)

fig, axs = plt.subplots(10, 3,
                        subplot_kw = {'projection': ccrs.Robinson(),
                                      'aspect': 0.75},
                                      gridspec_kw = {'wspace': 0.0, 'hspace': 0.1},
                                      layout = 'constrained',
                        figsize = (5, 11.25))

for i in np.arange(10):
    for j in np.arange(3):
        axs[i, j].add_feature(land_50m)
        axs[i, j].coastlines(resolution = '50m')

fig.suptitle("Navid's attempt", y = 1.0);


However, after the solution above was posted in Slack @lidefi87 pointed out that when trying to save the figure still some white space was there – things didn’t seem to work as well.

After some googling and fiddling around I came up with some improvements that make the saved figure look identical to how the figure is displayed on IDE.

import numpy as np
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import cartopy.feature as cft

land_50m = cft.NaturalEarthFeature('physical', 'land', '50m',
                                   edgecolor='black', facecolor='papayawhip', linewidth=0.5)



# 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 * 1.02  # height in inches; factor 1.02 to allow for suptitle

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

fig, axs = plt.subplots(rows, cols,
                        subplot_kw = {'projection': ccrs.Robinson()},
                        layout = 'constrained',
                        figsize=(wi, hi))

for i in np.arange(rows):
    for j in np.arange(cols):
        axs[i, j].add_feature(land_50m)
        axs[i, j].coastlines(resolution = '50m')

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

# Get proper ratio here; use one of the axis, e.g., axs[0, 0]
xmin, xmax = axs[0, 0].get_xbound()
ymin, ymax = axs[0, 0].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)

my_suptitle = fig.suptitle("Navid's attempt", y = 1.02)

fig.savefig("navids_fig.png",
            dpi=300,
            bbox_inches='tight',
            bbox_extra_artists=[my_suptitle])
plt.show()