Skip to content

Commit

Permalink
mermaid
Browse files Browse the repository at this point in the history
  • Loading branch information
mbostock committed Nov 7, 2023
1 parent 1ca9552 commit d0876a8
Show file tree
Hide file tree
Showing 6 changed files with 92 additions and 36 deletions.
88 changes: 53 additions & 35 deletions public/client.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,41 +64,6 @@ function recommendedLibraries() {
html: () => import("https://cdn.jsdelivr.net/npm/htl/+esm").then((htl) => htl.html),
svg: () => import("https://cdn.jsdelivr.net/npm/htl/+esm").then((htl) => htl.svg),
Plot: () => import("https://cdn.jsdelivr.net/npm/@observablehq/plot/+esm"),
dot: async () => {
// TODO Incorporate this into the standard library.
const viz = await import("https://cdn.jsdelivr.net/npm/@viz-js/viz/+esm").then(({instance}) => instance());
return function dot(strings) {
let string = strings[0] + "";
let i = 0;
let n = arguments.length;
while (++i < n) string += arguments[i] + "" + strings[i];
const svg = viz.renderSVGElement(string, {
graphAttributes: {
bgcolor: "none"
},
nodeAttributes: {
color: "#00000101",
fontcolor: "#00000101",
fontname: "var(--sans-serif)",
fontsize: "12"
},
edgeAttributes: {
color: "#00000101"
}
});
for (const e of svg.querySelectorAll("[stroke='#000001'][stroke-opacity='0.003922']")) {
e.setAttribute("stroke", "currentColor");
e.removeAttribute("stroke-opacity");
}
for (const e of svg.querySelectorAll("[fill='#000001'][fill-opacity='0.003922']")) {
e.setAttribute("fill", "currentColor");
e.removeAttribute("fill-opacity");
}
svg.remove();
svg.style = "max-width: 100%; height: auto;";
return svg;
};
},
Inputs: () => {
// TODO Observable Inputs needs to include the CSS in the dist folder
// published to npm, and we should replace the __ns__ namespace with
Expand All @@ -109,7 +74,60 @@ function recommendedLibraries() {
link.href = "https://cdn.jsdelivr.net/gh/observablehq/inputs/src/style.css";
document.head.append(link);
return inputs;
},
dot,
mermaid
};
}

// TODO Incorporate this into the standard library.
async function dot() {
const {instance} = await import("https://cdn.jsdelivr.net/npm/@viz-js/viz/+esm");
const viz = await instance();
return function dot(strings) {
let string = strings[0] + "";
let i = 0;
let n = arguments.length;
while (++i < n) string += arguments[i] + "" + strings[i];
const svg = viz.renderSVGElement(string, {
graphAttributes: {
bgcolor: "none"
},
nodeAttributes: {
color: "#00000101",
fontcolor: "#00000101",
fontname: "var(--sans-serif)",
fontsize: "12"
},
edgeAttributes: {
color: "#00000101"
}
});
for (const e of svg.querySelectorAll("[stroke='#000001'][stroke-opacity='0.003922']")) {
e.setAttribute("stroke", "currentColor");
e.removeAttribute("stroke-opacity");
}
for (const e of svg.querySelectorAll("[fill='#000001'][fill-opacity='0.003922']")) {
e.setAttribute("fill", "currentColor");
e.removeAttribute("fill-opacity");
}
svg.remove();
svg.style = "max-width: 100%; height: auto;";
return svg;
};
}

// TODO Incorporate this into the standard library.
async function mermaid() {
let nextId = 0;
const {default: mer} = await import("https://cdn.jsdelivr.net/npm/mermaid/+esm");
mer.initialize({startOnLoad: false, securityLevel: "loose", theme: "neutral"});
return async function mermaid() {
const div = document.createElement("div");
div.innerHTML = (await mer.render(`mermaid-${++nextId}`, String.raw.apply(String, arguments))).svg;
const svg = div.firstChild;
svg.remove();
return svg;
};
}

Expand Down
2 changes: 2 additions & 0 deletions src/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@ function getLiveSource(content, language, option) {
? transpileTag(content, "tex.block", true)
: language === "dot"
? transpileTag(content, "dot", false)
: language === "mermaid"
? transpileTag(content, "await mermaid", false)
: undefined;
}

Expand Down
3 changes: 2 additions & 1 deletion src/render.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,11 +115,12 @@ function getImportPreloads(parseResult: ParseResult): Iterable<string> {
const specifiers = new Set<string>(["npm:@observablehq/runtime"]);
for (const {name} of parseResult.imports) specifiers.add(name);
const inputs = new Set(parseResult.cells.flatMap((cell) => cell.inputs ?? []));
if (inputs.has("dot")) specifiers.add("npm:@viz-js/viz");
if (inputs.has("d3") || inputs.has("Plot")) specifiers.add("npm:d3");
if (inputs.has("Plot")) specifiers.add("npm:@observablehq/plot");
if (inputs.has("htl") || inputs.has("html") || inputs.has("svg") || inputs.has("Inputs")) specifiers.add("npm:htl");
if (inputs.has("Inputs")) specifiers.add("npm:@observablehq/inputs");
if (inputs.has("dot")) specifiers.add("npm:@viz-js/viz");
if (inputs.has("mermaid")) specifiers.add("npm:mermaid").add("npm:d3");
const preloads: string[] = [];
for (const specifier of specifiers) {
const resolved = resolveImport(specifier);
Expand Down
7 changes: 7 additions & 0 deletions test/input/mermaid.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
```mermaid
graph TD;
A-->B;
A-->C;
B-->D;
C-->D;
```
1 change: 1 addition & 0 deletions test/output/mermaid.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<div id="cell-1ffa6d4f" class="observablehq observablehq--block"></div>
27 changes: 27 additions & 0 deletions test/output/mermaid.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"data": null,
"title": null,
"files": [],
"imports": [],
"pieces": [
{
"type": "html",
"id": "",
"cellIds": [
"1ffa6d4f"
],
"html": "<div id=\"cell-1ffa6d4f\" class=\"observablehq observablehq--block\"></div>\n"
}
],
"cells": [
{
"type": "cell",
"id": "1ffa6d4f",
"inputs": [
"mermaid",
"display"
],
"body": "async (mermaid,display) => {\ndisplay((\nawait mermaid`graph TD;\n A-->B;\n A-->C;\n B-->D;\n C-->D;\n`\n))\n}"
}
]
}

0 comments on commit d0876a8

Please sign in to comment.