Colormap a 3D curve in matplotlib - python

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()

Related

Piecewise impclit functions in matplotlib (Python)

I would like to plot the function x^3 - 3xy + y^3 = 0 (Folium of Descartes) but with different colors in quadrant 2 and 4, and at the peak of the leaf I want the colors to change. I was thinking about converting to a piecewise parametric equation to make the plotting in different colors easier, but I don't know how to convert into a parametric form.
I was originally using a contour to plot the function only in one color, but I have no idea how to get the color changes.
Below is what I have for the single color, but I would like to have separate colors for each of the segments listed above.
import matplotlib.pyplot as plt
import numpy as np
xrange = np.arange(-5, 5, .025)
yrange = np.arange(-5, 5, .025)
X, Y = np.meshgrid(xrange, yrange)
plt.contour(X, Y, X**3 - 3*X*Y + Y**3, levels=[0], colors=['#000000'])
plt.show()
This website gives parametric Cartesian and polar equations of the curve.
The parametric equation isn't symmetric in x and y, which is annoying for drawing nice curves.
The polar equation gives a radius rho for a given angle theta. Some experimenting shows that the tails are formed by theta negative, or larger than pi/2. The cusp is at pi/4.
To visualize the polar curve, x=rho*cos(theta) and y=rho*sin(theta) can be used. Coloring can happen depending on theta.
import matplotlib.pyplot as plt
from matplotlib.colors import LinearSegmentedColormap, ListedColormap
import numpy as np
a = 1
tail = 0.5
theta = np.linspace(-tail, np.pi / 2 + tail, 500)
rho = 3 * a * np.sin(theta) * np.cos(theta) / (np.cos(theta) ** 3 + np.sin(theta) ** 3)
x = rho * np.cos(theta)
y = rho * np.sin(theta)
cmap = LinearSegmentedColormap.from_list('', ['red', 'gold', 'blue'])
# cmap = ListedColormap(['red', 'blue'])
# cmap = 'Spectral'
plt.scatter(x, y, c=theta, cmap=cmap, s=1)
plt.axis('equal')
plt.axis('off')
plt.show()
Copying the leave 4 times, and filling them in decreasing order creates the following plot.
import matplotlib.pyplot as plt
import numpy as np
for a in np.linspace(1, 0.001, 50):
theta = np.linspace(0, np.pi / 2, 500)
rho = 3 * a * np.sin(theta) * np.cos(theta) / (np.cos(theta) ** 3 + np.sin(theta) ** 3)
x = rho * np.cos(theta)
y = rho * np.sin(theta)
for i in [-1, 1]:
for j in [-1, 1]:
plt.fill(i * x, j * y, c=plt.cm.summer(1 - a))
plt.axis('equal')
plt.axis('off')
plt.show()

Plotting points from a function on a unit sphere using matplotlib

I am trying to plot the following function on a unit sphere, the points should be on the sphere and fill up the whole sphere however some of the points are falling off. Any suggestions why? I believe it is because the sphere is not spanning 1,1,1 3D grid but I am not sure how to edit my code to fix this.
from itertools import product, combinations
import matplotlib
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
def d(kx,ky):
M = 1
B = 1
vf = 1
kxx,kyy = np.meshgrid(kx,ky)
x = (vf*kxx)/(np.sqrt(((((vf**2)*(kxx**2)))+((vf**2)*(kyy**2))+(M-B*(kxx**2+(kyy**2)))**2)))
y = (vf*kxx)/(np.sqrt(((((vf**2)*(kxx**2)))+((vf**2)*(kyy**2))+(M-B*(kxx**2+(kyy**2)))**2)))
z = (M-B*(kxx**2+(kyy**2)))/(np.sqrt(((((vf**2)*(kxx**2)))+((vf**2)*(kyy**2))+(M-B*(kxx**2+(kyy**2)))**2)))
return x,y,z
kx = np.linspace(-2, 2, 10)
ky = np.linspace(-2, 2, 10)
xi, yi, zi = d(kx,ky)
phi = np.linspace(0, np.pi, 100)
theta = np.linspace(0, 2*np.pi, 100)
phi, theta = np.meshgrid(phi, theta)
x = np.sin(phi) * np.cos(theta)
y = np.sin(phi) * np.sin(theta)
z = np.cos(phi)
fig = plt.figure(figsize=plt.figaspect(1.))
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z, color="w", rstride=1, cstride=1)
ax.scatter(xi,yi,zi,color="k",s=20)
plt.show()
Thank you kindly,

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()

Spline interpolation over 3 variables for scattered data in Python?

