dynamic update for links/switches
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index 6ff36ab..5063039 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -121,6 +121,13 @@
 	pointer-events: auto;
 }
 
+.colorInactive {
+	opacity: 1;
+	fill: #444;
+	background-color: #444;
+	color: #222;
+}
+
 .color0 {
 	opacity: .2;
 	pointer-events: none;
diff --git a/web/ons-demo/data/configuration.json b/web/ons-demo/data/configuration.json
index ab304eb..b36fced 100644
--- a/web/ons-demo/data/configuration.json
+++ b/web/ons-demo/data/configuration.json
@@ -5,14 +5,7 @@
 		"00:00:20:4e:7f:51:8a:35",
 		"00:00:00:00:00:00:ba:12",
 		"00:00:00:00:ba:5e:ba:13",
-		"00:00:00:16:97:08:9a:46",
-
-		"00:00:0e:46:7a:3a:69:45",
-		"00:00:16:00:9d:ff:8f:4f",
-		"00:00:5e:80:3f:db:d7:4d",
-		"00:00:82:4f:75:82:a3:4b",
-		"00:00:36:7d:90:c9:4f:49",
-		"00:00:4e:27:27:d7:48:45"
+		"00:00:00:16:97:08:9a:46"
 	],
 	"aggregation": [
 		"00:00:00:00:00:00:01:01",
@@ -23,5 +16,26 @@
 		"00:00:00:00:00:00:06:01",
 		"00:00:00:00:00:00:07:01",
 		"00:00:00:00:00:00:08:01"
-	]
+	],
+	"association": {
+		"00:00:00:08:a2:08:f9:01": [
+			"00:00:00:00:00:00:03:01"
+		],
+		"00:00:00:00:ba:5e:ba:11": [
+			"00:00:00:00:00:00:02:01"
+		],
+		"00:00:20:4e:7f:51:8a:35": [
+			"00:00:00:00:00:00:07:01"
+		],
+		"00:00:00:00:00:00:ba:12": [
+			"00:00:00:00:00:00:04:01",
+			"00:00:00:00:00:00:05:01"
+		],
+		"00:00:00:00:ba:5e:ba:13": [
+			"00:00:00:00:00:00:06:01"
+		],
+		"00:00:00:16:97:08:9a:46": [
+			"00:00:00:00:00:00:08:01"
+		]
+	}
 }
\ No newline at end of file
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index 0a16fc2..432710f 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -36,9 +36,7 @@
   return angle * (Math.PI / 180);
 }
 
-function updateTopology(svg, model) {
-
-	// DRAW THE NODES
+function createRingsFromModel(model) {
 	var rings = [{
 		radius: 3,
 		width: 6,
@@ -80,8 +78,6 @@
 		} else {
 			aggRange.max = angle;
 		}
-
-
 	});
 
 	// arrange aggregation switches to "fan out" to edge switches
@@ -96,39 +92,35 @@
 	// find the association between core switches and aggregation switches
 	var aggregationSwitchMap = {};
 	model.aggregationSwitches.forEach(function (s, i) {
-		aggregationSwitchMap[s.dpid] = i + 1;
+		aggregationSwitchMap[s.dpid] = i;
 	});
 
-	var coreSwitchMap = {};
-	model.coreSwitches.forEach(function (s, i) {
-		coreSwitchMap[s.dpid] = i + 1;
-	});
-
-	var coreLinks = {};
-	model.links.forEach(function (l) {
-		if (aggregationSwitchMap[l['src-switch']] && coreSwitchMap[l['dst-switch']]) {
-			coreLinks[l['dst-switch']] = aggregationSwitchMap[l['src-switch']] - 1;
-		}
-		if (aggregationSwitchMap[l['dst-switch']] && coreSwitchMap[l['src-switch']]) {
-			coreLinks[l['src-switch']] = aggregationSwitchMap[l['dst-switch']] - 1;
-		}
-	});
-
-
-
 	// put core switches next to linked aggregation switches
 	k = 360 / rings[2].switches.length;
 	rings[2].switches.forEach(function (s, i) {
 //		rings[2].angles[i] = k * i;
-		rings[2].angles[i] = rings[1].angles[coreLinks[s.dpid]];
+		var associatedAggregationSwitches = model.configuration.association[s.dpid];
+		// TODO: go between if there are multiple
+		var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
+
+		rings[2].angles[i] = rings[1].angles[index];
 	});
 
+	return rings;
+}
+
+function updateTopology(svg, model) {
+
+	// DRAW THE SWITCHES
+	var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
+
 	function ringEnter(data, i) {
 		if (!data.switches.length) {
 			return;
 		}
 
-
+		// create the nodes
+		// TODO: do this in two layers so that the text can always be on top
 		var nodes = d3.select(this).selectAll("g")
 			.data(d3.range(data.switches.length).map(function() {
 				return data;
@@ -142,10 +134,8 @@
 				return "rotate(" + data.angles[i]+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angles[i]) + ")";
 			});
 
+		// add the cirles representing the switches
 		nodes.append("svg:circle")
-			.attr('class', function (_, i)  {
-				return data.className + ' ' + controllerColorMap[data.switches[i].controller];
-			})
 			.attr("transform", function(_, i) {
 				var m = document.querySelector('#viewbox').getTransformToElement().inverse();
 				if (data.scale) {
@@ -156,10 +146,8 @@
 			.attr("x", -data.width / 2)
 			.attr("y", -data.width / 2)
 			.attr("r", data.width)
-			// .attr("fill", function (_, i) {
-			// 	return controllerColorMap[data.switches[i].controller]
-			// })
 
+		// add the text nodes which show on mouse over
 		nodes.append("svg:text")
 				.text(function (d, i) {return d.switches[i].dpid})
 				.attr("x", 0)
@@ -172,6 +160,7 @@
 					return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
 				})
 
+		// setup the mouseover behaviors
 		function showLabel(data, index) {
 			d3.select(document.getElementById(data.switches[index].dpid)).classed('nolabel', false);
 		}
@@ -184,27 +173,44 @@
 		nodes.on('mouseout', hideLabel);
 	}
 
-	var ring = svg.selectAll("g")
-		.data(rings)
-		.enter().append("svg:g")
+	// append switches
+	rings.enter().append("svg:g")
 		.attr("class", "ring")
 		.each(ringEnter);
 
 
+	function ringUpdate(data, i) {
+		nodes = d3.select(this).selectAll("circle");
+		nodes.attr('class', function (_, i)  {
+				if (data.switches[i].state == 'ACTIVE') {
+					return data.className + ' ' + controllerColorMap[data.switches[i].controller];
+				} else {
+					return data.className + ' ' + 'colorInactive';
+				}
+			})
+	}
+
+	// update  switches
+	rings.each(ringUpdate);
+
+	// switches should not change during operation of the ui so no
+	// rings.exit()
+
+
 	// do mouseover zoom on edge nodes
-	function zoom(data, index) {
-		var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
-			g.transition().duration(100).attr("r", rings[0].width*3);
-	}
+	// function zoom(data, index) {
+	// 	var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
+	// 		g.transition().duration(100).attr("r", rings[0].width*3);
+	// }
 
-	svg.selectAll('.edge').on('mouseover', zoom);
-	svg.selectAll('.edge').on('mousedown', zoom);
+	// svg.selectAll('.edge').on('mouseover', zoom);
+	// svg.selectAll('.edge').on('mousedown', zoom);
 
-	function unzoom(data, index) {
-		var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
-			g.transition().duration(100).attr("r", rings[0].width);
-	}
-	svg.selectAll('.edge').on('mouseout', unzoom);
+	// function unzoom(data, index) {
+	// 	var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
+	// 		g.transition().duration(100).attr("r", rings[0].width);
+	// }
+	// svg.selectAll('.edge').on('mouseout', unzoom);
 
 
 	// DRAW THE LINKS
@@ -217,20 +223,30 @@
 	    })
 	    .interpolate("basis");
 
