Add cylinder to plot - python

I would like to add a transparent cylinder to my 3D scatter plot. How can I do it?
This is the code I am using to make the plot:
fig = plt.figure(2, figsize=(8, 6))
ax = fig.add_subplot(111, projection='3d')
ax.scatter(X, Y, Z, c=Z,cmap=plt.cm.Paired)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
plt.xticks()

Today I have to do the same thing in my project about adding a transparent cylinder in the result. This is the code I get finally. So I share it with you guys just for learning
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, 2*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)
plt.show()
And you will get this beautiful figure.

One possible method is to use the plot_surface. Adapting the solution given in this blog post then have
import matplotlib.pyplot as plt
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Scatter graph
N = 100
X = np.random.uniform(-1, 1, N)
Y = np.random.uniform(-1, 1, N)
Z = np.random.uniform(-2, 2, N)
ax.scatter(X, Y, Z)
# Cylinder
x=np.linspace(-1, 1, 100)
z=np.linspace(-2, 2, 100)
Xc, Zc=np.meshgrid(x, z)
Yc = np.sqrt(1-Xc**2)
# Draw parameters
rstride = 20
cstride = 10
ax.plot_surface(Xc, Yc, Zc, alpha=0.2, rstride=rstride, cstride=cstride)
ax.plot_surface(Xc, -Yc, Zc, alpha=0.2, rstride=rstride, cstride=cstride)
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
plt.show()
I've added some minimal configuration of the surface, better can be achieved by consulting the docs.

I improved on #Greg's answer and made a solid 3D cylinder with a top and bottom surface and rewrote the equation so that you can translate in the x, y,and z
from mpl_toolkits.mplot3d import Axes3D
import mpl_toolkits.mplot3d.art3d as art3d
import matplotlib.pyplot as plt
import numpy as np
from matplotlib.patches import Circle
def plot_3D_cylinder(radius, height, elevation=0, resolution=100, color='r', x_center = 0, y_center = 0):
fig=plt.figure()
ax = Axes3D(fig, azim=30, elev=30)
x = np.linspace(x_center-radius, x_center+radius, resolution)
z = np.linspace(elevation, elevation+height, resolution)
X, Z = np.meshgrid(x, z)
Y = np.sqrt(radius**2 - (X - x_center)**2) + y_center # Pythagorean theorem
ax.plot_surface(X, Y, Z, linewidth=0, color=color)
ax.plot_surface(X, (2*y_center-Y), Z, linewidth=0, color=color)
floor = Circle((x_center, y_center), radius, color=color)
ax.add_patch(floor)
art3d.pathpatch_2d_to_3d(floor, z=elevation, zdir="z")
ceiling = Circle((x_center, y_center), radius, color=color)
ax.add_patch(ceiling)
art3d.pathpatch_2d_to_3d(ceiling, z=elevation+height, zdir="z")
ax.set_xlabel('x-axis')
ax.set_ylabel('y-axis')
ax.set_zlabel('z-axis')
plt.show()
# params
radius = 3
height = 10
elevation = -5
resolution = 100
color = 'r'
x_center = 3
y_center = -2
plot_3D_cylinder(radius, height, elevation=elevation, resolution=resolution, color=color, x_center=x_center, y_center=y_center)

Related

Annotating heatmap in matplotlib

