diff --git a/app/static/index.html b/app/static/index.html index 8682667..028b9d7 100644 --- a/app/static/index.html +++ b/app/static/index.html @@ -6,9 +6,8 @@ - +
- diff --git a/app/static/index.js b/app/static/index.js index 5e9b337..ba51167 100644 --- a/app/static/index.js +++ b/app/static/index.js @@ -31,116 +31,117 @@ const drag = (simulation) => { .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 it’s 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 doesn’t - // really matter since the target alpha is zero and the simulation will - // stop naturally, but it’s a good practice.) - // invalidation.then(() => simulation.stop()); - - return svg.node(); -}; +// 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 it’s 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 doesn’t +// // really matter since the target alpha is zero and the simulation will +// // stop naturally, but it’s a good practice.) +// // invalidation.then(() => simulation.stop()); +// +// return svg.node(); +// }; const drawChart2 = (data) => { - const width = 1600; - const height = 1200; - const types = Array.from(new Set(data.map((d) => d.type))); + const width = window.visualViewport.width; + const height = window.visualViewport.height - 10; + + const colors = Array.from(new Set(data.map((d) => d.traceId))); const nodes = Array.from( new Set(data.flatMap((l) => [l.source, l.target])), (id) => ({ id }), ); const links = data.map((d) => Object.create(d)); - const color = d3.scaleOrdinal(types, d3.schemeCategory10); + const color = d3.scaleOrdinal(colors, d3.schemeCategory10); const simulation = d3 .forceSimulation(nodes) @@ -152,18 +153,19 @@ const drawChart2 = (data) => { .force("x", d3.forceX()) .force("y", d3.forceY()); + // Canvas settings const svg = d3 .create("svg") .attr("viewBox", [-width / 2, -height / 2, width, height]) .attr("width", width) .attr("height", height) - .attr("style", "max-width: 100%; height: auto; font: 12px sans-serif;"); + .attr("style", "max-width: 100%; height: auto; font: 14px monospace;"); // Per-type markers, as they don't inherit styles. svg .append("defs") .selectAll("marker") - .data(types) + .data(colors) .join("marker") .attr("id", (d) => `arrow-${d}`) .attr("viewBox", "0 -5 10 10") @@ -183,8 +185,11 @@ const drawChart2 = (data) => { .selectAll("path") .data(links) .join("path") - .attr("stroke", (d) => color(d.type)) - .attr("marker-end", (d) => `url(${new URL(`#arrow-${d.type}`, location)})`); + .attr("stroke", (d) => color(d.traceId)) + .attr( + "marker-end", + (d) => `url(${new URL(`#arrow-${d.traceId}`, location)})`, + ); const node = svg .append("g") @@ -223,6 +228,17 @@ const drawChart2 = (data) => { return Object.assign(svg.node(), { scales: { color } }); }; +const getId = (hop, prevId, trace) => { + if (prevId === null) { + return trace.origin; + } + if (hop.name === "*") { + return `${trace.id}-${hop.number}-*`; + } + + return hop.ip; +}; + const parseNodesAndLinks = (traces) => { const result = { nodes: [], @@ -232,24 +248,12 @@ const parseNodesAndLinks = (traces) => { traces.forEach((trace) => { let prevId = null; - const getId = (hop) => { - if (prevId === null) { - return trace.origin; - } - if (hop.name === "*") { - return `${trace.id}-${hop.number}-*`; - } - - return hop.ip; - }; - trace.hops.forEach((hop) => { - const id = getId(hop); + const id = getId(hop, prevId, trace); // New node result.nodes.push({ id: id, - group: trace.origin, radius: 8, value: hop.name || "name?", origin: trace.origin, @@ -260,8 +264,8 @@ const parseNodesAndLinks = (traces) => { result.links.push({ source: prevId, target: id, - type: trace.origin, - group: trace.origin, + traceId: trace.id, + origin: trace.origin, }); } @@ -271,9 +275,9 @@ const parseNodesAndLinks = (traces) => { // Last "destination" node result.nodes.push({ id: trace.id, - group: trace.origin, radius: 8, value: trace.target, + origin: trace.origin, }); if (prevId) { @@ -281,8 +285,8 @@ const parseNodesAndLinks = (traces) => { result.links.push({ source: prevId, target: trace.target, - type: trace.origin, - group: trace.origin, + traceId: trace.id, + origin: trace.origin, }); } });