Nodes

node(position, body, ..)

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

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.

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.