156 lines
4.7 KiB
HTML
Executable File
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">×</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>
|