Added simple topology gui

Change-Id: I313e8460c3b2c9c58ac815f64a24e9bc2587f0b6
diff --git a/web/js/topo.js b/web/js/topo.js
new file mode 100644
index 0000000..b518bd2
--- /dev/null
+++ b/web/js/topo.js
@@ -0,0 +1,435 @@
+function convert_to_topodata(switches, links, registry){
+    var controllers={};
+    var nr_controllers=0;
+    var sws=[];
+    var ls=[];
+    var topo = new Array();
+    switches.forEach(function(item) {
+	var sw={}
+	sw.name=item.dpid;
+	sw.group=-1;
+	sws.push(sw);
+    });
+    for (var r in registry){
+	if ( ! (registry[r][0]['controllerId'] in controllers) ){
+	    controllers[registry[r][0]['controllerId']] = ++nr_controllers;
+	}
+    }
+    for (var i = 0; i < sws.length; i++){
+	if (sws[i].name in registry){
+	    sws[i].group=controllers[registry[sws[i].name][0]['controllerId']];
+	    sws[i].controller=registry[sws[i].name][0]['controllerId'];
+	}
+    }
+    links.forEach(function(item) {
+	var link={};
+	for (var i = 0; i < sws.length; i++){
+	    if(sws[i].name == item['src-switch'])
+		break;
+	}
+	link.source=i;
+	for (var i = 0; i < sws.length; i++){
+	    if(sws[i].name == item['dst-switch'])
+		break;
+	}
+	link.target=i;
+	ls.push(link);
+    });
+    topo['nodes']=sws;
+    topo['links']=ls;
+    return topo;
+}
+
+var width = 1280;
+var height = 1280;
+var radius = 8;
+function gui(switch_url, link_url, registry_url){
+    var svg = d3.select("#topology")
+	.append("svg:svg")
+	.attr("width", width)
+	.attr("height", height);
+
+    var force = d3.layout.force()
+	.charge(-500)
+	.linkDistance(100)
+	.size([width, height]);
+
+    var node_drag = d3.behavior.drag()
+        .on("dragstart", dragstart)
+        .on("drag", dragmove)
+        .on("dragend", dragend);
+
+    var color = d3.scale.category20();
+    var topodata;
+    var nodes = force.nodes();
+    var links = force.links();
+
+    d3.json(switch_url, function(error, rest_switches) {
+	d3.json(link_url, function(error, rest_links) { 
+	    d3.json(registry_url, function(error, rest_registry) { 
+		topodata = convert_to_topodata(rest_switches, rest_links, rest_registry);
+		init(topodata, nodes, links);
+		path = svg.append("svg:g").selectAll("path").data(links);
+		circle = svg.append("svg:g").selectAll("circle").data(nodes);
+		text = svg.append("svg:g").selectAll("text").data(nodes);
+		draw();
+	    });
+	}); 
+    }); 
+
+    setInterval(function(){ 
+	d3.json(switch_url, function(error, rest_switches) {
+	    d3.json(link_url, function(error, rest_links) { 
+		d3.json(registry_url, function(error, rest_registry) { 
+		    topodata = convert_to_topodata(rest_switches, rest_links, rest_registry);
+		    var changed = update(topodata, nodes, links);
+		    path = svg.selectAll("path").data(links)
+		    circle = svg.selectAll("circle").data(nodes);
+		    text = svg.selectAll("text").data(nodes);
+		    if ( changed ){
+			draw();
+		    }
+		});
+	    }); 
+	}); 
+    }, 3000); 
+
+    function draw(){
+	force.stop();
+	svg.append("svg:text")
+	    .attr("x", 50)
+	    .attr("y", 20)
+            .text(function(){return "Switch: " + force.nodes().length + " (Active: " + nr_active_sw()  + ")/ Link: " + force.links().length});
+    
+	path.enter().append("svg:path")
+	    .attr("class", function(d) { return "link"; })
+	    .attr("marker-end", function(d) {
+		if(d.type == 1){
+		    return "url(#TriangleRed)";
+		} else {
+		    return "url(#Triangle)";
+		}
+	    });
+	
+	circle.enter().append("svg:circle")
+	    .attr("r", function(d) { 
+		if (d.group == 1000){
+		    return radius;
+		}else{
+		    return radius;
+		}
+	    })
+	    .call(node_drag);
+    
+	text.enter().append("svg:text")
+	    .attr("x", radius)
+	    .attr("y", ".31em")
+	    .text(function(d) { 
+		l=d.name.split(":").length
+		return d.name.split(":")[l-2] + ":" + d.name.split(":")[l-1]
+	    });
+    
+	circle.append("title")
+	    .text(function(d) { return d.name; });
+    
+	circle.attr("fill", function(d) {
+            if (d.group == 1){
+		return "red"
+            }else if (d.group == 2){
+		return "blue"
+            }else if (d.group == 3){
+		return "green"
+            }else if (d.group == 4){
+		return "orange"
+            }else if (d.group == 5){
+		return "cyan"
+            }else if (d.group == 6){
+		return "magenta"
+            }else if (d.group == 7){
+		return "yellow"
+            }else if (d.group == 8){
+		return "purple"
+            }else{
+		return "gray"
+            }
+	});
+
+        circle.on('mouseover', function(d){
+   	 var nodeSelection = d3.select(this).style({opacity:'0.8'});
+  	  nodeSelection.select("text").style({opacity:'1.0'});
+	});
+
+
+	path.attr("stroke", function(d) {
+	    if(d.type == 1){
+		return "red"
+	    } else {
+		return "black"
+	    }
+	}).attr("stroke-width", function(d) {
+	    if(d.type == 1){
+		return "2px";
+	    } else {
+		return "1.5px";
+	    }
+	}).attr("marker-end", function(d) {
+	    if(d.type == 1){
+		return "url(#TriangleRed)";
+	    } else {
+		return "url(#Triangle)";
+	    }
+	});
+	path.exit().remove();
+	circle.exit().remove();
+	text.exit().remove();
+	force.on("tick", tick);
+	force.start();
+    }
+    function nr_active_sw(){
+        var n=0; 
+        var nodes = force.nodes();
+        for(var i=0;i<nodes.length;i++){
+          if(nodes[i].group!=0)
+            n++;
+        }; 
+        return n;
+    }
+    function dragstart(d, i) {
+        force.stop() // stops the force auto positioning before you start dragging
+    }
+    function dragmove(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 dragend(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();
+    }
+    function tick() {
+	path.attr("d", function(d) {
+	    var dx = d.target.x - d.source.x,
+	    dy = d.target.y - d.source.y,
+	    dr = 1/d.linknum;  //linknum is defined above
+	    dr = 0;  // 0 for direct line
+	    return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
+	});
+	path.attr("stroke", function(d) {
+	    if(d.type == 1){
+		return "red"
+	    } else {
+		return "black"
+	    }
+	}).attr("stroke-width", function(d) {
+	    if(d.type == 1){
+		return "3px";
+	    } else {
+		return "1.5px";
+	    }
+	}).attr("marker-end", function(d) {
+	    if(d.type == 1){
+		return "url(#TriangleRed)";
+	    } else {
+		return "url(#Triangle)";
+	    }
+	});
+//	circle.attr("cx", function(d) { return d.x; }).attr("cy", function(d) { return d.y; });
+	circle.attr("transform", function(d) {
+	    x = Math.max(radius, Math.min(width - radius, d.x));
+	    y = Math.max(radius, Math.min(height - radius, d.y)); 
+//	    return "translate(" + d.x + "," + d.y + ")";
+	    return "translate(" + x + "," + y + ")";
+	})
+	circle.attr("fill", function(d) {
+                if (d.group == 1){
+                    return "red"
+                }else if (d.group == 2){
+                    return "blue"
+                }else if (d.group == 3){
+                    return "green"
+                }else if (d.group == 4){
+                    return "orange"
+                }else if (d.group == 5){
+                    return "cyan"
+                }else if (d.group == 6){
+                    return "magenta"
+                }else if (d.group == 7){
+                    return "yellow"
+                }else if (d.group == 8){
+                    return "purple"
+                }else{
+                    return "gray"
+                }
+	});
+//	text.attr("x", function(d) { return d.x; }).attr("y", function(d) { return d.y; });
+	text.attr("transform", function(d) {
+	    return "translate(" + d.x + "," + d.y + ")";
+	});
+    }
+
+}
+
+
+function init(topodata, nodes, links){
+    topodata.nodes.forEach(function(item) {
+        nodes.push(item);
+    });
+    topodata.links.forEach(function(item) {
+        links.push(item);
+    });
+    links.sort(compare_link);
+    // distinguish links that have the same src & dst node by 'linknum'
+    for (var i=1; i < links.length; i++) {
+        if (links[i].source == links[i-1].source &&
+            links[i].target == links[i-1].target) {
+            links[i].linknum = links[i-1].linknum + 1;
+        } else {
+	    links[i].linknum = 1;
+	};
+    };
+}
+
+function compare_link (a, b){
+    if (a.source > b.source)
+	return 1;
+    else if (a.source < b.source)
+	return -1;
+    else {
+        if (a.target > b.target)
+	    return 1;
+        else if (a.target < b.target)
+	    return -1;
+        else
+	    return 0;
+    }
+}
+
+/* Return nodes that is not in the current list of nodes */
+Array.prototype.node_diff = function(arr) {
+    return this.filter(function(i) {
+	for (var j = 0; j < arr.length ; j++) {
+	    if (arr[j].name === i.name)
+		return false;
+	}
+	return true;
+    });
+};
+
+/* Return removed links */
+function gone_links (topo_json, links){
+    gone = []
+    for (var i = 0; i < links.length ; i ++){
+	var found = 0;
+	for (var j = 0; j < topo_json.links.length ; j ++){
+	    if (links[i].source.name == topo_json.nodes[topo_json.links[j].source].name && 
+		links[i].target.name == topo_json.nodes[topo_json.links[j].target].name ){
+		found = 1;
+		break;
+	    }
+	}
+	if ( found == 0 ){
+	    gone.push(links[i]);
+	}
+    }
+    return gone;
+}
+
+/* Return added links */
+function added_links (topo_json, links) {
+    added = [];
+    for (var j = 0; j < topo_json.links.length ; j ++){
+	var found = 0;
+	for (var i = 0; i < links.length ; i ++){
+	    if (links[i].source.name == topo_json.nodes[topo_json.links[j].source].name && 
+		links[i].target.name == topo_json.nodes[topo_json.links[j].target].name ){
+		found = 1;
+		break;
+	    }
+	}
+	if ( found == 0 ){
+	    added.push(topo_json.links[j]);
+	}
+    }
+    return added;
+}
+
+/* check if toplogy has changed and update node[] and link[] accordingly */
+function update(json, nodes, links){
+    var changed = false;
+    var n_adds = json.nodes.node_diff(nodes);
+    var n_rems = nodes.node_diff(json.nodes);
+    for (var i = 0; i < n_adds.length; i++) {
+	nodes.push(n_adds[i]);
+	changed = true;
+    }
+    for (var i = 0; i < n_rems.length; i++) {
+	for (var j = 0; j < nodes.length; j++) {
+	    if ( nodes[j].name == n_rems[i].name ){
+		nodes.splice(j,1);
+		changed = true;
+		break;
+	    }
+	}
+    }
+    var l_adds = added_links(json, links);
+    var l_rems = gone_links(json, links);
+    for (var i = 0; i < l_rems.length ; i++) {
+	for (var j = 0; j < links.length; j++) {
+            if (links[j].source.name == l_rems[i].source.name &&
+		links[j].target.name == l_rems[i].target.name) {
+		links.splice(j,1);
+		changed = true;
+		break;
+            }
+	}
+    }
+    // Sorce/target of an element of l_adds[] are corresponding to the index of json.node[]
+    // which is different from the index of node[] (new nodes are always added to the last)
+    // So update soure/target node indexes of l_add[] need to be fixed to point to the proper
+    // node in node[];
+    for (var i = 0; i < l_adds.length; i++) {
+	for (var j = 0; j < nodes.length; j++) {
+	    if ( json.nodes[l_adds[i].source].name == nodes[j].name ){
+		l_adds[i].source = j; 
+		break;
+	    }
+	}
+	for (var j = 0; j < nodes.length; j++) {
+	    if ( json.nodes[l_adds[i].target].name == nodes[j].name ){
+		l_adds[i].target = j;
+		break;
+	    }
+	}
+	links.push(l_adds[i]);
+	changed = true;
+    }
+
+    // Update "group" attribute of nodes
+    for (var i = 0; i < nodes.length; i++) {
+        for (var j = 0; j < json.nodes.length; j++) {
+	    if ( nodes[i].name == json.nodes[j].name ){
+		if (nodes[i].group != json.nodes[j].group){
+		    nodes[i].group = json.nodes[j].group;
+		    changed = true;
+		}
+	    }
+	}
+    }
+    for (var i = 0; i < links.length; i++) {
+        for (var j = 0; j < json.links.length; j++) {
+	    if (links[i].target.name == json.nodes[json.links[j].target].name && 
+		links[i].source.name == json.nodes[json.links[j].source].name ){
+		if (links[i].type != json.links[j].type){
+		    links[i].type = json.links[j].type;
+		    changed = true;
+		}
+	    }
+	}
+    }
+    return changed
+}
+