I am plotting a contourf of some data with matplotlib, xarray, and cartopy. The data contouring is plotted using an ax.contourf instance, generating the colorbar in the image below. I then mask the array and hatch over all values that equal zero.
I would like to add an additional bar to the left side of the colorbar that is hatched out, like the following I constructed in GIMP
I imagine that this would be performed prior to the fig.colorbar() call, operating on the original contourf handle, but can't quite figure out how to do this.
Appreciate any advice.
Minimal sample follows, producing image below:
import matplotlib.pyplot as plt
import cartopy.crs as ccrs
import numpy as np
fig = plt.figure()
ax = plt.axes(projection = ccrs.Mollweide(central_longitude=96))
ax.coastlines()
X = np.arange(-180,180,1)
Y = np.arange(-90,91,1)
X_grid, Y_grid = np.meshgrid(X,Y)
Z = np.random.random_sample((len(Y),len(X))).round(1)
Z.sort()
levels = [0,0.05,0.25,0.5,0.75,0.85,1]
CS = ax.contourf(X_grid,Y_grid,Z,transform = ccrs.PlateCarree(), cmap='plasma', transform_first=True, levels=levels)
HS = ax.contourf(X_grid,Y_grid,np.ma.masked_not_equal(Z,0),transform=ccrs.PlateCarree(),colors='none', levels=[-0.01,0.01],hatches=['X','X'], transform_first=True)
cbar = fig.colorbar(CS, orientation='horizontal', aspect=40, location='top')
Edit: One solution, which I don't like because it isn't robust, is to define the hatched rectangle independently of the colorbar, shrink the colorbar, define the rectangle patch anchorpoint and size explicitly, and then add the rectangle to the figure. I've done that in the following code snippet, producing the image that follows (ignore that I've also made changes to the proportionality of the colorbar or the tick positions relative to the colorbar):
import matplotlib.patches as mpatches
hrect = mpatches.Rectangle((.0.9185, 0.8998), 0.057, 0.0395, ec='k',hatch='XXX', transform=fig.transFigure, figure=fig, fill=False)
fig.patches.extend([hrect])
This isn't a satisfying answer as the rectangle position will vary relative to the colorbar position if the figure size is changed, if the geoaxes change, etc. Ideally, this rectangle would be stuck on to the end of the colorbar object, but it doesn't seem like that's possible. One way to at least keep everything in line when changes are made is to define a specific colorbar axis with gridspec and then plot the rectangle in a separate gridspec subplot that's contiguous with the cax. Again, not elegant.
Final edit: And this is the result from the solution @JohanC provided:
The approach below adds an extra level to create a box for the hatched region.
A list of colors is created from the originally used colors, extended with a color for the first region. In the example code, white is used to color it, but you could e.g. also use the same color as the first region.
The list of colors is used to create a ListedColormap
(without a colormap, there is no information for a colormap).
After both the contour maps and the colorbar are created, a transparent rectangle with the same hatch pattern is drawn on top of the first colorbar region.
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import cartopy.crs as ccrs
import numpy as np
X = np.arange(-180, 180, 1)
Y = np.arange(-90, 91, 1)
X_grid, Y_grid = np.meshgrid(X, Y)
Z = np.random.random_sample((len(Y), len(X))).round(1)
Z.sort()
fig = plt.figure()
ax = plt.axes(projection=ccrs.Mollweide(central_longitude=96))
ax.coastlines()
hatch_level = 0.01
levels = [0, hatch_level, 0.05, 0.25, 0.5, 0.75, 0.85, 1]
colors = ['white'] + list(plt.get_cmap('plasma', len(levels) - 2).colors)
cmap = ListedColormap(colors)
CS = ax.contourf(X_grid, Y_grid, Z, transform=ccrs.PlateCarree(), cmap=cmap, transform_first=True, levels=levels)
HS = ax.contourf(X_grid, Y_grid, np.ma.masked_not_equal(Z, 0), transform=ccrs.PlateCarree(), colors='none',
levels=[-hatch_level, hatch_level], hatches=['XX'], transform_first=True)
HS.set_edgecolor('dodgerblue') # will be used for hatch marks and the contour edges
HS.set_linewidth(0) # don't show the contour edges
cbar = fig.colorbar(CS, orientation='horizontal', aspect=40, location='top')
# create a rectangle on top of the first colored region
rect = plt.Rectangle((0, 0), hatch_level, 1, # x between 0 and hatch_level, y between 0 and 1
facecolor='none', # make the facecolor transparent
hatch=HS.hatches[0], # use the same hatch pattern as in HS
edgecolor=HS.get_edgecolor(), # use the same hatch color as in HS
lw=0) # don't show an edge around the rectanble
# add the rectangle to the colorbar
cbar.ax.add_patch(rect)
# get the xtick labels of the colorbar
xtick_labels = [t.get_text() for t in cbar.ax.get_xticklabels()]
# replace the second label with the first, and empty the first
xtick_labels[1] = xtick_labels[0]
xtick_labels[0] = ''
cbar.ax.set_xticklabels(xtick_labels)
plt.show()
Here is how it could look like if the hatched region gets the same face color as the first region:
# ...
colors = list(plt.get_cmap('plasma', len(levels) - 2).colors)
colors = [colors[0] ]+ colors
# ...
HS.set_edgecolor('pink')