blob: 825b1a31d43aac0691f552f5afbb796d6f90a441 [file] [log] [blame]
Paul Greyson740bdaf2013-03-18 16:10:48 -07001/*global d3*/
2
Paul Greyson6d9ed862013-03-23 17:37:15 -07003d3.selection.prototype.moveToFront = function() {
4 return this.each(function(){
5 this.parentNode.appendChild(this);
6 });
7};
Paul Greysond1a22d92013-03-19 12:15:19 -07008
9var colors = [
Paul Greyson3e142162013-03-19 13:56:17 -070010 'color1',
11 'color2',
12 'color3',
13 'color4',
14 'color5',
15 'color6',
16 'color7',
17 'color8',
18 'color9',
19 'color10',
20 'color11',
21 'color12',
Paul Greysond1a22d92013-03-19 12:15:19 -070022]
Paul Greyson01a5dff2013-03-19 15:50:14 -070023colors.reverse();
Paul Greysond1a22d92013-03-19 12:15:19 -070024
25var controllerColorMap = {};
26
27
28
Paul Greyson740bdaf2013-03-18 16:10:48 -070029function createTopologyView() {
Paul Greysonb367de22013-03-23 11:09:11 -070030 var svg = d3.select('#svg-container').append('svg:svg');
31
32 svg.append("svg:defs").append("svg:marker")
33 .attr("id", "arrow")
34 .attr("viewBox", "0 -5 10 10")
35 .attr("refX", -1)
36 .attr("markerWidth", 5)
37 .attr("markerHeight", 5)
38 .attr("orient", "auto")
39 .append("svg:path")
Paul Greyson45303ac2013-03-23 16:44:01 -070040 .attr("d", "M0,-3L10,0L0,3");
Paul Greysonb367de22013-03-23 11:09:11 -070041
42 return svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
Paul Greyson952ccb62013-03-18 22:22:08 -070043 attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
Paul Greyson740bdaf2013-03-18 16:10:48 -070044}
45
Paul Greysond1a22d92013-03-19 12:15:19 -070046function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -070047 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -070048 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
49 d3.select('#activeFlows').text(model.flows.length);
50}
51
52function toRadians (angle) {
53 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -070054}
55
Paul Greysonc17278a2013-03-23 10:17:12 -070056function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -070057 var rings = [{
58 radius: 3,
Paul Greysonde7fad52013-03-19 12:47:32 -070059 width: 6,
Paul Greyson952ccb62013-03-18 22:22:08 -070060 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -070061 className: 'edge',
62 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -070063 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -070064 radius: 2.25,
Paul Greysonde7fad52013-03-19 12:47:32 -070065 width: 12,
Paul Greyson952ccb62013-03-18 22:22:08 -070066 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -070067 className: 'aggregation',
68 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -070069 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -070070 radius: .75,
Paul Greysonde7fad52013-03-19 12:47:32 -070071 width: 18,
Paul Greyson952ccb62013-03-18 22:22:08 -070072 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -070073 className: 'core',
74 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -070075 }];
76
Paul Greysonde7fad52013-03-19 12:47:32 -070077
78 var aggRanges = {};
79
80 // arrange edge switches at equal increments
81 var k = 360 / rings[0].switches.length;
82 rings[0].switches.forEach(function (s, i) {
83 var angle = k * i;
84
85 rings[0].angles[i] = angle;
86
87 // record the angle for the agg switch layout
88 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -070089 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -070090 var aggdpid = dpid.join(':');
91 var aggRange = aggRanges[aggdpid];
92 if (!aggRange) {
93 aggRange = aggRanges[aggdpid] = {};
94 aggRange.min = aggRange.max = angle;
95 } else {
96 aggRange.max = angle;
97 }
Paul Greysonde7fad52013-03-19 12:47:32 -070098 });
99
100 // arrange aggregation switches to "fan out" to edge switches
101 k = 360 / rings[1].switches.length;
102 rings[1].switches.forEach(function (s, i) {
103// rings[1].angles[i] = k * i;
104 var range = aggRanges[s.dpid];
105
Paul Greyson832d2202013-03-21 13:27:56 -0700106 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700107 });
108
Paul Greyson3f890b62013-03-22 17:39:36 -0700109 // find the association between core switches and aggregation switches
110 var aggregationSwitchMap = {};
111 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700112 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700113 });
114
Paul Greyson3f890b62013-03-22 17:39:36 -0700115 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700116 k = 360 / rings[2].switches.length;
117 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700118// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700119 var associatedAggregationSwitches = model.configuration.association[s.dpid];
120 // TODO: go between if there are multiple
121 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
122
123 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700124 });
125
Paul Greyson6d9ed862013-03-23 17:37:15 -0700126
127
128
129
Paul Greysonc17278a2013-03-23 10:17:12 -0700130 return rings;
131}
132
133function updateTopology(svg, model) {
134
135 // DRAW THE SWITCHES
136 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
137
Paul Greyson740bdaf2013-03-18 16:10:48 -0700138 function ringEnter(data, i) {
139 if (!data.switches.length) {
140 return;
141 }
142
Paul Greysonc17278a2013-03-23 10:17:12 -0700143 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700144 var nodes = d3.select(this).selectAll("g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700145 .data(d3.range(data.switches.length).map(function() {
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700146 return data;
147 }))
Paul Greyson740bdaf2013-03-18 16:10:48 -0700148 .enter().append("svg:g")
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700149 .classed('nolabel', true)
Paul Greyson23b0cd32013-03-18 23:45:48 -0700150 .attr("id", function (_, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700151 return data.switches[i].dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700152 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700153 .attr("transform", function(_, i) {
Paul Greysonde7fad52013-03-19 12:47:32 -0700154 return "rotate(" + data.angles[i]+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angles[i]) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700155 });
156
Paul Greysonc17278a2013-03-23 10:17:12 -0700157 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700158 nodes.append("svg:circle")
Paul Greyson952ccb62013-03-18 22:22:08 -0700159 .attr("transform", function(_, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700160 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700161 if (data.scale) {
162 m = m.scale(data.scale);
163 }
164 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700165 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700166 .attr("x", -data.width / 2)
167 .attr("y", -data.width / 2)
Paul Greysond1a22d92013-03-19 12:15:19 -0700168 .attr("r", data.width)
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700169
Paul Greysonc17278a2013-03-23 10:17:12 -0700170 // setup the mouseover behaviors
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700171 function showLabel(data, index) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700172 d3.select(document.getElementById(data.switches[index].dpid + '-label')).classed('nolabel', false);
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700173 }
174
175 function hideLabel(data, index) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700176 d3.select(document.getElementById(data.switches[index].dpid + '-label')).classed('nolabel', true);
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700177 }
178
179 nodes.on('mouseover', showLabel);
180 nodes.on('mouseout', hideLabel);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700181 }
182
Paul Greysonc17278a2013-03-23 10:17:12 -0700183 // append switches
184 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700185 .attr("class", "ring")
186 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700187
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700188
Paul Greysonc17278a2013-03-23 10:17:12 -0700189 function ringUpdate(data, i) {
190 nodes = d3.select(this).selectAll("circle");
191 nodes.attr('class', function (_, i) {
192 if (data.switches[i].state == 'ACTIVE') {
193 return data.className + ' ' + controllerColorMap[data.switches[i].controller];
194 } else {
195 return data.className + ' ' + 'colorInactive';
196 }
197 })
198 }
199
200 // update switches
201 rings.each(ringUpdate);
202
Paul Greyson968d1b42013-03-23 16:58:41 -0700203
204 // Now setup the labels
205 // This is done separately because SVG draws in node order and we want the labels
206 // always on top
207 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
208
209 function labelRingEnter(data, i) {
210 if (!data.switches.length) {
211 return;
212 }
213
214 // create the nodes
215 var nodes = d3.select(this).selectAll("g")
216 .data(d3.range(data.switches.length).map(function() {
217 return data;
218 }))
219 .enter().append("svg:g")
220 .classed('nolabel', true)
221 .attr("id", function (_, i) {
222 return data.switches[i].dpid + '-label';
223 })
224 .attr("transform", function(_, i) {
225 return "rotate(" + data.angles[i]+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angles[i]) + ")";
226 });
227
228 // add the text nodes which show on mouse over
229 nodes.append("svg:text")
230 .text(function (d, i) {return d.switches[i].dpid})
231 .attr("x", 0)
232 .attr("y", 0)
233 .attr("transform", function(_, i) {
234 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
235 if (data.scale) {
236 m = m.scale(data.scale);
237 }
238 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
239 })
240 }
241
242 labelRings.enter().append("svg:g")
243 .attr("class", "textRing")
244 .each(labelRingEnter);
245
246
Paul Greysonc17278a2013-03-23 10:17:12 -0700247 // switches should not change during operation of the ui so no
248 // rings.exit()
249
250
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700251 // do mouseover zoom on edge nodes
Paul Greyson6d9ed862013-03-23 17:37:15 -0700252 function zoom(data, index) {
253 var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
254 g.transition().duration(100).attr("r", g.data()[0].width*3);
255 // TODO: this doesn't work because the data binding is by index
256// d3.select(this.parentNode).moveToFront();
257 }
Paul Greysonf8f43172013-03-18 23:00:30 -0700258
Paul Greyson6d9ed862013-03-23 17:37:15 -0700259 svg.selectAll('.edge').on('mouseover', zoom);
260 svg.selectAll('.edge').on('mousedown', zoom);
Paul Greysond1a22d92013-03-19 12:15:19 -0700261
Paul Greyson6d9ed862013-03-23 17:37:15 -0700262 function unzoom(data, index) {
263 var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
264 g.transition().duration(100).attr("r", g.data()[0].width);
265 }
266 svg.selectAll('.edge').on('mouseout', unzoom);
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700267
268
Paul Greysond1a22d92013-03-19 12:15:19 -0700269 // DRAW THE LINKS
270 var line = d3.svg.line()
271 .x(function(d) {
272 return d.x;
273 })
274 .y(function(d) {
275 return d.y;
Paul Greysonb367de22013-03-23 11:09:11 -0700276 });
277// .interpolate("basis");
Paul Greysond1a22d92013-03-19 12:15:19 -0700278
Paul Greysonc17278a2013-03-23 10:17:12 -0700279 // key on link dpids since these will come/go during demo
Paul Greysonb367de22013-03-23 11:09:11 -0700280 var links = d3.select('svg').selectAll('.link').data(model.links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700281 return d['src-switch']+'->'+d['dst-switch'];
282 });
283
284 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700285 links.enter().append("svg:path")
286 .attr("class", "link")
287 .attr("d", function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700288
Paul Greysond1a22d92013-03-19 12:15:19 -0700289 var src = d3.select(document.getElementById(d['src-switch']));
290 var dst = d3.select(document.getElementById(d['dst-switch']));
291
292 var srcPt = document.querySelector('svg').createSVGPoint();
293 srcPt.x = src.attr('x');
Paul Greysonb367de22013-03-23 11:09:11 -0700294 srcPt.y = src.attr('y');
295 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700296
297 var dstPt = document.querySelector('svg').createSVGPoint();
298 dstPt.x = dst.attr('x');
Paul Greysonb367de22013-03-23 11:09:11 -0700299 dstPt.y = dst.attr('y'); // tmp: make up and down links distinguishable
300 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700301
Paul Greysonb367de22013-03-23 11:09:11 -0700302 var midPt = document.querySelector('svg').createSVGPoint();
303 midPt.x = (srcPt.x + dstPt.x)/2;
304 midPt.y = (srcPt.y + dstPt.y)/2;
305
306 return line([srcPt, midPt, dstPt]);
307 })
308 .attr("marker-mid", function(d) { return "url(#arrow)"; });
Paul Greysonc17278a2013-03-23 10:17:12 -0700309
310 // remove old links
311 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700312}
313
314function updateControllers(model) {
315 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700316 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700317 .each(function (c) {
318 controllerColorMap[c] = colors.pop();
319 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
320 })
321 .text(function (d) {
322 return d;
323 });
Paul Greysonbcd3c772013-03-21 13:16:44 -0700324
Paul Greysone262a292013-03-23 10:35:23 -0700325 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -0700326 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -0700327 if (model.activeControllers.indexOf(d) != -1) {
328 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -0700329 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700330 var className = 'controller ' + color;
331 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700332 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700333
Paul Greysone262a292013-03-23 10:35:23 -0700334 // this should never be needed
335 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700336
Paul Greyson3e142162013-03-19 13:56:17 -0700337 controllers.on('click', function (c, index) {
Paul Greysonc3e21a02013-03-21 13:56:05 -0700338 var allSelected = true;
339 for (var key in controllerColorMap) {
340 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
341 allSelected = false;
342 break;
343 }
344 }
345 if (allSelected) {
346 for (var key in controllerColorMap) {
347 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
348 }
349 } else {
350 for (var key in controllerColorMap) {
351 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
352 }
353 }
354
355 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
356 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -0700357 });
Paul Greyson740bdaf2013-03-18 16:10:48 -0700358}
359
Paul Greysonb48943b2013-03-19 13:27:57 -0700360var oldModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700361function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700362 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -0700363 updateModel(function (newModel) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700364 console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -0700365
Paul Greyson6d9ed862013-03-23 17:37:15 -0700366 if (true || !oldModel || JSON.stringify(oldModel) != JSON.stringify(newModel)) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700367 updateControllers(newModel);
368 updateTopology(svg, newModel);
369 } else {
370 console.log('no change');
371 }
372 updateHeader(newModel);
373
374 oldModel = newModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700375
376 // do it again in 1s
377 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -0700378 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -0700379 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -0700380 });
381}
Paul Greyson740bdaf2013-03-18 16:10:48 -0700382
Paul Greyson38d8bde2013-03-22 22:07:35 -0700383svg = createTopologyView();
384// workaround for Chrome v25 bug
385// if executed immediately, the view box transform logic doesn't work properly
386// fixed in Chrome v27
387setTimeout(function () {
388 // workaround for another Chrome v25 bug
389 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -0700390 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -0700391 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
392 sync(svg);
393}, 100);