@hpcc-js/chart 2.86.2 → 2.86.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/LICENSE +43 -43
  2. package/README.md +93 -93
  3. package/dist/index.es6.js.map +1 -1
  4. package/dist/index.js.map +1 -1
  5. package/dist/index.min.js.map +1 -1
  6. package/package.json +6 -6
  7. package/src/Area.md +176 -176
  8. package/src/Area.ts +12 -12
  9. package/src/Axis.css +34 -34
  10. package/src/Axis.ts +733 -733
  11. package/src/Bar.md +90 -90
  12. package/src/Bar.ts +9 -9
  13. package/src/Bubble.css +16 -16
  14. package/src/Bubble.md +69 -69
  15. package/src/Bubble.ts +191 -191
  16. package/src/BubbleXY.ts +14 -14
  17. package/src/Bullet.css +60 -60
  18. package/src/Bullet.md +104 -104
  19. package/src/Bullet.ts +167 -167
  20. package/src/Column.css +17 -17
  21. package/src/Column.md +90 -90
  22. package/src/Column.ts +659 -659
  23. package/src/Contour.md +88 -88
  24. package/src/Contour.ts +97 -97
  25. package/src/D3Cloud.ts +400 -400
  26. package/src/Gantt.md +119 -119
  27. package/src/Gantt.ts +14 -14
  28. package/src/Gauge.md +148 -148
  29. package/src/Gauge.ts +358 -358
  30. package/src/HalfPie.md +62 -62
  31. package/src/HalfPie.ts +26 -26
  32. package/src/Heat.md +42 -42
  33. package/src/Heat.ts +283 -283
  34. package/src/HexBin.css +9 -9
  35. package/src/HexBin.md +88 -88
  36. package/src/HexBin.ts +139 -139
  37. package/src/Line.css +6 -6
  38. package/src/Line.md +170 -170
  39. package/src/Line.ts +14 -14
  40. package/src/Pie.css +23 -23
  41. package/src/Pie.md +88 -88
  42. package/src/Pie.ts +503 -503
  43. package/src/QuarterPie.md +61 -61
  44. package/src/QuarterPie.ts +35 -35
  45. package/src/QuartileCandlestick.md +129 -129
  46. package/src/QuartileCandlestick.ts +349 -349
  47. package/src/Radar.css +15 -15
  48. package/src/Radar.md +104 -104
  49. package/src/Radar.ts +336 -336
  50. package/src/RadialBar.css +25 -25
  51. package/src/RadialBar.md +91 -91
  52. package/src/RadialBar.ts +212 -212
  53. package/src/Scatter.css +16 -16
  54. package/src/Scatter.md +163 -163
  55. package/src/Scatter.ts +376 -376
  56. package/src/StatChart.md +117 -117
  57. package/src/StatChart.ts +253 -253
  58. package/src/Step.md +163 -163
  59. package/src/Step.ts +12 -12
  60. package/src/Summary.css +56 -56
  61. package/src/Summary.md +219 -219
  62. package/src/Summary.ts +322 -322
  63. package/src/SummaryC.md +154 -154
  64. package/src/SummaryC.ts +240 -240
  65. package/src/WordCloud.css +3 -3
  66. package/src/WordCloud.md +144 -144
  67. package/src/WordCloud.ts +263 -263
  68. package/src/XYAxis.css +41 -41
  69. package/src/XYAxis.md +149 -149
  70. package/src/XYAxis.ts +803 -803
  71. package/src/__package__.ts +3 -3
  72. package/src/__tests__/heat.ts +71 -71
  73. package/src/__tests__/index.ts +3 -3
  74. package/src/__tests__/pie.ts +20 -20
  75. package/src/__tests__/stat.ts +16 -16
  76. package/src/__tests__/test3.ts +69 -69
  77. package/src/index.ts +27 -27
  78. package/src/test.ts +71 -71
