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/topoD3.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/topoD3.js')
-rw-r--r-- | framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoD3.js | 576 |
1 files changed, 576 insertions, 0 deletions
diff --git a/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoD3.js b/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoD3.js new file mode 100644 index 00000000..d29748b1 --- /dev/null +++ b/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoD3.js @@ -0,0 +1,576 @@ +/* + * 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 D3 Module. + Functions for manipulating the D3 visualizations of the Topology + */ + +(function () { + 'use strict'; + + // injected refs + var $log, fs, sus, is, ts; + + // api to topoForce + var api; + /* + node() // get ref to D3 selection of nodes + link() // get ref to D3 selection of links + linkLabel() // get ref to D3 selection of link labels + instVisible() // true if instances panel is visible + posNode() // position node + showHosts() // true if hosts are to be shown + restyleLinkElement() // update link styles based on backing data + updateLinkLabelModel() // update backing data for link labels + */ + + // configuration + var devCfg = { + xoff: -20, + yoff: -18 + }, + labelConfig = { + imgPad: 16, + padLR: 4, + padTB: 3, + marginLR: 3, + marginTB: 2, + port: { + gap: 3, + width: 18, + height: 14 + } + }, + icfg; + + // internal state + var deviceLabelIndex = 0, + hostLabelIndex = 0; + + + var dCol = { + black: '#000', + paleblue: '#acf', + offwhite: '#ddd', + darkgrey: '#444', + midgrey: '#888', + lightgrey: '#bbb', + orange: '#f90' + }; + + // note: these are the device icon colors without affinity + var dColTheme = { + light: { + rfill: dCol.offwhite, + online: { + glyph: dCol.darkgrey, + rect: dCol.paleblue + }, + offline: { + glyph: dCol.midgrey, + rect: dCol.lightgrey + } + }, + dark: { + rfill: dCol.midgrey, + online: { + glyph: dCol.darkgrey, + rect: dCol.paleblue + }, + offline: { + glyph: dCol.midgrey, + rect: dCol.darkgrey + } + } + }; + + function devBaseColor(d) { + var o = d.online ? 'online' : 'offline'; + return dColTheme[ts.theme()][o]; + } + + function setDeviceColor(d) { + var o = d.online, + s = d.el.classed('selected'), + c = devBaseColor(d), + a = instColor(d.master, o), + icon = d.el.select('g.deviceIcon'), + g, r; + + if (s) { + g = c.glyph; + r = dCol.orange; + } else if (api.instVisible()) { + g = o ? a : c.glyph; + r = o ? c.rfill : a; + } else { + g = c.glyph; + r = c.rect; + } + + icon.select('use').style('fill', g); + icon.select('rect').style('fill', r); + } + + function instColor(id, online) { + return sus.cat7().getColor(id, !online, ts.theme()); + } + + // ==== + + function incDevLabIndex() { + deviceLabelIndex = (deviceLabelIndex+1) % 3; + switch(deviceLabelIndex) { + case 0: return 'Hide device labels'; + case 1: return 'Show friendly device labels'; + case 2: return 'Show device ID labels'; + } + } + + // Returns the newly computed bounding box of the rectangle + function adjustRectToFitText(n) { + var text = n.select('text'), + box = text.node().getBBox(), + lab = labelConfig; + + text.attr('text-anchor', 'middle') + .attr('y', '-0.8em') + .attr('x', lab.imgPad/2); + + // translate the bbox so that it is centered on [x,y] + box.x = -box.width / 2; + box.y = -box.height / 2; + + // add padding + box.x -= (lab.padLR + lab.imgPad/2); + box.width += lab.padLR * 2 + lab.imgPad; + box.y -= lab.padTB; + box.height += lab.padTB * 2; + + return box; + } + + function hostLabel(d) { + var idx = (hostLabelIndex < d.labels.length) ? hostLabelIndex : 0; + return d.labels[idx]; + } + function deviceLabel(d) { + var idx = (deviceLabelIndex < d.labels.length) ? deviceLabelIndex : 0; + return d.labels[idx]; + } + function trimLabel(label) { + return (label && label.trim()) || ''; + } + + function emptyBox() { + return { + x: -2, + y: -2, + width: 4, + height: 4 + }; + } + + + function updateDeviceLabel(d) { + var label = trimLabel(deviceLabel(d)), + noLabel = !label, + node = d.el, + dim = icfg.device.dim, + box, dx, dy; + + node.select('text') + .text(label) + .style('opacity', 0) + .transition() + .style('opacity', 1); + + if (noLabel) { + box = emptyBox(); + dx = -dim/2; + dy = -dim/2; + } else { + box = adjustRectToFitText(node); + dx = box.x + devCfg.xoff; + dy = box.y + devCfg.yoff; + } + + node.select('rect') + .transition() + .attr(box); + + node.select('g.deviceIcon') + .transition() + .attr('transform', sus.translate(dx, dy)); + } + + function updateHostLabel(d) { + var label = trimLabel(hostLabel(d)); + d.el.select('text').text(label); + } + + function updateDeviceColors(d) { + if (d) { + setDeviceColor(d); + } else { + api.node().filter('.device').each(function (d) { + setDeviceColor(d); + }); + } + } + + + // ========================== + // updateNodes - subfunctions + + function deviceExisting(d) { + var node = d.el; + node.classed('online', d.online); + updateDeviceLabel(d); + api.posNode(d, true); + } + + function hostExisting(d) { + updateHostLabel(d); + api.posNode(d, true); + } + + function deviceEnter(d) { + var node = d3.select(this), + glyphId = d.type || 'unknown', + label = trimLabel(deviceLabel(d)), + //devCfg = deviceIconConfig, + noLabel = !label, + box, dx, dy, icon; + + d.el = node; + + node.append('rect').attr({ rx: 5, ry: 5 }); + node.append('text').text(label).attr('dy', '1.1em'); + box = adjustRectToFitText(node); + node.select('rect').attr(box); + + icon = is.addDeviceIcon(node, glyphId); + + if (noLabel) { + dx = -icon.dim/2; + dy = -icon.dim/2; + } else { + box = adjustRectToFitText(node); + dx = box.x + devCfg.xoff; + dy = box.y + devCfg.yoff; + } + + icon.attr('transform', sus.translate(dx, dy)); + } + + function hostEnter(d) { + var node = d3.select(this), + gid = d.type || 'unknown', + rad = icfg.host.radius, + r = d.type ? rad.withGlyph : rad.noGlyph, + textDy = r + 10; + + d.el = node; + sus.visible(node, api.showHosts()); + + is.addHostIcon(node, r, gid); + + node.append('text') + .text(hostLabel) + .attr('dy', textDy) + .attr('text-anchor', 'middle'); + } + + function hostExit(d) { + var node = d.el; + node.select('use') + .style('opacity', 0.5) + .transition() + .duration(800) + .style('opacity', 0); + + node.select('text') + .style('opacity', 0.5) + .transition() + .duration(800) + .style('opacity', 0); + + node.select('circle') + .style('stroke-fill', '#555') + .style('fill', '#888') + .style('opacity', 0.5) + .transition() + .duration(1500) + .attr('r', 0); + } + + function deviceExit(d) { + var node = d.el; + node.select('use') + .style('opacity', 0.5) + .transition() + .duration(800) + .style('opacity', 0); + + node.selectAll('rect') + .style('stroke-fill', '#555') + .style('fill', '#888') + .style('opacity', 0.5); + } + + + // ========================== + // updateLinks - subfunctions + + function linkEntering(d) { + var link = d3.select(this); + d.el = link; + api.restyleLinkElement(d); + if (d.type() === 'hostLink') { + sus.visible(link, api.showHosts()); + } + } + + var linkLabelOffset = '0.3em'; + + function applyLinkLabels() { + var entering; + + api.updateLinkLabelModel(); + + // for elements already existing, we need to update the text + // and adjust the rectangle size to fit + api.linkLabel().each(function (d) { + var el = d3.select(this), + rect = el.select('rect'), + text = el.select('text'); + text.text(d.label); + rect.attr(rectAroundText(el)); + }); + + entering = api.linkLabel().enter().append('g') + .classed('linkLabel', true) + .attr('id', function (d) { return d.id; }); + + entering.each(function (d) { + var el = d3.select(this), + rect, + text; + + if (d.ldata.type() === 'hostLink') { + el.classed('hostLinkLabel', true); + sus.visible(el, api.showHosts()); + } + + d.el = el; + rect = el.append('rect'); + text = el.append('text').text(d.label); + rect.attr(rectAroundText(el)); + text.attr('dy', linkLabelOffset); + + el.attr('transform', transformLabel(d.ldata.position)); + }); + + // Remove any labels that are no longer required. + api.linkLabel().exit().remove(); + } + + function rectAroundText(el) { + var text = el.select('text'), + box = text.node().getBBox(); + + // translate the bbox so that it is centered on [x,y] + box.x = -box.width / 2; + box.y = -box.height / 2; + + // add padding + box.x -= 1; + box.width += 2; + return box; + } + + function transformLabel(p) { + var dx = p.x2 - p.x1, + dy = p.y2 - p.y1, + xMid = dx/2 + p.x1, + yMid = dy/2 + p.y1; + return sus.translate(xMid, yMid); + } + + function applyPortLabels(data, portLabelG) { + var entering = portLabelG.selectAll('.portLabel') + .data(data).enter().append('g') + .classed('portLabel', true) + .attr('id', function (d) { return d.id; }); + + entering.each(function (d) { + var el = d3.select(this), + rect = el.append('rect'), + text = el.append('text').text(d.num); + + rect.attr(rectAroundText(el)); + text.attr('dy', linkLabelOffset); + el.attr('transform', sus.translate(d.x, d.y)); + }); + } + + function labelPoint(linkPos) { + var lengthUpLine = 1 / 3, + dx = linkPos.x2 - linkPos.x1, + dy = linkPos.y2 - linkPos.y1, + movedX = dx * lengthUpLine, + movedY = dy * lengthUpLine; + + return { + x: movedX, + y: movedY + }; + } + + function calcGroupPos(linkPos) { + var moved = labelPoint(linkPos); + return sus.translate(linkPos.x1 + moved.x, linkPos.y1 + moved.y); + } + + // calculates where on the link that the hash line for 5+ label appears + function hashAttrs(linkPos) { + var hashLength = 25, + halfLength = hashLength / 2, + dx = linkPos.x2 - linkPos.x1, + dy = linkPos.y2 - linkPos.y1, + length = Math.sqrt((dx * dx) + (dy * dy)), + moveAmtX = (dx / length) * halfLength, + moveAmtY = (dy / length) * halfLength, + mid = labelPoint(linkPos), + angle = Math.atan(dy / dx) + 45; + + return { + x1: mid.x - moveAmtX, + y1: mid.y - moveAmtY, + x2: mid.x + moveAmtX, + y2: mid.y + moveAmtY, + stroke: api.linkConfig()[ts.theme()].baseColor, + transform: 'rotate(' + angle + ',' + mid.x + ',' + mid.y + ')' + }; + } + + function textLabelPos(linkPos) { + var point = labelPoint(linkPos), + dist = 20; + return { + x: point.x + dist, + y: point.y + dist + }; + } + + function applyNumLinkLabels(data, lblsG) { + var labels = lblsG.selectAll('g.numLinkLabel') + .data(data, function (d) { return 'pair-' + d.id; }), + entering; + + // update existing labels + labels.each(function (d) { + var el = d3.select(this); + + el.attr({ + transform: function (d) { return calcGroupPos(d.linkCoords); } + }); + el.select('line') + .attr(hashAttrs(d.linkCoords)); + el.select('text') + .attr(textLabelPos(d.linkCoords)) + .text(d.num); + }); + + // add new labels + entering = labels + .enter() + .append('g') + .attr({ + transform: function (d) { return calcGroupPos(d.linkCoords); }, + id: function (d) { return 'pair-' + d.id; } + }) + .classed('numLinkLabel', true); + + entering.each(function (d) { + var el = d3.select(this); + + el.append('line') + .classed('numLinkHash', true) + .attr(hashAttrs(d.linkCoords)); + el.append('text') + .classed('numLinkText', true) + .attr(textLabelPos(d.linkCoords)) + .text(d.num); + }); + + // remove old labels + labels.exit().remove(); + } + + // ========================== + // Module definition + + angular.module('ovTopo') + .factory('TopoD3Service', + ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService', + + function (_$log_, _fs_, _sus_, _is_, _ts_) { + $log = _$log_; + fs = _fs_; + sus = _sus_; + is = _is_; + ts = _ts_; + + icfg = is.iconConfig(); + + function initD3(_api_) { + api = _api_; + } + + function destroyD3() { } + + return { + initD3: initD3, + destroyD3: destroyD3, + + incDevLabIndex: incDevLabIndex, + adjustRectToFitText: adjustRectToFitText, + hostLabel: hostLabel, + deviceLabel: deviceLabel, + trimLabel: trimLabel, + + updateDeviceLabel: updateDeviceLabel, + updateHostLabel: updateHostLabel, + updateDeviceColors: updateDeviceColors, + + deviceExisting: deviceExisting, + hostExisting: hostExisting, + deviceEnter: deviceEnter, + hostEnter: hostEnter, + hostExit: hostExit, + deviceExit: deviceExit, + + linkEntering: linkEntering, + applyLinkLabels: applyLinkLabels, + transformLabel: transformLabel, + applyPortLabels: applyPortLabels, + applyNumLinkLabels: applyNumLinkLabels + }; + }]); +}()); |