Merge pull request #301 from pgreyson/master

partial flow chooser, bug fixes and improvements
diff --git a/web/ons-demo/RELEASE_NOTES.txt b/web/ons-demo/RELEASE_NOTES.txt
index 95786fd..48834fb 100644
--- a/web/ons-demo/RELEASE_NOTES.txt
+++ b/web/ons-demo/RELEASE_NOTES.txt
@@ -1,3 +1,13 @@
+** March 28, 2013 **
+- basic flow chooser
+	- click in "eye" to show full list
+	- click on "eye" in full list to monitor that flow in the top slot (and show the flow in topology)
+		- other flows get pushed down one slot
+		- when a flow is pushed off the list, it is no longer displayed in topology
+- bug fix for link disappearing after being added
+- color improvements
+- draw vector while linking to make it clearer what's going to happen
+
 ** March 27, 2013 **
 - click onos node "eye" icon to highlight switches associated with that controller
 - double click onos node else where to activate/deactivate
diff --git a/web/ons-demo/assets/eye.svg b/web/ons-demo/assets/black-eye.svg
similarity index 100%
rename from web/ons-demo/assets/eye.svg
rename to web/ons-demo/assets/black-eye.svg
diff --git a/web/ons-demo/assets/white-eye.svg b/web/ons-demo/assets/white-eye.svg
new file mode 100644
index 0000000..8f3b180
--- /dev/null
+++ b/web/ons-demo/assets/white-eye.svg
@@ -0,0 +1,32 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   version="1.1"
+   width="512"
+   height="512"
+   viewBox="0 0 512 512"
+   id="Layer_1"
+   xml:space="preserve"><metadata
+   id="metadata13"><rdf:RDF><cc:Work
+       rdf:about=""><dc:format>image/svg+xml</dc:format><dc:type
+         rdf:resource="http://purl.org/dc/dcmitype/StillImage" /><dc:title></dc:title></cc:Work></rdf:RDF></metadata><defs
+   id="defs11" />
+<g
+   id="g3"
+   style="fill:#ffffff;fill-opacity:1">
+	<path
+   d="m 506.637,242.501 c -5.362,-6.347 -11.263,-12.33 -17.171,-18.193 -31.897,-31.679 -68.549,-59.921 -108.648,-80.411 -25.618,-13.08 -53.038,-23.655 -81.451,-28.721 -14.453,-2.586 -28.617,-3.912 -43.474,-3.938 -14.447,0.025 -28.908,1.353 -43.361,3.938 -28.412,5.065 -55.775,15.641 -81.393,28.721 -40.102,20.489 -76.724,48.733 -108.622,80.411 -5.909,5.862 -11.794,11.847 -17.155,18.193 -7.147,8.484 -7.147,18.515 0,27 16.344,19.353 35.774,36.575 55.542,52.321 42.57,33.915 91.25,62.278 144.993,73.711 16.621,3.524 33.299,5.244 49.998,5.228 16.904,0.018 33.488,-1.702 50.107,-5.228 53.744,-11.433 102.534,-39.796 145.104,-73.711 19.768,-15.745 39.194,-32.969 55.538,-52.321 7.146,-8.483 7.139,-18.514 -0.007,-27 z M 255.892,354.552 c -54.334,-0.104 -98.348,-44.177 -98.348,-98.554 0,-54.351 44.014,-98.438 98.348,-98.543 54.809,0.104 98.347,44.192 98.347,98.543 0.001,54.376 -43.538,98.447 -98.347,98.554 z"
+   id="path5"
+   style="fill:#ffffff;fill-opacity:1" />
+	<path
+   d="m 255.86,217.881 c -21.06,0 -38.106,17.059 -38.106,38.115 0,21.068 17.047,38.123 38.106,38.123 21.058,0 38.124,-17.055 38.124,-38.123 0,-21.056 -17.067,-38.115 -38.124,-38.115 z"
+   id="path7"
+   style="fill:#ffffff;fill-opacity:1" />
+</g>
+</svg>
\ No newline at end of file
diff --git a/web/ons-demo/css/layout.default.css b/web/ons-demo/css/layout.default.css
index e46f165..018e728 100644
--- a/web/ons-demo/css/layout.default.css
+++ b/web/ons-demo/css/layout.default.css
@@ -44,25 +44,33 @@
 	-webkit-box-orient: vertical;
 }
 