package/src/D3Cloud.ts CHANGED
@@ -1,400 +1,400 @@
1
- // Word cloud layout by Jason Davies, https://www.jasondavies.com/wordcloud/
2
- // Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf
3
-
4
- import { dispatch } from "d3-dispatch";
5
-
6
- const cloudRadians = Math.PI / 180;
7
- const cw = 1 << 11 >> 5;
8
- const ch = 1 << 11;
9
-
10
- export function d3Cloud() {
11
- const event = dispatch("word", "end");
12
- const cloud: any = {};
13
-
14
- let size = [256, 256];
15
- let text = cloudText;
16
- let font = cloudFont;
17
- let fontSize = cloudFontSize;
18
- let fontStyle = cloudFontNormal;
19
- let fontWeight = cloudFontNormal;
20
- let rotate = cloudRotate;
21
- let padding = cloudPadding;
22
- let words = [];
23
- let spiral = archimedeanSpiral;
24
- let timeInterval = Infinity;
25
- let timer = null;
26
- let random = Math.random;
27
- let canvas = cloudCanvas;
28
-
29
- cloud.canvas = function (_) {
30
- return arguments.length ? (canvas = functor(_), cloud) : canvas;
31
- };
32
-
33
- cloud.start = function () {
34
- const contextAndRatio = getContext(canvas());
35
- const board = zeroArray((size[0] >> 5) * size[1]);
36
- let bounds = null;
37
- const n = words.length;
38
- let i = -1;
39
- const tags = [];
40
- const data = words.map(function (d, i) {
41
- d.text = text.call(this, d, i);
42
- d.font = font.call(this, d, i);
43
- d.style = fontStyle.call(this, d, i);
44
- d.weight = fontWeight.call(this, d, i);
45
- d.rotate = rotate.call(this, d, i);
46
- d.size = ~~fontSize.call(this, d, i);
47
- d.padding = padding.call(this, d, i);
48
- return d;
49
- }).sort(function (a, b) { return b.size - a.size; });
50
-
51
- if (timer) clearInterval(timer);
52
- timer = setInterval(step, 0);
53
- step();
54
-
55
- return cloud;
56
-
57
- function step() {
58
- const start = Date.now();
59
- while (Date.now() - start < timeInterval && ++i < n && timer) {
60
- const d = data[i];
61
- d.x = (size[0] * (random() + .5)) >> 1;
62
- d.y = (size[1] * (random() + .5)) >> 1;
63
- cloudSprite(contextAndRatio, d, data, i);
64
- if (d.hasText && place(board, d, bounds)) {
65
- tags.push(d);
66
- event.call("word", cloud, d);
67
- if (bounds) cloudBounds(bounds, d);
68
- else bounds = [{ x: d.x + d.x0, y: d.y + d.y0 }, { x: d.x + d.x1, y: d.y + d.y1 }];
69
- // Temporary hack
70
- d.x -= size[0] >> 1;
71
- d.y -= size[1] >> 1;
72
- }
73
- }
74
- if (i >= n) {
75
- cloud.stop();
76
- event.call("end", cloud, tags, bounds);
77
- }
78
- }
79
- };
80
-
81
- cloud.stop = function () {
82
- if (timer) {
83
- clearInterval(timer);
84
- timer = null;
85
- }
86
- return cloud;
87
- };
88
-
89
- function getContext(canvas) {
90
- canvas.width = canvas.height = 1;
91
- const ratio = Math.sqrt(canvas.getContext("2d").getImageData(0, 0, 1, 1).data.length >> 2);
92
- canvas.width = (cw << 5) / ratio;
93
- canvas.height = ch / ratio;
94
-
95
- const context = canvas.getContext("2d");
96
- context.fillStyle = context.strokeStyle = "red";
97
- context.textAlign = "center";
98
-
99
- return { context, ratio };
100
- }
101
-
102
- function place(board, tag, bounds) {
103
- const startX = tag.x;
104
- const startY = tag.y;
105
- const maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]);
106
- const s = spiral(size);
107
- const dt = random() < .5 ? 1 : -1;
108
- let t = -dt;
109
- let dxdy;
110
- let dx;
111
- let dy;
112
-
113
- // eslint-disable-next-line no-cond-assign
114
- while (dxdy = s(t += dt)) {
115
- dx = ~~dxdy[0];
116
- dy = ~~dxdy[1];
117
-
118
- if (Math.min(Math.abs(dx), Math.abs(dy)) >= maxDelta) break;
119
-
120
- tag.x = startX + dx;
121
- tag.y = startY + dy;
122
-
123
- if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 ||
124
- tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
125
- // TODO only check for collisions within current bounds.
126
- if (!bounds || !cloudCollide(tag, board, size[0])) {
127
- if (!bounds || collideRects(tag, bounds)) {
128
- const sprite = tag.sprite;
129
- const w = tag.width >> 5;
130
- const sw = size[0] >> 5;
131
- const lx = tag.x - (w << 4);
132
- const sx = lx & 0x7f;
133
- const msx = 32 - sx;
134
- const h = tag.y1 - tag.y0;
135
- let x = (tag.y + tag.y0) * sw + (lx >> 5);
136
- let last;
137
- for (let j = 0; j < h; j++) {
138
- last = 0;
139
- for (let i = 0; i <= w; i++) {
140
- board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
141
- }
142
- x += sw;
143
- }
144
- delete tag.sprite;
145
- return true;
146
- }
147
- }
148
- }
149
- return false;
150
- }
151
-
152
- cloud.timeInterval = function (_) {
153
- return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud) : timeInterval;
154
- };
155
-
156
- cloud.words = function (_) {
157
- return arguments.length ? (words = _, cloud) : words;
158
- };
159
-
160
- cloud.size = function (_) {
161
- return arguments.length ? (size = [+_[0], +_[1]], cloud) : size;
162
- };
163
-
164
- cloud.font = function (_) {
165
- return arguments.length ? (font = functor(_), cloud) : font;
166
- };
167
-
168
- cloud.fontStyle = function (_) {
169
- return arguments.length ? (fontStyle = functor(_), cloud) : fontStyle;
170
- };
171
-
172
- cloud.fontWeight = function (_) {
173
- return arguments.length ? (fontWeight = functor(_), cloud) : fontWeight;
174
- };
175
-
176
- cloud.rotate = function (_) {
177
- return arguments.length ? (rotate = functor(_), cloud) : rotate;
178
- };
179
-
180
- cloud.text = function (_) {
181
- return arguments.length ? (text = functor(_), cloud) : text;
182
- };
183
-
184
- cloud.spiral = function (_) {
185
- return arguments.length ? (spiral = spirals[_] || _, cloud) : spiral;
186
- };
187
-
188
- cloud.fontSize = function (_) {
189
- return arguments.length ? (fontSize = functor(_), cloud) : fontSize;
190
- };
191
-
192
- cloud.padding = function (_) {
193
- return arguments.length ? (padding = functor(_), cloud) : padding;
194
- };
195
-
196
- cloud.random = function (_) {
197
- return arguments.length ? (random = _, cloud) : random;
198
- };
199
-
200
- cloud.on = function () {
201
- const value = event.on.apply(event, arguments);
202
- return value === event ? cloud : value;
203
- };
204
-
205
- return cloud;
206
- }
207
-
208
- function cloudText(d) {
209
- return d.text;
210
- }
211
-
212
- function cloudFont() {
213
- return "serif";
214
- }
215
-
216
- function cloudFontNormal() {
217
- return "normal";
218
- }
219
-
220
- function cloudFontSize(d) {
221
- return Math.sqrt(d.value);
222
- }
223
-
224
- function cloudRotate() {
225
- return (~~(Math.random() * 6) - 3) * 30;
226
- }
227
-
228
- function cloudPadding() {
229
- return 1;
230
- }
231
-
232
- // Fetches a monochrome sprite bitmap for the specified text.
233
- // Load in batches for speed.
234
- function cloudSprite(contextAndRatio, d, data, di) {
235
- if (d.sprite) return;
236
- const c = contextAndRatio.context;
237
- const ratio = contextAndRatio.ratio;
238
-
239
- c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio);
240
- let x = 0;
241
- let y = 0;
242
- let maxh = 0;
243
- const n = data.length;
244
- --di;
245
- while (++di < n) {
246
- d = data[di];
247
- c.save();
248
- c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font;
249
- let w = c.measureText(d.text + "m").width * ratio;
250
- let h = d.size << 1;
251
- if (d.rotate) {
252
- const sr = Math.sin(d.rotate * cloudRadians);
253
- const cr = Math.cos(d.rotate * cloudRadians);
254
- const wcr = w * cr;
255
- const wsr = w * sr;
256
- const hcr = h * cr;
257
- const hsr = h * sr;
258
- w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5;
259
- h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
260
- } else {
261
- w = (w + 0x1f) >> 5 << 5;
262
- }
263
- if (h > maxh) maxh = h;
264
- if (x + w >= (cw << 5)) {
265
- x = 0;
266
- y += maxh;
267
- maxh = 0;
268
- }
269
- if (y + h >= ch) break;
270
- c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio);
271
- if (d.rotate) c.rotate(d.rotate * cloudRadians);
272
- c.fillText(d.text, 0, 0);
273
- if (d.padding) c.lineWidth = 2 * d.padding, c.strokeText(d.text, 0, 0);
274
- c.restore();
275
- d.width = w;
276
- d.height = h;
277
- d.xoff = x;
278
- d.yoff = y;
279
- d.x1 = w >> 1;
280
- d.y1 = h >> 1;
281
- d.x0 = -d.x1;
282
- d.y0 = -d.y1;
283
- d.hasText = true;
284
- x += w;
285
- }
286
- const pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data;
287
- const sprite = [];
288
- while (--di >= 0) {
289
- d = data[di];
290
- if (!d.hasText) continue;
291
- const w = d.width;
292
- const w32 = w >> 5;
293
- let h = d.y1 - d.y0;
294
- // Zero the buffer
295
- for (let i = 0; i < h * w32; i++) sprite[i] = 0;
296
- x = d.xoff;
297
- if (x == null) return;
298
- y = d.yoff;
299
- let seen = 0;
300
- let seenRow = -1;
301
- for (let j = 0; j < h; j++) {
302
- for (let i = 0; i < w; i++) {
303
- const k = w32 * j + (i >> 5);
304
- const m = pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 << (31 - (i % 32)) : 0;
305
- sprite[k] |= m;
306
- seen |= m;
307
- }
308
- if (seen) seenRow = j;
309
- else {
310
- d.y0++;
311
- h--;
312
- j--;
313
- y++;
314
- }
315
- }
316
- d.y1 = d.y0 + seenRow;
317
- d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
318
- }
319
- }
320
-
321
- // Use mask-based collision detection.
322
- function cloudCollide(tag, board, sw) {
323
- sw >>= 5;
324
- const sprite = tag.sprite;
325
- const w = tag.width >> 5;
326
- const lx = tag.x - (w << 4);
327
- const sx = lx & 0x7f;
328
- const msx = 32 - sx;
329
- const h = tag.y1 - tag.y0;
330
- let x = (tag.y + tag.y0) * sw + (lx >> 5);
331
- let last;
332
- for (let j = 0; j < h; j++) {
333
- last = 0;
334
- for (let i = 0; i <= w; i++) {
335
- if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0))
336
- & board[x + i]) return true;
337
- }
338
- x += sw;
339
- }
340
- return false;
341
- }
342
-
343
- function cloudBounds(bounds, d) {
344
- const b0 = bounds[0];
345
- const b1 = bounds[1];
346
- if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
347
- if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
348
- if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
349
- if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
350
- }
351
-
352
- function collideRects(a, b) {
353
- 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;
354
- }
355
-
356
- function archimedeanSpiral(size) {
357
- const e = size[0] / size[1];
358
- return function (t) {
359
- return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)];
360
- };
361
- }
362
-
363
- function rectangularSpiral(size) {
364
- const dy = 4;
365
- const dx = dy * size[0] / size[1];
366
- let x = 0;
367
- let y = 0;
368
- return function (t) {
369
- const sign = t < 0 ? -1 : 1;
370
- // See triangular numbers: T_n = n * (n + 1) / 2.
371
- switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) {
372
- case 0: x += dx; break;
373
- case 1: y += dy; break;
374
- case 2: x -= dx; break;
375
- default: y -= dy; break;
376
- }
377
- return [x, y];
378
- };
379
- }
380
-
381
- // TODO reuse arrays?
382
- function zeroArray(n) {
383
- const a = [];
384
- let i = -1;
385
- while (++i < n) a[i] = 0;
386
- return a;
387
- }
388
-
389
- function cloudCanvas() {
390
- return document.createElement("canvas");
391
- }
392
-
393
- function functor(d) {
394
- return typeof d === "function" ? d : function () { return d; };
395
- }
396
-
397
- const spirals = {
398
- archimedean: archimedeanSpiral,
399
- rectangular: rectangularSpiral
400
- };
1
+ // Word cloud layout by Jason Davies, https://www.jasondavies.com/wordcloud/
2
+ // Algorithm due to Jonathan Feinberg, http://static.mrfeinberg.com/bv_ch03.pdf
3
+
4
+ import { dispatch } from "d3-dispatch";
5
+
6
+ const cloudRadians = Math.PI / 180;
7
+ const cw = 1 << 11 >> 5;
8
+ const ch = 1 << 11;
9
+
10
+ export function d3Cloud() {
11
+ const event = dispatch("word", "end");
12
+ const cloud: any = {};
13
+
14
+ let size = [256, 256];
15
+ let text = cloudText;
16
+ let font = cloudFont;
17
+ let fontSize = cloudFontSize;
18
+ let fontStyle = cloudFontNormal;
19
+ let fontWeight = cloudFontNormal;
20
+ let rotate = cloudRotate;
21
+ let padding = cloudPadding;
22
+ let words = [];
23
+ let spiral = archimedeanSpiral;
24
+ let timeInterval = Infinity;
25
+ let timer = null;
26
+ let random = Math.random;
27
+ let canvas = cloudCanvas;
28
+
29
+ cloud.canvas = function (_) {
30
+ return arguments.length ? (canvas = functor(_), cloud) : canvas;
31
+ };
32
+
33
+ cloud.start = function () {
34
+ const contextAndRatio = getContext(canvas());
35
+ const board = zeroArray((size[0] >> 5) * size[1]);
36
+ let bounds = null;
37
+ const n = words.length;
38
+ let i = -1;
39
+ const tags = [];
40
+ const data = words.map(function (d, i) {
41
+ d.text = text.call(this, d, i);
42
+ d.font = font.call(this, d, i);
43
+ d.style = fontStyle.call(this, d, i);
44
+ d.weight = fontWeight.call(this, d, i);
45
+ d.rotate = rotate.call(this, d, i);
46
+ d.size = ~~fontSize.call(this, d, i);
47
+ d.padding = padding.call(this, d, i);
48
+ return d;
49
+ }).sort(function (a, b) { return b.size - a.size; });
50
+
51
+ if (timer) clearInterval(timer);
52
+ timer = setInterval(step, 0);
53
+ step();
54
+
55
+ return cloud;
56
+
57
+ function step() {
58
+ const start = Date.now();
59
+ while (Date.now() - start < timeInterval && ++i < n && timer) {
60
+ const d = data[i];
61
+ d.x = (size[0] * (random() + .5)) >> 1;
62
+ d.y = (size[1] * (random() + .5)) >> 1;
63
+ cloudSprite(contextAndRatio, d, data, i);
64
+ if (d.hasText && place(board, d, bounds)) {
65
+ tags.push(d);
66
+ event.call("word", cloud, d);
67
+ if (bounds) cloudBounds(bounds, d);
68
+ else bounds = [{ x: d.x + d.x0, y: d.y + d.y0 }, { x: d.x + d.x1, y: d.y + d.y1 }];
69
+ // Temporary hack
70
+ d.x -= size[0] >> 1;
71
+ d.y -= size[1] >> 1;
72
+ }
73
+ }
74
+ if (i >= n) {
75
+ cloud.stop();
76
+ event.call("end", cloud, tags, bounds);
77
+ }
78
+ }
79
+ };
80
+
81
+ cloud.stop = function () {
82
+ if (timer) {
83
+ clearInterval(timer);
84
+ timer = null;
85
+ }
86
+ return cloud;
87
+ };
88
+
89
+ function getContext(canvas) {
90
+ canvas.width = canvas.height = 1;
91
+ const ratio = Math.sqrt(canvas.getContext("2d").getImageData(0, 0, 1, 1).data.length >> 2);
92
+ canvas.width = (cw << 5) / ratio;
93
+ canvas.height = ch / ratio;
94
+
95
+ const context = canvas.getContext("2d");
96
+ context.fillStyle = context.strokeStyle = "red";
97
+ context.textAlign = "center";
98
+
99
+ return { context, ratio };
100
+ }
101
+
102
+ function place(board, tag, bounds) {
103
+ const startX = tag.x;
104
+ const startY = tag.y;
105
+ const maxDelta = Math.sqrt(size[0] * size[0] + size[1] * size[1]);
106
+ const s = spiral(size);
107
+ const dt = random() < .5 ? 1 : -1;
108
+ let t = -dt;
109
+ let dxdy;
110
+ let dx;
111
+ let dy;
112
+
113
+ // eslint-disable-next-line no-cond-assign
114
+ while (dxdy = s(t += dt)) {
115
+ dx = ~~dxdy[0];
116
+ dy = ~~dxdy[1];
117
+
118
+ if (Math.min(Math.abs(dx), Math.abs(dy)) >= maxDelta) break;
119
+
120
+ tag.x = startX + dx;
121
+ tag.y = startY + dy;
122
+
123
+ if (tag.x + tag.x0 < 0 || tag.y + tag.y0 < 0 ||
124
+ tag.x + tag.x1 > size[0] || tag.y + tag.y1 > size[1]) continue;
125
+ // TODO only check for collisions within current bounds.
126
+ if (!bounds || !cloudCollide(tag, board, size[0])) {
127
+ if (!bounds || collideRects(tag, bounds)) {
128
+ const sprite = tag.sprite;
129
+ const w = tag.width >> 5;
130
+ const sw = size[0] >> 5;
131
+ const lx = tag.x - (w << 4);
132
+ const sx = lx & 0x7f;
133
+ const msx = 32 - sx;
134
+ const h = tag.y1 - tag.y0;
135
+ let x = (tag.y + tag.y0) * sw + (lx >> 5);
136
+ let last;
137
+ for (let j = 0; j < h; j++) {
138
+ last = 0;
139
+ for (let i = 0; i <= w; i++) {
140
+ board[x + i] |= (last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0);
141
+ }
142
+ x += sw;
143
+ }
144
+ delete tag.sprite;
145
+ return true;
146
+ }
147
+ }
148
+ }
149
+ return false;
150
+ }
151
+
152
+ cloud.timeInterval = function (_) {
153
+ return arguments.length ? (timeInterval = _ == null ? Infinity : _, cloud) : timeInterval;
154
+ };
155
+
156
+ cloud.words = function (_) {
157
+ return arguments.length ? (words = _, cloud) : words;
158
+ };
159
+
160
+ cloud.size = function (_) {
161
+ return arguments.length ? (size = [+_[0], +_[1]], cloud) : size;
162
+ };
163
+
164
+ cloud.font = function (_) {
165
+ return arguments.length ? (font = functor(_), cloud) : font;
166
+ };
167
+
168
+ cloud.fontStyle = function (_) {
169
+ return arguments.length ? (fontStyle = functor(_), cloud) : fontStyle;
170
+ };
171
+
172
+ cloud.fontWeight = function (_) {
173
+ return arguments.length ? (fontWeight = functor(_), cloud) : fontWeight;
174
+ };
175
+
176
+ cloud.rotate = function (_) {
177
+ return arguments.length ? (rotate = functor(_), cloud) : rotate;
178
+ };
179
+
180
+ cloud.text = function (_) {
181
+ return arguments.length ? (text = functor(_), cloud) : text;
182
+ };
183
+
184
+ cloud.spiral = function (_) {
185
+ return arguments.length ? (spiral = spirals[_] || _, cloud) : spiral;
186
+ };
187
+
188
+ cloud.fontSize = function (_) {
189
+ return arguments.length ? (fontSize = functor(_), cloud) : fontSize;
190
+ };
191
+
192
+ cloud.padding = function (_) {
193
+ return arguments.length ? (padding = functor(_), cloud) : padding;
194
+ };
195
+
196
+ cloud.random = function (_) {
197
+ return arguments.length ? (random = _, cloud) : random;
198
+ };
199
+
200
+ cloud.on = function () {
201
+ const value = event.on.apply(event, arguments);
202
+ return value === event ? cloud : value;
203
+ };
204
+
205
+ return cloud;
206
+ }
207
+
208
+ function cloudText(d) {
209
+ return d.text;
210
+ }
211
+
212
+ function cloudFont() {
213
+ return "serif";
214
+ }
215
+
216
+ function cloudFontNormal() {
217
+ return "normal";
218
+ }
219
+
220
+ function cloudFontSize(d) {
221
+ return Math.sqrt(d.value);
222
+ }
223
+
224
+ function cloudRotate() {
225
+ return (~~(Math.random() * 6) - 3) * 30;
226
+ }
227
+
228
+ function cloudPadding() {
229
+ return 1;
230
+ }
231
+
232
+ // Fetches a monochrome sprite bitmap for the specified text.
233
+ // Load in batches for speed.
234
+ function cloudSprite(contextAndRatio, d, data, di) {
235
+ if (d.sprite) return;
236
+ const c = contextAndRatio.context;
237
+ const ratio = contextAndRatio.ratio;
238
+
239
+ c.clearRect(0, 0, (cw << 5) / ratio, ch / ratio);
240
+ let x = 0;
241
+ let y = 0;
242
+ let maxh = 0;
243
+ const n = data.length;
244
+ --di;
245
+ while (++di < n) {
246
+ d = data[di];
247
+ c.save();
248
+ c.font = d.style + " " + d.weight + " " + ~~((d.size + 1) / ratio) + "px " + d.font;
249
+ let w = c.measureText(d.text + "m").width * ratio;
250
+ let h = d.size << 1;
251
+ if (d.rotate) {
252
+ const sr = Math.sin(d.rotate * cloudRadians);
253
+ const cr = Math.cos(d.rotate * cloudRadians);
254
+ const wcr = w * cr;
255
+ const wsr = w * sr;
256
+ const hcr = h * cr;
257
+ const hsr = h * sr;
258
+ w = (Math.max(Math.abs(wcr + hsr), Math.abs(wcr - hsr)) + 0x1f) >> 5 << 5;
259
+ h = ~~Math.max(Math.abs(wsr + hcr), Math.abs(wsr - hcr));
260
+ } else {
261
+ w = (w + 0x1f) >> 5 << 5;
262
+ }
263
+ if (h > maxh) maxh = h;
264
+ if (x + w >= (cw << 5)) {
265
+ x = 0;
266
+ y += maxh;
267
+ maxh = 0;
268
+ }
269
+ if (y + h >= ch) break;
270
+ c.translate((x + (w >> 1)) / ratio, (y + (h >> 1)) / ratio);
271
+ if (d.rotate) c.rotate(d.rotate * cloudRadians);
272
+ c.fillText(d.text, 0, 0);
273
+ if (d.padding) c.lineWidth = 2 * d.padding, c.strokeText(d.text, 0, 0);
274
+ c.restore();
275
+ d.width = w;
276
+ d.height = h;
277
+ d.xoff = x;
278
+ d.yoff = y;
279
+ d.x1 = w >> 1;
280
+ d.y1 = h >> 1;
281
+ d.x0 = -d.x1;
282
+ d.y0 = -d.y1;
283
+ d.hasText = true;
284
+ x += w;
285
+ }
286
+ const pixels = c.getImageData(0, 0, (cw << 5) / ratio, ch / ratio).data;
287
+ const sprite = [];
288
+ while (--di >= 0) {
289
+ d = data[di];
290
+ if (!d.hasText) continue;
291
+ const w = d.width;
292
+ const w32 = w >> 5;
293
+ let h = d.y1 - d.y0;
294
+ // Zero the buffer
295
+ for (let i = 0; i < h * w32; i++) sprite[i] = 0;
296
+ x = d.xoff;
297
+ if (x == null) return;
298
+ y = d.yoff;
299
+ let seen = 0;
300
+ let seenRow = -1;
301
+ for (let j = 0; j < h; j++) {
302
+ for (let i = 0; i < w; i++) {
303
+ const k = w32 * j + (i >> 5);
304
+ const m = pixels[((y + j) * (cw << 5) + (x + i)) << 2] ? 1 << (31 - (i % 32)) : 0;
305
+ sprite[k] |= m;
306
+ seen |= m;
307
+ }
308
+ if (seen) seenRow = j;
309
+ else {
310
+ d.y0++;
311
+ h--;
312
+ j--;
313
+ y++;
314
+ }
315
+ }
316
+ d.y1 = d.y0 + seenRow;
317
+ d.sprite = sprite.slice(0, (d.y1 - d.y0) * w32);
318
+ }
319
+ }
320
+
321
+ // Use mask-based collision detection.
322
+ function cloudCollide(tag, board, sw) {
323
+ sw >>= 5;
324
+ const sprite = tag.sprite;
325
+ const w = tag.width >> 5;
326
+ const lx = tag.x - (w << 4);
327
+ const sx = lx & 0x7f;
328
+ const msx = 32 - sx;
329
+ const h = tag.y1 - tag.y0;
330
+ let x = (tag.y + tag.y0) * sw + (lx >> 5);
331
+ let last;
332
+ for (let j = 0; j < h; j++) {
333
+ last = 0;
334
+ for (let i = 0; i <= w; i++) {
335
+ if (((last << msx) | (i < w ? (last = sprite[j * w + i]) >>> sx : 0))
336
+ & board[x + i]) return true;
337
+ }
338
+ x += sw;
339
+ }
340
+ return false;
341
+ }
342
+
343
+ function cloudBounds(bounds, d) {
344
+ const b0 = bounds[0];
345
+ const b1 = bounds[1];
346
+ if (d.x + d.x0 < b0.x) b0.x = d.x + d.x0;
347
+ if (d.y + d.y0 < b0.y) b0.y = d.y + d.y0;
348
+ if (d.x + d.x1 > b1.x) b1.x = d.x + d.x1;
349
+ if (d.y + d.y1 > b1.y) b1.y = d.y + d.y1;
350
+ }
351
+
352
+ function collideRects(a, b) {
353
+ 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;
354
+ }
355
+
356
+ function archimedeanSpiral(size) {
357
+ const e = size[0] / size[1];
358
+ return function (t) {
359
+ return [e * (t *= .1) * Math.cos(t), t * Math.sin(t)];
360
+ };
361
+ }
362
+
363
+ function rectangularSpiral(size) {
364
+ const dy = 4;
365
+ const dx = dy * size[0] / size[1];
366
+ let x = 0;
367
+ let y = 0;
368
+ return function (t) {
369
+ const sign = t < 0 ? -1 : 1;
370
+ // See triangular numbers: T_n = n * (n + 1) / 2.
371
+ switch ((Math.sqrt(1 + 4 * sign * t) - sign) & 3) {
372
+ case 0: x += dx; break;
373
+ case 1: y += dy; break;
374
+ case 2: x -= dx; break;
375
+ default: y -= dy; break;
376
+ }
377
+ return [x, y];
378
+ };
379
+ }
380
+
381
+ // TODO reuse arrays?
382
+ function zeroArray(n) {
383
+ const a = [];
384
+ let i = -1;
385
+ while (++i < n) a[i] = 0;
386
+ return a;
387
+ }
388
+
389
+ function cloudCanvas() {
390
+ return document.createElement("canvas");
391
+ }
392
+
393
+ function functor(d) {
394
+ return typeof d === "function" ? d : function () { return d; };
395
+ }
396
+
397
+ const spirals = {
398
+ archimedean: archimedeanSpiral,
399
+ rectangular: rectangularSpiral
400
+ };