Charts

CalendarHeatmap

A year of daily counts as weeks of shaded days, with month names, a less-to-more legend and each day named on hover.

#chart#calendar#data#no-js

Demo

Preview

1,021 commits in the last year

NovDecJanFebMarAprMayJunJulAugSepOctMonWedFriNo commits on Wed, Oct 29, 20256 commits on Thu, Oct 30, 2025No commits on Fri, Oct 31, 2025No commits on Sat, Nov 1, 2025No commits on Sun, Nov 2, 20256 commits on Mon, Nov 3, 2025No commits on Tue, Nov 4, 20257 commits on Wed, Nov 5, 20257 commits on Thu, Nov 6, 20257 commits on Fri, Nov 7, 2025No commits on Sat, Nov 8, 2025No commits on Sun, Nov 9, 20257 commits on Mon, Nov 10, 20258 commits on Tue, Nov 11, 20258 commits on Wed, Nov 12, 20258 commits on Thu, Nov 13, 20258 commits on Fri, Nov 14, 2025No commits on Sat, Nov 15, 20252 commits on Sun, Nov 16, 20258 commits on Mon, Nov 17, 2025No commits on Tue, Nov 18, 2025No commits on Wed, Nov 19, 2025No commits on Thu, Nov 20, 2025No commits on Fri, Nov 21, 2025No commits on Sat, Nov 22, 2025No commits on Sun, Nov 23, 2025No commits on Mon, Nov 24, 20251 commit on Tue, Nov 25, 20251 commit on Wed, Nov 26, 20251 commit on Thu, Nov 27, 20251 commit on Fri, Nov 28, 2025No commits on Sat, Nov 29, 20251 commit on Sun, Nov 30, 20252 commits on Mon, Dec 1, 20252 commits on Tue, Dec 2, 20252 commits on Wed, Dec 3, 20252 commits on Thu, Dec 4, 20252 commits on Fri, Dec 5, 2025No commits on Sat, Dec 6, 2025No commits on Sun, Dec 7, 2025No commits on Mon, Dec 8, 20253 commits on Tue, Dec 9, 2025No commits on Wed, Dec 10, 20253 commits on Thu, Dec 11, 2025No commits on Fri, Dec 12, 2025No commits on Sat, Dec 13, 2025No commits on Sun, Dec 14, 20254 commits on Mon, Dec 15, 20254 commits on Tue, Dec 16, 20254 commits on Wed, Dec 17, 20254 commits on Thu, Dec 18, 20254 commits on Fri, Dec 19, 2025No commits on Sat, Dec 20, 2025No commits on Sun, Dec 21, 20255 commits on Mon, Dec 22, 20255 commits on Tue, Dec 23, 20255 commits on Wed, Dec 24, 20255 commits on Thu, Dec 25, 20255 commits on Fri, Dec 26, 2025No commits on Sat, Dec 27, 2025No commits on Sun, Dec 28, 2025No commits on Mon, Dec 29, 20256 commits on Tue, Dec 30, 2025No commits on Wed, Dec 31, 20256 commits on Thu, Jan 1, 2026No commits on Fri, Jan 2, 20261 commit on Sat, Jan 3, 2026No commits on Sun, Jan 4, 20267 commits on Mon, Jan 5, 20267 commits on Tue, Jan 6, 20267 commits on Wed, Jan 7, 20267 commits on Thu, Jan 8, 20267 commits on Fri, Jan 9, 2026No commits on Sat, Jan 10, 2026No commits on Sun, Jan 11, 20268 commits on Mon, Jan 12, 20268 commits on Tue, Jan 13, 20268 commits on Wed, Jan 14, 20268 commits on Thu, Jan 15, 2026No commits on Fri, Jan 16, 2026No commits on Sat, Jan 17, 2026No commits on Sun, Jan 18, 2026No commits on Mon, Jan 19, 2026No commits on Tue, Jan 20, 2026No commits on Wed, Jan 21, 2026No commits on Thu, Jan 22, 20261 commit on Fri, Jan 23, 2026No commits on Sat, Jan 24, 2026No commits on Sun, Jan 25, 20261 commit on Mon, Jan 26, 20261 commit on Tue, Jan 27, 20261 commit on Wed, Jan 28, 20261 commit on Thu, Jan 29, 20262 commits on Fri, Jan 30, 20262 commits on Sat, Jan 31, 2026No commits on Sun, Feb 1, 20262 commits on Mon, Feb 2, 20262 commits on Tue, Feb 3, 20262 commits on Wed, Feb 4, 2026No commits on Thu, Feb 5, 20263 commits on Fri, Feb 6, 2026No commits on Sat, Feb 7, 2026No commits on Sun, Feb 8, 2026No commits on Mon, Feb 9, 20263 commits on Tue, Feb 10, 2026No commits on Wed, Feb 11, 20264 commits on Thu, Feb 12, 20264 commits on Fri, Feb 13, 20261 commit on Sat, Feb 14, 2026No commits on Sun, Feb 15, 20264 commits on Mon, Feb 16, 20264 commits on Tue, Feb 17, 20265 commits on Wed, Feb 18, 20265 commits on Thu, Feb 19, 20265 commits on Fri, Feb 20, 2026No commits on Sat, Feb 21, 2026No commits on Sun, Feb 22, 20265 commits on Mon, Feb 23, 20265 commits on Tue, Feb 24, 20266 commits on Wed, Feb 25, 2026No commits on Thu, Feb 26, 20266 commits on Fri, Feb 27, 2026No commits on Sat, Feb 28, 2026No commits on Sun, Mar 1, 2026No commits on Mon, Mar 2, 20267 commits on Tue, Mar 3, 20267 commits on Wed, Mar 4, 20267 commits on Thu, Mar 5, 20267 commits on Fri, Mar 6, 2026No commits on Sat, Mar 7, 2026No commits on Sun, Mar 8, 20267 commits on Mon, Mar 9, 20268 commits on Tue, Mar 10, 20268 commits on Wed, Mar 11, 20268 commits on Thu, Mar 12, 20268 commits on Fri, Mar 13, 20262 commits on Sat, Mar 14, 2026No commits on Sun, Mar 15, 20268 commits on Mon, Mar 16, 2026No commits on Tue, Mar 17, 2026No commits on Wed, Mar 18, 2026No commits on Thu, Mar 19, 2026No commits on Fri, Mar 20, 2026No commits on Sat, Mar 21, 2026No commits on Sun, Mar 22, 20261 commit on Mon, Mar 23, 20261 commit on Tue, Mar 24, 20261 commit on Wed, Mar 25, 20261 commit on Thu, Mar 26, 20261 commit on Fri, Mar 27, 20261 commit on Sat, Mar 28, 2026No commits on Sun, Mar 29, 20262 commits on Mon, Mar 30, 20262 commits on Tue, Mar 31, 20262 commits on Wed, Apr 1, 20262 commits on Thu, Apr 2, 20262 commits on Fri, Apr 3, 2026No commits on Sat, Apr 4, 2026No commits on Sun, Apr 5, 20263 commits on Mon, Apr 6, 2026No commits on Tue, Apr 7, 20263 commits on Wed, Apr 8, 2026No commits on Thu, Apr 9, 20263 commits on Fri, Apr 10, 2026No commits on Sat, Apr 11, 20261 commit on Sun, Apr 12, 20264 commits on Mon, Apr 13, 20264 commits on Tue, Apr 14, 20264 commits on Wed, Apr 15, 20264 commits on Thu, Apr 16, 20264 commits on Fri, Apr 17, 2026No commits on Sat, Apr 18, 2026No commits on Sun, Apr 19, 20265 commits on Mon, Apr 20, 20265 commits on Tue, Apr 21, 20265 commits on Wed, Apr 22, 20265 commits on Thu, Apr 23, 20265 commits on Fri, Apr 24, 2026No commits on Sat, Apr 25, 2026No commits on Sun, Apr 26, 20266 commits on Mon, Apr 27, 2026No commits on Tue, Apr 28, 20266 commits on Wed, Apr 29, 2026No commits on Thu, Apr 30, 20266 commits on Fri, May 1, 2026No commits on Sat, May 2, 2026No commits on Sun, May 3, 20267 commits on Mon, May 4, 20267 commits on Tue, May 5, 20267 commits on Wed, May 6, 20267 commits on Thu, May 7, 20268 commits on Fri, May 8, 2026No commits on Sat, May 9, 20262 commits on Sun, May 10, 20268 commits on Mon, May 11, 20268 commits on Tue, May 12, 20268 commits on Wed, May 13, 20268 commits on Thu, May 14, 2026No commits on Fri, May 15, 2026No commits on Sat, May 16, 2026No commits on Sun, May 17, 2026No commits on Mon, May 18, 2026No commits on Tue, May 19, 2026No commits on Wed, May 20, 2026No commits on Thu, May 21, 20261 commit on Fri, May 22, 2026No commits on Sat, May 23, 20261 commit on Sun, May 24, 20261 commit on Mon, May 25, 20261 commit on Tue, May 26, 20261 commit on Wed, May 27, 20262 commits on Thu, May 28, 20262 commits on Fri, May 29, 2026No commits on Sat, May 30, 2026No commits on Sun, May 31, 20262 commits on Mon, Jun 1, 20262 commits on Tue, Jun 2, 20262 commits on Wed, Jun 3, 20263 commits on Thu, Jun 4, 2026No commits on Fri, Jun 5, 2026No commits on Sat, Jun 6, 2026No commits on Sun, Jun 7, 20263 commits on Mon, Jun 8, 2026No commits on Tue, Jun 9, 20264 commits on Wed, Jun 10, 20264 commits on Thu, Jun 11, 20264 commits on Fri, Jun 12, 2026No commits on Sat, Jun 13, 2026No commits on Sun, Jun 14, 20264 commits on Mon, Jun 15, 20264 commits on Tue, Jun 16, 20265 commits on Wed, Jun 17, 20265 commits on Thu, Jun 18, 20265 commits on Fri, Jun 19, 2026No commits on Sat, Jun 20, 20262 commits on Sun, Jun 21, 20265 commits on Mon, Jun 22, 20265 commits on Tue, Jun 23, 2026No commits on Wed, Jun 24, 20266 commits on Thu, Jun 25, 2026No commits on Fri, Jun 26, 2026No commits on Sat, Jun 27, 2026No commits on Sun, Jun 28, 20266 commits on Mon, Jun 29, 20267 commits on Tue, Jun 30, 20267 commits on Wed, Jul 1, 20267 commits on Thu, Jul 2, 20267 commits on Fri, Jul 3, 2026No commits on Sat, Jul 4, 20261 commit on Sun, Jul 5, 20267 commits on Mon, Jul 6, 20268 commits on Tue, Jul 7, 20268 commits on Wed, Jul 8, 20268 commits on Thu, Jul 9, 20268 commits on Fri, Jul 10, 2026No commits on Sat, Jul 11, 2026No commits on Sun, Jul 12, 2026No commits on Mon, Jul 13, 2026No commits on Tue, Jul 14, 2026No commits on Wed, Jul 15, 2026No commits on Thu, Jul 16, 2026No commits on Fri, Jul 17, 2026No commits on Sat, Jul 18, 2026No commits on Sun, Jul 19, 20261 commit on Mon, Jul 20, 20261 commit on Tue, Jul 21, 20261 commit on Wed, Jul 22, 20261 commit on Thu, Jul 23, 20261 commit on Fri, Jul 24, 2026No commits on Sat, Jul 25, 2026No commits on Sun, Jul 26, 20262 commits on Mon, Jul 27, 20262 commits on Tue, Jul 28, 20262 commits on Wed, Jul 29, 20262 commits on Thu, Jul 30, 20262 commits on Fri, Jul 31, 2026No commits on Sat, Aug 1, 2026No commits on Sun, Aug 2, 2026No commits on Mon, Aug 3, 20263 commits on Tue, Aug 4, 2026No commits on Wed, Aug 5, 20263 commits on Thu, Aug 6, 2026No commits on Fri, Aug 7, 2026No commits on Sat, Aug 8, 2026No commits on Sun, Aug 9, 20264 commits on Mon, Aug 10, 20264 commits on Tue, Aug 11, 20264 commits on Wed, Aug 12, 20264 commits on Thu, Aug 13, 20264 commits on Fri, Aug 14, 2026No commits on Sat, Aug 15, 2026No commits on Sun, Aug 16, 20265 commits on Mon, Aug 17, 20265 commits on Tue, Aug 18, 20265 commits on Wed, Aug 19, 202611 commits on Thu, Aug 20, 202611 commits on Fri, Aug 21, 2026No commits on Sat, Aug 22, 2026No commits on Sun, Aug 23, 2026No commits on Mon, Aug 24, 202612 commits on Tue, Aug 25, 2026No commits on Wed, Aug 26, 202612 commits on Thu, Aug 27, 2026No commits on Fri, Aug 28, 2026No commits on Sat, Aug 29, 2026No commits on Sun, Aug 30, 202613 commits on Mon, Aug 31, 202613 commits on Tue, Sep 1, 202613 commits on Wed, Sep 2, 202613 commits on Thu, Sep 3, 202614 commits on Fri, Sep 4, 20262 commits on Sat, Sep 5, 2026No commits on Sun, Sep 6, 202614 commits on Mon, Sep 7, 202614 commits on Tue, Sep 8, 202614 commits on Wed, Sep 9, 202614 commits on Thu, Sep 10, 20266 commits on Fri, Sep 11, 2026No commits on Sat, Sep 12, 2026No commits on Sun, Sep 13, 2026No commits on Mon, Sep 14, 20266 commits on Tue, Sep 15, 2026No commits on Wed, Sep 16, 20267 commits on Thu, Sep 17, 20261 commit on Fri, Sep 18, 20261 commit on Sat, Sep 19, 2026No commits on Sun, Sep 20, 20261 commit on Mon, Sep 21, 20261 commit on Tue, Sep 22, 20261 commit on Wed, Sep 23, 20262 commits on Thu, Sep 24, 20262 commits on Fri, Sep 25, 2026No commits on Sat, Sep 26, 2026No commits on Sun, Sep 27, 20262 commits on Mon, Sep 28, 20262 commits on Tue, Sep 29, 20262 commits on Wed, Sep 30, 2026No commits on Thu, Oct 1, 20263 commits on Fri, Oct 2, 2026No commits on Sat, Oct 3, 2026No commits on Sun, Oct 4, 2026No commits on Mon, Oct 5, 20263 commits on Tue, Oct 6, 20264 commits on Wed, Oct 7, 20264 commits on Thu, Oct 8, 20264 commits on Fri, Oct 9, 2026No commits on Sat, Oct 10, 2026No commits on Sun, Oct 11, 20264 commits on Mon, Oct 12, 20264 commits on Tue, Oct 13, 20265 commits on Wed, Oct 14, 20265 commits on Thu, Oct 15, 20265 commits on Fri, Oct 16, 20262 commits on Sat, Oct 17, 2026No commits on Sun, Oct 18, 20265 commits on Mon, Oct 19, 2026No commits on Tue, Oct 20, 20266 commits on Wed, Oct 21, 2026No commits on Thu, Oct 22, 20266 commits on Fri, Oct 23, 2026No commits on Sat, Oct 24, 2026No commits on Sun, Oct 25, 2026No commits on Mon, Oct 26, 20267 commits on Tue, Oct 27, 20267 commits on Wed, Oct 28, 2026

