Search code examples
pythonmatplotlibcolorbar

How to get build a consistent discrete colormap/colorbar with "upper" and "lower" values


An image is worth a thousand words : https://www.harrisgeospatial.com/docs/html/images/colorbars.png

I want to obtain the same color bar than the one on the right with matplotlib. Default behavior use the same color for "upper"/"lower" and adjacent cell...

Thank you for your help!

Here is the code I have:

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors

N = 100
X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
Z1 = np.exp(-X**2 - Y**2)
Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
Z = (Z1 - Z2) * 2

fig, ax = plt.subplots(1, 1, figsize=(8, 8))

# even bounds gives a contour-like effect
bounds = np.linspace(-1, 1, 10)
norm = colors.BoundaryNorm(boundaries=bounds, ncolors=256)
pcm = ax.pcolormesh(X, Y, Z,
                    norm=norm,
                    cmap='RdBu_r')
fig.colorbar(pcm, ax=ax, extend='both', orientation='vertical')

Solution

  • In order to have the "over"/"under"-color of a colormap take the first/last color of that map but still be different from the last color inside the colormapped range you can get one more color from a colormap than you have boundaries in the BoundaryNorm and use the first and last color as the respective colors for the "over"/"under"-color.

    import numpy as np
    import matplotlib.pyplot as plt
    import matplotlib.colors as mcolors
    
    N = 100
    X, Y = np.mgrid[-3:3:complex(0, N), -2:2:complex(0, N)]
    Z1 = np.exp(-X**2 - Y**2)
    Z2 = np.exp(-(X - 1)**2 - (Y - 1)**2)
    Z = (Z1 - Z2) * 2
    
    fig, ax = plt.subplots(1, 1, figsize=(8, 8))
    
    # even bounds gives a contour-like effect
    bounds = np.linspace(-1, 1, 11)
    # get one more color than bounds from colormap
    colors = plt.get_cmap('RdBu_r')(np.linspace(0,1,len(bounds)+1))
    # create colormap without the outmost colors
    cmap = mcolors.ListedColormap(colors[1:-1])
    # set upper/lower color
    cmap.set_over(colors[-1])
    cmap.set_under(colors[0])
    # create norm from bounds
    norm = mcolors.BoundaryNorm(boundaries=bounds, ncolors=len(bounds)-1)
    pcm = ax.pcolormesh(X, Y, Z, norm=norm, cmap=cmap)
    fig.colorbar(pcm, ax=ax, extend='both', orientation='vertical')
    
    plt.show()
    

    enter image description here