Search code examples
matplotlibcolors3dsurface

unexpected constant color using matplotlib surface_plot and facecolors


I am plotting a function on the surface of a sphere. To test my code, I simply plot the spherical coordinate phi divided by pi. I get

half sphere with unexpected color

Unexpectedly, half of the sphere is of the same color, and the colors on the other half aren't correct (at phi=pi, i should get 1, not 2). If I divide the data array by 2, the problem disappears. Can someone explain to me what is happening?

Here is the code I use:

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

# prepare the sphere surface
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_xlabel('X axis')
ax.set_ylabel('Y axis')
ax.set_zlabel('Z axis')
phi = np.linspace(0,2*np.pi, 50)
theta = np.linspace(0, np.pi, 25)
x=np.outer(np.cos(phi), np.sin(theta))
y=np.outer(np.sin(phi), np.sin(theta))
z=np.outer(np.ones(np.size(phi)), np.cos(theta))

# prepare function to plot
PHI=np.outer(phi,np.ones(np.size(theta)))
THETA=np.outer(np.ones(np.size(phi)),theta)
data = PHI/np.pi

# plot
surface=ax.plot_surface(x, y, z, cstride=1, rstride=1, 
                        facecolors=cm.jet(data),cmap=plt.get_cmap('jet'))

# add colorbar
m = cm.ScalarMappable(cmap=surface.cmap,norm=surface.norm)
m.set_array(data)
plt.colorbar(m)
plt.show()

Solution

  • There is a little bit of chaos in the code.

    • When specifying facecolors, there is no reason to supply a colormap, because the facecolors do not need to be retrieved from a colormap.
    • Colormaps range from 0 to 1. Your data ranges from 0 to 2. Hence half of the facecolors are just the same. So you first need to normalize the data to the (0,1)-range, e.g. using a Normalize instance, then you can apply the colormap.

      norm = plt.Normalize(vmin=data.min(), vmax=data.max()) 
      surface=ax.plot_surface(x, y, z, cstride=1, rstride=1, 
                              facecolors=cm.jet(norm(data)))
      
    • For the colorbar you should then use the same colormap and the same normalization as for the plot itself.

      m = cm.ScalarMappable(cmap=cm.jet,norm=norm)
      m.set_array(data)
      

    Complete code:

    import matplotlib.pyplot as plt
    import numpy as np
    from matplotlib import cm
    from mpl_toolkits.mplot3d import Axes3D
    
    # prepare the sphere surface
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    ax.set_xlabel('X axis')
    ax.set_ylabel('Y axis')
    ax.set_zlabel('Z axis')
    phi = np.linspace(0,2*np.pi, 50)
    theta = np.linspace(0, np.pi, 25)
    x=np.outer(np.cos(phi), np.sin(theta))
    y=np.outer(np.sin(phi), np.sin(theta))
    z=np.outer(np.ones(np.size(phi)), np.cos(theta))
    
    # prepare function to plot
    PHI=np.outer(phi,np.ones(np.size(theta)))
    THETA=np.outer(np.ones(np.size(phi)),theta)
    data = PHI/np.pi
    
    # plot
    norm = plt.Normalize(vmin=data.min(), vmax=data.max()) 
    surface=ax.plot_surface(x, y, z, cstride=1, rstride=1, 
                            facecolors=cm.jet(norm(data)))
    
    # add colorbar
    m = cm.ScalarMappable(cmap=cm.jet,norm=norm)
    m.set_array(data)
    plt.colorbar(m)
    plt.show()
    

    enter image description here