Search code examples
pythonmatplotlibcolorbar

Creating a custom colorbar in matplotlib


How can I create a colorbar in matplotlib that looks like this:

enter image description here

Here is what I tried:

import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap
from matplotlib.cm import ScalarMappable
from matplotlib.colors import Normalize

# Define the custom colormap
colors = ['red', 'cyan', 'darkgreen']
cmap = LinearSegmentedColormap.from_list(
    'custom_colormap', 
    [(0.0, colors[0]), (0.5 / 2.0, colors[0]),
     (0.5 / 2.0, colors[1]), (1.5 / 2.0, colors[1]),
     (1.5 / 2.0, colors[2]), (2.0 / 2.0, colors[2])]
)

# Create a scalar mappable object with the colormap
sm = ScalarMappable(norm=Normalize(vmin=3.5, vmax=4.5), cmap=cmap)

# Create the colorbar
plt.figure(figsize=(3, 1))
cb = plt.colorbar(sm, orientation='horizontal', ticks=[3.5, 4.5], extend='neither')
cb.set_label('')

Solution

  • One solution is to draw wide white edges around the color segments (using cb.solids.set below), hide the spines (cb.outline.set_visible) and draw vertical lines as dividers (cb.ax.axvline). To match the desired colorbar, make sure to pass a ymin that is greater than 0.

    import matplotlib.pyplot as plt
    from matplotlib.colors import ListedColormap, Normalize
    from matplotlib.cm import ScalarMappable
    
    fig, ax = plt.subplots(figsize=(3, 1), layout='tight')
    
    colors = ['red', 'cyan', 'darkgreen']
    ticks = [3.5, 4.5]
    cmap = ListedColormap(colors)
    norm = Normalize(vmin=2.5, vmax=5.5)
    cb = fig.colorbar(
        mappable=ScalarMappable(norm=norm, cmap=cmap),
        cax=ax,
        ticks=ticks,
        ticklocation='top',
        orientation='horizontal',
    )
    cb.solids.set(edgecolor='white', linewidth=5)
    cb.outline.set_visible(False)
    cb.ax.tick_params(width=1, length=10, color='k')
    for bound in ticks:
        cb.ax.axvline(bound, c='k', linewidth=1, ymin=0.3, alpha=0.6)
    plt.setp(cb.ax.xaxis.get_ticklines(), alpha=0.6)
    cb.set_ticklabels(ticks, alpha=0.6, color='k', fontsize=15, fontfamily='Arial')
    

    result2

    Another solution is instead of drawing vertical lines as dividers, just use the divider lines defined on colorbar object itself (pass drawedges=True). However, the end result will be a little different from the desired result because the divider line draws from the bottom to the top (can't pass ymin like above).

    fig, ax = plt.subplots(figsize=(3, 1), layout='tight')
    
    colors = ['red', 'cyan', 'darkgreen']
    ticks = [3.5, 4.5]
    cmap = ListedColormap(colors)
    norm = Normalize(vmin=2.5, vmax=5.5)
    cb = fig.colorbar(
        mappable=ScalarMappable(norm=norm, cmap=cmap),
        cax=ax,
        ticks=ticks,
        ticklocation='top',
        orientation='horizontal',
        drawedges=True
    )
    cb.solids.set(edgecolor='white', linewidth=5)
    cb.outline.set_visible(False)
    cb.dividers.set(linewidth=1, alpha=0.6)
    cb.ax.tick_params(width=1, length=10, color='k')
    plt.setp(cb.ax.xaxis.get_ticklines(), alpha=0.6)
    cb.set_ticklabels(ticks, alpha=0.6, color='k', fontsize=15, fontfamily='Arial')
    

    result2