Ascii.Pieces.Heatmap (Ascii v0.3.0)

Copy Markdown View Source

Contribution calendar, a day at a time, weeks scrolling left.

A contribution calendar, weeks across and weekdays down, shaded by how busy each day was. As upstream draws it, days fill in one at a time and a full week moves it left; give it counts and it shows your activity instead, standing still.

Options

Give them to Ascii.new/2 as options:, and new ones while it plays with Ascii.put_options/2 (or a Phoenix player's options): the calendar is drawn anew from them, a day's new count as soon as it comes.

  • counts - each day's count, oldest first, the last being date, up to a billion either way. Days are shaded in four steps of the busiest day's count, as GitHub does; days before the first are blank. Default []: upstream's made-up activity, filling in a day at a time.
  • date - the last count's day, as "2026-10-07". Default "": today, from the clock (or now:, see Ascii.frame/3).
  • weeks - how many weeks show, 12 to 97; the frame is two columns a week and six more wide, and always 12 rows tall. Default 30.
  • unit - what is counted, after the total under the calendar. Default "contributions".
  • months, weekdays - the names to use, twelve from January and seven from Sunday, of which the scale shows the first three letters (months along the top, Monday, Wednesday and Friday down the side). English by default; a list of another length is ignored.
  • seed - the seed of upstream's made-up activity. Default 7.

Colours

In one ink, or a gradient, with :ink and :gradient (see Ascii.Ink); part by part with :parts (see Ascii.Parts), parts: true taking the colours suggested here, GitHub's greens:

  • scale - the months and weekdays, grey.
  • level.0 - an empty day, grey.
  • level.1, level.2, level.3, level.4 - the days by their shade, quietest to busiest: dark green, green, bright green and brightest green.
  • value, unit - the total under the calendar, and its unit.
  • legend - the key's words, grey; its shades are in their levels.

Keys and clicks

It takes none: it shows activity, and Ascii.handle_event/2 ignores every event. A Phoenix player given interactive and notify still tells your LiveView where it was clicked, and on which part (Ascii.part_at/4): a day's level.3, say.

Screen readers

Ascii.describe/1 reads the counts given, their total, unit and span, and the busiest day's count: "heatmap: 19 commits in the last 12 weeks, busiest day 9". With a date, the total is the frame's own; without one it takes the last weeks of counts. Without counts, it gives the piece's name and note.

Example

options = %{counts: [0, 1, 2, 4, 9, 3], date: "2026-10-07", unit: "commits", weeks: 12}
map = Ascii.new!("heatmap", options: options, parts: true)

{frame, _map} = Ascii.frame(map, 0.0)
{frame.cols, frame.rows}
#=> {30, 12}

Ascii.describe(map)
#=> "heatmap: 19 commits in the last 12 weeks, busiest day 9"
<Ascii.Phoenix.live_player id="commits" piece="heatmap" options={%{counts: @counts, unit: "commits"}} parts />

Ported from src/pieces/heatmap.ts in bas3line/ascii (MIT).