refactor: Update KnowledgeGraph and BaseLayout #4

Merged
KeyArgo merged 1 commits from fresh-main into main 2025-04-23 19:17:33 +00:00
2 changed files with 27 additions and 37 deletions

View File

@ -2,9 +2,6 @@
// src/components/KnowledgeGraph.astro
// Interactive visualization of content connections using Cytoscape.js
// Assuming Cytoscape is loaded via CDN in BaseLayout or globally
// If not, you might need: import cytoscape from 'cytoscape';
export interface GraphNode {
id: string;
label: string;
@ -34,7 +31,7 @@ const { graphData, height = "60vh" } = Astro.props;
// Generate colors based on categories for nodes
const uniqueCategories = [...new Set(graphData.nodes.map(node => node.category || 'Uncategorized'))];
const categoryColors = {};
const predefinedColors = { /* Colors from previous step */
const predefinedColors = {
'Kubernetes': '#326CE5', 'Docker': '#2496ED', 'DevOps': '#FF6F61',
'Homelab': '#06B6D4', 'Networking': '#9333EA', 'Infrastructure': '#10B981',
'Automation': '#F59E0B', 'Security': '#EF4444', 'Monitoring': '#6366F1',
@ -67,8 +64,11 @@ graphData.nodes.forEach(node => {
});
---
<!-- Include Cytoscape via CDN with is:inline to ensure it loads before the script runs -->
<script src="https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js" is:inline></script>
<div class="graph-wrapper" style={`--graph-height: ${height};`}>
{/* Loading Animation */}
<!-- Loading Animation -->
<div id="graph-loading" class="graph-loading">
<div class="loading-spinner">
<div class="spinner-ring"></div>
@ -78,10 +78,10 @@ graphData.nodes.forEach(node => {
<div class="loading-text">Initializing Knowledge Graph...</div>
</div>
{/* Cytoscape Container */}
<!-- Cytoscape Container -->
<div id="knowledge-graph" class="graph-container"></div>
{/* Node Details Panel */}
<!-- Node Details Panel -->
<div id="node-details" class="node-details">
<div class="node-details-header">
<h3 id="node-title" class="node-title">Node Title</h3>
@ -96,19 +96,19 @@ graphData.nodes.forEach(node => {
<div id="node-tags" class="node-tags">
<span class="tags-label">Tags:</span>
<div class="tags-container">
{/* Tags populated by JS */}
<!-- Tags populated by JS -->
</div>
</div>
<div id="node-connections" class="node-connections">
<span class="connections-label">Connections:</span>
<ul class="connections-list">
{/* Connections populated by JS */}
<!-- Connections populated by JS -->
</ul>
</div>
<a href="#" id="node-link" class="node-link" target="_blank" rel="noopener noreferrer">Read Article</a>
</div>
{/* Graph Controls */}
<!-- Graph Controls -->
<div class="graph-controls">
<div class="graph-filters">
<button class="graph-filter active" data-filter="all" style="--filter-color: var(--accent-primary);">All Topics</button>
@ -129,7 +129,7 @@ graphData.nodes.forEach(node => {
</div>
</div>
{/* Graph Legend */}
<!-- Graph Legend -->
<details class="graph-legend">
<summary class="legend-title">Legend</summary>
<div class="legend-items">
@ -141,18 +141,14 @@ graphData.nodes.forEach(node => {
))}
</div>
</details>
</div>
{/* Include Cytoscape via CDN - Ensure this is loaded, perhaps in BaseLayout */}
{/* <script src="https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js"></script> */}
<script define:vars={{ graphData, categoryColors, nodeSizes }}>
// Initialize the graph when the DOM is ready
function initializeGraph() {
// Check if Cytoscape is loaded
if (typeof cytoscape === 'undefined') {
console.error("Cytoscape library not loaded. Make sure it's included (e.g., via CDN in BaseLayout).");
console.error("Cytoscape library not loaded. Make sure it's included via the script tag.");
const loadingEl = document.getElementById('graph-loading');
if(loadingEl) loadingEl.innerHTML = "<p>Error: Cytoscape library not loaded.</p>";
return;
@ -202,7 +198,7 @@ graphData.nodes.forEach(node => {
const cy = cytoscape({
container: graphContainer,
elements: elements,
style: [ /* Styles from your snippet */
style: [
{ selector: 'node', style: { 'background-color': 'data(color)', 'label': 'data(label)', 'width': 'data(size)', 'height': 'data(size)', 'font-size': '10px', 'color': '#E2E8F0', 'text-valign': 'bottom', 'text-halign': 'center', 'text-margin-y': '7px', 'text-background-opacity': 0.7, 'text-background-color': '#0F1219', 'text-background-padding': '3px', 'text-background-shape': 'roundrectangle', 'text-max-width': '120px', 'text-wrap': 'ellipsis', 'text-overflow-wrap': 'anywhere', 'border-width': '2px', 'border-color': '#0F1219', 'border-opacity': 0.8, 'z-index': 10, 'text-outline-width': 1, 'text-outline-color': '#000', 'text-outline-opacity': 0.5 } },
{ selector: 'edge', style: { 'width': 'mapData(weight, 1, 10, 1, 4)', 'line-color': 'rgba(226, 232, 240, 0.2)', 'curve-style': 'bezier', 'opacity': 0.6, 'z-index': 1 } },
{ selector: '.highlighted', style: { 'background-color': 'data(color)', 'border-color': '#FFFFFF', 'border-width': '3px', 'color': '#FFFFFF', 'text-background-opacity': 0.9, 'opacity': 1, 'z-index': 20 } },
@ -227,7 +223,7 @@ graphData.nodes.forEach(node => {
clearTimeout(hoverTimeout);
node.addClass('highlighted');
node.connectedEdges().addClass('highlighted');
graphContainer.style.cursor = 'pointer'; // Use graphContainer
graphContainer.style.cursor = 'pointer';
});
cy.on('mouseout', 'node', function(e) {
@ -238,7 +234,7 @@ graphData.nodes.forEach(node => {
node.connectedEdges().removeClass('highlighted');
}
}, 100);
graphContainer.style.cursor = 'default'; // Use graphContainer
graphContainer.style.cursor = 'default';
});
cy.on('tap', 'node', function(e) {
@ -282,7 +278,7 @@ graphData.nodes.forEach(node => {
evt.preventDefault();
cy.$(':selected').unselect();
const targetNode = cy.getElementById(connectedData.id);
if (targetNode) { // Check if node exists
if (targetNode) {
targetNode.select();
cy.animate({ center: { eles: targetNode }, zoom: cy.zoom() }, { duration: 300 });
targetNode.trigger('tap');
@ -345,7 +341,7 @@ graphData.nodes.forEach(node => {
item.addEventListener('click', () => {
const category = item.dataset.category;
const filterButton = document.querySelector(`.graph-filter[data-filter="${category}"]`);
filterButton?.click();
if (filterButton) filterButton.click();
});
});
@ -355,10 +351,11 @@ graphData.nodes.forEach(node => {
document.getElementById('reset-graph')?.addEventListener('click', () => {
cy.fit(null, 30);
cy.elements().removeClass('faded highlighted filtered');
document.querySelector('.graph-filter[data-filter="all"]')?.click();
const allFilterButton = document.querySelector('.graph-filter[data-filter="all"]');
if (allFilterButton) allFilterButton.click();
});
// Add mouse wheel zoom controls (already present in original script)
// Add mouse wheel zoom controls
cy.on('zoom', function() {
if (cy.zoom() > 1.5) {
cy.style().selector('node').style({ 'text-max-width': '150px', 'font-size': '12px' }).update();
@ -436,4 +433,3 @@ graphData.nodes.forEach(node => {
.graph-legend[open] { transform: translateX(0); }
.graph-controls { bottom: 10px; }
}
</style>

View File

@ -26,7 +26,7 @@ const {
<!-- OpenGraph/Social Media Meta Tags -->
<meta property="og:title" content={title} />
<meta property="og:description" content={description} />
<meta property="og:image" content={Astro.site ? new URL(image, Astro.site).href : image} /> {/* Use absolute URL */}
<meta property="og:image" content={Astro.site ? new URL(image, Astro.site).href : image} /> <!-- Use absolute URL -->
<meta property="og:url" content={Astro.url} />
<meta property="og:type" content="website" />
@ -34,7 +34,7 @@ const {
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content={title}>
<meta name="twitter:description" content={description}>
<meta name="twitter:image" content={Astro.site ? new URL(image, Astro.site).href : image}> {/* Use absolute URL */}
<meta name="twitter:image" content={Astro.site ? new URL(image, Astro.site).href : image}> <!-- Use absolute URL -->
<!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com">
@ -44,6 +44,9 @@ const {
<!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<!-- Cytoscape Library for Knowledge Graph -->
<script src="https://unpkg.com/cytoscape@3.25.0/dist/cytoscape.min.js" is:inline></script>
<!-- Schema.org markup for Google -->
<script type="application/ld+json">
{
@ -295,10 +298,6 @@ const {
border-top: 1px solid var(--border-primary);
margin-top: 4rem; /* Add space above footer */
}
/* Add other global styles from your external file if needed */
/* Or remove conflicting styles from the external file */
</style>
</head>
<body>
@ -312,11 +311,10 @@ const {
<div class="floating-shape shape-3"></div>
</div>
{/* Use slots for Header and Footer */}
<slot name="header" />
<main>
<slot /> {/* Default slot for page content */}
<slot /> <!-- Default slot for page content -->
</main>
<slot name="footer" />
@ -345,10 +343,6 @@ const {
} else {
console.warn("Element with class 'neural-nodes' not found.");
}
// Terminal typing effect (if needed globally, otherwise keep in component)
// const typingElements = document.querySelectorAll('.terminal-typing');
// typingElements.forEach(typingElement => { ... });
});
</script>
</body>