Varendo

Timeline

Dated events/intervals with timezone semantics, explicit overlaps and chronological alternative.

FamilyvisualizationLicenseApache-2.0
StandaloneAvailable

Use the web element directly without the semantic runtime.

Semantic bindingAvailable

A registered presentation adapter can bind this component to host-owned meaning and state.

Automatic adaptationNo standard recipe

The standard recipe resolver does not select this component automatically.

Import and live example

A timeline keyed by the authorized date field.

Interactive preview requires JavaScript.

View the complete example source
import {registerAeliqoElements} from "@varendo/web";
import {
  AeliqoAreaElement,
  AeliqoBarElement,
  AeliqoCalendarGridElement,
  AeliqoHeatmapElement,
  AeliqoHistogramElement,
  AeliqoMatrixElement,
  AeliqoRelationshipElement,
  AeliqoScatterElement,
  AeliqoTimelineElement,
  AeliqoTrendElement,
  AeliqoTreeElement,
  AeliqoTreemapElement,
  } from "@varendo/web/visualization";

import type {Catalog,
  PlotUnit,
  Result,
  ResultRef,
  Scalar,
} from "@varendo/core";
import type {VisualizationBindingContext, VisualizationSpec} from "@varendo/core/visualization";
import type {VisualizationDataset} from "@varendo/web/visualization";

