How to draw Network Topology graph using D3.js?

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.

topology

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>