blob: 326271a996ebf4c2dbb7b27560e8a5a657f92ba5 [file] [log] [blame]
/*
* 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 Oblique View Module.
Provides functionality to view the topology as two planes (packet & optical)
from an oblique (side-on) perspective.
*/
(function () {
'use strict';
// injected refs
var $log, fs, sus, flash;
// api to topoForce
var api;
/*
force() // get ref to force layout object
zoomLayer() // get ref to zoom layer
nodeGBBox() // get bounding box of node group layer
node() // get ref to D3 selection of nodes
link() // get ref to D3 selection of links
nodes() // get ref to network nodes array
tickStuff // ref to tick functions
nodeLock(b) // test-and-set nodeLock state
opacifyMap(b) // show or hide map layer
inLayer(d, layer) // return true if d in layer {'pkt'|'opt'}
calcLinkPos() // recomputes link pos based on node data
*/
// configuration
var xsky = -.7, // x skew y factor
xsk = -35, // x skew angle
ysc = .5, // y scale
pad = 50,
time = 1500,
fill = {
pkt: 'rgba(130,130,170,0.3)', // blue-ish
opt: 'rgba(170,130,170,0.3)' // magenta-ish
};
// internal state
var oblique = false,
xffn = null,
plane = {},
oldNodeLock;
function planeId(tag) {
return 'topo-obview-' + tag + 'Plane';
}
function ytfn(h, dir) {
return h * ysc * dir * 1.1;
}
function obXform(h, dir) {
var yt = ytfn(h, dir);
return sus.scale(1, ysc) + sus.translate(0, yt) + sus.skewX(xsk);
}
function noXform() {
return sus.skewX(0) + sus.translate(0,0) + sus.scale(1,1);
}
function padBox(box, p) {
box.x -= p;
box.y -= p;
box.width += p*2;
box.height += p*2;
}
function toObliqueView() {
var box = api.nodeGBBox(),
ox, oy;
padBox(box, pad);
ox = box.x + box.width / 2;
oy = box.y + box.height / 2;
// remember node lock state, then lock the nodes down
oldNodeLock = api.nodeLock(true);
api.opacifyMap(false);
insertPlanes(ox, oy);
xffn = function (xy, dir) {
var yt = ytfn(box.height, dir),
ax = xy.x - ox,
ay = xy.y - oy,
x = ax + ay * xsky,
y = (ay + yt) * ysc;
return {x: ox + x, y: oy + y};
};
showPlane('pkt', box, -1);
showPlane('opt', box, 1);
obTransitionNodes();
}
function toNormalView() {
xffn = null;
hidePlane('pkt');
hidePlane('opt');
obTransitionNodes();
removePlanes();
// restore node lock state
api.nodeLock(oldNodeLock);
api.opacifyMap(true);
}
function obTransitionNodes() {
// return the direction for the node
// -1 for pkt layer, 1 for optical layer
function dir(d) {
return api.inLayer(d, 'pkt') ? -1 : 1;
}
if (xffn) {
api.nodes().forEach(function (d) {
var oldxy = {x: d.x, y: d.y},
coords = xffn(oldxy, dir(d));
d.oldxy = oldxy;
d.px = d.x = coords.x;
d.py = d.y = coords.y;
});
} else {
api.nodes().forEach(function (d) {
var old = d.oldxy || {x: d.x, y: d.y};
d.px = d.x = old.x;
d.py = d.y = old.y;
delete d.oldxy;
});
}
api.node().transition()
.duration(time)
.attr(api.tickStuff.nodeAttr);
api.link().transition()
.duration(time)
.call(api.calcLinkPos)
.attr(api.tickStuff.linkAttr);
api.linkLabel().transition()
.duration(time)
.attr(api.tickStuff.linkLabelAttr);
}
function showPlane(tag, box, dir) {
// set box origin at center..
box.x = -box.width/2;
box.y = -box.height/2;
plane[tag].select('rect')
.attr(box)
.attr('opacity', 0)
.transition()
.duration(time)
.attr('opacity', 1)
.attr('transform', obXform(box.height, dir));
}
function hidePlane(tag) {
plane[tag].select('rect')
.transition()
.duration(time)
.attr('opacity', 0)
.attr('transform', noXform());
}
function insertPlanes(ox, oy) {
function ins(tag) {
var id = planeId(tag),
g = api.zoomLayer().insert('g', '#topo-G')
.attr('id', id)
.attr('transform', sus.translate(ox,oy));
g.append('rect')
.attr('fill', fill[tag])
.attr('opacity', 0);
plane[tag] = g;
}
ins('opt');
ins('pkt');
}
function removePlanes() {
function rem(tag) {
var id = planeId(tag);
api.zoomLayer().select('#'+id)
.transition()
.duration(time + 50)
.remove();
delete plane[tag];
}
rem('opt');
rem('pkt');
}
// === -----------------------------------------------------
// === MODULE DEFINITION ===
angular.module('ovTopo')
.factory('TopoObliqueService',
['$log', 'FnService', 'SvgUtilService', 'FlashService',
function (_$log_, _fs_, _sus_, _flash_) {
$log = _$log_;
fs = _fs_;
sus = _sus_;
flash = _flash_;
function initOblique(_api_) {
api = _api_;
}
function destroyOblique() { }
function toggleOblique() {
oblique = !oblique;
if (oblique) {
api.force().stop();
flash.flash('Oblique view');
toObliqueView();
} else {
flash.flash('Normal view');
toNormalView();
}
}
return {
initOblique: initOblique,
destroyOblique: destroyOblique,
isOblique: function () { return oblique; },
toggleOblique: toggleOblique
};
}]);
}());