blob: 144a1d2e5bf8ec5523eb1e33bf89185f920a4afb [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
23var pendingTimeout = 10000;
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 Greyson13f02b92013-03-28 11:29:35 -070073 var flows = d3.select('svg').selectAll('.flow').data(selectedFlows);
Paul Greyson127d7fb2013-03-25 23:39:20 -070074
Paul Greyson29aa98d2013-03-28 00:09:31 -070075 flows.enter().append("svg:path").attr('class', 'flow')
76 .attr('stroke-dasharray', '4, 10')
77 .append('svg:animate')
78 .attr('attributeName', 'stroke-dashoffset')
79 .attr('attributeType', 'xml')
80 .attr('from', '500')
81 .attr('to', '-500')
82 .attr('dur', '20s')
83 .attr('repeatCount', 'indefinite');
84
85
86 flows.attr('d', function (d) {
Paul Greyson13f02b92013-03-28 11:29:35 -070087 if (!d) {
88 return;
89 }
90 var pts = [];
Paul Greyson6f918402013-03-28 12:18:30 -070091 if (!d.dataPath.flowEntries) {
Paul Greyson13f02b92013-03-28 11:29:35 -070092 // create a temporary vector to indicate the pending flow
93 var s1 = d3.select(document.getElementById(d.dataPath.srcPort.dpid.value));
94 var s2 = d3.select(document.getElementById(d.dataPath.dstPort.dpid.value));
95
96 var pt1 = document.querySelector('svg').createSVGPoint();
97 pt1.x = s1.attr('x');
98 pt1.y = s1.attr('y');
99 pt1 = pt1.matrixTransform(s1[0][0].getCTM());
100 pts.push(pt1);
101
102 var pt2 = document.querySelector('svg').createSVGPoint();
103 pt2.x = s2.attr('x');
104 pt2.y = s2.attr('y');
105 pt2 = pt2.matrixTransform(s2[0][0].getCTM());
106 pts.push(pt2);
107
108 } else {
109 d.dataPath.flowEntries.forEach(function (flowEntry) {
110 var s = d3.select(document.getElementById(flowEntry.dpid.value));
111 // s[0] is null if the flow entry refers to a non-existent switch
112 if (s[0][0]) {
113 var pt = document.querySelector('svg').createSVGPoint();
114 pt.x = s.attr('x');
115 pt.y = s.attr('y');
116 pt = pt.matrixTransform(s[0][0].getCTM());
117 pts.push(pt);
118 } else {
119 console.log('flow refers to non-existent switch: ' + flowEntry.dpid.value);
120 }
121 });
122 }
123 return line(pts);
124 })
Paul Greyson71b550d2013-03-28 11:56:19 -0700125 .attr('id', function (d) {
126 if (d) {
127 return makeFlowKey(d);
128 }
129 })
Paul Greyson13f02b92013-03-28 11:29:35 -0700130 .classed('pending', function (d) {
131 return d && d.pending
Paul Greyson127d7fb2013-03-25 23:39:20 -0700132 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700133
Paul Greyson56378ed2013-03-26 23:17:36 -0700134 // "marching ants"
Paul Greyson29aa98d2013-03-28 00:09:31 -0700135 flows.select('animate').attr('from', 500);
136
Paul Greyson13f02b92013-03-28 11:29:35 -0700137}
138
139function updateSelectedFlowsTable() {
140 function rowEnter(d) {
141 var row = d3.select(this);
Paul Greyson6f918402013-03-28 12:18:30 -0700142 row.append('div').classed('deleteFlow', true);
Paul Greyson13f02b92013-03-28 11:29:35 -0700143 row.append('div').classed('flowId', true);
144 row.append('div').classed('srcDPID', true);
145 row.append('div').classed('dstDPID', true);
146 row.append('div').classed('iperf', true);
Paul Greyson71b550d2013-03-28 11:56:19 -0700147
148 row.on('mouseover', function (d) {
149 if (d) {
150 var path = document.getElementById(makeFlowKey(d));
151 d3.select(path).classed('highlight', true);
152 }
153 });
154 row.on('mouseout', function (d) {
155 if (d) {
156 var path = document.getElementById(makeFlowKey(d));
157 d3.select(path).classed('highlight', false);
158 }
159 })
Paul Greyson13f02b92013-03-28 11:29:35 -0700160 }
161
162 function rowUpdate(d) {
163 var row = d3.select(this);
Paul Greyson6f918402013-03-28 12:18:30 -0700164 row.select('.deleteFlow').on('click', function () {
165 if (d) {
166 var prompt = 'Delete flow ' + d.flowId.value + '?';
167 if (confirm(prompt)) {
168 deleteFlow(d);
169 d.pending = true;
170 updateSelectedFlows();
171
172 setTimeout(function () {
173 d.pending = false;
174 updateSelectedFlows();
175 }, pendingTimeout)
176 };
177 }
178 });
179
Paul Greyson13f02b92013-03-28 11:29:35 -0700180 row.select('.flowId')
181 .text(function (d) {
182 if (d) {
183 if (d.flowId) {
184 return d.flowId.value;
185 } else {
186 return '0x--';
187 }
188 }
189 })
190 .classed('pending', d && d.pending);
191
192 row.select('.srcDPID')
193 .text(function (d) {
194 if (d) {
195 return d.dataPath.srcPort.dpid.value;
196 }
197 });
198
199 row.select('.dstDPID')
200 .text(function (d) {
201 if (d) {
202 return d.dataPath.dstPort.dpid.value;
203 }
204 });
205 }
206
207 var flows = d3.select('#selectedFlows')
208 .selectAll('.selectedFlow')
209 .data(selectedFlows);
210
211 flows.enter()
212 .append('div')
213 .classed('selectedFlow', true)
214 .each(rowEnter);
215
216 flows.each(rowUpdate);
217
Paul Greyson29aa98d2013-03-28 00:09:31 -0700218 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700219}
220
Paul Greyson13f02b92013-03-28 11:29:35 -0700221function updateSelectedFlows() {
222 // make sure that all of the selected flows are either
223 // 1) valid (meaning they are in the latest list of flows)
224 // 2) pending
225 if (model) {
226 var flowMap = {};
227 model.flows.forEach(function (flow) {
228 flowMap[makeFlowKey(flow)] = flow;
229 });
230
231 var newSelectedFlows = [];
232 selectedFlows.forEach(function (flow) {
233 if (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700234 var liveFlow = flowMap[makeFlowKey(flow)];
235 if (liveFlow) {
236 newSelectedFlows.push(liveFlow);
237 } else if (flow.pending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700238 newSelectedFlows.push(flow);
Paul Greyson13f02b92013-03-28 11:29:35 -0700239 }
240 } else {
241 newSelectedFlows.push(null);
242 }
243 });
244 selectedFlows = newSelectedFlows;
245 }
Paul Greyson6f918402013-03-28 12:18:30 -0700246 while (selectedFlows.length < 3) {
247 selectedFlows.push(null);
248 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700249
250 updateSelectedFlowsTable();
251 updateSelectedFlowsTopology();
252}
253
254function selectFlow(flow) {
Paul Greysonc30f75e2013-03-28 11:45:15 -0700255 var flowKey = makeFlowKey(flow);
256 var alreadySelected = false;
257 selectedFlows.forEach(function (f) {
258 if (f && makeFlowKey(f) === flowKey) {
259 alreadySelected = true;
260 }
261 });
262
263 if (!alreadySelected) {
264 selectedFlows.unshift(flow);
265 selectedFlows = selectedFlows.slice(0, 3);
266 updateSelectedFlows();
267 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700268}
269
Paul Greysonf430fd02013-03-28 12:32:24 -0700270function deselectFlow(flow, ifPending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700271 var flowKey = makeFlowKey(flow);
272 var newSelectedFlows = [];
273 selectedFlows.forEach(function (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700274 if (!flow ||
275 flowKey !== makeFlowKey(flow) ||
276 flowKey === makeFlowKey(flow) && ifPending && !flow.pending ) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700277 newSelectedFlows.push(flow);
278 }
279 });
280 selectedFlows = newSelectedFlows;
281 while (selectedFlows.length < 3) {
282 selectedFlows.push(null);
283 }
284
285 updateSelectedFlows();
286}
287
Paul Greysonf430fd02013-03-28 12:32:24 -0700288function deselectFlowIfPending(flow) {
289 deselectFlow(flow, true);
290}
291
Paul Greyson29aa98d2013-03-28 00:09:31 -0700292function showFlowChooser() {
293 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700294 var row = d3.select(this);
295
Paul Greyson127d7fb2013-03-25 23:39:20 -0700296 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700297 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700298 on('click', function () {
Paul Greyson13f02b92013-03-28 11:29:35 -0700299 selectFlow(d);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700300 });
301
302 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700303 .classed('flowId', true)
304 .text(function (d) {
305 return d.flowId.value;
306 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700307
308 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700309 .classed('srcDPID', true)
310 .text(function (d) {
311 return d.dataPath.srcPort.dpid.value;
312 });
313
Paul Greyson127d7fb2013-03-25 23:39:20 -0700314
315 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700316 .classed('dstDPID', true)
317 .text(function (d) {
318 return d.dataPath.dstPort.dpid.value;
319 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700320
Paul Greyson127d7fb2013-03-25 23:39:20 -0700321 }
322
Paul Greyson29aa98d2013-03-28 00:09:31 -0700323 var flows = d3.select('#flowChooser')
324 .append('div')
325 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700326 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700327 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700328 .enter()
329 .append('div')
330 .classed('selectedFlow', true)
331 .each(rowEnter);
332
Paul Greyson29aa98d2013-03-28 00:09:31 -0700333 setTimeout(function () {
334 d3.select(document.body).on('click', function () {
335 d3.select('#flowChooser').html('');
336 d3.select(document.body).on('click', null);
337 });
338 }, 0);
339}
340
Paul Greyson29aa98d2013-03-28 00:09:31 -0700341
Paul Greyson127d7fb2013-03-25 23:39:20 -0700342
Paul Greysond1a22d92013-03-19 12:15:19 -0700343function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700344 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700345 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
346 d3.select('#activeFlows').text(model.flows.length);
347}
348
349function toRadians (angle) {
350 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700351}
352
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700353var widths = {
354 edge: 6,
355 aggregation: 12,
356 core: 18
357}
358
Paul Greysonc17278a2013-03-23 10:17:12 -0700359function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700360 var rings = [{
361 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700362 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700363 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700364 className: 'edge',
365 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700366 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700367 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700368 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700369 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700370 className: 'aggregation',
371 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700372 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700373 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700374 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700375 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700376 className: 'core',
377 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700378 }];
379
Paul Greysonde7fad52013-03-19 12:47:32 -0700380
381 var aggRanges = {};
382
383 // arrange edge switches at equal increments
384 var k = 360 / rings[0].switches.length;
385 rings[0].switches.forEach(function (s, i) {
386 var angle = k * i;
387
388 rings[0].angles[i] = angle;
389
390 // record the angle for the agg switch layout
391 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700392 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700393 var aggdpid = dpid.join(':');
394 var aggRange = aggRanges[aggdpid];
395 if (!aggRange) {
396 aggRange = aggRanges[aggdpid] = {};
397 aggRange.min = aggRange.max = angle;
398 } else {
399 aggRange.max = angle;
400 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700401 });
402
403 // arrange aggregation switches to "fan out" to edge switches
404 k = 360 / rings[1].switches.length;
405 rings[1].switches.forEach(function (s, i) {
406// rings[1].angles[i] = k * i;
407 var range = aggRanges[s.dpid];
408
Paul Greyson832d2202013-03-21 13:27:56 -0700409 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700410 });
411
Paul Greyson3f890b62013-03-22 17:39:36 -0700412 // find the association between core switches and aggregation switches
413 var aggregationSwitchMap = {};
414 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700415 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700416 });
417
Paul Greyson3f890b62013-03-22 17:39:36 -0700418 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700419 k = 360 / rings[2].switches.length;
420 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700421// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700422 var associatedAggregationSwitches = model.configuration.association[s.dpid];
423 // TODO: go between if there are multiple
424 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
425
426 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700427 });
428
Paul Greyson644d92a2013-03-23 18:00:40 -0700429 // TODO: construct this form initially rather than converting. it works better because
430 // it allows binding by dpid
431 var testRings = [];
432 rings.forEach(function (ring) {
433 var testRing = [];
434 ring.switches.forEach(function (s, i) {
435 var testSwitch = {
436 dpid: s.dpid,
437 state: s.state,
438 radius: ring.radius,
439 width: ring.width,
440 className: ring.className,
441 angle: ring.angles[i],
442 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700443 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700444 testRing.push(testSwitch);
445 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700446
447
Paul Greyson644d92a2013-03-23 18:00:40 -0700448 testRings.push(testRing);
449 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700450
451
Paul Greyson644d92a2013-03-23 18:00:40 -0700452// return rings;
453 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700454}
455
Paul Greyson40c8a592013-03-27 14:10:33 -0700456function makeLinkKey(link) {
457 return link['src-switch'] + '=>' + link['dst-switch'];
458}
459
Paul Greyson13f02b92013-03-28 11:29:35 -0700460function makeFlowKey(flow) {
461 return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
462}
463
Paul Greyson40c8a592013-03-27 14:10:33 -0700464function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700465 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700466 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700467 var srcDPID = link['src-switch'];
468 var dstDPID = link['dst-switch'];
469
470 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700471
Paul Greyson8d1c6362013-03-27 13:05:24 -0700472 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700473
474 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700475 });
476 return linkMap;
477}
478
Paul Greyson5cc35f02013-03-28 10:07:36 -0700479// removes links from the pending list that are now in the model
480function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700481 var links = [];
482 model.links.forEach(function (link) {
483 links.push(link);
484 delete pendingLinks[makeLinkKey(link)]
485 })
486 var linkId;
487 for (linkId in pendingLinks) {
488 links.push(pendingLinks[linkId]);
489 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700490 return links
491}
Paul Greyson40c8a592013-03-27 14:10:33 -0700492
Paul Greyson5cc35f02013-03-28 10:07:36 -0700493updateTopology = function() {
494
495 // DRAW THE SWITCHES
496 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
497
498 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700499 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700500
Paul Greyson8d1c6362013-03-27 13:05:24 -0700501 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700502
503 d3.event.preventDefault();
504
Paul Greyson5cc35f02013-03-28 10:07:36 -0700505 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
506
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700507 if (data.highlighted) {
508 return;
509 }
510
511 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700512 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700513 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700514 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700515 // only allow links
516 // edge->edge (flow)
517 // aggregation->core
518 // core->core
519 if (data.className == 'edge' && !s.classed('edge') ||
520 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
521 data.className == 'aggregation' && !s.classed('core')) {
522 return;
523 }
524
525 // don't highlight if there's already a link or flow
526 // var map = linkMap[data.dpid];
527 // console.log(map);
528 // console.log(s.data()[0].dpid);
529 // console.log(map[s.data()[0].dpid]);
530 // if (map && map[s.data()[0].dpid]) {
531 // return;
532 // }
533
534 // the second highlighted switch is the target for a link or flow
535 data.target = true;
536 }
537
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700538 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700539 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700540 data.highlighted = true;
541 node.moveToFront();
542 }
543
Paul Greyson8d1c6362013-03-27 13:05:24 -0700544 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700545 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
546
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700547 if (data.mouseDown)
548 return;
549
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700550 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700551 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700552 data.highlighted = false;
553 data.target = false;
554 }
555
Paul Greyson8d1c6362013-03-27 13:05:24 -0700556 function mouseDownSwitch(data) {
557 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700558 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700559 d3.select('#topology').classed('linking', true);
560
Paul Greyson421bfcd2013-03-27 22:22:09 -0700561 d3.select('svg')
562 .append('svg:path')
563 .attr('id', 'linkVector')
564 .attr('d', function () {
565 var s = d3.select(document.getElementById(data.dpid));
566
567 var pt = document.querySelector('svg').createSVGPoint();
568 pt.x = s.attr('x');
569 pt.y = s.attr('y');
570 pt = pt.matrixTransform(s[0][0].getCTM());
571
572 return line([pt, pt]);
573 });
574
575
Paul Greyson72f18852013-03-27 15:56:11 -0700576 if (data.className === 'core') {
577 d3.selectAll('.edge').classed('nodrop', true);
578 }
579 if (data.className === 'edge') {
580 d3.selectAll('.core').classed('nodrop', true);
581 d3.selectAll('.aggregation').classed('nodrop', true);
582 }
583 if (data.className === 'aggregation') {
584 d3.selectAll('.edge').classed('nodrop', true);
585 d3.selectAll('.aggregation').classed('nodrop', true);
586 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700587 }
588
Paul Greyson8d1c6362013-03-27 13:05:24 -0700589 function mouseUpSwitch(data) {
590 if (data.mouseDown) {
591 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700592 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700593 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700594 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700595 }
596 }
597
598 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700599 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700600 if (data.state == 'ACTIVE') {
601 var prompt = 'Deactivate ' + data.dpid + '?';
602 if (confirm(prompt)) {
603 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700604 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700605 }
606 } else {
607 var prompt = 'Activate ' + data.dpid + '?';
608 if (confirm(prompt)) {
609 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700610 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700611 }
612 }
613 }
614
Paul Greyson740bdaf2013-03-18 16:10:48 -0700615 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700616 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700617 return;
618 }
619
Paul Greysonc17278a2013-03-23 10:17:12 -0700620 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700621 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700622 .data(data, function (data) {
623 return data.dpid;
624 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700625 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700626 .attr("id", function (data, i) {
627 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700628 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700629 .attr("transform", function(data, i) {
630 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700631 });
632
Paul Greysonc17278a2013-03-23 10:17:12 -0700633 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700634 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700635 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700636 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700637 if (data.scale) {
638 m = m.scale(data.scale);
639 }
640 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700641 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700642 .attr("x", function (data) {
643 return -data.width / 2;
644 })
645 .attr("y", function (data) {
646 return -data.width / 2;
647 })
648 .attr("r", function (data) {
649 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700650 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700651
Paul Greysonc17278a2013-03-23 10:17:12 -0700652 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700653 nodes.on('mouseover', mouseOverSwitch);
654 nodes.on('mouseout', mouseOutSwitch);
655 nodes.on('mouseup', mouseUpSwitch);
656 nodes.on('mousedown', mouseDownSwitch);
657
658 // only do switch up/down for core switches
659 if (i == 2) {
660 nodes.on('dblclick', doubleClickSwitch);
661 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700662 }
663
Paul Greysonc17278a2013-03-23 10:17:12 -0700664 // append switches
665 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700666 .attr("class", "ring")
667 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700668
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700669
Paul Greysonc17278a2013-03-23 10:17:12 -0700670 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700671 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700672 .data(data, function (data) {
673 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700674 });
Paul Greyson347fb742013-03-27 13:40:29 -0700675 nodes.select('circle')
676 .each(function (data) {
677 // if there's a pending state changed and then the state changes, clear the pending class
678 var circle = d3.select(this);
679 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
680 data.state === 'INACTIVE' && circle.classed('active')) {
681 circle.classed('pending', false);
682 }
683 })
684 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700685 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700686 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700687 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700688 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700689 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700690 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700691 }
692
693 // update switches
694 rings.each(ringUpdate);
695
Paul Greyson968d1b42013-03-23 16:58:41 -0700696
697 // Now setup the labels
698 // This is done separately because SVG draws in node order and we want the labels
699 // always on top
700 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
701
Paul Greyson421bfcd2013-03-27 22:22:09 -0700702 d3.select(document.body).on('mousemove', function () {
703 if (!d3.select('#topology').classed('linking')) {
704 return;
705 }
706 var linkVector = document.getElementById('linkVector');
707 if (!linkVector) {
708 return;
709 }
710 linkVector = d3.select(linkVector);
711
712 var highlighted = svg.selectAll('.highlight')[0];
713 var s1 = null, s2 = null;
714 if (highlighted.length > 1) {
715 var s1 = d3.select(highlighted[0]);
716 var s2 = d3.select(highlighted[1]);
717
718 } else if (highlighted.length > 0) {
719 var s1 = d3.select(highlighted[0]);
720 }
721 var src = s1;
722 if (s2 && !s2.data()[0].target) {
723 src = s2;
724 }
725 if (src) {
726 linkVector.attr('d', function () {
727 var srcPt = document.querySelector('svg').createSVGPoint();
728 srcPt.x = src.attr('x');
729 srcPt.y = src.attr('y');
730 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
731
732 var svg = document.getElementById('topology');
733 var mouse = d3.mouse(viewbox);
734 var dstPt = document.querySelector('svg').createSVGPoint();
735 dstPt.x = mouse[0];
736 dstPt.y = mouse[1];
737 dstPt = dstPt.matrixTransform(viewbox.getCTM());
738
739 return line([srcPt, dstPt]);
740 });
741 }
742 });
743
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700744 d3.select(document.body).on('mouseup', function () {
745 function clearHighlight() {
746 svg.selectAll('circle').each(function (data) {
747 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700748 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700749 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700750 });
751 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700752 };
753
Paul Greyson72f18852013-03-27 15:56:11 -0700754 d3.selectAll('.nodrop').classed('nodrop', false);
755
Paul Greyson084779b2013-03-27 13:55:49 -0700756 function removeLink(link) {
757 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
758 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
759
760 if (path1) {
761 setPending(d3.select(path1));
762 }
763 if (path2) {
764 setPending(d3.select(path2));
765 }
766
767 linkDown(link);
768 }
769
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700770
Paul Greyson421bfcd2013-03-27 22:22:09 -0700771 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700772 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700773 var s1Data = highlighted[0].__data__;
774 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700775
776 var srcData, dstData;
777 if (s1Data.target) {
778 dstData = s1Data;
779 srcData = s2Data;
780 } else {
781 dstData = s2Data;
782 srcData = s1Data;
783 }
784
785 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
786 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
787 if (confirm(prompt)) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700788 addFlow(srcData, dstData);
789
790 var flow = {
791 dataPath: {
792 srcPort: {
793 dpid: {
794 value: srcData.dpid
795 }
796 },
797 dstPort: {
798 dpid: {
799 value: dstData.dpid
800 }
801 }
802 },
803 pending: true
804 };
805
806 selectFlow(flow);
807
808 setTimeout(function () {
Paul Greysonf430fd02013-03-28 12:32:24 -0700809 deselectFlowIfPending(flow);
Paul Greyson6f918402013-03-28 12:18:30 -0700810 }, pendingTimeout);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700811 }
812 } else {
813 var map = linkMap[srcData.dpid];
814 if (map && map[dstData.dpid]) {
815 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
816 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700817 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700818 }
819 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700820 map = linkMap[dstData.dpid];
821 if (map && map[srcData.dpid]) {
822 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
823 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700824 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700825 }
826 } else {
827 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
828 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700829 var link1 = {
830 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700831 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700832 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700833 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700834 pending: true
835 };
836 pendingLinks[makeLinkKey(link1)] = link1;
837 var link2 = {
838 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700839 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700840 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700841 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700842 pending: true
843 };
844 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700845 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700846
Paul Greyson2913af82013-03-27 14:53:17 -0700847 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700848
Paul Greyson5cc35f02013-03-28 10:07:36 -0700849 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700850 setTimeout(function () {
851 delete pendingLinks[makeLinkKey(link1)];
852 delete pendingLinks[makeLinkKey(link2)];
853
Paul Greyson5cc35f02013-03-28 10:07:36 -0700854 updateTopology();
Paul Greyson6f918402013-03-28 12:18:30 -0700855 }, pendingTimeout);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700856 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700857 }
858 }
859 }
860
861 clearHighlight();
862 } else {
863 clearHighlight();
864 }
865
866 });
867
Paul Greyson9066ab02013-03-23 18:15:41 -0700868 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700869 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700870 return;
871 }
872
873 // create the nodes
874 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700875 .data(data, function (data) {
876 return data.dpid;
877 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700878 .enter().append("svg:g")
879 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700880 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700881 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700882 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700883 .attr("transform", function(data, i) {
884 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700885 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700886
887 // add the text nodes which show on mouse over
888 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700889 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700890 .attr("x", function (data) {
891 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
892 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700893 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700894 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700895 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700896 }
897 } else {
898 if (data.className == 'edge') {
899 return data.width*3 + 4;
900 } else {
901 return data.width + 4;
902 }
903 }
904 })
905 .attr("y", function (data) {
906 var y;
907 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
908 if (data.className == 'edge') {
909 y = data.width*3/2 + 4;
910 } else {
911 y = data.width/2 + 4;
912 }
913 } else {
914 if (data.className == 'edge') {
915 y = data.width*3/2 + 4;
916 } else {
917 y = data.width/2 + 4;
918 }
919 }
920 return y - 6;
921 })
922 .attr("text-anchor", function (data) {
923 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
924 return "end";
925 } else {
926 return "start";
927 }
928 })
929 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700930 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
931 if (data.scale) {
932 m = m.scale(data.scale);
933 }
934 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
935 })
936 }
937
938 labelRings.enter().append("svg:g")
939 .attr("class", "textRing")
940 .each(labelRingEnter);
941
Paul Greysonc17278a2013-03-23 10:17:12 -0700942 // switches should not change during operation of the ui so no
943 // rings.exit()
944
945
Paul Greysond1a22d92013-03-19 12:15:19 -0700946 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -0700947
Paul Greysonc17278a2013-03-23 10:17:12 -0700948 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -0700949 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700950 return d['src-switch']+'->'+d['dst-switch'];
951 });
952
953 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700954 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -0700955 .attr("class", "link");
956
Paul Greyson084779b2013-03-27 13:55:49 -0700957 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700958 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -0700959 })
Paul Greyson56378ed2013-03-26 23:17:36 -0700960 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -0700961 var src = d3.select(document.getElementById(d['src-switch']));
962 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -0700963
Paul Greyson084779b2013-03-27 13:55:49 -0700964 var srcPt = document.querySelector('svg').createSVGPoint();
965 srcPt.x = src.attr('x');
966 srcPt.y = src.attr('y');
967 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700968
Paul Greyson084779b2013-03-27 13:55:49 -0700969 var dstPt = document.querySelector('svg').createSVGPoint();
970 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -0700971 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -0700972 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700973
Paul Greyson084779b2013-03-27 13:55:49 -0700974 var midPt = document.querySelector('svg').createSVGPoint();
975 midPt.x = (srcPt.x + dstPt.x)/2;
976 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -0700977
Paul Greyson084779b2013-03-27 13:55:49 -0700978 return line([srcPt, midPt, dstPt]);
979 })
Paul Greyson40c8a592013-03-27 14:10:33 -0700980 .attr("marker-mid", function(d) { return "url(#arrow)"; })
981 .classed('pending', function (d) {
982 return d.pending;
983 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700984
Paul Greyson56378ed2013-03-26 23:17:36 -0700985
Paul Greysonc17278a2013-03-23 10:17:12 -0700986 // remove old links
987 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700988}
989
Paul Greyson5cc35f02013-03-28 10:07:36 -0700990function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -0700991 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700992 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700993 .each(function (c) {
994 controllerColorMap[c] = colors.pop();
995 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
996 })
997 .text(function (d) {
998 return d;
Paul Greyson2913af82013-03-27 14:53:17 -0700999 })
1000 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -07001001 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -07001002
Paul Greysone262a292013-03-23 10:35:23 -07001003 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -07001004 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -07001005 if (model.activeControllers.indexOf(d) != -1) {
1006 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -07001007 }
Paul Greysonbcd3c772013-03-21 13:16:44 -07001008 var className = 'controller ' + color;
1009 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -07001010 });
Paul Greysond1a22d92013-03-19 12:15:19 -07001011
Paul Greysone262a292013-03-23 10:35:23 -07001012 // this should never be needed
1013 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -07001014
Paul Greyson2913af82013-03-27 14:53:17 -07001015 controllers.on('dblclick', function (c) {
1016 if (model.activeControllers.indexOf(c) != -1) {
1017 var prompt = 'Dectivate ' + c + '?';
1018 if (confirm(prompt)) {
1019 controllerDown(c);
1020 setPending(d3.select(this));
1021 };
1022 } else {
1023 var prompt = 'Activate ' + c + '?';
1024 if (confirm(prompt)) {
1025 controllerUp(c);
1026 setPending(d3.select(this));
1027 };
1028 }
1029 });
1030
Paul Greyson8247c3f2013-03-28 00:24:02 -07001031 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -07001032 var allSelected = true;
1033 for (var key in controllerColorMap) {
1034 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
1035 allSelected = false;
1036 break;
1037 }
1038 }
1039 if (allSelected) {
1040 for (var key in controllerColorMap) {
1041 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
1042 }
1043 } else {
1044 for (var key in controllerColorMap) {
1045 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
1046 }
1047 }
1048
1049 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
1050 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -07001051 });
Paul Greyson8d1c6362013-03-27 13:05:24 -07001052
1053
Paul Greyson740bdaf2013-03-18 16:10:48 -07001054}
1055
Paul Greyson29aa98d2013-03-28 00:09:31 -07001056function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -07001057 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -07001058 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001059// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -07001060
Paul Greyson5cc35f02013-03-28 10:07:36 -07001061 var modelChanged = false;
Paul Greyson56378ed2013-03-26 23:17:36 -07001062 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
Paul Greyson5cc35f02013-03-28 10:07:36 -07001063 modelChanged = true;
1064 model = newModel;
Paul Greysonb48943b2013-03-19 13:27:57 -07001065 } else {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001066// console.log('no change');
Paul Greysonb48943b2013-03-19 13:27:57 -07001067 }
Paul Greysonb48943b2013-03-19 13:27:57 -07001068
Paul Greyson5cc35f02013-03-28 10:07:36 -07001069 if (modelChanged) {
1070 updateControllers();
1071 updateSelectedFlows();
1072 updateTopology();
1073 }
1074
1075 updateHeader(newModel);
Paul Greyson740bdaf2013-03-18 16:10:48 -07001076
1077 // do it again in 1s
1078 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -07001079 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -07001080 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -07001081 });
1082}
Paul Greyson740bdaf2013-03-18 16:10:48 -07001083
Paul Greyson38d8bde2013-03-22 22:07:35 -07001084svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -07001085updateSelectedFlows();
1086
1087d3.select('#showFlowChooser').on('click', function () {
1088 showFlowChooser();
1089});
1090
Paul Greyson72f18852013-03-27 15:56:11 -07001091
Paul Greyson38d8bde2013-03-22 22:07:35 -07001092// workaround for Chrome v25 bug
1093// if executed immediately, the view box transform logic doesn't work properly
1094// fixed in Chrome v27
1095setTimeout(function () {
1096 // workaround for another Chrome v25 bug
1097 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -07001098 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -07001099 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -07001100 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -07001101}, 100);