How can I visualize a function value in 3d? - python

Suppose I have some function, which maps 3 coordinates (x,y,z) to some real number.
How can I visualize the function values on a surface like a sphere?
Ideally, I would map the function's value to a color, and then color the sphere accordingly.
Here is my code to generate a sphere:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
%matplotlib inline
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.set_aspect("equal")
u = np.linspace(0, 2 * np.pi, 250)
v = np.linspace(0, np.pi, 250)
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
ax.plot_surface(x, y, z, color="w")
How can I edit my code to color it according to some function F(x,y,z)

Matplotlib allows to use the facecolor argument to plot_surface to set the color of each polygon in the surface. The argument needs to have the same shape as the input arrays and must consist of valid colors. A way to obtain those colors is a colormap.
Also see this question for details.
Below is a working example code.
import matplotlib.pyplot as plt
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
u = np.linspace(0, 2 * np.pi, 180)
v = np.linspace(0, np.pi, 90)
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones(np.size(u)), np.cos(v))
F = np.sin(x)*y + z
F = (F-F.min())/(F-F.min()).max()
#Set colours and render
fig = plt.figure(figsize=(8, 8))
fig.subplots_adjust(top=1, bottom=0, left=0, right=1)
ax = fig.add_subplot(111, projection='3d')
#use facecolors argument, provide array of same shape as z
# cm.<cmapname>() allows to get rgba color from array.
# array must be normalized between 0 and 1
ax.plot_surface(
x,y,z, rstride=1, cstride=1, facecolors=cm.jet(F), alpha=0.9, linewidth=0.9)
ax.set_xlim([-1,1])
ax.set_ylim([-1,1])
ax.set_zlim([-1,1])
ax.set_aspect("equal")
plt.savefig(__file__+".png")
plt.show()

The documentation for surface_plot lists the option facecolors. There is a example that alternates between two colors but you can pass any matplotlib color, including and array of RGB values.
You need to do the mapping yourself from x, y, z to F(x, y, z) to "color", then convert F to a RGB value.
facecolors = plt.cm.Red(F(x,y,z))
should do.
See: http://matplotlib.org/mpl_toolkits/mplot3d/tutorial.html#surface-plots

Related

Colormap a 3D curve in matplotlib

