policymap/render/templates/index.html
2025-04-05 19:57:29 -07:00

156 lines
4.7 KiB
HTML
Executable File

<!DOCTYPE html>
<html lang="en">
<head>
<style>
body { margin: 0; }
#3d-graph { width: 100vw; height: 100vh; }
.tooltip {
position: absolute;
background-color: rgba(255, 255, 255, 0.8);
padding: 10px;
border-radius: 5px;
pointer-events: none;
z-index: 10;
display: none;
}
#node-details {
position: fixed;
top: 20px;
left: 20px;
background-color: white;
padding: 20px;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
display: none;
}
#close-tooltip {
position: absolute;
top: -5px;
right: -5px;
background-color: red;
color: white;
padding: 2px 6px;
border-radius: 50%;
cursor: pointer;
}
</style>
<!-- Load the ForceGraph library -->
<script src="https://cdn.jsdelivr.net/npm/3d-force-graph@latest/dist/3d-force-graph.min.js"></script>
</head>
<body>
<div id="3d-graph"></div>
<div class="tooltip" id="node-tooltip">
<span id="close-tooltip">&times;</span>
</div>
<div id="node-details">
<h3>Node Details</h3>
<pre id="node-details-content"></pre>
</div>
<script>
async function fetchData() {
try {
const response = await fetch('/data/policymap.json');
const textData = await response.text();
const jsonData = textData.trim().split('\n').map(line => JSON.parse(line));
const nodes = [];
const links = [];
jsonData.forEach(item => {
if (item.labels) {
const id = item.id || item.properties.id;
let name = '';
let title = '';
if (item.properties.name) {
name = item.properties.name;
} else if (item.properties.title) {
title = item.properties.title;
}
nodes.push({
id: id,
name: name,
title: title,
type: item.labels[0],
properties: item.properties
});
} else if (item.start.id && item.end.id) {
links.push({
source: item.start.id,
target: item.end.id,
value: item?.weight || 1,
label: item.label
});
}
});
return {
nodes: nodes,
links: links
};
} catch (error) {
console.error('Error fetching or transforming JSON:', error);
}
}
async function initGraph() {
const data = await fetchData();
if (!data || !data.nodes || !data.links) {
console.error('Data is incomplete:', data);
return;
}
const elem = document.getElementById('3d-graph');
const tooltip = document.getElementById('node-tooltip');
const nodeDetailsContent = document.getElementById('node-details-content');
const closeTooltipButton = document.getElementById('close-tooltip');
closeTooltipButton.addEventListener('click', () => {
tooltip.style.display = 'none';
});
const Graph = ForceGraph3D()(elem)
.graphData(data)
.nodeColor(node => {
if (node.type === 'Legislation') {
return 'pink';
} else if (node.type === 'Bill') {
return 'yellow';
} else if (node.type === 'Order') {
return 'orange';
} else if (node.type === 'Law') {
return 'blue';
} else if (node.type === 'Person') {
return 'red';
}
})
.nodeLabel(node => node.labels === 'Person' ? node.properties.name : node.properties.title || 'Node')
.linkWidth(link => link.weight || 1)
.onNodeHover(node => {
if (node) {
const properties = Object.entries(node.properties).map(([key, value]) => `${key}: ${value}`).join('<br>');
tooltip.innerHTML = properties;
tooltip.style.display = 'block';
} else {
tooltip.style.display = 'none';
}
})
.onNodeDragEnd(() => tooltip.style.display = 'none')
.onNodeClick(node => {
const nodeDetailsDiv = document.getElementById('node-details');
if (node) {
nodeDetailsContent.innerHTML = JSON.stringify(node.properties, null, 2);
nodeDetailsDiv.style.display = 'block';
} else {
nodeDetailsDiv.style.display = 'none';
}
});
elem.addEventListener('mousemove', event => {
const rect = elem.getBoundingClientRect();
const x = event.clientX - rect.left;
const y = event.clientY - rect.top;
tooltip.style.transform = `translate(${x}px, ${y}px)`;
});
}
initGraph();
</script>
</body>
</html>