Schemescape

Development log of a life-long coder

Generating SVG diagrams automatically with Metalsmith

Now that I've compiled Graphviz to a WebAssembly module, I'm going to use it to automatically generate SVG diagrams when I build my static site using Metalsmith.

The plan

Here's a flow chart of my general plan:

Compile Graphviz to WebAssembly Compile Graphviz to WebAssembly Generate diagrams at build time Generate diagrams at build time Compile Graphviz to WebAssembly->Generate diagrams at build time ??? ??? Generate diagrams at build time->??? Profit! Profit! ???->Profit!

That diagram was generated using the following Markdown:

```dot2svg
digraph {
    "Compile Graphviz to WebAssembly" -> "Generate diagrams at build time" -> "???" -> "Profit!";
}
```

Extending the Marked renderer

As shown in the example near the top of this post, my plan is to piggyback on Markdown code blocks, using a special language tag dot2svg. For all other code blocks (and on error), I simply defer to the default code block rendering function:

// Generate diagrams with dot2svg
const baseCodeRenderer = markdownRenderer.code;
const dotConverter = await createDOTToSVGAsync();
markdownRenderer.code = function (code, language, escaped) {
    if (language === "dot2svg") {
        const svg = dotConverter.dotToSVG(code);
        if (svg) {
            return svg;
        } else {
            // On error, just treat the code block like normal
            language = "";
        }
    }
    return baseCodeRenderer.call(this, code, language, escaped);
};

Styling

The above rendering code worked (and didn't break my syntax highlighting), but there were a few tweaks I had in mind:

Fortunately, both of these transformations can be done with regular expression replacements:

...
if (svg) {
    // Remove XML prolog, since we're inlining
    // Also convert default styles to CSS classes, for custom styling
    return svg
        .replace(/^.*?<svg /s, "<svg ")
        .replace(/fill="([^"]+)" stroke="([^"]+)"/g, "class=\"diagram-$2-$1\"");
} else {
...

Now, I can just style everything with CSS:

/* Diagrams */
svg text { fill: #eee; }
.diagram-transparent-white { stroke: none; fill: none; }
ellipse.diagram-black-none { stroke: #ccc; fill: #444; }
.diagram-black-none { stroke: #999; fill:none; }
.diagram-black-black { stroke: #999; fill: #333; }

That's it!

To my surprise, this entire integration went smoothly, and only took an hour or so. It remains to be seen how useful these diagrams will be, but at least now if I ever feel the need to insert a superfluous diagram, the functionality will be there.

For reference, all of the code used to generate this site (along with the content) is in this repository.

Update: I've removed this feature from my static site generator for the time being because it's unclear what license covers Graphviz (the web site and source code are not in sync).