+#flowChooser {
+	position: absolute;
+	top: 0px;
+	left: 0px;
+	height: 100%;
+	display: -webkit-box;
+	overflow: scroll;
+}
+
 .selectedFlow {
 	display: -webkit-box;
+	position: relative;
+}
+
+#showFlowChooser {
+	position: relative;
+	display: -webkit-box;
+	-webkit-box-pack: center;
+}
+
+.selectedFlow .srcDPID, .selectedFlow .dstDPID {
 	-webkit-user-select: auto;
 }
 
 #selectedFlowsHeader {
 	display: -webkit-box;
-}
-
-.flowIndex {
-	width: 1.5em;
-}
-
-.flowId {
-	width: 5em;
-}
-
-.srcDPID, .dstDPID {
-	width: 20em;
+	height: 1.5em;
 }
 
 .iperf {
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index 579d940..aebf68b 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -6,6 +6,14 @@
 	margin: 0px;
 }
 
+#topology.linking {
+	cursor: crosshair;
+}
+
+.nodrop {
+	cursor: not-allowed;
+}
+
 .status {
 	padding: 1em;
 }
@@ -87,23 +95,40 @@
 	background-color: black;
 }
 
+#flowChooser .selectedFlow {
+	background-color: rgba(255, 255, 255, .75);
+	color: black;
+}
+
+#flowChooser .flowId {
+	padding-left: 2em;
+}
+
+
 .selectedFlow.selected {
 	color: black;
 	background-color:#AAA;
 }
 
-circle.highlight {
+.highlight circle {
 	stroke: rgba(255, 255, 255, .5);
 	stroke-width: 2px;
 }
 
+#linkVector {
+	fill: none;
+	stroke-width: 1px;
+	stroke: rgba(255, 255, 255, .75);
+	pointer-events: none;
+}
+
 path {
 	pointer-events: none;
 }
 
 path.flow {
 	fill: none;
-	stroke-width: 3px;
+	stroke-width: 5px;
 	stroke: rgba(255, 255, 255, .35);
 }
 
@@ -111,13 +136,26 @@
 	border-top: 1px solid #AAA;
 }
 
-.flowIndex, .flowId, .srcDPID, .dstDPID, .iperf {
+#flowChooser {
+	pointer-events: none;
+	background-color: rgba(0, 0, 0, .25);
+}
+
+
+.flowId, .srcDPID, .dstDPID, .iperf {
 	display: -webkit-box;
 	-webkit-box-pack: center;
 	-webkit-box-align: center;
+	width: 3em;
 }
 
-.flowIndex, .flowId, .srcDPID, .dstDPID {
+
+.srcDPID, .dstDPID {
+	width: 12em;
+}
+
+
+.flowId, .srcDPID, .dstDPID {
 	border-right: 1px solid #AAA;
 }
 
@@ -132,13 +170,22 @@
 	position: relative;
 }
 
-.controllerEye {
+.black-eye {
 	position: absolute;
 	top: 0px;
 	left: 0px;
 	height: 100%;
-	width: 2em;
-	background-image: url('../assets/eye.svg');
+	width: 2.25em;
+	background-image: url('../assets/black-eye.svg');
+	background-size: auto 100%;
+	background-repeat: no-repeat;
+	background-position: .25em center;
+}
+
+.white-eye {
+	height: 100%;
+	width: 2.25em;
+	background-image: url('../assets/white-eye.svg');
 	background-size: auto 100%;
 	background-repeat: no-repeat;
 	background-position: .25em center;
@@ -202,14 +249,14 @@
 }
 
 .color1 {
-	opacity: .2;
+	opacity: .15;
 	pointer-events: none;
 	fill: #EC0033;
 	background-color: #EC0033;
 }
 
 .color2 {
-	opacity: .2;
+	opacity: .15;
 	fill: #FFBA00;
 	background-color: #FFBA00;
 }
diff --git a/web/ons-demo/index.html b/web/ons-demo/index.html
index e89f409..d157ecc 100644
--- a/web/ons-demo/index.html
+++ b/web/ons-demo/index.html
@@ -38,13 +38,13 @@
 	</div>
 </div>
 <div id='selectedFlowsHeader'>
-	<div class='flowIndex'></div>
-	<div class='flowId'>flow id</div>
+	<div id='showFlowChooser' class='flowId'><div class='white-eye'></div></div>
 	<div class='srcDPID'>src</div>
 	<div class='dstDPID'>dst</div>
 	<div class='iperf'>iperf</div>
 </div>
 <div id='selectedFlows'></div>
+<div id='flowChooser'></div>
 
 <script src="js/app.js"></script>
 </body>
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index daf767c..0fa5fa3 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -15,7 +15,7 @@
     });
 
 var model;
