Search code examples
pythonmatplotlib3dcolormapmplot3d

Matplotlib 3d plot: get single colormap across 2 surfaces


I am making a 3d plot with matplotlib with 2 surfaces (see example below). So far both surfaces get their own colormap, being blue in the bottom and yellow on top.

However, I want a single colormap for both surfaces, i.e. the very bottom is blue, the very top is yellow and the touching point of both surfaces is green.

How can I achieve that? Do I need to somehow combine both surfaces before plotting, or do I need to restrict the colormaps of both surfaces (lower from blue to green, upper from green to yellow)?

Thanks for your help.

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import axes3d
from matplotlib import cm

ky = np.linspace(-np.pi*2/3,np.pi*2/3,100)
kz = np.linspace(-np.pi*2/3,np.pi*2/3,100)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')

KY, KZ = np.meshgrid(ky, kz)
E = np.cos(KY)*np.cos(KZ)
ax.plot_surface(KY, KZ, E-1, rstride=1, cstride=1, cmap=cm.viridis)   #surface 1
ax.plot_surface(KY, KZ, -E+1, rstride=1, cstride=1, cmap=cm.viridis)  #surface 2
ax.view_init(elev=7, azim=-69)
plt.show()

2 surfaces with independent colormap


Solution

  • You can explicitly set vmin and vmax for a colormap to force the range for the colors.

    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import axes3d
    from matplotlib import cm
    
    ky = np.linspace(-np.pi*2/3,np.pi*2/3,100)
    kz = np.linspace(-np.pi*2/3,np.pi*2/3,100)
    
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    
    KY, KZ = np.meshgrid(ky, kz)
    E = np.cos(KY)*np.cos(KZ)
    ax.plot_surface(KY, KZ, E-1, rstride=1, cstride=1, cmap=cm.viridis, vmin=-2, vmax=2)   #surface 1
    ax.plot_surface(KY, KZ, -E+1, rstride=1, cstride=1, cmap=cm.viridis, vmin=-2, vmax=2)  #surface 2
    ax.view_init(elev=7, azim=-69)
    plt.show()
    

    enter image description here

    To make the range tight against actual Z values in your two surfaces, you can use

    vmin=np.amin(E-1), vmax=np.amax(-E+1)
    

    You could also create this effect by defining your own color maps that go from yellow to green at the top, and from green to blue at the bottom.

    import numpy as np
    import matplotlib.pyplot as plt
    from mpl_toolkits.mplot3d import axes3d
    from matplotlib import cm
    from matplotlib.colors import ListedColormap
    
    ky = np.linspace(-np.pi*2/3,np.pi*2/3,100)
    kz = np.linspace(-np.pi*2/3,np.pi*2/3,100)
    
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    
    KY, KZ = np.meshgrid(ky, kz)
    E = np.cos(KY)*np.cos(KZ)
    
    viridis = cm.get_cmap('viridis', 512)
    topcolors = viridis(np.linspace(0.5, 1, 256))
    topcm = ListedColormap(topcolors)
    bottomcolors = viridis(np.linspace(0, 0.5, 256))
    bottomcm = ListedColormap(bottomcolors)
    
    ax.plot_surface(KY, KZ, E-1, rstride=1, cstride=1, cmap=bottomcm)   #surface 1
    ax.plot_surface(KY, KZ, -E+1, rstride=1, cstride=1, cmap=topcm)  #surface 2
    ax.view_init(elev=7, azim=-69)
    plt.show()
    

    enter image description here