import { UniversalGraphData, UniversalNode, UniversalLink, GraphVisualizationConfig, GraphStats, GraphExportOptions, } from "@/types/graph-visualization"; // Color schemes for different node types export const NODE_COLOR_SCHEMES = { default: [ "#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#06b6d4", "#84cc16", "#f97316", "#ec4899", "#6b7280", ], knowledge_graph: [ "#ff6b6b", "#4ecdc4", "#45b7d1", "#96ceb4", "#feca57", "#ff9ff3", ], temporal: ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6"], }; // Link color schemes export const LINK_COLOR_SCHEMES = { default: ["#6b7280", "#3b82f6", "#10b981", "#ef4444"], relationship_types: [ "#999999", "#ff9800", "#4caf50", "#2196f3", "#9c27b0", "#e91e63", ], }; // Get color for a node based on its type export function getNodeColor( node: UniversalNode, colorScheme: string[] = NODE_COLOR_SCHEMES.default ): string { if (node.color) return node.color; const typeIndex = node.group || 0; const color = colorScheme[typeIndex % colorScheme.length]; return color || colorScheme[0] || "#3b82f6"; } // Get color for a link based on its type export function getLinkColor( link: UniversalLink, colorScheme: string[] = LINK_COLOR_SCHEMES.default ): string { if (link.color) return link.color; // Map common relationship types to colors const typeColorMap: Record = { depends_on: "#ff9800", creates: "#4caf50", uses: "#2196f3", inputs_to: "#9c27b0", outputs_from: "#e91e63", communicates_with: "#00bcd4", contains: "#8bc34a", relates_to: "#ff5722", requires_tool: "#ffd600", assigned_to: "#ff5252", consumes: "#a259ff", intervenes: "#00b894", performs: "#00cec9", produces: "#fdcb6e", reviews: "#e17055", }; if (link.type) { const linkType = link.type.toLowerCase(); if (typeColorMap[linkType]) { return typeColorMap[linkType]; } } return colorScheme[0] || "#6b7280"; // Default color with fallback } // Get node size based on its properties export function getNodeSize( node: UniversalNode, config: GraphVisualizationConfig ): number { if (node.size) return node.size; const baseRadius = config.nodeRadius || 10; const minRadius = config.nodeMinRadius || 5; const maxRadius = config.nodeMaxRadius || 30; // Size based on degree (number of connections) if available if (node.properties?.degree) { const normalizedDegree = Math.min(node.properties.degree / 10, 1); return minRadius + (maxRadius - minRadius) * normalizedDegree; } return baseRadius; } // Get link width based on its properties export function getLinkWidth( link: UniversalLink, config: GraphVisualizationConfig ): number { const baseWidth = config.linkWidth || 2; if (link.weight && link.weight > 0) { return Math.max(1, baseWidth * Math.sqrt(link.weight)); } if (link.value && link.value > 0) { return Math.max(1, baseWidth * Math.sqrt(link.value)); } return baseWidth; } // Format tooltip text for nodes export function formatNodeTooltip(node: UniversalNode): string { const parts = []; if (node.name || node.label) { parts.push(`Name: ${node.name || node.label}`); } if (node.type) { parts.push(`Type: ${node.type}`); } if (node.raw_prompt) { parts.push( `Prompt: ${node.raw_prompt.substring(0, 100)}${ node.raw_prompt.length > 100 ? "..." : "" }` ); } if (node.properties?.degree) { parts.push(`Connections: ${node.properties.degree}`); } return parts.join("\n") || `Node: ${node.id}`; } // Format tooltip text for links export function formatLinkTooltip(link: UniversalLink): string { const parts = []; if (link.type || link.label) { parts.push(`Type: ${link.type || link.label}`); } if (link.relationship) { parts.push(`Relationship: ${link.relationship}`); } if (link.interaction_prompt) { parts.push( `Interaction: ${link.interaction_prompt.substring(0, 100)}${ link.interaction_prompt.length > 100 ? "..." : "" }` ); } if (link.weight) { parts.push(`Weight: ${link.weight}`); } const sourceId = typeof link.source === "string" ? link.source : link.source.id; const targetId = typeof link.target === "string" ? link.target : link.target.id; parts.push(`${sourceId} → ${targetId}`); return parts.join("\n"); } // Validate graph data structure export function validateGraphData(data: any): UniversalGraphData { if (!data || typeof data !== "object") { throw new Error("Invalid graph data: must be an object"); } if (!Array.isArray(data.nodes)) { throw new Error("Invalid graph data: nodes must be an array"); } if (!Array.isArray(data.links)) { throw new Error("Invalid graph data: links must be an array"); } // Validate nodes data.nodes.forEach((node: any, index: number) => { if (!node.id) { throw new Error(`Invalid node at index ${index}: missing id`); } }); // Validate links data.links.forEach((link: any, index: number) => { if (!link.id) { throw new Error(`Invalid link at index ${index}: missing id`); } if (!link.source) { throw new Error(`Invalid link at index ${index}: missing source`); } if (!link.target) { throw new Error(`Invalid link at index ${index}: missing target`); } }); return data as UniversalGraphData; } // Calculate graph statistics export function calculateGraphStatistics(data: UniversalGraphData): GraphStats { const nodeTypes: Record = {}; const linkTypes: Record = {}; const nodeDegrees: Record = {}; // Initialize node degrees data.nodes.forEach((node) => { nodeDegrees[node.id] = 0; const type = node.type || "Unknown"; nodeTypes[type] = (nodeTypes[type] || 0) + 1; }); // Count link types and calculate degrees data.links.forEach((link) => { const type = link.type || "Unknown"; linkTypes[type] = (linkTypes[type] || 0) + 1; const sourceId = typeof link.source === "string" ? link.source : link.source.id; const targetId = typeof link.target === "string" ? link.target : link.target.id; if (nodeDegrees[sourceId] !== undefined) { nodeDegrees[sourceId]++; } if (nodeDegrees[targetId] !== undefined) { nodeDegrees[targetId]++; } }); const degrees = Object.values(nodeDegrees); const averageDegree = degrees.length > 0 ? degrees.reduce((a, b) => a + b, 0) / degrees.length : 0; const maxDegree = degrees.length > 0 ? Math.max(...degrees) : 0; return { nodeCount: data.nodes.length, linkCount: data.links.length, nodeTypes, linkTypes, averageDegree: Math.round(averageDegree * 100) / 100, maxDegree, connectedComponents: 1, // Simplified }; } // Export graph data to different formats export function exportGraphData( data: UniversalGraphData, options: GraphExportOptions ): string | Blob { switch (options.format) { case "json": return JSON.stringify(data, null, 2); case "csv": return exportToCSV(data); case "svg": throw new Error("SVG export requires the actual SVG element"); case "png": throw new Error("PNG export requires canvas conversion"); default: throw new Error(`Unsupported export format: ${options.format}`); } } // Export to CSV format function exportToCSV(data: UniversalGraphData): string { const nodesCsv = [ "id,name,type,raw_prompt", ...data.nodes.map( (node) => `"${node.id}","${node.name || ""}","${node.type || ""}","${ node.raw_prompt || "" }"` ), ].join("\n"); const linksCsv = [ "id,source,target,type,relationship", ...data.links.map((link) => { const sourceId = typeof link.source === "string" ? link.source : link.source.id; const targetId = typeof link.target === "string" ? link.target : link.target.id; return `"${link.id}","${sourceId}","${targetId}","${link.type || ""}","${ link.relationship || "" }"`; }), ].join("\n"); return `NODES:\n${nodesCsv}\n\nLINKS:\n${linksCsv}`; } // Search and filter utilities export function searchNodes( nodes: UniversalNode[], searchTerm: string ): UniversalNode[] { if (!searchTerm.trim()) return nodes; const term = searchTerm.toLowerCase(); return nodes.filter( (node) => node.id.toLowerCase().includes(term) || node.name?.toLowerCase().includes(term) || node.label?.toLowerCase().includes(term) || node.type?.toLowerCase().includes(term) || node.raw_prompt?.toLowerCase().includes(term) ); } export function filterNodesByType( nodes: UniversalNode[], types: string[] ): UniversalNode[] { if (types.length === 0) return nodes; return nodes.filter((node) => types.includes(node.type || "Unknown")); } export function filterLinksByType( links: UniversalLink[], types: string[] ): UniversalLink[] { if (types.length === 0) return links; return links.filter((link) => types.includes(link.type || "Unknown")); }