With other words I got a set of data-points (x,y,z) associated to a value b and I would like to interpolate this data as accurate as possible.
Scipy.interpolate.griddata only can do a linear interpolation, what are the other options?
How about interpolating x, y, z separatly? I modified this tutorial example and added interpolation to it:
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
from scipy.interpolate import InterpolatedUnivariateSpline
mpl.rcParams['legend.fontsize'] = 10
# let's take only 20 points for original data:
n = 20
fig = plt.figure()
ax = fig.gca(projection='3d')
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)
ax.plot(x, y, z, label='rough curve')
# this variable represents distance along the curve:
t = np.arange(n)
# now let's refine it to 100 points:
t2 = np.linspace(t.min(), t.max(), 100)
# interpolate vector components separately:
x2 = InterpolatedUnivariateSpline(t, x)(t2)
y2 = InterpolatedUnivariateSpline(t, y)(t2)
z2 = InterpolatedUnivariateSpline(t, z)(t2)
ax.plot(x2, y2, z2, label='interpolated curve')
ax.legend()
plt.show()
The result looks like this:
UPDATE
Didn't understand the question at the first time, sorry.
You are probably looking for tricubic interpolation. Try this.

Plotting Ellipsoid with Matplotlib

Does anyone have sample code for plotting ellipsoids? There is one for sphere on matplotlib site, but nothing for ellipsoids. I am trying to plot
x**2 + 2*y**2 + 2*z**2 = c
where c is a constant (like 10) that defines an ellipsoid. I tried the meshgrid(x,y) route, reworked the equation so z is on one side, but the sqrt is a problem. The matplotlib sphere example works with angles, u,v, but I am not sure how to work that for ellipsoid.
Here is how you can do it via spherical coordinates:
# from mpl_toolkits.mplot3d import Axes3D # Not needed with Matplotlib 3.6.3
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=plt.figaspect(1)) # Square figure
ax = fig.add_subplot(111, projection='3d')
coefs = (1, 2, 2) # Coefficients in a0/c x**2 + a1/c y**2 + a2/c z**2 = 1
# Radii corresponding to the coefficients:
rx, ry, rz = 1/np.sqrt(coefs)
# Set of all spherical angles:
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
# Cartesian coordinates that correspond to the spherical angles:
# (this is the equation of an ellipsoid):
x = rx * np.outer(np.cos(u), np.sin(v))
y = ry * np.outer(np.sin(u), np.sin(v))
z = rz * np.outer(np.ones_like(u), np.cos(v))
# Plot:
ax.plot_surface(x, y, z, rstride=4, cstride=4, color='b')
# Adjustment of the axes, so that they all have the same span:
max_radius = max(rx, ry, rz)
for axis in 'xyz':
getattr(ax, 'set_{}lim'.format(axis))((-max_radius, max_radius))
plt.show()
The resulting plot is similar to
The program above actually produces a nicer looking "square" graphics.
This solution is strongly inspired from the example in Matplotlib's gallery.
Building on EOL's answer. Sometimes you have an ellipsoid in matrix format:
A and c Where A is the ellipsoid matrix and c is a vector representing the centre of the ellipsoid.
import numpy as np
import numpy.linalg as linalg
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# your ellispsoid and center in matrix form
A = np.array([[1,0,0],[0,2,0],[0,0,2]])
center = [0,0,0]
# find the rotation matrix and radii of the axes
U, s, rotation = linalg.svd(A)
radii = 1.0/np.sqrt(s)
# now carry on with EOL's answer
u = np.linspace(0.0, 2.0 * np.pi, 100)
v = np.linspace(0.0, np.pi, 100)
x = radii[0] * np.outer(np.cos(u), np.sin(v))
y = radii[1] * np.outer(np.sin(u), np.sin(v))
z = radii[2] * np.outer(np.ones_like(u), np.cos(v))
for i in range(len(x)):
for j in range(len(x)):
[x[i,j],y[i,j],z[i,j]] = np.dot([x[i,j],y[i,j],z[i,j]], rotation) + center
# plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_wireframe(x, y, z, rstride=4, cstride=4, color='b', alpha=0.2)
plt.show()
plt.close(fig)
del fig
So, not too much new here, but helpful if you've got an ellipsoid in matrix form which is rotated and perhaps not centered at 0,0,0 and want to plot it.
If you have an ellipsoid specified by an arbitrary covariance matrix cov and offset bias, you do not need to figure out the intuitive parameters of the ellipsoid to get the shape. Specifically, you don't need the individual axes or rotations. The whole point of the matrix is that it transforms a unit sphere (represented by the identity matrix) into your ellipse.
Starting with
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
Make a unit sphere
x = np.outer(np.cos(u), np.sin(v))
y = np.outer(np.sin(u), np.sin(v))
z = np.outer(np.ones_like(u), np.cos(v))
Now transform the sphere:
ellipsoid = (cov # np.stack((x, y, z), 0).reshape(3, -1) + bias).reshape(3, *x.shape)
You can plot the result pretty much as before:
ax.plot_surface(*ellipsoid, rstride=4, cstride=4, color='b', alpha=0.75)

Categories