#!/usr/bin/env python3
import json
import math
import sys
from datetime import datetime, timedelta
from pathlib import Path
COLORS = [
"#ebedf0",
"#9be9a8",
"#40c463",
"#30a14e",
"#216e39",
]
CELL = 12
GAP = 2
RECT = CELL - GAP
LEFT_PAD = 30
TOP_PAD = 20
RIGHT_PAD = 10
BOTTOM_PAD = 10
FONT_FAMILY = "-apple-system,BlinkMacSystemFont,Segoe UI,Helvetica,Arial,sans-serif"
LABEL_COLOR = "#57606a"
def load_heatmap(path):
with path.open() as handle:
return json.load(handle)
def date_range(start, end):
current = start
while current <= end:
yield current
current += timedelta(days=1)
def previous_sunday(day):
return day - timedelta(days=(day.weekday() + 1) % 7)
def next_saturday(day):
return day + timedelta(days=(5 - day.weekday()) % 7)
def intensity(count, max_count):
if count <= 0 or max_count <= 0:
return 0
return max(1, min(4, math.ceil((count / max_count) * 4)))
def weekday_row(day):
return (day.weekday() + 1) % 7
def month_labels(grid_start, visible_start, grid_end):
labels = []
total_weeks = ((grid_end - grid_start).days // 7) + 1
for week in range(total_weeks):
week_start = grid_start + timedelta(days=week * 7)
week_label = None
for offset in range(7):
day = week_start + timedelta(days=offset)
if day < visible_start:
continue
if week == 0 and week_label is None:
week_label = day.strftime("%b")
if day.day == 1:
week_label = day.strftime("%b")
break
if week_label is not None:
if not labels or labels[-1][1] != week_label:
labels.append((week, week_label))
return labels
def build_rect(day, start, counts, max_count):
date_str = day.isoformat()
count = counts.get(date_str, 0)
level = intensity(count, max_count)
x = LEFT_PAD + (((day - start).days // 7) * CELL)
y = TOP_PAD + (weekday_row(day) * CELL)
commit_label = "commit" if count == 1 else "commits"
return (
f''
f"{date_str}: {count} {commit_label}"
""
)
def generate_svg(data):
counts = {entry["date"]: entry["count"] for entry in data["days"]}
max_count = data["max_daily_commits"]
start = datetime.strptime(data["from_date"], "%Y-%m-%d").date()
end = datetime.strptime(data["to_date"], "%Y-%m-%d").date()
aligned_start = previous_sunday(start)
aligned_end = next_saturday(end)
weeks = ((aligned_end - aligned_start).days // 7) + 1
width = LEFT_PAD + (weeks * CELL) + RIGHT_PAD
height = TOP_PAD + (7 * CELL) + BOTTOM_PAD
svg = [
f'")
return "\n".join(svg) + "\n"
def main():
if len(sys.argv) not in {2, 3}:
print(
"Usage: generate_heatmap_svg.py [output.svg]",
file=sys.stderr,
)
return 1
input_path = Path(sys.argv[1])
output = generate_svg(load_heatmap(input_path))
if len(sys.argv) == 3:
output_path = Path(sys.argv[2])
output_path.parent.mkdir(parents=True, exist_ok=True)
output_path.write_text(output)
else:
sys.stdout.write(output)
return 0
if __name__ == "__main__":
raise SystemExit(main())