441 km run from Jul 1, 2026 to Oct 28, 2026

JulAugSepOctMonWedFriNo km run on Wed, Jul 1, 20269 km run on Thu, Jul 2, 20266 km run on Fri, Jul 3, 20263 km run on Sat, Jul 4, 2026No km run on Sun, Jul 5, 2026No km run on Mon, Jul 6, 202611 km run on Tue, Jul 7, 20268 km run on Wed, Jul 8, 20265 km run on Thu, Jul 9, 2026No km run on Fri, Jul 10, 2026No km run on Sat, Jul 11, 2026No km run on Sun, Jul 12, 202610 km run on Mon, Jul 13, 20267 km run on Tue, Jul 14, 20264 km run on Wed, Jul 15, 2026No km run on Thu, Jul 16, 2026No km run on Fri, Jul 17, 2026No km run on Sat, Jul 18, 20269 km run on Sun, Jul 19, 20266 km run on Mon, Jul 20, 20263 km run on Tue, Jul 21, 2026No km run on Wed, Jul 22, 2026No km run on Thu, Jul 23, 202611 km run on Fri, Jul 24, 20268 km run on Sat, Jul 25, 20265 km run on Sun, Jul 26, 2026No km run on Mon, Jul 27, 2026No km run on Tue, Jul 28, 2026No km run on Wed, Jul 29, 202610 km run on Thu, Jul 30, 20267 km run on Fri, Jul 31, 20264 km run on Sat, Aug 1, 2026No km run on Sun, Aug 2, 2026No km run on Mon, Aug 3, 2026No km run on Tue, Aug 4, 20269 km run on Wed, Aug 5, 20266 km run on Thu, Aug 6, 20263 km run on Fri, Aug 7, 2026No km run on Sat, Aug 8, 2026No km run on Sun, Aug 9, 202611 km run on Mon, Aug 10, 20268 km run on Tue, Aug 11, 20265 km run on Wed, Aug 12, 2026No km run on Thu, Aug 13, 2026No km run on Fri, Aug 14, 2026No km run on Sat, Aug 15, 202610 km run on Sun, Aug 16, 20267 km run on Mon, Aug 17, 20264 km run on Tue, Aug 18, 2026No km run on Wed, Aug 19, 2026No km run on Thu, Aug 20, 2026No km run on Fri, Aug 21, 20269 km run on Sat, Aug 22, 20266 km run on Sun, Aug 23, 20263 km run on Mon, Aug 24, 2026No km run on Tue, Aug 25, 2026No km run on Wed, Aug 26, 202611 km run on Thu, Aug 27, 20268 km run on Fri, Aug 28, 20265 km run on Sat, Aug 29, 2026No km run on Sun, Aug 30, 2026No km run on Mon, Aug 31, 2026No km run on Tue, Sep 1, 202610 km run on Wed, Sep 2, 20267 km run on Thu, Sep 3, 20264 km run on Fri, Sep 4, 2026No km run on Sat, Sep 5, 2026No km run on Sun, Sep 6, 2026No km run on Mon, Sep 7, 20269 km run on Tue, Sep 8, 20266 km run on Wed, Sep 9, 20263 km run on Thu, Sep 10, 2026No km run on Fri, Sep 11, 2026No km run on Sat, Sep 12, 202611 km run on Sun, Sep 13, 20268 km run on Mon, Sep 14, 20265 km run on Tue, Sep 15, 2026No km run on Wed, Sep 16, 2026No km run on Thu, Sep 17, 2026No km run on Fri, Sep 18, 202610 km run on Sat, Sep 19, 20267 km run on Sun, Sep 20, 20264 km run on Mon, Sep 21, 2026No km run on Tue, Sep 22, 2026No km run on Wed, Sep 23, 2026No km run on Thu, Sep 24, 20269 km run on Fri, Sep 25, 20266 km run on Sat, Sep 26, 20263 km run on Sun, Sep 27, 2026No km run on Mon, Sep 28, 2026No km run on Tue, Sep 29, 202611 km run on Wed, Sep 30, 20268 km run on Thu, Oct 1, 20265 km run on Fri, Oct 2, 2026No km run on Sat, Oct 3, 2026No km run on Sun, Oct 4, 2026No km run on Mon, Oct 5, 202610 km run on Tue, Oct 6, 20267 km run on Wed, Oct 7, 20264 km run on Thu, Oct 8, 2026No km run on Fri, Oct 9, 2026No km run on Sat, Oct 10, 2026No km run on Sun, Oct 11, 20269 km run on Mon, Oct 12, 20266 km run on Tue, Oct 13, 20263 km run on Wed, Oct 14, 2026No km run on Thu, Oct 15, 2026No km run on Fri, Oct 16, 202611 km run on Sat, Oct 17, 20268 km run on Sun, Oct 18, 20265 km run on Mon, Oct 19, 2026No km run on Tue, Oct 20, 2026No km run on Wed, Oct 21, 2026No km run on Thu, Oct 22, 202610 km run on Fri, Oct 23, 20267 km run on Sat, Oct 24, 20264 km run on Sun, Oct 25, 2026No km run on Mon, Oct 26, 2026No km run on Tue, Oct 27, 2026No km run on Wed, Oct 28, 2026

Get it

It needs theme.qwe, as every component does; or take the whole kit, quillo-ui.qwe. Each component brings its own styles to the pages that show it.

$ curl -O https://quillolang.org/components/ui/theme.qwe$ curl -O https://quillolang.org/components/ui/calendar_heatmap.qwe
main.qwe
import ui/theme.qwe use theme_cssuse ui/calendar_heatmap.qweroute GET / = <html><head><style>{theme_css}</style></head><body class="ui-root">...</body></html>

Example

The code of the preview above.

demos/calendar_heatmap.qwe
// A year of made-up commits: quieter at weekends, a few busy streaks.let calendar_heatmap_demo_values = reduce(range(365), {}, (m, i) => {    let n = calendar_heatmap_parse("2026-10-28") - i    let h = (n * 7919 + 104729) % 1009    let weekend = (n + 4) % 7 == 0 || (n + 4) % 7 == 6    let v = weekend ? (h % 4 == 0 ? h % 3 : 0) : (h % 6 == 0 ? 0 : h % 9 + (i > 40 && i < 70 ? 6 : 0))    v > 0 ? set(m, calendar_heatmap_iso(n), v) : m})let calendar_heatmap_demo_runs = reduce(range(120), {}, (m, i) => {    let n = calendar_heatmap_parse("2026-10-28") - i    let km = (n * 31) % 17    km < 9 ? set(m, calendar_heatmap_iso(n), km + 3) : m})view CalendarHeatmapDemo() =    <div.demo-stack>        <CalendarHeatmap values={calendar_heatmap_demo_values} end="2026-10-28" unit="commits" label="Commits"/>        <CalendarHeatmap values={calendar_heatmap_demo_runs} start="2026-07-01" end="2026-10-28" unit="km run"                         tone="success" week_start="monday" label="Running"/>    </div>

