first cut at edges. not quite working yet.
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index e578ba8..ce5752c 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -1,11 +1,33 @@
 /*global d3*/
 
+
+var colors = [
+	'#EC0033',
+	'#FFBA00',
+	'#3714B0',
+	'#B12C49',
+	'#BF9830',
+	'#402C84',
+	'#990021',
+	'#A67900',
+	'#F53D65',
+	'#1F0772',
+	'#F56E8B',
+	'#FFCB40',
+	'#6949D7',
+	'#FFD973'
+]
+
+var controllerColorMap = {};
+
+
+
 function createTopologyView() {
-	return d3.select('#svg-container').append('svg:svg').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
+	return d3.select('#svg-container').append('svg:svg').append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
 			attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
 }
 
-function drawHeader(model) {
+function updateHeader(model) {
 	d3.select('#lastUpdate').text(model.timestamp);
 	d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
 	d3.select('#activeFlows').text(model.flows.length);
@@ -15,21 +37,22 @@
   return angle * (Math.PI / 180);
 }
 
-function drawTopology(svg, model) {
+function updateTopology(svg, model) {
 
+	// DRAW THE NODES
 	var rings = [{
 		radius: 3,
 		width: 4,
 		switches: model.edgeSwitches,
 		className: 'edge'
 	}, {
-		radius: 1.5,
-		width: 32,
+		radius: 2.25,
+		width: 8,
 		switches: model.aggregationSwitches,
 		className: 'aggregation'
 	}, {
-		radius: 1,
-		width: 32,
+		radius: .75,
+		width: 16,
 		switches: model.coreSwitches,
 		className: 'core'
 	}];
@@ -47,7 +70,7 @@
 		}))
 			.enter().append("svg:g")
 			.attr("id", function (_, i) {
-				return data.className + i;
+				return data.switches[i].dpid;
 			})
 			.attr("transform", function(_, i) {
 				return "rotate(" + i * k + ")translate(" + data.radius * 150 + ")rotate(" + (-i * k) + ")";
@@ -55,7 +78,7 @@
 			.append("svg:circle")
 			.attr('class', data.className)
 			.attr("transform", function(_, i) {
-				var m = document.querySelector('svg').getTransformToElement().inverse();
+				var m = document.querySelector('#viewbox').getTransformToElement().inverse();
 				if (data.scale) {
 					m = m.scale(data.scale);
 				}
@@ -63,9 +86,10 @@
 			})
 			.attr("x", -data.width / 2)
 			.attr("y", -data.width / 2)
-		// .attr("width", data.width)
-		// .attr("height", data.width)
-		.attr("r", data.width)
+			.attr("r", data.width)
+			.attr("fill", function (_, i) {
+				return controllerColorMap[data.switches[i].controller]
+			})
 	}
 
 	var ring = svg.selectAll("g")
@@ -74,37 +98,73 @@
 		.attr("class", "ring")
 		.each(ringEnter);
 
-	function zoom(d, i) {
-		model.edgeSwitches.forEach(function (s) {
-			s.scale = 1;
-		});
-		d.scale = 2;
-
+	function zoom(data, index) {
 		svg.selectAll('.edge').data(model.edgeSwitches).transition().duration(100)
-			// .attr("transform", function(data, i) {
-			// 	var m = document.querySelector('svg').getTransformToElement().inverse();
-			// 		m = m.scale(data.scale);
-			// 	return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
-			// })
 			.attr("r", function (data, i) {
-				return rings[0].width * data.scale;
+				return rings[0].width * (index == i ? 2 : 1);
 			})
 	}
 
 	svg.selectAll('.edge').on('mouseover', zoom);
 	svg.selectAll('.edge').on('mousedown', zoom);
+
+	// DRAW THE LINKS
+	var line = d3.svg.line()
+	    .x(function(d) {
+	    	return d.x;
+	    })
+	    .y(function(d) {
+	    	return d.y;
+	    })
+	    .interpolate("basis");
+
+	d3.select('svg').selectAll('path').data(model.links).enter().append("svg:path").attr("d", function (d) {
+		var src = d3.select(document.getElementById(d['src-switch']));
+		var dst = d3.select(document.getElementById(d['dst-switch']));
+
+		var srcPt = document.querySelector('svg').createSVGPoint();
+		srcPt.x = src.attr('x');
+		srcPt.y = src.attr('y');
+
+		var dstPt = document.querySelector('svg').createSVGPoint();
+		dstPt.x = dst.attr('x');
+		dstPt.y = dst.attr('y');
+
+		return line([srcPt.matrixTransform(src[0][0].getCTM()), dstPt.matrixTransform(dst[0][0].getCTM())]);
+	});
+}
+
+function updateControllers(model) {
+	var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
+	controllers.enter().append('div').attr('class', 'controller')
+		.attr('style', function (d) {
+			var color = controllerColorMap[d];
+			if (!color) {
+				color = controllerColorMap[d] = colors.pop();
+			}
+			return 'background-color:' + color;
+		});
+	controllers.text(function (d) {
+		return d;
+	});
+	controllers.exit().remove();
+
+	controllers.on('click', function (data, index) {
+
+	});
 }
 
 function sync(svg) {
 	updateModel(function (model) {
 
-		drawHeader(model);
-		drawTopology(svg, model);
+		updateHeader(model);
+		updateControllers(model);
+		updateTopology(svg, model);
 
 		// do it again in 1s
 		setTimeout(function () {
 			sync(svg)
-		}, 5000);
+		}, 1000);
 	});
 }