|
|
|
@@ -6,21 +6,52 @@ |
|
|
|
<title>DAG Visualization</title> |
|
|
|
<script src="https://d3js.org/d3.v7.min.js"></script> |
|
|
|
<style> |
|
|
|
body { |
|
|
|
margin: 0; |
|
|
|
display: flex; |
|
|
|
justify-content: center; |
|
|
|
align-items: center; |
|
|
|
height: 100vh; |
|
|
|
background-color: #f4f4f4; |
|
|
|
html, body { |
|
|
|
height: 100%; /* Full height for the html and body */ |
|
|
|
margin: 0; /* Remove default margin */ |
|
|
|
padding: 0; /* Remove default padding */ |
|
|
|
display: flex; /* Enable flexbox */ |
|
|
|
justify-content: center; /* Center horizontally */ |
|
|
|
align-items: center; /* Center vertically */ |
|
|
|
background-color: #f4f4f4; /* Background color */ |
|
|
|
box-sizing: border-box; /* Border and padding included in width/height */ |
|
|
|
} |
|
|
|
#container { |
|
|
|
display: flex; /* Flex container for the panels */ |
|
|
|
width: 90vw; /* 90% of viewport width */ |
|
|
|
height: 80vh; /* 80% of viewport height */ |
|
|
|
padding: 20px; /* Padding around the container */ |
|
|
|
box-sizing: border-box; /* Include padding in the width and height calculation */ |
|
|
|
} |
|
|
|
#left-panel { |
|
|
|
width: 40%; |
|
|
|
height: 100%; /* Full height of the container */ |
|
|
|
overflow-y: auto; |
|
|
|
padding: 10px; |
|
|
|
border: 1px solid gray; |
|
|
|
border-radius: 10px; |
|
|
|
font-size: 10px; |
|
|
|
box-sizing: border-box; |
|
|
|
direction: rtl; /* Right to Left text direction for scrollbar */ |
|
|
|
} |
|
|
|
#left-panel > * { |
|
|
|
direction: ltr; /* Left to Right text direction for contents */ |
|
|
|
} |
|
|
|
#svg-container { |
|
|
|
border: 1px solid black; |
|
|
|
flex: 1; /* Take remaining space */ |
|
|
|
height: 100%; /* Full height of the container */ |
|
|
|
margin-left: 2%; /* Gutter between panels */ |
|
|
|
border: solid 1px black; |
|
|
|
border-radius: 10px; |
|
|
|
box-sizing: border-box; |
|
|
|
background-color: white; /* Ensure background is white for visibility */ |
|
|
|
} |
|
|
|
#svg { |
|
|
|
width: 100%; |
|
|
|
height: 100%; |
|
|
|
} |
|
|
|
|
|
|
|
text { |
|
|
|
font-size: 14px; /* Increased from 10px to 14px */ |
|
|
|
font-size: 14px; |
|
|
|
text-anchor: middle; |
|
|
|
pointer-events: none; |
|
|
|
fill: #333; |
|
|
|
@@ -37,27 +68,44 @@ |
|
|
|
stroke: #333; |
|
|
|
stroke-width: 1.5px; |
|
|
|
} |
|
|
|
.node-info { |
|
|
|
margin-bottom: 10px; |
|
|
|
padding: 10px; |
|
|
|
cursor: pointer; |
|
|
|
background-color: #fff; |
|
|
|
border: 1px solid #ccc; |
|
|
|
border-radius: 5px; |
|
|
|
} |
|
|
|
.node-info:hover { |
|
|
|
background-color: #eee; |
|
|
|
} |
|
|
|
.selected-message { |
|
|
|
outline: 2px solid blue; |
|
|
|
} |
|
|
|
</style> |
|
|
|
|
|
|
|
</head> |
|
|
|
<body> |
|
|
|
<svg id="svg-container"> |
|
|
|
<!-- Define arrowhead --> |
|
|
|
<defs> |
|
|
|
<marker id="arrow" markerWidth="10" markerHeight="10" refX="12" refY="3" orient="auto" markerUnits="strokeWidth"> |
|
|
|
<path d="M0,0 L0,6 L9,3 z" fill="#999"></path> |
|
|
|
</marker> |
|
|
|
</defs> |
|
|
|
</svg> |
|
|
|
|
|
|
|
<div id="container"> |
|
|
|
<div id="left-panel"> |
|
|
|
<!-- Node information will be populated here --> |
|
|
|
</div> |
|
|
|
<div id="svg-container"> |
|
|
|
<svg id="svg"></svg> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
<script> |
|
|
|
let dag_data = String.raw`__DAG_DATA__`; |
|
|
|
let data = JSON.parse(dag_data); |
|
|
|
let data = "__DAG_DATA__"; |
|
|
|
|
|
|
|
const svg = d3.select("#svg"); |
|
|
|
const svgContainer = d3.select("#svg-container"); |
|
|
|
const width = window.innerWidth * 0.8; // 80% width |
|
|
|
const height = window.innerHeight * 0.8; // 80% height |
|
|
|
const svg = svgContainer.attr("width", width).attr("height", height); |
|
|
|
const width = svgContainer.node().getBoundingClientRect().width; |
|
|
|
const height = svgContainer.node().getBoundingClientRect().height; |
|
|
|
|
|
|
|
const zoom = d3.zoom() |
|
|
|
.scaleExtent([0.1, 10]) |
|
|
|
.on("zoom", (event) => { |
|
|
|
container.attr("transform", event.transform); |
|
|
|
}); |
|
|
|
@@ -72,7 +120,7 @@ |
|
|
|
.attr("class", "link") |
|
|
|
.attr("marker-end", "url(#arrow)"); |
|
|
|
|
|
|
|
const nodeRadius = 30; // Increased from 20 to 30 |
|
|
|
const nodeRadius = 30; |
|
|
|
const simulation = d3.forceSimulation(data.nodes) |
|
|
|
.force("link", d3.forceLink(data.links).id(d => d.index).distance(100)) |
|
|
|
.force("charge", d3.forceManyBody().strength(-300)) |
|
|
|
@@ -86,14 +134,31 @@ |
|
|
|
.call(d3.drag() |
|
|
|
.on("start", dragstarted) |
|
|
|
.on("drag", dragged) |
|
|
|
.on("end", dragended)); |
|
|
|
.on("end", dragended)) |
|
|
|
.on("click", (event, d) => { |
|
|
|
const index = d.index; |
|
|
|
const nodeInfoElement = document.getElementById(`node-info-${index}`); |
|
|
|
if (nodeInfoElement) { |
|
|
|
// Scroll to the corresponding message |
|
|
|
nodeInfoElement.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" }); |
|
|
|
|
|
|
|
// Remove outline from previously selected message |
|
|
|
const previousSelected = document.querySelector(".selected-message"); |
|
|
|
if (previousSelected) { |
|
|
|
previousSelected.classList.remove("selected-message"); |
|
|
|
} |
|
|
|
|
|
|
|
// Add outline to the selected message |
|
|
|
nodeInfoElement.classList.add("selected-message"); |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
node.append("circle") |
|
|
|
.attr("r", nodeRadius); |
|
|
|
|
|
|
|
node.append("text") |
|
|
|
.attr("dy", "0.35em") |
|
|
|
.text(d => d.name); |
|
|
|
.text(d => d.profile.message.source); |
|
|
|
|
|
|
|
simulation.on("tick", () => { |
|
|
|
link.attr("x1", d => d.source.x) |
|
|
|
@@ -104,6 +169,15 @@ |
|
|
|
node.attr("transform", d => `translate(${d.x},${d.y})`); |
|
|
|
}); |
|
|
|
|
|
|
|
// Populate left panel with node information |
|
|
|
const leftPanel = d3.select("#left-panel"); |
|
|
|
const nodeInfo = leftPanel.selectAll(".node-info") |
|
|
|
.data(data.nodes) |
|
|
|
.enter().append("div") |
|
|
|
.attr("class", "node-info") |
|
|
|
.attr("id", (d, i) => `node-info-${i}`) // Add unique IDs to each node info |
|
|
|
.text(d => `${d.profile.message.source}: ${d.profile.message.content}`); |
|
|
|
|
|
|
|
function dragstarted(event, d) { |
|
|
|
if (!event.active) simulation.alphaTarget(0.3).restart(); |
|
|
|
d.fx = d.x; |
|
|
|
@@ -120,7 +194,6 @@ |
|
|
|
d.fx = null; |
|
|
|
d.fy = null; |
|
|
|
} |
|
|
|
|
|
|
|
</script> |
|
|
|
</body> |
|
|
|
</html> |