graph / index.html
trippykat's picture
undefined - Initial Deployment
42abb87 verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Legal Case Graph Visualization</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<script src="https://unpkg.com/vis-network/standalone/umd/vis-network.min.js"></script>
<style>
.pulse {
animation: pulse 2s infinite;
}
@keyframes pulse {
0% { opacity: 1; }
50% { opacity: 0.4; }
100% { opacity: 1; }
}
.edgehighlight {
stroke: #3b82f6;
stroke-width: 4px;
}
.node-tooltip {
position: absolute;
visibility: hidden;
background: white;
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
padding: 0.75rem;
box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
max-width: 300px;
z-index: 100;
pointer-events: none;
}
#graph-container {
height: calc(100vh - 180px);
border: 1px solid #e5e7eb;
border-radius: 0.5rem;
background: #f9fafb;
}
.evidence-badge {
transition: all 0.3s ease;
}
.evidence-badge:hover {
transform: scale(1.1);
}
.document-preview {
max-height: 300px;
overflow-y: auto;
}
/* Custom scrollbar */
.document-preview::-webkit-scrollbar {
width: 6px;
}
.document-preview::-webkit-scrollbar-track {
background: #f1f1f1;
}
.document-preview::-webkit-scrollbar-thumb {
background: #c7d2fe;
border-radius: 10px;
}
.document-preview::-webkit-scrollbar-thumb:hover {
background: #a5b4fc;
}
</style>
</head>
<body class="bg-gray-50 font-sans">
<div class="container mx-auto px-4 py-6">
<!-- Header -->
<header class="flex justify-between items-center mb-6">
<div>
<h1 class="text-2xl font-bold text-gray-800">Legal Case Graph Analysis</h1>
<p class="text-gray-600">Visualizing complex legal relationships for defense strategy</p>
</div>
<div class="flex space-x-3">
<button id="presentation-mode" class="px-4 py-2 bg-blue-50 text-blue-600 rounded-lg border border-blue-200 hover:bg-blue-100 transition flex items-center">
<i class="fas fa-tv mr-2"></i> Presentation Mode
</button>
<button class="px-4 py-2 bg-white text-gray-700 rounded-lg border border-gray-200 hover:bg-gray-50 transition flex items-center">
<i class="fas fa-file-export mr-2"></i> Export
</button>
</div>
</header>
<!-- Main Content -->
<div class="grid grid-cols-1 lg:grid-cols-12 gap-6">
<!-- Left Sidebar - Filters -->
<div class="lg:col-span-3 bg-white rounded-lg border border-gray-200 p-4">
<h2 class="text-lg font-semibold mb-4 text-gray-800">Case Filters</h2>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-1">Quick Search</label>
<div class="relative">
<input type="text" id="search-box" class="w-full pl-10 pr-4 py-2 border border-gray-300 rounded-lg focus:ring-blue-500 focus:border-blue-500" placeholder="Search people/documents...">
<div class="absolute inset-y-0 left-0 pl-3 flex items-center pointer-events-none">
<i class="fas fa-search text-gray-400"></i>
</div>
</div>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Entity Types</label>
<div class="space-y-2">
<label class="flex items-center space-x-2">
<input type="checkbox" class="rounded text-blue-600" checked data-type="officer">
<span class="text-gray-700">Officers</span>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" class="rounded text-blue-600" checked data-type="witness">
<span class="text-gray-700">Witnesses</span>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" class="rounded text-blue-600" checked data-type="defendant">
<span class="text-gray-700">Defendants</span>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" class="rounded text-blue-600" checked data-type="document">
<span class="text-gray-700">Documents</span>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" class="rounded text-blue-600" checked data-type="location">
<span class="text-gray-700">Locations</span>
</label>
</div>
</div>
<div class="mb-6">
<label class="block text-sm font-medium text-gray-700 mb-2">Legal Issues</label>
<div class="space-y-2">
<label class="flex items-center space-x-2">
<input type="checkbox" class="rounded text-blue-600" checked data-issue="violation">
<span class="text-gray-700">Constitutional Violations</span>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" class="rounded text-blue-600" data-issue="bias">
<span class="text-gray-700">Bias Indicators</span>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" class="rounded text-blue-600" data-issue="credibility">
<span class="text-gray-700">Credibility Issues</span>
</label>
<label class="flex items-center space-x-2">
<input type="checkbox" class="rounded text-blue-600" data-issue="procedure">
<span class="text-gray-700">Procedural Errors</span>
</label>
</div>
</div>
<div>
<label class="block text-sm font-medium text-gray-700 mb-2">Time Range</label>
<div class="grid grid-cols-2 gap-2">
<input type="time" class="border border-gray-300 rounded px-2 py-1" value="06:00">
<input type="time" class="border border-gray-300 rounded px-2 py-1" value="07:00">
</div>
<div class="mt-2">
<input type="range" min="0" max="100" value="100" class="w-full h-2 bg-blue-100 rounded-lg appearance-none cursor-pointer">
</div>
</div>
</div>
<!-- Main Graph Area -->
<div class="lg:col-span-9">
<!-- Toolbar -->
<div class="bg-white rounded-lg border border-gray-200 p-3 mb-4 flex flex-wrap items-center justify-between">
<div class="flex space-x-2 mb-2 sm:mb-0">
<button id="relationship-view" class="px-3 py-1 bg-blue-600 text-white rounded-md text-sm font-medium hover:bg-blue-700 transition active:bg-blue-800">
<i class="fas fa-project-diagram mr-1"></i> Relationships
</button>
<button id="timeline-view" class="px-3 py-1 bg-white text-gray-700 rounded-md text-sm font-medium border border-gray-300 hover:bg-gray-50 transition">
<i class="fas fa-clock mr-1"></i> Timeline
</button>
<button id="hierarchy-view" class="px-3 py-1 bg-white text-gray-700 rounded-md text-sm font-medium border border-gray-300 hover:bg-gray-50 transition">
<i class="fas fa-sitemap mr-1"></i> Hierarchy
</button>
</div>
<div class="flex space-x-2">
<button id="zoom-fit" class="p-1.5 bg-gray-100 text-gray-600 rounded hover:bg-gray-200 transition">
<i class="fas fa-expand"></i>
</button>
<div class="relative">
<button id="view-options" class="flex items-center px-3 py-1 bg-white text-gray-700 rounded-md text-sm font-medium border border-gray-300 hover:bg-gray-50 transition">
<i class="fas fa-sliders-h mr-1"></i> View Options
</button>
<div id="view-dropdown" class="hidden absolute right-0 mt-1 w-48 bg-white rounded-md shadow-lg z-10">
<div class="py-1">
<label class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<input type="checkbox" class="mr-2 rounded text-blue-600" checked> Show Connection Strength
</label>
<label class="flex items-center px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">
<input type="checkbox" class="mr-2 rounded text-blue-600" checked> Show Timeline Indicators
</label>
<div class="border-t border-gray-200"></div>
<a href="#" class="block px-4 py-2 text-sm text-gray-700 hover:bg-gray-100">Reset View</a>
</div>
</div>
</div>
</div>
</div>
<!-- Graph Visualization -->
<div id="graph-container"></div>
<!-- Bottom Panel -->
<div class="mt-4 bg-white rounded-lg border border-gray-200">
<div class="flex border-b border-gray-200">
<button id="info-tab" class="px-4 py-2 border-b-2 border-blue-500 font-medium text-blue-600">Entity Details</button>
<button id="connections-tab" class="px-4 py-2 font-medium text-gray-600 hover:text-blue-600">Connections</button>
<button id="timeline-tab" class="px-4 py-2 font-medium text-gray-600 hover:text-blue-600">Timeline</button>
<button id="evidence-tab" class="px-4 py-2 font-medium text-gray-600 hover:text-blue-600">Evidence Chains</button>
</div>
<div id="info-content" class="p-4">
<div class="flex flex-col md:flex-row">
<div class="md:w-1/3 mb-4 md:mb-0 md:pr-4">
<div class="h-full border border-gray-200 rounded-lg p-4 bg-gray-50">
<h3 class="text-lg font-semibold mb-3 text-gray-800">Selected Entity</h3>
<div class="flex items-center mb-3">
<div class="w-12 h-12 rounded-full bg-blue-100 flex items-center justify-center mr-3">
<i class="fas fa-user-shield text-blue-600 text-xl"></i>
</div>
<div>
<h4 class="font-medium">Officer James Smith</h4>
<p class="text-sm text-gray-600">Badge #4572, 5th Precinct</p>
</div>
</div>
<div class="space-y-3">
<div>
<p class="text-sm text-gray-500">Role in Case</p>
<p>Primary arresting officer, search execution</p>
</div>
<div>
<p class="text-sm text-gray-500">Documents</p>
<div class="flex flex-wrap gap-2 mt-1">
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs">Search Warrant #245</span>
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs">Arrest Report</span>
<span class="bg-blue-100 text-blue-800 px-2 py-1 rounded-full text-xs">Body Cam Transcript</span>
</div>
</div>
</div>
</div>
</div>
<div class="md:w-2/3 md:pl-4">
<div class="h-full border border-gray-200 rounded-lg p-4">
<h3 class="text-lg font-semibold mb-3 text-gray-800">Legal Analysis</h3>
<div class="space-y-4">
<div>
<div class="flex justify-between items-center mb-1">
<span class="font-medium">Constitutional Violations</span>
<span class="text-xs bg-red-100 text-red-800 px-2 py-0.5 rounded-full">High Priority</span>
</div>
<div class="bg-gray-100 rounded p-3 text-sm">
<p class="mb-1"><i class="fas fa-exclamation-triangle text-red-500 mr-1"></i> Search may have exceeded warrant scope based on comparison with warrant document</p>
<p><i class="fas fa-exclamation-triangle text-red-500 mr-1"></i> Inconsistent statements about probable cause between arrest report and testimony</p>
</div>
</div>
<div>
<div class="flex justify-between items-center mb-1">
<span class="font-medium">Credibility Indicators</span>
<span class="text-xs bg-yellow-100 text-yellow-800 px-2 py-0.5 rounded-full">Medium Priority</span>
</div>
<div class="bg-gray-100 rounded p-3 text-sm">
<p class="mb-1"><i class="fas fa-search text-blue-500 mr-1"></i> Discrepancy in time logs between officer's report and body cam timestamps</p>
<p><i class="fas fa-search text-blue-500 mr-1"></i> Multiple officers provide conflicting accounts about who initiated the search</p>
</div>
</div>
<div class="flex space-x-2">
<button class="px-3 py-1 bg-blue-600 text-white rounded-md text-sm hover:bg-blue-700 transition">
Add to Motion to Suppress
</button>
<button class="px-3 py-1 bg-white text-gray-700 rounded-md text-sm border border-gray-300 hover:bg-gray-50 transition">
Flag for Cross-Examination
</button>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="connections-content" class="p-4 hidden">
<!-- Connections content would go here -->
</div>
<div id="timeline-content" class="p-4 hidden">
<!-- Timeline content would go here -->
</div>
<div id="evidence-content" class="p-4 hidden">
<!-- Evidence content would go here -->
</div>
</div>
</div>
</div>
</div>
<div id="node-tooltip" class="node-tooltip"></div>
<script>
document.addEventListener('DOMContentLoaded', function() {
// Initialize the network visualization
const container = document.getElementById('graph-container');
// Sample data for the visualization
const nodes = new vis.DataSet([
// Officers
{ id: 1, label: "Officer\nSmith",
title: "Badge #4572\n5th Precinct\nPrimary arresting officer",
group: "officer", color: { background: "#3b82f6", border: "#1d4ed8" },
shape: "box", margin: 10, font: { size: 14 },
image: "https://cdn-icons-png.flaticon.com/512/1865/1865250.png",
shapeProperties: { borderRadius: 6 } },
{ id: 2, label: "Officer\nJohnson",
title: "Badge #3214\n5th Precinct\nAssisting officer",
group: "officer", color: { background: "#3b82f6", border: "#1d4ed8" },
shape: "box", margin: 10, font: { size: 14 },
shapeProperties: { borderRadius: 6 } },
// Defendants
{ id: 3, label: "Marcus\nGreen",
title: "Defendant\nPrimary subject of investigation",
group: "defendant", color: { background: "#ef4444", border: "#b91c1c" },
shape: "ellipse", margin: 10, font: { size: 14 } },
// Witnesses
{ id: 4, label: "Sarah\nWilliams",
title: "Neighbor witness\nSaw initial encounter",
group: "witness", color: { background: "#10b981", border: "#047857" },
shape: "circle", margin: 10, font: { size: 14 } },
// Documents
{ id: 5, label: "Search\nWarrant #245",
title: "Issued 04/15/2023\nJudge Martinez\nValid 04/16 from 12AM-8AM",
group: "document", color: { background: "#f59e0b", border: "#d97706" },
shape: "box", margin: 10, font: { size: 14 },
image: "https://cdn-icons-png.flaticon.com/512/2991/2991112.png",
shapeProperties: { borderRadius: 6 } },
{ id: 6, label: "Arrest\nReport",
title: "Filed 04/16/2023\nOfficer Smith",
group: "document", color: { background: "#f59e0b", border: "#d97706" },
shape: "box", margin: 10, font: { size: 14 },
shapeProperties: { borderRadius: 6 } },
// Locations
{ id: 7, label: "123 Main St\nApt 4B",
title: "Defendant's residence\nSearch executed here at 6:12AM",
group: "location", color: { background: "#8b5cf6", border: "#7c3aed" },
shape: "box", margin: 10, font: { size: 14 },
image: "https://cdn-icons-png.flaticon.com/512/484/484541.png",
shapeProperties: { borderRadius: 6 } }
]);
const edges = new vis.DataSet([
{ from: 1, to: 3, label: "arrested", width: 3, color: { color: "#3b82f6", highlight: "#2563eb" } },
{ from: 1, to: 5, label: "executed", width: 2, color: { color: "#3b82f6", highlight: "#2563eb" } },
{ from: 5, to: 7, label: "authorized", width: 4, color: { color: "#f59e0b", highlight: "#d97706" } },
{ from: 1, to: 7, label: "searched 6:12AM", width: 3, color: { color: "#3b82f6", highlight: "#2563eb" }, dashes: true },
{ from: 3, to: 7, label: "resides", width: 2, color: { color: "#ef4444", highlight: "#b91c1c" } },
{ from: 4, to: 1, label: "interacted", width: 1, color: { color: "#10b981", highlight: "#047857" } },
{ from: 4, to: 3, label: "saw", width: 1, color: { color: "#10b981", highlight: "#047857" } },
{ from: 1, to: 6, label: "filed", width: 2, color: { color: "#3b82f6", highlight: "#2563eb" } }
]);
const data = {
nodes: nodes,
edges: edges
};
const options = {
nodes: {
borderWidth: 2,
shadow: {
enabled: true,
color: 'rgba(0,0,0,0.2)',
size: 10,
x: 5,
y: 5
}
},
edges: {
arrows: {
to: { enabled: true, scaleFactor: 0.5 },
middle: { enabled: false },
from: { enabled: false }
},
color: {
inherit: 'to'
},
font: {
size: 12,
align: 'middle'
},
smooth: {
type: 'cubicBezier',
roundness: 0.4
},
selectionWidth: 3
},
physics: {
enabled: true,
solver: 'forceAtlas2Based',
forceAtlas2Based: {
gravitationalConstant: -50,
centralGravity: 0.01,
springLength: 200,
springConstant: 0.08,
damping: 0.4
},
stabilization: {
iterations: 250
}
},
groups: {
officer: {
color: {
border: '#1d4ed8',
background: '#3b82f6',
highlight: {
border: '#1e40af',
background: '#2563eb'
},
hover: {
border: '#1e40af',
background: '#2563eb'
}
},
font: { color: 'white' },
shape: 'box',
size: 20
},
defendant: {
color: {
border: '#b91c1c',
background: '#ef4444',
highlight: {
border: '#991b1b',
background: '#dc2626'
},
hover: {
border: '#991b1b',
background: '#dc2626'
}
},
font: { color: 'white' },
shape: 'ellipse',
size: 25
},
witness: {
color: {
border: '#047857',
background: '#10b981',
highlight: {
border: '#065f46',
background: '#059669'
},
hover: {
border: '#065f46',
background: '#059669'
}
},
font: { color: 'white' },
shape: 'circle',
size: 20
},
document: {
color: {
border: '#d97706',
background: '#f59e0b',
highlight: {
border: '#b45309',
background: '#d97706'
},
hover: {
border: '#b45309',
background: '#d97706'
}
},
font: { color: 'white' },
shape: 'box',
size: 20
},
location: {
color: {
border: '#7c3aed',
background: '#8b5cf6',
highlight: {
border: '#6d28d9',
background: '#7c3aed'
},
hover: {
border: '#6d28d9',
background: '#7c3aed'
}
},
font: { color: 'white' },
shape: 'box',
size: 20
}
},
interaction: {
hover: true,
tooltipDelay: 100,
hideEdgesOnDrag: false,
multiselect: true,
selectable: true,
selectConnectedEdges: true
},
layout: {
randomSeed: 42
}
};
const network = new vis.Network(container, data, options);
// Handle node clicks to update the details panel
network.on("selectNode", function(params) {
// In a real app, you would fetch more detailed data based on the node ID
console.log("Selected node:", params.nodes[0]);
});
// Handle edge clicks
network.on("selectEdge", function(params) {
console.log("Selected edge:", params.edges[0]);
});
// Handle double click for zooming
network.on("doubleClick", function(params) {
if (params.nodes.length > 0) {
network.focus(params.nodes[0], { scale: 1.5 });
}
});
// Custom node tooltip
const tooltip = document.getElementById('node-tooltip');
network.on("hoverNode", function(params) {
const nodeId = params.node;
const node = nodes.get(nodeId);
tooltip.innerHTML = `
<div class="font-medium text-gray-800">${node.label.replace('\n', ' ')}</div>
<div class="text-sm text-gray-600">${node.title.replace('\n', '<br>')}</div>
`;
const position = network.getPositions([nodeId])[nodeId];
const canvasPosition = network.canvasToDOM(position);
tooltip.style.left = (canvasPosition.x + 20) + 'px';
tooltip.style.top = (canvasPosition.y - 20) + 'px';
tooltip.style.visibility = 'visible';
});
network.on("blurNode", function() {
tooltip.style.visibility = 'hidden';
});
// Tab switching functionality
document.getElementById('info-tab').addEventListener('click', function() {
document.getElementById('info-content').classList.remove('hidden');
document.getElementById('connections-content').classList.add('hidden');
document.getElementById('timeline-content').classList.add('hidden');
document.getElementById('evidence-content').classList.add('hidden');
this.classList.add('border-blue-500', 'text-blue-600');
document.getElementById('connections-tab').classList.remove('border-blue-500', 'text-blue-600');
document.getElementById('timeline-tab').classList.remove('border-blue-500', 'text-blue-600');
document.getElementById('evidence-tab').classList.remove('border-blue-500', 'text-blue-600');
});
document.getElementById('connections-tab').addEventListener('click', function() {
document.getElementById('info-content').classList.add('hidden');
document.getElementById('connections-content').classList.remove('hidden');
document.getElementById('timeline-content').classList.add('hidden');
document.getElementById('evidence-content').classList.add('hidden');
this.classList.add('border-blue-500', 'text-blue-600');
document.getElementById('info-tab').classList.remove('border-blue-500', 'text-blue-600');
document.getElementById('timeline-tab').classList.remove('border-blue-500', 'text-blue-600');
document.getElementById('evidence-tab').classList.remove('border-blue-500', 'text-blue-600');
});
document.getElementById('timeline-tab').addEventListener('click', function() {
document.getElementById('info-content').classList.add('hidden');
document.getElementById('connections-content').classList.add('hidden');
document.getElementById('timeline-content').classList.remove('hidden');
document.getElementById('evidence-content').classList.add('hidden');
this.classList.add('border-blue-500', 'text-blue-600');
document.getElementById('info-tab').classList.remove('border-blue-500', 'text-blue-600');
document.getElementById('connections-tab').classList.remove('border-blue-500', 'text-blue-600');
document.getElementById('evidence-tab').classList.remove('border-blue-500', 'text-blue-600');
});
document.getElementById('evidence-tab').addEventListener('click', function() {
document.getElementById('info-content').classList.add('hidden');
document.getElementById('connections-content').classList.add('hidden');
document.getElementById('timeline-content').classList.add('hidden');
document.getElementById('evidence-content').classList.remove('hidden');
this.classList.add('border-blue-500', 'text-blue-600');
document.getElementById('info-tab').classList.remove('border-blue-500', 'text-blue-600');
document.getElementById('connections-tab').classList.remove('border-blue-500', 'text-blue-600');
document.getElementById('timeline-tab').classList.remove('border-blue-500', 'text-blue-600');
});
// Layout switching
document.getElementById('relationship-view').addEventListener('click', function() {
network.setOptions({
physics: {
solver: 'forceAtlas2Based',
forceAtlas2Based: {
gravitationalConstant: -50,
centralGravity: 0.01,
springLength: 200,
springConstant: 0.08,
damping: 0.4
}
}
});
this.classList.remove('bg-white', 'text-gray-700', 'border-gray-300');
this.classList.add('bg-blue-600', 'text-white');
document.getElementById('timeline-view').classList.remove('bg-blue-600', 'text-white');
document.getElementById('timeline-view').classList.add('bg-white', 'text-gray-700', 'border-gray-300');
document.getElementById('hierarchy-view').classList.remove('bg-blue-600', 'text-white');
document.getElementById('hierarchy-view').classList.add('bg-white', 'text-gray-700', 'border-gray-300');
});
document.getElementById('timeline-view').addEventListener('click', function() {
// In a real app, this would adjust node positions along a timeline axis
network.setOptions({
physics: {
solver: 'forceAtlas2Based',
forceAtlas2Based: {
gravitationalConstant: -50,
centralGravity: 0.01,
springLength: 200,
springConstant: 0.08,
damping: 0.4
}
}
});
this.classList.remove('bg-white', 'text-gray-700', 'border-gray-300');
this.classList.add('bg-blue-600', 'text-white');
document.getElementById('relationship-view').classList.remove('bg-blue-600', 'text-white');
document.getElementById('relationship-view').classList.add('bg-white', 'text-gray-700', 'border-gray-300');
document.getElementById('hierarchy-view').classList.remove('bg-blue-600', 'text-white');
document.getElementById('hierarchy-view').classList.add('bg-white', 'text-gray-700', 'border-gray-300');
});
document.getElementById('hierarchy-view').addEventListener('click', function() {
// In a real app, this would arrange nodes hierarchically
network.setOptions({
physics: {
solver: 'hierarchicalRepulsion',
hierarchicalRepulsion: {
nodeDistance: 120
}
}
});
this.classList.remove('bg-white', 'text-gray-700', 'border-gray-300');
this.classList.add('bg-blue-600', 'text-white');
document.getElementById('relationship-view').classList.remove('bg-blue-600', 'text-white');
document.getElementById('relationship-view').classList.add('bg-white', 'text-gray-700', 'border-gray-300');
document.getElementById('timeline-view').classList.remove('bg-blue-600', 'text-white');
document.getElementById('timeline-view').classList.add('bg-white', 'text-gray-700', 'border-gray-300');
});
// Zoom to fit
document.getElementById('zoom-fit').addEventListener('click', function() {
network.fit({ animation: { duration: 1000, easingFunction: 'easeInOutQuad' } });
});
// View options dropdown
document.getElementById('view-options').addEventListener('click', function() {
document.getElementById('view-dropdown').classList.toggle('hidden');
});
// Close dropdown when clicking outside
document.addEventListener('click', function(event) {
if (!event.target.closest('#view-options') && !event.target.closest('#view-dropdown')) {
document.getElementById('view-dropdown').classList.add('hidden');
}
});
// Search functionality
document.getElementById('search-box').addEventListener('input', function(e) {
const searchTerm = e.target.value.toLowerCase();
if (searchTerm.length > 0) {
const matchedNodes = nodes.get({
filter: function(node) {
return node.label.toLowerCase().includes(searchTerm) ||
(node.title && node.title.toLowerCase().includes(searchTerm));
}
});
const matchedNodeIds = matchedNodes.map(node => node.id);
network.selectNodes(matchedNodeIds);
if (matchedNodeIds.length > 0) {
network.focus(matchedNodeIds[0], { scale: 1.2 });
}
} else {
network.unselectAll();
}
});
// Filter by entity type
document.querySelectorAll('[data-type]').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const type = this.getAttribute('data-type');
const visible = this.checked;
nodes.update({
id: nodes.getIds({
filter: function(node) {
return node.group === type;
}
}),
hidden: !visible
});
});
});
// Filter by legal issue
document.querySelectorAll('[data-issue]').forEach(checkbox => {
checkbox.addEventListener('change', function() {
const issue = this.getAttribute('data-issue');
const visible = this.checked;
// In a real app, you would filter nodes and edges based on legal issues
console.log(`Filter ${issue} to ${visible}`);
});
});
// Presentation mode
document.getElementById('presentation-mode').addEventListener('click', function() {
document.body.classList.toggle('bg-gray-800');
document.querySelectorAll('.bg-white, .border-gray-200').forEach(el => {
el.classList.toggle('bg-gray-900');
el.classList.toggle('border-gray-700');
});
document.querySelectorAll('.text-gray-600, .text-gray-700, .text-gray-800').forEach(el => {
el.classList.toggle('text-gray-300');
});
network.setOptions({
nodes: {
color: {
background: '#3b82f6',
border: '#1d4ed8',
highlight: {
border: '#1e40af',
background: '#2563eb'
}
}
}
});
this.classList.toggle('text-white');
});
// Add some pulse effect for important nodes (demo purposes)
setTimeout(() => {
const importantNode = nodes.get(1);
const el = document.querySelector(`[data-node-id="${importantNode.id}"]`);
if (el) el.classList.add('pulse');
}, 1000);
});
</script>
<p style="border-radius: 8px; text-align: center; font-size: 12px; color: #fff; margin-top: 16px;position: fixed; left: 8px; bottom: 8px; z-index: 10; background: rgba(0, 0, 0, 0.8); padding: 4px 8px;">Made with <img src="https://enzostvs-deepsite.hf.space/logo.svg" alt="DeepSite Logo" style="width: 16px; height: 16px; vertical-align: middle;display:inline-block;margin-right:3px;filter:brightness(0) invert(1);"><a href="https://enzostvs-deepsite.hf.space" style="color: #fff;text-decoration: underline;" target="_blank" >DeepSite</a> - 🧬 <a href="https://enzostvs-deepsite.hf.space?remix=trippykat/graph" style="color: #fff;text-decoration: underline;" target="_blank" >Remix</a></p></body>
</html>