Plot unstructured triangular surfaces Python - python

I am trying to plot a surface created with >10000 unstructured triangles. I have the coordinates of the triangle points and the each triangle points list. My data is as follows,
0.1 0.2 0.1
0.2 0.4 0.6
0.4 0.6 0.4
.
.
.
1 2 3
.
.
.
The first three lines are coordinates (-X,Y,Z COORDINATES-) of the points (point 1 in line 1, point 2 in line 2 and etc). The number of points are more than 10000.
The "1 2 3" says that we have a triangle in which its corner points are 1, 2 and 3.
So, I want to plot the surface by starting from the 1st triangle and plotting them one by one. I have tried to follow the above procedure but I do not get the right figure and finally I get the following error message.
Figure size 432x288 with 0 Axes
I have tried the following code.
import matplotlib
import numpy as np
import matplotlib.cm as cm
import matplotlib.pyplot as plt
from matplotlib.tri import Triangulation
# from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits import mplot3d
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
fileName = open('surface.txt','r')
print(fileName.readline())
dummy = fileName.readline().split()
npo = int(dummy[2])
nel = int(dummy[4])
xp = np.zeros([npo])
yp = np.zeros([npo])
zp = np.zeros([npo])
el1 = np.zeros([nel])
el2 = np.zeros([nel])
el3 = np.zeros([nel])
for i in range(0,npo):
dummy = fileName.readline().split()
xp[i] = float(dummy[0])
yp[i] = float(dummy[1])
zp[i] = float(dummy[2])
# print(i,xp[i],yp[i],zp[i])
for i in range(0,nel):
dummy = fileName.readline().split()
el1[i] = int(dummy[0])
el2[i] = int(dummy[1])
el3[i] = int(dummy[2])
fig2 = plt.figure()
ax2 = fig2.add_subplot(111, projection='3d')
for i in range(0,nel):
x1 = xp[int(el1[i])-1]
y1 = yp[int(el1[i])-1]
z1 = zp[int(el1[i])-1]
x2 = xp[int(el2[i])-1]
y2 = yp[int(el2[i])-1]
z2 = zp[int(el2[i])-1]
x3 = xp[int(el3[i])-1]
y3 = yp[int(el3[i])-1]
z3 = zp[int(el3[i])-1]
xarr = [x1,x2,x3,x1]
yarr = [y1,y2,y3,y1]
zarr = [z1,z2,z3,z1]
verts = [list(zip(xarr,yarr,zarr))]
ax2.add_collection3d(Poly3DCollection(verts))
ax2.set_xbound(0,1)
ax2.set_ybound(0,1)
ax2.set_zbound(0,3)
I will appreciate to hear your opinion.

The function plo_trisurf does exactly what you want.
x, y, z are the nodes of your triangles
tri containes the indices of your triangle nodes
A small example:
import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D
x = np.array([0, -1, 1, 1])
y = np.array([0, 1, -1, 1])
z = np.array([0, 1, 1, -1])
tri = np.array([[0, 1, 2],
[0, 1, 3],
[0, 2, 3]])
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
ax.plot_trisurf(x, y, z, triangles=tri)

Related

Plot 3D Grid Data as Heat Map using matplotlib

