Draw an isosurface with Python Plotly with the help of Pyvista
Plotly isosurfaces are relatively new. I have not tried yet in Python. I tried in R and I found them a bit slow. In this post I showed how to plot an isosurface in R Plotly with the help of the misc3d package: by constructing a triangular 3D mesh and using the mesh3d
type in Plotly. Here I will show how to do something similar in Python, with the help of the PyVista package. I don’t know whether this is faster than the built-in way to draw an isosurface in Plotly, but there’s at least one advantage: the one I mentioned here, that is, the possibility to clip the isosurface to a region (usually a ball). I don’t think this is possible with the Plotly way (I may be wrong).
Let’s go. I gonna draw the Togliatti surface again. First, the mesh clipped to a box:
from math import sqrt
import numpy as np
import pyvista as pv
import plotly.graph_objects as go
import plotly.io as pio # to save the graphics as html
def f(x, y, z):
return (
64
* (x - 1)
* (
**4
x- 4 * x**3
- 10 * x**2 * y**2
- 4 * x**2
+ 16 * x
- 20 * x * y**2
+ 5 * y**4
+ 16
- 20 * y**2
)- 5
* sqrt(5 - sqrt(5))
* (2 * z - sqrt(5 - sqrt(5)))
* (4 * (x**2 + y**2 - z**2) + (1 + 3 * sqrt(5)))**2
)# generate data grid for computing the values
= np.mgrid[(-5):5:250j, (-5):5:250j, (-4):4:250j]
X, Y, Z # create a structured grid
= pv.StructuredGrid(X, Y, Z)
grid # compute and assign the values
= f(X, Y, Z)
values "values"] = values.ravel(order = "F")
grid.point_data[# compute the isosurface f(x, y, z) = 0
= grid.contour(isosurfaces = [0])
isosurf = isosurf.extract_geometry() mesh0
Now we clip the Togliatti surface to a ball. The mesh we obtain is named mesh
. It is triangular: mesh.is_all_triangles
returns True
. If it were not, we would need to run mesh.triangulate
. Finally we extract the triangular faces from this mesh. The easiest way is to reshape the vector mesh.faces
to a matrix.
# surface clipped to the ball of radius 4.8, with the help of `clip_scalar`:
"dist"] = np.linalg.norm(mesh0.points, axis = 1)
mesh0[= mesh0.clip_scalar("dist", value = 4.8)
mesh # True
mesh.is_all_triangles # mesh.triangulate()
= mesh.points
points = mesh.faces.reshape(-1, 4) triangles
Now it’s a child game to plot the isosurface with Plotly:
= go.Figure(data=[
fig
go.Mesh3d(=points[:, 0],
x=points[:, 1],
y=points[:, 2],
z= [[0, 'gold'],
colorscale 0.5, 'mediumturquoise'],
[1, 'magenta']],
[# Intensity of each vertex, which will be interpolated and color-coded
= np.linspace(0, 1, len(mesh.points)),
intensity # i, j and k give the vertices of the triangles
= triangles[:, 1],
i = triangles[:, 2],
j = triangles[:, 3],
k = False
showscale
)
])
fig.show()