Dynamically Generating Mermaid Diagrams

I recently started using the mermaid-tw5 plugin to diagram relationships between tiddlers. In my case, it was to show data flows between software components. To do this effectively, the diagrams needed to be generated dynamically. It would not be practical to type out the mermaid text.

The plugin enables embedding mermaid diagrams and supporting the syntax. I found it challenging, however, to pass transcluded content for dynamically generated diagrams. Having gotten it working, I thought I would share my experience. No doubt there are better ways to do some of this, but hopefully it will help others.

Step 1 - Use a macro to generate the mermaid widget and populate its text from a macro parameter

The widget does not tolerate wikitext at all, so all dynamic TW activity must be done before the mermaid widget executes. The below macro takes a body argument, which is the mermaid syntax diagram text. When it is written out by the macro, the widget then executes to generate the diagram. The eventcatcher tag is needed to get hyperlinks on the diagram nodes working. That will be explained further, below.

\define mmdiagram(body) 
<$eventcatcher selector="a:has(.node)" $click=<<clickactions>>>
<$mermaid>
graph LR
$body$
</$mermaid>
</$eventcatcher>
\end

Step 2 - Use a macro to generate the mermaid text in the context of the current tiddler

In order to pass the mermaid text into the above macro as a parameter or variable, you have to do any work with widgets, filters and transclusions up front and assign the result to that parameter or variable.

To pass field values from the current tiddler as macro parameters, I found it necessary to use the wikify widget. The three nested wikify statements below seem a bit verbose and complex, but it works.

The mermaid syntax does not tolerate spaces in node identifiers. So, it’s common practice to use a syntax like nodeid["Node label with spaces or special chars"] To facilitate that, I added a global javascript macro called MNodeID to generate a hashcode to use for a node id based on the string for the node label that most often will come from a tiddler title.

\define listSendFlows(comp)
<$list filter="[all[]tag[InformationFlow]Source Component[$comp$]]"> 
<$wikify name=from text="""{{!!Source Component}}"""> 
<$wikify name=to text="""{{!!Destination Component}}"""> 
<$wikify name=label text="""{{!!ID}}"""> 
<$macrocall $name=MNodeID src=<<from>>/>["<<from>>"] <$text text="-->"/>|<<label>>| <$macrocall $name=MNodeID src=<<to>>/>["<<to>>"]
click <$macrocall $name=MNodeID src=<<from>>/> href "<<from>>" "<<from>>"
click <$macrocall $name=MNodeID src=<<to>>/> href "<<to>>" "<<to>>"
</$wikify>
</$wikify>
</$wikify>
</$list>
\end

Step 3 - Enable click events on the diagram nodes

Mermaid has a syntax for adding click events and tooltips to the nodes of the diagram. Since TW is a single HTML page, a tiddler title given as an href url is interpreted as an anchor link. It works, but it changes the browser navigation bar and history, which is undesirable, and it only navigates the first time (ie. if you click the same link twice in a row, it will not navigate because the browser navigation bar says you are already there). In addition, for some reason, the tooltips are not styled correctly or visible when using the mermaid widget (though they do work when you use a tiddler with the special vnd.tiddlywiki5.mermaid tiddler type).

So, to enable the clicks in a way that works well in TW, surround the mermaid widget with the eventcatcher widget as shown in the second section, above, and include the macro that it is calling (below) and the style section for the tooltip (see below).

\define clickactions()
<$action-navigate $to=<<dom-xlink:href>>/>
\end
<style>
  .mermaidTooltip {
      position: absolute;
      text-align: center;
      max-width: 200px;
      padding: 2px;
      font-family: 'trebuchet ms', verdana, arial;
      font-size: 12px;
      background: #ffffde;
      border: 1px solid #aaaa33;
      border-radius: 2px;
      pointer-events: none;
      z-index: 1000;
    }
</style>

Step 4 - Finally, call the diagram macro passing the result of the mermaid text generation macro

In this example, listSendFlows is called in a wikify widget to generate the mermaid text and encapsulate it in a variable called flows. The flows variable is then passed as the body parameter to the mmdiagram macro, which simply encapsulates it in the mermaid widget. Then, finally, the mermaid widget does its magic and generates the resulting diagram.

<$wikify name=flows text="""<$macrocall $name=listReceiveFlows comp={{!!title}} />""" >
<$macrocall $name=mmdiagram body=<<flows>> />
</$wikify>

The macrocall widget is used instead of the native <<mmdiagram>> type of syntax because it will expand a macro, transclusion or filter that is supplied as a parameter value before invoking the macro.

Modifications to the mermaid-tw5 plugin to enable interactions with diagrams

The mermaid.js support for click interactions didn’t work out of the box with the plugin. To get them working, I modified the mermaid plugin file $:/plugins/orange/mermaid-tw5/wrapper.js to add configuration (specifically “securityLevel: ‘loose’”) and to apply bindFunctions.

Similarly, the mermaid.js library can support pan and zoom on diagrams. For generated diagrams with many nodes, it can quickly become too large to be viewed correctly without zooming. To get this working in TiddlyWiki, I added the $:/plugins/orange/mermaid-tw5/d3.v6.min.js library to the plugin and made additional changes in $:/plugins/orange/mermaid-tw5/wrapper.js to toggle pan and zoom on or off with a click on the diagram.

The changes to mermaid-tw5 plugin have been proposed in a pull request, but since the repo is marked as no longer supported, you can find the modified fork of the plugin.

Conclusion

While this was a lot of work to figure out, it accounts for everything that I wanted. I can generate diagrams from dynamic content with clickable nodes, working tooltips, pan and zoom, and it properly navigates the tiddlywiki without messing up the navigation bar or history. In addition, all of this can be included in a template tiddler that is transcluded into the actual content tiddlers driving diagrams.

10 Likes

Good stuff.

Thanks for the detailed explanations. Really helpful.