How to use Mermaid with Panel

From Add Mermaid pane · Issue #2733 · holoviz/panel · GitHub

import param
import panel as pn
from panel.custom import JSComponent

pn.extension()


class MermaidDiagram(JSComponent):

    value = param.String(default="graph TD; A-->B; A-->C; B-->D; C-->D;")

    _esm = """
    import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';

    mermaid.initialize({ startOnLoad: false });

    export async function render({ model, el }) {
      const { svg } = await mermaid.render('graphDiv', model.value);
      el.innerHTML = svg; // Set the rendered SVG in the container
    }
    """

    _importmap = {
        "imports": {
            "mermaid": "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs"
        }
    }

diagram = MermaidDiagram(
    value=(
        """
        graph LR
            A --- B
            B-->C[fa:fa-ban forbidden]
            B-->D(fa:fa-spinner);
        """
    )
)

diagram.show()
2 Likes

I played a bit around with it. Adding the ability to update the diagram and the config.

Try on PY.CAFE

import panel as pn
import param
from panel.custom import JSComponent
from panel.widgets import WidgetBase

pn.extension()


class MermaidConfiguration(pn.viewable.Viewer, WidgetBase):
    value = param.Dict(constant=True)

    look = param.Selector(default="classic", objects=["classic", "handDrawn"])
    theme = param.Selector(
        default="default", objects=["default", "forest", "dark", "neutral"]
    )

    @param.depends("look", "theme", watch=True, on_init=True)
    def _update_value(self):
        with param.edit_constant(self):
            self.value = {"look": self.look, "theme": self.theme}

    def __panel__(self):
        return pn.Column("### Mermaid Configuration", self.param.look, self.param.theme)


class MermaidDiagram(JSComponent):
    """A MermaidDiagram Pane

    Example:

    diagram = MermaidDiagram(
        object=(
            '''
            graph LR
                A --- B
                B-->C[fa:fa-ban forbidden]
                B-->D(fa:fa-spinner);
            '''
        )
    )


    """

    object = param.String(default="")
    configuration = param.Dict({}, allow_refs=True)

    _esm = """
import mermaid from 'mermaid';

async function getSVG(object) {
    if (object) {
        const { svg } = await mermaid.render('graphDiv', object);
        return svg;
    } else {
        return null;
    }
}

export async function render({ model, el }) {
    function update_configuration(){
        const config = {...model.configuration, startOnLoad: false};
        mermaid.initialize(config);
    }
    update_configuration();
    
    async function update_object() {
        el.innerHTML = await getSVG(model.object);
    }
    await update_object();
    
    model.on('object', update_object);
    model.on('configuration', ()=>{update_configuration();update_object()});
}
    """

    _importmap = {
        "imports": {
            "mermaid": "https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs"
        }
    }


examples = {
    "Default": """
graph LR
    A --- B
    B-->C[fa:fa-ban forbidden]
    B-->D(fa:fa-spinner);
""",
    "Architecture": """
architecture-beta
    group api(cloud)[API]

    service db(database)[Database] in api
    service disk1(disk)[Storage] in api
    service disk2(disk)[Storage] in api
    service server(server)[Server] in api

    db:L -- R:server
    disk1:T -- B:server
    disk2:T -- B:db
""",
}

config = MermaidConfiguration()
diagram = MermaidDiagram(
    configuration=config, styles={"border": "1px solid black"}, width=500
)

example = pn.widgets.Select(options=examples, name="Example")


@param.depends(example, watch=True)
def update(object):
    diagram.object = object


update(example.value)

pn.Column(config, example, diagram).servable()
1 Like

We should make a pane out of this

1 Like

I’ve released awesome-panel/panel-mermaid.

pip install panel-mermaid

1 Like