FEA Stress plot in Python from 3 1D-arrays - python

I have 3 1D arrays (node x-coordinates, node y-coordinates and Von-Mises stress scalar) exported from an FEA solver.
I want to create 2D contour plots as shown below in Python:
Stress plot example
I have managed to create such plot as shown below:
Stress plot result
import matplotlib as mpl
import numpy as np
import matplotlib.pyplot as plt
import matplotlib.tri as tri
for orient in ['top', 'bot', 'side']:
x = []
y = []
z = []
stress = []
with open('data.txt') as file:
for line in file:
cur_line = line.split('\t')
cur_x_old = cur_line[0]
cur_y_old = cur_line[1]
cur_z_old = cur_line[2]
cur_s_old = cur_line[3]
if cur_x_old == 'X Location (mm)':
pass
else:
cur_x = cur_x_old.replace(",",".")
cur_y = cur_y_old.replace(",",".")
cur_z = cur_z_old.replace(",",".")
cur_s = cur_s_old.replace(",",".")
x.append(float(cur_x))
y.append(float(cur_y))
z.append(float(cur_z))
stress.append(float(cur_s))
stress = np.array(stress)
x = np.array(x)
y = np.array(y)
z = np.array(z)
levels=np.linspace(stress.min(), stress.max(), num=100)
triang = tri.Triangulation(x, y)
if orient == 'side':
plt.figure(figsize = (max(x)/50, abs(min(y))/50))
plt.tricontourf(triang, stress, cmap = 'jet', norm = mpl.colors.Normalize(0, 100), levels = levels, extend = 'max')
plt.scatter(x, y, color = 'k')
else:
plt.figure(figsize = (max(x)/50, max(z)*2/50))
plt.tricontourf(x, z, stress, cmap = 'jet', norm = mpl.colors.Normalize(0, 100), levels = levels)
My problem is that by triangulating the data, unwanted triangles are generated at the edge of the mesh (see Stress plot result). The black dots are the scatter plot from x and y coordinates. I want the colour plot to be only inside the boundaries of the grid. Is there a way to remove these unwanted triangles?

Related

Can we put a flat heatmap on a 3D axis?

Can we plot a straight heatmap on a 3D axis? The heatmap is as follows:
I am able to get a 3D elevation map, but I am not looking for that. I just want this straight lying on a 3D axis.
Code:
import matplotlib
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
from scipy import interpolate
excel_data_df = pd.read_excel('test.xlsx')
X= excel_data_df['x'].tolist()
Y= excel_data_df['y'].tolist()
Z= excel_data_df['z'].tolist()
X = np.array(X)
Y = np.array(Y)
Z = np.array(Z)
# Flatten trial dataset to meet your requirement:
x = X.ravel()
y = Y.ravel()
z = Z.ravel()
# Resampling on as square grid with given resolution:
resolution = 8
xlin = np.linspace(min(x), max(x), resolution)
ylin = np.linspace(min(y), max(y), resolution)
Xlin, Ylin = np.meshgrid(xlin, ylin)
# Linear multi-dimensional interpolation:
interpolant = interpolate.NearestNDInterpolator([r for r in zip(x, y)], z)
Zhat = interpolant(Xlin.ravel(), Ylin.ravel()).reshape(Xlin.shape)
cmap = 'jet'
# Render and interpolate again if necessary:
fig, axe = plt.subplots()
axe.imshow(Zhat, origin="lower", cmap=cmap, interpolation='bicubic',extent=[min(x),max(x),min(y),max(y)])
plt.xticks(np.arange(min(x), max(x)+1, 1.0))
plt.yticks(np.arange(min(y), max(y)+1, 1.0))
axe.grid(True, linewidth=0.3, color='w')
norm = matplotlib.colors.Normalize(vmin = min(z), vmax = max(z), clip = False)
plt.colorbar(plt.cm.ScalarMappable(cmap = cmap, norm=norm))
plt.show()

How do I highlight a slice on a matplotlib 3D surface plot?

