From 78129ca25cbc86573de863f4b2fe64445bacbb60 Mon Sep 17 00:00:00 2001 From: Daniel LaForce Date: Sat, 26 Apr 2025 20:30:19 -0600 Subject: [PATCH] feat(MiniGraph): Add 2-level depth calculation and square aspect ratio --- src/components/MiniGraph.astro | 1107 +++++++------------------------- 1 file changed, 224 insertions(+), 883 deletions(-) diff --git a/src/components/MiniGraph.astro b/src/components/MiniGraph.astro index 8a2daed..65318f5 100644 --- a/src/components/MiniGraph.astro +++ b/src/components/MiniGraph.astro @@ -8,8 +8,8 @@ interface Props { title: string; // Current post title tags?: string[]; // Current post tags category?: string; // Current post category - allPosts?: any[]; // All posts for related content in fullscreen mode - content?: string; // Current post content for fullscreen view + relatedPosts?: any[]; // Related posts data + allPosts?: any[]; // All posts for second level relationships } // Extract props with defaults @@ -18,152 +18,142 @@ const { title, tags = [], category = "Uncategorized", - allPosts = [], - content = "" // Default to empty string if content is not passed + relatedPosts = [], + allPosts = [] } = Astro.props; // Generate unique ID for the graph container const graphId = `graph-${Math.random().toString(36).substring(2, 8)}`; -// Prepare graph data for current post and its tags +// Get all unique tags from related posts (Level 1 tags) +const relatedPostsTags = relatedPosts + .flatMap(post => post.data.tags || []) + .filter(tag => !tags.includes(tag)); // Exclude current post tags to avoid duplicates + +// Get Level 2 posts: posts related to Level 1 tags (excluding current post and Level 1 posts) +const level2PostIds = new Set(); +const level2Posts = []; + +// For each Level 1 tag, find related posts +tags.forEach(tag => { + allPosts.forEach(post => { + // Skip if post is current post or already in related posts + if (post.slug === slug || relatedPosts.some(rp => rp.slug === post.slug)) { + return; + } + + // If post has the tag and isn't already added + if (post.data.tags?.includes(tag) && !level2PostIds.has(post.slug)) { + level2PostIds.add(post.slug); + level2Posts.push(post); + } + }); +}); + +// Get Level 2 tags from Level 2 posts +const level2Tags = new Set(); +level2Posts.forEach(post => { + (post.data.tags || []).forEach(tag => { + // Only add if not already in Level 0 or Level 1 tags + if (!tags.includes(tag) && !relatedPostsTags.includes(tag)) { + level2Tags.add(tag); + } + }); +}); + +// Prepare nodes data with different levels const nodes = [ - // Current post node + // Level 0: Current post node { id: slug, label: title, type: "post", - category: category, - tags: tags, - content: content, // Use the content prop - url: `/posts/${slug}` + level: 0 }, - // Tag nodes + // Level 1: Tag nodes ...tags.map(tag => ({ id: `tag-${tag}`, label: tag, type: "tag", - url: `/tag/${tag}` + level: 1 + })), + // Level 1: Related post nodes + ...relatedPosts.map(post => ({ + id: post.slug, + label: post.data.title, + type: "post", + level: 1 + })), + // Level 2: Related tags nodes + ...relatedPostsTags.map(tag => ({ + id: `tag-${tag}`, + label: tag, + type: "tag", + level: 2 + })), + // Level 2: Posts related to tags + ...level2Posts.map(post => ({ + id: post.slug, + label: post.data.title, + type: "post", + level: 2 + })), + // Level 2: Tags from Level 2 posts + ...[...level2Tags].map(tag => ({ + id: `tag-${tag}`, + label: tag.toString(), + type: "tag", + level: 2 })) ]; -// Create edges connecting post to tags -const edges = tags.map(tag => ({ - source: slug, - target: `tag-${tag}`, - type: "post-tag" -})); +// Create edges connecting nodes +const edges = [ + // Level 0 to Level 1: Current post to its tags + ...tags.map(tag => ({ + source: slug, + target: `tag-${tag}`, + type: "post-tag" + })), + // Level 0 to Level 1: Current post to related posts + ...relatedPosts.map(post => ({ + source: slug, + target: post.slug, + type: "post-related" + })), + // Level 1 to Level 2: Related posts to their tags + ...relatedPosts.flatMap(post => + (post.data.tags || []).map(tag => ({ + source: post.slug, + target: `tag-${tag}`, + type: "post-tag" + })) + ), + // Level 1 to Level 2: Tags to related posts + ...level2Posts.flatMap(post => + tags.filter(tag => post.data.tags?.includes(tag)).map(tag => ({ + source: `tag-${tag}`, + target: post.slug, + type: "tag-post" + })) + ) +]; // Prepare graph data object const graphData = { nodes, edges }; - -// Generate predefined colors for categories (copied from KnowledgeGraph.astro) -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' -}; - -// Generate colors based on node types -const nodeTypeColors = { - 'post': '#3B82F6', // Blue for posts - 'tag': '#10B981' // Green for tags -}; --- - +

Post Connections

- - - - - -
-
-

Post Title

- -
- - - -
-
- - - - - - - -

Select a post node to view its content

-
-
- - -
- + -