diff options
author | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
---|---|---|
committer | Ashlee Young <ashlee@onosfw.com> | 2015-09-09 22:15:21 -0700 |
commit | 13d05bc8458758ee39cb829098241e89616717ee (patch) | |
tree | 22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoForce.js | |
parent | 6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff) |
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoForce.js')
-rw-r--r-- | framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoForce.js | 1134 |
1 files changed, 1134 insertions, 0 deletions
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoForce.js b/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoForce.js new file mode 100644 index 00000000..dbe8f9f5 --- /dev/null +++ b/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoForce.js @@ -0,0 +1,1134 @@ +/* + * Copyright 2015 Open Networking Laboratory + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/* + ONOS GUI -- Topology Force Module. + Visualization of the topology in an SVG layer, using a D3 Force Layout. + */ + +(function () { + 'use strict'; + + // injected refs + var $log, $timeout, fs, sus, ts, flash, wss, tov, + tis, tms, td3, tss, tts, tos, fltr, tls, uplink, svg; + + // configuration + var linkConfig = { + light: { + baseColor: '#666', + inColor: '#66f', + outColor: '#f00' + }, + dark: { + baseColor: '#aaa', + inColor: '#66f', + outColor: '#f66' + }, + inWidth: 12, + outWidth: 10 + }; + + // internal state + var settings, // merged default settings and options + force, // force layout object + drag, // drag behavior handler + network = { + nodes: [], + links: [], + linksByDevice: {}, + lookup: {}, + revLinkToKey: {} + }, + lu, // shorthand for lookup + rlk, // shorthand for revLinktoKey + showHosts = false, // whether hosts are displayed + showOffline = true, // whether offline devices are displayed + nodeLock = false, // whether nodes can be dragged or not (locked) + fTimer, // timer for delayed force layout + fNodesTimer, // timer for delayed nodes update + fLinksTimer, // timer for delayed links update + dim, // the dimensions of the force layout [w,h] + linkNums = []; // array of link number labels + + // SVG elements; + var linkG, linkLabelG, numLinkLblsG, portLabelG, nodeG; + + // D3 selections; + var link, linkLabel, node; + + // default settings for force layout + var defaultSettings = { + gravity: 0.4, + friction: 0.7, + charge: { + // note: key is node.class + device: -8000, + host: -5000, + _def_: -12000 + }, + linkDistance: { + // note: key is link.type + direct: 100, + optical: 120, + hostLink: 3, + _def_: 50 + }, + linkStrength: { + // note: key is link.type + // range: {0.0 ... 1.0} + //direct: 1.0, + //optical: 1.0, + //hostLink: 1.0, + _def_: 1.0 + } + }; + + + // ========================== + // === EVENT HANDLERS + + function addDevice(data) { + var id = data.id, + d; + + uplink.showNoDevs(false); + + // although this is an add device event, if we already have the + // device, treat it as an update instead.. + if (lu[id]) { + updateDevice(data); + return; + } + + d = tms.createDeviceNode(data); + network.nodes.push(d); + lu[id] = d; + updateNodes(); + fStart(); + } + + function updateDevice(data) { + var id = data.id, + d = lu[id], + wasOnline; + + if (d) { + wasOnline = d.online; + angular.extend(d, data); + if (tms.positionNode(d, true)) { + sendUpdateMeta(d); + } + updateNodes(); + if (wasOnline !== d.online) { + tms.findAttachedLinks(d.id).forEach(restyleLinkElement); + updateOfflineVisibility(d); + } + } + } + + function removeDevice(data) { + var id = data.id, + d = lu[id]; + if (d) { + removeDeviceElement(d); + } + } + + function addHost(data) { + var id = data.id, + d, lnk; + + // although this is an add host event, if we already have the + // host, treat it as an update instead.. + if (lu[id]) { + updateHost(data); + return; + } + + d = tms.createHostNode(data); + network.nodes.push(d); + lu[id] = d; + updateNodes(); + + lnk = tms.createHostLink(data); + if (lnk) { + d.linkData = lnk; // cache ref on its host + network.links.push(lnk); + lu[d.ingress] = lnk; + lu[d.egress] = lnk; + updateLinks(); + } + fStart(); + } + + function updateHost(data) { + var id = data.id, + d = lu[id]; + if (d) { + angular.extend(d, data); + if (tms.positionNode(d, true)) { + sendUpdateMeta(d); + } + updateNodes(); + } + } + + function removeHost(data) { + var id = data.id, + d = lu[id]; + if (d) { + removeHostElement(d, true); + } + } + + function addLink(data) { + var result = tms.findLink(data, 'add'), + bad = result.badLogic, + d = result.ldata; + + if (bad) { + //logicError(bad + ': ' + link.id); + return; + } + + if (d) { + // we already have a backing store link for src/dst nodes + addLinkUpdate(d, data); + return; + } + + // no backing store link yet + d = tms.createLink(data); + if (d) { + network.links.push(d); + aggregateLink(d, data); + lu[d.key] = d; + updateLinks(); + fStart(); + } + } + + function updateLink(data) { + var result = tms.findLink(data, 'update'), + bad = result.badLogic; + if (bad) { + //logicError(bad + ': ' + link.id); + return; + } + result.updateWith(link); + } + + function removeLink(data) { + var result = tms.findLink(data, 'remove'); + + if (!result.badLogic) { + result.removeRawLink(); + } + } + + // ======================== + + function nodeById(id) { + return lu[id]; + } + + function makeNodeKey(node1, node2) { + return node1 + '-' + node2; + } + + function findNodePair(key, keyRev) { + if (network.linksByDevice[key]) { + return key; + } else if (network.linksByDevice[keyRev]) { + return keyRev; + } else { + return false; + } + } + + function aggregateLink(ldata, link) { + var key = makeNodeKey(link.src, link.dst), + keyRev = makeNodeKey(link.dst, link.src), + found = findNodePair(key, keyRev); + + if (found) { + network.linksByDevice[found].push(ldata); + ldata.devicePair = found; + } else { + network.linksByDevice[key] = [ ldata ]; + ldata.devicePair = key; + } + } + + function addLinkUpdate(ldata, link) { + // add link event, but we already have the reverse link installed + ldata.fromTarget = link; + rlk[link.id] = ldata.key; + // possible solution to el being undefined in restyleLinkElement: + //_updateLinks(); + restyleLinkElement(ldata); + } + + + var widthRatio = 1.4, + linkScale = d3.scale.linear() + .domain([1, 12]) + .range([widthRatio, 12 * widthRatio]) + .clamp(true), + allLinkTypes = 'direct indirect optical tunnel'; + + function restyleLinkElement(ldata, immediate) { + // this fn's job is to look at raw links and decide what svg classes + // need to be applied to the line element in the DOM + var th = ts.theme(), + el = ldata.el, + type = ldata.type(), + lw = ldata.linkWidth(), + online = ldata.online(), + delay = immediate ? 0 : 1000; + + // FIXME: understand why el is sometimes undefined on addLink events... + // Investigated: + // el is undefined when it's a reverse link that is being added. + // updateLinks (which sets ldata.el) isn't called before this is called. + // Calling _updateLinks in addLinkUpdate fixes it, but there might be + // a more efficient way to fix it. + if (el && !el.empty()) { + el.classed('link', true); + el.classed('inactive', !online); + el.classed(allLinkTypes, false); + if (type) { + el.classed(type, true); + } + el.transition() + .duration(delay) + .attr('stroke-width', linkScale(lw)) + .attr('stroke', linkConfig[th].baseColor); + } + } + + function removeLinkElement(d) { + var idx = fs.find(d.key, network.links, 'key'), + removed; + if (idx >=0) { + // remove from links array + removed = network.links.splice(idx, 1); + // remove from lookup cache + delete lu[removed[0].key]; + updateLinks(); + fResume(); + } + } + + function removeHostElement(d, upd) { + // first, remove associated hostLink... + removeLinkElement(d.linkData); + + // remove hostLink bindings + delete lu[d.ingress]; + delete lu[d.egress]; + + // remove from lookup cache + delete lu[d.id]; + // remove from nodes array + var idx = fs.find(d.id, network.nodes); + network.nodes.splice(idx, 1); + + // remove from SVG + // NOTE: upd is false if we were called from removeDeviceElement() + if (upd) { + updateNodes(); + fResume(); + } + } + + function removeDeviceElement(d) { + var id = d.id, + idx; + // first, remove associated hosts and links.. + tms.findAttachedHosts(id).forEach(removeHostElement); + tms.findAttachedLinks(id).forEach(removeLinkElement); + + // remove from lookup cache + delete lu[id]; + // remove from nodes array + idx = fs.find(id, network.nodes); + if (idx > -1) { + network.nodes.splice(idx, 1); + } + + if (!network.nodes.length) { + uplink.showNoDevs(true); + } + + // remove from SVG + updateNodes(); + fResume(); + } + + function updateHostVisibility() { + sus.visible(nodeG.selectAll('.host'), showHosts); + sus.visible(linkG.selectAll('.hostLink'), showHosts); + sus.visible(linkLabelG.selectAll('.hostLinkLabel'), showHosts); + } + + function updateOfflineVisibility(dev) { + function updDev(d, show) { + var b; + sus.visible(d.el, show); + + tms.findAttachedLinks(d.id).forEach(function (link) { + b = show && ((link.type() !== 'hostLink') || showHosts); + sus.visible(link.el, b); + }); + tms.findAttachedHosts(d.id).forEach(function (host) { + b = show && showHosts; + sus.visible(host.el, b); + }); + } + + if (dev) { + // updating a specific device that just toggled off/on-line + updDev(dev, dev.online || showOffline); + } else { + // updating all offline devices + tms.findDevices(true).forEach(function (d) { + updDev(d, showOffline); + }); + } + } + + + function sendUpdateMeta(d, clearPos) { + var metaUi = {}, + ll; + + // if we are not clearing the position data (unpinning), + // attach the x, y, longitude, latitude... + if (!clearPos) { + ll = tms.lngLatFromCoord([d.x, d.y]); + metaUi = {x: d.x, y: d.y, lng: ll[0], lat: ll[1]}; + } + d.metaUi = metaUi; + wss.sendEvent('updateMeta', { + id: d.id, + class: d.class, + memento: metaUi + }); + } + + + function mkSvgClass(d) { + return d.fixed ? d.svgClass + ' fixed' : d.svgClass; + } + + function vis(b) { + return b ? 'visible' : 'hidden'; + } + + function toggleHosts(x) { + var kev = (x === 'keyev'), + on = kev ? !showHosts : !!x; + + showHosts = on; + updateHostVisibility(); + flash.flash('Hosts ' + vis(on)); + return on; + } + + function toggleOffline(x) { + var kev = (x === 'keyev'), + on = kev ? !showOffline : !!x; + + showOffline = on; + updateOfflineVisibility(); + flash.flash('Offline devices ' + vis(on)); + return on; + } + + function cycleDeviceLabels() { + flash.flash(td3.incDevLabIndex()); + tms.findDevices().forEach(function (d) { + td3.updateDeviceLabel(d); + }); + } + + function unpin() { + var hov = tss.hovered(); + if (hov) { + sendUpdateMeta(hov, true); + hov.fixed = false; + hov.el.classed('fixed', false); + fResume(); + } + } + + function showMastership(masterId) { + if (!masterId) { + restoreLayerState(); + } else { + showMastershipFor(masterId); + } + } + + function restoreLayerState() { + // NOTE: this level of indirection required, for when we have + // the layer filter functionality re-implemented + suppressLayers(false); + } + + function showMastershipFor(id) { + suppressLayers(true); + node.each(function (n) { + if (n.master === id) { + n.el.classed('suppressedmax', false); + } + }); + } + + function supAmt(less) { + return less ? "suppressed" : "suppressedmax"; + } + + function suppressLayers(b, less) { + var cls = supAmt(less); + node.classed(cls, b); + link.classed(cls, b); + } + + function unsuppressNode(id, less) { + var cls = supAmt(less); + node.each(function (n) { + if (n.id === id) { + n.el.classed(cls, false); + } + }); + } + + function unsuppressLink(key, less) { + var cls = supAmt(less); + link.each(function (n) { + if (n.key === key) { + n.el.classed(cls, false); + } + }); + } + + function showBadLinks() { + var badLinks = tms.findBadLinks(); + flash.flash('Bad Links: ' + badLinks.length); + $log.debug('Bad Link List (' + badLinks.length + '):'); + badLinks.forEach(function (d) { + $log.debug('bad link: (' + d.bad + ') ' + d.key, d); + if (d.el) { + d.el.attr('stroke-width', linkScale(2.8)) + .attr('stroke', 'red'); + } + }); + // back to normal after 2 seconds... + $timeout(updateLinks, 2000); + } + + // ========================================== + + function updateNodes() { + if (fNodesTimer) { + $timeout.cancel(fNodesTimer); + } + fNodesTimer = $timeout(_updateNodes, 150); + } + + // IMPLEMENTATION NOTE: _updateNodes() should NOT stop, start, or resume + // the force layout; that needs to be determined and implemented elsewhere + function _updateNodes() { + // select all the nodes in the layout: + node = nodeG.selectAll('.node') + .data(network.nodes, function (d) { return d.id; }); + + // operate on existing nodes: + node.filter('.device').each(td3.deviceExisting); + node.filter('.host').each(td3.hostExisting); + + // operate on entering nodes: + var entering = node.enter() + .append('g') + .attr({ + id: function (d) { return sus.safeId(d.id); }, + class: mkSvgClass, + transform: function (d) { + // Need to guard against NaN here ?? + return sus.translate(d.x, d.y); + }, + opacity: 0 + }) + .call(drag) + .on('mouseover', tss.nodeMouseOver) + .on('mouseout', tss.nodeMouseOut) + .transition() + .attr('opacity', 1); + + // augment entering nodes: + entering.filter('.device').each(td3.deviceEnter); + entering.filter('.host').each(td3.hostEnter); + + // operate on both existing and new nodes: + td3.updateDeviceColors(); + + // operate on exiting nodes: + // Note that the node is removed after 2 seconds. + // Sub element animations should be shorter than 2 seconds. + var exiting = node.exit() + .transition() + .duration(2000) + .style('opacity', 0) + .remove(); + + // exiting node specifics: + exiting.filter('.host').each(td3.hostExit); + exiting.filter('.device').each(td3.deviceExit); + } + + // ========================== + + function getDefaultPos(link) { + return { + x1: link.source.x, + y1: link.source.y, + x2: link.target.x, + y2: link.target.y + }; + } + + // returns amount of adjustment along the normal for given link + function amt(numLinks, linkIdx) { + var gap = 6; + return (linkIdx - ((numLinks - 1) / 2)) * gap; + } + + function calcMovement(d, amt, flipped) { + var pos = getDefaultPos(d), + mult = flipped ? -amt : amt, + dx = pos.x2 - pos.x1, + dy = pos.y2 - pos.y1, + length = Math.sqrt((dx * dx) + (dy * dy)); + + return { + x1: pos.x1 + (mult * dy / length), + y1: pos.y1 + (mult * -dx / length), + x2: pos.x2 + (mult * dy / length), + y2: pos.y2 + (mult * -dx / length) + }; + } + + function calcPosition() { + var lines = this, + linkSrcId; + linkNums = []; + lines.each(function (d) { + if (d.type() === 'hostLink') { + d.position = getDefaultPos(d); + } + }); + + function normalizeLinkSrc(link) { + // ensure source device is consistent across set of links + // temporary measure until link modeling is refactored + if (!linkSrcId) { + linkSrcId = link.source.id; + return false; + } + + return link.source.id !== linkSrcId; + } + + angular.forEach(network.linksByDevice, function (linkArr, key) { + var numLinks = linkArr.length, + link; + + if (numLinks === 1) { + link = linkArr[0]; + link.position = getDefaultPos(link); + link.position.multiLink = false; + } else if (numLinks >= 5) { + // this code is inefficient, in the future the way links + // are modeled will be changed + angular.forEach(linkArr, function (link) { + link.position = getDefaultPos(link); + link.position.multiLink = true; + }); + linkNums.push({ + id: key, + num: numLinks, + linkCoords: linkArr[0].position + }); + } else { + linkSrcId = null; + angular.forEach(linkArr, function (link, index) { + var offsetAmt = amt(numLinks, index), + needToFlip = normalizeLinkSrc(link); + link.position = calcMovement(link, offsetAmt, needToFlip); + link.position.multiLink = false; + }); + } + }); + } + + function updateLinks() { + if (fLinksTimer) { + $timeout.cancel(fLinksTimer); + } + fLinksTimer = $timeout(_updateLinks, 150); + } + + // IMPLEMENTATION NOTE: _updateLinks() should NOT stop, start, or resume + // the force layout; that needs to be determined and implemented elsewhere + function _updateLinks() { + var th = ts.theme(); + + link = linkG.selectAll('.link') + .data(network.links, function (d) { return d.key; }); + + // operate on existing links: + link.each(function (d) { + // this is supposed to be an existing link, but we have observed + // occasions (where links are deleted and added rapidly?) where + // the DOM element has not been defined. So protect against that... + if (d.el) { + restyleLinkElement(d, true); + } + }); + + // operate on entering links: + var entering = link.enter() + .append('line') + .call(calcPosition) + .attr({ + x1: function (d) { return d.position.x1; }, + y1: function (d) { return d.position.y1; }, + x2: function (d) { return d.position.x2; }, + y2: function (d) { return d.position.y2; }, + stroke: linkConfig[th].inColor, + 'stroke-width': linkConfig.inWidth + }); + + // augment links + entering.each(td3.linkEntering); + + // operate on both existing and new links: + //link.each(...) + + // add labels for how many links are in a thick line + td3.applyNumLinkLabels(linkNums, numLinkLblsG); + + // apply or remove labels + td3.applyLinkLabels(); + + // operate on exiting links: + link.exit() + .attr('stroke-dasharray', '3 3') + .attr('stroke', linkConfig[th].outColor) + .style('opacity', 0.5) + .transition() + .duration(1500) + .attr({ + 'stroke-dasharray': '3 12', + 'stroke-width': linkConfig.outWidth + }) + .style('opacity', 0.0) + .remove(); + } + + + // ========================== + // force layout tick function + + function fResume() { + if (!tos.isOblique()) { + force.resume(); + } + } + + function fStart() { + if (!tos.isOblique()) { + if (fTimer) { + $timeout.cancel(fTimer); + } + fTimer = $timeout(function () { + $log.debug("Starting force-layout"); + force.start(); + }, 200); + } + } + + var tickStuff = { + nodeAttr: { + transform: function (d) { + var dx = isNaN(d.x) ? 0 : d.x, + dy = isNaN(d.y) ? 0 : d.y; + return sus.translate(dx, dy); + } + }, + linkAttr: { + x1: function (d) { return d.position.x1; }, + y1: function (d) { return d.position.y1; }, + x2: function (d) { return d.position.x2; }, + y2: function (d) { return d.position.y2; } + }, + linkLabelAttr: { + transform: function (d) { + var lnk = tms.findLinkById(d.key); + if (lnk) { + return td3.transformLabel(lnk.position); + } + } + } + }; + + function tick() { + // guard against null (which can happen when our view pages out)... + if (node && node.size()) { + node.attr(tickStuff.nodeAttr); + } + if (link && link.size()) { + link.call(calcPosition) + .attr(tickStuff.linkAttr); + td3.applyNumLinkLabels(linkNums, numLinkLblsG); + } + if (linkLabel && linkLabel.size()) { + linkLabel.attr(tickStuff.linkLabelAttr); + } + } + + + // ========================== + // === MOUSE GESTURE HANDLERS + + function zoomingOrPanning(ev) { + return ev.metaKey || ev.altKey; + } + + function atDragEnd(d) { + // once we've finished moving, pin the node in position + d.fixed = true; + d3.select(this).classed('fixed', true); + sendUpdateMeta(d); + tss.clickConsumed(true); + } + + // predicate that indicates when dragging is active + function dragEnabled() { + var ev = d3.event.sourceEvent; + // nodeLock means we aren't allowing nodes to be dragged... + return !nodeLock && !zoomingOrPanning(ev); + } + + // predicate that indicates when clicking is active + function clickEnabled() { + return true; + } + + // ============================================= + // function entry points for overlay module + + // TODO: find an automatic way of tracking via the "showHighlights" events + var allTrafficClasses = 'primary secondary optical animated ' + + 'port-traffic-Kbps port-traffic-Mbps port-traffic-Gbps ' + + 'port-traffic-Gbps-choked'; + + function clearLinkTrafficStyle() { + link.style('stroke-width', null) + .classed(allTrafficClasses, false); + } + + function removeLinkLabels() { + network.links.forEach(function (d) { + d.label = ''; + }); + } + + function updateLinkLabelModel() { + // create the backing data for showing labels.. + var data = []; + link.each(function (d) { + if (d.label) { + data.push({ + id: 'lab-' + d.key, + key: d.key, + label: d.label, + ldata: d + }); + } + }); + + linkLabel = linkLabelG.selectAll('.linkLabel') + .data(data, function (d) { return d.id; }); + } + + // ========================== + // Module definition + + function mkModelApi(uplink) { + return { + projection: uplink.projection, + network: network, + restyleLinkElement: restyleLinkElement, + removeLinkElement: removeLinkElement + }; + } + + function mkD3Api() { + return { + node: function () { return node; }, + link: function () { return link; }, + linkLabel: function () { return linkLabel; }, + instVisible: function () { return tis.isVisible(); }, + posNode: tms.positionNode, + showHosts: function () { return showHosts; }, + restyleLinkElement: restyleLinkElement, + updateLinkLabelModel: updateLinkLabelModel, + linkConfig: function () { return linkConfig; } + }; + } + + function mkSelectApi() { + return { + node: function () { return node; }, + zoomingOrPanning: zoomingOrPanning, + updateDeviceColors: td3.updateDeviceColors, + deselectLink: tls.deselectLink + }; + } + + function mkTrafficApi() { + return { + hovered: tss.hovered, + somethingSelected: tss.somethingSelected, + selectOrder: tss.selectOrder + }; + } + + function mkOverlayApi() { + return { + clearLinkTrafficStyle: clearLinkTrafficStyle, + removeLinkLabels: removeLinkLabels, + findLinkById: tms.findLinkById, + findNodeById: nodeById, + updateLinks: updateLinks, + updateNodes: updateNodes, + supLayers: suppressLayers, + unsupNode: unsuppressNode, + unsupLink: unsuppressLink + }; + } + + function mkObliqueApi(uplink, fltr) { + return { + force: function() { return force; }, + zoomLayer: uplink.zoomLayer, + nodeGBBox: function() { return nodeG.node().getBBox(); }, + node: function () { return node; }, + link: function () { return link; }, + linkLabel: function () { return linkLabel; }, + nodes: function () { return network.nodes; }, + tickStuff: tickStuff, + nodeLock: function (b) { + var old = nodeLock; + nodeLock = b; + return old; + }, + opacifyMap: uplink.opacifyMap, + inLayer: fltr.inLayer, + calcLinkPos: calcPosition, + applyNumLinkLabels: function () { + td3.applyNumLinkLabels(linkNums, numLinkLblsG); + } + }; + } + + function mkFilterApi() { + return { + node: function () { return node; }, + link: function () { return link; } + }; + } + + function mkLinkApi(svg, uplink) { + return { + svg: svg, + zoomer: uplink.zoomer(), + network: network, + portLabelG: function () { return portLabelG; }, + showHosts: function () { return showHosts; } + }; + } + + angular.module('ovTopo') + .factory('TopoForceService', + ['$log', '$timeout', 'FnService', 'SvgUtilService', + 'ThemeService', 'FlashService', 'WebSocketService', + 'TopoOverlayService', 'TopoInstService', 'TopoModelService', + 'TopoD3Service', 'TopoSelectService', 'TopoTrafficService', + 'TopoObliqueService', 'TopoFilterService', 'TopoLinkService', + + function (_$log_, _$timeout_, _fs_, _sus_, _ts_, _flash_, _wss_, _tov_, + _tis_, _tms_, _td3_, _tss_, _tts_, _tos_, _fltr_, _tls_) { + $log = _$log_; + $timeout = _$timeout_; + fs = _fs_; + sus = _sus_; + ts = _ts_; + flash = _flash_; + wss = _wss_; + tov = _tov_; + tis = _tis_; + tms = _tms_; + td3 = _td3_; + tss = _tss_; + tts = _tts_; + tos = _tos_; + fltr = _fltr_; + tls = _tls_; + + var themeListener = ts.addListener(function () { + updateLinks(); + updateNodes(); + }); + + // forceG is the SVG group to display the force layout in + // uplink is the api from the main topo source file + // dim is the initial dimensions of the SVG as [w,h] + // opts are, well, optional :) + function initForce(_svg_, forceG, _uplink_, _dim_, opts) { + uplink = _uplink_; + dim = _dim_; + svg = _svg_; + + lu = network.lookup; + rlk = network.revLinkToKey; + + $log.debug('initForce().. dim = ' + dim); + + tov.setApi(mkOverlayApi(), tss); + tms.initModel(mkModelApi(uplink), dim); + td3.initD3(mkD3Api()); + tss.initSelect(mkSelectApi()); + tts.initTraffic(mkTrafficApi()); + tos.initOblique(mkObliqueApi(uplink, fltr)); + fltr.initFilter(mkFilterApi()); + tls.initLink(mkLinkApi(svg, uplink), td3); + + settings = angular.extend({}, defaultSettings, opts); + + linkG = forceG.append('g').attr('id', 'topo-links'); + linkLabelG = forceG.append('g').attr('id', 'topo-linkLabels'); + numLinkLblsG = forceG.append('g').attr('id', 'topo-numLinkLabels'); + nodeG = forceG.append('g').attr('id', 'topo-nodes'); + portLabelG = forceG.append('g').attr('id', 'topo-portLabels'); + + link = linkG.selectAll('.link'); + linkLabel = linkLabelG.selectAll('.linkLabel'); + node = nodeG.selectAll('.node'); + + force = d3.layout.force() + .size(dim) + .nodes(network.nodes) + .links(network.links) + .gravity(settings.gravity) + .friction(settings.friction) + .charge(settings.charge._def_) + .linkDistance(settings.linkDistance._def_) + .linkStrength(settings.linkStrength._def_) + .on('tick', tick); + + drag = sus.createDragBehavior(force, + tss.selectObject, atDragEnd, dragEnabled, clickEnabled); + } + + function newDim(_dim_) { + dim = _dim_; + force.size(dim); + tms.newDim(dim); + } + + function destroyForce() { + force.stop(); + + tls.destroyLink(); + tos.destroyOblique(); + tts.destroyTraffic(); + tss.destroySelect(); + td3.destroyD3(); + tms.destroyModel(); + // note: no need to destroy overlay service + ts.removeListener(themeListener); + themeListener = null; + + // clean up the DOM + svg.selectAll('g').remove(); + svg.selectAll('defs').remove(); + + // clean up internal state + network.nodes = []; + network.links = []; + network.linksByDevice = {}; + network.lookup = {}; + network.revLinkToKey = {}; + + linkNums = []; + + linkG = linkLabelG = numLinkLblsG = nodeG = portLabelG = null; + link = linkLabel = node = null; + force = drag = null; + + // clean up $timeout promises + if (fTimer) { + $timeout.cancel(fTimer); + } + if (fNodesTimer) { + $timeout.cancel(fNodesTimer); + } + if (fLinksTimer) { + $timeout.cancel(fLinksTimer); + } + } + + return { + initForce: initForce, + newDim: newDim, + destroyForce: destroyForce, + + updateDeviceColors: td3.updateDeviceColors, + toggleHosts: toggleHosts, + togglePorts: tls.togglePorts, + toggleOffline: toggleOffline, + cycleDeviceLabels: cycleDeviceLabels, + unpin: unpin, + showMastership: showMastership, + showBadLinks: showBadLinks, + + addDevice: addDevice, + updateDevice: updateDevice, + removeDevice: removeDevice, + addHost: addHost, + updateHost: updateHost, + removeHost: removeHost, + addLink: addLink, + updateLink: updateLink, + removeLink: removeLink + }; + }]); +}()); |