Piecewise impclit functions in matplotlib (Python) - 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()

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

Cut-off half a torus in a surface plot

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

Draw points connected by spiral on a sphere with Matplotlib

I would like to draw a sphere with points on its surface using Matplotlib. These points shall be connected by a spiral that spirals from one side of the sphere to the other.
To clarify this a little bit the plot should more or less look like this:
Has anyone a tip about how to do this?
Need to know parameters of spiral, formula or set of points.
However I post a code to plot a line with markers on a sphere for your start:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.set_aspect('equal')
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
x = 1 * np.outer(np.cos(u), np.sin(v))
y = 1 * np.outer(np.sin(u), np.sin(v))
z = 1 * np.outer(np.ones(np.size(u)), np.cos(v))
elev = 10.
rot = 80. / 180. * np.pi
ax.plot_surface(x, y, z, rstride=1, cstride=1, color='y', linewidth=0, alpha=0.5)
# plot lines in spherical coordinates system
a = np.array([-np.sin(elev / 180 * np.pi), 0, np.cos(elev / 180 * np.pi)])
b = np.array([0, 1, 0])
b = b * np.cos(rot) + np.cross(a, b) * np.sin(rot) + a * np.dot(a, b) * (1 - np.cos(rot))
ax.plot(np.sin(u),np.cos(u),0,color='r', linestyle = '-', marker='o', linewidth=2.5)
ax.view_init(elev = elev, azim = 0)
plt.show()

Using python, I am looking to plot a 3D spherical cap with a given radius r

I am looking to make a small program that will produce a 3D plot of a spherical cap given parameters h and a.
Any help would be greatly appreciated!
I've started with a matplotlib example to plot a sphere...
I want to enter a value for a and h, get the corresponding radius of the sphere, then plot a spherical cap of height h and base radius a. Ideally the z axis and x y axis on the 3d plot will correspond to my initial a and h entered (as long as it makes geometric sense i presume?)
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
def capRatio(r, a, h):
'''cap to sphere ratio'''
surface_cap = np.pi * (a**2 + h**2)
surface_sphere = 4.0 * np.pi * r**2
return surface_cap/surface_sphere
def findRadius(a, h):
"find radius if you have cap base radius a and height"
r = (a**2 + h**2) / (2*h)
return r
#choose a and h
a = 4
h = 3
r = findRadius(a,h)
p = capRatio(r, a, h) # Ratio of sphere to be plotted, could also be a function of a.
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, p*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))
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.plot_surface(x, y, z, rstride=4, cstride=4, alpha=0.3 , cmap= cm.winter)
plt.show()
The next part comes in limiting the range of coordinates you will accept and this could be done in a few ways. Here is one way that appears to work:
p = 0.8 # Ratio of sphere to be plotted, could also be a function of a.
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, p*np.pi, p*100)

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