const catalogRef: ResultRef = {
  id: "aeliqo-catalog-example",
  revision: "1",
  outputId: "people",
  queryDigest: "catalog-query",
  scopeDigest: "catalog-scope",
};
const rows: readonly Readonly<Record<string, Scalar>>[] = [
  {id: "ada", name: "Ada Lovelace", team: "Research", date: "2026-09-08", amount: 120, low: 80, high: 100, zero: 0},
  {id: "lin", name: "Lin Chen", team: "Product", date: "2026-09-09", amount: 96, low: 100, high: 140, zero: 0},
  {id: "grace", name: "Grace Hopper", team: "Research", date: "2026-09-10", amount: 144, low: 140, high: 160, zero: 0},
];
const text = {value: "text", nullable: false} as const;
const integer = {value: "integer", nullable: false} as const;
const date = {value: "date", nullable: false, temporal: {calendar: "gregory", grain: "day"}} as const;
const meaning = {
  id: "amount",
  revision: "1",
  label: "Amount",
  explanation: "Authorized additive amount",
  output: integer,
  implementation: {kind: "host-capability", capability: {id: "amount", revision: "1"}},
  dependencies: [],
  functionRegistryDigest: "catalog-functions",
  origin: "manual",
  lifecycle: "active",
  scope: "workspace",
  authority: "reviewed",
  aggregation: "additive",
  aggregationDimensions: [],
  missingPolicy: "reject",
} as const;
const peopleEntity: Catalog["entities"][number] = {
  id: "people",
  label: "People",
  identity: ["id"],
  rowGrain: ["id"],
  fields: [
    {id: "id", label: "ID", role: "identity", type: text},
    {id: "name", label: "Name", role: "attribute", type: text},
    {id: "team", label: "Team", role: "dimension", type: text},
    {id: "date", label: "Date", role: "time", type: date},
    {id: "amount", label: "Amount", role: "measure", type: integer, derivation: {id: "amount", revision: "1"}},
    {id: "low", label: "Bin start", role: "dimension", type: integer},
    {id: "high", label: "Bin end", role: "dimension", type: integer},
    {id: "zero", label: "Baseline", role: "measure", type: integer},
  ],
};
const catalog: Catalog = {
  version: "1",
  revision: "catalog-1",
  functionRegistryDigest: "catalog-functions",
  entities: [peopleEntity],
  relationships: [],
  meanings: [meaning],
  capabilities: [],
};
const catalogResult: Result = {
  version: "1",
  ref: catalogRef,
  taskId: "catalog-example-task",
  identity: ["id"],
  rowGrain: ["id", "date", "team", "name", "low", "high"],
  fields: peopleEntity.fields,
  counts: {loaded: rows.length, population: {kind: "unknown"}},
  precision: {kind: "exact"},
  coverage: {kind: "complete", populationDigest: catalogRef.scopeDigest},
  consistency: {kind: "snapshot", snapshotId: "catalog-snapshot", sourceRevisions: {people: "1"}},
  evidence: {kind: "computed", queryDigest: catalogRef.queryDigest, definitions: []},
  filters: [],
  warnings: [],
  lineage: [],
};
const plot = (mark: PlotUnit["mark"], encoding: PlotUnit["encoding"]): PlotUnit => ({kind: "unit", mark, result: catalogRef, missing: "gap", encoding});
const catalogVisualizationSpecs: Readonly<Record<"trend" | "bar" | "area" | "scatter" | "histogram" | "heatmap", VisualizationSpec>> = {
  trend: {version: "1", view: "trend", plot: {version: "1", root: plot("line", {x: {field: "date", scale: "temporal"}, y: {field: "amount", scale: "linear"}})}},
  bar: {version: "1", view: "bar", plot: {version: "1", root: plot("bar", {x: {field: "team", scale: "ordinal"}, y: {field: "amount", scale: "linear", zero: true}, series: {field: "name", scale: "ordinal"}})}},
  area: {version: "1", view: "area", plot: {version: "1", root: plot("area", {x: {field: "date", scale: "temporal"}, y: {field: "amount", scale: "linear", zero: true}})}, meaning: {id: "amount", revision: "1"}, stack: "none"},
  scatter: {version: "1", view: "scatter", plot: {version: "1", root: plot("point", {x: {field: "amount", scale: "linear"}, y: {field: "amount", scale: "linear"}})}},
  histogram: {version: "1", view: "histogram", plot: {version: "1", root: plot("rect", {x: {field: "low", scale: "linear"}, x2: {field: "high", scale: "linear"}, y: {field: "amount", scale: "linear", zero: true}, y2: {field: "zero", scale: "linear", zero: true}})}, bins: {start: "low", end: "high", value: "amount", measure: "count", boundary: "start-inclusive-end-exclusive"}},
  heatmap: {version: "1", view: "heatmap", plot: {version: "1", root: plot("cell", {x: {field: "team", scale: "ordinal"}, y: {field: "name", scale: "ordinal"}, color: {field: "amount", scale: "linear"}})}},
};
const catalogTemporalSpecs: Readonly<Record<"matrix" | "timeline" | "calendar-grid", VisualizationSpec>> = {
  matrix: {version: "1", view: "matrix", result: catalogRef, columns: ["name", "team", "amount"]},
  timeline: {version: "1", view: "timeline", result: catalogRef, start: "date"},
  "calendar-grid": {version: "1", view: "calendar-grid", result: catalogRef, date: "date"},
};
const catalogVisualizationDataset: VisualizationDataset = {result: catalogRef, rows};
const catalogVisualizationContext: VisualizationBindingContext = {results: [catalogResult], catalog};
const histogramSpec = catalogVisualizationSpecs.histogram as Extract<VisualizationSpec, {view: "histogram"}>;
const histogramContext: VisualizationBindingContext = {...catalogVisualizationContext, histograms: [{result: catalogRef, bins: histogramSpec.bins}]};
const hierarchyRef: ResultRef = {...catalogRef, id: "aeliqo-catalog-hierarchy", outputId: "nodes"};
const hierarchyRows: readonly Readonly<Record<string, Scalar>>[] = [
  {id: "company", parent: null, label: "Company", amount: 3},
  {id: "research", parent: "company", label: "Research", amount: 2},
  {id: "product", parent: "company", label: "Product", amount: 1},
];
const hierarchyResult: Result = {
  ...catalogResult,
  ref: hierarchyRef,
  taskId: "catalog-hierarchy-task",
  identity: ["id"],
  rowGrain: ["id"],
  fields: [
    {id: "id", label: "ID", role: "identity", type: text},
    {id: "parent", label: "Parent", role: "attribute", type: {...text, nullable: true}},
    {id: "label", label: "Label", role: "attribute", type: text},
    {id: "amount", label: "Amount", role: "measure", type: integer, derivation: {id: "amount", revision: "1"}},
  ],
  counts: {loaded: hierarchyRows.length, population: {kind: "unknown"}},
};
const hierarchyDataset: VisualizationDataset = {result: hierarchyRef, rows: hierarchyRows};
const hierarchySpec: VisualizationSpec = {version: "1", view: "tree", result: hierarchyRef, node: ["id"], parent: ["parent"], label: "label"};
const treemapSpec: VisualizationSpec = {...hierarchySpec, view: "treemap", value: "amount", meaning: {id: "amount", revision: "1"}};
const hierarchyContext: VisualizationBindingContext = {results: [hierarchyResult], catalog};
const relationshipRef: ResultRef = {...catalogRef, id: "aeliqo-catalog-relationship", outputId: "edges"};
const relationshipRows: readonly Readonly<Record<string, Scalar>>[] = [
  {edge: "e1", source: "research", target: "company"},
  {edge: "e2", source: "product", target: "company"},
];
const relationshipResult: Result = {
  ...hierarchyResult,
  ref: relationshipRef,
  identity: ["edge"],
  rowGrain: ["edge"],
  fields: [
    {id: "edge", label: "Edge", role: "identity", type: text},
    {id: "source", label: "Source", role: "dimension", type: text},
    {id: "target", label: "Target", role: "dimension", type: text},
  ],
  counts: {loaded: relationshipRows.length, population: {kind: "unknown"}},
};
const relationship: Catalog["relationships"][number] = {
  id: "reports-to",
  revision: "1",
  sourceEntity: "people",
  targetEntity: "people",
  keys: [{sourceField: "id", targetField: "id"}],
  cardinality: "many-to-one",
  optional: false,
  joinPolicy: "validated",
};
const relationshipDataset: VisualizationDataset = {result: relationshipRef, rows: relationshipRows};
const relationshipSpec: VisualizationSpec = {version: "1", view: "relationship", result: relationshipRef, source: ["source"], target: ["target"], relationship: {id: "reports-to", revision: "1"}};
const relationshipContext: VisualizationBindingContext = {
  results: [relationshipResult],
  catalog: {...catalog, relationships: [relationship]},
  relationships: [{result: relationshipRef, relationship: {id: "reports-to", revision: "1"}, source: ["source"], target: ["target"]}],
};
type CatalogVisualizationElement = HTMLElement & {
  visualization: VisualizationSpec | undefined;
  context: VisualizationBindingContext;
  datasets: readonly VisualizationDataset[];
  label: string;
  width: number;
  height: number;
  maxMarks: number;
  selectionEnabled: boolean;
};
function configure<T extends CatalogVisualizationElement>(element: T, visualization: VisualizationSpec, context: VisualizationBindingContext, datasets: readonly VisualizationDataset[] = [catalogVisualizationDataset]): T {
  element.visualization = visualization;
  element.context = context;
  element.datasets = datasets;
  element.label = "Sample people amounts";
  element.width = 640;
  element.height = 360;
  element.maxMarks = 500;
  element.selectionEnabled = true;
  return element;
}

