blob: 5b9991dd58efb495fee451bc794442903a27a093 [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 Huntfb8ea1f2015-02-24 21:38:09 -080026 var $log, fs, sus, is, ts, flash, tis, tms, td3, tss, tts, tos, fltr, tls,
Simon Hunt3ab20282015-02-26 20:32:19 -080027 icfg, uplink, svg;
Simon Huntac4c6f72015-02-03 19:50:53 -080028
29 // configuration
Simon Hunt1894d792015-02-04 17:09:20 -080030 var linkConfig = {
31 light: {
32 baseColor: '#666',
33 inColor: '#66f',
Simon Hunt3a6eec02015-02-09 21:16:43 -080034 outColor: '#f00'
Simon Hunt1894d792015-02-04 17:09:20 -080035 },
36 dark: {
Simon Hunt5724fb42015-02-05 16:59:40 -080037 baseColor: '#aaa',
Simon Hunt1894d792015-02-04 17:09:20 -080038 inColor: '#66f',
Simon Hunt5724fb42015-02-05 16:59:40 -080039 outColor: '#f66'
Simon Hunt1894d792015-02-04 17:09:20 -080040 },
41 inWidth: 12,
42 outWidth: 10
43 };
44
Simon Hunt737c89f2015-01-28 12:23:19 -080045 // internal state
Simon Huntac4c6f72015-02-03 19:50:53 -080046 var settings, // merged default settings and options
Simon Hunt737c89f2015-01-28 12:23:19 -080047 force, // force layout object
48 drag, // drag behavior handler
49 network = {
50 nodes: [],
51 links: [],
52 lookup: {},
53 revLinkToKey: {}
Simon Huntac4c6f72015-02-03 19:50:53 -080054 },
Simon Hunt3ab20282015-02-26 20:32:19 -080055 lu, // shorthand for lookup
56 rlk, // shorthand for revLinktoKey
Simon Hunta142dd22015-02-12 22:07:51 -080057 showHosts = false, // whether hosts are displayed
Simon Hunt5724fb42015-02-05 16:59:40 -080058 showOffline = true, // whether offline devices are displayed
Simon Hunt445e8152015-02-06 13:00:12 -080059 nodeLock = false, // whether nodes can be dragged or not (locked)
Simon Hunt08f841d02015-02-10 14:39:20 -080060 dim; // the dimensions of the force layout [w,h]
Simon Hunt737c89f2015-01-28 12:23:19 -080061
62 // SVG elements;
Simon Hunt1a5301e2015-02-25 15:31:25 -080063 var linkG, linkLabelG, portLabelG, nodeG;
Simon Hunt737c89f2015-01-28 12:23:19 -080064
65 // D3 selections;
66 var link, linkLabel, node;
67
68 // default settings for force layout
69 var defaultSettings = {
70 gravity: 0.4,
71 friction: 0.7,
72 charge: {
73 // note: key is node.class
74 device: -8000,
75 host: -5000,
76 _def_: -12000
77 },
78 linkDistance: {
79 // note: key is link.type
80 direct: 100,
81 optical: 120,
82 hostLink: 3,
83 _def_: 50
84 },
85 linkStrength: {
86 // note: key is link.type
87 // range: {0.0 ... 1.0}
88 //direct: 1.0,
89 //optical: 1.0,
90 //hostLink: 1.0,
91 _def_: 1.0
92 }
93 };
94
95
Simon Huntac4c6f72015-02-03 19:50:53 -080096 // ==========================
97 // === EVENT HANDLERS
98
99 function addDevice(data) {
100 var id = data.id,
101 d;
102
Simon Hunt1894d792015-02-04 17:09:20 -0800103 uplink.showNoDevs(false);
Simon Huntac4c6f72015-02-03 19:50:53 -0800104
105 // although this is an add device event, if we already have the
106 // device, treat it as an update instead..
Simon Hunt1894d792015-02-04 17:09:20 -0800107 if (lu[id]) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800108 updateDevice(data);
109 return;
110 }
111
Simon Hunt3a6eec02015-02-09 21:16:43 -0800112 d = tms.createDeviceNode(data);
Simon Huntac4c6f72015-02-03 19:50:53 -0800113 network.nodes.push(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800114 lu[id] = d;
Simon Huntac4c6f72015-02-03 19:50:53 -0800115 updateNodes();
116 fStart();
117 }
118
119 function updateDevice(data) {
120 var id = data.id,
Simon Hunt1894d792015-02-04 17:09:20 -0800121 d = lu[id],
Simon Huntac4c6f72015-02-03 19:50:53 -0800122 wasOnline;
123
124 if (d) {
125 wasOnline = d.online;
126 angular.extend(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800127 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800128 sendUpdateMeta(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800129 }
130 updateNodes();
131 if (wasOnline !== d.online) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800132 tms.findAttachedLinks(d.id).forEach(restyleLinkElement);
Simon Hunt5724fb42015-02-05 16:59:40 -0800133 updateOfflineVisibility(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800134 }
Simon Huntac4c6f72015-02-03 19:50:53 -0800135 }
136 }
137
Simon Hunt1894d792015-02-04 17:09:20 -0800138 function removeDevice(data) {
139 var id = data.id,
140 d = lu[id];
141 if (d) {
142 removeDeviceElement(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800143 }
144 }
145
146 function addHost(data) {
147 var id = data.id,
148 d, lnk;
149
150 // although this is an add host event, if we already have the
151 // host, treat it as an update instead..
152 if (lu[id]) {
153 updateHost(data);
154 return;
155 }
156
Simon Hunt3a6eec02015-02-09 21:16:43 -0800157 d = tms.createHostNode(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800158 network.nodes.push(d);
159 lu[id] = d;
Simon Hunt1894d792015-02-04 17:09:20 -0800160 updateNodes();
161
Simon Hunt3a6eec02015-02-09 21:16:43 -0800162 lnk = tms.createHostLink(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800163 if (lnk) {
Simon Hunt1894d792015-02-04 17:09:20 -0800164 d.linkData = lnk; // cache ref on its host
165 network.links.push(lnk);
166 lu[d.ingress] = lnk;
167 lu[d.egress] = lnk;
168 updateLinks();
169 }
170
171 fStart();
172 }
173
174 function updateHost(data) {
175 var id = data.id,
176 d = lu[id];
177 if (d) {
178 angular.extend(d, data);
Simon Hunt3a6eec02015-02-09 21:16:43 -0800179 if (tms.positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800180 sendUpdateMeta(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800181 }
182 updateNodes();
Simon Hunt1894d792015-02-04 17:09:20 -0800183 }
184 }
185
186 function removeHost(data) {
187 var id = data.id,
188 d = lu[id];
189 if (d) {
190 removeHostElement(d, true);
Simon Hunt1894d792015-02-04 17:09:20 -0800191 }
192 }
193
194 function addLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800195 var result = tms.findLink(data, 'add'),
Simon Hunt1894d792015-02-04 17:09:20 -0800196 bad = result.badLogic,
197 d = result.ldata;
198
199 if (bad) {
200 //logicError(bad + ': ' + link.id);
201 return;
202 }
203
204 if (d) {
205 // we already have a backing store link for src/dst nodes
206 addLinkUpdate(d, data);
207 return;
208 }
209
210 // no backing store link yet
Simon Hunt3a6eec02015-02-09 21:16:43 -0800211 d = tms.createLink(data);
Simon Hunt1894d792015-02-04 17:09:20 -0800212 if (d) {
213 network.links.push(d);
214 lu[d.key] = d;
215 updateLinks();
216 fStart();
217 }
218 }
219
220 function updateLink(data) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800221 var result = tms.findLink(data, 'update'),
Simon Hunt1894d792015-02-04 17:09:20 -0800222 bad = result.badLogic;
223 if (bad) {
224 //logicError(bad + ': ' + link.id);
225 return;
226 }
227 result.updateWith(link);
228 }
229
230 function removeLink(data) {
Simon Hunta4242de2015-02-24 17:11:55 -0800231 var result = tms.findLink(data, 'remove');
232
233 if (!result.badLogic) {
234 result.removeRawLink();
Simon Hunt1894d792015-02-04 17:09:20 -0800235 }
Simon Hunt1894d792015-02-04 17:09:20 -0800236 }
237
238 // ========================
239
240 function addLinkUpdate(ldata, link) {
241 // add link event, but we already have the reverse link installed
242 ldata.fromTarget = link;
Simon Huntdc6adea2015-02-09 22:29:36 -0800243 rlk[link.id] = ldata.key;
Simon Hunt1894d792015-02-04 17:09:20 -0800244 restyleLinkElement(ldata);
245 }
246
Simon Hunt1894d792015-02-04 17:09:20 -0800247
248 var widthRatio = 1.4,
249 linkScale = d3.scale.linear()
250 .domain([1, 12])
251 .range([widthRatio, 12 * widthRatio])
Simon Hunt5724fb42015-02-05 16:59:40 -0800252 .clamp(true),
Simon Hunt3a6eec02015-02-09 21:16:43 -0800253 allLinkTypes = 'direct indirect optical tunnel';
Simon Hunt1894d792015-02-04 17:09:20 -0800254
Simon Hunta142dd22015-02-12 22:07:51 -0800255 function restyleLinkElement(ldata, immediate) {
Simon Hunt1894d792015-02-04 17:09:20 -0800256 // this fn's job is to look at raw links and decide what svg classes
257 // need to be applied to the line element in the DOM
258 var th = ts.theme(),
259 el = ldata.el,
260 type = ldata.type(),
261 lw = ldata.linkWidth(),
Simon Hunta142dd22015-02-12 22:07:51 -0800262 online = ldata.online(),
263 delay = immediate ? 0 : 1000;
Simon Hunt1894d792015-02-04 17:09:20 -0800264
265 el.classed('link', true);
266 el.classed('inactive', !online);
267 el.classed(allLinkTypes, false);
268 if (type) {
269 el.classed(type, true);
270 }
271 el.transition()
Simon Hunta142dd22015-02-12 22:07:51 -0800272 .duration(delay)
Simon Hunt1894d792015-02-04 17:09:20 -0800273 .attr('stroke-width', linkScale(lw))
274 .attr('stroke', linkConfig[th].baseColor);
275 }
276
Simon Hunt1894d792015-02-04 17:09:20 -0800277 function removeLinkElement(d) {
278 var idx = fs.find(d.key, network.links, 'key'),
279 removed;
280 if (idx >=0) {
281 // remove from links array
282 removed = network.links.splice(idx, 1);
283 // remove from lookup cache
284 delete lu[removed[0].key];
285 updateLinks();
286 fResume();
287 }
288 }
289
290 function removeHostElement(d, upd) {
291 // first, remove associated hostLink...
292 removeLinkElement(d.linkData);
293
294 // remove hostLink bindings
295 delete lu[d.ingress];
296 delete lu[d.egress];
297
298 // remove from lookup cache
299 delete lu[d.id];
300 // remove from nodes array
301 var idx = fs.find(d.id, network.nodes);
302 network.nodes.splice(idx, 1);
303
304 // remove from SVG
305 // NOTE: upd is false if we were called from removeDeviceElement()
306 if (upd) {
307 updateNodes();
308 fResume();
309 }
310 }
311
312 function removeDeviceElement(d) {
313 var id = d.id;
314 // first, remove associated hosts and links..
Simon Huntdc6adea2015-02-09 22:29:36 -0800315 tms.findAttachedHosts(id).forEach(removeHostElement);
316 tms.findAttachedLinks(id).forEach(removeLinkElement);
Simon Hunt1894d792015-02-04 17:09:20 -0800317
318 // remove from lookup cache
319 delete lu[id];
320 // remove from nodes array
321 var idx = fs.find(id, network.nodes);
322 network.nodes.splice(idx, 1);
323
324 if (!network.nodes.length) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800325 uplink.showNoDevs(true);
Simon Hunt1894d792015-02-04 17:09:20 -0800326 }
327
328 // remove from SVG
329 updateNodes();
330 fResume();
331 }
332
Simon Hunt5724fb42015-02-05 16:59:40 -0800333 function updateHostVisibility() {
Simon Hunt18bf9822015-02-12 17:35:45 -0800334 sus.visible(nodeG.selectAll('.host'), showHosts);
335 sus.visible(linkG.selectAll('.hostLink'), showHosts);
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800336 sus.visible(linkLabelG.selectAll('.hostLinkLabel'), showHosts);
Simon Hunt5724fb42015-02-05 16:59:40 -0800337 }
338
339 function updateOfflineVisibility(dev) {
340 function updDev(d, show) {
Simon Hunt8eb4d3a2015-02-23 18:23:29 -0800341 var b;
Simon Hunt18bf9822015-02-12 17:35:45 -0800342 sus.visible(d.el, show);
Simon Hunt5724fb42015-02-05 16:59:40 -0800343
Simon Huntdc6adea2015-02-09 22:29:36 -0800344 tms.findAttachedLinks(d.id).forEach(function (link) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800345 b = show && ((link.type() !== 'hostLink') || showHosts);
Simon Hunt18bf9822015-02-12 17:35:45 -0800346 sus.visible(link.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800347 });
Simon Huntdc6adea2015-02-09 22:29:36 -0800348 tms.findAttachedHosts(d.id).forEach(function (host) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800349 b = show && showHosts;
Simon Hunt18bf9822015-02-12 17:35:45 -0800350 sus.visible(host.el, b);
Simon Hunt5724fb42015-02-05 16:59:40 -0800351 });
352 }
353
354 if (dev) {
355 // updating a specific device that just toggled off/on-line
356 updDev(dev, dev.online || showOffline);
357 } else {
358 // updating all offline devices
Simon Huntdc6adea2015-02-09 22:29:36 -0800359 tms.findDevices(true).forEach(function (d) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800360 updDev(d, showOffline);
361 });
362 }
363 }
364
Simon Hunt1894d792015-02-04 17:09:20 -0800365
Simon Hunt445e8152015-02-06 13:00:12 -0800366 function sendUpdateMeta(d, clearPos) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800367 var metaUi = {},
368 ll;
369
Simon Hunt445e8152015-02-06 13:00:12 -0800370 // if we are not clearing the position data (unpinning),
371 // attach the x, y, longitude, latitude...
372 if (!clearPos) {
Simon Hunt3a6eec02015-02-09 21:16:43 -0800373 ll = tms.lngLatFromCoord([d.x, d.y]);
Simon Huntdc6adea2015-02-09 22:29:36 -0800374 metaUi = {x: d.x, y: d.y, lng: ll[0], lat: ll[1]};
Simon Hunt1894d792015-02-04 17:09:20 -0800375 }
376 d.metaUi = metaUi;
377 uplink.sendEvent('updateMeta', {
378 id: d.id,
379 'class': d.class,
380 memento: metaUi
381 });
Simon Huntac4c6f72015-02-03 19:50:53 -0800382 }
383
Simon Hunt1894d792015-02-04 17:09:20 -0800384
Simon Huntac4c6f72015-02-03 19:50:53 -0800385 function mkSvgClass(d) {
386 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
387 }
388
Simon Hunt5724fb42015-02-05 16:59:40 -0800389 function vis(b) {
390 return b ? 'visible' : 'hidden';
391 }
392
393 function toggleHosts() {
394 showHosts = !showHosts;
395 updateHostVisibility();
396 flash.flash('Hosts ' + vis(showHosts));
397 }
398
399 function toggleOffline() {
400 showOffline = !showOffline;
401 updateOfflineVisibility();
402 flash.flash('Offline devices ' + vis(showOffline));
403 }
404
405 function cycleDeviceLabels() {
Simon Hunta4242de2015-02-24 17:11:55 -0800406 td3.incDevLabIndex();
Simon Huntdc6adea2015-02-09 22:29:36 -0800407 tms.findDevices().forEach(function (d) {
Simon Hunta4242de2015-02-24 17:11:55 -0800408 td3.updateDeviceLabel(d);
Simon Hunt1c367112015-02-05 18:02:46 -0800409 });
Simon Hunt5724fb42015-02-05 16:59:40 -0800410 }
411
Simon Hunt445e8152015-02-06 13:00:12 -0800412 function unpin() {
Simon Hunt08f841d02015-02-10 14:39:20 -0800413 var hov = tss.hovered();
414 if (hov) {
415 sendUpdateMeta(hov, true);
416 hov.fixed = false;
417 hov.el.classed('fixed', false);
Simon Hunt445e8152015-02-06 13:00:12 -0800418 fResume();
419 }
420 }
421
Simon Hunta142dd22015-02-12 22:07:51 -0800422 function showMastership(masterId) {
423 if (!masterId) {
424 restoreLayerState();
425 } else {
426 showMastershipFor(masterId);
427 }
428 }
429
430 function restoreLayerState() {
431 // NOTE: this level of indirection required, for when we have
432 // the layer filter functionality re-implemented
433 suppressLayers(false);
434 }
435
436 function showMastershipFor(id) {
437 suppressLayers(true);
438 node.each(function (n) {
439 if (n.master === id) {
440 n.el.classed('suppressed', false);
441 }
442 });
443 }
444
445 function suppressLayers(b) {
446 node.classed('suppressed', b);
447 link.classed('suppressed', b);
448// d3.selectAll('svg .port').classed('inactive', b);
449// d3.selectAll('svg .portText').classed('inactive', b);
450 }
Simon Hunt445e8152015-02-06 13:00:12 -0800451
Simon Hunt5724fb42015-02-05 16:59:40 -0800452 // ==========================================
453
Simon Huntac4c6f72015-02-03 19:50:53 -0800454 function updateNodes() {
Simon Hunt1894d792015-02-04 17:09:20 -0800455 // select all the nodes in the layout:
Simon Huntac4c6f72015-02-03 19:50:53 -0800456 node = nodeG.selectAll('.node')
457 .data(network.nodes, function (d) { return d.id; });
458
Simon Hunt1894d792015-02-04 17:09:20 -0800459 // operate on existing nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800460 node.filter('.device').each(td3.deviceExisting);
461 node.filter('.host').each(td3.hostExisting);
Simon Huntac4c6f72015-02-03 19:50:53 -0800462
463 // operate on entering nodes:
464 var entering = node.enter()
465 .append('g')
466 .attr({
467 id: function (d) { return sus.safeId(d.id); },
468 class: mkSvgClass,
469 transform: function (d) { return sus.translate(d.x, d.y); },
470 opacity: 0
471 })
472 .call(drag)
Simon Hunt08f841d02015-02-10 14:39:20 -0800473 .on('mouseover', tss.nodeMouseOver)
474 .on('mouseout', tss.nodeMouseOut)
Simon Huntac4c6f72015-02-03 19:50:53 -0800475 .transition()
476 .attr('opacity', 1);
477
Simon Hunt1894d792015-02-04 17:09:20 -0800478 // augment entering nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800479 entering.filter('.device').each(td3.deviceEnter);
480 entering.filter('.host').each(td3.hostEnter);
Simon Huntac4c6f72015-02-03 19:50:53 -0800481
Simon Hunt51056592015-02-03 21:48:07 -0800482 // operate on both existing and new nodes:
Simon Hunta4242de2015-02-24 17:11:55 -0800483 td3.updateDeviceColors();
Simon Huntac4c6f72015-02-03 19:50:53 -0800484
485 // operate on exiting nodes:
486 // Note that the node is removed after 2 seconds.
487 // Sub element animations should be shorter than 2 seconds.
488 var exiting = node.exit()
489 .transition()
490 .duration(2000)
491 .style('opacity', 0)
492 .remove();
493
Simon Hunt1894d792015-02-04 17:09:20 -0800494 // exiting node specifics:
Simon Hunta4242de2015-02-24 17:11:55 -0800495 exiting.filter('.host').each(td3.hostExit);
496 exiting.filter('.device').each(td3.deviceExit);
Simon Huntac4c6f72015-02-03 19:50:53 -0800497
Simon Hunt51056592015-02-03 21:48:07 -0800498 // finally, resume the force layout
Simon Huntac4c6f72015-02-03 19:50:53 -0800499 fResume();
500 }
501
Simon Hunt51056592015-02-03 21:48:07 -0800502 // ==========================
Simon Hunt1894d792015-02-04 17:09:20 -0800503
504 function updateLinks() {
505 var th = ts.theme();
506
507 link = linkG.selectAll('.link')
508 .data(network.links, function (d) { return d.key; });
509
510 // operate on existing links:
Simon Huntd5264122015-02-25 10:17:43 -0800511 link.each(function (d) {
512 // this is supposed to be an existing link, but we have observed
513 // occasions (where links are deleted and added rapidly?) where
514 // the DOM element has not been defined. So protect against that...
515 if (d.el) {
516 restyleLinkElement(d, true);
517 }
518 });
Simon Hunt1894d792015-02-04 17:09:20 -0800519
520 // operate on entering links:
521 var entering = link.enter()
522 .append('line')
523 .attr({
Simon Huntd5264122015-02-25 10:17:43 -0800524 x1: function (d) { return d.source.x; },
525 y1: function (d) { return d.source.y; },
526 x2: function (d) { return d.target.x; },
527 y2: function (d) { return d.target.y; },
Simon Hunt1894d792015-02-04 17:09:20 -0800528 stroke: linkConfig[th].inColor,
529 'stroke-width': linkConfig.inWidth
530 });
531
532 // augment links
Simon Hunta4242de2015-02-24 17:11:55 -0800533 entering.each(td3.linkEntering);
Simon Hunt1894d792015-02-04 17:09:20 -0800534
535 // operate on both existing and new links:
536 //link.each(...)
537
538 // apply or remove labels
Simon Hunta4242de2015-02-24 17:11:55 -0800539 td3.applyLinkLabels();
Simon Hunt1894d792015-02-04 17:09:20 -0800540
541 // operate on exiting links:
542 link.exit()
543 .attr('stroke-dasharray', '3 3')
Simon Hunt5724fb42015-02-05 16:59:40 -0800544 .attr('stroke', linkConfig[th].outColor)
Simon Hunt1894d792015-02-04 17:09:20 -0800545 .style('opacity', 0.5)
546 .transition()
547 .duration(1500)
548 .attr({
549 'stroke-dasharray': '3 12',
Simon Hunt1894d792015-02-04 17:09:20 -0800550 'stroke-width': linkConfig.outWidth
551 })
552 .style('opacity', 0.0)
553 .remove();
Simon Hunt1894d792015-02-04 17:09:20 -0800554 }
555
Simon Huntac4c6f72015-02-03 19:50:53 -0800556
557 // ==========================
Simon Hunt737c89f2015-01-28 12:23:19 -0800558 // force layout tick function
Simon Hunt737c89f2015-01-28 12:23:19 -0800559
Simon Hunt5724fb42015-02-05 16:59:40 -0800560 function fResume() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800561 if (!tos.isOblique()) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800562 force.resume();
563 }
564 }
565
566 function fStart() {
Simon Huntc3c5b672015-02-20 11:32:13 -0800567 if (!tos.isOblique()) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800568 force.start();
569 }
570 }
571
572 var tickStuff = {
573 nodeAttr: {
574 transform: function (d) { return sus.translate(d.x, d.y); }
575 },
576 linkAttr: {
577 x1: function (d) { return d.source.x; },
578 y1: function (d) { return d.source.y; },
579 x2: function (d) { return d.target.x; },
580 y2: function (d) { return d.target.y; }
581 },
582 linkLabelAttr: {
583 transform: function (d) {
Simon Huntdc6adea2015-02-09 22:29:36 -0800584 var lnk = tms.findLinkById(d.key);
Simon Hunt5724fb42015-02-05 16:59:40 -0800585 if (lnk) {
Simon Hunta4242de2015-02-24 17:11:55 -0800586 return td3.transformLabel({
Simon Hunt5724fb42015-02-05 16:59:40 -0800587 x1: lnk.source.x,
588 y1: lnk.source.y,
589 x2: lnk.target.x,
590 y2: lnk.target.y
591 });
592 }
593 }
594 }
595 };
596
597 function tick() {
Simon Hunt3ab20282015-02-26 20:32:19 -0800598 // guard against null (which can happen when our view pages out)...
599 if (node) node.attr(tickStuff.nodeAttr);
600 if (link) link.attr(tickStuff.linkAttr);
601 if (linkLabel) linkLabel.attr(tickStuff.linkLabelAttr);
Simon Hunt737c89f2015-01-28 12:23:19 -0800602 }
603
604
Simon Huntac4c6f72015-02-03 19:50:53 -0800605 // ==========================
606 // === MOUSE GESTURE HANDLERS
607
Simon Hunt205099e2015-02-07 13:12:01 -0800608 function zoomingOrPanning(ev) {
609 return ev.metaKey || ev.altKey;
Simon Hunt445e8152015-02-06 13:00:12 -0800610 }
611
612 function atDragEnd(d) {
613 // once we've finished moving, pin the node in position
614 d.fixed = true;
615 d3.select(this).classed('fixed', true);
616 sendUpdateMeta(d);
617 }
618
619 // predicate that indicates when dragging is active
620 function dragEnabled() {
621 var ev = d3.event.sourceEvent;
622 // nodeLock means we aren't allowing nodes to be dragged...
Simon Hunt205099e2015-02-07 13:12:01 -0800623 return !nodeLock && !zoomingOrPanning(ev);
Simon Hunt445e8152015-02-06 13:00:12 -0800624 }
625
626 // predicate that indicates when clicking is active
627 function clickEnabled() {
628 return true;
629 }
Simon Hunt737c89f2015-01-28 12:23:19 -0800630
Simon Huntf542d842015-02-11 16:20:33 -0800631 // ==========================
632 // function entry points for traffic module
633
634 var allTrafficClasses = 'primary secondary animated optical';
635
636 function clearLinkTrafficStyle() {
637 link.style('stroke-width', null)
638 .classed(allTrafficClasses, false);
639 }
640
641 function removeLinkLabels() {
642 network.links.forEach(function (d) {
643 d.label = '';
644 });
645 }
Simon Hunt737c89f2015-01-28 12:23:19 -0800646
Simon Hunta4242de2015-02-24 17:11:55 -0800647 function updateLinkLabelModel() {
648 // create the backing data for showing labels..
649 var data = [];
650 link.each(function (d) {
651 if (d.label) {
652 data.push({
653 id: 'lab-' + d.key,
654 key: d.key,
655 label: d.label,
656 ldata: d
657 });
658 }
659 });
660
661 linkLabel = linkLabelG.selectAll('.linkLabel')
662 .data(data, function (d) { return d.id; });
663 }
664
Simon Hunt737c89f2015-01-28 12:23:19 -0800665 // ==========================
Simon Huntac4c6f72015-02-03 19:50:53 -0800666 // Module definition
Simon Hunt737c89f2015-01-28 12:23:19 -0800667
Simon Huntdc6adea2015-02-09 22:29:36 -0800668 function mkModelApi(uplink) {
669 return {
670 projection: uplink.projection,
671 network: network,
672 restyleLinkElement: restyleLinkElement,
673 removeLinkElement: removeLinkElement
674 };
675 }
676
Simon Hunta4242de2015-02-24 17:11:55 -0800677 function mkD3Api(uplink) {
678 return {
679 node: function () { return node; },
680 link: function () { return link; },
681 linkLabel: function () { return linkLabel; },
682 instVisible: function () { return tis.isVisible(); },
683 posNode: tms.positionNode,
684 showHosts: function () { return showHosts; },
685 restyleLinkElement: restyleLinkElement,
686 updateLinkLabelModel: updateLinkLabelModel
687 }
688 }
689
Simon Hunt08f841d02015-02-10 14:39:20 -0800690 function mkSelectApi(uplink) {
691 return {
692 node: function () { return node; },
693 zoomingOrPanning: zoomingOrPanning,
Simon Hunta4242de2015-02-24 17:11:55 -0800694 updateDeviceColors: td3.updateDeviceColors,
Simon Hunt08f841d02015-02-10 14:39:20 -0800695 sendEvent: uplink.sendEvent
696 };
697 }
698
Simon Huntf542d842015-02-11 16:20:33 -0800699 function mkTrafficApi(uplink) {
700 return {
701 clearLinkTrafficStyle: clearLinkTrafficStyle,
702 removeLinkLabels: removeLinkLabels,
703 updateLinks: updateLinks,
704 findLinkById: tms.findLinkById,
705 hovered: tss.hovered,
706 validateSelectionContext: tss.validateSelectionContext,
707 selectOrder: tss.selectOrder,
708 sendEvent: uplink.sendEvent
709 }
710 }
711
Simon Huntc3c5b672015-02-20 11:32:13 -0800712 function mkObliqueApi(uplink, fltr) {
Simon Hunt96f88c62015-02-19 17:57:25 -0800713 return {
Simon Huntc3c5b672015-02-20 11:32:13 -0800714 force: function() { return force; },
715 zoomLayer: uplink.zoomLayer,
716 nodeGBBox: function() { return nodeG.node().getBBox(); },
Simon Hunt96f88c62015-02-19 17:57:25 -0800717 node: function () { return node; },
Simon Huntc3c5b672015-02-20 11:32:13 -0800718 link: function () { return link; },
719 linkLabel: function () { return linkLabel; },
720 nodes: function () { return network.nodes; },
721 tickStuff: tickStuff,
722 nodeLock: function (b) {
723 var old = nodeLock;
724 nodeLock = b;
725 return old;
726 },
727 opacifyMap: uplink.opacifyMap,
728 inLayer: fltr.inLayer
Simon Hunt96f88c62015-02-19 17:57:25 -0800729 };
730 }
731
Simon Hunteb0fa052015-02-17 19:20:28 -0800732 function mkFilterApi(uplink) {
733 return {
734 node: function () { return node; },
735 link: function () { return link; }
736 };
737 }
738
Simon Hunt9e2104c2015-02-26 10:48:59 -0800739 function mkLinkApi(svg, uplink) {
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800740 return {
741 svg: svg,
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800742 zoomer: uplink.zoomer(),
743 network: network,
Simon Hunt1a5301e2015-02-25 15:31:25 -0800744 portLabelG: function () { return portLabelG; },
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800745 showHosts: function () { return showHosts; }
746 };
747 }
748
Simon Hunt737c89f2015-01-28 12:23:19 -0800749 angular.module('ovTopo')
750 .factory('TopoForceService',
Simon Hunt1894d792015-02-04 17:09:20 -0800751 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
Simon Hunt3a6eec02015-02-09 21:16:43 -0800752 'FlashService', 'TopoInstService', 'TopoModelService',
Simon Hunta4242de2015-02-24 17:11:55 -0800753 'TopoD3Service', 'TopoSelectService', 'TopoTrafficService',
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800754 'TopoObliqueService', 'TopoFilterService', 'TopoLinkService',
Simon Hunt737c89f2015-01-28 12:23:19 -0800755
Simon Huntf542d842015-02-11 16:20:33 -0800756 function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_,
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800757 _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) {
Simon Hunt737c89f2015-01-28 12:23:19 -0800758 $log = _$log_;
Simon Hunt1894d792015-02-04 17:09:20 -0800759 fs = _fs_;
Simon Hunt737c89f2015-01-28 12:23:19 -0800760 sus = _sus_;
Simon Huntac4c6f72015-02-03 19:50:53 -0800761 is = _is_;
762 ts = _ts_;
Simon Hunt5724fb42015-02-05 16:59:40 -0800763 flash = _flash_;
Simon Huntac4c6f72015-02-03 19:50:53 -0800764 tis = _tis_;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800765 tms = _tms_;
Simon Hunta4242de2015-02-24 17:11:55 -0800766 td3 = _td3_;
Simon Hunt08f841d02015-02-10 14:39:20 -0800767 tss = _tss_;
Simon Huntf542d842015-02-11 16:20:33 -0800768 tts = _tts_;
Simon Hunt96f88c62015-02-19 17:57:25 -0800769 tos = _tos_;
Simon Hunteb0fa052015-02-17 19:20:28 -0800770 fltr = _fltr_;
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800771 tls = _tls_;
Simon Hunt737c89f2015-01-28 12:23:19 -0800772
Simon Hunt1894d792015-02-04 17:09:20 -0800773 icfg = is.iconConfig();
774
Simon Hunta142dd22015-02-12 22:07:51 -0800775 var themeListener = ts.addListener(function () {
776 updateLinks();
777 updateNodes();
778 });
779
Simon Hunt737c89f2015-01-28 12:23:19 -0800780 // forceG is the SVG group to display the force layout in
Simon Huntdc6adea2015-02-09 22:29:36 -0800781 // uplink is the api from the main topo source file
Simon Hunt3a6eec02015-02-09 21:16:43 -0800782 // dim is the initial dimensions of the SVG as [w,h]
Simon Hunt737c89f2015-01-28 12:23:19 -0800783 // opts are, well, optional :)
Simon Hunt3ab20282015-02-26 20:32:19 -0800784 function initForce(_svg_, forceG, _uplink_, _dim_, opts) {
Simon Hunt1894d792015-02-04 17:09:20 -0800785 uplink = _uplink_;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800786 dim = _dim_;
Simon Hunt3ab20282015-02-26 20:32:19 -0800787 svg = _svg_;
788
789 lu = network.lookup;
790 rlk = network.revLinkToKey;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800791
792 $log.debug('initForce().. dim = ' + dim);
793
Simon Huntdc6adea2015-02-09 22:29:36 -0800794 tms.initModel(mkModelApi(uplink), dim);
Simon Hunta4242de2015-02-24 17:11:55 -0800795 td3.initD3(mkD3Api(uplink));
Simon Hunt08f841d02015-02-10 14:39:20 -0800796 tss.initSelect(mkSelectApi(uplink));
Simon Huntf542d842015-02-11 16:20:33 -0800797 tts.initTraffic(mkTrafficApi(uplink));
Simon Huntc3c5b672015-02-20 11:32:13 -0800798 tos.initOblique(mkObliqueApi(uplink, fltr));
Simon Hunteb0fa052015-02-17 19:20:28 -0800799 fltr.initFilter(mkFilterApi(uplink), d3.select('#mast-right'));
Simon Hunt9e2104c2015-02-26 10:48:59 -0800800 tls.initLink(mkLinkApi(svg, uplink), td3);
Simon Hunta11b4eb2015-01-28 16:20:50 -0800801
Simon Hunt737c89f2015-01-28 12:23:19 -0800802 settings = angular.extend({}, defaultSettings, opts);
803
804 linkG = forceG.append('g').attr('id', 'topo-links');
805 linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
806 nodeG = forceG.append('g').attr('id', 'topo-nodes');
Simon Hunt1a5301e2015-02-25 15:31:25 -0800807 portLabelG = forceG.append('g').attr('id', 'topo-portLabels');
Simon Hunt737c89f2015-01-28 12:23:19 -0800808
809 link = linkG.selectAll('.link');
810 linkLabel = linkLabelG.selectAll('.linkLabel');
811 node = nodeG.selectAll('.node');
812
813 force = d3.layout.force()
Simon Hunt3a6eec02015-02-09 21:16:43 -0800814 .size(dim)
Simon Hunt737c89f2015-01-28 12:23:19 -0800815 .nodes(network.nodes)
816 .links(network.links)
817 .gravity(settings.gravity)
818 .friction(settings.friction)
819 .charge(settings.charge._def_)
820 .linkDistance(settings.linkDistance._def_)
821 .linkStrength(settings.linkStrength._def_)
822 .on('tick', tick);
823
824 drag = sus.createDragBehavior(force,
Simon Hunt08f841d02015-02-10 14:39:20 -0800825 tss.selectObject, atDragEnd, dragEnabled, clickEnabled);
Simon Hunt737c89f2015-01-28 12:23:19 -0800826 }
827
Simon Hunt3a6eec02015-02-09 21:16:43 -0800828 function newDim(_dim_) {
829 dim = _dim_;
830 force.size(dim);
831 tms.newDim(dim);
Simon Hunt737c89f2015-01-28 12:23:19 -0800832 }
833
Simon Hunt3a6eec02015-02-09 21:16:43 -0800834 function destroyForce() {
Simon Hunt3ab20282015-02-26 20:32:19 -0800835 force.stop();
836
Simon Huntfb8ea1f2015-02-24 21:38:09 -0800837 tls.destroyLink();
Simon Hunteb0fa052015-02-17 19:20:28 -0800838 fltr.destroyFilter();
Simon Hunt96f88c62015-02-19 17:57:25 -0800839 tos.destroyOblique();
Simon Huntf542d842015-02-11 16:20:33 -0800840 tts.destroyTraffic();
841 tss.destroySelect();
Simon Hunta4242de2015-02-24 17:11:55 -0800842 td3.destroyD3();
Simon Huntf542d842015-02-11 16:20:33 -0800843 tms.destroyModel();
Simon Hunta142dd22015-02-12 22:07:51 -0800844 ts.removeListener(themeListener);
845 themeListener = null;
Simon Hunt3ab20282015-02-26 20:32:19 -0800846
847 // clean up the DOM
848 svg.selectAll('g').remove();
849 svg.selectAll('defs').remove();
850
851 // clean up internal state
852 network.nodes = [];
853 network.links = [];
854 network.lookup = {};
855 network.revLinkToKey = {};
856
857 linkG = linkLabelG = nodeG = portLabelG = null;
858 link = linkLabel = node = null;
859 force = drag = null;
Simon Hunt3a6eec02015-02-09 21:16:43 -0800860 }
861
Simon Hunt737c89f2015-01-28 12:23:19 -0800862 return {
863 initForce: initForce,
Simon Hunt3a6eec02015-02-09 21:16:43 -0800864 newDim: newDim,
865 destroyForce: destroyForce,
Simon Huntac4c6f72015-02-03 19:50:53 -0800866
Simon Hunta4242de2015-02-24 17:11:55 -0800867 updateDeviceColors: td3.updateDeviceColors,
Simon Hunt5724fb42015-02-05 16:59:40 -0800868 toggleHosts: toggleHosts,
Simon Hunt9e2104c2015-02-26 10:48:59 -0800869 togglePorts: tls.togglePorts,
Simon Hunt5724fb42015-02-05 16:59:40 -0800870 toggleOffline: toggleOffline,
871 cycleDeviceLabels: cycleDeviceLabels,
Simon Hunt445e8152015-02-06 13:00:12 -0800872 unpin: unpin,
Simon Hunta142dd22015-02-12 22:07:51 -0800873 showMastership: showMastership,
Simon Huntac4c6f72015-02-03 19:50:53 -0800874
875 addDevice: addDevice,
Simon Hunt1894d792015-02-04 17:09:20 -0800876 updateDevice: updateDevice,
877 removeDevice: removeDevice,
878 addHost: addHost,
879 updateHost: updateHost,
880 removeHost: removeHost,
881 addLink: addLink,
882 updateLink: updateLink,
883 removeLink: removeLink
Simon Hunt737c89f2015-01-28 12:23:19 -0800884 };
885 }]);
886}());