blob: 63048bd210bec1ecf3403fc3f6d2770be87c90a4 [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) {
219 selectedFlows.unshift(flow);
220 selectedFlows = selectedFlows.slice(0, 3);
221 updateSelectedFlows();
222}
223
224function deselectFlow(flow) {
225 var flowKey = makeFlowKey(flow);
226 var newSelectedFlows = [];
227 selectedFlows.forEach(function (flow) {
228 if (!flow || flowKey !== makeFlowKey(flow)) {
229 newSelectedFlows.push(flow);
230 }
231 });
232 selectedFlows = newSelectedFlows;
233 while (selectedFlows.length < 3) {
234 selectedFlows.push(null);
235 }
236
237 updateSelectedFlows();
238}
239
Paul Greyson29aa98d2013-03-28 00:09:31 -0700240function showFlowChooser() {
241 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700242 var row = d3.select(this);
243
Paul Greyson127d7fb2013-03-25 23:39:20 -0700244 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700245 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700246 on('click', function () {
Paul Greyson13f02b92013-03-28 11:29:35 -0700247 selectFlow(d);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700248 });
249
250 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700251 .classed('flowId', true)
252 .text(function (d) {
253 return d.flowId.value;
254 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700255
256 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700257 .classed('srcDPID', true)
258 .text(function (d) {
259 return d.dataPath.srcPort.dpid.value;
260 });
261
Paul Greyson127d7fb2013-03-25 23:39:20 -0700262
263 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700264 .classed('dstDPID', true)
265 .text(function (d) {
266 return d.dataPath.dstPort.dpid.value;
267 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700268
Paul Greyson127d7fb2013-03-25 23:39:20 -0700269 }
270
Paul Greyson29aa98d2013-03-28 00:09:31 -0700271 var flows = d3.select('#flowChooser')
272 .append('div')
273 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700274 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700275 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700276 .enter()
277 .append('div')
278 .classed('selectedFlow', true)
279 .each(rowEnter);
280
Paul Greyson29aa98d2013-03-28 00:09:31 -0700281 setTimeout(function () {
282 d3.select(document.body).on('click', function () {
283 d3.select('#flowChooser').html('');
284 d3.select(document.body).on('click', null);
285 });
286 }, 0);
287}
288
Paul Greyson29aa98d2013-03-28 00:09:31 -0700289
Paul Greyson127d7fb2013-03-25 23:39:20 -0700290
Paul Greysond1a22d92013-03-19 12:15:19 -0700291function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700292 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700293 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
294 d3.select('#activeFlows').text(model.flows.length);
295}
296
297function toRadians (angle) {
298 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700299}
300
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700301var widths = {
302 edge: 6,
303 aggregation: 12,
304 core: 18
305}
306
Paul Greysonc17278a2013-03-23 10:17:12 -0700307function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700308 var rings = [{
309 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700310 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700311 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700312 className: 'edge',
313 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700314 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700315 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700316 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700317 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700318 className: 'aggregation',
319 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700320 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700321 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700322 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700323 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700324 className: 'core',
325 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700326 }];
327
Paul Greysonde7fad52013-03-19 12:47:32 -0700328
329 var aggRanges = {};
330
331 // arrange edge switches at equal increments
332 var k = 360 / rings[0].switches.length;
333 rings[0].switches.forEach(function (s, i) {
334 var angle = k * i;
335
336 rings[0].angles[i] = angle;
337
338 // record the angle for the agg switch layout
339 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700340 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700341 var aggdpid = dpid.join(':');
342 var aggRange = aggRanges[aggdpid];
343 if (!aggRange) {
344 aggRange = aggRanges[aggdpid] = {};
345 aggRange.min = aggRange.max = angle;
346 } else {
347 aggRange.max = angle;
348 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700349 });
350
351 // arrange aggregation switches to "fan out" to edge switches
352 k = 360 / rings[1].switches.length;
353 rings[1].switches.forEach(function (s, i) {
354// rings[1].angles[i] = k * i;
355 var range = aggRanges[s.dpid];
356
Paul Greyson832d2202013-03-21 13:27:56 -0700357 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700358 });
359
Paul Greyson3f890b62013-03-22 17:39:36 -0700360 // find the association between core switches and aggregation switches
361 var aggregationSwitchMap = {};
362 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700363 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700364 });
365
Paul Greyson3f890b62013-03-22 17:39:36 -0700366 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700367 k = 360 / rings[2].switches.length;
368 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700369// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700370 var associatedAggregationSwitches = model.configuration.association[s.dpid];
371 // TODO: go between if there are multiple
372 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
373
374 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700375 });
376
Paul Greyson644d92a2013-03-23 18:00:40 -0700377 // TODO: construct this form initially rather than converting. it works better because
378 // it allows binding by dpid
379 var testRings = [];
380 rings.forEach(function (ring) {
381 var testRing = [];
382 ring.switches.forEach(function (s, i) {
383 var testSwitch = {
384 dpid: s.dpid,
385 state: s.state,
386 radius: ring.radius,
387 width: ring.width,
388 className: ring.className,
389 angle: ring.angles[i],
390 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700391 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700392 testRing.push(testSwitch);
393 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700394
395
Paul Greyson644d92a2013-03-23 18:00:40 -0700396 testRings.push(testRing);
397 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700398
399
Paul Greyson644d92a2013-03-23 18:00:40 -0700400// return rings;
401 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700402}
403
Paul Greyson40c8a592013-03-27 14:10:33 -0700404function makeLinkKey(link) {
405 return link['src-switch'] + '=>' + link['dst-switch'];
406}
407
Paul Greyson13f02b92013-03-28 11:29:35 -0700408function makeFlowKey(flow) {
409 return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
410}
411
Paul Greyson40c8a592013-03-27 14:10:33 -0700412function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700413 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700414 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700415 var srcDPID = link['src-switch'];
416 var dstDPID = link['dst-switch'];
417
418 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700419
Paul Greyson8d1c6362013-03-27 13:05:24 -0700420 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700421
422 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700423 });
424 return linkMap;
425}
426
Paul Greyson5cc35f02013-03-28 10:07:36 -0700427// removes links from the pending list that are now in the model
428function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700429 var links = [];
430 model.links.forEach(function (link) {
431 links.push(link);
432 delete pendingLinks[makeLinkKey(link)]
433 })
434 var linkId;
435 for (linkId in pendingLinks) {
436 links.push(pendingLinks[linkId]);
437 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700438 return links
439}
Paul Greyson40c8a592013-03-27 14:10:33 -0700440
Paul Greyson5cc35f02013-03-28 10:07:36 -0700441updateTopology = function() {
442
443 // DRAW THE SWITCHES
444 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
445
446 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700447 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700448
Paul Greyson8d1c6362013-03-27 13:05:24 -0700449 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700450
451 d3.event.preventDefault();
452
Paul Greyson5cc35f02013-03-28 10:07:36 -0700453 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
454
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700455 if (data.highlighted) {
456 return;
457 }
458
459 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700460 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700461 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700462 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700463 // only allow links
464 // edge->edge (flow)
465 // aggregation->core
466 // core->core
467 if (data.className == 'edge' && !s.classed('edge') ||
468 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
469 data.className == 'aggregation' && !s.classed('core')) {
470 return;
471 }
472
473 // don't highlight if there's already a link or flow
474 // var map = linkMap[data.dpid];
475 // console.log(map);
476 // console.log(s.data()[0].dpid);
477 // console.log(map[s.data()[0].dpid]);
478 // if (map && map[s.data()[0].dpid]) {
479 // return;
480 // }
481
482 // the second highlighted switch is the target for a link or flow
483 data.target = true;
484 }
485
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700486 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700487 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700488 data.highlighted = true;
489 node.moveToFront();
490 }
491
Paul Greyson8d1c6362013-03-27 13:05:24 -0700492 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700493 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
494
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700495 if (data.mouseDown)
496 return;
497
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700498 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700499 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700500 data.highlighted = false;
501 data.target = false;
502 }
503
Paul Greyson8d1c6362013-03-27 13:05:24 -0700504 function mouseDownSwitch(data) {
505 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700506 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700507 d3.select('#topology').classed('linking', true);
508
Paul Greyson421bfcd2013-03-27 22:22:09 -0700509 d3.select('svg')
510 .append('svg:path')
511 .attr('id', 'linkVector')
512 .attr('d', function () {
513 var s = d3.select(document.getElementById(data.dpid));
514
515 var pt = document.querySelector('svg').createSVGPoint();
516 pt.x = s.attr('x');
517 pt.y = s.attr('y');
518 pt = pt.matrixTransform(s[0][0].getCTM());
519
520 return line([pt, pt]);
521 });
522
523
Paul Greyson72f18852013-03-27 15:56:11 -0700524 if (data.className === 'core') {
525 d3.selectAll('.edge').classed('nodrop', true);
526 }
527 if (data.className === 'edge') {
528 d3.selectAll('.core').classed('nodrop', true);
529 d3.selectAll('.aggregation').classed('nodrop', true);
530 }
531 if (data.className === 'aggregation') {
532 d3.selectAll('.edge').classed('nodrop', true);
533 d3.selectAll('.aggregation').classed('nodrop', true);
534 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700535 }
536
Paul Greyson8d1c6362013-03-27 13:05:24 -0700537 function mouseUpSwitch(data) {
538 if (data.mouseDown) {
539 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700540 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700541 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700542 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700543 }
544 }
545
546 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700547 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700548 if (data.state == 'ACTIVE') {
549 var prompt = 'Deactivate ' + data.dpid + '?';
550 if (confirm(prompt)) {
551 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700552 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700553 }
554 } else {
555 var prompt = 'Activate ' + data.dpid + '?';
556 if (confirm(prompt)) {
557 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700558 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700559 }
560 }
561 }
562
Paul Greyson740bdaf2013-03-18 16:10:48 -0700563 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700564 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700565 return;
566 }
567
Paul Greysonc17278a2013-03-23 10:17:12 -0700568 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700569 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700570 .data(data, function (data) {
571 return data.dpid;
572 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700573 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700574 .attr("id", function (data, i) {
575 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700576 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700577 .attr("transform", function(data, i) {
578 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700579 });
580
Paul Greysonc17278a2013-03-23 10:17:12 -0700581 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700582 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700583 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700584 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700585 if (data.scale) {
586 m = m.scale(data.scale);
587 }
588 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700589 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700590 .attr("x", function (data) {
591 return -data.width / 2;
592 })
593 .attr("y", function (data) {
594 return -data.width / 2;
595 })
596 .attr("r", function (data) {
597 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700598 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700599
Paul Greysonc17278a2013-03-23 10:17:12 -0700600 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700601 nodes.on('mouseover', mouseOverSwitch);
602 nodes.on('mouseout', mouseOutSwitch);
603 nodes.on('mouseup', mouseUpSwitch);
604 nodes.on('mousedown', mouseDownSwitch);
605
606 // only do switch up/down for core switches
607 if (i == 2) {
608 nodes.on('dblclick', doubleClickSwitch);
609 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700610 }
611
Paul Greysonc17278a2013-03-23 10:17:12 -0700612 // append switches
613 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700614 .attr("class", "ring")
615 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700616
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700617
Paul Greysonc17278a2013-03-23 10:17:12 -0700618 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700619 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700620 .data(data, function (data) {
621 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700622 });
Paul Greyson347fb742013-03-27 13:40:29 -0700623 nodes.select('circle')
624 .each(function (data) {
625 // if there's a pending state changed and then the state changes, clear the pending class
626 var circle = d3.select(this);
627 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
628 data.state === 'INACTIVE' && circle.classed('active')) {
629 circle.classed('pending', false);
630 }
631 })
632 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700633 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700634 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700635 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700636 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700637 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700638 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700639 }
640
641 // update switches
642 rings.each(ringUpdate);
643
Paul Greyson968d1b42013-03-23 16:58:41 -0700644
645 // Now setup the labels
646 // This is done separately because SVG draws in node order and we want the labels
647 // always on top
648 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
649
Paul Greyson421bfcd2013-03-27 22:22:09 -0700650 d3.select(document.body).on('mousemove', function () {
651 if (!d3.select('#topology').classed('linking')) {
652 return;
653 }
654 var linkVector = document.getElementById('linkVector');
655 if (!linkVector) {
656 return;
657 }
658 linkVector = d3.select(linkVector);
659
660 var highlighted = svg.selectAll('.highlight')[0];
661 var s1 = null, s2 = null;
662 if (highlighted.length > 1) {
663 var s1 = d3.select(highlighted[0]);
664 var s2 = d3.select(highlighted[1]);
665
666 } else if (highlighted.length > 0) {
667 var s1 = d3.select(highlighted[0]);
668 }
669 var src = s1;
670 if (s2 && !s2.data()[0].target) {
671 src = s2;
672 }
673 if (src) {
674 linkVector.attr('d', function () {
675 var srcPt = document.querySelector('svg').createSVGPoint();
676 srcPt.x = src.attr('x');
677 srcPt.y = src.attr('y');
678 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
679
680 var svg = document.getElementById('topology');
681 var mouse = d3.mouse(viewbox);
682 var dstPt = document.querySelector('svg').createSVGPoint();
683 dstPt.x = mouse[0];
684 dstPt.y = mouse[1];
685 dstPt = dstPt.matrixTransform(viewbox.getCTM());
686
687 return line([srcPt, dstPt]);
688 });
689 }
690 });
691
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700692 d3.select(document.body).on('mouseup', function () {
693 function clearHighlight() {
694 svg.selectAll('circle').each(function (data) {
695 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700696 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700697 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700698 });
699 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700700 };
701
Paul Greyson72f18852013-03-27 15:56:11 -0700702 d3.selectAll('.nodrop').classed('nodrop', false);
703
Paul Greyson084779b2013-03-27 13:55:49 -0700704 function removeLink(link) {
705 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
706 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
707
708 if (path1) {
709 setPending(d3.select(path1));
710 }
711 if (path2) {
712 setPending(d3.select(path2));
713 }
714
715 linkDown(link);
716 }
717
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700718
Paul Greyson421bfcd2013-03-27 22:22:09 -0700719 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700720 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700721 var s1Data = highlighted[0].__data__;
722 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700723
724 var srcData, dstData;
725 if (s1Data.target) {
726 dstData = s1Data;
727 srcData = s2Data;
728 } else {
729 dstData = s2Data;
730 srcData = s1Data;
731 }
732
733 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
734 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
735 if (confirm(prompt)) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700736 addFlow(srcData, dstData);
737
738 var flow = {
739 dataPath: {
740 srcPort: {
741 dpid: {
742 value: srcData.dpid
743 }
744 },
745 dstPort: {
746 dpid: {
747 value: dstData.dpid
748 }
749 }
750 },
751 pending: true
752 };
753
754 selectFlow(flow);
755
756 setTimeout(function () {
757 deselectFlow(flow);
758 }, 10000);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700759 }
760 } else {
761 var map = linkMap[srcData.dpid];
762 if (map && map[dstData.dpid]) {
763 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
764 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700765 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700766 }
767 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700768 map = linkMap[dstData.dpid];
769 if (map && map[srcData.dpid]) {
770 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
771 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700772 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700773 }
774 } else {
775 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
776 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700777 var link1 = {
778 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700779 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700780 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700781 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700782 pending: true
783 };
784 pendingLinks[makeLinkKey(link1)] = link1;
785 var link2 = {
786 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700787 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700788 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700789 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700790 pending: true
791 };
792 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700793 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700794
Paul Greyson2913af82013-03-27 14:53:17 -0700795 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700796
Paul Greyson5cc35f02013-03-28 10:07:36 -0700797 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700798 setTimeout(function () {
799 delete pendingLinks[makeLinkKey(link1)];
800 delete pendingLinks[makeLinkKey(link2)];
801
Paul Greyson5cc35f02013-03-28 10:07:36 -0700802 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700803 }, 10000);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700804 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700805 }
806 }
807 }
808
809 clearHighlight();
810 } else {
811 clearHighlight();
812 }
813
814 });
815
Paul Greyson9066ab02013-03-23 18:15:41 -0700816 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700817 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700818 return;
819 }
820
821 // create the nodes
822 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700823 .data(data, function (data) {
824 return data.dpid;
825 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700826 .enter().append("svg:g")
827 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700828 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700829 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700830 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700831 .attr("transform", function(data, i) {
832 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700833 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700834
835 // add the text nodes which show on mouse over
836 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700837 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700838 .attr("x", function (data) {
839 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
840 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700841 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700842 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700843 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700844 }
845 } else {
846 if (data.className == 'edge') {
847 return data.width*3 + 4;
848 } else {
849 return data.width + 4;
850 }
851 }
852 })
853 .attr("y", function (data) {
854 var y;
855 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
856 if (data.className == 'edge') {
857 y = data.width*3/2 + 4;
858 } else {
859 y = data.width/2 + 4;
860 }
861 } else {
862 if (data.className == 'edge') {
863 y = data.width*3/2 + 4;
864 } else {
865 y = data.width/2 + 4;
866 }
867 }
868 return y - 6;
869 })
870 .attr("text-anchor", function (data) {
871 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
872 return "end";
873 } else {
874 return "start";
875 }
876 })
877 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700878 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
879 if (data.scale) {
880 m = m.scale(data.scale);
881 }
882 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
883 })
884 }
885
886 labelRings.enter().append("svg:g")
887 .attr("class", "textRing")
888 .each(labelRingEnter);
889
Paul Greysonc17278a2013-03-23 10:17:12 -0700890 // switches should not change during operation of the ui so no
891 // rings.exit()
892
893
Paul Greysond1a22d92013-03-19 12:15:19 -0700894 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -0700895
Paul Greysonc17278a2013-03-23 10:17:12 -0700896 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -0700897 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700898 return d['src-switch']+'->'+d['dst-switch'];
899 });
900
901 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700902 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -0700903 .attr("class", "link");
904
Paul Greyson084779b2013-03-27 13:55:49 -0700905 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700906 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -0700907 })
Paul Greyson56378ed2013-03-26 23:17:36 -0700908 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -0700909 var src = d3.select(document.getElementById(d['src-switch']));
910 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -0700911
Paul Greyson084779b2013-03-27 13:55:49 -0700912 var srcPt = document.querySelector('svg').createSVGPoint();
913 srcPt.x = src.attr('x');
914 srcPt.y = src.attr('y');
915 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700916
Paul Greyson084779b2013-03-27 13:55:49 -0700917 var dstPt = document.querySelector('svg').createSVGPoint();
918 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -0700919 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -0700920 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700921
Paul Greyson084779b2013-03-27 13:55:49 -0700922 var midPt = document.querySelector('svg').createSVGPoint();
923 midPt.x = (srcPt.x + dstPt.x)/2;
924 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -0700925
Paul Greyson084779b2013-03-27 13:55:49 -0700926 return line([srcPt, midPt, dstPt]);
927 })
Paul Greyson40c8a592013-03-27 14:10:33 -0700928 .attr("marker-mid", function(d) { return "url(#arrow)"; })
929 .classed('pending', function (d) {
930 return d.pending;
931 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700932
Paul Greyson56378ed2013-03-26 23:17:36 -0700933
Paul Greysonc17278a2013-03-23 10:17:12 -0700934 // remove old links
935 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700936}
937
Paul Greyson5cc35f02013-03-28 10:07:36 -0700938function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -0700939 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700940 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700941 .each(function (c) {
942 controllerColorMap[c] = colors.pop();
943 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
944 })
945 .text(function (d) {
946 return d;
Paul Greyson2913af82013-03-27 14:53:17 -0700947 })
948 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700949 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -0700950
Paul Greysone262a292013-03-23 10:35:23 -0700951 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -0700952 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -0700953 if (model.activeControllers.indexOf(d) != -1) {
954 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -0700955 }
Paul Greysonbcd3c772013-03-21 13:16:44 -0700956 var className = 'controller ' + color;
957 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -0700958 });
Paul Greysond1a22d92013-03-19 12:15:19 -0700959
Paul Greysone262a292013-03-23 10:35:23 -0700960 // this should never be needed
961 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700962
Paul Greyson2913af82013-03-27 14:53:17 -0700963 controllers.on('dblclick', function (c) {
964 if (model.activeControllers.indexOf(c) != -1) {
965 var prompt = 'Dectivate ' + c + '?';
966 if (confirm(prompt)) {
967 controllerDown(c);
968 setPending(d3.select(this));
969 };
970 } else {
971 var prompt = 'Activate ' + c + '?';
972 if (confirm(prompt)) {
973 controllerUp(c);
974 setPending(d3.select(this));
975 };
976 }
977 });
978
Paul Greyson8247c3f2013-03-28 00:24:02 -0700979 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -0700980 var allSelected = true;
981 for (var key in controllerColorMap) {
982 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
983 allSelected = false;
984 break;
985 }
986 }
987 if (allSelected) {
988 for (var key in controllerColorMap) {
989 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
990 }
991 } else {
992 for (var key in controllerColorMap) {
993 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
994 }
995 }
996
997 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
998 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -0700999 });
Paul Greyson8d1c6362013-03-27 13:05:24 -07001000
1001
Paul Greyson740bdaf2013-03-18 16:10:48 -07001002}
1003
Paul Greyson29aa98d2013-03-28 00:09:31 -07001004function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -07001005 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -07001006 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001007// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -07001008
Paul Greyson5cc35f02013-03-28 10:07:36 -07001009 var modelChanged = false;
Paul Greyson56378ed2013-03-26 23:17:36 -07001010 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
Paul Greyson5cc35f02013-03-28 10:07:36 -07001011 modelChanged = true;
1012 model = newModel;
Paul Greysonb48943b2013-03-19 13:27:57 -07001013 } else {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001014// console.log('no change');
Paul Greysonb48943b2013-03-19 13:27:57 -07001015 }
Paul Greysonb48943b2013-03-19 13:27:57 -07001016
Paul Greyson5cc35f02013-03-28 10:07:36 -07001017 if (modelChanged) {
1018 updateControllers();
1019 updateSelectedFlows();
1020 updateTopology();
1021 }
1022
1023 updateHeader(newModel);
Paul Greyson740bdaf2013-03-18 16:10:48 -07001024
1025 // do it again in 1s
1026 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -07001027 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -07001028 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -07001029 });
1030}
Paul Greyson740bdaf2013-03-18 16:10:48 -07001031
Paul Greyson38d8bde2013-03-22 22:07:35 -07001032svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -07001033updateSelectedFlows();
1034
1035d3.select('#showFlowChooser').on('click', function () {
1036 showFlowChooser();
1037});
1038
Paul Greyson72f18852013-03-27 15:56:11 -07001039
Paul Greyson38d8bde2013-03-22 22:07:35 -07001040// workaround for Chrome v25 bug
1041// if executed immediately, the view box transform logic doesn't work properly
1042// fixed in Chrome v27
1043setTimeout(function () {
1044 // workaround for another Chrome v25 bug
1045 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -07001046 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -07001047 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -07001048 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -07001049}, 100);