blob: 28e4da72b5d1450ef93fc712420f09d00a931c0a [file] [log] [blame]
Paul Greyson127d7fb2013-03-25 23:39:20 -07001/*global d3, document∆*/
Paul Greyson740bdaf2013-03-18 16:10:48 -07002
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
Paul Greyson127d7fb2013-03-25 23:39:20 -07009var line = d3.svg.line()
10 .x(function(d) {
11 return d.x;
12 })
13 .y(function(d) {
14 return d.y;
15 });
16
Paul Greyson56378ed2013-03-26 23:17:36 -070017var model;
Paul Greyson29aa98d2013-03-28 00:09:31 -070018var svg;
Paul Greyson56378ed2013-03-26 23:17:36 -070019var updateTopology;
Paul Greyson40c8a592013-03-27 14:10:33 -070020var pendingLinks = {};
Paul Greyson6f918402013-03-28 12:18:30 -070021var selectedFlows = [];
22
Paul Greyson97cc9f52013-04-04 01:45:40 -070023var pendingTimeout = 30000;
Paul Greyson127d7fb2013-03-25 23:39:20 -070024
Paul Greysond1a22d92013-03-19 12:15:19 -070025var colors = [
Paul Greyson3e142162013-03-19 13:56:17 -070026 'color1',
27 'color2',
28 'color3',
29 'color4',
Paul Greyson3e142162013-03-19 13:56:17 -070030 'color7',
31 'color8',
32 'color9',
Paul Greyson5cc35f02013-03-28 10:07:36 -070033// 'color11',
Paul Greyson127d7fb2013-03-25 23:39:20 -070034 'color12'
35];
Paul Greyson01a5dff2013-03-19 15:50:14 -070036colors.reverse();
Paul Greysond1a22d92013-03-19 12:15:19 -070037
38var controllerColorMap = {};
39
Paul Greyson084779b2013-03-27 13:55:49 -070040function setPending(selection) {
41 selection.classed('pending', false);
42 setTimeout(function () {
43 selection.classed('pending', true);
44 })
45}
Paul Greysond1a22d92013-03-19 12:15:19 -070046
Paul Greyson740bdaf2013-03-18 16:10:48 -070047function createTopologyView() {
Paul Greyson56378ed2013-03-26 23:17:36 -070048
49 window.addEventListener('resize', function () {
50 // this is too slow. instead detect first resize event and hide the paths that have explicit matrix applied
51 // either that or is it possible to position the paths so they get the automatic transform as well?
Paul Greyson5cc35f02013-03-28 10:07:36 -070052// updateTopology();
Paul Greyson56378ed2013-03-26 23:17:36 -070053 });
54
Paul Greysonb367de22013-03-23 11:09:11 -070055 var svg = d3.select('#svg-container').append('svg:svg');
56
57 svg.append("svg:defs").append("svg:marker")
58 .attr("id", "arrow")
59 .attr("viewBox", "0 -5 10 10")
60 .attr("refX", -1)
61 .attr("markerWidth", 5)
62 .attr("markerHeight", 5)
63 .attr("orient", "auto")
64 .append("svg:path")
Paul Greyson45303ac2013-03-23 16:44:01 -070065 .attr("d", "M0,-3L10,0L0,3");
Paul Greysonb367de22013-03-23 11:09:11 -070066
67 return svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
Paul Greyson952ccb62013-03-18 22:22:08 -070068 attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
Paul Greyson740bdaf2013-03-18 16:10:48 -070069}
70
Paul Greyson13f02b92013-03-28 11:29:35 -070071function updateSelectedFlowsTopology() {
Paul Greyson127d7fb2013-03-25 23:39:20 -070072 // DRAW THE FLOWS
Paul Greyson97cc9f52013-04-04 01:45:40 -070073 var topologyFlows = [];
74 selectedFlows.forEach(function (flow) {
75 if (flow) {
76 topologyFlows.push(flow);
77 }
78 });
79
80 var flows = d3.select('svg').selectAll('.flow').data(topologyFlows);
Paul Greyson127d7fb2013-03-25 23:39:20 -070081
Paul Greyson29aa98d2013-03-28 00:09:31 -070082 flows.enter().append("svg:path").attr('class', 'flow')
83 .attr('stroke-dasharray', '4, 10')
84 .append('svg:animate')
85 .attr('attributeName', 'stroke-dashoffset')
86 .attr('attributeType', 'xml')
87 .attr('from', '500')
88 .attr('to', '-500')
89 .attr('dur', '20s')
90 .attr('repeatCount', 'indefinite');
91
Paul Greyson97cc9f52013-04-04 01:45:40 -070092 flows.exit().remove();
Paul Greyson29aa98d2013-03-28 00:09:31 -070093
94 flows.attr('d', function (d) {
Paul Greyson13f02b92013-03-28 11:29:35 -070095 if (!d) {
96 return;
97 }
98 var pts = [];
Paul Greyson280a2752013-04-04 03:21:59 -070099 if (!d.dataPath.flowEntries || !d.dataPath.flowEntries.length) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700100 // create a temporary vector to indicate the pending flow
101 var s1 = d3.select(document.getElementById(d.dataPath.srcPort.dpid.value));
102 var s2 = d3.select(document.getElementById(d.dataPath.dstPort.dpid.value));
103
104 var pt1 = document.querySelector('svg').createSVGPoint();
105 pt1.x = s1.attr('x');
106 pt1.y = s1.attr('y');
107 pt1 = pt1.matrixTransform(s1[0][0].getCTM());
108 pts.push(pt1);
109
110 var pt2 = document.querySelector('svg').createSVGPoint();
111 pt2.x = s2.attr('x');
112 pt2.y = s2.attr('y');
113 pt2 = pt2.matrixTransform(s2[0][0].getCTM());
114 pts.push(pt2);
115
116 } else {
117 d.dataPath.flowEntries.forEach(function (flowEntry) {
118 var s = d3.select(document.getElementById(flowEntry.dpid.value));
119 // s[0] is null if the flow entry refers to a non-existent switch
120 if (s[0][0]) {
121 var pt = document.querySelector('svg').createSVGPoint();
122 pt.x = s.attr('x');
123 pt.y = s.attr('y');
124 pt = pt.matrixTransform(s[0][0].getCTM());
125 pts.push(pt);
126 } else {
127 console.log('flow refers to non-existent switch: ' + flowEntry.dpid.value);
128 }
129 });
130 }
131 return line(pts);
132 })
Paul Greyson71b550d2013-03-28 11:56:19 -0700133 .attr('id', function (d) {
134 if (d) {
135 return makeFlowKey(d);
136 }
137 })
Paul Greyson13f02b92013-03-28 11:29:35 -0700138 .classed('pending', function (d) {
Paul Greysonaa812562013-03-28 12:43:12 -0700139 return d && (d.createPending || d.deletePending);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700140 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700141
Paul Greyson56378ed2013-03-26 23:17:36 -0700142 // "marching ants"
Paul Greyson29aa98d2013-03-28 00:09:31 -0700143 flows.select('animate').attr('from', 500);
144
Paul Greyson13f02b92013-03-28 11:29:35 -0700145}
146
147function updateSelectedFlowsTable() {
148 function rowEnter(d) {
149 var row = d3.select(this);
Paul Greyson6f918402013-03-28 12:18:30 -0700150 row.append('div').classed('deleteFlow', true);
Paul Greyson13f02b92013-03-28 11:29:35 -0700151 row.append('div').classed('flowId', true);
152 row.append('div').classed('srcDPID', true);
153 row.append('div').classed('dstDPID', true);
154 row.append('div').classed('iperf', true);
Paul Greyson71b550d2013-03-28 11:56:19 -0700155
Paul Greyson95db7a12013-04-04 14:57:58 -0700156 row.select('.iperf')
157 .append('div')
158 .attr('class', 'iperf-container')
159 .append('svg:svg')
160 .attr('viewBox', '0 0 1000 32')
161 .attr('preserveAspectRatio', 'none')
162 .append('svg:g')
163 .append('svg:path')
164 .attr('class', 'iperfdata');
165
Paul Greyson71b550d2013-03-28 11:56:19 -0700166 row.on('mouseover', function (d) {
167 if (d) {
168 var path = document.getElementById(makeFlowKey(d));
169 d3.select(path).classed('highlight', true);
170 }
171 });
172 row.on('mouseout', function (d) {
173 if (d) {
174 var path = document.getElementById(makeFlowKey(d));
175 d3.select(path).classed('highlight', false);
176 }
Paul Greyson95db7a12013-04-04 14:57:58 -0700177 });
Paul Greyson13f02b92013-03-28 11:29:35 -0700178 }
179
180 function rowUpdate(d) {
181 var row = d3.select(this);
Paul Greyson95db7a12013-04-04 14:57:58 -0700182 row.attr('id', function (d) {
183 if (d) {
184 return makeSelectedFlowKey(d);
185 }
186 });
Paul Greyson6f918402013-03-28 12:18:30 -0700187 row.select('.deleteFlow').on('click', function () {
Paul Greysonbe8bf872013-04-04 01:53:45 -0700188 selectedFlows[selectedFlows.indexOf(d)] = null;
189 updateSelectedFlows();
190 });
191 row.on('dblclick', function () {
Paul Greyson6f918402013-03-28 12:18:30 -0700192 if (d) {
193 var prompt = 'Delete flow ' + d.flowId.value + '?';
194 if (confirm(prompt)) {
195 deleteFlow(d);
Paul Greysonaa812562013-03-28 12:43:12 -0700196 d.deletePending = true;
Paul Greyson6f918402013-03-28 12:18:30 -0700197 updateSelectedFlows();
198
199 setTimeout(function () {
Paul Greysonaa812562013-03-28 12:43:12 -0700200 d.deletePending = false;
Paul Greyson6f918402013-03-28 12:18:30 -0700201 updateSelectedFlows();
202 }, pendingTimeout)
203 };
204 }
205 });
206
Paul Greyson13f02b92013-03-28 11:29:35 -0700207 row.select('.flowId')
208 .text(function (d) {
209 if (d) {
210 if (d.flowId) {
211 return d.flowId.value;
212 } else {
213 return '0x--';
214 }
215 }
216 })
Paul Greysoncb5d30d2013-04-04 02:00:56 -0700217 .classed('pending', function (d) {
218 return d && (d.createPending || d.deletePending);
219 });
Paul Greyson13f02b92013-03-28 11:29:35 -0700220
221 row.select('.srcDPID')
222 .text(function (d) {
223 if (d) {
224 return d.dataPath.srcPort.dpid.value;
225 }
226 });
227
228 row.select('.dstDPID')
229 .text(function (d) {
230 if (d) {
231 return d.dataPath.dstPort.dpid.value;
232 }
233 });
234 }
235
236 var flows = d3.select('#selectedFlows')
237 .selectAll('.selectedFlow')
238 .data(selectedFlows);
239
240 flows.enter()
241 .append('div')
242 .classed('selectedFlow', true)
243 .each(rowEnter);
244
245 flows.each(rowUpdate);
246
Paul Greyson29aa98d2013-03-28 00:09:31 -0700247 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700248}
249
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700250// TODO: cancel the interval when the flow is desel
251function startIPerfForFlow(flow) {
Paul Greyson95db7a12013-04-04 14:57:58 -0700252 var duration = 10000; // seconds
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700253 var interval = 100; // ms. this is defined by the server
254 var updateRate = 1000; // ms
255
256 if (flow.flowId) {
257 console.log('starting iperf for: ' + flow.flowId.value);
258 startIPerf(flow, duration, updateRate/interval);
259 flow.iperfInterval = setInterval(function () {
260 getIPerfData(flow, function (data) {
Paul Greyson95db7a12013-04-04 14:57:58 -0700261 try {
262 var iperfData = JSON.parse(data);
263 // if the data is fresh
264 if (flow.iperfData && iperfData.timeStamp != flow.iperfData.timestamp) {
265 var iperfPath = d3.select(document.getElementById(makeSelectedFlowKey(flow))).select('path');
266 var pts = [];
267 var i;
268 for (i = 0; i < iperfData.samples.length; i += 1) {
269 var sample = iperfData.samples[i];
270 var height = 32 * sample/100000000;
271 if (height > 32)
272 height = 32;
273 pts.push({
274 x: i * 1000/(iperfData.samples.length-1),
275 y: height
276 })
277 }
278 iperfPath.attr('d', line(pts));
279
280 }
281 flow.iperfData = iperfData;
282 } catch (e) {
283 console.log('bad iperf data: ' + data);
284 }
285// console.log(data);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700286 });
Paul Greyson95db7a12013-04-04 14:57:58 -0700287 }, updateRate);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700288 }
289}
290
Paul Greyson13f02b92013-03-28 11:29:35 -0700291function updateSelectedFlows() {
292 // make sure that all of the selected flows are either
293 // 1) valid (meaning they are in the latest list of flows)
294 // 2) pending
295 if (model) {
296 var flowMap = {};
297 model.flows.forEach(function (flow) {
298 flowMap[makeFlowKey(flow)] = flow;
299 });
300
301 var newSelectedFlows = [];
302 selectedFlows.forEach(function (flow) {
303 if (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700304 var liveFlow = flowMap[makeFlowKey(flow)];
305 if (liveFlow) {
306 newSelectedFlows.push(liveFlow);
Paul Greysonaa812562013-03-28 12:43:12 -0700307 liveFlow.deletePending = flow.deletePending;
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700308 liveFlow.iperfInterval = flow.iperfInterval;
Paul Greysonaa812562013-03-28 12:43:12 -0700309 } else if (flow.createPending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700310 newSelectedFlows.push(flow);
Paul Greyson13f02b92013-03-28 11:29:35 -0700311 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700312 }
313 });
314 selectedFlows = newSelectedFlows;
315 }
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700316 selectedFlows.forEach(function (flow) {
317 if (!flow.iperfInterval) {
318 startIPerfForFlow(flow);
319 }
320 });
Paul Greyson6f918402013-03-28 12:18:30 -0700321 while (selectedFlows.length < 3) {
322 selectedFlows.push(null);
323 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700324
325 updateSelectedFlowsTable();
326 updateSelectedFlowsTopology();
327}
328
329function selectFlow(flow) {
Paul Greysonc30f75e2013-03-28 11:45:15 -0700330 var flowKey = makeFlowKey(flow);
331 var alreadySelected = false;
332 selectedFlows.forEach(function (f) {
333 if (f && makeFlowKey(f) === flowKey) {
334 alreadySelected = true;
335 }
336 });
337
338 if (!alreadySelected) {
339 selectedFlows.unshift(flow);
340 selectedFlows = selectedFlows.slice(0, 3);
341 updateSelectedFlows();
342 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700343}
344
Paul Greysonaa812562013-03-28 12:43:12 -0700345function deselectFlow(flow, ifCreatePending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700346 var flowKey = makeFlowKey(flow);
347 var newSelectedFlows = [];
348 selectedFlows.forEach(function (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700349 if (!flow ||
350 flowKey !== makeFlowKey(flow) ||
Paul Greysonaa812562013-03-28 12:43:12 -0700351 flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700352 newSelectedFlows.push(flow);
Paul Greyson4b4c8af2013-04-04 09:02:09 -0700353 } else {
354 if (flow && flow.iperfInterval) {
355 console.log('clearing iperf interval for: ' + flow.flowId.value);
356 clearInterval(flow.iperfInterval);
357 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700358 }
359 });
360 selectedFlows = newSelectedFlows;
361 while (selectedFlows.length < 3) {
362 selectedFlows.push(null);
363 }
364
365 updateSelectedFlows();
366}
367
Paul Greysonaa812562013-03-28 12:43:12 -0700368function deselectFlowIfCreatePending(flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700369 deselectFlow(flow, true);
370}
371
Paul Greyson29aa98d2013-03-28 00:09:31 -0700372function showFlowChooser() {
373 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700374 var row = d3.select(this);
375
Paul Greyson127d7fb2013-03-25 23:39:20 -0700376 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700377 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700378 on('click', function () {
Paul Greyson13f02b92013-03-28 11:29:35 -0700379 selectFlow(d);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700380 });
381
382 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700383 .classed('flowId', true)
384 .text(function (d) {
385 return d.flowId.value;
386 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700387
388 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700389 .classed('srcDPID', true)
390 .text(function (d) {
391 return d.dataPath.srcPort.dpid.value;
392 });
393
Paul Greyson127d7fb2013-03-25 23:39:20 -0700394
395 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700396 .classed('dstDPID', true)
397 .text(function (d) {
398 return d.dataPath.dstPort.dpid.value;
399 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700400
Paul Greyson127d7fb2013-03-25 23:39:20 -0700401 }
402
Paul Greyson29aa98d2013-03-28 00:09:31 -0700403 var flows = d3.select('#flowChooser')
404 .append('div')
405 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700406 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700407 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700408 .enter()
409 .append('div')
410 .classed('selectedFlow', true)
411 .each(rowEnter);
412
Paul Greyson29aa98d2013-03-28 00:09:31 -0700413 setTimeout(function () {
414 d3.select(document.body).on('click', function () {
415 d3.select('#flowChooser').html('');
416 d3.select(document.body).on('click', null);
417 });
418 }, 0);
419}
420
Paul Greyson29aa98d2013-03-28 00:09:31 -0700421
Paul Greyson127d7fb2013-03-25 23:39:20 -0700422
Paul Greysond1a22d92013-03-19 12:15:19 -0700423function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700424 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700425 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
426 d3.select('#activeFlows').text(model.flows.length);
427}
428
429function toRadians (angle) {
430 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700431}
432
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700433var widths = {
434 edge: 6,
435 aggregation: 12,
436 core: 18
437}
438
Paul Greysonc17278a2013-03-23 10:17:12 -0700439function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700440 var rings = [{
441 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700442 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700443 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700444 className: 'edge',
445 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700446 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700447 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700448 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700449 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700450 className: 'aggregation',
451 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700452 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700453 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700454 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700455 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700456 className: 'core',
457 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700458 }];
459
Paul Greysonde7fad52013-03-19 12:47:32 -0700460
461 var aggRanges = {};
462
463 // arrange edge switches at equal increments
464 var k = 360 / rings[0].switches.length;
465 rings[0].switches.forEach(function (s, i) {
466 var angle = k * i;
467
468 rings[0].angles[i] = angle;
469
470 // record the angle for the agg switch layout
471 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700472 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700473 var aggdpid = dpid.join(':');
474 var aggRange = aggRanges[aggdpid];
475 if (!aggRange) {
476 aggRange = aggRanges[aggdpid] = {};
477 aggRange.min = aggRange.max = angle;
478 } else {
479 aggRange.max = angle;
480 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700481 });
482
483 // arrange aggregation switches to "fan out" to edge switches
484 k = 360 / rings[1].switches.length;
485 rings[1].switches.forEach(function (s, i) {
486// rings[1].angles[i] = k * i;
487 var range = aggRanges[s.dpid];
488
Paul Greyson832d2202013-03-21 13:27:56 -0700489 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700490 });
491
Paul Greyson3f890b62013-03-22 17:39:36 -0700492 // find the association between core switches and aggregation switches
493 var aggregationSwitchMap = {};
494 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700495 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700496 });
497
Paul Greyson3f890b62013-03-22 17:39:36 -0700498 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700499 k = 360 / rings[2].switches.length;
500 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700501// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700502 var associatedAggregationSwitches = model.configuration.association[s.dpid];
503 // TODO: go between if there are multiple
504 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
505
506 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700507 });
508
Paul Greyson644d92a2013-03-23 18:00:40 -0700509 // TODO: construct this form initially rather than converting. it works better because
510 // it allows binding by dpid
511 var testRings = [];
512 rings.forEach(function (ring) {
513 var testRing = [];
514 ring.switches.forEach(function (s, i) {
515 var testSwitch = {
516 dpid: s.dpid,
517 state: s.state,
518 radius: ring.radius,
519 width: ring.width,
520 className: ring.className,
521 angle: ring.angles[i],
522 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700523 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700524 testRing.push(testSwitch);
525 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700526
527
Paul Greyson644d92a2013-03-23 18:00:40 -0700528 testRings.push(testRing);
529 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700530
531
Paul Greyson644d92a2013-03-23 18:00:40 -0700532// return rings;
533 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700534}
535
Paul Greyson40c8a592013-03-27 14:10:33 -0700536function makeLinkKey(link) {
537 return link['src-switch'] + '=>' + link['dst-switch'];
538}
539
Paul Greyson13f02b92013-03-28 11:29:35 -0700540function makeFlowKey(flow) {
541 return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
542}
543
Paul Greyson95db7a12013-04-04 14:57:58 -0700544function makeSelectedFlowKey(flow) {
545 return 'S' + makeFlowKey(flow);
546}
547
Paul Greyson40c8a592013-03-27 14:10:33 -0700548function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700549 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700550 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700551 var srcDPID = link['src-switch'];
552 var dstDPID = link['dst-switch'];
553
554 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700555
Paul Greyson8d1c6362013-03-27 13:05:24 -0700556 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700557
558 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700559 });
560 return linkMap;
561}
562
Paul Greyson5cc35f02013-03-28 10:07:36 -0700563// removes links from the pending list that are now in the model
564function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700565 var links = [];
566 model.links.forEach(function (link) {
567 links.push(link);
568 delete pendingLinks[makeLinkKey(link)]
569 })
570 var linkId;
571 for (linkId in pendingLinks) {
572 links.push(pendingLinks[linkId]);
573 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700574 return links
575}
Paul Greyson40c8a592013-03-27 14:10:33 -0700576
Paul Greyson5cc35f02013-03-28 10:07:36 -0700577updateTopology = function() {
578
579 // DRAW THE SWITCHES
580 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
581
582 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700583 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700584
Paul Greyson8d1c6362013-03-27 13:05:24 -0700585 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700586
587 d3.event.preventDefault();
588
Paul Greyson5cc35f02013-03-28 10:07:36 -0700589 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
590
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700591 if (data.highlighted) {
592 return;
593 }
594
595 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700596 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700597 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700598 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700599 // only allow links
600 // edge->edge (flow)
601 // aggregation->core
602 // core->core
603 if (data.className == 'edge' && !s.classed('edge') ||
604 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
605 data.className == 'aggregation' && !s.classed('core')) {
606 return;
607 }
608
609 // don't highlight if there's already a link or flow
610 // var map = linkMap[data.dpid];
611 // console.log(map);
612 // console.log(s.data()[0].dpid);
613 // console.log(map[s.data()[0].dpid]);
614 // if (map && map[s.data()[0].dpid]) {
615 // return;
616 // }
617
618 // the second highlighted switch is the target for a link or flow
619 data.target = true;
620 }
621
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700622 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700623 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700624 data.highlighted = true;
625 node.moveToFront();
626 }
627
Paul Greyson8d1c6362013-03-27 13:05:24 -0700628 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700629 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
630
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700631 if (data.mouseDown)
632 return;
633
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700634 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700635 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700636 data.highlighted = false;
637 data.target = false;
638 }
639
Paul Greyson8d1c6362013-03-27 13:05:24 -0700640 function mouseDownSwitch(data) {
641 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700642 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700643 d3.select('#topology').classed('linking', true);
644
Paul Greyson421bfcd2013-03-27 22:22:09 -0700645 d3.select('svg')
646 .append('svg:path')
647 .attr('id', 'linkVector')
648 .attr('d', function () {
649 var s = d3.select(document.getElementById(data.dpid));
650
651 var pt = document.querySelector('svg').createSVGPoint();
652 pt.x = s.attr('x');
653 pt.y = s.attr('y');
654 pt = pt.matrixTransform(s[0][0].getCTM());
655
656 return line([pt, pt]);
657 });
658
659
Paul Greyson72f18852013-03-27 15:56:11 -0700660 if (data.className === 'core') {
661 d3.selectAll('.edge').classed('nodrop', true);
662 }
663 if (data.className === 'edge') {
664 d3.selectAll('.core').classed('nodrop', true);
665 d3.selectAll('.aggregation').classed('nodrop', true);
666 }
667 if (data.className === 'aggregation') {
668 d3.selectAll('.edge').classed('nodrop', true);
669 d3.selectAll('.aggregation').classed('nodrop', true);
670 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700671 }
672
Paul Greyson8d1c6362013-03-27 13:05:24 -0700673 function mouseUpSwitch(data) {
674 if (data.mouseDown) {
675 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700676 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700677 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700678 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700679 }
680 }
681
682 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700683 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700684 if (data.state == 'ACTIVE') {
685 var prompt = 'Deactivate ' + data.dpid + '?';
686 if (confirm(prompt)) {
687 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700688 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700689 }
690 } else {
691 var prompt = 'Activate ' + data.dpid + '?';
692 if (confirm(prompt)) {
693 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700694 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700695 }
696 }
697 }
698
Paul Greyson740bdaf2013-03-18 16:10:48 -0700699 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700700 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700701 return;
702 }
703
Paul Greysonc17278a2013-03-23 10:17:12 -0700704 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700705 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700706 .data(data, function (data) {
707 return data.dpid;
708 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700709 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700710 .attr("id", function (data, i) {
711 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700712 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700713 .attr("transform", function(data, i) {
714 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700715 });
716
Paul Greysonc17278a2013-03-23 10:17:12 -0700717 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700718 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700719 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700720 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700721 if (data.scale) {
722 m = m.scale(data.scale);
723 }
724 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700725 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700726 .attr("x", function (data) {
727 return -data.width / 2;
728 })
729 .attr("y", function (data) {
730 return -data.width / 2;
731 })
732 .attr("r", function (data) {
733 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700734 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700735
Paul Greysonc17278a2013-03-23 10:17:12 -0700736 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700737 nodes.on('mouseover', mouseOverSwitch);
738 nodes.on('mouseout', mouseOutSwitch);
739 nodes.on('mouseup', mouseUpSwitch);
740 nodes.on('mousedown', mouseDownSwitch);
741
742 // only do switch up/down for core switches
743 if (i == 2) {
744 nodes.on('dblclick', doubleClickSwitch);
745 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700746 }
747
Paul Greysonc17278a2013-03-23 10:17:12 -0700748 // append switches
749 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700750 .attr("class", "ring")
751 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700752
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700753
Paul Greysonc17278a2013-03-23 10:17:12 -0700754 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700755 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700756 .data(data, function (data) {
757 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700758 });
Paul Greyson347fb742013-03-27 13:40:29 -0700759 nodes.select('circle')
760 .each(function (data) {
761 // if there's a pending state changed and then the state changes, clear the pending class
762 var circle = d3.select(this);
763 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
764 data.state === 'INACTIVE' && circle.classed('active')) {
765 circle.classed('pending', false);
766 }
767 })
768 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700769 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700770 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700771 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700772 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700773 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700774 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700775 }
776
777 // update switches
778 rings.each(ringUpdate);
779
Paul Greyson968d1b42013-03-23 16:58:41 -0700780
781 // Now setup the labels
782 // This is done separately because SVG draws in node order and we want the labels
783 // always on top
784 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
785
Paul Greyson421bfcd2013-03-27 22:22:09 -0700786 d3.select(document.body).on('mousemove', function () {
787 if (!d3.select('#topology').classed('linking')) {
788 return;
789 }
790 var linkVector = document.getElementById('linkVector');
791 if (!linkVector) {
792 return;
793 }
794 linkVector = d3.select(linkVector);
795
796 var highlighted = svg.selectAll('.highlight')[0];
797 var s1 = null, s2 = null;
798 if (highlighted.length > 1) {
799 var s1 = d3.select(highlighted[0]);
800 var s2 = d3.select(highlighted[1]);
801
802 } else if (highlighted.length > 0) {
803 var s1 = d3.select(highlighted[0]);
804 }
805 var src = s1;
806 if (s2 && !s2.data()[0].target) {
807 src = s2;
808 }
809 if (src) {
810 linkVector.attr('d', function () {
811 var srcPt = document.querySelector('svg').createSVGPoint();
812 srcPt.x = src.attr('x');
813 srcPt.y = src.attr('y');
814 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
815
816 var svg = document.getElementById('topology');
817 var mouse = d3.mouse(viewbox);
818 var dstPt = document.querySelector('svg').createSVGPoint();
819 dstPt.x = mouse[0];
820 dstPt.y = mouse[1];
821 dstPt = dstPt.matrixTransform(viewbox.getCTM());
822
823 return line([srcPt, dstPt]);
824 });
825 }
826 });
827
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700828 d3.select(document.body).on('mouseup', function () {
829 function clearHighlight() {
830 svg.selectAll('circle').each(function (data) {
831 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700832 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700833 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700834 });
835 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700836 };
837
Paul Greyson72f18852013-03-27 15:56:11 -0700838 d3.selectAll('.nodrop').classed('nodrop', false);
839
Paul Greyson084779b2013-03-27 13:55:49 -0700840 function removeLink(link) {
841 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
842 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
843
844 if (path1) {
845 setPending(d3.select(path1));
846 }
847 if (path2) {
848 setPending(d3.select(path2));
849 }
850
851 linkDown(link);
852 }
853
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700854
Paul Greyson421bfcd2013-03-27 22:22:09 -0700855 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700856 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700857 var s1Data = highlighted[0].__data__;
858 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700859
860 var srcData, dstData;
861 if (s1Data.target) {
862 dstData = s1Data;
863 srcData = s2Data;
864 } else {
865 dstData = s2Data;
866 srcData = s1Data;
867 }
868
869 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
870 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
871 if (confirm(prompt)) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700872 addFlow(srcData, dstData);
873
874 var flow = {
875 dataPath: {
876 srcPort: {
877 dpid: {
878 value: srcData.dpid
879 }
880 },
881 dstPort: {
882 dpid: {
883 value: dstData.dpid
884 }
885 }
886 },
Paul Greysonaa812562013-03-28 12:43:12 -0700887 createPending: true
Paul Greyson13f02b92013-03-28 11:29:35 -0700888 };
889
890 selectFlow(flow);
891
892 setTimeout(function () {
Paul Greysonaa812562013-03-28 12:43:12 -0700893 deselectFlowIfCreatePending(flow);
Paul Greyson6f918402013-03-28 12:18:30 -0700894 }, pendingTimeout);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700895 }
896 } else {
897 var map = linkMap[srcData.dpid];
898 if (map && map[dstData.dpid]) {
899 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
900 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700901 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700902 }
903 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700904 map = linkMap[dstData.dpid];
905 if (map && map[srcData.dpid]) {
906 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
907 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700908 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700909 }
910 } else {
911 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
912 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700913 var link1 = {
914 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700915 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700916 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700917 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700918 pending: true
919 };
920 pendingLinks[makeLinkKey(link1)] = link1;
921 var link2 = {
922 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700923 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700924 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700925 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700926 pending: true
927 };
928 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700929 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700930
Paul Greyson2913af82013-03-27 14:53:17 -0700931 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700932
Paul Greyson5cc35f02013-03-28 10:07:36 -0700933 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700934 setTimeout(function () {
935 delete pendingLinks[makeLinkKey(link1)];
936 delete pendingLinks[makeLinkKey(link2)];
937
Paul Greyson5cc35f02013-03-28 10:07:36 -0700938 updateTopology();
Paul Greyson6f918402013-03-28 12:18:30 -0700939 }, pendingTimeout);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700940 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700941 }
942 }
943 }
944
945 clearHighlight();
946 } else {
947 clearHighlight();
948 }
949
950 });
951
Paul Greyson9066ab02013-03-23 18:15:41 -0700952 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700953 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700954 return;
955 }
956
957 // create the nodes
958 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700959 .data(data, function (data) {
960 return data.dpid;
961 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700962 .enter().append("svg:g")
963 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700964 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700965 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700966 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700967 .attr("transform", function(data, i) {
968 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700969 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700970
971 // add the text nodes which show on mouse over
972 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700973 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700974 .attr("x", function (data) {
975 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
976 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700977 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700978 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700979 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700980 }
981 } else {
982 if (data.className == 'edge') {
983 return data.width*3 + 4;
984 } else {
985 return data.width + 4;
986 }
987 }
988 })
989 .attr("y", function (data) {
990 var y;
991 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
992 if (data.className == 'edge') {
993 y = data.width*3/2 + 4;
994 } else {
995 y = data.width/2 + 4;
996 }
997 } else {
998 if (data.className == 'edge') {
999 y = data.width*3/2 + 4;
1000 } else {
1001 y = data.width/2 + 4;
1002 }
1003 }
1004 return y - 6;
1005 })
1006 .attr("text-anchor", function (data) {
1007 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
1008 return "end";
1009 } else {
1010 return "start";
1011 }
1012 })
1013 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -07001014 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
1015 if (data.scale) {
1016 m = m.scale(data.scale);
1017 }
1018 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
1019 })
1020 }
1021
1022 labelRings.enter().append("svg:g")
1023 .attr("class", "textRing")
1024 .each(labelRingEnter);
1025
Paul Greysonc17278a2013-03-23 10:17:12 -07001026 // switches should not change during operation of the ui so no
1027 // rings.exit()
1028
1029
Paul Greysond1a22d92013-03-19 12:15:19 -07001030 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -07001031
Paul Greysonc17278a2013-03-23 10:17:12 -07001032 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -07001033 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -07001034 return d['src-switch']+'->'+d['dst-switch'];
1035 });
1036
1037 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -07001038 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -07001039 .attr("class", "link");
1040
Paul Greyson084779b2013-03-27 13:55:49 -07001041 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -07001042 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -07001043 })
Paul Greyson56378ed2013-03-26 23:17:36 -07001044 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -07001045 var src = d3.select(document.getElementById(d['src-switch']));
1046 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -07001047
Paul Greyson084779b2013-03-27 13:55:49 -07001048 var srcPt = document.querySelector('svg').createSVGPoint();
1049 srcPt.x = src.attr('x');
1050 srcPt.y = src.attr('y');
1051 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -07001052
Paul Greyson084779b2013-03-27 13:55:49 -07001053 var dstPt = document.querySelector('svg').createSVGPoint();
1054 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -07001055 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -07001056 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -07001057
Paul Greyson084779b2013-03-27 13:55:49 -07001058 var midPt = document.querySelector('svg').createSVGPoint();
1059 midPt.x = (srcPt.x + dstPt.x)/2;
1060 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -07001061
Paul Greyson084779b2013-03-27 13:55:49 -07001062 return line([srcPt, midPt, dstPt]);
1063 })
Paul Greyson40c8a592013-03-27 14:10:33 -07001064 .attr("marker-mid", function(d) { return "url(#arrow)"; })
1065 .classed('pending', function (d) {
1066 return d.pending;
1067 });
Paul Greysonc17278a2013-03-23 10:17:12 -07001068
Paul Greyson56378ed2013-03-26 23:17:36 -07001069
Paul Greysonc17278a2013-03-23 10:17:12 -07001070 // remove old links
1071 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -07001072}
1073
Paul Greyson5cc35f02013-03-28 10:07:36 -07001074function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -07001075 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -07001076 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -07001077 .each(function (c) {
1078 controllerColorMap[c] = colors.pop();
1079 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
1080 })
1081 .text(function (d) {
1082 return d;
Paul Greyson2913af82013-03-27 14:53:17 -07001083 })
1084 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -07001085 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -07001086
Paul Greysone262a292013-03-23 10:35:23 -07001087 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -07001088 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -07001089 if (model.activeControllers.indexOf(d) != -1) {
1090 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -07001091 }
Paul Greysonbcd3c772013-03-21 13:16:44 -07001092 var className = 'controller ' + color;
1093 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -07001094 });
Paul Greysond1a22d92013-03-19 12:15:19 -07001095
Paul Greysone262a292013-03-23 10:35:23 -07001096 // this should never be needed
1097 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -07001098
Paul Greyson2913af82013-03-27 14:53:17 -07001099 controllers.on('dblclick', function (c) {
1100 if (model.activeControllers.indexOf(c) != -1) {
1101 var prompt = 'Dectivate ' + c + '?';
1102 if (confirm(prompt)) {
1103 controllerDown(c);
1104 setPending(d3.select(this));
1105 };
1106 } else {
1107 var prompt = 'Activate ' + c + '?';
1108 if (confirm(prompt)) {
1109 controllerUp(c);
1110 setPending(d3.select(this));
1111 };
1112 }
1113 });
1114
Paul Greyson8247c3f2013-03-28 00:24:02 -07001115 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -07001116 var allSelected = true;
1117 for (var key in controllerColorMap) {
1118 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
1119 allSelected = false;
1120 break;
1121 }
1122 }
1123 if (allSelected) {
1124 for (var key in controllerColorMap) {
1125 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
1126 }
1127 } else {
1128 for (var key in controllerColorMap) {
1129 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
1130 }
1131 }
1132
1133 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
1134 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -07001135 });
Paul Greyson8d1c6362013-03-27 13:05:24 -07001136
1137
Paul Greyson740bdaf2013-03-18 16:10:48 -07001138}
1139
Paul Greyson29aa98d2013-03-28 00:09:31 -07001140function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -07001141 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -07001142 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001143// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -07001144
Paul Greysone5991b52013-04-04 01:34:04 -07001145 if (newModel) {
1146 var modelChanged = false;
1147 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
1148 modelChanged = true;
1149 model = newModel;
1150 } else {
1151 // console.log('no change');
1152 }
Paul Greysonb48943b2013-03-19 13:27:57 -07001153
Paul Greysone5991b52013-04-04 01:34:04 -07001154 if (modelChanged) {
1155 updateControllers();
1156 updateSelectedFlows();
1157 updateTopology();
1158 }
Paul Greyson5cc35f02013-03-28 10:07:36 -07001159
Paul Greysone5991b52013-04-04 01:34:04 -07001160 updateHeader(newModel);
1161 }
Paul Greyson740bdaf2013-03-18 16:10:48 -07001162
1163 // do it again in 1s
1164 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -07001165 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -07001166 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -07001167 });
1168}
Paul Greyson740bdaf2013-03-18 16:10:48 -07001169
Paul Greyson38d8bde2013-03-22 22:07:35 -07001170svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -07001171updateSelectedFlows();
1172
1173d3.select('#showFlowChooser').on('click', function () {
1174 showFlowChooser();
1175});
1176
Paul Greyson72f18852013-03-27 15:56:11 -07001177
Paul Greyson38d8bde2013-03-22 22:07:35 -07001178// workaround for Chrome v25 bug
1179// if executed immediately, the view box transform logic doesn't work properly
1180// fixed in Chrome v27
1181setTimeout(function () {
1182 // workaround for another Chrome v25 bug
1183 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -07001184 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -07001185 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -07001186 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -07001187}, 100);