Search code examples
pythonmatplotlibcolorspatchcolorbar

Setting the Colorbar Midpoint Value


Take this piece of code which generates a random integer within a range, assigns it an RGBA colour, and then plots it as a patch.

import numpy
import matplotlib as mpl
import matplotlib.pyplot as plt
import matplotlib.patches as patches
import matplotlib.colors as mcolors
import matplotlib.colorbar as cbar
from matplotlib import cm
import random

class MidpointNormalize(mpl.colors.Normalize):
    def __init__(self,vmin=None,vmax=None,midpoint=None,clip=False):
        self.midpoint=midpoint
        mpl.colors.Normalize.__init__(self,vmin,vmax,clip)
    def __call__(self,value,clip=None):
        x,y=[self.vmin,self.midpoint,self.vmax],[0,0.5,1]
        return numpy.ma.masked_array(numpy.interp(value,x,y),numpy.isnan(value))

colors=plt.cm.RdBu(numpy.linspace(0,1,256))
colormap=mcolors.LinearSegmentedColormap.from_list('colormap',colors)
normalisecolors=MidpointNormalize(vmin=-50,midpoint=0,vmax=100)
scalecolors=cm.ScalarMappable(norm=normalisecolors,cmap=colormap) 

fig,ax=plt.subplots(figsize=(8,8))
ax.set_xlim(0,100)
ax.set_ylim(0,100)

for x in range(0,100):
    for y in range(0,100):
        val=random.randint(-50,100)
        rgbacolor=scalecolors.to_rgba(val)
        ax.add_patch(patches.Polygon([(x,y),(x,y+1),(x+1,y+1),(x+1,y)],fill=True,facecolor=rgbacolor))

cax,_=cbar.make_axes(ax,orientation='horizontal',aspect=50,pad=0.12)
cb=cbar.ColorbarBase(cax,cmap=colormap,norm=normalisecolors,orientation='horizontal')

I want to modify the colour bar so that the colour white is represented by the value 0 (which is what I thought I'd done on line 20), but this hasn't worked:

enter image description here

I'd be grateful if anyone could suggest where I've gone wrong.

I'd kindly ask contributors to refrain from suggesting alternative ways of plotting this data; the actual data I am plotting requires hundreds of lines of code to read and process output files from a numerical model. I've stripped away all of this code and replaced it with a random number generator to enable users to run the code themselves to help me.


Solution

  • I have resolved this by using TwoSlopeNorm instead:

    normalisecolors=TwoSlopeNorm(vmin=-50,vcenter=0,vmax=100)
    

    and by adding a key line of code:

    cb.ax.set_xscale('linear')
    

    The MidpointNormalize elements of the code are therefore redundant and have been removed.

    The new code generates a plot and colour bar identical to previous versions of matplotlib.

    import numpy
    import matplotlib.pyplot as plt
    import matplotlib.patches as patches
    import matplotlib.colors as mcolors
    from matplotlib.colors import TwoSlopeNorm
    import matplotlib.colorbar as cbar
    from matplotlib import cm
    import random
    
    colors=plt.cm.RdBu(numpy.linspace(0,1,256))
    colormap=mcolors.LinearSegmentedColormap.from_list('colormap',colors)
    normalisecolors=TwoSlopeNorm(vmin=-50,vcenter=0,vmax=100)
    scalecolors=cm.ScalarMappable(norm=normalisecolors,cmap=colormap) 
    
    fig,ax=plt.subplots(figsize=(8,8))
    ax.set_xlim(0,100)
    ax.set_ylim(0,100)
    
    for x in range(0,100):
        for y in range(0,100):
            val=random.randint(-50,100)
            rgbacolor=scalecolors.to_rgba(val)      # Calculate RGBA color code for value
            ax.add_patch(patches.Polygon([(x,y),(x,y+1),(x+1,y+1),(x+1,y)],fill=True,facecolor=rgbacolor))
    
    cax,_=cbar.make_axes(ax,orientation='horizontal',aspect=50,pad=0.12)
    cb=cbar.ColorbarBase(cax,cmap=colormap,norm=normalisecolors,orientation='horizontal')
    cb.ax.set_xscale('linear')
    

    enter image description here