Spaces:
Running
Running
| 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<string, string> = { | |
| 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<string, number> = {}; | |
| const linkTypes: Record<string, number> = {}; | |
| const nodeDegrees: Record<string, number> = {}; | |
| // 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")); | |
| } | |