blob: 0d0614889b646c599377811a8b98043b9d95fa2c [file] [log] [blame]
Paul Greyson127d7fb2013-03-25 23:39:20 -07001/*global d3, document∆*/
Paul Greyson740bdaf2013-03-18 16:10:48 -07002
Paul Greyson6d9ed862013-03-23 17:37:15 -07003d3.selection.prototype.moveToFront = function() {
4 return this.each(function(){
5 this.parentNode.appendChild(this);
6 });
7};
Paul Greysond1a22d92013-03-19 12:15:19 -07008
Paul Greyson127d7fb2013-03-25 23:39:20 -07009var line = d3.svg.line()
10 .x(function(d) {
11 return d.x;
12 })
13 .y(function(d) {
14 return d.y;
15 });
16
Paul Greyson56378ed2013-03-26 23:17:36 -070017var model;
Paul Greyson29aa98d2013-03-28 00:09:31 -070018var svg;
Paul Greyson56378ed2013-03-26 23:17:36 -070019var updateTopology;
Paul Greyson40c8a592013-03-27 14:10:33 -070020var pendingLinks = {};
Paul Greyson6f918402013-03-28 12:18:30 -070021var selectedFlows = [];
22
23var pendingTimeout = 10000;
Paul Greyson127d7fb2013-03-25 23:39:20 -070024
Paul Greysond1a22d92013-03-19 12:15:19 -070025var colors = [
Paul Greyson3e142162013-03-19 13:56:17 -070026 'color1',
27 'color2',
28 'color3',
29 'color4',
Paul Greyson3e142162013-03-19 13:56:17 -070030 'color7',
31 'color8',
32 'color9',
Paul Greyson5cc35f02013-03-28 10:07:36 -070033// 'color11',
Paul Greyson127d7fb2013-03-25 23:39:20 -070034 'color12'
35];
Paul Greyson01a5dff2013-03-19 15:50:14 -070036colors.reverse();
Paul Greysond1a22d92013-03-19 12:15:19 -070037
38var controllerColorMap = {};
39
Paul Greyson084779b2013-03-27 13:55:49 -070040function setPending(selection) {
41 selection.classed('pending', false);
42 setTimeout(function () {
43 selection.classed('pending', true);
44 })
45}
Paul Greysond1a22d92013-03-19 12:15:19 -070046
Paul Greyson740bdaf2013-03-18 16:10:48 -070047function createTopologyView() {
Paul Greyson56378ed2013-03-26 23:17:36 -070048
49 window.addEventListener('resize', function () {
50 // this is too slow. instead detect first resize event and hide the paths that have explicit matrix applied
51 // either that or is it possible to position the paths so they get the automatic transform as well?
Paul Greyson5cc35f02013-03-28 10:07:36 -070052// updateTopology();
Paul Greyson56378ed2013-03-26 23:17:36 -070053 });
54
Paul Greysonb367de22013-03-23 11:09:11 -070055 var svg = d3.select('#svg-container').append('svg:svg');
56
57 svg.append("svg:defs").append("svg:marker")
58 .attr("id", "arrow")
59 .attr("viewBox", "0 -5 10 10")
60 .attr("refX", -1)
61 .attr("markerWidth", 5)
62 .attr("markerHeight", 5)
63 .attr("orient", "auto")
64 .append("svg:path")
Paul Greyson45303ac2013-03-23 16:44:01 -070065 .attr("d", "M0,-3L10,0L0,3");
Paul Greysonb367de22013-03-23 11:09:11 -070066
67 return svg.append('svg:svg').attr('id', 'viewBox').attr('viewBox', '0 0 1000 1000').attr('preserveAspectRatio', 'none').
Paul Greyson952ccb62013-03-18 22:22:08 -070068 attr('id', 'viewbox').append('svg:g').attr('transform', 'translate(500 500)');
Paul Greyson740bdaf2013-03-18 16:10:48 -070069}
70
Paul Greyson13f02b92013-03-28 11:29:35 -070071function updateSelectedFlowsTopology() {
Paul Greyson127d7fb2013-03-25 23:39:20 -070072 // DRAW THE FLOWS
Paul Greyson13f02b92013-03-28 11:29:35 -070073 var flows = d3.select('svg').selectAll('.flow').data(selectedFlows);
Paul Greyson127d7fb2013-03-25 23:39:20 -070074
Paul Greyson29aa98d2013-03-28 00:09:31 -070075 flows.enter().append("svg:path").attr('class', 'flow')
76 .attr('stroke-dasharray', '4, 10')
77 .append('svg:animate')
78 .attr('attributeName', 'stroke-dashoffset')
79 .attr('attributeType', 'xml')
80 .attr('from', '500')
81 .attr('to', '-500')
82 .attr('dur', '20s')
83 .attr('repeatCount', 'indefinite');
84
85
86 flows.attr('d', function (d) {
Paul Greyson13f02b92013-03-28 11:29:35 -070087 if (!d) {
88 return;
89 }
90 var pts = [];
Paul Greyson6f918402013-03-28 12:18:30 -070091 if (!d.dataPath.flowEntries) {
Paul Greyson13f02b92013-03-28 11:29:35 -070092 // create a temporary vector to indicate the pending flow
93 var s1 = d3.select(document.getElementById(d.dataPath.srcPort.dpid.value));
94 var s2 = d3.select(document.getElementById(d.dataPath.dstPort.dpid.value));
95
96 var pt1 = document.querySelector('svg').createSVGPoint();
97 pt1.x = s1.attr('x');
98 pt1.y = s1.attr('y');
99 pt1 = pt1.matrixTransform(s1[0][0].getCTM());
100 pts.push(pt1);
101
102 var pt2 = document.querySelector('svg').createSVGPoint();
103 pt2.x = s2.attr('x');
104 pt2.y = s2.attr('y');
105 pt2 = pt2.matrixTransform(s2[0][0].getCTM());
106 pts.push(pt2);
107
108 } else {
109 d.dataPath.flowEntries.forEach(function (flowEntry) {
110 var s = d3.select(document.getElementById(flowEntry.dpid.value));
111 // s[0] is null if the flow entry refers to a non-existent switch
112 if (s[0][0]) {
113 var pt = document.querySelector('svg').createSVGPoint();
114 pt.x = s.attr('x');
115 pt.y = s.attr('y');
116 pt = pt.matrixTransform(s[0][0].getCTM());
117 pts.push(pt);
118 } else {
119 console.log('flow refers to non-existent switch: ' + flowEntry.dpid.value);
120 }
121 });
122 }
123 return line(pts);
124 })
Paul Greyson71b550d2013-03-28 11:56:19 -0700125 .attr('id', function (d) {
126 if (d) {
127 return makeFlowKey(d);
128 }
129 })
Paul Greyson13f02b92013-03-28 11:29:35 -0700130 .classed('pending', function (d) {
Paul Greysonaa812562013-03-28 12:43:12 -0700131 return d && (d.createPending || d.deletePending);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700132 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700133
Paul Greyson56378ed2013-03-26 23:17:36 -0700134 // "marching ants"
Paul Greyson29aa98d2013-03-28 00:09:31 -0700135 flows.select('animate').attr('from', 500);
136
Paul Greyson13f02b92013-03-28 11:29:35 -0700137}
138
139function updateSelectedFlowsTable() {
140 function rowEnter(d) {
141 var row = d3.select(this);
Paul Greyson6f918402013-03-28 12:18:30 -0700142 row.append('div').classed('deleteFlow', true);
Paul Greyson13f02b92013-03-28 11:29:35 -0700143 row.append('div').classed('flowId', true);
144 row.append('div').classed('srcDPID', true);
145 row.append('div').classed('dstDPID', true);
146 row.append('div').classed('iperf', true);
Paul Greyson71b550d2013-03-28 11:56:19 -0700147
148 row.on('mouseover', function (d) {
149 if (d) {
150 var path = document.getElementById(makeFlowKey(d));
151 d3.select(path).classed('highlight', true);
152 }
153 });
154 row.on('mouseout', function (d) {
155 if (d) {
156 var path = document.getElementById(makeFlowKey(d));
157 d3.select(path).classed('highlight', false);
158 }
159 })
Paul Greyson13f02b92013-03-28 11:29:35 -0700160 }
161
162 function rowUpdate(d) {
163 var row = d3.select(this);
Paul Greyson6f918402013-03-28 12:18:30 -0700164 row.select('.deleteFlow').on('click', function () {
165 if (d) {
166 var prompt = 'Delete flow ' + d.flowId.value + '?';
167 if (confirm(prompt)) {
168 deleteFlow(d);
Paul Greysonaa812562013-03-28 12:43:12 -0700169 d.deletePending = true;
Paul Greyson6f918402013-03-28 12:18:30 -0700170 updateSelectedFlows();
171
172 setTimeout(function () {
Paul Greysonaa812562013-03-28 12:43:12 -0700173 d.deletePending = false;
Paul Greyson6f918402013-03-28 12:18:30 -0700174 updateSelectedFlows();
175 }, pendingTimeout)
176 };
177 }
178 });
179
Paul Greyson13f02b92013-03-28 11:29:35 -0700180 row.select('.flowId')
181 .text(function (d) {
182 if (d) {
183 if (d.flowId) {
184 return d.flowId.value;
185 } else {
186 return '0x--';
187 }
188 }
189 })
Paul Greysonaa812562013-03-28 12:43:12 -0700190 .classed('pending', d && (d.deletePending || d.createPending));
Paul Greyson13f02b92013-03-28 11:29:35 -0700191
192 row.select('.srcDPID')
193 .text(function (d) {
194 if (d) {
195 return d.dataPath.srcPort.dpid.value;
196 }
197 });
198
199 row.select('.dstDPID')
200 .text(function (d) {
201 if (d) {
202 return d.dataPath.dstPort.dpid.value;
203 }
204 });
205 }
206
207 var flows = d3.select('#selectedFlows')
208 .selectAll('.selectedFlow')
209 .data(selectedFlows);
210
211 flows.enter()
212 .append('div')
213 .classed('selectedFlow', true)
214 .each(rowEnter);
215
216 flows.each(rowUpdate);
217
Paul Greyson29aa98d2013-03-28 00:09:31 -0700218 flows.exit().remove();
Paul Greyson127d7fb2013-03-25 23:39:20 -0700219}
220
Paul Greyson13f02b92013-03-28 11:29:35 -0700221function updateSelectedFlows() {
222 // make sure that all of the selected flows are either
223 // 1) valid (meaning they are in the latest list of flows)
224 // 2) pending
225 if (model) {
226 var flowMap = {};
227 model.flows.forEach(function (flow) {
228 flowMap[makeFlowKey(flow)] = flow;
229 });
230
231 var newSelectedFlows = [];
232 selectedFlows.forEach(function (flow) {
233 if (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700234 var liveFlow = flowMap[makeFlowKey(flow)];
235 if (liveFlow) {
236 newSelectedFlows.push(liveFlow);
Paul Greysonaa812562013-03-28 12:43:12 -0700237 liveFlow.deletePending = flow.deletePending;
238 } else if (flow.createPending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700239 newSelectedFlows.push(flow);
Paul Greyson13f02b92013-03-28 11:29:35 -0700240 }
241 } else {
242 newSelectedFlows.push(null);
243 }
244 });
245 selectedFlows = newSelectedFlows;
246 }
Paul Greyson6f918402013-03-28 12:18:30 -0700247 while (selectedFlows.length < 3) {
248 selectedFlows.push(null);
249 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700250
251 updateSelectedFlowsTable();
252 updateSelectedFlowsTopology();
253}
254
255function selectFlow(flow) {
Paul Greysonc30f75e2013-03-28 11:45:15 -0700256 var flowKey = makeFlowKey(flow);
257 var alreadySelected = false;
258 selectedFlows.forEach(function (f) {
259 if (f && makeFlowKey(f) === flowKey) {
260 alreadySelected = true;
261 }
262 });
263
264 if (!alreadySelected) {
265 selectedFlows.unshift(flow);
266 selectedFlows = selectedFlows.slice(0, 3);
267 updateSelectedFlows();
268 }
Paul Greyson13f02b92013-03-28 11:29:35 -0700269}
270
Paul Greysonaa812562013-03-28 12:43:12 -0700271function deselectFlow(flow, ifCreatePending) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700272 var flowKey = makeFlowKey(flow);
273 var newSelectedFlows = [];
274 selectedFlows.forEach(function (flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700275 if (!flow ||
276 flowKey !== makeFlowKey(flow) ||
Paul Greysonaa812562013-03-28 12:43:12 -0700277 flowKey === makeFlowKey(flow) && ifCreatePending && !flow.createPending ) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700278 newSelectedFlows.push(flow);
279 }
280 });
281 selectedFlows = newSelectedFlows;
282 while (selectedFlows.length < 3) {
283 selectedFlows.push(null);
284 }
285
286 updateSelectedFlows();
287}
288
Paul Greysonaa812562013-03-28 12:43:12 -0700289function deselectFlowIfCreatePending(flow) {
Paul Greysonf430fd02013-03-28 12:32:24 -0700290 deselectFlow(flow, true);
291}
292
Paul Greyson29aa98d2013-03-28 00:09:31 -0700293function showFlowChooser() {
294 function rowEnter(d) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700295 var row = d3.select(this);
296
Paul Greyson127d7fb2013-03-25 23:39:20 -0700297 row.append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -0700298 .classed('black-eye', true).
Paul Greyson29aa98d2013-03-28 00:09:31 -0700299 on('click', function () {
Paul Greyson13f02b92013-03-28 11:29:35 -0700300 selectFlow(d);
Paul Greyson127d7fb2013-03-25 23:39:20 -0700301 });
302
303 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700304 .classed('flowId', true)
305 .text(function (d) {
306 return d.flowId.value;
307 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700308
309 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700310 .classed('srcDPID', true)
311 .text(function (d) {
312 return d.dataPath.srcPort.dpid.value;
313 });
314
Paul Greyson127d7fb2013-03-25 23:39:20 -0700315
316 row.append('div')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700317 .classed('dstDPID', true)
318 .text(function (d) {
319 return d.dataPath.dstPort.dpid.value;
320 });
Paul Greyson127d7fb2013-03-25 23:39:20 -0700321
Paul Greyson127d7fb2013-03-25 23:39:20 -0700322 }
323
Paul Greyson29aa98d2013-03-28 00:09:31 -0700324 var flows = d3.select('#flowChooser')
325 .append('div')
326 .style('pointer-events', 'auto')
Paul Greyson127d7fb2013-03-25 23:39:20 -0700327 .selectAll('.selectedFlow')
Paul Greyson29aa98d2013-03-28 00:09:31 -0700328 .data(model.flows)
Paul Greyson127d7fb2013-03-25 23:39:20 -0700329 .enter()
330 .append('div')
331 .classed('selectedFlow', true)
332 .each(rowEnter);
333
Paul Greyson29aa98d2013-03-28 00:09:31 -0700334 setTimeout(function () {
335 d3.select(document.body).on('click', function () {
336 d3.select('#flowChooser').html('');
337 d3.select(document.body).on('click', null);
338 });
339 }, 0);
340}
341
Paul Greyson29aa98d2013-03-28 00:09:31 -0700342
Paul Greyson127d7fb2013-03-25 23:39:20 -0700343
Paul Greysond1a22d92013-03-19 12:15:19 -0700344function updateHeader(model) {
Paul Greysonb48943b2013-03-19 13:27:57 -0700345 d3.select('#lastUpdate').text(new Date());
Paul Greyson952ccb62013-03-18 22:22:08 -0700346 d3.select('#activeSwitches').text(model.edgeSwitches.length + model.aggregationSwitches.length + model.coreSwitches.length);
347 d3.select('#activeFlows').text(model.flows.length);
348}
349
350function toRadians (angle) {
351 return angle * (Math.PI / 180);
Paul Greyson740bdaf2013-03-18 16:10:48 -0700352}
353
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700354var widths = {
355 edge: 6,
356 aggregation: 12,
357 core: 18
358}
359
Paul Greysonc17278a2013-03-23 10:17:12 -0700360function createRingsFromModel(model) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700361 var rings = [{
362 radius: 3,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700363 width: widths.edge,
Paul Greyson952ccb62013-03-18 22:22:08 -0700364 switches: model.edgeSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700365 className: 'edge',
366 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700367 }, {
Paul Greysond1a22d92013-03-19 12:15:19 -0700368 radius: 2.25,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700369 width: widths.aggregation,
Paul Greyson952ccb62013-03-18 22:22:08 -0700370 switches: model.aggregationSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700371 className: 'aggregation',
372 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700373 }, {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700374 radius: 0.75,
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700375 width: widths.core,
Paul Greyson952ccb62013-03-18 22:22:08 -0700376 switches: model.coreSwitches,
Paul Greysonde7fad52013-03-19 12:47:32 -0700377 className: 'core',
378 angles: []
Paul Greyson740bdaf2013-03-18 16:10:48 -0700379 }];
380
Paul Greysonde7fad52013-03-19 12:47:32 -0700381
382 var aggRanges = {};
383
384 // arrange edge switches at equal increments
385 var k = 360 / rings[0].switches.length;
386 rings[0].switches.forEach(function (s, i) {
387 var angle = k * i;
388
389 rings[0].angles[i] = angle;
390
391 // record the angle for the agg switch layout
392 var dpid = s.dpid.split(':');
Paul Greyson832d2202013-03-21 13:27:56 -0700393 dpid[7] = '01'; // the last component of the agg switch is always '01'
Paul Greysonde7fad52013-03-19 12:47:32 -0700394 var aggdpid = dpid.join(':');
395 var aggRange = aggRanges[aggdpid];
396 if (!aggRange) {
397 aggRange = aggRanges[aggdpid] = {};
398 aggRange.min = aggRange.max = angle;
399 } else {
400 aggRange.max = angle;
401 }
Paul Greysonde7fad52013-03-19 12:47:32 -0700402 });
403
404 // arrange aggregation switches to "fan out" to edge switches
405 k = 360 / rings[1].switches.length;
406 rings[1].switches.forEach(function (s, i) {
407// rings[1].angles[i] = k * i;
408 var range = aggRanges[s.dpid];
409
Paul Greyson832d2202013-03-21 13:27:56 -0700410 rings[1].angles[i] = (range.min + range.max)/2;
Paul Greysonde7fad52013-03-19 12:47:32 -0700411 });
412
Paul Greyson3f890b62013-03-22 17:39:36 -0700413 // find the association between core switches and aggregation switches
414 var aggregationSwitchMap = {};
415 model.aggregationSwitches.forEach(function (s, i) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700416 aggregationSwitchMap[s.dpid] = i;
Paul Greyson3f890b62013-03-22 17:39:36 -0700417 });
418
Paul Greyson3f890b62013-03-22 17:39:36 -0700419 // put core switches next to linked aggregation switches
Paul Greysonde7fad52013-03-19 12:47:32 -0700420 k = 360 / rings[2].switches.length;
421 rings[2].switches.forEach(function (s, i) {
Paul Greyson3f890b62013-03-22 17:39:36 -0700422// rings[2].angles[i] = k * i;
Paul Greysonc17278a2013-03-23 10:17:12 -0700423 var associatedAggregationSwitches = model.configuration.association[s.dpid];
424 // TODO: go between if there are multiple
425 var index = aggregationSwitchMap[associatedAggregationSwitches[0]];
426
427 rings[2].angles[i] = rings[1].angles[index];
Paul Greysonde7fad52013-03-19 12:47:32 -0700428 });
429
Paul Greyson644d92a2013-03-23 18:00:40 -0700430 // TODO: construct this form initially rather than converting. it works better because
431 // it allows binding by dpid
432 var testRings = [];
433 rings.forEach(function (ring) {
434 var testRing = [];
435 ring.switches.forEach(function (s, i) {
436 var testSwitch = {
437 dpid: s.dpid,
438 state: s.state,
439 radius: ring.radius,
440 width: ring.width,
441 className: ring.className,
442 angle: ring.angles[i],
443 controller: s.controller
Paul Greyson127d7fb2013-03-25 23:39:20 -0700444 };
Paul Greyson644d92a2013-03-23 18:00:40 -0700445 testRing.push(testSwitch);
446 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700447
448
Paul Greyson644d92a2013-03-23 18:00:40 -0700449 testRings.push(testRing);
450 });
Paul Greyson6d9ed862013-03-23 17:37:15 -0700451
452
Paul Greyson644d92a2013-03-23 18:00:40 -0700453// return rings;
454 return testRings;
Paul Greysonc17278a2013-03-23 10:17:12 -0700455}
456
Paul Greyson40c8a592013-03-27 14:10:33 -0700457function makeLinkKey(link) {
458 return link['src-switch'] + '=>' + link['dst-switch'];
459}
460
Paul Greyson13f02b92013-03-28 11:29:35 -0700461function makeFlowKey(flow) {
462 return flow.dataPath.srcPort.dpid.value + '=>' + flow.dataPath.dstPort.dpid.value;
463}
464
Paul Greyson40c8a592013-03-27 14:10:33 -0700465function createLinkMap(links) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700466 var linkMap = {};
Paul Greyson40c8a592013-03-27 14:10:33 -0700467 links.forEach(function (link) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700468 var srcDPID = link['src-switch'];
469 var dstDPID = link['dst-switch'];
470
471 var srcMap = linkMap[srcDPID] || {};
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700472
Paul Greyson8d1c6362013-03-27 13:05:24 -0700473 srcMap[dstDPID] = link;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700474
475 linkMap[srcDPID] = srcMap;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700476 });
477 return linkMap;
478}
479
Paul Greyson5cc35f02013-03-28 10:07:36 -0700480// removes links from the pending list that are now in the model
481function reconcilePendingLinks(model) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700482 var links = [];
483 model.links.forEach(function (link) {
484 links.push(link);
485 delete pendingLinks[makeLinkKey(link)]
486 })
487 var linkId;
488 for (linkId in pendingLinks) {
489 links.push(pendingLinks[linkId]);
490 }
Paul Greyson5cc35f02013-03-28 10:07:36 -0700491 return links
492}
Paul Greyson40c8a592013-03-27 14:10:33 -0700493
Paul Greyson5cc35f02013-03-28 10:07:36 -0700494updateTopology = function() {
495
496 // DRAW THE SWITCHES
497 var rings = svg.selectAll('.ring').data(createRingsFromModel(model));
498
499 var links = reconcilePendingLinks(model);
Paul Greyson40c8a592013-03-27 14:10:33 -0700500 var linkMap = createLinkMap(links);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700501
Paul Greyson8d1c6362013-03-27 13:05:24 -0700502 function mouseOverSwitch(data) {
Paul Greyson72f18852013-03-27 15:56:11 -0700503
504 d3.event.preventDefault();
505
Paul Greyson5cc35f02013-03-28 10:07:36 -0700506 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', false);
507
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700508 if (data.highlighted) {
509 return;
510 }
511
512 // only highlight valid link or flow destination by checking for class of existing highlighted circle
Paul Greyson421bfcd2013-03-27 22:22:09 -0700513 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700514 if (highlighted.length == 1) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700515 var s = d3.select(highlighted[0]).select('circle');
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700516 // only allow links
517 // edge->edge (flow)
518 // aggregation->core
519 // core->core
520 if (data.className == 'edge' && !s.classed('edge') ||
521 data.className == 'core' && !s.classed('core') && !s.classed('aggregation') ||
522 data.className == 'aggregation' && !s.classed('core')) {
523 return;
524 }
525
526 // don't highlight if there's already a link or flow
527 // var map = linkMap[data.dpid];
528 // console.log(map);
529 // console.log(s.data()[0].dpid);
530 // console.log(map[s.data()[0].dpid]);
531 // if (map && map[s.data()[0].dpid]) {
532 // return;
533 // }
534
535 // the second highlighted switch is the target for a link or flow
536 data.target = true;
537 }
538
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700539 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700540 node.classed('highlight', true).select('circle').transition().duration(100).attr("r", widths.core);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700541 data.highlighted = true;
542 node.moveToFront();
543 }
544
Paul Greyson8d1c6362013-03-27 13:05:24 -0700545 function mouseOutSwitch(data) {
Paul Greyson5cc35f02013-03-28 10:07:36 -0700546 d3.select(document.getElementById(data.dpid + '-label')).classed('nolabel', true);
547
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700548 if (data.mouseDown)
549 return;
550
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700551 var node = d3.select(document.getElementById(data.dpid));
Paul Greyson421bfcd2013-03-27 22:22:09 -0700552 node.classed('highlight', false).select('circle').transition().duration(100).attr("r", widths[data.className]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700553 data.highlighted = false;
554 data.target = false;
555 }
556
Paul Greyson8d1c6362013-03-27 13:05:24 -0700557 function mouseDownSwitch(data) {
558 mouseOverSwitch(data);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700559 data.mouseDown = true;
Paul Greyson72f18852013-03-27 15:56:11 -0700560 d3.select('#topology').classed('linking', true);
561
Paul Greyson421bfcd2013-03-27 22:22:09 -0700562 d3.select('svg')
563 .append('svg:path')
564 .attr('id', 'linkVector')
565 .attr('d', function () {
566 var s = d3.select(document.getElementById(data.dpid));
567
568 var pt = document.querySelector('svg').createSVGPoint();
569 pt.x = s.attr('x');
570 pt.y = s.attr('y');
571 pt = pt.matrixTransform(s[0][0].getCTM());
572
573 return line([pt, pt]);
574 });
575
576
Paul Greyson72f18852013-03-27 15:56:11 -0700577 if (data.className === 'core') {
578 d3.selectAll('.edge').classed('nodrop', true);
579 }
580 if (data.className === 'edge') {
581 d3.selectAll('.core').classed('nodrop', true);
582 d3.selectAll('.aggregation').classed('nodrop', true);
583 }
584 if (data.className === 'aggregation') {
585 d3.selectAll('.edge').classed('nodrop', true);
586 d3.selectAll('.aggregation').classed('nodrop', true);
587 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700588 }
589
Paul Greyson8d1c6362013-03-27 13:05:24 -0700590 function mouseUpSwitch(data) {
591 if (data.mouseDown) {
592 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700593 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700594 d3.event.stopPropagation();
Paul Greyson72f18852013-03-27 15:56:11 -0700595 d3.selectAll('.nodrop').classed('nodrop', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700596 }
597 }
598
599 function doubleClickSwitch(data) {
Paul Greyson084779b2013-03-27 13:55:49 -0700600 var circle = d3.select(document.getElementById(data.dpid)).select('circle');
Paul Greyson8d1c6362013-03-27 13:05:24 -0700601 if (data.state == 'ACTIVE') {
602 var prompt = 'Deactivate ' + data.dpid + '?';
603 if (confirm(prompt)) {
604 switchDown(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700605 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700606 }
607 } else {
608 var prompt = 'Activate ' + data.dpid + '?';
609 if (confirm(prompt)) {
610 switchUp(data);
Paul Greyson084779b2013-03-27 13:55:49 -0700611 setPending(circle);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700612 }
613 }
614 }
615
Paul Greyson740bdaf2013-03-18 16:10:48 -0700616 function ringEnter(data, i) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700617 if (!data.length) {
Paul Greyson740bdaf2013-03-18 16:10:48 -0700618 return;
619 }
620
Paul Greysonc17278a2013-03-23 10:17:12 -0700621 // create the nodes
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700622 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700623 .data(data, function (data) {
624 return data.dpid;
625 })
Paul Greyson740bdaf2013-03-18 16:10:48 -0700626 .enter().append("svg:g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700627 .attr("id", function (data, i) {
628 return data.dpid;
Paul Greyson23b0cd32013-03-18 23:45:48 -0700629 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700630 .attr("transform", function(data, i) {
631 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700632 });
633
Paul Greysonc17278a2013-03-23 10:17:12 -0700634 // add the cirles representing the switches
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700635 nodes.append("svg:circle")
Paul Greyson644d92a2013-03-23 18:00:40 -0700636 .attr("transform", function(data, i) {
Paul Greysond1a22d92013-03-19 12:15:19 -0700637 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
Paul Greysonf8f43172013-03-18 23:00:30 -0700638 if (data.scale) {
639 m = m.scale(data.scale);
640 }
641 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
Paul Greyson952ccb62013-03-18 22:22:08 -0700642 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700643 .attr("x", function (data) {
644 return -data.width / 2;
645 })
646 .attr("y", function (data) {
647 return -data.width / 2;
648 })
649 .attr("r", function (data) {
650 return data.width;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700651 });
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700652
Paul Greysonc17278a2013-03-23 10:17:12 -0700653 // setup the mouseover behaviors
Paul Greyson8d1c6362013-03-27 13:05:24 -0700654 nodes.on('mouseover', mouseOverSwitch);
655 nodes.on('mouseout', mouseOutSwitch);
656 nodes.on('mouseup', mouseUpSwitch);
657 nodes.on('mousedown', mouseDownSwitch);
658
659 // only do switch up/down for core switches
660 if (i == 2) {
661 nodes.on('dblclick', doubleClickSwitch);
662 }
Paul Greyson740bdaf2013-03-18 16:10:48 -0700663 }
664
Paul Greysonc17278a2013-03-23 10:17:12 -0700665 // append switches
666 rings.enter().append("svg:g")
Paul Greyson740bdaf2013-03-18 16:10:48 -0700667 .attr("class", "ring")
668 .each(ringEnter);
Paul Greysonf8f43172013-03-18 23:00:30 -0700669
Paul Greysonf9edc1a2013-03-19 13:22:06 -0700670
Paul Greysonc17278a2013-03-23 10:17:12 -0700671 function ringUpdate(data, i) {
Paul Greyson127d7fb2013-03-25 23:39:20 -0700672 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700673 .data(data, function (data) {
674 return data.dpid;
Paul Greyson127d7fb2013-03-25 23:39:20 -0700675 });
Paul Greyson347fb742013-03-27 13:40:29 -0700676 nodes.select('circle')
677 .each(function (data) {
678 // if there's a pending state changed and then the state changes, clear the pending class
679 var circle = d3.select(this);
680 if (data.state === 'ACTIVE' && circle.classed('inactive') ||
681 data.state === 'INACTIVE' && circle.classed('active')) {
682 circle.classed('pending', false);
683 }
684 })
685 .attr('class', function (data) {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700686 if (data.state === 'ACTIVE' && data.controller) {
Paul Greyson347fb742013-03-27 13:40:29 -0700687 return data.className + ' active ' + controllerColorMap[data.controller];
Paul Greysonc17278a2013-03-23 10:17:12 -0700688 } else {
Paul Greyson347fb742013-03-27 13:40:29 -0700689 return data.className + ' inactive ' + 'colorInactive';
Paul Greysonc17278a2013-03-23 10:17:12 -0700690 }
Paul Greyson127d7fb2013-03-25 23:39:20 -0700691 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700692 }
693
694 // update switches
695 rings.each(ringUpdate);
696
Paul Greyson968d1b42013-03-23 16:58:41 -0700697
698 // Now setup the labels
699 // This is done separately because SVG draws in node order and we want the labels
700 // always on top
701 var labelRings = svg.selectAll('.labelRing').data(createRingsFromModel(model));
702
Paul Greyson421bfcd2013-03-27 22:22:09 -0700703 d3.select(document.body).on('mousemove', function () {
704 if (!d3.select('#topology').classed('linking')) {
705 return;
706 }
707 var linkVector = document.getElementById('linkVector');
708 if (!linkVector) {
709 return;
710 }
711 linkVector = d3.select(linkVector);
712
713 var highlighted = svg.selectAll('.highlight')[0];
714 var s1 = null, s2 = null;
715 if (highlighted.length > 1) {
716 var s1 = d3.select(highlighted[0]);
717 var s2 = d3.select(highlighted[1]);
718
719 } else if (highlighted.length > 0) {
720 var s1 = d3.select(highlighted[0]);
721 }
722 var src = s1;
723 if (s2 && !s2.data()[0].target) {
724 src = s2;
725 }
726 if (src) {
727 linkVector.attr('d', function () {
728 var srcPt = document.querySelector('svg').createSVGPoint();
729 srcPt.x = src.attr('x');
730 srcPt.y = src.attr('y');
731 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
732
733 var svg = document.getElementById('topology');
734 var mouse = d3.mouse(viewbox);
735 var dstPt = document.querySelector('svg').createSVGPoint();
736 dstPt.x = mouse[0];
737 dstPt.y = mouse[1];
738 dstPt = dstPt.matrixTransform(viewbox.getCTM());
739
740 return line([srcPt, dstPt]);
741 });
742 }
743 });
744
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700745 d3.select(document.body).on('mouseup', function () {
746 function clearHighlight() {
747 svg.selectAll('circle').each(function (data) {
748 data.mouseDown = false;
Paul Greyson72f18852013-03-27 15:56:11 -0700749 d3.select('#topology').classed('linking', false);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700750 mouseOutSwitch(data);
Paul Greyson421bfcd2013-03-27 22:22:09 -0700751 });
752 d3.select('#linkVector').remove();
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700753 };
754
Paul Greyson72f18852013-03-27 15:56:11 -0700755 d3.selectAll('.nodrop').classed('nodrop', false);
756
Paul Greyson084779b2013-03-27 13:55:49 -0700757 function removeLink(link) {
758 var path1 = document.getElementById(link['src-switch'] + '=>' + link['dst-switch']);
759 var path2 = document.getElementById(link['dst-switch'] + '=>' + link['src-switch']);
760
761 if (path1) {
762 setPending(d3.select(path1));
763 }
764 if (path2) {
765 setPending(d3.select(path2));
766 }
767
768 linkDown(link);
769 }
770
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700771
Paul Greyson421bfcd2013-03-27 22:22:09 -0700772 var highlighted = svg.selectAll('.highlight')[0];
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700773 if (highlighted.length == 2) {
Paul Greyson421bfcd2013-03-27 22:22:09 -0700774 var s1Data = highlighted[0].__data__;
775 var s2Data = highlighted[1].__data__;
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700776
777 var srcData, dstData;
778 if (s1Data.target) {
779 dstData = s1Data;
780 srcData = s2Data;
781 } else {
782 dstData = s2Data;
783 srcData = s1Data;
784 }
785
786 if (s1Data.className == 'edge' && s2Data.className == 'edge') {
787 var prompt = 'Create flow from ' + srcData.dpid + ' to ' + dstData.dpid + '?';
788 if (confirm(prompt)) {
Paul Greyson13f02b92013-03-28 11:29:35 -0700789 addFlow(srcData, dstData);
790
791 var flow = {
792 dataPath: {
793 srcPort: {
794 dpid: {
795 value: srcData.dpid
796 }
797 },
798 dstPort: {
799 dpid: {
800 value: dstData.dpid
801 }
802 }
803 },
Paul Greysonaa812562013-03-28 12:43:12 -0700804 createPending: true
Paul Greyson13f02b92013-03-28 11:29:35 -0700805 };
806
807 selectFlow(flow);
808
809 setTimeout(function () {
Paul Greysonaa812562013-03-28 12:43:12 -0700810 deselectFlowIfCreatePending(flow);
Paul Greyson6f918402013-03-28 12:18:30 -0700811 }, pendingTimeout);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700812 }
813 } else {
814 var map = linkMap[srcData.dpid];
815 if (map && map[dstData.dpid]) {
816 var prompt = 'Remove link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
817 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700818 removeLink(map[dstData.dpid]);
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700819 }
820 } else {
Paul Greyson8d1c6362013-03-27 13:05:24 -0700821 map = linkMap[dstData.dpid];
822 if (map && map[srcData.dpid]) {
823 var prompt = 'Remove link between ' + dstData.dpid + ' and ' + srcData.dpid + '?';
824 if (confirm(prompt)) {
Paul Greyson084779b2013-03-27 13:55:49 -0700825 removeLink(map[srcData.dpid]);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700826 }
827 } else {
828 var prompt = 'Create link between ' + srcData.dpid + ' and ' + dstData.dpid + '?';
829 if (confirm(prompt)) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700830 var link1 = {
831 'src-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700832 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700833 'dst-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700834 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700835 pending: true
836 };
837 pendingLinks[makeLinkKey(link1)] = link1;
838 var link2 = {
839 'src-switch': dstData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700840 'src-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700841 'dst-switch': srcData.dpid,
Paul Greyson2913af82013-03-27 14:53:17 -0700842 'dst-port': 1,
Paul Greyson40c8a592013-03-27 14:10:33 -0700843 pending: true
844 };
845 pendingLinks[makeLinkKey(link2)] = link2;
Paul Greyson5cc35f02013-03-28 10:07:36 -0700846 updateTopology();
Paul Greyson40c8a592013-03-27 14:10:33 -0700847
Paul Greyson2913af82013-03-27 14:53:17 -0700848 linkUp(link1);
Paul Greyson40c8a592013-03-27 14:10:33 -0700849
Paul Greyson5cc35f02013-03-28 10:07:36 -0700850 // remove the pending links after 10s
Paul Greyson40c8a592013-03-27 14:10:33 -0700851 setTimeout(function () {
852 delete pendingLinks[makeLinkKey(link1)];
853 delete pendingLinks[makeLinkKey(link2)];
854
Paul Greyson5cc35f02013-03-28 10:07:36 -0700855 updateTopology();
Paul Greyson6f918402013-03-28 12:18:30 -0700856 }, pendingTimeout);
Paul Greyson8d1c6362013-03-27 13:05:24 -0700857 }
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700858 }
859 }
860 }
861
862 clearHighlight();
863 } else {
864 clearHighlight();
865 }
866
867 });
868
Paul Greyson9066ab02013-03-23 18:15:41 -0700869 function labelRingEnter(data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700870 if (!data.length) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700871 return;
872 }
873
874 // create the nodes
875 var nodes = d3.select(this).selectAll("g")
Paul Greyson644d92a2013-03-23 18:00:40 -0700876 .data(data, function (data) {
877 return data.dpid;
878 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700879 .enter().append("svg:g")
880 .classed('nolabel', true)
Paul Greyson9066ab02013-03-23 18:15:41 -0700881 .attr("id", function (data) {
Paul Greyson644d92a2013-03-23 18:00:40 -0700882 return data.dpid + '-label';
Paul Greyson968d1b42013-03-23 16:58:41 -0700883 })
Paul Greyson644d92a2013-03-23 18:00:40 -0700884 .attr("transform", function(data, i) {
885 return "rotate(" + data.angle+ ")translate(" + data.radius * 150 + ")rotate(" + (-data.angle) + ")";
Paul Greyson4e6dc3a2013-03-27 11:37:14 -0700886 })
Paul Greyson968d1b42013-03-23 16:58:41 -0700887
888 // add the text nodes which show on mouse over
889 nodes.append("svg:text")
Paul Greyson127d7fb2013-03-25 23:39:20 -0700890 .text(function (data) {return data.dpid;})
Paul Greyson9066ab02013-03-23 18:15:41 -0700891 .attr("x", function (data) {
892 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
893 if (data.className == 'edge') {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700894 return - data.width*3 - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700895 } else {
Paul Greyson1eb2dd12013-03-23 18:22:00 -0700896 return - data.width - 4;
Paul Greyson9066ab02013-03-23 18:15:41 -0700897 }
898 } else {
899 if (data.className == 'edge') {
900 return data.width*3 + 4;
901 } else {
902 return data.width + 4;
903 }
904 }
905 })
906 .attr("y", function (data) {
907 var y;
908 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
909 if (data.className == 'edge') {
910 y = data.width*3/2 + 4;
911 } else {
912 y = data.width/2 + 4;
913 }
914 } else {
915 if (data.className == 'edge') {
916 y = data.width*3/2 + 4;
917 } else {
918 y = data.width/2 + 4;
919 }
920 }
921 return y - 6;
922 })
923 .attr("text-anchor", function (data) {
924 if (data.angle <= 90 || data.angle >= 270 && data.angle <= 360) {
925 return "end";
926 } else {
927 return "start";
928 }
929 })
930 .attr("transform", function(data) {
Paul Greyson968d1b42013-03-23 16:58:41 -0700931 var m = document.querySelector('#viewbox').getTransformToElement().inverse();
932 if (data.scale) {
933 m = m.scale(data.scale);
934 }
935 return "matrix( " + m.a + " " + m.b + " " + m.c + " " + m.d + " " + m.e + " " + m.f + " )";
936 })
937 }
938
939 labelRings.enter().append("svg:g")
940 .attr("class", "textRing")
941 .each(labelRingEnter);
942
Paul Greysonc17278a2013-03-23 10:17:12 -0700943 // switches should not change during operation of the ui so no
944 // rings.exit()
945
946
Paul Greysond1a22d92013-03-19 12:15:19 -0700947 // DRAW THE LINKS
Paul Greysond1a22d92013-03-19 12:15:19 -0700948
Paul Greysonc17278a2013-03-23 10:17:12 -0700949 // key on link dpids since these will come/go during demo
Paul Greyson40c8a592013-03-27 14:10:33 -0700950 var links = d3.select('svg').selectAll('.link').data(links, function (d) {
Paul Greysonc17278a2013-03-23 10:17:12 -0700951 return d['src-switch']+'->'+d['dst-switch'];
952 });
953
954 // add new links
Paul Greysonb367de22013-03-23 11:09:11 -0700955 links.enter().append("svg:path")
Paul Greyson56378ed2013-03-26 23:17:36 -0700956 .attr("class", "link");
957
Paul Greyson084779b2013-03-27 13:55:49 -0700958 links.attr('id', function (d) {
Paul Greyson40c8a592013-03-27 14:10:33 -0700959 return makeLinkKey(d);
Paul Greyson084779b2013-03-27 13:55:49 -0700960 })
Paul Greyson56378ed2013-03-26 23:17:36 -0700961 .attr("d", function (d) {
Paul Greyson084779b2013-03-27 13:55:49 -0700962 var src = d3.select(document.getElementById(d['src-switch']));
963 var dst = d3.select(document.getElementById(d['dst-switch']));
Paul Greysonc17278a2013-03-23 10:17:12 -0700964
Paul Greyson084779b2013-03-27 13:55:49 -0700965 var srcPt = document.querySelector('svg').createSVGPoint();
966 srcPt.x = src.attr('x');
967 srcPt.y = src.attr('y');
968 srcPt = srcPt.matrixTransform(src[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700969
Paul Greyson084779b2013-03-27 13:55:49 -0700970 var dstPt = document.querySelector('svg').createSVGPoint();
971 dstPt.x = dst.attr('x');
Paul Greyson421bfcd2013-03-27 22:22:09 -0700972 dstPt.y = dst.attr('y');
Paul Greyson084779b2013-03-27 13:55:49 -0700973 dstPt = dstPt.matrixTransform(dst[0][0].getCTM());
Paul Greysond1a22d92013-03-19 12:15:19 -0700974
Paul Greyson084779b2013-03-27 13:55:49 -0700975 var midPt = document.querySelector('svg').createSVGPoint();
976 midPt.x = (srcPt.x + dstPt.x)/2;
977 midPt.y = (srcPt.y + dstPt.y)/2;
Paul Greysond1a22d92013-03-19 12:15:19 -0700978
Paul Greyson084779b2013-03-27 13:55:49 -0700979 return line([srcPt, midPt, dstPt]);
980 })
Paul Greyson40c8a592013-03-27 14:10:33 -0700981 .attr("marker-mid", function(d) { return "url(#arrow)"; })
982 .classed('pending', function (d) {
983 return d.pending;
984 });
Paul Greysonc17278a2013-03-23 10:17:12 -0700985
Paul Greyson56378ed2013-03-26 23:17:36 -0700986
Paul Greysonc17278a2013-03-23 10:17:12 -0700987 // remove old links
988 links.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -0700989}
990
Paul Greyson5cc35f02013-03-28 10:07:36 -0700991function updateControllers() {
Paul Greysond1a22d92013-03-19 12:15:19 -0700992 var controllers = d3.select('#controllerList').selectAll('.controller').data(model.controllers);
Paul Greyson3e142162013-03-19 13:56:17 -0700993 controllers.enter().append('div')
Paul Greysone262a292013-03-23 10:35:23 -0700994 .each(function (c) {
995 controllerColorMap[c] = colors.pop();
996 d3.select(document.body).classed(controllerColorMap[c] + '-selected', true);
997 })
998 .text(function (d) {
999 return d;
Paul Greyson2913af82013-03-27 14:53:17 -07001000 })
1001 .append('div')
Paul Greyson8247c3f2013-03-28 00:24:02 -07001002 .attr('class', 'black-eye');
Paul Greysonbcd3c772013-03-21 13:16:44 -07001003
Paul Greysone262a292013-03-23 10:35:23 -07001004 controllers.attr('class', function (d) {
Paul Greysoneed36352013-03-23 11:19:11 -07001005 var color = 'colorInactive';
Paul Greysonbcd3c772013-03-21 13:16:44 -07001006 if (model.activeControllers.indexOf(d) != -1) {
1007 color = controllerColorMap[d];
Paul Greysond1a22d92013-03-19 12:15:19 -07001008 }
Paul Greysonbcd3c772013-03-21 13:16:44 -07001009 var className = 'controller ' + color;
1010 return className;
Paul Greysond1a22d92013-03-19 12:15:19 -07001011 });
Paul Greysond1a22d92013-03-19 12:15:19 -07001012
Paul Greysone262a292013-03-23 10:35:23 -07001013 // this should never be needed
1014 // controllers.exit().remove();
Paul Greysond1a22d92013-03-19 12:15:19 -07001015
Paul Greyson2913af82013-03-27 14:53:17 -07001016 controllers.on('dblclick', function (c) {
1017 if (model.activeControllers.indexOf(c) != -1) {
1018 var prompt = 'Dectivate ' + c + '?';
1019 if (confirm(prompt)) {
1020 controllerDown(c);
1021 setPending(d3.select(this));
1022 };
1023 } else {
1024 var prompt = 'Activate ' + c + '?';
1025 if (confirm(prompt)) {
1026 controllerUp(c);
1027 setPending(d3.select(this));
1028 };
1029 }
1030 });
1031
Paul Greyson8247c3f2013-03-28 00:24:02 -07001032 controllers.select('.black-eye').on('click', function (c) {
Paul Greysonc3e21a02013-03-21 13:56:05 -07001033 var allSelected = true;
1034 for (var key in controllerColorMap) {
1035 if (!d3.select(document.body).classed(controllerColorMap[key] + '-selected')) {
1036 allSelected = false;
1037 break;
1038 }
1039 }
1040 if (allSelected) {
1041 for (var key in controllerColorMap) {
1042 d3.select(document.body).classed(controllerColorMap[key] + '-selected', key == c)
1043 }
1044 } else {
1045 for (var key in controllerColorMap) {
1046 d3.select(document.body).classed(controllerColorMap[key] + '-selected', true)
1047 }
1048 }
1049
1050 // var selected = d3.select(document.body).classed(controllerColorMap[c] + '-selected');
1051 // d3.select(document.body).classed(controllerColorMap[c] + '-selected', !selected);
Paul Greysond1a22d92013-03-19 12:15:19 -07001052 });
Paul Greyson8d1c6362013-03-27 13:05:24 -07001053
1054
Paul Greyson740bdaf2013-03-18 16:10:48 -07001055}
1056
Paul Greyson29aa98d2013-03-28 00:09:31 -07001057function sync(svg) {
Paul Greysonbcd3c772013-03-21 13:16:44 -07001058 var d = Date.now();
Paul Greysonb48943b2013-03-19 13:27:57 -07001059 updateModel(function (newModel) {
Paul Greyson4e6dc3a2013-03-27 11:37:14 -07001060// console.log('Update time: ' + (Date.now() - d)/1000 + 's');
Paul Greyson740bdaf2013-03-18 16:10:48 -07001061
Paul Greysone5991b52013-04-04 01:34:04 -07001062 if (newModel) {
1063 var modelChanged = false;
1064 if (!model || JSON.stringify(model) != JSON.stringify(newModel)) {
1065 modelChanged = true;
1066 model = newModel;
1067 } else {
1068 // console.log('no change');
1069 }
Paul Greysonb48943b2013-03-19 13:27:57 -07001070
Paul Greysone5991b52013-04-04 01:34:04 -07001071 if (modelChanged) {
1072 updateControllers();
1073 updateSelectedFlows();
1074 updateTopology();
1075 }
Paul Greyson5cc35f02013-03-28 10:07:36 -07001076
Paul Greysone5991b52013-04-04 01:34:04 -07001077 updateHeader(newModel);
1078 }
Paul Greyson740bdaf2013-03-18 16:10:48 -07001079
1080 // do it again in 1s
1081 setTimeout(function () {
Paul Greysona36a9232013-03-22 22:41:27 -07001082 sync(svg)
Paul Greysond1a22d92013-03-19 12:15:19 -07001083 }, 1000);
Paul Greyson6f86d1e2013-03-18 14:40:39 -07001084 });
1085}
Paul Greyson740bdaf2013-03-18 16:10:48 -07001086
Paul Greyson38d8bde2013-03-22 22:07:35 -07001087svg = createTopologyView();
Paul Greyson29aa98d2013-03-28 00:09:31 -07001088updateSelectedFlows();
1089
1090d3.select('#showFlowChooser').on('click', function () {
1091 showFlowChooser();
1092});
1093
Paul Greyson72f18852013-03-27 15:56:11 -07001094
Paul Greyson38d8bde2013-03-22 22:07:35 -07001095// workaround for Chrome v25 bug
1096// if executed immediately, the view box transform logic doesn't work properly
1097// fixed in Chrome v27
1098setTimeout(function () {
1099 // workaround for another Chrome v25 bug
1100 // viewbox transform stuff doesn't work in combination with browser zoom
Paul Greysonc17278a2013-03-23 10:17:12 -07001101 // also works in Chrome v27
Paul Greyson38d8bde2013-03-22 22:07:35 -07001102 d3.select('#svg-container').style('zoom', window.document.body.clientWidth/window.document.width);
Paul Greyson29aa98d2013-03-28 00:09:31 -07001103 sync(svg);
Paul Greyson38d8bde2013-03-22 22:07:35 -07001104}, 100);