mirror of
https://github.com/prdlk/leetcode.git
synced 2026-11-02 21:22:43 +00:00
feat: refactored docs structure
This commit is contained in:
1 parent
d388b3000e
commit
9de25fecff
212 files changed
+641
-4169
No files matched your search
@@ -1,165 +0,0 @@
|
||||
/**
|
||||
* 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 { ref, show, hide, node } = useHoverTip<HTMLDivElement>();
|
||||
|
||||
if (load.phase !== "ready") {
|
||||
return (
|
||||
<Card>
|
||||
<CardContent>
|
||||
<StatsPending load={load} className="h-40 w-full" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-baseline gap-2">
|
||||
<span className="font-semibold text-2xl tabular-nums">{cells.reduce((n, c) => n + c.attempts, 0)}</span>
|
||||
<span className="font-normal text-muted-foreground text-sm">attempts in the past {MONTHS} months</span>
|
||||
</CardTitle>
|
||||
<CardDescription className="flex gap-4">
|
||||
<span>
|
||||
Active days:{" "}
|
||||
<span className="text-foreground tabular-nums">{cells.filter((c) => c.attempts > 0).length}</span>
|
||||
</span>
|
||||
<span>
|
||||
Max streak: <span className="text-foreground tabular-nums">{streak}</span>
|
||||
</span>
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="flex flex-1 items-center">
|
||||
<div ref={ref} className="relative w-full" onPointerLeave={hide}>
|
||||
<svg
|
||||
viewBox={`0 0 ${width} ${height}`}
|
||||
className="h-auto w-full"
|
||||
role="img"
|
||||
aria-label={`Attempts per day over the past ${MONTHS} months`}
|
||||
>
|
||||
{blocks.map((block) => (
|
||||
<g key={`${block.label}-${block.x}`}>
|
||||
{block.cells.map((cell) => (
|
||||
<rect
|
||||
key={cell.x * 1000 + cell.y}
|
||||
x={cell.x}
|
||||
y={cell.y}
|
||||
width={CELL}
|
||||
height={CELL}
|
||||
rx={2}
|
||||
fill={HEAT[Math.min(cell.attempts, 4)]}
|
||||
onPointerMove={(event) =>
|
||||
show(
|
||||
event,
|
||||
<>
|
||||
<div className="font-medium">{DAY.format(cell.date)}</div>
|
||||
<TipRow color={HEAT[Math.min(cell.attempts, 4)]!} label="Attempts" value={cell.attempts} />
|
||||
<TipRow color="var(--chart-4)" label="Passed" value={cell.passes} />
|
||||
</>,
|
||||
)
|
||||
}
|
||||
/>
|
||||
))}
|
||||
<text
|
||||
x={block.x + block.width / 2}
|
||||
y={height - 3}
|
||||
textAnchor="middle"
|
||||
className="fill-muted-foreground text-[9px]"
|
||||
>
|
||||
{block.label}
|
||||
</text>
|
||||
</g>
|
||||
))}
|
||||
</svg>
|
||||
{node}
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user