Reputation: 2981
I have numpy arrays of x and y values along two concentric circles that have been rotated from an arbitrary 3D orientation, and projected into the xy plane of the plot, xs1, ys1, xs2, ys2
.
I want to plot them with matplotlib, filling the space between them. I'm having trouble with fill_between, since the curves I would fill between are multi-valued. I was thinking a brute force method would be to split into single-valued segments, interpolate the curves along the same set of x values and use fill_between, but wondered if there might be more elegant solutions. Code:
import numpy as np
import matplotlib.pyplot as plt
phi = 0.5
theta = 1.2
Rphi = np.array([[np.cos(phi), -np.sin(phi), 0.], [np.sin(phi), np.cos(phi), 0.], [0.,0.,1.]])
Rtheta = np.array([[1.,0.,0.], [0., np.cos(theta), -np.sin(theta)], [0., np.sin(theta), np.cos(theta)]])
R = np.dot(Rphi, Rtheta)
Router = 0.06
Rinner = 0.04
rs = np.array([[np.cos(phi), np.sin(phi), 0.] for phi in np.linspace(0., 2*np.pi, 100)])
rs = np.dot(rs, R.T)
plt.plot(Rinner*rs[:,0], Rinner*rs[:,1], Router*rs[:,0], Router*rs[:,1])
plt.plot([0], [0], 'oy', ms=100)
I want to fill in the space between the two rings (yes, I am trying to plot Saturn!)
Bonus: Is there a simple way to get the depth right? i.e., Saturn appears in front of the back side as below, but behind the rings on the front side?
Upvotes: 2
Views: 4025
Reputation: 63
This just adds to Andras Deaks very good answer. To get rid of the slight gap between the two half-rings just change the color of the edge from 'none' to 'blue'.
# draw foreground
semiring1 = np.concatenate((Router*rs1[:,:2],Rinner*rs1[::-1,:2]),axis=0)
plt.fill(semiring1[:,0], semiring1[:,1],'blue',edgecolor='blue')
Upvotes: 1
Reputation: 35176
You're using the wrong function: plt.fill() is what you probably need:
plt.fill(Router*rs[:,0], Router*rs[:,1],'blue')
plt.fill(Rinner*rs[:,0], Rinner*rs[:,1],'white')
plt.plot([0], [0], 'oy', ms=100)
Result:
Making the middle circle hide the background bits should be pretty manual: even 3d plots with matplotlib have serious difficulty with z order (every object is either fully before, or fully behind another object). You probably have to split your ellipses into two parts, and draw them with the order background -> planet -> foreground.
Also, as you can see, the planet turned out to be of a different size on my system, due to some adjustments to matplotlib.rc
. I suggest properly plotting the planet as well, using data-based dimensions:
Rsaturn = 0.018
rs0 = np.array([[np.cos(phi), np.sin(phi), 0.] for phi in np.linspace(0., 2*np.pi, 100)])
plt.fill(Rsaturn*rs0[:,0], Rsaturn*rs0[:,1],'yellow')
plt.axis('equal')
New result:
The bonus question made me do this properly. In order to be able to hide Saturn with the front part of the ring, you have to fill the ring properly, rather than plotting two full ellipses over each other. Here's my solution:
import numpy as np
import matplotlib.pyplot as plt
phi = 0.5
theta = 1.2
Rphi = np.array([[np.cos(phi), -np.sin(phi), 0.], [np.sin(phi), np.cos(phi), 0.], [0.,0.,1.]])
Rtheta = np.array([[1.,0.,0.], [0., np.cos(theta), -np.sin(theta)], [0., np.sin(theta), np.cos(theta)]])
R = np.dot(Rphi, Rtheta)
Router = 0.06
Rinner = 0.04
Rsaturn = 0.018
#rs0 = np.array([[np.cos(phi), np.sin(phi), 0.] for phi in np.linspace(0., 2*np.pi, 100)])
phivec1 = np.linspace(0., np.pi, 50)
phivec2 = np.linspace(np.pi,2*np.pi, 50)
rs1 = np.array([np.cos(phivec1), np.sin(phivec1), np.zeros_like(phivec1)]).T # first half arc
rs2 = np.array([np.cos(phivec2), np.sin(phivec2), np.zeros_like(phivec2)]).T # second half arc
rs0 = np.concatenate((rs1,rs2),axis=0) # full arc for Saturn
rs1 = np.dot(rs1, R.T) # rotate
rs2 = np.dot(rs2, R.T) # rotate
# draw foreground
semiring1 = np.concatenate((Router*rs1[:,:2],Rinner*rs1[::-1,:2]),axis=0)
plt.fill(semiring1[:,0], semiring1[:,1],'blue',edgecolor='none')
# draw Saturn
plt.fill(Rsaturn*rs0[:,0], Rsaturn*rs0[:,1],'yellow')
# draw foreground
semiring2 = np.concatenate((Router*rs2[:,:2],Rinner*rs2[::-1,:2]),axis=0)
plt.fill(semiring2[:,0], semiring2[:,1],'blue',edgecolor='none')
plt.axis('equal')
One change is that I use two semiellipses by separating the angle range into two parts, and I'm concatenating the two concentric semiellipses to fill
the ring parts one at a time. We have to turn off the edge colours in fill
, otherwise the jumps in data would manifest as black lines messing up the figure. If you need the edge lines, draw them manually using plot
.
Here's the result of the above:
Upvotes: 5