On this page

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

The 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>).label

source(..) 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. Its eval-graph-fields, eval-node-fields, eval-edge-fields, eval-source-fields, and eval-sink-fields arguments are convenience arguments for graph.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>) and graph.edge-data(graph, <name>) return one named node or edge data.
  • graph.update-node-data(graph, <name>, data) and graph.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(..) and sink(..) 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).name

Default: none

data

any

Native Typst graph data.

#let g = build(data: (a:(b:(1, ))), {
})
#info(g).data

Default: 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-statements

Default: (:)

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

Graph-level handling:

  • The DOT graph name becomes graph.info(g).name. A graph-level name attribute can name an anonymous graph; a named graph/digraph header takes precedence.
#let src = ```dot
digraph header_wins { graph [name="ignored"]; a }
```
#info(parse(src.text).first()).name
  • Top-level key=value statements and graph [key=value] attributes become graph.info(g).global-statements, except for the consumed name. They do not configure layout, even when a key looks like a layout option; pass layout options directly to layout.
#let src = ```dot
digraph { steps=1; graph [subtitle="nested"]; a }
```
#let statements = info(parse(src.text).first()).global-statements
#statements.steps
#statements.subtitle
  • node [key=value] and edge [key=value] become default-node-statements and default-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.particle

Node handling:

  • The DOT node id becomes the node name returned by nodes. If the DOT node id is numeric, it is consumed as the node index instead and the public name is none.
#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=invis marks the DOT node as a dangling external endpoint. It is not returned by nodes. Its remaining attributes are copied onto the external edge that touches it, except shape and label; style and id are 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 style is consumed for every node; only style=invis has 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().statements
  • pos is parsed as node placement. Simple numeric values are exposed as node.pos; extended placement values are used by layout. pin is 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))
  • shift is parsed as a drawing shift and also remains available as a statement. eval is 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.statements

Edge handling:

  • id=<n> is consumed as the edge index. It is not preserved as a statement; drawing callbacks expose the resulting stable edge index as eid. 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, and dir=none become 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="..." and sink="..." are consumed as endpoint statement values and removed from edge statements. On an external edge, use the attribute matching the real endpoint side in the DOT edge: ext -> a uses sink=..., while a -> ext uses source=....
#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))
  • pos is parsed as the edge control-point placement. pin is an explicit edge layout constraint. shift, label-pos, label-angle, and bend are parsed into edge geometry fields; except for pin, 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 shape or label.
#let src = ```dot
digraph { ext [style=invis, column=left, shape=none, label=skip]; ext -> a [id=0]; }
```
#edges(parse(src.text).first()).first().statements

Port and half-edge handling:

  • DOT ports of the form node:port and node:port:compass are preserved on source/sink endpoint records. Numeric ports also assign explicit half-edge ids. Non-numeric ports are exposed only as port-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 include n, 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 join with key: "id"; query and eval records expose the resulting half-edge index as hedge.
#let src = ```dot
digraph { a:1 -> b:0 [id=0]; }
```
#let e = edges(parse(src.text).first()).first()
#e.source.hedge
#e.sink.hedge

Placement 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!" and pos="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> and y:<coord> entries. ! applies to the individual axis, for example pos="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 @-name in axis form and must be pinned with !, for example pos="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().pos
  • pin accepts numeric point constraints, x:<coord>, y:<coord>, and grouped constraints such as x:@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.label

Default: ()

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

Default: ()

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

Default: ()

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

Default: ()

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

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: (:)

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

Parameters

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).name

Parameters

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>).label

Parameters

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>).label

Parameters

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

Parameters

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

Parameters

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.

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.