References
Naming a construct with #id, reaching what an id names by a binding or an expression, member access, and the derived attributes a construct publishes as it renders.
#Naming a construct
##id in the head
Any construct may carry an identifier, written with # after its name and before its implicit value: curve#flight, graph#survey, #islands, #accent:text. An id starts with a letter — #2d is not a valid id — and is read in lower case, so #Flight reaches curve#flight.
#One namespace, document-wide
Every id in a document shares one namespace — nodes, datasets, presets and controls alike — and a reference may name a construct declared before it or after it. Two declarations of one id are refused:
@preset#apple(width: 4)
graph{
curve#apple(expression: x^2)
}[reference]duplicate id `#apple` — already declared by a preset at line 7
An id written inside a @define template is the exception: each use gets its own private copy, so two uses never collide.
#Parameters are not ids
A control's implicit value names a parameter, not an id. slider:a declares the parameter a for its scene, so every scene may have its own a; an id is the whole document's. A control whose value the prose cites carries both: slider#spread:s(range: [0, 3]) is the parameter s, read as {{#spread.value}}. Without the id, {{#s.value}} is unknown id `#s`.
#Reaching what an id names
#Bindings
A binding is a reference written bare as an attribute value: #points, or with a member, #islands.area. The compiled document carries the pointer, not a copy of the value; the construct that receives it reads the target as it renders.
graph#survey{
scatter#points(x: #islands.area; y: #islands.species)
fit(of: #points; model: linear)
}
table(data: #islands; columns: island, area)
graph{
curve(expression: x)
}(
continue: #survey
)Only an attribute whose type takes a reference reads one: the type reference, and the series types, which take a dataset column. The reference page of each construct names the type of every attribute.
label: is rich text, so label: slope = #f.slope shows the words slope = #f.slope.The target is checked as the document compiles. An id nothing declares is unknown id `#nope`, and a reference that names the wrong kind of construct says which kinds it takes — reference `#flight` targets a `curve`, expected one of: graph, code, region, flowchart, logic, network.
#Reading a value in an expression
The other way to reach a value is to read it in a {{…}} expression, which works in prose and in any attribute:
Two islands cover {{#islands.area}} km$^2$; the trajectory is drawn in {{#flight.colour}}.
The fit explains {{round(100 * #trend.r2, 1)}}% of the variance.The value read takes part in the expression's arithmetic (Expressions), and a value that only exists once the canvas renders — a derived attribute — makes the expression bound, evaluated by the renderer. Where a bound value is refused lists the positions that cannot wait for it.
#Member access
The . after an id reaches into what it names:
- a dataset's column —
#islands.area; - an attribute written on a construct —
#flight.colour; - a derived attribute —
#trend.slope; - an attribute or export of a named template use —
#tile.area, see@define; - a pack's dataset, through the pack's name —
#biology.lynx-hare.hare.
A column is always reached through its dataset's id. Attribute values are unquoted, so label: island is the word island, never a column. A member the target does not have is a compile error: `curve` (`#c`) declares no attribute `nosuch`, dataset `#d` has no column `c`.
#wards.name is refused with the fix — `#wards` is geo data and has no columns — bind the dataset itself (`attr: #wards`).#Derived attributes
Some constructs compute a value as they render — a fit's slope, an integral's area, where a reader has set a slider. They publish it as a derived attribute: read-only, recomputed every frame, and listed under Derived attributes on the construct's reference page. Writing one is an error:
fit#f(of: #points; model: linear; slope: 2)[attribute]node `fit`: 'slope' is derived and cannot be set
#Reading a derived value
A derived attribute is read through the construct's id, in a {{…}} expression, in prose or in a rich-text attribute such as a mark's label:. A fit has no label:, so a text mark beside it carries its slope:


graph{
scatter#points(x: 1, 2, 3, 4; y: 1.1, 2.3, 2.8, 4.2; colour: grey)
fit#f(of: #points; model: linear)
text(x: 1.5; y: 3.8; label: slope = {{round(#f.slope, 2)}})
}The narrative reads it the same way, and a mark with a label: may caption itself with its own value:
scene{
The fitted slope is {{round(#f.slope, 2)}}.
}[
graph{
scatter#points(x: 1, 2, 3, 4; y: 1.1, 2.3, 2.8, 4.2)
fit#f(of: #points; model: linear)
curve#c(expression: sin(x); label: peaks at {{#c.max}})
}
]The value is written to four significant figures, and as an em dash until the construct has computed it. Arithmetic and round in the expression shape the number; the text micronodes shape how it is written. A member the construct does not publish is a compile error, so a misspelt name never reaches the page.
#Why a derived value stays bound
The compiler has no canvas: it never draws the fit, so it never has the slope. An expression that reads #f.slope is therefore bound — the compiled document keeps it rather than a number, and the renderer evaluates it every frame from the value the fit publishes. That is also why the number follows a reader who moves the data or the degree: nothing was fixed when the document compiled.
The same reason decides where a derived value cannot go. A position the compiler must settle itself — an condition, a loop count, a line of code, a $…$ maths run, an attribute that is not rich text — refuses a bound value with a diagnostic; Where a bound value is refused lists them.
#Derived attributes of a template
A @define template declares derived attributes of its own with entries whose key starts with _. Each is computed once per use, from the use's attributes and the entries above it, and is read through the use's id without the underscore:
@define:square-plot{
square(x: 0; y: 0; size: {{side}})
}(
side: !number(default: 1)
_area: {{side * side}}
)
scene{
The square covers {{#plot.area}} m$^2$.
}[
graph{
square-plot#plot(side: 3)
}
]An export computed from a bound value, such as _slope: {{#f.slope}} over a fit inside the template, is itself bound. Packs publish their constructs' results this way: statistics.normal-area exports probability.
#Every derived attribute
question—response,verdict,correct,answered,scoreslider—valuecue.slider—valueselector—valuecue.selector—valuecurve—max,minintegral—value,exactangle—value,radiansboxplot—median,q1,q3,iqr,min,max,x-median,x-q1,x-q3,x-iqr,x-min,x-max,y-median,y-q1,y-q3,y-iqr,y-min,y-max,re-median,re-q1,re-q3,re-iqr,re-min,re-max,im-median,im-q1,im-q3,im-iqr,im-min,im-maxdensity—mode,peak,current-bandwidthfit—slope,intercept,r2contour—min,maxsurface—min,maxpolyhedron—volume,area,vertices,edges,faceschoropleth—min,max,mean,countarc—distanceswitch—levelclock—levelgate—levelmux—levelalu—levelflipflop—levellamp—levelnetwork—vertices,edges,components,communities,densityvertex—degree,in-degree,out-degree,betweennesspath—length,hops