plot 3D polygons in 2D - python

I search for the correct implementation for this a long time now.
I have a 3D delaunay triangulation and want to plot this in 2D.
In 3D i manage to do so:
I need a 2D plot though. What i get using matplotlib.tripcolor method or the matplotlib.collections.PolyCollection is:
How do i plot this in 2D without the top and back triangles all mixed up? With all methods tried so far, some triangles are hidden by triangles that should be in the back of the structure.
I see, that the methods just do not have the information necessary to plot in the correct order, since i have to provide 2D arrays already. The depth information is lost.
Does anybody know how to do this?
Thanks a lot!

You can mimic a 2D plot with Axes3d by setting an orthographic projection, initialising the view to face the desired plane, and removing unwanted plot elements along the axis orthogonal to the chosen plane of view. In addition, you can plot 2D elements using the zdir keyword argument.
Here's one of the matplotlib 3D plot examples I modified to demonstrate
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
from matplotlib import cm
import numpy as np
# Enable orthographic projection
# https://stackoverflow.com/questions/23840756/how-to-disable-perspective-in-mplot3d
from mpl_toolkits.mplot3d import proj3d
def orthogonal_proj(zfront, zback):
a = (zfront+zback)/(zfront-zback)
b = -2*(zfront*zback)/(zfront-zback)
return np.array([[1,0,0,0],
[0,1,0,0],
[0,0,a,b],
[0,0,-0.000001,zback]])
proj3d.persp_transformation = orthogonal_proj
fig = plt.figure()
ax = fig.gca(projection='3d')
# Init view to YZ plane
ax.view_init(azim=0, elev=0)
# Hide the X axis
ax.w_xaxis.line.set_lw(0.)
ax.set_xticks([])
# Change YZ plane colour to white
ax.w_xaxis.set_pane_color((1.0, 1.0, 1.0, 1.0))
# Make data.
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
# Plot the surface.
surf = ax.plot_surface(X, Y, Z, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
ax.set_zlim(-1.1, 1.1)
ax.set_ylabel('y')
ax.set_zlabel('z')
# Plot 2D elements with zdir argument
# https://stackoverflow.com/questions/29549905/pylab-3d-scatter-plots-with-2d-projections-of-plotted-data
stepsize = 0.1
t = np.arange(-4, 4+stepsize, step=stepsize)
ax.plot(t, 0.5*np.sin(t), 'k', zdir='x', linewidth=1.0)
ax.text(0, 0, 1, 'Text', zdir='y', ha='center', va='top')
plt.show()

Related

3d contour with 3 variables and 1 variable as colour

I have three variables for my plot and I colour by the fourth variable. I have made a scatter plot via the following code, but I want a contour plot. My code:
import numpy as np
import matplotlib.pyplot as plt
a=np.linspace(4.0,14.0,3)
b=np.linspace(0.5,2.5,3)
c=np.linspace(0.0,1.0,3)
d=np.random.rand(len(a),len(b),len(c)) #colour by this variable
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
z,y,x=np.meshgrid(c,a,b)
img = ax.scatter(x, y, z, c=d, cmap='RdGy')
fig.colorbar(img, pad=0.2).set_label('colour')
ax.set_xlabel('c')
ax.set_ylabel('a')
ax.set_zlabel('b')
I want a filled contour instead of scatter plot. I know mayavi.mlab has this feature, but I cannot import mlab for some reason. Is there an alternative, or is there a better way of presenting this data?
Here is how I would present this 3-dimensional data. Each plot is a cross-section through the cube. This makes sense intuitively.
import numpy as np
import matplotlib.pyplot as plt
x = np.linspace(4.0, 14.0, 3)
y = np.linspace(0.5, 2.5, 3)
z = np.linspace(0.0, 1.0, 3)
data = np.random.rand(len(x), len(y), len(z))
fig, axes = plt.subplots(len(z), 1, figsize=(3.5, 9),
sharex=True,sharey=True)
for i, (ax, d) in enumerate(zip(axes, data.swapaxes(0, 2))):
ax.contour(x, y, d)
ax.set_ylabel('y')
ax.grid()
ax.set_title(f"z = {z[i]}")
axes[-1].set_xlabel('x')
plt.tight_layout()
plt.show()
My advice: 3D plots are rarely used for serious data visualization. While they look cool, it is virtually impossible to read any data points with any accuracy.
Same thing goes for colours. I recommend labelling the contours rather than using a colour map.
You can always use a filled contour plot to add colours as well.

3D surface with matplotlib is incorrect but contour plot is correct

I am trying to plot a 3D surface with matplotlib for a bunch of math functions. These functions are defined to take in a 1-D numpy array with arbitrary length as input.
When plotted as contours plot, the plot looks correct.
. However, the 3D surface plot shows a surface that has been squashed onto a single line. I am using the same values for plotting, so they should be the same, but that's not what I'm getting and I'm very puzzled by this
Please see my code below:
from mpl_toolkits.mplot3d import Axes3D
# build the meshgrid
x = np.linspace(bounds[0][0],bounds[0][1])
y = np.linspace(bounds[1][0], bounds[1][1])
xv, yv = np.meshgrid(x, y)
# populate z
z = np.zeros_like(xv)
for row_idx in range(xv.shape[0]):
for col_idx in range(xv.shape[1]):
z[row_idx][col_idx] = function(np.array([xv[row_idx][col_idx], yv[row_idx][col_idx]]))
# plot 3D surface
fig = plt.figure()
ax = plt.axes(projection='3d')
ax.plot_surface(x, y, z, cmap='viridis', edgecolor='none')
# plot a contour
ax.contourf(x, y, z, cmap='viridis')
plt.show()
# function that I'm plotting -> bowl shaped for 2D x
def function(x):
return sum(x**2)
I might have this wrong but if you are just wanting the surface mesh you need to plot the gridded data as opposed to the linear one so just change this line:
ax.plot_surface(xv, yv, z, cmap='viridis', edgecolor='none')
Note I'm using xv, yv instead of X,Y.
Here is my output.

matplotlib set_data() not updating plot on next draw()

I have a 2D plot placed on one of the walls of a 3D plot that doesn't seem to reflect any changes from set_data(), I would like to understand what I'm doing wrong here.
Here is a sample code showing the 3D plot with the 2D 'projection' plot in question.
The output is shown here:
from mpl_toolkits.mplot3d import Axes3D
import numpy as np
import matplotlib.pyplot as plt
plt.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
# Test data for projection onto xz plane
t = linspace(0,10, num=20)
z = np.sin(t)
# Plot projection
projx, = ax.plot(np.linspace(-1,0, num=len(z)), z, 'r', zdir='y', zs=1)
# Labels and scaling
ax.set_xlabel('M_x')
ax.set_ylabel('M_y')
ax.set_zlabel('M_z')
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_zlim(-1, 1)
# Update projection data
projx.set_data([0],[0])
# See if actually updated data
print(projx.get_xdata())
# Draw and display window
plt.draw()
ax.legend()
plt.show()
I imagine that this line:
projx.set_data([0],[0])
would make the projection plot virtually empty. Instead, the sine wave remains.
Furthermore, the printout yields [0] as expected, so the set_data() call was successful, but for some reason the plot doesn't get drawn with the new data.
Shouldn't the set_data() changes be reflected when drawn afterwards?
There is a way to update a Line3D object by directly setting its vertices. Not sure, if this might have any negative side effects, though.
import numpy as np
import matplotlib.pyplot as plt
fig = plt.figure()
ax = fig.gca(projection='3d')
# Test data for projection onto xz plane
t = np.linspace(0,10, num=20)
z = np.sin(t)
# Plot projections
projx, = ax.plot(np.linspace(-1,0, num=len(z)), z, 'r', zdir='y', zs=1, label="changed")
projy, = ax.plot(np.linspace(-1,0, num=len(z)), z, 'b', zdir='x', zs=-1, label="not changed")
# Labels and scaling
ax.set_xlabel('M_x')
ax.set_ylabel('M_y')
ax.set_zlabel('M_z')
ax.set_xlim(-1, 1)
ax.set_ylim(-1, 1)
ax.set_zlim(-1, 1)
#update vertices of one Line3D object
projx._verts3d = [0, 0.2, 0.7], [1, 1, 1], [0.5, 0.2, 0.7]
ax.legend()
plt.show()
Sample output:
However, since one cannot omit any of the x, y, and z arrays, there is no real advantage over plotting it as a 3D array with one array being a constant.

How to rotate a 3d plot in python? (or as a animation) Rotate 3-D view using mouse

I have this code which contains a 3D plot. I run the code in Spyder; I want to know if it is possible to make this plot a rotating one (360 degrees) and save it.
Thanks!
P.s. Sorry if it is a silly question, but I am a newby in Python.
import matplotlib.pyplot as plt
import numpy as np
from scipy import array
jet = plt.get_cmap('jet')
from matplotlib import animation
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.linspace(70,40,4)
Y = np.linspace(5,2,4)
X,Y= np.meshgrid(X, Y)
Z = array ([
[1223.539555, 1428.075086,1714.479425, 2144.053223],
[1567.26647,1829.056119,2990.416079,2745.320067],
[2135.163957,2491.534201, 2990.416079,3738.761638],
[3257.280827, 3800.655101, 4561.372117, 5702.458776],
])
surf = ax.plot_surface(X, Y, Z, rstride = 1, cstride = 1, cmap = jet,linewidth = 0,alpha= 1)
ax.set_zlim3d(0, Z.max())
fig.colorbar(surf, shrink=0.8, aspect=5)
ax.set_xlabel('Axial Length [mm]')
ax.set_ylabel('nbTurns')
ax.set_zlabel('RPM')
plt.show()
You need to define a function in order to get a specific animation. In your case it is a simple rotation:
def rotate(angle):
ax.view_init(azim=angle)
Then use the matplotlib animation:
rot_animation = animation.FuncAnimation(fig, rotate, frames=np.arange(0,362,2),interval=100)
This will call the rotate function with the frames argument as angles and with an interval of 100ms, so this will result in a rotation over 360° with a 2° step each 100ms. To save the animation as a gif file:
rot_animation.save('path/rotation.gif', dpi=80, writer='imagemagick')

create stereoscopic 3d plot with matplotlib

I would like to be able to make 3d plots in matplotlib that can be displayed in stereoscopic 3d, like this:
The camera of the left image is slightly translated with respect to the right. If you practice for a bit, you can trick your brain into having your left eye look at the left image and your right eye at the right image and see 3D on any screen!
Anyway, I would like to be able to make any matplotlib 3d plot show up twice, with one camera slightly translated. I've managed to get just the plot itself in stereo, simply by translating the data:
from matplotlib import cm
from matplotlib.ticker import LinearLocator, FormatStrFormatter
import matplotlib.pyplot as plt
import numpy as np
fig = plt.figure(figsize=(10,5))
axl = fig.add_subplot(1,2,1,projection='3d')
axr = fig.add_subplot(1,2,2,projection='3d')
X = np.arange(-5, 5, 0.25)
Y = np.arange(-5, 5, 0.25)
X, Y = np.meshgrid(X, Y)
R = np.sqrt(X**2 + Y**2)
Z = np.sin(R)
axr.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
axl.plot_surface(X, Y-5, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
for ax in [axr,axl]:
ax.set_zlim(-1.01, 1.01)
ax.set_ylim(-10,10)
ax.view_init(azim=10)
ax.set_axis_off()
This gives the desired effect, but I would like to be able to also show the axes themselves in stereoscopic 3d. I can only find how to set the elevation and azimuth of the camera, using the view_init method of the Axes object. There doesn't seem to be a 'nice' way of translating the camera.
Does anyone know how to do it?

Categories

Resources