Mermaid Diagrams¶
Mermaid is a powerful tool for creating diagrams and visualizations using a simple markdown-like syntax. It allows you to create flowcharts, sequence diagrams, class diagrams, and more. Below are some examples of Mermaid diagrams which use the Hand Drawn style with the Excalifont(s) for a more informal and visually appealing look.
Sequence Diagram¶
%% Example Sequence Diagram
sequenceDiagram
actor Alice
actor Bob
Alice->>+Bob: Hello Bob, how are you?
Bob->>Bob: Thinking...
Bob-->>-Alice: Hi Alice, I'm good thanks!
Flowchart¶
%% Example Flowchart
flowchart TD
classDef fillBlack fill:#000
classDef fillBrown fill:#c96
classDef fillRed fill:#fcc
classDef fillOrange fill:#fc9
classDef fillYellow fill:#ffc
classDef fillGreen fill:#cfc
classDef fillAqua fill:#cff
classDef fillBlue fill:#69f
classDef fillViolet fill:#ccf
classDef fillGray fill:#ccc
classDef fillWhite fill:#fff
A[Start]:::fillGreen
B{Is it sunny?}:::fillViolet
C[Go for a walk]:::fillYellow
D[Stay indoors]:::fillBlue
E[/Enjoy the sunshine!\]:::fillOrange
F[\Find something fun to do inside!/]:::fillAqua
G((End)):::fillRed
A --> B
B --> C
B --> D
C --> E
D --> F
E --> G
F --> G
Class Diagram¶
%% Example Class Diagram
classDiagram
class Animal {
+String name
+int age
+void eat()
}
class Dog {
+String breed
+void bark()
}
Animal <|-- Dog
Entity Relationship Diagram¶
%% Example ER Diagram
erDiagram
CUSTOMER ||--o{ ORDER : places
ORDER ||--|{ LINE_ITEM : contains
CUSTOMER }|..|{ DELIVERY_ADDRESS : uses
MkDocs Integrations¶
In order to force the first diagram on each page to render properly, I had to add a section to the mkdocs_hooks.py and a mermaid-init.js. These files force a fake blank diagram on the page and to wait for the fonts to fully load before any rendering or sizing is done on the first real diagram.
-
from __future__ import annotations from pathlib import Path from typing import Any _MERMAID_WARMUP_HTML = ( '<pre class="mermaid mermaid-warmup"' ' style="height: 0; overflow: hidden; margin: 0; padding: 0; border: none; opacity: 0; pointer-events: none;">' '<code>flowchart TD</code>' '</pre>\n' ) def on_page_content(html, page, config, files, **kwargs): """Inject a warmup <pre class="mermaid"> element at the beginning of the page content to ensure that Mermaid is loaded before any diagrams are rendered.""" if "mermaid" not in html.lower(): return html return _MERMAID_WARMUP_HTML + html -
document$.subscribe(() => { if (typeof mermaid === "undefined") { return; } document.querySelectorAll(".mermaid-warmup").forEach((el) => el.remove()); mermaid.initialize({ startOnLoad: false, securityLevel: 'loose', fontFamily: 'Excalifont, cursive', // Use Excalifont layout: "elk", look: "handDrawn", theme: "base", // You can also try 'neutral', 'dark', 'forest', 'default' themeVariables: { 'fontFamily': 'Excalifont, cursive', 'fontSize': '16px', 'primaryColor': '#ecccff', 'primaryTextColor': '#000000', 'primaryBorderColor': '#000000', 'lineColor': '#000000', 'textColor': '#000000', 'handDrawnCurve': true // Enable hand-drawn style }, flowchart: { curve: 'linear', htmlLabels: true, useMaxWidth: true, }, sequence: { actorFontFamily: 'Excalifont, cursive', messageFrontFontFamily: 'Excalifont, cursive', noteFontFamily: 'Excalifont, cursive', }, }); const blocks = document.querySelectorAll("pre.mermaid:not(.mermaid-warmup)"); blocks.forEach((block, index) => { const code = block.querySelector("code"); const text = code ? code.textContent : block.textContent; const div = document.createElement("div"); div.classList.add("mermaid"); div.id = "mermaid-" + Date.now() + "-" + index; div.textContent = text || ""; block.replaceWith(div); }); Promise.allSettled([ document.fonts.load("16px 'Excalifont'"), document.fonts.load("16px 'Cascadia Code'"), ]).then(() => { mermaid.run({ querySelector: ".mermaid:not([data-processed])", }).then(() => { requestAnimationFrame(() => fixActorBoxPadding(12)); }).catch((err) => { console.warn("Mermaid rendering error:", err); mermaid.run({ querySelector: ".mermaid:not([data-processed])", }).then(() => { requestAnimationFrame(() => fixActorBoxPadding(12)); }).catch((err) => { console.warn("Mermaid retry error:", err); }); }); }); }); /** * After Mermaid renders a sequence diagram, the actor/participant boxes may be sized using fallback-font metrics. * This function uses the live getBBox() of each rendered element to compute the true width, expands the surrounding * <rect>, and re-centers the label so every box has at least `padding` pixels of clear space. */ function fixActorBoxPadding(padding) { document.querySelectorAll("div.mermaid svg").forEach((svg) => { svg.querySelectorAll("rect.actor").forEach((rect) => { const g = rect.parentElement; if (!g) return; const text = g.querySelector("text"); if (!text) return; let tBBox, rBBox; try { tBBox = text.getBBox(); rBBox = rect.getBBox(); } catch (e) { return; } if (tBBox.width === 0) return; const needed = tBBox.width + padding *2; if (needed <= rBBox.width) return; // already fits const extra = needed - rBBox.width; const newX = rBBox.x - extra / 2; rect.setAttribute("x", newX); rect.setAttribute("width", needed); // Re-center the text label inside the expanded box const centerX = newX + (needed / 2); text.setAttribute("x", centerX); text.querySelectorAll("tspan[x]").forEach((ts) => { ts.setAttribute("x", centerX); }); }); }); }