FormatDistributionChart renders a pie chart showing the breakdown of manga formats.

export const FormatDistributionChart: FC<FormatDistributionChartProps> =
React.memo(function FormatDistributionChartMemo({
// eslint-disable-next-line react/prop-types
matchResults,
// eslint-disable-next-line react/prop-types
className,
// eslint-disable-next-line react/prop-types
onDrillDown,
// eslint-disable-next-line react/prop-types
filteredMatchResults,
// eslint-disable-next-line react/prop-types
readingHistory,
}) {
const data = useMemo(() => buildFormatData(matchResults), [matchResults]);
const total = useMemo(
() => data.reduce((acc, item) => acc + item.value, 0),
[data],
);

return (
<section
aria-label="Format distribution"
className={cn(
"rounded-2xl border border-slate-200 bg-white/90 p-6 shadow-sm backdrop-blur-md dark:border-slate-800 dark:bg-slate-900/90",
className,
)}
>
<header className="mb-4 flex items-start justify-between gap-3">
<div>
<div className="flex items-center gap-2">
<span className="bg-linear-to-r inline-flex h-9 min-h-9 w-9 min-w-9 items-center justify-center rounded-full from-blue-500/15 via-purple-500/15 to-emerald-500/15 text-blue-500 dark:text-blue-300">
<BookOpen className="h-4 w-4" aria-hidden="true" />
</span>
<h2 className="text-foreground text-lg font-semibold">
Format Distribution
</h2>
</div>
<p className="text-muted-foreground mt-1 text-sm">
Breakdown of matched entries across manga formats.
</p>
</div>
{total > 0 && (
<span className="text-muted-foreground text-sm">
{total.toLocaleString()} matched entries
</span>
)}
</header>

{data.length === 0 ? (
<div className="text-muted-foreground flex flex-col items-center justify-center rounded-xl border border-dashed border-slate-200 bg-slate-50/60 p-8 text-center dark:border-slate-800 dark:bg-slate-900/60">
<AlertCircle className="mb-3 h-6 w-6" aria-hidden="true" />
<p className="font-medium">Match manga to see format breakdown</p>
<p className="mt-1 text-sm">
Complete matching to reveal the formats in your AniList
collection.
</p>
</div>
) : (
<figure className="h-[300px] w-full">
<ResponsiveContainer width="100%" height="100%">
<PieChart>
<Pie
data={data}
dataKey="value"
nameKey="name"
outerRadius={90}
labelLine={false}
label={(entry: PieLabelPayload) =>
`${(entry.value ?? 0).toLocaleString()}`
}
cursor={onDrillDown ? "pointer" : "default"}
onClick={(entry) => {
if (
onDrillDown &&
filteredMatchResults &&
readingHistory &&
entry
) {
const raw = (entry as { payload?: { raw?: string } })
?.payload?.raw;
if (!raw) return;
onDrillDown(
computeDrillDownData(
filteredMatchResults,
"format",
raw,
readingHistory,
),
);
}
}}
>
{data.map((entry) => (
<Cell key={entry.raw} fill={entry.color} />
))}
</Pie>
<Tooltip
contentStyle={{
backgroundColor: "hsl(var(--muted))",
borderRadius: "calc(var(--radius) - 4px)",
padding: "0.5rem 0.75rem",
border: "none",
}}
itemStyle={{ color: "white" }}
formatter={(value: number, name: string) => {
const numeric = Number(value ?? 0);
const percent = total > 0 ? numeric / total : 0;
return [
`${numeric.toLocaleString()} (${Math.round(percent * 100)}%)`,
name,
];
}}
/>
<Legend iconType="circle" verticalAlign="bottom" height={36} />
</PieChart>
</ResponsiveContainer>
</figure>
)}
</section>
);
});