/** * The last six months of attempts — LeetCode's calendar heatmap, halved to * share its row with the submissions chart: one cell per day, weeks as * columns, each month its own block, and the window's attempt count, active * days and longest streak above it. The figures are counted from exactly the * cells drawn, so they cannot disagree with the grid. 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 its column instead of scrolling. */ import { StatsPending, TipRow, useHoverTip } from "../components/progress/shared"; import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "../components/ui/card"; import { type Day, useStats, utcDate } from "../lib/stats"; export const client = "load"; const MONTHS = 6; 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 Cell { x: number; y: number; date: Date; attempts: number; passes: number; } interface Block { label: string; x: number; width: number; cells: Cell[]; } /** * Month blocks laid left to right over the last MONTHS calendar months. Within * a block a day sits at row = weekday (Mon = 0) and column = its week counted * from the 1st, so a month starting on a Thursday opens with a short column. */ function layout(year: Day[]): { blocks: Block[]; width: number } { const last = utcDate(year.at(-1)?.date ?? "1970-01-01"); const from = Date.UTC(last.getUTCFullYear(), last.getUTCMonth() - (MONTHS - 1), 1); const blocks: Block[] = []; let x = 0; for (const day of year) { const date = utcDate(day.date); if (date.getTime() < from) continue; let block = blocks.at(-1); if (date.getUTCDate() === 1 || !block) { if (block) x += block.width + MONTH_GAP; block = { label: MONTH.format(date), x, width: 0, cells: [] }; blocks.push(block); } const first = new Date(Date.UTC(date.getUTCFullYear(), date.getUTCMonth(), 1)); const index = (first.getUTCDay() + 6) % 7 + date.getUTCDate() - 1; const col = Math.floor(index / 7); block.cells.push({ x: x + col * STEP, y: (index % 7) * STEP, date, attempts: day.attempts.easy + day.attempts.medium + day.attempts.hard, passes: day.passes, }); block.width = col * STEP + CELL; } const end = blocks.at(-1); return { blocks, width: end ? end.x + end.width : 0 }; } export default function SubmissionHeatmap() { const load = useStats(); const hover = useHoverTip(); if (load.phase !== "ready") { return ( ); } const { blocks, width } = layout(load.stats.year); const cells = blocks.flatMap((b) => b.cells); let run = 0; let streak = 0; for (const c of cells) { run = c.attempts > 0 ? run + 1 : 0; streak = Math.max(streak, run); } const height = 7 * STEP - (STEP - CELL) + LABEL_H; return ( {cells.reduce((n, c) => n + c.attempts, 0)} attempts in the past {MONTHS} months Active days:{" "} {cells.filter((c) => c.attempts > 0).length} Max streak: {streak}
{blocks.map((block) => ( {block.cells.map((cell) => ( hover.show( event, <>
{DAY.format(cell.date)}
, ) } /> ))} {block.label} ))} {hover.node}
); }