refactor: Update KnowledgeGraph and BaseLayout
This commit is contained in:
parent
d10f87e899
commit
b392c30aba
|
@ -2,9 +2,6 @@
|
||||||
// src/components/KnowledgeGraph.astro
|
// src/components/KnowledgeGraph.astro
|
||||||
// Interactive visualization of content connections using Cytoscape.js
|
// 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 {
|
export interface GraphNode {
|
||||||
id: string;
|
id: string;
|
||||||
label: string;
|
label: string;
|
||||||
|
@ -34,7 +31,7 @@ const { graphData, height = "60vh" } = Astro.props;
|
||||||
// Generate colors based on categories for nodes
|
// Generate colors based on categories for nodes
|
||||||
const uniqueCategories = [...new Set(graphData.nodes.map(node => node.category || 'Uncategorized'))];
|
const uniqueCategories = [...new Set(graphData.nodes.map(node => node.category || 'Uncategorized'))];
|
||||||
const categoryColors = {};
|
const categoryColors = {};
|
||||||
const predefinedColors = { /* Colors from previous step */
|
const predefinedColors = {
|
||||||
'Kubernetes': '#326CE5', 'Docker': '#2496ED', 'DevOps': '#FF6F61',
|
'Kubernetes': '#326CE5', 'Docker': '#2496ED', 'DevOps': '#FF6F61',
|
||||||
'Homelab': '#06B6D4', 'Networking': '#9333EA', 'Infrastructure': '#10B981',
|
'Homelab': '#06B6D4', 'Networking': '#9333EA', 'Infrastructure': '#10B981',
|
||||||
'Automation': '#F59E0B', 'Security': '#EF4444', 'Monitoring': '#6366F1',
|
'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};`}>
|
<div class="graph-wrapper" style={`--graph-height: ${height};`}>
|
||||||
{/* Loading Animation */}
|
<!-- Loading Animation -->
|
||||||
<div id="graph-loading" class="graph-loading">
|
<div id="graph-loading" class="graph-loading">
|
||||||
<div class="loading-spinner">
|
<div class="loading-spinner">
|
||||||
<div class="spinner-ring"></div>
|
<div class="spinner-ring"></div>
|
||||||
|
@ -78,10 +78,10 @@ graphData.nodes.forEach(node => {
|
||||||
<div class="loading-text">Initializing Knowledge Graph...</div>
|
<div class="loading-text">Initializing Knowledge Graph...</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Cytoscape Container */}
|
<!-- Cytoscape Container -->
|
||||||
<div id="knowledge-graph" class="graph-container"></div>
|
<div id="knowledge-graph" class="graph-container"></div>
|
||||||
|
|
||||||
{/* Node Details Panel */}
|
<!-- Node Details Panel -->
|
||||||
<div id="node-details" class="node-details">
|
<div id="node-details" class="node-details">
|
||||||
<div class="node-details-header">
|
<div class="node-details-header">
|
||||||
<h3 id="node-title" class="node-title">Node Title</h3>
|
<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">
|
<div id="node-tags" class="node-tags">
|
||||||
<span class="tags-label">Tags:</span>
|
<span class="tags-label">Tags:</span>
|
||||||
<div class="tags-container">
|
<div class="tags-container">
|
||||||
{/* Tags populated by JS */}
|
<!-- Tags populated by JS -->
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="node-connections" class="node-connections">
|
<div id="node-connections" class="node-connections">
|
||||||
<span class="connections-label">Connections:</span>
|
<span class="connections-label">Connections:</span>
|
||||||
<ul class="connections-list">
|
<ul class="connections-list">
|
||||||
{/* Connections populated by JS */}
|
<!-- Connections populated by JS -->
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
<a href="#" id="node-link" class="node-link" target="_blank" rel="noopener noreferrer">Read Article</a>
|
<a href="#" id="node-link" class="node-link" target="_blank" rel="noopener noreferrer">Read Article</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Graph Controls */}
|
<!-- Graph Controls -->
|
||||||
<div class="graph-controls">
|
<div class="graph-controls">
|
||||||
<div class="graph-filters">
|
<div class="graph-filters">
|
||||||
<button class="graph-filter active" data-filter="all" style="--filter-color: var(--accent-primary);">All Topics</button>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Graph Legend */}
|
<!-- Graph Legend -->
|
||||||
<details class="graph-legend">
|
<details class="graph-legend">
|
||||||
<summary class="legend-title">Legend</summary>
|
<summary class="legend-title">Legend</summary>
|
||||||
<div class="legend-items">
|
<div class="legend-items">
|
||||||
|
@ -141,18 +141,14 @@ graphData.nodes.forEach(node => {
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
</div>
|
</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 }}>
|
<script define:vars={{ graphData, categoryColors, nodeSizes }}>
|
||||||
// Initialize the graph when the DOM is ready
|
// Initialize the graph when the DOM is ready
|
||||||
function initializeGraph() {
|
function initializeGraph() {
|
||||||
// Check if Cytoscape is loaded
|
// Check if Cytoscape is loaded
|
||||||
if (typeof cytoscape === 'undefined') {
|
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');
|
const loadingEl = document.getElementById('graph-loading');
|
||||||
if(loadingEl) loadingEl.innerHTML = "<p>Error: Cytoscape library not loaded.</p>";
|
if(loadingEl) loadingEl.innerHTML = "<p>Error: Cytoscape library not loaded.</p>";
|
||||||
return;
|
return;
|
||||||
|
@ -202,7 +198,7 @@ graphData.nodes.forEach(node => {
|
||||||
const cy = cytoscape({
|
const cy = cytoscape({
|
||||||
container: graphContainer,
|
container: graphContainer,
|
||||||
elements: elements,
|
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: '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: '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 } },
|
{ 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);
|
clearTimeout(hoverTimeout);
|
||||||
node.addClass('highlighted');
|
node.addClass('highlighted');
|
||||||
node.connectedEdges().addClass('highlighted');
|
node.connectedEdges().addClass('highlighted');
|
||||||
graphContainer.style.cursor = 'pointer'; // Use graphContainer
|
graphContainer.style.cursor = 'pointer';
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.on('mouseout', 'node', function(e) {
|
cy.on('mouseout', 'node', function(e) {
|
||||||
|
@ -238,7 +234,7 @@ graphData.nodes.forEach(node => {
|
||||||
node.connectedEdges().removeClass('highlighted');
|
node.connectedEdges().removeClass('highlighted');
|
||||||
}
|
}
|
||||||
}, 100);
|
}, 100);
|
||||||
graphContainer.style.cursor = 'default'; // Use graphContainer
|
graphContainer.style.cursor = 'default';
|
||||||
});
|
});
|
||||||
|
|
||||||
cy.on('tap', 'node', function(e) {
|
cy.on('tap', 'node', function(e) {
|
||||||
|
@ -282,7 +278,7 @@ graphData.nodes.forEach(node => {
|
||||||
evt.preventDefault();
|
evt.preventDefault();
|
||||||
cy.$(':selected').unselect();
|
cy.$(':selected').unselect();
|
||||||
const targetNode = cy.getElementById(connectedData.id);
|
const targetNode = cy.getElementById(connectedData.id);
|
||||||
if (targetNode) { // Check if node exists
|
if (targetNode) {
|
||||||
targetNode.select();
|
targetNode.select();
|
||||||
cy.animate({ center: { eles: targetNode }, zoom: cy.zoom() }, { duration: 300 });
|
cy.animate({ center: { eles: targetNode }, zoom: cy.zoom() }, { duration: 300 });
|
||||||
targetNode.trigger('tap');
|
targetNode.trigger('tap');
|
||||||
|
@ -345,7 +341,7 @@ graphData.nodes.forEach(node => {
|
||||||
item.addEventListener('click', () => {
|
item.addEventListener('click', () => {
|
||||||
const category = item.dataset.category;
|
const category = item.dataset.category;
|
||||||
const filterButton = document.querySelector(`.graph-filter[data-filter="${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', () => {
|
document.getElementById('reset-graph')?.addEventListener('click', () => {
|
||||||
cy.fit(null, 30);
|
cy.fit(null, 30);
|
||||||
cy.elements().removeClass('faded highlighted filtered');
|
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() {
|
cy.on('zoom', function() {
|
||||||
if (cy.zoom() > 1.5) {
|
if (cy.zoom() > 1.5) {
|
||||||
cy.style().selector('node').style({ 'text-max-width': '150px', 'font-size': '12px' }).update();
|
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-legend[open] { transform: translateX(0); }
|
||||||
.graph-controls { bottom: 10px; }
|
.graph-controls { bottom: 10px; }
|
||||||
}
|
}
|
||||||
</style>
|
|
|
@ -26,7 +26,7 @@ const {
|
||||||
<!-- OpenGraph/Social Media Meta Tags -->
|
<!-- OpenGraph/Social Media Meta Tags -->
|
||||||
<meta property="og:title" content={title} />
|
<meta property="og:title" content={title} />
|
||||||
<meta property="og:description" content={description} />
|
<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:url" content={Astro.url} />
|
||||||
<meta property="og:type" content="website" />
|
<meta property="og:type" content="website" />
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ const {
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
<meta name="twitter:title" content={title}>
|
<meta name="twitter:title" content={title}>
|
||||||
<meta name="twitter:description" content={description}>
|
<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 -->
|
<!-- Fonts -->
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
@ -44,6 +44,9 @@ const {
|
||||||
<!-- Favicon -->
|
<!-- Favicon -->
|
||||||
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
<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 -->
|
<!-- Schema.org markup for Google -->
|
||||||
<script type="application/ld+json">
|
<script type="application/ld+json">
|
||||||
{
|
{
|
||||||
|
@ -295,10 +298,6 @@ const {
|
||||||
border-top: 1px solid var(--border-primary);
|
border-top: 1px solid var(--border-primary);
|
||||||
margin-top: 4rem; /* Add space above footer */
|
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>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
@ -312,11 +311,10 @@ const {
|
||||||
<div class="floating-shape shape-3"></div>
|
<div class="floating-shape shape-3"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Use slots for Header and Footer */}
|
|
||||||
<slot name="header" />
|
<slot name="header" />
|
||||||
|
|
||||||
<main>
|
<main>
|
||||||
<slot /> {/* Default slot for page content */}
|
<slot /> <!-- Default slot for page content -->
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<slot name="footer" />
|
<slot name="footer" />
|
||||||
|
@ -345,10 +343,6 @@ const {
|
||||||
} else {
|
} else {
|
||||||
console.warn("Element with class 'neural-nodes' not found.");
|
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>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|
Loading…
Reference in New Issue