From d6f5a27ef8c87718bc86d00a4634fadb2a617bf5 Mon Sep 17 00:00:00 2001 From: Simon Hunt Date: Sat, 29 Nov 2014 23:45:50 -0800 Subject: [PATCH] GUI -- [ONOS-309] - Oblique view of packet and optical layers (Experimental). Change-Id: I5e3ac53192eb6c0d7bfab599fe2254f58f192a50 --- .../webapp/json/ev/oblique/ev_10_onos.json | 13 + .../webapp/json/ev/oblique/ev_11_onos.json | 13 + .../webapp/json/ev/oblique/ev_12_onos.json | 13 + .../webapp/json/ev/oblique/ev_1_onos.json | 17 ++ .../webapp/json/ev/oblique/ev_2_onos.json | 17 ++ .../webapp/json/ev/oblique/ev_3_onos.json | 17 ++ .../webapp/json/ev/oblique/ev_4_onos.json | 17 ++ .../webapp/json/ev/oblique/ev_5_onos.json | 17 ++ .../webapp/json/ev/oblique/ev_6_onos.json | 17 ++ .../webapp/json/ev/oblique/ev_7_onos.json | 17 ++ .../webapp/json/ev/oblique/ev_8_onos.json | 17 ++ .../webapp/json/ev/oblique/ev_9_onos.json | 13 + .../main/webapp/json/ev/oblique/scenario.json | 11 + web/gui/src/main/webapp/topo.js | 267 ++++++++++++++++-- 14 files changed, 435 insertions(+), 31 deletions(-) create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_10_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_11_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_12_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_1_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_2_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_3_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_4_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_5_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_6_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_7_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_8_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/ev_9_onos.json create mode 100644 web/gui/src/main/webapp/json/ev/oblique/scenario.json 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 0000000000..10f868fa6f --- /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 0000000000..deaf0a1420 --- /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 0000000000..ff47e5cd19 --- /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 0000000000..780f0aaf56 --- /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 0000000000..e0dba2b34b --- /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 0000000000..38d066a7cd --- /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 0000000000..bece53db39 --- /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 0000000000..a05f3f91a5 --- /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 0000000000..27e3b14f29 --- /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 0000000000..992b964d49 --- /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 0000000000..2c33d50199 --- /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 0000000000..57e97069e4 --- /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 0000000000..7b349ead96 --- /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 d2e67bf957..704ade1f91 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(); } } @@ -387,6 +398,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 @@ -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); } // ==============================