How can a colormap of a surface be mapped to a scalar function?

2

Translation of question I asked no OS :

I have a scalar function that represents the electrical potential on a spherical surface. I want to plot, for a given radius, the surface and map its color points based on the potential function.

How can I map this function to the surface? I suspect there are arguments to the ax.plot_surface . I tried to use the argument: facecolors=potencial(x,y,z) , ma received ValueError: Invalid RGBA argument. Looking at the source code of the third example , there is:

# Create an empty array of strings with the same shape as the meshgrid, and
# populate it with two colors in a checkerboard pattern.
colortuple = ('y', 'b')
colors = np.empty(X.shape, dtype=str)
for y in range(ylen):
    for x in range(xlen):
        colors[x, y] = colortuple[(x + y) % len(colortuple)]

That I did not understand, I have no idea how to link with a scalar function.

My code

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

def potencial(x,y,z, a=1., v=1.):
    r = np.sqrt( np.square(x) + np.square(y) + np.square(z) )    
    p = r/z #cos(theta)
    asr = a/r
    s=0
    s += np.polyval(special.legendre(1), x) * 3/2*np.power(asr, 2)
    s += np.polyval(special.legendre(3), x) * -7/8*np.power(asr, 4)
    s += np.polyval(special.legendre(5), x) * 11/16*np.power(asr, 6)    
    return v*s

# criar dados
def sphere_surface(r):
    u = np.linspace(0, 2 * np.pi, 100)
    v = np.linspace(0, np.pi, 100)
    x = r * np.outer(np.cos(u), np.sin(v))
    y = r * np.outer(np.sin(u), np.sin(v))
    z = r * np.outer(np.ones(np.size(u)), np.cos(v))
    return x,y,z

x,y,z = sphere_surface(1.5)

fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Plotar a superficie
surf = ax.plot_surface(x,y,z, cmap=cm.coolwarm,
                       linewidth=0, antialiased=False)
fig.colorbar(surf, shrink=0.5, aspect=5)
# Está mapeado aos valores do eixo-z

ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
plt.show()
    
asked by anonymous 01.05.2017 / 16:45

1 answer

1

Translated answer ( original ):

In principle there are two ways to color a surface in matplotlib.

  • Use the cmap argument to specify a colormap . In this case the color will be chosen according to the array z . In this case this is not desired,
  • Use the facecolors argument. It expects an array of colors in the same way as z.
  • So, in this case we need to choose option 2 and construct an array of colors. For this purpose, one can choose a colormap . A colormap maps values between 0 and 1 to a color. Since the potential has values well above and below this range, we need to normalize it to the interval [0,1]. Matplotlib already provides a help function to do this normalization and as the potential has a 1 / x dependency, a logarithmic color scale may be appropriate.

    In the end, facecolors can be given by an array

    colors = cmap(norm(potential(...)))
    

    The missing part is the colorbar ( colorbar ). In order for it to be linked to the surface colors of the plot , we need to manually mount a ScalarMappable with colormap and the normalization instance, which we can then provide colorbar .

    sm = plt.cm.ScalarMappable(cmap=plt.cm.coolwarm, norm=norm)
    sm.set_array(pot)
    fig.colorbar(sm, shrink=0.5, aspect=5)
    

    Here is a complete example

    from __future__ import division
    from mpl_toolkits.mplot3d import Axes3D
    import matplotlib.pyplot as plt
    import matplotlib.colors
    import numpy as np
    from scipy import special    
    
    def potencial(x,y,z, a=1., v=1.):
        r = np.sqrt( np.square(x) + np.square(y) + np.square(z) )    
        p = z/r #cos(theta)
        asr = a/r
        s=0
        s += np.polyval(special.legendre(1), p) * 3/2*np.power(asr, 2)
        s += np.polyval(special.legendre(3), p) * -7/8*np.power(asr, 4)
        s += np.polyval(special.legendre(5), p) * 11/16*np.power(asr, 6)    
        return v*s
    
    # Make data
    def sphere_surface(r):
        u = np.linspace(0, 2 * np.pi, 100)
        v = np.linspace(0, np.pi, 100)
        x = r * np.outer(np.cos(u), np.sin(v))
        y = r * np.outer(np.sin(u), np.sin(v))
        z = r * np.outer(np.ones(np.size(u)), np.cos(v))
        return x,y,z
    
    x,y,z = sphere_surface(1.5)
    pot = potencial(x,y,z)
    
    
    norm=matplotlib.colors.SymLogNorm(1,vmin=pot.min(),vmax=pot.max())
    colors=plt.cm.coolwarm(norm(pot))
    
    fig = plt.figure()
    ax = fig.add_subplot(111, projection='3d')
    # Plot the surface
    surf = ax.plot_surface(x,y,z, facecolors=colors,
                           linewidth=0, antialiased=False)
    # Set up colorbar
    sm = plt.cm.ScalarMappable(cmap=plt.cm.coolwarm, norm=norm)
    sm.set_array(pot)
    fig.colorbar(sm, shrink=0.5, aspect=5)
    
    
    ax.set_xlabel("x")
    ax.set_ylabel("y")
    ax.set_zlabel("z")
    plt.show()
    
        
    01.05.2017 / 18:54