Skip to content

Commit

Permalink
migrated graphviz and plantuml to rehype-code-hook-img
Browse files Browse the repository at this point in the history
  • Loading branch information
stereobooster committed Nov 30, 2024
1 parent dfc6a46 commit 6eed68e
Show file tree
Hide file tree
Showing 9 changed files with 132 additions and 437 deletions.
4 changes: 2 additions & 2 deletions packages/demo/src/components/graphviz.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ type D = { [node: string]: Path };

// interactivity for graphviz diagrams
document.querySelectorAll(".graphviz").forEach((container) => {
const data = container.getAttribute("data-graph")
? JSON.parse(container.getAttribute("data-graph")!)
const data = container.getAttribute("data-beoe")
? JSON.parse(container.getAttribute("data-beoe")!)
: null;

if (!data) return;
Expand Down
6 changes: 2 additions & 4 deletions packages/rehype-graphviz/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,11 +32,9 @@
"clean": "rm -rf dist"
},
"dependencies": {
"@beoe/rehype-code-hook-img": "workspace:*",
"@beoe/rehype-code-hook": "workspace:*",
"@beoe/fenceparser": "workspace:*",
"@hpcc-js/wasm": "^2.16.1",
"hast-util-from-html-isomorphic": "^2.0.0",
"svgo": "^3.2.0"
"@hpcc-js/wasm": "^2.16.1"
},
"devDependencies": {
"@types/hast": "^3.0.4",
Expand Down
70 changes: 0 additions & 70 deletions packages/rehype-graphviz/src/graphviz.ts

This file was deleted.

158 changes: 10 additions & 148 deletions packages/rehype-graphviz/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,152 +1,14 @@
// @ts-expect-error The inferred type of '...' cannot be named without a reference to
import type { Plugin } from "unified";
// @ts-expect-error The inferred type of '...' cannot be named without a reference to
import type { Root } from "hast";
import { processGraphvizSvg } from "./graphviz.js";
import { rehypeCodeHook, type MapLike } from "@beoe/rehype-code-hook";
import { type Config as SvgoConfig } from "svgo";
import parse from "@beoe/fenceparser";

function processMeta(meta?: string): Record<string, any> {
return meta ? parse(meta, { lowerCase: false }) : {};
}

// it is possible to add other formats, like Graphology etc.
type GraphFormat = "dagre" | "graphology";

// import { type Engine } from "@hpcc-js/wasm";
type Engine =
| "circo"
| "dot"
| "fdp"
| "sfdp"
| "neato"
| "osage"
| "patchwork"
| "twopi"
| "nop"
| "nop2";

export type RenderGraphvizOptions = {
code: string;
class?: string;
/* it is possible to change layout with in dot code itself */
engine?: Engine;
svgo?: SvgoConfig | boolean;
dataGraph?: GraphFormat;
};

// import { Graphviz } from "@hpcc-js/wasm";
// const graphviz = await Graphviz.load();
// export const renderGraphviz = (o: RenderGraphvizOptions) =>
// processGraphvizSvg(
// graphviz.layout(o.code, "svg", o.engine || "dot"),
// o.class
// );

import { waitFor } from "@beoe/rehype-code-hook";
/**
* If all graphviz diagrams are cached it would not even load module in memory.
* If there are diagrams, it would load module and first few renders would be async,
* but all consequent renders would be sync
*/
export const renderGraphviz = waitFor(
async () => {
// @ts-ignore
const Graphviz = (await import("@hpcc-js/wasm")).Graphviz;
return await Graphviz.load();
},
(graphviz) => (o: RenderGraphvizOptions) => {
let code = o.code;
let graph;
if (o.dataGraph) {
// without this I can't get consistent ids for JSON and SVG outputs
code = graphviz.unflatten(code);
code = graphviz.nop(code);

const obj = JSON.parse(
graphviz.layout(code, "dot_json", o.engine || "dot")
);

if (o.dataGraph === "graphology") {
graph = {
attributes: { name: "g" },
options: {
allowSelfLoops: true,
multi: true,
type: obj.directed ? "directed" : "mixed",
},
nodes: obj.objects.map((node: any) => ({
key: node._gvid + 1,
})),
edges: obj.edges.map((edge: any) => ({
key: edge._gvid + 1,
source: edge.tail + 1,
target: edge.head + 1,
})),
};
}

if (o.dataGraph === "dagre") {
graph = {
options: {
directed: obj.directed,
multigraph: true,
compound: false,
},
nodes: obj.objects.map((node: any) => ({
v: node.unique_id + 1,
})),
edges: obj.edges.map((edge: any) => ({
v: edge.tail + 1,
w: edge.head + 1,
name: edge._gvid + 1,
})),
};
}
}

return processGraphvizSvg(
graphviz.layout(code, "svg", o.engine || "dot"),
o.class,
o.svgo,
graph
);
}
);

export { processGraphvizSvg };

export type RehypeGraphvizConfig = {
cache?: MapLike;
class?: string;
svgo?: SvgoConfig | boolean;
// adds data-graph to each figure with JSON representation of graph
dataGraph?: GraphFormat;
};

export const rehypeGraphviz: Plugin<[RehypeGraphvizConfig?], Root> = (
options = {}
) => {
const salt = { class: options.class, svgo: options.svgo };
// @ts-expect-error
return rehypeCodeHook({
...options,
salt,
language: "dot",
code: ({ code, meta }) => {
const metaOptions = processMeta(meta);
const dataGraph =
metaOptions.datagraph !== undefined
? metaOptions.datagraph
: options.dataGraph;
const cssClass = `${options.class || ""} ${
metaOptions.class || ""
}`.trim();
const svgo =
metaOptions.svgo !== undefined ? metaOptions.svgo : options.svgo;

return renderGraphviz({ code, class: cssClass, svgo, dataGraph });
},
});
};
import { rehypeCodeHookImg } from "@beoe/rehype-code-hook-img";
import { RehypeGraphvizConfig, renderGraphviz } from "./render.js";

export const rehypeGraphviz = rehypeCodeHookImg<RehypeGraphvizConfig>({
language: "dot",
class: "graphviz",
render: (code, options) => renderGraphviz({ ...options, code }),
});

export default rehypeGraphviz;
101 changes: 101 additions & 0 deletions packages/rehype-graphviz/src/render.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
/**
* it is possible to add other formats
*/
export type DataFormat = "dagre" | "graphology";

// import { type Engine } from "@hpcc-js/wasm";
type Engine =
| "circo"
| "dot"
| "fdp"
| "sfdp"
| "neato"
| "osage"
| "patchwork"
| "twopi"
| "nop"
| "nop2";

export type RehypeGraphvizConfig = {
dataGraph: DataFormat;
engine: Engine;
};

type RenderGraphvizOptions = RehypeGraphvizConfig & {
code: string;
};

import { waitFor } from "@beoe/rehype-code-hook";
/**
* If all graphviz diagrams are cached it would not even load module in memory.
* If there are diagrams, it would load module and first few renders would be async,
* but all consequent renders would be sync
*/
export const renderGraphviz = waitFor(
async () => {
// @ts-ignore
const Graphviz = (await import("@hpcc-js/wasm")).Graphviz;
return await Graphviz.load();
},
(graphviz) =>
({ code, engine, dataGraph }: RenderGraphvizOptions) => {
let data;
if (dataGraph) {
// without this I can't get consistent ids for JSON and SVG outputs
code = graphviz.unflatten(code);
code = graphviz.nop(code);

const obj = JSON.parse(
graphviz.layout(code, "dot_json", engine || "dot")
);

if (dataGraph === "graphology") {
data = {
attributes: { name: "g" },
options: {
allowSelfLoops: true,
multi: true,
type: obj.directed ? "directed" : "mixed",
},
nodes: obj.objects.map((node: any) => ({
key: node._gvid + 1,
})),
edges: obj.edges.map((edge: any) => ({
key: edge._gvid + 1,
source: edge.tail + 1,
target: edge.head + 1,
})),
};
}

if (dataGraph === "dagre") {
data = {
options: {
directed: obj.directed,
multigraph: true,
compound: false,
},
nodes: obj.objects.map((node: any) => ({
v: node.unique_id + 1,
})),
edges: obj.edges.map((edge: any) => ({
v: edge.tail + 1,
w: edge.head + 1,
name: edge._gvid + 1,
})),
};
}
}

return {
svg: cleanup(graphviz.layout(code, "svg", engine || "dot")),
data,
};
}
);

/**
* removes `<?xml version="1.0" encoding="UTF-8" standalone="no"?>`
* removes `<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN"`
*/
const cleanup = (svg: string) => svg.split("\n").slice(6).join("\n");
Loading

0 comments on commit 6eed68e

Please sign in to comment.