#!/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(start, end): labels = [(0, start.strftime("%b"))] total_weeks = ((end - start).days // 7) + 1 for week in range(total_weeks): week_start = start + timedelta(days=week * 7) for offset in range(7): day = week_start + timedelta(days=offset) if day.day == 1 and week != 0: labels.append((week, day.strftime("%b"))) break 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'', '', ] for week, label in month_labels(aligned_start, aligned_end): x = LEFT_PAD + (week * CELL) svg.append(f'{label}') for label, row in (("Mon", 1), ("Wed", 3), ("Fri", 5)): y = TOP_PAD + (row * CELL) + 8 svg.append(f'{label}') for day in date_range(aligned_start, aligned_end): svg.append(build_rect(day, aligned_start, counts, max_count)) svg.append("") 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())