-var svg, selectedFlowsView;
+var svg;
 var updateTopology;
 var pendingLinks = {};
 
@@ -24,13 +24,10 @@
 	'color2',
 	'color3',
 	'color4',
-	'color5',
-	'color6',
 	'color7',
 	'color8',
 	'color9',
-	'color10',
-	'color11',
+//	'color11',
 	'color12'
 ];
 colors.reverse();
@@ -49,7 +46,7 @@
 	window.addEventListener('resize', function () {
 		// this is too slow. instead detect first resize event and hide the paths that have explicit matrix applied
 		// either that or is it possible to position the paths so they get the automatic transform as well?
-//		updateTopology(svg, model);
+//		updateTopology();
 	});
 
 	var svg = d3.select('#svg-container').append('svg:svg');
@@ -68,26 +65,31 @@
 			attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
 }
 
-var selectedFlowsData = [
-	{selected: false, flow: null},
-	{selected: false, flow: null},
-	{selected: false, flow: null}
-];
+var selectedFlows = [null, null, null];
 
 function drawFlows() {
 	// DRAW THE FLOWS
-	var flows = d3.select('svg').selectAll('.flow').data(selectedFlowsData, function (d) {
-		return d.flow ? d.flow.flowId.value : null;
+	var flows = d3.select('svg').selectAll('.flow').data(selectedFlows, function (d) {
+		return d ? d.flowId.value : null;
 	});
 
-	flows.enter().append("svg:path")
-	.attr('class', 'flow')
-	.attr('d', function (d) {
-		if (!d.flow) {
+	flows.enter().append("svg:path").attr('class', 'flow')
+		.attr('stroke-dasharray', '4, 10')
+		.append('svg:animate')
+		.attr('attributeName', 'stroke-dashoffset')
+		.attr('attributeType', 'xml')
+		.attr('from', '500')
+		.attr('to', '-500')
+		.attr('dur', '20s')
+		.attr('repeatCount', 'indefinite');
+
+
+	flows.attr('d', function (d) {
+		if (!d) {
 			return;
 		}
 		var pts = [];
-		d.flow.dataPath.flowEntries.forEach(function (flowEntry) {
+		d.dataPath.flowEntries.forEach(function (flowEntry) {
 			var s = d3.select(document.getElementById(flowEntry.dpid.value));
 			var pt = document.querySelector('svg').createSVGPoint();
 			pt.x = s.attr('x');
@@ -97,102 +99,111 @@
 		});
 		return line(pts);
 	})
-	.attr('stroke-dasharray', '3, 10')
-	.append('svg:animate')
-	.attr('attributeName', 'stroke-dashoffset')
-	.attr('attributeType', 'xml')
-	.attr('from', '500')
-	.attr('to', '-500')
-	.attr('dur', '20s')
-	.attr('repeatCount', 'indefinite');
-
-	flows.style('visibility', function (d) {
-		if (d) {
-			return d.selected ? '' : 'hidden';
-		}
-	})
 
 	// "marching ants"
-	// TODO: this will only be true if there's an iperf session running
-	flows.select('animate').attr('from', function (d) {
-		if (d.flow) {
-			if (d.selected) {
-				return '500';
-			} else {
-				return '-500';
-			}
-		}
-	});
+	flows.select('animate').attr('from', 500);
+
+	flows.exit().remove();
 }
 
-function updateFlowView() {
-	selectedFlowsView.data(selectedFlowsData);
-
-	selectedFlowsView.classed('selected', function (d) {
-		if (d.flow) {
-			return d.selected;
-		}
-	});
-
-	selectedFlowsView.select('.flowId')
-		.text(function (d) {
-			if (d.flow) {
-				return d.flow.flowId.value;
-			}
-		});
-
-	selectedFlowsView.select('.srcDPID')
-		.text(function (d) {
-			if (d.flow) {
-				return d.flow.dataPath.srcPort.dpid.value;
-			}
-		});
-
-	selectedFlowsView.select('.dstDPID')
-		.text(function (d) {
-			if (d.flow) {
-				return d.flow.dataPath.dstPort.dpid.value;
-			}
-		});
-}
-
-function createFlowView() {
-	function rowEnter(d, i) {
+function showFlowChooser() {
+	function rowEnter(d) {
 		var row = d3.select(this);
 
-		row.on('click', function () {
-			selectedFlowsData[i].selected = !selectedFlowsData[i].selected;
-			updateFlowView();
-			drawFlows();
-		});
-
 		row.append('div')
-			.classed('flowIndex', true)
-			.text(function () {
-				return i+1;
+			.classed('black-eye', true).
+			on('click', function () {
+				selectedFlows.unshift(d);
+				selectedFlows = selectedFlows.slice(0, 3);
+
+				updateSelectedFlows();
+				updateTopology();
 			});
 
 		row.append('div')
-			.classed('flowId', true);
+			.classed('flowId', true)
+			.text(function (d) {
+				return d.flowId.value;
+			});
 
 		row.append('div')
-			.classed('srcDPID', true);
+			.classed('srcDPID', true)
+			.text(function (d) {
+				return d.dataPath.srcPort.dpid.value;
+			});
+
 
 		row.append('div')
-			.classed('dstDPID', true);
+			.classed('dstDPID', true)
+			.text(function (d) {
+				return d.dataPath.dstPort.dpid.value;
+			});
 
-		row.append('div')
-			.classed('iperf', true);
 	}
 
-	var flows = d3.select('#selectedFlows')
+	var flows = d3.select('#flowChooser')
+		.append('div')
+		.style('pointer-events', 'auto')
 		.selectAll('.selectedFlow')
-		.data(selectedFlowsData)
+		.data(model.flows)
 		.enter()
 		.append('div')
 		.classed('selectedFlow', true)
 		.each(rowEnter);
 
+	setTimeout(function () {
+		d3.select(document.body).on('click', function () {
+			d3.select('#flowChooser').html('');
+			d3.select(document.body).on('click', null);
+		});
+	}, 0);
+}
+
+function updateSelectedFlows() {
+	function rowEnter(d) {
+		var row = d3.select(this);
+		row.append('div').classed('flowId', true);
+		row.append('div').classed('srcDPID', true);
+		row.append('div').classed('dstDPID', true);
+		row.append('div').classed('iperf', true);
+	}
+
+	function rowUpdate(d) {
+		var row = d3.select(this);
+		row.select('.flowId')
+			.text(function (d) {
+				if (d) {
+					return d.flowId.value;
+				}
+			});
+
+		row.select('.srcDPID')
+			.text(function (d) {
+				if (d) {
+					return d.dataPath.srcPort.dpid.value;
+				}
+			});
+
+		row.select('.dstDPID')
+			.text(function (d) {
+				if (d) {
+					return d.dataPath.dstPort.dpid.value;
+				}
+			});
+	}
+
+	var flows = d3.select('#selectedFlows')
+		.selectAll('.selectedFlow')
+		.data(selectedFlows);
+
+	flows.enter()
+		.append('div')
+		.classed('selectedFlow', true)
+		.each(rowEnter);
+
+	flows.each(rowUpdate);
+
+	flows.exit().remove();
 
 	return flows;
 }
@@ -329,12 +340,8 @@
 	return linkMap;
 }
 
-updateTopology = function(svg, model) {
-
-	// DRAW THE SWITCHES
-	var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
-
-
+// removes links from the pending list that are now in the model
+function reconcilePendingLinks(model) {
 	var links = [];
 	model.links.forEach(function (link) {
 		links.push(link);
@@ -344,19 +351,32 @@
 	for (linkId in pendingLinks) {
 		links.push(pendingLinks[linkId]);
 	}
+	return links
+}
 
+updateTopology = function() {
+
+	// DRAW THE SWITCHES
+	var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
+
+	var links = reconcilePendingLinks(model);
 	var linkMap = createLinkMap(links);
 //	var flowMap = createFlowMap(model);
 
 	function mouseOverSwitch(data) {
+
+		d3.event.preventDefault();
+
+		d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
+
 		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];
+		var highlighted = svg.selectAll('.highlight')[0];
 		if (highlighted.length == 1) {
-			var s = d3.select(highlighted[0]);
+			var s = d3.select(highlighted[0]).select('circle');
 			// only allow links
 			// 	edge->edge (flow)
 			//  aggregation->core
@@ -380,21 +400,20 @@
 			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);
+		node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
 		data.highlighted = true;
 		node.moveToFront();
 	}
 
 	function mouseOutSwitch(data) {
+		d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
+
 		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]);
+		node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
 		data.highlighted = false;
 		data.target = false;
 	}
@@ -402,12 +421,42 @@
 	function mouseDownSwitch(data) {
 		mouseOverSwitch(data);
 		data.mouseDown = true;
+		d3.select('#topology').classed('linking', true);
+
+		d3.select('svg')
+			.append('svg:path')
+			.attr('id', 'linkVector')
+			.attr('d', function () {
+				var s = d3.select(document.getElementById(data.dpid));
+
+				var pt = document.querySelector('svg').createSVGPoint();
+				pt.x = s.attr('x');
+				pt.y = s.attr('y');
+				pt = pt.matrixTransform(s[0][0].getCTM());
+
+				return line([pt, pt]);
+			});
+
+
+		if (data.className === 'core') {
+			d3.selectAll('.edge').classed('nodrop', true);
+		}
+		if (data.className === 'edge') {
+			d3.selectAll('.core').classed('nodrop', true);
+			d3.selectAll('.aggregation').classed('nodrop', true);
+		}
+		if (data.className === 'aggregation') {
+			d3.selectAll('.edge').classed('nodrop', true);
+			d3.selectAll('.aggregation').classed('nodrop', true);
+		}
 	}
 
 	function mouseUpSwitch(data) {
 		if (data.mouseDown) {
 			data.mouseDown = false;
+			d3.select('#topology').classed('linking', false);
 			d3.event.stopPropagation();
+			d3.selectAll('.nodrop').classed('nodrop', false);
 		}
 	}
 
@@ -515,14 +564,60 @@
 	// always on top
 	var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
 
+	d3.select(document.body).on('mousemove', function () {
+		if (!d3.select('#topology').classed('linking')) {
+			return;
+		}
+		var linkVector = document.getElementById('linkVector');
+		if (!linkVector) {
+			return;
+		}
+		linkVector = d3.select(linkVector);
+
+		var highlighted = svg.selectAll('.highlight')[0];
+		var s1 = null, s2 = null;
+		if (highlighted.length > 1) {
+			var s1 = d3.select(highlighted[0]);
+			var s2 = d3.select(highlighted[1]);
+
+		} else if (highlighted.length > 0) {
+			var s1 = d3.select(highlighted[0]);
+		}
+		var src = s1;
+		if (s2 && !s2.data()[0].target) {
+			src = s2;
+		}
+		if (src) {
+			linkVector.attr('d', function () {
+					var srcPt = document.querySelector('svg').createSVGPoint();
+					srcPt.x = src.attr('x');
+					srcPt.y = src.attr('y');
+					srcPt = srcPt.matrixTransform(src[0][0].getCTM());
+
+					var svg = document.getElementById('topology');
+					var mouse = d3.mouse(viewbox);
+					var dstPt = document.querySelector('svg').createSVGPoint();
+					dstPt.x = mouse[0];
+					dstPt.y = mouse[1];
+					dstPt = dstPt.matrixTransform(viewbox.getCTM());
+
+					return line([srcPt, dstPt]);
+				});
+		}
+	});
+
 	d3.select(document.body).on('mouseup', function () {
 		function clearHighlight() {
 			svg.selectAll('circle').each(function (data) {
 				data.mouseDown = false;
+				d3.select('#topology').classed('linking', false);
 				mouseOutSwitch(data);
-			})
+			});
+			d3.select('#linkVector').remove();
 		};
 
+		d3.selectAll('.nodrop').classed('nodrop', false);
+
 		function removeLink(link) {
 			var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
 			var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
@@ -538,10 +633,10 @@
 		}
 
 
-		var highlighted = svg.selectAll('circle.highlight')[0];
+		var highlighted = svg.selectAll('.highlight')[0];
 		if (highlighted.length == 2) {
-			var s1Data = d3.select(highlighted[0]).data()[0];
-			var s2Data = d3.select(highlighted[1]).data()[0];
+			var s1Data = highlighted[0].__data__;
+			var s2Data = highlighted[1].__data__;
 
 			var srcData, dstData;
 			if (s1Data.target) {
@@ -592,16 +687,16 @@
 								pending: true
 							};
 							pendingLinks[makeLinkKey(link2)] = link2;
-							updateTopology(svg, model);
+							updateTopology();
 
 							linkUp(link1);
 
-							// remove the pending link after 10s
+							// remove the pending links after 10s
 							setTimeout(function () {
 								delete pendingLinks[makeLinkKey(link1)];
 								delete pendingLinks[makeLinkKey(link2)];
 
-								updateTopology(svg, model);
+								updateTopology();
 							}, 10000);
 						}
 					}
@@ -718,7 +813,7 @@
 
 			var dstPt = document.querySelector('svg').createSVGPoint();
 			dstPt.x = dst.attr('x');
-			dstPt.y = dst.attr('y'); // tmp: make up and down links distinguishable
+			dstPt.y = dst.attr('y');
 			dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
 
 			var midPt = document.querySelector('svg').createSVGPoint();
@@ -740,7 +835,7 @@
 	drawFlows();
 }
 
-function updateControllers(model) {
+function updateControllers() {
 	var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
 	controllers.enter().append('div')
 		.each(function (c) {
@@ -751,7 +846,7 @@
 			return d;
 		})
 		.append('div')
-		.attr('class', 'controllerEye');
+		.attr('class', 'black-eye');
 
 	controllers.attr('class', function (d) {
 			var color = 'colorInactive';
@@ -781,7 +876,7 @@
 		}
 	});
 
-	controllers.select('.controllerEye').on('click', function (c) {
+	controllers.select('.black-eye').on('click', function (c) {
 		var allSelected = true;
 		for (var key in controllerColorMap) {
 			if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
@@ -806,30 +901,26 @@
 
 }
 
-function sync(svg, selectedFlowsView) {
+function sync(svg) {
 	var d = Date.now();
 	updateModel(function (newModel) {
 //		console.log('Update time: ' + (Date.now() - d)/1000 + 's');
 
+		var modelChanged = false;
 		if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
-			updateControllers(newModel);
-
-	// fake flows right now
-	var i;
-	for (i = 0; i < newModel.flows.length && i < selectedFlowsData.length; i+=1) {
-		var selected = selectedFlowsData[i] ? selectedFlowsData[i].selected : false;
-		selectedFlowsData[i].flow = newModel.flows[i];
-		selectedFlowsData[i].selected = selected;
-	}
-
-			updateFlowView(newModel);
-			updateTopology(svg, newModel);
+			modelChanged = true;
+			model = newModel;
 		} else {
 //			console.log('no change');
 		}
-		updateHeader(newModel);
 
-		model = newModel;
+		if (modelChanged) {
+			updateControllers();
+			updateSelectedFlows();
+			updateTopology();
+		}
+
+		updateHeader(newModel);
 
 		// do it again in 1s
 		setTimeout(function () {
@@ -839,7 +930,13 @@
 }
 
 svg = createTopologyView();
-selectedFlowsView = createFlowView();
+updateSelectedFlows();
+
+d3.select('#showFlowChooser').on('click', function () {
+	showFlowChooser();
+});
+
+
 // workaround for Chrome v25 bug
 // if executed immediately, the view box transform logic doesn't work properly
 // fixed in Chrome v27
@@ -848,5 +945,5 @@
 	// 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, selectedFlowsView);
+	sync(svg);
 }, 100);
diff --git a/web/ons-demo/js/utils.js b/web/ons-demo/js/utils.js
index 17100b1..4f6d0c1 100644
--- a/web/ons-demo/js/utils.js
+++ b/web/ons-demo/js/utils.js
@@ -11,4 +11,14 @@
 	}
 
 	return parameters;
+}
+
+function findLink(model, dpid) {
+	var links = [];
+	model.links.forEach(function (link) {
+		if (link['src-switch'] == dpid || link['dst-switch'] == dpid) {
+			links.push(link);
+		}
+	});
+	return links;
 }
\ No newline at end of file