/** * The past year of attempts — LeetCode's calendar heatmap: one cell per day, * weeks as columns, each month its own block with a gap between, and the * year's attempt count, active days and longest streak above it. The colour * ramp is the README/email heatmap's (theme.css `--heat-0..4`, level = * attempts capped at 4) and rows start on Monday like it, so the three * heatmaps read the same. Drawn as SVG in a viewBox, so it scales to the * column instead of scrolling. */ import { StatsPending, TipRow, useHoverTip } from "../components/progress/shared"; import { Card, CardAction, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card"; import { type Stats, useStats, utcDate } from "../lib/stats"; export const client = "load"; const CELL = 10; const STEP = 13; // cell + gap const MONTH_GAP = 8; const LABEL_H = 16; const HEAT = ["var(--heat-0)", "var(--heat-1)", "var(--heat-2)", "var(--heat-3)", "var(--heat-4)"]; const MONTH = new Intl.DateTimeFormat("en-US", { month: "short", timeZone: "UTC" }); const DAY = new Intl.DateTimeFormat("en-US", { month: "short", day: "numeric", year: "numeric", timeZone: "UTC" }); interface Block { label: string; x: number; width: number; cells: { x: number; y: number; date: Date; attempts: number; passes: number }[]; } /** * Month blocks laid left to right. Within a block a day sits at row = * weekday (Mon = 0) and column = its week counted from the block's first day, * so a month starting on a Thursday opens with a short first column. */ function layout(days: Stats["year"]["days"]): { blocks: Block[]; width: number } { const blocks: Block[] = []; let x = 0; let month = -1; let block: Block | undefined; let lead = 0; let index = 0; for (const day of days) { const date = utcDate(day.date); if (date.getUTCMonth() !== month) { if (block) x += block.width + MONTH_GAP; month = date.getUTCMonth(); lead = (date.getUTCDay() + 6) % 7; index = 0; block = { label: MONTH.format(date), x, width: 0, cells: [] }; blocks.push(block); } const row = (lead + index) % 7; const col = Math.floor((lead + index) / 7); block!.cells.push({ x: x + col * STEP, y: row * STEP, date, attempts: day.attempts, passes: day.passes }); block!.width = (col + 1) * STEP - (STEP - CELL); index++; } return { blocks, width: block ? x + block.width : 0 }; } export default function SubmissionHeatmap() { const load = useStats(); const hover = useHoverTip(); if (load.phase !== "ready") { return ( ); } const { year } = load.stats; const { blocks, width } = layout(year.days); const height = 7 * STEP - (STEP - CELL) + LABEL_H; return ( {year.attempts} attempts in the past one year Total active days: {year.active_days} Max streak: {year.max_streak}
{blocks.map((block) => ( {block.cells.map((cell) => ( hover.show( event, <>
{DAY.format(cell.date)}
, ) } /> ))} {block.width >= 2 * STEP && ( {block.label} )} ))} {hover.node}
); }