blob: 0bc0d5c5d0293c43f061cfdf9aa0a6c62a1f1a07 [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)
Simon Hunt08f841d02015-02-10 14:39:20 -080061 dim; // the dimensions of the force layout [w,h]
Simon Hunt737c89f2015-01-28 12:23:19 -080062
63 // SVG elements;
Simon Hunt1a5301e2015-02-25 15:31:25 -080064 var linkG, linkLabelG, portLabelG, nodeG;
Simon Hunt737c89f2015-01-28 12:23:19 -080065
66 // D3 selections;
67 var link, linkLabel, node;
68
69 // default settings for force layout
70 var defaultSettings = {
71 gravity: 0.4,
72 friction: 0.7,
73 charge: {
74 // note: key is node.class
75 device: -8000,
76 host: -5000,
77 _def_: -12000
78 },
79 linkDistance: {
80 // note: key is link.type
81 direct: 100,
82 optical: 120,
83 hostLink: 3,
84 _def_: 50
85 },
86 linkStrength: {
87 // note: key is link.type
88 // range: {0.0 ... 1.0}
89 //direct: 1.0,
90 //optical: 1.0,
91 //hostLink: 1.0,
92 _def_: 1.0
93 }
94 };
95
96
Simon Huntac4c6f72015-02-03 19:50:53 -080097 // ==========================
98 // === EVENT HANDLERS
99
100 function addDevice(data) {
101 var id = data.id,
102 d;
103
Simon Hunt1894d792015-02-04 17:09:20 -0800104 uplink.showNoDevs(false);
Simon Huntac4c6f72015-02-03 19:50:53 -0800105
106 // although this is an add device event, if we already have the
107 // device, treat it as an update instead..
Simon Hunt1894d792015-02-04 17:09:20 -0800108 if (lu[id]) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800109 updateDevice(data);
110 return;
111 }
112
Simon Hunt3a6eec02015-02-09 21:16:43 -0800113 d = tms.createDeviceNode(data);
Simon Huntac4c6f72015-02-03 19:50:53 -0800114 network.nodes.push(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800115 lu[id] = d;
Simon Huntac4c6f72015-02-03 19:50:53 -0800116 updateNodes();
117 fStart();
118 }
119
120 function updateDevice(data) {
121 var id = data.id,
Simon Hunt1894d792015-02-04 17:09:20 -0800122 d = lu[id],
Simon Huntac4c6f72015-02-03 19:50:53 -0800123 wasOnline;
124
125 if (d) {
126 wasOnline = d.online;
127 angular.extend(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800128 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800129 sendUpdateMeta(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800130 }
131 updateNodes();
132 if (wasOnline !== d.online) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800133 tms.findAttachedLinks(d.id).forEach(restyleLinkElement);
Simon Hunt5724fb42015-02-05 16:59:40 -0800134 updateOfflineVisibility(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800135 }
Simon Huntac4c6f72015-02-03 19:50:53 -0800136 }
137 }
138
Simon Hunt1894d792015-02-04 17:09:20 -0800139 function removeDevice(data) {
140 var id = data.id,
141 d = lu[id];
142 if (d) {
143 removeDeviceElement(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800144 }
145 }
146
147 function addHost(data) {
148 var id = data.id,
149 d, lnk;
150
151 // although this is an add host event, if we already have the
152 // host, treat it as an update instead..
153 if (lu[id]) {
154 updateHost(data);
155 return;
156 }
157
Simon Hunt3a6eec02015-02-09 21:16:43 -0800158 d = tms.createHostNode(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800159 network.nodes.push(d);
160 lu[id] = d;
Simon Hunt1894d792015-02-04 17:09:20 -0800161 updateNodes();
162
Simon Hunt3a6eec02015-02-09 21:16:43 -0800163 lnk = tms.createHostLink(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800164 if (lnk) {
Simon Hunt1894d792015-02-04 17:09:20 -0800165 d.linkData = lnk; // cache ref on its host
166 network.links.push(lnk);
167 lu[d.ingress] = lnk;
168 lu[d.egress] = lnk;
169 updateLinks();
170 }
171
172 fStart();
173 }
174
175 function updateHost(data) {
176 var id = data.id,
177 d = lu[id];
178 if (d) {
179 angular.extend(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800180 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800181 sendUpdateMeta(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800182 }
183 updateNodes();
Simon Hunt1894d792015-02-04 17:09:20 -0800184 }
185 }
186
187 function removeHost(data) {
188 var id = data.id,
189 d = lu[id];
190 if (d) {
191 removeHostElement(d, true);
Simon Hunt1894d792015-02-04 17:09:20 -0800192 }
193 }
194
195 function addLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800196 var result = tms.findLink(data, 'add'),
Simon Hunt1894d792015-02-04 17:09:20 -0800197 bad = result.badLogic,
198 d = result.ldata;
199
200 if (bad) {
201 //logicError(bad + ': ' + link.id);
202 return;
203 }
204
205 if (d) {
206 // we already have a backing store link for src/dst nodes
207 addLinkUpdate(d, data);
208 return;
209 }
210
211 // no backing store link yet
Simon Hunt3a6eec02015-02-09 21:16:43 -0800212 d = tms.createLink(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800213 if (d) {
214 network.links.push(d);
215 lu[d.key] = d;
216 updateLinks();
217 fStart();
218 }
219 }
220
221 function updateLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800222 var result = tms.findLink(data, 'update'),
Simon Hunt1894d792015-02-04 17:09:20 -0800223 bad = result.badLogic;
224 if (bad) {
225 //logicError(bad + ': ' + link.id);
226 return;
227 }
228 result.updateWith(link);
229 }
230
231 function removeLink(data) {
Simon Hunta4242de2015-02-24 17:11:55 -0800232 var result = tms.findLink(data, 'remove');
233
234 if (!result.badLogic) {
235 result.removeRawLink();
Simon Hunt1894d792015-02-04 17:09:20 -0800236 }
Simon Hunt1894d792015-02-04 17:09:20 -0800237 }
238
239 // ========================
240
241 function addLinkUpdate(ldata, link) {
242 // add link event, but we already have the reverse link installed
243 ldata.fromTarget = link;
Simon Huntdc6adea2015-02-09 22:29:36 -0800244 rlk[link.id] = ldata.key;
Simon Hunt1894d792015-02-04 17:09:20 -0800245 restyleLinkElement(ldata);
246 }
247
Simon Hunt1894d792015-02-04 17:09:20 -0800248
249 var widthRatio = 1.4,
250 linkScale = d3.scale.linear()
251 .domain([1, 12])
252 .range([widthRatio, 12 * widthRatio])
Simon Hunt5724fb42015-02-05 16:59:40 -0800253 .clamp(true),
Simon Hunt3a6eec02015-02-09 21:16:43 -0800254 allLinkTypes = 'direct indirect optical tunnel';
Simon Hunt1894d792015-02-04 17:09:20 -0800255
Simon Hunta142dd22015-02-12 22:07:51 -0800256 function restyleLinkElement(ldata, immediate) {
Simon Hunt1894d792015-02-04 17:09:20 -0800257 // this fn's job is to look at raw links and decide what svg classes
258 // need to be applied to the line element in the DOM
259 var th = ts.theme(),
260 el = ldata.el,
261 type = ldata.type(),
262 lw = ldata.linkWidth(),
Simon Hunta142dd22015-02-12 22:07:51 -0800263 online = ldata.online(),
264 delay = immediate ? 0 : 1000;
Simon Hunt1894d792015-02-04 17:09:20 -0800265
Thomas Vachuskacb5016f2015-05-18 14:11:43 -0700266 // FIXME: understand why el is sometimes undefined on addLink events...
267 if (el) {
268 el.classed('link', true);
269 el.classed('inactive', !online);
270 el.classed(allLinkTypes, false);
271 if (type) {
272 el.classed(type, true);
273 }
274 el.transition()
275 .duration(delay)
276 .attr('stroke-width', linkScale(lw))
277 .attr('stroke', linkConfig[th].baseColor);
Simon Hunt1894d792015-02-04 17:09:20 -0800278 }
Simon Hunt1894d792015-02-04 17:09:20 -0800279 }
280
Simon Hunt1894d792015-02-04 17:09:20 -0800281 function removeLinkElement(d) {
282 var idx = fs.find(d.key, network.links, 'key'),
283 removed;
284 if (idx >=0) {
285 // remove from links array
286 removed = network.links.splice(idx, 1);
287 // remove from lookup cache
288 delete lu[removed[0].key];
289 updateLinks();
290 fResume();
291 }
292 }
293
294 function removeHostElement(d, upd) {
295 // first, remove associated hostLink...
296 removeLinkElement(d.linkData);
297
298 // remove hostLink bindings
299 delete lu[d.ingress];
300 delete lu[d.egress];
301
302 // remove from lookup cache
303 delete lu[d.id];
304 // remove from nodes array
305 var idx = fs.find(d.id, network.nodes);
306 network.nodes.splice(idx, 1);
307
308 // remove from SVG
309 // NOTE: upd is false if we were called from removeDeviceElement()
310 if (upd) {
311 updateNodes();
312 fResume();
313 }
314 }
315
316 function removeDeviceElement(d) {
317 var id = d.id;
318 // first, remove associated hosts and links..
Simon Huntdc6adea2015-02-09 22:29:36 -0800319 tms.findAttachedHosts(id).forEach(removeHostElement);
320 tms.findAttachedLinks(id).forEach(removeLinkElement);
Simon Hunt1894d792015-02-04 17:09:20 -0800321
322 // remove from lookup cache
323 delete lu[id];
324 // remove from nodes array
325 var idx = fs.find(id, network.nodes);
326 network.nodes.splice(idx, 1);
327
328 if (!network.nodes.length) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800329 uplink.showNoDevs(true);
Simon Hunt1894d792015-02-04 17:09:20 -0800330 }
331
332 // remove from SVG
333 updateNodes();
334 fResume();
335 }
336
Simon Hunt5724fb42015-02-05 16:59:40 -0800337 function updateHostVisibility() {
Simon Hunt18bf9822015-02-12 17:35:45 -0800338 sus.visible(nodeG.selectAll('.host'), showHosts);
339 sus.visible(linkG.selectAll('.hostLink'), showHosts);
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800340 sus.visible(linkLabelG.selectAll('.hostLinkLabel'), showHosts);
Simon Hunt5724fb42015-02-05 16:59:40 -0800341 }
342
343 function updateOfflineVisibility(dev) {
344 function updDev(d, show) {
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800345 var b;
Simon Hunt18bf9822015-02-12 17:35:45 -0800346 sus.visible(d.el, show);
Simon Hunt5724fb42015-02-05 16:59:40 -0800347
Simon Huntdc6adea2015-02-09 22:29:36 -0800348 tms.findAttachedLinks(d.id).forEach(function (link) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800349 b = show && ((link.type() !== 'hostLink') || showHosts);
Simon Hunt18bf9822015-02-12 17:35:45 -0800350 sus.visible(link.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800351 });
Simon Huntdc6adea2015-02-09 22:29:36 -0800352 tms.findAttachedHosts(d.id).forEach(function (host) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800353 b = show && showHosts;
Simon Hunt18bf9822015-02-12 17:35:45 -0800354 sus.visible(host.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800355 });
356 }
357
358 if (dev) {
359 // updating a specific device that just toggled off/on-line
360 updDev(dev, dev.online || showOffline);
361 } else {
362 // updating all offline devices
Simon Huntdc6adea2015-02-09 22:29:36 -0800363 tms.findDevices(true).forEach(function (d) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800364 updDev(d, showOffline);
365 });
366 }
367 }
368
Simon Hunt1894d792015-02-04 17:09:20 -0800369
Simon Hunt445e8152015-02-06 13:00:12 -0800370 function sendUpdateMeta(d, clearPos) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800371 var metaUi = {},
372 ll;
373
Simon Hunt445e8152015-02-06 13:00:12 -0800374 // if we are not clearing the position data (unpinning),
375 // attach the x, y, longitude, latitude...
376 if (!clearPos) {
Simon Hunt3a6eec02015-02-09 21:16:43 -0800377 ll = tms.lngLatFromCoord([d.x, d.y]);
Simon Huntdc6adea2015-02-09 22:29:36 -0800378 metaUi = {x: d.x, y: d.y, lng: ll[0], lat: ll[1]};
Simon Hunt1894d792015-02-04 17:09:20 -0800379 }
380 d.metaUi = metaUi;
Simon Hunt237676b52015-03-10 19:04:26 -0700381 wss.sendEvent('updateMeta', {
Simon Hunt1894d792015-02-04 17:09:20 -0800382 id: d.id,
383 'class': d.class,
384 memento: metaUi
385 });
Simon Huntac4c6f72015-02-03 19:50:53 -0800386 }
387
Simon Hunt1894d792015-02-04 17:09:20 -0800388
Simon Huntac4c6f72015-02-03 19:50:53 -0800389 function mkSvgClass(d) {
390 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
391 }
392
Simon Hunt5724fb42015-02-05 16:59:40 -0800393 function vis(b) {
394 return b ? 'visible' : 'hidden';
395 }
396
Simon Huntfcbde892015-04-16 12:05:28 -0700397 function toggleHosts(x) {
398 var kev = (x === 'keyev'),
399 on = kev ? !showHosts : !!x;
400
401 showHosts = on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800402 updateHostVisibility();
Simon Huntfcbde892015-04-16 12:05:28 -0700403 flash.flash('Hosts ' + vis(on));
404 return on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800405 }
406
Simon Huntfcbde892015-04-16 12:05:28 -0700407 function toggleOffline(x) {
408 var kev = (x === 'keyev'),
409 on = kev ? !showOffline : !!x;
410
411 showOffline = on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800412 updateOfflineVisibility();
Simon Huntfcbde892015-04-16 12:05:28 -0700413 flash.flash('Offline devices ' + vis(on));
414 return on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800415 }
416
417 function cycleDeviceLabels() {
Bri Prebilic Cole9cf1a8d2015-04-21 13:15:29 -0700418 flash.flash(td3.incDevLabIndex());
Simon Huntdc6adea2015-02-09 22:29:36 -0800419 tms.findDevices().forEach(function (d) {
Simon Hunta4242de2015-02-24 17:11:55 -0800420 td3.updateDeviceLabel(d);
Simon Hunt1c367112015-02-05 18:02:46 -0800421 });
Simon Hunt5724fb42015-02-05 16:59:40 -0800422 }
423
Simon Hunt445e8152015-02-06 13:00:12 -0800424 function unpin() {
Simon Hunt08f841d02015-02-10 14:39:20 -0800425 var hov = tss.hovered();
426 if (hov) {
427 sendUpdateMeta(hov, true);
428 hov.fixed = false;
429 hov.el.classed('fixed', false);
Simon Hunt445e8152015-02-06 13:00:12 -0800430 fResume();
431 }
432 }
433
Simon Hunta142dd22015-02-12 22:07:51 -0800434 function showMastership(masterId) {
435 if (!masterId) {
436 restoreLayerState();
437 } else {
438 showMastershipFor(masterId);
439 }
440 }
441
442 function restoreLayerState() {
443 // NOTE: this level of indirection required, for when we have
444 // the layer filter functionality re-implemented
445 suppressLayers(false);
446 }
447
448 function showMastershipFor(id) {
449 suppressLayers(true);
450 node.each(function (n) {
451 if (n.master === id) {
452 n.el.classed('suppressed', false);
453 }
454 });
455 }
456
457 function suppressLayers(b) {
458 node.classed('suppressed', b);
459 link.classed('suppressed', b);
460// d3.selectAll('svg .port').classed('inactive', b);
461// d3.selectAll('svg .portText').classed('inactive', b);
462 }
Simon Hunt445e8152015-02-06 13:00:12 -0800463
Simon Hunt86b7c882015-04-02 23:06:08 -0700464 function showBadLinks() {
465 var badLinks = tms.findBadLinks();
466 flash.flash('Bad Links: ' + badLinks.length);
467 $log.debug('Bad Link List (' + badLinks.length + '):');
468 badLinks.forEach(function (d) {
469 $log.debug('bad link: (' + d.bad + ') ' + d.key, d);
470 if (d.el) {
471 d.el.attr('stroke-width', linkScale(2.8))
472 .attr('stroke', 'red');
473 }
474 });
475 // back to normal after 2 seconds...
476 $timeout(updateLinks, 2000);
477 }
478
Simon Hunt5724fb42015-02-05 16:59:40 -0800479 // ==========================================
480
Simon Huntac4c6f72015-02-03 19:50:53 -0800481 function updateNodes() {
Simon Hunt1894d792015-02-04 17:09:20 -0800482 // select all the nodes in the layout:
Simon Huntac4c6f72015-02-03 19:50:53 -0800483 node = nodeG.selectAll('.node')
484 .data(network.nodes, function (d) { return d.id; });
485
Simon Hunt1894d792015-02-04 17:09:20 -0800486 // operate on existing nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800487 node.filter('.device').each(td3.deviceExisting);
488 node.filter('.host').each(td3.hostExisting);
Simon Huntac4c6f72015-02-03 19:50:53 -0800489
490 // operate on entering nodes:
491 var entering = node.enter()
492 .append('g')
493 .attr({
494 id: function (d) { return sus.safeId(d.id); },
495 class: mkSvgClass,
496 transform: function (d) { return sus.translate(d.x, d.y); },
497 opacity: 0
498 })
499 .call(drag)
Simon Hunt08f841d02015-02-10 14:39:20 -0800500 .on('mouseover', tss.nodeMouseOver)
501 .on('mouseout', tss.nodeMouseOut)
Simon Huntac4c6f72015-02-03 19:50:53 -0800502 .transition()
503 .attr('opacity', 1);
504
Simon Hunt1894d792015-02-04 17:09:20 -0800505 // augment entering nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800506 entering.filter('.device').each(td3.deviceEnter);
507 entering.filter('.host').each(td3.hostEnter);
Simon Huntac4c6f72015-02-03 19:50:53 -0800508
Simon Hunt51056592015-02-03 21:48:07 -0800509 // operate on both existing and new nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800510 td3.updateDeviceColors();
Simon Huntac4c6f72015-02-03 19:50:53 -0800511
512 // operate on exiting nodes:
513 // Note that the node is removed after 2 seconds.
514 // Sub element animations should be shorter than 2 seconds.
515 var exiting = node.exit()
516 .transition()
517 .duration(2000)
518 .style('opacity', 0)
519 .remove();
520
Simon Hunt1894d792015-02-04 17:09:20 -0800521 // exiting node specifics:
Simon Hunta4242de2015-02-24 17:11:55 -0800522 exiting.filter('.host').each(td3.hostExit);
523 exiting.filter('.device').each(td3.deviceExit);
Simon Huntac4c6f72015-02-03 19:50:53 -0800524 }
525
Simon Hunt51056592015-02-03 21:48:07 -0800526 // ==========================
Simon Hunt1894d792015-02-04 17:09:20 -0800527
528 function updateLinks() {
529 var th = ts.theme();
530
531 link = linkG.selectAll('.link')
532 .data(network.links, function (d) { return d.key; });
533
534 // operate on existing links:
Simon Huntd5264122015-02-25 10:17:43 -0800535 link.each(function (d) {
536 // this is supposed to be an existing link, but we have observed
537 // occasions (where links are deleted and added rapidly?) where
538 // the DOM element has not been defined. So protect against that...
539 if (d.el) {
540 restyleLinkElement(d, true);
541 }
542 });
Simon Hunt1894d792015-02-04 17:09:20 -0800543
544 // operate on entering links:
545 var entering = link.enter()
546 .append('line')
547 .attr({
Simon Huntd5264122015-02-25 10:17:43 -0800548 x1: function (d) { return d.source.x; },
549 y1: function (d) { return d.source.y; },
550 x2: function (d) { return d.target.x; },
551 y2: function (d) { return d.target.y; },
Simon Hunt1894d792015-02-04 17:09:20 -0800552 stroke: linkConfig[th].inColor,
553 'stroke-width': linkConfig.inWidth
554 });
555
556 // augment links
Simon Hunta4242de2015-02-24 17:11:55 -0800557 entering.each(td3.linkEntering);
Simon Hunt1894d792015-02-04 17:09:20 -0800558
559 // operate on both existing and new links:
560 //link.each(...)
561
562 // apply or remove labels
Simon Hunta4242de2015-02-24 17:11:55 -0800563 td3.applyLinkLabels();
Simon Hunt1894d792015-02-04 17:09:20 -0800564
565 // operate on exiting links:
566 link.exit()
567 .attr('stroke-dasharray', '3 3')
Simon Hunt5724fb42015-02-05 16:59:40 -0800568 .attr('stroke', linkConfig[th].outColor)
Simon Hunt1894d792015-02-04 17:09:20 -0800569 .style('opacity', 0.5)
570 .transition()
571 .duration(1500)
572 .attr({
573 'stroke-dasharray': '3 12',
Simon Hunt1894d792015-02-04 17:09:20 -0800574 'stroke-width': linkConfig.outWidth
575 })
576 .style('opacity', 0.0)
577 .remove();
Simon Hunt1894d792015-02-04 17:09:20 -0800578 }
579
Simon Huntac4c6f72015-02-03 19:50:53 -0800580
581 // ==========================
Simon Hunt737c89f2015-01-28 12:23:19 -0800582 // force layout tick function
Simon Hunt737c89f2015-01-28 12:23:19 -0800583
Simon Hunt5724fb42015-02-05 16:59:40 -0800584 function fResume() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800585 if (!tos.isOblique()) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800586 force.resume();
587 }
588 }
589
590 function fStart() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800591 if (!tos.isOblique()) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800592 force.start();
593 }
594 }
595
596 var tickStuff = {
597 nodeAttr: {
598 transform: function (d) { return sus.translate(d.x, d.y); }
599 },
600 linkAttr: {
601 x1: function (d) { return d.source.x; },
602 y1: function (d) { return d.source.y; },
603 x2: function (d) { return d.target.x; },
604 y2: function (d) { return d.target.y; }
605 },
606 linkLabelAttr: {
607 transform: function (d) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800608 var lnk = tms.findLinkById(d.key);
Simon Hunt5724fb42015-02-05 16:59:40 -0800609 if (lnk) {
Simon Hunta4242de2015-02-24 17:11:55 -0800610 return td3.transformLabel({
Simon Hunt5724fb42015-02-05 16:59:40 -0800611 x1: lnk.source.x,
612 y1: lnk.source.y,
613 x2: lnk.target.x,
614 y2: lnk.target.y
615 });
616 }
617 }
618 }
619 };
620
621 function tick() {
Simon Hunt3ab20282015-02-26 20:32:19 -0800622 // guard against null (which can happen when our view pages out)...
623 if (node) node.attr(tickStuff.nodeAttr);
624 if (link) link.attr(tickStuff.linkAttr);
625 if (linkLabel) linkLabel.attr(tickStuff.linkLabelAttr);
Simon Hunt737c89f2015-01-28 12:23:19 -0800626 }
627
628
Simon Huntac4c6f72015-02-03 19:50:53 -0800629 // ==========================
630 // === MOUSE GESTURE HANDLERS
631
Simon Hunt205099e2015-02-07 13:12:01 -0800632 function zoomingOrPanning(ev) {
633 return ev.metaKey || ev.altKey;
Simon Hunt445e8152015-02-06 13:00:12 -0800634 }
635
636 function atDragEnd(d) {
637 // once we've finished moving, pin the node in position
638 d.fixed = true;
639 d3.select(this).classed('fixed', true);
640 sendUpdateMeta(d);
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700641 tss.clickConsumed(true);
Simon Hunt445e8152015-02-06 13:00:12 -0800642 }
643
644 // predicate that indicates when dragging is active
645 function dragEnabled() {
646 var ev = d3.event.sourceEvent;
647 // nodeLock means we aren't allowing nodes to be dragged...
Simon Hunt205099e2015-02-07 13:12:01 -0800648 return !nodeLock && !zoomingOrPanning(ev);
Simon Hunt445e8152015-02-06 13:00:12 -0800649 }
650
651 // predicate that indicates when clicking is active
652 function clickEnabled() {
653 return true;
654 }
Simon Hunt737c89f2015-01-28 12:23:19 -0800655
Simon Huntf542d842015-02-11 16:20:33 -0800656 // ==========================
657 // function entry points for traffic module
658
659 var allTrafficClasses = 'primary secondary animated optical';
660
661 function clearLinkTrafficStyle() {
662 link.style('stroke-width', null)
663 .classed(allTrafficClasses, false);
664 }
665
666 function removeLinkLabels() {
667 network.links.forEach(function (d) {
668 d.label = '';
669 });
670 }
Simon Hunt737c89f2015-01-28 12:23:19 -0800671
Simon Hunta4242de2015-02-24 17:11:55 -0800672 function updateLinkLabelModel() {
673 // create the backing data for showing labels..
674 var data = [];
675 link.each(function (d) {
676 if (d.label) {
677 data.push({
678 id: 'lab-' + d.key,
679 key: d.key,
680 label: d.label,
681 ldata: d
682 });
683 }
684 });
685
686 linkLabel = linkLabelG.selectAll('.linkLabel')
687 .data(data, function (d) { return d.id; });
688 }
689
Simon Hunt737c89f2015-01-28 12:23:19 -0800690 // ==========================
Simon Huntac4c6f72015-02-03 19:50:53 -0800691 // Module definition
Simon Hunt737c89f2015-01-28 12:23:19 -0800692
Simon Huntdc6adea2015-02-09 22:29:36 -0800693 function mkModelApi(uplink) {
694 return {
695 projection: uplink.projection,
696 network: network,
697 restyleLinkElement: restyleLinkElement,
698 removeLinkElement: removeLinkElement
699 };
700 }
701
Simon Hunta4242de2015-02-24 17:11:55 -0800702 function mkD3Api(uplink) {
703 return {
704 node: function () { return node; },
705 link: function () { return link; },
706 linkLabel: function () { return linkLabel; },
707 instVisible: function () { return tis.isVisible(); },
708 posNode: tms.positionNode,
709 showHosts: function () { return showHosts; },
710 restyleLinkElement: restyleLinkElement,
711 updateLinkLabelModel: updateLinkLabelModel
712 }
713 }
714
Simon Hunt08f841d02015-02-10 14:39:20 -0800715 function mkSelectApi(uplink) {
716 return {
717 node: function () { return node; },
718 zoomingOrPanning: zoomingOrPanning,
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700719 updateDeviceColors: td3.updateDeviceColors,
720 deselectLink: tls.deselectLink
Simon Hunt08f841d02015-02-10 14:39:20 -0800721 };
722 }
723
Simon Huntf542d842015-02-11 16:20:33 -0800724 function mkTrafficApi(uplink) {
725 return {
726 clearLinkTrafficStyle: clearLinkTrafficStyle,
727 removeLinkLabels: removeLinkLabels,
728 updateLinks: updateLinks,
729 findLinkById: tms.findLinkById,
730 hovered: tss.hovered,
731 validateSelectionContext: tss.validateSelectionContext,
Simon Hunt237676b52015-03-10 19:04:26 -0700732 selectOrder: tss.selectOrder
Simon Huntf542d842015-02-11 16:20:33 -0800733 }
734 }
735
Simon Huntc3c5b672015-02-20 11:32:13 -0800736 function mkObliqueApi(uplink, fltr) {
Simon Hunt96f88c62015-02-19 17:57:25 -0800737 return {
Simon Huntc3c5b672015-02-20 11:32:13 -0800738 force: function() { return force; },
739 zoomLayer: uplink.zoomLayer,
740 nodeGBBox: function() { return nodeG.node().getBBox(); },
Simon Hunt96f88c62015-02-19 17:57:25 -0800741 node: function () { return node; },
Simon Huntc3c5b672015-02-20 11:32:13 -0800742 link: function () { return link; },
743 linkLabel: function () { return linkLabel; },
744 nodes: function () { return network.nodes; },
745 tickStuff: tickStuff,
746 nodeLock: function (b) {
747 var old = nodeLock;
748 nodeLock = b;
749 return old;
750 },
751 opacifyMap: uplink.opacifyMap,
752 inLayer: fltr.inLayer
Simon Hunt96f88c62015-02-19 17:57:25 -0800753 };
754 }
755
Simon Hunteb0fa052015-02-17 19:20:28 -0800756 function mkFilterApi(uplink) {
757 return {
758 node: function () { return node; },
759 link: function () { return link; }
760 };
761 }
762
Simon Hunt9e2104c2015-02-26 10:48:59 -0800763 function mkLinkApi(svg, uplink) {
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800764 return {
765 svg: svg,
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800766 zoomer: uplink.zoomer(),
767 network: network,
Simon Hunt1a5301e2015-02-25 15:31:25 -0800768 portLabelG: function () { return portLabelG; },
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800769 showHosts: function () { return showHosts; }
770 };
771 }
772
Simon Hunt737c89f2015-01-28 12:23:19 -0800773 angular.module('ovTopo')
774 .factory('TopoForceService',
Simon Hunt86b7c882015-04-02 23:06:08 -0700775 ['$log', '$timeout', 'FnService', 'SvgUtilService', 'IconService',
776 'ThemeService', 'FlashService', 'WebSocketService',
Simon Hunt237676b52015-03-10 19:04:26 -0700777 'TopoInstService', 'TopoModelService',
Simon Hunta4242de2015-02-24 17:11:55 -0800778 'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800779 'TopoObliqueService', 'TopoFilterService', 'TopoLinkService',
Simon Hunt737c89f2015-01-28 12:23:19 -0800780
Simon Hunt86b7c882015-04-02 23:06:08 -0700781 function (_$log_, _$timeout_, _fs_, _sus_, _is_, _ts_, _flash_, _wss_,
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800782 _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) {
Simon Hunt737c89f2015-01-28 12:23:19 -0800783 $log = _$log_;
Simon Hunt86b7c882015-04-02 23:06:08 -0700784 $timeout = _$timeout_;
Simon Hunt1894d792015-02-04 17:09:20 -0800785 fs = _fs_;
Simon Hunt737c89f2015-01-28 12:23:19 -0800786 sus = _sus_;
Simon Huntac4c6f72015-02-03 19:50:53 -0800787 is = _is_;
788 ts = _ts_;
Simon Hunt5724fb42015-02-05 16:59:40 -0800789 flash = _flash_;
Simon Hunt237676b52015-03-10 19:04:26 -0700790 wss = _wss_;
Simon Huntac4c6f72015-02-03 19:50:53 -0800791 tis = _tis_;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800792 tms = _tms_;
Simon Hunta4242de2015-02-24 17:11:55 -0800793 td3 = _td3_;
Simon Hunt08f841d02015-02-10 14:39:20 -0800794 tss = _tss_;
Simon Huntf542d842015-02-11 16:20:33 -0800795 tts = _tts_;
Simon Hunt96f88c62015-02-19 17:57:25 -0800796 tos = _tos_;
Simon Hunteb0fa052015-02-17 19:20:28 -0800797 fltr = _fltr_;
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800798 tls = _tls_;
Simon Hunt737c89f2015-01-28 12:23:19 -0800799
Simon Hunt1894d792015-02-04 17:09:20 -0800800 icfg = is.iconConfig();
801
Simon Hunta142dd22015-02-12 22:07:51 -0800802 var themeListener = ts.addListener(function () {
803 updateLinks();
804 updateNodes();
805 });
806
Simon Hunt737c89f2015-01-28 12:23:19 -0800807 // forceG is the SVG group to display the force layout in
Simon Huntdc6adea2015-02-09 22:29:36 -0800808 // uplink is the api from the main topo source file
Simon Hunt3a6eec02015-02-09 21:16:43 -0800809 // dim is the initial dimensions of the SVG as [w,h]
Simon Hunt737c89f2015-01-28 12:23:19 -0800810 // opts are, well, optional :)
Simon Hunt3ab20282015-02-26 20:32:19 -0800811 function initForce(_svg_, forceG, _uplink_, _dim_, opts) {
Simon Hunt1894d792015-02-04 17:09:20 -0800812 uplink = _uplink_;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800813 dim = _dim_;
Simon Hunt3ab20282015-02-26 20:32:19 -0800814 svg = _svg_;
815
816 lu = network.lookup;
817 rlk = network.revLinkToKey;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800818
819 $log.debug('initForce().. dim = ' + dim);
820
Simon Huntdc6adea2015-02-09 22:29:36 -0800821 tms.initModel(mkModelApi(uplink), dim);
Simon Hunta4242de2015-02-24 17:11:55 -0800822 td3.initD3(mkD3Api(uplink));
Simon Hunt08f841d02015-02-10 14:39:20 -0800823 tss.initSelect(mkSelectApi(uplink));
Simon Huntf542d842015-02-11 16:20:33 -0800824 tts.initTraffic(mkTrafficApi(uplink));
Simon Huntc3c5b672015-02-20 11:32:13 -0800825 tos.initOblique(mkObliqueApi(uplink, fltr));
Bri Prebilic Coleb5f2b152015-04-07 14:58:09 -0700826 fltr.initFilter(mkFilterApi(uplink));
Simon Hunt9e2104c2015-02-26 10:48:59 -0800827 tls.initLink(mkLinkApi(svg, uplink), td3);
Simon Hunta11b4eb2015-01-28 16:20:50 -0800828
Simon Hunt737c89f2015-01-28 12:23:19 -0800829 settings = angular.extend({}, defaultSettings, opts);
830
831 linkG = forceG.append('g').attr('id', 'topo-links');
832 linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
833 nodeG = forceG.append('g').attr('id', 'topo-nodes');
Simon Hunt1a5301e2015-02-25 15:31:25 -0800834 portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
Simon Hunt737c89f2015-01-28 12:23:19 -0800835
836 link = linkG.selectAll('.link');
837 linkLabel = linkLabelG.selectAll('.linkLabel');
838 node = nodeG.selectAll('.node');
839
840 force = d3.layout.force()
Simon Hunt3a6eec02015-02-09 21:16:43 -0800841 .size(dim)
Simon Hunt737c89f2015-01-28 12:23:19 -0800842 .nodes(network.nodes)
843 .links(network.links)
844 .gravity(settings.gravity)
845 .friction(settings.friction)
846 .charge(settings.charge._def_)
847 .linkDistance(settings.linkDistance._def_)
848 .linkStrength(settings.linkStrength._def_)
849 .on('tick', tick);
850
851 drag = sus.createDragBehavior(force,
Simon Hunt08f841d02015-02-10 14:39:20 -0800852 tss.selectObject, atDragEnd, dragEnabled, clickEnabled);
Simon Hunt737c89f2015-01-28 12:23:19 -0800853 }
854
Simon Hunt3a6eec02015-02-09 21:16:43 -0800855 function newDim(_dim_) {
856 dim = _dim_;
857 force.size(dim);
858 tms.newDim(dim);
Simon Hunt737c89f2015-01-28 12:23:19 -0800859 }
860
Simon Hunt3a6eec02015-02-09 21:16:43 -0800861 function destroyForce() {
Simon Hunt3ab20282015-02-26 20:32:19 -0800862 force.stop();
863
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800864 tls.destroyLink();
Simon Hunt96f88c62015-02-19 17:57:25 -0800865 tos.destroyOblique();
Simon Huntf542d842015-02-11 16:20:33 -0800866 tts.destroyTraffic();
867 tss.destroySelect();
Simon Hunta4242de2015-02-24 17:11:55 -0800868 td3.destroyD3();
Simon Huntf542d842015-02-11 16:20:33 -0800869 tms.destroyModel();
Simon Hunta142dd22015-02-12 22:07:51 -0800870 ts.removeListener(themeListener);
871 themeListener = null;
Simon Hunt3ab20282015-02-26 20:32:19 -0800872
873 // clean up the DOM
874 svg.selectAll('g').remove();
875 svg.selectAll('defs').remove();
876
877 // clean up internal state
878 network.nodes = [];
879 network.links = [];
880 network.lookup = {};
881 network.revLinkToKey = {};
882
883 linkG = linkLabelG = nodeG = portLabelG = null;
884 link = linkLabel = node = null;
885 force = drag = null;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800886 }
887
Simon Hunt737c89f2015-01-28 12:23:19 -0800888 return {
889 initForce: initForce,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800890 newDim: newDim,
891 destroyForce: destroyForce,
Simon Huntac4c6f72015-02-03 19:50:53 -0800892
Simon Hunta4242de2015-02-24 17:11:55 -0800893 updateDeviceColors: td3.updateDeviceColors,
Simon Hunt5724fb42015-02-05 16:59:40 -0800894 toggleHosts: toggleHosts,
Simon Hunt9e2104c2015-02-26 10:48:59 -0800895 togglePorts: tls.togglePorts,
Simon Hunt5724fb42015-02-05 16:59:40 -0800896 toggleOffline: toggleOffline,
897 cycleDeviceLabels: cycleDeviceLabels,
Simon Hunt445e8152015-02-06 13:00:12 -0800898 unpin: unpin,
Simon Hunta142dd22015-02-12 22:07:51 -0800899 showMastership: showMastership,
Simon Hunt86b7c882015-04-02 23:06:08 -0700900 showBadLinks: showBadLinks,
Simon Huntac4c6f72015-02-03 19:50:53 -0800901
902 addDevice: addDevice,
Simon Hunt1894d792015-02-04 17:09:20 -0800903 updateDevice: updateDevice,
904 removeDevice: removeDevice,
905 addHost: addHost,
906 updateHost: updateHost,
907 removeHost: removeHost,
908 addLink: addLink,
909 updateLink: updateLink,
910 removeLink: removeLink
Simon Hunt737c89f2015-01-28 12:23:19 -0800911 };
912 }]);
913}());