I have a 3D surface plot. I would also like to plot slices of this plot in 2D, and somehow indicate on the 3D plot where the slices came from (such as coloring the points along the slice to 'highlight' the slice, or plotting an intersecting plane or something).
Following is an example where I am simply setting a particular row to 0 so I can see where the slice is on the 3D plot.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# Grid and test function
N = 29;
x,y = np.linspace(-1,1, N*2), np.linspace(-1,1, N)
X,Y = np.meshgrid(x,y)
F = lambda X,Y : np.sin(10*X)/(1+5*(X**2+Y**2))
Z = F(X,Y)
# 3D Surface plot
plt.figure(figsize = (5,6))
Z2 = Z.copy(); Z2[10,:] = 0 # <----- Replace this code
ax = plt.subplot(211, projection='3d')
ax.plot_surface(X,Y,Z2)
# 2D Plot of slice of 3D plot
plt.subplot(212)
plt.plot(x,Z[10,:])
plt.show()
plt.savefig('surfacePlotHighlight.png')
You can color slices in the X or Y directions using the facecoloroptions in plot_surface, and a similar setting of the color in plot. E.g.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# Grid and test function
N = 29;
x,y = np.linspace(-1,1, N*2), np.linspace(-1,1, N)
X,Y = np.meshgrid(x,y)
F = lambda X,Y : np.sin(10*X)/(1+5*(X**2+Y**2))
Z = F(X,Y)
# 3D Surface plot
plt.figure(figsize = (5,6))
ax = plt.subplot(211, projection='3d')
# Normalise Y for calling in the cmap.
Ys = Y/Y.max()
cmap = plt.cm.viridis
ax.plot_surface(X, Y, Z2, facecolors=cmap(Ys))
# 2D Plot of slice of 3D plot
# Normalise y for calling in the cmap.
ys = y/y.max()
plt.subplot(212)
plt.plot(x,Z[10,:], color=cmap(ys[10]))
plt.plot(x,Z[20,:], color=cmap(ys[20]))
plt.show()
plt.savefig('surfacePlotHighlight.png')
EDIT:
This can be used to highlight a single row (or column, or arbitrary set of points) by editing the color array to call out specific cells, such as:
# 3D Surface plot
plt.figure(1,figsize = (5,6))
ax = plt.subplot(211, projection='3d')
# Create array to specify color of each pixel on surface
Ys = Y*0
Ys[:,:] = .3
Ys[10] = 1
Ys[20] = .7
cmap = plt.cm.viridis
ax.plot_surface(X, Y, Z, facecolors=cmap(Ys))
# 2D Plot of slice of 3D plot
# Normalise y for calling in the cmap.
ys = Ys[:,0]
plt.subplot(212)
plt.plot(x,Z[10,:], color=cmap(ys[10]))
plt.plot(x,Z[20,:], color=cmap(ys[20]))
plt.show()
plt.savefig('surfacePlotHighlight.png')
You may colorize the row that is shown in a different color than the rest.
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# Grid and test function
N = 29;
x,y = np.linspace(-1,1, N*2), np.linspace(-1,1, N)
X,Y = np.meshgrid(x,y)
F = lambda X,Y : np.sin(10*X)/(1+5*(X**2+Y**2))
Z = F(X,Y)
y0 = 10
norm=plt.Normalize(Z.min(), Z.max())
C = plt.cm.Blues_r(norm(Z)/2)
C[y0] = plt.cm.Reds_r(norm(Z[y0])/2)
# 3D Surface plot
plt.figure(figsize = (5,6))
ax = plt.subplot(211, projection='3d')
ax.plot_surface(X,Y,Z, facecolors=C)
# 2D Plot of slice of 3D plot
plt.subplot(212)
plt.plot(x,Z[y0,:], color=plt.cm.Reds(.7))
plt.show()

How to 4D plot with contour over cube, using matplotlib?

I would like to 4D plot over the cube (x,y,z) vs. q, using the colormap on the 3 surfaces of the cubes, where the color and contour are determined depending on the q variable. Basically, I am looking for a similar image like this:
Any help is appreciated.
See my example of 3D ABC feild
import pyvista as pv
import numpy as np
from numpy import mgrid
import matplotlib.pyplot as plt
print('initializing domain')
xmin = -800.
xmax = 800.
Lx = xmax-xmin
B0 = 1
k = 1
alpha = 2.0*np.pi*k/Lx
x, y, z = Lx*mgrid[0:1:51j, 0:1:51j, 0:1:51j]
print('initializing 3D B field')
Bx = B0*(np.sin(alpha*z) + np.cos(alpha*y))
By = B0*(np.sin(alpha*x) + np.cos(alpha*z))
Bz = B0*(np.sin(alpha*y) + np.cos(alpha*x))
B = np.column_stack((Bx.ravel(), By.ravel(), Bz.ravel()))
grid = pv.StructuredGrid(x, y, z)
grid["ABC field magnitude"] = np.linalg.norm(B, axis=1)
grid["ABC field vectors"] = B
grid.set_active_vectors("ABC field vectors")
#contours = grid.contour(8, scalars="ABC field magnitude")
#arrows = contours.glyph(orient="ABC field vectors", factor=50.0)
print('plotting')
pv.set_plot_theme('document')
p = pv.Plotter(notebook=0, shape=(1,1))
#p.background_color='white'
#p.window_size
cmap = plt.cm.get_cmap("viridis", 4)
p.add_mesh(grid, cmap=cmap)
p.show_grid()
#p.add_mesh(arrows)
#p.subplot(0,1)
#slices = grid.slice_orthogonal(x=20, y=20, z=30)
#slices = grid.slice_orthogonal()
#p.add_mesh(slices, cmap=cmap)
##p.subplot(1,0)
#p.add_mesh(contours, opacity=1)
#p.subplot(1,1)
#p.add_mesh(arrows)
#single_slice = arrows.slice(normal=[1, 1, 0])
#slices = arrows.slice_orthogonal(x=20, y=20, z=30)
#slices = grid.slice_orthogonal()
#p.add_mesh(single_slice, cmap=cmap)
p.show_grid()
p.link_views()
p.view_isometric()
p.show(screenshot='abc3d_slicing.png')
A simple answer is
import numpy as np
import matplotlib.pyplot as plt
length = 10
data = length*np.mgrid[0:1:51j, 0:1:51j, 0:1:51j].reshape(3,-1).T
contour = np.random.rand(data.shape[0])
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
data_plot = ax.scatter(data[:,0], data[:,1], data[:,2], c=contour)
fig.colorbar(data_plot)
To optimize to only boundary points
length = 10
vol_data = length*np.mgrid[0:1:51j, 0:1:51j, 0:1:51j].reshape(3,-1).T
bound_data = np.array([data_i for data_i in vol_data
if any([coord in [0, length] for coord in data_i])])
contour = np.random.rand(bound_data.shape[0])
fig = plt.figure()
ax = fig.add_subplot(projection='3d')
data_plot = ax.scatter(bound_data[:,0], bound_data[:,1], bound_data[:,2], c=contour)
fig.colorbar(data_plot)

