CalendarGrid
Calendar-aligned values/events with an explicit week start and exact values beyond color.
Use the web element directly without the semantic runtime.
A registered presentation adapter can bind this component to host-owned meaning and state.
The standard recipe resolver does not select this component automatically.
Import and live example#
A calendar grid 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<AeliqoCalendarGridElement>("aeliqo-calendar-grid", root), catalogTemporalSpecs["calendar-grid"], catalogVisualizationContext)))(root);
Purpose#
Place loaded rows on Gregorian calendar days. Tiles show row counts and up to three row labels; the
data table carries exact values and remaining rows. weekStartsOn defaults to Monday.
When to use it#
- Spot daily patterns like activity by weekday or week.
- Show loaded rows on real calendar days with an optional week-start override.
- Keep exact values and rows available in the data table.
When to use a different component#
- Use Timeline for sequence or elapsed time instead of calendar cells.
- Use Heatmap for two chosen dimensions rather than calendar days.
Properties and defaults#
Calendar placement follows the spec and declared calendar descriptor; missing dates are not invented.
| Property | Use and ownership | Declared type | Initial value |
|---|---|---|---|
visualization | Supplies the validated visualization specification to render. | VisualizationSpec | undefined | undefined |
context | Supplies the validated catalog and result context used to bind the visualization specification. | VisualizationBindingContext | { results: [] } |
datasets | Supplies rows keyed to the declared Result references. The component does not fetch or expand them. | readonly VisualizationDataset[] | [] |
selectedIdentity | Controls the selected visualization identity and keeps it tied to the current Result. | string | '' |
selectionEnabled | Enables mark selection proposals for the visualization. | boolean | true |
label | Provides the visible label and accessible name for the component. | string | 'Data visualization' |
width | Sets the visualization layout width before measured container geometry is applied. | number | 640 |
height | Sets the visualization layout height. The chart still computes mark geometry from its measured container. | number | 320 |
maxMarks | Limits rendered visualization marks. Results above this boundary must be reduced or rejected explicitly. | number | 1000 |
Events#
| Event | Payload | Emitted | Application responsibility |
|---|---|---|---|
aeliqo-visualization-select | Selected 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
const element = document.querySelector('aeliqo-calendar-grid');<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:
- ready
- partial
- empty
- error
Expected result: The calendar preserves date meaning and exposes the supplied rows through accessible data.
Keyboard, focus, and accessibility#
Keyboard behavior:
- Tab reaches the scrollable graphic when present and the native row-selection buttons; screen readers can navigate the labelled data table.
- Enter or Space activates the focused selection button.
- Visual marks do not form an arrow-key grid; use the exact-value table to inspect and select rows.
Exposed semantics:
- aria-label
- aria-pressed
- button
- caption
- figcaption
- figure
- nav
- role
- section
- svg
- table
- tbody
- td
- th
- thead
- tr
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.
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#
- Shadow parts:
data,scope,scroll-hint,viewport - Varendo design tokens referenced:
--aeliqo-color-accent,--aeliqo-color-border,--aeliqo-color-focus,--aeliqo-color-surface,--aeliqo-color-text,--aeliqo-control-border-width
Performance limits#
- Public workload controls: `maxMarks` (default 1000).
Related components#
Generated TypeScript declaration#
View generated declaration
export declare class AeliqoCalendarGridElement extends AeliqoTemporalElement {
protected readonly view: 'calendar-grid';
}Version#
This page documents the Varendo 0.6 component contract.
