GUI -- Experimental Transform.
Change-Id: Iebf31a32707f2736b22102b6d4620db3489fe252
diff --git a/web/gui/src/main/webapp/_sdh/oblique.html b/web/gui/src/main/webapp/_sdh/oblique.html
new file mode 100644
index 0000000..868ba33
--- /dev/null
+++ b/web/gui/src/main/webapp/_sdh/oblique.html
@@ -0,0 +1,460 @@
+<!DOCTYPE html>
+<!--
+ Testing transformations for transitioning between overhead and
+ perspective projections of two layers.
+
+ @author Simon Hunt
+ -->
+<html>
+<head>
+ <meta charset="utf-8">
+ <title>Layer Transformations</title>
+
+ <script src="../tp/d3.js"></script>
+ <script src="../tp/topojson.v1.min.js"></script>
+ <script src="../tp/jquery-2.1.1.min.js"></script>
+
+ <style>
+ html,
+ body {
+ background-color: #ccc;
+ font-family: Arial, Helvetica, sans-serif;
+ font-size: 9pt;
+ }
+
+ svg {
+ position: absolute;
+ background-color: #fff;
+ top: 30px;
+ left: 60px;
+ }
+
+ svg text {
+ font-size: 3pt;
+ }
+
+ </style>
+</head>
+<body>
+ <svg width="1000px" height="600px" viewBox="0 0 160 120"></svg>
+
+ <script>
+ (function (){
+
+ // Configuration...
+ var w = 160,
+ h = 120,
+ time = 1500;
+
+ var pktData = [
+ [20,60,'a'],
+ [60,20,'b'],
+ [100,20,'c'],
+ [140,60,'d'],
+ [100,100,'e'],
+ [60,100,'f'],
+ [20,20,'w'],
+ [140,20,'x'],
+ [20,100,'y'],
+ [140,100,'z']
+ ],
+ optData = [
+ [40,40,'p'],
+ [120,40,'q'],
+ [120,80,'r'],
+ [40,80,'s'],
+ [20,20,'j'],
+ [140,20,'k'],
+ [20,100,'l'],
+ [140,100,'m']
+ ],
+ linkData = [
+ ['a','p'],
+ ['p','b'],
+ ['b','c'],
+ ['c','q'],
+ ['q','d'],
+ ['d','r'],
+ ['r','e'],
+ ['e','f'],
+ ['f','s'],
+ ['s','a'],
+ ['s','q'],
+ ['p','r'],
+ ['b','f'],
+ ['c','e'],
+ ['w','j'],
+ ['x','k'],
+ ['z','m'],
+ ['y','l']
+ ];
+
+ // Transform parameters
+ var tf = {
+ tt: -.7, // x skew y factor
+ xsk: -35, // x skew angle
+ ysc: 0.5, // y scale
+ ytr: 50, // y translate
+ pad: 5
+ },
+ rectFill = {
+ pkt: 'rgba(130,130,170,0.3)',
+ opt: 'rgba(170,130,170,0.3)'
+ };
+
+ // Internal state...
+ var nodes = [],
+ links = [],
+ overhead = true,
+ xffn;
+
+ // D3/DOM magic...
+ var svg = d3.select('svg'),
+ nodeG,
+ linkG,
+ node,
+ link,
+ force,
+ pktLayer,
+ optLayer;
+
+
+ // General functions ...
+ function isF(f) {
+ return $.isFunction(f) ? f : null;
+ }
+
+ function translate(x,y) {
+ return 'translate(' + x + ',' + y + ')';
+ }
+
+ function scale(x,y) {
+ return 'scale(' + x + ',' + y + ')';
+ }
+ function skewX(x) {
+ return 'skewX(' + x + ')';
+ }
+
+
+ // Key Bindings...
+ var keyHandler = {
+ T: transform
+ };
+
+ function whatKey(code) {
+ switch (code) {
+ case 13: return 'enter';
+ case 16: return 'shift';
+ case 17: return 'ctrl';
+ case 18: return 'alt';
+ case 27: return 'esc';
+ case 32: return 'space';
+ case 37: return 'leftArrow';
+ case 38: return 'upArrow';
+ case 39: return 'rightArrow';
+ case 40: return 'downArrow';
+ case 91: return 'cmdLeft';
+ case 93: return 'cmdRight';
+ case 187: return 'equals';
+ case 189: return 'dash';
+ case 191: return 'slash';
+ default:
+ if ((code >= 48 && code <= 57) ||
+ (code >= 65 && code <= 90)) {
+ return String.fromCharCode(code);
+ } else if (code >= 112 && code <= 123) {
+ return 'F' + (code - 111);
+ }
+ return '.';
+ }
+ }
+
+ function keyIn() {
+ var event = d3.event,
+ keyCode = event.keyCode,
+ key = whatKey(keyCode),
+ fn = isF(keyHandler[key]);
+ if (fn) {
+ fn(key, keyCode, event);
+ }
+ }
+
+ // Key events....
+ function transform() {
+ overhead = !overhead;
+ if (overhead) {
+ toOverhead();
+ } else {
+ toOblique();
+ }
+ }
+
+ function toOverhead() {
+ xffn = null;
+ hidePlane(pktLayer);
+ hidePlane(optLayer);
+ transitionNodes();
+ }
+
+ function padBox(box, p) {
+ box.x -= p;
+ box.y -= p;
+ box.width += p*2;
+ box.height += p*2;
+ }
+
+ function toOblique() {
+ var box = nodeG.node().getBBox();
+ padBox(box, tf.pad);
+
+ xffn = function (xy, dir) {
+ var x = xy.x + xy.y*tf.tt,
+ y = xy.y*tf.ysc + tf.ysc*tf.ytr*dir;
+ return { x: x, y: y};
+ };
+
+ showPlane(pktLayer, box, -1);
+ showPlane(optLayer, box, 1);
+ transitionNodes();
+ }
+
+ function transitionNodes() {
+ // note: turn off force layout while transitioning.. if it is on
+// force.stop();
+
+ if (xffn) {
+ nodes.forEach(function (d) {
+ var dir = d.type === 'pkt' ? -1 : 1,
+ oldxy = {x: d.x, y: d.y},
+ coords = xffn(oldxy, dir);
+ d.oldxy = oldxy;
+ d.x = coords.x;
+ d.y = coords.y;
+ });
+ } else {
+ nodes.forEach(function (d) {
+ d.x = d.oldxy.x;
+ d.y = d.oldxy.y;
+ delete d.oldxy;
+ });
+ }
+
+ nodeG.selectAll('.node')
+ .transition()
+ .duration(time)
+ .attr({
+ transform: function (d) {
+ return translate(d.x, d.y);
+ }
+ });
+
+ linkG.selectAll('.link')
+ .transition()
+ .duration(time)
+ .attr({
+ 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; }
+ });
+ }
+
+ function showPlane(layer, box, dir) {
+ layer.select('rect')
+ .attr(box)
+ .attr('opacity', 0)
+ .transition()
+ .duration(time)
+ .attr('opacity', 1)
+ .attr('transform', obliqueXform(dir));
+ }
+
+ function hidePlane(layer) {
+ var rect = layer.select('rect');
+ rect.transition()
+ .duration(time)
+ .attr('opacity', 0)
+ .attr('transform', overheadXform());
+
+ }
+
+ function obliqueXform(dir) {
+ return scale(1, tf.ysc) + translate(0, dir * tf.ytr) + skewX(tf.xsk);
+ }
+
+
+ function overheadXform() {
+ return skewX(0) + translate(0,0) + scale(1,1);
+ }
+
+ // Nodes and Links...
+ function prepareNodes() {
+ var hw = w/2,
+ hh = h/2;
+
+ function addNode(t, d) {
+ nodes.push({
+ type: t,
+ x: d[0] - hw,
+ y: d[1] - hh,
+ id: d[2],
+ fixed: true
+ });
+ }
+
+ optData.forEach(function (d) {
+ addNode('opt', d);
+ });
+ pktData.forEach(function (d) {
+ addNode('pkt', d);
+ });
+ }
+
+ function findNode(id) {
+ for (var i=0,n=nodes.length; i<n; i++) {
+ if (nodes[i].id === id) {
+ return nodes[i];
+ }
+ }
+ return null;
+ }
+
+ function prepareLinks() {
+ linkData.forEach(function (d) {
+ var src = d[0],
+ dst = d[1];
+ links.push({
+ id: src + '-' + dst,
+ source: findNode(src),
+ target: findNode(dst)
+ });
+ });
+
+ }
+
+ function updateNodes() {
+ node = nodeG.selectAll('.node')
+ .data(nodes, function (d) { return d.id; });
+
+ var entering = node.enter()
+ .append('g').attr({
+ id: function (d) { return d.id; },
+ 'class': function (d) { return 'node ' + d.type; }
+ });
+
+ entering.each(function (d) {
+ var el = d3.select(this);
+ d.el = el;
+
+ el.append('rect').attr({
+ width: 5,
+ height: 5,
+ fill: function (d) {
+ return d.type === 'pkt' ? '#669' : '#969';
+ },
+ rx: 1,
+ transform: 'translate(-2.5,-2.5)'
+ });
+ el.append('text')
+ .text(d.id)
+ .attr({
+ dy: '0.9em',
+ 'text-anchor': 'middle',
+ transform: 'translate(0,-2.5)',
+ fill: 'white'
+ });
+ });
+ }
+
+ function updateLinks() {
+ link = linkG.selectAll('.link')
+ .data(links, function (d) { return d.id; });
+
+ var entering = link.enter()
+ .append('line').attr({
+ id: function (d) { return d.id; },
+ class: 'link',
+ stroke: '#888',
+ 'stroke-width': 0.4,
+ opacity: 0.7
+ });
+
+ entering.each(function (d) {
+ d.el = d3.select(this);
+
+ });
+ }
+
+ function update() {
+ updateNodes();
+ updateLinks();
+ }
+
+ var ntick = 0;
+ function tick() {
+ console.log('tick ' + (++ntick));
+ node.attr({
+ transform: function (d) { return translate(d.x, d.y); }
+ });
+
+ link.attr({
+ 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; }
+ });
+ }
+
+ function setOrigin(/*varargs*/) {
+ var i, n, g;
+ for (i= 0,n=arguments.length; i< n; i++) {
+ g = arguments[i];
+ g.attr('transform', translate(w/2, h/2));
+ }
+ }
+
+ function initLayers() {
+ optLayer.attr('class', 'layer').append('rect')
+ .attr('fill', rectFill.opt);
+ pktLayer.attr('class', 'layer').append('rect')
+ .attr('fill', rectFill.pkt);
+ }
+
+ function init() {
+ svg.append('text')
+ .text('Press the "T" key....')
+ .attr({ dy: '1.2em', fill: '#999'})
+ .style('font-size', '2.4pt')
+ .style('font-style', 'italic');
+
+ optLayer = svg.append('g').attr('id', 'optLayer');
+ pktLayer = svg.append('g').attr('id', 'pktLayer');
+ linkG = svg.append('g').attr('id', 'links');
+ nodeG = svg.append('g').attr('id', 'nodes');
+
+ setOrigin(optLayer, pktLayer, linkG, nodeG);
+
+ node = nodeG.selectAll('.node');
+ link = linkG.selectAll('.link');
+
+ initLayers();
+ prepareNodes();
+ prepareLinks();
+
+ force = d3.layout.force()
+ .size([w,h])
+ .nodes(nodes)
+ .links(links)
+ .gravity(0.4)
+ .friction(0.7)
+ .on('tick', tick);
+ update();
+ tick();
+ d3.select('body').on('keydown', keyIn);
+ }
+
+ init();
+ })();
+ </script>
+</body>
+</html>