import { useMemo, useState } from "react"; import { Pack, hierarchy } from "@visx/hierarchy"; import { Group } from "@visx/group"; import type { DonutSlice } from "./DonutChart"; interface Props { data: DonutSlice[]; size: number; formatValue: (n: number) => string; } /** Synthetic root d3-hierarchy packs against; never rendered itself. */ interface Root { children: DonutSlice[]; } /** * The same DonutSlice[] a DonutChart takes, laid out as proportional * circles instead of a ring. A caller toggling between the two swaps one * component for the other with no data reshaping. */ export function BubbleChart({ data, size, formatValue }: Props) { const [active, setActive] = useState(null); const total = data.reduce((s, d) => s + d.value, 0); const root = useMemo(() => { const shown = data.filter((d) => d.value > 0); return hierarchy({ children: shown }) .sum((d) => ("value" in d ? (d as unknown as DonutSlice).value : 0)) .sort((a, b) => (b.value ?? 0) - (a.value ?? 0)); }, [data]); if (data.every((d) => d.value <= 0)) return null; return (
{(packed) => ( {packed .descendants() .filter((d) => d.depth > 0) .map((c) => { const d = c.data as unknown as DonutSlice; const isActive = active === d.name; const pct = total > 0 ? (d.value / total) * 100 : 0; const r = c.r; const showLabel = r > 22; return ( setActive(d.name)} onPointerLeave={() => setActive(null)} onFocus={() => setActive(d.name)} onBlur={() => setActive(null)} > {showLabel && ( {pct.toFixed(0)}% )} {showLabel && ( {d.name} )} ); })} )}
); }