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