blob: 926ddf40e5e2f5d1a199883d1df377f7d1b24c2c [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 })
125 .classed('pending', function (d) {
126 return d && d.pending
Paul Greyson127d7fb2013-03-25 23:39:20 -0700127 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700128
Paul Greyson56378ed2013-03-26 23:17:36 -0700129 // "marching ants"
Paul Greyson29aa98d2013-03-28 00:09:31 -0700130 flows.select('animate').attr('from', 500);
131
Paul Greyson13f02b92013-03-28 11:29:35 -0700132}
133
134function updateSelectedFlowsTable() {
135 function rowEnter(d) {
136 var row = d3.select(this);
137 row.append('div').classed('flowId', true);
138 row.append('div').classed('srcDPID', true);
139 row.append('div').classed('dstDPID', true);
140 row.append('div').classed('iperf', true);
141 }
142
143 function rowUpdate(d) {
144 var row = d3.select(this);
145 row.select('.flowId')
146 .text(function (d) {
147 if (d) {
148 if (d.flowId) {
149 return d.flowId.value;
150 } else {
151 return '0x--';
152 }
153 }
154 })
155 .classed('pending', d && d.pending);
156
157 row.select('.srcDPID')
158 .text(function (d) {
159 if (d) {
160 return d.dataPath.srcPort.dpid.value;
161 }
162 });
163
164 row.select('.dstDPID')
165 .text(function (d) {
166 if (d) {
167 return d.dataPath.dstPort.dpid.value;
168 }
169 });
170 }
171
172 var flows = d3.select('#selectedFlows')
173 .selectAll('.selectedFlow')
174 .data(selectedFlows);
175
176 flows.enter()
177 .append('div')
178 .classed('selectedFlow', true)
179 .each(rowEnter);
180
181 flows.each(rowUpdate);
182
Paul Greyson29aa98d2013-03-28 00:09:31 -0700183 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700184}
185
Paul Greyson13f02b92013-03-28 11:29:35 -0700186function updateSelectedFlows() {
187 // make sure that all of the selected flows are either
188 // 1) valid (meaning they are in the latest list of flows)
189 // 2) pending
190 if (model) {
191 var flowMap = {};
192 model.flows.forEach(function (flow) {
193 flowMap[makeFlowKey(flow)] = flow;
194 });
195
196 var newSelectedFlows = [];
197 selectedFlows.forEach(function (flow) {
198 if (flow) {
199 if (flow.pending) {
200 newSelectedFlows.push(flow);
201 } else {
202 var liveFlow = flowMap[makeFlowKey(flow)];
203 if (liveFlow) {
204 newSelectedFlows.push(liveFlow);
205 }
206 }
207 } else {
208 newSelectedFlows.push(null);
209 }
210 });
211 selectedFlows = newSelectedFlows;
212 }
213
214 updateSelectedFlowsTable();
215 updateSelectedFlowsTopology();
216}
217
218function selectFlow(flow) {
Paul Greysonc30f75e2013-03-28 11:45:15 -0700219 var flowKey = makeFlowKey(flow);
220 var alreadySelected = false;
221 selectedFlows.forEach(function (f) {
222 if (f && makeFlowKey(f) === flowKey) {
223 alreadySelected = true;
224 }
225 });
226
227 if (!alreadySelected) {
228 selectedFlows.unshift(flow);
229 selectedFlows = selectedFlows.slice(0, 3);
230 updateSelectedFlows();
231 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700232}
233
234function deselectFlow(flow) {
235 var flowKey = makeFlowKey(flow);
236 var newSelectedFlows = [];
237 selectedFlows.forEach(function (flow) {
238 if (!flow || flowKey !== makeFlowKey(flow)) {
239 newSelectedFlows.push(flow);
240 }
241 });
242 selectedFlows = newSelectedFlows;
243 while (selectedFlows.length < 3) {
244 selectedFlows.push(null);
245 }
246
247 updateSelectedFlows();
248}
249
Paul Greyson29aa98d2013-03-28 00:09:31 -0700250function showFlowChooser() {
251 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700252 var row = d3.select(this);
253
Paul Greyson127d7fb2013-03-25 23:39:20 -0700254 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700255 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700256 on('click', function () {
Paul Greyson13f02b92013-03-28 11:29:35 -0700257 selectFlow(d);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700258 });
259
260 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700261 .classed('flowId', true)
262 .text(function (d) {
263 return d.flowId.value;
264 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700265
266 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700267 .classed('srcDPID', true)
268 .text(function (d) {
269 return d.dataPath.srcPort.dpid.value;
270 });
271
Paul Greyson127d7fb2013-03-25 23:39:20 -0700272
273 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700274 .classed('dstDPID', true)
275 .text(function (d) {
276 return d.dataPath.dstPort.dpid.value;
277 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700278
Paul Greyson127d7fb2013-03-25 23:39:20 -0700279 }
280
Paul Greyson29aa98d2013-03-28 00:09:31 -0700281 var flows = d3.select('#flowChooser')
282 .append('div')
283 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700284 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700285 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700286 .enter()
287 .append('div')
288 .classed('selectedFlow', true)
289 .each(rowEnter);
290
Paul Greyson29aa98d2013-03-28 00:09:31 -0700291 setTimeout(function () {
292 d3.select(document.body).on('click', function () {
293 d3.select('#flowChooser').html('');
294 d3.select(document.body).on('click', null);
295 });
296 }, 0);
297}
298
Paul Greyson29aa98d2013-03-28 00:09:31 -0700299
Paul Greyson127d7fb2013-03-25 23:39:20 -0700300
Paul Greysond1a22d92013-03-19 12:15:19 -0700301function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700302 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700303 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
304 d3.select('#activeFlows').text(model.flows.length);
305}
306
307function toRadians (angle) {
308 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700309}
310
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700311var widths = {
312 edge: 6,
313 aggregation: 12,
314 core: 18
315}
316
Paul Greysonc17278a2013-03-23 10:17:12 -0700317function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700318 var rings = [{
319 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700320 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700321 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700322 className: 'edge',
323 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700324 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700325 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700326 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700327 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700328 className: 'aggregation',
329 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700330 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700331 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700332 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700333 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700334 className: 'core',
335 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700336 }];
337
Paul Greysonde7fad52013-03-19 12:47:32 -0700338
339 var aggRanges = {};
340
341 // arrange edge switches at equal increments
342 var k = 360 / rings[0].switches.length;
343 rings[0].switches.forEach(function (s, i) {
344 var angle = k * i;
345
346 rings[0].angles[i] = angle;
347
348 // record the angle for the agg switch layout
349 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700350 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700351 var aggdpid = dpid.join(':');
352 var aggRange = aggRanges[aggdpid];
353 if (!aggRange) {
354 aggRange = aggRanges[aggdpid] = {};
355 aggRange.min = aggRange.max = angle;
356 } else {
357 aggRange.max = angle;
358 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700359 });
360
361 // arrange aggregation switches to "fan out" to edge switches
362 k = 360 / rings[1].switches.length;
363 rings[1].switches.forEach(function (s, i) {
364// rings[1].angles[i] = k * i;
365 var range = aggRanges[s.dpid];
366
Paul Greyson832d2202013-03-21 13:27:56 -0700367 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700368 });
369
Paul Greyson3f890b62013-03-22 17:39:36 -0700370 // find the association between core switches and aggregation switches
371 var aggregationSwitchMap = {};
372 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700373 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700374 });
375
Paul Greyson3f890b62013-03-22 17:39:36 -0700376 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700377 k = 360 / rings[2].switches.length;
378 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700379// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700380 var associatedAggregationSwitches = model.configuration.association[s.dpid];
381 // TODO: go between if there are multiple
382 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
383
384 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700385 });
386
Paul Greyson644d92a2013-03-23 18:00:40 -0700387 // TODO: construct this form initially rather than converting. it works better because
388 // it allows binding by dpid
389 var testRings = [];
390 rings.forEach(function (ring) {
391 var testRing = [];
392 ring.switches.forEach(function (s, i) {
393 var testSwitch = {
394 dpid: s.dpid,
395 state: s.state,
396 radius: ring.radius,
397 width: ring.width,
398 className: ring.className,
399 angle: ring.angles[i],
400 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700401 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700402 testRing.push(testSwitch);
403 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700404
405
Paul Greyson644d92a2013-03-23 18:00:40 -0700406 testRings.push(testRing);
407 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700408
409
Paul Greyson644d92a2013-03-23 18:00:40 -0700410// return rings;
411 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700412}
413
Paul Greyson40c8a592013-03-27 14:10:33 -0700414function makeLinkKey(link) {
415 return link['src-switch'] + '=>' + link['dst-switch'];
416}
417
Paul Greyson13f02b92013-03-28 11:29:35 -0700418function makeFlowKey(flow) {
419 return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
420}
421
Paul Greyson40c8a592013-03-27 14:10:33 -0700422function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700423 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700424 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700425 var srcDPID = link['src-switch'];
426 var dstDPID = link['dst-switch'];
427
428 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700429
Paul Greyson8d1c6362013-03-27 13:05:24 -0700430 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700431
432 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700433 });
434 return linkMap;
435}
436
Paul Greyson5cc35f02013-03-28 10:07:36 -0700437// removes links from the pending list that are now in the model
438function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700439 var links = [];
440 model.links.forEach(function (link) {
441 links.push(link);
442 delete pendingLinks[makeLinkKey(link)]
443 })
444 var linkId;
445 for (linkId in pendingLinks) {
446 links.push(pendingLinks[linkId]);
447 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700448 return links
449}
Paul Greyson40c8a592013-03-27 14:10:33 -0700450
Paul Greyson5cc35f02013-03-28 10:07:36 -0700451updateTopology = function() {
452
453 // DRAW THE SWITCHES
454 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
455
456 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700457 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700458
Paul Greyson8d1c6362013-03-27 13:05:24 -0700459 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700460
461 d3.event.preventDefault();
462
Paul Greyson5cc35f02013-03-28 10:07:36 -0700463 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
464
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700465 if (data.highlighted) {
466 return;
467 }
468
469 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700470 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700471 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700472 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700473 // only allow links
474 // edge->edge (flow)
475 // aggregation->core
476 // core->core
477 if (data.className == 'edge' && !s.classed('edge') ||
478 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
479 data.className == 'aggregation' && !s.classed('core')) {
480 return;
481 }
482
483 // don't highlight if there's already a link or flow
484 // var map = linkMap[data.dpid];
485 // console.log(map);
486 // console.log(s.data()[0].dpid);
487 // console.log(map[s.data()[0].dpid]);
488 // if (map && map[s.data()[0].dpid]) {
489 // return;
490 // }
491
492 // the second highlighted switch is the target for a link or flow
493 data.target = true;
494 }
495
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700496 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700497 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700498 data.highlighted = true;
499 node.moveToFront();
500 }
501
Paul Greyson8d1c6362013-03-27 13:05:24 -0700502 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700503 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
504
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700505 if (data.mouseDown)
506 return;
507
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700508 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700509 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700510 data.highlighted = false;
511 data.target = false;
512 }
513
Paul Greyson8d1c6362013-03-27 13:05:24 -0700514 function mouseDownSwitch(data) {
515 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700516 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700517 d3.select('#topology').classed('linking', true);
518
Paul Greyson421bfcd2013-03-27 22:22:09 -0700519 d3.select('svg')
520 .append('svg:path')
521 .attr('id', 'linkVector')
522 .attr('d', function () {
523 var s = d3.select(document.getElementById(data.dpid));
524
525 var pt = document.querySelector('svg').createSVGPoint();
526 pt.x = s.attr('x');
527 pt.y = s.attr('y');
528 pt = pt.matrixTransform(s[0][0].getCTM());
529
530 return line([pt, pt]);
531 });
532
533
Paul Greyson72f18852013-03-27 15:56:11 -0700534 if (data.className === 'core') {
535 d3.selectAll('.edge').classed('nodrop', true);
536 }
537 if (data.className === 'edge') {
538 d3.selectAll('.core').classed('nodrop', true);
539 d3.selectAll('.aggregation').classed('nodrop', true);
540 }
541 if (data.className === 'aggregation') {
542 d3.selectAll('.edge').classed('nodrop', true);
543 d3.selectAll('.aggregation').classed('nodrop', true);
544 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700545 }
546
Paul Greyson8d1c6362013-03-27 13:05:24 -0700547 function mouseUpSwitch(data) {
548 if (data.mouseDown) {
549 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700550 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700551 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700552 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700553 }
554 }
555
556 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700557 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700558 if (data.state == 'ACTIVE') {
559 var prompt = 'Deactivate ' + data.dpid + '?';
560 if (confirm(prompt)) {
561 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700562 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700563 }
564 } else {
565 var prompt = 'Activate ' + data.dpid + '?';
566 if (confirm(prompt)) {
567 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700568 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700569 }
570 }
571 }
572
Paul Greyson740bdaf2013-03-18 16:10:48 -0700573 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700574 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700575 return;
576 }
577
Paul Greysonc17278a2013-03-23 10:17:12 -0700578 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700579 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700580 .data(data, function (data) {
581 return data.dpid;
582 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700583 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700584 .attr("id", function (data, i) {
585 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700586 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700587 .attr("transform", function(data, i) {
588 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700589 });
590
Paul Greysonc17278a2013-03-23 10:17:12 -0700591 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700592 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700593 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700594 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700595 if (data.scale) {
596 m = m.scale(data.scale);
597 }
598 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700599 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700600 .attr("x", function (data) {
601 return -data.width / 2;
602 })
603 .attr("y", function (data) {
604 return -data.width / 2;
605 })
606 .attr("r", function (data) {
607 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700608 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700609
Paul Greysonc17278a2013-03-23 10:17:12 -0700610 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700611 nodes.on('mouseover', mouseOverSwitch);
612 nodes.on('mouseout', mouseOutSwitch);
613 nodes.on('mouseup', mouseUpSwitch);
614 nodes.on('mousedown', mouseDownSwitch);
615
616 // only do switch up/down for core switches
617 if (i == 2) {
618 nodes.on('dblclick', doubleClickSwitch);
619 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700620 }
621
Paul Greysonc17278a2013-03-23 10:17:12 -0700622 // append switches
623 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700624 .attr("class", "ring")
625 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700626
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700627
Paul Greysonc17278a2013-03-23 10:17:12 -0700628 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700629 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700630 .data(data, function (data) {
631 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700632 });
Paul Greyson347fb742013-03-27 13:40:29 -0700633 nodes.select('circle')
634 .each(function (data) {
635 // if there's a pending state changed and then the state changes, clear the pending class
636 var circle = d3.select(this);
637 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
638 data.state === 'INACTIVE' && circle.classed('active')) {
639 circle.classed('pending', false);
640 }
641 })
642 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700643 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700644 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700645 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700646 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700647 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700648 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700649 }
650
651 // update switches
652 rings.each(ringUpdate);
653
Paul Greyson968d1b42013-03-23 16:58:41 -0700654
655 // Now setup the labels
656 // This is done separately because SVG draws in node order and we want the labels
657 // always on top
658 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
659
Paul Greyson421bfcd2013-03-27 22:22:09 -0700660 d3.select(document.body).on('mousemove', function () {
661 if (!d3.select('#topology').classed('linking')) {
662 return;
663 }
664 var linkVector = document.getElementById('linkVector');
665 if (!linkVector) {
666 return;
667 }
668 linkVector = d3.select(linkVector);
669
670 var highlighted = svg.selectAll('.highlight')[0];
671 var s1 = null, s2 = null;
672 if (highlighted.length > 1) {
673 var s1 = d3.select(highlighted[0]);
674 var s2 = d3.select(highlighted[1]);
675
676 } else if (highlighted.length > 0) {
677 var s1 = d3.select(highlighted[0]);
678 }
679 var src = s1;
680 if (s2 && !s2.data()[0].target) {
681 src = s2;
682 }
683 if (src) {
684 linkVector.attr('d', function () {
685 var srcPt = document.querySelector('svg').createSVGPoint();
686 srcPt.x = src.attr('x');
687 srcPt.y = src.attr('y');
688 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
689
690 var svg = document.getElementById('topology');
691 var mouse = d3.mouse(viewbox);
692 var dstPt = document.querySelector('svg').createSVGPoint();
693 dstPt.x = mouse[0];
694 dstPt.y = mouse[1];
695 dstPt = dstPt.matrixTransform(viewbox.getCTM());
696
697 return line([srcPt, dstPt]);
698 });
699 }
700 });
701
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700702 d3.select(document.body).on('mouseup', function () {
703 function clearHighlight() {
704 svg.selectAll('circle').each(function (data) {
705 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700706 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700707 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700708 });
709 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700710 };
711
Paul Greyson72f18852013-03-27 15:56:11 -0700712 d3.selectAll('.nodrop').classed('nodrop', false);
713
Paul Greyson084779b2013-03-27 13:55:49 -0700714 function removeLink(link) {
715 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
716 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
717
718 if (path1) {
719 setPending(d3.select(path1));
720 }
721 if (path2) {
722 setPending(d3.select(path2));
723 }
724
725 linkDown(link);
726 }
727
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700728
Paul Greyson421bfcd2013-03-27 22:22:09 -0700729 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700730 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700731 var s1Data = highlighted[0].__data__;
732 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700733
734 var srcData, dstData;
735 if (s1Data.target) {
736 dstData = s1Data;
737 srcData = s2Data;
738 } else {
739 dstData = s2Data;
740 srcData = s1Data;
741 }
742
743 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
744 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
745 if (confirm(prompt)) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700746 addFlow(srcData, dstData);
747
748 var flow = {
749 dataPath: {
750 srcPort: {
751 dpid: {
752 value: srcData.dpid
753 }
754 },
755 dstPort: {
756 dpid: {
757 value: dstData.dpid
758 }
759 }
760 },
761 pending: true
762 };
763
764 selectFlow(flow);
765
766 setTimeout(function () {
767 deselectFlow(flow);
768 }, 10000);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700769 }
770 } else {
771 var map = linkMap[srcData.dpid];
772 if (map && map[dstData.dpid]) {
773 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
774 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700775 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700776 }
777 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700778 map = linkMap[dstData.dpid];
779 if (map && map[srcData.dpid]) {
780 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
781 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700782 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700783 }
784 } else {
785 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
786 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700787 var link1 = {
788 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700789 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700790 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700791 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700792 pending: true
793 };
794 pendingLinks[makeLinkKey(link1)] = link1;
795 var link2 = {
796 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700797 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700798 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700799 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700800 pending: true
801 };
802 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700803 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700804
Paul Greyson2913af82013-03-27 14:53:17 -0700805 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700806
Paul Greyson5cc35f02013-03-28 10:07:36 -0700807 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700808 setTimeout(function () {
809 delete pendingLinks[makeLinkKey(link1)];
810 delete pendingLinks[makeLinkKey(link2)];
811
Paul Greyson5cc35f02013-03-28 10:07:36 -0700812 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700813 }, 10000);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700814 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700815 }
816 }
817 }
818
819 clearHighlight();
820 } else {
821 clearHighlight();
822 }
823
824 });
825
Paul Greyson9066ab02013-03-23 18:15:41 -0700826 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700827 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700828 return;
829 }
830
831 // create the nodes
832 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700833 .data(data, function (data) {
834 return data.dpid;
835 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700836 .enter().append("svg:g")
837 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700838 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700839 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700840 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700841 .attr("transform", function(data, i) {
842 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700843 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700844
845 // add the text nodes which show on mouse over
846 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700847 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700848 .attr("x", function (data) {
849 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
850 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700851 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700852 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700853 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700854 }
855 } else {
856 if (data.className == 'edge') {
857 return data.width*3 + 4;
858 } else {
859 return data.width + 4;
860 }
861 }
862 })
863 .attr("y", function (data) {
864 var y;
865 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
866 if (data.className == 'edge') {
867 y = data.width*3/2 + 4;
868 } else {
869 y = data.width/2 + 4;
870 }
871 } else {
872 if (data.className == 'edge') {
873 y = data.width*3/2 + 4;
874 } else {
875 y = data.width/2 + 4;
876 }
877 }
878 return y - 6;
879 })
880 .attr("text-anchor", function (data) {
881 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
882 return "end";
883 } else {
884 return "start";
885 }
886 })
887 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700888 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
889 if (data.scale) {
890 m = m.scale(data.scale);
891 }
892 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
893 })
894 }
895
896 labelRings.enter().append("svg:g")
897 .attr("class", "textRing")
898 .each(labelRingEnter);
899
Paul Greysonc17278a2013-03-23 10:17:12 -0700900 // switches should not change during operation of the ui so no
901 // rings.exit()
902
903
Paul Greysond1a22d92013-03-19 12:15:19 -0700904 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -0700905
Paul Greysonc17278a2013-03-23 10:17:12 -0700906 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -0700907 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700908 return d['src-switch']+'->'+d['dst-switch'];
909 });
910
911 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700912 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -0700913 .attr("class", "link");
914
Paul Greyson084779b2013-03-27 13:55:49 -0700915 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700916 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -0700917 })
Paul Greyson56378ed2013-03-26 23:17:36 -0700918 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -0700919 var src = d3.select(document.getElementById(d['src-switch']));
920 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -0700921
Paul Greyson084779b2013-03-27 13:55:49 -0700922 var srcPt = document.querySelector('svg').createSVGPoint();
923 srcPt.x = src.attr('x');
924 srcPt.y = src.attr('y');
925 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700926
Paul Greyson084779b2013-03-27 13:55:49 -0700927 var dstPt = document.querySelector('svg').createSVGPoint();
928 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -0700929 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -0700930 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700931
Paul Greyson084779b2013-03-27 13:55:49 -0700932 var midPt = document.querySelector('svg').createSVGPoint();
933 midPt.x = (srcPt.x + dstPt.x)/2;
934 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -0700935
Paul Greyson084779b2013-03-27 13:55:49 -0700936 return line([srcPt, midPt, dstPt]);
937 })
Paul Greyson40c8a592013-03-27 14:10:33 -0700938 .attr("marker-mid", function(d) { return "url(#arrow)"; })
939 .classed('pending', function (d) {
940 return d.pending;
941 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700942
Paul Greyson56378ed2013-03-26 23:17:36 -0700943
Paul Greysonc17278a2013-03-23 10:17:12 -0700944 // remove old links
945 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700946}
947
Paul Greyson5cc35f02013-03-28 10:07:36 -0700948function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -0700949 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700950 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700951 .each(function (c) {
952 controllerColorMap[c] = colors.pop();
953 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
954 })
955 .text(function (d) {
956 return d;
Paul Greyson2913af82013-03-27 14:53:17 -0700957 })
958 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700959 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -0700960
Paul Greysone262a292013-03-23 10:35:23 -0700961 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -0700962 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -0700963 if (model.activeControllers.indexOf(d) != -1) {
964 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -0700965 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700966 var className = 'controller ' + color;
967 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700968 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700969
Paul Greysone262a292013-03-23 10:35:23 -0700970 // this should never be needed
971 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700972
Paul Greyson2913af82013-03-27 14:53:17 -0700973 controllers.on('dblclick', function (c) {
974 if (model.activeControllers.indexOf(c) != -1) {
975 var prompt = 'Dectivate ' + c + '?';
976 if (confirm(prompt)) {
977 controllerDown(c);
978 setPending(d3.select(this));
979 };
980 } else {
981 var prompt = 'Activate ' + c + '?';
982 if (confirm(prompt)) {
983 controllerUp(c);
984 setPending(d3.select(this));
985 };
986 }
987 });
988
Paul Greyson8247c3f2013-03-28 00:24:02 -0700989 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -0700990 var allSelected = true;
991 for (var key in controllerColorMap) {
992 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
993 allSelected = false;
994 break;
995 }
996 }
997 if (allSelected) {
998 for (var key in controllerColorMap) {
999 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
1000 }
1001 } else {
1002 for (var key in controllerColorMap) {
1003 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
1004 }
1005 }
1006
1007 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
1008 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -07001009 });
Paul Greyson8d1c6362013-03-27 13:05:24 -07001010
1011
Paul Greyson740bdaf2013-03-18 16:10:48 -07001012}
1013
Paul Greyson29aa98d2013-03-28 00:09:31 -07001014function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -07001015 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -07001016 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001017// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -07001018
Paul Greyson5cc35f02013-03-28 10:07:36 -07001019 var modelChanged = false;
Paul Greyson56378ed2013-03-26 23:17:36 -07001020 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
Paul Greyson5cc35f02013-03-28 10:07:36 -07001021 modelChanged = true;
1022 model = newModel;
Paul Greysonb48943b2013-03-19 13:27:57 -07001023 } else {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001024// console.log('no change');
Paul Greysonb48943b2013-03-19 13:27:57 -07001025 }
Paul Greysonb48943b2013-03-19 13:27:57 -07001026
Paul Greyson5cc35f02013-03-28 10:07:36 -07001027 if (modelChanged) {
1028 updateControllers();
1029 updateSelectedFlows();
1030 updateTopology();
1031 }
1032
1033 updateHeader(newModel);
Paul Greyson740bdaf2013-03-18 16:10:48 -07001034
1035 // do it again in 1s
1036 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -07001037 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -07001038 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -07001039 });
1040}
Paul Greyson740bdaf2013-03-18 16:10:48 -07001041
Paul Greyson38d8bde2013-03-22 22:07:35 -07001042svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -07001043updateSelectedFlows();
1044
1045d3.select('#showFlowChooser').on('click', function () {
1046 showFlowChooser();
1047});
1048
Paul Greyson72f18852013-03-27 15:56:11 -07001049
Paul Greyson38d8bde2013-03-22 22:07:35 -07001050// workaround for Chrome v25 bug
1051// if executed immediately, the view box transform logic doesn't work properly
1052// fixed in Chrome v27
1053setTimeout(function () {
1054 // workaround for another Chrome v25 bug
1055 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -07001056 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -07001057 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -07001058 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -07001059}, 100);