Here is a Hopf torus created in Python with PyVista:
import numpy as np
import pyvista as pv
A = 0.44
n = 3
def Gamma(t):
alpha = np.pi/2 - (np.pi/2-A)*np.cos(n*t)
beta = t + A*np.sin(2*n*t)
return np.array([
np.sin(alpha) * np.cos(beta),
np.sin(alpha) * np.sin(beta),
np.cos(alpha)
])
def HopfInverse(p, phi):
return np.array([
(1+p[2])*np.cos(phi),
p[0]*np.sin(phi) - p[1]*np.cos(phi),
p[0]*np.cos(phi) + p[1]*np.sin(phi),
(1+p[2])*np.sin(phi)
]) / np.sqrt(2*(1+p[2]))
def Stereo(q):
return 2*q[0:3] / (1-q[3])
def F(t, phi):
return Stereo(HopfInverse(Gamma(t), phi))
angle = np.linspace(0, 2*np.pi, 300)
angle2 = np.linspace(0, np.pi, 150)
theta, phi = np.meshgrid(angle, angle2)
x, y, z = F(theta, phi)
# Display the mesh
grid = pv.StructuredGrid(x, y, z)
grid.plot(smooth_shading=True)
I would like to add a palette of colors to this surface. The torus is centered at the origin (0,0,0). I would like to have a color in function of the distance to the origin.
With Matplotlib, I do:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.colors as mcolors
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
A = 0.44
n = 3
......
colorfunction = (X**2+Y**2+Z**2)
norm = mcolors.Normalize(colorfunction.min(),colorfunction.max())
# Display the mesh
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.plot_surface(z, x, y, rstride = 1, cstride = 1, facecolors=cm.jet(norm(colorfunction)))
plt.show()
EDIT
I have a solution, but I don't control the colors:
grid = pv.StructuredGrid(x, y, z)
grid['Data'] = grid.points
grid.plot(smooth_shading=True, scalars="Data")
As a side note, at least to me it's clearer to compute the magnitude of the points yourself and set those as scalars (rather than relying on the magnitude of vector data as scalars for colour mapping, even though this is supported and valid).
What you're missing is just a choice of colourmap. The default, just like with matplotlib, is viridis. Instead it seems you want jet (although I'd recommend against this; perceptually uniform colourmaps are preferable in most cases for data visualization):
import numpy as np
import pyvista as pv
A = 0.44
n = 3
def Gamma(t):
alpha = np.pi/2 - (np.pi/2-A)*np.cos(n*t)
beta = t + A*np.sin(2*n*t)
return np.array([
np.sin(alpha) * np.cos(beta),
np.sin(alpha) * np.sin(beta),
np.cos(alpha)
])
def HopfInverse(p, phi):
return np.array([
(1+p[2])*np.cos(phi),
p[0]*np.sin(phi) - p[1]*np.cos(phi),
p[0]*np.cos(phi) + p[1]*np.sin(phi),
(1+p[2])*np.sin(phi)
]) / np.sqrt(2*(1+p[2]))
def Stereo(q):
return 2*q[0:3] / (1-q[3])
def F(t, phi):
return Stereo(HopfInverse(Gamma(t), phi))
angle = np.linspace(0, 2 * np.pi, 300)
theta, phi = np.meshgrid(angle, angle)
x, y, z = F(theta, phi)
grid = pv.StructuredGrid(x, y, z)
# convert to PolyData and clean to remove the seam
cleaned_poly = grid.extract_geometry().clean(tolerance=1e-6)
# add distance from origin as scalars
cleaned_poly.point_data['distance'] = np.linalg.norm(cleaned_poly.points, axis=1)
# this also makes these the default scalars
cleaned_poly.plot(smooth_shading=True, cmap='jet') # but don't use jet if possible
Related
I want to plot a quantity which is given on a parametric surface in 3d space (for example the temperature distribution on a sphere). I can plot a parametric 3D plot of the sphere (as a function of the two parameters phi and theta) but I don't know how to make the colors of the polygons making up the sphere depend on the parameters theta and phi (normally, the color of a polygon is simply determined by the z-Position of the polygon).
Here's a basic example which plots a torus with colormap:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
angle = np.linspace(0, 2 * np.pi, 32)
theta, phi = np.meshgrid(angle, angle)
r, R = .25, 1.
X = (R + r * np.cos(phi)) * np.cos(theta)
Y = (R + r * np.cos(phi)) * np.sin(theta)
Z = r * np.sin(phi)
# Display the mesh
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.set_xlim3d(-1, 1)
ax.set_ylim3d(-1, 1)
ax.set_zlim3d(-1, 1)
ax.plot_surface(X, Y, Z, rstride = 1, cstride = 1,cmap="hot")
plt.show()
However, the colors of the files are given by the z position of the tile, I want the color to be given by a function f(x,y).
Does anyone know how I can achieve this dependency in Matplotlib?
Thanks very much!
Ok, if anyone else is looking for a solution to this problem here's a possible solution:
The colors of the individual faces making up the surface plot can be set using the keyword argument facecolors. The following code will use the function X**2+Y**2 for coloring the faces of the parametric surface:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.colors as mcolors
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
# Generate torus mesh
angle = np.linspace(0, 2 * np.pi, 32)
theta, phi = np.meshgrid(angle, angle)
r, R = .25, 1.
X = (R + r * np.cos(phi)) * np.cos(theta)
Y = (R + r * np.cos(phi)) * np.sin(theta)
Z = r * np.sin(phi)
colorfunction=(X**2+Y**2)
norm=mcolors.Normalize(colorfunction.min(),colorfunction.max())
# Display the mesh
fig = plt.figure(figsize=(7, 7))
ax = fig.add_subplot(projection='3d')
ax.set_xlim3d(-1, 1)
ax.set_ylim3d(-1, 1)
ax.set_zlim3d(-1, 1)
ax.plot_surface(X, Y, Z, rstride = 1, cstride = 1, facecolors=cm.jet(norm(colorfunction)))
plt.show()
The idea is to plot the following vector field:
I have two main issue with it:
1) I do not know how to make sure that the arrows are not too long (I know I have to use length, but how?).
2) I am told to use Numpyto draw the vector field but again, how?
This is what I have tried:
# The components of the vector field
F_x = y*e**x
F_y = x**2 + e**x
F_z = z**2*e**z
# The grid
xf = np.linspace(-0.15, 2.25, 8)
yf = np.linspace(-0.15, 2.25, 8)
zf = np.linspace(-0.75, 2.50, 8)
X_grid, Y_grid, Z_grid = np.meshgrid(xf, yf, zf)
# The arrows; how to deal with them?
dx = 1
#dy = ...
#dz = ...
# Standardize the arrows; In this way all arrows have the same length.
length = np.sqrt(dx**2 + dy**2 + dz**2)
dx_N = dx/length
dy_N = dy/length
dz_N = dz/length
#how to involve numpy in the process??
# Drawing the figure
fig, ax = plt.subplots(1, 1)
ax.quiver(X_grid, Y_grid, Z_grid, dx_N, dy_N, dz_N, dy, dz, cmap=plt.get_cmap('gnuplot2'))
plt.show()
Thanks
EDIT
Based on the provided link I tried:
from sympy import *
x,y,z = sp.symbols('x y z', real = True)
import matplotlib.pyplot as plt
x, y, z = np.meshgrid(np.arange(0, 2 * np.pi, .2), np.arange(0, 2 * np.pi, .2), np.arange(0, 2 * np.pi, .2))
F_x = y * exp(x)
F_y = x**2 + exp(x)
F_z = z**2 * exp(z)
# Normalize the arrows:
F_x = F_x / np.sqrt(F_x**2 + F_y**2 + F_z**2)
F_y = F_y / np.sqrt(F_x**2 + F_y**2 + F_z**2)
F_z = F_z / np.sqrt(F_x**2 + F_y**2 + F_z**2)
plt.figure()
plt.title('Vector field')
Q = plt.quiver(x, y, z, F_x, F_y, F_z, units='width')
qk = plt.quiverkey(Q, 0.9, 0.9, 2, r'$2 \frac{m}{s}$', labelpos='E',
coordinates='figure')#I don't understand this line
The TypeError: Shape should contain integers only comes up.
The problem is that I don't understand this part of the code:
qk = plt.quiverkey(Q, 0.9, 0.9, 2, r'$2 \frac{m}{s}$', labelpos='E',
coordinates='figure')
I am still stuck on how to plot this vector field
Assume that you want a 3D quiver, you can check out the matplotlib tutorial on quiver3D. And to control the arrow size, check out the Axes3d.quiver library doc, especially the parameters.
A quick snippet:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
x, y, z = np.meshgrid(np.arange(0, 2*np.pi, .5), np.arange(0, 2*np.pi, .5), np.arange(0, 2*np.pi, .5))
F_x = y * np.exp(x)
F_y = x**2 + np.exp(x)
F_z = z**2 * np.exp(z)
fig = plt.figure()
ax = fig.gca(projection='3d')
Q = ax.quiver(x, y, z, F_x, F_y, F_z, length=0.3, normalize=True)
But 3d quiver plot can be very crowded! : )
The quiver() method is a great tool to render vector fields. Since Matplotlib is a two-dimensional plotting library, we need to import the mplot3d toolkit to generate a three-dimensional plot.
Here's a good example:
Dependencies:
Axes3D for 3D rendering
Pyplot to get a MATLAB-like plotting framework
Numpy for numeric-array manipulation
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
x, y, z = np.meshgrid(np.arange(-0.8, 1, 0.25),
np.arange(-0.8, 1, 0.25),
np.arange(-0.8, 1, 0.8))
u = np.sin(np.pi * x) * np.cos(np.pi * y) * np.cos(np.pi * z)
v = -np.cos(np.pi * x) * np.sin(np.pi * y) * np.cos(np.pi * z)
w = (np.sqrt(2.0 / 3.0) * np.cos(np.pi * x) * np.cos(np.pi * y) * np.sin(np.pi * z))
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.quiver(x, y, z, u, v, w,
length=0.15,
color='Purple'
)
ax.view_init(elev=10, azim=30)
ax.dist=8
plt.show()
I am trying to make a 'closed' cylinder in matplotlib but I am not sure how to go about doing this. So far I have a cylinder with the ends open, the code for this is as follows:
#make a cylinder without the ends closed
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import norm
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
import math
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
origin = [0,0,0]
#radius = R
p0 = np.array(origin)
p1 = np.array([8, 8, 8])
origin = np.array(origin)
R = 4
#vector in direction of axis
v = p1 - p0
#find magnitude of vector
mag = norm(v)
#unit vector in direction of axis
v = v / mag
#make some vector not in the same direction as v
not_v = np.array([1, 0, 0])
if (v == not_v).all():
not_v = np.array([0, 1, 0])
#make vector perpendicular to v
n1 = np.cross(v, not_v)
#normalize n1
n1 /= norm(n1)
#make unit vector perpendicular to v and n1
n2 = np.cross(v, n1)
#surface ranges over t from 0 to length of axis and 0 to 2*pi
t = np.linspace(0, mag, 600)
theta = np.linspace(0, 2 * np.pi, 100)
#use meshgrid to make 2d arrays
t, theta = np.meshgrid(t, theta)
#generate coordinates for surface
X, Y, Z = [p0[i] + v[i] * t + R * np.sin(theta) * n1[i] + R * np.cos(theta) * n2[i] for i in [0, 1, 2]]
#make the color for the faces
col1 = plt.cm.autumn(np.ones(600)) # linear gradient along the t-axis
col1 = np.repeat(col1[np.newaxis,:, :], 100, axis=0) # expand over the theta-axis
ax.plot_surface(X, Y,Z, facecolors = col1, shade = True,edgecolors = "None", alpha = 0.4, linewidth = 0)
plt.show()
Running this code produces the following image
How would I close the ends of the cylinder with a solid circle (i.e. disk)?
A quick and easy way that's similar to your other code is to generate a surface using strips from r=0 to r=R. Right before plt.show() add the following lines:
R = np.array([0,R])
# cap at t=0
X, Y, Z = [p0[i] + np.outer(R, np.sin(theta)) * n1[i] + np.outer(R, np.cos(theta))*n2[i] for i in [0, 1, 2]]
ax.plot_surface(X, Y, Z, edgecolors = "r", alpha=.4, linewidth = .1)
# cap at t=mag
X, Y, Z = [p0[i] + v[i]*mag + np.outer(R, np.sin(theta)) * n1[i] + np.outer(R, np.cos(theta))*n2[i] for i in [0, 1, 2]]
ax.plot_surface(X, Y, Z, edgecolors = "r", alpha=.4, linewidth = .1)
Here the colors are more for illustrative purposes, mostly so you can see the strips. The result looks like:
I have the following code which produces a cylinder-like object using matplotlib:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
nphi,nz=7,20
r=1 # radius of cylinder
phi = np.linspace(0,360, nphi)/180.0*np.pi
z= np.linspace(0,1.0,nz)
print z
cols=[]
verts2 = []
for i in range(len(phi)-1):
cp0= r*np.cos(phi[i])
cp1= r*np.cos(phi[i+1])
sp0= r*np.sin(phi[i])
sp1= r*np.sin(phi[i+1])
for j in range(len(z)-1):
z0=z[j]
z1=z[j+1]
verts=[]
verts.append((cp0, sp0, z0))
verts.append((cp1, sp1, z0))
verts.append((cp1, sp1, z1))
verts.append((cp0, sp0, z1))
verts2.append(verts)
value=np.random.rand()
#print value
col=plt.cm.rainbow(0.9)
#print col
cols.append(col)
poly3= Poly3DCollection(verts2, facecolor=cols,edgecolor = "none" )
poly3.set_alpha(0.8)
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(0, 1)
plt.show()
This code produces the following image:
However as you can see the are sharp corners in the figure. Is there anyway to make these edges rounder so that the figure looks like a proper cylinder with a circular cross-section as opposed to a hexagonal cross-section?
The third argument to
np.linspace
controls how many values you want it to generate. Thus, nphi controls the
number of values in phi, and nz controls the number of values in z:
phi = np.linspace(0,360, nphi)/180.0*np.pi
z = np.linspace(0,1.0,nz)
So if you increase nphi, then you'll get more points along the circle:
cp0 = r*np.cos(phi[i])
sp0 = r*np.sin(phi[i])
For example, try changing nphi, nz = 7,20 to nphi, nz = 70, 2.
Note that there is no need for nz to be greater than 2 since the sides of the
cylinder are flat in the z direction.
By the way, the double for-loop can be replaced by:
PHI, Z = np.meshgrid(phi, z)
CP = r * np.cos(PHI)
SP = r * np.sin(PHI)
XYZ = np.dstack([CP, SP, Z])
verts = np.stack(
[XYZ[:-1, :-1], XYZ[:-1, 1:], XYZ[1:, 1:], XYZ[1:, :-1]], axis=-2).reshape(-1, 4, 3)
So, for example,
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
nphi, nz = 70, 2
r = 1 # radius of cylinder
phi = np.linspace(0, 360, nphi) / 180.0 * np.pi
z = np.linspace(0, 1.0, nz)
PHI, Z = np.meshgrid(phi, z)
CP = r * np.cos(PHI)
SP = r * np.sin(PHI)
XYZ = np.dstack([CP, SP, Z])
verts = np.stack(
[XYZ[:-1, :-1], XYZ[:-1, 1:], XYZ[1:, 1:], XYZ[1:, :-1]], axis=-2).reshape(-1, 4, 3)
cmap = plt.cm.rainbow
cols = cmap(np.random.random())
poly3 = Poly3DCollection(verts, facecolor=cols, edgecolor="none")
poly3.set_alpha(0.8)
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(0, 1)
plt.show()
yields
I have the following problem:
a have N points on a sphere specified by a array x, with x.shape=(N,3). This array contains their cartesian coordinates. Furthermore, at each point, I have a specified temperature. This quantity is saved in an array T, with T.shape=(N,).
Is there any straight forward way to map this temperature distribution into the plane using different colors?
If it simplifies the task, the position can also be given in polar coordinates (\theta,\phi).
To plot your data, you can use Basemap. The only problem is, that both contour and contourf routines needs gridded data. Here is example with naive (and slow) IDW-like interpolation on sphere. Any comments are welcome.
import numpy as np
from mpl_toolkits.basemap import Basemap
import matplotlib.pyplot as plt
def cart2sph(x, y, z):
dxy = np.sqrt(x**2 + y**2)
r = np.sqrt(dxy**2 + z**2)
theta = np.arctan2(y, x)
phi = np.arctan2(z, dxy)
theta, phi = np.rad2deg([theta, phi])
return theta % 360, phi, r
def sph2cart(theta, phi, r=1):
theta, phi = np.deg2rad([theta, phi])
z = r * np.sin(phi)
rcosphi = r * np.cos(phi)
x = rcosphi * np.cos(theta)
y = rcosphi * np.sin(theta)
return x, y, z
# random data
pts = 1 - 2 * np.random.rand(500, 3)
l = np.sqrt(np.sum(pts**2, axis=1))
pts = pts / l[:, np.newaxis]
T = 150 * np.random.rand(500)
# naive IDW-like interpolation on regular grid
theta, phi, r = cart2sph(*pts.T)
nrows, ncols = (90,180)
lon, lat = np.meshgrid(np.linspace(0,360,ncols), np.linspace(-90,90,nrows))
xg,yg,zg = sph2cart(lon,lat)
Ti = np.zeros_like(lon)
for r in range(nrows):
for c in range(ncols):
v = np.array([xg[r,c], yg[r,c], zg[r,c]])
angs = np.arccos(np.dot(pts, v))
idx = np.where(angs == 0)[0]
if idx.any():
Ti[r,c] = T[idx[0]]
else:
idw = 1 / angs**2 / sum(1 / angs**2)
Ti[r,c] = np.sum(T * idw)
# set up map projection
map = Basemap(projection='ortho', lat_0=45, lon_0=15)
# draw lat/lon grid lines every 30 degrees.
map.drawmeridians(np.arange(0, 360, 30))
map.drawparallels(np.arange(-90, 90, 30))
# compute native map projection coordinates of lat/lon grid.
x, y = map(lon, lat)
# contour data over the map.
cs = map.contourf(x, y, Ti, 15)
plt.title('Contours of T')
plt.show()
One way to do this is to set facecolors by mapping your heat data through the colormap.
Here's an example:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
from matplotlib import cm
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
u = np.linspace(0, 2 * np.pi, 80)
v = np.linspace(0, np.pi, 80)
# create the sphere surface
x=10 * np.outer(np.cos(u), np.sin(v))
y=10 * np.outer(np.sin(u), np.sin(v))
z=10 * np.outer(np.ones(np.size(u)), np.cos(v))
# simulate heat pattern (striped)
myheatmap = np.abs(np.sin(y))
ax.plot_surface(x, y, z, cstride=1, rstride=1, facecolors=cm.hot(myheatmap))
plt.show()
Here, my "heatmap" is just stripes along the y-axis, which I made using the function np.abs(np.sin(y)), but anything that goes form 0 to 1 will work (and, of course, it needs to match the shapes on x, etc.