Calendar heatmap (year of days)
type: calendar renders a GitHub-commit-style 7-row x ~53-column grid. Each cell shaded by its value; days with no value render as outlined blanks so missing data reads as missing. Filter the grid Type column and the heatmap re-aggregates.
A live, editable Svelte 5 data grid example. Open the interactive demo or read the documentation.
What this example shows
`type: 'calendar'` renders a GitHub-commit-style grid: 7 rows (days of the week) x ~53 columns (weeks of the year). Each cell is shaded by `calendarValues[i].value` via the sequential color scale; days with no value render as outlined blanks so missing data is visually obvious. Hover any cell for the date + value.
The grid drives the chart: filter by activity type and the year heatmap re-aggregates.
Source code (162-chart-calendar.svelte)
<!-- Documented in: docs/help/charts.md -->
<script lang="ts">
/**
* 162. Calendar heatmap (year-of-days)
* -------------------------------------
* `type: 'calendar'` renders a GitHub-commit-style grid: 7 rows
* (days of the week) x ~53 columns (weeks of the year). Each cell is
* shaded by `calendarValues[i].value` via the sequential color scale;
* days with no value render as outlined blanks so missing data is
* visually obvious. Hover any cell for the date + value.
*
* The grid drives the chart: filter by activity type and the year
* heatmap re-aggregates.
*/
import {
SvGrid,
SvGridChart,
tableFeatures,
rowSortingFeature,
columnFilteringFeature,
type ColumnDef,
type SvGridApi,
type ChartSpec,
} from '@svgrid/grid'
const features = tableFeatures({ rowSortingFeature, columnFilteringFeature })
type Row = { id: number; date: string; type: 'commit' | 'pr' | 'review'; count: number }
// Year of synthetic GitHub activity: workday peaks, weekly cycle, off
// weeks every few months.
let seed = 0xb00ff1
const rnd = () => ((seed = (seed * 1103515245 + 12345) >>> 0) / 0xffffffff)
const TYPES: Row['type'][] = ['commit', 'pr', 'review']
let nid = 0
const rows: Row[] = []
for (let i = 0; i < 365; i += 1) {
const d = new Date(2026, 0, 1 + i)
const wd = d.getDay()
const weekend = wd === 0 || wd === 6
const offWeek = Math.floor(i / 7) % 9 === 0
for (const type of TYPES) {
let count = 0
if (!offWeek) {
count = type === 'commit' ? Math.round(rnd() * (weekend ? 3 : 12))
: type === 'pr' ? Math.round(rnd() * (weekend ? 1 : 4))
: Math.round(rnd() * (weekend ? 1 : 6))
}
if (count > 0) rows.push({ id: nid++, date: d.toISOString().slice(0, 10), type, count })
}
}
const columns: ColumnDef<typeof features, Row>[] = [
{ field: 'date', header: 'Date', width: 130 },
{ field: 'type', header: 'Type', width: 110 },
{ field: 'count', header: 'Count', width: 90, align: 'right' },
]
let api = $state<SvGridApi<typeof features, Row> | null>(null)
let displayed = $state<Row[]>(rows)
function sync() { displayed = (api?.getDisplayedRows() as Row[]) ?? rows }
const spec = $derived.by<ChartSpec>(() => {
// Aggregate filtered rows by date.
const byDate = new Map<string, number>()
for (const r of displayed) byDate.set(r.date, (byDate.get(r.date) ?? 0) + r.count)
const calendarValues = Array.from(byDate.entries()).map(([date, value]) => ({ date, value }))
return {
type: 'calendar',
categories: [],
series: [],
calendarValues,
calendarStart: '2026-01-01',
calendarEnd: '2026-12-31',
width: 800,
height: 180,
colorScale: 'sequential',
}
})
</script>
<section class="flex flex-col flex-1 min-h-0 gap-3">
<div class="shrink-0 rounded-lg border px-4 py-3" style="border-color: var(--sg-border); background: var(--sg-header-bg);">
<p class="text-sm font-semibold" style="color: var(--sg-fg);">
Calendar heatmap - a year of activity at a glance
</p>
<p class="mt-0.5 text-xs" style="color: var(--sg-muted);">
Filter the Type column (commit / pr / review) and the heatmap re-aggregates. Hover any cell
for the date and count. Off-weeks every 9 weeks render as outlined blanks so missing data
reads as missing, not zero.
</p>
</div>
<div class="flex flex-1 min-h-0 gap-3 flex-col">
<div class="shrink-0 rounded-lg border p-3" style="border-color: var(--sg-border); background: var(--sg-bg);">
<SvGridChart {spec} />
</div>
<div class="flex-1 min-h-0">
<SvGrid responsive={true}
data={rows}
columns={columns}
features={features}
sortable
filterable
selectionMode="none"
enableRowSummaries={false}
rowHeight={28}
containerHeight="100%"
fitColumns={true}
onApiReady={(a) => { api = a; sync() }}
onFiltersChange={sync}
onSortingChange={sync}
/>
</div>
</div>
</section>More Charts examples
- Chart a selection (context menu) - With integrated charting enabled, the right-click menu gains a Chart selected range item: select a block of cells, right-click, and the chart panel opens scoped to that range - the Excel chart-this gesture, built in. The item appears only when charting is on and is appended to the default context menu automatically.
- Chart view of the grid - The `chart` prop turns the same <SvGrid> into a chart, driven by the grid’s filtered + sorted rows (search + sort flow through). A view of the grid like board and scheduler, but the renderer is free: the grid lazy-loads a built-in view wrapping the standalone SvChart via rowsToChartSpec. Flip Table <-> Chart (bar / line / area) over one source of truth.
- Integrated charts (no deps) - Chart the grid data with no external charting library. SvGridChart renders a ChartSpec; rowsToChartSpec aggregates the grid current (filtered/sorted) rows into one. Bar, line, area, pie - plus 100% stacked, top-N + Other, an average reference line, and double-click-to-isolate a series. Filter the grid and the chart re-aggregates live.
- Scatter / bubble chart - A scatter plot maps two numeric measures (x vs y); a bubble chart adds a third via dot radius. type: scatter with series points [{ x, y, r }]. Spend vs revenue, sized by deals, coloured by region, with an average-revenue reference line. Filter the grid and the cloud re-plots.
- Time-series chart (date axis) - xType: time spaces points by ACTUAL time - irregular date gaps render proportionally - and shows real date ticks. A referenceLines target/SLA line spans the plot; toggle 100% stacked to read each day as a share of its total. Line, stacked area, or stacked bar.