Skip to content

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:

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

ts
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.

ts
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

PlacementMermaid shapeExample output
singlerectanglegreet["greet"]
scattertrapezoidscout[/"scout"/]
gatherhexagoncollectcollect
embedded-dagsubroutineinvoke[["invoke"]]
terminal (completed)double-circledone((("done")))
terminal (failed)asymmetric flagfail>"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

ts
<<< @/../examples/the-archivist/viz/render-mermaid.ts#mermaid-render

Combining with the dispatcher's read accessors

ts
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.

ts
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.

ts
<<< @/../examples/the-archivist/viz/render-jsonld.ts#jsonld-render

DAGONIZER_VOCAB

ts
// 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

ts
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.

ts
declare const 
container
: HTMLElement;
declare const
dag
:
DAGType
;
const
graph
= new
CytoscapeGraph
(
container
,
dag
);
const
cy
= await
graph
.
mount
(); // returns cytoscape.Core

Constructor

ts
declare const 
container
: HTMLElement;
declare const
dag
:
DAGType
;
declare const
options
:
CytoscapeGraphOptionsType
;
// new CytoscapeGraph(container, dag, options?) const
graph
= new
CytoscapeGraph
(
container
,
dag
,
options
);
ParameterTypeDescription
containerHTMLElementDOM element to mount the graph into
dagDAGThe DAG to render
optionsCytoscapeGraphOptionsType?Optional configuration

CytoscapeGraphOptionsType

FieldTypeDescription
embeddedDAGs?ReadonlyMap<string, DAG>Registry of embedded DAGs by DAG IRI, passed to CytoscapeRenderer and CompositeLayout for recursive expansion. Default: empty Map.
layoutOptions?CompositeLayoutOptionsTypeLayout 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

ts
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)

HookSignaturePurpose
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.PresetLayoutOptionsOverride 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) => voidOverride to replace the self-loop size-cache flush strategy. Default toggles display off then on in two cy.batch() calls.
onReady(cy: cytoscape.Core) => voidCalled 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:

ts
<<< @/../examples/the-archivist/viz/ArchivistGraph.ts#cytoscape-graph-subclass

CytoscapeRenderer

Static class. Returns a plain element array with NO computed positions. Layout is performed separately by CompositeLayout.compute or handled internally by CytoscapeGraph.

ts
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 type field ('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.
  • GatherNode placements render as first-class nodes with their own route edges; scatter fan-out routes to gather fan-in when records need to be folded.
ts
<<< @/../examples/the-archivist/viz/render-cytoscape.ts#cytoscape-render

RenderOptionsType

ts
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

ts
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.

ts
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.

ts
declare const 
dag
:
DAGType
;
declare const
embeddedDAGs
:
ReadonlyMap
<string,
DAGType
>;
declare const
options
:
CompositeLayoutOptionsType
;
const
result
:
LayoutResultType
= await
CompositeLayout
.
compute
(
dag
,
embeddedDAGs
,
options
);

LayoutResultType:

ts
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.

Watched over by the Order of Dagon.