mermaid-js / mermaid

Generation of diagrams like flowcharts or sequence diagrams from text in a similar manner as markdown
https://mermaid.js.org
MIT License
71.53k stars 6.48k forks source link

Chart does not render if parent element is not displayed #1846

Open GiovanH opened 3 years ago

GiovanH commented 3 years ago

To Reproduce

Render a standard mermaid diagram, except let the mermaid element be nested in an element with the style display: none.

If the container element is set to display: none when the mermaid script runs, the SVG will be blank when that element is set to visible.

Example, where the chart is in a togglable parent container: image

If the container element is set to visible before the mermaid script runs, it renders as expected.

Tested with a gantt chart:

<script src="https://unpkg.com/mermaid@8.8.0/dist/mermaid.min.js"></script>
...
<pre class="mermaid mermaid">gantt
...
</pre>

Desktop (please complete the following information):

GiovanH commented 3 years ago

I should also note that in the fail case the expected fallback text isn’t rendered either. Normally the text renders but is quickly replaced by the graph. In the issue described, there is a blank graph that takes up room and hides the text, but is empty.

jgreywolf commented 3 years ago

In this specific example, I wonder if you could call the initialize method when setting visibility...

GiovanH commented 3 years ago

In this specific example, I wonder if you could call the initialize method when setting visibility...

If I understand what you're suggesting, that would require the "Show" button to have a reference to any mermaid objects inside it. In this case, the show/hide box contains arbitrary HTML, and even nested show/hide boxes, so that doesn't help in this case. This might be a workaround if you wanted to adjust the visibility of graphs specifically, though.

GiovanH commented 3 years ago

Interesting behavior to note: The mermaid graph definitely does get processed, and builds an SVG with a defined width and viewbox. It's just empty.

image

GiovanH commented 3 years ago

Another possible workaround would be to re-render errored mermaid elements when element visibility is changed, but mermaid thinks the SVG rendered correctly and marked the DOM as processed, as seen above. (Even though element.innerHTML = svgCode raises an error, since the rendered SVG isn't valid)

ShaunaGordon commented 3 years ago

I just got bitten by this issue, myself. 8.9.0 results in "syntax error" errors by Mermaid if it tries to render in non-visible elements. After some attempts to try to get a version that doesn't have the issue (I swear, it was working last night, and stopped this morning... 😒 ), I finally figured out a workaround that seems to be stable.

This is admittedly somewhat quick and dirty, and could probably be refined, but it works. I built this in the context of Reveal.js, so fragment starts invisible and then adds a visible class to the element when it needs to become visible, so this particular code looks for the addition of the visible class. That bit would need to be adapted to the specific context.

I feel like there could/should be some type of render function in the API that uses the internal ID generation logic (or that logic should be its own function that's then exposed in the API), and/or an API call that tells Mermaid "render this element the way you would if you had autostarted," but that doesn't seem to be available that I've found.

<div class="mermaid fragment">
graph
    A-->B
</div>

<script>
    // Don't try to render immediately
    mermaid.initialize({startOnLoad:false});
    mermaid.mermaidAPI.initialize();

    // Watch the element's attributes for changes
    let mermaidObserverOpts = {
        attributes: true
    };

    // Find all Mermaid elements and act on each
    document.querySelectorAll('.mermaid').forEach(function(el) {
        let observer = new MutationObserver((entries) => {
            let target = entries[0].target;

            // Act only when the element becomes visible
            if(target.classList.contains('visible')) {
                // Get the contents of the Mermaid element
                let html = el.textContent;

                // Generate a unique-ish ID so we don't clobber existing graphs
                // This is definitely quick and dirty and could be improved to 
                // avoid collisions when many charts are used
                let id = 'graph-' + Math.floor(Math.random() * Math.floor(1000));

                // Actually render the chart
                mermaid.mermaidAPI.render(id, html, content => {
                    el.innerHTML = content;
                });

                // Disconnect the observer, since the chart is now on the page. 
                // There's no point in continuing to watch it
                observer.disconnect();
            }
        });

        observer.observe(el, mermaidObserverOpts);
    });
</script>
frainfreeze commented 1 year ago

Similar happens to the popular FullCalendar library, for example when putting calendar object into a tab with display: none one has to "render" the calendar using appropriate .render() function when displaying it.

Is there any solution or workaround to this issue in mermaid at the moment?