Source

ui/calendar_heatmap.qwe
// CalendarHeatmap: a year of daily counts as a grid of weeks, each day shaded by// how much happened on it, as on a contributions page. Hovering a day names it.////   <CalendarHeatmap values={commits_by_day} end="2026-10-28" unit="commits"/>//   <CalendarHeatmap values={{ "2026-03-14": 4, "2026-03-15": 1 }} tone="success" week_start="monday"/>//// values: a map from ISO dates ("2026-03-14") to numbers; a day not in it counts// as 0. end: the last day shown (by default the latest day in values, else today,// in UTC); start: the first (a year before end, 365 days in all). Each column is// a week, Sunday first, or Monday with week_start="monday". The days that had// something get one of four shades, by their share of the busiest day; the legend// under it goes from less to more. unit: what is counted, in the plural// ("contributions"); a day of 1 drops its final "s". tone: "accent" (default),// "success", "info", "warning" or "danger". total: the line above it, "1,204// contributions in the last year" (true). label: what it shows, for screen// readers, who hear the total and the busiest day. On a narrow screen it scrolls// sideways, starting at the latest weeks.import ui/theme.qwe use ui_clslet calendar_heatmap_css = css`.ui-ch { display: grid; gap: 8px; min-width: 0; margin: 0; font: 12px/1.3 var(--ui-font); color: var(--ui-fg); --t: var(--ui-accent); }.ui-ch-success { --t: var(--ui-success); }.ui-ch-info { --t: var(--ui-info); }.ui-ch-warning { --t: var(--ui-warning); }.ui-ch-danger { --t: var(--ui-danger); }.ui-ch-total { margin: 0; font-size: 14px; }.ui-ch-scroll { overflow-x: auto; overflow-y: hidden; direction: rtl; }.ui-ch-scroll:focus-visible { outline: none; border-radius: var(--ui-radius-sm); box-shadow: var(--ui-focus); }.ui-ch-svg { display: block; width: 100%; height: auto; margin-right: auto; direction: ltr; }.ui-ch-svg text { fill: var(--ui-muted); font: 9px var(--ui-font); }.ui-ch-day { stroke: transparent; stroke-width: 1; }.ui-ch-day:hover { stroke: var(--ui-fg); }.ui-ch-l0 { fill: color-mix(in srgb, var(--ui-border) 55%, var(--ui-subtle)); }.ui-ch-l1 { fill: color-mix(in srgb, var(--t) 30%, var(--ui-subtle)); }.ui-ch-l2 { fill: color-mix(in srgb, var(--t) 52%, var(--ui-subtle)); }.ui-ch-l3 { fill: color-mix(in srgb, var(--t) 76%, var(--ui-subtle)); }.ui-ch-l4 { fill: var(--t); }.ui-ch-legend { display: flex; align-items: center; justify-content: flex-end; gap: 3px; color: var(--ui-muted); }.ui-ch-legend svg { width: 10px; height: 10px; }.ui-ch-legend span:first-child { margin-right: 4px; }.ui-ch-legend span:last-child { margin-left: 4px; }`let calendar_heatmap_months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"]let calendar_heatmap_weekdays = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"]// n (a whole number, 0 or more) with at least `width` digits.fn calendar_heatmap_pad(n: int, width: int) -> str {    let s = str(n)    len(s) < width ? repeat("0", width - len(s)) + s : s}// A whole number with commas: 1234567 is "1,234,567".fn calendar_heatmap_group(n: int) -> str =    n < 1000 ? str(n) : calendar_heatmap_group(floor(n / 1000)) + "," + calendar_heatmap_pad(n % 1000, 3)// A count as it reads, to two decimals at most: 1204 is "1,204", 2.5 is "2.5".fn calendar_heatmap_num(v: num) -> str {    let cents = round(abs(v) * 100)    let frac = cents % 100    let tail = frac == 0 ? "" : (frac % 10 == 0 ? `.${floor(frac / 10)}` : `.${calendar_heatmap_pad(frac, 2)}`)    `${v < 0 && cents > 0 ? "-" : ""}${calendar_heatmap_group(floor(cents / 100))}${tail}`}// The day number of a date, counted from 1970-01-01 (day 0).fn calendar_heatmap_day(y: int, m: int, d: int) -> int {    let yy = m <= 2 ? y - 1 : y    let era = floor(yy / 400)    let yoe = yy - era * 400    let doy = floor((153 * ((m + 9) % 12) + 2) / 5) + d - 1    era * 146097 + yoe * 365 + floor(yoe / 4) - floor(yoe / 100) + doy - 719468}// The date of a day number: { y, m, d }.fn calendar_heatmap_date(n: int) -> map {    let z = n + 719468    let era = floor(z / 146097)    let doe = z - era * 146097    let yoe = floor((doe - floor(doe / 1460) + floor(doe / 36524) - floor(doe / 146096)) / 365)    let doy = doe - (365 * yoe + floor(yoe / 4) - floor(yoe / 100))    let mp = floor((5 * doy + 2) / 153)    let m = mp < 10 ? mp + 3 : mp - 9    { y: yoe + era * 400 + (m <= 2 ? 1 : 0), m, d: doy - floor((153 * mp + 2) / 5) + 1 }}// The day number of an ISO date ("2026-03-14", or the start of a date-time), or null.fn calendar_heatmap_parse(s: str) -> int? {    let p = re.find(s, "^(\\d{4})-(\\d{2})-(\\d{2})")    p == null ? null : calendar_heatmap_day(int(p.groups[0]), int(p.groups[1]), int(p.groups[2]))}// A day number as an ISO date, "2026-03-14".fn calendar_heatmap_iso(n: int) -> str {    let t = calendar_heatmap_date(n)    `${t.y}-${calendar_heatmap_pad(t.m, 2)}-${calendar_heatmap_pad(t.d, 2)}`}// A day number as people read it: "Mar 14, 2026".fn calendar_heatmap_show(n: int) -> str {    let t = calendar_heatmap_date(n)    `${calendar_heatmap_months[t.m - 1]} ${t.d}, ${t.y}`}view CalendarHeatmap(values: map = {}, end: str? = null, start: str? = null, week_start: str = "sunday",                     unit: str = "contributions", tone: str = "accent", total: bool = true,                     label: str = "Activity", class: str? = null) {    style global calendar_heatmap_css    let known = keys(values) | map((k) => calendar_heatmap_parse(k)) | filter((d) => d != null)    let last = (end != null ? calendar_heatmap_parse(end) : null)        ?? (len(known) > 0 ? max(known) : floor(now() / 86400000))    let first0 = start != null ? calendar_heatmap_parse(start) : null    let first = first0 != null && first0 <= last ? first0 : last - 364    let monday = week_start == "monday"    let row = (n) => (n + (monday ? 3 : 4)) % 7                   // 1970-01-01 was a Thursday    let origin = first - row(first)                               // the first column's first day    let weeks = floor((last - origin) / 7) + 1    let at = (n) => {        let v = values[calendar_heatmap_iso(n)]        type_of(v) == "int" || type_of(v) == "float" ? v : 0    }    let days = range(first, last + 1)    let counts = days | map(at)    let top = max([0] + counts)    let sum_all = sum(counts)    let busiest = top > 0 ? find(days, (n) => at(n) == top) : null    let level = (v) => v <= 0 || top <= 0 ? 0 : (ceil(v * 4 / top) > 4 ? 4 : ceil(v * 4 / top))    let one = ends_with(unit, "s") ? slice(unit, 0, -1) : unit    let says = (v) => `${v == 0 ? "No" : calendar_heatmap_num(v)} ${v == 1 ? one : unit}`    let left = 30    let pitch = 13    let width = left + weeks * pitch    let height = 18 + 7 * pitch    // A month's name over the week its first day falls in (or over the first week, from the    // middle of a month), unless the next one is too close.    let month_day = (c) => find(range(origin + c * 7, origin + c * 7 + 7),                                (n) => n >= first && n <= last && calendar_heatmap_date(n).d == 1)    let marks = range(weeks)        | map((c) => { c, day: month_day(c) ?? (c == 0 && calendar_heatmap_date(first).d <= 14 ? first : null) })        | filter((m) => m.day != null)    let named = marks | filter((m) => {        let next = find(marks, (o) => o.c > m.c)        next == null ? m.c <= weeks - 3 : next.c - m.c >= 3    })    let range_text = start == null ? "in the last year"        : `from ${calendar_heatmap_show(first)} to ${calendar_heatmap_show(last)}`    <figure class={ui_cls(["ui-ch", tone != "accent" && `ui-ch-${tone}`, class])}>        {total && <p.ui-ch-total>{calendar_heatmap_num(sum_all)} {sum_all == 1 ? one : unit} {range_text}</p>}        <div.ui-ch-scroll>            <svg.ui-ch-svg viewBox={`0 0 ${width} ${height}`} style={`min-width: ${floor(width * 0.8)}px; max-width: ${floor(width * 1.3)}px`} role="img"                 aria-label={`${label}: ${calendar_heatmap_num(sum_all)} ${sum_all == 1 ? one : unit} ${range_text}` +                             (busiest != null ? `; the busiest day was ${calendar_heatmap_show(busiest)}, with ${calendar_heatmap_num(top)}` : "")}>                {for m in named => <text x={str(left + m.c * pitch)} y="10">{calendar_heatmap_months[calendar_heatmap_date(m.day).m - 1]}</text>}                {for r in (monday ? [0, 2, 4] : [1, 3, 5]) =>                    <text x="0" y={str(18 + r * pitch + 8)}>{calendar_heatmap_weekdays[(r + (monday ? 1 : 0)) % 7]}</text>}                {for n in days => {                    let v = at(n)                    <rect class={`ui-ch-day ui-ch-l${level(v)}`} x={str(left + floor((n - origin) / 7) * pitch)}                          y={str(18 + row(n) * pitch)} width="10" height="10" rx="2" data-date={calendar_heatmap_iso(n)}>                        <title>{says(v)} on {calendar_heatmap_weekdays[(n + 4) % 7]}, {calendar_heatmap_show(n)}</title>                    </rect>                }}            </svg>        </div>        <div.ui-ch-legend aria-hidden="true">            <span>Less</span>            {for l in range(5) => <svg viewBox="0 0 10 10"><rect class={`ui-ch-l${l}`} width="10" height="10" rx="2"/></svg>}            <span>More</span>        </div>    </figure>}
Thread
66Useful? Like it, or add to its thread below.

Thread 0 posts

On CalendarHeatmap: questions, tips, corrections, and code that uses it.

No posts yet. The first one opens the thread.