Create surface grid from point cloud data in Python

Here is an example creating a point cloud which I then want to fit a grided surface to. The problem comes at the end when I try to pass in meshgrid arrays to a function which interpolated the data:
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
# Create some point cloud data:
c = 1
a = 3
b = 4
slice = {}
t = np.linspace(0,2*np.pi,50)
for s in np.linspace(1,9,10):
c = 5*s
r = (-s**2+10.0*s)/10.0
X = r*np.cos(t)
Y = r*np.sin(t)
Z = c*(Y**2/b**2 - X**2/a**2) + c
slice[str(int(s))] = np.vstack([X,Y,Z])
# Visualize it:
fig = plt.figure()
ax = fig.gca(projection = '3d')
for k,v in slice.iteritems():
print type(v)
print np.shape(v)
X = v[0,:]
Y = v[1,:]
Z = v[2,:]
ax.scatter(X,Y,Z)
plt.show()
It looks like this:
I now need to create a surface mesh based on these points. There are multiple interpretations of surface in this case because I just have a point cloud rather than a function z = f(x,y) but the correct surface in this case should be the one that creates a hollow "warped cylinder". I thought of attacking the problem like this:
# stack all points from each slice into one vector for each coordinate:
Xs = []
Ys = []
Zs = []
for k,v in slice.iteritems():
#ax.plot_surface(X,Y,Z)
X = v[0,:]
Y = v[1,:]
Z = v[2,:]
Xs = np.hstack((Xs,X))
Ys = np.hstack((Ys,Y))
Zs = np.hstack((Zs,Z))
XX, YY = np.meshgrid(Xs,Ys)
from scipy import interpolate
f = interpolate.interp2d(Xs,Ys,Zs, kind = 'cubic')
ZZ = f(XX,YY)
which I would then be able to plot using
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.plot_surface(XX, YY, ZZ)
plt.show()
However the interpolated function does not seem to accept arrays as inputs so this method might not work. Can anyone come up with a suggestion on how to do this properly?
Edit:
Actually the data is obviously not able to be represented as a function as it would not be one to one.
I stumbled upon the same question and wondered why it has not been solved in the last 7 years. Here's my solution for any future reader based on plot_trisurf (and the corresponding code examples).
import numpy as np
from mpl_toolkits.mplot3d import Axes3D
import matplotlib.pyplot as plt
import matplotlib.tri as mtri
# Create some point cloud data:
a = 3
b = 4
# def grid of parametric variables
u = np.linspace(0,2*np.pi,50)
v = np.linspace(1,9,50)
U, V = np.meshgrid(u, v)
U, V = U.flatten(), V.flatten()
# Triangulate parameter space to determine the triangles
tri = mtri.Triangulation(U, V)
# get the transformed data as list
X,Y,Z = [],[],[]
for _u in u:
for _v in v:
r = (-_v**2+10.0*_v)/10.0
x = r*np.cos(_u)
y = r*np.sin(_u)
z = 5*_v*(y**2/b**2 - x**2/a**2) + 5*_v
X.append(x)
Y.append(y)
Z.append(z)
# Visualize it:
fig = plt.figure()
ax = fig.gca(projection = '3d')
ax.scatter(X,Y,Z, s=1, c='r')
ax.plot_trisurf(X, Y, Z, triangles=tri.triangles, alpha=.5)
plt.show()
This produces the following plot.

