import React, { useState, useRef, useMemo, useEffect, type FC } from 'react' import Empty from '@/components/Empty' import { GRAPH_COLORS, initCommunityGraph } from './utils' import { useD3Graph } from './hooks' import type { CommunityD3Node, D3Link, CommunityGraphProps } from './types' // ─── Component ──────────────────────────────────────────────────────────────── // Renders a D3-powered community graph with optional tooltip and legend. const CommunityGraph: FC = ({ data, empty: emptyProp, colors = GRAPH_COLORS, renderTooltip, showLegend = true, onCommunityClick, onNodeClick, defaultZoom = 1, }) => { // Tooltip position and hovered node state const [tooltip, setTooltip] = useState<{ x: number; y: number; node: CommunityD3Node } | null>(null) // Keep callback refs stable to avoid re-initializing the graph on every render const onCommunityClickRef = useRef(onCommunityClick) const onNodeClickRef = useRef(onNodeClick) const renderTooltipRef = useRef(renderTooltip) useEffect(() => { onCommunityClickRef.current = onCommunityClick }, [onCommunityClick]) useEffect(() => { onNodeClickRef.current = onNodeClick }, [onNodeClick]) useEffect(() => { renderTooltipRef.current = renderTooltip }, [renderTooltip]) const graphState = useMemo(() => data, [data]) // Show empty state when explicitly flagged or when there are no nodes const isEmpty = emptyProp ?? !data?.nodes.length // Initialize (or re-initialize) the D3 graph whenever relevant state changes const containerRef = useD3Graph((container) => { if (!graphState) return return initCommunityGraph( container, graphState.nodes, graphState.links as D3Link[], graphState.communityMap, graphState.communityCaption, graphState.communityNodeMap, { colors, showLegend, defaultZoom, setTooltip: renderTooltip ? setTooltip : () => {}, onCommunityClickRef, onNodeClickRef } ) }, [graphState, showLegend, defaultZoom]) // Resolve tooltip content: use custom renderer if provided, otherwise fall back to DefaultTooltip const tooltipNode = tooltip && renderTooltipRef.current ? renderTooltipRef.current(tooltip.node) : null if (isEmpty) return return (
{tooltipNode ? (
{tooltipNode}
) : undefined}
) } export default React.memo(CommunityGraph)