Simon Hunt | 6931d59 | 2014-11-25 17:42:58 -0800 | [diff] [blame] | 1 | <!DOCTYPE html> |
| 2 | <!-- |
Brian O'Connor | a09fe5b | 2017-08-03 21:12:30 -0700 | [diff] [blame] | 3 | ~ Copyright 2016-present Open Networking Foundation |
alshabib | ab98466 | 2014-12-04 18:56:18 -0800 | [diff] [blame] | 4 | ~ |
| 5 | ~ Licensed under the Apache License, Version 2.0 (the "License"); |
| 6 | ~ you may not use this file except in compliance with the License. |
| 7 | ~ You may obtain a copy of the License at |
| 8 | ~ |
| 9 | ~ http://www.apache.org/licenses/LICENSE-2.0 |
| 10 | ~ |
| 11 | ~ Unless required by applicable law or agreed to in writing, software |
| 12 | ~ distributed under the License is distributed on an "AS IS" BASIS, |
| 13 | ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 14 | ~ See the License for the specific language governing permissions and |
| 15 | ~ limitations under the License. |
| 16 | --> |
Simon Hunt | 71fbdb3 | 2014-12-01 14:32:25 -0800 | [diff] [blame] | 17 | |
| 18 | <!-- |
Simon Hunt | 6931d59 | 2014-11-25 17:42:58 -0800 | [diff] [blame] | 19 | Testing transformations for transitioning between overhead and |
| 20 | perspective projections of two layers. |
Simon Hunt | 6931d59 | 2014-11-25 17:42:58 -0800 | [diff] [blame] | 21 | --> |
| 22 | <html> |
| 23 | <head> |
| 24 | <meta charset="utf-8"> |
| 25 | <title>Layer Transformations</title> |
| 26 | |
| 27 | <script src="../tp/d3.js"></script> |
Simon Hunt | 7313d1f | 2016-11-16 13:10:49 -0800 | [diff] [blame] | 28 | <script src="../tp/angular.js"></script> |
Simon Hunt | 6931d59 | 2014-11-25 17:42:58 -0800 | [diff] [blame] | 29 | |
| 30 | <style> |
| 31 | html, |
| 32 | body { |
| 33 | background-color: #ccc; |
| 34 | font-family: Arial, Helvetica, sans-serif; |
| 35 | font-size: 9pt; |
| 36 | } |
| 37 | |
| 38 | svg { |
| 39 | position: absolute; |
| 40 | background-color: #fff; |
| 41 | top: 30px; |
| 42 | left: 60px; |
| 43 | } |
| 44 | |
| 45 | svg text { |
| 46 | font-size: 3pt; |
| 47 | } |
| 48 | |
| 49 | </style> |
| 50 | </head> |
| 51 | <body> |
| 52 | <svg width="1000px" height="600px" viewBox="0 0 160 120"></svg> |
| 53 | |
| 54 | <script> |
| 55 | (function (){ |
| 56 | |
| 57 | // Configuration... |
| 58 | var w = 160, |
| 59 | h = 120, |
| 60 | time = 1500; |
| 61 | |
| 62 | var pktData = [ |
| 63 | [20,60,'a'], |
| 64 | [60,20,'b'], |
| 65 | [100,20,'c'], |
| 66 | [140,60,'d'], |
| 67 | [100,100,'e'], |
| 68 | [60,100,'f'], |
| 69 | [20,20,'w'], |
| 70 | [140,20,'x'], |
| 71 | [20,100,'y'], |
| 72 | [140,100,'z'] |
| 73 | ], |
| 74 | optData = [ |
| 75 | [40,40,'p'], |
| 76 | [120,40,'q'], |
| 77 | [120,80,'r'], |
| 78 | [40,80,'s'], |
| 79 | [20,20,'j'], |
| 80 | [140,20,'k'], |
| 81 | [20,100,'l'], |
| 82 | [140,100,'m'] |
| 83 | ], |
| 84 | linkData = [ |
| 85 | ['a','p'], |
| 86 | ['p','b'], |
| 87 | ['b','c'], |
| 88 | ['c','q'], |
| 89 | ['q','d'], |
| 90 | ['d','r'], |
| 91 | ['r','e'], |
| 92 | ['e','f'], |
| 93 | ['f','s'], |
| 94 | ['s','a'], |
| 95 | ['s','q'], |
| 96 | ['p','r'], |
| 97 | ['b','f'], |
| 98 | ['c','e'], |
| 99 | ['w','j'], |
| 100 | ['x','k'], |
| 101 | ['z','m'], |
| 102 | ['y','l'] |
| 103 | ]; |
| 104 | |
| 105 | // Transform parameters |
| 106 | var tf = { |
| 107 | tt: -.7, // x skew y factor |
| 108 | xsk: -35, // x skew angle |
| 109 | ysc: 0.5, // y scale |
| 110 | ytr: 50, // y translate |
| 111 | pad: 5 |
| 112 | }, |
| 113 | rectFill = { |
| 114 | pkt: 'rgba(130,130,170,0.3)', |
| 115 | opt: 'rgba(170,130,170,0.3)' |
| 116 | }; |
| 117 | |
| 118 | // Internal state... |
| 119 | var nodes = [], |
| 120 | links = [], |
| 121 | overhead = true, |
| 122 | xffn; |
| 123 | |
| 124 | // D3/DOM magic... |
| 125 | var svg = d3.select('svg'), |
| 126 | nodeG, |
| 127 | linkG, |
| 128 | node, |
| 129 | link, |
| 130 | force, |
| 131 | pktLayer, |
| 132 | optLayer; |
| 133 | |
| 134 | |
| 135 | // General functions ... |
| 136 | function isF(f) { |
Simon Hunt | 7313d1f | 2016-11-16 13:10:49 -0800 | [diff] [blame] | 137 | return angular.isFunction(f) ? f : null; |
Simon Hunt | 6931d59 | 2014-11-25 17:42:58 -0800 | [diff] [blame] | 138 | } |
| 139 | |
| 140 | function translate(x,y) { |
| 141 | return 'translate(' + x + ',' + y + ')'; |
| 142 | } |
| 143 | |
| 144 | function scale(x,y) { |
| 145 | return 'scale(' + x + ',' + y + ')'; |
| 146 | } |
| 147 | function skewX(x) { |
| 148 | return 'skewX(' + x + ')'; |
| 149 | } |
| 150 | |
| 151 | |
| 152 | // Key Bindings... |
| 153 | var keyHandler = { |
Simon Hunt | 7313d1f | 2016-11-16 13:10:49 -0800 | [diff] [blame] | 154 | Z: transform |
Simon Hunt | 6931d59 | 2014-11-25 17:42:58 -0800 | [diff] [blame] | 155 | }; |
| 156 | |
| 157 | function whatKey(code) { |
| 158 | switch (code) { |
| 159 | case 13: return 'enter'; |
| 160 | case 16: return 'shift'; |
| 161 | case 17: return 'ctrl'; |
| 162 | case 18: return 'alt'; |
| 163 | case 27: return 'esc'; |
| 164 | case 32: return 'space'; |
| 165 | case 37: return 'leftArrow'; |
| 166 | case 38: return 'upArrow'; |
| 167 | case 39: return 'rightArrow'; |
| 168 | case 40: return 'downArrow'; |
| 169 | case 91: return 'cmdLeft'; |
| 170 | case 93: return 'cmdRight'; |
| 171 | case 187: return 'equals'; |
| 172 | case 189: return 'dash'; |
| 173 | case 191: return 'slash'; |
| 174 | default: |
| 175 | if ((code >= 48 && code <= 57) || |
| 176 | (code >= 65 && code <= 90)) { |
| 177 | return String.fromCharCode(code); |
| 178 | } else if (code >= 112 && code <= 123) { |
| 179 | return 'F' + (code - 111); |
| 180 | } |
| 181 | return '.'; |
| 182 | } |
| 183 | } |
| 184 | |
| 185 | function keyIn() { |
| 186 | var event = d3.event, |
| 187 | keyCode = event.keyCode, |
| 188 | key = whatKey(keyCode), |
| 189 | fn = isF(keyHandler[key]); |
| 190 | if (fn) { |
| 191 | fn(key, keyCode, event); |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | // Key events.... |
| 196 | function transform() { |
| 197 | overhead = !overhead; |
| 198 | if (overhead) { |
| 199 | toOverhead(); |
| 200 | } else { |
| 201 | toOblique(); |
| 202 | } |
| 203 | } |
| 204 | |
| 205 | function toOverhead() { |
| 206 | xffn = null; |
| 207 | hidePlane(pktLayer); |
| 208 | hidePlane(optLayer); |
| 209 | transitionNodes(); |
| 210 | } |
| 211 | |
| 212 | function padBox(box, p) { |
| 213 | box.x -= p; |
| 214 | box.y -= p; |
| 215 | box.width += p*2; |
| 216 | box.height += p*2; |
| 217 | } |
| 218 | |
| 219 | function toOblique() { |
| 220 | var box = nodeG.node().getBBox(); |
| 221 | padBox(box, tf.pad); |
| 222 | |
| 223 | xffn = function (xy, dir) { |
| 224 | var x = xy.x + xy.y*tf.tt, |
| 225 | y = xy.y*tf.ysc + tf.ysc*tf.ytr*dir; |
| 226 | return { x: x, y: y}; |
| 227 | }; |
| 228 | |
| 229 | showPlane(pktLayer, box, -1); |
| 230 | showPlane(optLayer, box, 1); |
| 231 | transitionNodes(); |
| 232 | } |
| 233 | |
| 234 | function transitionNodes() { |
| 235 | // note: turn off force layout while transitioning.. if it is on |
| 236 | // force.stop(); |
| 237 | |
| 238 | if (xffn) { |
| 239 | nodes.forEach(function (d) { |
| 240 | var dir = d.type === 'pkt' ? -1 : 1, |
| 241 | oldxy = {x: d.x, y: d.y}, |
| 242 | coords = xffn(oldxy, dir); |
| 243 | d.oldxy = oldxy; |
| 244 | d.x = coords.x; |
| 245 | d.y = coords.y; |
| 246 | }); |
| 247 | } else { |
| 248 | nodes.forEach(function (d) { |
| 249 | d.x = d.oldxy.x; |
| 250 | d.y = d.oldxy.y; |
| 251 | delete d.oldxy; |
| 252 | }); |
| 253 | } |
| 254 | |
| 255 | nodeG.selectAll('.node') |
| 256 | .transition() |
| 257 | .duration(time) |
| 258 | .attr({ |
| 259 | transform: function (d) { |
| 260 | return translate(d.x, d.y); |
| 261 | } |
| 262 | }); |
| 263 | |
| 264 | linkG.selectAll('.link') |
| 265 | .transition() |
| 266 | .duration(time) |
| 267 | .attr({ |
| 268 | x1: function (d) { return d.source.x; }, |
| 269 | y1: function (d) { return d.source.y; }, |
| 270 | x2: function (d) { return d.target.x; }, |
| 271 | y2: function (d) { return d.target.y; } |
| 272 | }); |
| 273 | } |
| 274 | |
| 275 | function showPlane(layer, box, dir) { |
| 276 | layer.select('rect') |
| 277 | .attr(box) |
| 278 | .attr('opacity', 0) |
| 279 | .transition() |
| 280 | .duration(time) |
| 281 | .attr('opacity', 1) |
| 282 | .attr('transform', obliqueXform(dir)); |
| 283 | } |
| 284 | |
| 285 | function hidePlane(layer) { |
| 286 | var rect = layer.select('rect'); |
| 287 | rect.transition() |
| 288 | .duration(time) |
| 289 | .attr('opacity', 0) |
| 290 | .attr('transform', overheadXform()); |
| 291 | |
| 292 | } |
| 293 | |
| 294 | function obliqueXform(dir) { |
| 295 | return scale(1, tf.ysc) + translate(0, dir * tf.ytr) + skewX(tf.xsk); |
| 296 | } |
| 297 | |
| 298 | |
| 299 | function overheadXform() { |
| 300 | return skewX(0) + translate(0,0) + scale(1,1); |
| 301 | } |
| 302 | |
| 303 | // Nodes and Links... |
| 304 | function prepareNodes() { |
| 305 | var hw = w/2, |
| 306 | hh = h/2; |
| 307 | |
| 308 | function addNode(t, d) { |
| 309 | nodes.push({ |
| 310 | type: t, |
| 311 | x: d[0] - hw, |
| 312 | y: d[1] - hh, |
| 313 | id: d[2], |
| 314 | fixed: true |
| 315 | }); |
| 316 | } |
| 317 | |
| 318 | optData.forEach(function (d) { |
| 319 | addNode('opt', d); |
| 320 | }); |
| 321 | pktData.forEach(function (d) { |
| 322 | addNode('pkt', d); |
| 323 | }); |
| 324 | } |
| 325 | |
| 326 | function findNode(id) { |
| 327 | for (var i=0,n=nodes.length; i<n; i++) { |
| 328 | if (nodes[i].id === id) { |
| 329 | return nodes[i]; |
| 330 | } |
| 331 | } |
| 332 | return null; |
| 333 | } |
| 334 | |
| 335 | function prepareLinks() { |
| 336 | linkData.forEach(function (d) { |
| 337 | var src = d[0], |
| 338 | dst = d[1]; |
| 339 | links.push({ |
| 340 | id: src + '-' + dst, |
| 341 | source: findNode(src), |
| 342 | target: findNode(dst) |
| 343 | }); |
| 344 | }); |
| 345 | |
| 346 | } |
| 347 | |
| 348 | function updateNodes() { |
| 349 | node = nodeG.selectAll('.node') |
| 350 | .data(nodes, function (d) { return d.id; }); |
| 351 | |
| 352 | var entering = node.enter() |
| 353 | .append('g').attr({ |
| 354 | id: function (d) { return d.id; }, |
| 355 | 'class': function (d) { return 'node ' + d.type; } |
| 356 | }); |
| 357 | |
| 358 | entering.each(function (d) { |
| 359 | var el = d3.select(this); |
| 360 | d.el = el; |
| 361 | |
| 362 | el.append('rect').attr({ |
| 363 | width: 5, |
| 364 | height: 5, |
| 365 | fill: function (d) { |
| 366 | return d.type === 'pkt' ? '#669' : '#969'; |
| 367 | }, |
| 368 | rx: 1, |
| 369 | transform: 'translate(-2.5,-2.5)' |
| 370 | }); |
| 371 | el.append('text') |
| 372 | .text(d.id) |
| 373 | .attr({ |
| 374 | dy: '0.9em', |
| 375 | 'text-anchor': 'middle', |
| 376 | transform: 'translate(0,-2.5)', |
| 377 | fill: 'white' |
| 378 | }); |
| 379 | }); |
| 380 | } |
| 381 | |
| 382 | function updateLinks() { |
| 383 | link = linkG.selectAll('.link') |
| 384 | .data(links, function (d) { return d.id; }); |
| 385 | |
| 386 | var entering = link.enter() |
| 387 | .append('line').attr({ |
| 388 | id: function (d) { return d.id; }, |
| 389 | class: 'link', |
| 390 | stroke: '#888', |
| 391 | 'stroke-width': 0.4, |
| 392 | opacity: 0.7 |
| 393 | }); |
| 394 | |
| 395 | entering.each(function (d) { |
| 396 | d.el = d3.select(this); |
| 397 | |
| 398 | }); |
| 399 | } |
| 400 | |
| 401 | function update() { |
| 402 | updateNodes(); |
| 403 | updateLinks(); |
| 404 | } |
| 405 | |
| 406 | var ntick = 0; |
| 407 | function tick() { |
| 408 | console.log('tick ' + (++ntick)); |
| 409 | node.attr({ |
| 410 | transform: function (d) { return translate(d.x, d.y); } |
| 411 | }); |
| 412 | |
| 413 | link.attr({ |
| 414 | x1: function (d) { return d.source.x; }, |
| 415 | y1: function (d) { return d.source.y; }, |
| 416 | x2: function (d) { return d.target.x; }, |
| 417 | y2: function (d) { return d.target.y; } |
| 418 | }); |
| 419 | } |
| 420 | |
| 421 | function setOrigin(/*varargs*/) { |
| 422 | var i, n, g; |
| 423 | for (i= 0,n=arguments.length; i< n; i++) { |
| 424 | g = arguments[i]; |
| 425 | g.attr('transform', translate(w/2, h/2)); |
| 426 | } |
| 427 | } |
| 428 | |
| 429 | function initLayers() { |
| 430 | optLayer.attr('class', 'layer').append('rect') |
| 431 | .attr('fill', rectFill.opt); |
| 432 | pktLayer.attr('class', 'layer').append('rect') |
| 433 | .attr('fill', rectFill.pkt); |
| 434 | } |
| 435 | |
| 436 | function init() { |
| 437 | svg.append('text') |
Simon Hunt | 7313d1f | 2016-11-16 13:10:49 -0800 | [diff] [blame] | 438 | .text('Press the "Z" key....') |
Simon Hunt | 6931d59 | 2014-11-25 17:42:58 -0800 | [diff] [blame] | 439 | .attr({ dy: '1.2em', fill: '#999'}) |
| 440 | .style('font-size', '2.4pt') |
| 441 | .style('font-style', 'italic'); |
| 442 | |
| 443 | optLayer = svg.append('g').attr('id', 'optLayer'); |
| 444 | pktLayer = svg.append('g').attr('id', 'pktLayer'); |
| 445 | linkG = svg.append('g').attr('id', 'links'); |
| 446 | nodeG = svg.append('g').attr('id', 'nodes'); |
| 447 | |
| 448 | setOrigin(optLayer, pktLayer, linkG, nodeG); |
| 449 | |
| 450 | node = nodeG.selectAll('.node'); |
| 451 | link = linkG.selectAll('.link'); |
| 452 | |
| 453 | initLayers(); |
| 454 | prepareNodes(); |
| 455 | prepareLinks(); |
| 456 | |
| 457 | force = d3.layout.force() |
| 458 | .size([w,h]) |
| 459 | .nodes(nodes) |
| 460 | .links(links) |
| 461 | .gravity(0.4) |
| 462 | .friction(0.7) |
| 463 | .on('tick', tick); |
| 464 | update(); |
| 465 | tick(); |
| 466 | d3.select('body').on('keydown', keyIn); |
| 467 | } |
| 468 | |
| 469 | init(); |
| 470 | })(); |
| 471 | </script> |
| 472 | </body> |
| 473 | </html> |