export function flattenData(raw) { const result = {}; for (const [date, repos] of Object.entries(raw)) { result[date] = Object.values(repos).reduce((sum, n) => sum + n, 0); } return result; } export function constructWeeks() { const today = new Date(); today.setHours(0, 0, 0, 0); const start = new Date(today); start.setDate(today.getDate() - 52 * 7); start.setDate(start.getDate() - start.getDay()); const weeks = []; const cur = new Date(start); while (cur <= today) { const week = []; for (let d = 0; d < 7; d++) { const day = new Date(cur); week.push(day <= today ? day : null); cur.setDate(cur.getDate() + 1); } weeks.push(week); } return weeks; } function getColor(count) { if (count === 0) return "var(--color-empty)"; if (count <= 2) return "var(--color-l1)"; if (count <= 5) return "var(--color-l2)"; if (count <= 10) return "var(--color-l3)"; return "var(--color-l4)"; } export function render(weeks, counts) { const CELL = 13; const GAP = 2; const SHIFT = CELL + GAP; const svgNS = "http://www.w3.org/2000/svg"; const svg = document.createElementNS(svgNS, "svg"); const svgWidth = weeks.length * SHIFT + 30; const svgHeight = SHIFT * 7; svg.setAttribute("viewBox", `0 0 ${svgWidth} ${svgHeight}`); svg.setAttribute("width", "100%"); // Place the labels first ["Mon", "Wed", "Fri"].forEach((label, i) => { const dayLabel = document.createElementNS(svgNS, "text"); dayLabel.setAttribute("x", 0); dayLabel.setAttribute("y", (2 * i + 1) * SHIFT + 0.75 * CELL); dayLabel.setAttribute("font-size", 12); const text = document.createTextNode(label); dayLabel.appendChild(text); svg.appendChild(dayLabel); }); weeks.forEach((week, col) => { week.forEach((day, row) => { if (day === null) { return; } const key = day.toISOString().slice(0, 10); // Get the key in yyyy-mm-dd format const count = counts[key] || 0; const rect = document.createElementNS(svgNS, "rect"); rect.setAttribute("x", col * SHIFT + 30); rect.setAttribute("y", row * SHIFT); rect.setAttribute("height", CELL); rect.setAttribute("width", CELL); rect.setAttribute("fill", getColor(count)); rect.setAttribute("rx", 2); rect.setAttribute("data-date", key); rect.setAttribute("data-count", count); svg.appendChild(rect); }); }); return svg; } export function totalCount(counts) { return Object.values(counts).reduce((s, v) => s + v, 0); } export function renderLegend() { const CELL = 11; const GAP = 3; const SHIFT = CELL + GAP; const svgNS = "http://www.w3.org/2000/svg"; const svg = document.createElementNS(svgNS, "svg"); svg.setAttribute("width", 5 * SHIFT - GAP); svg.setAttribute("height", CELL); svg.style.display = "inline-block"; svg.style.verticalAlign = "middle"; const colors = [ "var(--color-empty)", "var(--color-l1)", "var(--color-l2)", "var(--color-l3)", "var(--color-l4)", ]; colors.forEach((color, i) => { const rect = document.createElementNS(svgNS, "rect"); rect.setAttribute("x", i * SHIFT); rect.setAttribute("y", 0); rect.setAttribute("width", CELL); rect.setAttribute("height", CELL); rect.setAttribute("fill", color); rect.setAttribute("rx", 2); svg.appendChild(rect); }); return svg; } export function setupTooltips(svg) { const tooltip = document.getElementById("tooltip"); document.body.appendChild(tooltip); svg.addEventListener("mouseover", (e) => { if (e.target.tagName != "rect") return; const date = e.target.dataset.date; const count = e.target.dataset.count; tooltip.textContent = `${date} - ${count} contribution${count == 1 ? "" : "s"}`; tooltip.classList.remove("hidden"); }); svg.addEventListener("mousemove", (e) => { tooltip.style.left = e.clientX + 12 + "px"; tooltip.style.top = e.clientY - 28 + "px"; }); svg.addEventListener("mouseout", (e) => { if (e.target.tagName === "rect") tooltip.classList.add("hidden"); }); }