blob: 7ef1a49428d05a2360de8f38470442229b9898b8 [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
Paul Greyson97cc9f52013-04-04 01:45:40 -070023var pendingTimeout = 30000;
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 Greyson97cc9f52013-04-04 01:45:40 -070073 var topologyFlows = [];
74 selectedFlows.forEach(function (flow) {
75 if (flow) {
76 topologyFlows.push(flow);
77 }
78 });
79
80 var flows = d3.select('svg').selectAll('.flow').data(topologyFlows);
Paul Greyson127d7fb2013-03-25 23:39:20 -070081
Paul Greyson29aa98d2013-03-28 00:09:31 -070082 flows.enter().append("svg:path").attr('class', 'flow')
83 .attr('stroke-dasharray', '4, 10')
84 .append('svg:animate')
85 .attr('attributeName', 'stroke-dashoffset')
86 .attr('attributeType', 'xml')
87 .attr('from', '500')
88 .attr('to', '-500')
89 .attr('dur', '20s')
90 .attr('repeatCount', 'indefinite');
91
Paul Greyson97cc9f52013-04-04 01:45:40 -070092 flows.exit().remove();
Paul Greyson29aa98d2013-03-28 00:09:31 -070093
94 flows.attr('d', function (d) {
Paul Greyson13f02b92013-03-28 11:29:35 -070095 if (!d) {
96 return;
97 }
98 var pts = [];
Paul Greyson6f918402013-03-28 12:18:30 -070099 if (!d.dataPath.flowEntries) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700100 // create a temporary vector to indicate the pending flow
101 var s1 = d3.select(document.getElementById(d.dataPath.srcPort.dpid.value));
102 var s2 = d3.select(document.getElementById(d.dataPath.dstPort.dpid.value));
103
104 var pt1 = document.querySelector('svg').createSVGPoint();
105 pt1.x = s1.attr('x');
106 pt1.y = s1.attr('y');
107 pt1 = pt1.matrixTransform(s1[0][0].getCTM());
108 pts.push(pt1);
109
110 var pt2 = document.querySelector('svg').createSVGPoint();
111 pt2.x = s2.attr('x');
112 pt2.y = s2.attr('y');
113 pt2 = pt2.matrixTransform(s2[0][0].getCTM());
114 pts.push(pt2);
115
116 } else {
117 d.dataPath.flowEntries.forEach(function (flowEntry) {
118 var s = d3.select(document.getElementById(flowEntry.dpid.value));
119 // s[0] is null if the flow entry refers to a non-existent switch
120 if (s[0][0]) {
121 var pt = document.querySelector('svg').createSVGPoint();
122 pt.x = s.attr('x');
123 pt.y = s.attr('y');
124 pt = pt.matrixTransform(s[0][0].getCTM());
125 pts.push(pt);
126 } else {
127 console.log('flow refers to non-existent switch: ' + flowEntry.dpid.value);
128 }
129 });
130 }
131 return line(pts);
132 })
Paul Greyson71b550d2013-03-28 11:56:19 -0700133 .attr('id', function (d) {
134 if (d) {
135 return makeFlowKey(d);
136 }
137 })
Paul Greyson13f02b92013-03-28 11:29:35 -0700138 .classed('pending', function (d) {
Paul Greysonaa812562013-03-28 12:43:12 -0700139 return d && (d.createPending || d.deletePending);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700140 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700141
Paul Greyson56378ed2013-03-26 23:17:36 -0700142 // "marching ants"
Paul Greyson29aa98d2013-03-28 00:09:31 -0700143 flows.select('animate').attr('from', 500);
144
Paul Greyson13f02b92013-03-28 11:29:35 -0700145}
146
147function updateSelectedFlowsTable() {
148 function rowEnter(d) {
149 var row = d3.select(this);
Paul Greyson6f918402013-03-28 12:18:30 -0700150 row.append('div').classed('deleteFlow', true);
Paul Greyson13f02b92013-03-28 11:29:35 -0700151 row.append('div').classed('flowId', true);
152 row.append('div').classed('srcDPID', true);
153 row.append('div').classed('dstDPID', true);
154 row.append('div').classed('iperf', true);
Paul Greyson71b550d2013-03-28 11:56:19 -0700155
156 row.on('mouseover', function (d) {
157 if (d) {
158 var path = document.getElementById(makeFlowKey(d));
159 d3.select(path).classed('highlight', true);
160 }
161 });
162 row.on('mouseout', function (d) {
163 if (d) {
164 var path = document.getElementById(makeFlowKey(d));
165 d3.select(path).classed('highlight', false);
166 }
167 })
Paul Greyson13f02b92013-03-28 11:29:35 -0700168 }
169
170 function rowUpdate(d) {
171 var row = d3.select(this);
Paul Greyson6f918402013-03-28 12:18:30 -0700172 row.select('.deleteFlow').on('click', function () {
Paul Greysonbe8bf872013-04-04 01:53:45 -0700173 selectedFlows[selectedFlows.indexOf(d)] = null;
174 updateSelectedFlows();
175 });
176 row.on('dblclick', function () {
Paul Greyson6f918402013-03-28 12:18:30 -0700177 if (d) {
178 var prompt = 'Delete flow ' + d.flowId.value + '?';
179 if (confirm(prompt)) {
180 deleteFlow(d);
Paul Greysonaa812562013-03-28 12:43:12 -0700181 d.deletePending = true;
Paul Greyson6f918402013-03-28 12:18:30 -0700182 updateSelectedFlows();
183
184 setTimeout(function () {
Paul Greysonaa812562013-03-28 12:43:12 -0700185 d.deletePending = false;
Paul Greyson6f918402013-03-28 12:18:30 -0700186 updateSelectedFlows();
187 }, pendingTimeout)
188 };
189 }
190 });
191
Paul Greyson13f02b92013-03-28 11:29:35 -0700192 row.select('.flowId')
193 .text(function (d) {
194 if (d) {
195 if (d.flowId) {
196 return d.flowId.value;
197 } else {
198 return '0x--';
199 }
200 }
201 })
Paul Greysonaa812562013-03-28 12:43:12 -0700202 .classed('pending', d && (d.deletePending || d.createPending));
Paul Greyson13f02b92013-03-28 11:29:35 -0700203
204 row.select('.srcDPID')
205 .text(function (d) {
206 if (d) {
207 return d.dataPath.srcPort.dpid.value;
208 }
209 });
210
211 row.select('.dstDPID')
212 .text(function (d) {
213 if (d) {
214 return d.dataPath.dstPort.dpid.value;
215 }
216 });
217 }
218
219 var flows = d3.select('#selectedFlows')
220 .selectAll('.selectedFlow')
221 .data(selectedFlows);
222
223 flows.enter()
224 .append('div')
225 .classed('selectedFlow', true)
226 .each(rowEnter);
227
228 flows.each(rowUpdate);
229
Paul Greyson29aa98d2013-03-28 00:09:31 -0700230 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700231}
232
Paul Greyson13f02b92013-03-28 11:29:35 -0700233function updateSelectedFlows() {
234 // make sure that all of the selected flows are either
235 // 1) valid (meaning they are in the latest list of flows)
236 // 2) pending
237 if (model) {
238 var flowMap = {};
239 model.flows.forEach(function (flow) {
240 flowMap[makeFlowKey(flow)] = flow;
241 });
242
243 var newSelectedFlows = [];
244 selectedFlows.forEach(function (flow) {
245 if (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700246 var liveFlow = flowMap[makeFlowKey(flow)];
247 if (liveFlow) {
248 newSelectedFlows.push(liveFlow);
Paul Greysonaa812562013-03-28 12:43:12 -0700249 liveFlow.deletePending = flow.deletePending;
250 } else if (flow.createPending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700251 newSelectedFlows.push(flow);
Paul Greyson13f02b92013-03-28 11:29:35 -0700252 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700253 }
254 });
255 selectedFlows = newSelectedFlows;
256 }
Paul Greyson6f918402013-03-28 12:18:30 -0700257 while (selectedFlows.length < 3) {
258 selectedFlows.push(null);
259 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700260
261 updateSelectedFlowsTable();
262 updateSelectedFlowsTopology();
263}
264
265function selectFlow(flow) {
Paul Greysonc30f75e2013-03-28 11:45:15 -0700266 var flowKey = makeFlowKey(flow);
267 var alreadySelected = false;
268 selectedFlows.forEach(function (f) {
269 if (f && makeFlowKey(f) === flowKey) {
270 alreadySelected = true;
271 }
272 });
273
274 if (!alreadySelected) {
275 selectedFlows.unshift(flow);
276 selectedFlows = selectedFlows.slice(0, 3);
277 updateSelectedFlows();
278 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700279}
280
Paul Greysonaa812562013-03-28 12:43:12 -0700281function deselectFlow(flow, ifCreatePending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700282 var flowKey = makeFlowKey(flow);
283 var newSelectedFlows = [];
284 selectedFlows.forEach(function (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700285 if (!flow ||
286 flowKey !== makeFlowKey(flow) ||
Paul Greysonaa812562013-03-28 12:43:12 -0700287 flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700288 newSelectedFlows.push(flow);
289 }
290 });
291 selectedFlows = newSelectedFlows;
292 while (selectedFlows.length < 3) {
293 selectedFlows.push(null);
294 }
295
296 updateSelectedFlows();
297}
298
Paul Greysonaa812562013-03-28 12:43:12 -0700299function deselectFlowIfCreatePending(flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700300 deselectFlow(flow, true);
301}
302
Paul Greyson29aa98d2013-03-28 00:09:31 -0700303function showFlowChooser() {
304 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700305 var row = d3.select(this);
306
Paul Greyson127d7fb2013-03-25 23:39:20 -0700307 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700308 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700309 on('click', function () {
Paul Greyson13f02b92013-03-28 11:29:35 -0700310 selectFlow(d);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700311 });
312
313 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700314 .classed('flowId', true)
315 .text(function (d) {
316 return d.flowId.value;
317 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700318
319 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700320 .classed('srcDPID', true)
321 .text(function (d) {
322 return d.dataPath.srcPort.dpid.value;
323 });
324
Paul Greyson127d7fb2013-03-25 23:39:20 -0700325
326 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700327 .classed('dstDPID', true)
328 .text(function (d) {
329 return d.dataPath.dstPort.dpid.value;
330 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700331
Paul Greyson127d7fb2013-03-25 23:39:20 -0700332 }
333
Paul Greyson29aa98d2013-03-28 00:09:31 -0700334 var flows = d3.select('#flowChooser')
335 .append('div')
336 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700337 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700338 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700339 .enter()
340 .append('div')
341 .classed('selectedFlow', true)
342 .each(rowEnter);
343
Paul Greyson29aa98d2013-03-28 00:09:31 -0700344 setTimeout(function () {
345 d3.select(document.body).on('click', function () {
346 d3.select('#flowChooser').html('');
347 d3.select(document.body).on('click', null);
348 });
349 }, 0);
350}
351
Paul Greyson29aa98d2013-03-28 00:09:31 -0700352
Paul Greyson127d7fb2013-03-25 23:39:20 -0700353
Paul Greysond1a22d92013-03-19 12:15:19 -0700354function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700355 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700356 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
357 d3.select('#activeFlows').text(model.flows.length);
358}
359
360function toRadians (angle) {
361 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700362}
363
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700364var widths = {
365 edge: 6,
366 aggregation: 12,
367 core: 18
368}
369
Paul Greysonc17278a2013-03-23 10:17:12 -0700370function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700371 var rings = [{
372 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700373 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700374 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700375 className: 'edge',
376 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700377 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700378 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700379 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700380 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700381 className: 'aggregation',
382 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700383 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700384 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700385 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700386 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700387 className: 'core',
388 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700389 }];
390
Paul Greysonde7fad52013-03-19 12:47:32 -0700391
392 var aggRanges = {};
393
394 // arrange edge switches at equal increments
395 var k = 360 / rings[0].switches.length;
396 rings[0].switches.forEach(function (s, i) {
397 var angle = k * i;
398
399 rings[0].angles[i] = angle;
400
401 // record the angle for the agg switch layout
402 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700403 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700404 var aggdpid = dpid.join(':');
405 var aggRange = aggRanges[aggdpid];
406 if (!aggRange) {
407 aggRange = aggRanges[aggdpid] = {};
408 aggRange.min = aggRange.max = angle;
409 } else {
410 aggRange.max = angle;
411 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700412 });
413
414 // arrange aggregation switches to "fan out" to edge switches
415 k = 360 / rings[1].switches.length;
416 rings[1].switches.forEach(function (s, i) {
417// rings[1].angles[i] = k * i;
418 var range = aggRanges[s.dpid];
419
Paul Greyson832d2202013-03-21 13:27:56 -0700420 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700421 });
422
Paul Greyson3f890b62013-03-22 17:39:36 -0700423 // find the association between core switches and aggregation switches
424 var aggregationSwitchMap = {};
425 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700426 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700427 });
428
Paul Greyson3f890b62013-03-22 17:39:36 -0700429 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700430 k = 360 / rings[2].switches.length;
431 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700432// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700433 var associatedAggregationSwitches = model.configuration.association[s.dpid];
434 // TODO: go between if there are multiple
435 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
436
437 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700438 });
439
Paul Greyson644d92a2013-03-23 18:00:40 -0700440 // TODO: construct this form initially rather than converting. it works better because
441 // it allows binding by dpid
442 var testRings = [];
443 rings.forEach(function (ring) {
444 var testRing = [];
445 ring.switches.forEach(function (s, i) {
446 var testSwitch = {
447 dpid: s.dpid,
448 state: s.state,
449 radius: ring.radius,
450 width: ring.width,
451 className: ring.className,
452 angle: ring.angles[i],
453 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700454 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700455 testRing.push(testSwitch);
456 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700457
458
Paul Greyson644d92a2013-03-23 18:00:40 -0700459 testRings.push(testRing);
460 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700461
462
Paul Greyson644d92a2013-03-23 18:00:40 -0700463// return rings;
464 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700465}
466
Paul Greyson40c8a592013-03-27 14:10:33 -0700467function makeLinkKey(link) {
468 return link['src-switch'] + '=>' + link['dst-switch'];
469}
470
Paul Greyson13f02b92013-03-28 11:29:35 -0700471function makeFlowKey(flow) {
472 return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
473}
474
Paul Greyson40c8a592013-03-27 14:10:33 -0700475function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700476 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700477 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700478 var srcDPID = link['src-switch'];
479 var dstDPID = link['dst-switch'];
480
481 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700482
Paul Greyson8d1c6362013-03-27 13:05:24 -0700483 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700484
485 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700486 });
487 return linkMap;
488}
489
Paul Greyson5cc35f02013-03-28 10:07:36 -0700490// removes links from the pending list that are now in the model
491function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700492 var links = [];
493 model.links.forEach(function (link) {
494 links.push(link);
495 delete pendingLinks[makeLinkKey(link)]
496 })
497 var linkId;
498 for (linkId in pendingLinks) {
499 links.push(pendingLinks[linkId]);
500 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700501 return links
502}
Paul Greyson40c8a592013-03-27 14:10:33 -0700503
Paul Greyson5cc35f02013-03-28 10:07:36 -0700504updateTopology = function() {
505
506 // DRAW THE SWITCHES
507 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
508
509 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700510 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700511
Paul Greyson8d1c6362013-03-27 13:05:24 -0700512 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700513
514 d3.event.preventDefault();
515
Paul Greyson5cc35f02013-03-28 10:07:36 -0700516 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
517
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700518 if (data.highlighted) {
519 return;
520 }
521
522 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700523 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700524 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700525 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700526 // only allow links
527 // edge->edge (flow)
528 // aggregation->core
529 // core->core
530 if (data.className == 'edge' && !s.classed('edge') ||
531 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
532 data.className == 'aggregation' && !s.classed('core')) {
533 return;
534 }
535
536 // don't highlight if there's already a link or flow
537 // var map = linkMap[data.dpid];
538 // console.log(map);
539 // console.log(s.data()[0].dpid);
540 // console.log(map[s.data()[0].dpid]);
541 // if (map && map[s.data()[0].dpid]) {
542 // return;
543 // }
544
545 // the second highlighted switch is the target for a link or flow
546 data.target = true;
547 }
548
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700549 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700550 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700551 data.highlighted = true;
552 node.moveToFront();
553 }
554
Paul Greyson8d1c6362013-03-27 13:05:24 -0700555 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700556 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
557
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700558 if (data.mouseDown)
559 return;
560
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700561 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700562 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700563 data.highlighted = false;
564 data.target = false;
565 }
566
Paul Greyson8d1c6362013-03-27 13:05:24 -0700567 function mouseDownSwitch(data) {
568 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700569 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700570 d3.select('#topology').classed('linking', true);
571
Paul Greyson421bfcd2013-03-27 22:22:09 -0700572 d3.select('svg')
573 .append('svg:path')
574 .attr('id', 'linkVector')
575 .attr('d', function () {
576 var s = d3.select(document.getElementById(data.dpid));
577
578 var pt = document.querySelector('svg').createSVGPoint();
579 pt.x = s.attr('x');
580 pt.y = s.attr('y');
581 pt = pt.matrixTransform(s[0][0].getCTM());
582
583 return line([pt, pt]);
584 });
585
586
Paul Greyson72f18852013-03-27 15:56:11 -0700587 if (data.className === 'core') {
588 d3.selectAll('.edge').classed('nodrop', true);
589 }
590 if (data.className === 'edge') {
591 d3.selectAll('.core').classed('nodrop', true);
592 d3.selectAll('.aggregation').classed('nodrop', true);
593 }
594 if (data.className === 'aggregation') {
595 d3.selectAll('.edge').classed('nodrop', true);
596 d3.selectAll('.aggregation').classed('nodrop', true);
597 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700598 }
599
Paul Greyson8d1c6362013-03-27 13:05:24 -0700600 function mouseUpSwitch(data) {
601 if (data.mouseDown) {
602 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700603 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700604 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700605 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700606 }
607 }
608
609 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700610 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700611 if (data.state == 'ACTIVE') {
612 var prompt = 'Deactivate ' + data.dpid + '?';
613 if (confirm(prompt)) {
614 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700615 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700616 }
617 } else {
618 var prompt = 'Activate ' + data.dpid + '?';
619 if (confirm(prompt)) {
620 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700621 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700622 }
623 }
624 }
625
Paul Greyson740bdaf2013-03-18 16:10:48 -0700626 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700627 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700628 return;
629 }
630
Paul Greysonc17278a2013-03-23 10:17:12 -0700631 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700632 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700633 .data(data, function (data) {
634 return data.dpid;
635 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700636 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700637 .attr("id", function (data, i) {
638 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700639 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700640 .attr("transform", function(data, i) {
641 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700642 });
643
Paul Greysonc17278a2013-03-23 10:17:12 -0700644 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700645 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700646 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700647 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700648 if (data.scale) {
649 m = m.scale(data.scale);
650 }
651 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700652 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700653 .attr("x", function (data) {
654 return -data.width / 2;
655 })
656 .attr("y", function (data) {
657 return -data.width / 2;
658 })
659 .attr("r", function (data) {
660 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700661 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700662
Paul Greysonc17278a2013-03-23 10:17:12 -0700663 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700664 nodes.on('mouseover', mouseOverSwitch);
665 nodes.on('mouseout', mouseOutSwitch);
666 nodes.on('mouseup', mouseUpSwitch);
667 nodes.on('mousedown', mouseDownSwitch);
668
669 // only do switch up/down for core switches
670 if (i == 2) {
671 nodes.on('dblclick', doubleClickSwitch);
672 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700673 }
674
Paul Greysonc17278a2013-03-23 10:17:12 -0700675 // append switches
676 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700677 .attr("class", "ring")
678 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700679
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700680
Paul Greysonc17278a2013-03-23 10:17:12 -0700681 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700682 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700683 .data(data, function (data) {
684 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700685 });
Paul Greyson347fb742013-03-27 13:40:29 -0700686 nodes.select('circle')
687 .each(function (data) {
688 // if there's a pending state changed and then the state changes, clear the pending class
689 var circle = d3.select(this);
690 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
691 data.state === 'INACTIVE' && circle.classed('active')) {
692 circle.classed('pending', false);
693 }
694 })
695 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700696 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700697 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700698 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700699 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700700 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700701 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700702 }
703
704 // update switches
705 rings.each(ringUpdate);
706
Paul Greyson968d1b42013-03-23 16:58:41 -0700707
708 // Now setup the labels
709 // This is done separately because SVG draws in node order and we want the labels
710 // always on top
711 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
712
Paul Greyson421bfcd2013-03-27 22:22:09 -0700713 d3.select(document.body).on('mousemove', function () {
714 if (!d3.select('#topology').classed('linking')) {
715 return;
716 }
717 var linkVector = document.getElementById('linkVector');
718 if (!linkVector) {
719 return;
720 }
721 linkVector = d3.select(linkVector);
722
723 var highlighted = svg.selectAll('.highlight')[0];
724 var s1 = null, s2 = null;
725 if (highlighted.length > 1) {
726 var s1 = d3.select(highlighted[0]);
727 var s2 = d3.select(highlighted[1]);
728
729 } else if (highlighted.length > 0) {
730 var s1 = d3.select(highlighted[0]);
731 }
732 var src = s1;
733 if (s2 && !s2.data()[0].target) {
734 src = s2;
735 }
736 if (src) {
737 linkVector.attr('d', function () {
738 var srcPt = document.querySelector('svg').createSVGPoint();
739 srcPt.x = src.attr('x');
740 srcPt.y = src.attr('y');
741 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
742
743 var svg = document.getElementById('topology');
744 var mouse = d3.mouse(viewbox);
745 var dstPt = document.querySelector('svg').createSVGPoint();
746 dstPt.x = mouse[0];
747 dstPt.y = mouse[1];
748 dstPt = dstPt.matrixTransform(viewbox.getCTM());
749
750 return line([srcPt, dstPt]);
751 });
752 }
753 });
754
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700755 d3.select(document.body).on('mouseup', function () {
756 function clearHighlight() {
757 svg.selectAll('circle').each(function (data) {
758 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700759 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700760 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700761 });
762 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700763 };
764
Paul Greyson72f18852013-03-27 15:56:11 -0700765 d3.selectAll('.nodrop').classed('nodrop', false);
766
Paul Greyson084779b2013-03-27 13:55:49 -0700767 function removeLink(link) {
768 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
769 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
770
771 if (path1) {
772 setPending(d3.select(path1));
773 }
774 if (path2) {
775 setPending(d3.select(path2));
776 }
777
778 linkDown(link);
779 }
780
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700781
Paul Greyson421bfcd2013-03-27 22:22:09 -0700782 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700783 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700784 var s1Data = highlighted[0].__data__;
785 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700786
787 var srcData, dstData;
788 if (s1Data.target) {
789 dstData = s1Data;
790 srcData = s2Data;
791 } else {
792 dstData = s2Data;
793 srcData = s1Data;
794 }
795
796 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
797 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
798 if (confirm(prompt)) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700799 addFlow(srcData, dstData);
800
801 var flow = {
802 dataPath: {
803 srcPort: {
804 dpid: {
805 value: srcData.dpid
806 }
807 },
808 dstPort: {
809 dpid: {
810 value: dstData.dpid
811 }
812 }
813 },
Paul Greysonaa812562013-03-28 12:43:12 -0700814 createPending: true
Paul Greyson13f02b92013-03-28 11:29:35 -0700815 };
816
817 selectFlow(flow);
818
819 setTimeout(function () {
Paul Greysonaa812562013-03-28 12:43:12 -0700820 deselectFlowIfCreatePending(flow);
Paul Greyson6f918402013-03-28 12:18:30 -0700821 }, pendingTimeout);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700822 }
823 } else {
824 var map = linkMap[srcData.dpid];
825 if (map && map[dstData.dpid]) {
826 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
827 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700828 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700829 }
830 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700831 map = linkMap[dstData.dpid];
832 if (map && map[srcData.dpid]) {
833 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
834 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700835 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700836 }
837 } else {
838 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
839 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700840 var link1 = {
841 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700842 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700843 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700844 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700845 pending: true
846 };
847 pendingLinks[makeLinkKey(link1)] = link1;
848 var link2 = {
849 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700850 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700851 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700852 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700853 pending: true
854 };
855 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700856 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700857
Paul Greyson2913af82013-03-27 14:53:17 -0700858 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700859
Paul Greyson5cc35f02013-03-28 10:07:36 -0700860 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700861 setTimeout(function () {
862 delete pendingLinks[makeLinkKey(link1)];
863 delete pendingLinks[makeLinkKey(link2)];
864
Paul Greyson5cc35f02013-03-28 10:07:36 -0700865 updateTopology();
Paul Greyson6f918402013-03-28 12:18:30 -0700866 }, pendingTimeout);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700867 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700868 }
869 }
870 }
871
872 clearHighlight();
873 } else {
874 clearHighlight();
875 }
876
877 });
878
Paul Greyson9066ab02013-03-23 18:15:41 -0700879 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700880 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700881 return;
882 }
883
884 // create the nodes
885 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700886 .data(data, function (data) {
887 return data.dpid;
888 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700889 .enter().append("svg:g")
890 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700891 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700892 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700893 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700894 .attr("transform", function(data, i) {
895 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700896 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700897
898 // add the text nodes which show on mouse over
899 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700900 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700901 .attr("x", function (data) {
902 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
903 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700904 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700905 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700906 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700907 }
908 } else {
909 if (data.className == 'edge') {
910 return data.width*3 + 4;
911 } else {
912 return data.width + 4;
913 }
914 }
915 })
916 .attr("y", function (data) {
917 var y;
918 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
919 if (data.className == 'edge') {
920 y = data.width*3/2 + 4;
921 } else {
922 y = data.width/2 + 4;
923 }
924 } else {
925 if (data.className == 'edge') {
926 y = data.width*3/2 + 4;
927 } else {
928 y = data.width/2 + 4;
929 }
930 }
931 return y - 6;
932 })
933 .attr("text-anchor", function (data) {
934 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
935 return "end";
936 } else {
937 return "start";
938 }
939 })
940 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700941 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
942 if (data.scale) {
943 m = m.scale(data.scale);
944 }
945 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
946 })
947 }
948
949 labelRings.enter().append("svg:g")
950 .attr("class", "textRing")
951 .each(labelRingEnter);
952
Paul Greysonc17278a2013-03-23 10:17:12 -0700953 // switches should not change during operation of the ui so no
954 // rings.exit()
955
956
Paul Greysond1a22d92013-03-19 12:15:19 -0700957 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -0700958
Paul Greysonc17278a2013-03-23 10:17:12 -0700959 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -0700960 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700961 return d['src-switch']+'->'+d['dst-switch'];
962 });
963
964 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700965 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -0700966 .attr("class", "link");
967
Paul Greyson084779b2013-03-27 13:55:49 -0700968 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700969 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -0700970 })
Paul Greyson56378ed2013-03-26 23:17:36 -0700971 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -0700972 var src = d3.select(document.getElementById(d['src-switch']));
973 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -0700974
Paul Greyson084779b2013-03-27 13:55:49 -0700975 var srcPt = document.querySelector('svg').createSVGPoint();
976 srcPt.x = src.attr('x');
977 srcPt.y = src.attr('y');
978 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700979
Paul Greyson084779b2013-03-27 13:55:49 -0700980 var dstPt = document.querySelector('svg').createSVGPoint();
981 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -0700982 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -0700983 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700984
Paul Greyson084779b2013-03-27 13:55:49 -0700985 var midPt = document.querySelector('svg').createSVGPoint();
986 midPt.x = (srcPt.x + dstPt.x)/2;
987 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -0700988
Paul Greyson084779b2013-03-27 13:55:49 -0700989 return line([srcPt, midPt, dstPt]);
990 })
Paul Greyson40c8a592013-03-27 14:10:33 -0700991 .attr("marker-mid", function(d) { return "url(#arrow)"; })
992 .classed('pending', function (d) {
993 return d.pending;
994 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700995
Paul Greyson56378ed2013-03-26 23:17:36 -0700996
Paul Greysonc17278a2013-03-23 10:17:12 -0700997 // remove old links
998 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700999}
1000
Paul Greyson5cc35f02013-03-28 10:07:36 -07001001function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -07001002 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -07001003 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -07001004 .each(function (c) {
1005 controllerColorMap[c] = colors.pop();
1006 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
1007 })
1008 .text(function (d) {
1009 return d;
Paul Greyson2913af82013-03-27 14:53:17 -07001010 })
1011 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -07001012 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -07001013
Paul Greysone262a292013-03-23 10:35:23 -07001014 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -07001015 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -07001016 if (model.activeControllers.indexOf(d) != -1) {
1017 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -07001018 }
Paul Greysonbcd3c772013-03-21 13:16:44 -07001019 var className = 'controller ' + color;
1020 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -07001021 });
Paul Greysond1a22d92013-03-19 12:15:19 -07001022
Paul Greysone262a292013-03-23 10:35:23 -07001023 // this should never be needed
1024 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -07001025
Paul Greyson2913af82013-03-27 14:53:17 -07001026 controllers.on('dblclick', function (c) {
1027 if (model.activeControllers.indexOf(c) != -1) {
1028 var prompt = 'Dectivate ' + c + '?';
1029 if (confirm(prompt)) {
1030 controllerDown(c);
1031 setPending(d3.select(this));
1032 };
1033 } else {
1034 var prompt = 'Activate ' + c + '?';
1035 if (confirm(prompt)) {
1036 controllerUp(c);
1037 setPending(d3.select(this));
1038 };
1039 }
1040 });
1041
Paul Greyson8247c3f2013-03-28 00:24:02 -07001042 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -07001043 var allSelected = true;
1044 for (var key in controllerColorMap) {
1045 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
1046 allSelected = false;
1047 break;
1048 }
1049 }
1050 if (allSelected) {
1051 for (var key in controllerColorMap) {
1052 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
1053 }
1054 } else {
1055 for (var key in controllerColorMap) {
1056 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
1057 }
1058 }
1059
1060 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
1061 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -07001062 });
Paul Greyson8d1c6362013-03-27 13:05:24 -07001063
1064
Paul Greyson740bdaf2013-03-18 16:10:48 -07001065}
1066
Paul Greyson29aa98d2013-03-28 00:09:31 -07001067function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -07001068 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -07001069 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001070// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -07001071
Paul Greysone5991b52013-04-04 01:34:04 -07001072 if (newModel) {
1073 var modelChanged = false;
1074 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
1075 modelChanged = true;
1076 model = newModel;
1077 } else {
1078 // console.log('no change');
1079 }
Paul Greysonb48943b2013-03-19 13:27:57 -07001080
Paul Greysone5991b52013-04-04 01:34:04 -07001081 if (modelChanged) {
1082 updateControllers();
1083 updateSelectedFlows();
1084 updateTopology();
1085 }
Paul Greyson5cc35f02013-03-28 10:07:36 -07001086
Paul Greysone5991b52013-04-04 01:34:04 -07001087 updateHeader(newModel);
1088 }
Paul Greyson740bdaf2013-03-18 16:10:48 -07001089
1090 // do it again in 1s
1091 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -07001092 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -07001093 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -07001094 });
1095}
Paul Greyson740bdaf2013-03-18 16:10:48 -07001096
Paul Greyson38d8bde2013-03-22 22:07:35 -07001097svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -07001098updateSelectedFlows();
1099
1100d3.select('#showFlowChooser').on('click', function () {
1101 showFlowChooser();
1102});
1103
Paul Greyson72f18852013-03-27 15:56:11 -07001104
Paul Greyson38d8bde2013-03-22 22:07:35 -07001105// workaround for Chrome v25 bug
1106// if executed immediately, the view box transform logic doesn't work properly
1107// fixed in Chrome v27
1108setTimeout(function () {
1109 // workaround for another Chrome v25 bug
1110 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -07001111 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -07001112 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -07001113 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -07001114}, 100);