import type { GraphNode } from "@/lib/lexicon"; const REL_COLOR: Record = { hypernym: "#576139", hyponym: "#486830", mero_part: "#886620", mero_substance: "#a0733a", mero_member: "#b8864c", holo_part: "#7a667a", holo_substance: "#918091", holo_member: "#a89aa8", attribute: "#a85070", }; /** * Mini force-directed-style semantic graph (SRS §7.7.2, §8.1.2, §9.2). * Renders the focus synset at the centre with immediate neighbours laid * out radially. Nodes are clickable for graph navigation. When * `showIds` is false (Visitor view) no synset IDs are rendered — nodes * are labelled by their Bengali words only. */ export function SemanticGraph({ nodes, showIds, hrefFor, width = 640, height = 420, }: { nodes: GraphNode[]; showIds: boolean; /** Build the navigation URL for a node; return null for non-clickable. */ hrefFor: (node: GraphNode) => string | null; width?: number; height?: number; }) { if (nodes.length === 0) return null; const centre = nodes[0]; const neighbours = nodes.slice(1); const cx = width / 2; const cy = height / 2; const radius = Math.min(width, height) / 2 - 70; const placed = neighbours.map((node, i) => { // Distribute outgoing relations on top half, incoming on bottom const angle = (2 * Math.PI * i) / neighbours.length - Math.PI / 2 + (neighbours.length === 1 ? Math.PI / 4 : 0); const jitter = i % 2 === 0 ? 0 : 18; return { node, x: cx + (radius + jitter) * Math.cos(angle), y: cy + (radius + jitter) * Math.sin(angle), }; }); const label = (n: GraphNode, max = 16) => { const word = n.words[0] ?? (showIds ? n.synsetId : "—"); return word.length > max ? `${word.slice(0, max)}…` : word; }; const usedRels = [...new Set(neighbours.map((n) => n.relation))]; return (
{/* edges */} {placed.map(({ node, x, y }) => ( {node.relation} ))} {/* neighbour nodes */} {placed.map(({ node, x, y }) => { const href = hrefFor(node); const circle = ( {label(node, 8)} {showIds ? ( {node.synsetId} ) : null} ); return href ? ( {circle} ) : ( circle ); })} {/* centre node */} {label(centre, 10)} {showIds ? ( {centre.synsetId} ) : null}
{usedRels.map((rel) => ( {rel} ))} dashed = incoming relation · click a node to navigate
); }