refactor for new topology view
diff --git a/web/ons-demo/js/flows.js b/web/ons-demo/js/flows.js
new file mode 100644
index 0000000..29fc4fa
--- /dev/null
+++ b/web/ons-demo/js/flows.js
@@ -0,0 +1,408 @@
+function updateSelectedFlowsTopology() {
+	// DRAW THE FLOWS
+	var topologyFlows = [];
+	selectedFlows.forEach(function (flow) {
+		if (flow) {
+			topologyFlows.push(flow);
+		}
+	});
+
+	var flows = d3.select('svg').selectAll('.flow').data(topologyFlows);
+
+	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.exit().remove();
+
+	flows.attr('d', function (d) {
+			if (!d) {
+				return;
+			}
+			var pts = [];
+			if (!d.dataPath.flowEntries) {
+				// create a temporary vector to indicate the pending flow
+				var s1 = d3.select(document.getElementById(d.srcDpid));
+				var s2 = d3.select(document.getElementById(d.dstDpid));
+
+				var pt1 = document.querySelector('svg').createSVGPoint();
+				pt1.x = s1.attr('x');
+				pt1.y = s1.attr('y');
+				pt1 = pt1.matrixTransform(s1[0][0].getCTM());
+				pts.push(pt1);
+
+				var pt2 = document.querySelector('svg').createSVGPoint();
+				pt2.x = s2.attr('x');
+				pt2.y = s2.attr('y');
+				pt2 = pt2.matrixTransform(s2[0][0].getCTM());
+				pts.push(pt2);
+
+			} else {
+				d.dataPath.flowEntries.forEach(function (flowEntry) {
+					var s = d3.select(document.getElementById(flowEntry.dpid.value));
+					// s[0] is null if the flow entry refers to a non-existent switch
+					if (s[0][0]) {
+						var pt = document.querySelector('svg').createSVGPoint();
+						pt.x = s.attr('x');
+						pt.y = s.attr('y');
+						pt = pt.matrixTransform(s[0][0].getCTM());
+						pts.push(pt);
+					} else {
+						console.log('flow refers to non-existent switch: ' + flowEntry.dpid.value);
+					}
+				});
+			}
+			if (pts.length) {
+				return line(pts);
+			} else {
+				return "M0,0";
+			}
+		})
+		.attr('id', function (d) {
+			if (d) {
+				return makeFlowKey(d);
+			}
+		})
+		.classed('pending', function (d) {
+			return d && (d.createPending || d.deletePending);
+		});
+
+	// "marching ants"
+	flows.select('animate').attr('from', 500);
+
+}
+
+function updateSelectedFlowsTable() {
+	function rowEnter(d) {
+		var row = d3.select(this);
+		row.append('div').classed('deleteFlow', true);
+		row.append('div').classed('flowId', true);
+		row.append('div').classed('srcDPID', true);
+		row.append('div').classed('dstDPID', true);
+		row.append('div').classed('iperf', true);
+
+		row.select('.iperf')
+			.append('div')
+			.attr('class', 'iperf-container')
+			.append('svg:svg')
+			.attr('viewBox', '0 0 1000 32')
+			.attr('preserveAspectRatio', 'none')
+			.append('svg:g')
+			.append('svg:path')
+			.attr('class', 'iperfdata');
+
+		row.on('mouseover', function (d) {
+			if (d) {
+				var path = document.getElementById(makeFlowKey(d));
+				d3.select(path).classed('highlight', true);
+			}
+		});
+		row.on('mouseout', function (d) {
+			if (d) {
+				var path = document.getElementById(makeFlowKey(d));
+				d3.select(path).classed('highlight', false);
+			}
+		});
+	}
+
+	function rowUpdate(d) {
+		var row = d3.select(this);
+		row.attr('id', function (d) {
+			if (d) {
+				return makeSelectedFlowKey(d);
+			}
+		});
+
+		if (!d || !hasIPerf(d)) {
+			row.select('.iperfdata')
+				.attr('d', 'M0,0');
+		}
+
+		row.select('.deleteFlow').on('click', function () {
+			deselectFlow(d);
+		});
+		row.on('dblclick', function () {
+			if (d) {
+				var prompt = 'Delete flow ' + d.flowId + '?';
+				if (confirm(prompt)) {
+					deleteFlow(d);
+					d.deletePending = true;
+					updateSelectedFlows();
+
+					setTimeout(function () {
+						d.deletePending = false;
+						updateSelectedFlows();
+					}, pendingTimeout)
+				};
+			}
+		});
+
+		row.select('.flowId')
+			.text(function (d) {
+				if (d) {
+					if (d.flowId) {
+						return d.flowId;
+					} else {
+						return '0x--';
+					}
+				}
+			})
+			.classed('pending', function (d) {
+				return d && (d.createPending || d.deletePending);
+			});
+
+		row.select('.srcDPID')
+			.text(function (d) {
+				if (d) {
+					return d.srcDpid;
+				}
+			});
+
+		row.select('.dstDPID')
+			.text(function (d) {
+				if (d) {
+					return d.dstDpid;
+				}
+			});
+	}
+
+	var flows = d3.select('#selectedFlows')
+		.selectAll('.selectedFlow')
+		.data(selectedFlows);
+
+	flows.enter()
+		.append('div')
+		.classed('selectedFlow', true)
+		.each(rowEnter);
+
+	flows.each(rowUpdate);
+
+	flows.exit().remove();
+}
+
+// TODO: cancel the interval when the flow is desel
+function startIPerfForFlow(flow) {
+	var duration = 10000; // seconds
+	var interval = 100; // ms. this is defined by the server
+	var updateRate = 2000; // ms
+	var pointsToDisplay = 1000;
+
+	function makePoints() {
+		var pts = [];
+		var i;
+		for (i=0; i < pointsToDisplay; ++i) {
+			var sample = flow.iperfData.samples[i];
+			var height = 30 * sample/1000000;
+			if (height > 30)
+				height = 30;
+			pts.push({
+				x: i * 1000/(pointsToDisplay-1),
+				y: 32 - height
+			})
+		}
+		return pts;
+	}
+
+	if (flow.flowId) {
+		console.log('starting iperf for: ' + flow.flowId);
+		startIPerf(flow, duration, updateRate/interval);
+		flow.iperfDisplayInterval = setInterval(function () {
+			if (flow.iperfData) {
+				while (flow.iperfData.samples.length < pointsToDisplay) {
+					flow.iperfData.samples.push(0);
+				}
+				var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
+				iperfPath.attr('d', line(makePoints()));
+				flow.iperfData.samples.shift();
+			}
+
+
+		}, interval);
+		flow.iperfFetchInterval = setInterval(function () {
+			getIPerfData(flow, function (data) {
+				try {
+					if (!flow.iperfData) {
+						flow.iperfData = {
+							samples: []
+						};
+						var i;
+						for (i = 0; i < pointsToDisplay; ++i) {
+							flow.iperfData.samples.push(0);
+						}
+					}
+
+					var iperfData = JSON.parse(data);
+
+//				console.log(iperfData.timestamp);
+
+					// if the data is fresh
+					if (flow.iperfData.timestamp && iperfData.timestamp != flow.iperfData.timestamp) {
+
+						while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
+							flow.iperfData.samples.shift();
+						}
+
+						iperfData.samples.forEach(function (s) {
+							flow.iperfData.samples.push(s);
+						});
+					}
+					flow.iperfData.timestamp = iperfData.timestamp;
+				} catch (e) {
+					console.log('bad iperf data: ' + data);
+				}
+//				console.log(data);
+			});
+		}, updateRate/2); // over sample to avoid gaps
+	}
+}
+
+function updateSelectedFlows() {
+	// make sure that all of the selected flows are either
+	// 1) valid (meaning they are in the latest list of flows)
+	// 2) pending
+	if (model) {
+		var flowMap = {};
+		model.flows.forEach(function (flow) {
+			flowMap[makeFlowKey(flow)] = flow;
+		});
+
+		var newSelectedFlows = [];
+		selectedFlows.forEach(function (flow) {
+			if (flow) {
+				var liveFlow = flowMap[makeFlowKey(flow)];
+				if (liveFlow) {
+					newSelectedFlows.push(liveFlow);
+					liveFlow.deletePending = flow.deletePending;
+					liveFlow.iperfFetchInterval = flow.iperfFetchInterval;
+					liveFlow.iperfDisplayInterval = flow.iperfDisplayInterval;
+				} else if (flow.createPending) {
+					newSelectedFlows.push(flow);
+				} else if (hasIPerf(flow)) {
+					clearIPerf(flow);
+				}
+			}
+		});
+		selectedFlows = newSelectedFlows;
+	}
+	selectedFlows.forEach(function (flow) {
+		if (!hasIPerf(flow)) {
+			startIPerfForFlow(flow);
+		}
+	});
+	while (selectedFlows.length < 3) {
+		selectedFlows.push(null);
+	}
+
+	updateSelectedFlowsTable();
+	updateSelectedFlowsTopology();
+}
+
+function selectFlow(flow) {
+	var flowKey = makeFlowKey(flow);
+	var alreadySelected = false;
+	selectedFlows.forEach(function (f) {
+		if (f && makeFlowKey(f) === flowKey) {
+			alreadySelected = true;
+		}
+	});
+
+	if (!alreadySelected) {
+		selectedFlows.unshift(flow);
+		selectedFlows = selectedFlows.slice(0, 3);
+		updateSelectedFlows();
+	}
+}
+
+function hasIPerf(flow) {
+	return flow && flow.iperfFetchInterval;
+}
+
+function clearIPerf(flow) {
+	console.log('clearing iperf interval for: ' + flow.flowId);
+	clearInterval(flow.iperfFetchInterval);
+	delete flow.iperfFetchInterval;
+	clearInterval(flow.iperfDisplayInterval);
+	delete flow.iperfDisplayInterval;
+	delete flow.iperfData;
+}
+
+function deselectFlow(flow, ifCreatePending) {
+	var flowKey = makeFlowKey(flow);
+	var newSelectedFlows = [];
+	selectedFlows.forEach(function (flow) {
+		if (!flow ||
+				flowKey !== makeFlowKey(flow) ||
+				flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
+			newSelectedFlows.push(flow);
+		} else {
+			if (hasIPerf(flow)) {
+				clearIPerf(flow);
+			}
+		}
+	});
+	selectedFlows = newSelectedFlows;
+	while (selectedFlows.length < 3) {
+		selectedFlows.push(null);
+	}
+
+	updateSelectedFlows();
+}
+
+function deselectFlowIfCreatePending(flow) {
+	deselectFlow(flow, true);
+}
+
+function showFlowChooser() {
+	function rowEnter(d) {
+		var row = d3.select(this);
+
+		row.append('div')
+			.classed('black-eye', true).
+			on('click', function () {
+				selectFlow(d);
+			});
+
+		row.append('div')
+			.classed('flowId', true)
+			.text(function (d) {
+				return d.flowId;
+			});
+
+		row.append('div')
+			.classed('srcDPID', true)
+			.text(function (d) {
+				return d.srcDpid;
+			});
+
+
+		row.append('div')
+			.classed('dstDPID', true)
+			.text(function (d) {
+				return d.dstDpid;
+			});
+
+	}
+
+	var flows = d3.select('#flowChooser')
+		.append('div')
+		.style('pointer-events', 'auto')
+		.selectAll('.selectedFlow')
+		.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);
+}