graph module
Use graph for graph construction, DOT parsing, inspection, data transforms, joins, and graph algorithms. Import the module for qualified calls, or import selected functions from it when a short construction DSL is clearer.
Concepts
Graph Specs
The Typst construction API is half-edge first: create node items, create source and sink half-edge endpoints, then pass edge items to graph.build. graph.build accepts both comma-separated items and ordinary Typst code blocks. Typst labels such as <a>, <h1>, and <e1> are API names; node and edge names are emitted back from nodes(g) and edges(g) as Typst labels. They are resolved before the wire format is sent to the Rust plugin. Numeric id arguments choose graph indexes or ordering: on nodes, id fixes the resulting node index and must be unique and in bounds. On nodes, edges, sources, and sinks, extra named arguments are captured as opaque Typst data fields: edge(source(<a>, style: physics.source-stroke()), sink(<b>), particle: "g") stores (style: ..) in the source data and (particle: "g") in the edge data. These user data values are not sent through the Rust plugin boundary. Typst sends an internal opaque payload with correlation data, asks Rust to resolve the graph indexes, then stores user data in arrays at those resolved graph, node, edge, and half-edge ids. graph.info, graph.nodes, and graph.edges merge those native values into the returned records. A captured label data field on nodes and edges is display content used by the default drawing style; use statements: (label: "...") when a flat metadata label string is needed. Statements are flat metadata used by DOT; they cannot nest. Values are scalar strings/numbers/booleans. Use data fields for structured Typst data or content.
graph.build and graph.parse also accept default-node-data, default-edge-data, default-source-data, and default-sink-data. These defaults are merged into the corresponding data; captured data fields on nodes, edges, sources, and sinks override the defaults. For drawing, the physics helpers read data.style on source and sink half-edges and data.display-label/data.label on edges:
#let g = build({
node(<a>, label: [a])
node(<b>, label: [b])
edge(
source(<a>, style: physics.source-stroke(c: red)),
<e>,
sink(<b>, style: physics.sink-stroke(c: blue)),
label: [$p$],
particle: "g",
)
},
default-edge-data: (kind: "propagator"),
)
#let callbacks = physics.style()
#draw(
layout(g),
source-style: callbacks.source-style,
sink-style: callbacks.sink-style,
edge-label: callbacks.edge-label,
)When parsing DOT, the same native data arrays can be filled from selected string fields. Rust parses DOT into topology and statement metadata; Typst applies defaults and evaluates selected fields afterward. The selected fields are evaluated with the record’s merged fields dictionary in scope. Since node names are labels, use #str(name) when a name should become visible text:
#let g = parse(
"digraph g { a [label=\"A\"]; a -> b [label=\"$p$\", source=\"out\", sink=\"in\"] }",
eval-node-fields: ("label",),
eval-edge-fields: ("label",),
eval-source-fields: ("statement",),
eval-sink-fields: ("statement",),
).first()
#nodes(g).first().data.label
#edges(g).first().data.labelThe same transform can run after construction. This is useful for global edge statements that should apply to every edge while still seeing local edge fields:
#let g = build({
node(<a>)
node(<b>)
edge(source(<a>), sink(<b>), statements: (mom: "p"))
}, default-edge-statements: (display-label: "$#mom$"))
#let g = graph.eval-fields(g, eval-edge-fields: ("display-label",))
#edges(g).first().data.at("display-label")#let g = build({
node(<a>)
node(<c>)
edge(source(<a>), <e1>, sink(<c>))
})Named nodes and edges can be updated after construction without scanning in the caller. The update replaces the native data, or it can be a callback receiving (data, record):
#let g = build({
node(<a>)
node(<c>)
edge(source(<a>), <e1>, sink(<c>))
})
#let g = update-node-data(g, <a>, (label: [A]))
#let g = update-edge-data(g, <e1>, (data, edge) => (
label: [$p$],
source: edge.source.node,
))
#node-data(g, <a>).label
#edge-data(g, <e1>).labelsource(..) and sink(..) accept a node reference plus optional name, id, statement, and compass. name is a Typst label name for the half-edge; id is numeric:
edge(source(<a>, name: <h1>, id: 0), <e1>, sink(<c>, id: 2), label: [a-c])One half-edge creates an external edge. The side is determined by the constructor, so there is no public flow argument:
edge(<incoming>, sink(<a>))
edge(source(<c>), <outgoing>)graph.build does not interpolate statement strings on the Rust side. Use graph.eval-fields or graph.map when a default statement should turn into Typst content or structured data. The evaluation scope includes the record’s merged fields, so default edge statements can still refer to local edge fields:
#let g = build({
node(<a>)
node(<c>)
edge(
source(<a>),
<a-c>,
sink(<c>),
label: [a-c],
statements: (color: "0055ff", label: "a-c"),
)
},
default-edge-statements: (
color: "000000",
display-label: "$#label$",
),
)
#let g = graph.eval-fields(g, eval-edge-fields: ("display-label",))Graph object API
Graph objects are Typst dictionaries wrapping archived Rust graph bytes plus native Typst data arrays for graph, node, edge, source, and sink data. The Rust graph may also carry an internal opaque payload, but that payload is used only by the Typst wrapper and is not exposed in public records. Build or parse graph objects with graph, transform graph objects with layout, and pass objects back to graph or subgraph for inspection. Subgraph objects are still opaque zero-copy values.
graph.parse(input)parses one or more DOT digraphs and returns an array of graph objects. Itseval-graph-fields,eval-node-fields,eval-edge-fields,eval-source-fields, andeval-sink-fieldsarguments are convenience arguments forgraph.eval-fields.graph.build(..)constructs one graph object from a stream of node and edge items.graph.map(graph, ..)maps graph, node, edge, source, and sink records to new native data without changing topology.graph.node-data(graph, <name>)andgraph.edge-data(graph, <name>)return one named node or edge data.graph.update-node-data(graph, <name>, data)andgraph.update-edge-data(graph, <name>, data)update one named node or edge data. Direct replacements and callbacks run in Typst with(data, record).graph.eval-fields(graph, ..)evaluates selected record fields into data entries. It works on parsed and built graph objects.node(..)returns a node item.source(..)andsink(..)return half-edge endpoints.edge(..)returns an edge item built from source/sink endpoints and an optional edge name.layout(graph, ..)runs layout as an explicit second step. Its settings are named parameters so calls stay descriptive and Tidy can document each field.draw(graph, ..)draws a laid-out graph object with CeTZ.dot(graph)returns a DOT string for inspection or export.
Graph Queries
graph.info(g) returns graph metadata. nodes(g) returns node records, and edges(g) returns edge records. Node and edge record name values are Typst labels when present. Pass subgraph: sg to filter nodes or edges by a subgraph object.
graph.join(left, right, key: "statement") joins matching dangling half edges. The key is read from half-edge statements or numeric ids and can be "statement", "compass", or "id".
graph.cycles(g) returns subgraph objects for a cycle basis. graph.forests(g) returns subgraph objects for spanning forests.
graph
- build()
- parse()
- node()
- source()
- sink()
- edge()
- group()
- pin()
- start()
- pos()
- map()
- style()
- eval-fields()
- info()
- dot()
- nodes()
- edges()
- node-data()
- edge-data()
- update-node-data()
- update-edge-data()
- join()
- cycles()
- forests()
build
Build one graph object from a stream of node and edge items.
Use node, source, sink, and edge to create graph items. Positional items may be passed as comma-separated arguments or yielded from a Typst code block.
Parameters
..items
array
Node and edge items returned by node and edge. The best way to use these is to use a code scope, so that the edges and nodes append each other:
#let g = build({
node(<a>, label: [a])
node(<b>, label: [b])
node(<c>, label: [c])
edge(source(<a>), sink(<b>))
edge(source(<b>), sink(<c>))
edge(source(<a>), sink(<c>))
})name
none or string
Graph name.
#let g = build(name: "My Graph", {
})
#info(g).nameDefault: none
data
any
Native Typst graph data.
#let g = build(data: (a:(b:(1, ))), {
})
#info(g).dataDefault: none
statements
dictionary
Flat graph statements, that get turned into a string to string dictionary in rust. Used by DOT parsing; values cannot nest.
#let g = build(statements: (a:1), {
})
#info(g).global-statementsDefault: (:)
default-node-statements
dictionary
Flat default node statements. Used by DOT; values cannot nest.
Default: (:)
default-edge-statements
dictionary
Flat default edge statements. Used by DOT parsing; values cannot nest. These are applied/delegated to all edges.
#let g = build(default-edge-statements: (a:1), {
node(<a>)
edge(source(<a>))
edge(sink(<a>))
})
#edges(g).map(e=>e.statements)Default: (:)
default-node-data
any
Default data merged into every node data. Captured node data fields override it.
#let g = build(default-node-data: (a:1), {
node(<a>)
node(<b>,a:2)
node(<c>,b:(a:1))
})
#nodes(g).map(n=>n.data)Default: none
default-edge-data
any
Default data merged into every edge data. Captured edge data fields override it.
#let g = build(default-edge-data: (a:1,b:[$m_mu$]), {
node(<a>,id:0)
edge(source(<a>),id:1)
edge(sink(<a>),<e>,id:0,b:[#set text(font:"Reforma")
This is a test: ])
})
#edges(g).map(e=>e.data.b).join()Default: none
default-source-data
any
Default data merged into every source half-edge data. Captured source data fields override it.
#let g = build(default-source-data: (a:1), {
node(<a>)
edge(source(<a>))
edge(sink(<a>))
})
#edges(g).map(e=>if e.source != none {e.source.data} else {none})Default: none
default-sink-data
any
Default data merged into every sink half-edge data. Captured sink data fields override it.
#let g = build(default-sink-data: (a:1), {
node(<a>)
edge(source(<a>))
edge(sink(<a>))
})
#edges(g).map(e=>if e.sink != none {e.sink.data} else {none})Default: none
parse
Parse one or more DOT graphs into graph objects.
Default data are applied before eval-* fields are evaluated, so default data strings can refer to parsed record fields such as #str(name). Parsed fields take precedence over default data fields, so DOT label="..." overrides default-node-data: (label: ...).
Linnest uses dot-parser for DOT syntax and then gives special meaning to a small set of attributes. Every other attribute is preserved as a flat string statement. Preserved statements are visible through info, nodes, edges, map, and eval-fields, but they do not become native Typst data unless a matching eval-* argument is passed.
#let src = ```dot
digraph { a [label="A"]; }
```
#let n = nodes(parse(src.text).first()).first()
#n.statements.label
#n.dataGraph-level handling:
- The DOT graph name becomes
graph.info(g).name. A graph-levelnameattribute can name an anonymous graph; a namedgraph/digraphheader takes precedence.
#let src = ```dot
digraph header_wins { graph [name="ignored"]; a }
```
#info(parse(src.text).first()).name- Top-level
key=valuestatements andgraph [key=value]attributes becomegraph.info(g).global-statements, except for the consumedname. They do not configure layout, even when a key looks like a layout option; pass layout options directly tolayout.
#let src = ```dot
digraph { steps=1; graph [subtitle="nested"]; a }
```
#let statements = info(parse(src.text).first()).global-statements
#statements.steps
#statements.subtitlenode [key=value]andedge [key=value]becomedefault-node-statementsanddefault-edge-statements. They are merged into each parsed node or edge before local attributes, so local attributes override defaults.
#let src = ```dot
digraph { node [color=gray]; edge [particle=g]; a [color=red]; a -> b }
```
#let g = parse(src.text).first()
#nodes(g).first().statements.color
#edges(g).first().statements.particleNode handling:
- The DOT node id becomes the node
namereturned bynodes. If the DOT node id is numeric, it is consumed as the node index instead and the publicnameisnone.
#let src = ```dot
digraph { alpha; 1; }
```
#nodes(parse(src.text).first()).map(n => n.name)id=<n>is also consumed as an explicit node index. Explicit node indexes must be unique and in bounds after parsing.
#let src = ```dot
digraph { a [id=1]; b [id=0]; }
```
#nodes(parse(src.text).first()).map(n => n.name)style=invismarks the DOT node as a dangling external endpoint. It is not returned bynodes. Its remaining attributes are copied onto the external edge that touches it, exceptshapeandlabel;styleandidare consumed.
#let src = ```dot
digraph { ext [style=invis, column=left, label=skip]; ext -> a [id=0]; }
```
#let g = parse(src.text).first()
#nodes(g).map(n => n.name)
#edges(g).first().statements.column
#edges(g).first().statements.at("label", default: none)- Node
styleis consumed for every node; onlystyle=invishas special behavior. Use another attribute name for drawing style metadata that must survive parsing.
#let src = ```dot
digraph { a [style=filled, "draw-style"=filled]; }
```
#nodes(parse(src.text).first()).first().statementsposis parsed as node placement. Simple numeric values are exposed asnode.pos; extended placement values are used bylayout.pinis an explicit layout constraint and is not exposed as a public statement.
#let src = ```dot
digraph { a [id=0, pos="0,0!"]; b [id=1, pos="ref(node:0)+2,0!"]; }
```
#let parsed-nodes = nodes(parse(src.text).first())
#parsed-nodes.map(n => n.pos)
#parsed-nodes.map(n => n.statements.at("pin", default: none))shiftis parsed as a drawing shift and also remains available as a statement.evalis retained for legacy round-tripping. Other node attributes are preserved as node statements.
#let src = ```dot
digraph { a [shift="0.1,0", eval=legacy, label=A]; }
```
#let n = nodes(parse(src.text).first()).first()
#n.shift
#n.statementsEdge handling:
id=<n>is consumed as the edge index. It is not preserved as a statement; drawing callbacks expose the resulting stable edge index aseid. Explicit edge indexes must be unique and in bounds. Without explicit ids, edge order is parser-internal, not DOT input order.
#let src = ```dot
digraph { a -> b [id=1, label=later]; b -> c [id=0, label=first]; }
```
#let parsed-edges = edges(parse(src.text).first())
#parsed-edges.map(e => e.edge)
#parsed-edges.map(e => e.statements.label)
#parsed-edges.map(e => e.statements.at("id", default: none))dir=forward,dir=back, anddir=nonebecome edge orientations"default","reversed", and"undirected". If omitted, directed DOT edges are"default"and undirected DOT edges are"undirected".
#let src = ```dot
digraph { a -> b [id=0]; b -> c [id=1, dir=back]; c -> d [id=2, dir=none]; }
```
#edges(parse(src.text).first()).map(e => e.orientation)source="..."andsink="..."are consumed as endpointstatementvalues and removed from edge statements. On an external edge, use the attribute matching the real endpoint side in the DOT edge:ext -> ausessink=..., whilea -> extusessource=....
#let src = ```dot
digraph { ext [style=invis]; ext -> a [id=0, sink=in]; a -> ext [id=1, source=out]; }
```
#let endpoint-statement(endpoint) = if endpoint == none { none } else { endpoint.at("statement", default: none) }
#let parsed-edges = edges(parse(src.text).first())
#parsed-edges.map(e => endpoint-statement(e.source))
#parsed-edges.map(e => endpoint-statement(e.sink))
#parsed-edges.map(e => e.statements.at("source", default: none))posis parsed as the edge control-point placement.pinis an explicit edge layout constraint.shift,label-pos,label-angle, andbendare parsed into edge geometry fields; except forpin, they also remain available as statements. Other edge attributes are preserved as edge statements.
#let src = ```dot
digraph { a -> b [id=0, pos="0,1!", pin="x:@edge", shift="0.1,0", "label-pos"="0,1.2", "label-angle"="0.3rad", bend="0.4rad", particle=g]; }
```
#let e = edges(parse(src.text).first()).first()
#e.pos
#e.shift
#e.label-pos
#e.label-angle
#e.bend
#e.statements.particle
#e.statements.at("pin", default: none)- Attributes copied from an invisible endpoint node are merged into the external edge statements unless their keys are
shapeorlabel.
#let src = ```dot
digraph { ext [style=invis, column=left, shape=none, label=skip]; ext -> a [id=0]; }
```
#edges(parse(src.text).first()).first().statementsPort and half-edge handling:
- DOT ports of the form
node:portandnode:port:compassare preserved on source/sink endpoint records. Numeric ports also assign explicit half-edge ids. Non-numeric ports are exposed only asport-label.
#let src = ```dot
digraph { a:left:e -> b:0:w [id=0]; }
```
#let e = edges(parse(src.text).first()).first()
#e.source.port-label
#e.sink.hedge- Compass values are exposed as
compass; valid DOT compass names includen,ne,e,se,s,sw,w,nw,c, and_.
#let src = ```dot
digraph { a:n -> b:sw [id=0]; }
```
#let e = edges(parse(src.text).first()).first()
#e.source.compass
#e.sink.compass- Explicit half-edge ids from numeric ports must be unique and in bounds. They are retained for half-edge ordering and for
joinwithkey: "id"; query and eval records expose the resulting half-edge index ashedge.
#let src = ```dot
digraph { a:1 -> b:0 [id=0]; }
```
#let e = edges(parse(src.text).first()).first()
#e.source.hedge
#e.sink.hedgePlacement fields:
pos="x,y"gives a starting coordinate;pos="x,y!"pins both axes.
#let src = ```dot
digraph { a [id=0, pos="0,0"]; b [id=1, pos="2,0!"]; }
```
#let parsed-nodes = nodes(parse(src.text).first())
#parsed-nodes.map(n => n.pos)
#parsed-nodes.map(n => n.statements.at("pos-mode", default: none))pos="ref(node:<id>)+dx,dy!"andpos="ref(edge:<id>)+dx,dy!"reference explicit DOT node or edge ids, not names and not implicit parse order.
#let src = ```dot
digraph { a [id=0, pos="1,1!"]; b [id=1, pos="ref(node:0)+2,0!"]; a -> b [id=0, pos="ref(node:1)+0,1!"]; }
```
#let g = parse(src.text).first()
#nodes(g).at(1).pos
#edges(g).first().pos- Axis form accepts
x:<coord>andy:<coord>entries.!applies to the individual axis, for examplepos="x:2!,y:0".
#let src = ```dot
digraph { a [id=0, pos="x:2!,y:0"]; }
```
#nodes(parse(src.text).first()).first().pos- Group coordinates use
@name,@+name, or@-namein axis form and must be pinned with!, for examplepos="x:@-left!,y:@row!".
#let src = ```dot
digraph { ext [style=invis]; ext -> a [id=0, pos="x:@-left!,y:@row!"]; }
```
#edges(layout(parse(src.text).first(), layout-algo: "tree")).first().pospinaccepts numeric point constraints,x:<coord>,y:<coord>, and grouped constraints such asx:@left,x:@+right, or@row.
#let src = ```dot
digraph { a -> b [id=0, pin="x:@+right"]; }
```
#edges(layout(parse(src.text).first(), layout-algo: "tree")).first().statements.at("pin", default: none)Parameters
input
string or bytes
DOT source text containing one or more graph or digraph definitions.
default-node-data
any
Default data merged into every node data. Captured node data fields override it, but bare dot statements don’t set data fields. To turn statements into data fields, use eval-node-fields.
#let a = ```dot
digraph {
a -> b -> c -> d -> a
a -> a
b -> d
c [particle="q"]
}
```
#let g = parse(a.text,default-node-data:(particle:"g")).at(0)
#nodes(g).map(n=>n.data.particle)Default: none
eval-node-fields
string or array
Node fields to evaluate into node data. The eval scope includes node statements plus node, name, and pos.
#let src = ```dot
digraph { a [label="#str(name)"]; }
```
#nodes(parse(src.text, eval-node-fields: "label").first()).first().data.labelDefault: ()
default-edge-data
any
Default data merged into every edge data. Captured edge data fields override it, but bare dot statements don’t set data fields. To turn statements into data fields, use eval-edge-fields.
Default: none
eval-edge-fields
string or array
Edge statement fields to evaluate into graph.edges(g).at(i).data. The eval scope includes edge statements plus edge, orientation, endpoint records, placement fields, and existing edge data. Drawing callbacks later expose the edge index as eid.
#let src = ```dot
digraph { a -> b [id=0, label="#orientation"]; }
```
#edges(parse(src.text, eval-edge-fields: "label").first()).first().data.labelDefault: ()
default-source-data
any
Default data merged into every source half-edge data. Captured source data fields override it.
Default: none
eval-source-fields
string or array
Source half-edge fields to evaluate into edge.source.data. The eval scope includes source endpoint fields statement, port-label, compass, node, and hedge, plus the surrounding edge fields.
#let src = ```dot
digraph { a:left:e -> b [id=0, source="#port-label"]; }
```
#edges(parse(src.text, eval-source-fields: "statement").first()).first().source.data.statementDefault: ()
default-sink-data
any
Default data merged into every sink half-edge data. Captured sink data fields override it.
Default: none
eval-sink-fields
string or array
Sink half-edge fields to evaluate into edge.sink.data. The eval scope includes sink endpoint fields statement, port-label, compass, node, and hedge, plus the surrounding edge fields.
#let src = ```dot
digraph { a -> b:0:w [id=0, sink="#str(hedge)"]; }
```
#edges(parse(src.text, eval-sink-fields: "statement").first()).first().sink.data.statementDefault: ()
eval-graph-fields
string or array
Graph statement fields to evaluate into graph.info(g).data. The eval scope includes graph statements and direct graph record fields.
#let src = ```dot
digraph {
title = "Strong graph";
}
```
#info(parse(src.text, eval-graph-fields: "title").first()).data.titleDefault: ()
eval-mode
string
Typst eval mode used for string field values.
Default: "markup"
scope
dictionary
Additional Typst names available while evaluating field values.
Default: (:)
node
Create a graph node item for build.
A Typst label is the node name used by source, sink, and pos. The optional numeric id fixes the resulting graph node index. Extra named arguments are captured as node data fields, so node(<a>, label: [A], color: red) stores (label: [A], color: red). The default draw style uses data.label as the visible node label when present.
Parameters
..args
label
Optional positional node name. Must be a Typst label when provided; extra named arguments become data fields.
name
none or label
Typst node name for references.
Default: none
id
none or int
Numeric graph node index. Must be unique and in bounds when provided.
Default: none
pos
none or dictionary
Node placement.
Default: none
shift
none or string or array or dictionary
Drawing shift stored as a statement.
Default: none
statements
dictionary
Additional flat node statements. Used by DOT; values cannot nest.
Default: (:)
source
Create a source half-edge endpoint.
node may be a node name like <a> or a numeric node index. name gives the half-edge a Typst name; id is a numeric half-edge order/index override. Extra named arguments are captured as source data fields.
Parameters
node
label or int
Referenced node, either by Typst label name or numeric node index.
..args
any
Extra named arguments become source data fields.
name
none or label
Optional half-edge name used for later references.
Default: none
id
none or int
Optional numeric half-edge index/order override.
Default: none
statement
none or string
DOT-ish flat statement used for matching/joining dangling half edges.
Default: none
compass
none or string
DOT compass point such as "n", "s", "e", or "w".
Default: none
sink
Create a sink half-edge endpoint.
node may be a node name like <a> or a numeric node index. name gives the half-edge a Typst name; id is a numeric half-edge order/index override. Extra named arguments are captured as sink data fields.
Parameters
node
label or int
Referenced node, either by Typst label name or numeric node index.
..args
any
Extra named arguments become sink data fields.
name
none or label
Optional half-edge name used for later references.
Default: none
id
none or int
Optional numeric half-edge index/order override.
Default: none
statement
none or string
DOT-ish flat statement used for matching/joining dangling half edges.
Default: none
compass
none or string
DOT compass point such as "n", "s", "e", or "w".
Default: none
edge
Create a graph edge item for build.
Positional arguments may contain one source, one sink, and optionally one Typst label used as the edge name. The numeric id chooses the edge order. Extra named arguments are captured as edge data fields, so edge(source(<a>), sink(<b>), particle: "g") stores (particle: "g"). The default draw style uses data.label as the visible edge label when present.
Parameters
..args
any
Source/sink half-edges and optional edge name; extra named arguments become data fields.
name
none or label
Typst edge name.
Default: none
id
none or int
Numeric edge order/index override.
Default: none
orientation
string
Edge orientation: "default", "reversed", or "undirected".
Default: "default"
pos
none or dictionary
Edge placement.
Default: none
shift
none or string or array or dictionary
Drawing shift stored as a statement.
Default: none
label-pos
none or string or array or dictionary
Edge label position stored as a statement.
Default: none
label-angle
none or int or float or string
Edge label angle stored as a statement.
Default: none
bend
none or int or float or string
Edge bend stored as a statement.
Default: none
statements
dictionary
Additional flat edge statements. Used by DOT; values cannot nest.
Default: (:)
group
Create a grouped placement coordinate.
side: "+" keeps the solved coordinate non-negative and side: "-" keeps it non-positive. Groups are layout constraints and therefore require pin placement, which is the pos default.
#group("right", side: "+")Parameters
name
string or int or bool
Group identifier shared by positions constrained to the same coordinate.
side
none or string
Optional sign constraint: "+", "-", "positive", or "negative".
Default: none
pin
Mark one coordinate as a layout constraint.
Parameters
value
int or float or dictionary
Coordinate value to constrain.
start
Mark one coordinate as an initial layout value only.
Parameters
value
int or float
Coordinate value to use as the layout seed.
pos
Create a first-class graph placement.
The default mode: "pin" turns numeric coordinates into layout constraints and also makes the coordinates immediately drawable without a layout pass. Use start(value) for an individual coordinate that should only seed the layout, or pin(value) to pin an individual numeric coordinate when mode: "start" is used. Grouped coordinates are always layout constraints for their axis.
#pos(x: group("right", side: "+"), y: start(10))Parameters
x
none or int or float or dictionary
Absolute or grouped x coordinate.
Default: none
y
none or int or float or dictionary
Absolute or grouped y coordinate.
Default: none
ref
none or label or int
Node reference for relative placement, by name or numeric index.
Default: none
dx
none or int or float
Relative x offset from ref.
Default: none
dy
none or int or float
Relative y offset from ref.
Default: none
mode
string
Placement mode: "pin" constrains layout, "start" only seeds it.
Default: "pin"
map
Map graph metadata to new native data.
The callbacks receive decoded records plus a fields dictionary containing merged statements and direct record fields. A callback returns none to leave the record unchanged, (data: value) to set new native data, or structural fields such as pos, shift, and statements to patch data seen by later layout calls.
#let g = build({ node(<a>) })
#let g = map(g, node: node => (data: (label: [A])))
#nodes(g).first().data.labelParameters
graph_
dictionary
Graph object to transform.
graph
none or function
Callback for graph metadata records.
Default: none
node
none or function
Callback for node records.
Default: none
edge
none or function
Callback for edge records.
Default: none
source
none or function
Callback for source half-edge records.
Default: none
sink
none or function
Callback for sink half-edge records.
Default: none
style
Attach layout-relevant drawing style to a graph.
Node style is measured immediately and stored as layout-width / layout-height statements for later layout calls. Edge labels are measured as label-width / label-height statements for label placement. draw uses the stored node and edge-label style by default.
Parameters
graph_
dictionary
Graph object to style.
scope
dictionary
Extra scope visible to label/style callbacks.
Default: (:)
unit
int or float or length or ratio
Coordinate length for one graph-layout unit. Numbers are interpreted as em.
Default: 1
node-label
auto or content or string or function or none
Node label content or callback. auto uses node data/statement label or node name.
Default: auto
node-label-style
dictionary or function
CeTZ content style for node labels. Its padding contributes to measured node size.
Default: (:)
node-style
dictionary or function or none
CeTZ node shape style or callback. Explicit numeric radii contribute to measured node size.
Default: (:)
edge-label
content or string or function or none
Edge label content or callback. none leaves edge labels unstyled and unmeasured.
Default: none
edge-label-style
dictionary or function
CeTZ content style for edge labels. Its padding contributes to measured edge-label size.
Default: (:)
eval-fields
Evaluate selected fields into native data entries.
Each selected field is read from the record’s merged fields dictionary, evaluated in a scope containing those fields, and written to data.<field>.
#let g = build({
node(<a>)
node(<b>)
edge(source(<a>), sink(<b>), statements: (mom: "p"))
}, default-edge-statements: (display-label: "$#mom$"))
#let g = eval-fields(g, eval-edge-fields: ("display-label",))
#edges(g).first().data.at("display-label")Parameters
graph_
dictionary
Graph object whose selected statement fields should be evaluated.
eval-graph-fields
string or array
Graph statement fields to evaluate into graph.info(g).data.
Default: ()
eval-node-fields
string or array
Node statement fields to evaluate into node data.
Default: ()
eval-edge-fields
string or array
Edge statement fields to evaluate into edge data.
Default: ()
eval-source-fields
string or array
Source half-edge fields to evaluate into source data.
Default: ()
eval-sink-fields
string or array
Sink half-edge fields to evaluate into sink data.
Default: ()
eval-mode
string
Typst eval mode used for string field values.
Default: "markup"
scope
dictionary
Additional Typst names available while evaluating field values.
Default: (:)
info
Return graph metadata.
The result has name, global-statements, default-edge-statements, and default-node-statements.
#let g = build({ node(<a>) }, name: "demo")
#info(g).nameParameters
graph
dictionary
Graph object returned by build, parse, layout, or another graph API.
dot
Serialize a graph object to DOT.
#let g = build({
node(<a>)
node(<b>)
edge(source(<a>), sink(<b>))
}, name: "demo")
#dot(g).contains("digraph demo")Parameters
graph
dictionary
Graph object to serialize.
nodes
Return node records, optionally filtered by a subgraph object.
Node name values are Typst labels when present.
#let g = build({
node(<a>)
node(<b>)
})
#nodes(g).map(node => str(node.name)).join(", ")Parameters
graph
dictionary
Graph object to inspect.
subgraph
none or bytes
Optional subgraph filter; only nodes incident to selected half edges are returned.
Default: none
edges
Return edge records, optionally filtered by a subgraph object.
Edge name values are Typst labels when present.
#let g = build({
node(<a>)
node(<b>)
edge(source(<a>, compass: "e"), sink(<b>))
})
#edges(g).len()Parameters
graph
dictionary
Graph object to inspect.
subgraph
none or bytes
Optional subgraph filter; only selected edges/half-edges are returned.
Default: none
node-data
Return one named node’s native data.
name is a Typst label such as <a> or the corresponding string name.
#let g = build({ node(<a>, label: [A]) })
#node-data(g, <a>).labelParameters
graph_
dictionary
Graph object to inspect.
name
label or string
Node name as a Typst label or its string form.
edge-data
Return one named edge’s native data.
name is a Typst label such as <e> or the corresponding string name.
#let g = build({
node(<a>)
node(<b>)
edge(source(<a>), <e>, sink(<b>), label: [$p$])
})
#edge-data(g, <e>).labelParameters
graph_
dictionary
Graph object to inspect.
name
label or string
Edge name as a Typst label or its string form.
update-node-data
Update one named node’s native data.
update may be a replacement data value or a function (data, node) => new-data.
#let g = build({ node(<a>) })
#let g = update-node-data(g, <a>, (label: [A]))
#nodes(g).first().data.labelParameters
graph_
dictionary
Graph object to update.
name
label or string
Node name as a Typst label or its string form.
update
any or function
Replacement data or (data, node) => new-data callback.
update-edge-data
Update one named edge’s native data.
update may be a replacement data value or a function (data, edge) => new-data.
#let g = build({
node(<a>)
node(<b>)
edge(source(<a>), <e>, sink(<b>))
})
#let g = update-edge-data(g, <e>, (label: [$p$]))
#edges(g).first().data.labelParameters
graph_
dictionary
Graph object to update.
name
label or string
Edge name as a Typst label or its string form.
update
any or function
Replacement data or (data, edge) => new-data callback.
join
Join two graphs by matching dangling half-edge statements or ids on key.
Supported key values are "statement", "compass", and "id".
#let left = build({
node(<a>)
edge(sink(<a>, statement: "j"))
})
#let right = build({
node(<b>)
edge(source(<b>, statement: "j"))
})
#edges(join(left, right, key: "statement")).len()Parameters
left
dictionary
Left graph object.
right
dictionary
Right graph object.
key
string
Dangling half-edge match key: "statement", "compass", or "id".
Default: "statement"
cycles
Return subgraph objects for the graph’s cycle basis.
#let g = build({
node(<a>)
node(<b>)
edge(source(<a>), sink(<b>))
})
#cycles(g).len()Parameters
graph
dictionary
Graph object to analyze.
forests
Return subgraph objects for the graph’s spanning forests.
#let g = build({
node(<a>)
node(<b>)
edge(source(<a>), sink(<b>))
})
#forests(g).len()Parameters
graph
dictionary
Graph object to analyze.