I would like to plot a rectangle centered at the position (x, y, z), with length a along the (n1x, n1y, n1z) direction and width b along the (n2x, n2y, n2z) direction. I also want the surface of the rectangle to face a certain direction, the direction being specified by a unit vector perpendicular to the face of the rectangle (nx, ny, nz).
How do I create a function that takes in (x, y, z), a, b, and (nx, ny, nz) which plots a rectangle using matplotlib?
This answer is wrong! See comments.
The equation of a plane with normal vector `(a, b, c)` passing through `(0, 0, 0)` is `ax + by + cz = 0`. From this equation we can get the explicit formula `z = -(a/c)x - (b/c)y` to calculate the height of the plane at each planar point `(x, y)`. We can set up the X, Y, Z matrices using `numpy.meshgrid` and the previous formula.
from matplotlib import pyplot as plt
import numpy as np
def plane(cx, cy, cz, nx, ny, nz, length, width):
x = np.linspace(-length/2, length/2)
y = np.linspace(-width/2, width/2)
X, Y = np.meshgrid(x, y)
Z = -(nx/nz)*X - (ny/nz)*Y
X += cx
Y += cy
Z += cz
return X, Y, Z
# coordinates of the center point
cx, cy, cz = 3, 6, 9
# components of the normal vector
nx, ny, nz = 1, 1, 1
# dimensions of the rectangle
length, width = 4, 5
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = plane(cx, cy, cz, nx, ny, nz, length, width)
ax.plot_surface(X, Y, Z, alpha=1)
plt.show()
**UPDATE:** (including the center point in the calculations instead of deferring translations to the end)
The equation of a plane with normal vector (a, b, c) passing through (x0, y0, z0) is ax + by + cz = ax0 + by0 + c*z0. From this equation we can get the explicit formula z = (ax0 + by0 + cz0)/c - (a/c)x - (b/c)y to calculate the height of the plane at each planar point (x, y). We can set up the X, Y, Z matrices using numpy.meshgrid and the previous formula.
from matplotlib import pyplot as plt
import numpy as np
def plane(cx, cy, cz, nx, ny, nz, length, width):
x = np.linspace(cx - length/2, cx + length/2)
y = np.linspace(cy - width/2, cy + width/2)
X, Y = np.meshgrid(x, y)
Z = (nx*cx + ny*cy + nz*cz)/nz - (nx/nz)*X - (ny/nz)*Y
return X, Y, Z
# coordinates of the center point
cx, cy, cz = 3, 6, 9
# components of the normal vector
nx, ny, nz = 1, 1, 1
# dimensions of the rectangle
length, width = 4, 5
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
X, Y, Z = plane(cx, cy, cz, nx, ny, nz, length, width)
ax.plot_surface(X, Y, Z, alpha=1)
plt.show()
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 have 2 arrays with 3D points (name, X, Y, Z). First array contains reference points, through which I'm drawing spline. Second array contains measured points, from which I need to calculate normals to spline and get the coordinates of the normal on spline (I need to calculate the XY and height standard deviations of the measured points). This is the test data (in fact, I have several thousand points):
1st array - reference points/ generate spline:
r1,1.5602,6.0310,4.8289
r2,1.6453,5.8504,4.8428
r3,1.7172,5.6732,4.8428
r4,1.8018,5.5296,4.8474
r5,1.8700,5.3597,4.8414
2nd array - measured points:
m1, 1.8592, 5.4707, 4.8212
m2, 1.7642, 5.6362, 4.8441
m3, 1.6842, 5.7920, 4.8424
m4, 1.6048, 5.9707, 4.8465
The code I wrote, to read the data, calculate spline (using scipy) and display it via matplotlib:
import numpy as np
import matplotlib.pyplot as plt
from scipy import interpolate
# import measured points
filename = "measpts.csv"
meas_pts = np.genfromtxt(filename, delimiter=',')
# import reference points
filename = "refpts.csv"
ref = np.genfromtxt(filename, delimiter=',')
# divide data to X, Y, Z
x = ref[:, 2]
y = ref[:, 1]
z = ref[:, 3]
# spline interpolation
tck, u = interpolate.splprep([x, y, z], s=0)
u_new = np.linspace(u.min(), u.max(), 1000000)
x_new, y_new, z_new = interpolate.splev(u_new, tck, der=0)
xs = tck[1][0]
ys = tck[1][1]
zs = tck[1][2]
# PLOT 3D
fig = plt.figure()
ax3d = fig.add_subplot(111, projection='3d', proj_type='ortho')
ax3d.plot(x, y, z, 'ro') # ref points
ax3d.plot(xs, ys, zs, 'yo') # spline knots
ax3d.plot(x_new, y_new, z_new, 'b--') # spline
ax3d.plot(meas_pts[:, 2], meas_pts[:, 1], meas_pts[:, 3], 'g*') # measured points
# ax3d.view_init(90, -90) # 2D TOP view
# ax3d.view_init(0, -90) # 2D from SOUTH to NORTH view
# ax3d.view_init(0, 0) # 2D from EAST to WEST view
plt.show()
To sum up: I need array contains pairs: [[measured point X, Y, Z], [closest (normal) point on the spline X,Y,Z]]
Given a point P and a line in a 3d space, the distance from the point P and the points of the line is the diagonal of the box, so you wish to minimize this diagonal, the minimum distance will be normal to the line
You can use this property. So, for example
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
# generate sample line
x = np.linspace(-2, 2, 100)
y = np.cbrt( np.exp(2*x) -1 )
z = (y + 1) * (y - 2)
# a point
P = (-1, 3, 2)
# 3d plot
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d', proj_type='ortho')
ax.plot(x, y, z)
ax.plot(P[0], P[1], P[2], 'or')
plt.show()
def distance_3d(x, y, z, x0, y0, z0):
"""
3d distance from a point and a line
"""
dx = x - x0
dy = y - y0
dz = z - z0
d = np.sqrt(dx**2 + dy**2 + dz**2)
return d
def min_distance(x, y, z, P, precision=5):
"""
Compute minimum/a distance/s between
a point P[x0,y0,z0] and a curve (x,y,z)
rounded at `precision`.
ARGS:
x, y, z (array)
P (3dtuple)
precision (integer)
Returns min indexes and distances array.
"""
# compute distance
d = distance_3d(x, y, z, P[0], P[1], P[2])
d = np.round(d, precision)
# find the minima
glob_min_idxs = np.argwhere(d==np.min(d)).ravel()
return glob_min_idxs, d
that gives
min_idx, d = min_distance(x, y, z, P)
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d', proj_type='ortho')
ax.plot(x, y, z)
ax.plot(P[0], P[1], P[2], 'or')
ax.plot(x[min_idx], y[min_idx], z[min_idx], 'ok')
for idx in min_idx:
ax.plot(
[P[0], x[idx]],
[P[1], y[idx]],
[P[2], z[idx]],
'k--'
)
plt.show()
print("distance:", d[min_idx])
distance: [2.4721]
You can implement a similar function for your needs.
I'm plotting the vector magnetic potential of a finite wire. The resultant vector field should be circular and pointing upwards (z-direction). The field I get is pointing upwards, but it's not circular. It looks like a square. Can someone please point out the mistake I'm making.
from mpl_toolkits.mplot3d import axes3d
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure()
ax = fig.gca(projection='3d')
l = 10
x, y, z = np.meshgrid(np.linspace(-1, 1, 20),
np.linspace(-1, 1, 20),
np.linspace(-1, 1, 3))
u = 0
v = 0
w = np.log(2*l/((x**2 + y**2)**0.5))
ax.quiver(x, y, z, u, v, w, length=0.1, color = 'black')
plt.show()
You can use a mask, as was pointed out here.
for your specific example, you can create this mask by calculating which points in your xy plane lie inside the circle:
x, y, z = np.meshgrid(np.linspace(-1, 1, 20),
np.linspace(-1, 1, 20),
np.linspace(-1, 1, 3))
radius = 1
mask = x**2 + y**2 <= radius
Now apply the mask
x, y, z = x[mask], y[mask], z[mask]
u = 0
v = 0
w = np.log(2*l/((x**2 + y**2)**0.5))
ax.quiver(x, y, z, u, v, w, length=0.1, color = 'black')
plt.show()
I'm trying to plot this "F" vector function in R3. Together with the Sphere centered at origin of radius Pi/2.
I do not understand why my vectors and my sphere are not at the same position.
I do not understand mlab.axes "axes visibility", I just would like to display the usual x,y,z axes.
Sorry if it is obvious to some, but I went through the standard documentation, few examples and google searches for a few hours, and I'm still clueless.
My current Mayavi Result and Code:
import numpy as np
from mayavi import mlab
# Functions
def h(t):
return np.exp(-1/t) * (1/np.cos(t))
def F(x, y, z):
norm = np.linalg.norm([x, y, z])
h_norm = (h(norm)/norm)
return [x*h_norm, y*h_norm, z*h_norm]
# Vectors
x, y, z = np.meshgrid(np.arange(-2, 2, 0.5),
np.arange(-2, 2, 0.5),
np.arange(-2, 2, 0.5))
u, v, w = F(x, y, z)
src = mlab.pipeline.vector_field(u, v, w)
mlab.pipeline.vectors(src, mask_points=20, scale_factor=.5)
# Ball
dphi, dtheta = np.pi/250.0, np.pi/250.0
[phi, theta] = np.mgrid[0:2*np.pi:dphi,
0:np.pi:dtheta]
r = np.pi / 2
x2 = r*np.sin(theta)*np.cos(phi)
y2 = r*np.sin(theta)*np.sin(phi)
z2 = r*np.cos(theta)
s = mlab.mesh(x2, y2, z2)
#mlab.axes(x_axis_visibility=True, y_axis_visibility=True)
# View it all.
mlab.outline()
mlab.show()
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()