Visualization
What It Is
The visualization surface turns canonical DAG documents into Mermaid source, JSON-LD graph documents, Cytoscape element data, and layout metadata.
Use this page when docs, developer tools, or product UI need to show the graph shape that the dispatcher executes. Guide and example pages use Mermaid; runnable browser demos use Cytoscape for live execution state.
How It Works
Renderers read DAGType documents. They do not inspect dispatcher internals, node implementations, or live runtime state unless a caller supplies explicit metadata. That keeps visualization tied to the same JSON-LD artifact used for registration: DAG and placement IRIs define identity, while name labels make the graph readable.
Mermaid is the lightweight static format for docs. Cytoscape is the richer element format for browser runners that need expansion, live state, selection, and interaction.
Diagrams, Examples, and Outputs
The renderers are used throughout the docs and demos. Start here for API details, then compare against pages that show JSON-LD beside the generated graph:
- Reference: Dagonizer - read accessors
- Reference: Entities -
DAG
What It Lets You Do
The visualization reference lets applications render one canonical DAG document into Mermaid, JSON-LD, or Cytoscape element data.
DAG visualization helpers. Ship through @studnicky/dagonizer/viz.
Code Samples
The code below covers Mermaid rendering, JSON-LD rendering, Cytoscape element generation, styling options, layout helpers, and graph metadata.
Import
import {
MermaidRenderer,
JsonLdRenderer,
CytoscapeRenderer,
DAGONIZER_VOCAB,
} from '@studnicky/dagonizer/viz';
import type {
DagJsonLdDocumentType,
JsonLdGraphEntryType,
CytoscapeElementType,
CytoscapeNodeElementType,
CytoscapeEdgeElementType,
} from '@studnicky/dagonizer/viz';
export {};MermaidRenderer
Static class.
declare const dag: DAGType;
const mermaid: string = MermaidRenderer.render(dag);Render a DAG as Mermaid flowchart source. The output is a complete Mermaid block ready to embed in a Markdown ```mermaid fence.
Shape vocabulary
| Placement | Mermaid shape | Example output |
|---|---|---|
single | rectangle | greet["greet"] |
scatter | trapezoid | scout[/"scout"/] |
gather | hexagon | collectcollect |
embedded-dag | subroutine | invoke[["invoke"]] |
terminal (completed) | double-circle | done((("done"))) |
terminal (failed) | asymmetric flag | fail>"fail"] |
Every output route renders as a labeled directed edge: from -->|outcome| to. Route targets are placement IRIs in the DAG document; labels come from placement name. Flows terminate at explicit TerminalNode placements, which render as double-circle (completed) or asymmetric-flag (failed) shapes and emit no outbound edges.
Style and layout options
MermaidRenderer.render(dag, { theme }) accepts pluggable style/layout parameters. primaryColor, lineColor, textColor, background, and containerTints control emitted colours. fontFamily, fontSize, nodeSpacing, rankSpacing, and padding emit a Mermaid init directive so renderers can lay out the graph with caller-provided visual settings.
Containment coloring
Placements with a non-empty container role each receive a per-role Mermaid class (contained-<role>) whose fill and stroke come from RoleColorUtils.forRole. One classDef contained-<role> rule is emitted per distinct role that appears in the DAG, so two different roles produce two distinct fill/stroke colors. The @type-derived shape is unchanged — only the color dimension signals containment. In-process placements receive no class. classDef rules are omitted entirely when no contained placement exists.
Example
<<< @/../examples/the-archivist/viz/render-mermaid.ts#mermaid-renderCombining with the dispatcher's read accessors
const dispatcher = new Dagonizer<MyState>();
const sources = dispatcher.listDAGs().map((dag) => ({
name: dag.name,
mermaid: MermaidRenderer.render(dag),
}));getDAG, listDAGs, getNode, and listNodes give tooling everything it needs to walk the registry and emit per-DAG documentation.
JsonLdRenderer
Static class.
declare const dag: DAGType;
const doc: DagJsonLdDocumentType = JsonLdRenderer.render(dag);Renders a DAG as a JSON-LD document with a @context and a @graph containing the DAG root plus every placement, all typed against the Dagonizer vocabulary (DAGONIZER_VOCAB). The output is a plain object; serialize with JSON.stringify.
Each placement's @type is prefixed with dag:: dag:SingleNode, dag:ScatterNode, dag:GatherNode, dag:EmbeddedDAGNode, dag:TerminalNode, or dag:PhaseNode.
<<< @/../examples/the-archivist/viz/render-jsonld.ts#jsonld-renderDAGONIZER_VOCAB
// DAGONIZER_VOCAB === 'https://noocodec.dev/ontology/dagonizer/'
const vocab = DAGONIZER_VOCAB; // type: "https://noocodec.dev/ontology/dagonizer/"
export {};Stable JSON-LD vocabulary URI for the Dagonizer DAG vocabulary. Prefixed as dag: in rendered documents.
Types
declare const doc: DagJsonLdDocumentType;
const ctx: Record<string, string> = doc['@context'];
const graph: readonly JsonLdGraphEntryType[] = doc['@graph'];
declare const entry: JsonLdGraphEntryType;
const id: string = entry['@id'];
const type: string = entry['@type'];CytoscapeGraph
Subclassable factory class for mounting an interactive cytoscape graph in a DOM container. cytoscape and @dagrejs/dagre are optional peer dependencies; install them to use this class. The cytoscape runtime is resolved internally by a lazy Cytoscape.create() dynamic import, so the package never bundles cytoscape and SSR/headless builds never load it until a graph mounts. A subclass that needs a custom cytoscape.Core build (extensions registered, a pinned cytoscape version, a renderer-less test harness) overrides the protected construct(options) hook instead of injecting a factory.
declare const container: HTMLElement;
declare const dag: DAGType;
const graph = new CytoscapeGraph(container, dag);
const cy = await graph.mount(); // returns cytoscape.CoreConstructor
declare const container: HTMLElement;
declare const dag: DAGType;
declare const options: CytoscapeGraphOptionsType;
// new CytoscapeGraph(container, dag, options?)
const graph = new CytoscapeGraph(container, dag, options);| Parameter | Type | Description |
|---|---|---|
container | HTMLElement | DOM element to mount the graph into |
dag | DAG | The DAG to render |
options | CytoscapeGraphOptionsType? | Optional configuration |
CytoscapeGraphOptionsType
| Field | Type | Description |
|---|---|---|
embeddedDAGs? | ReadonlyMap<string, DAG> | Registry of embedded DAGs by DAG IRI, passed to CytoscapeRenderer and CompositeLayout for recursive expansion. Default: empty Map. |
layoutOptions? | CompositeLayoutOptionsType | Layout tuning options forwarded to CompositeLayout.compute. Default: {} (all tuning delegated to CompositeLayout's own defaults). |
The constructor accepts Partial<CytoscapeGraphOptionsType>; both fields are optional at the call site with the defaults noted above.
async mount(): Promise<cytoscape.Core>
Builds elements via CytoscapeRenderer.render, computes layout via CompositeLayout.compute (async), mounts the cytoscape instance into the container, and calls onReady. Returns the mounted cytoscape.Core.
cy getter
declare const container: HTMLElement;
declare const dag: DAGType;
const graph = new CytoscapeGraph(container, dag);
// cy is null before mount, cytoscape.Core after
const cy = graph.cy;Returns the cytoscape.Core after a successful mount(), or null if the graph has not yet been mounted.
Protected hooks (override in subclasses)
| Hook | Signature | Purpose |
|---|---|---|
construct | (options: cytoscape.CytoscapeOptions) => Promise<cytoscape.Core> | Override to supply a custom cytoscape.Core (extensions registered, a pinned build, a headless harness). Default delegates to Cytoscape.create, which lazily dynamic-imports the optional cytoscape peer. This is the extension point that replaces the former injected factory. |
composeElements | () => ReadonlyArray<cytoscape.ElementDefinition> | Override to customize element construction. Default delegates to CytoscapeRenderer.render. |
stylesheet | () => cytoscape.StylesheetStyle[] | Override to supply a custom stylesheet. |
presetLayout | () => cytoscape.PresetLayoutOptions | Override to change the preset layout options passed to cytoscape. Default uses preset with fit: true, padding: 60. |
interactionDefaults | () => Record<string, unknown> | Override to customize pan/zoom/interaction defaults spread into the cytoscape constructor. |
layoutRegistry | () => ReadonlyMap<string, DAG> | Override to return the embedded-DAG subset used for layout. Default returns the embeddedDAGs passed at construction. |
applyLayout | (elements: ReadonlyArray<cytoscape.ElementDefinition>) => Promise<cytoscape.ElementDefinition[]> | Override to customize the layout application step. Default calls CompositeLayout.compute and attaches positions to each node element. |
enforceVisibility | (cy: cytoscape.Core) => void | Override to replace the self-loop size-cache flush strategy. Default toggles display off then on in two cy.batch() calls. |
onReady | (cy: cytoscape.Core) => void | Called after mount and visibility sweep complete. Override to wire animation machines or event listeners. Default is a no-op. |
Example: subclassing for doc animations
The Archivist example's ArchivistGraph extends CytoscapeGraph and overrides onReady to attach execution-trace animation:
<<< @/../examples/the-archivist/viz/ArchivistGraph.ts#cytoscape-graph-subclassCytoscapeRenderer
Static class. Returns a plain element array with NO computed positions. Layout is performed separately by CompositeLayout.compute or handled internally by CytoscapeGraph.
declare const dag: DAGType;
const elements: readonly CytoscapeElementType[] = CytoscapeRenderer.render(dag);Renders a DAG as a Cytoscape elements array.
- Every placement becomes a node element with a
typefield ('single'|'scatter'|'gather'|'embedded-dag'|'terminal'|'phase') for per-type stylesheet selectors. - Every output route becomes a labeled edge element whose source and target are placement IDs derived from placement IRIs.
- Embedded-DAG placements are expanded inline when their target DAG is supplied via
options.embeds, showing the full inner flow as a compound cluster. GatherNodeplacements render as first-class nodes with their own route edges; scatter fan-out routes to gather fan-in when records need to be folded.
<<< @/../examples/the-archivist/viz/render-cytoscape.ts#cytoscape-renderRenderOptionsType
declare const opts: RenderOptionsType;
// embeddedDAGs?: ReadonlyMap<string, DAGType>
// maxDepth?: number (default 6)
export {};Note: computeLayout and layoutOptions are not options on CytoscapeRenderer.render. Positioning is performed by CompositeLayout.compute (async) or handled internally by CytoscapeGraph.
Containment metadata
Placements bound to a container role (worker/isolate) carry:
data.container— the role string (e.g.'cpu'), present only when a role is set- CSS class
dag-contained— appended alongside the type class (e.g.'dag-scatter dag-contained')
In-process placements omit data.container entirely and carry only the type class.
Select contained nodes via .dag-contained (class selector) or node[container] / node[container="<role>"] (data selectors).
Types
declare const el: CytoscapeElementType;
// CytoscapeNodeElementType
declare const node: CytoscapeNodeElementType;
const _group: 'nodes' = node.group;
const _id: string = node.data.id;
const _label: string = node.data.label;
const _type: 'single' | 'scatter' | 'gather' | 'embedded-dag' | 'terminal' | 'phase' = node.data.type;
const _classes: string = node.classes;
// CytoscapeEdgeElementType
declare const edge: CytoscapeEdgeElementType;
const _eg: 'edges' = edge.group;
const _eid: string = edge.data.id;
const _src: string = edge.data.source;
const _tgt: string = edge.data.target;
const _lbl: string = edge.data.label;
const _route: string = edge.data.route;
const _eclasses: string = edge.classes;CompositeLayout
Static class that computes node positions for a DAG using @dagrejs/dagre. compute is async: it lazy-loads dagre, recursively lays out embedded-DAG sub-graphs bottom-up, and returns a LayoutResultType with a position map and bounding-box dimensions.
declare const dag: DAGType;
const embeddedDAGs: ReadonlyMap<string, DAGType> = new Map();
const result: LayoutResultType = await CompositeLayout.compute(dag, embeddedDAGs);
// result.positions: ReadonlyMap<string, { x: number; y: number }>
// result.width: number (total bounding-box width)
// result.height: number (total bounding-box height)CytoscapeGraph.mount() calls CompositeLayout.compute internally via applyLayout; direct use is for applications managing their own cytoscape instances outside the factory.
declare const dag: DAGType;
declare const embeddedDAGs: ReadonlyMap<string, DAGType>;
declare const options: CompositeLayoutOptionsType;
const result: LayoutResultType = await CompositeLayout.compute(dag, embeddedDAGs, options);LayoutResultType:
declare const result: LayoutResultType;
const positions: ReadonlyMap<string, NodePositionType> = result.positions;
const width: number = result.width;
const height: number = result.height;
declare const pos: NodePositionType;
const x: number = pos.x;
const y: number = pos.y;Details for Nerds
Mermaid rendering is text-first and static. Cytoscape rendering is element-first and suitable for live browser state. JsonLdRenderer preserves semantic graph data for tools that need linked-data output rather than a visual graph.
Renderer options should make style pluggable without changing DAG documents. A style choice must not hide node text, break Mermaid parsing, or alter graph IRIs.
Related Concepts
- Reference: Dagonizer - read accessors
- Reference: Entities -
DAG - Visualization - JSON-LD/Mermaid correlation and Cytoscape-only runnable demos
- DAGBuilder - builder output rendered by the visualization layer