I'm trying to do something similar to this 3D example from the docs, but with a point cloud instead of a smooth surface. The example projects 2D contours onto each of the three coordinate planes. This shows that I'm able to do that onto the xy-plane.
When I try doing the same onto the other two planes, I get either a weird contour collapsed down to a thin strip,
or an exception way beyond my reach in the bowels of matplotlib.
Traceback (most recent call last):
File ".../matplotlib/backends/backend_qt5.py", line 519, in _draw_idle
self.draw()
File ".../matplotlib/backends/backend_agg.py", line 433, in draw
self.figure.draw(self.renderer)
File ".../matplotlib/artist.py", line 55, in draw_wrapper
return draw(artist, renderer, *args, **kwargs)
File ".../matplotlib/figure.py", line 1475, in draw
renderer, self, artists, self.suppressComposite)
File ".../matplotlib/image.py", line 141, in _draw_list_compositing_images
a.draw(renderer)
File ".../mpl_toolkits/mplot3d/axes3d.py", line 281, in draw
reverse=True)):
File ".../mpl_toolkits/mplot3d/axes3d.py", line 280, in <lambda>
key=lambda col: col.do_3d_projection(renderer),
File ".../mpl_toolkits/mplot3d/art3d.py", line 226, in do_3d_projection
self._segments3d]
File ".../mpl_toolkits/mplot3d/art3d.py", line 225, in <listcomp>
proj3d.proj_trans_points(points, renderer.M) for points in
File ".../mpl_toolkits/mplot3d/proj3d.py", line 188, in proj_trans_points
xs, ys, zs = zip(*points)
ValueError: not enough values to unpack (expected 3, got 0)
Here's an example of the problem. This version works. Uncomment one or both of the calls to ax.contour() in the plot() function to see the weird contours, or more likely, the exception.
import math
import sys
import matplotlib as mpl
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d
import numpy as np
import scipy.spatial
#np.random.seed(4)
numPts = 1000 # number of points in cloud
scatter = False # adds scatter plot to show point cloud
def main():
(pts, f) = mkData() # create the point cloud
tris = mkTris(pts) # triangulate it
plot(pts, tris, f) # plot it
def plot(pts, tris, f):
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
cmap = plt.get_cmap("coolwarm")
collec = ax.plot_trisurf(tris, pts[:, 2], cmap=cmap)
colors = np.mean(f[tris.triangles], axis=1)
collec.set_array(colors)
collec.autoscale()
(xr, yr, zr, xMin, yMin, zMin, zMax) = resample(ax, tris, f)
ax.set_zlim(zMin, zMax) # default always includes zero for some reason
#
# Uncomment one or both of these lines to see the problem.
#
ax.contour(xr, yr, zr, 10, zdir="z", cmap=cmap, offset=zMin)
#ax.contour(xr, yr, zr, 10, zdir="x", cmap=cmap, offset=xMin)
#ax.contour(xr, yr, zr, 10, zdir="y", cmap=cmap, offset=yMin)
if scatter:
ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], alpha=0.1)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
fig.colorbar(collec, shrink=0.5, aspect=5)
plt.show()
def mkData():
"""
Create a random point cloud near a random plane, and define a function on
the plane for colors and contours.
"""
offset = 1 # generate points near a unit square, xy-plane
pts = 2 * np.random.rand(numPts, 3) - 1
pts[:, 2] = offset * (2 * np.random.rand(numPts) - 1)
x = 2 * np.ravel(pts[:, 0])
y = 2 * np.ravel(pts[:, 1])
f = x * np.exp(-x**2 - y**2) # just some function for colors, contours
width = 100 # scale unit square to a larger rectangle
height = 20
pts[:, 0] *= width
pts[:, 1] *= height
(e1, e2, e3) =[2 * np.pi * np.random.rand() for _ in range(3)]
(c1, s1) = (math.cos(e1), math.sin(e1)) # rotate scaled rectangle
(c2, s2) = (math.cos(e2), math.sin(e2))
(c3, s3) = (math.cos(e3), math.sin(e3))
Ta2b = np.array(( # do not have scipy.spatial.transform
[ c1 *c2, s2, -s1 * c2],
[s1 * s3 - c1 * s2 * c3, c2 * c3, c1 *s3 + s1 * s2 * c3],
[s1 * c3 + c1 * s2 * s3, -c2 * s3, c1 *c3 - s1 * s2 * s3]))
pts = (Ta2b # pts.T).T
dist = 500 # translate away from the origin
Ra2bNb = dist * (2 * np.random.rand(3, 1) - 1)
pts += Ra2bNb.T
return (pts, f)
def mkTris(pts): # triangulate the point cloud
u = np.ravel(pts[:, 0])
v = np.ravel(pts[:, 1])
try:
return mpl.tri.Triangulation(u, v)
except (ValueError, RuntimeError) as ex:
sys.exit(f"Unable to compute triangulation: {ex}.")
def resample(ax, tris, f): # resample triangulation onto a regular grid
(xMin, xMax) = ax.get_xlim()
(yMin, yMax) = ax.get_ylim()
(zMin, zMax) = ax.get_zlim()
x = np.linspace(xMin, xMax, 30)
y = np.linspace(yMin, yMax, 30)
(xm, ym)=np.meshgrid(x, y)
interp = mpl.tri.triinterpolate.LinearTriInterpolator(tris, f)
zm = interp(xm, ym)
return (xm, ym, zm, xMin, yMin, zMin, zMax)
if __name__ == "__main__":
main()
This is with matplotlib 2.2.2 and 3.1.1. Thanks for any help you can provide to get contours on all three planes, like the demo.
Jim
A matplotlib developer pointed out that the resampling was wrong. After fixing that, here's the corrected plot.
For coordinate planes that see the data edge-on, like the yz-plane in this case, the contours can look a little wonky. That's expected, since the data can approach being multi-valued. The xz-plane contours look pretty ragged too. I suspect both problems would improve by triangulating and contouring each plane individually, instead of favoring the xy-plane as done here.
Here's the fixed test script. The only important changes were in plot() and resample().
import math
import sys
import matplotlib as mpl
import matplotlib.pyplot as plt
import mpl_toolkits.mplot3d
import numpy as np
import scipy.spatial
#np.random.seed(4)
numPts = 1000 # number of points in cloud
numGrid = 120 # number of points in meshgrid used in resample for contours
scatter = False # adds scatter plot to show point cloud
def main():
(pts, f) = mkData() # create the point cloud
tris = mkTris(pts) # triangulate it
plot(pts, tris, f) # plot it
def plot(pts, tris, f):
fig = plt.figure()
ax = fig.add_subplot(111, projection="3d")
cmap = plt.get_cmap("coolwarm")
collec = ax.plot_trisurf(tris, pts[:, 2], cmap=cmap)
colors = np.mean(f[tris.triangles], axis=1)
collec.set_array(colors)
collec.autoscale()
(xr, yr, zr, fr, xMin, xMax, yMin, yMax, zMin, zMax) = resample(ax, tris,
pts, f)
ax.set_xlim(xMin, xMax) # default always includes zero for some reason
ax.set_ylim(yMin, yMax)
ax.set_zlim(zMin, zMax)
ax.contour(xr, yr, fr, 10, zdir="z", cmap=cmap, offset=zMin)
ax.contour(fr, yr, zr, 10, zdir="x", cmap=cmap, offset=xMin)
ax.contour(xr, fr, zr, 10, zdir="y", cmap=cmap, offset=yMax)
if scatter:
ax.scatter(pts[:, 0], pts[:, 1], pts[:, 2], alpha=0.1)
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
fig.colorbar(collec, shrink=0.5, aspect=5)
plt.show()
def mkData():
"""
Create a random point cloud near a random plane, and define a function on
the plane for colors and contours.
"""
offset = 1 # generate points near a unit square, xy-plane
pts = 2 * np.random.rand(numPts, 3) - 1
pts[:, 2] = offset * (2 * np.random.rand(numPts) - 1)
x = 2 * np.ravel(pts[:, 0])
y = 2 * np.ravel(pts[:, 1])
f = x * np.exp(-x**2 - y**2) # just some function for colors, contours
width = 100 # scale unit square to a larger rectangle
height = 20
pts[:, 0] *= width
pts[:, 1] *= height
(e1, e2, e3) =[2 * np.pi * np.random.rand() for _ in range(3)]
(c1, s1) = (math.cos(e1), math.sin(e1)) # rotate scaled rectangle
(c2, s2) = (math.cos(e2), math.sin(e2))
(c3, s3) = (math.cos(e3), math.sin(e3))
Ta2b = np.array(( # do not have scipy.spatial.transform
[ c1 *c2, s2, -s1 * c2],
[s1 * s3 - c1 * s2 * c3, c2 * c3, c1 *s3 + s1 * s2 * c3],
[s1 * c3 + c1 * s2 * s3, -c2 * s3, c1 *c3 - s1 * s2 * s3]))
pts = (Ta2b # pts.T).T
dist = 500 # translate away from the origin
Ra2bNb = dist * (2 * np.random.rand(3, 1) - 1)
pts += Ra2bNb.T
return (pts, f)
def mkTris(pts):
"Triangulate the point cloud."
u = np.ravel(pts[:, 0])
v = np.ravel(pts[:, 1])
try:
return mpl.tri.Triangulation(u, v)
except (ValueError, RuntimeError) as ex:
sys.exit(f"Unable to compute triangulation: {ex}.")
def resample(ax, tris, pts, f):
"Resample the triangulation onto a regular grid for contours."
(xMin, xMax) = ax.get_xlim()
(yMin, yMax) = ax.get_ylim()
(zMin, zMax) = ax.get_zlim()
x = np.linspace(xMin, xMax, numGrid)
y = np.linspace(yMin, yMax, numGrid)
(xm, ym)=np.meshgrid(x, y)
fInterp = mpl.tri.CubicTriInterpolator(tris, f)
fm = fInterp(xm, ym)
zInterp = mpl.tri.CubicTriInterpolator(tris, pts[:,2])
zm = zInterp(xm, ym)
return (xm, ym, zm, fm, xMin, xMax, yMin, yMax, zMin, zMax)
if __name__ == "__main__":
main()
Related
I'm just starting with pyqtgraph and I want to make 3d surface plots in spherical coordinates. I've taken a look at the example GLSurfacePlot.py from the documentation but there are only plots in cartesian coordinates.
This is the plot I want to make (it's a half wave dipole radiation pattern):
How to plot r(theta, phi) with pyqtgraph?
EDIT: I could do it with matplotlib mplot3d, here is the script:
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
k = 2*np.pi
long = 0.5
theta = np.linspace(0, np.pi, 361)
phi = np.linspace(0, 2*np.pi, 361)
PHI, THETA = np.meshgrid(phi, theta)
R = np.absolute((np.cos(k*long/2*np.cos(THETA))-np.cos(k*long/2))/np.sin(THETA))
R = np.nan_to_num(R)
X = R * np.sin(THETA) * np.cos(PHI)
Y = R * np.sin(THETA) * np.sin(PHI)
Z = R * np.cos(THETA)
fig = plt.figure()
ax = fig.add_subplot(111, projection = '3d')
cmap = plt.get_cmap('jet')
plot = ax.plot_surface(X, Y, Z, rstride=10, cstride=10, facecolors=cmap(R),linewidth=0, antialiased=False, alpha=1)
plt.show()
The problem is that it's too slow when rotating and zooming it, and I definitely need that feature for my application, that's why I'm trying to do it with pyqtgraph.
Drawing this type of equations is not possible through GLSurfacePlotItem, in this case you must use GLMeshItem, but for this you must create an appropriate MeshData, so it takes as a reference sphere obtaining the following function:
def DipoleData(rows, cols, func, args=None):
verts = np.empty((rows+1, cols, 3), dtype=float)
phi = (np.arange(rows+1) * 2*np.pi *(1+2/rows)/ rows).reshape(rows+1, 1)
th = ((np.arange(cols) * np.pi / cols).reshape(1, cols))
if args is not None:
r = func(th, phi, *args)
else:
r = func(th, phi)
s = r* np.sin(th)
verts[...,2] = r * np.cos(th)
verts[...,0] = s * np.cos(phi)
verts[...,1] = s * np.sin(phi)
verts = verts.reshape((rows+1)*cols, 3)[cols-1:-(cols-1)] ## remove redundant vertexes from top and bottom
faces = np.empty((rows*cols*2, 3), dtype=np.uint)
rowtemplate1 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 0]])) % cols) + np.array([[0, 0, cols]])
rowtemplate2 = ((np.arange(cols).reshape(cols, 1) + np.array([[0, 1, 1]])) % cols) + np.array([[cols, 0, cols]])
for row in range(rows):
start = row * cols * 2
faces[start:start+cols] = rowtemplate1 + row * cols
faces[start+cols:start+(cols*2)] = rowtemplate2 + row * cols
faces = faces[cols:-cols] ## cut off zero-area triangles at top and bottom
## adjust for redundant vertexes that were removed from top and bottom
vmin = cols-1
faces[faces<vmin] = vmin
faces -= vmin
vmax = verts.shape[0]-1
faces[faces>vmax] = vmax
return gl.MeshData(vertexes=verts, faces=faces)
It is then used in the following example:
app = QtGui.QApplication([])
w = gl.GLViewWidget()
w.opts['distance'] = 3
w.show()
w.setWindowTitle('Half Wave Dipole Radiation Pattern')
def r_theta_phi(theta, phi, k, l):
return np.absolute((np.cos((k*l/2)*np.cos(theta)) -np.cos(k*l/2))/np.sin(theta))
p = 2*np.pi
q = 0.5
md = DipoleData(100, 100, r_theta_phi, args=(p, q))
colors = np.ones((md.faceCount(), 4), dtype=float)
colors[:,0] = np.linspace(0.1, 0.2, colors.shape[0])
colors[:,1] = np.linspace(0.2, 0.9, colors.shape[0])
colors[:,2] = np.linspace(0.0, 0.1, colors.shape[0])
md.setFaceColors(colors)
m = gl.GLMeshItem(meshdata=md, smooth=False)
w.addItem(m)
ax = gl.GLAxisItem()
ax.setSize(100,100,100)
w.addItem(ax)
g = gl.GLGridItem()
g.scale(0.2, 0.2, 0.2)
w.addItem(g)
## Start Qt event loop unless running in interactive mode.
if __name__ == '__main__':
import sys
if (sys.flags.interactive != 1) or not hasattr(QtCore, 'PYQT_VERSION'):
QtGui.QApplication.instance().exec_()
Obtaining what is shown in the following image:
I fit a plane to a bunch of points in 3d and initially gave it an arbitrary size using np.meshgrid, but now I'm trying to plot a cylinder centered on that plane and oriented the same way (such that the plane fit would cut the height of the cylinder in half), but with a specified radius and height. The only examples of cylinders plotted in matplotlib I can find are hollow and usually open at the top and bottom. I want the one I plot to be solid so I can clearly see what points it's enclosing.
Here's a minimum working example with a randomly generated plane. Since the plane I'm using is always given by a point and a normal vector, the cylinder should be based off of those things as well (plus a provided radius, and height to extend above and below the plane).
from __future__ import division #Enables new-style division
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
import seaborn as sns
import numpy as np
cen_x = 0
cen_y = 0
cen_z = 0
origin = np.array([cen_x,cen_y,cen_z])
normal = np.array([np.random.uniform(-1,1),np.random.uniform(-1,1),np.random.uniform(0,1)])
a = normal[0]
b = normal[1]
c = normal[2]
#equation for a plane is a*x+b*y+c*z+d=0 where [a,b,c] is the normal
#so calculate d from the normal
d = -origin.dot(normal)
# create x,y meshgrid
xx, yy = np.meshgrid(np.arange(cen_x-1,cen_x+1,0.01),np.arange(cen_y-1,cen_y+1,0.01))
# calculate corresponding z
zz = (-a * xx - b * yy - d) * 1./c
halo_x = [-0.3, -0.9, 0.8, 1.3, -0.1, 0.5]
halo_y = [0.8, 1.1, -0.5, -0.7, -1.2, 0.1]
halo_z = [1.0, -0.4, 0.3, -1.2, 0.9, 1.2]
fig = plt.figure(figsize=(9,9))
plt3d = fig.gca(projection='3d')
plt3d.plot_surface(xx, yy, zz, color='r', alpha=0.4)
plt3d.set_xlim3d(cen_x-3,cen_x+3)
plt3d.set_ylim3d(cen_y-3,cen_y+3)
plt3d.set_zlim3d(cen_z-3,cen_z+3)
plt3d.set_xlabel('X')
plt3d.set_ylabel('Y')
plt3d.set_zlabel('Z')
plt.show()
I have modified a solution to a question How to add colors to each individual face of a cylinder using matplotlib, removing the fancy shading and adding end caps. If you want to show the enclosed points, you can use alpha=0.5 or some such to make the cylinder semi-transparent.
The orientation of the cylinder is defined by a unit vector v with length mag, which could be the normal to your surface.
#!/usr/bin/env python2
# -*- coding: utf-8 -*-
"""
Created on Sun Oct 2 18:33:10 2016
Modified from https://stackoverflow.com/questions/38076682/how-to-add-colors-to-each-individual-face-of-a-cylinder-using-matplotlib
to add "end caps" and to undo fancy coloring.
#author: astrokeat
"""
import numpy as np
from matplotlib import pyplot as plt
from scipy.linalg import norm
#axis and radius
p0 = np.array([1, 3, 2]) #point at one end
p1 = np.array([8, 5, 9]) #point at other end
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, 2)
theta = np.linspace(0, 2 * np.pi, 100)
rsample = np.linspace(0, R, 2)
#use meshgrid to make 2d arrays
t, theta2 = np.meshgrid(t, theta)
rsample,theta = np.meshgrid(rsample, theta)
#generate coordinates for surface
# "Tube"
X, Y, Z = [p0[i] + v[i] * t + R * np.sin(theta2) * n1[i] + R * np.cos(theta2) * n2[i] for i in [0, 1, 2]]
# "Bottom"
X2, Y2, Z2 = [p0[i] + rsample[i] * np.sin(theta) * n1[i] + rsample[i] * np.cos(theta) * n2[i] for i in [0, 1, 2]]
# "Top"
X3, Y3, Z3 = [p0[i] + v[i]*mag + rsample[i] * np.sin(theta) * n1[i] + rsample[i] * np.cos(theta) * n2[i] for i in [0, 1, 2]]
ax=plt.subplot(111, projection='3d')
ax.plot_surface(X, Y, Z, color='blue')
ax.plot_surface(X2, Y2, Z2, color='blue')
ax.plot_surface(X3, Y3, Z3, color='blue')
plt.show()
The result:
I calculate ellipse-like polygons which are drawn over another image. The polygons together form one shape and must have a single transparency. Unfortunately, if I draw one polygon after another, the stack gets different transparencies. In the example below I have 3 polygons, creating 3 transparencies, resulting in different grey areas instead of one. Does anyone know how to obtain a single transparency?
Thank you
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.patches import Polygon, Rectangle
def get_ellipse_coords(a=0.0, b=0.0, x=0.0, y=0.0, angle=0.0, k=2):
pts = np.zeros((360*k+1, 2))
beta = -angle * np.pi/180.0
sin_beta = np.sin(beta)
cos_beta = np.cos(beta)
alpha = np.radians(np.r_[0.:360.:1j*(360*k+1)])
sin_alpha = np.sin(alpha)
cos_alpha = np.cos(alpha)
pts[:, 0] = x + (a * cos_alpha * cos_beta - b * sin_alpha * sin_beta)
pts[:, 1] = y + (a * cos_alpha * sin_beta + b * sin_alpha * cos_beta)
return pts
if __name__ == '__main__':
fig = plt.figure()
ax = fig.add_subplot(111, aspect='equal')
plt.xlim([-5,5])
plt.ylim([-5,5])
c='0.5'
alp=0.5
ax.add_patch(Rectangle((1.5, -4.0),0.5,8.0,
color='lightblue', zorder=1))
pts = get_ellipse_coords(a=4.0, b=1.0)
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp,zorder=2))
pts = get_ellipse_coords(a=4.0, b=1.0, x=1.0, angle=30)
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp, zorder=2))
pts = get_ellipse_coords(a=2.0, b=0.25, x=2.0, y=-2.0, angle=250)
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp, zorder=2))
plt.show()
np.concatenate, suggested in the first answer, does the trick I need:
[...]
ax.add_patch(Rectangle((1.5, -4.0),0.5,8.0,
color='lightblue', zorder=1))
pts1 = get_ellipse_coords(a=4.0, b=1.0)
pts2 = get_ellipse_coords(a=4.0, b=1.0, x=1.0, angle=30)
pts3 = get_ellipse_coords(a=2.0, b=0.25, x=2.0, y=-2.0, angle=250)
stoppoint = np.array([[np.nan,np.nan]])
pts = np.concatenate((pts1, stoppoint, pts2))
pts = np.concatenate((pts, stoppoint, pts3))
ax.add_patch(Polygon(pts, closed=True, lw=0.0,
color=c, alpha=alp, zorder=2))
Like this:
pts1 = get_ellipse_coords(a=4.0, b=1.0)
pts2 = get_ellipse_coords(a=4.0, b=1.0, x=1.0, angle=30)
stoppoint = np.array([[nan,nan]])
pts = np.concatenate((pts1, stoppoint, pts2))
ax.add_patch(Polygon(pts, closed=True, lw=0.5,
color=c, alpha=alp,zorder=2))
The stoppoint prevents a line connecting the ellipses.
The accepted solution did not work for me, I had to build my compound path with matplotlib.path.Path.make_compound_path_from_polys and then pass its vertices to my matplotlib.patches.Polygon class instance.
The gist of it as documented is as follows:
data = np.random.randn(1000)
n, bins = np.histogram(data, 50)
# get the corners of the rectangles for the histogram
left = np.array(bins[:-1])
right = np.array(bins[1:])
bottom = np.zeros(len(left))
top = bottom + n
# we need a (numrects x numsides x 2) numpy array for the path helper
# function to build a compound path
XY = np.array([[left, left, right, right], [bottom, top, top, bottom]]).T
# get the Path object
barpath = path.Path.make_compound_path_from_polys(XY)
# make a patch out of it
patch = patches.PathPatch(barpath)
ax.add_patch(patch)
I am trying to make a 'closed' cylinder in matplotlib but I am not sure how to go about doing this. So far I have a cylinder with the ends open, the code for this is as follows:
#make a cylinder without the ends closed
import numpy as np
from matplotlib import cm
from matplotlib import pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
from scipy.linalg import norm
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
import numpy as np
import math
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
origin = [0,0,0]
#radius = R
p0 = np.array(origin)
p1 = np.array([8, 8, 8])
origin = np.array(origin)
R = 4
#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, 600)
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]]
#make the color for the faces
col1 = plt.cm.autumn(np.ones(600)) # linear gradient along the t-axis
col1 = np.repeat(col1[np.newaxis,:, :], 100, axis=0) # expand over the theta-axis
ax.plot_surface(X, Y,Z, facecolors = col1, shade = True,edgecolors = "None", alpha = 0.4, linewidth = 0)
plt.show()
Running this code produces the following image
How would I close the ends of the cylinder with a solid circle (i.e. disk)?
A quick and easy way that's similar to your other code is to generate a surface using strips from r=0 to r=R. Right before plt.show() add the following lines:
R = np.array([0,R])
# cap at t=0
X, Y, Z = [p0[i] + np.outer(R, np.sin(theta)) * n1[i] + np.outer(R, np.cos(theta))*n2[i] for i in [0, 1, 2]]
ax.plot_surface(X, Y, Z, edgecolors = "r", alpha=.4, linewidth = .1)
# cap at t=mag
X, Y, Z = [p0[i] + v[i]*mag + np.outer(R, np.sin(theta)) * n1[i] + np.outer(R, np.cos(theta))*n2[i] for i in [0, 1, 2]]
ax.plot_surface(X, Y, Z, edgecolors = "r", alpha=.4, linewidth = .1)
Here the colors are more for illustrative purposes, mostly so you can see the strips. The result looks like:
I've currently constructed a plot using rectangle Patches to display a sequence of positions.
EDIT: Code used to generate this (built off of the RLPy library)-
def visualize_trajectory(self, trajectory=[[0,0,0,0], [0.1,0.1,0,0]]):
domain_fig = plt.figure()
for i, s in enumerate(trajectory):
x, y, speed, heading = s[:4]
car_xmin = x - self.REAR_WHEEL_RELATIVE_LOC
car_ymin = y - self.CAR_WIDTH / 2.
car_fig = matplotlib.patches.Rectangle(
[car_xmin,
car_ymin],
self.CAR_LENGTH,
self.CAR_WIDTH,
alpha=(0.8 * i) / len(trajectory) )
rotation = Affine2D().rotate_deg_around(
x, y, heading * 180 / np.pi) + plt.gca().transData
car_fig.set_transform(rotation)
plt.gca().add_patch(car_fig)
Is there any way to overlay each of these patches with images? Ideally, there would be a car image instead of a rectangle at each of the positions.
I've played around with AnnotationBbox and TransformedBbox, but both seem to be inflexible when dealing with rotations.
Take a look at
demo_affine_image
from the matplotlib gallery. It shows how
to rotate an image.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.transforms as mtransforms
import matplotlib.cbook as cbook
def get_image():
fn = cbook.get_sample_data("necked_tensile_specimen.png")
arr = plt.imread(fn)
# make background transparent
# you won't have to do this if your car image already has a transparent background
mask = (arr == (1,1,1,1)).all(axis=-1)
arr[mask] = 0
return arr
def imshow_affine(ax, z, *args, **kwargs):
im = ax.imshow(z, *args, **kwargs)
x1, x2, y1, y2 = im.get_extent()
im._image_skew_coordinate = (x2, y1)
return im
N = 7
x = np.linspace(0, 1, N)
y = x**1.1
heading = np.linspace(10, 90, N)
trajectory = list(zip(x, y, heading))
width, height = 0.3, 0.3
car = get_image()
fig, ax = plt.subplots()
for i, t in enumerate(trajectory, start=1):
xi, yi, deg = t
im = imshow_affine(ax, car, interpolation='none',
extent=[0, width, 0, height], clip_on=True,
alpha=0.8*i/len(trajectory))
center_x, center_y = width//2, height//2
im_trans = (mtransforms.Affine2D()
.rotate_deg_around(center_x, center_y, deg)
.translate(xi, yi)
+ ax.transData)
im.set_transform(im_trans)
ax.set_xlim(-0.5, 1.5)
ax.set_ylim(-0.5, 1.7)
plt.show()