// Data processing // =============== d3.json("network.json", function (error, graph) { console.log("Number of nodes at start: " + graph.nodes.length) if (error) throw error; // Prepare all data structures var linksUpdate, linksEnter, linksMerge, linksExit, nodesUpdate, nodesEnter, nodesMerge, nodesExit, g_links, g_nodes, filteredNodes; // Define color palet to use for nodes var color = d3.scaleOrdinal(d3.schemeCategory10); // Tooltips var tool_tip = d3 .tip() .attr("class", "d3-tip") .offset([0, 0]) .html(function (d) { return "IP: " + d.id + "
" + "Links: " + d.value; }); // The SVG element already exists, get a reference to it var svg = d3 .select("svg") .classed("svg-content", true) svg.call(tool_tip); g_links = svg.append("g").attr("class", "links") g_nodes = svg.append("g").attr("class", "nodes") // Describe zoom behavior var zoom = d3.zoom() .scaleExtent([1 / 10, 4]) .on("zoom", zoomed); // Describe forces to be used for the simulation var simulation = d3.forceSimulation() .force("link", d3.forceLink().id(function (d) { return d.id; })) .force("center", d3.forceCenter(svg.attr("width") / 2, svg.attr("height") / 2)) .force("charge", d3.forceManyBody().strength(function (d) { return Math.log(d.value) * -4 })); svg .style("fill", "none") .style("pointer-events", "all") .call(zoom); // Search var optArray = svg.nodes() .map(function (node) { return node.id; }) .sort(); $(function () { $("#search").autocomplete({ source: optArray }); }); // Draw the graph redraw(); // Bind UI event handlers $('#searchbutton').click(function () { console.log("search clicked") }); $('[name="filter"]').change(function (ev) { filter(ev.target.value); }); function zoomed() { var transform = d3.event.transform; g_nodes.attr("transform", d3.event.transform); g_links.attr("transform", d3.event.transform); } function dragsubject() { searchRadius = 40; return simulation.find(d3.event.x - svg.attr("height"), d3.event.y - svg.attr("height") / 2, searchRadius); } function dragstarted(d) { if (!d3.event.active) simulation.alphaTarget(0.3).restart(); d.fx = d.x; d.fy = d.y; } function dragged(d) { d.fx = d3.event.x; d.fy = d3.event.y; } function dragended(d) { if (!d3.event.active) simulation.alphaTarget(0); d.fx = null; d.fy = null; } function filter(filter) { console.log("Number of elements after restoring data: " + graph.nodes.length) if (filter == "DNS") { filteredLinks = graph.links.filter(function (i, n) { return i.proto == "DNS"; }); } if (filter == "HTTP") { filteredLinks = graph.links.filter(function (i, n) { return i.proto == "HTTP"; }); } // Update links graph.links = filteredLinks; // Remove all nodes without links removeOrphanedNodes(); console.log("Filtered links set: " + graph.links.length); console.log("Filtered nodes set: " + graph.nodes.length); redraw(); console.log("Number of elements after filtering data: " + graph.nodes.length) } function removeOrphanedNodes() { var ipMap = {}; // Iterate all IPs graph.links.forEach(function (link) { ipMap[ link.source.id ] = true; ipMap[ link.target.id ] = true; }); var filteredNodes = graph.nodes.filter(function (node) { return ipMap[node.id] }); graph.nodes = filteredNodes; } function redraw() { linksUpdate = g_links .selectAll("line") .data(graph.links, function (d) { return d.id; }); linksEnter = linksUpdate.enter() .append("line") .style("opacity", 1) .attr("stroke-width", function (d) { return 0.3; }); linksMerge = linksUpdate.merge(linksEnter); linksExit = linksUpdate.exit().transition() .style("opacity", 0) .duration(500) .remove(); nodesUpdate = g_nodes .selectAll("circle") .data(graph.nodes, function (d) { return d.id; }); nodesEnter = nodesUpdate.enter() .append("circle") .style("opacity", 1) .attr("r", function (d) { return Math.log(d.value); }) .attr("fill", function (d) { return color(d.group); }) .attr("stroke-opacity", 0.4) .attr("stroke-width", 1) .call(d3.drag() .on("start", dragstarted) .on("drag", dragged) .on("end", dragended)) .on('mouseover', tool_tip.show) //Added .on('mouseout', tool_tip.hide); //Added nodesMerge = nodesUpdate.merge(nodesEnter); nodesExit = nodesUpdate.exit().transition() .style("opacity", 0) .duration(500) .remove(); nodesMerge.append("title") .attr("dx", 12) .attr("dy", ".35em") .text(function (d) { return d.has_ip_layer; }); console.log('graph.nodes BEFORE', JSON.parse(JSON.stringify(graph.nodes))) simulation .nodes(graph.nodes) .on("tick", ticked) .force("link") .links(graph.links); console.log('graph.nodes AFTER', JSON.parse(JSON.stringify(graph.nodes))) } function ticked() { linksMerge .attr("x1", function (d) { return d.source.x; }) .attr("y1", function (d) { return d.source.y; }) .attr("x2", function (d) { return d.target.x; }) .attr("y2", function (d) { return d.target.y; }); nodesMerge .attr("cx", function (d) { return d.x; }) .attr("cy", function (d) { return d.y; }); } });