I am trying to plot a ribbon wrapped around a sphere but without much success
The result should look like this, it is basically the part of the surface of a sphere but only the ribbon part.
The code that I wrote:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
# Funzione per convertire le coordinate sferiche in coordinate cartesiane
# Le coordinate sferiche ci permettono di rappresentare la sfera in modo più semplice
def sferiche_a_cartesiane(r, theta, phi): # r raggio che varia, theta è la colatitudine
x = r * np.sin(phi) * np.cos(theta)
y = r * np.sin(phi) * np.sin(theta)
z = r * np.cos(phi)
return x, y, z
# Genera le coordinate sferiche per la sfera usando un numpy array di x numeri equidistanti
theta_sfera = np.linspace(0, 2 * np.pi, 100)
phi_sfera = np.linspace(0, np.pi, 50)
theta_sfera, phi_sfera = np.meshgrid(theta_sfera, phi_sfera) # coordinate vere e proprie
# Imposta il raggio della sfera
raggio_sfera = 1.0
# Calcola le coordinate cartesiane dalla sferiche
x_sfera, y_sfera, z_sfera = sferiche_a_cartesiane(raggio_sfera, theta_sfera, phi_sfera)
# Crea la figura e gli assi 3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Disegna la sfera
ax.plot_surface(x_sfera, y_sfera, z_sfera, color='r', alpha=0.1, edgecolors=None, zorder=0)
t = np.linspace(-1, 1, 1000) # t appartiene a [-1, 1]
# Genera la curva parametrica con raggio leggermente ingrandito. Si derivano dal fatto che la sfera ha eq. x**2 + y**2 + z**2 = 1
x_curva = 1.01 * ((1 - t**2)**0.5) * np.cos(10 * np.pi * t)
y_curva = 1.01 * ((1 - t**2)**0.5) * np.sin(10 * np.pi * t)
z_curva = 1 * t
# Ruota la curva parametrica attorno alla sfera
angolo_rotazione = np.pi / 4
x_rotato = x_curva * np.cos(angolo_rotazione) - y_curva * np.sin(angolo_rotazione)
y_rotato = x_curva * np.sin(angolo_rotazione) + y_curva * np.cos(angolo_rotazione)
z_rotato = z_curva
# Disegna la curva parametrica ruotata
ax.plot(x_rotato, y_rotato, z_rotato, 'y', linewidth=2, zorder=1)
# Imposta gli assi
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
# Mostra il grafico
plt.show()
This is the current output:
Anyone can help me?
I looked at your code, but I was not figure out how to coerce it into looking like the image you provided. However, I was able to generate a very similar image using the code shown below. This was primarily based on this question which covers generating and drawing those triangles. This code is also pretty amenable to changing the ribbon to include more or fewer loops around, or making it thinner or thicker, so it should adaptable to your needs.
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
def poltocart(r, theta, phi):
# Clamp theta so that our slices don't stick out weirdly near the poles
theta = max(min(theta, np.pi), 0)
x = r * np.sin(theta) * np.cos(phi)
y = r * np.sin(theta) * np.sin(phi)
z = r * np.cos(theta)
return x, y, z
def _main():
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Based on the provided image it looks like goes around
# maybe 5 times?
turns = 5
# How tightly to quantize the ribbon (100 has visible artifacts)
phi_slices = 500
# How thick each slice is (large = fatter ribbon)
slice_thick = np.deg2rad(20)
# The list of triangles to draw
ribbon = []
# How many times around to go based on turns
max_phi = turns * 2 * np.pi
# Generate all values of phi so that we can iterate through in pairs
phi_values = list(np.linspace(-np.pi, max_phi, phi_slices, endpoint=True))
for i in range(1, phi_slices - 1):
# The previous slice (so that edges align)
phix_minus1 = phi_values[i - 1]
# The current slice
phix0 = phi_values[i]
# Top edge triangle
p1 = poltocart(1, np.pi * phix_minus1 / max_phi, phix_minus1)
p2 = poltocart(1, np.pi * phix0 / max_phi + slice_thick, phix_minus1)
p3 = poltocart(1, np.pi * phix0 / max_phi, phix0)
# Bottom edge triangle
p1r = poltocart(1, np.pi * phix0 / max_phi + slice_thick + np.pi / phi_slices, phix0)
p2r = poltocart(1, np.pi * phix0 / max_phi + slice_thick, phix_minus1) # same as top
p3r = poltocart(1, np.pi * phix0 / max_phi, phix0) # same as top
ribbon.append((p1, p2, p3))
ribbon.append((p1r, p2r, p3r))
p31 = Poly3DCollection(ribbon)
p31.set(color=(1, 0, 0, 0.25))
p31.set(ec=(1, 0, 0, 0.0))
ax.add_collection(p31)
ax.set_xlim([-1, 1])
ax.set_ylim([-1, 1])
ax.set_zlim([-1, 1])
ax.set_axis_off()
plt.show()
if __name__ == '__main__':
_main()
And here is the output it generates:

Let me know if you have any questions.