|
| 1 | +# Recharts — POC Report |
| 2 | + |
| 3 | +**Date:** 2026-06-09 |
| 4 | +**Author:** Serhii Shramko |
| 5 | +**Demo route:** `/labs/recharts` (animations on) / `/labs/recharts?perf=1` (animations off) |
| 6 | +**Source:** `components/recharts-poc/` |
| 7 | +**Plan:** `docs/superpowers/plans/2026-06-09-recharts-poc-plan.md` (local-only) |
| 8 | +**Design spec:** `docs/superpowers/specs/2026-06-09-recharts-poc-design.md` (local-only) |
| 9 | + |
| 10 | +## Verdict |
| 11 | + |
| 12 | +**Ship — conditional on perf finding on Marta's Lenovo.** Recharts cleanly satisfies every functional acceptance criterion attempted in this POC: bar/line/dual-axis/histogram/donut charts, a sortable breakdown table, period-over-period deltas, label position control, label toggling, legend-driven series visibility, and tooltips. The library is actively maintained, fully typed, well-documented, and the API surface for our use cases is small. The only open question is rendering performance on low-spec hardware, captured in the "Weak-laptop test" section below; the `?perf=1` opt-out gives Marta a clean A/B test on her Lenovo. |
| 13 | + |
| 14 | +## Functional acceptance criteria |
| 15 | + |
| 16 | +| # | Criterion | Status | Evidence | Notes | |
| 17 | +|---|-----------|:------:|----------|-------| |
| 18 | +| 1 | Bar chart w/ rounded corners + custom colors | ✅ | `components/recharts-poc/charts/BarChartCard.tsx` | `radius={[8,8,0,0]}` on `<Bar>`; per-bar `<Cell fill>`; colors driven by `usePalette()` so the palette adapts to theme. | |
| 19 | +| 2 | Line chart w/ splines | ✅ | `components/recharts-poc/charts/LineChartCard.tsx` | `<Line type="monotone">` + `<Area>` fill overlay for the long-tail series. | |
| 20 | +| 3 | Dual-axis different scales | ✅ | `components/recharts-poc/charts/DualAxisChartCard.tsx` | `<ComposedChart>` + two `<YAxis yAxisId="left" \| "right">` with independent orientations, tick formatters, and colours. | |
| 21 | +| 4 | Histogram comparing 2 metrics | ⚠️ | `components/recharts-poc/charts/HistogramCard.tsx` | Recharts has no native histogram primitive — we pre-bucket data in `mocks/keywordLength.ts` and render two `<Bar>` series side-by-side. Standard, idiomatic Recharts approach. | |
| 22 | +| 5 | Pie/donut chart with %-labels | ✅ | `components/recharts-poc/charts/DonutCard.tsx` | `<Pie innerRadius={60} outerRadius={110}>` + custom percent-label renderer positioned outside the slice. Hover-grow via `activeShape={{ outerRadius: 120 }}` (note: Recharts 3.x drives this through internal Redux tooltip state, not the deprecated `activeIndex` prop). | |
| 23 | +| 6 | Table with metric breakdown | ✅ | `components/recharts-poc/table/BreakdownTable.tsx` | Plain HTML table; sortable column headers; heat-tinted "position now" cell scaled by `\|delta\|/maxDelta`; Δ column rendered via shared `DeltaBadge`. | |
| 24 | +| 7 | Data labels with position control + format hooks | ✅ | `<LabelList>` children across charts; position picker on `BarChartCard` | The bar chart exposes a `top / inside / center / insideTop` picker so a reviewer can see position control without reading source. Formatter hook used on bar + histogram (`Intl.NumberFormat('en-US')`). | |
| 25 | +| 8 | Toggle data labels on/off | ✅ | `primitives/LabelsToggle.tsx` per chart | Bar, line, histogram each have an independent toggle. Donut renders labels always (it's unreadable without them). | |
| 26 | +| 9 | Period-over-period delta | ✅ | `primitives/periodOverPeriod.ts` + `kpi/DeltaBadge.tsx` | Pure helper, fully tested (positive, negative, flat, zero-prev, zero-curr, both-zero). Renders `▲ +N%` / `▼ -N%` / `— 0%` and gracefully degrades to absolute delta when there's no prior-period baseline. | |
| 27 | +| 10 | Tooltips, legend visible, series toggle, responsive layout | ✅ | `<ResponsiveContainer>` + `<Legend onClick>` + `hide` prop | Legend click toggles a local `Set<string>` of hidden series; the corresponding `<Bar hide>` / `<Line hide>` reads from it. Applied to line, histogram, dual-axis (bar and donut don't need it — single effective series). | |
| 28 | + |
| 29 | +## Non-functional |
| 30 | + |
| 31 | +| Criterion | Status | Evidence | |
| 32 | +|-----------|:------:|----------| |
| 33 | +| Last release ≤ 6 months | ✅ | `recharts@3.8.1` released **2026-03-25** (npm registry). Today is 2026-06-09 — about 2.5 months ago. Well within the window. | |
| 34 | +| TypeScript types | ✅ | Recharts ships its own types — no `@types/recharts` needed. The POC's `pnpm typecheck` is clean across 21 new files. Two type widenings were needed in v3 strict types: `Tooltip.formatter` / `LabelList.formatter` accept `(v: unknown) => ...`, and `Pie.activeShape` needs a cast to attach a plain object — both documented inline in the chart files. | |
| 35 | +| Documentation quality | ✅ | recharts.org + Storybook stories were sufficient for every chart attempted. The only "discoverability" pain point is the v3 API rewrite around `Pie` hover behaviour, which has migrated from props to internal state — but the new behaviour is correct and the types make the right shape obvious. | |
| 36 | +| Bundle impact | ⚠️ | Measured from `next build` output via `.next/build-manifest.json`. **First Load JS (gzipped) — `/labs/recharts`: 315.0 kB vs homepage `/`: 206.0 kB → Δ ≈ 109 kB.** Raw uncompressed delta: ~393 kB (Recharts + d3-* family + Redux Toolkit + react-redux + immer, all from Recharts' transitive graph). Pages Router code-splits per route, so this is **route-local — the homepage and other pages are unaffected**. Reasonable for an analytics page; heavy for a marketing page. | |
| 37 | + |
| 38 | +## Weak-laptop test (Marta's Lenovo) |
| 39 | + |
| 40 | +To be filled in after running on the target hardware. Test instructions: |
| 41 | + |
| 42 | +1. Open `http://shramko.dev/labs/recharts` (or local dev URL). |
| 43 | +2. Note cold-load time, scroll smoothness, tooltip lag, series-toggle responsiveness. |
| 44 | +3. Re-open `http://shramko.dev/labs/recharts?perf=1` (animations off) and repeat. |
| 45 | +4. Fill in the scores below (1 = unusable, 5 = smooth). |
| 46 | + |
| 47 | +| Scenario | Animations on | Animations off | |
| 48 | +|----------|---------------|----------------| |
| 49 | +| Cold load to interactive | _ s | _ s | |
| 50 | +| Scroll smoothness (1–5) | _ | _ | |
| 51 | +| Tooltip lag (1–5) | _ | _ | |
| 52 | +| Series toggle response (1–5) | _ | _ | |
| 53 | +| Subjective verdict | _ | _ | |
| 54 | + |
| 55 | +## Known limitations / workarounds |
| 56 | + |
| 57 | +- **No native histogram primitive.** Recharts has no `HistogramChart` — we pre-bucket data and render with `<BarChart>` and two `<Bar>` series. This is the library-idiomatic approach. |
| 58 | +- **Legend `onClick` not wired by default.** We attach a small handler that toggles a local `Set<string>` of hidden series; each series reads `hide={hiddenSet.has(key)}`. ~6 lines per chart. |
| 59 | +- **`ResponsiveContainer` + SSR.** Under Pages Router SSR, `ResponsiveContainer` measures DOM dimensions before they exist and would render at 0×0 on first paint. Mitigated by a `useIsMounted()` guard in `ChartCard` that renders a same-height skeleton until the component mounts. No hydration warnings observed. |
| 60 | +- **`next-themes` flash on first render.** `resolvedTheme` is `undefined` on first render. `usePalette()` defaults to the light palette until mounted, then switches once `resolvedTheme` is available. Combined with the `ChartCard` mount guard, charts only paint once the theme is resolved. |
| 61 | +- **`Pie.activeIndex` removed in v3.** Hover-grow now flows through the chart's internal Redux tooltip state; the consumer just declares `activeShape={...}` and the active sector renders with those props automatically. Documented inline in `DonutCard.tsx`. |
| 62 | +- **Bundle size.** ~109 kB gzipped per route is real. Pages Router code-splits per route, so this is contained to `/labs/recharts` and any future chart pages; the rest of the site is unaffected. |
| 63 | + |
| 64 | +## Extraction notes |
| 65 | + |
| 66 | +To lift this POC into a separate app: |
| 67 | + |
| 68 | +1. Copy `pages/labs/recharts.tsx` and the entire `components/recharts-poc/` folder. |
| 69 | +2. Install: `recharts`, `next-themes`, `clsx`, `lucide-react`, plus the new app's normal Next.js + Tailwind setup. |
| 70 | +3. The POC has zero imports reaching outside its own folder. Only the page file imports from the barrel `@/components/recharts-poc`. Verified by `grep -rn "recharts-poc" pages/ components/`. |
0 commit comments