import { Bar, BarChart, CartesianGrid, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; import { CHART_DEFAULTS, CHART_THEME, TOOLTIP_CONTENT_STYLE } from '../../lib/chart-theme'; import { formatNumber } from '../../lib/formatters'; import { TREND_FREQUENCY_BANDS, type TrendFrequencyBand, type TrendFrequencyBandPoint, } from '../../types/stats'; interface FrequencyBandChartProps { data: TrendFrequencyBandPoint[]; } const BAND_LABELS: Record = { top1k: 'Top 1k', top5k: '1k to 5k', top10k: '5k to 10k', top20k: '10k to 20k', beyond20k: 'Beyond 20k', unranked: 'Unranked', }; // Bands are ordered, so one hue runs from faint (common) to strong (rare): the bar // brightens as new vocabulary moves further into rare words. Unranked words have // no place in that order and get a neutral color. const BAND_FILLS: Record = { top1k: { fill: '#8aadf4', fillOpacity: 0.2 }, top5k: { fill: '#8aadf4', fillOpacity: 0.36 }, top10k: { fill: '#8aadf4', fillOpacity: 0.54 }, top20k: { fill: '#8aadf4', fillOpacity: 0.74 }, beyond20k: { fill: '#8aadf4', fillOpacity: 1 }, unranked: { fill: '#6e738d', fillOpacity: 0.6 }, }; interface BandTooltipEntry { dataKey?: unknown; value?: unknown; } function BandTooltip({ active, label, payload, }: { active?: boolean; label?: string | number; payload?: BandTooltipEntry[]; }) { if (!active || !payload?.length) return null; const rows = payload.map((entry) => ({ band: TREND_FREQUENCY_BANDS.find((band) => band === entry.dataKey), count: typeof entry.value === 'number' ? entry.value : 0, })); const total = rows.reduce((sum, row) => sum + row.count, 0); return (
{label}: {formatNumber(total)} new words
{rows.map(({ band, count }) => band ? (
{BAND_LABELS[band]} {formatNumber(count)} ({total > 0 ? Math.round((count / total) * 100) : 0}%)
) : null, )}
); } export function FrequencyBandChart({ data }: FrequencyBandChartProps) { const rows = data.map((point) => ({ label: point.label, ...point.counts })); return (

New Words by Frequency Rank

{rows.length === 0 ? (
No data
) : ( <> `${Math.round(value * 100)}%`} /> } wrapperStyle={{ zIndex: 50 }} /> {TREND_FREQUENCY_BANDS.map((band) => ( ))}
{TREND_FREQUENCY_BANDS.map((band) => ( {BAND_LABELS[band]} ))}
)}
); }