Diagrams and Layout

Fletcher encourages the use of an “elastic, tabular layout” called a flexigrid.

Diagrams use a flexigrid layout by default, but you can also draw in a CeTZ canvas instead (see Drawing in a CeTZ canvas). To render a diagram, fletcher first collects the row/column positions (or 𝑢𝑣 coordinates) of all nodes in the diagram and calculates the row and column sizes for the flexigrid layout. After the final flexigrid is determined, all nodes, edges and other objects are drawn in a context where both 𝑢𝑣 coordinates and normal 𝑥𝑦 coordinates can be used.

If nodes have fractional 𝑢𝑣 coordinates, an iterative algorithm is used to calculate the minimum row and column sizes which accommodate the nodes. This usually requires only a few steps (see the flexigrid.max-layout-iterations option for details).

Nodes are placed within cells

A node inside a flexigrid lives within a cell, which is visible when the "node.cell" debug option is turned on for the node or diagram.

#diagram(
  debug: "grid.coords",
  node-stroke: 1pt,
  node((0,0), radius: 5mm, debug: "node.cell"),
  node((0,1))[This is a wide node],
)

Nodes placed at fractional coordinates still live in their own cell, defined by a linear interpolation. For example, below the circle’s cell is shown as it moves along (0,0)(1,1).

This behaviour guarantees that a small nudge in a node’s position only results in a small change to the diagram’s final appearance.

Node alignment within cells

A node’s cell is defined by the flexigrid’s rows and columns, and grows with the size of the node. However, the cell can be larger than the node’s bounding box (visible with the "node.bounds" debug option). By default, nodes are placed in the center of their cell, but they can also be aligned within cells with node.align.

#diagram(
  node-stroke: 1pt,
  node((0,0), radius: 5mm, debug: "node.cell",
    align: right),
  node((0,1))[This is a wide node],
)

Node row and column span

A node’s cell can be made to span multiple columns or rows in a flexigrid. When this happens, the node’s size is automatically set to the full size of the cell.

#diagram(
  debug: "grid",
  node-fill: teal.lighten(50%),
  node-stroke: 0.5pt,
  node-shape: rect,
  node((0,0), $X$),
  node((0,1), $Y$),
  node((1,0), rowspan: 2)[Two Rows],
  node((0,2), colspan: 2)[Two Columns],
)

Note that node.rowspan and node.colspan affect the node’s enclosing cell, while the size of the node can be controlled independently. However, this use case is rare, since you can also place nodes in between cells by using fractional coordinates.

#diagram(
  debug: "grid.coords",
  node-fill: green.lighten(50%),
  node-stroke: 0.5pt,
  node((0,0), $A B$),
  node((1,0), $C D$),
  node((0,1), [Columns],
    colspan: 2, width: 16mm,
    debug: "node.cell"),
)

This is similar but different to Enclose nodes; row and column spans only work inside a flexigrid.