Download web/src/views/network.php from datamatters24/research-document-archive: direct link, hf CLI and curl.
- Browser
- Download file 10 kB
-
https://huggingface.co/datamatters24/research-document-archive/resolve/main/web/src/views/network.php
- Command line
-
hf download hf://datamatters24/research-document-archive/web/src/views/network.php
-
curl -L -o network.php https://huggingface.co/datamatters24/research-document-archive/resolve/main/web/src/views/network.php
10 kB
| $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> | |
| 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'; | |
| ?> | |