blob: b365db83658f7ccba1eff70f19f69a8cdfe87416 [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
Simon Hunt86b7c882015-04-02 23:06:08 -0700266 // TODO: understand why el is sometimes undefined on addLink events...
Simon Hunt1894d792015-02-04 17:09:20 -0800267 el.classed('link', true);
268 el.classed('inactive', !online);
269 el.classed(allLinkTypes, false);
270 if (type) {
271 el.classed(type, true);
272 }
273 el.transition()
Simon Hunta142dd22015-02-12 22:07:51 -0800274 .duration(delay)
Simon Hunt1894d792015-02-04 17:09:20 -0800275 .attr('stroke-width', linkScale(lw))
276 .attr('stroke', linkConfig[th].baseColor);
277 }
278
Simon Hunt1894d792015-02-04 17:09:20 -0800279 function removeLinkElement(d) {
280 var idx = fs.find(d.key, network.links, 'key'),
281 removed;
282 if (idx >=0) {
283 // remove from links array
284 removed = network.links.splice(idx, 1);
285 // remove from lookup cache
286 delete lu[removed[0].key];
287 updateLinks();
288 fResume();
289 }
290 }
291
292 function removeHostElement(d, upd) {
293 // first, remove associated hostLink...
294 removeLinkElement(d.linkData);
295
296 // remove hostLink bindings
297 delete lu[d.ingress];
298 delete lu[d.egress];
299
300 // remove from lookup cache
301 delete lu[d.id];
302 // remove from nodes array
303 var idx = fs.find(d.id, network.nodes);
304 network.nodes.splice(idx, 1);
305
306 // remove from SVG
307 // NOTE: upd is false if we were called from removeDeviceElement()
308 if (upd) {
309 updateNodes();
310 fResume();
311 }
312 }
313
314 function removeDeviceElement(d) {
315 var id = d.id;
316 // first, remove associated hosts and links..
Simon Huntdc6adea2015-02-09 22:29:36 -0800317 tms.findAttachedHosts(id).forEach(removeHostElement);
318 tms.findAttachedLinks(id).forEach(removeLinkElement);
Simon Hunt1894d792015-02-04 17:09:20 -0800319
320 // remove from lookup cache
321 delete lu[id];
322 // remove from nodes array
323 var idx = fs.find(id, network.nodes);
324 network.nodes.splice(idx, 1);
325
326 if (!network.nodes.length) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800327 uplink.showNoDevs(true);
Simon Hunt1894d792015-02-04 17:09:20 -0800328 }
329
330 // remove from SVG
331 updateNodes();
332 fResume();
333 }
334
Simon Hunt5724fb42015-02-05 16:59:40 -0800335 function updateHostVisibility() {
Simon Hunt18bf9822015-02-12 17:35:45 -0800336 sus.visible(nodeG.selectAll('.host'), showHosts);
337 sus.visible(linkG.selectAll('.hostLink'), showHosts);
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800338 sus.visible(linkLabelG.selectAll('.hostLinkLabel'), showHosts);
Simon Hunt5724fb42015-02-05 16:59:40 -0800339 }
340
341 function updateOfflineVisibility(dev) {
342 function updDev(d, show) {
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800343 var b;
Simon Hunt18bf9822015-02-12 17:35:45 -0800344 sus.visible(d.el, show);
Simon Hunt5724fb42015-02-05 16:59:40 -0800345
Simon Huntdc6adea2015-02-09 22:29:36 -0800346 tms.findAttachedLinks(d.id).forEach(function (link) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800347 b = show && ((link.type() !== 'hostLink') || showHosts);
Simon Hunt18bf9822015-02-12 17:35:45 -0800348 sus.visible(link.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800349 });
Simon Huntdc6adea2015-02-09 22:29:36 -0800350 tms.findAttachedHosts(d.id).forEach(function (host) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800351 b = show && showHosts;
Simon Hunt18bf9822015-02-12 17:35:45 -0800352 sus.visible(host.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800353 });
354 }
355
356 if (dev) {
357 // updating a specific device that just toggled off/on-line
358 updDev(dev, dev.online || showOffline);
359 } else {
360 // updating all offline devices
Simon Huntdc6adea2015-02-09 22:29:36 -0800361 tms.findDevices(true).forEach(function (d) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800362 updDev(d, showOffline);
363 });
364 }
365 }
366
Simon Hunt1894d792015-02-04 17:09:20 -0800367
Simon Hunt445e8152015-02-06 13:00:12 -0800368 function sendUpdateMeta(d, clearPos) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800369 var metaUi = {},
370 ll;
371
Simon Hunt445e8152015-02-06 13:00:12 -0800372 // if we are not clearing the position data (unpinning),
373 // attach the x, y, longitude, latitude...
374 if (!clearPos) {
Simon Hunt3a6eec02015-02-09 21:16:43 -0800375 ll = tms.lngLatFromCoord([d.x, d.y]);
Simon Huntdc6adea2015-02-09 22:29:36 -0800376 metaUi = {x: d.x, y: d.y, lng: ll[0], lat: ll[1]};
Simon Hunt1894d792015-02-04 17:09:20 -0800377 }
378 d.metaUi = metaUi;
Simon Hunt237676b52015-03-10 19:04:26 -0700379 wss.sendEvent('updateMeta', {
Simon Hunt1894d792015-02-04 17:09:20 -0800380 id: d.id,
381 'class': d.class,
382 memento: metaUi
383 });
Simon Huntac4c6f72015-02-03 19:50:53 -0800384 }
385
Simon Hunt1894d792015-02-04 17:09:20 -0800386
Simon Huntac4c6f72015-02-03 19:50:53 -0800387 function mkSvgClass(d) {
388 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
389 }
390
Simon Hunt5724fb42015-02-05 16:59:40 -0800391 function vis(b) {
392 return b ? 'visible' : 'hidden';
393 }
394
Simon Huntfcbde892015-04-16 12:05:28 -0700395 function toggleHosts(x) {
396 var kev = (x === 'keyev'),
397 on = kev ? !showHosts : !!x;
398
399 showHosts = on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800400 updateHostVisibility();
Simon Huntfcbde892015-04-16 12:05:28 -0700401 flash.flash('Hosts ' + vis(on));
402 return on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800403 }
404
Simon Huntfcbde892015-04-16 12:05:28 -0700405 function toggleOffline(x) {
406 var kev = (x === 'keyev'),
407 on = kev ? !showOffline : !!x;
408
409 showOffline = on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800410 updateOfflineVisibility();
Simon Huntfcbde892015-04-16 12:05:28 -0700411 flash.flash('Offline devices ' + vis(on));
412 return on;
Simon Hunt5724fb42015-02-05 16:59:40 -0800413 }
414
415 function cycleDeviceLabels() {
Bri Prebilic Cole9cf1a8d2015-04-21 13:15:29 -0700416 flash.flash(td3.incDevLabIndex());
Simon Huntdc6adea2015-02-09 22:29:36 -0800417 tms.findDevices().forEach(function (d) {
Simon Hunta4242de2015-02-24 17:11:55 -0800418 td3.updateDeviceLabel(d);
Simon Hunt1c367112015-02-05 18:02:46 -0800419 });
Simon Hunt5724fb42015-02-05 16:59:40 -0800420 }
421
Simon Hunt445e8152015-02-06 13:00:12 -0800422 function unpin() {
Simon Hunt08f841d02015-02-10 14:39:20 -0800423 var hov = tss.hovered();
424 if (hov) {
425 sendUpdateMeta(hov, true);
426 hov.fixed = false;
427 hov.el.classed('fixed', false);
Simon Hunt445e8152015-02-06 13:00:12 -0800428 fResume();
429 }
430 }
431
Simon Hunta142dd22015-02-12 22:07:51 -0800432 function showMastership(masterId) {
433 if (!masterId) {
434 restoreLayerState();
435 } else {
436 showMastershipFor(masterId);
437 }
438 }
439
440 function restoreLayerState() {
441 // NOTE: this level of indirection required, for when we have
442 // the layer filter functionality re-implemented
443 suppressLayers(false);
444 }
445
446 function showMastershipFor(id) {
447 suppressLayers(true);
448 node.each(function (n) {
449 if (n.master === id) {
450 n.el.classed('suppressed', false);
451 }
452 });
453 }
454
455 function suppressLayers(b) {
456 node.classed('suppressed', b);
457 link.classed('suppressed', b);
458// d3.selectAll('svg .port').classed('inactive', b);
459// d3.selectAll('svg .portText').classed('inactive', b);
460 }
Simon Hunt445e8152015-02-06 13:00:12 -0800461
Simon Hunt86b7c882015-04-02 23:06:08 -0700462 function showBadLinks() {
463 var badLinks = tms.findBadLinks();
464 flash.flash('Bad Links: ' + badLinks.length);
465 $log.debug('Bad Link List (' + badLinks.length + '):');
466 badLinks.forEach(function (d) {
467 $log.debug('bad link: (' + d.bad + ') ' + d.key, d);
468 if (d.el) {
469 d.el.attr('stroke-width', linkScale(2.8))
470 .attr('stroke', 'red');
471 }
472 });
473 // back to normal after 2 seconds...
474 $timeout(updateLinks, 2000);
475 }
476
Simon Hunt5724fb42015-02-05 16:59:40 -0800477 // ==========================================
478
Simon Huntac4c6f72015-02-03 19:50:53 -0800479 function updateNodes() {
Simon Hunt1894d792015-02-04 17:09:20 -0800480 // select all the nodes in the layout:
Simon Huntac4c6f72015-02-03 19:50:53 -0800481 node = nodeG.selectAll('.node')
482 .data(network.nodes, function (d) { return d.id; });
483
Simon Hunt1894d792015-02-04 17:09:20 -0800484 // operate on existing nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800485 node.filter('.device').each(td3.deviceExisting);
486 node.filter('.host').each(td3.hostExisting);
Simon Huntac4c6f72015-02-03 19:50:53 -0800487
488 // operate on entering nodes:
489 var entering = node.enter()
490 .append('g')
491 .attr({
492 id: function (d) { return sus.safeId(d.id); },
493 class: mkSvgClass,
494 transform: function (d) { return sus.translate(d.x, d.y); },
495 opacity: 0
496 })
497 .call(drag)
Simon Hunt08f841d02015-02-10 14:39:20 -0800498 .on('mouseover', tss.nodeMouseOver)
499 .on('mouseout', tss.nodeMouseOut)
Simon Huntac4c6f72015-02-03 19:50:53 -0800500 .transition()
501 .attr('opacity', 1);
502
Simon Hunt1894d792015-02-04 17:09:20 -0800503 // augment entering nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800504 entering.filter('.device').each(td3.deviceEnter);
505 entering.filter('.host').each(td3.hostEnter);
Simon Huntac4c6f72015-02-03 19:50:53 -0800506
Simon Hunt51056592015-02-03 21:48:07 -0800507 // operate on both existing and new nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800508 td3.updateDeviceColors();
Simon Huntac4c6f72015-02-03 19:50:53 -0800509
510 // operate on exiting nodes:
511 // Note that the node is removed after 2 seconds.
512 // Sub element animations should be shorter than 2 seconds.
513 var exiting = node.exit()
514 .transition()
515 .duration(2000)
516 .style('opacity', 0)
517 .remove();
518
Simon Hunt1894d792015-02-04 17:09:20 -0800519 // exiting node specifics:
Simon Hunta4242de2015-02-24 17:11:55 -0800520 exiting.filter('.host').each(td3.hostExit);
521 exiting.filter('.device').each(td3.deviceExit);
Simon Huntac4c6f72015-02-03 19:50:53 -0800522
Simon Hunt51056592015-02-03 21:48:07 -0800523 // finally, resume the force layout
Simon Huntac4c6f72015-02-03 19:50:53 -0800524 fResume();
525 }
526
Simon Hunt51056592015-02-03 21:48:07 -0800527 // ==========================
Simon Hunt1894d792015-02-04 17:09:20 -0800528
529 function updateLinks() {
530 var th = ts.theme();
531
532 link = linkG.selectAll('.link')
533 .data(network.links, function (d) { return d.key; });
534
535 // operate on existing links:
Simon Huntd5264122015-02-25 10:17:43 -0800536 link.each(function (d) {
537 // this is supposed to be an existing link, but we have observed
538 // occasions (where links are deleted and added rapidly?) where
539 // the DOM element has not been defined. So protect against that...
540 if (d.el) {
541 restyleLinkElement(d, true);
542 }
543 });
Simon Hunt1894d792015-02-04 17:09:20 -0800544
545 // operate on entering links:
546 var entering = link.enter()
547 .append('line')
548 .attr({
Simon Huntd5264122015-02-25 10:17:43 -0800549 x1: function (d) { return d.source.x; },
550 y1: function (d) { return d.source.y; },
551 x2: function (d) { return d.target.x; },
552 y2: function (d) { return d.target.y; },
Simon Hunt1894d792015-02-04 17:09:20 -0800553 stroke: linkConfig[th].inColor,
554 'stroke-width': linkConfig.inWidth
555 });
556
557 // augment links
Simon Hunta4242de2015-02-24 17:11:55 -0800558 entering.each(td3.linkEntering);
Simon Hunt1894d792015-02-04 17:09:20 -0800559
560 // operate on both existing and new links:
561 //link.each(...)
562
563 // apply or remove labels
Simon Hunta4242de2015-02-24 17:11:55 -0800564 td3.applyLinkLabels();
Simon Hunt1894d792015-02-04 17:09:20 -0800565
566 // operate on exiting links:
567 link.exit()
568 .attr('stroke-dasharray', '3 3')
Simon Hunt5724fb42015-02-05 16:59:40 -0800569 .attr('stroke', linkConfig[th].outColor)
Simon Hunt1894d792015-02-04 17:09:20 -0800570 .style('opacity', 0.5)
571 .transition()
572 .duration(1500)
573 .attr({
574 'stroke-dasharray': '3 12',
Simon Hunt1894d792015-02-04 17:09:20 -0800575 'stroke-width': linkConfig.outWidth
576 })
577 .style('opacity', 0.0)
578 .remove();
Simon Hunt1894d792015-02-04 17:09:20 -0800579 }
580
Simon Huntac4c6f72015-02-03 19:50:53 -0800581
582 // ==========================
Simon Hunt737c89f2015-01-28 12:23:19 -0800583 // force layout tick function
Simon Hunt737c89f2015-01-28 12:23:19 -0800584
Simon Hunt5724fb42015-02-05 16:59:40 -0800585 function fResume() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800586 if (!tos.isOblique()) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800587 force.resume();
588 }
589 }
590
591 function fStart() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800592 if (!tos.isOblique()) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800593 force.start();
594 }
595 }
596
597 var tickStuff = {
598 nodeAttr: {
599 transform: function (d) { return sus.translate(d.x, d.y); }
600 },
601 linkAttr: {
602 x1: function (d) { return d.source.x; },
603 y1: function (d) { return d.source.y; },
604 x2: function (d) { return d.target.x; },
605 y2: function (d) { return d.target.y; }
606 },
607 linkLabelAttr: {
608 transform: function (d) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800609 var lnk = tms.findLinkById(d.key);
Simon Hunt5724fb42015-02-05 16:59:40 -0800610 if (lnk) {
Simon Hunta4242de2015-02-24 17:11:55 -0800611 return td3.transformLabel({
Simon Hunt5724fb42015-02-05 16:59:40 -0800612 x1: lnk.source.x,
613 y1: lnk.source.y,
614 x2: lnk.target.x,
615 y2: lnk.target.y
616 });
617 }
618 }
619 }
620 };
621
622 function tick() {
Simon Hunt3ab20282015-02-26 20:32:19 -0800623 // guard against null (which can happen when our view pages out)...
624 if (node) node.attr(tickStuff.nodeAttr);
625 if (link) link.attr(tickStuff.linkAttr);
626 if (linkLabel) linkLabel.attr(tickStuff.linkLabelAttr);
Simon Hunt737c89f2015-01-28 12:23:19 -0800627 }
628
629
Simon Huntac4c6f72015-02-03 19:50:53 -0800630 // ==========================
631 // === MOUSE GESTURE HANDLERS
632
Simon Hunt205099e2015-02-07 13:12:01 -0800633 function zoomingOrPanning(ev) {
634 return ev.metaKey || ev.altKey;
Simon Hunt445e8152015-02-06 13:00:12 -0800635 }
636
637 function atDragEnd(d) {
638 // once we've finished moving, pin the node in position
639 d.fixed = true;
640 d3.select(this).classed('fixed', true);
641 sendUpdateMeta(d);
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700642 tss.clickConsumed(true);
Simon Hunt445e8152015-02-06 13:00:12 -0800643 }
644
645 // predicate that indicates when dragging is active
646 function dragEnabled() {
647 var ev = d3.event.sourceEvent;
648 // nodeLock means we aren't allowing nodes to be dragged...
Simon Hunt205099e2015-02-07 13:12:01 -0800649 return !nodeLock && !zoomingOrPanning(ev);
Simon Hunt445e8152015-02-06 13:00:12 -0800650 }
651
652 // predicate that indicates when clicking is active
653 function clickEnabled() {
654 return true;
655 }
Simon Hunt737c89f2015-01-28 12:23:19 -0800656
Simon Huntf542d842015-02-11 16:20:33 -0800657 // ==========================
658 // function entry points for traffic module
659
660 var allTrafficClasses = 'primary secondary animated optical';
661
662 function clearLinkTrafficStyle() {
663 link.style('stroke-width', null)
664 .classed(allTrafficClasses, false);
665 }
666
667 function removeLinkLabels() {
668 network.links.forEach(function (d) {
669 d.label = '';
670 });
671 }
Simon Hunt737c89f2015-01-28 12:23:19 -0800672
Simon Hunta4242de2015-02-24 17:11:55 -0800673 function updateLinkLabelModel() {
674 // create the backing data for showing labels..
675 var data = [];
676 link.each(function (d) {
677 if (d.label) {
678 data.push({
679 id: 'lab-' + d.key,
680 key: d.key,
681 label: d.label,
682 ldata: d
683 });
684 }
685 });
686
687 linkLabel = linkLabelG.selectAll('.linkLabel')
688 .data(data, function (d) { return d.id; });
689 }
690
Simon Hunt737c89f2015-01-28 12:23:19 -0800691 // ==========================
Simon Huntac4c6f72015-02-03 19:50:53 -0800692 // Module definition
Simon Hunt737c89f2015-01-28 12:23:19 -0800693
Simon Huntdc6adea2015-02-09 22:29:36 -0800694 function mkModelApi(uplink) {
695 return {
696 projection: uplink.projection,
697 network: network,
698 restyleLinkElement: restyleLinkElement,
699 removeLinkElement: removeLinkElement
700 };
701 }
702
Simon Hunta4242de2015-02-24 17:11:55 -0800703 function mkD3Api(uplink) {
704 return {
705 node: function () { return node; },
706 link: function () { return link; },
707 linkLabel: function () { return linkLabel; },
708 instVisible: function () { return tis.isVisible(); },
709 posNode: tms.positionNode,
710 showHosts: function () { return showHosts; },
711 restyleLinkElement: restyleLinkElement,
712 updateLinkLabelModel: updateLinkLabelModel
713 }
714 }
715
Simon Hunt08f841d02015-02-10 14:39:20 -0800716 function mkSelectApi(uplink) {
717 return {
718 node: function () { return node; },
719 zoomingOrPanning: zoomingOrPanning,
Simon Hunt0c6b2d32015-03-26 17:46:29 -0700720 updateDeviceColors: td3.updateDeviceColors,
721 deselectLink: tls.deselectLink
Simon Hunt08f841d02015-02-10 14:39:20 -0800722 };
723 }
724
Simon Huntf542d842015-02-11 16:20:33 -0800725 function mkTrafficApi(uplink) {
726 return {
727 clearLinkTrafficStyle: clearLinkTrafficStyle,
728 removeLinkLabels: removeLinkLabels,
729 updateLinks: updateLinks,
730 findLinkById: tms.findLinkById,
731 hovered: tss.hovered,
732 validateSelectionContext: tss.validateSelectionContext,
Simon Hunt237676b52015-03-10 19:04:26 -0700733 selectOrder: tss.selectOrder
Simon Huntf542d842015-02-11 16:20:33 -0800734 }
735 }
736
Simon Huntc3c5b672015-02-20 11:32:13 -0800737 function mkObliqueApi(uplink, fltr) {
Simon Hunt96f88c62015-02-19 17:57:25 -0800738 return {
Simon Huntc3c5b672015-02-20 11:32:13 -0800739 force: function() { return force; },
740 zoomLayer: uplink.zoomLayer,
741 nodeGBBox: function() { return nodeG.node().getBBox(); },
Simon Hunt96f88c62015-02-19 17:57:25 -0800742 node: function () { return node; },
Simon Huntc3c5b672015-02-20 11:32:13 -0800743 link: function () { return link; },
744 linkLabel: function () { return linkLabel; },
745 nodes: function () { return network.nodes; },
746 tickStuff: tickStuff,
747 nodeLock: function (b) {
748 var old = nodeLock;
749 nodeLock = b;
750 return old;
751 },
752 opacifyMap: uplink.opacifyMap,
753 inLayer: fltr.inLayer
Simon Hunt96f88c62015-02-19 17:57:25 -0800754 };
755 }
756
Simon Hunteb0fa052015-02-17 19:20:28 -0800757 function mkFilterApi(uplink) {
758 return {
759 node: function () { return node; },
760 link: function () { return link; }
761 };
762 }
763
Simon Hunt9e2104c2015-02-26 10:48:59 -0800764 function mkLinkApi(svg, uplink) {
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800765 return {
766 svg: svg,
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800767 zoomer: uplink.zoomer(),
768 network: network,
Simon Hunt1a5301e2015-02-25 15:31:25 -0800769 portLabelG: function () { return portLabelG; },
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800770 showHosts: function () { return showHosts; }
771 };
772 }
773
Simon Hunt737c89f2015-01-28 12:23:19 -0800774 angular.module('ovTopo')
775 .factory('TopoForceService',
Simon Hunt86b7c882015-04-02 23:06:08 -0700776 ['$log', '$timeout', 'FnService', 'SvgUtilService', 'IconService',
777 'ThemeService', 'FlashService', 'WebSocketService',
Simon Hunt237676b52015-03-10 19:04:26 -0700778 'TopoInstService', 'TopoModelService',
Simon Hunta4242de2015-02-24 17:11:55 -0800779 'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800780 'TopoObliqueService', 'TopoFilterService', 'TopoLinkService',
Simon Hunt737c89f2015-01-28 12:23:19 -0800781
Simon Hunt86b7c882015-04-02 23:06:08 -0700782 function (_$log_, _$timeout_, _fs_, _sus_, _is_, _ts_, _flash_, _wss_,
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800783 _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) {
Simon Hunt737c89f2015-01-28 12:23:19 -0800784 $log = _$log_;
Simon Hunt86b7c882015-04-02 23:06:08 -0700785 $timeout = _$timeout_;
Simon Hunt1894d792015-02-04 17:09:20 -0800786 fs = _fs_;
Simon Hunt737c89f2015-01-28 12:23:19 -0800787 sus = _sus_;
Simon Huntac4c6f72015-02-03 19:50:53 -0800788 is = _is_;
789 ts = _ts_;
Simon Hunt5724fb42015-02-05 16:59:40 -0800790 flash = _flash_;
Simon Hunt237676b52015-03-10 19:04:26 -0700791 wss = _wss_;
Simon Huntac4c6f72015-02-03 19:50:53 -0800792 tis = _tis_;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800793 tms = _tms_;
Simon Hunta4242de2015-02-24 17:11:55 -0800794 td3 = _td3_;
Simon Hunt08f841d02015-02-10 14:39:20 -0800795 tss = _tss_;
Simon Huntf542d842015-02-11 16:20:33 -0800796 tts = _tts_;
Simon Hunt96f88c62015-02-19 17:57:25 -0800797 tos = _tos_;
Simon Hunteb0fa052015-02-17 19:20:28 -0800798 fltr = _fltr_;
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800799 tls = _tls_;
Simon Hunt737c89f2015-01-28 12:23:19 -0800800
Simon Hunt1894d792015-02-04 17:09:20 -0800801 icfg = is.iconConfig();
802
Simon Hunta142dd22015-02-12 22:07:51 -0800803 var themeListener = ts.addListener(function () {
804 updateLinks();
805 updateNodes();
806 });
807
Simon Hunt737c89f2015-01-28 12:23:19 -0800808 // forceG is the SVG group to display the force layout in
Simon Huntdc6adea2015-02-09 22:29:36 -0800809 // uplink is the api from the main topo source file
Simon Hunt3a6eec02015-02-09 21:16:43 -0800810 // dim is the initial dimensions of the SVG as [w,h]
Simon Hunt737c89f2015-01-28 12:23:19 -0800811 // opts are, well, optional :)
Simon Hunt3ab20282015-02-26 20:32:19 -0800812 function initForce(_svg_, forceG, _uplink_, _dim_, opts) {
Simon Hunt1894d792015-02-04 17:09:20 -0800813 uplink = _uplink_;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800814 dim = _dim_;
Simon Hunt3ab20282015-02-26 20:32:19 -0800815 svg = _svg_;
816
817 lu = network.lookup;
818 rlk = network.revLinkToKey;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800819
820 $log.debug('initForce().. dim = ' + dim);
821
Simon Huntdc6adea2015-02-09 22:29:36 -0800822 tms.initModel(mkModelApi(uplink), dim);
Simon Hunta4242de2015-02-24 17:11:55 -0800823 td3.initD3(mkD3Api(uplink));
Simon Hunt08f841d02015-02-10 14:39:20 -0800824 tss.initSelect(mkSelectApi(uplink));
Simon Huntf542d842015-02-11 16:20:33 -0800825 tts.initTraffic(mkTrafficApi(uplink));
Simon Huntc3c5b672015-02-20 11:32:13 -0800826 tos.initOblique(mkObliqueApi(uplink, fltr));
Bri Prebilic Coleb5f2b152015-04-07 14:58:09 -0700827 fltr.initFilter(mkFilterApi(uplink));
Simon Hunt9e2104c2015-02-26 10:48:59 -0800828 tls.initLink(mkLinkApi(svg, uplink), td3);
Simon Hunta11b4eb2015-01-28 16:20:50 -0800829
Simon Hunt737c89f2015-01-28 12:23:19 -0800830 settings = angular.extend({}, defaultSettings, opts);
831
832 linkG = forceG.append('g').attr('id', 'topo-links');
833 linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
834 nodeG = forceG.append('g').attr('id', 'topo-nodes');
Simon Hunt1a5301e2015-02-25 15:31:25 -0800835 portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
Simon Hunt737c89f2015-01-28 12:23:19 -0800836
837 link = linkG.selectAll('.link');
838 linkLabel = linkLabelG.selectAll('.linkLabel');
839 node = nodeG.selectAll('.node');
840
841 force = d3.layout.force()
Simon Hunt3a6eec02015-02-09 21:16:43 -0800842 .size(dim)
Simon Hunt737c89f2015-01-28 12:23:19 -0800843 .nodes(network.nodes)
844 .links(network.links)
845 .gravity(settings.gravity)
846 .friction(settings.friction)
847 .charge(settings.charge._def_)
848 .linkDistance(settings.linkDistance._def_)
849 .linkStrength(settings.linkStrength._def_)
850 .on('tick', tick);
851
852 drag = sus.createDragBehavior(force,
Simon Hunt08f841d02015-02-10 14:39:20 -0800853 tss.selectObject, atDragEnd, dragEnabled, clickEnabled);
Simon Hunt737c89f2015-01-28 12:23:19 -0800854 }
855
Simon Hunt3a6eec02015-02-09 21:16:43 -0800856 function newDim(_dim_) {
857 dim = _dim_;
858 force.size(dim);
859 tms.newDim(dim);
Simon Hunt737c89f2015-01-28 12:23:19 -0800860 }
861
Simon Hunt3a6eec02015-02-09 21:16:43 -0800862 function destroyForce() {
Simon Hunt3ab20282015-02-26 20:32:19 -0800863 force.stop();
864
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800865 tls.destroyLink();
Simon Hunt96f88c62015-02-19 17:57:25 -0800866 tos.destroyOblique();
Simon Huntf542d842015-02-11 16:20:33 -0800867 tts.destroyTraffic();
868 tss.destroySelect();
Simon Hunta4242de2015-02-24 17:11:55 -0800869 td3.destroyD3();
Simon Huntf542d842015-02-11 16:20:33 -0800870 tms.destroyModel();
Simon Hunta142dd22015-02-12 22:07:51 -0800871 ts.removeListener(themeListener);
872 themeListener = null;
Simon Hunt3ab20282015-02-26 20:32:19 -0800873
874 // clean up the DOM
875 svg.selectAll('g').remove();
876 svg.selectAll('defs').remove();
877
878 // clean up internal state
879 network.nodes = [];
880 network.links = [];
881 network.lookup = {};
882 network.revLinkToKey = {};
883
884 linkG = linkLabelG = nodeG = portLabelG = null;
885 link = linkLabel = node = null;
886 force = drag = null;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800887 }
888
Simon Hunt737c89f2015-01-28 12:23:19 -0800889 return {
890 initForce: initForce,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800891 newDim: newDim,
892 destroyForce: destroyForce,
Simon Huntac4c6f72015-02-03 19:50:53 -0800893
Simon Hunta4242de2015-02-24 17:11:55 -0800894 updateDeviceColors: td3.updateDeviceColors,
Simon Hunt5724fb42015-02-05 16:59:40 -0800895 toggleHosts: toggleHosts,
Simon Hunt9e2104c2015-02-26 10:48:59 -0800896 togglePorts: tls.togglePorts,
Simon Hunt5724fb42015-02-05 16:59:40 -0800897 toggleOffline: toggleOffline,
898 cycleDeviceLabels: cycleDeviceLabels,
Simon Hunt445e8152015-02-06 13:00:12 -0800899 unpin: unpin,
Simon Hunta142dd22015-02-12 22:07:51 -0800900 showMastership: showMastership,
Simon Hunt86b7c882015-04-02 23:06:08 -0700901 showBadLinks: showBadLinks,
Simon Huntac4c6f72015-02-03 19:50:53 -0800902
903 addDevice: addDevice,
Simon Hunt1894d792015-02-04 17:09:20 -0800904 updateDevice: updateDevice,
905 removeDevice: removeDevice,
906 addHost: addHost,
907 updateHost: updateHost,
908 removeHost: removeHost,
909 addLink: addLink,
910 updateLink: updateLink,
911 removeLink: removeLink
Simon Hunt737c89f2015-01-28 12:23:19 -0800912 };
913 }]);
914}());