blob: 5f41617b5419be666ecbedf26e57eb292056ce44 [file] [log] [blame]
Paul Greyson740bdaf2013-03-18 16:10:48 -07001/*global d3*/
2
Paul Greysond1a22d92013-03-19 12:15:19 -07003
4var colors = [
Paul Greyson3e142162013-03-19 13:56:17 -07005 'color1',
6 'color2',
7 'color3',
8 'color4',
9 'color5',
10 'color6',
11 'color7',
12 'color8',
13 'color9',
14 'color10',
15 'color11',
16 'color12',
Paul Greysond1a22d92013-03-19 12:15:19 -070017]
Paul Greyson01a5dff2013-03-19 15:50:14 -070018colors.reverse();
Paul Greysond1a22d92013-03-19 12:15:19 -070019
20var controllerColorMap = {};
21
22
23
Paul Greyson740bdaf2013-03-18 16:10:48 -070024function createTopologyView() {
Paul Greysonb367de22013-03-23 11:09:11 -070025 var svg = d3.select('#svg-container').append('svg:svg');
26
27 svg.append("svg:defs").append("svg:marker")
28 .attr("id", "arrow")
29 .attr("viewBox", "0 -5 10 10")
30 .attr("refX", -1)
31 .attr("markerWidth", 5)
32 .attr("markerHeight", 5)
33 .attr("orient", "auto")
34 .append("svg:path")
35 .attr("d", "M0,-5L10,0L0,5");
36
37 return svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
Paul Greyson952ccb62013-03-18 22:22:08 -070038 attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
Paul Greyson740bdaf2013-03-18 16:10:48 -070039}
40
Paul Greysond1a22d92013-03-19 12:15:19 -070041function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -070042 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -070043 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
44 d3.select('#activeFlows').text(model.flows.length);
45}
46
47function toRadians (angle) {
48 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -070049}
50
Paul Greysonc17278a2013-03-23 10:17:12 -070051function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -070052 var rings = [{
53 radius: 3,
Paul Greysonde7fad52013-03-19 12:47:32 -070054 width: 6,
Paul Greyson952ccb62013-03-18 22:22:08 -070055 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -070056 className: 'edge',
57 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -070058 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -070059 radius: 2.25,
Paul Greysonde7fad52013-03-19 12:47:32 -070060 width: 12,
Paul Greyson952ccb62013-03-18 22:22:08 -070061 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -070062 className: 'aggregation',
63 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -070064 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -070065 radius: .75,
Paul Greysonde7fad52013-03-19 12:47:32 -070066 width: 18,
Paul Greyson952ccb62013-03-18 22:22:08 -070067 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -070068 className: 'core',
69 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -070070 }];
71
Paul Greysonde7fad52013-03-19 12:47:32 -070072
73 var aggRanges = {};
74
75 // arrange edge switches at equal increments
76 var k = 360 / rings[0].switches.length;
77 rings[0].switches.forEach(function (s, i) {
78 var angle = k * i;
79
80 rings[0].angles[i] = angle;
81
82 // record the angle for the agg switch layout
83 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -070084 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -070085 var aggdpid = dpid.join(':');
86 var aggRange = aggRanges[aggdpid];
87 if (!aggRange) {
88 aggRange = aggRanges[aggdpid] = {};
89 aggRange.min = aggRange.max = angle;
90 } else {
91 aggRange.max = angle;
92 }
Paul Greysonde7fad52013-03-19 12:47:32 -070093 });
94
95 // arrange aggregation switches to "fan out" to edge switches
96 k = 360 / rings[1].switches.length;
97 rings[1].switches.forEach(function (s, i) {
98// rings[1].angles[i] = k * i;
99 var range = aggRanges[s.dpid];
100
Paul Greyson832d2202013-03-21 13:27:56 -0700101 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700102 });
103
Paul Greyson3f890b62013-03-22 17:39:36 -0700104 // find the association between core switches and aggregation switches
105 var aggregationSwitchMap = {};
106 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700107 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700108 });
109
Paul Greyson3f890b62013-03-22 17:39:36 -0700110 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700111 k = 360 / rings[2].switches.length;
112 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700113// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700114 var associatedAggregationSwitches = model.configuration.association[s.dpid];
115 // TODO: go between if there are multiple
116 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
117
118 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700119 });
120
Paul Greysonc17278a2013-03-23 10:17:12 -0700121 return rings;
122}
123
124function updateTopology(svg, model) {
125
126 // DRAW THE SWITCHES
127 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
128
Paul Greyson740bdaf2013-03-18 16:10:48 -0700129 function ringEnter(data, i) {
130 if (!data.switches.length) {
131 return;
132 }
133
Paul Greysonc17278a2013-03-23 10:17:12 -0700134 // create the nodes
135 // TODO: do this in two layers so that the text can always be on top
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700136 var nodes = d3.select(this).selectAll("g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700137 .data(d3.range(data.switches.length).map(function() {
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700138 return data;
139 }))
Paul Greyson740bdaf2013-03-18 16:10:48 -0700140 .enter().append("svg:g")
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700141 .classed('nolabel', true)
Paul Greyson23b0cd32013-03-18 23:45:48 -0700142 .attr("id", function (_, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700143 return data.switches[i].dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700144 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700145 .attr("transform", function(_, i) {
Paul Greysonde7fad52013-03-19 12:47:32 -0700146 return "rotate(" + data.angles[i]+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angles[i]) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700147 });
148
Paul Greysonc17278a2013-03-23 10:17:12 -0700149 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700150 nodes.append("svg:circle")
Paul Greyson952ccb62013-03-18 22:22:08 -0700151 .attr("transform", function(_, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700152 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700153 if (data.scale) {
154 m = m.scale(data.scale);
155 }
156 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700157 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700158 .attr("x", -data.width / 2)
159 .attr("y", -data.width / 2)
Paul Greysond1a22d92013-03-19 12:15:19 -0700160 .attr("r", data.width)
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700161
Paul Greysonc17278a2013-03-23 10:17:12 -0700162 // add the text nodes which show on mouse over
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700163 nodes.append("svg:text")
164 .text(function (d, i) {return d.switches[i].dpid})
165 .attr("x", 0)
166 .attr("y", 0)
167 .attr("transform", function(_, i) {
168 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
169 if (data.scale) {
170 m = m.scale(data.scale);
171 }
172 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
173 })
174
Paul Greysonc17278a2013-03-23 10:17:12 -0700175 // setup the mouseover behaviors
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700176 function showLabel(data, index) {
177 d3.select(document.getElementById(data.switches[index].dpid)).classed('nolabel', false);
178 }
179
180 function hideLabel(data, index) {
181 d3.select(document.getElementById(data.switches[index].dpid)).classed('nolabel', true);
182 }
183
184 nodes.on('mouseover', showLabel);
185 nodes.on('mouseout', hideLabel);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700186 }
187
Paul Greysonc17278a2013-03-23 10:17:12 -0700188 // append switches
189 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700190 .attr("class", "ring")
191 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700192
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700193
Paul Greysonc17278a2013-03-23 10:17:12 -0700194 function ringUpdate(data, i) {
195 nodes = d3.select(this).selectAll("circle");
196 nodes.attr('class', function (_, i) {
197 if (data.switches[i].state == 'ACTIVE') {
198 return data.className + ' ' + controllerColorMap[data.switches[i].controller];
199 } else {
200 return data.className + ' ' + 'colorInactive';
201 }
202 })
203 }
204
205 // update switches
206 rings.each(ringUpdate);
207
208 // switches should not change during operation of the ui so no
209 // rings.exit()
210
211
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700212 // do mouseover zoom on edge nodes
Paul Greysonc17278a2013-03-23 10:17:12 -0700213 // function zoom(data, index) {
214 // var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
215 // g.transition().duration(100).attr("r", rings[0].width*3);
216 // }
Paul Greysonf8f43172013-03-18 23:00:30 -0700217
Paul Greysonc17278a2013-03-23 10:17:12 -0700218 // svg.selectAll('.edge').on('mouseover', zoom);
219 // svg.selectAll('.edge').on('mousedown', zoom);
Paul Greysond1a22d92013-03-19 12:15:19 -0700220
Paul Greysonc17278a2013-03-23 10:17:12 -0700221 // function unzoom(data, index) {
222 // var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
223 // g.transition().duration(100).attr("r", rings[0].width);
224 // }
225 // svg.selectAll('.edge').on('mouseout', unzoom);
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700226
227
Paul Greysond1a22d92013-03-19 12:15:19 -0700228 // DRAW THE LINKS
229 var line = d3.svg.line()
230 .x(function(d) {
231 return d.x;
232 })
233 .y(function(d) {
234 return d.y;
Paul Greysonb367de22013-03-23 11:09:11 -0700235 });
236// .interpolate("basis");
Paul Greysond1a22d92013-03-19 12:15:19 -0700237
Paul Greysonc17278a2013-03-23 10:17:12 -0700238 // key on link dpids since these will come/go during demo
Paul Greysonb367de22013-03-23 11:09:11 -0700239 var links = d3.select('svg').selectAll('.link').data(model.links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700240 return d['src-switch']+'->'+d['dst-switch'];
241 });
242
243 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700244 links.enter().append("svg:path")
245 .attr("class", "link")
246 .attr("d", function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700247
Paul Greysond1a22d92013-03-19 12:15:19 -0700248 var src = d3.select(document.getElementById(d['src-switch']));
249 var dst = d3.select(document.getElementById(d['dst-switch']));
250
251 var srcPt = document.querySelector('svg').createSVGPoint();
252 srcPt.x = src.attr('x');
Paul Greysonb367de22013-03-23 11:09:11 -0700253 srcPt.y = src.attr('y');
254 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700255
256 var dstPt = document.querySelector('svg').createSVGPoint();
257 dstPt.x = dst.attr('x');
Paul Greysonb367de22013-03-23 11:09:11 -0700258 dstPt.y = dst.attr('y'); // tmp: make up and down links distinguishable
259 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700260
Paul Greysonb367de22013-03-23 11:09:11 -0700261 var midPt = document.querySelector('svg').createSVGPoint();
262 midPt.x = (srcPt.x + dstPt.x)/2;
263 midPt.y = (srcPt.y + dstPt.y)/2;
264
265 return line([srcPt, midPt, dstPt]);
266 })
267 .attr("marker-mid", function(d) { return "url(#arrow)"; });
Paul Greysonc17278a2013-03-23 10:17:12 -0700268
269 // remove old links
270 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700271}
272
273function updateControllers(model) {
274 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700275 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700276 .each(function (c) {
277 controllerColorMap[c] = colors.pop();
278 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
279 })
280 .text(function (d) {
281 return d;
282 });
Paul Greysonbcd3c772013-03-21 13:16:44 -0700283
Paul Greysone262a292013-03-23 10:35:23 -0700284 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -0700285 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -0700286 if (model.activeControllers.indexOf(d) != -1) {
287 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -0700288 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700289 var className = 'controller ' + color;
290 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700291 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700292
Paul Greysone262a292013-03-23 10:35:23 -0700293 // this should never be needed
294 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700295
Paul Greyson3e142162013-03-19 13:56:17 -0700296 controllers.on('click', function (c, index) {
Paul Greysonc3e21a02013-03-21 13:56:05 -0700297 var allSelected = true;
298 for (var key in controllerColorMap) {
299 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
300 allSelected = false;
301 break;
302 }
303 }
304 if (allSelected) {
305 for (var key in controllerColorMap) {
306 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
307 }
308 } else {
309 for (var key in controllerColorMap) {
310 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
311 }
312 }
313
314 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
315 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -0700316 });
Paul Greyson740bdaf2013-03-18 16:10:48 -0700317}
318
Paul Greysonb48943b2013-03-19 13:27:57 -0700319var oldModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700320function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700321 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -0700322 updateModel(function (newModel) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700323 console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -0700324
Paul Greysone262a292013-03-23 10:35:23 -0700325 if (!oldModel || JSON.stringify(oldModel) != JSON.stringify(newModel)) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700326 updateControllers(newModel);
327 updateTopology(svg, newModel);
328 } else {
329 console.log('no change');
330 }
331 updateHeader(newModel);
332
333 oldModel = newModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700334
335 // do it again in 1s
336 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -0700337 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -0700338 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -0700339 });
340}
Paul Greyson740bdaf2013-03-18 16:10:48 -0700341
Paul Greyson38d8bde2013-03-22 22:07:35 -0700342svg = createTopologyView();
343// workaround for Chrome v25 bug
344// if executed immediately, the view box transform logic doesn't work properly
345// fixed in Chrome v27
346setTimeout(function () {
347 // workaround for another Chrome v25 bug
348 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -0700349 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -0700350 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
351 sync(svg);
352}, 100);