-	d3.select('svg').selectAll('path').data(model.links).enter().append("svg:path").attr("d", function (d) {
+	// key on link dpids since these will come/go during demo
+	var links = d3.select('svg').selectAll('path').data(model.links, function (d) {
+			return d['src-switch']+'->'+d['dst-switch'];
+	});
+
+	// add new links
+	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') + 10;
+		srcPt.y = src.attr('y') + 10; // tmp: make up and down links distinguishable
 
 		var dstPt = document.querySelector('svg').createSVGPoint();
 		dstPt.x = dst.attr('x');
-		dstPt.y = dst.attr('y') - 10;
+		dstPt.y = dst.attr('y') - 10; // tmp: make up and down links distinguishable
 
 		return line([srcPt.matrixTransform(src[0][0].getCTM()), dstPt.matrixTransform(dst[0][0].getCTM())]);
 	});
+
+	// remove old links
+	links.exit().remove();
 }
 
 function updateControllers(model) {
@@ -288,7 +304,7 @@
 	updateModel(function (newModel) {
 		console.log('Update time: ' + (Date.now() - d)/1000 + 's');
 
-		if (!oldModel && JSON.stringify(oldModel) != JSON.stringify(newModel)) {
+		if (true || !oldModel && JSON.stringify(oldModel) != JSON.stringify(newModel)) {
 			updateControllers(newModel);
 			updateTopology(svg, newModel);
 		} else {
@@ -312,6 +328,7 @@
 setTimeout(function () {
 	// workaround for another Chrome v25 bug
 	// viewbox transform stuff doesn't work in combination with browser zoom
+	// also works in Chrome v27
 	d3.select('#svg-container').style('zoom',  window.document.body.clientWidth/window.document.width);
 	sync(svg);
 }, 100);
diff --git a/web/ons-demo/js/model.js b/web/ons-demo/js/model.js
index 1f90362..94bbdfe 100644
--- a/web/ons-demo/js/model.js
+++ b/web/ons-demo/js/model.js
@@ -8,7 +8,8 @@
 		flows: [],
 		controllers: results.controllers,
 		activeControllers: results.activeControllers,
-		links: results.links
+		links: results.links,
+		configuration: results.configuration
 	}
 
 	// sort the switches
@@ -35,7 +36,10 @@
 	});
 
 	results.switches.forEach(function (s) {
-		s.controller = results.mapping[s.dpid][0].controllerId;
+		var mapping = results.mapping[s.dpid]
+		if (mapping) {
+			s.controller = mapping[0].controllerId;
+		}
 
 		if (coreSwitchDPIDs[s.dpid]) {
 			model.coreSwitches.push(s);