datamatters24's picture
Upload web/src/views/network.php with huggingface_hub
b009e55 verified
<?php
$title = 'Entity Network - Research Document Archive';
$content = '';
ob_start();
$sectionNames = [
'cia_declassified' => 'CIA Declassified',
'cia_mkultra' => 'CIA MKUltra',
'cia_stargate' => 'CIA Stargate',
'doj_disclosures' => 'DOJ Disclosures',
'house_resolutions' => 'House Resolutions',
'jfk_assassination' => 'JFK Assassination',
'lincoln_archives' => 'Lincoln Archives',
];
?>
<div class="mb-6">
<h1 class="text-2xl font-bold text-gray-900">Entity Network</h1>
<p class="mt-1 text-sm text-gray-600">Explore co-occurrence relationships between entities across document collections.</p>
</div>
<!-- Filters -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm p-4 mb-6">
<form method="get" class="flex flex-wrap gap-4 items-end">
<div>
<label class="block text-xs font-medium text-gray-500 uppercase mb-1">Collection</label>
<select name="section" class="rounded-md border-gray-300 text-sm py-1.5">
<option value="">Select collection...</option>
<?php foreach ($sections as $s): ?>
<option value="<?= htmlspecialchars($s['source_section']) ?>"
<?= $section === $s['source_section'] ? 'selected' : '' ?>>
<?= htmlspecialchars($sectionNames[$s['source_section']] ?? $s['source_section']) ?>
</option>
<?php endforeach; ?>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-500 uppercase mb-1">Entity Type</label>
<select name="type" class="rounded-md border-gray-300 text-sm py-1.5">
<option value="PERSON" <?= $entityType === 'PERSON' ? 'selected' : '' ?>>People</option>
<option value="ORG" <?= $entityType === 'ORG' ? 'selected' : '' ?>>Organizations</option>
</select>
</div>
<div>
<label class="block text-xs font-medium text-gray-500 uppercase mb-1">Min Co-occurrences</label>
<input type="number" name="min" value="<?= $minCount ?>" min="2" max="1000"
class="rounded-md border-gray-300 text-sm py-1.5 w-24">
</div>
<button type="submit" class="px-4 py-1.5 bg-blue-600 text-white text-sm font-medium rounded-md hover:bg-blue-700 transition-colors">
Explore
</button>
</form>
</div>
<?php if (!empty($relationships)): ?>
<!-- Network Graph -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm mb-6 overflow-hidden">
<div class="p-3 border-b border-gray-200 bg-gray-50 flex justify-between items-center">
<h2 class="text-sm font-semibold text-gray-700">
Network Graph — <?= htmlspecialchars($sectionNames[$section] ?? $section) ?>
<span class="font-normal text-gray-500">(<?= count($nodes) ?> entities, <?= count($relationships) ?> connections)</span>
</h2>
</div>
<div id="network-graph" style="height: 600px;"></div>
</div>
<!-- Top Connections Table -->
<div class="bg-white border border-gray-200 rounded-lg shadow-sm overflow-hidden">
<div class="p-3 border-b border-gray-200 bg-gray-50">
<h2 class="text-sm font-semibold text-gray-700">Top Connections</h2>
</div>
<table class="min-w-full divide-y divide-gray-200">
<thead class="bg-gray-50">
<tr>
<th class="px-4 py-2 text-left text-[11px] font-semibold text-gray-500 uppercase">Entity A</th>
<th class="px-4 py-2 text-left text-[11px] font-semibold text-gray-500 uppercase">Entity B</th>
<th class="px-4 py-2 text-center text-[11px] font-semibold text-gray-500 uppercase">Co-occurrences</th>
<th class="px-4 py-2 text-center text-[11px] font-semibold text-gray-500 uppercase">Documents</th>
<th class="px-4 py-2 text-center text-[11px] font-semibold text-gray-500 uppercase">Sample</th>
</tr>
</thead>
<tbody class="divide-y divide-gray-100">
<?php foreach (array_slice($relationships, 0, 50) as $rel):
$sampleIds = $rel['sample_doc_ids'] ?? '';
if (is_string($sampleIds)) {
$sampleIds = trim($sampleIds, '{}');
$sampleIds = $sampleIds ? explode(',', $sampleIds) : [];
}
?>
<tr class="hover:bg-gray-50">
<td class="px-4 py-2 text-sm text-gray-900"><?= htmlspecialchars($rel['entity_a']) ?></td>
<td class="px-4 py-2 text-sm text-gray-900"><?= htmlspecialchars($rel['entity_b']) ?></td>
<td class="px-4 py-2 text-center text-sm font-mono text-gray-700"><?= number_format((int)$rel['co_occurrence_count']) ?></td>
<td class="px-4 py-2 text-center text-sm text-gray-600"><?= number_format((int)$rel['document_count']) ?></td>
<td class="px-4 py-2 text-center">
<?php foreach (array_slice($sampleIds, 0, 3) as $sid): ?>
<a href="/document/<?= (int)$sid ?>" class="text-xs text-blue-600 hover:underline mr-1">#<?= (int)$sid ?></a>
<?php endforeach; ?>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- D3 Force-Directed Graph -->
<script src="https://d3js.org/d3.v7.min.js"></script>
<script>
(function() {
const nodes = <?= json_encode(array_map(function($name, $degree) {
return ['id' => $name, 'degree' => $degree];
}, array_keys($nodes), array_values($nodes))) ?>;
const links = <?= json_encode(array_map(function($r) {
return ['source' => $r['entity_a'], 'target' => $r['entity_b'], 'value' => (int)$r['co_occurrence_count']];
}, $relationships)) ?>;
const container = document.getElementById('network-graph');
const width = container.clientWidth;
const height = 600;
const svg = d3.select('#network-graph')
.append('svg')
.attr('width', width)
.attr('height', height)
.attr('viewBox', [0, 0, width, height]);
// Scale for node sizes
const maxDegree = d3.max(nodes, d => d.degree) || 1;
const sizeScale = d3.scaleSqrt().domain([1, maxDegree]).range([3, 20]);
const linkScale = d3.scaleLog().domain([d3.min(links, d => d.value) || 1, d3.max(links, d => d.value) || 1]).range([0.5, 4]);
const simulation = d3.forceSimulation(nodes)
.force('link', d3.forceLink(links).id(d => d.id).distance(80))
.force('charge', d3.forceManyBody().strength(-120))
.force('center', d3.forceCenter(width / 2, height / 2))
.force('collision', d3.forceCollide().radius(d => sizeScale(d.degree) + 2));
const g = svg.append('g');
// Zoom
svg.call(d3.zoom().scaleExtent([0.1, 5]).on('zoom', e => g.attr('transform', e.transform)));
const link = g.append('g')
.selectAll('line')
.data(links)
.join('line')
.attr('stroke', '#999')
.attr('stroke-opacity', 0.4)
.attr('stroke-width', d => linkScale(d.value));
const node = g.append('g')
.selectAll('circle')
.data(nodes)
.join('circle')
.attr('r', d => sizeScale(d.degree))
.attr('fill', '#3b82f6')
.attr('stroke', '#1d4ed8')
.attr('stroke-width', 1)
.call(d3.drag()
.on('start', (e, d) => { if (!e.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; })
.on('drag', (e, d) => { d.fx = e.x; d.fy = e.y; })
.on('end', (e, d) => { if (!e.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; }));
const label = g.append('g')
.selectAll('text')
.data(nodes.filter(d => d.degree > maxDegree * 0.1))
.join('text')
.text(d => d.id)
.attr('font-size', '9px')
.attr('fill', '#374151')
.attr('dx', 8)
.attr('dy', 3);
node.append('title').text(d => d.id + ' (' + d.degree + ' co-occurrences)');
simulation.on('tick', () => {
link.attr('x1', d => d.source.x).attr('y1', d => d.source.y)
.attr('x2', d => d.target.x).attr('y2', d => d.target.y);
node.attr('cx', d => d.x).attr('cy', d => d.y);
label.attr('x', d => d.x).attr('y', d => d.y);
});
})();
</script>
<?php elseif ($section): ?>
<div class="text-center py-12 bg-white rounded-lg border border-gray-200">
<p class="text-sm text-gray-500">No relationships found with minimum <?= $minCount ?> co-occurrences. Try lowering the threshold.</p>
</div>
<?php else: ?>
<!-- Overview Stats -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4">
<?php foreach ($stats as $s): ?>
<a href="?section=<?= urlencode($s['section']) ?>&type=<?= urlencode($s['type']) ?>&min=10"
class="bg-white border border-gray-200 rounded-lg shadow-sm p-4 hover:shadow-md transition-shadow">
<div class="flex items-center justify-between">
<div>
<p class="text-sm font-medium text-gray-900">
<?= htmlspecialchars($sectionNames[$s['section']] ?? $s['section']) ?>
</p>
<p class="text-xs text-gray-500 mt-0.5"><?= htmlspecialchars($s['type']) ?> entities</p>
</div>
<span class="inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium
<?= $s['type'] === 'PERSON' ? 'bg-blue-100 text-blue-800' : 'bg-purple-100 text-purple-800' ?>">
<?= number_format((int)$s['rels']) ?> links
</span>
</div>
<p class="mt-2 text-xs text-gray-500"><?= number_format((int)$s['cooccurrences']) ?> total co-occurrences</p>
</a>
<?php endforeach; ?>
</div>
<?php endif; ?>
<?php
$content = ob_get_clean();
include __DIR__ . '/layout.php';
?>