blob: 54a35de9b55ebeede2d483c3e51e96a0f7a21c30 [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")
Paul Greyson45303ac2013-03-23 16:44:01 -070035 .attr("d", "M0,-3L10,0L0,3");
Paul Greysonb367de22013-03-23 11:09:11 -070036
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
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700135 var nodes = d3.select(this).selectAll("g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700136 .data(d3.range(data.switches.length).map(function() {
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700137 return data;
138 }))
Paul Greyson740bdaf2013-03-18 16:10:48 -0700139 .enter().append("svg:g")
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700140 .classed('nolabel', true)
Paul Greyson23b0cd32013-03-18 23:45:48 -0700141 .attr("id", function (_, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700142 return data.switches[i].dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700143 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700144 .attr("transform", function(_, i) {
Paul Greysonde7fad52013-03-19 12:47:32 -0700145 return "rotate(" + data.angles[i]+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angles[i]) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700146 });
147
Paul Greysonc17278a2013-03-23 10:17:12 -0700148 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700149 nodes.append("svg:circle")
Paul Greyson952ccb62013-03-18 22:22:08 -0700150 .attr("transform", function(_, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700151 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700152 if (data.scale) {
153 m = m.scale(data.scale);
154 }
155 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700156 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700157 .attr("x", -data.width / 2)
158 .attr("y", -data.width / 2)
Paul Greysond1a22d92013-03-19 12:15:19 -0700159 .attr("r", data.width)
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700160
Paul Greysonc17278a2013-03-23 10:17:12 -0700161 // setup the mouseover behaviors
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700162 function showLabel(data, index) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700163 d3.select(document.getElementById(data.switches[index].dpid + '-label')).classed('nolabel', false);
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700164 }
165
166 function hideLabel(data, index) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700167 d3.select(document.getElementById(data.switches[index].dpid + '-label')).classed('nolabel', true);
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700168 }
169
170 nodes.on('mouseover', showLabel);
171 nodes.on('mouseout', hideLabel);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700172 }
173
Paul Greysonc17278a2013-03-23 10:17:12 -0700174 // append switches
175 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700176 .attr("class", "ring")
177 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700178
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700179
Paul Greysonc17278a2013-03-23 10:17:12 -0700180 function ringUpdate(data, i) {
181 nodes = d3.select(this).selectAll("circle");
182 nodes.attr('class', function (_, i) {
183 if (data.switches[i].state == 'ACTIVE') {
184 return data.className + ' ' + controllerColorMap[data.switches[i].controller];
185 } else {
186 return data.className + ' ' + 'colorInactive';
187 }
188 })
189 }
190
191 // update switches
192 rings.each(ringUpdate);
193
Paul Greyson968d1b42013-03-23 16:58:41 -0700194
195 // Now setup the labels
196 // This is done separately because SVG draws in node order and we want the labels
197 // always on top
198 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
199
200 function labelRingEnter(data, i) {
201 if (!data.switches.length) {
202 return;
203 }
204
205 // create the nodes
206 var nodes = d3.select(this).selectAll("g")
207 .data(d3.range(data.switches.length).map(function() {
208 return data;
209 }))
210 .enter().append("svg:g")
211 .classed('nolabel', true)
212 .attr("id", function (_, i) {
213 return data.switches[i].dpid + '-label';
214 })
215 .attr("transform", function(_, i) {
216 return "rotate(" + data.angles[i]+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angles[i]) + ")";
217 });
218
219 // add the text nodes which show on mouse over
220 nodes.append("svg:text")
221 .text(function (d, i) {return d.switches[i].dpid})
222 .attr("x", 0)
223 .attr("y", 0)
224 .attr("transform", function(_, i) {
225 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
226 if (data.scale) {
227 m = m.scale(data.scale);
228 }
229 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
230 })
231 }
232
233 labelRings.enter().append("svg:g")
234 .attr("class", "textRing")
235 .each(labelRingEnter);
236
237
Paul Greysonc17278a2013-03-23 10:17:12 -0700238 // switches should not change during operation of the ui so no
239 // rings.exit()
240
241
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700242 // do mouseover zoom on edge nodes
Paul Greysonc17278a2013-03-23 10:17:12 -0700243 // function zoom(data, index) {
244 // var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
245 // g.transition().duration(100).attr("r", rings[0].width*3);
246 // }
Paul Greysonf8f43172013-03-18 23:00:30 -0700247
Paul Greysonc17278a2013-03-23 10:17:12 -0700248 // svg.selectAll('.edge').on('mouseover', zoom);
249 // svg.selectAll('.edge').on('mousedown', zoom);
Paul Greysond1a22d92013-03-19 12:15:19 -0700250
Paul Greysonc17278a2013-03-23 10:17:12 -0700251 // function unzoom(data, index) {
252 // var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
253 // g.transition().duration(100).attr("r", rings[0].width);
254 // }
255 // svg.selectAll('.edge').on('mouseout', unzoom);
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700256
257
Paul Greysond1a22d92013-03-19 12:15:19 -0700258 // DRAW THE LINKS
259 var line = d3.svg.line()
260 .x(function(d) {
261 return d.x;
262 })
263 .y(function(d) {
264 return d.y;
Paul Greysonb367de22013-03-23 11:09:11 -0700265 });
266// .interpolate("basis");
Paul Greysond1a22d92013-03-19 12:15:19 -0700267
Paul Greysonc17278a2013-03-23 10:17:12 -0700268 // key on link dpids since these will come/go during demo
Paul Greysonb367de22013-03-23 11:09:11 -0700269 var links = d3.select('svg').selectAll('.link').data(model.links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700270 return d['src-switch']+'->'+d['dst-switch'];
271 });
272
273 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700274 links.enter().append("svg:path")
275 .attr("class", "link")
276 .attr("d", function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700277
Paul Greysond1a22d92013-03-19 12:15:19 -0700278 var src = d3.select(document.getElementById(d['src-switch']));
279 var dst = d3.select(document.getElementById(d['dst-switch']));
280
281 var srcPt = document.querySelector('svg').createSVGPoint();
282 srcPt.x = src.attr('x');
Paul Greysonb367de22013-03-23 11:09:11 -0700283 srcPt.y = src.attr('y');
284 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700285
286 var dstPt = document.querySelector('svg').createSVGPoint();
287 dstPt.x = dst.attr('x');
Paul Greysonb367de22013-03-23 11:09:11 -0700288 dstPt.y = dst.attr('y'); // tmp: make up and down links distinguishable
289 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700290
Paul Greysonb367de22013-03-23 11:09:11 -0700291 var midPt = document.querySelector('svg').createSVGPoint();
292 midPt.x = (srcPt.x + dstPt.x)/2;
293 midPt.y = (srcPt.y + dstPt.y)/2;
294
295 return line([srcPt, midPt, dstPt]);
296 })
297 .attr("marker-mid", function(d) { return "url(#arrow)"; });
Paul Greysonc17278a2013-03-23 10:17:12 -0700298
299 // remove old links
300 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700301}
302
303function updateControllers(model) {
304 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700305 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700306 .each(function (c) {
307 controllerColorMap[c] = colors.pop();
308 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
309 })
310 .text(function (d) {
311 return d;
312 });
Paul Greysonbcd3c772013-03-21 13:16:44 -0700313
Paul Greysone262a292013-03-23 10:35:23 -0700314 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -0700315 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -0700316 if (model.activeControllers.indexOf(d) != -1) {
317 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -0700318 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700319 var className = 'controller ' + color;
320 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700321 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700322
Paul Greysone262a292013-03-23 10:35:23 -0700323 // this should never be needed
324 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700325
Paul Greyson3e142162013-03-19 13:56:17 -0700326 controllers.on('click', function (c, index) {
Paul Greysonc3e21a02013-03-21 13:56:05 -0700327 var allSelected = true;
328 for (var key in controllerColorMap) {
329 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
330 allSelected = false;
331 break;
332 }
333 }
334 if (allSelected) {
335 for (var key in controllerColorMap) {
336 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
337 }
338 } else {
339 for (var key in controllerColorMap) {
340 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
341 }
342 }
343
344 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
345 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -0700346 });
Paul Greyson740bdaf2013-03-18 16:10:48 -0700347}
348
Paul Greysonb48943b2013-03-19 13:27:57 -0700349var oldModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700350function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700351 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -0700352 updateModel(function (newModel) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700353 console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -0700354
Paul Greysone262a292013-03-23 10:35:23 -0700355 if (!oldModel || JSON.stringify(oldModel) != JSON.stringify(newModel)) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700356 updateControllers(newModel);
357 updateTopology(svg, newModel);
358 } else {
359 console.log('no change');
360 }
361 updateHeader(newModel);
362
363 oldModel = newModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700364
365 // do it again in 1s
366 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -0700367 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -0700368 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -0700369 });
370}
Paul Greyson740bdaf2013-03-18 16:10:48 -0700371
Paul Greyson38d8bde2013-03-22 22:07:35 -0700372svg = createTopologyView();
373// workaround for Chrome v25 bug
374// if executed immediately, the view box transform logic doesn't work properly
375// fixed in Chrome v27
376setTimeout(function () {
377 // workaround for another Chrome v25 bug
378 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -0700379 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -0700380 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
381 sync(svg);
382}, 100);