/** * React Hook for Graph Data Management * Processes parsed RDF data for D3.js visualization */ import { useState, useCallback, useMemo } from 'react'; import { extractNodeTypes, extractPredicates, getLinkCounts, getNodeCounts, } from '@/lib/rdf/graph-utils'; import type { GraphData, GraphNode, GraphLink } from '@/types/rdf'; export interface GraphFilters { nodeTypes: Set; predicates: Set; searchTerm: string; } interface UseGraphDataReturn { // Data nodes: GraphNode[]; links: GraphLink[]; filteredNodes: GraphNode[]; filteredLinks: GraphLink[]; // Metadata nodeTypes: string[]; predicates: string[]; linkCounts: Record; nodeCounts: Record; // Filters filters: GraphFilters; setFilters: (filters: Partial) => void; resetFilters: () => void; // Selection selectedNode: GraphNode | null; setSelectedNode: (node: GraphNode | null) => void; // Data loading loadGraphData: (data: GraphData) => void; clearGraphData: () => void; // Stats stats: { totalNodes: number; totalLinks: number; filteredNodeCount: number; filteredLinkCount: number; }; } const DEFAULT_FILTERS: GraphFilters = { nodeTypes: new Set(), predicates: new Set(), searchTerm: '', }; export function useGraphData(): UseGraphDataReturn { const [graphData, setGraphData] = useState(null); const [filters, setFiltersState] = useState(DEFAULT_FILTERS); const [selectedNode, setSelectedNode] = useState(null); // Extract metadata from graph data const nodeTypes = useMemo( () => (graphData ? extractNodeTypes(graphData) : []), [graphData] ); const predicates = useMemo( () => (graphData ? extractPredicates(graphData) : []), [graphData] ); const linkCounts = useMemo( () => (graphData ? getLinkCounts(graphData) : {}), [graphData] ); const nodeCounts = useMemo( () => (graphData ? getNodeCounts(graphData) : {}), [graphData] ); // Get raw nodes and links const nodes = graphData?.nodes || []; const links = graphData?.links || []; // Apply filters to nodes const filteredNodes = useMemo(() => { if (!graphData) return []; let result = nodes; // Filter by node type if (filters.nodeTypes.size > 0) { result = result.filter((node) => filters.nodeTypes.has(node.type)); } // Filter by search term if (filters.searchTerm) { const term = filters.searchTerm.toLowerCase(); result = result.filter( (node) => node.id.toLowerCase().includes(term) || node.label?.toLowerCase().includes(term) ); } return result; }, [graphData, nodes, filters]); // Apply filters to links const filteredLinks = useMemo(() => { if (!graphData) return []; const nodeIds = new Set(filteredNodes.map((n) => n.id)); let result = links.filter((link) => { const sourceId = typeof link.source === 'string' ? link.source : link.source.id; const targetId = typeof link.target === 'string' ? link.target : link.target.id; return nodeIds.has(sourceId) && nodeIds.has(targetId); }); // Filter by predicate if (filters.predicates.size > 0) { result = result.filter((link) => filters.predicates.has(link.predicate)); } return result; }, [graphData, links, filteredNodes, filters.predicates]); // Load graph data const loadGraphData = useCallback((data: GraphData) => { setGraphData(data); setSelectedNode(null); // Reset filters when loading new data setFiltersState(DEFAULT_FILTERS); }, []); // Clear graph data const clearGraphData = useCallback(() => { setGraphData(null); setSelectedNode(null); setFiltersState(DEFAULT_FILTERS); }, []); // Update filters const setFilters = useCallback((newFilters: Partial) => { setFiltersState((prev) => ({ ...prev, ...newFilters, })); }, []); // Reset filters const resetFilters = useCallback(() => { setFiltersState(DEFAULT_FILTERS); }, []); // Stats const stats = useMemo( () => ({ totalNodes: nodes.length, totalLinks: links.length, filteredNodeCount: filteredNodes.length, filteredLinkCount: filteredLinks.length, }), [nodes.length, links.length, filteredNodes.length, filteredLinks.length] ); return { nodes, links, filteredNodes, filteredLinks, nodeTypes, predicates, linkCounts, nodeCounts, filters, setFilters, resetFilters, selectedNode, setSelectedNode, loadGraphData, clearGraphData, stats, }; }