Boris
Boris

Reputation: 471

How to render a mermaid flowchart dynamically?

I am using the mermaid library to build flowcharts. The principle of its work is that inside a block there is a pseudocode - commands of a special syntax, on the basis of which the flowchart is built in the block.

I want to be able to change the contents of the block dynamically, and the script rebuilds the block diagram every time.

How should I set up initialization? Perhaps I should add some callback function in the settings?

I initialized in this way:

mermaid.init({/*what setting parameters should be here?*/}, ".someClass"/*selector*/);

but the script doesn’t render any new commands. It only renders the commands that existed at the moment the document was loaded.

In other words, I want to edit a flowchart online.

function edit() {
  const new_mermaid = document.createElement("div");
  new_mermaid.classList.add("mermaid");
  new_mermaid.classList.add(".someClass");
  /*new_mermaid.innerHTML =
            `graph TD
   1[point 1] --> 2[point 2]`;*/
  // it doesn't work when I append the new   element dynamically! 
  new_mermaid.innerHTML = document.querySelector(".mermaid").innerHTML;
  // it works always.
  document.body.append(new_mermaid);
  /* document.querySelector(".mermaid").innerHTML = 
            `
    graph TD
    A --> B`*/
  // it doesn’t work with event listener
}
edit(); // it works
document.body.addEventListener("click", edit)
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
<script>
  // how to do it correctly?
  mermaid.init({
    noteMargin: 10
  }, ".someClass");
</script>

<div class="mermaid someClass">
  graph TD
  1--> 2
  3 --> 2
  2 --> 1
</div>

Upvotes: 14

Views: 16056

Answers (3)

Nolca
Nolca

Reputation: 45

Refer: https://mermaid.js.org/config/usage.html#api-usage

My minimal implementation:

<script>
  VAR1 = 1;
  VAR2 = 2;
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/mermaid/10.9.0/mermaid.min.js"></script>
<button onclick="mermaidDraw()">render</button>
<textarea>flowchart LR
A["`var1=${VAR1}`"] -->|"{VAR2}"| B("{VAR3}")</textarea>
<div class="mermaid"></div>
<pre id="err"></pre>
<script>
  mermaid.initialize({
    startOnLoad: true
  });
  eleM = document.querySelector('.mermaid');
  eleE = document.querySelector('#err');
  setTimeout(mermaidDraw, 200);
  async function mermaidDraw() {
    try {
      graphDefinition = await mermaidEval('LocalFile.md');
      const {
        svg
      } = await mermaid.render('graphDiv', graphDefinition);
      eleM.innerHTML = svg;
    } catch (err) {
      if (err instanceof ReferenceError) {
        varname = err.message.split(' ')[0];
        window[varname] = varname;
        setTimeout(mermaidDraw, 0);
      }
      console.error(err);
      eleE.insertAdjacentHTML('beforeend', `🚫${err.message}\n`);
    }
  };
  async function mermaidEval(url) {
    //const response = await fetch(url);
    //text = await response.text();
    text = document.querySelector('textarea').value;
    if (!text.match(/^[a-zA-Z]/)) {
    // markdown ```mermaid, remove first and last line
      text = text.split('\n').slice(1, -1).join('\n');
    }
    text = text.replace(/"`.*?`"/g, function(match) {
      return eval(match.slice(1, -1));
    });
    text = text.replace(/"\{.*?\}"/g, function(match) {
      return eval(match.slice(1, -1));
    });
    return text;
  }
</script>

Use "`Text ${varName}`" or "{varName}" in your mermaid raw content
Try this example in textarea in Run code snippet:

flowchart LR
A["`var1=${VAR1}`"] -->|"{VAR2}"| B("{VAR1+VAR2}")

Upvotes: 1

The gates of Zion
The gates of Zion

Reputation: 311

Thanks for the answer above. I would like to add a react wrapper to the answer scope for whoever using react:

import React, {Component} from "react";
import mermaid from "mermaid";

export default class Mermaid extends Component {
    constructor(props){
        super(props)
        this.state={
            chart: this.props.chart || ""
        }
        mermaid.initialize({
            mermaid : {
                startOnLoad: false,
            }
        })
        this.mermaidRef = React.createRef()
    }
    mermaidUpdate(){

        var cb = function (svgGraph) {
           this.mermaidRef.current.innerHTML = svgGraph
        };
        //console.log("this.state.chart", this.state.chart)
        mermaid.mermaidAPI.render('id0', this.state.chart, cb.bind(this));
    }
    componentDidMount(){
        this.mermaidUpdate()
    }
    componentDidUpdate(prevProps, prevState) {
        //console.log("Mermiad prevProps.chart", prevProps.chart)
        if (this.props.chart !== prevProps.chart) {
          this.setState({chart:this.props.chart},()=>{
            this.mermaidUpdate()
          })
        }
      }
    render() {
      var outObj = (
        <div 
            ref={this.mermaidRef}
            className="mermaid"
        >
            {this.state.chart}
        </div>
        )
      return outObj
    }
  }

Upvotes: 3

Boris
Boris

Reputation: 471

It seems, I know the answer. Look at the solution below:

  document.querySelector("button").addEventListener("click", (e) => {
  const output = document.querySelector(".flowchart");
  if (output.firstChild !== null) {
    output.innerHTML = "";
  }
  const code = document.querySelector(" textarea").value.trim();
  let insert = function (code) {
    output.innerHTML = code;
  };
  mermaid.render("preparedScheme", code, insert);
});
   <script src="https://unpkg.com/[email protected]/dist/mermaid.min.js"></script>

<p>Input your data:</p>
<div class="input">
  <textarea style="width:300px; height:200px"></textarea>
  <br>
  <button>render</button>
</div>
<div>
  <p>output:</p>

  <div class="render_container" style = "width:300px; height:200px; border:thin solid silver" >
      <div class="flowchart"></div>
    </div>
  </div>

Upvotes: 15

Related Questions