--- // src/components/KnowledgeGraph.astro // Enhanced interactive visualization of content connections using Cytoscape.js export interface GraphNode { id: string; label: string; type: 'post' | 'tag' | 'category'; // Node types to distinguish posts from tags category?: string; tags?: string[]; url?: string; // URL for linking } export interface GraphEdge { source: string; target: string; type: 'post-tag' | 'post-category' | 'post-post'; // Edge types strength?: number; } export interface GraphData { nodes: GraphNode[]; edges: GraphEdge[]; } interface Props { graphData: GraphData; height?: string; // e.g., '500px' initialFilter?: string; // Optional initial filter } const { graphData, height = "50vh", initialFilter = "all" } = Astro.props; // Generate colors based on node types const nodeTypeColors = { 'post': '#3B82F6', // Blue for posts 'tag': '#10B981', // Green for tags 'category': '#8B5CF6' // Purple for categories }; // Generate predefined colors for categories const predefinedColors = { 'Kubernetes': '#326CE5', 'Docker': '#2496ED', 'DevOps': '#FF6F61', 'Homelab': '#06B6D4', 'Networking': '#9333EA', 'Infrastructure': '#10B981', 'Automation': '#F59E0B', 'Security': '#EF4444', 'Monitoring': '#6366F1', 'Storage': '#8B5CF6', 'Obsidian': '#7C3AED', 'Tutorial': '#3B82F6', 'Uncategorized': '#A0AEC0' }; // Calculate node sizes const nodeSizes = {}; const minSize = 15; const maxSize = 35; const degreeMap = new Map(); graphData.nodes.forEach(node => degreeMap.set(node.id, 0)); graphData.edges.forEach(edge => { degreeMap.set(edge.source, (degreeMap.get(edge.source) || 0) + 1); degreeMap.set(edge.target, (degreeMap.get(edge.target) || 0) + 1); }); const maxDegree = Math.max(...Array.from(degreeMap.values()), 1); graphData.nodes.forEach(node => { const degree = degreeMap.get(node.id) || 0; // Make tags slightly smaller than posts by default const baseSize = node.type === 'post' ? minSize : minSize * 0.8; const normalizedSize = maxDegree === 0 ? 0.5 : degree / maxDegree; nodeSizes[node.id] = baseSize + normalizedSize * (maxSize - minSize); }); // Count node types for legend const nodeTypeCounts = { post: graphData.nodes.filter(node => node.type === 'post').length, tag: graphData.nodes.filter(node => node.type === 'tag').length, category: graphData.nodes.filter(node => node.type === 'category').length }; ---
This Knowledge Graph visualizes connections between blog content:
Interactions:
Select a post in the graph to view its content here