Calendar heatmap
daily activity
Daily activity over a year: deploys, incidents, commits.
- Format.d3
- Length70 lines
- Includesnone
The source
70 lines of D3 JavaScript, and uses only the bundled D3 build. Copy it, or open the template inside Gnomon and render it as it is.
// Calendar heatmap — a year of daily values, one cell per day.
// Weeks run across, weekdays down, which is the layout everyone already
// knows from commit graphs.
// Seeded so the sample looks the same on every render; swap this loop for
// your own [{date, value}] array.
const rng = d3.randomLcg(7);
const start = new Date(Date.UTC(2025, 0, 1));
const end = new Date(Date.UTC(2026, 0, 1));
const days = d3.utcDay.range(start, end).map(date => {
const weekday = date.getUTCDay();
const weekend = weekday === 0 || weekday === 6;
const base = weekend ? 2 : 14;
return { date, value: Math.round(base * (0.2 + rng() * 1.6)) };
});
const cell = 15;
const gap = 2;
const margin = { top: 46, right: 20, bottom: 20, left: 44 };
const colour = d3.scaleSequential([0, d3.max(days, d => d.value)], d3.interpolateGreens);
const weekOf = date => d3.utcSunday.count(d3.utcYear(date), date);
const boxWidth = margin.left + (weekOf(days[days.length - 1].date) + 1) * (cell + gap) + margin.right;
const boxHeight = margin.top + 7 * (cell + gap) + margin.bottom;
const svg = d3.select(container).append('svg')
.attr('viewBox', [0, 0, boxWidth, boxHeight])
.attr('width', boxWidth)
.attr('height', boxHeight)
.attr('font-family', 'system-ui, sans-serif')
.attr('font-size', 10);
svg.append('text')
.attr('x', margin.left)
.attr('y', 20)
.attr('fill', theme.foreground)
.attr('font-size', 13)
.attr('font-weight', 600)
.text('Deployments per day · 2025');
svg.append('g')
.attr('fill', theme.muted)
.selectAll('text')
.data(['Mon', 'Wed', 'Fri'])
.join('text')
.attr('x', margin.left - 6)
.attr('y', (_, i) => margin.top + (i * 2 + 1) * (cell + gap) + cell / 2)
.attr('dy', '0.35em')
.attr('text-anchor', 'end')
.text(d => d);
svg.append('g')
.attr('fill', theme.muted)
.selectAll('text')
.data(d3.utcMonths(start, end))
.join('text')
.attr('x', d => margin.left + weekOf(d) * (cell + gap))
.attr('y', margin.top - 6)
.text(d3.utcFormat('%b'));
svg.append('g')
.selectAll('rect')
.data(days)
.join('rect')
.attr('x', d => margin.left + weekOf(d.date) * (cell + gap))
.attr('y', d => margin.top + d.date.getUTCDay() * (cell + gap))
.attr('width', cell)
.attr('height', cell)
.attr('rx', 2)
.attr('fill', d => colour(d.value));Render this offline
This template ships in Gnomon and renders on your machine, with no account and nothing sent to a server. The browser editor is free and needs no install.
Others in D3 visualisations
- Sankey — request flowFlow with volume. The best chart here for "where does it all go".
- Chord — service interactionWho talks to whom, when the traffic is bidirectional.
- Arc diagram — dependenciesNodes on one axis, arcs above. Readable where a force graph is not, provided the ordering means something.
- Adjacency matrix — couplingAdjacency matrix. Unfashionable, and better than a force graph for dense dependencies.
- Edge bundling — module importsHierarchical edge bundling. For import graphs big enough that straight edges become a hairball.
- Sunburst — nested spendNested hierarchy, radially. Prettier than an icicle, harder to compare.
- Icicle — nested spend (linear)A sunburst unrolled flat. Harder to love, much easier to compare siblings.
- Circle packing — nested sizeCircle packing. Nested size when the nesting matters more than reading exact areas.
- Treemap — portfolio costNested size. Good for cost, storage, lines of code.
- Tidy tree — structureA tidy tree. The default for anything with one parent per node.
- Radial tree — structure (radial)The same tree bent into a circle. Fits more depth on a slide, costs you easy comparison.
- Dendrogram — clusteringClustering, with join height carrying the distance. Not merely a tree with curves.
- Indented tree — file/spec outlineA file or spec outline. The chart that looks like the thing it describes.
- Force graph — service mapService map. Use for clusters, not for reading individual edges.
- Force graph — disjoint clustersForce graph that keeps unconnected clusters apart rather than flinging them off screen.
- Force graph — radial tiersForce layout pinned to rings, so tier is a position instead of a colour legend.
- Directed graph — call directionDirected edges with arrowheads. For when direction is the question, not just adjacency.
- Force tree — blast radiusBlast radius from one node: what breaks if this goes.
- Streamgraph — shifting mixShifting composition over time. Good for the mix, poor for reading any single value.
- Gantt — delivery roadmapDelivery roadmap, rendered from data.
- Radar — capability scoringCapability scoring across axes. Fine for one subject, misleading with four overlaid.
- Bullet chart — target vs actualTarget against actual, in one line. The chart a gauge wishes it were.
- Beeswarm — distribution by groupEvery point, grouped, without the overplotting a strip plot suffers.
- Horizon chart — many series, little spaceMany series in little space. Takes a moment to learn, then very dense.
- Slope chart — before and afterBefore and after, two points, one line each. Devastatingly clear.
- Parallel coordinates — multi-criteriaMulti-criteria comparison: for option analysis.
- Box plot — latency distributionLatency distribution. The chart that shows the tail a mean hides.
- Grouped bar — category comparisonCategory comparison. Unglamorous, and usually the right answer.
- Multi-line — metrics over timeMetrics over time. Keep it under about five series, or switch to horizon.