blob: 2ceee48513ea1613f9c97a7bc0ad0f610661bb36 [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')
Paul Greysone262a292013-03-23 10:35:23 -0700255 .each(function (c) {
256 controllerColorMap[c] = colors.pop();
257 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
258 })
259 .text(function (d) {
260 return d;
261 });
Paul Greysonbcd3c772013-03-21 13:16:44 -0700262
Paul Greysone262a292013-03-23 10:35:23 -0700263 controllers.attr('class', function (d) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700264 var color = 'color0';
265 if (model.activeControllers.indexOf(d) != -1) {
266 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -0700267 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700268 var className = 'controller ' + color;
269 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700270 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700271
Paul Greysone262a292013-03-23 10:35:23 -0700272 // this should never be needed
273 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700274
Paul Greyson3e142162013-03-19 13:56:17 -0700275 controllers.on('click', function (c, index) {
Paul Greysonc3e21a02013-03-21 13:56:05 -0700276 var allSelected = true;
277 for (var key in controllerColorMap) {
278 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
279 allSelected = false;
280 break;
281 }
282 }
283 if (allSelected) {
284 for (var key in controllerColorMap) {
285 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
286 }
287 } else {
288 for (var key in controllerColorMap) {
289 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
290 }
291 }
292
293 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
294 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -0700295 });
Paul Greyson740bdaf2013-03-18 16:10:48 -0700296}
297
Paul Greysonb48943b2013-03-19 13:27:57 -0700298var oldModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700299function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700300 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -0700301 updateModel(function (newModel) {
Paul Greysonbcd3c772013-03-21 13:16:44 -0700302 console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -0700303
Paul Greysone262a292013-03-23 10:35:23 -0700304 if (!oldModel || JSON.stringify(oldModel) != JSON.stringify(newModel)) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700305 updateControllers(newModel);
306 updateTopology(svg, newModel);
307 } else {
308 console.log('no change');
309 }
310 updateHeader(newModel);
311
312 oldModel = newModel;
Paul Greyson740bdaf2013-03-18 16:10:48 -0700313
314 // do it again in 1s
315 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -0700316 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -0700317 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -0700318 });
319}
Paul Greyson740bdaf2013-03-18 16:10:48 -0700320
Paul Greyson38d8bde2013-03-22 22:07:35 -0700321svg = createTopologyView();
322// workaround for Chrome v25 bug
323// if executed immediately, the view box transform logic doesn't work properly
324// fixed in Chrome v27
325setTimeout(function () {
326 // workaround for another Chrome v25 bug
327 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -0700328 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -0700329 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
330 sync(svg);
331}, 100);