registerAeliqoElements();

const host = document.querySelector<HTMLElement>("#aeliqo-example") ?? (() => {
  const created = document.createElement("div");
  created.id = "aeliqo-example";
  (document.body ?? document.documentElement).append(created);
  return created;
})();

function createCatalogRoot(container: HTMLElement): HTMLElement {
  const root = document.createElement("div");
  root.dataset.catalogSourceRoot = "true";
  root.style.display = "grid";
  root.style.gap = "12px";
  container.append(root);
  return root;
}

function createCatalogElement<T extends HTMLElement>(tagName: string, container: HTMLElement): T {
  const element = document.createElement(tagName) as T;
  element.setAttribute("data-aeliqo-theme", "light");
  container.append(element);
  return element;
}

function appendSlottedText(root: HTMLElement, slot: string, textContent: string): void {
  const content = document.createElement("span");
  content.slot = slot;
  content.textContent = textContent;
  root.append(content);
}

const root = createCatalogRoot(host);
(((root) => configure(createCatalogElement<AeliqoTimelineElement>("aeliqo-timeline", root), catalogTemporalSpecs.timeline, catalogVisualizationContext)))(root);

Purpose

Order dated events or intervals chronologically. Overlapping intervals get separate lanes; exact dates stay in the data table.

When to use it

When to use a different component

Properties and defaults

The timeline start field is explicit and dates retain calendar semantics from the Result descriptor.

Properties, ownership, and initial values
PropertyUse and ownershipDeclared typeInitial value
visualizationSupplies the validated visualization specification to render.VisualizationSpec | undefinedundefined
contextSupplies the validated catalog and result context used to bind the visualization specification.VisualizationBindingContext{ results: [] }
datasetsSupplies rows keyed to the declared Result references. The component does not fetch or expand them.readonly VisualizationDataset[][]
selectedIdentityControls the selected visualization identity and keeps it tied to the current Result.string''
selectionEnabledEnables mark selection proposals for the visualization.booleantrue
labelProvides the visible label and accessible name for the component.string'Data visualization'
widthSets the visualization layout width before measured container geometry is applied.number640
heightSets the visualization layout height. The chart still computes mark geometry from its measured container.number320
maxMarksLimits rendered visualization marks. Results above this boundary must be reduced or rejected explicitly.number1000

Events

Events, payloads, timing, and application responsibilities
EventPayloadEmittedApplication responsibility
aeliqo-visualization-selectSelected mark identity plus Result lineage.After a selectable mark is activated.Validate lineage and update controlled visualization or linked-detail selection.

Events cross the component boundary as user proposals. Validate their detail and current authority before applying a business effect.

Listen for aeliqo-visualization-select

TypeScript example
const element = document.querySelector('aeliqo-timeline');<p>element?.addEventListener('aeliqo-visualization-select', (event) => {
  const proposal = (event as CustomEvent<unknown>).detail;
  // Validate the proposal, update host state, then assign controlled properties.
  applyProposal(proposal);
});

States and failure handling

Relevant states:

Expected result: Events are placed on the declared date axis and remain linked to result identities.

Keyboard, focus, and accessibility

Keyboard behavior:

Exposed semantics:

Responsive behavior

At container widths of 30rem or less, exact-data rows become stacked records so labels remain readable inside narrow desktop panels as well as mobile pages. The graphic keeps its declared dimensions inside a focusable scroll region instead of shrinking its text to fit. Tab to the region to access its overflow; exact values remain available below.

At 7rem or less, each selection label moves above its button. The button text stays on one line, including when text is enlarged.

Style hooks

Performance limits

Generated TypeScript declaration

View generated declaration
export declare class AeliqoTimelineElement extends AeliqoTemporalElement {
    protected readonly view: 'timeline';
}

Version

This page documents the Varendo 0.6 component contract.