pythonmatplotlibplotvisualizationcolorbar

Ensure matplotlib colorbar looks the same across multiple figure


Consider the following code

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

def make_Z(X,Y, offset=0.):
    return np.sin(X) + np.cos(Y) + offset

class MidpointNormalize(colors.Normalize):
    def __init__(self, vmin=None, vmax=None, midpoint=None, clip=False):
        self.midpoint = midpoint
        colors.Normalize.__init__(self, vmin, vmax, clip)

    def __call__(self, value, clip=None):
        # I'm ignoring masked values and all kinds of edge cases to make a
        # simple example...
        x, y = [self.vmin, self.midpoint, self.vmax], [0, 0.5, 1]
        return np.ma.masked_array(np.interp(value, x, y), np.isnan(value))

################################################################################


x,y = np.linspace(-10, 10, 100), np.linspace(-10, 10, 100)
X, Y = np.meshgrid(x,y)
Z = make_Z(X,Y, offset=0.8)#; print(Z.shape)
tens = np.logspace(0., 3.0, num=4); print(tens)
Zs = [Z/i for i in tens]#; print(len(Zs))
min_Z = np.min(Zs)
max_Z = np.max(Zs)
norm = MidpointNormalize(vmin=min_Z, vmax=max_Z, midpoint=0.)
for Z in Zs:
    print("min, max: %f, %f"%(np.min(Z), np.max(Z)))
    fig, ax = plt.subplots()
    Z_plot = ax.contourf(X,Y,Z, levels=100, norm=norm, cmap='seismic', vmin=min_Z, vmax=max_Z)
    plt.colorbar(Z_plot, ax=ax)
    plt.show()
    print("-"*70)

This produces the following output:

enter image description here

Explanation: I make a Z value based on X and Y coordinates, then divide the Z value in each figure by powers of ten (100, 101, 102, 103), hence the plots becoming weaker and weaker further down.

Basically, everything is as expected. However, I would like the same colourbar to be applied to all figures. At the moment it looks like the same colormap is applied (which it should be), but I want the full "blue-on-bottom-red-above"- colorbar in all figures, even those that appear all white in colour. I am somewhat confident that I tried out all the arguments to contourf and colorbar, including the vmin and vmax arguments, but can not get the full range colorbar to be shown on all figures. Any hints?

EDIT:

I had the idea to maybe adjust the ylim property of the colorbar, since it is after all just a regular axis. Defining the colorbar as cbar and then doing cbar.ax.set_ylim(min_Z, max_Z) results in gibberish, however.


Solution

  • Option 1

    I don't know if this is acceptable for you, but you can replace contourf with imshow:

    Z_plot = ax.imshow(Z, norm=norm, cmap='seismic', vmin=min_Z, vmax=max_Z, interpolation = 'bilinear')
    

    enter image description here enter image description here enter image description here enter image description here


    Option 2

    matplotlib.pyplot.colorbar is linked to the mappable you pass it as the first argument. A trick can be to pass to colorbar the non scaled contour.
    Try if this code fit for you:

    for Z, ten in zip(Zs, tens):
        print("min, max: %f, %f"%(np.min(Z), np.max(Z)))
        fig, ax = plt.subplots()
        plt.colorbar(ax.contourf(X,Y,Z*ten, levels=100, norm=norm, cmap='seismic', vmin=min_Z, vmax=max_Z), ax=ax)
        Z_plot = ax.contourf(X, Y, Z, levels = 100, norm = norm, cmap = 'seismic', vmin = min_Z, vmax = max_Z)
        plt.show()
        print("-"*70)
    

    enter image description here enter image description here enter image description here enter image description here


    Option 3

    The same concept of above but this time I set up the mappable to pass to colorbar in a more clean way.

    for Z in Zs:
        print("min, max: %f, %f"%(np.min(Z), np.max(Z)))
        fig, ax = plt.subplots()
        Z_plot = ax.contourf(X, Y, Z, levels = 100, norm = norm, cmap = 'seismic', vmin = min_Z, vmax = max_Z)
        m = cm.ScalarMappable(cmap = cm.seismic, norm = colors.TwoSlopeNorm(vmin = np.min(Z), vcenter = 0, vmax = np.max(Z)))
        m.set_array(Z)
        m.set_clim(np.min(Z), np.max(Z))
        cbar = plt.colorbar(m, boundaries = np.linspace(np.min(Z), np.max(Z), 100))
        ticks = np.append(0, np.linspace(np.min(Z), np.max(Z), 9))
        ticks.sort()
        cbar.set_ticks(ticks)
        plt.show()
        print("-"*70)
    

    enter image description here enter image description here enter image description here enter image description here