Search code examples
javascriptreactjsperformancefrontendgraphviz

How to render a large graph smoothly as a react.js component?


I want to visualize large graphs, but the UI freezes and the V8 engine throws out-of-memory errors after about 25 seconds.

Ideally, the graph rendering should be smooth and non-blocking.

My Graph.js file below assigns the graph dot string to the src variable when the "Set" button is pressed and attempts to display the graph when the "Render" button is pressed.

enter image description here

import React, { useRef, useState } from 'react';
import Viz from 'viz.js';
import { Module, render } from 'viz.js/full.render.js';

const largegraph = "digraph { " + Array.from(Array(1000).keys()).map(i => "a" + i + " [label=" + i + "];").join(" ") + Array.from(Array(10000).keys()).map(i => "a" + Math.floor(Math.random() * 1000) + " -> " + "a" + Math.floor(Math.random() * 1000) + ";").join(" ") + " }";

function GraphvizGraph(props) {
    const container = useRef(null);
    const [src, setSrc] = useState(props.src);

    const renderGraph = (src) => {
        const viz = new Viz({ Module, render });
        viz.renderSVGElement(src)
            .then(function (element) {
                container.current.innerHTML = "";
                container.current.appendChild(element);
            });
    }

    console.log(largegraph)

    return (
        <div>
            <button onClick={() => setSrc(largegraph)}>Set</button>
            <button onClick={() => renderGraph(src)}>Render</button>
            <div ref={container} />
        </div>
    );
}

export default GraphvizGraph;

After setting up the large graph, clicking the "Render" button fails to display anything and causes the UI to freeze. After a few seconds, Chrome throws an exception. Is there a way to fix this issue and successfully visualize smoothly the graph?

full.render.js:24 Cannot enlarge memory arrays. Either (1) compile with  -s TOTAL_MEMORY=X  with X higher than the current value 16777216, (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or (4) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 
abort @ full.render.js:24
react-dom.development.js:4161 [Violation] 'click' handler took 23773ms
[Violation] 'click' handler took 23773ms

Uncaught (in promise) abort("Cannot enlarge memory arrays. Either (1) compile with  -s TOTAL_MEMORY=X  with X higher than the current value 16777216, (2) compile with  -s ALLOW_MEMORY_GROWTH=1  which allows increasing the size at runtime but prevents some optimizations, (3) set Module.TOTAL_MEMORY to a higher value before the program runs, or (4) if you want malloc to return NULL (0) instead of this abort, compile with  -s ABORTING_MALLOC=0 "). Build with -s ASSERTIONS=1 for more info.

Solution

  • I did something similar to what was suggested earlier on, just using hpcc-js/wasm/graphviz which seems a more established solution that viz.js mentioned by OP. Yet, still seeking for the best practice of doing this.

    import './App.css';
    import { Graphviz } from "@hpcc-js/wasm/graphviz";
    
    import React from 'react';
    
    function Graph(props) {
      const [svg, setSvg] = React.useState(null);
      React.useEffect(() => {
        async function name(params) {
          const graphviz = await Graphviz.load();
    
          const dot = `digraph G5 {
            edge [fontcolor=black fontsize="10" fontname=Arial];
            subgraph referenceNodes {
                node [style="filled" fontcolor=white fontsize="12" fontname=Arial];
                subgraph person {
                    node [fillcolor="blue"];
                    person_JaneDoe [label="<person>\nJane Doe"];
                    person_WendyDoe [label="<person>\nWendy Doe"];
                    person_StacyDoe [label="<person>\nStacy Doe"];
                }
                subgraph profession {
                    node [fillcolor="darkGreen"];
                    profession_Programmer [label="<profession>\nProgrammer"];
                    profession_Writer [label="<profession>\nWriter"];
                }
                subgraph vehicle {
                    node [fillcolor="purple"];
                    vehicle_Subaru [label="<vehicle>\nSubaru"];
                    vehicle_Ford [label="<vehicle>\nFord"];
                }
            }
            subgraph dataNodes {
                node [shape=box style="filled" fillcolor=yellow fontcolor=black fontsize="12" fontname=Arial];
                value_age24_1 [label="<integer>\n24";];
                value_age24_2 [label="<integer>\n24";];
                value_age21_1 [label="<integer>\n21";];
            }
            subgraph JaneDoe {
                person_JaneDoe -> person_WendyDoe [label=" has sister" dir=both];
                person_JaneDoe -> person_StacyDoe [label=" has sister" dir=both];
                person_JaneDoe -> profession_Programmer [label=" is a"];
                person_JaneDoe -> vehicle_Subaru [label=" owns a"];
                person_JaneDoe -> value_age24_1 [label="has age"];
            }
            subgraph WendyDoe {
                person_WendyDoe -> profession_Writer [label=" is a"];
                person_WendyDoe -> vehicle_Subaru [label=" owns a"];
                person_WendyDoe -> value_age24_2 [label="has age"];
                person_WendyDoe -> person_StacyDoe [label=" has sister" dir=both];
            }
            subgraph StacyDoe {
                person_StacyDoe -> profession_Writer [label="is a"]
                person_StacyDoe -> vehicle_Ford [label="owns a"]
                person_StacyDoe -> value_age21_1 [label="has age"];
            }
    }`;
    
          const svg_tmp = graphviz.dot(dot);
          setSvg(svg_tmp);
          console.log(graphviz.version(), svg_tmp);
        } name();
      }, [])
    
      return <div>
        <div dangerouslySetInnerHTML={{ __html: svg }} />
      </div>
    
    }
    
    function App() {
      return (
    
        <div className="App">
          <Graph />
        </div>
      );
    }
    
    export default App;