During Network related web application development some time we required to draw Topology view. Recently I worked for the Customer Vodafone to design their mobile tracking system. Here I got an opportunity to create node & traffic related topology view. To achieve this in the app I used D3.js. D3.js is a popular JavaScript library to draw graphs using SVG. It accepts JSON formatted data. Due to the customer database is with MongoDB I preferred to use D3.js to draw their Network Topology.
In the below example I am not using any database to fetch the data from server. To show you how to create a Network Topology graph using D3.js here I declared some demo data in data variable. Which is in JSON format. To start with here I referred D3.js CDN link in head part of the HTML. In body part I have a div with id “topology”. Using D3.js select method I am appending SVG to the topology div. Width & Height are the parameters of the SVG. In this example not only I implemented topology view but also I added features like drag, zoom & tooltip.
To run this sample application copy the following code to a Notepad file & save it as html. Then open the HTML file. Make sure before run the file you are connected to internet. For nodes images here I added imageByType. You need to update image paths refer to your images.
Network Topology graph using D3.js
<!DOCTYPE html> <html> <head> <title>Network Topology graph using D3.js</title> <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/> <script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.5.3/d3.min.js"></script> <style type="text/css"> .online-color { stroke: #01ACC6; stroke-width: 1px; stroke-opacity: 1.0; } </style> </head> <body> <div id="topology"></div> <script type="text/javascript"> var data = { "status": 200, "content": { "directed": false, "links": [ { "status": "online", "source": 1, "target": 2 }, { "status": "online", "source": 2, "target": 5 }, { "status": "online", "source": 3, "target": 4 }, { "status": "online", "source": 3, "target": 6 } ], "multigraph": false, "graph": [], "nodes": [ { "status": "online", "forwarding_policy": "0", "type": "switch", "id": "00:00:00:00:00:00:00:01", "type_of_switch": "OF" }, { "status": "offline", "_id": "54af98aea1234b06bbac8d5d", "type": "host", "id": "10.0.0.4" }, { "status": "online", "forwarding_policy": "0", "type": "switch", "id": "00:00:00:00:00:00:00:03", "type_of_switch": "OF" }, { "status": "online", "forwarding_policy": "0", "type": "switch", "id": "00:00:00:00:00:00:00:02", "type_of_switch": "OF" }, { "status": "offline", "_id": "54af98aea1234b06bbac8d5e", "type": "host", "id": "10.0.0.1" }, { "status": "offline", "_id": "54af98aea1234b06bbac8d5f", "type": "host", "id": "10.0.0.3" }, { "status": "offline", "_id": "54af98aea1234b06bbac8d60", "type": "host", "id": "10.0.0.2" } ], "last_seen": "1421832648.22" }, "message": "success" }; var width = 1013, height = 578, linkedByIndex = {}, node=null, link=null, force=null, nodelinks = null, sourceStatus="", imageByType = { "host_online" : "images/ap.png", "switch_online" : "images/laptop.png", "host_offline" : "images/ipad.png", "switch_offline" : "images/ipad.png" }; function drawNetworkTopology(data) { force = d3.layout.force() .nodes(data.content.nodes) .links(data.content.links) .linkDistance(100) .charge(-600) .size([width, height]) .start(); //add zoom behavior to nodes var zoom = d3.behavior.zoom() .scaleExtent([1, 3]) .on("zoom", zoomed); //add drag behavior to nodes var drag = d3.behavior.drag() .on("dragstart", dragstarted) .on("drag", dragged) .on("dragend", dragended); //create svg element using d3 var svg = d3.select("div#topology").append("svg") .attr("viewBox", "0 0 " + width + " " + height ) .attr("preserveAspectRatio", "xMidYMid meet") .call(zoom); //append container lable to bounding box svg.append("text") .text("") .attr({ 'x' : width-70, 'y' : height-5, "text-anchor" : "middle", }); //add bounding box svg.append("rect") .attr({ "width" : width, "height" : height, }) .style({ "fill" : "none", }); //add container var containerGrp = svg.append("g"); //add group of all lines link = containerGrp .selectAll("line") .data(data.content.links) .enter().append("line") .attr({ "class" : function (data) { return data.status+"-color"; } }); //add group of all nodes node = containerGrp .selectAll(".node") .data(data.content.nodes) .enter().append("g") .attr({ "class" : "nodes", "cx" : function (d) { return d.x; }, "cy" : function (d) { return d.y; }, }) .call(drag); //add image to node dynamically node.append("image") .attr({ "xlink:href" : function (d) { if(d.status === ('online').toLocaleLowerCase() && d.forwarding_policy == 1) return "images/proactive_forward.png"; else return imageByType[d.type+"_"+d.status]; }, "x" : -15, "y" : -15, "width" : 30, "height" : 30, }) .on("click",function(d){ if (d3.event.defaultPrevented) return; // click suppressed }) .on("mouseover", mouseOverFunction) .on("mouseout", mouseOutFunction); //add labeled text to each node node.append("text") .attr({ "y" : 25, "text-anchor" : "middle" }) .text(function (d) { return d.id; }); //tick event of network node force .on("tick", tick); //map of all connected nodes index data.content.links.forEach(function (d) { linkedByIndex[d.source.index + "," + d.target.index] = true; }); var tooltip = d3.select("body").append("div") .attr("class", "tooltip") .style("opacity", 0); /** * Event - mouseover for network nodes * @param data */ function mouseOverFunction(d, i) { if (d3.event.defaultPrevented) return; tooltip.transition().duration(200).style("opacity", 1); tooltip.html("<B>Properties</B><BR/><b>ID:</b>" + data.content.nodes[i].id + "<BR/><b>Type:</b>" + data.content.nodes[i].type+"<BR/><b>Status:</b>" + data.content.nodes[i].status) .style({ "left" : (d3.event.pageX - 130) + "px", "top" : (d3.event.pageY + 10) + "px" }); } /** * Event-mouseout for network nodes */ function mouseOutFunction() { if (d3.event.defaultPrevented) return; tooltip.transition().duration(500).style("opacity", 0); } /** * check for nodes connection * @param a * @param b * @returns {Boolean} */ function isConnected(a, b) { return isConnectedAsTarget(a, b) || isConnectedAsSource(a, b) || a.index == b.index; } /** * check for node connection as soure * @param a * @param b * @returns{boolean} */ function isConnectedAsSource(a, b) { return linkedByIndex[a.index + "," + b.index]; } /** * check for node connection as target * @param a * @param b * @returns {boolean} */ function isConnectedAsTarget(a, b) { return linkedByIndex[b.index + "," + a.index]; } /** * This method can be used in conjunction with force.start() and force.stop() to compute a static layout. */ function tick() { node .attr({ "cx" : function (d) { return d.x = Math.max(15, Math.min(width - 15, d.x)); }, "cy" : function (d) { return d.y = Math.max(15, Math.min(height - 15, d.y)); }, "transform" : function (d) { return "translate(" + d.x + "," + d.y + ")"; } }); link .attr({ "x1" : function (d) { return d.source.x; }, "y1" : function (d) { return d.source.y; }, "x2" : function (d) { return d.target.x; }, "y2" : function (d) { return d.target.y; }, }); } /** * zoomed function */ function zoomed() { var e = d3.event, tx = Math.min(0, Math.max(e.translate[0], width - width * e.scale)), ty = Math.min(0, Math.max(e.translate[1], height - height * e.scale)); zoom.translate([tx, ty]); containerGrp.attr("transform", ["translate(" + [tx, ty] + ")", "scale(" + e.scale + ")"].join(" ")); } <span style="color:brown"><code>function dragstarted(d, i) { force.stop(); // stops the force auto positioning before you start dragging } function dragged(d, i) { d.px += d3.event.dx; d.py += d3.event.dy; d.x += d3.event.dx; d.y += d3.event.dy; tick(); // this is the key to make it work together with updating both px,py,x,y on d ! } function dragended(d, i) { d.fixed = true; // of course set the node to fixed so the force doesn't include the node in its auto positioning stuff tick(); force.resume(); } };// drawNetworkTopology() closed drawNetworkTopology(data); <span style="color:brown"> </body> </html>