I have to do three plots (contour, 3d surface, and heatmap) in matplotlib. The corresponding grid dimension for the three plots are ([0, 0], [0, 1], and [1, 0:1])
I have a few problems
The text annotation for heatmap (ax3), seem to fly out of ax3, into
ax1 and ax2. How can I constrain them to be within the ax3 only ?
Is this the fastest way to annotate text assuming that I do not want
to use seaborn ?
Can I get some tips on how to resolve my problems ?
Below is the code snippet to perform the plot operation
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gspec
from scipy.interpolate import griddata
import pyautogui
from scipy import stats
x = pyautogui.size()
width = x.width
height = x.height
x = np.arange(0, 10, 0.5)
y = np.arange(0, 10, 0.5)
X, Y = np.meshgrid(x, y)
data = 2 * (np.sin(X) + np.sin(3 * Y))
fig = plt.figure()
fig.set_figheight(height / 100)
fig.set_figwidth(width / 100)
fig.set_dpi(100)
gs = gspec.GridSpec(nrows=2, ncols=2)
ax1 = plt.subplot(gs[0, 0])
ax2 = plt.subplot(gs[0, 1], projection='3d')
ax3 = plt.subplot(gs[1, 0:1])
ctr = ax1.contourf(X, Y, data, 10, cmap='viridis')
ax1.clabel(ctr, inline=True, fontsize=8)
cbar = plt.colorbar(ctr, ax=ax1)
cbar.set_label('ColorbarLabel', size=15)
surf = ax2.plot_surface(X, Y, data, cmap='jet')
cbar1 = plt.colorbar(surf, ax=ax2)
cbar1.set_label('Colorbar2', size=15)
hmap = ax3.pcolormesh(X, Y, data, cmap='viridis')
cbar2 = plt.colorbar(hmap, ax=ax3)
for y in range(data.shape[0]):
for x in range(data.shape[1]):
ax3.text(x, y, '%.1f' % data[y, x], size=3)
I assume you want your heatmap to cover both columns. To achieve that you have to use ax3 = plt.subplot(gs[1, 0:2]): this tells matplotlib to use columns 0 and 1 (2 is excluded).
The text annotation for heatmap (ax3), seem to fly out of ax3, into ax1 and ax2. How can I constrain them to be within the ax3 only ?
That's because you are using the wrong coordinates in ax3.text.
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.gridspec as gspec
from scipy.interpolate import griddata
import pyautogui
from scipy import stats
x = pyautogui.size()
width = x.width
height = x.height
x = np.arange(0, 10, 0.5)
y = np.arange(0, 10, 0.5)
X, Y = np.meshgrid(x, y)
data = 2 * (np.sin(X) + np.sin(3 * Y))
fig = plt.figure()
fig.set_figheight(height / 100)
fig.set_figwidth(width / 100)
fig.set_dpi(100)
gs = gspec.GridSpec(nrows=2, ncols=2)
ax1 = plt.subplot(gs[0, 0])
ax2 = plt.subplot(gs[0, 1], projection='3d')
ax3 = plt.subplot(gs[1, 0:2])
ctr = ax1.contourf(X, Y, data, 10, cmap='viridis')
ax1.clabel(ctr, inline=True, fontsize=8)
cbar = plt.colorbar(ctr, ax=ax1)
cbar.set_label('ColorbarLabel', size=15)
surf = ax2.plot_surface(X, Y, data, cmap='jet')
cbar1 = plt.colorbar(surf, ax=ax2)
cbar1.set_label('Colorbar2', size=15)
hmap = ax3.pcolormesh(X, Y, data, cmap='viridis')
cbar2 = plt.colorbar(hmap, ax=ax3)
for i in range(data.shape[0]):
for j in range(data.shape[1]):
ax3.text(x[j], y[i], '%.1f' % data[i, j], size=5)

Colouring the surface of a tetrahedra in matplotlib

I'm trying to colour the faces of the tetrahedra determined by the edges shown here in green:
The plot is generated using this code:
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
%matplotlib notebook
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x,y,z = nCompoundTetra(bloch_vectors,nTetrahedron).getEdges()
x,y,z = list(x)[0],list(y)[0],list(z)[0]
ax.scatter3D(x, y, z, color = "black", s = 3.5)
ax.plot(x,y,z, color="g", linewidth= 1)
ax.set_xlim([-1.1, 1.1])
ax.set_ylim([-1.1, 1.1])
ax.set_zlim([-1.1, 1.1])
u = np.linspace(0, 2 * np.pi, 100)
v = np.linspace(0, np.pi, 100)
xs = np.outer(np.cos(u), np.sin(v))
ys = np.outer(np.sin(u), np.sin(v))
zs = np.outer(np.ones(np.size(u)), np.cos(v))
ax.plot_wireframe(xs, ys, zs, color="grey", alpha=0.15)
ax.get_proj = lambda: np.dot(Axes3D.get_proj(ax), np.diag([5, 5, 7, 7]))
ax.set_xlim([-1,1])
ax.set_ylim([-1,1])
ax.set_zlim([-1,1])
ax.set_axis_off()
plt.tight_layout()
plt.show()
nCompoundTetra(bloch_vectors,nTetrahedron).getEdges() is a method I'm implementing to generate more tetrahedron

Multiple 2D contour plots in one 3D figure in python

