ok-claude 0.1.0

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.
@@ -0,0 +1,1249 @@
1
+ /*!
2
+ * wordcloud2.js
3
+ * http://timdream.org/wordcloud2.js/
4
+ *
5
+ * Copyright 2011 - 2019 Tim Guan-tin Chien and contributors.
6
+ * Released under the MIT license
7
+ *
8
+ * Vendored for ok-claude.
9
+ * Source: https://registry.npmjs.org/wordcloud (npm package "wordcloud")
10
+ * Version: 1.2.3
11
+ * File: package/src/wordcloud2.js (unminified — npm package ships no min build)
12
+ * License: MIT (see src/vendor/LICENSE-wordcloud2.txt)
13
+ */
14
+
15
+ 'use strict'
16
+
17
+ // setImmediate
18
+ if (!window.setImmediate) {
19
+ window.setImmediate = (function setupSetImmediate () {
20
+ return window.msSetImmediate ||
21
+ window.webkitSetImmediate ||
22
+ window.mozSetImmediate ||
23
+ window.oSetImmediate ||
24
+ (function setupSetZeroTimeout () {
25
+ if (!window.postMessage || !window.addEventListener) {
26
+ return null
27
+ }
28
+
29
+ var callbacks = [undefined]
30
+ var message = 'zero-timeout-message'
31
+
32
+ // Like setTimeout, but only takes a function argument. There's
33
+ // no time argument (always zero) and no arguments (you have to
34
+ // use a closure).
35
+ var setZeroTimeout = function setZeroTimeout (callback) {
36
+ var id = callbacks.length
37
+ callbacks.push(callback)
38
+ window.postMessage(message + id.toString(36), '*')
39
+
40
+ return id
41
+ }
42
+
43
+ window.addEventListener('message', function setZeroTimeoutMessage (evt) {
44
+ // Skipping checking event source, retarded IE confused this window
45
+ // object with another in the presence of iframe
46
+ if (typeof evt.data !== 'string' ||
47
+ evt.data.substr(0, message.length) !== message/* ||
48
+ evt.source !== window */) {
49
+ return
50
+ }
51
+
52
+ evt.stopImmediatePropagation()
53
+
54
+ var id = parseInt(evt.data.substr(message.length), 36)
55
+ if (!callbacks[id]) {
56
+ return
57
+ }
58
+
59
+ callbacks[id]()
60
+ callbacks[id] = undefined
61
+ }, true)
62
+
63
+ /* specify clearImmediate() here since we need the scope */
64
+ window.clearImmediate = function clearZeroTimeout (id) {
65
+ if (!callbacks[id]) {
66
+ return
67
+ }
68
+
69
+ callbacks[id] = undefined
70
+ }
71
+
72
+ return setZeroTimeout
73
+ })() ||
74
+ // fallback
75
+ function setImmediateFallback (fn) {
76
+ window.setTimeout(fn, 0)
77
+ }
78
+ })()
79
+ }
80
+
81
+ if (!window.clearImmediate) {
82
+ window.clearImmediate = (function setupClearImmediate () {
83
+ return window.msClearImmediate ||
84
+ window.webkitClearImmediate ||
85
+ window.mozClearImmediate ||
86
+ window.oClearImmediate ||
87
+ // "clearZeroTimeout" is implement on the previous block ||
88
+ // fallback
89
+ function clearImmediateFallback (timer) {
90
+ window.clearTimeout(timer)
91
+ }
92
+ })()
93
+ }
94
+
95
+ (function (global) {
96
+ // Check if WordCloud can run on this browser
97
+ var isSupported = (function isSupported () {
98
+ var canvas = document.createElement('canvas')
99
+ if (!canvas || !canvas.getContext) {
100
+ return false
101
+ }
102
+
103
+ var ctx = canvas.getContext('2d')
104
+ if (!ctx) {
105
+ return false
106
+ }
107
+ if (!ctx.getImageData) {
108
+ return false
109
+ }
110
+ if (!ctx.fillText) {
111
+ return false
112
+ }
113
+
114
+ if (!Array.prototype.some) {
115
+ return false
116
+ }
117
+ if (!Array.prototype.push) {
118
+ return false
119
+ }
120
+
121
+ return true
122
+ }())
123
+
124
+ // Find out if the browser impose minium font size by
125
+ // drawing small texts on a canvas and measure it's width.
126
+ var minFontSize = (function getMinFontSize () {
127
+ if (!isSupported) {
128
+ return
129
+ }
130
+
131
+ var ctx = document.createElement('canvas').getContext('2d')
132
+
133
+ // start from 20
134
+ var size = 20
135
+
136
+ // two sizes to measure
137
+ var hanWidth, mWidth
138
+
139
+ while (size) {
140
+ ctx.font = size.toString(10) + 'px sans-serif'
141
+ if ((ctx.measureText('\uFF37').width === hanWidth) &&
142
+ (ctx.measureText('m').width) === mWidth) {
143
+ return (size + 1)
144
+ }
145
+
146
+ hanWidth = ctx.measureText('\uFF37').width
147
+ mWidth = ctx.measureText('m').width
148
+
149
+ size--
150
+ }
151
+
152
+ return 0
153
+ })()
154
+
155
+ var getItemExtraData = function (item) {
156
+ if (Array.isArray(item)) {
157
+ var itemCopy = item.slice()
158
+ // remove data we already have (word and weight)
159
+ itemCopy.splice(0, 2)
160
+ return itemCopy
161
+ } else {
162
+ return []
163
+ }
164
+ }
165
+
166
+ // Based on http://jsfromhell.com/array/shuffle
167
+ var shuffleArray = function shuffleArray (arr) {
168
+ for (var j, x, i = arr.length; i;) {
169
+ j = Math.floor(Math.random() * i)
170
+ x = arr[--i]
171
+ arr[i] = arr[j]
172
+ arr[j] = x
173
+ }
174
+ return arr
175
+ }
176
+
177
+ var timer = {};
178
+ var WordCloud = function WordCloud (elements, options) {
179
+ if (!isSupported) {
180
+ return
181
+ }
182
+
183
+ var timerId = Math.floor(Math.random() * Date.now())
184
+
185
+ if (!Array.isArray(elements)) {
186
+ elements = [elements]
187
+ }
188
+
189
+ elements.forEach(function (el, i) {
190
+ if (typeof el === 'string') {
191
+ elements[i] = document.getElementById(el)
192
+ if (!elements[i]) {
193
+ throw new Error('The element id specified is not found.')
194
+ }
195
+ } else if (!el.tagName && !el.appendChild) {
196
+ throw new Error('You must pass valid HTML elements, or ID of the element.')
197
+ }
198
+ })
199
+
200
+ /* Default values to be overwritten by options object */
201
+ var settings = {
202
+ list: [],
203
+ fontFamily: '"Trebuchet MS", "Heiti TC", "微軟正黑體", ' +
204
+ '"Arial Unicode MS", "Droid Fallback Sans", sans-serif',
205
+ fontWeight: 'normal',
206
+ color: 'random-dark',
207
+ minSize: 0, // 0 to disable
208
+ weightFactor: 1,
209
+ clearCanvas: true,
210
+ backgroundColor: '#fff', // opaque white = rgba(255, 255, 255, 1)
211
+
212
+ gridSize: 8,
213
+ drawOutOfBound: false,
214
+ shrinkToFit: false,
215
+ origin: null,
216
+
217
+ drawMask: false,
218
+ maskColor: 'rgba(255,0,0,0.3)',
219
+ maskGapWidth: 0.3,
220
+
221
+ wait: 0,
222
+ abortThreshold: 0, // disabled
223
+ abort: function noop () {},
224
+
225
+ minRotation: -Math.PI / 2,
226
+ maxRotation: Math.PI / 2,
227
+ rotationSteps: 0,
228
+
229
+ shuffle: true,
230
+ rotateRatio: 0.1,
231
+
232
+ shape: 'circle',
233
+ ellipticity: 0.65,
234
+
235
+ classes: null,
236
+
237
+ hover: null,
238
+ click: null
239
+ }
240
+
241
+ if (options) {
242
+ for (var key in options) {
243
+ if (key in settings) {
244
+ settings[key] = options[key]
245
+ }
246
+ }
247
+ }
248
+
249
+ /* Convert weightFactor into a function */
250
+ if (typeof settings.weightFactor !== 'function') {
251
+ var factor = settings.weightFactor
252
+ settings.weightFactor = function weightFactor (pt) {
253
+ return pt * factor // in px
254
+ }
255
+ }
256
+
257
+ /* Convert shape into a function */
258
+ if (typeof settings.shape !== 'function') {
259
+ switch (settings.shape) {
260
+ case 'circle':
261
+ /* falls through */
262
+ default:
263
+ // 'circle' is the default and a shortcut in the code loop.
264
+ settings.shape = 'circle'
265
+ break
266
+
267
+ case 'cardioid':
268
+ settings.shape = function shapeCardioid (theta) {
269
+ return 1 - Math.sin(theta)
270
+ }
271
+ break
272
+
273
+ /*
274
+ To work out an X-gon, one has to calculate "m",
275
+ where 1/(cos(2*PI/X)+m*sin(2*PI/X)) = 1/(cos(0)+m*sin(0))
276
+ http://www.wolframalpha.com/input/?i=1%2F%28cos%282*PI%2FX%29%2Bm*sin%28
277
+ 2*PI%2FX%29%29+%3D+1%2F%28cos%280%29%2Bm*sin%280%29%29
278
+ Copy the solution into polar equation r = 1/(cos(t') + m*sin(t'))
279
+ where t' equals to mod(t, 2PI/X)
280
+ */
281
+
282
+ case 'diamond':
283
+ // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
284
+ // %28t%2C+PI%2F2%29%29%2Bsin%28mod+%28t%2C+PI%2F2%29%29%29%2C+t+%3D
285
+ // +0+..+2*PI
286
+ settings.shape = function shapeSquare (theta) {
287
+ var thetaPrime = theta % (2 * Math.PI / 4)
288
+ return 1 / (Math.cos(thetaPrime) + Math.sin(thetaPrime))
289
+ }
290
+ break
291
+
292
+ case 'square':
293
+ // http://www.wolframalpha.com/input/?i=plot+r+%3D+min(1%2Fabs(cos(t
294
+ // )),1%2Fabs(sin(t)))),+t+%3D+0+..+2*PI
295
+ settings.shape = function shapeSquare (theta) {
296
+ return Math.min(
297
+ 1 / Math.abs(Math.cos(theta)),
298
+ 1 / Math.abs(Math.sin(theta))
299
+ )
300
+ }
301
+ break
302
+
303
+ case 'triangle-forward':
304
+ // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
305
+ // %28t%2C+2*PI%2F3%29%29%2Bsqrt%283%29sin%28mod+%28t%2C+2*PI%2F3%29
306
+ // %29%29%2C+t+%3D+0+..+2*PI
307
+ settings.shape = function shapeTriangle (theta) {
308
+ var thetaPrime = theta % (2 * Math.PI / 3)
309
+ return 1 / (Math.cos(thetaPrime) +
310
+ Math.sqrt(3) * Math.sin(thetaPrime))
311
+ }
312
+ break
313
+
314
+ case 'triangle':
315
+ case 'triangle-upright':
316
+ settings.shape = function shapeTriangle (theta) {
317
+ var thetaPrime = (theta + Math.PI * 3 / 2) % (2 * Math.PI / 3)
318
+ return 1 / (Math.cos(thetaPrime) +
319
+ Math.sqrt(3) * Math.sin(thetaPrime))
320
+ }
321
+ break
322
+
323
+ case 'pentagon':
324
+ settings.shape = function shapePentagon (theta) {
325
+ var thetaPrime = (theta + 0.955) % (2 * Math.PI / 5)
326
+ return 1 / (Math.cos(thetaPrime) +
327
+ 0.726543 * Math.sin(thetaPrime))
328
+ }
329
+ break
330
+
331
+ case 'star':
332
+ settings.shape = function shapeStar (theta) {
333
+ var thetaPrime = (theta + 0.955) % (2 * Math.PI / 10)
334
+ if ((theta + 0.955) % (2 * Math.PI / 5) - (2 * Math.PI / 10) >= 0) {
335
+ return 1 / (Math.cos((2 * Math.PI / 10) - thetaPrime) +
336
+ 3.07768 * Math.sin((2 * Math.PI / 10) - thetaPrime))
337
+ } else {
338
+ return 1 / (Math.cos(thetaPrime) +
339
+ 3.07768 * Math.sin(thetaPrime))
340
+ }
341
+ }
342
+ break
343
+ }
344
+ }
345
+
346
+ /* Make sure gridSize is a whole number and is not smaller than 4px */
347
+ settings.gridSize = Math.max(Math.floor(settings.gridSize), 4)
348
+
349
+ /* shorthand */
350
+ var g = settings.gridSize
351
+ var maskRectWidth = g - settings.maskGapWidth
352
+
353
+ /* normalize rotation settings */
354
+ var rotationRange = Math.abs(settings.maxRotation - settings.minRotation)
355
+ var rotationSteps = Math.abs(Math.floor(settings.rotationSteps))
356
+ var minRotation = Math.min(settings.maxRotation, settings.minRotation)
357
+
358
+ /* information/object available to all functions, set when start() */
359
+ var grid, // 2d array containing filling information
360
+ ngx, ngy, // width and height of the grid
361
+ center, // position of the center of the cloud
362
+ maxRadius
363
+
364
+ /* timestamp for measuring each putWord() action */
365
+ var escapeTime
366
+
367
+ /* function for getting the color of the text */
368
+ var getTextColor
369
+ function randomHslColor (min, max) {
370
+ return 'hsl(' +
371
+ (Math.random() * 360).toFixed() + ',' +
372
+ (Math.random() * 30 + 70).toFixed() + '%,' +
373
+ (Math.random() * (max - min) + min).toFixed() + '%)'
374
+ }
375
+ switch (settings.color) {
376
+ case 'random-dark':
377
+ getTextColor = function getRandomDarkColor () {
378
+ return randomHslColor(10, 50)
379
+ }
380
+ break
381
+
382
+ case 'random-light':
383
+ getTextColor = function getRandomLightColor () {
384
+ return randomHslColor(50, 90)
385
+ }
386
+ break
387
+
388
+ default:
389
+ if (typeof settings.color === 'function') {
390
+ getTextColor = settings.color
391
+ }
392
+ break
393
+ }
394
+
395
+ /* function for getting the font-weight of the text */
396
+ var getTextFontWeight
397
+ if (typeof settings.fontWeight === 'function') {
398
+ getTextFontWeight = settings.fontWeight
399
+ }
400
+
401
+ /* function for getting the classes of the text */
402
+ var getTextClasses = null
403
+ if (typeof settings.classes === 'function') {
404
+ getTextClasses = settings.classes
405
+ }
406
+
407
+ /* Interactive */
408
+ var interactive = false
409
+ var infoGrid = []
410
+ var hovered
411
+
412
+ var getInfoGridFromMouseTouchEvent =
413
+ function getInfoGridFromMouseTouchEvent (evt) {
414
+ var canvas = evt.currentTarget
415
+ var rect = canvas.getBoundingClientRect()
416
+ var clientX
417
+ var clientY
418
+ /** Detect if touches are available */
419
+ if (evt.touches) {
420
+ clientX = evt.touches[0].clientX
421
+ clientY = evt.touches[0].clientY
422
+ } else {
423
+ clientX = evt.clientX
424
+ clientY = evt.clientY
425
+ }
426
+
427
+ var eventXvalue = clientX - rect.left
428
+ var eventX = eventXvalue < 0 ? 0 : eventXvalue
429
+ var eventY = clientY - rect.top
430
+
431
+ var x = Math.floor(eventX * ((canvas.width / rect.width) || 1) / g)
432
+ var y = Math.floor(eventY * ((canvas.height / rect.height) || 1) / g)
433
+
434
+ if (!infoGrid[x]) {
435
+ return null
436
+ }
437
+
438
+ return infoGrid[x][y]
439
+ }
440
+
441
+ var wordcloudhover = function wordcloudhover (evt) {
442
+ var info = getInfoGridFromMouseTouchEvent(evt)
443
+
444
+ if (hovered === info) {
445
+ return
446
+ }
447
+
448
+ hovered = info
449
+ if (!info) {
450
+ settings.hover(undefined, undefined, evt)
451
+
452
+ return
453
+ }
454
+
455
+ settings.hover(info.item, info.dimension, evt)
456
+ }
457
+
458
+ var wordcloudclick = function wordcloudclick (evt) {
459
+ var info = getInfoGridFromMouseTouchEvent(evt)
460
+ if (!info) {
461
+ return
462
+ }
463
+
464
+ settings.click(info.item, info.dimension, evt)
465
+ evt.preventDefault()
466
+ }
467
+
468
+ /* Get points on the grid for a given radius away from the center */
469
+ var pointsAtRadius = []
470
+ var getPointsAtRadius = function getPointsAtRadius (radius) {
471
+ if (pointsAtRadius[radius]) {
472
+ return pointsAtRadius[radius]
473
+ }
474
+
475
+ // Look for these number of points on each radius
476
+ var T = radius * 8
477
+
478
+ // Getting all the points at this radius
479
+ var t = T
480
+ var points = []
481
+
482
+ if (radius === 0) {
483
+ points.push([center[0], center[1], 0])
484
+ }
485
+
486
+ while (t--) {
487
+ // distort the radius to put the cloud in shape
488
+ var rx = 1
489
+ if (settings.shape !== 'circle') {
490
+ rx = settings.shape(t / T * 2 * Math.PI) // 0 to 1
491
+ }
492
+
493
+ // Push [x, y, t] t is used solely for getTextColor()
494
+ points.push([
495
+ center[0] + radius * rx * Math.cos(-t / T * 2 * Math.PI),
496
+ center[1] + radius * rx * Math.sin(-t / T * 2 * Math.PI) *
497
+ settings.ellipticity,
498
+ t / T * 2 * Math.PI])
499
+ }
500
+
501
+ pointsAtRadius[radius] = points
502
+ return points
503
+ }
504
+
505
+ /* Return true if we had spent too much time */
506
+ var exceedTime = function exceedTime () {
507
+ return ((settings.abortThreshold > 0) &&
508
+ ((new Date()).getTime() - escapeTime > settings.abortThreshold))
509
+ }
510
+
511
+ /* Get the deg of rotation according to settings, and luck. */
512
+ var getRotateDeg = function getRotateDeg () {
513
+ if (settings.rotateRatio === 0) {
514
+ return 0
515
+ }
516
+
517
+ if (Math.random() > settings.rotateRatio) {
518
+ return 0
519
+ }
520
+
521
+ if (rotationRange === 0) {
522
+ return minRotation
523
+ }
524
+
525
+ if (rotationSteps > 0) {
526
+ // Min rotation + zero or more steps * span of one step
527
+ return minRotation +
528
+ Math.floor(Math.random() * rotationSteps) *
529
+ rotationRange / (rotationSteps - 1)
530
+ } else {
531
+ return minRotation + Math.random() * rotationRange
532
+ }
533
+ }
534
+
535
+ var getTextInfo = function getTextInfo (word, weight, rotateDeg, extraDataArray) {
536
+ // calculate the acutal font size
537
+ // fontSize === 0 means weightFactor function wants the text skipped,
538
+ // and size < minSize means we cannot draw the text.
539
+ var debug = false
540
+ var fontSize = settings.weightFactor(weight)
541
+ if (fontSize <= settings.minSize) {
542
+ return false
543
+ }
544
+
545
+ // Scale factor here is to make sure fillText is not limited by
546
+ // the minium font size set by browser.
547
+ // It will always be 1 or 2n.
548
+ var mu = 1
549
+ if (fontSize < minFontSize) {
550
+ mu = (function calculateScaleFactor () {
551
+ var mu = 2
552
+ while (mu * fontSize < minFontSize) {
553
+ mu += 2
554
+ }
555
+ return mu
556
+ })()
557
+ }
558
+
559
+ // Get fontWeight that will be used to set fctx.font
560
+ var fontWeight
561
+ if (getTextFontWeight) {
562
+ fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray)
563
+ } else {
564
+ fontWeight = settings.fontWeight
565
+ }
566
+
567
+ var fcanvas = document.createElement('canvas')
568
+ var fctx = fcanvas.getContext('2d', { willReadFrequently: true })
569
+
570
+ fctx.font = fontWeight + ' ' +
571
+ (fontSize * mu).toString(10) + 'px ' + settings.fontFamily
572
+
573
+ // Estimate the dimension of the text with measureText().
574
+ var fw = fctx.measureText(word).width / mu
575
+ var fh = Math.max(fontSize * mu,
576
+ fctx.measureText('m').width,
577
+ fctx.measureText('\uFF37').width
578
+ ) / mu
579
+
580
+ // Create a boundary box that is larger than our estimates,
581
+ // so text don't get cut of (it sill might)
582
+ var boxWidth = fw + fh * 2
583
+ var boxHeight = fh * 3
584
+ var fgw = Math.ceil(boxWidth / g)
585
+ var fgh = Math.ceil(boxHeight / g)
586
+ boxWidth = fgw * g
587
+ boxHeight = fgh * g
588
+
589
+ // Calculate the proper offsets to make the text centered at
590
+ // the preferred position.
591
+
592
+ // This is simply half of the width.
593
+ var fillTextOffsetX = -fw / 2
594
+ // Instead of moving the box to the exact middle of the preferred
595
+ // position, for Y-offset we move 0.4 instead, so Latin alphabets look
596
+ // vertical centered.
597
+ var fillTextOffsetY = -fh * 0.4
598
+
599
+ // Calculate the actual dimension of the canvas, considering the rotation.
600
+ var cgh = Math.ceil((boxWidth * Math.abs(Math.sin(rotateDeg)) +
601
+ boxHeight * Math.abs(Math.cos(rotateDeg))) / g)
602
+ var cgw = Math.ceil((boxWidth * Math.abs(Math.cos(rotateDeg)) +
603
+ boxHeight * Math.abs(Math.sin(rotateDeg))) / g)
604
+ var width = cgw * g
605
+ var height = cgh * g
606
+
607
+ fcanvas.setAttribute('width', width)
608
+ fcanvas.setAttribute('height', height)
609
+
610
+ if (debug) {
611
+ // Attach fcanvas to the DOM
612
+ document.body.appendChild(fcanvas)
613
+ // Save it's state so that we could restore and draw the grid correctly.
614
+ fctx.save()
615
+ }
616
+
617
+ // Scale the canvas with |mu|.
618
+ fctx.scale(1 / mu, 1 / mu)
619
+ fctx.translate(width * mu / 2, height * mu / 2)
620
+ fctx.rotate(-rotateDeg)
621
+
622
+ // Once the width/height is set, ctx info will be reset.
623
+ // Set it again here.
624
+ fctx.font = fontWeight + ' ' +
625
+ (fontSize * mu).toString(10) + 'px ' + settings.fontFamily
626
+
627
+ // Fill the text into the fcanvas.
628
+ // XXX: We cannot because textBaseline = 'top' here because
629
+ // Firefox and Chrome uses different default line-height for canvas.
630
+ // Please read https://bugzil.la/737852#c6.
631
+ // Here, we use textBaseline = 'middle' and draw the text at exactly
632
+ // 0.5 * fontSize lower.
633
+ fctx.fillStyle = '#000'
634
+ fctx.textBaseline = 'middle'
635
+ fctx.fillText(
636
+ word, fillTextOffsetX * mu,
637
+ (fillTextOffsetY + fontSize * 0.5) * mu
638
+ )
639
+
640
+ // Get the pixels of the text
641
+ var imageData = fctx.getImageData(0, 0, width, height).data
642
+
643
+ if (exceedTime()) {
644
+ return false
645
+ }
646
+
647
+ if (debug) {
648
+ // Draw the box of the original estimation
649
+ fctx.strokeRect(
650
+ fillTextOffsetX * mu,
651
+ fillTextOffsetY, fw * mu, fh * mu
652
+ )
653
+ fctx.restore()
654
+ }
655
+
656
+ // Read the pixels and save the information to the occupied array
657
+ var occupied = []
658
+ var gx = cgw
659
+ var gy, x, y
660
+ var bounds = [cgh / 2, cgw / 2, cgh / 2, cgw / 2]
661
+ while (gx--) {
662
+ gy = cgh
663
+ while (gy--) {
664
+ y = g
665
+ /* eslint no-labels: ["error", { "allowLoop": true }] */
666
+ singleGridLoop: while (y--) {
667
+ x = g
668
+ while (x--) {
669
+ if (imageData[((gy * g + y) * width +
670
+ (gx * g + x)) * 4 + 3]) {
671
+ occupied.push([gx, gy])
672
+
673
+ if (gx < bounds[3]) {
674
+ bounds[3] = gx
675
+ }
676
+ if (gx > bounds[1]) {
677
+ bounds[1] = gx
678
+ }
679
+ if (gy < bounds[0]) {
680
+ bounds[0] = gy
681
+ }
682
+ if (gy > bounds[2]) {
683
+ bounds[2] = gy
684
+ }
685
+
686
+ if (debug) {
687
+ fctx.fillStyle = 'rgba(255, 0, 0, 0.5)'
688
+ fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5)
689
+ }
690
+ break singleGridLoop
691
+ }
692
+ }
693
+ }
694
+ if (debug) {
695
+ fctx.fillStyle = 'rgba(0, 0, 255, 0.5)'
696
+ fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5)
697
+ }
698
+ }
699
+ }
700
+
701
+ if (debug) {
702
+ fctx.fillStyle = 'rgba(0, 255, 0, 0.5)'
703
+ fctx.fillRect(
704
+ bounds[3] * g,
705
+ bounds[0] * g,
706
+ (bounds[1] - bounds[3] + 1) * g,
707
+ (bounds[2] - bounds[0] + 1) * g
708
+ )
709
+ }
710
+
711
+ // Return information needed to create the text on the real canvas
712
+ return {
713
+ mu: mu,
714
+ occupied: occupied,
715
+ bounds: bounds,
716
+ gw: cgw,
717
+ gh: cgh,
718
+ fillTextOffsetX: fillTextOffsetX,
719
+ fillTextOffsetY: fillTextOffsetY,
720
+ fillTextWidth: fw,
721
+ fillTextHeight: fh,
722
+ fontSize: fontSize
723
+ }
724
+ }
725
+
726
+ /* Determine if there is room available in the given dimension */
727
+ var canFitText = function canFitText (gx, gy, gw, gh, occupied) {
728
+ // Go through the occupied points,
729
+ // return false if the space is not available.
730
+ var i = occupied.length
731
+ while (i--) {
732
+ var px = gx + occupied[i][0]
733
+ var py = gy + occupied[i][1]
734
+
735
+ if (px >= ngx || py >= ngy || px < 0 || py < 0) {
736
+ if (!settings.drawOutOfBound) {
737
+ return false
738
+ }
739
+ continue
740
+ }
741
+
742
+ if (!grid[px][py]) {
743
+ return false
744
+ }
745
+ }
746
+ return true
747
+ }
748
+
749
+ /* Actually draw the text on the grid */
750
+ var drawText = function drawText (gx, gy, info, word, weight, distance, theta, rotateDeg, attributes, extraDataArray) {
751
+ var fontSize = info.fontSize
752
+ var color
753
+ if (getTextColor) {
754
+ color = getTextColor(word, weight, fontSize, distance, theta, extraDataArray)
755
+ } else {
756
+ color = settings.color
757
+ }
758
+
759
+ // get fontWeight that will be used to set ctx.font and font style rule
760
+ var fontWeight
761
+ if (getTextFontWeight) {
762
+ fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray)
763
+ } else {
764
+ fontWeight = settings.fontWeight
765
+ }
766
+
767
+ var classes
768
+ if (getTextClasses) {
769
+ classes = getTextClasses(word, weight, fontSize, extraDataArray)
770
+ } else {
771
+ classes = settings.classes
772
+ }
773
+
774
+ elements.forEach(function (el) {
775
+ if (el.getContext) {
776
+ var ctx = el.getContext('2d')
777
+ var mu = info.mu
778
+
779
+ // Save the current state before messing it
780
+ ctx.save()
781
+ ctx.scale(1 / mu, 1 / mu)
782
+
783
+ ctx.font = fontWeight + ' ' +
784
+ (fontSize * mu).toString(10) + 'px ' + settings.fontFamily
785
+ ctx.fillStyle = color
786
+
787
+ // Translate the canvas position to the origin coordinate of where
788
+ // the text should be put.
789
+ ctx.translate(
790
+ (gx + info.gw / 2) * g * mu,
791
+ (gy + info.gh / 2) * g * mu
792
+ )
793
+
794
+ if (rotateDeg !== 0) {
795
+ ctx.rotate(-rotateDeg)
796
+ }
797
+
798
+ // Finally, fill the text.
799
+
800
+ // XXX: We cannot because textBaseline = 'top' here because
801
+ // Firefox and Chrome uses different default line-height for canvas.
802
+ // Please read https://bugzil.la/737852#c6.
803
+ // Here, we use textBaseline = 'middle' and draw the text at exactly
804
+ // 0.5 * fontSize lower.
805
+ ctx.textBaseline = 'middle'
806
+ ctx.fillText(
807
+ word, info.fillTextOffsetX * mu,
808
+ (info.fillTextOffsetY + fontSize * 0.5) * mu
809
+ )
810
+
811
+ // The below box is always matches how <span>s are positioned
812
+ /* ctx.strokeRect(info.fillTextOffsetX, info.fillTextOffsetY,
813
+ info.fillTextWidth, info.fillTextHeight) */
814
+
815
+ // Restore the state.
816
+ ctx.restore()
817
+ } else {
818
+ // drawText on DIV element
819
+ var span = document.createElement('span')
820
+ var transformRule = ''
821
+ transformRule = 'rotate(' + (-rotateDeg / Math.PI * 180) + 'deg) '
822
+ if (info.mu !== 1) {
823
+ transformRule +=
824
+ 'translateX(-' + (info.fillTextWidth / 4) + 'px) ' +
825
+ 'scale(' + (1 / info.mu) + ')'
826
+ }
827
+ var styleRules = {
828
+ position: 'absolute',
829
+ display: 'block',
830
+ font: fontWeight + ' ' +
831
+ (fontSize * info.mu) + 'px ' + settings.fontFamily,
832
+ left: ((gx + info.gw / 2) * g + info.fillTextOffsetX) + 'px',
833
+ top: ((gy + info.gh / 2) * g + info.fillTextOffsetY) + 'px',
834
+ width: info.fillTextWidth + 'px',
835
+ height: info.fillTextHeight + 'px',
836
+ lineHeight: fontSize + 'px',
837
+ whiteSpace: 'nowrap',
838
+ transform: transformRule,
839
+ webkitTransform: transformRule,
840
+ msTransform: transformRule,
841
+ transformOrigin: '50% 40%',
842
+ webkitTransformOrigin: '50% 40%',
843
+ msTransformOrigin: '50% 40%'
844
+ }
845
+ if (color) {
846
+ styleRules.color = color
847
+ }
848
+ span.textContent = word
849
+ for (var cssProp in styleRules) {
850
+ span.style[cssProp] = styleRules[cssProp]
851
+ }
852
+ if (attributes) {
853
+ for (var attribute in attributes) {
854
+ span.setAttribute(attribute, attributes[attribute])
855
+ }
856
+ }
857
+ if (classes) {
858
+ span.className += classes
859
+ }
860
+ el.appendChild(span)
861
+ }
862
+ })
863
+ }
864
+
865
+ /* Help function to updateGrid */
866
+ var fillGridAt = function fillGridAt (x, y, drawMask, dimension, item) {
867
+ if (x >= ngx || y >= ngy || x < 0 || y < 0) {
868
+ return
869
+ }
870
+
871
+ grid[x][y] = false
872
+
873
+ if (drawMask) {
874
+ var ctx = elements[0].getContext('2d')
875
+ ctx.fillRect(x * g, y * g, maskRectWidth, maskRectWidth)
876
+ }
877
+
878
+ if (interactive) {
879
+ infoGrid[x][y] = { item: item, dimension: dimension }
880
+ }
881
+ }
882
+
883
+ /* Update the filling information of the given space with occupied points.
884
+ Draw the mask on the canvas if necessary. */
885
+ var updateGrid = function updateGrid (gx, gy, gw, gh, info, item) {
886
+ var occupied = info.occupied
887
+ var drawMask = settings.drawMask
888
+ var ctx
889
+ if (drawMask) {
890
+ ctx = elements[0].getContext('2d')
891
+ ctx.save()
892
+ ctx.fillStyle = settings.maskColor
893
+ }
894
+
895
+ var dimension
896
+ if (interactive) {
897
+ var bounds = info.bounds
898
+ dimension = {
899
+ x: (gx + bounds[3]) * g,
900
+ y: (gy + bounds[0]) * g,
901
+ w: (bounds[1] - bounds[3] + 1) * g,
902
+ h: (bounds[2] - bounds[0] + 1) * g
903
+ }
904
+ }
905
+
906
+ var i = occupied.length
907
+ while (i--) {
908
+ var px = gx + occupied[i][0]
909
+ var py = gy + occupied[i][1]
910
+
911
+ if (px >= ngx || py >= ngy || px < 0 || py < 0) {
912
+ continue
913
+ }
914
+
915
+ fillGridAt(px, py, drawMask, dimension, item)
916
+ }
917
+
918
+ if (drawMask) {
919
+ ctx.restore()
920
+ }
921
+ }
922
+
923
+ /* putWord() processes each item on the list,
924
+ calculate it's size and determine it's position, and actually
925
+ put it on the canvas. */
926
+ var putWord = function putWord (item) {
927
+ var word, weight, attributes
928
+ if (Array.isArray(item)) {
929
+ word = item[0]
930
+ weight = item[1]
931
+ } else {
932
+ word = item.word
933
+ weight = item.weight
934
+ attributes = item.attributes
935
+ }
936
+ var rotateDeg = getRotateDeg()
937
+
938
+ var extraDataArray = getItemExtraData(item)
939
+
940
+ // get info needed to put the text onto the canvas
941
+ var info = getTextInfo(word, weight, rotateDeg, extraDataArray)
942
+
943
+ // not getting the info means we shouldn't be drawing this one.
944
+ if (!info) {
945
+ return false
946
+ }
947
+
948
+ if (exceedTime()) {
949
+ return false
950
+ }
951
+
952
+ // If drawOutOfBound is set to false,
953
+ // skip the loop if we have already know the bounding box of
954
+ // word is larger than the canvas.
955
+ if (!settings.drawOutOfBound && !settings.shrinkToFit) {
956
+ var bounds = info.bounds;
957
+ if ((bounds[1] - bounds[3] + 1) > ngx ||
958
+ (bounds[2] - bounds[0] + 1) > ngy) {
959
+ return false
960
+ }
961
+ }
962
+
963
+ // Determine the position to put the text by
964
+ // start looking for the nearest points
965
+ var r = maxRadius + 1
966
+
967
+ var tryToPutWordAtPoint = function (gxy) {
968
+ var gx = Math.floor(gxy[0] - info.gw / 2)
969
+ var gy = Math.floor(gxy[1] - info.gh / 2)
970
+ var gw = info.gw
971
+ var gh = info.gh
972
+
973
+ // If we cannot fit the text at this position, return false
974
+ // and go to the next position.
975
+ if (!canFitText(gx, gy, gw, gh, info.occupied)) {
976
+ return false
977
+ }
978
+
979
+ // Actually put the text on the canvas
980
+ drawText(gx, gy, info, word, weight,
981
+ (maxRadius - r), gxy[2], rotateDeg, attributes, extraDataArray)
982
+
983
+ // Mark the spaces on the grid as filled
984
+ updateGrid(gx, gy, gw, gh, info, item)
985
+
986
+ // Return true so some() will stop and also return true.
987
+ return true
988
+ }
989
+
990
+ while (r--) {
991
+ var points = getPointsAtRadius(maxRadius - r)
992
+
993
+ if (settings.shuffle) {
994
+ points = [].concat(points)
995
+ shuffleArray(points)
996
+ }
997
+
998
+ // Try to fit the words by looking at each point.
999
+ // array.some() will stop and return true
1000
+ // when putWordAtPoint() returns true.
1001
+ // If all the points returns false, array.some() returns false.
1002
+ var drawn = points.some(tryToPutWordAtPoint)
1003
+
1004
+ if (drawn) {
1005
+ // leave putWord() and return true
1006
+ return true
1007
+ }
1008
+ }
1009
+ if (settings.shrinkToFit) {
1010
+ if (Array.isArray(item)) {
1011
+ item[1] = item[1] * 3 / 4
1012
+ } else {
1013
+ item.weight = item.weight * 3 / 4
1014
+ }
1015
+ return putWord(item)
1016
+ }
1017
+ // we tried all distances but text won't fit, return false
1018
+ return false
1019
+ }
1020
+
1021
+ /* Send DOM event to all elements. Will stop sending event and return
1022
+ if the previous one is canceled (for cancelable events). */
1023
+ var sendEvent = function sendEvent (type, cancelable, details) {
1024
+ if (cancelable) {
1025
+ return !elements.some(function (el) {
1026
+ var event = new CustomEvent(type, {
1027
+ detail: details || {}
1028
+ })
1029
+ return !el.dispatchEvent(event)
1030
+ }, this)
1031
+ } else {
1032
+ elements.forEach(function (el) {
1033
+ var event = new CustomEvent(type, {
1034
+ detail: details || {}
1035
+ })
1036
+ el.dispatchEvent(event)
1037
+ }, this)
1038
+ }
1039
+ }
1040
+
1041
+ /* Start drawing on a canvas */
1042
+ var start = function start () {
1043
+ // For dimensions, clearCanvas etc.,
1044
+ // we only care about the first element.
1045
+ var canvas = elements[0]
1046
+
1047
+ if (canvas.getContext) {
1048
+ ngx = Math.ceil(canvas.width / g)
1049
+ ngy = Math.ceil(canvas.height / g)
1050
+ } else {
1051
+ var rect = canvas.getBoundingClientRect()
1052
+ ngx = Math.ceil(rect.width / g)
1053
+ ngy = Math.ceil(rect.height / g)
1054
+ }
1055
+
1056
+ // Sending a wordcloudstart event which cause the previous loop to stop.
1057
+ // Do nothing if the event is canceled.
1058
+ if (!sendEvent('wordcloudstart', true)) {
1059
+ return
1060
+ }
1061
+
1062
+ // Determine the center of the word cloud
1063
+ center = (settings.origin)
1064
+ ? [settings.origin[0] / g, settings.origin[1] / g]
1065
+ : [ngx / 2, ngy / 2]
1066
+
1067
+ // Maxium radius to look for space
1068
+ maxRadius = Math.floor(Math.sqrt(ngx * ngx + ngy * ngy))
1069
+
1070
+ /* Clear the canvas only if the clearCanvas is set,
1071
+ if not, update the grid to the current canvas state */
1072
+ grid = []
1073
+
1074
+ var gx, gy, i
1075
+ if (!canvas.getContext || settings.clearCanvas) {
1076
+ elements.forEach(function (el) {
1077
+ if (el.getContext) {
1078
+ var ctx = el.getContext('2d')
1079
+ ctx.fillStyle = settings.backgroundColor
1080
+ ctx.clearRect(0, 0, ngx * (g + 1), ngy * (g + 1))
1081
+ ctx.fillRect(0, 0, ngx * (g + 1), ngy * (g + 1))
1082
+ } else {
1083
+ el.textContent = ''
1084
+ el.style.backgroundColor = settings.backgroundColor
1085
+ el.style.position = 'relative'
1086
+ }
1087
+ })
1088
+
1089
+ /* fill the grid with empty state */
1090
+ gx = ngx
1091
+ while (gx--) {
1092
+ grid[gx] = []
1093
+ gy = ngy
1094
+ while (gy--) {
1095
+ grid[gx][gy] = true
1096
+ }
1097
+ }
1098
+ } else {
1099
+ /* Determine bgPixel by creating
1100
+ another canvas and fill the specified background color. */
1101
+ var bctx = document.createElement('canvas').getContext('2d')
1102
+
1103
+ bctx.fillStyle = settings.backgroundColor
1104
+ bctx.fillRect(0, 0, 1, 1)
1105
+ var bgPixel = bctx.getImageData(0, 0, 1, 1).data
1106
+
1107
+ /* Read back the pixels of the canvas we got to tell which part of the
1108
+ canvas is empty.
1109
+ (no clearCanvas only works with a canvas, not divs) */
1110
+ var imageData =
1111
+ canvas.getContext('2d').getImageData(0, 0, ngx * g, ngy * g).data
1112
+
1113
+ gx = ngx
1114
+ var x, y
1115
+ while (gx--) {
1116
+ grid[gx] = []
1117
+ gy = ngy
1118
+ while (gy--) {
1119
+ y = g
1120
+ /* eslint no-labels: ["error", { "allowLoop": true }] */
1121
+ singleGridLoop: while (y--) {
1122
+ x = g
1123
+ while (x--) {
1124
+ i = 4
1125
+ while (i--) {
1126
+ if (imageData[((gy * g + y) * ngx * g +
1127
+ (gx * g + x)) * 4 + i] !== bgPixel[i]) {
1128
+ grid[gx][gy] = false
1129
+ break singleGridLoop
1130
+ }
1131
+ }
1132
+ }
1133
+ }
1134
+ if (grid[gx][gy] !== false) {
1135
+ grid[gx][gy] = true
1136
+ }
1137
+ }
1138
+ }
1139
+
1140
+ imageData = bctx = bgPixel = undefined
1141
+ }
1142
+
1143
+ // fill the infoGrid with empty state if we need it
1144
+ if (settings.hover || settings.click) {
1145
+ interactive = true
1146
+
1147
+ /* fill the grid with empty state */
1148
+ gx = ngx + 1
1149
+ while (gx--) {
1150
+ infoGrid[gx] = []
1151
+ }
1152
+
1153
+ if (settings.hover) {
1154
+ canvas.addEventListener('mousemove', wordcloudhover)
1155
+ }
1156
+
1157
+ if (settings.click) {
1158
+ canvas.addEventListener('click', wordcloudclick)
1159
+ canvas.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)'
1160
+ }
1161
+
1162
+ canvas.addEventListener('wordcloudstart', function stopInteraction () {
1163
+ canvas.removeEventListener('wordcloudstart', stopInteraction)
1164
+ canvas.removeEventListener('mousemove', wordcloudhover)
1165
+ canvas.removeEventListener('click', wordcloudclick)
1166
+ hovered = undefined
1167
+ })
1168
+ }
1169
+
1170
+ i = 0
1171
+ var loopingFunction, stoppingFunction
1172
+ if (settings.wait !== 0) {
1173
+ loopingFunction = window.setTimeout
1174
+ stoppingFunction = window.clearTimeout
1175
+ } else {
1176
+ loopingFunction = window.setImmediate
1177
+ stoppingFunction = window.clearImmediate
1178
+ }
1179
+
1180
+ var addEventListener = function addEventListener (type, listener) {
1181
+ elements.forEach(function (el) {
1182
+ el.addEventListener(type, listener)
1183
+ }, this)
1184
+ }
1185
+
1186
+ var removeEventListener = function removeEventListener (type, listener) {
1187
+ elements.forEach(function (el) {
1188
+ el.removeEventListener(type, listener)
1189
+ }, this)
1190
+ }
1191
+
1192
+ var anotherWordCloudStart = function anotherWordCloudStart () {
1193
+ removeEventListener('wordcloudstart', anotherWordCloudStart)
1194
+ stoppingFunction(timer[timerId])
1195
+ }
1196
+
1197
+ addEventListener('wordcloudstart', anotherWordCloudStart)
1198
+ timer[timerId] = loopingFunction(function loop () {
1199
+ if (i >= settings.list.length) {
1200
+ stoppingFunction(timer[timerId])
1201
+ sendEvent('wordcloudstop', false)
1202
+ removeEventListener('wordcloudstart', anotherWordCloudStart)
1203
+ delete timer[timerId];
1204
+ return
1205
+ }
1206
+ escapeTime = (new Date()).getTime()
1207
+ var drawn = putWord(settings.list[i])
1208
+ var canceled = !sendEvent('wordclouddrawn', true, {
1209
+ item: settings.list[i],
1210
+ drawn: drawn
1211
+ })
1212
+ if (exceedTime() || canceled) {
1213
+ stoppingFunction(timer[timerId])
1214
+ settings.abort()
1215
+ sendEvent('wordcloudabort', false)
1216
+ sendEvent('wordcloudstop', false)
1217
+ removeEventListener('wordcloudstart', anotherWordCloudStart)
1218
+ delete timer[timerId]
1219
+ return
1220
+ }
1221
+ i++
1222
+ timer[timerId] = loopingFunction(loop, settings.wait)
1223
+ }, settings.wait)
1224
+ }
1225
+
1226
+ // All set, start the drawing
1227
+ start()
1228
+ }
1229
+
1230
+ WordCloud.isSupported = isSupported
1231
+ WordCloud.minFontSize = minFontSize
1232
+ WordCloud.stop = function stop () {
1233
+ if (timer) {
1234
+ for (var timerId in timer) {
1235
+ window.clearImmediate(timer[timerId])
1236
+ }
1237
+ }
1238
+ }
1239
+
1240
+ // Expose the library as an AMD module
1241
+ if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef
1242
+ global.WordCloud = WordCloud
1243
+ define('wordcloud', [], function () { return WordCloud }) // eslint-disable-line no-undef
1244
+ } else if (typeof module !== 'undefined' && module.exports) { // eslint-disable-line no-undef
1245
+ module.exports = WordCloud // eslint-disable-line no-undef
1246
+ } else {
1247
+ global.WordCloud = WordCloud
1248
+ }
1249
+ })(this) // jshint ignore:line