196 lines
6.7 KiB
HTML
196 lines
6.7 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Render Network</title>
|
|
<!-- Include D3.js -->
|
|
<script src="https://d3js.org/d3.v7.min.js"></script>
|
|
<style>
|
|
body {
|
|
margin: 0;
|
|
overflow: hidden;
|
|
}
|
|
#chart {
|
|
width: 100vw;
|
|
height: 100vh;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
}
|
|
.node {
|
|
stroke: #fff;
|
|
stroke-width: 1.5px;
|
|
cursor: pointer; /* Change cursor to indicate interactivity */
|
|
}
|
|
.link {
|
|
fill: none;
|
|
stroke: #999;
|
|
stroke-opacity: 0.6;
|
|
}
|
|
text {
|
|
font-size: 12px;
|
|
}
|
|
/* Tooltip styles */
|
|
.tooltip {
|
|
position: absolute;
|
|
padding: 10px;
|
|
background-color: rgba(255, 255, 255, 0.9);
|
|
border: 1px solid #ccc;
|
|
pointer-events: none; /* Make sure the tooltip doesn't interfere with mouse events */
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div id="chart"></div>
|
|
|
|
<!-- Tooltip container -->
|
|
<div class="tooltip" style="display: none;"></div>
|
|
|
|
<script type="text/javascript">
|
|
// Get container dimensions
|
|
const chartContainer = d3.select("#chart");
|
|
let width = parseInt(chartContainer.style("width"));
|
|
let height = parseInt(chartContainer.style("height"));
|
|
|
|
// Create SVG container
|
|
const svg = chartContainer.append("svg")
|
|
.attr("width", width)
|
|
.attr("height", height);
|
|
|
|
// Define simulation
|
|
const simulation = d3.forceSimulation()
|
|
.force("link", d3.forceLink().id(d => d.id).distance(100))
|
|
.force("charge", d3.forceManyBody().strength(-500)) // Adjust charge strength for better distribution
|
|
.force("center", d3.forceCenter(width / 2, height / 2));
|
|
|
|
// Fetch network data from Flask endpoint
|
|
d3.json('/person_network').then(function(data) {
|
|
if (data.error) {
|
|
console.error(data.error);
|
|
return;
|
|
}
|
|
|
|
const nodesData = data.nodes;
|
|
const linksData = data.links;
|
|
|
|
console.log("Nodes:", nodesData);
|
|
console.log("Links:", linksData);
|
|
|
|
// Append links to the SVG container
|
|
const link = svg.append("g")
|
|
.attr("class", "links")
|
|
.selectAll("line")
|
|
.data(linksData)
|
|
.enter()
|
|
.append("line")
|
|
.attr("class", "link");
|
|
|
|
// Append nodes to the SVG container
|
|
const node = svg.append("g")
|
|
.attr("class", "nodes")
|
|
.selectAll("circle")
|
|
.data(nodesData)
|
|
.enter()
|
|
.append("circle")
|
|
.attr("class", "node")
|
|
.attr("r", d => d.group === 1 ? 8 : 6) // Larger circles for Person nodes
|
|
.style("fill", d => d.group === 1 ? "green" : "lightblue") // Green for Person, light blue for others
|
|
.call(d3.drag()
|
|
.on("start", dragstarted)
|
|
.on("drag", dragged)
|
|
.on("end", dragended))
|
|
.on("click", showTooltip);
|
|
|
|
// Append labels to the SVG container
|
|
const label = svg.append("g")
|
|
.attr("class", "labels")
|
|
.selectAll("text")
|
|
.data(nodesData)
|
|
.enter()
|
|
.append("text")
|
|
.text(d => {
|
|
if (d.labels.includes('Person')) {
|
|
return d.name || 'Person';
|
|
} else if (['Legislation', 'Bill', 'Law'].some(label => d.labels.includes(label))) {
|
|
return d.title || 'Title';
|
|
}
|
|
// Fallback for other types
|
|
return Object.keys(d).filter(key => key !== 'labels').map(key => d[key]).join(', ') || 'Node';
|
|
})
|
|
.attr("dx", 10) // Offset from node
|
|
.attr("dy", 4); // Offset from node
|
|
|
|
// Initialize simulation with nodes and links
|
|
simulation.nodes(nodesData)
|
|
.on("tick", ticked);
|
|
simulation.force("link")
|
|
.links(linksData);
|
|
|
|
function ticked() {
|
|
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 => Math.max(10, Math.min(width - 10, d.x)))
|
|
.attr("cy", d => Math.max(10, Math.min(height - 10, d.y)));
|
|
|
|
label.attr("x", d => d.x)
|
|
.attr("y", d => d.y);
|
|
}
|
|
|
|
function dragstarted(event, d) {
|
|
if (!event.active) simulation.alphaTarget(0.3).restart();
|
|
d.fx = d.x;
|
|
d.fy = d.y;
|
|
}
|
|
|
|
function dragged(event, d) {
|
|
d.fx = event.x;
|
|
d.fy = event.y;
|
|
}
|
|
|
|
function dragended(event, d) {
|
|
if (!event.active) simulation.alphaTarget(0);
|
|
d.fx = null;
|
|
d.fy = null;
|
|
}
|
|
|
|
// Show tooltip on node click
|
|
function showTooltip(event, d) {
|
|
const tooltip = d3.select(".tooltip");
|
|
tooltip.style("display", "block")
|
|
.html(Object.entries(d).map(([key, value]) => `<strong>${key}:</strong> ${value}`).join("<br />"))
|
|
.style("left", `${event.pageX}px`)
|
|
.style("top", `${event.pageY}px`);
|
|
}
|
|
|
|
// Hide tooltip on document click
|
|
d3.select(document).on("click.tooltip", function() {
|
|
const tooltip = d3.select(".tooltip");
|
|
if (!d3.event.target.classList.contains("node")) {
|
|
tooltip.style("display", "none");
|
|
}
|
|
});
|
|
|
|
}).catch(function(error) {
|
|
console.error("Error fetching data:", error);
|
|
});
|
|
|
|
// Handle window resize
|
|
window.addEventListener('resize', () => {
|
|
width = parseInt(chartContainer.style("width"));
|
|
height = parseInt(chartContainer.style("height"));
|
|
|
|
svg.attr("width", width)
|
|
.attr("height", height);
|
|
|
|
simulation.force("center")
|
|
.x(width / 2)
|
|
.y(height / 2);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|