Data
@data datasets: declaring them inline or from CSV, reaching columns, and feeding tables and marks.
#Declaring a dataset
@data declares a named dataset — tabular data with named columns, written once and reached from anywhere in the document. Inline, the body is pipe-delimited rows under a header row that names the columns:
@data#islands{
island | area | species
Baltra | 25.09 | 58
Bartolomé | 1.24 | 31
Santa Cruz | 903.82 | 444
}Attached, a CSV supplies both — #survey:galapagos-plants-1973.csv — with src as the implicit attribute and column names from the file's header row. Downstream, nothing distinguishes the two forms: inline for a table small enough to read in the source, attached for real data. An id is required either way, because datasets are reached as #id.column.
#Reaching columns
Columns are reached by ordinary member access, fully qualified — there is no bare-column shorthand, because attributes are unquoted and label: island must stay the literal string island. Two mechanisms consume a column, and the braces are the difference:
{{#survey.area}}— substitution: compile-time text, the cells joined with commas, spliced before parsing. Works in prose, code bodies and attribute values alike.#islands/#islands.column— binding: a reference carried through to the renderer, for attributes typed to accept one.
#Substitution vs binding
A substitution is a snapshot; a binding is live. Columns are substitutable precisely because a dataset is immutable by construction — nothing can swap it and no cell can depend on a varying value. The values that do vary at render time — derived attributes, animated attributes, reader-set parameters — refuse substitution as a compile error and must be bound: {{#fit.slope}} fails, !value:#fit.slope() is the way to say it in prose.
#Tables from data
A table binds a dataset directly and projects columns out of it:
table(data: #islands; columns: island, area, species)columns: is a list of plain strings the table resolves against its own data: — library-interpreted names, not references, which is why they are written bare. Elsewhere, a column reaches a consumer as text through substitution — the Code panes chapter splices {{#survey.area}} straight into a numpy array literal.
#Scope and limits
- Directives have no placement rules — a
is document-visible wherever it is written, including inside an @included partial. - CSV only, for attached data. Other formats convert to CSV trivially.
- No computed columns, no row filters, no aggregates — a transform belongs upstream, stored in the data. This is what keeps column access a pure lookup (and keeps columns substitutable).
- For scalars and repeated fragments, use @let; for tables of values,
is the tool.