Skip to content

Instantly share code, notes, and snippets.

@recollir
Last active March 27, 2026 21:18
Show Gist options
  • Select an option

  • Save recollir/8f3bcd6c91a482dc9a5b13b7fc823799 to your computer and use it in GitHub Desktop.

Select an option

Save recollir/8f3bcd6c91a482dc9a5b13b7fc823799 to your computer and use it in GitHub Desktop.
Create GitHub styled heatmaps with gnuplot
# set terminal pngcairo size 1200,210 font "Sans,10"
# set output 'calendar_heatmap.png'
set terminal svg size 1200,210
set output 'calendar_heatmap.svg'
set termoption font "Sans,10"
set datafile separator ","
# Clean look
unset border
unset key
unset colorbox
# Weeks on top (x2): 1..53
set xrange [0.5:53.5]
set x2range [0.5:53.5]
unset xtics
set x2tics 1,1,53 \
scale 0 offset 0,-0.5 font "Sans,8"
# Weekdays Mon..Sun, top-to-bottom
set yrange [7.5:0.5]
set ytics ("Mon" 1, "Tue" 2, "Wed" 3, "Thu" 4, "Fri" 5, "Sat" 6, "Sun" 7) \
scale 0 offset 0,0 font "Sans,8"
# GitHub-ish palette (0 = empty gray)
set palette defined ( \
0 "#ebedf0", \
1 "#9be9a8", \
2 "#40c463", \
3 "#30a14e", \
4 "#216e39", \
5 "#0d4a20" \
)
set cbrange [0:5]
# Sizes - tile_inner would normally equal tile_data
tile_outer = 0.78 # outer cell as border
tile_inner = 0.76 # inner gray cell
tile_data = 0.76 # colored cell
border_col = "#d0d7de" # outer border
empty_col = "#ebedf0" # empty tile fill
unset object
# Draw the full grid of "empty" cells
do for [w=1:53] {
do for [d=1:7] {
# outer cell as border
set object rect from (w-tile_outer/2.0),(d-tile_outer/2.0) to (w+tile_outer/2.0),(d+tile_outer/2.0) \
fc rgb border_col fs solid 1.0 noborder behind
# inner gray cell
set object rect from (w-tile_inner/2.0),(d-tile_inner/2.0) to (w+tile_inner/2.0),(d+tile_inner/2.0) \
fc rgb empty_col fs solid 1.0 noborder behind
}
}
set style fill solid 1.0 noborder
# Bucket values into 0..5, cap at 5 and ensure integer value
level(v) = (v <= 0 ? 0 : (v >= 5 ? 5 : int(v)))
# Plot values as colored squares using the boxxyerrorbars style (boxxy)
# The zero value is colored the same as an empty cell
plot 'releases_with_isoweek_wday.csv' using 1:2:(tile_data/2):(tile_data/2):(level($3)) \
with boxxy lc palette notitle
# Generate calendar.csv as input data for heatmap
curl -s https://curl.se/docs/releases.csv |\
awk -F';' '{print $4}' |\
while read -r d; do
wday=$(date -d "$d" +%u) # 1=Mon..7=Sun
week=$(date -d "$d" +%V) # ISO week 01..53
printf "%s,%s,%s\n" "$d" "$week" "$wday"
done |\
awk -F',' '{ key=$2","$3; c[key]++ }
END {for (k in c) print k","c[k]}' |\
sort -t, -k1,1n -k2,2n > releases_with_isoweek_wday.csv
# Generate heatmap (saved as calendar_heatmap.svg)
gnuplot calendar_heatmap.gnuplot
@recollir
Copy link
Copy Markdown
Author

recollir commented Mar 27, 2026

Result of above gnuplot script based on the releases_with_isoweek_wday.csv data

calendar_heatmap

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment