blob: d1d15d84559497748d308d03fe1059f4fe15adab [file] [log] [blame]
Simon Hunt6931d592014-11-25 17:42:58 -08001<!DOCTYPE html>
2<!--
Brian O'Connor5ab426f2016-04-09 01:19:45 -07003 ~ Copyright 2016-present Open Networking Laboratory
alshabibab984662014-12-04 18:56:18 -08004 ~
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.
Simon Hunt6931d592014-11-25 17:42:58 -080021 -->
22<html>
23<head>
24 <meta charset="utf-8">
25 <title>Layer Transformations</title>
26
27 <script src="../tp/d3.js"></script>
Simon Hunt6931d592014-11-25 17:42:58 -080028 <script src="../tp/jquery-2.1.1.min.js"></script>
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) {
137 return $.isFunction(f) ? f : null;
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 = {
154 T: transform
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')
438 .text('Press the "T" key....')
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>