How can a colormap of a Surface be mapped to a scalar function?
Translation of the question I asked in the SO :
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 such a function to the surface? I suspect it has to do with the arguments passed to the function ax.plot_surface . I tried using the argument: facecolors=potencial(x,y,z)
, ma I got ValueError: Invalid RGBA argument.
looking at the source code from the third example , there are:
# 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)]
Which I didn't understand, nor have any idea how 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()
1 answers
Translated answer (original):
In principle there are two ways to color a surface plot in matplotlib.
- use the argument
cmap
to specify a colormap . In this case the color will be chosen according to the arrayz
. In this case this is not desired, - use the argument
facecolors
. It expects a array of colors in the same shape ( shape ) as z.
So in this case we need to choose Option 2 and build a 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 far above and below this range, it must be normalized to the range [0,1]. Matplotlib already provides a Help function to do this normalization and since the potential has a 1/x dependency, a logarithmic color scale may be appropriate.
At the end as facecolors can be given by an array
colors = cmap(norm(potential(...)))
The missing part now is the Color Bar (colorbar ). For it to be linked colours from plot surface, you need to manually mount a ScalarMappable in the colormap, instance normalization, which we will then provide the 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()