I tried to visualize data as a cube but want to plot each cell inside the cube.
This is a cube not divided by cell but I want this 3D heatmap
I need to visualize the predicted result in a 3D cube with a heatmap
Here is the code:
%matplotlib notebook
%matplotlib inline
from mpl_toolkits.mplot3d import Axes3D
matplotlib.rcParams['backend'] = "Qt4Agg"
import numpy as np
#each sell has its value
y_pred = ([-3.4942558e+01, -1.0922590e+01, -9.4551229e+00, -2.6156654e+01,
1.1722648e+01, 6.4425749e-01, -1.1141028e+01, 5.9728470e+01,
-4.5249408e-01, -8.7741699e+00, 9.9452820e+00, -2.8595707e+00,
2.6979692e+00, 7.9802217e+00, -1.1204488e+00, -4.5213123e+01,
-7.1909481e-01, -8.4901733e+00, -9.2283611e+00, 5.0730385e+01,
-7.9520082e-01, 1.4589276e+02, 8.4267479e+01, -6.7399621e+00,
2.1536992e+02, 5.0958019e+01, -7.7071385e+00, 8.4650040e+01,
2.5421507e+01, -4.1403370e+00, -8.5559702e+00, 5.5478176e+01,
-1.0955868e+01, 3.1425345e+02, 1.9285686e+02, 4.6105843e+00,
6.6680554e+02, 2.7745572e+02, 1.2241451e+01, 5.1578967e+02,
1.8129390e+02, 7.3322144e+00, 1.2733205e+02, 4.4435711e+01,
-2.0441423e-01, 8.3673248e+01, 1.7386259e+02, -1.6146477e-01,
5.9598431e+02, 5.7501422e+02, 4.7413929e+01, 7.6495886e+02,
6.5124884e+02, 7.9399048e+01, 4.0769174e+02, 3.2358469e+02,
3.6726327e+00, 7.5896362e+01, 9.8999245e+01, -1.7699455e+00,
1.0632815e+02, 2.3123619e+02, 4.5826878e+01, 3.6063211e+02,
5.6004309e+02, 1.3757048e+02, 3.7029037e+02, 5.5426331e+02,
1.0625824e+02, 1.7360068e+02, 2.6583237e+02, 2.0798336e+01,
4.7036118e+01, 7.3350151e+01, 9.1420832e+00])
ex1 = np.trunc(y_pred).reshape(5,5,3)
max_value = np.amax(ex1)
min_value = np.amin(ex1)
average = 1/2*(min_value + max_value)
mean_value = np.mean(ex1)
print(f"max: {max_value}\nmin: {min_value}\naverage: {average}\nmean: {mean_value}")
x = 5
y = 5
z = 3
axes = [5, 5, 3]
data = np.ones(axes, dtype=bool)
alpha = 0.5
colors = np.empty(axes + [4])
for x1 in range(x):
for y1 in range(y):
for z1 in range(z):
if ex1[x1][y1][z1] >= average:
colors[x1][y1][z1] = [1, 0, 0, alpha]
elif ex1[x1][y1][z1] > mean_value:
colors[x1][y1][z1] = [1, 1, 0, alpha]
elif ex1[x1][y1][z1] <= mean_value:
colors[x1][y1][z1] = [0, 0, 1, alpha]
fig = plt.figure()
ax = fig.add_subplot(111, projection='3d')
ax.voxels(data, facecolors=colors, edgecolors='grey')
ax.axis('on')
ax.set_aspect('auto')
plt.show()
I would really appreciate it if you help me with this

matplotlib: Plotting the path in 3D axis

