initial topology view (sample JSON data isn't complete)
diff --git a/web/ons-demo/css/layout.default.css b/web/ons-demo/css/layout.default.css
index cbff528..647763c 100644
--- a/web/ons-demo/css/layout.default.css
+++ b/web/ons-demo/css/layout.default.css
@@ -1,3 +1,7 @@
+html, body {
+	height: 100%;
+}
+
 body {
 	display: -webkit-box;
 }
@@ -13,14 +17,19 @@
 	display: -webkit-box;
 }
 
+#left, #right {
+	display: -webkit-box;
+	-webkit-box-orient: vertical;
+}
+
 #right {
 	width: 100%;
 	-webkit-box-flex: 1.0;
 }
 
 #controllers, #topology {
-	height: 100%;
 	-webkit-box-flex: 1.0;
+	position: relative;
 }
 
 #selectedFlows {
@@ -28,9 +37,15 @@
 }
 
 /* tmp */
-#controllers, #topology, #selectedFlows {
+#controllers, #selectedFlows {
 	display: -webkit-box;
 	-webkit-box-align: center;
 	-webkit-box-pack: center;
 }
 
+#svg-container {
+	position: absolute;
+	top: 0px;
+	height: 100%;
+	width: 100%;
+}
diff --git a/web/ons-demo/css/skin.default.css b/web/ons-demo/css/skin.default.css
index 2a6d95a..dd1e0ac 100644
--- a/web/ons-demo/css/skin.default.css
+++ b/web/ons-demo/css/skin.default.css
@@ -1,3 +1,4 @@
+
 body {
 	background-color: black;
 	color: white;
diff --git a/web/ons-demo/index.html b/web/ons-demo/index.html
index 46316d2..580a230 100644
--- a/web/ons-demo/index.html
+++ b/web/ons-demo/index.html
@@ -27,7 +27,7 @@
 		<div id='traceButton' class='button'>Trace</div>
 	</div>
 	<div id='selectedFlows'>Selected Flows</div>
-	<div id='topology'>Topology</div>
+	<div id='topology'><div id='svg-container'> </div></div>
 </div>
 
 
diff --git a/web/ons-demo/js/app.js b/web/ons-demo/js/app.js
index c7e96f7..c22a603 100644
--- a/web/ons-demo/js/app.js
+++ b/web/ons-demo/js/app.js
@@ -1,7 +1,73 @@
-function sync() {
+/*global d3*/
+
+function createTopologyView() {
+	return d3.select('#svg-container').append('svg:svg').attr('viewBox', '0 0 1000 1000').
+			append('svg:g').attr('transform', 'translate(500 500)');
+}
+
+
+
+function drawHeader(model) {
+	d3.select('#lastUpdate').text(model.timestamp);
+}
+
+function drawTopology(svg, model) {
+
+	var rings = [{
+		radius: 3,
+		width: 16,
+		switches: model.edgeSwitches
+	}, {
+		radius: 1.5,
+		width: 32,
+		switches: model.aggregationSwitches
+	}, {
+		radius: 1,
+		width: 32,
+		switches: model.coreSwitches
+	}];
+
+	function ringEnter(data, i) {
+		if (!data.switches.length) {
+			return;
+		}
+
+		var k = 360 / data.switches.length;
+
+		d3.select(this).selectAll("g")
+			.data(d3.range(data.switches.length).map(function() {
+			return data;
+		}))
+			.enter().append("svg:g")
+			.attr("class", "square")
+			.attr("transform", function(_, i) {
+			return "rotate(" + i * k + ")translate(" + data.radius*150 + ")";
+		})
+			.append("svg:rect")
+			.attr("x", -data.width / 2)
+			.attr("y", -data.width / 2)
+			.attr("width", data.width)
+			.attr("height", data.width);
+	}
+
+	var ring = svg.selectAll("g")
+		.data(rings)
+		.enter().append("svg:g")
+		.attr("class", "ring")
+		.each(ringEnter);
+}
+
+function sync(svg) {
 	updateModel(function (model) {
-		d3.select('#lastUpdate').text(model.timestamp);
-		setTimeout(sync, 1000);
+
+		drawHeader(model);
+		drawTopology(svg, model);
+
+		// do it again in 1s
+		setTimeout(function () {
+			sync(svg)
+		}, 1000);
 	});
 }
-sync();
\ No newline at end of file
+
+sync(createTopologyView());
\ No newline at end of file
diff --git a/web/ons-demo/js/model.js b/web/ons-demo/js/model.js
index 27a03b2..e979196 100644
--- a/web/ons-demo/js/model.js
+++ b/web/ons-demo/js/model.js
@@ -2,7 +2,7 @@
 
 function toD3(results) {
 	var model = {
-		edgeSwitchs: [],
+		edgeSwitches: [],
 		aggregationSwitches: [],
 		coreSwitches: []
 	}
@@ -25,7 +25,7 @@
 		} else if (aggregationSwitchDPIDs[s.dpid]) {
 			model.aggregationSwitches.push(s);
 		} else {
-			model.edgeSwitchs.push(s);
+			model.edgeSwitches.push(s);
 		}
 	});