I am working on making a nice precipitation map using Metpy. I think this is more of a matplotlib question, but I am stumped on how to add two horizontal colorbars onto this figure. The overall idea is to have snow and rain rates shown on this map which I have accomplished by utilizing two contour fills onto one figure. I want to show two colorbars since I have two separate rates for the snow rates vs the rain rates. Ideally, I want the two colorbars side by side horizontally. The closest I have gotten is to have them stacked with a large space between them. Here is my sloppy code.
import metpy
from siphon.catalog import TDSCatalog
from datetime import datetime, timedelta
%matplotlib inline
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from metpy.units import units
import metpy.calc as mpcalc
import matplotlib.pyplot as plt
import matplotlib.colors as cls
from xarray.backends import NetCDF4DataStore
import xarray as xr
from scipy.ndimage import gaussian_filter
import numpy as np
# Set up access via NCSS
gfs_catalog = ('http://thredds.ucar.edu/thredds/catalog/grib/NCEP/GFS/'
'Global_0p5deg/catalog.xml?dataset=grib/NCEP/GFS/Global_0p5deg/Best')
cat = TDSCatalog(gfs_catalog)
ncss = cat.datasets[0].subset()
query3 = ncss.query()
query3.accept('netcdf')
query3.variables('Pressure_reduced_to_MSL_msl', 'Precipitation_rate_surface', 'Snow_depth_surface', 'Categorical_Snow_surface')
now = datetime.utcnow()
query3.time_range(now, now + timedelta(days=4))
query3.lonlat_box(west=-140, east=-60, north=60, south=20)
data3 = ncss.get_data(query3)
ds3 = xr.open_dataset(NetCDF4DataStore(data3))
#parsing data
isSnow_var = ds3.metpy.parse_cf('Categorical_Snow_surface')
precip_var = ds3.metpy.parse_cf('Precipitation_rate_surface')
longitude = precip_var.metpy.x
latitude = precip_var.metpy.y
time_index = 11
#All the Precip Stuff
precip_inch_hour = precip_var[time_index].squeeze() * 141.73228346457
precip2 = mpcalc.smooth_n_point(precip_inch_hour, 5, 1)
#Converting to 10:1 snow ratio
snow_precip = (precip_inch_hour * isSnow_var[time_index].squeeze()) * 10
# Plot using CartoPy and Matplotlib
mapproj = ccrs.LambertConformal(central_latitude=45., central_longitude=-100.)
# Set projection of data
data_projection = ccrs.PlateCarree()
# Grab data for plotting state boundaries
states_provinces = cfeature.NaturalEarthFeature(
category='cultural',
name='admin_1_states_provinces_lakes',
scale='50m',
facecolor='none')
# Set extent and plot map lines
fig = plt.figure(1, figsize=(25.,25.))
ax = plt.subplot(111, projection=mapproj)
ax.set_extent([-125., -70, 25., 50.], ccrs.PlateCarree())
ax.coastlines('50m', edgecolor='black', linewidth=0.75)
ax.add_feature(states_provinces, edgecolor='black', linewidth=0.5)
#colormap data
precip_colors = [
"#bde9bf", # 0.01 - 0.02 inches 1
"#adddb0", # 0.02 - 0.03 inches 2
"#9ed0a0", # 0.03 - 0.04 inches 3
"#8ec491", # 0.04 - 0.05 inches 4
"#7fb882", # 0.05 - 0.06 inches 5
"#70ac74", # 0.06 - 0.07 inches 6
"#60a065", # 0.07 - 0.08 inches 7
"#519457", # 0.08 - 0.09 inches 8
"#418849", # 0.09 - 0.10 inches 9
"#307c3c", # 0.10 - 0.12 inches 10
"#1c712e", # 0.12 - 0.14 inches 11
"#f7f370", # 0.14 - 0.16 inches 12
"#fbdf65", # 0.16 - 0.18 inches 13
"#fecb5a", # 0.18 - 0.2 inches 14
"#ffb650", # 0.2 - 0.3 inches 15
"#ffa146", # 0.3 - 0.4 inches 16
"#ff8b3c", # 0.4 - 0.5 inches 17
"#ff8b3c" # 0.5 - 0.6 inches 18
]
precip_colormap = cls.ListedColormap(precip_colors)
#Precip Rate
clev_precip = np.concatenate((np.arange(0.01, 0.1, .01), np.arange(.1, .2, .02), np.arange(.2, .61, .1)))
norm = cls.BoundaryNorm(clev_precip, 18)
cf = ax.contourf(longitude, latitude, precip2, clev_precip, cmap=precip_colormap, norm=norm, transform=ccrs.PlateCarree())
cb = plt.colorbar(cf, ticks=clev_precip, aspect=65, orientation = 'horizontal', shrink=0.6, pad=0.01)
snow_colors = [
"#63c9d5", # 0.1 - 0.2 inches 1
"#5fb4ca", # 0.2 - 0.3 inches 2
"#5a9fc0", # 0.3 - 0.4 inches 3
"#558ab5", # 0.4 - 0.5 inches 4
"#4e76aa", # 0.5 - 0.6 inches 5
"#4763a0", # 0.6 - 0.7 inches 6
"#3e4f95", # 0.7 - 0.8 inches 7
"#353c8b", # 0.8 - 0.9 inches 8
"#292980", # 0.9 - 1.0 inches 9
"#493387", # 1.0 - 1.2 inches 10
"#643e8e", # 1.2 - 1.4 inches 11
"#7c4995", # 1.4 - 1.6 inches 12
"#94559c", # 1.6 - 0.18 inches 13
"#ab61a3", # 1.8 - 2 inches 14
"#c36eaa", # 2 - 3 inches 15
"#da7bb0", # 3 - 4 inches 16
"#f288b7" # 4 - 5 inches 17
]
snow_colormap = cls.ListedColormap(snow_colors)
#Snow Rate
clev_snow = np.concatenate((np.arange(.1, 1, .1), np.arange(1, 2, .2), np.arange(2, 6, 1)))
norm2 = cls.BoundaryNorm(clev_snow, 17)
cf2 = ax.contourf(longitude, latitude, snow_precip, clev_snow, cmap=snow_colormap, norm=norm2, transform=ccrs.PlateCarree())
cb2 = plt.colorbar(cf2, ticks=clev_snow, orientation = 'horizontal', pad=0.01, shrink=0.6, aspect=65)
#Valid Time
vtime = isSnow_var.metpy.time[time_index].values
#Title Info
plt.title('MSLP (hPa) with Highs and Lows, 1000-500 hPa Thickness (m), Rain (in/hr), Snow 10:1 (in/hr)', loc='left')
plt.title(f'VALID: {vtime}', loc='right')
I've tried subplots and I've tried gridSpec but I keep hitting different roadblocks, getting different errors when I try those different methods. Mainly I think it has to do with my entry level knowledge with Metpy, Cartopy, and Matplotlib. Apprecaite any insight and possible solution to this issue. Thanks!
First off, thank you for posting all your code in a reproducible fashion!
This looks like a solution for subaxes. Using fig.add_axes
you can create subaxes precisely where you want them.
In place of your cb
statements I used the following two blocks:
cax1 = fig.add_axes([0.15, 0.25, 0.3, 0.02])
cb = fig.colorbar(cf, cax=cax1, orientation = 'horizontal')
cax2 = fig.add_axes([0.55, 0.25, 0.3, 0.02])
cb2 = fig.colorbar(cf2, cax=cax2, orientation = 'horizontal')
This creates two horizontal colourbars side by side. The four numbers are the location of the bottom left corner in x then y and the size in x then y, all as a proportion of the figure size. I don't have metpy working on this machine so had to use random data, that shouldn't affect results for you though:
n.b. as you are creating new axes, any calls to plt.<function>
will default to using the last created axes, in this case cb2. For this reason, you chould use mantplotlib's preferred object-oriented programming style, so create your title with ax.set_title
rather than plt.title