I have 4 arrays x, y, z and T of length n and I want to plot a 3D curve using matplotlib. The (x, y, z) are the points positions and T is the value of each point (which is plotted as color), like the temperature of each point. How can I do it?
Example code:
import numpy as np
from matplotlib import pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
n = 100
cmap = plt.get_cmap("bwr")
theta = np.linspace(-4 * np.pi, 4 * np.pi, n)
z = np.linspace(-2, 2, n)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
T = (2*np.random.rand(n) - 1) # All the values are in [-1, 1]
What I found over the internet:
It's possible to use cmap with scatter like shown in the docs and in this stackoverflow question
ax = plt.gca()
ax.scatter(x, y, z, cmap=cmap, c=T)
The problem is that scatter is a set of points, not a curve.
In this stackoverflow question the solution was divide in n-1 intervals and each interval we use a different color like
t = (T - np.min(T))/(np.max(T)-np.min(T)) # Normalize
for i in range(n-1):
plt.plot(x[i:i+2], y[i:i+2], z[i:i+2], c=cmap(t[i])
The problem is that each segment has only one color, but it should be an gradient. The last value is not even used.
Useful links:
Matplotlib - Colormaps
Matplotlib - Tutorial 3D
This is a case where you probably need to use Line3DCollection. This is the recipe:
create segments from your array of coordinates.
create a Line3DCollection object.
add that collection to the axis.
set the axis limits.
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Line3DCollection
from matplotlib.cm import ScalarMappable
from matplotlib.colors import Normalize
def get_segments(x, y, z):
"""Convert lists of coordinates to a list of segments to be used
with Matplotlib's Line3DCollection.
"""
points = np.ma.array((x, y, z)).T.reshape(-1, 1, 3)
return np.ma.concatenate([points[:-1], points[1:]], axis=1)
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
n = 100
cmap = plt.get_cmap("bwr")
theta = np.linspace(-4 * np.pi, 4 * np.pi, n)
z = np.linspace(-2, 2, n)
r = z**2 + 1
x = r * np.sin(theta)
y = r * np.cos(theta)
T = np.cos(theta)
segments = get_segments(x, y, z)
c = Line3DCollection(segments, cmap=cmap, array=T)
ax.add_collection(c)
fig.colorbar(c)
ax.set_xlim(x.min(), x.max())
ax.set_ylim(y.min(), y.max())
ax.set_zlim(z.min(), z.max())
plt.show()

How to make my surface plot appear using Axes3D?

I'm trying to create a surface plot using Python Matplotlib. I've read the documentation in an attempt to figure out where my code was wrong or if I've left anything out, but was having trouble.
The code that I've written is
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
def computeCost(X, y, theta):
m = len(y)
predictions = np.dot(X, theta)
squareErros = (predictions - y) ** 2
J = (1 / (2 * m)) * sum(squareErrors)
return J
data = np.loadtxt("./data1.txt", delimiter=',')
X = data[:, 0].reshape(-1, 1)
y = data[:, 1].reshape(-1, 1)
m = len(y)
X = np.concatenate((np.ones((m, 1)), X), axis=1)
theta0_vals = np.linspace(-10, 10, 100) # size (100,)
theta1_vals = np.linspace(-1, 4, 100) # size (100,)
J_vals = np.zeros((len(theta0_vals), len(theta1_vals)))
for i in range(len(x_values)):
for j in range(len(y_values)):
t = np.array([theta0_vals[i], theta1_vals[j]]).reshape(-1, 1)
J_vals[i][j] = computeCost(X, y, t) # size (100, 100)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(theta0_vals, theta1_vals, J_vals)
plt.show()
When I invoke plt.show() I get no output. The surface plot that I'm expecting to see is similar to this:
Would anybody be kind enough to let me know where my usage of the surface plot library went wrong? Thank you.
EDIT
I've tried to run the demo code provided here and it works fine. Here's the code for that:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
# Make data.
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
# Plot the surface.
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
# Customize the z axis.
ax.set_zlim(-1.01, 1.01)
ax.zaxis.set_major_locator(LinearLocator(10))
ax.zaxis.set_major_formatter(FormatStrFormatter('%.02f'))
# Add a color bar which maps values to colors.
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
I think I've figured out the issue by changing a couple of the last lines of code from
ax = fig.add_subplot(111, projection='3d')
surf = ax.plot_surface(theta0_vals, theta1_vals, J_vals)
to
ax = plt.axes(projection='3d')
surf = ax.plot_surface(theta0_vals, theta1_vals, J_vals, rstride=1, cstride=1, cmap='viridis', edgecolor='none')
Making this change gives me a surface plot such that:
The link that gave me reference to this was this.

How can a 3d surface colormap be mapped to a scalar function?

I have a scalar function that represents the electric potential in a spherical surface. I want to plot, for a given radius, the surface and link its points to a colormap based on the potential function.
How do I map that scalar function to the colormap in the surface? I suspect it must be in the arguments passed to the function ax.plot_surface. I tried using the argument: facecolors=potencial(x,y,z), but it gave me a 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)]
Which I do not understand, nor have an ideia how to link to 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 = 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)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Plot the surface
surf = ax.plot_surface(x,y,z, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
fig.colorbar(surf, shrink=0.5, aspect=5)
# This is mapping the color to the z-axis value
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
plt.show()
In principle there are two ways to colorize a surface plot in matplotlib.
Use the cmap argument to specify a colormap. In this case the color will be chosen according to the z array. In case that is not desired,
Use the facecolors argument. This expects an array of colors of the same shape as z.
So in this case we need to choose option 2 and build a color array.
To this end, one may choose a colormap. A colormap maps values between 0 and 1 to a color. Since the potential has values much above and below this range, one need to normalize them into the [0,1] range.
Matplotlib already provides some helper function to do this normalization and since the potential has a 1/x dependency, a logarithmic colorscale may be suitable.
At the end the facecolors may thus be given an array
colors = cmap(norm(potential(...)))
The missing bit is now the colorbar. In order for the colorbar to be linked to the colors from the surface plot, we need to manually set up a ScalarMappable with the colormap and the normalization instance, which we can then supply to 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 full 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 = r/z #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()

Spherical coordinates plot in matplotlib

R(teta, phi) = cos(phi^2), teta[0, 2*pi], phi[0,pi]
How to draw a graph of this function (R(teta, phi)) in spherical coordinates with the help of matplotlib?
The documentation I have not found Spherical coordinates.
The code below is very much like the 3D polar plot from the Matplotlib gallery. The only difference is that you use np.meshgrid to make 2D arrays for PHI and THETA instead of R and THETA (or what the 3D polar plot example calls P).
The moral of the story is that as long as X, Y, and Z can be expressed as (smooth) functions of two parameters, plot_surface can plot it.
import numpy as np
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d.axes3d as axes3d
theta, phi = np.linspace(0, 2 * np.pi, 40), np.linspace(0, np.pi, 40)
THETA, PHI = np.meshgrid(theta, phi)
R = np.cos(PHI**2)
X = R * np.sin(PHI) * np.cos(THETA)
Y = R * np.sin(PHI) * np.sin(THETA)
Z = R * np.cos(PHI)
fig = plt.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
plot = ax.plot_surface(
X, Y, Z, rstride=1, cstride=1, cmap=plt.get_cmap('jet'),
linewidth=0, antialiased=False, alpha=0.5)
plt.show()
yields
Typically R, the radius, should be positive, so you might want
R = np.abs(np.cos(PHI**2))
In that case,
import matplotlib.colors as mcolors
cmap = plt.get_cmap('jet')
norm = mcolors.Normalize(vmin=Z.min(), vmax=Z.max())
plot = ax.plot_surface(
X, Y, Z, rstride=1, cstride=1,
facecolors=cmap(norm(Z)),
linewidth=0, antialiased=False, alpha=0.5)
yields
Who knew R = np.abs(np.cos(PHI**2)) is a little girl in a dress? :)
If you want a lot of control you can use Poly3Dcollection directly and roll your own (allows you to have portions of the surface, that you don't plot.
Note that I changed the variables to the more common definition of phi in the azimuth and theta for the z-direction.
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
from __future__ import division
fig = plt.figure()
ax = fig.gca(projection='3d')
nphi,nth=48,12
phi = np.linspace(0,360, nphi)/180.0*np.pi
th = np.linspace(-90,90, nth)/180.0*np.pi
verts2 = []
for i in range(len(phi)-1):
for j in range(len(th)-1):
r= np.cos(phi[i])**2 # <----- your function is here
r1= np.cos(phi[i+1])**2
cp0= r*np.cos(phi[i])
cp1= r1*np.cos(phi[i+1])
sp0= r*np.sin(phi[i])
sp1= r1*np.sin(phi[i+1])
ct0= np.cos(th[j])
ct1= np.cos(th[j+1])
st0= np.sin(th[j])
st1= np.sin(th[j+1])
verts=[]
verts.append((cp0*ct0, sp0*ct0, st0))
verts.append((cp1*ct0, sp1*ct0, st0))
verts.append((cp1*ct1, sp1*ct1, st1))
verts.append((cp0*ct1, sp0*ct1, st1))
verts2.append(verts )
poly3= Poly3DCollection(verts2, facecolor='g')
poly3.set_alpha(0.2)
ax.add_collection3d(poly3)
ax.set_xlabel('X')
ax.set_xlim3d(-1, 1)
ax.set_ylabel('Y')
ax.set_ylim3d(-1, 1)
ax.set_zlabel('Z')
ax.set_zlim3d(-1, 1)
plt.show()

Fixing jagged edges of 3D plot, selecting an appropriate mask

So I have some 3D data that I am able to plot just fine except the edges look jagged.
The relevant code:
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.arange(-1, 1, 0.01)
y = np.arange(-1, 1, 0.01)
x, y = np.meshgrid(x, y)
rho = np.sqrt(x**2 + y**2)
# Attempts at masking shown here
# My Mask
row=0
while row<np.shape(x)[0]:
col=0
while col<np.shape(x)[1]:
if rho[row][col] > 1:
rho[row][col] = None
col=col+1
row=row+1
# Calculate & Plot
z = rho**2
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(x, y, z, rstride=8, cstride=8, cmap=cm.bone, alpha=0.15, linewidth=0.25)
plt.show()
Produces:
This is so close to what I want except the edges are jagged.
If I disable my mask in the code above & replace it with rho = np.ma.masked_where(rho > 1, rho) it gives:
It isn't jagged but not want I want in the corners.
Any suggestions on different masking or plotting methods to get rid of this jaggedness?
Did you consider using polar coordinates (like in this example) ?
Something like:
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# create supporting points in polar coordinates
r = np.linspace(0,1.25,50)
p = np.linspace(0,2*np.pi,50)
R,P = np.meshgrid(r,p)
# transform them to cartesian system
x, y = R * np.cos(P), R * np.sin(P)
rho = np.sqrt(x**2 + y**2)
# Calculate & Plot
z = rho**2
fig = plt.figure()
ax = fig.gca(projection='3d')
surf = ax.plot_surface(x, y, z, rstride=1, cstride=1, cmap=cm.bone, alpha=0.15, linewidth=0.25)
plt.show()

Categories