Cut-off half a torus in a surface plot - python

I am trying to plot only half of a torus using matplotlib.
This is my approach so far:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
n = 100
# theta: poloidal angle; phi: toroidal angle
theta = np.linspace(0, 2.*np.pi, n)
phi = np.linspace(0, 2.*np.pi, n)
theta, phi = np.meshgrid(theta, phi)
# R0: major radius; a: minor radius
R0, a = 2., 1.
# torus parametrization
x = (R0 + a*np.cos(theta)) * np.cos(phi)
y = (R0 + a*np.cos(theta)) * np.sin(phi)
z = a * np.sin(theta)
# "cut-off" half of the torus
x[x>0] = np.nan
fig = plt.figure()
ax1 = fig.add_subplot(111, projection='3d')
ax1.set_zlim(-3,3)
ax1.plot_surface(x, y, z, rstride=5, cstride=5,)
# elev: elevation angle in z-plane
# azim: azimuth angle in x,y plane
ax1.view_init(elev=15, azim=0)
plt.show()
Doing so, gives me indeed half a torus, but one of the cut surfaces is not clear, as can be seen in the figure (it is the left cut surface which is problematic here).
Any ideas how to make a clean cut surface?

Cutting off surfaces with nans will usually do that. This is due to the fact that patches of the surface are drawn using linear interpolation over a subset of the input data, and having nans on the boundary will lead to nan results for values for some edge patches.
In your specific case you can just limit your toroidal angle to half a torus:
theta = np.linspace(0, 2*np.pi, n)
phi = np.linspace(0, np.pi, n)
You'll have to set manual x/y limits as well for a pretty aspect ratio:
ax1.axis([-3, 3]*2)
There's a very general but hands-on alternative by passing an explicit array of facecolors to plot_surface, and manipulating the transparency of the values inside. This will be much uglier than the default unless you work hard, since shading will be missing with flat colours. Here's a very basic (and ugly) example for what I mean:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
n = 100
# theta: poloidal angle; phi: toroidal angle
theta = np.linspace(0, 2*np.pi, n)
phi = np.linspace(0, 2*np.pi, n)
theta, phi = np.meshgrid(theta, phi)
# R0: major radius; a: minor radius
R0, a = 2., 1.
# torus parametrization
x = (R0 + a*np.cos(theta)) * np.cos(phi)
y = (R0 + a*np.cos(theta)) * np.sin(phi)
z = a * np.sin(theta)
# "cut-off" half of the torus using transparent colors
c = np.full(x.shape + (4,), [0, 0, 0.85, 1]) # shape (nx, ny, 4)
c[x>0, -1] = 0 # set these to transparent
fig = plt.figure()
ax1 = fig.add_subplot(111, projection='3d')
ax1.set_zlim(-3,3)
ax1.plot_surface(x, y, z, facecolors=c, rstride=5, cstride=5,)
# elev: elevation angle in z-plane
# azim: azimuth angle in x,y plane
ax1.view_init(elev=15, azim=0)
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 to draw a line behind a surface plot using pyplot

