GUI -- TopoView - Re-instated the oblique view function. (Keystroke 'Z').
Change-Id: I1bc4c11590660142a6bc9f5f71c06a664dbfa80b
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 89e2706..998f442 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -204,6 +204,11 @@
return ms.loadMapInto(mapG, '*continental_us');
}
+ function opacifyMap(b) {
+ mapG.transition()
+ .duration(1000)
+ .attr('opacity', b ? 1 : 0);
+ }
// --- Controller Definition -----------------------------------------
@@ -226,6 +231,8 @@
// provides function calls back into this space
showNoDevs: showNoDevs,
projection: function () { return projection; },
+ zoomLayer: function () { return zoomLayer; },
+ opacifyMap: opacifyMap,
sendEvent: _tes_.sendEvent
};
diff --git a/web/gui/src/main/webapp/app/view/topo/topoFilter.js b/web/gui/src/main/webapp/app/view/topo/topoFilter.js
index c2e7a62..53a6302 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoFilter.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoFilter.js
@@ -117,7 +117,6 @@
return btnG ? btnG.selected : '';
}
- // code to manipulate the nodes and links as per the filter settings
function inLayer(d, layer) {
var type = d.class === 'link' ? d.type() : d.type,
look = layerLookup[d.class],
@@ -186,7 +185,8 @@
destroyFilter: destroyFilter,
clickAction: clickAction,
- selected: selected
+ selected: selected,
+ inLayer: inLayer
};
}]);
}());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 80a7b63..e2d1809 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -76,7 +76,6 @@
hostLabelIndex = 0, // for host label cycling
showHosts = false, // whether hosts are displayed
showOffline = true, // whether offline devices are displayed
- oblique = false, // whether we are in the oblique view
nodeLock = false, // whether nodes can be dragged or not (locked)
dim; // the dimensions of the force layout [w,h]
@@ -965,13 +964,13 @@
// force layout tick function
function fResume() {
- if (!oblique) {
+ if (!tos.isOblique()) {
force.resume();
}
}
function fStart() {
- if (!oblique) {
+ if (!tos.isOblique()) {
force.start();
}
}
@@ -1084,10 +1083,23 @@
}
}
- function mkObliqueApi(uplink) {
+ 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; }
+ 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
};
}
@@ -1140,7 +1152,7 @@
tms.initModel(mkModelApi(uplink), dim);
tss.initSelect(mkSelectApi(uplink));
tts.initTraffic(mkTrafficApi(uplink));
- tos.initOblique(mkObliqueApi(uplink));
+ tos.initOblique(mkObliqueApi(uplink, fltr));
fltr.initFilter(mkFilterApi(uplink), d3.select('#mast-right'));
settings = angular.extend({}, defaultSettings, opts);
diff --git a/web/gui/src/main/webapp/app/view/topo/topoOblique.js b/web/gui/src/main/webapp/app/view/topo/topoOblique.js
index 4b7bc50..8a30862 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoOblique.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoOblique.js
@@ -24,35 +24,206 @@
'use strict';
// injected refs
- var $log, fs;
+ var $log, fs, sus, ts;
// 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'}
*/
+ // 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 foo;
-
- // ==========================
+ var oblique = false,
+ xffn = null,
+ plane = {},
+ oldNodeLock;
- function toggleOblique() {
- $log.log("TOGGLING OBLIQUE VIEW");
+ 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)
+ .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',
+ ['$log', 'FnService', 'SvgUtilService', 'ThemeService',
- function (_$log_, _fs_) {
+ function (_$log_, _fs_, _sus_, _ts_) {
$log = _$log_;
fs = _fs_;
+ sus = _sus_;
+ ts = _ts_;
function initOblique(_api_) {
api = _api_;
@@ -60,10 +231,21 @@
function destroyOblique() { }
+ function toggleOblique() {
+ oblique = !oblique;
+ if (oblique) {
+ api.force().stop();
+ toObliqueView();
+ } else {
+ toNormalView();
+ }
+ }
+
return {
initOblique: initOblique,
destroyOblique: destroyOblique,
+ isOblique: function () { return oblique; },
toggleOblique: toggleOblique
};
}]);
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js
index 79a4398..a9c7ddd 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js
@@ -54,7 +54,7 @@
it('should define api functions', function () {
expect(fs.areFunctions(fltr, [
'initFilter', 'destroyFilter',
- 'clickAction', 'selected'
+ 'clickAction', 'selected', 'inLayer',
])).toBeTruthy();
});
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoOblique-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoOblique-spec.js
index 5b8e120..bf07569 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoOblique-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoOblique-spec.js
@@ -34,7 +34,7 @@
it('should define api functions', function () {
expect(fs.areFunctions(tos, [
- 'initOblique', 'destroyOblique', 'toggleOblique'
+ 'initOblique', 'destroyOblique', 'isOblique', 'toggleOblique'
])).toBeTruthy();
});