blob: 02dbc5d10211017dacb28d069514405f7cac3649 [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) {
234 if (flow.pending) {
235 newSelectedFlows.push(flow);
236 } else {
237 var liveFlow = flowMap[makeFlowKey(flow)];
238 if (liveFlow) {
239 newSelectedFlows.push(liveFlow);
240 }
241 }
242 } else {
243 newSelectedFlows.push(null);
244 }
245 });
246 selectedFlows = newSelectedFlows;
247 }
Paul Greyson6f918402013-03-28 12:18:30 -0700248 while (selectedFlows.length < 3) {
249 selectedFlows.push(null);
250 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700251
252 updateSelectedFlowsTable();
253 updateSelectedFlowsTopology();
254}
255
256function selectFlow(flow) {
Paul Greysonc30f75e2013-03-28 11:45:15 -0700257 var flowKey = makeFlowKey(flow);
258 var alreadySelected = false;
259 selectedFlows.forEach(function (f) {
260 if (f && makeFlowKey(f) === flowKey) {
261 alreadySelected = true;
262 }
263 });
264
265 if (!alreadySelected) {
266 selectedFlows.unshift(flow);
267 selectedFlows = selectedFlows.slice(0, 3);
268 updateSelectedFlows();
269 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700270}
271
272function deselectFlow(flow) {
273 var flowKey = makeFlowKey(flow);
274 var newSelectedFlows = [];
275 selectedFlows.forEach(function (flow) {
276 if (!flow || flowKey !== makeFlowKey(flow)) {
277 newSelectedFlows.push(flow);
278 }
279 });
280 selectedFlows = newSelectedFlows;
281 while (selectedFlows.length < 3) {
282 selectedFlows.push(null);
283 }
284
285 updateSelectedFlows();
286}
287
Paul Greyson29aa98d2013-03-28 00:09:31 -0700288function showFlowChooser() {
289 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700290 var row = d3.select(this);
291
Paul Greyson127d7fb2013-03-25 23:39:20 -0700292 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700293 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700294 on('click', function () {
Paul Greyson13f02b92013-03-28 11:29:35 -0700295 selectFlow(d);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700296 });
297
298 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700299 .classed('flowId', true)
300 .text(function (d) {
301 return d.flowId.value;
302 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700303
304 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700305 .classed('srcDPID', true)
306 .text(function (d) {
307 return d.dataPath.srcPort.dpid.value;
308 });
309
Paul Greyson127d7fb2013-03-25 23:39:20 -0700310
311 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700312 .classed('dstDPID', true)
313 .text(function (d) {
314 return d.dataPath.dstPort.dpid.value;
315 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700316
Paul Greyson127d7fb2013-03-25 23:39:20 -0700317 }
318
Paul Greyson29aa98d2013-03-28 00:09:31 -0700319 var flows = d3.select('#flowChooser')
320 .append('div')
321 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700322 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700323 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700324 .enter()
325 .append('div')
326 .classed('selectedFlow', true)
327 .each(rowEnter);
328
Paul Greyson29aa98d2013-03-28 00:09:31 -0700329 setTimeout(function () {
330 d3.select(document.body).on('click', function () {
331 d3.select('#flowChooser').html('');
332 d3.select(document.body).on('click', null);
333 });
334 }, 0);
335}
336
Paul Greyson29aa98d2013-03-28 00:09:31 -0700337
Paul Greyson127d7fb2013-03-25 23:39:20 -0700338
Paul Greysond1a22d92013-03-19 12:15:19 -0700339function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700340 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700341 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
342 d3.select('#activeFlows').text(model.flows.length);
343}
344
345function toRadians (angle) {
346 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700347}
348
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700349var widths = {
350 edge: 6,
351 aggregation: 12,
352 core: 18
353}
354
Paul Greysonc17278a2013-03-23 10:17:12 -0700355function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700356 var rings = [{
357 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700358 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700359 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700360 className: 'edge',
361 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700362 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700363 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700364 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700365 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700366 className: 'aggregation',
367 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700368 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700369 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700370 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700371 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700372 className: 'core',
373 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700374 }];
375
Paul Greysonde7fad52013-03-19 12:47:32 -0700376
377 var aggRanges = {};
378
379 // arrange edge switches at equal increments
380 var k = 360 / rings[0].switches.length;
381 rings[0].switches.forEach(function (s, i) {
382 var angle = k * i;
383
384 rings[0].angles[i] = angle;
385
386 // record the angle for the agg switch layout
387 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700388 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700389 var aggdpid = dpid.join(':');
390 var aggRange = aggRanges[aggdpid];
391 if (!aggRange) {
392 aggRange = aggRanges[aggdpid] = {};
393 aggRange.min = aggRange.max = angle;
394 } else {
395 aggRange.max = angle;
396 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700397 });
398
399 // arrange aggregation switches to "fan out" to edge switches
400 k = 360 / rings[1].switches.length;
401 rings[1].switches.forEach(function (s, i) {
402// rings[1].angles[i] = k * i;
403 var range = aggRanges[s.dpid];
404
Paul Greyson832d2202013-03-21 13:27:56 -0700405 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700406 });
407
Paul Greyson3f890b62013-03-22 17:39:36 -0700408 // find the association between core switches and aggregation switches
409 var aggregationSwitchMap = {};
410 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700411 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700412 });
413
Paul Greyson3f890b62013-03-22 17:39:36 -0700414 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700415 k = 360 / rings[2].switches.length;
416 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700417// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700418 var associatedAggregationSwitches = model.configuration.association[s.dpid];
419 // TODO: go between if there are multiple
420 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
421
422 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700423 });
424
Paul Greyson644d92a2013-03-23 18:00:40 -0700425 // TODO: construct this form initially rather than converting. it works better because
426 // it allows binding by dpid
427 var testRings = [];
428 rings.forEach(function (ring) {
429 var testRing = [];
430 ring.switches.forEach(function (s, i) {
431 var testSwitch = {
432 dpid: s.dpid,
433 state: s.state,
434 radius: ring.radius,
435 width: ring.width,
436 className: ring.className,
437 angle: ring.angles[i],
438 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700439 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700440 testRing.push(testSwitch);
441 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700442
443
Paul Greyson644d92a2013-03-23 18:00:40 -0700444 testRings.push(testRing);
445 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700446
447
Paul Greyson644d92a2013-03-23 18:00:40 -0700448// return rings;
449 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700450}
451
Paul Greyson40c8a592013-03-27 14:10:33 -0700452function makeLinkKey(link) {
453 return link['src-switch'] + '=>' + link['dst-switch'];
454}
455
Paul Greyson13f02b92013-03-28 11:29:35 -0700456function makeFlowKey(flow) {
457 return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
458}
459
Paul Greyson40c8a592013-03-27 14:10:33 -0700460function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700461 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700462 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700463 var srcDPID = link['src-switch'];
464 var dstDPID = link['dst-switch'];
465
466 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700467
Paul Greyson8d1c6362013-03-27 13:05:24 -0700468 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700469
470 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700471 });
472 return linkMap;
473}
474
Paul Greyson5cc35f02013-03-28 10:07:36 -0700475// removes links from the pending list that are now in the model
476function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700477 var links = [];
478 model.links.forEach(function (link) {
479 links.push(link);
480 delete pendingLinks[makeLinkKey(link)]
481 })
482 var linkId;
483 for (linkId in pendingLinks) {
484 links.push(pendingLinks[linkId]);
485 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700486 return links
487}
Paul Greyson40c8a592013-03-27 14:10:33 -0700488
Paul Greyson5cc35f02013-03-28 10:07:36 -0700489updateTopology = function() {
490
491 // DRAW THE SWITCHES
492 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
493
494 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700495 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700496
Paul Greyson8d1c6362013-03-27 13:05:24 -0700497 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700498
499 d3.event.preventDefault();
500
Paul Greyson5cc35f02013-03-28 10:07:36 -0700501 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
502
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700503 if (data.highlighted) {
504 return;
505 }
506
507 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700508 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700509 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700510 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700511 // only allow links
512 // edge->edge (flow)
513 // aggregation->core
514 // core->core
515 if (data.className == 'edge' && !s.classed('edge') ||
516 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
517 data.className == 'aggregation' && !s.classed('core')) {
518 return;
519 }
520
521 // don't highlight if there's already a link or flow
522 // var map = linkMap[data.dpid];
523 // console.log(map);
524 // console.log(s.data()[0].dpid);
525 // console.log(map[s.data()[0].dpid]);
526 // if (map && map[s.data()[0].dpid]) {
527 // return;
528 // }
529
530 // the second highlighted switch is the target for a link or flow
531 data.target = true;
532 }
533
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700534 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700535 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700536 data.highlighted = true;
537 node.moveToFront();
538 }
539
Paul Greyson8d1c6362013-03-27 13:05:24 -0700540 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700541 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
542
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700543 if (data.mouseDown)
544 return;
545
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700546 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700547 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700548 data.highlighted = false;
549 data.target = false;
550 }
551
Paul Greyson8d1c6362013-03-27 13:05:24 -0700552 function mouseDownSwitch(data) {
553 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700554 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700555 d3.select('#topology').classed('linking', true);
556
Paul Greyson421bfcd2013-03-27 22:22:09 -0700557 d3.select('svg')
558 .append('svg:path')
559 .attr('id', 'linkVector')
560 .attr('d', function () {
561 var s = d3.select(document.getElementById(data.dpid));
562
563 var pt = document.querySelector('svg').createSVGPoint();
564 pt.x = s.attr('x');
565 pt.y = s.attr('y');
566 pt = pt.matrixTransform(s[0][0].getCTM());
567
568 return line([pt, pt]);
569 });
570
571
Paul Greyson72f18852013-03-27 15:56:11 -0700572 if (data.className === 'core') {
573 d3.selectAll('.edge').classed('nodrop', true);
574 }
575 if (data.className === 'edge') {
576 d3.selectAll('.core').classed('nodrop', true);
577 d3.selectAll('.aggregation').classed('nodrop', true);
578 }
579 if (data.className === 'aggregation') {
580 d3.selectAll('.edge').classed('nodrop', true);
581 d3.selectAll('.aggregation').classed('nodrop', true);
582 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700583 }
584
Paul Greyson8d1c6362013-03-27 13:05:24 -0700585 function mouseUpSwitch(data) {
586 if (data.mouseDown) {
587 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700588 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700589 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700590 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700591 }
592 }
593
594 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700595 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700596 if (data.state == 'ACTIVE') {
597 var prompt = 'Deactivate ' + data.dpid + '?';
598 if (confirm(prompt)) {
599 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700600 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700601 }
602 } else {
603 var prompt = 'Activate ' + data.dpid + '?';
604 if (confirm(prompt)) {
605 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700606 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700607 }
608 }
609 }
610
Paul Greyson740bdaf2013-03-18 16:10:48 -0700611 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700612 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700613 return;
614 }
615
Paul Greysonc17278a2013-03-23 10:17:12 -0700616 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700617 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700618 .data(data, function (data) {
619 return data.dpid;
620 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700621 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700622 .attr("id", function (data, i) {
623 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700624 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700625 .attr("transform", function(data, i) {
626 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700627 });
628
Paul Greysonc17278a2013-03-23 10:17:12 -0700629 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700630 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700631 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700632 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700633 if (data.scale) {
634 m = m.scale(data.scale);
635 }
636 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700637 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700638 .attr("x", function (data) {
639 return -data.width / 2;
640 })
641 .attr("y", function (data) {
642 return -data.width / 2;
643 })
644 .attr("r", function (data) {
645 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700646 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700647
Paul Greysonc17278a2013-03-23 10:17:12 -0700648 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700649 nodes.on('mouseover', mouseOverSwitch);
650 nodes.on('mouseout', mouseOutSwitch);
651 nodes.on('mouseup', mouseUpSwitch);
652 nodes.on('mousedown', mouseDownSwitch);
653
654 // only do switch up/down for core switches
655 if (i == 2) {
656 nodes.on('dblclick', doubleClickSwitch);
657 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700658 }
659
Paul Greysonc17278a2013-03-23 10:17:12 -0700660 // append switches
661 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700662 .attr("class", "ring")
663 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700664
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700665
Paul Greysonc17278a2013-03-23 10:17:12 -0700666 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700667 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700668 .data(data, function (data) {
669 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700670 });
Paul Greyson347fb742013-03-27 13:40:29 -0700671 nodes.select('circle')
672 .each(function (data) {
673 // if there's a pending state changed and then the state changes, clear the pending class
674 var circle = d3.select(this);
675 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
676 data.state === 'INACTIVE' && circle.classed('active')) {
677 circle.classed('pending', false);
678 }
679 })
680 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700681 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700682 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700683 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700684 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700685 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700686 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700687 }
688
689 // update switches
690 rings.each(ringUpdate);
691
Paul Greyson968d1b42013-03-23 16:58:41 -0700692
693 // Now setup the labels
694 // This is done separately because SVG draws in node order and we want the labels
695 // always on top
696 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
697
Paul Greyson421bfcd2013-03-27 22:22:09 -0700698 d3.select(document.body).on('mousemove', function () {
699 if (!d3.select('#topology').classed('linking')) {
700 return;
701 }
702 var linkVector = document.getElementById('linkVector');
703 if (!linkVector) {
704 return;
705 }
706 linkVector = d3.select(linkVector);
707
708 var highlighted = svg.selectAll('.highlight')[0];
709 var s1 = null, s2 = null;
710 if (highlighted.length > 1) {
711 var s1 = d3.select(highlighted[0]);
712 var s2 = d3.select(highlighted[1]);
713
714 } else if (highlighted.length > 0) {
715 var s1 = d3.select(highlighted[0]);
716 }
717 var src = s1;
718 if (s2 && !s2.data()[0].target) {
719 src = s2;
720 }
721 if (src) {
722 linkVector.attr('d', function () {
723 var srcPt = document.querySelector('svg').createSVGPoint();
724 srcPt.x = src.attr('x');
725 srcPt.y = src.attr('y');
726 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
727
728 var svg = document.getElementById('topology');
729 var mouse = d3.mouse(viewbox);
730 var dstPt = document.querySelector('svg').createSVGPoint();
731 dstPt.x = mouse[0];
732 dstPt.y = mouse[1];
733 dstPt = dstPt.matrixTransform(viewbox.getCTM());
734
735 return line([srcPt, dstPt]);
736 });
737 }
738 });
739
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700740 d3.select(document.body).on('mouseup', function () {
741 function clearHighlight() {
742 svg.selectAll('circle').each(function (data) {
743 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700744 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700745 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700746 });
747 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700748 };
749
Paul Greyson72f18852013-03-27 15:56:11 -0700750 d3.selectAll('.nodrop').classed('nodrop', false);
751
Paul Greyson084779b2013-03-27 13:55:49 -0700752 function removeLink(link) {
753 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
754 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
755
756 if (path1) {
757 setPending(d3.select(path1));
758 }
759 if (path2) {
760 setPending(d3.select(path2));
761 }
762
763 linkDown(link);
764 }
765
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700766
Paul Greyson421bfcd2013-03-27 22:22:09 -0700767 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700768 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700769 var s1Data = highlighted[0].__data__;
770 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700771
772 var srcData, dstData;
773 if (s1Data.target) {
774 dstData = s1Data;
775 srcData = s2Data;
776 } else {
777 dstData = s2Data;
778 srcData = s1Data;
779 }
780
781 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
782 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
783 if (confirm(prompt)) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700784 addFlow(srcData, dstData);
785
786 var flow = {
787 dataPath: {
788 srcPort: {
789 dpid: {
790 value: srcData.dpid
791 }
792 },
793 dstPort: {
794 dpid: {
795 value: dstData.dpid
796 }
797 }
798 },
799 pending: true
800 };
801
802 selectFlow(flow);
803
804 setTimeout(function () {
805 deselectFlow(flow);
Paul Greyson6f918402013-03-28 12:18:30 -0700806 }, pendingTimeout);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700807 }
808 } else {
809 var map = linkMap[srcData.dpid];
810 if (map && map[dstData.dpid]) {
811 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
812 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700813 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700814 }
815 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700816 map = linkMap[dstData.dpid];
817 if (map && map[srcData.dpid]) {
818 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
819 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700820 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700821 }
822 } else {
823 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
824 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700825 var link1 = {
826 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700827 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700828 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700829 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700830 pending: true
831 };
832 pendingLinks[makeLinkKey(link1)] = link1;
833 var link2 = {
834 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700835 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700836 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700837 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700838 pending: true
839 };
840 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700841 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700842
Paul Greyson2913af82013-03-27 14:53:17 -0700843 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700844
Paul Greyson5cc35f02013-03-28 10:07:36 -0700845 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700846 setTimeout(function () {
847 delete pendingLinks[makeLinkKey(link1)];
848 delete pendingLinks[makeLinkKey(link2)];
849
Paul Greyson5cc35f02013-03-28 10:07:36 -0700850 updateTopology();
Paul Greyson6f918402013-03-28 12:18:30 -0700851 }, pendingTimeout);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700852 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700853 }
854 }
855 }
856
857 clearHighlight();
858 } else {
859 clearHighlight();
860 }
861
862 });
863
Paul Greyson9066ab02013-03-23 18:15:41 -0700864 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700865 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700866 return;
867 }
868
869 // create the nodes
870 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700871 .data(data, function (data) {
872 return data.dpid;
873 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700874 .enter().append("svg:g")
875 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700876 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700877 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700878 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700879 .attr("transform", function(data, i) {
880 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700881 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700882
883 // add the text nodes which show on mouse over
884 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700885 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700886 .attr("x", function (data) {
887 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
888 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700889 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700890 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700891 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700892 }
893 } else {
894 if (data.className == 'edge') {
895 return data.width*3 + 4;
896 } else {
897 return data.width + 4;
898 }
899 }
900 })
901 .attr("y", function (data) {
902 var y;
903 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
904 if (data.className == 'edge') {
905 y = data.width*3/2 + 4;
906 } else {
907 y = data.width/2 + 4;
908 }
909 } else {
910 if (data.className == 'edge') {
911 y = data.width*3/2 + 4;
912 } else {
913 y = data.width/2 + 4;
914 }
915 }
916 return y - 6;
917 })
918 .attr("text-anchor", function (data) {
919 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
920 return "end";
921 } else {
922 return "start";
923 }
924 })
925 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700926 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
927 if (data.scale) {
928 m = m.scale(data.scale);
929 }
930 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
931 })
932 }
933
934 labelRings.enter().append("svg:g")
935 .attr("class", "textRing")
936 .each(labelRingEnter);
937
Paul Greysonc17278a2013-03-23 10:17:12 -0700938 // switches should not change during operation of the ui so no
939 // rings.exit()
940
941
Paul Greysond1a22d92013-03-19 12:15:19 -0700942 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -0700943
Paul Greysonc17278a2013-03-23 10:17:12 -0700944 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -0700945 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700946 return d['src-switch']+'->'+d['dst-switch'];
947 });
948
949 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700950 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -0700951 .attr("class", "link");
952
Paul Greyson084779b2013-03-27 13:55:49 -0700953 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700954 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -0700955 })
Paul Greyson56378ed2013-03-26 23:17:36 -0700956 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -0700957 var src = d3.select(document.getElementById(d['src-switch']));
958 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -0700959
Paul Greyson084779b2013-03-27 13:55:49 -0700960 var srcPt = document.querySelector('svg').createSVGPoint();
961 srcPt.x = src.attr('x');
962 srcPt.y = src.attr('y');
963 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700964
Paul Greyson084779b2013-03-27 13:55:49 -0700965 var dstPt = document.querySelector('svg').createSVGPoint();
966 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -0700967 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -0700968 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700969
Paul Greyson084779b2013-03-27 13:55:49 -0700970 var midPt = document.querySelector('svg').createSVGPoint();
971 midPt.x = (srcPt.x + dstPt.x)/2;
972 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -0700973
Paul Greyson084779b2013-03-27 13:55:49 -0700974 return line([srcPt, midPt, dstPt]);
975 })
Paul Greyson40c8a592013-03-27 14:10:33 -0700976 .attr("marker-mid", function(d) { return "url(#arrow)"; })
977 .classed('pending', function (d) {
978 return d.pending;
979 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700980
Paul Greyson56378ed2013-03-26 23:17:36 -0700981
Paul Greysonc17278a2013-03-23 10:17:12 -0700982 // remove old links
983 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700984}
985
Paul Greyson5cc35f02013-03-28 10:07:36 -0700986function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -0700987 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700988 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700989 .each(function (c) {
990 controllerColorMap[c] = colors.pop();
991 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
992 })
993 .text(function (d) {
994 return d;
Paul Greyson2913af82013-03-27 14:53:17 -0700995 })
996 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700997 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -0700998
Paul Greysone262a292013-03-23 10:35:23 -0700999 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -07001000 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -07001001 if (model.activeControllers.indexOf(d) != -1) {
1002 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -07001003 }
Paul Greysonbcd3c772013-03-21 13:16:44 -07001004 var className = 'controller ' + color;
1005 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -07001006 });
Paul Greysond1a22d92013-03-19 12:15:19 -07001007
Paul Greysone262a292013-03-23 10:35:23 -07001008 // this should never be needed
1009 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -07001010
Paul Greyson2913af82013-03-27 14:53:17 -07001011 controllers.on('dblclick', function (c) {
1012 if (model.activeControllers.indexOf(c) != -1) {
1013 var prompt = 'Dectivate ' + c + '?';
1014 if (confirm(prompt)) {
1015 controllerDown(c);
1016 setPending(d3.select(this));
1017 };
1018 } else {
1019 var prompt = 'Activate ' + c + '?';
1020 if (confirm(prompt)) {
1021 controllerUp(c);
1022 setPending(d3.select(this));
1023 };
1024 }
1025 });
1026
Paul Greyson8247c3f2013-03-28 00:24:02 -07001027 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -07001028 var allSelected = true;
1029 for (var key in controllerColorMap) {
1030 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
1031 allSelected = false;
1032 break;
1033 }
1034 }
1035 if (allSelected) {
1036 for (var key in controllerColorMap) {
1037 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
1038 }
1039 } else {
1040 for (var key in controllerColorMap) {
1041 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
1042 }
1043 }
1044
1045 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
1046 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -07001047 });
Paul Greyson8d1c6362013-03-27 13:05:24 -07001048
1049
Paul Greyson740bdaf2013-03-18 16:10:48 -07001050}
1051
Paul Greyson29aa98d2013-03-28 00:09:31 -07001052function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -07001053 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -07001054 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001055// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -07001056
Paul Greyson5cc35f02013-03-28 10:07:36 -07001057 var modelChanged = false;
Paul Greyson56378ed2013-03-26 23:17:36 -07001058 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
Paul Greyson5cc35f02013-03-28 10:07:36 -07001059 modelChanged = true;
1060 model = newModel;
Paul Greysonb48943b2013-03-19 13:27:57 -07001061 } else {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001062// console.log('no change');
Paul Greysonb48943b2013-03-19 13:27:57 -07001063 }
Paul Greysonb48943b2013-03-19 13:27:57 -07001064
Paul Greyson5cc35f02013-03-28 10:07:36 -07001065 if (modelChanged) {
1066 updateControllers();
1067 updateSelectedFlows();
1068 updateTopology();
1069 }
1070
1071 updateHeader(newModel);
Paul Greyson740bdaf2013-03-18 16:10:48 -07001072
1073 // do it again in 1s
1074 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -07001075 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -07001076 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -07001077 });
1078}
Paul Greyson740bdaf2013-03-18 16:10:48 -07001079
Paul Greyson38d8bde2013-03-22 22:07:35 -07001080svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -07001081updateSelectedFlows();
1082
1083d3.select('#showFlowChooser').on('click', function () {
1084 showFlowChooser();
1085});
1086
Paul Greyson72f18852013-03-27 15:56:11 -07001087
Paul Greyson38d8bde2013-03-22 22:07:35 -07001088// workaround for Chrome v25 bug
1089// if executed immediately, the view box transform logic doesn't work properly
1090// fixed in Chrome v27
1091setTimeout(function () {
1092 // workaround for another Chrome v25 bug
1093 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -07001094 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -07001095 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -07001096 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -07001097}, 100);