blob: 432710f99927cf633c487eb099fb005ea5776c8c [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 Greysond1a22d92013-03-19 12:15:19 -070025 return d3.select('#svg-container').append('svg:svg').append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
Paul Greyson952ccb62013-03-18 22:22:08 -070026 attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
Paul Greyson740bdaf2013-03-18 16:10:48 -070027}
28
Paul Greysond1a22d92013-03-19 12:15:19 -070029function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -070030 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -070031 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
32 d3.select('#activeFlows').text(model.flows.length);
33}
34
35function toRadians (angle) {
36 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -070037}
38
Paul Greysonc17278a2013-03-23 10:17:12 -070039function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -070040 var rings = [{
41 radius: 3,
Paul Greysonde7fad52013-03-19 12:47:32 -070042 width: 6,
Paul Greyson952ccb62013-03-18 22:22:08 -070043 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -070044 className: 'edge',
45 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -070046 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -070047 radius: 2.25,
Paul Greysonde7fad52013-03-19 12:47:32 -070048 width: 12,
Paul Greyson952ccb62013-03-18 22:22:08 -070049 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -070050 className: 'aggregation',
51 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -070052 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -070053 radius: .75,
Paul Greysonde7fad52013-03-19 12:47:32 -070054 width: 18,
Paul Greyson952ccb62013-03-18 22:22:08 -070055 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -070056 className: 'core',
57 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -070058 }];
59
Paul Greysonde7fad52013-03-19 12:47:32 -070060
61 var aggRanges = {};
62
63 // arrange edge switches at equal increments
64 var k = 360 / rings[0].switches.length;
65 rings[0].switches.forEach(function (s, i) {
66 var angle = k * i;
67
68 rings[0].angles[i] = angle;
69
70 // record the angle for the agg switch layout
71 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -070072 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -070073 var aggdpid = dpid.join(':');
74 var aggRange = aggRanges[aggdpid];
75 if (!aggRange) {
76 aggRange = aggRanges[aggdpid] = {};
77 aggRange.min = aggRange.max = angle;
78 } else {
79 aggRange.max = angle;
80 }
Paul Greysonde7fad52013-03-19 12:47:32 -070081 });
82
83 // arrange aggregation switches to "fan out" to edge switches
84 k = 360 / rings[1].switches.length;
85 rings[1].switches.forEach(function (s, i) {
86// rings[1].angles[i] = k * i;
87 var range = aggRanges[s.dpid];
88
Paul Greyson832d2202013-03-21 13:27:56 -070089 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -070090 });
91
Paul Greyson3f890b62013-03-22 17:39:36 -070092 // find the association between core switches and aggregation switches
93 var aggregationSwitchMap = {};
94 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -070095 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -070096 });
97
Paul Greyson3f890b62013-03-22 17:39:36 -070098 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -070099 k = 360 / rings[2].switches.length;
100 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700101// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700102 var associatedAggregationSwitches = model.configuration.association[s.dpid];
103 // TODO: go between if there are multiple
104 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
105
106 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700107 });
108
Paul Greysonc17278a2013-03-23 10:17:12 -0700109 return rings;
110}
111
112function updateTopology(svg, model) {
113
114 // DRAW THE SWITCHES
115 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
116
Paul Greyson740bdaf2013-03-18 16:10:48 -0700117 function ringEnter(data, i) {
118 if (!data.switches.length) {
119 return;
120 }
121
Paul Greysonc17278a2013-03-23 10:17:12 -0700122 // create the nodes
123 // TODO: do this in two layers so that the text can always be on top
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700124 var nodes = d3.select(this).selectAll("g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700125 .data(d3.range(data.switches.length).map(function() {
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700126 return data;
127 }))
Paul Greyson740bdaf2013-03-18 16:10:48 -0700128 .enter().append("svg:g")
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700129 .classed('nolabel', true)
Paul Greyson23b0cd32013-03-18 23:45:48 -0700130 .attr("id", function (_, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700131 return data.switches[i].dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700132 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700133 .attr("transform", function(_, i) {
Paul Greysonde7fad52013-03-19 12:47:32 -0700134 return "rotate(" + data.angles[i]+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angles[i]) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700135 });
136
Paul Greysonc17278a2013-03-23 10:17:12 -0700137 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700138 nodes.append("svg:circle")
Paul Greyson952ccb62013-03-18 22:22:08 -0700139 .attr("transform", function(_, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700140 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700141 if (data.scale) {
142 m = m.scale(data.scale);
143 }
144 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700145 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700146 .attr("x", -data.width / 2)
147 .attr("y", -data.width / 2)
Paul Greysond1a22d92013-03-19 12:15:19 -0700148 .attr("r", data.width)
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700149
Paul Greysonc17278a2013-03-23 10:17:12 -0700150 // add the text nodes which show on mouse over
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700151 nodes.append("svg:text")
152 .text(function (d, i) {return d.switches[i].dpid})
153 .attr("x", 0)
154 .attr("y", 0)
155 .attr("transform", function(_, i) {
156 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
157 if (data.scale) {
158 m = m.scale(data.scale);
159 }
160 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
161 })
162
Paul Greysonc17278a2013-03-23 10:17:12 -0700163 // setup the mouseover behaviors
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700164 function showLabel(data, index) {
165 d3.select(document.getElementById(data.switches[index].dpid)).classed('nolabel', false);
166 }
167
168 function hideLabel(data, index) {
169 d3.select(document.getElementById(data.switches[index].dpid)).classed('nolabel', true);
170 }
171
172 nodes.on('mouseover', showLabel);
173 nodes.on('mouseout', hideLabel);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700174 }
175
Paul Greysonc17278a2013-03-23 10:17:12 -0700176 // append switches
177 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700178 .attr("class", "ring")
179 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700180
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700181
Paul Greysonc17278a2013-03-23 10:17:12 -0700182 function ringUpdate(data, i) {
183 nodes = d3.select(this).selectAll("circle");
184 nodes.attr('class', function (_, i) {
185 if (data.switches[i].state == 'ACTIVE') {
186 return data.className + ' ' + controllerColorMap[data.switches[i].controller];
187 } else {
188 return data.className + ' ' + 'colorInactive';
189 }
190 })
191 }
192
193 // update switches
194 rings.each(ringUpdate);
195
196 // switches should not change during operation of the ui so no
197 // rings.exit()
198
199
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700200 // do mouseover zoom on edge nodes
Paul Greysonc17278a2013-03-23 10:17:12 -0700201 // function zoom(data, index) {
202 // var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
203 // g.transition().duration(100).attr("r", rings[0].width*3);
204 // }
Paul Greysonf8f43172013-03-18 23:00:30 -0700205
Paul Greysonc17278a2013-03-23 10:17:12 -0700206 // svg.selectAll('.edge').on('mouseover', zoom);
207 // svg.selectAll('.edge').on('mousedown', zoom);
Paul Greysond1a22d92013-03-19 12:15:19 -0700208
Paul Greysonc17278a2013-03-23 10:17:12 -0700209 // function unzoom(data, index) {
210 // var g = d3.select(document.getElementById(data.switches[index].dpid)).select('circle');
211 // g.transition().duration(100).attr("r", rings[0].width);
212 // }
213 // svg.selectAll('.edge').on('mouseout', unzoom);
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700214
215
Paul Greysond1a22d92013-03-19 12:15:19 -0700216 // DRAW THE LINKS
217 var line = d3.svg.line()
218 .x(function(d) {
219 return d.x;
220 })
221 .y(function(d) {
222 return d.y;
223 })
224 .interpolate("basis");
225
Paul Greysonc17278a2013-03-23 10:17:12 -0700226 // key on link dpids since these will come/go during demo
227 var links = d3.select('svg').selectAll('path').data(model.links, function (d) {
228 return d['src-switch']+'->'+d['dst-switch'];
229 });
230
231 // add new links
232 links.enter().append("svg:path").attr("d", function (d) {
233
Paul Greysond1a22d92013-03-19 12:15:19 -0700234 var src = d3.select(document.getElementById(d['src-switch']));
235 var dst = d3.select(document.getElementById(d['dst-switch']));
236
237 var srcPt = document.querySelector('svg').createSVGPoint();
238 srcPt.x = src.attr('x');
Paul Greysonc17278a2013-03-23 10:17:12 -0700239 srcPt.y = src.attr('y') + 10; // tmp: make up and down links distinguishable
Paul Greysond1a22d92013-03-19 12:15:19 -0700240
241 var dstPt = document.querySelector('svg').createSVGPoint();
242 dstPt.x = dst.attr('x');
Paul Greysonc17278a2013-03-23 10:17:12 -0700243 dstPt.y = dst.attr('y') - 10; // tmp: make up and down links distinguishable
Paul Greysond1a22d92013-03-19 12:15:19 -0700244
245 return line([srcPt.matrixTransform(src[0][0].getCTM()), dstPt.matrixTransform(dst[0][0].getCTM())]);
246 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700247
248 // remove old links
249 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700250}
251
252function updateControllers(model) {
253 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700254 controllers.enter().append('div')
255 .attr('class', function (d) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700256
257 var color = 'color0';
258 if (model.activeControllers.indexOf(d) != -1) {
259 color = controllerColorMap[d];
260 if (!color) {
261 color = controllerColorMap[d] = colors.pop();
262 }
263 } else {
264 controllerColorMap[d] = color;
Paul Greysond1a22d92013-03-19 12:15:19 -0700265 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700266 var className = 'controller ' + color;
267 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700268 });
269 controllers.text(function (d) {
270 return d;
271 });
272 controllers.exit().remove();
273
Paul Greyson3e142162013-03-19 13:56:17 -0700274 model.controllers.forEach(function (c) {
275 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
276 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700277
Paul Greyson3e142162013-03-19 13:56:17 -0700278 controllers.on('click', function (c, index) {
Paul Greysonc3e21a02013-03-21 13:56:05 -0700279 var allSelected = true;
280 for (var key in controllerColorMap) {
281 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
282 allSelected = false;
283 break;
284 }
285 }
286 if (allSelected) {
287 for (var key in controllerColorMap) {
288 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
289 }
290 } else {
291 for (var key in controllerColorMap) {
292 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
293 }
294 }
295
296 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
297 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -0700298 });
Paul Greyson740bdaf2013-03-18 16:10:48 -0700299}
300
Paul Greysonb48943b2013-03-19 13:27:57 -0700301var oldModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700302function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700303 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -0700304 updateModel(function (newModel) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700305 console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -0700306
Paul Greysonc17278a2013-03-23 10:17:12 -0700307 if (true || !oldModel && JSON.stringify(oldModel) != JSON.stringify(newModel)) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700308 updateControllers(newModel);
309 updateTopology(svg, newModel);
310 } else {
311 console.log('no change');
312 }
313 updateHeader(newModel);
314
315 oldModel = newModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700316
317 // do it again in 1s
318 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -0700319 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -0700320 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -0700321 });
322}
Paul Greyson740bdaf2013-03-18 16:10:48 -0700323
Paul Greyson38d8bde2013-03-22 22:07:35 -0700324svg = createTopologyView();
325// workaround for Chrome v25 bug
326// if executed immediately, the view box transform logic doesn't work properly
327// fixed in Chrome v27
328setTimeout(function () {
329 // workaround for another Chrome v25 bug
330 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -0700331 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -0700332 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
333 sync(svg);
334}, 100);