blob: b99cb9a12b36555a8d1085acb4ebd7c3d9140664 [file] [log] [blame]
Simon Hunt737c89f2015-01-28 12:23:19 -08001/*
2 * Copyright 2015 Open Networking Laboratory
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
Simon Hunt3a6eec02015-02-09 21:16:43 -080018 ONOS GUI -- Topology Force Module.
19 Visualization of the topology in an SVG layer, using a D3 Force Layout.
Simon Hunt737c89f2015-01-28 12:23:19 -080020 */
21
22(function () {
23 'use strict';
24
25 // injected refs
Simon Hunt86b7c882015-04-02 23:06:08 -070026 var $log, $timeout, fs, sus, is, ts, flash, wss,
Simon Hunt237676b52015-03-10 19:04:26 -070027 tis, tms, td3, tss, tts, tos, fltr, tls,
Simon Hunt3ab20282015-02-26 20:32:19 -080028 icfg, uplink, svg;
Simon Huntac4c6f72015-02-03 19:50:53 -080029
30 // configuration
Simon Hunt1894d792015-02-04 17:09:20 -080031 var linkConfig = {
32 light: {
33 baseColor: '#666',
34 inColor: '#66f',
Simon Hunt3a6eec02015-02-09 21:16:43 -080035 outColor: '#f00'
Simon Hunt1894d792015-02-04 17:09:20 -080036 },
37 dark: {
Simon Hunt5724fb42015-02-05 16:59:40 -080038 baseColor: '#aaa',
Simon Hunt1894d792015-02-04 17:09:20 -080039 inColor: '#66f',
Simon Hunt5724fb42015-02-05 16:59:40 -080040 outColor: '#f66'
Simon Hunt1894d792015-02-04 17:09:20 -080041 },
42 inWidth: 12,
43 outWidth: 10
44 };
45
Simon Hunt737c89f2015-01-28 12:23:19 -080046 // internal state
Simon Huntac4c6f72015-02-03 19:50:53 -080047 var settings, // merged default settings and options
Simon Hunt737c89f2015-01-28 12:23:19 -080048 force, // force layout object
49 drag, // drag behavior handler
50 network = {
51 nodes: [],
52 links: [],
53 lookup: {},
54 revLinkToKey: {}
Simon Huntac4c6f72015-02-03 19:50:53 -080055 },
Simon Hunt3ab20282015-02-26 20:32:19 -080056 lu, // shorthand for lookup
57 rlk, // shorthand for revLinktoKey
Simon Hunta142dd22015-02-12 22:07:51 -080058 showHosts = false, // whether hosts are displayed
Simon Hunt5724fb42015-02-05 16:59:40 -080059 showOffline = true, // whether offline devices are displayed
Simon Hunt445e8152015-02-06 13:00:12 -080060 nodeLock = false, // whether nodes can be dragged or not (locked)
Thomas Vachuska1a989c12015-06-09 18:29:22 -070061 fTimer, // timer for delayed force layout
62 fNodesTimer, // timer for delayed nodes update
63 fLinksTimer, // timer for delayed links update
Simon Hunt08f841d02015-02-10 14:39:20 -080064 dim; // the dimensions of the force layout [w,h]
Simon Hunt737c89f2015-01-28 12:23:19 -080065
66 // SVG elements;
Simon Hunt1a5301e2015-02-25 15:31:25 -080067 var linkG, linkLabelG, portLabelG, nodeG;
Simon Hunt737c89f2015-01-28 12:23:19 -080068
69 // D3 selections;
70 var link, linkLabel, node;
71
72 // default settings for force layout
73 var defaultSettings = {
74 gravity: 0.4,
75 friction: 0.7,
76 charge: {
77 // note: key is node.class
78 device: -8000,
79 host: -5000,
80 _def_: -12000
81 },
82 linkDistance: {
83 // note: key is link.type
84 direct: 100,
85 optical: 120,
86 hostLink: 3,
87 _def_: 50
88 },
89 linkStrength: {
90 // note: key is link.type
91 // range: {0.0 ... 1.0}
92 //direct: 1.0,
93 //optical: 1.0,
94 //hostLink: 1.0,
95 _def_: 1.0
96 }
97 };
98
99
Simon Huntac4c6f72015-02-03 19:50:53 -0800100 // ==========================
101 // === EVENT HANDLERS
102
103 function addDevice(data) {
104 var id = data.id,
105 d;
106
Simon Hunt1894d792015-02-04 17:09:20 -0800107 uplink.showNoDevs(false);
Simon Huntac4c6f72015-02-03 19:50:53 -0800108
109 // although this is an add device event, if we already have the
110 // device, treat it as an update instead..
Simon Hunt1894d792015-02-04 17:09:20 -0800111 if (lu[id]) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800112 updateDevice(data);
113 return;
114 }
115
Simon Hunt3a6eec02015-02-09 21:16:43 -0800116 d = tms.createDeviceNode(data);
Simon Huntac4c6f72015-02-03 19:50:53 -0800117 network.nodes.push(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800118 lu[id] = d;
Simon Huntac4c6f72015-02-03 19:50:53 -0800119 updateNodes();
120 fStart();
121 }
122
123 function updateDevice(data) {
124 var id = data.id,
Simon Hunt1894d792015-02-04 17:09:20 -0800125 d = lu[id],
Simon Huntac4c6f72015-02-03 19:50:53 -0800126 wasOnline;
127
128 if (d) {
129 wasOnline = d.online;
130 angular.extend(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800131 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800132 sendUpdateMeta(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800133 }
134 updateNodes();
135 if (wasOnline !== d.online) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800136 tms.findAttachedLinks(d.id).forEach(restyleLinkElement);
Simon Hunt5724fb42015-02-05 16:59:40 -0800137 updateOfflineVisibility(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800138 }
Simon Huntac4c6f72015-02-03 19:50:53 -0800139 }
140 }
141
Simon Hunt1894d792015-02-04 17:09:20 -0800142 function removeDevice(data) {
143 var id = data.id,
144 d = lu[id];
145 if (d) {
146 removeDeviceElement(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800147 }
148 }
149
150 function addHost(data) {
151 var id = data.id,
152 d, lnk;
153
154 // although this is an add host event, if we already have the
155 // host, treat it as an update instead..
156 if (lu[id]) {
157 updateHost(data);
158 return;
159 }
160
Simon Hunt3a6eec02015-02-09 21:16:43 -0800161 d = tms.createHostNode(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800162 network.nodes.push(d);
163 lu[id] = d;
Simon Hunt1894d792015-02-04 17:09:20 -0800164 updateNodes();
165
Simon Hunt3a6eec02015-02-09 21:16:43 -0800166 lnk = tms.createHostLink(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800167 if (lnk) {
Simon Hunt1894d792015-02-04 17:09:20 -0800168 d.linkData = lnk; // cache ref on its host
169 network.links.push(lnk);
170 lu[d.ingress] = lnk;
171 lu[d.egress] = lnk;
172 updateLinks();
173 }
174
175 fStart();
176 }
177
178 function updateHost(data) {
179 var id = data.id,
180 d = lu[id];
181 if (d) {
182 angular.extend(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800183 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800184 sendUpdateMeta(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800185 }
186 updateNodes();
Simon Hunt1894d792015-02-04 17:09:20 -0800187 }
188 }
189
190 function removeHost(data) {
191 var id = data.id,
192 d = lu[id];
193 if (d) {
194 removeHostElement(d, true);
Simon Hunt1894d792015-02-04 17:09:20 -0800195 }
196 }
197
198 function addLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800199 var result = tms.findLink(data, 'add'),
Simon Hunt1894d792015-02-04 17:09:20 -0800200 bad = result.badLogic,
201 d = result.ldata;
202
203 if (bad) {
204 //logicError(bad + ': ' + link.id);
205 return;
206 }
207
208 if (d) {
209 // we already have a backing store link for src/dst nodes
210 addLinkUpdate(d, data);
211 return;
212 }
213
214 // no backing store link yet
Simon Hunt3a6eec02015-02-09 21:16:43 -0800215 d = tms.createLink(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800216 if (d) {
217 network.links.push(d);
218 lu[d.key] = d;
219 updateLinks();
220 fStart();
221 }
222 }
223
224 function updateLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800225 var result = tms.findLink(data, 'update'),
Simon Hunt1894d792015-02-04 17:09:20 -0800226 bad = result.badLogic;
227 if (bad) {
228 //logicError(bad + ': ' + link.id);
229 return;
230 }
231 result.updateWith(link);
232 }
233
234 function removeLink(data) {
Simon Hunta4242de2015-02-24 17:11:55 -0800235 var result = tms.findLink(data, 'remove');
236
237 if (!result.badLogic) {
238 result.removeRawLink();
Simon Hunt1894d792015-02-04 17:09:20 -0800239 }
Simon Hunt1894d792015-02-04 17:09:20 -0800240 }
241
242 // ========================
243
244 function addLinkUpdate(ldata, link) {
245 // add link event, but we already have the reverse link installed
246 ldata.fromTarget = link;
Simon Huntdc6adea2015-02-09 22:29:36 -0800247 rlk[link.id] = ldata.key;
Simon Hunt1894d792015-02-04 17:09:20 -0800248 restyleLinkElement(ldata);
249 }
250
Simon Hunt1894d792015-02-04 17:09:20 -0800251
252 var widthRatio = 1.4,
253 linkScale = d3.scale.linear()
254 .domain([1, 12])
255 .range([widthRatio, 12 * widthRatio])
Simon Hunt5724fb42015-02-05 16:59:40 -0800256 .clamp(true),
Simon Hunt3a6eec02015-02-09 21:16:43 -0800257 allLinkTypes = 'direct indirect optical tunnel';
Simon Hunt1894d792015-02-04 17:09:20 -0800258
Simon Hunta142dd22015-02-12 22:07:51 -0800259 function restyleLinkElement(ldata, immediate) {
Simon Hunt1894d792015-02-04 17:09:20 -0800260 // this fn's job is to look at raw links and decide what svg classes
261 // need to be applied to the line element in the DOM
262 var th = ts.theme(),
263 el = ldata.el,
264 type = ldata.type(),
265 lw = ldata.linkWidth(),
Simon Hunta142dd22015-02-12 22:07:51 -0800266 online = ldata.online(),
267 delay = immediate ? 0 : 1000;
Simon Hunt1894d792015-02-04 17:09:20 -0800268
Thomas Vachuskacb5016f2015-05-18 14:11:43 -0700269 // FIXME: understand why el is sometimes undefined on addLink events...
270 if (el) {
271 el.classed('link', true);
272 el.classed('inactive', !online);
273 el.classed(allLinkTypes, false);
274 if (type) {
275 el.classed(type, true);
276 }
277 el.transition()
278 .duration(delay)
279 .attr('stroke-width', linkScale(lw))
280 .attr('stroke', linkConfig[th].baseColor);
Simon Hunt1894d792015-02-04 17:09:20 -0800281 }
Simon Hunt1894d792015-02-04 17:09:20 -0800282 }
283
Simon Hunt1894d792015-02-04 17:09:20 -0800284 function removeLinkElement(d) {
285 var idx = fs.find(d.key, network.links, 'key'),
286 removed;
287 if (idx >=0) {
288 // remove from links array
289 removed = network.links.splice(idx, 1);
290 // remove from lookup cache
291 delete lu[removed[0].key];
292 updateLinks();
293 fResume();
294 }
295 }
296
297 function removeHostElement(d, upd) {
298 // first, remove associated hostLink...
299 removeLinkElement(d.linkData);
300
301 // remove hostLink bindings
302 delete lu[d.ingress];
303 delete lu[d.egress];
304
305 // remove from lookup cache
306 delete lu[d.id];
307 // remove from nodes array
308 var idx = fs.find(d.id, network.nodes);
309 network.nodes.splice(idx, 1);
310
311 // remove from SVG
312 // NOTE: upd is false if we were called from removeDeviceElement()
313 if (upd) {
314 updateNodes();
315 fResume();
316 }
317 }
318
319 function removeDeviceElement(d) {
320 var id = d.id;
321 // first, remove associated hosts and links..
Simon Huntdc6adea2015-02-09 22:29:36 -0800322 tms.findAttachedHosts(id).forEach(removeHostElement);
323 tms.findAttachedLinks(id).forEach(removeLinkElement);
Simon Hunt1894d792015-02-04 17:09:20 -0800324
325 // remove from lookup cache
326 delete lu[id];
327 // remove from nodes array
328 var idx = fs.find(id, network.nodes);
329 network.nodes.splice(idx, 1);
330
331 if (!network.nodes.length) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800332 uplink.showNoDevs(true);
Simon Hunt1894d792015-02-04 17:09:20 -0800333 }
334
335 // remove from SVG
336 updateNodes();
337 fResume();
338 }
339
Simon Hunt5724fb42015-02-05 16:59:40 -0800340 function updateHostVisibility() {
Simon Hunt18bf9822015-02-12 17:35:45 -0800341 sus.visible(nodeG.selectAll('.host'), showHosts);
342 sus.visible(linkG.selectAll('.hostLink'), showHosts);
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800343 sus.visible(linkLabelG.selectAll('.hostLinkLabel'), showHosts);
Simon Hunt5724fb42015-02-05 16:59:40 -0800344 }
345
346 function updateOfflineVisibility(dev) {
347 function updDev(d, show) {
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800348 var b;
Simon Hunt18bf9822015-02-12 17:35:45 -0800349 sus.visible(d.el, show);
Simon Hunt5724fb42015-02-05 16:59:40 -0800350
Simon Huntdc6adea2015-02-09 22:29:36 -0800351 tms.findAttachedLinks(d.id).forEach(function (link) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800352 b = show && ((link.type() !== 'hostLink') || showHosts);
Simon Hunt18bf9822015-02-12 17:35:45 -0800353 sus.visible(link.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800354 });
Simon Huntdc6adea2015-02-09 22:29:36 -0800355 tms.findAttachedHosts(d.id).forEach(function (host) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800356 b = show && showHosts;
Simon Hunt18bf9822015-02-12 17:35:45 -0800357 sus.visible(host.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800358 });
359 }
360
361 if (dev) {
362 // updating a specific device that just toggled off/on-line
363 updDev(dev, dev.online || showOffline);
364 } else {
365 // updating all offline devices
Simon Huntdc6adea2015-02-09 22:29:36 -0800366 tms.findDevices(true).forEach(function (d) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800367 updDev(d, showOffline);
368 });
369 }
370 }
371
Simon Hunt1894d792015-02-04 17:09:20 -0800372
Simon Hunt445e8152015-02-06 13:00:12 -0800373 function sendUpdateMeta(d, clearPos) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800374 var metaUi = {},
375 ll;
376
Simon Hunt445e8152015-02-06 13:00:12 -0800377 // if we are not clearing the position data (unpinning),
378 // attach the x, y, longitude, latitude...
379 if (!clearPos) {
Simon Hunt3a6eec02015-02-09 21:16:43 -0800380 ll = tms.lngLatFromCoord([d.x, d.y]);
Simon Huntdc6adea2015-02-09 22:29:36 -0800381 metaUi = {x: d.x, y: d.y, lng: ll[0], lat: ll[1]};
Simon Hunt1894d792015-02-04 17:09:20 -0800382 }
383 d.metaUi = metaUi;
Simon Hunt237676b52015-03-10 19:04:26 -0700384 wss.sendEvent('updateMeta', {
Simon Hunt1894d792015-02-04 17:09:20 -0800385 id: d.id,
386 'class': d.class,
387 memento: metaUi
388 });
Simon Huntac4c6f72015-02-03 19:50:53 -0800389 }
390
Simon Hunt1894d792015-02-04 17:09:20 -0800391
Simon Huntac4c6f72015-02-03 19:50:53 -0800392 function mkSvgClass(d) {
393 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
394 }
395
Simon Hunt5724fb42015-02-05 16:59:40 -0800396 function vis(b) {
397 return b ? 'visible' : 'hidden';
398 }
399
Simon Huntfcbde892015-04-16 12:05:28 -0700400 function toggleHosts(x) {
401 var kev = (x === 'keyev'),
402 on = kev ? !showHosts : !!x;
403
404 showHosts = on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800405 updateHostVisibility();
Simon Huntfcbde892015-04-16 12:05:28 -0700406 flash.flash('Hosts ' + vis(on));
407 return on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800408 }
409
Simon Huntfcbde892015-04-16 12:05:28 -0700410 function toggleOffline(x) {
411 var kev = (x === 'keyev'),
412 on = kev ? !showOffline : !!x;
413
414 showOffline = on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800415 updateOfflineVisibility();
Simon Huntfcbde892015-04-16 12:05:28 -0700416 flash.flash('Offline devices ' + vis(on));
417 return on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800418 }
419
420 function cycleDeviceLabels() {
Bri Prebilic Cole9cf1a8d2015-04-21 13:15:29 -0700421 flash.flash(td3.incDevLabIndex());
Simon Huntdc6adea2015-02-09 22:29:36 -0800422 tms.findDevices().forEach(function (d) {
Simon Hunta4242de2015-02-24 17:11:55 -0800423 td3.updateDeviceLabel(d);
Simon Hunt1c367112015-02-05 18:02:46 -0800424 });
Simon Hunt5724fb42015-02-05 16:59:40 -0800425 }
426
Simon Hunt445e8152015-02-06 13:00:12 -0800427 function unpin() {
Simon Hunt08f841d02015-02-10 14:39:20 -0800428 var hov = tss.hovered();
429 if (hov) {
430 sendUpdateMeta(hov, true);
431 hov.fixed = false;
432 hov.el.classed('fixed', false);
Simon Hunt445e8152015-02-06 13:00:12 -0800433 fResume();
434 }
435 }
436
Simon Hunta142dd22015-02-12 22:07:51 -0800437 function showMastership(masterId) {
438 if (!masterId) {
439 restoreLayerState();
440 } else {
441 showMastershipFor(masterId);
442 }
443 }
444
445 function restoreLayerState() {
446 // NOTE: this level of indirection required, for when we have
447 // the layer filter functionality re-implemented
448 suppressLayers(false);
449 }
450
451 function showMastershipFor(id) {
452 suppressLayers(true);
453 node.each(function (n) {
454 if (n.master === id) {
455 n.el.classed('suppressed', false);
456 }
457 });
458 }
459
460 function suppressLayers(b) {
461 node.classed('suppressed', b);
462 link.classed('suppressed', b);
463// d3.selectAll('svg .port').classed('inactive', b);
464// d3.selectAll('svg .portText').classed('inactive', b);
465 }
Simon Hunt445e8152015-02-06 13:00:12 -0800466
Simon Hunt86b7c882015-04-02 23:06:08 -0700467 function showBadLinks() {
468 var badLinks = tms.findBadLinks();
469 flash.flash('Bad Links: ' + badLinks.length);
470 $log.debug('Bad Link List (' + badLinks.length + '):');
471 badLinks.forEach(function (d) {
472 $log.debug('bad link: (' + d.bad + ') ' + d.key, d);
473 if (d.el) {
474 d.el.attr('stroke-width', linkScale(2.8))
475 .attr('stroke', 'red');
476 }
477 });
478 // back to normal after 2 seconds...
479 $timeout(updateLinks, 2000);
480 }
481
Simon Hunt5724fb42015-02-05 16:59:40 -0800482 // ==========================================
483
Simon Huntac4c6f72015-02-03 19:50:53 -0800484 function updateNodes() {
Thomas Vachuska1a989c12015-06-09 18:29:22 -0700485 if (fNodesTimer) {
486 $timeout.cancel(fNodesTimer);
487 }
488 fNodesTimer = $timeout(_updateNodes, 150);
489 }
490
491 function _updateNodes() {
Simon Hunt1894d792015-02-04 17:09:20 -0800492 // select all the nodes in the layout:
Simon Huntac4c6f72015-02-03 19:50:53 -0800493 node = nodeG.selectAll('.node')
494 .data(network.nodes, function (d) { return d.id; });
495
Simon Hunt1894d792015-02-04 17:09:20 -0800496 // operate on existing nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800497 node.filter('.device').each(td3.deviceExisting);
498 node.filter('.host').each(td3.hostExisting);
Simon Huntac4c6f72015-02-03 19:50:53 -0800499
500 // operate on entering nodes:
501 var entering = node.enter()
502 .append('g')
503 .attr({
504 id: function (d) { return sus.safeId(d.id); },
505 class: mkSvgClass,
506 transform: function (d) { return sus.translate(d.x, d.y); },
507 opacity: 0
508 })
509 .call(drag)
Simon Hunt08f841d02015-02-10 14:39:20 -0800510 .on('mouseover', tss.nodeMouseOver)
511 .on('mouseout', tss.nodeMouseOut)
Simon Huntac4c6f72015-02-03 19:50:53 -0800512 .transition()
513 .attr('opacity', 1);
514
Simon Hunt1894d792015-02-04 17:09:20 -0800515 // augment entering nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800516 entering.filter('.device').each(td3.deviceEnter);
517 entering.filter('.host').each(td3.hostEnter);
Simon Huntac4c6f72015-02-03 19:50:53 -0800518
Simon Hunt51056592015-02-03 21:48:07 -0800519 // operate on both existing and new nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800520 td3.updateDeviceColors();
Simon Huntac4c6f72015-02-03 19:50:53 -0800521
522 // operate on exiting nodes:
523 // Note that the node is removed after 2 seconds.
524 // Sub element animations should be shorter than 2 seconds.
525 var exiting = node.exit()
526 .transition()
527 .duration(2000)
528 .style('opacity', 0)
529 .remove();
530
Simon Hunt1894d792015-02-04 17:09:20 -0800531 // exiting node specifics:
Simon Hunta4242de2015-02-24 17:11:55 -0800532 exiting.filter('.host').each(td3.hostExit);
533 exiting.filter('.device').each(td3.deviceExit);
Simon Huntac4c6f72015-02-03 19:50:53 -0800534 }
535
Simon Hunt51056592015-02-03 21:48:07 -0800536 // ==========================
Simon Hunt1894d792015-02-04 17:09:20 -0800537
538 function updateLinks() {
Thomas Vachuska1a989c12015-06-09 18:29:22 -0700539 if (fLinksTimer) {
540 $timeout.cancel(fLinksTimer);
541 }
542 fLinksTimer = $timeout(_updateLinks, 150);
543 }
544
545 function _updateLinks() {
Simon Hunt1894d792015-02-04 17:09:20 -0800546 var th = ts.theme();
547
548 link = linkG.selectAll('.link')
549 .data(network.links, function (d) { return d.key; });
550
551 // operate on existing links:
Simon Huntd5264122015-02-25 10:17:43 -0800552 link.each(function (d) {
553 // this is supposed to be an existing link, but we have observed
554 // occasions (where links are deleted and added rapidly?) where
555 // the DOM element has not been defined. So protect against that...
556 if (d.el) {
557 restyleLinkElement(d, true);
558 }
559 });
Simon Hunt1894d792015-02-04 17:09:20 -0800560
561 // operate on entering links:
562 var entering = link.enter()
563 .append('line')
564 .attr({
Simon Huntd5264122015-02-25 10:17:43 -0800565 x1: function (d) { return d.source.x; },
566 y1: function (d) { return d.source.y; },
567 x2: function (d) { return d.target.x; },
568 y2: function (d) { return d.target.y; },
Simon Hunt1894d792015-02-04 17:09:20 -0800569 stroke: linkConfig[th].inColor,
570 'stroke-width': linkConfig.inWidth
571 });
572
573 // augment links
Simon Hunta4242de2015-02-24 17:11:55 -0800574 entering.each(td3.linkEntering);
Simon Hunt1894d792015-02-04 17:09:20 -0800575
576 // operate on both existing and new links:
577 //link.each(...)
578
579 // apply or remove labels
Simon Hunta4242de2015-02-24 17:11:55 -0800580 td3.applyLinkLabels();
Simon Hunt1894d792015-02-04 17:09:20 -0800581
582 // operate on exiting links:
583 link.exit()
584 .attr('stroke-dasharray', '3 3')
Simon Hunt5724fb42015-02-05 16:59:40 -0800585 .attr('stroke', linkConfig[th].outColor)
Simon Hunt1894d792015-02-04 17:09:20 -0800586 .style('opacity', 0.5)
587 .transition()
588 .duration(1500)
589 .attr({
590 'stroke-dasharray': '3 12',
Simon Hunt1894d792015-02-04 17:09:20 -0800591 'stroke-width': linkConfig.outWidth
592 })
593 .style('opacity', 0.0)
594 .remove();
Simon Hunt1894d792015-02-04 17:09:20 -0800595 }
596
Simon Huntac4c6f72015-02-03 19:50:53 -0800597
598 // ==========================
Simon Hunt737c89f2015-01-28 12:23:19 -0800599 // force layout tick function
Simon Hunt737c89f2015-01-28 12:23:19 -0800600
Simon Hunt5724fb42015-02-05 16:59:40 -0800601 function fResume() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800602 if (!tos.isOblique()) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800603 force.resume();
604 }
605 }
606
607 function fStart() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800608 if (!tos.isOblique()) {
Thomas Vachuska1a989c12015-06-09 18:29:22 -0700609 if (fTimer) {
610 $timeout.cancel(fTimer);
611 }
612 fTimer = $timeout(function () {
613 $log.debug("Starting force-layout");
614 force.start();
615 }, 200);
Simon Hunt5724fb42015-02-05 16:59:40 -0800616 }
617 }
618
619 var tickStuff = {
620 nodeAttr: {
621 transform: function (d) { return sus.translate(d.x, d.y); }
622 },
623 linkAttr: {
624 x1: function (d) { return d.source.x; },
625 y1: function (d) { return d.source.y; },
626 x2: function (d) { return d.target.x; },
627 y2: function (d) { return d.target.y; }
628 },
629 linkLabelAttr: {
630 transform: function (d) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800631 var lnk = tms.findLinkById(d.key);
Simon Hunt5724fb42015-02-05 16:59:40 -0800632 if (lnk) {
Simon Hunta4242de2015-02-24 17:11:55 -0800633 return td3.transformLabel({
Simon Hunt5724fb42015-02-05 16:59:40 -0800634 x1: lnk.source.x,
635 y1: lnk.source.y,
636 x2: lnk.target.x,
637 y2: lnk.target.y
638 });
639 }
640 }
641 }
642 };
643
644 function tick() {
Simon Hunt3ab20282015-02-26 20:32:19 -0800645 // guard against null (which can happen when our view pages out)...
646 if (node) node.attr(tickStuff.nodeAttr);
647 if (link) link.attr(tickStuff.linkAttr);
648 if (linkLabel) linkLabel.attr(tickStuff.linkLabelAttr);
Simon Hunt737c89f2015-01-28 12:23:19 -0800649 }
650
651
Simon Huntac4c6f72015-02-03 19:50:53 -0800652 // ==========================
653 // === MOUSE GESTURE HANDLERS
654
Simon Hunt205099e2015-02-07 13:12:01 -0800655 function zoomingOrPanning(ev) {
656 return ev.metaKey || ev.altKey;
Simon Hunt445e8152015-02-06 13:00:12 -0800657 }
658
659 function atDragEnd(d) {
660 // once we've finished moving, pin the node in position
661 d.fixed = true;
662 d3.select(this).classed('fixed', true);
663 sendUpdateMeta(d);
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700664 tss.clickConsumed(true);
Simon Hunt445e8152015-02-06 13:00:12 -0800665 }
666
667 // predicate that indicates when dragging is active
668 function dragEnabled() {
669 var ev = d3.event.sourceEvent;
670 // nodeLock means we aren't allowing nodes to be dragged...
Simon Hunt205099e2015-02-07 13:12:01 -0800671 return !nodeLock && !zoomingOrPanning(ev);
Simon Hunt445e8152015-02-06 13:00:12 -0800672 }
673
674 // predicate that indicates when clicking is active
675 function clickEnabled() {
676 return true;
677 }
Simon Hunt737c89f2015-01-28 12:23:19 -0800678
Simon Huntf542d842015-02-11 16:20:33 -0800679 // ==========================
680 // function entry points for traffic module
681
Simon Hunte50829c2015-06-09 08:39:28 -0700682 var allTrafficClasses = 'primary secondary optical animated ' +
683 'port-traffic-Kbps port-traffic-Mbps port-traffic-Gbps ' +
684 'port-traffic-Gbps-choked';
Simon Huntf542d842015-02-11 16:20:33 -0800685
686 function clearLinkTrafficStyle() {
687 link.style('stroke-width', null)
688 .classed(allTrafficClasses, false);
689 }
690
691 function removeLinkLabels() {
692 network.links.forEach(function (d) {
693 d.label = '';
694 });
695 }
Simon Hunt737c89f2015-01-28 12:23:19 -0800696
Simon Hunta4242de2015-02-24 17:11:55 -0800697 function updateLinkLabelModel() {
698 // create the backing data for showing labels..
699 var data = [];
700 link.each(function (d) {
701 if (d.label) {
702 data.push({
703 id: 'lab-' + d.key,
704 key: d.key,
705 label: d.label,
706 ldata: d
707 });
708 }
709 });
710
711 linkLabel = linkLabelG.selectAll('.linkLabel')
712 .data(data, function (d) { return d.id; });
713 }
714
Simon Hunt737c89f2015-01-28 12:23:19 -0800715 // ==========================
Simon Huntac4c6f72015-02-03 19:50:53 -0800716 // Module definition
Simon Hunt737c89f2015-01-28 12:23:19 -0800717
Simon Huntdc6adea2015-02-09 22:29:36 -0800718 function mkModelApi(uplink) {
719 return {
720 projection: uplink.projection,
721 network: network,
722 restyleLinkElement: restyleLinkElement,
723 removeLinkElement: removeLinkElement
724 };
725 }
726
Simon Hunta4242de2015-02-24 17:11:55 -0800727 function mkD3Api(uplink) {
728 return {
729 node: function () { return node; },
730 link: function () { return link; },
731 linkLabel: function () { return linkLabel; },
732 instVisible: function () { return tis.isVisible(); },
733 posNode: tms.positionNode,
734 showHosts: function () { return showHosts; },
735 restyleLinkElement: restyleLinkElement,
736 updateLinkLabelModel: updateLinkLabelModel
737 }
738 }
739
Simon Hunt08f841d02015-02-10 14:39:20 -0800740 function mkSelectApi(uplink) {
741 return {
742 node: function () { return node; },
743 zoomingOrPanning: zoomingOrPanning,
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700744 updateDeviceColors: td3.updateDeviceColors,
745 deselectLink: tls.deselectLink
Simon Hunt08f841d02015-02-10 14:39:20 -0800746 };
747 }
748
Simon Huntf542d842015-02-11 16:20:33 -0800749 function mkTrafficApi(uplink) {
750 return {
751 clearLinkTrafficStyle: clearLinkTrafficStyle,
752 removeLinkLabels: removeLinkLabels,
753 updateLinks: updateLinks,
754 findLinkById: tms.findLinkById,
755 hovered: tss.hovered,
756 validateSelectionContext: tss.validateSelectionContext,
Simon Hunt237676b52015-03-10 19:04:26 -0700757 selectOrder: tss.selectOrder
Simon Huntf542d842015-02-11 16:20:33 -0800758 }
759 }
760
Simon Huntc3c5b672015-02-20 11:32:13 -0800761 function mkObliqueApi(uplink, fltr) {
Simon Hunt96f88c62015-02-19 17:57:25 -0800762 return {
Simon Huntc3c5b672015-02-20 11:32:13 -0800763 force: function() { return force; },
764 zoomLayer: uplink.zoomLayer,
765 nodeGBBox: function() { return nodeG.node().getBBox(); },
Simon Hunt96f88c62015-02-19 17:57:25 -0800766 node: function () { return node; },
Simon Huntc3c5b672015-02-20 11:32:13 -0800767 link: function () { return link; },
768 linkLabel: function () { return linkLabel; },
769 nodes: function () { return network.nodes; },
770 tickStuff: tickStuff,
771 nodeLock: function (b) {
772 var old = nodeLock;
773 nodeLock = b;
774 return old;
775 },
776 opacifyMap: uplink.opacifyMap,
777 inLayer: fltr.inLayer
Simon Hunt96f88c62015-02-19 17:57:25 -0800778 };
779 }
780
Simon Hunteb0fa052015-02-17 19:20:28 -0800781 function mkFilterApi(uplink) {
782 return {
783 node: function () { return node; },
784 link: function () { return link; }
785 };
786 }
787
Simon Hunt9e2104c2015-02-26 10:48:59 -0800788 function mkLinkApi(svg, uplink) {
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800789 return {
790 svg: svg,
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800791 zoomer: uplink.zoomer(),
792 network: network,
Simon Hunt1a5301e2015-02-25 15:31:25 -0800793 portLabelG: function () { return portLabelG; },
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800794 showHosts: function () { return showHosts; }
795 };
796 }
797
Simon Hunt737c89f2015-01-28 12:23:19 -0800798 angular.module('ovTopo')
799 .factory('TopoForceService',
Simon Hunt86b7c882015-04-02 23:06:08 -0700800 ['$log', '$timeout', 'FnService', 'SvgUtilService', 'IconService',
801 'ThemeService', 'FlashService', 'WebSocketService',
Simon Hunt237676b52015-03-10 19:04:26 -0700802 'TopoInstService', 'TopoModelService',
Simon Hunta4242de2015-02-24 17:11:55 -0800803 'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800804 'TopoObliqueService', 'TopoFilterService', 'TopoLinkService',
Simon Hunt737c89f2015-01-28 12:23:19 -0800805
Simon Hunt86b7c882015-04-02 23:06:08 -0700806 function (_$log_, _$timeout_, _fs_, _sus_, _is_, _ts_, _flash_, _wss_,
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800807 _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) {
Simon Hunt737c89f2015-01-28 12:23:19 -0800808 $log = _$log_;
Simon Hunt86b7c882015-04-02 23:06:08 -0700809 $timeout = _$timeout_;
Simon Hunt1894d792015-02-04 17:09:20 -0800810 fs = _fs_;
Simon Hunt737c89f2015-01-28 12:23:19 -0800811 sus = _sus_;
Simon Huntac4c6f72015-02-03 19:50:53 -0800812 is = _is_;
813 ts = _ts_;
Simon Hunt5724fb42015-02-05 16:59:40 -0800814 flash = _flash_;
Simon Hunt237676b52015-03-10 19:04:26 -0700815 wss = _wss_;
Simon Huntac4c6f72015-02-03 19:50:53 -0800816 tis = _tis_;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800817 tms = _tms_;
Simon Hunta4242de2015-02-24 17:11:55 -0800818 td3 = _td3_;
Simon Hunt08f841d02015-02-10 14:39:20 -0800819 tss = _tss_;
Simon Huntf542d842015-02-11 16:20:33 -0800820 tts = _tts_;
Simon Hunt96f88c62015-02-19 17:57:25 -0800821 tos = _tos_;
Simon Hunteb0fa052015-02-17 19:20:28 -0800822 fltr = _fltr_;
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800823 tls = _tls_;
Simon Hunt737c89f2015-01-28 12:23:19 -0800824
Simon Hunt1894d792015-02-04 17:09:20 -0800825 icfg = is.iconConfig();
826
Simon Hunta142dd22015-02-12 22:07:51 -0800827 var themeListener = ts.addListener(function () {
828 updateLinks();
829 updateNodes();
830 });
831
Simon Hunt737c89f2015-01-28 12:23:19 -0800832 // forceG is the SVG group to display the force layout in
Simon Huntdc6adea2015-02-09 22:29:36 -0800833 // uplink is the api from the main topo source file
Simon Hunt3a6eec02015-02-09 21:16:43 -0800834 // dim is the initial dimensions of the SVG as [w,h]
Simon Hunt737c89f2015-01-28 12:23:19 -0800835 // opts are, well, optional :)
Simon Hunt3ab20282015-02-26 20:32:19 -0800836 function initForce(_svg_, forceG, _uplink_, _dim_, opts) {
Simon Hunt1894d792015-02-04 17:09:20 -0800837 uplink = _uplink_;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800838 dim = _dim_;
Simon Hunt3ab20282015-02-26 20:32:19 -0800839 svg = _svg_;
840
841 lu = network.lookup;
842 rlk = network.revLinkToKey;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800843
844 $log.debug('initForce().. dim = ' + dim);
845
Simon Huntdc6adea2015-02-09 22:29:36 -0800846 tms.initModel(mkModelApi(uplink), dim);
Simon Hunta4242de2015-02-24 17:11:55 -0800847 td3.initD3(mkD3Api(uplink));
Simon Hunt08f841d02015-02-10 14:39:20 -0800848 tss.initSelect(mkSelectApi(uplink));
Simon Huntf542d842015-02-11 16:20:33 -0800849 tts.initTraffic(mkTrafficApi(uplink));
Simon Huntc3c5b672015-02-20 11:32:13 -0800850 tos.initOblique(mkObliqueApi(uplink, fltr));
Bri Prebilic Coleb5f2b152015-04-07 14:58:09 -0700851 fltr.initFilter(mkFilterApi(uplink));
Simon Hunt9e2104c2015-02-26 10:48:59 -0800852 tls.initLink(mkLinkApi(svg, uplink), td3);
Simon Hunta11b4eb2015-01-28 16:20:50 -0800853
Simon Hunt737c89f2015-01-28 12:23:19 -0800854 settings = angular.extend({}, defaultSettings, opts);
855
856 linkG = forceG.append('g').attr('id', 'topo-links');
857 linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
858 nodeG = forceG.append('g').attr('id', 'topo-nodes');
Simon Hunt1a5301e2015-02-25 15:31:25 -0800859 portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
Simon Hunt737c89f2015-01-28 12:23:19 -0800860
861 link = linkG.selectAll('.link');
862 linkLabel = linkLabelG.selectAll('.linkLabel');
863 node = nodeG.selectAll('.node');
864
865 force = d3.layout.force()
Simon Hunt3a6eec02015-02-09 21:16:43 -0800866 .size(dim)
Simon Hunt737c89f2015-01-28 12:23:19 -0800867 .nodes(network.nodes)
868 .links(network.links)
869 .gravity(settings.gravity)
870 .friction(settings.friction)
871 .charge(settings.charge._def_)
872 .linkDistance(settings.linkDistance._def_)
873 .linkStrength(settings.linkStrength._def_)
874 .on('tick', tick);
875
876 drag = sus.createDragBehavior(force,
Simon Hunt08f841d02015-02-10 14:39:20 -0800877 tss.selectObject, atDragEnd, dragEnabled, clickEnabled);
Simon Hunt737c89f2015-01-28 12:23:19 -0800878 }
879
Simon Hunt3a6eec02015-02-09 21:16:43 -0800880 function newDim(_dim_) {
881 dim = _dim_;
882 force.size(dim);
883 tms.newDim(dim);
Simon Hunt737c89f2015-01-28 12:23:19 -0800884 }
885
Simon Hunt3a6eec02015-02-09 21:16:43 -0800886 function destroyForce() {
Simon Hunt3ab20282015-02-26 20:32:19 -0800887 force.stop();
888
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800889 tls.destroyLink();
Simon Hunt96f88c62015-02-19 17:57:25 -0800890 tos.destroyOblique();
Simon Huntf542d842015-02-11 16:20:33 -0800891 tts.destroyTraffic();
892 tss.destroySelect();
Simon Hunta4242de2015-02-24 17:11:55 -0800893 td3.destroyD3();
Simon Huntf542d842015-02-11 16:20:33 -0800894 tms.destroyModel();
Simon Hunta142dd22015-02-12 22:07:51 -0800895 ts.removeListener(themeListener);
896 themeListener = null;
Simon Hunt3ab20282015-02-26 20:32:19 -0800897
898 // clean up the DOM
899 svg.selectAll('g').remove();
900 svg.selectAll('defs').remove();
901
902 // clean up internal state
903 network.nodes = [];
904 network.links = [];
905 network.lookup = {};
906 network.revLinkToKey = {};
907
908 linkG = linkLabelG = nodeG = portLabelG = null;
909 link = linkLabel = node = null;
910 force = drag = null;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800911 }
912
Simon Hunt737c89f2015-01-28 12:23:19 -0800913 return {
914 initForce: initForce,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800915 newDim: newDim,
916 destroyForce: destroyForce,
Simon Huntac4c6f72015-02-03 19:50:53 -0800917
Simon Hunta4242de2015-02-24 17:11:55 -0800918 updateDeviceColors: td3.updateDeviceColors,
Simon Hunt5724fb42015-02-05 16:59:40 -0800919 toggleHosts: toggleHosts,
Simon Hunt9e2104c2015-02-26 10:48:59 -0800920 togglePorts: tls.togglePorts,
Simon Hunt5724fb42015-02-05 16:59:40 -0800921 toggleOffline: toggleOffline,
922 cycleDeviceLabels: cycleDeviceLabels,
Simon Hunt445e8152015-02-06 13:00:12 -0800923 unpin: unpin,
Simon Hunta142dd22015-02-12 22:07:51 -0800924 showMastership: showMastership,
Simon Hunt86b7c882015-04-02 23:06:08 -0700925 showBadLinks: showBadLinks,
Simon Huntac4c6f72015-02-03 19:50:53 -0800926
927 addDevice: addDevice,
Simon Hunt1894d792015-02-04 17:09:20 -0800928 updateDevice: updateDevice,
929 removeDevice: removeDevice,
930 addHost: addHost,
931 updateHost: updateHost,
932 removeHost: removeHost,
933 addLink: addLink,
934 updateLink: updateLink,
935 removeLink: removeLink
Simon Hunt737c89f2015-01-28 12:23:19 -0800936 };
937 }]);
938}());