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, @data#islands, @preset#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.

Warning
Elsewhere the characters are text: a 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`.

A geo dataset has no columns. #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:

Four grey readings rising from (1, 1.1) to (4, 4.2) with a teal line fitted through them, and a caption above them reading slope = 0.97.Four grey readings rising from (1, 1.1) to (4, 4.2) with a teal line fitted through them, and a caption above them reading slope = 0.97.
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 @if 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, score
  • slider — value
  • cue.slider — value
  • selector — value
  • cue.selector — value
  • curve — max, min
  • integral — value, exact
  • angle — value, radians
  • boxplot — 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-max
  • density — mode, peak, current-bandwidth
  • fit — slope, intercept, r2
  • contour — min, max
  • surface — min, max
  • polyhedron — volume, area, vertices, edges, faces
  • choropleth — min, max, mean, count
  • arc — distance
  • switch — level
  • clock — level
  • gate — level
  • mux — level
  • alu — level
  • flipflop — level
  • lamp — level
  • network — vertices, edges, components, communities, density
  • vertex — degree, in-degree, out-degree, betweenness
  • path — length, hops

Last updated