matplotlib: continuous colormap fill between two lines

It's possible to fill between lines with a color:
http://matplotlib.sourceforge.net/examples/pylab_examples/fill_between_demo.html
It's also possible to use a continuous colormap for a line:
http://matplotlib.sourceforge.net/examples/pylab_examples/multicolored_line.html
Is it possible (and reasonably easy) to use a continuous colormap for the colored fill between two lines? For example, the color fill may change along x based on the difference between the two lines at x (or based on another set of data).
I found a solution to this problem. It builds on the brilliant but hacky solution of #Hooked. You create a 2D grid filed from lots of small boxes. It's not the fastest solution but it should be pretty flexible (more so than solutions which apply imshow to the patches).
import numpy as np
import pylab as plt
#Plot a rectangle
def rect(ax, x, y, w, h, c,**kwargs):
#Varying only in x
if len(c.shape) is 1:
rect = plt.Rectangle((x, y), w, h, color=c, ec=c,**kwargs)
ax.add_patch(rect)
#Varying in x and y
else:
#Split into a number of bins
N = c.shape[0]
hb = h/float(N); yl = y
for i in range(N):
yl += hb
rect = plt.Rectangle((x, yl), w, hb,
color=c[i,:], ec=c[i,:],**kwargs)
ax.add_patch(rect)
#Fill a contour between two lines
def rainbow_fill_between(ax, X, Y1, Y2, colors=None,
cmap=plt.get_cmap("Reds"),**kwargs):
plt.plot(X,Y1,lw=0) # Plot so the axes scale correctly
dx = X[1]-X[0]
N = X.size
#Pad a float or int to same size as x
if (type(Y2) is float or type(Y2) is int):
Y2 = np.array([Y2]*N)
#No colors -- specify linear
if colors is None:
colors = []
for n in range(N):
colors.append(cmap(n/float(N)))
#Varying only in x
elif len(colors.shape) is 1:
colors = cmap((colors-colors.min())
/(colors.max()-colors.min()))
#Varying only in x and y
else:
cnp = np.array(colors)
colors = np.empty([colors.shape[0],colors.shape[1],4])
for i in range(colors.shape[0]):
for j in range(colors.shape[1]):
colors[i,j,:] = cmap((cnp[i,j]-cnp[:,:].min())
/(cnp[:,:].max()-cnp[:,:].min()))
colors = np.array(colors)
#Create the patch objects
for (color,x,y1,y2) in zip(colors,X,Y1,Y2):
rect(ax,x,y2,dx,y1-y2,color,**kwargs)
# Some Test data
X = np.linspace(0,10,100)
Y1 = .25*X**2 - X
Y2 = X
g = np.exp(-.3*(X-5)**2)
#Plot fill and curves changing in x only
fig, axs =plt.subplots(1,2)
colors = g
rainbow_fill_between(axs[0],X,Y1,Y2,colors=colors)
axs[0].plot(X,Y1,'k-',lw=4)
axs[0].plot(X,Y2,'k-',lw=4)
#Plot fill and curves changing in x and y
colors = np.outer(g,g)
rainbow_fill_between(axs[1],X,Y1,Y2,colors=colors)
axs[1].plot(X,Y1,'k-',lw=4)
axs[1].plot(X,Y2,'k-',lw=4)
plt.show()
The result is,
Your solution is great and flexible ! In particular the 2D case is really nice. Such a feature could be added to fill_between maybe if the colors kwargs of the function would accept an array of the same length of x and y ?
Here is a simpler case for the 1D case using the fill_between function. It does the same but as it use trapezes instead of rectangle the result is smoother.
import matplotlib as mpl
import matplotlib.pyplot as plt
import numpy as np
from scipy.stats import norm
# Select a color map
cmap = mpl.cm.bwr
# Some Test data
npts = 100
x = np.linspace(-4, 4, npts)
y = norm.pdf(x)
z = np.sin(2 * x)
normalize = mpl.colors.Normalize(vmin=z.min(), vmax=z.max())
# The plot
fig = plt.figure()
ax = fig.add_axes([0.12, 0.12, 0.68, 0.78])
plt.plot(x, y, color="gray")
for i in range(npts - 1):
plt.fill_between([x[i], x[i+1]], [y[i], y[i+1]], color=cmap(normalize(z[i])))
cbax = fig.add_axes([0.85, 0.12, 0.05, 0.78])
cb = mpl.colorbar.ColorbarBase(cbax, cmap=cmap, norm=normalize, orientation='vertical')
cb.set_label("Sin function", rotation=270, labelpad=15)
plt.show()

Categories