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