Simon Hunt

GUI -- [ONOS-309] - Oblique view of packet and optical layers (Experimental).

Change-Id: I5e3ac53192eb6c0d7bfab599fe2254f58f192a50
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "id": "2-2b",
5 + "type": "direct",
6 + "online": true,
7 + "linkWidth": 2,
8 + "src": "sw2",
9 + "srcPort": "20",
10 + "dst": "sw2b",
11 + "dstPort": "10"
12 + }
13 +}
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "id": "3-3b",
5 + "type": "direct",
6 + "online": true,
7 + "linkWidth": 2,
8 + "src": "sw3",
9 + "srcPort": "20",
10 + "dst": "sw3b",
11 + "dstPort": "10"
12 + }
13 +}
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "id": "4-4b",
5 + "type": "direct",
6 + "online": true,
7 + "linkWidth": 2,
8 + "src": "sw4",
9 + "srcPort": "20",
10 + "dst": "sw4b",
11 + "dstPort": "10"
12 + }
13 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "sw1b",
5 + "type": "roadm",
6 + "online": true,
7 + "labels": [
8 + "",
9 + "sw-1b",
10 + "00001b"
11 + ],
12 + "metaUi": {
13 + "x": 200,
14 + "y": 200
15 + }
16 + }
17 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "sw2b",
5 + "type": "roadm",
6 + "online": true,
7 + "labels": [
8 + "",
9 + "sw-2b",
10 + "00002b"
11 + ],
12 + "metaUi": {
13 + "x": 800,
14 + "y": 200
15 + }
16 + }
17 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "sw3b",
5 + "type": "roadm",
6 + "online": true,
7 + "labels": [
8 + "",
9 + "sw-3b",
10 + "00003b"
11 + ],
12 + "metaUi": {
13 + "x": 200,
14 + "y": 600
15 + }
16 + }
17 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "sw4b",
5 + "type": "roadm",
6 + "online": true,
7 + "labels": [
8 + "",
9 + "sw-4b",
10 + "00004b"
11 + ],
12 + "metaUi": {
13 + "x": 800,
14 + "y": 600
15 + }
16 + }
17 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "sw1",
5 + "type": "switch",
6 + "online": true,
7 + "labels": [
8 + "",
9 + "sw-1",
10 + "00001"
11 + ],
12 + "metaUi": {
13 + "x": 200,
14 + "y": 200
15 + }
16 + }
17 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "sw2",
5 + "type": "switch",
6 + "online": true,
7 + "labels": [
8 + "",
9 + "sw-2",
10 + "00002"
11 + ],
12 + "metaUi": {
13 + "x": 800,
14 + "y": 200
15 + }
16 + }
17 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "sw3",
5 + "type": "switch",
6 + "online": true,
7 + "labels": [
8 + "",
9 + "sw-3",
10 + "00003"
11 + ],
12 + "metaUi": {
13 + "x": 200,
14 + "y": 600
15 + }
16 + }
17 +}
1 +{
2 + "event": "addDevice",
3 + "payload": {
4 + "id": "sw4",
5 + "type": "switch",
6 + "online": true,
7 + "labels": [
8 + "",
9 + "sw-4",
10 + "00004"
11 + ],
12 + "metaUi": {
13 + "x": 800,
14 + "y": 600
15 + }
16 + }
17 +}
1 +{
2 + "event": "addLink",
3 + "payload": {
4 + "id": "1-1b",
5 + "type": "direct",
6 + "online": true,
7 + "linkWidth": 2,
8 + "src": "sw1",
9 + "srcPort": "20",
10 + "dst": "sw1b",
11 + "dstPort": "10"
12 + }
13 +}
1 +{
2 + "title": "Oblique Test Scenario",
3 + "params": {
4 + "lastAuto": 8
5 + },
6 + "description": [
7 + "Test Scenario for Oblique view",
8 + "",
9 + "Press '=' to load initial events."
10 + ]
11 +}
...@@ -149,7 +149,7 @@ ...@@ -149,7 +149,7 @@
149 A: [showAllTrafficAction, 'Show all traffic'], 149 A: [showAllTrafficAction, 'Show all traffic'],
150 F: [showDeviceLinkFlowsAction, 'Show device link flows'], 150 F: [showDeviceLinkFlowsAction, 'Show device link flows'],
151 X: [toggleNodeLock, 'Lock / unlock node positions'], 151 X: [toggleNodeLock, 'Lock / unlock node positions'],
152 - Z: [toggleOblique, 'Toggle oblique view'], 152 + Z: [toggleOblique, 'Toggle oblique view (Experimental)'],
153 esc: handleEscape 153 esc: handleEscape
154 }; 154 };
155 155
...@@ -326,6 +326,12 @@ ...@@ -326,6 +326,12 @@
326 bgImg.style('visibility', visVal(vis === 'hidden')); 326 bgImg.style('visibility', visVal(vis === 'hidden'));
327 } 327 }
328 328
329 + function opacifyBg(b) {
330 + bgImg.transition()
331 + .duration(1000)
332 + .attr('opacity', b ? 1 : 0);
333 + }
334 +
329 function toggleNodeLock() { 335 function toggleNodeLock() {
330 nodeLock = !nodeLock; 336 nodeLock = !nodeLock;
331 flash('Node positions ' + (nodeLock ? 'locked' : 'unlocked')) 337 flash('Node positions ' + (nodeLock ? 'locked' : 'unlocked'))
...@@ -333,7 +339,12 @@ ...@@ -333,7 +339,12 @@
333 339
334 function toggleOblique() { 340 function toggleOblique() {
335 oblique = !oblique; 341 oblique = !oblique;
336 - // TODO: oblique transformation 342 + if (oblique) {
343 + network.force.stop();
344 + toObliqueView();
345 + } else {
346 + toNormalView();
347 + }
337 } 348 }
338 349
339 function toggleHosts() { 350 function toggleHosts() {
...@@ -368,7 +379,7 @@ ...@@ -368,7 +379,7 @@
368 sendUpdateMeta(hovered); 379 sendUpdateMeta(hovered);
369 hovered.fixed = false; 380 hovered.fixed = false;
370 hovered.el.classed('fixed', false); 381 hovered.el.classed('fixed', false);
371 - network.force.resume(); 382 + fResume();
372 } 383 }
373 } 384 }
374 385
...@@ -388,6 +399,179 @@ ...@@ -388,6 +399,179 @@
388 } 399 }
389 400
390 // ============================== 401 // ==============================
402 + // Oblique view ...
403 +
404 + var obview = {
405 + tt: -.7, // x skew y factor
406 + xsk: -35, // x skew angle
407 + ysc: 0.5, // y scale
408 + pad: 50,
409 + time: 1500,
410 + fill: {
411 + pkt: 'rgba(130,130,170,0.3)',
412 + opt: 'rgba(170,130,170,0.3)'
413 + },
414 + id: function (tag) {
415 + return 'obview-' + tag + 'Plane';
416 + },
417 + yt: function (h, dir) {
418 + return h * obview.ysc * dir * 1.1;
419 + },
420 + obXform: function (h, dir) {
421 + var yt = obview.yt(h, dir);
422 + return scale(1, obview.ysc) + translate(0, yt) + skewX(obview.xsk);
423 + },
424 + noXform: function () {
425 + return skewX(0) + translate(0,0) + scale(1,1);
426 + },
427 + xffn: null,
428 + plane: {}
429 + };
430 +
431 +
432 + function toObliqueView() {
433 + var box = nodeG.node().getBBox(),
434 + ox, oy;
435 +
436 + padBox(box, obview.pad);
437 +
438 + ox = box.x + box.width / 2;
439 + oy = box.y + box.height / 2;
440 +
441 + // remember node lock state, then lock the nodes down
442 + obview.nodeLock = nodeLock;
443 + nodeLock = true;
444 + opacifyBg(false);
445 +
446 + insertPlanes(ox, oy);
447 +
448 + obview.xffn = function (xy, dir) {
449 + var yt = obview.yt(box.height, dir),
450 + ax = xy.x - ox,
451 + ay = xy.y - oy,
452 + x = ax + ay * obview.tt,
453 + y = ay * obview.ysc + obview.ysc * yt;
454 + return {x: ox + x, y: oy + y};
455 + };
456 +
457 + showPlane('pkt', box, -1);
458 + showPlane('opt', box, 1);
459 + obTransitionNodes();
460 + }
461 +
462 + function toNormalView() {
463 + obview.xffn = null;
464 +
465 + hidePlane('pkt');
466 + hidePlane('opt');
467 + obTransitionNodes();
468 +
469 + removePlanes();
470 +
471 + // restore node lock state
472 + nodeLock = obview.nodeLock;
473 + opacifyBg(true);
474 + }
475 +
476 + function obTransitionNodes() {
477 + var xffn = obview.xffn;
478 +
479 + // return the direction for the node
480 + // -1 for pkt layer, 1 for optical layer
481 + function dir(d) {
482 + return inLayer(d, 'pkt') ? -1 : 1;
483 + }
484 +
485 + if (xffn) {
486 + network.nodes.forEach(function (d) {
487 + var oldxy = {x: d.x, y: d.y},
488 + coords = xffn(oldxy, dir(d));
489 + d.oldxy = oldxy;
490 + d.px = d.x = coords.x;
491 + d.py = d.y = coords.y;
492 + });
493 + } else {
494 + network.nodes.forEach(function (d) {
495 + var old = d.oldxy || {x: d.x, y: d.y};
496 + d.px = d.x = old.x;
497 + d.py = d.y = old.y;
498 + delete d.oldxy;
499 + });
500 + }
501 +
502 + node.transition()
503 + .duration(obview.time)
504 + .attr(tickStuff.nodeAttr);
505 + link.transition()
506 + .duration(obview.time)
507 + .attr(tickStuff.linkAttr);
508 + linkLabel.transition()
509 + .duration(obview.time)
510 + .attr(tickStuff.linkLabelAttr);
511 + }
512 +
513 + function showPlane(tag, box, dir) {
514 + var g = obview.plane[tag];
515 +
516 + // set box origin at center..
517 + box.x = -box.width/2;
518 + box.y = -box.height/2;
519 +
520 + g.select('rect')
521 + .attr(box)
522 + .attr('opacity', 0)
523 + .transition()
524 + .duration(obview.time)
525 + .attr('opacity', 1)
526 + .attr('transform', obview.obXform(box.height, dir));
527 + }
528 +
529 + function hidePlane(tag) {
530 + var g = obview.plane[tag];
531 +
532 + g.select('rect')
533 + .transition()
534 + .duration(obview.time)
535 + .attr('opacity', 0)
536 + .attr('transform', obview.noXform());
537 + }
538 +
539 + function insertPlanes(ox, oy) {
540 + function ins(tag) {
541 + var id = obview.id(tag),
542 + g = panZoomContainer.insert('g', '#topo-G')
543 + .attr('id', id)
544 + .attr('transform', translate(ox,oy));
545 + g.append('rect')
546 + .attr('fill', obview.fill[tag])
547 + .attr('opacity', 0);
548 + obview.plane[tag] = g;
549 + }
550 + ins('opt');
551 + ins('pkt');
552 + }
553 +
554 + function removePlanes() {
555 + function rem(tag) {
556 + var id = obview.id(tag);
557 + panZoomContainer.select('#'+id)
558 + .transition()
559 + .duration(obview.time + 50)
560 + .remove();
561 + delete obview.plane[tag];
562 + }
563 + rem('opt');
564 + rem('pkt');
565 + }
566 +
567 + function padBox(box, p) {
568 + box.x -= p;
569 + box.y -= p;
570 + box.width += p*2;
571 + box.height += p*2;
572 + }
573 +
574 + // ==============================
391 // Radio Button Callbacks 575 // Radio Button Callbacks
392 576
393 var layerLookup = { 577 var layerLookup = {
...@@ -651,7 +835,7 @@ ...@@ -651,7 +835,7 @@
651 network.nodes.push(d); 835 network.nodes.push(d);
652 network.lookup[id] = d; 836 network.lookup[id] = d;
653 updateNodes(); 837 updateNodes();
654 - network.force.start(); 838 + fStart();
655 } 839 }
656 840
657 function addLink(data) { 841 function addLink(data) {
...@@ -678,7 +862,7 @@ ...@@ -678,7 +862,7 @@
678 network.links.push(d); 862 network.links.push(d);
679 network.lookup[d.key] = d; 863 network.lookup[d.key] = d;
680 updateLinks(); 864 updateLinks();
681 - network.force.start(); 865 + fStart();
682 } 866 }
683 } 867 }
684 868
...@@ -707,7 +891,7 @@ ...@@ -707,7 +891,7 @@
707 network.lookup[d.egress] = lnk; 891 network.lookup[d.egress] = lnk;
708 updateLinks(); 892 updateLinks();
709 } 893 }
710 - network.force.start(); 894 + fStart();
711 } 895 }
712 896
713 // TODO: fold updateX(...) methods into one base method; remove duplication 897 // TODO: fold updateX(...) methods into one base method; remove duplication
...@@ -1332,7 +1516,12 @@ ...@@ -1332,7 +1516,12 @@
1332 function translate(x, y) { 1516 function translate(x, y) {
1333 return 'translate(' + x + ',' + y + ')'; 1517 return 'translate(' + x + ',' + y + ')';
1334 } 1518 }
1335 - 1519 + function scale(x,y) {
1520 + return 'scale(' + x + ',' + y + ')';
1521 + }
1522 + function skewX(x) {
1523 + return 'skewX(' + x + ')';
1524 + }
1336 function rotate(deg) { 1525 function rotate(deg) {
1337 return 'rotate(' + deg + ')'; 1526 return 'rotate(' + deg + ')';
1338 } 1527 }
...@@ -1965,8 +2154,7 @@ ...@@ -1965,8 +2154,7 @@
1965 .style('fill', '#888') 2154 .style('fill', '#888')
1966 .style('opacity', 0.5); 2155 .style('opacity', 0.5);
1967 }); 2156 });
1968 - 2157 + fResume();
1969 - network.force.resume();
1970 } 2158 }
1971 2159
1972 var dCol = { 2160 var dCol = {
...@@ -2091,7 +2279,7 @@ ...@@ -2091,7 +2279,7 @@
2091 // remove from lookup cache 2279 // remove from lookup cache
2092 delete network.lookup[removed[0].key]; 2280 delete network.lookup[removed[0].key];
2093 updateLinks(); 2281 updateLinks();
2094 - network.force.resume(); 2282 + fResume();
2095 } 2283 }
2096 } 2284 }
2097 2285
...@@ -2113,7 +2301,7 @@ ...@@ -2113,7 +2301,7 @@
2113 // NOTE: upd is false if we were called from removeDeviceElement() 2301 // NOTE: upd is false if we were called from removeDeviceElement()
2114 if (upd) { 2302 if (upd) {
2115 updateNodes(); 2303 updateNodes();
2116 - network.force.resume(); 2304 + fResume();
2117 } 2305 }
2118 } 2306 }
2119 2307
...@@ -2131,7 +2319,7 @@ ...@@ -2131,7 +2319,7 @@
2131 network.nodes.splice(idx, 1); 2319 network.nodes.splice(idx, 1);
2132 // remove from SVG 2320 // remove from SVG
2133 updateNodes(); 2321 updateNodes();
2134 - network.force.resume(); 2322 + fResume();
2135 } 2323 }
2136 2324
2137 function findAttachedHosts(devId) { 2325 function findAttachedHosts(devId) {
...@@ -2154,32 +2342,49 @@ ...@@ -2154,32 +2342,49 @@
2154 return links; 2342 return links;
2155 } 2343 }
2156 2344
2157 - function tick() { 2345 + function fResume() {
2158 - node.attr({ 2346 + if (!oblique) {
2159 - transform: function (d) { return translate(d.x, d.y); } 2347 + network.force.resume();
2160 - }); 2348 + }
2349 + }
2350 +
2351 + function fStart() {
2352 + if (!oblique) {
2353 + network.force.start();
2354 + }
2355 + }
2161 2356
2162 - link.attr({ 2357 + var tickStuff = {
2358 + nodeAttr: {
2359 + transform: function (d) { return translate(d.x, d.y); }
2360 + },
2361 + linkAttr: {
2163 x1: function (d) { return d.source.x; }, 2362 x1: function (d) { return d.source.x; },
2164 y1: function (d) { return d.source.y; }, 2363 y1: function (d) { return d.source.y; },
2165 x2: function (d) { return d.target.x; }, 2364 x2: function (d) { return d.target.x; },
2166 y2: function (d) { return d.target.y; } 2365 y2: function (d) { return d.target.y; }
2167 - }); 2366 + },
2168 - 2367 + linkLabelAttr: {
2169 - linkLabel.each(function (d) { 2368 + transform: function (d) {
2170 - var el = d3.select(this); 2369 + var lnk = findLinkById(d.key);
2171 - var lnk = findLinkById(d.key); 2370 +
2172 - 2371 + if (lnk) {
2173 - if (lnk) { 2372 + var parms = {
2174 - var parms = { 2373 + x1: lnk.source.x,
2175 - x1: lnk.source.x, 2374 + y1: lnk.source.y,
2176 - y1: lnk.source.y, 2375 + x2: lnk.target.x,
2177 - x2: lnk.target.x, 2376 + y2: lnk.target.y
2178 - y2: lnk.target.y 2377 + };
2179 - }; 2378 + return transformLabel(parms);
2180 - el.attr('transform', transformLabel(parms)); 2379 + }
2181 } 2380 }
2182 - }); 2381 + }
2382 + };
2383 +
2384 + function tick() {
2385 + node.attr(tickStuff.nodeAttr);
2386 + link.attr(tickStuff.linkAttr);
2387 + linkLabel.attr(tickStuff.linkLabelAttr);
2183 } 2388 }
2184 2389
2185 // ============================== 2390 // ==============================
......