Nodes
Nodes are content centered at a coordinate. By default, nodes fit to their content (with an inset), but can also be given a specific size and shape. Nodes can be given various styles including stroke and fill.
Edges automatically snap to nodes (with an node.outset) and the positions and sizes of nodes affects diagram layout (unlike edges or plain CeTZ objects).
#diagram(
spacing: (5pt, 2em), // small columns, large rows
node((0,0), $A$),
node((1,0), $f$, stroke: 1pt),
node((2,0), $g$, stroke: 2pt + blue, shape: rect),
node((3,0), $X$, stroke: blue, extrude: (0, 3)),
{
let b = blue.lighten(70%)
node((0,1), `xyz`, fill: b, )
let dash = (paint: blue, dash: "dashed")
node((1,1), `xyz`, stroke: dash, inset: 1em)
node((2,1), `xyz`, stroke: b, extrude: (0, -2))
node((3,1), `xyz`, fill: b, height: 5em,
corner-radius: 5pt)
}
)Node styles
Node styles can be set with named arguments, like
node(stroke: 2pt, ..)while default styles can be set by passing options to the enclosing diagram by adding a prefix, like
diagram(node-stroke: 2pt, ..)or by using
cetz.draw.set-style(node: (stroke: 2pt))which works in a diagram() and a CeTZ canvas. Like CeTZ styles, cetz.draw.set-style() is scoped to the current cetz.draw.group().
#diagram(
node-stroke: 2pt, // default stroke style for nodes
node((0,0), fill: yellow, [A]),
edge("<->"),
{
import cetz.draw: *
set-style(node: (extrude: (2,0)))
// stroke becomes 2pt + blue
node((0,1), stroke: blue, [B], outset: 4pt)
}
)Available node styles:
node.fillnode.strokenode.extrudenode.insetnode.outsetnode.shapenode.layerAny other styles specific to the node shape:
width,height,corner-radiusforrect()nodesradiusforcircle()nodesfitandcell-fit- and so on
Node shapes
By default, nodes are circular if their content is small and square, and rectangular if it is tall or wide. The shape option can be set to any of the following built-in shapes.
Most shapes have additional styles specific to the shape, such as:
width,heightandfitfor all shapescorner-radiusforrect()radiusforcircle()angleforparallelogram(),keystone(),triangle(),house(),chevron()andhexagon()dirfortriangle(),house(),chevron()- and others
Additional styles are described in each shape’s documentation.
A node’s shape can often be automatically inferred from the other styles given. For example, you can write node(.., radius: 3cm) instead of node(.., shape: "circle", radius: 3cm).
#diagram(
node-fill: yellow,
node-stroke: 0.7pt,
node((0,0), corner-radius: 5pt)[Rounded],
edge("->-"),
node((0,1), radius: 2mm)
)Making shapes fit better
All shapes have a fit parameter, which adjusts how tightly the shape fits in the body.
fit: 0makes the shape small enough to fit inside the bodyfit: 1makes the shape large enough so the node body fits inside
The default is usually in between, striking a balance. If a node looks too cramped inside a shape, you can usually adjust the fit instead of tweaking the node.inset.
You can see the bounding box of the node body with the "node.body" debug option.
#diagram(
debug: "node.body",
spacing: 5pt,
node-stroke: blue,
node-fill: blue.lighten(80%),
node-shape: "diamond",
node((0,0), fit: 0)[Zero fit],
node((0,1), fit: 0.5)[Partial],
node((0,2), fit: 1)[Whole fit],
)In addition to controlling how the node’s body fits in the shape, the fit-cell parameter controls how the shape fits in the surrounding flexigrid cell.
This only matters for the layout of a surrounding flexigrid() or diagram(); the fit-cell style doesn’t affect node itself.
#diagram(
debug: "grid.cells node.body",
spacing: 5pt,
node-stroke: blue,
node-fill: blue.lighten(80%),
node-shape: "triangle",
node((0,0), fit-cell: 0)[Zero cell fit],
node((1,1), fit-cell: 1)[Total cell fit],
)Enclose nodes
Enclose nodes are a special type of node that are positioned around other nodes, which is useful for diagrams with nested layouts. Nodes with the node.enclose option automatically wrap around the specified nodes.
#diagram(
spacing: (10mm, 5mm),
node-stroke: 0.5pt,
node-fill: white,
node-corner-radius: 2pt,
node((-2,0), radius: 2pt, fill: black),
edge("r,u,r", "->", $f$),
edge("r,d,r", "..>", $g$),
node((0,-1), $F(s)$, <f>),
edge("->", (1,0), corner: "-|"),
node((0,+1), $G(s)$, <g>),
edge("..>", (1,0), corner: "-|"),
node((1,0), $ plus.o $, stroke: none, inset: 2pt),
edge("->", "r"),
// enclose nodes by name
node(enclose: (<f>, <g>), text(teal)[Group],
inset: 10pt,
stroke: teal,
fill: teal.transparentize(90%),
),
)Nodes can also have row and column spans, which is a similar but distinct concept. Unlike nodes which span rows or columns, enclose nodes do not exist within a flexigrid layout, and do not affect the position of other nodes.