implement interactions for link up/down flow up/down
setup controller interface. link up/down not currently working (or I'm not using them correctly)
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index d4cb18f..8cb50ef 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -201,22 +201,28 @@
   return angle * (Math.PI / 180);
 }
 
+var widths = {
+	edge: 6,
+	aggregation: 12,
+	core: 18
+}
+
 function createRingsFromModel(model) {
 	var rings = [{
 		radius: 3,
-		width: 6,
+		width: widths.edge,
 		switches: model.edgeSwitches,
 		className: 'edge',
 		angles: []
 	}, {
 		radius: 2.25,
-		width: 12,
+		width: widths.aggregation,
 		switches: model.aggregationSwitches,
 		className: 'aggregation',
 		angles: []
 	}, {
 		radius: 0.75,
-		width: 18,
+		width: widths.core,
 		switches: model.coreSwitches,
 		className: 'core',
 		angles: []
@@ -298,11 +304,88 @@
 	return testRings;
 }
 
+function createLinkMap(model) {
+	var linkMap = {};
+	model.links.forEach(function (link) {
+		var srcDPID = link['src-switch'];
+		var dstDPID = link['dst-switch'];
+
+		var srcMap = linkMap[srcDPID] || {};
+		var dstMap = linkMap[dstDPID] || {};
+
+		srcMap[dstDPID] = true;
+		dstMap[srcDPID] = true;
+
+		linkMap[srcDPID]  = srcMap;
+		linkMap[dstDPID]  = dstMap;
+	});
+	return linkMap;
+}
+
 updateTopology = function(svg, model) {
 
 	// DRAW THE SWITCHES
 	var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
 
+	var linkMap = createLinkMap(model);
+//	var flowMap = createFlowMap(model);
+
+	function mouseOver(data) {
+		if (data.highlighted) {
+			return;
+		}
+
+		// only highlight valid link or flow destination by checking for class of existing highlighted circle
+		var highlighted = svg.selectAll('circle.highlight')[0];
+		if (highlighted.length == 1) {
+			var s = d3.select(highlighted[0]);
+			// only allow links
+			// 	edge->edge (flow)
+			//  aggregation->core
+			//	core->core
+			if (data.className == 'edge' && !s.classed('edge') ||
+				data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
+				data.className == 'aggregation' && !s.classed('core')) {
+				return;
+			}
+
+			// don't highlight if there's already a link or flow
+			// var map = linkMap[data.dpid];
+			// console.log(map);
+			// console.log(s.data()[0].dpid);
+			// console.log(map[s.data()[0].dpid]);
+			// if (map && map[s.data()[0].dpid]) {
+			// 	return;
+			// }
+
+			// the second highlighted switch is the target for a link or flow
+			data.target = true;
+		}
+
+
+		d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
+		var node = d3.select(document.getElementById(data.dpid));
+		node.select('circle').classed('highlight', true).transition().duration(100).attr("r", widths.core);
+		data.highlighted = true;
+		node.moveToFront();
+	}
+
+	function mouseOut(data) {
+		if (data.mouseDown)
+			return;
+
+		d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
+		var node = d3.select(document.getElementById(data.dpid));
+		node.select('circle').classed('highlight', false).transition().duration(100).attr("r", widths[data.className]);
+		data.highlighted = false;
+		data.target = false;
+	}
+
+	function mouseDown(data) {
+		mouseOver(data);
+		data.mouseDown = true;
+	}
+
 	function ringEnter(data, i) {
 		if (!data.length) {
 			return;
@@ -314,7 +397,6 @@
 				return data.dpid;
 			})
 			.enter().append("svg:g")
-			.classed('nolabel', true)
 			.attr("id", function (data, i) {
 				return data.dpid;
 			})
@@ -342,16 +424,9 @@
 			});
 
 		// setup the mouseover behaviors
-		function showLabel(data, index) {
-			d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
-		}
-
-		function hideLabel(data, index) {
-			d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
-		}
-
-		nodes.on('mouseover', showLabel);
-		nodes.on('mouseout', hideLabel);
+		nodes.on('mouseover', mouseOver);
+		nodes.on('mouseout', mouseOut);
+		nodes.on('mousedown', mouseDown)
 	}
 
 	// append switches
@@ -383,6 +458,58 @@
 	// always on top
 	var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
 
+	d3.select(document.body).on('mouseup', function () {
+		function clearHighlight() {
+			svg.selectAll('circle').each(function (data) {
+				data.mouseDown = false;
+				mouseOut(data);
+			})
+		};
+
+
+		var highlighted = svg.selectAll('circle.highlight')[0];
+		if (highlighted.length == 2) {
+			var s1Data = d3.select(highlighted[0]).data()[0];
+			var s2Data = d3.select(highlighted[1]).data()[0];
+
+			var srcData, dstData;
+			if (s1Data.target) {
+				dstData = s1Data;
+				srcData = s2Data;
+			} else {
+				dstData = s2Data;
+				srcData = s1Data;
+			}
+
+			if (s1Data.className == 'edge' && s2Data.className == 'edge') {
+				var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
+				if (confirm(prompt)) {
+					alert('do create flow');
+				} else {
+					alert('do not create flow');
+				}
+			} else {
+				var map = linkMap[srcData.dpid];
+				if (map && map[dstData.dpid]) {
+					var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
+					if (confirm(prompt)) {
+						linkDown(srcData, dstData);
+					}
+				} else {
+					var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
+					if (confirm(prompt)) {
+						linkUp(srcData, dstData);
+					}
+				}
+			}
+
+			clearHighlight();
+		} else {
+			clearHighlight();
+		}
+
+	});
+
 	function labelRingEnter(data) {
 		if (!data.length) {
 			return;
@@ -400,7 +527,7 @@
 			})
 			.attr("transform", function(data, i) {
 				return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
-			});
+			})
 
 		// add the text nodes which show on mouse over
 		nodes.append("svg:text")
@@ -457,29 +584,10 @@
 		.attr("class", "textRing")
 		.each(labelRingEnter);
 
-
 	// 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.dpid)).select('circle');
-			g.transition().duration(100).attr("r", data.width*3);
-			// TODO: this doesn't work because the data binding is by index
-			d3.select(this.parentNode).moveToFront();
-	}
-
-	svg.selectAll('.edge').on('mouseover', zoom);
-	svg.selectAll('.edge').on('mousedown', zoom);
-
-	function unzoom(data, index) {
-		var g = d3.select(document.getElementById(data.dpid)).select('circle');
-			g.transition().duration(100).attr("r", data.width);
-	}
-	svg.selectAll('.edge').on('mouseout', unzoom);
-
-
 	// DRAW THE LINKS
 
 	// key on link dpids since these will come/go during demo
@@ -572,7 +680,7 @@
 function sync(svg, selectedFlowsView) {
 	var d = Date.now();
 	updateModel(function (newModel) {
-		console.log('Update time: ' + (Date.now() - d)/1000 + 's');
+//		console.log('Update time: ' + (Date.now() - d)/1000 + 's');
 
 		if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
 			updateControllers(newModel);
@@ -588,7 +696,7 @@
 			updateFlowView(newModel);
 			updateTopology(svg, newModel);
 		} else {
-			console.log('no change');
+//			console.log('no change');
 		}
 		updateHeader(newModel);