use timestamps to display iperf data so that variability in server response time doesn't cause spurious gaps
diff --git a/web/ons-demo/js/flows.js b/web/ons-demo/js/flows.js
index ab5eddf..32f9347 100644
--- a/web/ons-demo/js/flows.js
+++ b/web/ons-demo/js/flows.js
@@ -206,20 +206,42 @@
 	var updateRate = 2000; // ms
 	var pointsToDisplay = 1000;
 
-	function makeGraph() {
-		var d = ['M0,30'];
-		var i;
-		for (i=0; i < pointsToDisplay; ++i) {
-			var sample = flow.iperfData.samples[i];
-			if (!sample) {
-				sample = 0;
+	function makeGraph(iperfData) {
+		var d = 'M0,0';
+
+		if (iperfData.samples && iperfData.samples.length) {
+
+			var lastX;
+			var i = iperfData.samples.length - 1;
+			while (i) {
+				var sample = iperfData.samples[i];
+
+				var x = (1000 - (iperfData.now - sample.time)*10);
+				var y = 28 * sample.value/1000000;
+				if (y > 28) {
+					y = 28;
+				}
+				if (i == iperfData.samples.length - 1) {
+					d = 'M' + x + ',30';
+				}
+
+				// handle gaps
+				// 1.5 for rounding error
+				if (lastX && lastX - x > 1.5) {
+					d += 'L' + lastX + ',30';
+					d += 'M' + x + ',30'
+				}
+				lastX = x;
+
+				d += 'L' + x + ',' + (30-y);
+
+				i -= 1;
+
+				if (!i) {
+					d += 'L' + x + ',30';
+				}
 			}
-			var height = 28 * sample/1000000;
-			if (height > 28)
-				height = 28;
-			d += 'L' + i * pointsToDisplay/(pointsToDisplay-1) + ',' + (30-height);
 		}
-		d += 'L'+pointsToDisplay + ',' + 30;
 		return d;
 	}
 
@@ -228,32 +250,24 @@
 		startIPerf(flow, duration, updateRate/interval);
 		flow.iperfDisplayInterval = setInterval(function () {
 			if (flow.iperfData) {
-				while (flow.iperfData.samples.length < pointsToDisplay) {
-					flow.iperfData.samples.push(null);
-				}
 				var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
-				iperfPath.attr('d', makeGraph());
-				flow.iperfData.samples.shift();
+				iperfPath.attr('d', makeGraph(flow.iperfData));
+				flow.iperfData.now += interval/1000;
 			}
 
 
 		}, interval);
 
 		var animationTimeout;
+		flow.iperfData = {
+			samples: []
+		}
 
+		var lastTime;
 		flow.iperfFetchInterval = setInterval(function () {
+			console.log('Requesting iperf data');
 			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);
@@ -269,12 +283,38 @@
 							stopFlowAnimation(flowSelection);
 						}, updateRate*1.5);
 
+						var endTime = Math.floor(iperfData['end-time']*10)/10;
+
+						var startTime = endTime - (iperfData.samples.length * interval/1000);
+						// set now on the first buffer
+						if (!flow.iperfData.now) {
+							flow.iperfData.now = startTime;
+						}
+
+						console.log('iperf buffer start time: ' + startTime);
+						if (lastTime && (startTime - lastTime) > updateRate/1000) {
+							console.log('iperf buffer gap: ' + startTime + ',' + lastTime);
+						}
+						lastTime = startTime;
+
+						// clear out the old data
 						while (flow.iperfData.samples.length > pointsToDisplay + iperfData.samples.length) {
 							flow.iperfData.samples.shift();
 						}
 
+						// if the client gets too out of sync, resynchronize
+						if (Math.abs(flow.iperfData.now - startTime) > (updateRate/1000) * 1.25) {
+							flow.iperfData.now = startTime;
+						}
+
+						var time = startTime;
 						iperfData.samples.forEach(function (s) {
-							flow.iperfData.samples.push(s);
+							var sample = {
+								time: time,
+								value: s
+							};
+							flow.iperfData.samples.push(sample);
+							time += interval/1000;
 						});
 					}
 					flow.iperfData.timestamp = iperfData.timestamp;
@@ -283,7 +323,7 @@
 				}
 //				console.log(data);
 			});
-		}, updateRate/2); // over sample to avoid gaps
+		}, updateRate*.5); // over sample to avoid gaps
 	}
 }