Skip to content

ref ​

References another node so later marks can reuse its position or bounding box — the basis for overlays, connectors, and arrows.

ref is the single reference noun, usable in two positions:

  • Inline in a layout (this page): arrow({}, [ref("a"), ref("b")]) inside a .relate() clause, ref(token).row[2] anywhere — resolved at layout time against the name tree.
  • As chart data: chart(ref("maxBar")).mark(text(...)) — resolved at build time against the named-layer registry, where it must match exactly one node (use selectAll for many). See ref / selectAll for the chart-data role and node-unit selection.

See also How to name and scope for when to use strings vs. tokens vs. paths.

Signature ​

ts
ref(
  nodeOrSelection:
    | string                              // nearest-match lookup, bounded by createMark
    | Token                               // global lookup; chainable as ref(token).foo[i].bar
    | (Token | string | number)[]         // path (array form)
    | GoFishNode                          // direct node
    | { __ref: GoFishNode }
);

Forms ​

String — nearest match ​

A string ref("x") is legal only inside a .relate() clause. Anywhere else it throws, and the message says to move it into .relate(). Inside a clause it finds the node named .name("x") (or carrying a token tagged "x") that is nearest to the layer that relates it: the closest match inside that layer wins, counted in steps down from the layer, so a direct child beats a node with the same name nested deeper. The search never crosses a createMark boundary, in either direction, and the match must lie inside the related layer. Two matches at the same smallest distance is an error, and so is no match at all. A data key is not a name, so ref("a") does not find a mark just because its row is keyed "a".

ts
layer([
  rect({ w: 80, h: 40 }).name("bg"),
  text({ text: "label" }).name("label"),
]).relate(() => [
  arrow({}, [ref("label"), ref("bg")]), // resolve to the text and the rect
]);

A .relate() operand is the same thing spelled as a variable: ({ bg, label }) => [arrow({}, [label, bg])] builds those two refs. See How to name and scope and Name Resolution & Scoping.

Token — globally addressable ​

A Token (from createName) is a unique JS value. .name(token) registers the node in a global token context; ref(token) retrieves it.

ts
const targetName = createName("target");

layer([
  rect({ w: 80, h: 40 }).name(targetName),
  // ...somewhere in a sibling subtree:
  ref(targetName),
]);

Path — step through scopes + positional children ​

A path starts at a Token and descends one step per segment. Tag strings resolve against the current node's scope map (populated by createName-tagged children inside a scope root). Numbers pick the positional child at that index.

Two equivalent spellings:

ts
// Chained (proxy) — preferred for static paths
ref(globalFrameName).variables[2].value;

// Array — useful when segments are dynamic
ref([globalFrameName, "variables", 2, "value"]);

The chained form is a Proxy that wraps the underlying GoFishRef; instanceof GoFishRef still holds, so it can go anywhere a ref is expected.

For variadic dynamic segments (e.g. spreading a [row, col] tuple into the path), use .path(...):

ts
const cell = addrPos.get(addr)!; // [row, col]
ref(heapName).path(...cell).elmTuples[0];
// equivalent to:
ref([heapName, ...cell, "elmTuples", 0]);

Reserved names. Children registered with one of GoFishRef's own property names (name, type, parent, dims, layout, place, color, …), the escape-hatch method path, or JS-language names (then, toString, constructor, __proto__, …) are not reachable via dotted access. Use ref(token).path("name") or the array form for those.

Direct node ​

Pass a GoFishNode (or a .__ref wrapper) to reference it without name resolution.

ts
const bar = rect({ h: "value" });
layer([bar, ref(bar)]);

Parameters ​

ParameterTypeDescription
nodeOrSelectionstring | Token | (Token | string | number)[] | GoFishNode | { __ref: GoFishNode }What to reference. See forms above — string (local), Token (global), path, or the node.

ref.datum ​

A ref exposes the datum bound to the node it points at via the public .datum getter:

ts
const bars = selectAll("bars"); // GoFishRef[]
bars[0].datum; // the raw row-bag behind the first bar

ref.datum is the raw bag of rows that flowed into the referenced node — an array of records. A fully-split leaf (one datum per node) is a 1-row array; an aggregate — for example a bar produced by rect({ h: "count" }) over a partition, whose height auto-sums several rows — holds all the rows of its partition.

Because it is the raw, un-collapsed bag, you can aggregate over it directly:

ts
import { sumBy } from "lodash";
sumBy(bars[0].datum, "count"); // total count across the bar's rows

Operators read this same bag when you re-encode a selection by a field, e.g. group({ by: "species" }), but with homogeneity collapse applied: the field resolves to a scalar only if every row in the bag agrees on it — see path-aware by. To get every distinct value at a path instead, use pluck.

Notes ​

  • Refs participate in layout: the referenced node's placement determines the ref's bounding box, and Arrow, line, ribbon, etc. use that to draw geometry between nodes.
  • Cross-subtree refs resolve correctly: the ref traverses to the least common ancestor and accumulates coordinate transforms along the way, so you can ref a node inside one component from inside another.
  • Errors name the scope: if a path segment misses, the error lists the tags or indices available at that level.