I would like to plot the path based on x y z location data. Below is a reproducible example, all the lines keep starting from 0 instead of following one after each other.
import seaborn as sns
# loading sample data and replicating my scenario
data = sns.load_dataset("iris")
# giving it a numeric value to replicate my scenario
cat_lbl = {'setosa': 1, 'versicolor': 2,'virginica' : 3}
data['cat_lbl'] = data['species'].map(cat_lbl)
#plot headings
species = ['setosa', 'versicolor', 'virginica']
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
sepal_length = data.loc[:,['sepal_length','cat_lbl']]
sepal_width = data.loc[:,['sepal_width','cat_lbl']]
petal_length = data.loc[:,['petal_length','cat_lbl']]
fig = plt.figure(figsize=([20,15]))
for lbl in range(3):
lbl=lbl+1
x=sepal_length[(sepal_length.cat_lbl == lbl)].values
y=sepal_width[(sepal_width.cat_lbl == lbl)].values
z=petal_length[(petal_length.cat_lbl == lbl)].values
ax=fig.add_subplot(3,3,lbl, projection='3d')
ax.plot(x.flatten(),y.flatten(),z.flatten())
ax.set_title(species[lbl-1])
plt.show()
Your problem is that
x=sepal_length[(sepal_length.cat_lbl == lbl)].values
y=sepal_width[(sepal_width.cat_lbl == lbl)].values
z=petal_length[(petal_length.cat_lbl == lbl)].values
are actually 2D arrays that contain the category index (1,2,3). So when you flatten x.flatten(), you alternate between the coordinate and the category index (you can see that the lines actually loop back to (1,1) on the first graph, (2,2) on the second and (3,3) on the third)
Here is how I would write your code:
import seaborn as sns
import matplotlib as mpl
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
data = sns.load_dataset("iris")
species = ['setosa', 'versicolor', 'virginica']
fig,axs = plt.subplots(1,3,subplot_kw=dict(projection='3d'),figsize=(9,3))
for sp,ax in zip(species, axs.flat):
temp = data.loc[data['species']==sp]
x=temp['sepal_length'].values
y=temp['sepal_width'].values
z=temp['petal_length'].values
ax.plot(x,y,z)
ax.set_title(sp)
plt.show()
Try ax.plot3D(...) instead of ax.plot(...) as indicated in this tutorial for 3D plotting:
ax = plt.axes(projection='3d')
# Data for a three-dimensional line
zline = np.linspace(0, 15, 1000)
xline = np.sin(zline)
yline = np.cos(zline)
ax.plot3D(xline, yline, zline, 'gray')
# Data for three-dimensional scattered points
zdata = 15 * np.random.random(100)
xdata = np.sin(zdata) + 0.1 * np.random.randn(100)
ydata = np.cos(zdata) + 0.1 * np.random.randn(100)
ax.scatter3D(xdata, ydata, zdata, c=zdata, cmap='Greens');

How do I draw a 3D truncated pyramid in Python(with user-inputs)?

i'm relatively new with python and have been trying to create a small program, that draws a truncated pyramid based on the larger and smaller base, and the height, by inputs from the user. But every time, i plot the drawing, the smaller base is not centered.
I would very appreciate it, if someone could point out the mistake in my code.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection, Line3DCollection
import matplotlib.pyplot as plt
import math
G = 4
g = 2
h = 2
a = math.sqrt(math.pow(h,2)+math.pow((((G/2)-(g/2))),2))
Z = np.array([[-1, -1, -1],
[(-1+G), -1, -1 ],
[(-1+G), (-1+G), -1],
[-1, (-1+G), -1],
[(-1+(a/3)), (-1+(a/3)), (-1+h)],
[(-1+(a/3)+g), (-1+(a/3)), (-1+h)],
[(-1+(a/3)+g), (-1+(a/3)+g), (-1+h)],
[(-1+(a/3)), (-1+(a/3)+g), (-1+h)]])
fig = plt.figure()
ax = fig.add_subplot(1,1,1, projection='3d')
r = [-1,1]
X, Y = np.meshgrid(r, r)
ax.scatter3D(Z[:, 0], Z[:, 1], Z[:, 2])
faces = [[Z[0],Z[1],Z[2],Z[3]],
[Z[4],Z[5],Z[6],Z[7]],
[Z[0],Z[1],Z[5],Z[4]],
[Z[2],Z[3],Z[7],Z[6]],
[Z[1],Z[2],Z[6],Z[5]],
[Z[4],Z[7],Z[3],Z[0]],
[Z[2],Z[3],Z[7],Z[6]]]
ax.add_collection3d(Poly3DCollection(faces,
facecolors='cyan', linewidths=1, edgecolors='r', alpha=.25))
plt.show()

How to change a 'LinearSegmentedColormap' to a different distribution of color?

