"use client"; import { useState, useMemo, forwardRef } from "react"; import { Graph, GraphRef } from "./graph"; import { GraphPopovers } from "./graph-popover"; import type { RawTriplet, NodePopupContent, EdgePopupContent } from "./type"; import { toGraphTriplets } from "./type"; import { createLabelColorMap, getNodeColor } from "./node-colors"; import { HoverCard, HoverCardContent, HoverCardTrigger, } from "@/components/ui/hover-card"; import { useTheme } from "remix-themes"; interface GraphVisualizationProps { triplets: RawTriplet[]; width?: number; height?: number; zoomOnMount?: boolean; className?: string; } export const GraphVisualization = forwardRef( ( { triplets, width = window.innerWidth * 0.85, height = window.innerHeight * 0.85, zoomOnMount = true, className = "border border-border rounded-md h-[85vh] overflow-hidden relative", }, ref, ) => { const [resolvedTheme] = useTheme(); const isDarkMode = resolvedTheme === "dark"; // Graph state for popovers const [showNodePopup, setShowNodePopup] = useState(false); const [showEdgePopup, setShowEdgePopup] = useState(false); const [nodePopupContent, setNodePopupContent] = useState(null); const [edgePopupContent, setEdgePopupContent] = useState(null); // Convert raw triplets to graph triplets const graphTriplets = useMemo(() => toGraphTriplets(triplets), [triplets]); // Extract all unique labels from triplets const allLabels = useMemo(() => { const labels = new Set(); labels.add("Entity"); // Always include Entity as default graphTriplets.forEach((triplet) => { if (triplet.source.primaryLabel) labels.add(triplet.source.primaryLabel); if (triplet.target.primaryLabel) labels.add(triplet.target.primaryLabel); }); return Array.from(labels).sort((a, b) => { // Always put "Entity" first if (a === "Entity") return -1; if (b === "Entity") return 1; // Sort others alphabetically return a.localeCompare(b); }); }, [graphTriplets]); // Create a shared label color map const sharedLabelColorMap = useMemo(() => { return createLabelColorMap(allLabels); }, [allLabels]); // Handle node click const handleNodeClick = (nodeId: string) => { // Find the triplet that contains this node const triplet = triplets.find( (t) => t.sourceNode.uuid === nodeId || t.targetNode.uuid === nodeId, ); if (!triplet) return; // Determine which node was clicked (source or target) const node = triplet.sourceNode.uuid === nodeId ? triplet.sourceNode : triplet.targetNode; // Set popup content and show the popup setNodePopupContent({ id: nodeId, node: node, }); setShowNodePopup(true); setShowEdgePopup(false); }; // Handle edge click const handleEdgeClick = (edgeId: string) => { // Find the triplet that contains this edge const triplet = triplets.find((t) => t.edge.uuid === edgeId); if (!triplet) return; // Set popup content and show the popup setEdgePopupContent({ id: edgeId, source: triplet.sourceNode, target: triplet.targetNode, relation: triplet.edge, }); setShowEdgePopup(true); setShowNodePopup(false); }; // Handle popover close const handlePopoverClose = () => { setShowNodePopup(false); setShowEdgePopup(false); }; return (
{/* Entity Types Legend Button */}
{allLabels.map((label) => (
{label}
))}
{triplets.length > 0 ? ( ) : (

No graph data to visualize.

)}
); }, );