Is there any way available in python to plot multiple 2D contour plots in one 3D plot in python. I am currently using matplotlib for contouring, but not finding any option for what I am searching for. A sample image I have added. But I want to do it on Z-axis.
You can try this.
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
from matplotlib.path import Path
import matplotlib.patches as patches
fig = plt.figure()
ax = fig.gca(projection='3d')
x = np.linspace(0, 1, 100)
X, Y = np.meshgrid(x, x)
levels = np.linspace(-0.1, 0.4, 100) #(z_min,z_max,number of contour),
a=0
b=1
c=2
Z1 = a+.1*np.sin(2*X)*np.sin(4*Y)
Z2 = b+.1*np.sin(3*X)*np.sin(4*Y)
Z3 = c+.1*np.sin(4*X)*np.sin(5*Y)
plt.contourf(X, Y,Z1, levels=a+levels,cmap=plt.get_cmap('rainbow'))
plt.contourf(X, Y,Z2, levels=b+levels,cmap=plt.get_cmap('rainbow'))
plt.contourf(X, Y,Z3, levels=c+levels,cmap=plt.get_cmap('rainbow'))
ax.set_xlim3d(0, 1)
ax.set_ylim3d(0, 1)
ax.set_zlim3d(0, 2)
plt.show()
In order to plot true 2-D contour plots in one 3D plot, try this:
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
x = np.linspace(0, 1, 100)
X, Y = np.meshgrid(x, x)
Z1 = .1*np.sin(2*X)*np.sin(4*Y)
Z2 = .1*np.sin(3*X)*np.sin(4*Y)
Z3 = .1*np.sin(4*X)*np.sin(5*Y)
levels=np.linspace(Z1.min(), Z1.max(), 100)
ax.contourf(X, Y,Z1, levels=levels, zdir='z', offset=0, cmap=plt.get_cmap('rainbow'))
levels=np.linspace(Z2.min(), Z2.max(), 100)
ax.contourf(X, Y,Z2, levels=levels, zdir='z', offset=1, cmap=plt.get_cmap('rainbow'))
levels=np.linspace(Z3.min(), Z3.max(), 100)
ax.contourf(X, Y,Z3, levels=levels, zdir='z', offset=2, cmap=plt.get_cmap('rainbow'))
ax.set_xlim3d(0, 1)
ax.set_ylim3d(0, 1)
ax.set_zlim3d(0, 2)
plt.show()
enter image description here

how to plot image in 3d graph matplotlib

I have a cylinder in my matplotlib. I want to be able to put small 2d pictures of mixing blades inside. They can remain in 2d, but the graph and the cylinder are 3d. How do I go about doing this? I can't seem to find anything online about this. Code below:
import matplotlib.pyplot as plt
import matplotlib.image as img
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
# Scatter graph
N = 100
X = np.random.uniform(-1, 1, N)
Y = np.random.uniform(-1, 1, N)
Z = np.random.uniform(-2, 2, N)
ax.scatter(X, Y, Z)
# Cylinder
x=np.linspace(-1, 1, 100)
z=np.linspace(-2, 2, 100)
Xc, Zc=np.meshgrid(x, z)
Yc = np.sqrt(1-Xc**2)
# Draw parameters
rstride = 20
cstride = 10
ax.plot_surface(Xc, Yc, Zc, alpha=0.2, rstride=rstride, cstride=cstride)
ax.plot_surface(Xc, -Yc, Zc, alpha=0.2, rstride=rstride, cstride=cstride)
image_name='download.jpeg'
im_data = img.imread(image_name)
print(im_data)
print(np.shape(im_data))
cmap = 'jet'
ax.imshow(im_data, interpolation='nearest')
ax.set_xlabel("X")
ax.set_ylabel("Y")
ax.set_zlabel("Z")
plt.show()

Adding legend to a surface plot

I am trying to add legend to a surface plot but unable to do so. Here is the code.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import random
def fun(x, y):
return 0.063*x**2 + 0.0628*x*y - 0.15015876*x + 96.1659*y**2 - 74.05284306*y + 14.319143466051
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = y = np.arange(-1.0, 1.0, 0.05)
X, Y = np.meshgrid(x, y)
zs = np.array([fun(x,y) for x,y in zip(np.ravel(X), np.ravel(Y))])
Z = zs.reshape(X.shape)
ax.plot_surface(X, Y, Z)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
plt.plot(color='red',label='Lyapunov function on XY plane',linewidth=4) # Adding legend
plt.show()
Kindly help. Thanks in advance.
It is not trivial to make a legend in a 3D axis. You can use the following hack:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib as mpl
import random
def fun(x, y):
return 0.063*x**2 + 0.0628*x*y - 0.15015876*x + 96.1659*y**2 - 74.05284306*y + 14.319143466051
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
x = y = np.arange(-1.0, 1.0, 0.05)
X, Y = np.meshgrid(x, y)
zs = np.array([fun(x,y) for x,y in zip(np.ravel(X), np.ravel(Y))])
Z = zs.reshape(X.shape)
ax.plot_surface(X, Y, Z)
ax.set_xlabel('X')
ax.set_ylabel('Y')
ax.set_zlabel('Z')
fake2Dline = mpl.lines.Line2D([0],[0], linestyle="none", c='b', marker = 'o')
ax.legend([fake2Dline], ['Lyapunov function on XY plane'], numpoints = 1)
plt.show()
I would say a title is more appropriate than a legend in this case.
According to this question, the issue is ongoing, and there is a relatively simple workaround. You can manually set the two missing attributes that would allow legend to automatically create the patch for you:
surf = ax.plot_surface(X, Y, Z, label='Lyapunov function on XY plane')
surf._edgecolors2d = surf._edgecolor3d
surf._facecolors2d = surf._facecolor3d
ax.legend()
The attribute names on the right hand side of the assignment are surf._edgecolors3d and surf.facecolors3d for matplotlib < v3.3.3.

Categories