Edges

edge(..vertices, marks, labels, ..)

Use the edge() function inside a diagram(), cetz.canvas() or flexigrid() to draw lines or paths with various edge effects, including:

You can specify an edge with a sequence of vertex coordinates, similar to cetz.draw.line(..). There are a few built-in edge kinds which accept extra named arguments (such as bend: 30deg for arc edges or corner: "|-" for right-angled corners).

Edge effects can also be applied to any CeTZ path by wrapping it in the edge() function.

Specifying vertices

Vertices should be the first arguments, unless there are two vertices, in which case you may put the marks argument in between:

edge((0,0), (1,0), (2,1), "->")
edge(<from>, "->", <to>)

Alternatively, an array of vertices may be supplied to the edge.vertices argument.

Automatic vertices

To refer to the previous or next node in a diagram, you can use auto, as in:

edge(<from>, <to>)
edge(<from>, auto)  // to next node
edge(auto, <to>)    // from previous node
edge(auto, auto)    // connects surrounding nodes

In unambiguous situations, auto vertices can be omitted altogether:

edge(<coord>) == edge(<coord>, auto)
edge("->", <coord>) == edge(auto, "->", <coord>)

For example, this diagram uses implicit automatic coordinates:

#diagram({
  edge((-1,0), "~>")               // to next node (A)
  node((0,0), [A])
  edge("<..>")                     // connects A to B
  edge("->", (.5,1), bend: -30deg) // from previous node (A)
  node((1,0), [B])
})

Relative coordinates

You may use strings such as "u" (up) or "sw" (south west) as shorthands for relative vertex coordinates (rel: (du, dv)). The first letters of top/up/north, bottom/down/south, left/west, and right/east are allowed.

Commas can be used to separate multiple coordinates, so "r,d" is understood as "r", "d" which is (rel: (1, 0)), (rel: (0, 1)), assuming flexigrid.axes is set so (𝑢,𝑣) goes (→,↓).

#diagram(
  spacing: 5mm,
  node((0,0), $A$),
  edge("u,rr,d", "-->"),
  node((1,0), stroke: 1pt)[Box],
  node((2,0), $B$),
)

In example above, the edge implicitly begins from the previous node (relative coordinates cannot be used as the first coordinate).

Edge labels

Edges can have any number of labels attached to them at specific positions. Any markup or math content passed to edge() after vertices is interpreted a label.

#diagram(
  debug: "edge.label",
  node((0,0), [A]),
  edge("->", [Label], bend: 60deg),
  edge("->", label: $f$, bend: -60deg),
  node((1,0), [B]),
)

The following edge options set properties of the edge’s label(s):

To specify multiple labels, pass an array of dictionaries to edge.label, where each contains properties without the label- prefix:

label: (
  (body: [First label], pos: .., side: ..),
  (body: [Second label], angle: .., sep: ..),
)

In the example below, the vertical edges have two labels each:

#diagram(
  spacing: 15mm,
  node((0,0), $V$),   edge("->", $f$),        node((1,0), $W$),
  node((0,1), $K^n$), edge("->", $tilde(f)$), node((1,1), $K^m$),

  edge((0,0), (0,1), "->", label: (
    (body: $ tilde $, angle: auto),
    (body: $kappa_X$, side: right),
  ), label-sep: 3pt), // label-sep applies to both labels

  edge((1,0), (1,1), "->", label: (
    (body: $ tilde $, angle: auto),
    (body: $kappa_Y$, side: right),
  ), label-sep: 3pt),
)

Kinds of edges

To make it easy to achieve common edge shapes, like arcs, loops or right-angled corners, edges can have different kinds, depending on the combination of named arguments present.

Edge KindArguments
arcbend
bezier-cubicfrom, to
bezier-fromfrom
bezier-toto
bezier-throughthrough
looploop, loop-angle
cornercorner

Polyline

By default, edges are displayed as straight paths between two or more vertices. As with all edge kinds, they can have rounded corners.

Arc

edge(.., bend: angle | length)

A perfect arc can be made with the bend option, which can be an angle (measuring the initial angle of the arc relative to a straight edge) or a length, specifying the height of the arc.

Bézier

edge(.., from: angle | (angle, length), to: angle | (angle, length))

Quadratic or cubic Bézier curves can be specified by giving an angle or polar coordinate such as (90deg, 5mm) to one of from, to or both.

You can also specify a Bézier curve through another point with edge(.., through: <coord>).

Loop

A perfectly circular loop can be specified by giving either the loop’s radius as loop or its direction loop-angle (which can be an angle or a direction like "north" or right.)

Corner

Edges with one or two right-angled corners can be specified with corner, which is a string of "-" and "|" specifying the order of horizontal or vertical segments.

CeTZ

When integrating with CeTZ, you can wrap a CeTZ element in edge() to apply any of fletcher’s edge effects to it. When used in this mode, edges may have no vertices.

For example, below we draw a composite CeTZ path using lines and a cubic Bézier segment and apply fletcher’s marks, multistroke effects, label placement and snapping.

#cetz.canvas({
  import cetz.draw: *
  let path = merge-path({
    line((0,0), (0,1))
    bezier((0,1), (2,0), (1,1), (1,0))
    line((2,0), (2,1))
  })
  scale(1.4)
  circle((0,0), radius: .4, name: "orb")
  edge(path, "<=>", snap-to: ("orb", none), label: (
    (body: $L$, pos: 0.5),
    (body: $R$, pos: 2.5, side: right),
    (body: `mid`, pos: 1.5, side: center, angle: auto),
  ), label-sep: 10pt)
})

Path anchors

Edges support path anchors, like most CeTZ elements. This allows you to refer to points along an edge. In particular, the label position is a path anchor, and if the edge has a edge.name, you can place other elements using the coordinates <name.anchor>, "name.anchor" or (name: "name", anchor: "anchor").

Path anchorDescription
25%Fraction of total length along path
"start", "mid", "end"Aliases for 0%, 50% and 100%
10pt, -2emCertain length from start or end of path
0.5, 2.5, -1Segment indices referring to or between vertices of the path

The default path anchor is the midpoint, so in the example below <f> refers to (name: "f", anchor: 50%).

#diagram(
  spacing: 15mm,
  node((0,0), $X$),
  edge("->", bend: +60deg, $f$, name: <f>),
  edge("->", bend: -60deg, $g$, name: <g>),
  node((1,0), $Y$),

  edge(<f>, "=>", <g>, $alpha$, shorten: 3pt),
)

Fletcher defines segment indices in addition to CeTZ’s usual path anchors, to make it easier to place labels along complex paths. A segment index is a number which interpolates between the edge’s vertices. For example, 0 is the start, 1 is the first vertex, and 2.5 is halfway along the third segment of the edge. Negative indices refer to vertices in reverse order.

#diagram(
  spacing: 15mm,
  import cetz.draw: *,
  set-style(circle: (radius: 4pt)),
  edge("r,t,rd,r", ">>->", name: "foo", stroke: 1pt,
    [3#super[rd]], label-pos: 2),     // label at 3rd vertex
  circle("foo.0.5", stroke: red),     // 50% along first segment
  circle("foo.1", stroke: green),     // second vertex
  circle("foo.-0", stroke: blue),     // last vertex
  circle("foo.2.5", stroke: fuchsia), // 50% along third segment
)

Importantly, node positions cannot depend on edge anchors. This is because nodes are processed before edges. To place annotations on edges, you can use labels or draw directly with CeTZ, like in the example above.