Diagram recipes
Complete starting points for common architecture, flow, lifecycle, lineage, dependency, and topology diagrams.
Pick the layout that matches what the diagram needs to preserve. Layered layout explains relationships. Row, column, and grid explain composition. Manual layout preserves an authored shape.
Service architecture
Use layered layout when relationships should decide placement. Named ports keep the gateway branches attached to stable sides.
const services = defineDiagram({
layout: layouts.layered({
direction: "right",
spacing: 48,
layerSpacing: 88
}),
nodes: [
node("client", { label: "Client" }),
node("gateway", {
label: "Gateway",
ports: [
{ id: "sync", side: "right", position: 0.3 },
{ id: "async", side: "right", position: 0.72 }
]
}),
node("worker", { label: "Worker" }),
node("queue", { label: "Queue" }),
node("store", { label: "Store", shape: "ellipse" })
],
edges: [
edge("request", "client", "gateway"),
edge("dispatch", anchors.port("gateway", "sync"), "worker"),
edge("publish", anchors.port("gateway", "async"), "queue"),
edge("write", "worker", "store"),
edge("consume", "queue", "store")
]
});
<Diagram {...services} spawn={{ mode: "flow", interval: 160 }} />;Request path
A row layout works for a strict left-to-right sequence. Exact side anchors stop endpoints from moving when node sizes differ.
const requestPath = defineDiagram({
layout: layouts.row({ gap: 72, align: "center" }),
nodes: [
node("browser", { label: "Browser" }),
node("edge", { label: "CDN" }),
node("api", { label: "API" }),
node("database", { label: "Database", shape: "ellipse" })
],
edges: [
edge("tls", anchors.side("browser", "right"), anchors.side("edge", "left"), {
label: "TLS",
route: routes.straight(),
animated: "flow"
}),
edge("forward", "edge", "api", { route: routes.straight() }),
edge("query", "api", "database", { label: "SQL", route: routes.straight() })
]
});Event pipeline
A downward layered graph makes fan-out readable. Automatic routes use ELK bends and still clip to node boundaries.
const events = defineDiagram({
layout: layouts.layered({ direction: "down", edgeRouting: "orthogonal" }),
nodes: [
node("producer", { label: "Producer" }),
node("topic", { label: "Topic", shape: "ellipse" }),
node("indexer", { label: "Indexer" }),
node("notifier", { label: "Notifier" }),
node("archive", { label: "Archive" })
],
edges: [
edge("publish", "producer", "topic", { animated: "draw" }),
edge("index", "topic", "indexer", { route: routes.auto(10) }),
edge("notify", "topic", "notifier", { route: routes.auto(10) }),
edge("retain", "topic", "archive", { route: routes.auto(10) })
]
});Deployment pipeline
Grid layout fits a known matrix. Use explicit route choices when the diagram should explain each transition rather than mirror graph depth.
const deploy = defineDiagram({
layout: layouts.grid({ columns: 2, columnGap: 96, rowGap: 64 }),
nodes: [
node("commit", { label: "Commit" }),
node("build", { label: "Build" }),
node("test", { label: "Test" }),
node("release", { label: "Production", shape: "ellipse" })
],
edges: [
edge("commit-build", "commit", "build", { route: routes.elbow("horizontal", 8) }),
edge("build-test", "build", "test", { route: routes.elbow("vertical", 8) }),
edge("test-release", "test", "release", {
route: routes.orthogonal("horizontal", 0.5, 8)
})
]
});State machine
Manual layout is the right choice when loops and branch placement carry meaning. Manual waypoints keep the return path outside the main states.
const lifecycle = defineDiagram({
layout: layouts.manual(),
nodes: [
node("draft", { label: "Draft", position: { x: 0, y: 120 } }),
node("review", { label: "Review", position: { x: 220, y: 120 } }),
node("live", { label: "Live", position: { x: 440, y: 20 } }),
node("rejected", { label: "Rejected", position: { x: 440, y: 220 } })
],
edges: [
edge("submit", "draft", "review", { label: "submit" }),
edge("approve", "review", "live", { label: "approve" }),
edge("reject", "review", "rejected", { label: "reject" }),
edge("revise", "rejected", "draft", {
route: routes.manual([{ x: 300, y: 330 }, { x: 80, y: 330 }], 12)
})
]
});Data lineage
Multiple sources can converge on one transform. Edge labels record the actual transfer contract instead of decorating the line.
const lineage = defineDiagram({
layout: layouts.layered({ direction: "right" }),
nodes: [
node("orders", { label: "Orders DB", shape: "ellipse" }),
node("events", { label: "Event log", shape: "ellipse" }),
node("transform", { label: "Transform" }),
node("warehouse", { label: "Warehouse", shape: "ellipse" }),
node("dashboard", { label: "Dashboard" })
],
edges: [
edge("orders-transform", "orders", "transform", { label: "CDC" }),
edge("events-transform", "events", "transform", { label: "stream" }),
edge("transform-warehouse", "transform", "warehouse", { label: "merge" }),
edge("warehouse-dashboard", "warehouse", "dashboard", { label: "query" })
]
});Dependency graph
Layered layout exposes shared dependencies and merge points. The document stays useful outside React because it contains only data.
const dependencies = defineDiagram({
layout: layouts.layered({ direction: "down" }),
nodes: ["app", "editor", "viewer", "document", "utils"].map((id) =>
node(id, { label: id })
),
edges: [
edge("app-editor", "app", "editor"),
edge("app-viewer", "app", "viewer"),
edge("editor-document", "editor", "document"),
edge("viewer-document", "viewer", "document"),
edge("document-utils", "document", "utils")
]
});
const result = await computeDiagram(dependencies, { layoutEngine: elkLayoutEngine });Network topology
Manual positions give a network map a stable physical shape. Side anchors and outward offsets keep lines away from node borders.
const network = defineDiagram({
layout: layouts.manual(),
nodes: [
node("internet", { label: "Internet", shape: "ellipse", position: { x: 220, y: 0 } }),
node("router", { label: "Router", position: { x: 220, y: 130 } }),
node("web-a", { label: "Web A", position: { x: 20, y: 280 } }),
node("web-b", { label: "Web B", position: { x: 220, y: 280 } }),
node("database", { label: "Database", shape: "ellipse", position: { x: 420, y: 280 } })
],
edges: [
edge("uplink", anchors.side("internet", "bottom", 0.5, 6), "router"),
edge("route-a", "router", "web-a", { route: routes.orthogonal("vertical", 0.45, 8) }),
edge("route-b", "router", "web-b", { route: routes.straight() }),
edge("route-db", "router", "database", { route: routes.orthogonal("vertical", 0.45, 8) })
]
});