4. Interactive visualization of CAE results
Once a CAE solver has produced a result file, the analytical value of that data depends entirely on how effectively it can be inspected. A raw .vtu, .vtk, or .foam file contains nodal coordinates, connectivity, and field arrays, but no interpretation. This section focuses on turning those arrays into interactive contour plots, vector fields, and time-dependent animations inside a Streamlit application, using Plotly for lightweight 2D/3D rendering and PyVista for full volumetric mesh visualization.
Choosing the Right Rendering Engine
Plotly and PyVista serve different visualization needs, and selecting the wrong one leads to either poor performance or missing capability. Plotly excels at 2D contour maps, scatter-based vector overlays, and browser-native interactivity with hover tooltips, zoom, and pan. PyVista wraps VTK and handles unstructured meshes, iso-surfaces, streamlines, and volume rendering with GPU acceleration. A practical rule: if the result can be expressed as a structured grid slice or a 2D field, use Plotly; if it requires the actual mesh topology, use PyVista.
| Requirement | Plotly | PyVista |
|---|---|---|
| 2D contour / heatmap | Native, fast | Possible via screenshot |
| Unstructured mesh rendering | Limited | Full support |
| Vector field (quiver/arrows) | 2D and 3D quiver | Glyphs, streamlines |
| Time animation | Frame-based slider | Off-screen render loop |
| Browser interactivity | Built-in | Requires stpyvista or export |
Contour Plots from Nodal Results
Consider a steady-state thermal analysis of a rectangular heat sink base, 120 mm × 80 mm, meshed with 2 mm quadrilateral elements. The solver exports nodal temperatures ranging from 28 °C at the inlet edge to 94 °C near the chip footprint. To visualize this in Streamlit, load the nodal coordinates and temperature array, then reshape into a structured grid:
import numpy as np
import plotly.graph_objects as go
import streamlit as st
# x, y: 1D coordinate arrays (61 x 41 nodes)
# T: temperature array shape (41, 61)
X, Y = np.meshgrid(x, y)
fig = go.Figure(data=go.Contour(
z=T, x=x, y=y,
colorscale="Turbo",
contours=dict(showlabels=True, labelfont=dict(size=10)),
colorbar=dict(title="Temperature (°C)")
))
fig.update_layout(
xaxis_title="X (mm)", yaxis_title="Y (mm)",
yaxis_scaleanchor="x"
)
st.plotly_chart(fig, use_container_width=True)
The yaxis_scaleanchor="x" setting preserves aspect ratio, which is critical for physical interpretation — a distorted contour can mislead hotspot assessment. The Turbo colorscale provides perceptually uniform color transitions, preferable to rainbow for quantitative reading. Contour labels should be enabled when the audience needs to extract numeric values without hovering.
Vector Fields: Velocity and Flux
Vector fields appear in CFD (velocity), electromagnetics (E-field), and structural analysis (displacement). Plotly's go.Cone or go.Scatter with quiver-style line segments can render 2D vectors. For a 2D CFD channel flow with velocity components u and v on a coarse 20 × 10 grid, subsample the field to avoid visual clutter:
skip = 3 # plot every 3rd vector
fig = go.Figure()
fig.add_trace(go.Scatter(
x=X[::skip, ::skip].ravel(),
y=Y[::skip, ::skip].ravel(),
mode="markers",
marker=dict(
symbol="arrow", size=12, angle=np.degrees(
np.arctan2(V[::skip,::skip], U[::skip,::skip])
),
color=np.sqrt(U[::skip,::skip]**2 + V[::skip,::skip]**2),
colorscale="Blues", showscale=True,
colorbar=dict(title="|V| (m/s)")
)
))
For 3D vector fields, PyVista's glyph filter scales arrows by magnitude and orients them along the vector. Streamlines via streamlines_from_source reveal flow topology that individual arrows cannot convey. In a Streamlit context, wrap PyVista rendering with stpyvista to embed the interactive 3D scene directly in the browser.
Animating Transient Results
Transient analyses produce a sequence of result files, typically named result_0000.vtu through result_0100.vtu. Plotly animations use a frame list with a slider:
frames = []
for i, T_frame in enumerate(temperature_history):
frames.append(go.Frame(
data=[go.Contour(z=T_frame, x=x, y=y,
colorscale="Turbo", zmin=T_min, zmax=T_max)],
name=f"t={i*dt:.2f}s"
))
fig = go.Figure(data=frames[0].data, frames=frames)
fig.update_layout(
updatemenus=[dict(type="buttons", buttons=[
dict(label="Play", method="animate",
args=[None, dict(frame=dict(duration=80, redraw=True))])
])],
sliders=[dict(steps=[
dict(method="animate", label=f.name,
args=[[f.name], dict(mode="immediate")])
for f in frames
])]
)
Two details matter for correct interpretation. First, fix zmin and zmax across all frames; otherwise the colorscale rescales per frame and a growing hotspot appears static. Second, set a consistent duration that matches the physical time step ratio — if the solver used adaptive time stepping, map frame duration to actual simulation time, not frame index.
@st.cache_data. Sending 500 full-resolution frames to the browser will stall the session.
Result Interpretation Workflow
The diagram below summarizes how a CAE result file flows through the visualization pipeline, including the decision point between Plotly and PyVista and the key parameter controls at each stage.
Verification and Common Pitfalls
After building the visualization, verify it against the solver's own report. Check that the maximum contour value matches the solver's reported peak, and that the location of that peak corresponds to the expected physical hotspot. If the contour maximum is lower than the solver output, the field array may have been read with the wrong node ordering or the mesh was reshaped incorrectly.
Common failure symptoms and their causes:
- Mirrored or rotated contour: the y-axis is inverted relative to the solver coordinate system. Check whether the solver uses a bottom-left or top-left origin and set
yaxis_autorange="reversed"accordingly. - Animation flickers between frames: colorscale range is recalculated per frame. Fix
zmin/zmaxexplicitly. - Vector arrows all point the same direction: the angle array was computed in radians but passed as degrees, or
uandvwere swapped during array reshaping. - PyVista plot blank in browser: the mesh was not passed through
stpyvista, or the VTK off-screen rendering backend is unavailable in the deployment environment.
With contours, vectors, and animations working together in a single Streamlit page, the analyst can move from a global temperature map to a local velocity vector inspection to a transient hotspot evolution without leaving the browser. The next step is to make these views respond to user-selected result files and field variables, which is covered when we address dynamic data loading and widget-driven state management.