@@ -74,29 +74,43 @@ export function createContourLineOptions(options = {}) {
7474
7575export 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
8892export 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+
277375function 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