I am trying to make a color map that 'favors' lower values, i.e. it takes longer to get out of the darker color to get to the light color. At the moment I am using this as the colormap:
cmap = clr.LinearSegmentedColormap.from_list('custom blue', ['#ffff00','#002266'], N=256)
I am plotting this around a cylinder to see the effect (see code for cylinder at the end of the post), this is what happens when you run the code:
As you can see this is very 'linear'. The color starts changing about halfway along the cylinder. Is there a way to increase the threshold for when the colors start to change rapidly? I.e. I want only very high numbers to have the brightest level of yellow. Thanks.
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
import mpl_toolkits.mplot3d.art3d as art3d
import matplotlib.colors as clr
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 = 1
#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]]
#THIS IS WHERE THE COLOR MAP IS
cmap = clr.LinearSegmentedColormap.from_list('custom blue', ['#ffff00','#002266'], N=256)
col1 = cmap(np.linspace(0,1,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.9, linewidth = 0)
ax.view_init(15,-40)
plt.show()
When making colormaps with LinearSegmentedColormap.from_list, you can supply a list of tuples of the form (value, color) (as opposed to simply a list of colors) where the values correspond to the relative positions of colors. The values must range from 0 to 1 so you will have to supply an intermediate color. In your case I might try this,
cmap = clr.LinearSegmentedColormap.from_list('custom blue',
[(0, '#ffff00'),
(0.25, '#002266'),
(1, '#002266')], N=256)
and tweak color/value until satisfied. Credit goes to https://stackoverflow.com/a/25000108/5285918

Vertically fill 3d matplotlib plot

I have a 3d plot made using matplotlib. I now want to fill the vertical space between the drawn line and the x,y axis to highlight the height of the line on the z axis. On a 2d plot this would be done with fill_between but there does not seem to be anything similar for a 3d plot. Can anyone help?
here is my current code
from stravalib import Client
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
... code to get the data ....
mpl.rcParams['legend.fontsize'] = 10
fig = plt.figure()
ax = fig.gca(projection='3d')
zi = alt
x = df['x'].tolist()
y = df['y'].tolist()
ax.plot(x, y, zi, label='line')
ax.legend()
plt.show()
and the current plot
just to be clear I want a vertical fill to the x,y axis intersection NOT this...
You're right. It seems that there is no equivalent in 3D plot for the 2D plot function fill_between. The solution I propose is to convert your data in 3D polygons. Here is the corresponding code:
import math as mt
import matplotlib.pyplot as pl
import numpy as np
import random as rd
from mpl_toolkits.mplot3d import Axes3D
from mpl_toolkits.mplot3d.art3d import Poly3DCollection
# Parameter (reference height)
h = 0.0
# Code to generate the data
n = 200
alpha = 0.75 * mt.pi
theta = [alpha + 2.0 * mt.pi * (float(k) / float(n)) for k in range(0, n + 1)]
xs = [1.0 * mt.cos(k) for k in theta]
ys = [1.0 * mt.sin(k) for k in theta]
zs = [abs(k - alpha - mt.pi) * rd.random() for k in theta]
# Code to convert data in 3D polygons
v = []
for k in range(0, len(xs) - 1):
x = [xs[k], xs[k+1], xs[k+1], xs[k]]
y = [ys[k], ys[k+1], ys[k+1], ys[k]]
z = [zs[k], zs[k+1], h, h]
#list is necessary in python 3/remove for python 2
v.append(list(zip(x, y, z)))
poly3dCollection = Poly3DCollection(v)
# Code to plot the 3D polygons
fig = pl.figure()
ax = Axes3D(fig)
ax.add_collection3d(poly3dCollection)
ax.set_xlim([min(xs), max(xs)])
ax.set_ylim([min(ys), max(ys)])
ax.set_zlim([min(zs), max(zs)])
ax.set_xlabel("x")
ax.set_ylabel("y")
ax.set_zlabel("z")
pl.show()
It produces the following figure:
I hope this will help you.

Categories