Initial OpenECOMP policy/engine commit
[policy/engine.git] / ecomp-sdk-app / src / main / webapp / app / fusion / external / d3 / js / d3.layout.cloud.js
1 // Word cloud layout by Jason Davies, http://www.jasondavies.com/word-cloud/
2 // Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf
3 (function() {
4   function cloud() {
5     var size = [256, 256],
6         text = cloudText,
7         font = cloudFont,
8         fontSize = cloudFontSize,
9         fontStyle = cloudFontNormal,
10         fontWeight = cloudFontNormal,
11         rotate = cloudRotate,
12         padding = cloudPadding,
13         spiral = archimedeanSpiral,
14         words = [],
15         timeInterval = Infinity,
16         event = d3.dispatch("word", "end"),
17         timer = null,
18         cloud = {};
19
20     cloud.start = function() {
21       var board = zeroArray((size[0] >> 5) * size[1]),
22           bounds = null,
23           n = words.length,
24           i = -1,
25           tags = [],
26           data = words.map(function(d, i) {
27             d.text = text.call(this, d, i);
28             d.font = font.call(this, d, i);
29             d.style = fontStyle.call(this, d, i);
30             d.weight = fontWeight.call(this, d, i);
31             d.rotate = rotate.call(this, d, i);
32             d.size = ~~fontSize.call(this, d, i);
33             d.padding = padding.call(this, d, i);
34             return d;
35           }).sort(function(a, b) { return b.size - a.size; });
36 //      createMask();
37 /*      var bd = 3000;
38       while (bd < 4000) {
39         board[bd] = -1;
40         bd++
41       }
42       */
43       function createMask()
44       {
45         d3.select("body").append("canvas").attr("id", "maskCanvas")
46         maskCanvas = document.getElementById('maskCanvas'),
47         maskCanvas.width = 85;
48         maskCanvas.height = 85;
49         maskContext = maskCanvas.getContext('2d');
50         maskImage = new Image();
51         maskImage.src = 'mask.png';
52         maskImage.onload = function(){
53         maskContext.drawImage(maskImage,0,0,85,85 * maskImage.height / maskImage.width);
54         maskData = maskContext.getImageData(0,0,85,85).data;
55 /*
56         var hh = 0;
57         while (hh < maskData.length) {
58           if (hh%4 == 0) {
59             if (maskData[hh] == 0) {
60               board[~~(hh/4)] = -1;
61             }
62           }
63           hh++;
64         }
65         */
66
67         }
68       }
69       if (timer) clearInterval(timer);
70       timer = setInterval(step, 0);
71       step();
72
73       return cloud;
74
75       function step() {
76         var start = +new Date,
77             d;
78         while (+new Date - start < timeInterval && ++i < n && timer) {
79           d = data[i];
80           d.x = (size[0] * (Math.random() + .5)) >> 1;
81           d.y = (size[1] * (Math.random() + .5)) >> 1;
82           cloudSprite(d, data, i);
83           if (d.hasText && place(board, d, bounds)) {
84             tags.push(d);
85             event.word(d);
86             if (bounds) cloudBounds(bounds, d);
87             else bounds = [{x: d.x + d.x0, y: d.y + d.y0}, {x: d.x + d.x1, y: d.y + d.y1}];
88             // Temporary hack
89             d.x -= size[0] >> 1;
90             d.y -= size[1] >> 1;
91           }
92         }
93         if (i >= n) {
94           cloud.stop();
95           event.end(tags, bounds);
96         }
97       }
98     }
99
100     cloud.stop = function() {
101       if (timer) {
102         clearInterval(timer);
103         timer = null;
104       }
105       return cloud;
106     };
107
108     cloud.timeInterval = function(x) {
109       if (!arguments.length) return timeInterval;
110       timeInterval = x == null ? Infinity : x;
111       return cloud;
112     };
113
114     function place(board, tag, bounds) {
115       var perimeter = [{x: 0, y: 0}, {x: size[0], y: size[1]}],
116           startX = tag.x,
117           startY = tag.y,
118           maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]),
119           s = spiral(size),
120           dt = Math.random() < .5 ? 1 : -1,
121           t = -dt,
122           dxdy,
123           dx,
124           dy;
125
126       while (dxdy = s(t += dt)) {
127         dx = ~~dxdy[0];
128         dy = ~~dxdy[1];
129
130         if (Math.min(dx, dy) > maxDelta) break;
131
132         tag.x = startX + dx;
133         tag.y = startY + dy;
134
135         if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 ||
136             tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
137         // TODO only check for collisions within current bounds.
138         if (!bounds || !cloudCollide(tag, board, size[0])) {
139           if (!bounds || collideRects(tag, bounds)) {
140             var sprite = tag.sprite,
141                 w = tag.width >> 5,
142                 sw = size[0] >> 5,
143                 lx = tag.x - (w << 4),
144                 sx = lx & 0x7f,
145                 msx = 32 - sx,
146                 h = tag.y1 - tag.y0,
147                 x = (tag.y + tag.y0) * sw + (lx >> 5),
148                 last;
149             for (var j = 0; j < h; j++) {
150               last = 0;
151               for (var i = 0; i <= w; i++) {
152                 board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
153               }
154               x += sw;
155             }
156             delete tag.sprite;
157             return true;
158           }
159         }
160       }
161       return false;
162     }
163
164     cloud.words = function(x) {
165       if (!arguments.length) return words;
166       words = x;
167       return cloud;
168     };
169
170     cloud.size = function(x) {
171       if (!arguments.length) return size;
172       size = [+x[0], +x[1]];
173       return cloud;
174     };
175
176     cloud.font = function(x) {
177       if (!arguments.length) return font;
178       font = d3.functor(x);
179       return cloud;
180     };
181
182     cloud.fontStyle = function(x) {
183       if (!arguments.length) return fontStyle;
184       fontStyle = d3.functor(x);
185       return cloud;
186     };
187
188     cloud.fontWeight = function(x) {
189       if (!arguments.length) return fontWeight;
190       fontWeight = d3.functor(x);
191       return cloud;
192     };
193
194     cloud.rotate = function(x) {
195       if (!arguments.length) return rotate;
196       rotate = d3.functor(x);
197       return cloud;
198     };
199
200     cloud.text = function(x) {
201       if (!arguments.length) return text;
202       text = d3.functor(x);
203       return cloud;
204     };
205
206     cloud.spiral = function(x) {
207       if (!arguments.length) return spiral;
208       spiral = spirals[x + ""] || x;
209       return cloud;
210     };
211
212     cloud.fontSize = function(x) {
213       if (!arguments.length) return fontSize;
214       fontSize = d3.functor(x);
215       return cloud;
216     };
217
218     cloud.padding = function(x) {
219       if (!arguments.length) return padding;
220       padding = d3.functor(x);
221       return cloud;
222     };
223
224     return d3.rebind(cloud, event, "on");
225   }
226
227   function cloudText(d) {
228     return d.text;
229   }
230
231   function cloudFont() {
232     return "serif";
233   }
234
235   function cloudFontNormal() {
236     return "normal";
237   }
238
239   function cloudFontSize(d) {
240     return Math.sqrt(d.value);
241   }
242
243   function cloudRotate() {
244     return (~~(Math.random() * 6) - 3) * 30;
245   }
246
247   function cloudPadding() {
248     return 1;
249   }
250
251   // Fetches a monochrome sprite bitmap for the specified text.
252   // Load in batches for speed.
253   function cloudSprite(d, data, di) {
254     if (d.sprite) return;
255     c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio);
256     var x = 0,
257         y = 0,
258         maxh = 0,
259         n = data.length;
260     --di;
261     while (++di < n) {
262       d = data[di];
263       c.save();
264       c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font;
265       var w = c.measureText(d.text + "m").width * ratio,
266           h = d.size << 1;
267       if (d.rotate) {
268         var sr = Math.sin(d.rotate * cloudRadians),
269             cr = Math.cos(d.rotate * cloudRadians),
270             wcr = w * cr,
271             wsr = w * sr,
272             hcr = h * cr,
273             hsr = h * sr;
274         w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5;
275         h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
276       } else {
277         w = (w + 0x1f) >> 5 << 5;
278       }
279       if (h > maxh) maxh = h;
280       if (x + w >= (cw << 5)) {
281         x = 0;
282         y += maxh;
283         maxh = 0;
284       }
285       if (y + h >= ch) break;
286       c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio);
287       if (d.rotate) c.rotate(d.rotate * cloudRadians);
288       c.fillText(d.text, 0, 0);
289       if (d.padding) c.lineWidth = 2 * d.padding, c.strokeText(d.text, 0, 0);
290       c.restore();
291       d.width = w;
292       d.height = h;
293       d.xoff = x;
294       d.yoff = y;
295       d.x1 = w >> 1;
296       d.y1 = h >> 1;
297       d.x0 = -d.x1;
298       d.y0 = -d.y1;
299       d.hasText = true;
300       x += w;
301     }
302     var pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data,
303         sprite = [];
304     while (--di >= 0) {
305       d = data[di];
306       if (!d.hasText) continue;
307       var w = d.width,
308           w32 = w >> 5,
309           h = d.y1 - d.y0;
310       // Zero the buffer
311       for (var i = 0; i < h * w32; i++) sprite[i] = 0;
312       x = d.xoff;
313       if (x == null) return;
314       y = d.yoff;
315       var seen = 0,
316           seenRow = -1;
317       for (var j = 0; j < h; j++) {
318         for (var i = 0; i < w; i++) {
319           var k = w32 * j + (i >> 5),
320               m = pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 << (31 - (i % 32)) : 0;
321           sprite[k] |= m;
322           seen |= m;
323         }
324         if (seen) seenRow = j;
325         else {
326           d.y0++;
327           h--;
328           j--;
329           y++;
330         }
331       }
332       d.y1 = d.y0 + seenRow;
333       d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
334     }
335   }
336
337   // Use mask-based collision detection.
338   function cloudCollide(tag, board, sw) {
339     sw >>= 5;
340     var sprite = tag.sprite,
341         w = tag.width >> 5,
342         lx = tag.x - (w << 4),
343         sx = lx & 0x7f,
344         msx = 32 - sx,
345         h = tag.y1 - tag.y0,
346         x = (tag.y + tag.y0) * sw + (lx >> 5),
347         last;
348     for (var j = 0; j < h; j++) {
349       last = 0;
350       for (var i = 0; i <= w; i++) {
351         if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0))
352             & board[x + i]) return true;
353       }
354       x += sw;
355     }
356     return false;
357   }
358
359   function cloudBounds(bounds, d) {
360     var b0 = bounds[0],
361         b1 = bounds[1];
362     if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
363     if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
364     if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
365     if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
366   }
367
368   function collideRects(a, b) {
369     return a.x + a.x1 > b[0].x && a.x + a.x0 < b[1].x && a.y + a.y1 > b[0].y && a.y + a.y0 < b[1].y;
370   }
371
372   function archimedeanSpiral(size) {
373     var e = size[0] / size[1];
374     return function(t) {
375       return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)];
376     };
377   }
378
379   function rectangularSpiral(size) {
380     var dy = 4,
381         dx = dy * size[0] / size[1],
382         x = 0,
383         y = 0;
384     return function(t) {
385       var sign = t < 0 ? -1 : 1;
386       // See triangular numbers: T_n = n * (n + 1) / 2.
387       switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) {
388         case 0:  x += dx; break;
389         case 1:  y += dy; break;
390         case 2:  x -= dx; break;
391         default: y -= dy; break;
392       }
393       return [x, y];
394     };
395   }
396
397   // TODO reuse arrays?
398   function zeroArray(n) {
399     var a = [],
400         i = -1;
401     while (++i < n) a[i] = 0;
402     return a;
403   }
404
405   var cloudRadians = Math.PI / 180,
406       cw = 1 << 11 >> 5,
407       ch = 1 << 11,
408       canvas,
409       ratio = 1;
410
411   if (typeof document !== "undefined") {
412     canvas = document.createElement("canvas");
413     canvas.width = 1;
414     canvas.height = 1;
415     ratio = Math.sqrt(canvas.getContext("2d").getImageData(0, 0, 1, 1).data.length >> 2);
416     canvas.width = (cw << 5) / ratio;
417     canvas.height = ch / ratio;
418   } else {
419     // Attempt to use node-canvas.
420     canvas = new Canvas(cw << 5, ch);
421   }
422
423   var c = canvas.getContext("2d"),
424       spirals = {
425         archimedean: archimedeanSpiral,
426         rectangular: rectangularSpiral
427       };
428   c.fillStyle = c.strokeStyle = "red";
429   c.textAlign = "center";
430
431   if (typeof module === "object" && module.exports) module.exports = cloud;
432   else (d3.layout || (d3.layout = {})).cloud = cloud;
433 })();