Rehype plugin to generate Vizdom diagrams (as inline SVGs) in place of code fences. This
```vizdom
digraph G { Hello -> World }
```
will be converted to
<figure class="beoe vizdom">
<svg>...</svg>
</figure>
which can look like this:
TODO: add screenshot
import rehypeVizdom from "@beoe/rehype-vizdom";
const html = await unified()
.use(remarkParse)
.use(remarkRehype)
.use(rehypeVizdom)
.use(rehypeStringify)
.process(`markdown`);
It support caching the same way as @beoe/rehype-code-hook does.
You can add dark mode with something like this:
:root {
--sl-color-white: #000;
--sl-color-black: #fff;
}
@media (prefers-color-scheme: dark) {
:root {
--sl-color-white: #fff;
--sl-color-black: #000;
}
}
.vizdom {
:not([fill]) {
fill: var(--sl-color-white);
}
[fill="black"],
[fill="#000"] {
fill: var(--sl-color-white);
}
[stroke="black"],
[stroke="#000"] {
stroke: var(--sl-color-white);
}
[fill="white"],
[fill="#fff"] {
fill: var(--sl-color-black);
}
[stroke="white"],
[stroke="#fff"] {
stroke: var(--sl-color-black);
}
}
You have two options:
- either globally
.use(rehypeVizdom, { class: "something" })
- or locally
```vizdom class=something digraph G { Hello -> World } ```
If you're using @tailwindcss/typography
, it is probably a good idea to add not-content
globally.
Inline SVG can contain HTML links:
```vizdom
digraph G {
node[URL="https://example.com"]
}
```
- Disable
svgo
or provide your own configuration withconvertShapeToPath: false
- either globally
.use(rehypeVizdom, { svgo: false })
- or locally
```vizdom svgo=false digraph G { Hello -> World } ```
- either globally
- Add CSS
.vizdom { .node rect { rx: 7px; } }
-
Set option to generate
data-graph
HTML attribute- either globally
.use(rehypeVizdom, { datagraph: 'dagre' })
- or locally
```vizdom datagraph=dagre digraph G { Hello -> World } ```
- either globally
-
Add JS
import { json, alg, type Path } from "@dagrejs/graphlib"; type D = { [node: string]: Path }; document.querySelectorAll(".vizdom").forEach((container) => { const data = container.getAttribute("data-graph") ? JSON.parse(container.getAttribute("data-graph")!) : null; if (!data) return; const graph = json.read(data); function clear() { container.querySelectorAll(".node").forEach((node) => { node.classList.remove("active"); node.classList.remove("selected"); }); container .querySelectorAll(".edge") .forEach((node) => node.classList.remove("active")); } function highlight(id: string) { alg.postorder(graph, [id]).forEach((node) => { container.querySelector(`#node-${node}`)?.classList.add("active"); graph.outEdges(node)?.forEach(({ name }) => { container.querySelector(`#edge-${name}`)?.classList.add("active"); }); }); } // highlight on hover let currentHover: string | null = null; container.addEventListener("mouseover", (e) => { // @ts-expect-error const node = e.target?.closest(".node"); if (node) { const id = node.getAttribute("id").replace("node-", ""); if (currentHover == id) return; clear(); highlight(id); currentHover = id; } else { if (currentHover == null) return; clear(); currentHover = null; } }); });
-
Add CSS
@keyframes dash { from { stroke-dashoffset: 40; } to { stroke-dashoffset: 0; } } .vizdom[data-graph] { .edge.active a path:first-child { stroke-dasharray: 5 5; animation-name: dash; animation-duration: 1000ms; stroke-dashoffset: 0; animation-iteration-count: infinite; animation-timing-function: linear; } .node { cursor: pointer; } .node.selected a *:first-child { stroke-width: 2px; } } @media (prefers-reduced-motion) { .vizdom[data-graph] { .edge.active a path:first-child { animation-duration: 4000ms; } } }
- Text is transformed to path, so Cmd + F doesn't work
- alternatively one can generate JSON representation of graph and search through labels with text-search
- Doesn't support some unicode chars, like
label="∅"
- Client-side JS library
- graphology is about 70kb uncompressed
-
@dagrejs/graphlib
is about 12kb - graph-data-structure is about 4kb (but it doesn't support ids for edges)
- DirectedGraph is about 40kb
- Other options