Skip to content

Commit c509948

Browse files
committed
feat: implemented both vtk js and plotly.
1 parent 434ef04 commit c509948

4 files changed

Lines changed: 136 additions & 21 deletions

File tree

package.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,12 @@
6666
},
6767
"peerDependencies": {
6868
"mathjs": "^11.12.0",
69-
"@kitware/vtk.js": "^32.12.0"
69+
"@kitware/vtk.js": "^32.12.0",
70+
"plotly.js": "^2.35.3"
71+
},
72+
"peerDependenciesMeta": {
73+
"@kitware/vtk.js": { "optional": true },
74+
"plotly.js": { "optional": true }
7075
},
7176
"devDependencies": {
7277
"@kitware/vtk.js": "^32.12.0",

rollup.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,5 @@ export default {
3939
}),
4040
terser(),
4141
],
42-
external: (id) => id === "@kitware/vtk.js" || id.startsWith("@kitware/vtk.js/"),
42+
external: (id) => id === "@kitware/vtk.js" || id.startsWith("@kitware/vtk.js/") || id === "plotly.js",
4343
};

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,6 @@ export {
1818
transformSolverOutputToVtkData,
1919
transformSolverOutputToVTP,
2020
transformSolverOutputToMLBuffers,
21-
} from "./visualization/vtkSolutionScript.js";
21+
} from "./visualization/vizSolutionScript.js";
2222
export { FEAScriptWorker } from "./workers/workerScript.js";
2323
export const printVersion = "0.2.0";
Lines changed: 128 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -74,29 +74,43 @@ export function createContourLineOptions(options = {}) {
7474

7575
export async function plotSolution(model, result, plotType, plotDivId, renderOptions = {}) {
7676
console.time("plottingTime");
77-
78-
const meshDimension = model.meshConfig.meshDimension;
79-
const meshData = prepareMesh(model.meshConfig);
80-
const vtkData = await transformSolverOutputToVtkData(model, result, meshData, {
81-
mode: meshDimension === "1D" && plotType === "line" ? "line" : "surface",
82-
});
83-
84-
await renderVtkScene(vtkData, plotDivId, model.solverConfig, plotType, renderOptions);
77+
const backend = renderOptions.backend ?? "vtk";
78+
79+
if (backend === "plotly") {
80+
await renderPlotlyScene(model, result, plotType, plotDivId, false, renderOptions);
81+
} else {
82+
const meshDimension = model.meshConfig.meshDimension;
83+
const meshData = prepareMesh(model.meshConfig);
84+
const vtkData = await transformSolverOutputToVtkData(model, result, meshData, {
85+
mode: meshDimension === "1D" && plotType === "line" ? "line" : "surface",
86+
});
87+
await renderVtkScene(vtkData, plotDivId, model.solverConfig, plotType, renderOptions);
88+
}
8589
console.timeEnd("plottingTime");
8690
}
8791

8892
export async function plotInterpolatedSolution(model, result, plotType, plotDivId, renderOptions = {}) {
8993
console.time("plottingTime");
90-
91-
if (model.meshConfig.meshDimension !== "2D" || plotType !== "contour") {
92-
await plotSolution(model, result, plotType, plotDivId, renderOptions);
93-
console.timeEnd("plottingTime");
94-
return;
94+
const backend = renderOptions.backend ?? "vtk";
95+
96+
if (backend === "plotly") {
97+
if (model.meshConfig.meshDimension === "2D" && plotType === "contour") {
98+
const meshData = prepareMesh(model.meshConfig);
99+
const grid = await buildInterpolatedGridValues(model, result, meshData);
100+
await renderPlotlyScene(model, result, plotType, plotDivId, true, renderOptions, grid);
101+
} else {
102+
await renderPlotlyScene(model, result, plotType, plotDivId, false, renderOptions);
103+
}
104+
} else {
105+
if (model.meshConfig.meshDimension !== "2D" || plotType !== "contour") {
106+
await plotSolution(model, result, plotType, plotDivId, renderOptions);
107+
console.timeEnd("plottingTime");
108+
return;
109+
}
110+
const meshData = prepareMesh(model.meshConfig);
111+
const interpolatedVtkData = await buildInterpolatedVtkData(model, result, meshData);
112+
await renderVtkScene(interpolatedVtkData, plotDivId, model.solverConfig, `${plotType}-interpolated`, renderOptions);
95113
}
96-
97-
const meshData = prepareMesh(model.meshConfig);
98-
const interpolatedVtkData = await buildInterpolatedVtkData(model, result, meshData);
99-
await renderVtkScene(interpolatedVtkData, plotDivId, model.solverConfig, `${plotType}-interpolated`, renderOptions);
100114
console.timeEnd("plottingTime");
101115
}
102116

@@ -274,6 +288,90 @@ async function addContourLinesToRenderer(renderer, vtkData, scalarRange, contour
274288
renderer.addActor(contourActor);
275289
}
276290

291+
async function renderPlotlyScene(model, result, plotType, plotDivId, interpolated, renderOptions = {}, prebuiltGrid = null) {
292+
if (typeof document === "undefined") {
293+
errorLog("Plotly visualization requires a browser environment");
294+
return;
295+
}
296+
const { default: Plotly } = await import("plotly.js");
297+
const { nodesXCoordinates, nodesYCoordinates } = result.nodesCoordinates;
298+
const meshDimension = model.meshConfig.meshDimension;
299+
const plotWidth = typeof window !== "undefined" ? Math.min(window.innerWidth, 600) : 600;
300+
const colorScale = renderOptions.colorScale ?? {};
301+
const plotlyColorscale = colorScale.reverse ? "RdBu" : "RdBu_r";
302+
const scalarBarTitle = colorScale.scalarBarTitle ?? "Solution";
303+
304+
let traces, layout;
305+
306+
if (meshDimension === "1D") {
307+
const scalars = extractScalarSolution(result.solutionVector, nodesXCoordinates.length);
308+
traces = [{
309+
x: Array.from(nodesXCoordinates),
310+
y: Array.from(scalars),
311+
mode: "lines",
312+
type: "scatter",
313+
line: { color: "royalblue" },
314+
}];
315+
layout = {
316+
title: `${plotType} plot - ${model.solverConfig}`,
317+
xaxis: { title: "x" },
318+
yaxis: { title: "Solution" },
319+
width: plotWidth,
320+
};
321+
} else if (interpolated && prebuiltGrid) {
322+
const { visNodesX, visNodesY, visSolution, insideMask, minX, minY, deltaX, deltaY, lengthX, lengthY } = prebuiltGrid;
323+
const xVals = Array.from({ length: visNodesX }, (_, i) => minX + i * deltaX);
324+
const yVals = Array.from({ length: visNodesY }, (_, i) => minY + i * deltaY);
325+
// Plotly contour expects z[iy][ix]
326+
const zGrid = Array.from({ length: visNodesY }, (_, iy) =>
327+
Array.from({ length: visNodesX }, (_, ix) => {
328+
const idx = ix * visNodesY + iy;
329+
return insideMask[idx] ? visSolution[idx] : null;
330+
})
331+
);
332+
const showContourLines = renderOptions.contourLines?.enabled ?? true;
333+
traces = [{
334+
x: xVals,
335+
y: yVals,
336+
z: zGrid,
337+
type: "contour",
338+
colorscale: plotlyColorscale,
339+
colorbar: { title: scalarBarTitle },
340+
contours: { coloring: showContourLines ? "fill" : "heatmap", showlines: showContourLines },
341+
ncontours: renderOptions.contourLines?.numberOfContours ?? 12,
342+
}];
343+
layout = {
344+
title: `${plotType} plot (interpolated) - ${model.solverConfig}`,
345+
xaxis: { title: "x", scaleanchor: "y", scaleratio: 1 },
346+
yaxis: { title: "y" },
347+
width: plotWidth,
348+
height: plotWidth * (lengthY / lengthX),
349+
};
350+
} else {
351+
const scalars = extractScalarSolution(result.solutionVector, nodesXCoordinates.length);
352+
traces = [{
353+
x: Array.from(nodesXCoordinates),
354+
y: nodesYCoordinates ? Array.from(nodesYCoordinates) : undefined,
355+
mode: "markers",
356+
type: "scatter",
357+
marker: {
358+
color: Array.from(scalars),
359+
colorscale: plotlyColorscale,
360+
showscale: true,
361+
colorbar: { title: scalarBarTitle },
362+
},
363+
}];
364+
layout = {
365+
title: `${plotType} plot - ${model.solverConfig}`,
366+
xaxis: { title: "x", scaleanchor: "y", scaleratio: 1 },
367+
yaxis: { title: "y" },
368+
width: plotWidth,
369+
};
370+
}
371+
372+
await Plotly.newPlot(plotDivId, traces, layout, { responsive: true });
373+
}
374+
277375
function reverseColorMapPreset(preset, reverse) {
278376
if (!reverse || !preset?.RGBPoints?.length) return preset;
279377
const rgb = preset.RGBPoints;
@@ -450,7 +548,7 @@ function buildVTPString(vtkData) {
450548
].join("\n");
451549
}
452550

453-
async function buildInterpolatedVtkData(model, result, meshData) {
551+
async function buildInterpolatedGridValues(model, result, meshData) {
454552
const { nodesXCoordinates, nodesYCoordinates } = result.nodesCoordinates;
455553
const basisFunctions = new BasisFunctions({
456554
meshDimension: model.meshConfig.meshDimension,
@@ -553,6 +651,18 @@ async function buildInterpolatedVtkData(model, result, meshData) {
553651
}
554652
}
555653

654+
return {
655+
visNodesX, visNodesY,
656+
minX, minY, deltaX, deltaY, lengthX, lengthY,
657+
visNodeXCoordinates, visNodeYCoordinates,
658+
visSolution, insideMask,
659+
};
660+
}
661+
662+
async function buildInterpolatedVtkData(model, result, meshData) {
663+
const grid = await buildInterpolatedGridValues(model, result, meshData);
664+
const { visNodesX, visNodesY, minX, minY, deltaX, deltaY, visNodeXCoordinates, visNodeYCoordinates, visSolution, insideMask } = grid;
665+
556666
const points = buildPointsArray(visNodeXCoordinates, visNodeYCoordinates);
557667
const cells = buildStructuredGridCells(visNodesX, visNodesY, insideMask);
558668
const polyData = await buildPolyData(points, visSolution, cells, "surface");

0 commit comments

Comments
 (0)