blob: e189ef195e2a6c80be1d8f39a12c3596a9744293 [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/*
18 ONOS GUI -- Topology Event Module.
19 Defines event handling for events received from the server.
20 */
21
22(function () {
23 'use strict';
24
25 // injected refs
Simon Hunt5724fb42015-02-05 16:59:40 -080026 var $log, fs, sus, is, ts, flash, tis, icfg, uplink;
Simon Huntac4c6f72015-02-03 19:50:53 -080027
28 // configuration
29 var labelConfig = {
30 imgPad: 16,
31 padLR: 4,
32 padTB: 3,
33 marginLR: 3,
34 marginTB: 2,
35 port: {
36 gap: 3,
37 width: 18,
38 height: 14
39 }
40 };
41
42 var deviceIconConfig = {
43 xoff: -20,
44 yoff: -18
45 };
Simon Hunt737c89f2015-01-28 12:23:19 -080046
Simon Hunt1894d792015-02-04 17:09:20 -080047 var linkConfig = {
48 light: {
49 baseColor: '#666',
50 inColor: '#66f',
51 outColor: '#f00',
52 },
53 dark: {
Simon Hunt5724fb42015-02-05 16:59:40 -080054 baseColor: '#aaa',
Simon Hunt1894d792015-02-04 17:09:20 -080055 inColor: '#66f',
Simon Hunt5724fb42015-02-05 16:59:40 -080056 outColor: '#f66'
Simon Hunt1894d792015-02-04 17:09:20 -080057 },
58 inWidth: 12,
59 outWidth: 10
60 };
61
Simon Hunt737c89f2015-01-28 12:23:19 -080062 // internal state
Simon Huntac4c6f72015-02-03 19:50:53 -080063 var settings, // merged default settings and options
Simon Hunt737c89f2015-01-28 12:23:19 -080064 force, // force layout object
65 drag, // drag behavior handler
66 network = {
67 nodes: [],
68 links: [],
69 lookup: {},
70 revLinkToKey: {}
Simon Huntac4c6f72015-02-03 19:50:53 -080071 },
Simon Hunt1894d792015-02-04 17:09:20 -080072 lu = network.lookup, // shorthand
Simon Huntac4c6f72015-02-03 19:50:53 -080073 deviceLabelIndex = 0, // for device label cycling
Simon Hunt1894d792015-02-04 17:09:20 -080074 hostLabelIndex = 0, // for host label cycling
Simon Hunt5724fb42015-02-05 16:59:40 -080075 showHosts = true, // whether hosts are displayed
76 showOffline = true, // whether offline devices are displayed
77 oblique = false, // whether we are in the oblique view
Simon Hunt445e8152015-02-06 13:00:12 -080078 nodeLock = false, // whether nodes can be dragged or not (locked)
79 width, height, // the width and height of the force layout
Simon Hunt205099e2015-02-07 13:12:01 -080080 hovered, // the node over which the mouse is hovering
81 selections = {}, // what is currently selected
82 selectOrder = []; // the order in which we made selections
Simon Hunt737c89f2015-01-28 12:23:19 -080083
84 // SVG elements;
85 var linkG, linkLabelG, nodeG;
86
87 // D3 selections;
88 var link, linkLabel, node;
89
90 // default settings for force layout
91 var defaultSettings = {
92 gravity: 0.4,
93 friction: 0.7,
94 charge: {
95 // note: key is node.class
96 device: -8000,
97 host: -5000,
98 _def_: -12000
99 },
100 linkDistance: {
101 // note: key is link.type
102 direct: 100,
103 optical: 120,
104 hostLink: 3,
105 _def_: 50
106 },
107 linkStrength: {
108 // note: key is link.type
109 // range: {0.0 ... 1.0}
110 //direct: 1.0,
111 //optical: 1.0,
112 //hostLink: 1.0,
113 _def_: 1.0
114 }
115 };
116
117
Simon Huntac4c6f72015-02-03 19:50:53 -0800118 // ==========================
119 // === EVENT HANDLERS
120
121 function addDevice(data) {
122 var id = data.id,
123 d;
124
Simon Hunt1894d792015-02-04 17:09:20 -0800125 uplink.showNoDevs(false);
Simon Huntac4c6f72015-02-03 19:50:53 -0800126
127 // although this is an add device event, if we already have the
128 // device, treat it as an update instead..
Simon Hunt1894d792015-02-04 17:09:20 -0800129 if (lu[id]) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800130 updateDevice(data);
131 return;
132 }
133
134 d = createDeviceNode(data);
135 network.nodes.push(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800136 lu[id] = d;
Simon Huntac4c6f72015-02-03 19:50:53 -0800137
138 $log.debug("Created new device.. ", d.id, d.x, d.y);
139
140 updateNodes();
141 fStart();
142 }
143
144 function updateDevice(data) {
145 var id = data.id,
Simon Hunt1894d792015-02-04 17:09:20 -0800146 d = lu[id],
Simon Huntac4c6f72015-02-03 19:50:53 -0800147 wasOnline;
148
149 if (d) {
150 wasOnline = d.online;
151 angular.extend(d, data);
152 if (positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800153 sendUpdateMeta(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800154 }
155 updateNodes();
156 if (wasOnline !== d.online) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800157 findAttachedLinks(d.id).forEach(restyleLinkElement);
158 updateOfflineVisibility(d);
Simon Huntac4c6f72015-02-03 19:50:53 -0800159 }
160 } else {
161 // TODO: decide whether we want to capture logic errors
162 //logicError('updateDevice lookup fail. ID = "' + id + '"');
163 }
164 }
165
Simon Hunt1894d792015-02-04 17:09:20 -0800166 function removeDevice(data) {
167 var id = data.id,
168 d = lu[id];
169 if (d) {
170 removeDeviceElement(d);
171 } else {
172 // TODO: decide whether we want to capture logic errors
173 //logicError('removeDevice lookup fail. ID = "' + id + '"');
174 }
175 }
176
177 function addHost(data) {
178 var id = data.id,
179 d, lnk;
180
181 // although this is an add host event, if we already have the
182 // host, treat it as an update instead..
183 if (lu[id]) {
184 updateHost(data);
185 return;
186 }
187
188 d = createHostNode(data);
189 network.nodes.push(d);
190 lu[id] = d;
191
192 $log.debug("Created new host.. ", d.id, d.x, d.y);
193
194 updateNodes();
195
196 lnk = createHostLink(data);
197 if (lnk) {
198
199 $log.debug("Created new host-link.. ", lnk.key);
200
201 d.linkData = lnk; // cache ref on its host
202 network.links.push(lnk);
203 lu[d.ingress] = lnk;
204 lu[d.egress] = lnk;
205 updateLinks();
206 }
207
208 fStart();
209 }
210
211 function updateHost(data) {
212 var id = data.id,
213 d = lu[id];
214 if (d) {
215 angular.extend(d, data);
216 if (positionNode(d, true)) {
Simon Hunt445e8152015-02-06 13:00:12 -0800217 sendUpdateMeta(d);
Simon Hunt1894d792015-02-04 17:09:20 -0800218 }
219 updateNodes();
220 } else {
221 // TODO: decide whether we want to capture logic errors
222 //logicError('updateHost lookup fail. ID = "' + id + '"');
223 }
224 }
225
226 function removeHost(data) {
227 var id = data.id,
228 d = lu[id];
229 if (d) {
230 removeHostElement(d, true);
231 } else {
232 // may have already removed host, if attached to removed device
233 //console.warn('removeHost lookup fail. ID = "' + id + '"');
234 }
235 }
236
237 function addLink(data) {
238 var result = findLink(data, 'add'),
239 bad = result.badLogic,
240 d = result.ldata;
241
242 if (bad) {
243 //logicError(bad + ': ' + link.id);
244 return;
245 }
246
247 if (d) {
248 // we already have a backing store link for src/dst nodes
249 addLinkUpdate(d, data);
250 return;
251 }
252
253 // no backing store link yet
254 d = createLink(data);
255 if (d) {
256 network.links.push(d);
257 lu[d.key] = d;
258 updateLinks();
259 fStart();
260 }
261 }
262
263 function updateLink(data) {
264 var result = findLink(data, 'update'),
265 bad = result.badLogic;
266 if (bad) {
267 //logicError(bad + ': ' + link.id);
268 return;
269 }
270 result.updateWith(link);
271 }
272
273 function removeLink(data) {
274 var result = findLink(data, 'remove'),
275 bad = result.badLogic;
276 if (bad) {
277 // may have already removed link, if attached to removed device
278 //console.warn(bad + ': ' + link.id);
279 return;
280 }
281 result.removeRawLink();
282 }
283
284 // ========================
285
286 function addLinkUpdate(ldata, link) {
287 // add link event, but we already have the reverse link installed
288 ldata.fromTarget = link;
289 network.revLinkToKey[link.id] = ldata.key;
290 restyleLinkElement(ldata);
291 }
292
293 function createLink(link) {
294 var lnk = linkEndPoints(link.src, link.dst);
295
296 if (!lnk) {
297 return null;
298 }
299
300 angular.extend(lnk, {
301 key: link.id,
302 class: 'link',
303 fromSource: link,
304
305 // functions to aggregate dual link state
306 type: function () {
307 var s = lnk.fromSource,
308 t = lnk.fromTarget;
309 return (s && s.type) || (t && t.type) || defaultLinkType;
310 },
311 online: function () {
312 var s = lnk.fromSource,
313 t = lnk.fromTarget,
314 both = lnk.source.online && lnk.target.online;
315 return both && ((s && s.online) || (t && t.online));
316 },
317 linkWidth: function () {
318 var s = lnk.fromSource,
319 t = lnk.fromTarget,
320 ws = (s && s.linkWidth) || 0,
321 wt = (t && t.linkWidth) || 0;
322 return Math.max(ws, wt);
323 }
324 });
325 return lnk;
326 }
327
328
329 function makeNodeKey(d, what) {
330 var port = what + 'Port';
331 return d[what] + '/' + d[port];
332 }
333
334 function makeLinkKey(d, flipped) {
335 var one = flipped ? makeNodeKey(d, 'dst') : makeNodeKey(d, 'src'),
336 two = flipped ? makeNodeKey(d, 'src') : makeNodeKey(d, 'dst');
337 return one + '-' + two;
338 }
339
340 var widthRatio = 1.4,
341 linkScale = d3.scale.linear()
342 .domain([1, 12])
343 .range([widthRatio, 12 * widthRatio])
Simon Hunt5724fb42015-02-05 16:59:40 -0800344 .clamp(true),
345 allLinkTypes = 'direct indirect optical tunnel',
Simon Hunt1894d792015-02-04 17:09:20 -0800346 defaultLinkType = 'direct';
347
348 function restyleLinkElement(ldata) {
349 // this fn's job is to look at raw links and decide what svg classes
350 // need to be applied to the line element in the DOM
351 var th = ts.theme(),
352 el = ldata.el,
353 type = ldata.type(),
354 lw = ldata.linkWidth(),
355 online = ldata.online();
356
357 el.classed('link', true);
358 el.classed('inactive', !online);
359 el.classed(allLinkTypes, false);
360 if (type) {
361 el.classed(type, true);
362 }
363 el.transition()
364 .duration(1000)
365 .attr('stroke-width', linkScale(lw))
366 .attr('stroke', linkConfig[th].baseColor);
367 }
368
Simon Hunt5724fb42015-02-05 16:59:40 -0800369 function findLinkById(id) {
370 // check to see if this is a reverse lookup, else default to given id
371 var key = network.revLinkToKey[id] || id;
372 return key && lu[key];
373 }
374
Simon Hunt1894d792015-02-04 17:09:20 -0800375 function findLink(linkData, op) {
376 var key = makeLinkKey(linkData),
377 keyrev = makeLinkKey(linkData, 1),
378 link = lu[key],
379 linkRev = lu[keyrev],
380 result = {},
381 ldata = link || linkRev,
382 rawLink;
383
384 if (op === 'add') {
385 if (link) {
386 // trying to add a link that we already know about
387 result.ldata = link;
388 result.badLogic = 'addLink: link already added';
389
390 } else if (linkRev) {
391 // we found the reverse of the link to be added
392 result.ldata = linkRev;
393 if (linkRev.fromTarget) {
394 result.badLogic = 'addLink: link already added';
395 }
396 }
397 } else if (op === 'update') {
398 if (!ldata) {
399 result.badLogic = 'updateLink: link not found';
400 } else {
401 rawLink = link ? ldata.fromSource : ldata.fromTarget;
402 result.updateWith = function (data) {
403 angular.extend(rawLink, data);
404 restyleLinkElement(ldata);
405 }
406 }
407 } else if (op === 'remove') {
408 if (!ldata) {
409 result.badLogic = 'removeLink: link not found';
410 } else {
411 rawLink = link ? ldata.fromSource : ldata.fromTarget;
412
413 if (!rawLink) {
414 result.badLogic = 'removeLink: link not found';
415
416 } else {
417 result.removeRawLink = function () {
418 if (link) {
419 // remove fromSource
420 ldata.fromSource = null;
421 if (ldata.fromTarget) {
422 // promote target into source position
423 ldata.fromSource = ldata.fromTarget;
424 ldata.fromTarget = null;
425 ldata.key = keyrev;
426 delete network.lookup[key];
427 network.lookup[keyrev] = ldata;
428 delete network.revLinkToKey[keyrev];
429 }
430 } else {
431 // remove fromTarget
432 ldata.fromTarget = null;
433 delete network.revLinkToKey[keyrev];
434 }
435 if (ldata.fromSource) {
436 restyleLinkElement(ldata);
437 } else {
438 removeLinkElement(ldata);
439 }
440 }
441 }
442 }
443 }
444 return result;
445 }
446
Simon Hunt1c367112015-02-05 18:02:46 -0800447 function findDevices(offlineOnly) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800448 var a = [];
449 network.nodes.forEach(function (d) {
Simon Hunt1c367112015-02-05 18:02:46 -0800450 if (d.class === 'device' && !(offlineOnly && d.online)) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800451 a.push(d);
452 }
453 });
454 return a;
455 }
Simon Hunt1894d792015-02-04 17:09:20 -0800456
457 function findAttachedHosts(devId) {
458 var hosts = [];
459 network.nodes.forEach(function (d) {
460 if (d.class === 'host' && d.cp.device === devId) {
461 hosts.push(d);
462 }
463 });
464 return hosts;
465 }
466
467 function findAttachedLinks(devId) {
468 var links = [];
469 network.links.forEach(function (d) {
470 if (d.source.id === devId || d.target.id === devId) {
471 links.push(d);
472 }
473 });
474 return links;
475 }
476
477 function removeLinkElement(d) {
478 var idx = fs.find(d.key, network.links, 'key'),
479 removed;
480 if (idx >=0) {
481 // remove from links array
482 removed = network.links.splice(idx, 1);
483 // remove from lookup cache
484 delete lu[removed[0].key];
485 updateLinks();
486 fResume();
487 }
488 }
489
490 function removeHostElement(d, upd) {
491 // first, remove associated hostLink...
492 removeLinkElement(d.linkData);
493
494 // remove hostLink bindings
495 delete lu[d.ingress];
496 delete lu[d.egress];
497
498 // remove from lookup cache
499 delete lu[d.id];
500 // remove from nodes array
501 var idx = fs.find(d.id, network.nodes);
502 network.nodes.splice(idx, 1);
503
504 // remove from SVG
505 // NOTE: upd is false if we were called from removeDeviceElement()
506 if (upd) {
507 updateNodes();
508 fResume();
509 }
510 }
511
512 function removeDeviceElement(d) {
513 var id = d.id;
514 // first, remove associated hosts and links..
515 findAttachedHosts(id).forEach(removeHostElement);
516 findAttachedLinks(id).forEach(removeLinkElement);
517
518 // remove from lookup cache
519 delete lu[id];
520 // remove from nodes array
521 var idx = fs.find(id, network.nodes);
522 network.nodes.splice(idx, 1);
523
524 if (!network.nodes.length) {
525 xlink.showNoDevs(true);
526 }
527
528 // remove from SVG
529 updateNodes();
530 fResume();
531 }
532
Simon Hunt5724fb42015-02-05 16:59:40 -0800533 function updateHostVisibility() {
534 sus.makeVisible(nodeG.selectAll('.host'), showHosts);
535 sus.makeVisible(linkG.selectAll('.hostLink'), showHosts);
536 }
537
538 function updateOfflineVisibility(dev) {
539 function updDev(d, show) {
540 sus.makeVisible(d.el, show);
541
542 findAttachedLinks(d.id).forEach(function (link) {
543 b = show && ((link.type() !== 'hostLink') || showHosts);
544 sus.makeVisible(link.el, b);
545 });
546 findAttachedHosts(d.id).forEach(function (host) {
547 b = show && showHosts;
548 sus.makeVisible(host.el, b);
549 });
550 }
551
552 if (dev) {
553 // updating a specific device that just toggled off/on-line
554 updDev(dev, dev.online || showOffline);
555 } else {
556 // updating all offline devices
Simon Hunt1c367112015-02-05 18:02:46 -0800557 findDevices(true).forEach(function (d) {
Simon Hunt5724fb42015-02-05 16:59:40 -0800558 updDev(d, showOffline);
559 });
560 }
561 }
562
Simon Hunt1894d792015-02-04 17:09:20 -0800563
Simon Hunt445e8152015-02-06 13:00:12 -0800564 function sendUpdateMeta(d, clearPos) {
Simon Huntac4c6f72015-02-03 19:50:53 -0800565 var metaUi = {},
566 ll;
567
Simon Hunt445e8152015-02-06 13:00:12 -0800568 // if we are not clearing the position data (unpinning),
569 // attach the x, y, longitude, latitude...
570 if (!clearPos) {
Simon Hunt1894d792015-02-04 17:09:20 -0800571 ll = lngLatFromCoord([d.x, d.y]);
572 metaUi = {
573 x: d.x,
574 y: d.y,
575 lng: ll[0],
576 lat: ll[1]
577 };
578 }
579 d.metaUi = metaUi;
580 uplink.sendEvent('updateMeta', {
581 id: d.id,
582 'class': d.class,
583 memento: metaUi
584 });
Simon Huntac4c6f72015-02-03 19:50:53 -0800585 }
586
Simon Hunt445e8152015-02-06 13:00:12 -0800587 function requestTrafficForMode() {
588 $log.debug('TODO: requestTrafficForMode()...');
589 }
Simon Huntac4c6f72015-02-03 19:50:53 -0800590
Simon Huntac4c6f72015-02-03 19:50:53 -0800591 // ==========================
592 // === Devices and hosts - helper functions
593
594 function coordFromLngLat(loc) {
Simon Hunt1894d792015-02-04 17:09:20 -0800595 var p = uplink.projection();
596 return p ? p([loc.lng, loc.lat]) : [0, 0];
597 }
598
599 function lngLatFromCoord(coord) {
600 var p = uplink.projection();
Simon Hunt445e8152015-02-06 13:00:12 -0800601 return p ? p.invert(coord) : [0, 0];
Simon Huntac4c6f72015-02-03 19:50:53 -0800602 }
603
604 function positionNode(node, forUpdate) {
605 var meta = node.metaUi,
606 x = meta && meta.x,
607 y = meta && meta.y,
608 xy;
609
610 // If we have [x,y] already, use that...
611 if (x && y) {
612 node.fixed = true;
613 node.px = node.x = x;
614 node.py = node.y = y;
615 return;
616 }
617
618 var location = node.location,
619 coord;
620
621 if (location && location.type === 'latlng') {
622 coord = coordFromLngLat(location);
623 node.fixed = true;
624 node.px = node.x = coord[0];
625 node.py = node.y = coord[1];
626 return true;
627 }
628
629 // if this is a node update (not a node add).. skip randomizer
630 if (forUpdate) {
631 return;
632 }
633
634 // Note: Placing incoming unpinned nodes at exactly the same point
635 // (center of the view) causes them to explode outwards when
636 // the force layout kicks in. So, we spread them out a bit
637 // initially, to provide a more serene layout convergence.
638 // Additionally, if the node is a host, we place it near
639 // the device it is connected to.
640
641 function spread(s) {
642 return Math.floor((Math.random() * s) - s/2);
643 }
644
645 function randDim(dim) {
646 return dim / 2 + spread(dim * 0.7071);
647 }
648
649 function rand() {
650 return {
Simon Hunt1894d792015-02-04 17:09:20 -0800651 x: randDim(width),
652 y: randDim(height)
Simon Huntac4c6f72015-02-03 19:50:53 -0800653 };
654 }
655
656 function near(node) {
657 var min = 12,
658 dx = spread(12),
659 dy = spread(12);
660 return {
661 x: node.x + min + dx,
662 y: node.y + min + dy
663 };
664 }
665
666 function getDevice(cp) {
Simon Hunt1894d792015-02-04 17:09:20 -0800667 var d = lu[cp.device];
Simon Huntac4c6f72015-02-03 19:50:53 -0800668 return d || rand();
669 }
670
671 xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand();
672 angular.extend(node, xy);
673 }
674
675 function createDeviceNode(device) {
676 // start with the object as is
677 var node = device,
678 type = device.type,
679 svgCls = type ? 'node device ' + type : 'node device';
680
681 // Augment as needed...
682 node.class = 'device';
683 node.svgClass = device.online ? svgCls + ' online' : svgCls;
684 positionNode(node);
685 return node;
686 }
687
Simon Hunt1894d792015-02-04 17:09:20 -0800688 function createHostNode(host) {
689 var node = host;
690
691 // Augment as needed...
692 node.class = 'host';
693 if (!node.type) {
694 node.type = 'endstation';
695 }
696 node.svgClass = 'node host ' + node.type;
697 positionNode(node);
698 return node;
699 }
700
701 function createHostLink(host) {
702 var src = host.id,
703 dst = host.cp.device,
704 id = host.ingress,
705 lnk = linkEndPoints(src, dst);
706
707 if (!lnk) {
708 return null;
709 }
710
711 // Synthesize link ...
712 angular.extend(lnk, {
713 key: id,
714 class: 'link',
715
716 type: function () { return 'hostLink'; },
717 online: function () {
718 // hostlink target is edge switch
719 return lnk.target.online;
720 },
721 linkWidth: function () { return 1; }
722 });
723 return lnk;
724 }
725
726 function linkEndPoints(srcId, dstId) {
727 var srcNode = lu[srcId],
728 dstNode = lu[dstId],
729 sMiss = !srcNode ? missMsg('src', srcId) : '',
730 dMiss = !dstNode ? missMsg('dst', dstId) : '';
731
732 if (sMiss || dMiss) {
733 $log.error('Node(s) not on map for link:\n' + sMiss + dMiss);
734 //logicError('Node(s) not on map for link:\n' + sMiss + dMiss);
735 return null;
736 }
737 return {
738 source: srcNode,
739 target: dstNode,
740 x1: srcNode.x,
741 y1: srcNode.y,
742 x2: dstNode.x,
743 y2: dstNode.y
744 };
745 }
746
747 function missMsg(what, id) {
748 return '\n[' + what + '] "' + id + '" missing ';
749 }
750
Simon Huntac4c6f72015-02-03 19:50:53 -0800751 // ==========================
752 // === Devices and hosts - D3 rendering
753
Simon Hunt1894d792015-02-04 17:09:20 -0800754 function nodeMouseOver(m) {
755 // TODO
Simon Hunt445e8152015-02-06 13:00:12 -0800756 if (!m.dragStarted) {
757 $log.debug("MouseOver()...", m);
758 if (hovered != m) {
759 hovered = m;
760 requestTrafficForMode();
761 }
762 }
Simon Hunt1894d792015-02-04 17:09:20 -0800763 }
764
765 function nodeMouseOut(m) {
766 // TODO
Simon Hunt445e8152015-02-06 13:00:12 -0800767 if (!m.dragStarted) {
768 if (hovered) {
769 hovered = null;
770 requestTrafficForMode();
771 }
772 $log.debug("MouseOut()...", m);
773 }
Simon Hunt1894d792015-02-04 17:09:20 -0800774 }
775
776
Simon Huntac4c6f72015-02-03 19:50:53 -0800777 // Returns the newly computed bounding box of the rectangle
778 function adjustRectToFitText(n) {
779 var text = n.select('text'),
780 box = text.node().getBBox(),
781 lab = labelConfig;
782
783 text.attr('text-anchor', 'middle')
784 .attr('y', '-0.8em')
785 .attr('x', lab.imgPad/2);
786
787 // translate the bbox so that it is centered on [x,y]
788 box.x = -box.width / 2;
789 box.y = -box.height / 2;
790
791 // add padding
792 box.x -= (lab.padLR + lab.imgPad/2);
793 box.width += lab.padLR * 2 + lab.imgPad;
794 box.y -= lab.padTB;
795 box.height += lab.padTB * 2;
796
797 return box;
798 }
799
800 function mkSvgClass(d) {
801 return d.fixed ? d.svgClass + ' fixed' : d.svgClass;
802 }
803
804 function hostLabel(d) {
805 var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0;
806 return d.labels[idx];
807 }
808 function deviceLabel(d) {
809 var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0;
810 return d.labels[idx];
811 }
812 function trimLabel(label) {
813 return (label && label.trim()) || '';
814 }
815
816 function emptyBox() {
817 return {
818 x: -2,
819 y: -2,
820 width: 4,
821 height: 4
822 };
823 }
824
825
826 function updateDeviceLabel(d) {
827 var label = trimLabel(deviceLabel(d)),
828 noLabel = !label,
829 node = d.el,
Simon Hunt1894d792015-02-04 17:09:20 -0800830 dim = icfg.device.dim,
Simon Huntac4c6f72015-02-03 19:50:53 -0800831 devCfg = deviceIconConfig,
832 box, dx, dy;
833
834 node.select('text')
835 .text(label)
836 .style('opacity', 0)
837 .transition()
838 .style('opacity', 1);
839
840 if (noLabel) {
841 box = emptyBox();
842 dx = -dim/2;
843 dy = -dim/2;
844 } else {
845 box = adjustRectToFitText(node);
846 dx = box.x + devCfg.xoff;
847 dy = box.y + devCfg.yoff;
848 }
849
850 node.select('rect')
851 .transition()
852 .attr(box);
853
854 node.select('g.deviceIcon')
855 .transition()
856 .attr('transform', sus.translate(dx, dy));
857 }
858
859 function updateHostLabel(d) {
860 var label = trimLabel(hostLabel(d));
861 d.el.select('text').text(label);
862 }
863
Simon Huntac4c6f72015-02-03 19:50:53 -0800864 function updateDeviceColors(d) {
865 if (d) {
866 setDeviceColor(d);
867 } else {
868 node.filter('.device').each(function (d) {
869 setDeviceColor(d);
870 });
871 }
872 }
873
Simon Hunt5724fb42015-02-05 16:59:40 -0800874 function vis(b) {
875 return b ? 'visible' : 'hidden';
876 }
877
878 function toggleHosts() {
879 showHosts = !showHosts;
880 updateHostVisibility();
881 flash.flash('Hosts ' + vis(showHosts));
882 }
883
884 function toggleOffline() {
885 showOffline = !showOffline;
886 updateOfflineVisibility();
887 flash.flash('Offline devices ' + vis(showOffline));
888 }
889
890 function cycleDeviceLabels() {
Simon Hunt1c367112015-02-05 18:02:46 -0800891 deviceLabelIndex = (deviceLabelIndex+1) % 3;
892 findDevices().forEach(function (d) {
893 updateDeviceLabel(d);
894 });
Simon Hunt5724fb42015-02-05 16:59:40 -0800895 }
896
Simon Hunt445e8152015-02-06 13:00:12 -0800897 function unpin() {
898 if (hovered) {
899 sendUpdateMeta(hovered, true);
900 hovered.fixed = false;
901 hovered.el.classed('fixed', false);
902 fResume();
903 }
904 }
905
906
Simon Hunt5724fb42015-02-05 16:59:40 -0800907 // ==========================================
908
Simon Huntac4c6f72015-02-03 19:50:53 -0800909 var dCol = {
910 black: '#000',
911 paleblue: '#acf',
912 offwhite: '#ddd',
Simon Hunt78c10bf2015-02-03 21:18:20 -0800913 darkgrey: '#444',
Simon Huntac4c6f72015-02-03 19:50:53 -0800914 midgrey: '#888',
915 lightgrey: '#bbb',
916 orange: '#f90'
917 };
918
919 // note: these are the device icon colors without affinity
920 var dColTheme = {
921 light: {
Simon Hunt78c10bf2015-02-03 21:18:20 -0800922 rfill: dCol.offwhite,
Simon Huntac4c6f72015-02-03 19:50:53 -0800923 online: {
Simon Hunt78c10bf2015-02-03 21:18:20 -0800924 glyph: dCol.darkgrey,
Simon Huntac4c6f72015-02-03 19:50:53 -0800925 rect: dCol.paleblue
926 },
927 offline: {
928 glyph: dCol.midgrey,
929 rect: dCol.lightgrey
930 }
931 },
Simon Huntac4c6f72015-02-03 19:50:53 -0800932 dark: {
Simon Hunt78c10bf2015-02-03 21:18:20 -0800933 rfill: dCol.midgrey,
Simon Huntac4c6f72015-02-03 19:50:53 -0800934 online: {
Simon Hunt78c10bf2015-02-03 21:18:20 -0800935 glyph: dCol.darkgrey,
Simon Huntac4c6f72015-02-03 19:50:53 -0800936 rect: dCol.paleblue
937 },
938 offline: {
939 glyph: dCol.midgrey,
Simon Hunt78c10bf2015-02-03 21:18:20 -0800940 rect: dCol.darkgrey
Simon Huntac4c6f72015-02-03 19:50:53 -0800941 }
942 }
943 };
944
945 function devBaseColor(d) {
946 var o = d.online ? 'online' : 'offline';
947 return dColTheme[ts.theme()][o];
948 }
949
950 function setDeviceColor(d) {
951 var o = d.online,
952 s = d.el.classed('selected'),
953 c = devBaseColor(d),
954 a = instColor(d.master, o),
Simon Hunt51056592015-02-03 21:48:07 -0800955 icon = d.el.select('g.deviceIcon'),
956 g, r;
Simon Huntac4c6f72015-02-03 19:50:53 -0800957
958 if (s) {
959 g = c.glyph;
960 r = dCol.orange;
961 } else if (tis.isVisible()) {
962 g = o ? a : c.glyph;
Simon Hunt78c10bf2015-02-03 21:18:20 -0800963 r = o ? c.rfill : a;
Simon Huntac4c6f72015-02-03 19:50:53 -0800964 } else {
965 g = c.glyph;
966 r = c.rect;
967 }
968
Simon Hunt51056592015-02-03 21:48:07 -0800969 icon.select('use').style('fill', g);
970 icon.select('rect').style('fill', r);
Simon Huntac4c6f72015-02-03 19:50:53 -0800971 }
972
973 function instColor(id, online) {
974 return sus.cat7().getColor(id, !online, ts.theme());
975 }
976
Simon Hunt1894d792015-02-04 17:09:20 -0800977 // ==========================
Simon Huntac4c6f72015-02-03 19:50:53 -0800978
979 function updateNodes() {
Simon Hunt1894d792015-02-04 17:09:20 -0800980 // select all the nodes in the layout:
Simon Huntac4c6f72015-02-03 19:50:53 -0800981 node = nodeG.selectAll('.node')
982 .data(network.nodes, function (d) { return d.id; });
983
Simon Hunt1894d792015-02-04 17:09:20 -0800984 // operate on existing nodes:
Simon Hunt51056592015-02-03 21:48:07 -0800985 node.filter('.device').each(deviceExisting);
986 node.filter('.host').each(hostExisting);
Simon Huntac4c6f72015-02-03 19:50:53 -0800987
988 // operate on entering nodes:
989 var entering = node.enter()
990 .append('g')
991 .attr({
992 id: function (d) { return sus.safeId(d.id); },
993 class: mkSvgClass,
994 transform: function (d) { return sus.translate(d.x, d.y); },
995 opacity: 0
996 })
997 .call(drag)
998 .on('mouseover', nodeMouseOver)
999 .on('mouseout', nodeMouseOut)
1000 .transition()
1001 .attr('opacity', 1);
1002
Simon Hunt1894d792015-02-04 17:09:20 -08001003 // augment entering nodes:
Simon Hunt51056592015-02-03 21:48:07 -08001004 entering.filter('.device').each(deviceEnter);
1005 entering.filter('.host').each(hostEnter);
Simon Huntac4c6f72015-02-03 19:50:53 -08001006
Simon Hunt51056592015-02-03 21:48:07 -08001007 // operate on both existing and new nodes:
Simon Huntac4c6f72015-02-03 19:50:53 -08001008 updateDeviceColors();
1009
1010 // operate on exiting nodes:
1011 // Note that the node is removed after 2 seconds.
1012 // Sub element animations should be shorter than 2 seconds.
1013 var exiting = node.exit()
1014 .transition()
1015 .duration(2000)
1016 .style('opacity', 0)
1017 .remove();
1018
Simon Hunt1894d792015-02-04 17:09:20 -08001019 // exiting node specifics:
Simon Hunt51056592015-02-03 21:48:07 -08001020 exiting.filter('.host').each(hostExit);
1021 exiting.filter('.device').each(deviceExit);
Simon Huntac4c6f72015-02-03 19:50:53 -08001022
Simon Hunt51056592015-02-03 21:48:07 -08001023 // finally, resume the force layout
Simon Huntac4c6f72015-02-03 19:50:53 -08001024 fResume();
1025 }
1026
Simon Hunt51056592015-02-03 21:48:07 -08001027 // ==========================
1028 // updateNodes - subfunctions
1029
1030 function deviceExisting(d) {
1031 var node = d.el;
1032 node.classed('online', d.online);
1033 updateDeviceLabel(d);
1034 positionNode(d, true);
1035 }
1036
1037 function hostExisting(d) {
1038 updateHostLabel(d);
1039 positionNode(d, true);
1040 }
1041
1042 function deviceEnter(d) {
1043 var node = d3.select(this),
1044 glyphId = d.type || 'unknown',
1045 label = trimLabel(deviceLabel(d)),
1046 devCfg = deviceIconConfig,
1047 noLabel = !label,
1048 box, dx, dy, icon;
1049
1050 d.el = node;
1051
1052 node.append('rect').attr({ rx: 5, ry: 5 });
1053 node.append('text').text(label).attr('dy', '1.1em');
1054 box = adjustRectToFitText(node);
1055 node.select('rect').attr(box);
1056
1057 icon = is.addDeviceIcon(node, glyphId);
1058
1059 if (noLabel) {
1060 dx = -icon.dim/2;
1061 dy = -icon.dim/2;
1062 } else {
1063 box = adjustRectToFitText(node);
1064 dx = box.x + devCfg.xoff;
1065 dy = box.y + devCfg.yoff;
1066 }
1067
1068 icon.attr('transform', sus.translate(dx, dy));
1069 }
1070
1071 function hostEnter(d) {
Simon Hunt1894d792015-02-04 17:09:20 -08001072 var node = d3.select(this),
1073 gid = d.type || 'unknown',
1074 rad = icfg.host.radius,
1075 r = d.type ? rad.withGlyph : rad.noGlyph,
1076 textDy = r + 10;
Simon Hunt51056592015-02-03 21:48:07 -08001077
1078 d.el = node;
Simon Hunt1894d792015-02-04 17:09:20 -08001079 sus.makeVisible(node, showHosts);
Simon Hunt51056592015-02-03 21:48:07 -08001080
Simon Hunt1894d792015-02-04 17:09:20 -08001081 is.addHostIcon(node, r, gid);
Simon Hunt51056592015-02-03 21:48:07 -08001082
Simon Hunt51056592015-02-03 21:48:07 -08001083 node.append('text')
1084 .text(hostLabel)
Simon Hunt1894d792015-02-04 17:09:20 -08001085 .attr('dy', textDy)
Simon Hunt51056592015-02-03 21:48:07 -08001086 .attr('text-anchor', 'middle');
1087 }
1088
1089 function hostExit(d) {
1090 var node = d.el;
1091 node.select('use')
1092 .style('opacity', 0.5)
1093 .transition()
1094 .duration(800)
1095 .style('opacity', 0);
1096
1097 node.select('text')
1098 .style('opacity', 0.5)
1099 .transition()
1100 .duration(800)
1101 .style('opacity', 0);
1102
1103 node.select('circle')
1104 .style('stroke-fill', '#555')
1105 .style('fill', '#888')
1106 .style('opacity', 0.5)
1107 .transition()
1108 .duration(1500)
1109 .attr('r', 0);
1110 }
1111
1112 function deviceExit(d) {
1113 var node = d.el;
1114 node.select('use')
1115 .style('opacity', 0.5)
1116 .transition()
1117 .duration(800)
1118 .style('opacity', 0);
1119
1120 node.selectAll('rect')
1121 .style('stroke-fill', '#555')
1122 .style('fill', '#888')
1123 .style('opacity', 0.5);
1124 }
1125
Simon Hunt1894d792015-02-04 17:09:20 -08001126 // ==========================
1127
1128 function updateLinks() {
1129 var th = ts.theme();
1130
1131 link = linkG.selectAll('.link')
1132 .data(network.links, function (d) { return d.key; });
1133
1134 // operate on existing links:
1135 //link.each(linkExisting);
1136
1137 // operate on entering links:
1138 var entering = link.enter()
1139 .append('line')
1140 .attr({
1141 x1: function (d) { return d.x1; },
1142 y1: function (d) { return d.y1; },
1143 x2: function (d) { return d.x2; },
1144 y2: function (d) { return d.y2; },
1145 stroke: linkConfig[th].inColor,
1146 'stroke-width': linkConfig.inWidth
1147 });
1148
1149 // augment links
1150 entering.each(linkEntering);
1151
1152 // operate on both existing and new links:
1153 //link.each(...)
1154
1155 // apply or remove labels
1156 var labelData = getLabelData();
1157 applyLinkLabels(labelData);
1158
1159 // operate on exiting links:
1160 link.exit()
1161 .attr('stroke-dasharray', '3 3')
Simon Hunt5724fb42015-02-05 16:59:40 -08001162 .attr('stroke', linkConfig[th].outColor)
Simon Hunt1894d792015-02-04 17:09:20 -08001163 .style('opacity', 0.5)
1164 .transition()
1165 .duration(1500)
1166 .attr({
1167 'stroke-dasharray': '3 12',
Simon Hunt1894d792015-02-04 17:09:20 -08001168 'stroke-width': linkConfig.outWidth
1169 })
1170 .style('opacity', 0.0)
1171 .remove();
1172
1173 // NOTE: invoke a single tick to force the labels to position
1174 // onto their links.
1175 tick();
Simon Hunt5724fb42015-02-05 16:59:40 -08001176 // TODO: this causes undesirable behavior when in oblique view
Simon Hunt1894d792015-02-04 17:09:20 -08001177 // It causes the nodes to jump into "overhead" view positions, even
1178 // though the oblique planes are still showing...
1179 }
1180
1181 // ==========================
1182 // updateLinks - subfunctions
1183
1184 function getLabelData() {
1185 // create the backing data for showing labels..
1186 var data = [];
1187 link.each(function (d) {
1188 if (d.label) {
1189 data.push({
1190 id: 'lab-' + d.key,
1191 key: d.key,
1192 label: d.label,
1193 ldata: d
1194 });
1195 }
1196 });
1197 return data;
1198 }
1199
1200 //function linkExisting(d) { }
1201
1202 function linkEntering(d) {
1203 var link = d3.select(this);
1204 d.el = link;
1205 restyleLinkElement(d);
1206 if (d.type() === 'hostLink') {
1207 sus.makeVisible(link, showHosts);
1208 }
1209 }
1210
1211 //function linkExiting(d) { }
1212
1213 var linkLabelOffset = '0.3em';
1214
1215 function applyLinkLabels(data) {
1216 var entering;
1217
1218 linkLabel = linkLabelG.selectAll('.linkLabel')
1219 .data(data, function (d) { return d.id; });
1220
1221 // for elements already existing, we need to update the text
1222 // and adjust the rectangle size to fit
1223 linkLabel.each(function (d) {
1224 var el = d3.select(this),
1225 rect = el.select('rect'),
1226 text = el.select('text');
1227 text.text(d.label);
1228 rect.attr(rectAroundText(el));
1229 });
1230
1231 entering = linkLabel.enter().append('g')
1232 .classed('linkLabel', true)
1233 .attr('id', function (d) { return d.id; });
1234
1235 entering.each(function (d) {
1236 var el = d3.select(this),
1237 rect,
1238 text,
1239 parms = {
1240 x1: d.ldata.x1,
1241 y1: d.ldata.y1,
1242 x2: d.ldata.x2,
1243 y2: d.ldata.y2
1244 };
1245
1246 d.el = el;
1247 rect = el.append('rect');
1248 text = el.append('text').text(d.label);
1249 rect.attr(rectAroundText(el));
1250 text.attr('dy', linkLabelOffset);
1251
1252 el.attr('transform', transformLabel(parms));
1253 });
1254
1255 // Remove any labels that are no longer required.
1256 linkLabel.exit().remove();
1257 }
1258
1259 function rectAroundText(el) {
1260 var text = el.select('text'),
1261 box = text.node().getBBox();
1262
1263 // translate the bbox so that it is centered on [x,y]
1264 box.x = -box.width / 2;
1265 box.y = -box.height / 2;
1266
1267 // add padding
1268 box.x -= 1;
1269 box.width += 2;
1270 return box;
1271 }
1272
1273 function transformLabel(p) {
1274 var dx = p.x2 - p.x1,
1275 dy = p.y2 - p.y1,
1276 xMid = dx/2 + p.x1,
1277 yMid = dy/2 + p.y1;
1278 return sus.translate(xMid, yMid);
1279 }
Simon Huntac4c6f72015-02-03 19:50:53 -08001280
1281 // ==========================
Simon Hunt737c89f2015-01-28 12:23:19 -08001282 // force layout tick function
Simon Hunt737c89f2015-01-28 12:23:19 -08001283
Simon Hunt5724fb42015-02-05 16:59:40 -08001284 function fResume() {
1285 if (!oblique) {
1286 force.resume();
1287 }
1288 }
1289
1290 function fStart() {
1291 if (!oblique) {
1292 force.start();
1293 }
1294 }
1295
1296 var tickStuff = {
1297 nodeAttr: {
1298 transform: function (d) { return sus.translate(d.x, d.y); }
1299 },
1300 linkAttr: {
1301 x1: function (d) { return d.source.x; },
1302 y1: function (d) { return d.source.y; },
1303 x2: function (d) { return d.target.x; },
1304 y2: function (d) { return d.target.y; }
1305 },
1306 linkLabelAttr: {
1307 transform: function (d) {
1308 var lnk = findLinkById(d.key);
1309 if (lnk) {
1310 return transformLabel({
1311 x1: lnk.source.x,
1312 y1: lnk.source.y,
1313 x2: lnk.target.x,
1314 y2: lnk.target.y
1315 });
1316 }
1317 }
1318 }
1319 };
1320
1321 function tick() {
1322 node.attr(tickStuff.nodeAttr);
1323 link.attr(tickStuff.linkAttr);
1324 linkLabel.attr(tickStuff.linkLabelAttr);
Simon Hunt737c89f2015-01-28 12:23:19 -08001325 }
1326
1327
Simon Hunt205099e2015-02-07 13:12:01 -08001328 function updateDetailPanel() {
1329 // TODO update detail panel
1330 $log.debug("TODO: updateDetailPanel() ...");
1331 }
1332
1333
1334 // ==========================
1335 // === SELECTION / DESELECTION
1336
1337 function selectObject(obj) {
1338 var el = this,
1339 ev = d3.event.sourceEvent,
1340 n;
1341
1342 if (zoomingOrPanning(ev)) {
1343 return;
1344 }
1345
1346 if (el) {
1347 n = d3.select(el);
1348 } else {
1349 node.each(function (d) {
1350 if (d == obj) {
1351 n = d3.select(el = this);
1352 }
1353 });
1354 }
1355 if (!n) return;
1356
1357 if (ev.shiftKey && n.classed('selected')) {
1358 deselectObject(obj.id);
1359 updateDetailPanel();
1360 return;
1361 }
1362
1363 if (!ev.shiftKey) {
1364 deselectAll();
1365 }
1366
1367 selections[obj.id] = { obj: obj, el: el };
1368 selectOrder.push(obj.id);
1369
1370 n.classed('selected', true);
1371 updateDeviceColors(obj);
1372 updateDetailPanel();
1373 }
1374
1375 function deselectObject(id) {
1376 var obj = selections[id];
1377 if (obj) {
1378 d3.select(obj.el).classed('selected', false);
1379 delete selections[id];
1380 fs.removeFromArray(id, selectOrder);
1381 updateDeviceColors(obj.obj);
1382 }
1383 }
1384
1385 function deselectAll() {
1386 // deselect all nodes in the network...
1387 node.classed('selected', false);
1388 selections = {};
1389 selectOrder = [];
1390 updateDeviceColors();
1391 updateDetailPanel();
1392 }
1393
Simon Huntac4c6f72015-02-03 19:50:53 -08001394 // ==========================
1395 // === MOUSE GESTURE HANDLERS
1396
Simon Hunt205099e2015-02-07 13:12:01 -08001397 function zoomingOrPanning(ev) {
1398 return ev.metaKey || ev.altKey;
Simon Hunt445e8152015-02-06 13:00:12 -08001399 }
1400
1401 function atDragEnd(d) {
1402 // once we've finished moving, pin the node in position
1403 d.fixed = true;
1404 d3.select(this).classed('fixed', true);
1405 sendUpdateMeta(d);
1406 }
1407
1408 // predicate that indicates when dragging is active
1409 function dragEnabled() {
1410 var ev = d3.event.sourceEvent;
1411 // nodeLock means we aren't allowing nodes to be dragged...
Simon Hunt205099e2015-02-07 13:12:01 -08001412 return !nodeLock && !zoomingOrPanning(ev);
Simon Hunt445e8152015-02-06 13:00:12 -08001413 }
1414
1415 // predicate that indicates when clicking is active
1416 function clickEnabled() {
1417 return true;
1418 }
Simon Hunt737c89f2015-01-28 12:23:19 -08001419
1420
1421 // ==========================
Simon Huntac4c6f72015-02-03 19:50:53 -08001422 // Module definition
Simon Hunt737c89f2015-01-28 12:23:19 -08001423
1424 angular.module('ovTopo')
1425 .factory('TopoForceService',
Simon Hunt1894d792015-02-04 17:09:20 -08001426 ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
Simon Hunt5724fb42015-02-05 16:59:40 -08001427 'FlashService', 'TopoInstService',
Simon Hunt737c89f2015-01-28 12:23:19 -08001428
Simon Hunt5724fb42015-02-05 16:59:40 -08001429 function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, _tis_) {
Simon Hunt737c89f2015-01-28 12:23:19 -08001430 $log = _$log_;
Simon Hunt1894d792015-02-04 17:09:20 -08001431 fs = _fs_;
Simon Hunt737c89f2015-01-28 12:23:19 -08001432 sus = _sus_;
Simon Huntac4c6f72015-02-03 19:50:53 -08001433 is = _is_;
1434 ts = _ts_;
Simon Hunt5724fb42015-02-05 16:59:40 -08001435 flash = _flash_;
Simon Huntac4c6f72015-02-03 19:50:53 -08001436 tis = _tis_;
Simon Hunt737c89f2015-01-28 12:23:19 -08001437
Simon Hunt1894d792015-02-04 17:09:20 -08001438 icfg = is.iconConfig();
1439
Simon Hunt737c89f2015-01-28 12:23:19 -08001440 // forceG is the SVG group to display the force layout in
Simon Huntac4c6f72015-02-03 19:50:53 -08001441 // xlink is the cross-link api from the main topo source file
Simon Hunt737c89f2015-01-28 12:23:19 -08001442 // w, h are the initial dimensions of the SVG
1443 // opts are, well, optional :)
Simon Hunt1894d792015-02-04 17:09:20 -08001444 function initForce(forceG, _uplink_, w, h, opts) {
Simon Hunta11b4eb2015-01-28 16:20:50 -08001445 $log.debug('initForce().. WxH = ' + w + 'x' + h);
Simon Hunt1894d792015-02-04 17:09:20 -08001446 uplink = _uplink_;
1447 width = w;
1448 height = h;
Simon Hunta11b4eb2015-01-28 16:20:50 -08001449
Simon Hunt737c89f2015-01-28 12:23:19 -08001450 settings = angular.extend({}, defaultSettings, opts);
1451
1452 linkG = forceG.append('g').attr('id', 'topo-links');
1453 linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels');
1454 nodeG = forceG.append('g').attr('id', 'topo-nodes');
1455
1456 link = linkG.selectAll('.link');
1457 linkLabel = linkLabelG.selectAll('.linkLabel');
1458 node = nodeG.selectAll('.node');
1459
1460 force = d3.layout.force()
Simon Hunta11b4eb2015-01-28 16:20:50 -08001461 .size([w, h])
Simon Hunt737c89f2015-01-28 12:23:19 -08001462 .nodes(network.nodes)
1463 .links(network.links)
1464 .gravity(settings.gravity)
1465 .friction(settings.friction)
1466 .charge(settings.charge._def_)
1467 .linkDistance(settings.linkDistance._def_)
1468 .linkStrength(settings.linkStrength._def_)
1469 .on('tick', tick);
1470
1471 drag = sus.createDragBehavior(force,
Simon Hunt205099e2015-02-07 13:12:01 -08001472 selectObject, atDragEnd, dragEnabled, clickEnabled);
Simon Hunt737c89f2015-01-28 12:23:19 -08001473 }
1474
Simon Huntb0ec1e52015-01-28 18:13:49 -08001475 function resize(dim) {
Simon Hunt1894d792015-02-04 17:09:20 -08001476 width = dim.width;
1477 height = dim.height;
1478 force.size([width, height]);
Simon Hunt737c89f2015-01-28 12:23:19 -08001479 // Review -- do we need to nudge the layout ?
Simon Hunt737c89f2015-01-28 12:23:19 -08001480 }
1481
1482 return {
1483 initForce: initForce,
Simon Huntac4c6f72015-02-03 19:50:53 -08001484 resize: resize,
1485
1486 updateDeviceColors: updateDeviceColors,
Simon Hunt5724fb42015-02-05 16:59:40 -08001487 toggleHosts: toggleHosts,
1488 toggleOffline: toggleOffline,
1489 cycleDeviceLabels: cycleDeviceLabels,
Simon Hunt445e8152015-02-06 13:00:12 -08001490 unpin: unpin,
Simon Huntac4c6f72015-02-03 19:50:53 -08001491
1492 addDevice: addDevice,
Simon Hunt1894d792015-02-04 17:09:20 -08001493 updateDevice: updateDevice,
1494 removeDevice: removeDevice,
1495 addHost: addHost,
1496 updateHost: updateHost,
1497 removeHost: removeHost,
1498 addLink: addLink,
1499 updateLink: updateLink,
1500 removeLink: removeLink
Simon Hunt737c89f2015-01-28 12:23:19 -08001501 };
1502 }]);
1503}());