ASRRONNX / kurugraph
trysem's picture
Create kurugraph
e4389f2 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Kuru-Graph: Mahabharata Network</title>
<!-- Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- Vis-Network for Graph Visualization -->
<script type="text/javascript" src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
<!-- Lucide Icons -->
<script src="https://unpkg.com/lucide@latest"></script>
<style>
body { margin: 0; padding: 0; overflow: hidden; background-color: #111827; color: white; }
#network-container { width: 100vw; height: 100vh; }
/* Remove Vis.js default focus border */
.vis-network:focus { outline: none; }
/* Custom Scrollbar for sidebar */
::-webkit-scrollbar { width: 6px; }
::-webkit-scrollbar-track { background: #1f2937; }
::-webkit-scrollbar-thumb { background: #4b5563; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #6b7280; }
.glass-panel {
background: rgba(31, 41, 55, 0.85);
backdrop-filter: blur(12px);
-webkit-backdrop-filter: blur(12px);
border: 1px solid rgba(75, 85, 99, 0.4);
}
</style>
</head>
<body class="font-sans antialiased text-gray-100 selection:bg-indigo-500 selection:text-white">
<!-- Graph Container -->
<div id="network-container" class="absolute inset-0 z-0"></div>
<!-- Top UI Bar -->
<div class="absolute top-0 left-0 w-full p-4 md:p-6 z-10 pointer-events-none flex justify-between items-start">
<div class="pointer-events-auto">
<h1 class="text-3xl md:text-4xl font-bold bg-clip-text text-transparent bg-gradient-to-r from-amber-400 to-orange-500 flex items-center gap-3 shadow-sm">
<i data-lucide="network"></i> Kuru-Graph
</h1>
<p class="text-gray-400 text-sm mt-1 max-w-xs">An interactive character network of the Mahabharata. Scroll to zoom, drag to pan.</p>
</div>
<!-- Search Bar -->
<div class="pointer-events-auto relative mt-2 md:mt-0">
<div class="relative">
<i data-lucide="search" class="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-400 w-4 h-4"></i>
<input type="text" id="searchInput" placeholder="Search character..."
class="bg-gray-800/80 border border-gray-700 text-white rounded-full pl-10 pr-4 py-2 w-48 md:w-64 focus:outline-none focus:border-amber-500 focus:ring-1 focus:ring-amber-500 transition-all shadow-lg backdrop-blur-md">
</div>
<ul id="searchResults" class="absolute top-full left-0 w-full mt-2 bg-gray-800 border border-gray-700 rounded-lg shadow-xl max-h-60 overflow-y-auto hidden">
<!-- Results injected here -->
</ul>
</div>
</div>
<!-- Legend -->
<div class="absolute bottom-6 left-6 z-10 pointer-events-auto glass-panel p-4 rounded-xl shadow-2xl hidden md:block">
<h3 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-3">Factions</h3>
<div class="grid grid-cols-2 gap-x-6 gap-y-2 text-sm" id="legendContainer">
<!-- Legend injected here -->
</div>
<div class="mt-4 pt-3 border-t border-gray-700">
<h3 class="text-xs font-bold text-gray-400 uppercase tracking-wider mb-2">Relationships</h3>
<div class="flex items-center gap-2 text-xs text-gray-300"><span class="w-4 h-0.5 bg-gray-400"></span> Family / Neutral</div>
<div class="flex items-center gap-2 text-xs text-gray-300"><span class="w-4 h-0.5 bg-pink-500"></span> Spouse</div>
<div class="flex items-center gap-2 text-xs text-gray-300"><span class="w-4 h-0.5 border-t border-dashed border-red-500"></span> Rivalry</div>
<div class="flex items-center gap-2 text-xs text-gray-300"><span class="w-4 h-0.5 bg-purple-500"></span> Mentor / Student</div>
</div>
</div>
<!-- Sidebar (Character Details) -->
<div id="sidebar" class="fixed right-0 top-0 h-full w-full md:w-96 glass-panel transform translate-x-full transition-transform duration-300 ease-in-out z-20 shadow-2xl flex flex-col pointer-events-auto">
<!-- Header -->
<div class="p-6 pb-4 border-b border-gray-700 flex justify-between items-start relative">
<div>
<span id="charFaction" class="px-2 py-1 text-xs font-semibold rounded-md bg-gray-700 text-gray-300 mb-2 inline-block">Faction</span>
<h2 id="charName" class="text-3xl font-bold text-white mb-1">Character Name</h2>
<p id="charTitle" class="text-amber-400 text-sm italic">Title / Role</p>
</div>
<button id="closeSidebar" class="p-2 text-gray-400 hover:text-white hover:bg-gray-700 rounded-full transition-colors">
<i data-lucide="x" class="w-5 h-5"></i>
</button>
</div>
<!-- Body -->
<div class="p-6 flex-1 overflow-y-auto">
<h3 class="text-lg font-semibold border-b border-gray-700 pb-2 mb-3">About</h3>
<p id="charDesc" class="text-gray-300 text-sm leading-relaxed mb-6">
Character description goes here.
</p>
<h3 class="text-lg font-semibold border-b border-gray-700 pb-2 mb-3">Key Relationships</h3>
<ul id="charRelations" class="space-y-3">
<!-- Relations injected here -->
</ul>
</div>
<!-- Footer actions -->
<div class="p-4 border-t border-gray-700 bg-gray-800/50">
<button id="focusNodeBtn" class="w-full py-2 bg-gray-700 hover:bg-gray-600 text-white rounded-lg transition-colors flex items-center justify-center gap-2 font-medium">
<i data-lucide="target" class="w-4 h-4"></i> Center in Graph
</button>
</div>
</div>
<!-- Mobile Legend Toggle -->
<button id="mobileLegendBtn" class="md:hidden absolute bottom-6 left-6 z-10 glass-panel p-3 rounded-full text-white pointer-events-auto shadow-lg">
<i data-lucide="info" class="w-6 h-6"></i>
</button>
<script>
// Initialize Icons
lucide.createIcons();
// --- Data Definition ---
// Faction Colors & Styling
const groups = {
Pandava: { color: { background: '#2563eb', border: '#1d4ed8', hover: { background: '#3b82f6', border: '#2563eb' } } },
Kaurava: { color: { background: '#dc2626', border: '#b91c1c', hover: { background: '#ef4444', border: '#dc2626' } } },
Elder: { color: { background: '#d97706', border: '#b45309', hover: { background: '#f59e0b', border: '#d97706' } } },
Yadava: { color: { background: '#059669', border: '#047857', hover: { background: '#10b981', border: '#059669' } } },
Teacher: { color: { background: '#7c3aed', border: '#6d28d9', hover: { background: '#8b5cf6', border: '#7c3aed' } } },
Panchala: { color: { background: '#db2777', border: '#be185d', hover: { background: '#ec4899', border: '#db2777' } } },
Divine: { color: { background: '#0891b2', border: '#0e7490', hover: { background: '#06b6d4', border: '#0891b2' } } },
Neutral: { color: { background: '#4b5563', border: '#374151', hover: { background: '#6b7280', border: '#4b5563' } } }
};
// Edge Types Styling
const edgeStyles = {
Parent: { color: '#9ca3af', dashes: false },
Spouse: { color: '#ec4899', dashes: false },
Rival: { color: '#ef4444', dashes: [5, 5] },
Teacher: { color: '#a855f7', dashes: false },
Friend: { color: '#10b981', dashes: false },
Sibling: { color: '#60a5fa', dashes: false },
Default: { color: '#6b7280', dashes: false }
};
// Character Database
const rawNodes = [
{ id: 1, label: "Shantanu", group: "Elder", title: "King of Hastinapur", desc: "A great king of the Kuru dynasty, ancestor to both Pandavas and Kauravas." },
{ id: 2, label: "Ganga", group: "Divine", title: "River Goddess", desc: "First wife of Shantanu, mother of Bhishma." },
{ id: 3, label: "Bhishma", group: "Elder", title: "The Grandsire", desc: "Born Devavrata. Took a vow of lifelong celibacy and service to the throne of Hastinapur. A peerless warrior." },
{ id: 4, label: "Satyavati", group: "Elder", title: "Queen Mother", desc: "Second wife of Shantanu. Mother of Vyasa, Chitrangada, and Vichitravirya." },
{ id: 5, label: "Vyasa", group: "Divine", title: "The Sage/Author", desc: "Biological father of Dhritarashtra, Pandu, and Vidura via Niyoga. The traditional author of the epic." },
{ id: 6, label: "Dhritarashtra", group: "Kaurava", title: "Blind King", desc: "The blind king of Hastinapur, father of the Kauravas. Torn between dharma and his love for his son Duryodhana." },
{ id: 7, label: "Pandu", group: "Pandava", title: "Former King", desc: "Younger brother of Dhritarashtra. Cursed to die if he engaged in marital relations. Father of the Pandavas via divine boons." },
{ id: 8, label: "Vidura", group: "Elder", title: "Prime Minister", desc: "Incarnation of Yama (Dharma). Known for his unparalleled wisdom and adherence to righteousness." },
{ id: 9, label: "Gandhari", group: "Kaurava", title: "Queen of Hastinapur", desc: "Wife of Dhritarashtra. Blindfolded herself out of solidarity with her blind husband." },
{ id: 10, label: "Kunti", group: "Pandava", title: "Mother of Pandavas", desc: "First wife of Pandu. Possessed a mantra to invoke gods to grant her children." },
{ id: 11, label: "Madri", group: "Pandava", title: "Second Queen", desc: "Second wife of Pandu. Mother of the twins Nakula and Sahadeva. Committed Sati on Pandu's pyre." },
{ id: 12, label: "Yudhishthira", group: "Pandava", title: "Eldest Pandava", desc: "Son of Yama (Dharma). Known for his absolute, sometimes stubborn, adherence to truth and righteousness." },
{ id: 13, label: "Bhima", group: "Pandava", title: "The Mighty", desc: "Son of Vayu (Wind). Possessed the strength of ten thousand elephants. Vowed to kill all Kauravas." },
{ id: 14, label: "Arjuna", group: "Pandava", title: "The Great Archer", desc: "Son of Indra. The central hero of the epic, a peerless archer, and recipient of the Bhagavad Gita." },
{ id: 15, label: "Nakula", group: "Pandava", title: "The Handsome", desc: "Son of the Ashvins. Unmatched in beauty and a master of sword fighting and horsemanship." },
{ id: 16, label: "Sahadeva", group: "Pandava", title: "The Scholar", desc: "Son of the Ashvins. A master of astrology and swordsmanship. Swore to kill Shakuni." },
{ id: 17, label: "Duryodhana", group: "Kaurava", title: "Eldest Kaurava", desc: "The primary antagonist. Driven by envy and hatred toward his cousins, the Pandavas." },
{ id: 18, label: "Dushasana", group: "Kaurava", title: "Second Kaurava", desc: "Duryodhana's loyal brother. Infamous for dragging Draupadi by her hair." },
{ id: 19, label: "Karna", group: "Kaurava", title: "King of Anga", desc: "Eldest son of Kunti (abandoned). Raised by a charioteer. A tragic hero of immense generosity and martial skill, allied with Duryodhana." },
{ id: 20, label: "Draupadi", group: "Panchala", title: "Empress of Indraprastha", desc: "Born from fire. Common wife to the five Pandavas. Her humiliation fueled the great war." },
{ id: 21, label: "Krishna", group: "Yadava", title: "Avatar of Vishnu", desc: "Cousin and primary ally of the Pandavas. The supreme strategist and speaker of the Bhagavad Gita." },
{ id: 22, label: "Balarama", group: "Yadava", title: "Elder Brother of Krishna", desc: "Teacher of the mace to both Bhima and Duryodhana. Remained neutral during the war." },
{ id: 23, label: "Drona", group: "Teacher", title: "Royal Preceptor", desc: "Master of advanced military arts. Taught both Kauravas and Pandavas, but fought for Hastinapur due to duty." },
{ id: 24, label: "Ashwatthama", group: "Teacher", title: "Son of Drona", desc: "Born with a gem on his forehead granting immortality. Committed a horrific night-massacre after the war." },
{ id: 25, label: "Shakuni", group: "Kaurava", title: "Prince of Gandhara", desc: "Brother of Gandhari. The mastermind behind the game of dice and the poisoning of the Kauravas' minds." },
{ id: 26, label: "Drupada", group: "Panchala", title: "King of Panchala", desc: "Father of Draupadi, Shikhandi, and Dhrishtadyumna. Sworn enemy of Drona." },
{ id: 27, label: "Shikhandi", group: "Panchala", title: "The Reborn Princess", desc: "Born Amba, reborn as a male warrior to fulfill a vow to kill Bhishma." },
{ id: 28, label: "Dhrishtadyumna", group: "Panchala", title: "Commander of Pandavas", desc: "Born from the same fire as Draupadi, destined to kill Drona." },
{ id: 29, label: "Abhimanyu", group: "Pandava", title: "The Tragic Hero", desc: "Son of Arjuna and Subhadra. Unjustly killed in the Chakravyuha formation by multiple Kaurava warriors." },
{ id: 30, label: "Subhadra", group: "Yadava", title: "Sister of Krishna", desc: "Wife of Arjuna, mother of Abhimanyu." },
{ id: 31, label: "Sanjaya", group: "Neutral", title: "The Narrator", desc: "Advisor and charioteer to Dhritarashtra. Granted divine vision by Vyasa to narrate the war." },
{ id: 32, label: "Kripacharya", group: "Teacher", title: "Kulaguru", desc: "The royal teacher of the Kurus before Drona. One of the few survivors of the war." },
];
const rawEdges = [
// Lineage
{ from: 1, to: 3, label: "Parent", type: "Parent" },
{ from: 2, to: 3, label: "Parent", type: "Parent" },
{ from: 1, to: 4, label: "Spouse", type: "Spouse" },
{ from: 4, to: 5, label: "Parent", type: "Parent" },
{ from: 5, to: 6, label: "Parent", type: "Parent" },
{ from: 5, to: 7, label: "Parent", type: "Parent" },
{ from: 5, to: 8, label: "Parent", type: "Parent" },
// Siblings Generation 1
{ from: 6, to: 7, label: "Sibling", type: "Sibling" },
{ from: 7, to: 8, label: "Sibling", type: "Sibling" },
{ from: 9, to: 25, label: "Sibling", type: "Sibling" },
{ from: 21, to: 22, label: "Sibling", type: "Sibling" },
{ from: 21, to: 30, label: "Sibling", type: "Sibling" },
// Marriages
{ from: 6, to: 9, label: "Spouse", type: "Spouse" },
{ from: 7, to: 10, label: "Spouse", type: "Spouse" },
{ from: 7, to: 11, label: "Spouse", type: "Spouse" },
{ from: 14, to: 30, label: "Spouse", type: "Spouse" },
// Draupadi
{ from: 12, to: 20, label: "Spouse", type: "Spouse" },
{ from: 13, to: 20, label: "Spouse", type: "Spouse" },
{ from: 14, to: 20, label: "Spouse", type: "Spouse" },
{ from: 15, to: 20, label: "Spouse", type: "Spouse" },
{ from: 16, to: 20, label: "Spouse", type: "Spouse" },
// Offspring
{ from: 6, to: 17, label: "Parent", type: "Parent" },
{ from: 6, to: 18, label: "Parent", type: "Parent" },
{ from: 9, to: 17, label: "Parent", type: "Parent" },
{ from: 10, to: 12, label: "Parent", type: "Parent" },
{ from: 10, to: 13, label: "Parent", type: "Parent" },
{ from: 10, to: 14, label: "Parent", type: "Parent" },
{ from: 10, to: 19, label: "Parent", type: "Parent" }, // Kunti -> Karna
{ from: 11, to: 15, label: "Parent", type: "Parent" },
{ from: 11, to: 16, label: "Parent", type: "Parent" },
{ from: 14, to: 29, label: "Parent", type: "Parent" },
{ from: 30, to: 29, label: "Parent", type: "Parent" },
{ from: 23, to: 24, label: "Parent", type: "Parent" },
{ from: 26, to: 20, label: "Parent", type: "Parent" },
{ from: 26, to: 27, label: "Parent", type: "Parent" },
{ from: 26, to: 28, label: "Parent", type: "Parent" },
// Mentorships / Loyalty
{ from: 3, to: 6, label: "Guardian", type: "Teacher" },
{ from: 25, to: 17, label: "Mentor", type: "Teacher" },
{ from: 23, to: 14, label: "Teacher", type: "Teacher" },
{ from: 23, to: 17, label: "Teacher", type: "Teacher" },
{ from: 32, to: 14, label: "Teacher", type: "Teacher" },
{ from: 21, to: 14, label: "Guide/Friend", type: "Friend" },
{ from: 22, to: 13, label: "Teacher", type: "Teacher" },
{ from: 22, to: 17, label: "Teacher", type: "Teacher" },
{ from: 17, to: 19, label: "Friend/Ally", type: "Friend" },
{ from: 20, to: 21, label: "Friend", type: "Friend" },
// Rivalries
{ from: 14, to: 19, label: "Rival", type: "Rival" },
{ from: 13, to: 17, label: "Rival", type: "Rival" },
{ from: 13, to: 18, label: "Rival", type: "Rival" },
{ from: 16, to: 25, label: "Rival", type: "Rival" },
{ from: 23, to: 26, label: "Rival", type: "Rival" },
{ from: 28, to: 23, label: "Killer", type: "Rival" },
{ from: 27, to: 3, label: "Killer", type: "Rival" },
{ from: 17, to: 12, label: "Rival", type: "Rival" }
];
// Format Edges for Vis.js
const edges = new vis.DataSet(rawEdges.map(edge => {
const style = edgeStyles[edge.type] || edgeStyles.Default;
return {
...edge,
color: style.color,
dashes: style.dashes,
font: { align: 'middle', size: 10, color: '#9ca3af', strokeWidth: 0 },
arrows: 'to',
smooth: { type: 'continuous' }
};
}));
// Format Nodes for Vis.js
const nodes = new vis.DataSet(rawNodes.map(node => ({
...node,
shape: 'dot',
size: 20,
font: { size: 14, color: '#f3f4f6', face: 'ui-sans-serif, system-ui, sans-serif' },
borderWidth: 2,
borderWidthSelected: 4,
shadow: { enabled: true, color: 'rgba(0,0,0,0.5)', size: 10, x: 2, y: 2 }
})));
// --- Network Initialization ---
const container = document.getElementById('network-container');
const data = { nodes: nodes, edges: edges };
const options = {
groups: groups,
nodes: {
scaling: { min: 10, max: 30 }
},
edges: {
width: 1.5,
hoverWidth: 2,
selectionWidth: 3
},
physics: {
solver: 'barnesHut',
barnesHut: {
gravitationalConstant: -4000,
centralGravity: 0.3,
springLength: 150,
springConstant: 0.04,
damping: 0.09
},
stabilization: {
enabled: true,
iterations: 200,
updateInterval: 50
}
},
interaction: {
hover: true,
tooltipDelay: 200,
hideEdgesOnDrag: true
}
};
const network = new vis.Network(container, data, options);
// --- UI Interactions ---
const sidebar = document.getElementById('sidebar');
const closeSidebarBtn = document.getElementById('closeSidebar');
const searchInput = document.getElementById('searchInput');
const searchResults = document.getElementById('searchResults');
let selectedNodeId = null;
// Open Sidebar Function
function openSidebar(nodeId) {
const node = rawNodes.find(n => n.id === nodeId);
if (!node) return;
selectedNodeId = nodeId;
document.getElementById('charName').textContent = node.label;
document.getElementById('charTitle').textContent = node.title;
document.getElementById('charDesc').textContent = node.desc;
const factionBadge = document.getElementById('charFaction');
factionBadge.textContent = node.group;
factionBadge.style.backgroundColor = groups[node.group].color.background;
factionBadge.style.color = '#fff';
// Find Relations
const relationsList = document.getElementById('charRelations');
relationsList.innerHTML = '';
const connectedEdges = rawEdges.filter(e => e.from === nodeId || e.to === nodeId);
if (connectedEdges.length === 0) {
relationsList.innerHTML = '<li class="text-sm text-gray-500">No known relationships in database.</li>';
} else {
connectedEdges.forEach(edge => {
const isSource = edge.from === nodeId;
const otherNodeId = isSource ? edge.to : edge.from;
const otherNode = rawNodes.find(n => n.id === otherNodeId);
let relationText = edge.label;
if (edge.type === 'Parent') {
relationText = isSource ? 'Parent of' : 'Child of';
}
const li = document.createElement('li');
li.className = "flex items-center justify-between p-3 bg-gray-800/50 rounded-lg border border-gray-700/50 hover:bg-gray-700/50 cursor-pointer transition-colors";
li.innerHTML = `
<div class="flex items-center gap-3">
<div class="w-3 h-3 rounded-full" style="background-color: ${groups[otherNode.group].color.background}"></div>
<span class="font-medium text-gray-200">${otherNode.label}</span>
</div>
<span class="text-xs text-gray-400 font-medium px-2 py-1 bg-gray-900/50 rounded">${relationText}</span>
`;
// Clicking relation opens that character
li.onclick = () => {
network.selectNodes([otherNodeId]);
openSidebar(otherNodeId);
focusOnNode(otherNodeId);
};
relationsList.appendChild(li);
});
}
// Show Sidebar
sidebar.classList.remove('translate-x-full');
}
// Close Sidebar
function closeSidebar() {
sidebar.classList.add('translate-x-full');
network.unselectAll();
selectedNodeId = null;
}
closeSidebarBtn.addEventListener('click', closeSidebar);
// Network Events
network.on('selectNode', function (params) {
openSidebar(params.nodes[0]);
});
network.on('deselectNode', function (params) {
closeSidebar();
});
// Focus Button
function focusOnNode(nodeId) {
network.focus(nodeId, {
scale: 1.2,
animation: { duration: 1000, easingFunction: 'easeInOutQuad' }
});
}
document.getElementById('focusNodeBtn').addEventListener('click', () => {
if (selectedNodeId) focusOnNode(selectedNodeId);
});
// Search Functionality
searchInput.addEventListener('input', (e) => {
const term = e.target.value.toLowerCase();
searchResults.innerHTML = '';
if (term.length < 1) {
searchResults.classList.add('hidden');
return;
}
const matches = rawNodes.filter(n =>
n.label.toLowerCase().includes(term) ||
n.title.toLowerCase().includes(term)
);
if (matches.length > 0) {
searchResults.classList.remove('hidden');
matches.forEach(match => {
const li = document.createElement('li');
li.className = "px-4 py-3 hover:bg-gray-700 cursor-pointer border-b border-gray-700/50 last:border-0 flex justify-between items-center";
li.innerHTML = `
<div>
<div class="font-medium text-white">${match.label}</div>
<div class="text-xs text-gray-400">${match.title}</div>
</div>
<div class="w-2 h-2 rounded-full" style="background-color: ${groups[match.group].color.background}"></div>
`;
li.onclick = () => {
network.selectNodes([match.id]);
openSidebar(match.id);
focusOnNode(match.id);
searchResults.classList.add('hidden');
searchInput.value = '';
};
searchResults.appendChild(li);
});
} else {
searchResults.classList.remove('hidden');
searchResults.innerHTML = '<li class="px-4 py-3 text-sm text-gray-500 text-center">No characters found</li>';
}
});
// Close search results on outside click
document.addEventListener('click', (e) => {
if (!searchInput.contains(e.target) && !searchResults.contains(e.target)) {
searchResults.classList.add('hidden');
}
});
// Populate Legend
const legendContainer = document.getElementById('legendContainer');
Object.keys(groups).forEach(group => {
const color = groups[group].color.background;
legendContainer.innerHTML += `
<div class="flex items-center gap-2 cursor-pointer hover:opacity-80 transition-opacity" onclick="filterByGroup('${group}')">
<div class="w-3 h-3 rounded-full shadow-sm" style="background-color: ${color}"></div>
<span class="text-gray-300 hover:text-white">${group}</span>
</div>
`;
});
// Simple Filter highlight
let currentFilter = null;
window.filterByGroup = function(group) {
if (currentFilter === group) {
// Reset
nodes.forEach(n => nodes.update({id: n.id, hidden: false}));
edges.forEach(e => edges.update({id: e.id, hidden: false}));
currentFilter = null;
} else {
// Filter
nodes.forEach(n => {
nodes.update({id: n.id, hidden: n.group !== group});
});
// Hide edges if either node is hidden
edges.forEach(e => {
const fromNode = nodes.get(e.from);
const toNode = nodes.get(e.to);
edges.update({id: e.id, hidden: fromNode.group !== group || toNode.group !== group});
});
currentFilter = group;
network.fit({animation: {duration: 1000}});
}
};
// Zoom out slightly on initial load
network.once("stabilizationIterationsDone", function() {
network.fit({
animation: {
duration: 1500,
easingFunction: "easeOutQuint"
}
});
});
</script>
</body>
</html>