traceroute_map/app/static/index.js
2024-06-02 00:12:19 +03:00

358 lines
8.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

const linkArc = (d) => {
const r = Math.hypot(d.target.x - d.source.x, d.target.y - d.source.y) * 3;
return `
M${d.source.x},${d.source.y}
A${r},${r} 0 0,1 ${d.target.x},${d.target.y}
`;
};
const drag = (simulation) => {
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;
}
return d3
.drag()
.on("start", dragstarted)
.on("drag", dragged)
.on("end", dragended);
};
// const drawChart = (data) => {
// // Specify the dimensions of the chart.
// const width = 1600;
// const height = 1200;
//
// // Specify the color scale.
// const color = d3.scaleOrdinal(d3.schemeCategory10);
//
// // The force simulation mutates links and nodes, so create a copy
// // so that re-evaluating this cell produces the same result.
// const links = data.links.map((d) => ({ ...d }));
// const nodes = data.nodes.map((d) => ({ ...d }));
//
// // Create a simulation with several forces.
// const simulation = d3
// .forceSimulation(nodes)
// .force(
// "link",
// d3.forceLink(links).id((d) => d.id),
// )
// .force("charge", d3.forceManyBody())
// .force("x", d3.forceX())
// .force("y", d3.forceY());
//
// // Create the SVG container.
// const svg = d3
// .create("svg")
// .attr("width", width)
// .attr("height", height)
// .attr("viewBox", [-width / 2, -height / 2, width, height])
// .attr("style", "max-width: 100%; height: auto;");
//
// // Add a line for each link, and a circle for each node.
// const link = svg
// .append("g")
// .attr("stroke", "#999")
// .attr("stroke-opacity", 0.6)
// .selectAll("line")
// .data(links)
// .join("line")
// .attr("stroke-width", 1); // (d) => Math.sqrt(d.value));
//
// const node = svg
// .append("g")
// .attr("stroke", "#fff")
// .attr("stroke-width", 1.5)
// .selectAll("circle")
// .data(nodes)
// .join("circle")
// .attr("r", 5)
// .attr("fill", (d) => color(d.group));
//
// node.append("title").text((d) => d.id);
//
// // Add a drag behavior.
// node.call(
// d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended),
// );
//
// // Set the position attributes of links and nodes each time the simulation ticks.
// 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);
// });
//
// // Reheat the simulation when drag starts, and fix the subject position.
// function dragstarted(event) {
// if (!event.active) simulation.alphaTarget(0.3).restart();
// event.subject.fx = event.subject.x;
// event.subject.fy = event.subject.y;
// }
//
// // Update the subject (dragged node) position during drag.
// function dragged(event) {
// event.subject.fx = event.x;
// event.subject.fy = event.y;
// }
//
// // Restore the target alpha so the simulation cools after dragging ends.
// // Unfix the subject position now that its no longer being dragged.
// function dragended(event) {
// if (!event.active) simulation.alphaTarget(0);
// event.subject.fx = null;
// event.subject.fy = null;
// }
//
// // When this cell is re-run, stop the previous simulation. (This doesnt
// // really matter since the target alpha is zero and the simulation will
// // stop naturally, but its a good practice.)
// // invalidation.then(() => simulation.stop());
//
// return svg.node();
// };
const drawChart2 = (data) => {
// Data parsing
const nodes = Array.from(
new Set(data.flatMap((l) => [l.source, l.target])),
(id) => ({ id }),
);
const links = data.map((d) => Object.create(d));
// Styles
const width = window.visualViewport.width;
const height = window.visualViewport.height - 10;
const colors = Array.from(new Set(data.map((d) => d.traceId)));
const color = d3.scaleOrdinal(colors, d3.schemeCategory10);
const simulation = d3
.forceSimulation(nodes)
.force(
"link",
d3.forceLink(links).id((d) => d.id),
)
.force("charge", d3.forceManyBody().strength(-400))
.force("x", d3.forceX())
.force("y", d3.forceY());
// Canvas settings
const svg = d3
.create("svg")
.attr("width", width)
.attr("height", height)
.attr("viewBox", [-width / 2, -height / 2, width, height])
.attr("style", "max-width: 100%; height: auto; font: 14px monospace;");
// Pre-type arrowheads, as they don't inherit styles.
svg
.append("defs")
.selectAll("marker")
.data(colors)
.join("marker")
.attr("id", (d) => `arrow-${d}`)
.attr("viewBox", "0 -5 10 10")
.attr("refX", 15)
.attr("refY", -0.5)
.attr("markerWidth", 5)
.attr("markerHeight", 5)
.attr("orient", "auto")
.append("path")
.attr("fill", color)
.attr("d", "M0,-5L10,0L0,5");
const link = svg
.append("g")
.attr("fill", "none")
.attr("stroke-width", 1.5)
.selectAll("path")
.data(links)
.join("path")
.attr("stroke", (d) => color(d.traceId))
.attr(
"marker-end",
(d) => `url(${new URL(`#arrow-${d.traceId}`, location)})`,
);
const node = svg
.append("g")
.attr("fill", "currentColor")
.attr("stroke-linecap", "round")
.attr("stroke-linejoin", "round")
.selectAll("g")
.data(nodes)
.join("g")
.call(drag(simulation));
// Node "icon"
node
.append("circle")
.attr("stroke", "white")
.attr("stroke-width", 1.5)
.attr("r", 5);
// Node text
node
.append("text")
.attr("x", 8)
.attr("y", 4)
.text((d) => (d.id.endsWith("*") ? "*" : d.id))
.clone(true)
.lower()
.attr("fill", "black")
.attr("stroke", "white")
.attr("stroke-width", 2);
simulation.on("tick", () => {
link.attr("d", linkArc);
// 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("transform", (d) => `translate(${d.x},${d.y})`);
});
// invalidation.then(() => simulation.stop());
return Object.assign(svg.node(), { scales: { color } });
};
const drawSigma = (data) => {
// Create a graphology graph
const graph = new graphology.MultiDirectedGraph();
data.nodes.forEach((n, i) => {
console.log("Node:", n);
try {
graph.addNode(n.id, n);
} catch (e) {
console.log("Node add:", e);
}
});
data.links.forEach((l, i) => {
graph.addEdge(l.source, l.target, l);
});
// Instantiate sigma.js and render the graph
const renderer = new Sigma(graph, document.getElementById("container"), {
labelThreshold: -10000,
});
};
const getNodeID = (hop, prevId, trace) => {
if (prevId === null) {
return trace.origin;
}
if (hop.name === "*") {
return `${trace.id}-${hop.number}-*`;
}
return hop.ip;
};
const parseNodesAndLinks = (traces) => {
const nodes = [];
const links = [];
const colors = Array.from(new Set(traces.map((t) => t.id)));
const color = d3.scaleOrdinal(colors, d3.schemeCategory10);
traces.forEach((trace) => {
let prevId = null;
let latestNumber = null;
trace.hops.forEach((hop) => {
const id = getNodeID(hop, prevId, trace);
// New node
nodes.push({
id: id,
label: id.endsWith("*") ? "*" : id,
x: trace.id,
y: hop.number / 2,
size: 9,
labelSize: 30,
color: color(trace.id),
});
if (prevId) {
// New link
links.push({
label: "asd",
source: prevId,
target: id,
traceId: trace.id,
origin: trace.origin,
size: 1,
color: color(trace.id),
});
}
prevId = id;
latestNumber = hop.number;
});
/**
* Last "destination" node, just for candy
*/
nodes.push({
id: trace.id,
label: trace.target,
x: trace.id,
y: (latestNumber + 1) / 2,
size: 8,
color: "black",
});
links.push({
source: prevId,
target: trace.id,
});
});
// { id: ip, group: origin, radius: 2 }
// { source: prev.ip, target: ip, value: latency }
return {
nodes,
links,
};
};
async function main() {
const response = await fetch("/trace/");
const traces = await response.json();
console.log("Traces:", traces);
const data = parseNodesAndLinks(traces);
console.log("Data:", data);
// const chart = drawChart2(data.links);
const chart = drawSigma(data);
// container.append(chart);
}
main();