ref / selectAll
ref is the single reference noun in GoFish, and it works in two positions:
- Inline in a layout —
arrow(ref("a"), ref("b")),ref(token).row[2]— it resolves at layout time against the name tree, hygienically scoped (see scoping). This is therefmark. - As chart data —
chart(ref("maxBar")).mark(text(...))— it resolves at build time against the named-layer registry and stands in for the one node registered under that name.
selectAll(name) is the plural chart-data verb: it returns an array of refs, one per node a named mark produced (node-unit; aggregate or not, no flattening). Pass either form as the data argument to a second chart() call to build overlays and connectors.
Think of selectAll as the DOM's querySelectorAll (always a collection) and ref(name)-as-data as querySelector (the one-or-bust singular).
const lakeTotals = Object.entries(_.groupBy(seafood, "lake")).map(
([lake, items]) => ({
lake,
count: items.reduce((sum, item) => sum + item.count, 0),
})
);
gf.layer([
// Step 1: name the mark
gf
.chart(lakeTotals)
.flow(gf.spread({ by: "lake", dir: "x" }))
.mark(gf.rect({ h: "count" }).name("bars")),
// Step 2: selectAll those nodes as data for a connector
gf
.chart(gf.selectAll("bars"))
.mark(gf.line({ stroke: "coral", strokeWidth: 2 })),
]).render(root, { w: 400, h: 250, axes: true });Signature
ref(name: string): GoFishRef; // singular; see ref mark for all forms
selectAll(layerName: string): GoFishRef[]; // one ref per matching noderef has several other inline forms (Token, path, direct node) — see the ref mark for the full signature. This page covers its use as chart data alongside selectAll.
Parameters
| Parameter | Type | Description |
|---|---|---|
name | string | The name of the layer to reference (registered via .name() on a mark) |
Singular as data: exactly one
When you pass ref(name) as chart data it must resolve to exactly one node:
- Zero matches → error. Nothing was registered under that name in scope.
- More than one match → error, with a hint to use
selectAll(name)instead. A named mark that produced several nodes is a collection, and the singular reference refuses to silently pick one.
gf.layer([
gf
.chart(data)
.flow(/* ... */)
.mark(gf.rect({ h: "total" }).name("kpi")),
gf.text({ text: "peak" }).name("label"),
// ref("kpi") as the connector's target: one ref; throws on 0 or >1 nodes
gf.Connect({ source: "middle" }, [gf.ref("label"), gf.ref("kpi")]),
]);Node-unit selection
selectAll selects at node granularity: one ref per node the named mark produced, never flattened and never merged. Each ref points at a placed node, so overlay marks position themselves relative to it, and ref.datum is that node's data bag.
const bars = gf.selectAll("bars"); // GoFishRef[]
bars[0].datum; // the raw row-bag behind the first barSee ref.datum for what the bag contains (a 1-row array for a fully-split leaf, all the partition's rows for an auto-summed aggregate).
Why a ref, not a "selection"?
GoFish models a selection as a plain array of refs rather than a bespoke selection object, and this is deliberate:
- A ref is structurally a one-element selection.
ref(name)(one ref) andselectAll(an array of refs) are the singular/plural of the very same noun, so there is nothing new to learn — the ref you get from a selection behaves exactly like a ref you wrote by hand inline. - Geometry is decoupled from data. A ref points at a placed node; you read its placement off the ref (that is how
line/areadraw) and its bound datum viaref.datum. Selecting does not flatten or reshape your data. - Batch operations live in
.flow, not on the noun. Unlike D3, where the selection object owns.data(),.attr(),.filter(), etc., GoFish keeps a selection inert. To partition, re-key, or re-encode a selection you run it through operators in.flow(e.g.group,spread) — see path-awarebybelow.
Hygienic scoping
Layer-name lookup is hygienic: a name registered via .name() is visible only within its scope and does not cross component boundaries. A name registered on a mark inside a createMark component is internal to that component — it is not selectable from outside. This is the same component-boundary rule that string-name ref resolution always followed inline, so the inline-layout and chart-data lookup paths now share one scoping rule.
Inline selectAll is not supported yet
selectAll is a chart-data verb only. Using it inline inside a layout throws — pass it as the data argument to a chart() instead. (Inline plural references may arrive later; for now use a named layer + selectAll as data.)
Connectors take selectAll directly
line and area consume an array of refs and read placed geometry off them, so feed them selectAll:
gf.chart(gf.selectAll("points")).mark(gf.line({ stroke: "black" }));When the connector traces a chart's own marks, the builder method .connect() is sugar for this two-layer selectAll recipe — only reach for selectAll by hand to connect another chart's marks.
Path-aware by after a selection
After selectAll, the stream items are refs, not raw records. Operators' by option is path-aware (lodash _.get), so re-encode by the datum path:
gf.chart(gf.selectAll("bars"))
.flow(gf.group({ by: "datum.species" })) // not "species"
.mark(gf.area({ opacity: 0.8 }));A datum.field path resolves to a scalar only if every row in the ref's bag agrees on that field (homogeneity collapse — SQL's ONLY_FULL_GROUP_BY rule); otherwise it is undefined. So by: "datum.lake" works on lake-aggregate bars (all rows share a lake) but by: "datum.species" does not until you disaggregate. by also accepts a function as an escape hatch: group({ by: (r) => r.datum.species }). See spread for the full explanation of why by is path-prefixed but mark channels are not.
pluck(source, path) — every value at a path
Where by's path projection collapses a row-bag to a single scalar (and goes undefined when the bag disagrees), pluck is its un-collapsed counterpart: it returns the full set of distinct values present at a path — "every possible value here."
import { pluck } from "gofish-graphics";
pluck(ref, "species"); // → ["Bass", "Trout", ...] (distinct across the bag)source may be a ref (reads its .datum bag), a row array, or a single row. Reach for pluck when a field is multi-valued in the current bag and you want to enumerate its values rather than group by it — the case where by: "datum.field" would resolve to undefined.
JavaScript only
pluck is exported from the JS package (gofish-graphics). The Python wrapper does not expose it yet.
