Skip to content

Structure Nodes

The structure property of a UnitOlliVisSpec controls how the accessible tree is organized. It's a tree of OlliNode values that describe groupby operations, predicate filters, and annotation highlights.

When structure is omitted, Olli infers it from the spec's axes, legends, and guides. But you can provide it explicitly to customize the navigation hierarchy.

OlliNode

ts
type OlliNode = OlliGroupNode | OlliPredicateNode | OlliAnnotationNode;

OlliGroupNode

Groups the data by a field. Each distinct value (or bin range) of the field becomes a child node.

ts
interface OlliGroupNode {
  groupby: string;
  children?: OlliNode[];
}
PropertyTypeRequiredDescription
groupbystringyesThe field name to group by.
childrenOlliNode[]noNested structure within each group.

For example, grouping a bar chart by the x-axis field and then by a color field:

ts
const structure: OlliNode = {
  groupby: 'category',
  children: [{ groupby: 'color' }]
};

The lowerer expands each OlliGroupNode into one child hyperedge per distinct value. For quantitative and temporal fields, values are binned using axis ticks or auto-computed bin edges.

OlliPredicateNode

Filters the data by a predicate expression. Use this for curated data highlights or interesting subsets.

ts
interface OlliPredicateNode {
  predicate: LogicalComposition<FieldPredicate>;
  name?: string;
  reasoning?: string;
  children?: OlliNode[];
}
PropertyTypeRequiredDescription
predicateLogicalComposition<FieldPredicate>yesA filter expression. See Predicates & Selection.
namestringnoDisplay name. If omitted, auto-generated from the predicate.
reasoningstringnoWhy this subset is interesting. Not currently surfaced in descriptions.
childrenOlliNode[]noNested structure within the filtered subset.
ts
const highlight: OlliPredicateNode = {
  predicate: { field: 'temperature', gte: 100 },
  name: 'Extreme heat days',
};

OlliAnnotationNode

A container for data highlights. Its children are displayed under a "Data highlights" label.

ts
interface OlliAnnotationNode {
  annotations: OlliNode[];
}

Annotation nodes are typically placed at the top level alongside groupby nodes:

ts
const structure: OlliNode[] = [
  { groupby: 'date' },
  {
    annotations: [
      { predicate: { field: 'price', gte: 200 }, name: 'High prices' },
      { predicate: { field: 'price', lte: 10 }, name: 'Low prices' },
    ],
  },
];

VisPayload

When the lowerer converts structure nodes into hyperedges, each edge carries a VisPayload:

ts
interface VisPayload {
  nodeType: OlliNodeType;
  predicate?: FieldPredicate;
  groupby?: string;
  specIndex?: number;
  viewType?: 'facet' | 'layer' | 'concat';
  spec: UnitOlliVisSpec;
}
PropertyTypeRequiredDescription
nodeTypeOlliNodeTypeyesThe role of this node — 'root', 'view', 'xAxis', 'yAxis', 'legend', 'guide', 'filteredData', 'annotations', or 'other'.
predicateFieldPredicatenoThe filter predicate for this node, if any.
groupbystringnoThe field being grouped on, if any.
specIndexnumbernoIndex into MultiOlliVisSpec.units for multi-view charts.
viewTypestringnoFor multi-view: 'facet', 'layer', or 'concat'.
specUnitOlliVisSpecyesThe unit spec this node belongs to.

The VisPayload is available on every hyperedge via edge.payload, and tokens use it to compute descriptions.

OlliNodeType

ts
type OlliNodeType =
  | 'root'
  | 'view'
  | 'xAxis'
  | 'yAxis'
  | 'legend'
  | 'guide'
  | 'filteredData'
  | 'annotations'
  | 'other';

Node types serve as roles for the description system. Each role can have a different set of tokens and a different recipe. See Presets for the role/token matrix.

Next

Released under the BSD-3-Clause License.