blob: 7d6774c1eeb2228729f6ced8d81bf1aefc0029f8 [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 Greyson13f02b92013-03-28 11:29:35 -070021var pendingFlows = {};
Paul Greyson127d7fb2013-03-25 23:39:20 -070022
Paul Greysond1a22d92013-03-19 12:15:19 -070023var colors = [
Paul Greyson3e142162013-03-19 13:56:17 -070024 'color1',
25 'color2',
26 'color3',
27 'color4',
Paul Greyson3e142162013-03-19 13:56:17 -070028 'color7',
29 'color8',
30 'color9',
Paul Greyson5cc35f02013-03-28 10:07:36 -070031// 'color11',
Paul Greyson127d7fb2013-03-25 23:39:20 -070032 'color12'
33];
Paul Greyson01a5dff2013-03-19 15:50:14 -070034colors.reverse();
Paul Greysond1a22d92013-03-19 12:15:19 -070035
36var controllerColorMap = {};
37
Paul Greyson084779b2013-03-27 13:55:49 -070038function setPending(selection) {
39 selection.classed('pending', false);
40 setTimeout(function () {
41 selection.classed('pending', true);
42 })
43}
Paul Greysond1a22d92013-03-19 12:15:19 -070044
Paul Greyson740bdaf2013-03-18 16:10:48 -070045function createTopologyView() {
Paul Greyson56378ed2013-03-26 23:17:36 -070046
47 window.addEventListener('resize', function () {
48 // this is too slow. instead detect first resize event and hide the paths that have explicit matrix applied
49 // 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 -070050// updateTopology();
Paul Greyson56378ed2013-03-26 23:17:36 -070051 });
52
Paul Greysonb367de22013-03-23 11:09:11 -070053 var svg = d3.select('#svg-container').append('svg:svg');
54
55 svg.append("svg:defs").append("svg:marker")
56 .attr("id", "arrow")
57 .attr("viewBox", "0 -5 10 10")
58 .attr("refX", -1)
59 .attr("markerWidth", 5)
60 .attr("markerHeight", 5)
61 .attr("orient", "auto")
62 .append("svg:path")
Paul Greyson45303ac2013-03-23 16:44:01 -070063 .attr("d", "M0,-3L10,0L0,3");
Paul Greysonb367de22013-03-23 11:09:11 -070064
65 return svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
Paul Greyson952ccb62013-03-18 22:22:08 -070066 attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
Paul Greyson740bdaf2013-03-18 16:10:48 -070067}
68
Paul Greyson29aa98d2013-03-28 00:09:31 -070069var selectedFlows = [null, null, null];
Paul Greyson127d7fb2013-03-25 23:39:20 -070070
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 = [];
91 if (d.pending) {
92 // 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);
142 row.append('div').classed('flowId', true);
143 row.append('div').classed('srcDPID', true);
144 row.append('div').classed('dstDPID', true);
145 row.append('div').classed('iperf', true);
Paul Greyson71b550d2013-03-28 11:56:19 -0700146
147 row.on('mouseover', function (d) {
148 if (d) {
149 var path = document.getElementById(makeFlowKey(d));
150 d3.select(path).classed('highlight', true);
151 }
152 });
153 row.on('mouseout', function (d) {
154 if (d) {
155 var path = document.getElementById(makeFlowKey(d));
156 d3.select(path).classed('highlight', false);
157 }
158 })
Paul Greyson13f02b92013-03-28 11:29:35 -0700159 }
160
161 function rowUpdate(d) {
162 var row = d3.select(this);
163 row.select('.flowId')
164 .text(function (d) {
165 if (d) {
166 if (d.flowId) {
167 return d.flowId.value;
168 } else {
169 return '0x--';
170 }
171 }
172 })
173 .classed('pending', d && d.pending);
174
175 row.select('.srcDPID')
176 .text(function (d) {
177 if (d) {
178 return d.dataPath.srcPort.dpid.value;
179 }
180 });
181
182 row.select('.dstDPID')
183 .text(function (d) {
184 if (d) {
185 return d.dataPath.dstPort.dpid.value;
186 }
187 });
188 }
189
190 var flows = d3.select('#selectedFlows')
191 .selectAll('.selectedFlow')
192 .data(selectedFlows);
193
194 flows.enter()
195 .append('div')
196 .classed('selectedFlow', true)
197 .each(rowEnter);
198
199 flows.each(rowUpdate);
200
Paul Greyson29aa98d2013-03-28 00:09:31 -0700201 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700202}
203
Paul Greyson13f02b92013-03-28 11:29:35 -0700204function updateSelectedFlows() {
205 // make sure that all of the selected flows are either
206 // 1) valid (meaning they are in the latest list of flows)
207 // 2) pending
208 if (model) {
209 var flowMap = {};
210 model.flows.forEach(function (flow) {
211 flowMap[makeFlowKey(flow)] = flow;
212 });
213
214 var newSelectedFlows = [];
215 selectedFlows.forEach(function (flow) {
216 if (flow) {
217 if (flow.pending) {
218 newSelectedFlows.push(flow);
219 } else {
220 var liveFlow = flowMap[makeFlowKey(flow)];
221 if (liveFlow) {
222 newSelectedFlows.push(liveFlow);
223 }
224 }
225 } else {
226 newSelectedFlows.push(null);
227 }
228 });
229 selectedFlows = newSelectedFlows;
230 }
231
232 updateSelectedFlowsTable();
233 updateSelectedFlowsTopology();
234}
235
236function selectFlow(flow) {
Paul Greysonc30f75e2013-03-28 11:45:15 -0700237 var flowKey = makeFlowKey(flow);
238 var alreadySelected = false;
239 selectedFlows.forEach(function (f) {
240 if (f && makeFlowKey(f) === flowKey) {
241 alreadySelected = true;
242 }
243 });
244
245 if (!alreadySelected) {
246 selectedFlows.unshift(flow);
247 selectedFlows = selectedFlows.slice(0, 3);
248 updateSelectedFlows();
249 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700250}
251
252function deselectFlow(flow) {
253 var flowKey = makeFlowKey(flow);
254 var newSelectedFlows = [];
255 selectedFlows.forEach(function (flow) {
256 if (!flow || flowKey !== makeFlowKey(flow)) {
257 newSelectedFlows.push(flow);
258 }
259 });
260 selectedFlows = newSelectedFlows;
261 while (selectedFlows.length < 3) {
262 selectedFlows.push(null);
263 }
264
265 updateSelectedFlows();
266}
267
Paul Greyson29aa98d2013-03-28 00:09:31 -0700268function showFlowChooser() {
269 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700270 var row = d3.select(this);
271
Paul Greyson127d7fb2013-03-25 23:39:20 -0700272 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700273 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700274 on('click', function () {
Paul Greyson13f02b92013-03-28 11:29:35 -0700275 selectFlow(d);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700276 });
277
278 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700279 .classed('flowId', true)
280 .text(function (d) {
281 return d.flowId.value;
282 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700283
284 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700285 .classed('srcDPID', true)
286 .text(function (d) {
287 return d.dataPath.srcPort.dpid.value;
288 });
289
Paul Greyson127d7fb2013-03-25 23:39:20 -0700290
291 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700292 .classed('dstDPID', true)
293 .text(function (d) {
294 return d.dataPath.dstPort.dpid.value;
295 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700296
Paul Greyson127d7fb2013-03-25 23:39:20 -0700297 }
298
Paul Greyson29aa98d2013-03-28 00:09:31 -0700299 var flows = d3.select('#flowChooser')
300 .append('div')
301 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700302 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700303 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700304 .enter()
305 .append('div')
306 .classed('selectedFlow', true)
307 .each(rowEnter);
308
Paul Greyson29aa98d2013-03-28 00:09:31 -0700309 setTimeout(function () {
310 d3.select(document.body).on('click', function () {
311 d3.select('#flowChooser').html('');
312 d3.select(document.body).on('click', null);
313 });
314 }, 0);
315}
316
Paul Greyson29aa98d2013-03-28 00:09:31 -0700317
Paul Greyson127d7fb2013-03-25 23:39:20 -0700318
Paul Greysond1a22d92013-03-19 12:15:19 -0700319function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700320 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700321 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
322 d3.select('#activeFlows').text(model.flows.length);
323}
324
325function toRadians (angle) {
326 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700327}
328
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700329var widths = {
330 edge: 6,
331 aggregation: 12,
332 core: 18
333}
334
Paul Greysonc17278a2013-03-23 10:17:12 -0700335function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700336 var rings = [{
337 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700338 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700339 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700340 className: 'edge',
341 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700342 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700343 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700344 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700345 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700346 className: 'aggregation',
347 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700348 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700349 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700350 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700351 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700352 className: 'core',
353 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700354 }];
355
Paul Greysonde7fad52013-03-19 12:47:32 -0700356
357 var aggRanges = {};
358
359 // arrange edge switches at equal increments
360 var k = 360 / rings[0].switches.length;
361 rings[0].switches.forEach(function (s, i) {
362 var angle = k * i;
363
364 rings[0].angles[i] = angle;
365
366 // record the angle for the agg switch layout
367 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700368 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700369 var aggdpid = dpid.join(':');
370 var aggRange = aggRanges[aggdpid];
371 if (!aggRange) {
372 aggRange = aggRanges[aggdpid] = {};
373 aggRange.min = aggRange.max = angle;
374 } else {
375 aggRange.max = angle;
376 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700377 });
378
379 // arrange aggregation switches to "fan out" to edge switches
380 k = 360 / rings[1].switches.length;
381 rings[1].switches.forEach(function (s, i) {
382// rings[1].angles[i] = k * i;
383 var range = aggRanges[s.dpid];
384
Paul Greyson832d2202013-03-21 13:27:56 -0700385 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700386 });
387
Paul Greyson3f890b62013-03-22 17:39:36 -0700388 // find the association between core switches and aggregation switches
389 var aggregationSwitchMap = {};
390 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700391 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700392 });
393
Paul Greyson3f890b62013-03-22 17:39:36 -0700394 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700395 k = 360 / rings[2].switches.length;
396 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700397// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700398 var associatedAggregationSwitches = model.configuration.association[s.dpid];
399 // TODO: go between if there are multiple
400 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
401
402 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700403 });
404
Paul Greyson644d92a2013-03-23 18:00:40 -0700405 // TODO: construct this form initially rather than converting. it works better because
406 // it allows binding by dpid
407 var testRings = [];
408 rings.forEach(function (ring) {
409 var testRing = [];
410 ring.switches.forEach(function (s, i) {
411 var testSwitch = {
412 dpid: s.dpid,
413 state: s.state,
414 radius: ring.radius,
415 width: ring.width,
416 className: ring.className,
417 angle: ring.angles[i],
418 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700419 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700420 testRing.push(testSwitch);
421 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700422
423
Paul Greyson644d92a2013-03-23 18:00:40 -0700424 testRings.push(testRing);
425 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700426
427
Paul Greyson644d92a2013-03-23 18:00:40 -0700428// return rings;
429 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700430}
431
Paul Greyson40c8a592013-03-27 14:10:33 -0700432function makeLinkKey(link) {
433 return link['src-switch'] + '=>' + link['dst-switch'];
434}
435
Paul Greyson13f02b92013-03-28 11:29:35 -0700436function makeFlowKey(flow) {
437 return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
438}
439
Paul Greyson40c8a592013-03-27 14:10:33 -0700440function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700441 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700442 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700443 var srcDPID = link['src-switch'];
444 var dstDPID = link['dst-switch'];
445
446 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700447
Paul Greyson8d1c6362013-03-27 13:05:24 -0700448 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700449
450 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700451 });
452 return linkMap;
453}
454
Paul Greyson5cc35f02013-03-28 10:07:36 -0700455// removes links from the pending list that are now in the model
456function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700457 var links = [];
458 model.links.forEach(function (link) {
459 links.push(link);
460 delete pendingLinks[makeLinkKey(link)]
461 })
462 var linkId;
463 for (linkId in pendingLinks) {
464 links.push(pendingLinks[linkId]);
465 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700466 return links
467}
Paul Greyson40c8a592013-03-27 14:10:33 -0700468
Paul Greyson5cc35f02013-03-28 10:07:36 -0700469updateTopology = function() {
470
471 // DRAW THE SWITCHES
472 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
473
474 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700475 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700476
Paul Greyson8d1c6362013-03-27 13:05:24 -0700477 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700478
479 d3.event.preventDefault();
480
Paul Greyson5cc35f02013-03-28 10:07:36 -0700481 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
482
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700483 if (data.highlighted) {
484 return;
485 }
486
487 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700488 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700489 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700490 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700491 // only allow links
492 // edge->edge (flow)
493 // aggregation->core
494 // core->core
495 if (data.className == 'edge' && !s.classed('edge') ||
496 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
497 data.className == 'aggregation' && !s.classed('core')) {
498 return;
499 }
500
501 // don't highlight if there's already a link or flow
502 // var map = linkMap[data.dpid];
503 // console.log(map);
504 // console.log(s.data()[0].dpid);
505 // console.log(map[s.data()[0].dpid]);
506 // if (map && map[s.data()[0].dpid]) {
507 // return;
508 // }
509
510 // the second highlighted switch is the target for a link or flow
511 data.target = true;
512 }
513
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700514 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700515 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700516 data.highlighted = true;
517 node.moveToFront();
518 }
519
Paul Greyson8d1c6362013-03-27 13:05:24 -0700520 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700521 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
522
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700523 if (data.mouseDown)
524 return;
525
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700526 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700527 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700528 data.highlighted = false;
529 data.target = false;
530 }
531
Paul Greyson8d1c6362013-03-27 13:05:24 -0700532 function mouseDownSwitch(data) {
533 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700534 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700535 d3.select('#topology').classed('linking', true);
536
Paul Greyson421bfcd2013-03-27 22:22:09 -0700537 d3.select('svg')
538 .append('svg:path')
539 .attr('id', 'linkVector')
540 .attr('d', function () {
541 var s = d3.select(document.getElementById(data.dpid));
542
543 var pt = document.querySelector('svg').createSVGPoint();
544 pt.x = s.attr('x');
545 pt.y = s.attr('y');
546 pt = pt.matrixTransform(s[0][0].getCTM());
547
548 return line([pt, pt]);
549 });
550
551
Paul Greyson72f18852013-03-27 15:56:11 -0700552 if (data.className === 'core') {
553 d3.selectAll('.edge').classed('nodrop', true);
554 }
555 if (data.className === 'edge') {
556 d3.selectAll('.core').classed('nodrop', true);
557 d3.selectAll('.aggregation').classed('nodrop', true);
558 }
559 if (data.className === 'aggregation') {
560 d3.selectAll('.edge').classed('nodrop', true);
561 d3.selectAll('.aggregation').classed('nodrop', true);
562 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700563 }
564
Paul Greyson8d1c6362013-03-27 13:05:24 -0700565 function mouseUpSwitch(data) {
566 if (data.mouseDown) {
567 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700568 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700569 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700570 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700571 }
572 }
573
574 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700575 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700576 if (data.state == 'ACTIVE') {
577 var prompt = 'Deactivate ' + data.dpid + '?';
578 if (confirm(prompt)) {
579 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700580 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700581 }
582 } else {
583 var prompt = 'Activate ' + data.dpid + '?';
584 if (confirm(prompt)) {
585 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700586 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700587 }
588 }
589 }
590
Paul Greyson740bdaf2013-03-18 16:10:48 -0700591 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700592 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700593 return;
594 }
595
Paul Greysonc17278a2013-03-23 10:17:12 -0700596 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700597 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700598 .data(data, function (data) {
599 return data.dpid;
600 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700601 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700602 .attr("id", function (data, i) {
603 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700604 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700605 .attr("transform", function(data, i) {
606 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700607 });
608
Paul Greysonc17278a2013-03-23 10:17:12 -0700609 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700610 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700611 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700612 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700613 if (data.scale) {
614 m = m.scale(data.scale);
615 }
616 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700617 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700618 .attr("x", function (data) {
619 return -data.width / 2;
620 })
621 .attr("y", function (data) {
622 return -data.width / 2;
623 })
624 .attr("r", function (data) {
625 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700626 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700627
Paul Greysonc17278a2013-03-23 10:17:12 -0700628 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700629 nodes.on('mouseover', mouseOverSwitch);
630 nodes.on('mouseout', mouseOutSwitch);
631 nodes.on('mouseup', mouseUpSwitch);
632 nodes.on('mousedown', mouseDownSwitch);
633
634 // only do switch up/down for core switches
635 if (i == 2) {
636 nodes.on('dblclick', doubleClickSwitch);
637 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700638 }
639
Paul Greysonc17278a2013-03-23 10:17:12 -0700640 // append switches
641 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700642 .attr("class", "ring")
643 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700644
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700645
Paul Greysonc17278a2013-03-23 10:17:12 -0700646 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700647 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700648 .data(data, function (data) {
649 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700650 });
Paul Greyson347fb742013-03-27 13:40:29 -0700651 nodes.select('circle')
652 .each(function (data) {
653 // if there's a pending state changed and then the state changes, clear the pending class
654 var circle = d3.select(this);
655 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
656 data.state === 'INACTIVE' && circle.classed('active')) {
657 circle.classed('pending', false);
658 }
659 })
660 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700661 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700662 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700663 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700664 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700665 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700666 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700667 }
668
669 // update switches
670 rings.each(ringUpdate);
671
Paul Greyson968d1b42013-03-23 16:58:41 -0700672
673 // Now setup the labels
674 // This is done separately because SVG draws in node order and we want the labels
675 // always on top
676 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
677
Paul Greyson421bfcd2013-03-27 22:22:09 -0700678 d3.select(document.body).on('mousemove', function () {
679 if (!d3.select('#topology').classed('linking')) {
680 return;
681 }
682 var linkVector = document.getElementById('linkVector');
683 if (!linkVector) {
684 return;
685 }
686 linkVector = d3.select(linkVector);
687
688 var highlighted = svg.selectAll('.highlight')[0];
689 var s1 = null, s2 = null;
690 if (highlighted.length > 1) {
691 var s1 = d3.select(highlighted[0]);
692 var s2 = d3.select(highlighted[1]);
693
694 } else if (highlighted.length > 0) {
695 var s1 = d3.select(highlighted[0]);
696 }
697 var src = s1;
698 if (s2 && !s2.data()[0].target) {
699 src = s2;
700 }
701 if (src) {
702 linkVector.attr('d', function () {
703 var srcPt = document.querySelector('svg').createSVGPoint();
704 srcPt.x = src.attr('x');
705 srcPt.y = src.attr('y');
706 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
707
708 var svg = document.getElementById('topology');
709 var mouse = d3.mouse(viewbox);
710 var dstPt = document.querySelector('svg').createSVGPoint();
711 dstPt.x = mouse[0];
712 dstPt.y = mouse[1];
713 dstPt = dstPt.matrixTransform(viewbox.getCTM());
714
715 return line([srcPt, dstPt]);
716 });
717 }
718 });
719
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700720 d3.select(document.body).on('mouseup', function () {
721 function clearHighlight() {
722 svg.selectAll('circle').each(function (data) {
723 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700724 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700725 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700726 });
727 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700728 };
729
Paul Greyson72f18852013-03-27 15:56:11 -0700730 d3.selectAll('.nodrop').classed('nodrop', false);
731
Paul Greyson084779b2013-03-27 13:55:49 -0700732 function removeLink(link) {
733 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
734 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
735
736 if (path1) {
737 setPending(d3.select(path1));
738 }
739 if (path2) {
740 setPending(d3.select(path2));
741 }
742
743 linkDown(link);
744 }
745
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700746
Paul Greyson421bfcd2013-03-27 22:22:09 -0700747 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700748 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700749 var s1Data = highlighted[0].__data__;
750 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700751
752 var srcData, dstData;
753 if (s1Data.target) {
754 dstData = s1Data;
755 srcData = s2Data;
756 } else {
757 dstData = s2Data;
758 srcData = s1Data;
759 }
760
761 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
762 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
763 if (confirm(prompt)) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700764 addFlow(srcData, dstData);
765
766 var flow = {
767 dataPath: {
768 srcPort: {
769 dpid: {
770 value: srcData.dpid
771 }
772 },
773 dstPort: {
774 dpid: {
775 value: dstData.dpid
776 }
777 }
778 },
779 pending: true
780 };
781
782 selectFlow(flow);
783
784 setTimeout(function () {
785 deselectFlow(flow);
786 }, 10000);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700787 }
788 } else {
789 var map = linkMap[srcData.dpid];
790 if (map && map[dstData.dpid]) {
791 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
792 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700793 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700794 }
795 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700796 map = linkMap[dstData.dpid];
797 if (map && map[srcData.dpid]) {
798 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
799 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700800 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700801 }
802 } else {
803 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
804 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700805 var link1 = {
806 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700807 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700808 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700809 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700810 pending: true
811 };
812 pendingLinks[makeLinkKey(link1)] = link1;
813 var link2 = {
814 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700815 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700816 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700817 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700818 pending: true
819 };
820 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700821 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700822
Paul Greyson2913af82013-03-27 14:53:17 -0700823 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700824
Paul Greyson5cc35f02013-03-28 10:07:36 -0700825 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700826 setTimeout(function () {
827 delete pendingLinks[makeLinkKey(link1)];
828 delete pendingLinks[makeLinkKey(link2)];
829
Paul Greyson5cc35f02013-03-28 10:07:36 -0700830 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700831 }, 10000);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700832 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700833 }
834 }
835 }
836
837 clearHighlight();
838 } else {
839 clearHighlight();
840 }
841
842 });
843
Paul Greyson9066ab02013-03-23 18:15:41 -0700844 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700845 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700846 return;
847 }
848
849 // create the nodes
850 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700851 .data(data, function (data) {
852 return data.dpid;
853 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700854 .enter().append("svg:g")
855 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700856 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700857 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700858 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700859 .attr("transform", function(data, i) {
860 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700861 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700862
863 // add the text nodes which show on mouse over
864 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700865 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700866 .attr("x", function (data) {
867 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
868 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700869 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700870 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700871 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700872 }
873 } else {
874 if (data.className == 'edge') {
875 return data.width*3 + 4;
876 } else {
877 return data.width + 4;
878 }
879 }
880 })
881 .attr("y", function (data) {
882 var y;
883 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
884 if (data.className == 'edge') {
885 y = data.width*3/2 + 4;
886 } else {
887 y = data.width/2 + 4;
888 }
889 } else {
890 if (data.className == 'edge') {
891 y = data.width*3/2 + 4;
892 } else {
893 y = data.width/2 + 4;
894 }
895 }
896 return y - 6;
897 })
898 .attr("text-anchor", function (data) {
899 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
900 return "end";
901 } else {
902 return "start";
903 }
904 })
905 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700906 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
907 if (data.scale) {
908 m = m.scale(data.scale);
909 }
910 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
911 })
912 }
913
914 labelRings.enter().append("svg:g")
915 .attr("class", "textRing")
916 .each(labelRingEnter);
917
Paul Greysonc17278a2013-03-23 10:17:12 -0700918 // switches should not change during operation of the ui so no
919 // rings.exit()
920
921
Paul Greysond1a22d92013-03-19 12:15:19 -0700922 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -0700923
Paul Greysonc17278a2013-03-23 10:17:12 -0700924 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -0700925 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700926 return d['src-switch']+'->'+d['dst-switch'];
927 });
928
929 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700930 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -0700931 .attr("class", "link");
932
Paul Greyson084779b2013-03-27 13:55:49 -0700933 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700934 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -0700935 })
Paul Greyson56378ed2013-03-26 23:17:36 -0700936 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -0700937 var src = d3.select(document.getElementById(d['src-switch']));
938 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -0700939
Paul Greyson084779b2013-03-27 13:55:49 -0700940 var srcPt = document.querySelector('svg').createSVGPoint();
941 srcPt.x = src.attr('x');
942 srcPt.y = src.attr('y');
943 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700944
Paul Greyson084779b2013-03-27 13:55:49 -0700945 var dstPt = document.querySelector('svg').createSVGPoint();
946 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -0700947 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -0700948 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700949
Paul Greyson084779b2013-03-27 13:55:49 -0700950 var midPt = document.querySelector('svg').createSVGPoint();
951 midPt.x = (srcPt.x + dstPt.x)/2;
952 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -0700953
Paul Greyson084779b2013-03-27 13:55:49 -0700954 return line([srcPt, midPt, dstPt]);
955 })
Paul Greyson40c8a592013-03-27 14:10:33 -0700956 .attr("marker-mid", function(d) { return "url(#arrow)"; })
957 .classed('pending', function (d) {
958 return d.pending;
959 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700960
Paul Greyson56378ed2013-03-26 23:17:36 -0700961
Paul Greysonc17278a2013-03-23 10:17:12 -0700962 // remove old links
963 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700964}
965
Paul Greyson5cc35f02013-03-28 10:07:36 -0700966function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -0700967 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700968 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700969 .each(function (c) {
970 controllerColorMap[c] = colors.pop();
971 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
972 })
973 .text(function (d) {
974 return d;
Paul Greyson2913af82013-03-27 14:53:17 -0700975 })
976 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700977 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -0700978
Paul Greysone262a292013-03-23 10:35:23 -0700979 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -0700980 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -0700981 if (model.activeControllers.indexOf(d) != -1) {
982 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -0700983 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700984 var className = 'controller ' + color;
985 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700986 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700987
Paul Greysone262a292013-03-23 10:35:23 -0700988 // this should never be needed
989 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700990
Paul Greyson2913af82013-03-27 14:53:17 -0700991 controllers.on('dblclick', function (c) {
992 if (model.activeControllers.indexOf(c) != -1) {
993 var prompt = 'Dectivate ' + c + '?';
994 if (confirm(prompt)) {
995 controllerDown(c);
996 setPending(d3.select(this));
997 };
998 } else {
999 var prompt = 'Activate ' + c + '?';
1000 if (confirm(prompt)) {
1001 controllerUp(c);
1002 setPending(d3.select(this));
1003 };
1004 }
1005 });
1006
Paul Greyson8247c3f2013-03-28 00:24:02 -07001007 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -07001008 var allSelected = true;
1009 for (var key in controllerColorMap) {
1010 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
1011 allSelected = false;
1012 break;
1013 }
1014 }
1015 if (allSelected) {
1016 for (var key in controllerColorMap) {
1017 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
1018 }
1019 } else {
1020 for (var key in controllerColorMap) {
1021 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
1022 }
1023 }
1024
1025 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
1026 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -07001027 });
Paul Greyson8d1c6362013-03-27 13:05:24 -07001028
1029
Paul Greyson740bdaf2013-03-18 16:10:48 -07001030}
1031
Paul Greyson29aa98d2013-03-28 00:09:31 -07001032function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -07001033 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -07001034 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001035// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -07001036
Paul Greyson5cc35f02013-03-28 10:07:36 -07001037 var modelChanged = false;
Paul Greyson56378ed2013-03-26 23:17:36 -07001038 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
Paul Greyson5cc35f02013-03-28 10:07:36 -07001039 modelChanged = true;
1040 model = newModel;
Paul Greysonb48943b2013-03-19 13:27:57 -07001041 } else {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001042// console.log('no change');
Paul Greysonb48943b2013-03-19 13:27:57 -07001043 }
Paul Greysonb48943b2013-03-19 13:27:57 -07001044
Paul Greyson5cc35f02013-03-28 10:07:36 -07001045 if (modelChanged) {
1046 updateControllers();
1047 updateSelectedFlows();
1048 updateTopology();
1049 }
1050
1051 updateHeader(newModel);
Paul Greyson740bdaf2013-03-18 16:10:48 -07001052
1053 // do it again in 1s
1054 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -07001055 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -07001056 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -07001057 });
1058}
Paul Greyson740bdaf2013-03-18 16:10:48 -07001059
Paul Greyson38d8bde2013-03-22 22:07:35 -07001060svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -07001061updateSelectedFlows();
1062
1063d3.select('#showFlowChooser').on('click', function () {
1064 showFlowChooser();
1065});
1066
Paul Greyson72f18852013-03-27 15:56:11 -07001067
Paul Greyson38d8bde2013-03-22 22:07:35 -07001068// workaround for Chrome v25 bug
1069// if executed immediately, the view box transform logic doesn't work properly
1070// fixed in Chrome v27
1071setTimeout(function () {
1072 // workaround for another Chrome v25 bug
1073 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -07001074 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -07001075 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -07001076 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -07001077}, 100);