aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/web/gui/src/main/webapp/app/view/topo/topoD3.js
diff options
context:
space:
mode:
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.js576
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
+ };
+ }]);
+}());