I want to draw a line inside a torus which I have drawn with a surface plot. The line should not be visible inside the torus - like the inner side of the torus, which can only be seen at the "ends" of the torus (I cut-off one half of the torus). The line I have drawn is however visible everywhere (as you can see in the plot).
I have used the following code:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# theta: poloidal angle | phi: toroidal angle
# note: only plot half a torus, thus phi=0...pi
theta = np.linspace(0, 2.*np.pi, 200)
phi = np.linspace(0, 1.*np.pi, 200)
theta, phi = np.meshgrid(theta, phi)
# major and minor radius
R0, a = 3., 1.
# torus parametrization
x_torus = (R0 + a*np.cos(theta)) * np.cos(phi)
y_torus = (R0 + a*np.cos(theta)) * np.sin(phi)
z_torus = a * np.sin(theta)
# parametrization for a circular line at theta=0
x_circle = (R0-a/2. + a*np.cos(.0)) * np.cos(phi)
y_circle = (R0-a/2. + a*np.cos(.0)) * np.sin(phi)
z_circle = a * np.sin(.0)
fig = plt.figure()
ax1 = fig.add_subplot(111, projection='3d')
# plot half of a circular line
ax1.plot3D( x_circle, y_circle, z_circle )
# plot half of torus
ax1.plot_surface( x_torus, y_torus, z_torus )
ax1.view_init(elev=15, azim=270)
ax1.set_xlim( -3, 3)
ax1.set_ylim( -3, 3)
ax1.set_zlim( -3, 3)
ax1.set_axis_off()
plt.show()
I thought simply plotting the line first should solve my problem, but it doesn't. Any suggestion or help how to change the behaviour of the line is greatly appreciated.
numpy.__version__ : 1.12.1
matplotlib.__version__: 2.0.0
Option one - use Mayavi
The easier way to do this would be with the Mayavi library. This is pretty similar to matplotlib, the only meaningful differences for this script are that the x, y, and z arrays passed to plot3d to plot the line should be 1d and the view is set a bit differently (depending on whether it is set before or after plotting, and the alt/az are measured from different reference).
import numpy as np
import mayavi.mlab as mlab
from mayavi.api import OffScreenEngine
mlab.options.offscreen = True
# theta: poloidal angle | phi: toroidal angle
# note: only plot half a torus, thus phi=0...pi
theta = np.linspace(0, 2.*np.pi, 200)
phi = np.linspace(0, 1.*np.pi, 200)
# major and minor radius
R0, a = 3., 1.
x_circle = R0 * np.cos(phi)
y_circle = R0 * np.sin(phi)
z_circle = np.zeros_like(x_circle)
# Delay meshgrid until after circle construction
theta, phi = np.meshgrid(theta, phi)
x_torus = (R0 + a*np.cos(theta)) * np.cos(phi)
y_torus = (R0 + a*np.cos(theta)) * np.sin(phi)
z_torus = a * np.sin(theta)
mlab.figure(bgcolor=(1.0, 1.0, 1.0), size=(1000,1000))
mlab.view(azimuth=90, elevation=105)
mlab.plot3d(x_circle, y_circle, z_circle)
mlab.mesh(x_torus, y_torus, z_torus, color=(0.0, 0.5, 1.0))
mlab.savefig("./example.png")
# mlab.show() has issues with rendering for some setups
Option two - use matplotlib (with some added unpleasantness)
If you can't use mayavi it is possible to accomplish this with matplotlib, it's just... unpleasant. The approach is based on the idea of creating transparent 'bridges' between surfaces and then plotting them together as one surface. This is not trivial for more complex combinations, but here is an example for the toroid with a line which is fairly straightforward
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
theta = np.linspace(0, 2.*np.pi, 200)
phi = np.linspace(0, 1.*np.pi, 200)
theta, phi = np.meshgrid(theta, phi)
# major and minor radius
R0, a = 3., 1.
lw = 0.05 # Width of line
# Cue the unpleasantness - the circle must also be drawn as a toroid
x_circle = (R0 + lw*np.cos(theta)) * np.cos(phi)
y_circle = (R0 + lw*np.cos(theta)) * np.sin(phi)
z_circle = lw * np.sin(theta)
c_circle = np.full_like(x_circle, (1.0, 1.0, 1.0, 1.0), dtype=(float,4))
# Delay meshgrid until after circle construction
x_torus = (R0 + a*np.cos(theta)) * np.cos(phi)
y_torus = (R0 + a*np.cos(theta)) * np.sin(phi)
z_torus = a * np.sin(theta)
c_torus = np.full_like(x_torus, (0.0, 0.5, 1.0, 1.0), dtype=(float, 4))
# Create the bridge, filled with transparency
x_bridge = np.vstack([x_circle[-1,:],x_torus[0,:]])
y_bridge = np.vstack([y_circle[-1,:],y_torus[0,:]])
z_bridge = np.vstack([z_circle[-1,:],z_torus[0,:]])
c_bridge = np.full_like(z_bridge, (0.0, 0.0, 0.0, 0.0), dtype=(float, 4))
# Join the circle and torus with the transparent bridge
X = np.vstack([x_circle, x_bridge, x_torus])
Y = np.vstack([y_circle, y_bridge, y_torus])
Z = np.vstack([z_circle, z_bridge, z_torus])
C = np.vstack([c_circle, c_bridge, c_torus])
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.plot_surface(X, Y, Z, rstride=1, cstride=1, facecolors=C, linewidth=0)
ax.view_init(elev=15, azim=270)
ax.set_xlim( -3, 3)
ax.set_ylim( -3, 3)
ax.set_zlim( -3, 3)
ax.set_axis_off()
plt.show()
Note in both cases I changed the circle to match the major radius of the toroid for demonstration simplicity, it can easily be altered as needed.

How to draw a cylinder using matplotlib along length of point (x1,y1) and (x2,y2) with specified radius?

I would like to draw a cylinder using matplotlib along length of point (x1,y1) and (x2,y2) with specified radius r. Please let me know how to do this.
Just for fun, I'm going to generalize this to any axis (x0, y0, z0) to (x1, y1, z1). Set z0 and z1 to 0 if you want an axis in the xy plane.
You can find a vector equation for the axis pretty easily by finding the unit vector in the same direction as the axis, then adding it to p0 and scaling it along the length of the axis. Normally you can find the coordinates of a circle with x = x0 + cos(theta) * R and y = y0 + sin(theta) * R, but the circles aren't in the xy plane, so we're going to need to make our own axes with unit vectors perpendicular to the axis of the cylinder and each other and then get the xyz coordinates from that. I used this site to help me figure this out: Link. Here's the code:
import numpy as np
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import norm
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
origin = np.array([0, 0, 0])
#axis and radius
p0 = np.array([1, 3, 2])
p1 = np.array([8, 5, 9])
R = 5
#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, 100)
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]]
ax.plot_surface(X, Y, Z)
#plot axis
ax.plot(*zip(p0, p1), color = 'red')
ax.set_xlim(0, 10)
ax.set_ylim(0, 10)
ax.set_zlim(0, 10)
plt.show()

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