GUI -- [ONOS-309] - Oblique view of packet and optical layers (Experimental).
Change-Id: I5e3ac53192eb6c0d7bfab599fe2254f58f192a50
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_10_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_10_onos.json
new file mode 100644
index 0000000..10f868f
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_10_onos.json
@@ -0,0 +1,13 @@
+{
+ "event": "addLink",
+ "payload": {
+ "id": "2-2b",
+ "type": "direct",
+ "online": true,
+ "linkWidth": 2,
+ "src": "sw2",
+ "srcPort": "20",
+ "dst": "sw2b",
+ "dstPort": "10"
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_11_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_11_onos.json
new file mode 100644
index 0000000..deaf0a1
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_11_onos.json
@@ -0,0 +1,13 @@
+{
+ "event": "addLink",
+ "payload": {
+ "id": "3-3b",
+ "type": "direct",
+ "online": true,
+ "linkWidth": 2,
+ "src": "sw3",
+ "srcPort": "20",
+ "dst": "sw3b",
+ "dstPort": "10"
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_12_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_12_onos.json
new file mode 100644
index 0000000..ff47e5c
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_12_onos.json
@@ -0,0 +1,13 @@
+{
+ "event": "addLink",
+ "payload": {
+ "id": "4-4b",
+ "type": "direct",
+ "online": true,
+ "linkWidth": 2,
+ "src": "sw4",
+ "srcPort": "20",
+ "dst": "sw4b",
+ "dstPort": "10"
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_1_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_1_onos.json
new file mode 100644
index 0000000..780f0aa
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_1_onos.json
@@ -0,0 +1,17 @@
+{
+ "event": "addDevice",
+ "payload": {
+ "id": "sw1b",
+ "type": "roadm",
+ "online": true,
+ "labels": [
+ "",
+ "sw-1b",
+ "00001b"
+ ],
+ "metaUi": {
+ "x": 200,
+ "y": 200
+ }
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_2_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_2_onos.json
new file mode 100644
index 0000000..e0dba2b
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_2_onos.json
@@ -0,0 +1,17 @@
+{
+ "event": "addDevice",
+ "payload": {
+ "id": "sw2b",
+ "type": "roadm",
+ "online": true,
+ "labels": [
+ "",
+ "sw-2b",
+ "00002b"
+ ],
+ "metaUi": {
+ "x": 800,
+ "y": 200
+ }
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_3_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_3_onos.json
new file mode 100644
index 0000000..38d066a
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_3_onos.json
@@ -0,0 +1,17 @@
+{
+ "event": "addDevice",
+ "payload": {
+ "id": "sw3b",
+ "type": "roadm",
+ "online": true,
+ "labels": [
+ "",
+ "sw-3b",
+ "00003b"
+ ],
+ "metaUi": {
+ "x": 200,
+ "y": 600
+ }
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_4_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_4_onos.json
new file mode 100644
index 0000000..bece53d
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_4_onos.json
@@ -0,0 +1,17 @@
+{
+ "event": "addDevice",
+ "payload": {
+ "id": "sw4b",
+ "type": "roadm",
+ "online": true,
+ "labels": [
+ "",
+ "sw-4b",
+ "00004b"
+ ],
+ "metaUi": {
+ "x": 800,
+ "y": 600
+ }
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_5_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_5_onos.json
new file mode 100644
index 0000000..a05f3f9
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_5_onos.json
@@ -0,0 +1,17 @@
+{
+ "event": "addDevice",
+ "payload": {
+ "id": "sw1",
+ "type": "switch",
+ "online": true,
+ "labels": [
+ "",
+ "sw-1",
+ "00001"
+ ],
+ "metaUi": {
+ "x": 200,
+ "y": 200
+ }
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_6_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_6_onos.json
new file mode 100644
index 0000000..27e3b14
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_6_onos.json
@@ -0,0 +1,17 @@
+{
+ "event": "addDevice",
+ "payload": {
+ "id": "sw2",
+ "type": "switch",
+ "online": true,
+ "labels": [
+ "",
+ "sw-2",
+ "00002"
+ ],
+ "metaUi": {
+ "x": 800,
+ "y": 200
+ }
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_7_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_7_onos.json
new file mode 100644
index 0000000..992b964
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_7_onos.json
@@ -0,0 +1,17 @@
+{
+ "event": "addDevice",
+ "payload": {
+ "id": "sw3",
+ "type": "switch",
+ "online": true,
+ "labels": [
+ "",
+ "sw-3",
+ "00003"
+ ],
+ "metaUi": {
+ "x": 200,
+ "y": 600
+ }
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_8_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_8_onos.json
new file mode 100644
index 0000000..2c33d50
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_8_onos.json
@@ -0,0 +1,17 @@
+{
+ "event": "addDevice",
+ "payload": {
+ "id": "sw4",
+ "type": "switch",
+ "online": true,
+ "labels": [
+ "",
+ "sw-4",
+ "00004"
+ ],
+ "metaUi": {
+ "x": 800,
+ "y": 600
+ }
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/ev_9_onos.json b/web/gui/src/main/webapp/json/ev/oblique/ev_9_onos.json
new file mode 100644
index 0000000..57e9706
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/ev_9_onos.json
@@ -0,0 +1,13 @@
+{
+ "event": "addLink",
+ "payload": {
+ "id": "1-1b",
+ "type": "direct",
+ "online": true,
+ "linkWidth": 2,
+ "src": "sw1",
+ "srcPort": "20",
+ "dst": "sw1b",
+ "dstPort": "10"
+ }
+}
diff --git a/web/gui/src/main/webapp/json/ev/oblique/scenario.json b/web/gui/src/main/webapp/json/ev/oblique/scenario.json
new file mode 100644
index 0000000..7b349ea
--- /dev/null
+++ b/web/gui/src/main/webapp/json/ev/oblique/scenario.json
@@ -0,0 +1,11 @@
+{
+ "title": "Oblique Test Scenario",
+ "params": {
+ "lastAuto": 8
+ },
+ "description": [
+ "Test Scenario for Oblique view",
+ "",
+ "Press '=' to load initial events."
+ ]
+}
diff --git a/web/gui/src/main/webapp/topo.js b/web/gui/src/main/webapp/topo.js
index d2e67bf..704ade1 100644
--- a/web/gui/src/main/webapp/topo.js
+++ b/web/gui/src/main/webapp/topo.js
@@ -149,7 +149,7 @@
A: [showAllTrafficAction, 'Show all traffic'],
F: [showDeviceLinkFlowsAction, 'Show device link flows'],
X: [toggleNodeLock, 'Lock / unlock node positions'],
- Z: [toggleOblique, 'Toggle oblique view'],
+ Z: [toggleOblique, 'Toggle oblique view (Experimental)'],
esc: handleEscape
};
@@ -326,6 +326,12 @@
bgImg.style('visibility', visVal(vis === 'hidden'));
}
+ function opacifyBg(b) {
+ bgImg.transition()
+ .duration(1000)
+ .attr('opacity', b ? 1 : 0);
+ }
+
function toggleNodeLock() {
nodeLock = !nodeLock;
flash('Node positions ' + (nodeLock ? 'locked' : 'unlocked'))
@@ -333,7 +339,12 @@
function toggleOblique() {
oblique = !oblique;
- // TODO: oblique transformation
+ if (oblique) {
+ network.force.stop();
+ toObliqueView();
+ } else {
+ toNormalView();
+ }
}
function toggleHosts() {
@@ -368,7 +379,7 @@
sendUpdateMeta(hovered);
hovered.fixed = false;
hovered.el.classed('fixed', false);
- network.force.resume();
+ fResume();
}
}
@@ -388,6 +399,179 @@
}
// ==============================
+ // Oblique view ...
+
+ var obview = {
+ tt: -.7, // x skew y factor
+ xsk: -35, // x skew angle
+ ysc: 0.5, // y scale
+ pad: 50,
+ time: 1500,
+ fill: {
+ pkt: 'rgba(130,130,170,0.3)',
+ opt: 'rgba(170,130,170,0.3)'
+ },
+ id: function (tag) {
+ return 'obview-' + tag + 'Plane';
+ },
+ yt: function (h, dir) {
+ return h * obview.ysc * dir * 1.1;
+ },
+ obXform: function (h, dir) {
+ var yt = obview.yt(h, dir);
+ return scale(1, obview.ysc) + translate(0, yt) + skewX(obview.xsk);
+ },
+ noXform: function () {
+ return skewX(0) + translate(0,0) + scale(1,1);
+ },
+ xffn: null,
+ plane: {}
+ };
+
+
+ function toObliqueView() {
+ var box = nodeG.node().getBBox(),
+ ox, oy;
+
+ padBox(box, obview.pad);
+
+ ox = box.x + box.width / 2;
+ oy = box.y + box.height / 2;
+
+ // remember node lock state, then lock the nodes down
+ obview.nodeLock = nodeLock;
+ nodeLock = true;
+ opacifyBg(false);
+
+ insertPlanes(ox, oy);
+
+ obview.xffn = function (xy, dir) {
+ var yt = obview.yt(box.height, dir),
+ ax = xy.x - ox,
+ ay = xy.y - oy,
+ x = ax + ay * obview.tt,
+ y = ay * obview.ysc + obview.ysc * yt;
+ return {x: ox + x, y: oy + y};
+ };
+
+ showPlane('pkt', box, -1);
+ showPlane('opt', box, 1);
+ obTransitionNodes();
+ }
+
+ function toNormalView() {
+ obview.xffn = null;
+
+ hidePlane('pkt');
+ hidePlane('opt');
+ obTransitionNodes();
+
+ removePlanes();
+
+ // restore node lock state
+ nodeLock = obview.nodeLock;
+ opacifyBg(true);
+ }
+
+ function obTransitionNodes() {
+ var xffn = obview.xffn;
+
+ // return the direction for the node
+ // -1 for pkt layer, 1 for optical layer
+ function dir(d) {
+ return inLayer(d, 'pkt') ? -1 : 1;
+ }
+
+ if (xffn) {
+ network.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 {
+ network.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;
+ });
+ }
+
+ node.transition()
+ .duration(obview.time)
+ .attr(tickStuff.nodeAttr);
+ link.transition()
+ .duration(obview.time)
+ .attr(tickStuff.linkAttr);
+ linkLabel.transition()
+ .duration(obview.time)
+ .attr(tickStuff.linkLabelAttr);
+ }
+
+ function showPlane(tag, box, dir) {
+ var g = obview.plane[tag];
+
+ // set box origin at center..
+ box.x = -box.width/2;
+ box.y = -box.height/2;
+
+ g.select('rect')
+ .attr(box)
+ .attr('opacity', 0)
+ .transition()
+ .duration(obview.time)
+ .attr('opacity', 1)
+ .attr('transform', obview.obXform(box.height, dir));
+ }
+
+ function hidePlane(tag) {
+ var g = obview.plane[tag];
+
+ g.select('rect')
+ .transition()
+ .duration(obview.time)
+ .attr('opacity', 0)
+ .attr('transform', obview.noXform());
+ }
+
+ function insertPlanes(ox, oy) {
+ function ins(tag) {
+ var id = obview.id(tag),
+ g = panZoomContainer.insert('g', '#topo-G')
+ .attr('id', id)
+ .attr('transform', translate(ox,oy));
+ g.append('rect')
+ .attr('fill', obview.fill[tag])
+ .attr('opacity', 0);
+ obview.plane[tag] = g;
+ }
+ ins('opt');
+ ins('pkt');
+ }
+
+ function removePlanes() {
+ function rem(tag) {
+ var id = obview.id(tag);
+ panZoomContainer.select('#'+id)
+ .transition()
+ .duration(obview.time + 50)
+ .remove();
+ delete obview.plane[tag];
+ }
+ rem('opt');
+ rem('pkt');
+ }
+
+ function padBox(box, p) {
+ box.x -= p;
+ box.y -= p;
+ box.width += p*2;
+ box.height += p*2;
+ }
+
+ // ==============================
// Radio Button Callbacks
var layerLookup = {
@@ -651,7 +835,7 @@
network.nodes.push(d);
network.lookup[id] = d;
updateNodes();
- network.force.start();
+ fStart();
}
function addLink(data) {
@@ -678,7 +862,7 @@
network.links.push(d);
network.lookup[d.key] = d;
updateLinks();
- network.force.start();
+ fStart();
}
}
@@ -707,7 +891,7 @@
network.lookup[d.egress] = lnk;
updateLinks();
}
- network.force.start();
+ fStart();
}
// TODO: fold updateX(...) methods into one base method; remove duplication
@@ -1332,7 +1516,12 @@
function translate(x, y) {
return 'translate(' + x + ',' + y + ')';
}
-
+ function scale(x,y) {
+ return 'scale(' + x + ',' + y + ')';
+ }
+ function skewX(x) {
+ return 'skewX(' + x + ')';
+ }
function rotate(deg) {
return 'rotate(' + deg + ')';
}
@@ -1965,8 +2154,7 @@
.style('fill', '#888')
.style('opacity', 0.5);
});
-
- network.force.resume();
+ fResume();
}
var dCol = {
@@ -2091,7 +2279,7 @@
// remove from lookup cache
delete network.lookup[removed[0].key];
updateLinks();
- network.force.resume();
+ fResume();
}
}
@@ -2113,7 +2301,7 @@
// NOTE: upd is false if we were called from removeDeviceElement()
if (upd) {
updateNodes();
- network.force.resume();
+ fResume();
}
}
@@ -2131,7 +2319,7 @@
network.nodes.splice(idx, 1);
// remove from SVG
updateNodes();
- network.force.resume();
+ fResume();
}
function findAttachedHosts(devId) {
@@ -2154,32 +2342,49 @@
return links;
}
- function tick() {
- node.attr({
- transform: function (d) { return translate(d.x, d.y); }
- });
+ function fResume() {
+ if (!oblique) {
+ network.force.resume();
+ }
+ }
- link.attr({
+ function fStart() {
+ if (!oblique) {
+ network.force.start();
+ }
+ }
+
+ var tickStuff = {
+ nodeAttr: {
+ transform: function (d) { return translate(d.x, d.y); }
+ },
+ linkAttr: {
x1: function (d) { return d.source.x; },
y1: function (d) { return d.source.y; },
x2: function (d) { return d.target.x; },
y2: function (d) { return d.target.y; }
- });
+ },
+ linkLabelAttr: {
+ transform: function (d) {
+ var lnk = findLinkById(d.key);
- linkLabel.each(function (d) {
- var el = d3.select(this);
- var lnk = findLinkById(d.key);
-
- if (lnk) {
- var parms = {
- x1: lnk.source.x,
- y1: lnk.source.y,
- x2: lnk.target.x,
- y2: lnk.target.y
- };
- el.attr('transform', transformLabel(parms));
+ if (lnk) {
+ var parms = {
+ x1: lnk.source.x,
+ y1: lnk.source.y,
+ x2: lnk.target.x,
+ y2: lnk.target.y
+ };
+ return transformLabel(parms);
+ }
}
- });
+ }
+ };
+
+ function tick() {
+ node.attr(tickStuff.nodeAttr);
+ link.attr(tickStuff.linkAttr);
+ linkLabel.attr(tickStuff.linkLabelAttr);
}
// ==============================