webpro / reveal-md

reveal.js on steroids! Get beautiful reveal.js presentations from any Markdown file
MIT License
3.77k stars 419 forks source link

Support mermaid diagrams #197

Closed amra closed 6 years ago

amra commented 6 years ago

Add support for mermaid - generating diagram and flowchart from text: https://mermaidjs.github.io/

Adding following code

~~~mermaid
sequenceDiagram
    Alice->>John: Hello John, how are you?
    John-->>Alice: Great!
~~~

would create such image mermaid-graph

amra commented 6 years ago

It would be similar to mermaid pandoc filter: https://github.com/raghur/mermaid-filter

webpro commented 6 years ago

Great idea! Looking forward to your PR.

stale[bot] commented 6 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.

Zachu commented 5 years ago

I've achieved mermaid diagrams by running reveal-md like this:

$ reveal-md presentation.md --scripts "mermaid.min.js,reveal-mermaid.js"

Where mermaid.min.js is minified mermaid of course, and reveal-mermaid.js is following:

window.setTimeout(function(){
  mermaids = document.getElementsByClassName('language-mermaid');
  let i=0;
  for (let el of mermaids) {
    newNode = document.createElement('div');
    newNode.innerHTML = el.innerHTML;
    newNode.classList.add('mermaid');
    newNode.id = 'mermaid-' + i;
    // This is temporarily placed to documentElement because styling issues.
    el.parentNode.setAttribute('data-id', newNode.id);
    document.documentElement.appendChild(newNode);
    i++;
  }

  // Generate mermaid diagrams
  try {
    mermaid.init({
      theme: 'dark',
      themeCSS: '.node rect { fill: red; }',
      flowchart:{
        useMaxWidth:false
      }
    }, '.mermaid');
  } catch (e) {}

  // Move new nodes in place of the old code
  while (mermaids.length > 0) {
    let el = mermaids.item(0);
    oldNode = el.parentNode;
    id = oldNode.getAttribute('data-id')
    console.log(id);
    oldNode.parentNode.replaceChild(document.getElementById(id), oldNode);
  }
  window.dispatchEvent(new Event('resize'));
}, 5000);

Afterwards mermaid diagrams are specified as a code block with syntax mermaid:

```mermaid
graph TD
node1 --> node2


I have no idea how to implement this built-in feature PR, but if someone does know how then this can be used if it helps :)
ukabu commented 4 years ago

If it would support the same language types as https://notable.md/, it would be awesome.

Zachu commented 4 years ago

If it would support the same language types as https://notable.md/, it would be awesome.

I suppose using the method described above and just grabbing the latest mermaid.min.js would give you the same Mermaid diagrams that project has. If this is what you meant. Or what do you mean by language types?

loranger commented 4 years ago

I've found a way to use the latest mermaid version (including themes) within reveal-md using reveal events and waiting for highlight.js has done its job :

mermaid.initialize({
    startOnLoad: false,
    theme: 'forest',
    flowchart:{
        useMaxWidth: false,
        htmlLabels: true
    }
});

Reveal.addEventListener('ready', function( event ) {
    (async() => {
        while(!window.hasOwnProperty("hljs"))
            await new Promise(resolve => setTimeout(resolve, 1000));

        document.querySelectorAll('.language-mermaid').forEach(function (item, index) {
            mermaidDiv = document.createElement("div");
            mermaidDiv.innerHTML = item.innerHTML;
            mermaidDiv.classList.add('mermaid')
            item.parentNode.replaceWith(mermaidDiv)
        });

        mermaid.init(undefined,document.querySelectorAll(".mermaid"));
    })();
});
quulah commented 4 years ago

I found that the above solution worked well enough, but broke when printing. I use Decktape for that.

Here's a tweaked version that runs the render on slide change:

Reveal.addEventListener('slidechanged', event => {
    if (event.currentSlide) {
        event.currentSlide.querySelectorAll('.language-mermaid').forEach(item => {
            mermaidDiv = document.createElement('div');
            mermaidDiv.innerHTML = item.innerHTML;
            mermaidDiv.classList.add('mermaid');

            item.parentNode.replaceWith(mermaidDiv);
        });

        mermaid.init(event.currentSlide, '.mermaid');
    }
});

Could possibly be simplified further to just the .init() call with a different selector query, but I found the added <div> useful. Thanks @loranger!

amra commented 4 years ago

@quulah I was not able to make your script running. Could you post a complete example?

quulah commented 4 years ago

@amra Sure!

I put that in a separate file reveal-mermaid.js. That file is passed on to reveal-md as a custom script in addition to mermaid.min.js itself.

A snippet from reveal-md.json config:

{
    "scripts": "lib/mermaid.min.js,lib/reveal-mermaid.js"
}

The reveal-mermaid.js file in full in it's current form:

mermaid.initialize({
    startOnLoad: false,
    theme: 'dark',
});

Reveal.addEventListener('slidechanged', event => {
    if (event.currentSlide) {
        event.currentSlide.querySelectorAll('.language-mermaid').forEach(item => {
            mermaidDiv = document.createElement('div');
            mermaidDiv.innerHTML = item.innerHTML;
            mermaidDiv.classList.add('mermaid');

            item.parentNode.replaceWith(mermaidDiv);
        });

        mermaid.init(event.currentSlide, '.mermaid');
    }
});

After that the Mermaid blocks in should render correctly. For the Markdown:


```mermaid
graph...

