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> ); }); Copy
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> ); });
Component props containing match results.
Pie chart visualization or empty state placeholder.
FormatDistributionChart renders a pie chart showing the breakdown of manga formats.
Source