I'm looking for help to draw a 3D cone using matplotlib.
My goal is to draw a HSL cone, then base on the vertex coordinats i will select the color.
from matplotlib import cm
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
theta1 = np.linspace(0, 2*np.pi, 100)
r1 = np.linspace(-2, 0, 100)
t1, R1 = np.meshgrid(theta1, r1)
X1 = R1*np.cos(t1)
Y1 = R1*np.sin(t1)
Z1 = 5+R1*2.5
theta2 = np.linspace(0, 2*np.pi, 100)
r2 = np.linspace(0, 2, 100)
t2, R2 = np.meshgrid(theta2, r2)
X2 = R2*np.cos(t2)
Y2 = R2*np.sin(t2)
Z2 = -5+R2*2.5
ax.set_xlabel('x axis')
ax.set_ylabel('y axis')
ax.set_zlabel('z axis')
# ax.set_xlim(-2.5, 2.5)
# ax.set_ylim(-2.5, 2.5)
# ax.set_zlim(0, 5)
ax.set_aspect('equal')
ax.plot_surface(X1, Y1, Z1, alpha=0.8, color="blue")
ax.plot_surface(X2, Y2, Z2, alpha=0.8, color="blue")
# ax.plot_surface(X, Y, Z, alpha=0.8)
#fig. savefig ("Cone.png", dpi=100, transparent = False)
plt.show()
HSL CONE
My cone
So my question now is how to define color of each element.
i have found a solution, maybe it will be usefull for others.
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
import matplotlib.pyplot as plt
import numpy as np
import colorsys
from matplotlib.tri import Triangulation
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
n_angles = 80
n_radii = 20
# An array of radii
# Does not include radius r=0, this is to eliminate duplicate points
radii = np.linspace(0.0, 0.5, n_radii)
# An array of angles
angles = np.linspace(0, 2*np.pi, n_angles, endpoint=False)
# Repeat all angles for each radius
angles = np.repeat(angles[..., np.newaxis], n_radii, axis=1)
# Convert polar (radii, angles) coords to cartesian (x, y) coords
# (0, 0) is added here. There are no duplicate points in the (x, y) plane
x = np.append(0, (radii*np.cos(angles)).flatten())
y = np.append(0, (radii*np.sin(angles)).flatten())
# Pringle surface
z = 1+-np.sqrt(x**2+y**2)*2
print(x.shape, y.shape, angles.shape, radii.shape, z.shape)
# NOTE: This assumes that there is a nice projection of the surface into the x/y-plane!
tri = Triangulation(x, y)
triangle_vertices = np.array([np.array([[x[T[0]], y[T[0]], z[T[0]]],
[x[T[1]], y[T[1]], z[T[1]]],
[x[T[2]], y[T[2]], z[T[2]]]]) for T in tri.triangles])
x2 = np.append(0, (radii*np.cos(angles)).flatten())
y2 = np.append(0, (radii*np.sin(angles)).flatten())
# Pringle surface
z2 = -1+np.sqrt(x**2+y**2)*2
# NOTE: This assumes that there is a nice projection of the surface into the x/y-plane!
tri2 = Triangulation(x2, y2)
triangle_vertices2 = np.array([np.array([[x2[T[0]], y2[T[0]], z2[T[0]]],
[x2[T[1]], y2[T[1]], z2[T[1]]],
[x2[T[2]], y2[T[2]], z2[T[2]]]]) for T in tri2.triangles])
triangle_vertices = np.concatenate([triangle_vertices, triangle_vertices2])
midpoints = np.average(triangle_vertices, axis=1)
def find_color_for_point(pt):
c_x, c_y, c_z = pt
angle = np.arctan2(c_x, c_y)*180/np.pi
if (angle < 0):
angle = angle + 360
if c_z < 0:
l = 0.5 - abs(c_z)/2
#l=0
if c_z == 0:
l = 0.5
if c_z > 0:
l = (1 - (1-c_z)/2)
if c_z > 0.97:
l = (1 - (1-c_z)/2)
col = colorsys.hls_to_rgb(angle/360, l, 1)
return col
facecolors = [find_color_for_point(pt) for pt in midpoints] # smooth gradient
# facecolors = [np.random.random(3) for pt in midpoints] # random colors
coll = Poly3DCollection(
triangle_vertices, facecolors=facecolors, edgecolors=None)
fig = plt.figure()
ax = fig.gca(projection='3d')
ax.add_collection(coll)
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_zlim(-1, 1)
ax.elev = 50
plt.show()
Inspired from Jake Vanderplas with Python Data Science Handbook, when you are drawing some 3-D plot whose base is a circle, it is likely that you would try:
# Actually not sure about the math here though:
u, v = np.mgrid[0:2*np.pi:100j, 0:np.pi:20j]
x = np.cos(u)*np.sin(v)
y = np.sin(u)*np.sin(v)
and then think about the z-axis. Since viewing from the z-axis the cone is just a circle, so the relationships between z and x and y is clear, which is simply: z = np.sqrt(x ** 2 + y ** 2). Then you can draw the cone based on the codes below:
from mpl_toolkits import mplot3d
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import cm
def f(x, y):
return np.sqrt(x ** 2 + y ** 2)
fig = plt.figure()
ax = plt.axes(projection='3d')
# Can manipulate with 100j and 80j values to make your cone looks different
u, v = np.mgrid[0:2*np.pi:100j, 0:np.pi:80j]
x = np.cos(u)*np.sin(v)
y = np.sin(u)*np.sin(v)
z = f(x, y)
ax.plot_surface(x, y, z, cmap=cm.coolwarm)
# Some other effects you may want to try based on your needs:
# ax.plot_surface(x, y, -z, cmap=cm.coolwarm)
# ax.scatter3D(x, y, z, color="b")
# ax.plot_wireframe(x, y, z, color="b")
# ax.plot_wireframe(x, y, -z, color="r")
# Can set your view from different angles.
ax.view_init(azim=15, elev=15)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
plt.show()
ax.set_ylabel("y")
ax.set_zlabel("z")
plt.show()
And from my side, the cone looks like:
and hope it helps.
Related
I have this half cylinder plot, but it is not closed on the surface. How to make it close?
Is it possible to plot cylinder from vertices and sides? With 2 vertices become an arc?
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
import numpy as np
def data_for_cylinder_along_z(center_x,center_y,radius,height_z):
z = np.linspace(0, height_z, 50)
theta = np.linspace(0, 1*np.pi, 50)
theta_grid, z_grid=np.meshgrid(theta, z)
x_grid = radius*np.cos(theta_grid) + center_x
y_grid = radius*np.sin(theta_grid) + center_y
return x_grid,y_grid,z_grid
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
Xc,Yc,Zc = data_for_cylinder_along_z(0.2,0.2,0.05,0.1)
ax.plot_surface(Xc, Yc, Zc, alpha=0.5)
# Annotation
ax.set_title("Half Cylinder"))
plt.show()
If you can accept manually plotting each plane to enclose the shape, here's the code:
def data_for_horizontal_plane(center_x, center_y, radius, height_z):
# define the horizontal surface using polar coordinates
_radius = np.linspace(0, radius)
_theta = np.linspace(0, np.pi)
R, T = np.meshgrid(_radius, _theta)
# convert polar to cartesian coordinates and add translation
X = R*np.cos(T) + center_x
Y = R*np.sin(T) + center_y
Z = np.zeros(X.shape) + height_z
return X, Y, Z
def data_for_vertical_plane(center_x, center_y, radius, height_z):
# define the vertical rectangle on the X-Z plane
x = np.linspace(center_x - radius, center_x + radius)
z = np.linspace(0, height_z)
X, Z = np.meshgrid(x, z)
Y = np.zeros(X.shape) + center_y
return X, Y, Z
X, Y, Z = data_for_horizontal_plane(0.2, 0.2, 0.05, 0.0)
ax.plot_surface(X, Y, Z, alpha=0.5)
X, Y, Z = data_for_horizontal_plane(0.2, 0.2, 0.05, 0.1)
ax.plot_surface(X, Y, Z, alpha=0.5)
X, Y, Z = data_for_vertical_plane(0.2, 0.2, 0.05, 0.1)
ax.plot_surface(X, Y, Z, alpha=0.5)
I'm trying to place a plane on the surface of a sphere, although I think the math is correct, the resulting figure displays the plane at some point else.
Here is the code to compute and visualize it;
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
plt.style.use('dark_background')
# point and the unit vector
point = np.array([ 349370.39178182, 5570903.05977037, 3085958.36621096])
unit_vector = point/np.linalg.norm(point)
# the math
print(f'plane equation:\nAx + By + Cz + D = 0')
D = np.sum(unit_vector * point)
print(f'D=-(point * unit_vector) = {D:.2f}')
print(f'plane equation:\n{unit_vector[0]:1.4f}x + {unit_vector[1]:1.4f}y + {unit_vector[2]:1.4f}z + {D:.1f} = 0')
print(f'{-1*unit_vector[2]:1.4f}z = {unit_vector[0]:1.4f}x + {unit_vector[1]:1.4f}y + {D:.1f}')
print(f'z = ({unit_vector[0]:1.4f}x + {unit_vector[1]:1.4f}y + {D:.1f}) / {-1*unit_vector[2]:1.4f}')
x = np.linspace(-3e6,+3e6,100)
y = np.linspace(-3e6,+3e6,100)
X,Y = np.meshgrid(x,y)
Z = (0.05477656*X +0.87344241*Y + 6378100.0)/-0.48383662
# plotting stuff
def set_axis_equal_scale(ax, ticks_off=True):
xl = ax.set_xlim()
yl = ax.set_ylim()
zl = ax.set_zlim()
maxx=max(max(xl), max(yl), max(zl))
minn=min(min(xl), min(yl), min(zl))
ax.set_xlim(minn, maxx)
ax.set_ylim(minn, maxx)
ax.set_zlim(minn, maxx)
if ticks_off:
ax.set_xticks([])
ax.set_yticks([])
ax.set_zticks([])
fig = plt.figure(figsize=(5,5))
ax = fig.add_subplot(111, projection='3d')
# plot wireframe
radius = 6.3781e6 # in units m
uu, vv = np.mgrid[0:2*np.pi:200j, 0:np.pi:100j]
xE = radius * np.cos(uu)*np.sin(vv)
yE = radius * np.sin(uu)*np.sin(vv)
zE = radius * np.cos(vv)
ax.plot_wireframe(xE,yE,zE, color='w', alpha=0.1)
ax.scatter(point[0], point[1], point[2], s=500, color='r')
ax.plot([0,point[0]], [0,point[1]], [0,point[2]], color='w', lw=2)
surf = ax.plot_surface(X, Y, Z)
ax.scatter(0,0,0, marker='o', s=900, color='b')
ax.view_init(25, -190)
ax.axis('off')
set_axis_equal_scale(ax)
I expect the plane to be on where the red marker is and perpendicular to the white line connecting the center and the red marker.
[here is the image][1]
[1]: https://i.stack.imgur.com/LHPzW.png
I am trying to linearly interpolate values using scipy of sets of coordinates, thereafter plotting in matplotlib. How can I achieve the distinct boundaries between each region?
import matplotlib.pyplot as plt
import matplotlib.colors as mcolors
import numpy as np
np.random.seed(42)
from scipy.interpolate import griddata
x = np.random.random(20)
y = np.random.random(20)
z = np.random.random(20)
meshSize = 50
extensionFact = 10
xi, yi, = np.meshgrid(
np.linspace(np.min(x) - np.average(x) / extensionFact, np.max(x) + np.average(x) / extensionFact, meshSize),
np.linspace(np.min(y) - np.average(y) / extensionFact, np.max(y) + np.average(y) / extensionFact, meshSize))
zi = griddata((x, y), z, (xi, yi), method='nearest')
fig = plt.figure(figsize=(8, 6))
ax1 = fig.add_subplot(111)
bounds1 = np.linspace(np.nanmin(zi), np.nanmax(zi), 11)
colors1 = plt.get_cmap('jet')(np.linspace(0, 1, len(bounds1) + 1))
cmap1 = mcolors.ListedColormap(colors1[1:-1])
norm1 = mcolors.BoundaryNorm(boundaries=bounds1, ncolors=len(bounds1) - 1)
im1 = ax1.contourf(xi, yi, zi, levels=bounds1, cmap=cmap1, alpha=1)
fig.colorbar(im1, orientation='vertical', shrink=1, aspect=30, pad=0.03, ticks=bounds1)
plt.scatter(x, y, marker='x', c='k')
plt.show()
Presently the intersection of two regions are blurred.
I have a script for plotting a cylinder using the function data_for_cylinder_along_z. How to remove the grid on the cylinder? Is it possible to set two colours for it?
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from matplotlib.patches import FancyArrowPatch
# Coordinate for the cylinder
def data_for_cylinder_along_z(center_x,center_y,radius,height_z):
z = np.linspace(0, height_z, 200)
theta = np.linspace(0, 2*np.pi, 200)
theta_grid, z_grid=np.meshgrid(theta, z)
x_grid = radius*np.cos(theta_grid) + center_x
y_grid = radius*np.sin(theta_grid) + center_y
return x_grid,y_grid,z_grid
figsize=[5,5]
fig = plt.figure(figsize=figsize)
ax = fig.add_subplot(111, projection='3d')
ax.azim = -65
ax.elev = 11
ax.set_xlim(0.03, 0.049)
ax.set_ylim(0.02, 0.04)
ax.set_zlim(0.009, 0.02)
y_shift = 0.1
# Cylinder
Xc,Zc,Yc = data_for_cylinder_along_z(0,0,0.05,0.14) # center_x,center_y,radius,height_z
ax.plot_surface(Xc, Yc+y_shift, Zc, alpha=0.4, color = 'blue')
# Setting
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([0.2, 1, 0.13, 1]))
# Hide axes
ax._axis3don = False
plt.tight_layout(pad=0)
plt.show()
I would like to create 3d scatter plot with colormap range from min(u), u =64 to max(u), u=100. u is a 1d array
The code works as expected, u is increasing from the center (x,y,z)=(0,0,0) but the colors is incorrect, the color gradient should range according to u, from min(u) to max(u) instead of depending on x,y,z coordinate. Also colorbar is not correct (should be from 0 to 100)
fig = plt.figure(figsize = (8,6))
ax = fig.add_subplot(111, projection='3d')
ax.set_title('normal distribution')
#add the line/data in our plot
x = 18 * np.random.normal(size =500)
y = 18 * np.random.normal(size =500)
z = 18 * np.random.normal(size =500)
u = np.linspace(64, 100, 500)
norma = mpl.colors.Normalize(min(u), max(u))
color = np.linalg.norm([x,y,z], axis=0)
track = ax.scatter(x,y,z, s=35, c = color, alpha = 1, cmap='inferno', norm = norma)
plt.colorbar(track, label='color map', shrink=0.6)
fig = plt.figure(figsize = (8,6))
ax = fig.add_subplot(111, projection='3d')
ax.set_title('normal distribution')
the above code figure
When the color map Normalise to vmin=min(u) and vmax=max(u), the color gradient is lost and colormap gradient values are spread randomly along the x,y,z axis instead of being in ordered array.
Does someone know how to fix the color gradient along the axis, while the center of u is at (0,0,0) with the correct color bar (0-100) please?
fig = plt.figure(figsize = (8,6))
ax = fig.add_subplot(111, projection='3d')
ax.set_title('normal distribution')
#add the line/data in our plot
x = 18 * np.random.normal(size =500)
y = 18 * np.random.normal(size =500)
z = 18 * np.random.normal(size =500)
u = np.linspace(100, 64, 500)
norma = mpl.colors.Normalize(vmin=0, vmax = 100)
color = np.linalg.norm([u], axis=0)
track = ax.scatter(x,y,z, s=35, c = color, alpha = 1, cmap='inferno', norm = norma)
plt.colorbar(track, label='color map', shrink=0.6)
The result of the second example
x = 18 * np.random.normal(size =500)
y = 18 * np.random.normal(size =500)
z = 18 * np.random.normal(size =500)
# collect all data in array
data = np.array([x,y,z])
# center in a given dimension is the mean of all datapoints:
# reshape to allow easy subtraction
center = np.mean(data, axis=1).reshape(3,-1)
# for each datapoint, calculate distance to center and use as color value
color = np.linalg.norm(data - center, axis=0)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
track = ax.scatter(x,y,z, s=35, c = color, alpha = 1, cmap='inferno')
plt.colorbar(track, label='color map', shrink=0.6)
I found this question which seems to answer your question about the coordinates. The answers also show how to evenly distribute coordinates if you prefer to do that.
After getting the coordinates, you can then get the distance from the center as the color value (like warped did in his answer). I adjusted the distance to reflect your specifications. This is the resulting code:
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.colors import Normalize
from mpl_toolkits.mplot3d import Axes3D
number_of_particles = 500
sphere_radius = 18
# create the particles
radius = sphere_radius * np.random.uniform(0.0, 1.0, number_of_particles)
theta = np.random.uniform(0., 1., number_of_particles) * 2 * np.pi
phi = np.random.uniform(0., 1., number_of_particles) * 2 * np.pi
x = radius * np.sin(theta) * np.cos(phi)
y = radius * np.sin(theta) * np.sin(phi)
z = radius * np.cos(theta)
# collect all data in array
data = np.array([x, y, z])
# for each datapoint, calculate distance to center and use as color value
color = radius
color /= sphere_radius
color = color * 36 + 64
# initialize a figure with a plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# add the points and the colorbar
track = ax.scatter(x, y, z, s=35, c=color, alpha=1, cmap='inferno',
norm=Normalize(0, 100))
plt.colorbar(track, label='color map', shrink=0.6)
plt.show()
My result looks like this: