Vis Lowering
Lowering converts an OlliVisSpec into a Hypergraph<VisPayload>. This is the bridge between the declarative spec and the navigable tree structure.
lowerVisSpec
function lowerVisSpec(spec: OlliVisSpec): Hypergraph<VisPayload>lowerVisSpec is the visualization domain's lowerer. It handles both unit and multi-view specs.
Pipeline
- Elaborate —
elaborateSpecfills in missing fields, infers types, and generates a defaultstructureif none is provided. - Lower — The elaborated spec is walked to produce hyperedges.
- Build —
buildHypergraphvalidates parent/child symmetry, detects cycles, and returns the finalHypergraph.
Unit spec lowering
For a UnitOlliVisSpec, the lowerer:
- Creates a root edge with role
'root'. - Walks each
OlliNodeinstructure:- OlliGroupNode: Creates a guide edge (e.g.,
xAxis,yAxis,legend) with one child edge per distinct value or bin of thegroupbyfield. - OlliPredicateNode: Creates a
filteredDataedge with the predicate in its payload. - OlliAnnotationNode: Creates an
annotationsedge containing the annotation children.
- OlliGroupNode: Creates a guide edge (e.g.,
- If the structure produces a single non-filteredData child, it's collapsed into the root to avoid unnecessary nesting.
Multi-view lowering
For a MultiOlliVisSpec:
- Creates a root edge.
- Creates one
viewedge per unit, withspecIndexandviewTypein the payload. - Each unit is lowered independently under its view edge.
Faceting
When facet is set and the top-level structure is a single groupby on the facet field, the lowerer produces a faceted layout: the root groups by the facet field, and each child becomes a view with viewType: 'facet'.
elaborateSpec
function elaborateSpec(spec: OlliVisSpec): OlliVisSpecPre-processes the spec before lowering:
- Field inference: If
fieldsis empty, creates oneOlliFieldDefper key indata[0]. - Type inference: For fields without a
type, examines the data column to determinequantitative,temporal, ornominal. - Structure inference: If
structureis missing, builds a tree from axes, legends, and guides — each becomes agroupbynode.
How groupby expansion works
Given a groupby field, the lowerer calls fieldToPredicates to produce one FieldPredicate per distinct value or bin:
- Nominal/ordinal fields: One
FieldEqualPredicateper distinct value, in data order. - Quantitative fields: Binned into ranges using axis
ticksor auto-computed edges. Each bin becomes aFieldRangePredicate. - Temporal fields: Similar to quantitative, but with date-aware binning.
Each predicate becomes a child edge with role filteredData and the predicate stored in payload.predicate.
Example
Given a simple bar chart spec:
const spec: UnitOlliVisSpec = {
data: [
{ category: 'A', value: 10 },
{ category: 'B', value: 20 },
{ category: 'C', value: 15 },
],
mark: 'bar',
axes: [
{ field: 'category', axisType: 'x' },
{ field: 'value', axisType: 'y' },
],
};After elaboration and lowering, the hypergraph looks like:
root ("bar chart")
├── xAxis ("x-axis titled category")
│ ├── filteredData ("A")
│ ├── filteredData ("B")
│ └── filteredData ("C")The y-axis is quantitative with only three values, so it may also produce a guide node depending on the inferred structure.
Next
- OlliVisSpec — the spec types.
- Hypergraph — the data model produced by lowering.
- Structure Nodes — customizing the tree structure.