Hopefully that helps!

amra commented 4 years ago

Thanks. It certainly did helped. Unfortunately I still cannot make this working and it ends with an error Syntax error in graph.

The code within the Reveal.addEventListener is running after Reveal Highlight Plugin. That plugin runs first and changes the graph source.

For example from

sequenceDiagram
  Alice->>John: Hello John, how are you?
  John-->>Alice: Great!

To

"<h2 id="sequence-diagram">Sequence diagram</h2>
<pre><code class="mermaid hljs xl">
sequenceDiagram
    A<span class="hljs-function"><span class="hljs-title">lice</span>-&gt;</span>&gt;John: Hello John, how are you?
    J<span class="hljs-function"><span class="hljs-title">ohn</span>--&gt;</span>&gt;Alice: Great!
</code></pre>

Setting logging level helped a lot:

mermaid.initialize({
    logLevel: 3,
});
amra commented 4 years ago

Here is my current version. It renders all graphs at startup. Reveal.addEventListener('slidechanged', ...) has issue with not rendering first slide. Also it triggered rendering of everything on each page.

My version has problems that it doesn't renders fully some graphs, which are on later slides. If you move to that particular slides and refresh the page, it renders it correctly. Also there is difference between Chrome and Firefox (multi time refresh needed). I do not know, why this happens. If you have a hint, please make a comment.

reveal-mermaid.js

mermaid.initialize({
    startOnLoad: false,
    theme: 'dark',
    logLevel: 3,
});

Reveal.addEventListener('ready', event => {
    var graphs = document.getElementsByClassName("mermaid");
    graphs.forEach((item, index) => {
        var mermaidDiv = document.createElement('div');
        mermaidDiv.innerHTML = item.innerText; // Ignores html elements added by revealjs highlight plugin.
        mermaidDiv.classList.add('mermaid');
        item.replaceWith(mermaidDiv);
    })
    mermaid.init(document, '.mermaid');
});

reveal-md.json

{
    "scripts": "lib/mermaid.min.js,lib/reveal-mermaid.js"
}
amra commented 4 years ago

I did update reveal-mermaid.js. All previous issues are solved.

mermaid.initialize({
    startOnLoad: false,
    theme: 'dark',
    logLevel: 3,
});

Reveal.addEventListener('ready', event => asyncMermaidRender(event));

async function asyncMermaidRender(event) {
    var graphs = document.getElementsByClassName("mermaid");
    graphs.forEach((item, index) => {
        let graphCode = item.innerText.trim(); //trim() becase of gantt, class and git diagram
        let mermaidDiv = document.createElement('div');
        mermaidDiv.classList.add('mermaid');
        mermaidDiv.setAttribute("data-processed", "true");

        try {
            // item.innerText ignores html elements added by revealjs highlight plugin.
            mermaid.mermaidAPI.render('theGraph' + index, graphCode, function(svgCode) {
                mermaidDiv.innerHTML = svgCode;
            });

            let parentDiv = document.createElement('div');
            parentDiv.appendChild(mermaidDiv);
            item.parentNode.parentNode.insertBefore(parentDiv, item.parentNode);
            item.parentNode.remove();
        }
        catch(err) {
            console.log("Cannot render mermaid diagram " + index + "\n" + graphCode);
            console.log(err.message);
        }
    })
}

reveal-md.json stays the same

{
    "scripts": "lib/mermaid.min.js,lib/reveal-mermaid.js"
}
dmastag commented 4 years ago

I just wanted to give a big shout out to @amra

For still trying 2 years after asking the question

Also tried your repo at https://github.com/amra/reveal-md-scripts

And got it working based on that

jalmeter commented 1 year ago

@amra - Thank you!

I finally got it working after making this change:

Array.from(graphs).forEach((item, index) => {

webpro commented 10 months ago

Y'all might be interested in https://github.com/webpro/reveal-md/releases/tag/6.1.0-next.0

See https://github.com/webpro/reveal-md/tree/main/demo-mermaid for example usage. Feedback welcome!

maurerle commented 8 months ago

This looks super nice! However, I could not get the graph to render using the demo-mermaid and

reveal-md slides.md

Furthermore I was wondering if this is also affected by this issue: https://github.com/mermaid-js/mermaid/issues/1824

or do you have a workaround for it?

webpro commented 6 months ago

If there are any issues with mermaid, please open a new issue for visibility and tracking. Also make sure to use the latest version of reveal-md