├── .eslintrc.js
├── .eslintrc.yaml
├── .gitignore
├── .npmignore
├── .prettierignore
├── .prettierrc
├── README.md
├── dist
├── echarts-wordcloud.js
├── echarts-wordcloud.js.map
├── echarts-wordcloud.min.js
├── echarts-wordcloud.min.js.LICENSE.txt
└── echarts-wordcloud.min.js.map
├── example
├── logo.png
├── optionKeywords.html
├── test.html
├── typeTest.ts
├── word-cloud.png
└── wordCloud.html
├── index.d.ts
├── index.js
├── package-lock.json
├── package.json
├── src
├── WordCloudSeries.js
├── WordCloudView.js
├── layout.js
└── wordCloud.js
└── webpack.config.js
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | node: true
6 | },
7 | extends: ['prettier'],
8 | plugins: ['prettier'],
9 | // add your custom rules here
10 | rules: {
11 | 'space-before-function-paren': 0
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/.eslintrc.yaml:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | root: true,
3 | env: {
4 | browser: true,
5 | node: true
6 | },
7 | extends: ['prettier'],
8 | plugins: ['prettier'],
9 | // add your custom rules here
10 | rules: {
11 | 'space-before-function-paren': 0
12 | }
13 | };
14 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | lib
3 |
4 | npm-debug.log
5 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /build
2 | /example
3 | npm-debug.log
--------------------------------------------------------------------------------
/.prettierignore:
--------------------------------------------------------------------------------
1 | node_modules
2 | dist
3 | example
4 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | {
2 | "tabWidth": 2,
3 | "semi": true,
4 | "singleQuote": true,
5 | "trailingComma": "none",
6 | "printWidth": 80
7 | }
8 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # echarts-wordcloud
2 |
3 | Third-party Wordcloud extension based on [wordcloud2.js](https://github.com/timdream/wordcloud2.js) for [Apache ECharts](https://github.com/apache/echarts).
4 |
5 | 
6 |
7 | ## Examples
8 |
9 | [Google Trends](https://ecomfe.github.io/echarts-wordcloud/example/wordCloud.html)
10 |
11 | [ECharts Option Keywords](https://ecomfe.github.io/echarts-wordcloud/example/optionKeywords.html)
12 |
13 | ## Install
14 |
15 | ```html
16 |
17 |
18 | ```
19 |
20 | Or
21 |
22 | ```shell
23 | npm install echarts
24 | npm install echarts-wordcloud
25 | ```
26 |
27 | ```js
28 | import * as echarts from 'echarts';
29 | import 'echarts-wordcloud';
30 | ```
31 |
32 | ⚠️ NOTE:
33 |
34 | echarts-wordcloud@2 is for echarts@5
35 |
36 | echarts-wordcloud@1 is for echarts@4
37 |
38 | ## Usage
39 |
40 | ```js
41 | var chart = echarts.init(document.getElementById('main'));
42 |
43 | chart.setOption({
44 | ...
45 | series: [{
46 | type: 'wordCloud',
47 |
48 | // The shape of the "cloud" to draw. Can be any polar equation represented as a
49 | // callback function, or a keyword present. Available presents are circle (default),
50 | // cardioid (apple or heart shape curve, the most known polar equation), diamond (
51 | // alias of square), triangle-forward, triangle, (alias of triangle-upright, pentagon, and star.
52 |
53 | shape: 'circle',
54 |
55 | // Keep aspect ratio of maskImage or 1:1 for shapes
56 | // This option is supported since echarts-wordcloud@2.1.0
57 | keepAspect: false,
58 |
59 | // A silhouette image which the white area will be excluded from drawing texts.
60 | // The shape option will continue to apply as the shape of the cloud to grow.
61 |
62 | maskImage: maskImage,
63 |
64 | // Folllowing left/top/width/height/right/bottom are used for positioning the word cloud
65 | // Default to be put in the center and has 75% x 80% size.
66 |
67 | left: 'center',
68 | top: 'center',
69 | width: '70%',
70 | height: '80%',
71 | right: null,
72 | bottom: null,
73 |
74 | // Text size range which the value in data will be mapped to.
75 | // Default to have minimum 12px and maximum 60px size.
76 |
77 | sizeRange: [12, 60],
78 |
79 | // Text rotation range and step in degree. Text will be rotated randomly in range [-90, 90] by rotationStep 45
80 |
81 | rotationRange: [-90, 90],
82 | rotationStep: 45,
83 |
84 | // size of the grid in pixels for marking the availability of the canvas
85 | // the larger the grid size, the bigger the gap between words.
86 |
87 | gridSize: 8,
88 |
89 | // set to true to allow word to be drawn partly outside of the canvas.
90 | // Allow word bigger than the size of the canvas to be drawn
91 | // This option is supported since echarts-wordcloud@2.1.0
92 | drawOutOfBound: false,
93 |
94 | // if the font size is too large for the text to be displayed,
95 | // whether to shrink the text. If it is set to false, the text will
96 | // not be rendered. If it is set to true, the text will be shrinked.
97 | // This option is supported since echarts-wordcloud@2.1.0
98 | shrinkToFit: false,
99 |
100 | // If perform layout animation.
101 | // NOTE disable it will lead to UI blocking when there is lots of words.
102 | layoutAnimation: true,
103 |
104 | // Global text style
105 | textStyle: {
106 | fontFamily: 'sans-serif',
107 | fontWeight: 'bold',
108 | // Color can be a callback function or a color string
109 | color: function () {
110 | // Random color
111 | return 'rgb(' + [
112 | Math.round(Math.random() * 160),
113 | Math.round(Math.random() * 160),
114 | Math.round(Math.random() * 160)
115 | ].join(',') + ')';
116 | }
117 | },
118 | emphasis: {
119 | focus: 'self',
120 |
121 | textStyle: {
122 | textShadowBlur: 10,
123 | textShadowColor: '#333'
124 | }
125 | },
126 |
127 | // Data is an array. Each array item must have name and value property.
128 | data: [{
129 | name: 'Farrah Abraham',
130 | value: 366,
131 | // Style of single text
132 | textStyle: {
133 | }
134 | }]
135 | }]
136 | });
137 | ```
138 |
139 | ## Changelog
140 |
141 | ### 2.1.0
142 |
143 | - [chore] Update Apache ECharts version
144 | - [chore] Sync with the latest wordcloud2.js and use prettier to format the code
145 | - [feature] Add `keepAspect` option to keep aspect ratio of maskImage or 1:1 for shapes
146 | - [feature] Add `drawOutOfBound` option to allow words to be drawn partly outside of the canvas
147 | - [feature] Add `shrinkToFit` option to shrink the text if the font size is too large for the text to be displayed
148 |
149 | ## Notice
150 |
151 | The Apache Software Foundation [Apache ECharts, ECharts](https://echarts.apache.org/), Apache, the Apache feather, and the Apache ECharts project logo are either registered trademarks or trademarks of the [Apache Software Foundation](https://www.apache.org/).
152 |
--------------------------------------------------------------------------------
/dist/echarts-wordcloud.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory(require("echarts"));
4 | else if(typeof define === 'function' && define.amd)
5 | define(["echarts"], factory);
6 | else if(typeof exports === 'object')
7 | exports["echarts-wordcloud"] = factory(require("echarts"));
8 | else
9 | root["echarts-wordcloud"] = factory(root["echarts"]);
10 | })(self, function(__WEBPACK_EXTERNAL_MODULE_echarts_lib_echarts__) {
11 | return /******/ (() => { // webpackBootstrap
12 | /******/ "use strict";
13 | /******/ var __webpack_modules__ = ({
14 |
15 | /***/ "./index.js":
16 | /*!******************************!*\
17 | !*** ./index.js + 4 modules ***!
18 | \******************************/
19 | /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
20 |
21 | // ESM COMPAT FLAG
22 | __webpack_require__.r(__webpack_exports__);
23 |
24 | // EXTERNAL MODULE: external "echarts"
25 | var external_echarts_ = __webpack_require__("echarts/lib/echarts");
26 | ;// CONCATENATED MODULE: ./src/WordCloudSeries.js
27 |
28 |
29 | external_echarts_.extendSeriesModel({
30 | type: 'series.wordCloud',
31 |
32 | visualStyleAccessPath: 'textStyle',
33 | visualStyleMapper: function (model) {
34 | return {
35 | fill: model.get('color')
36 | };
37 | },
38 | visualDrawType: 'fill',
39 |
40 | optionUpdated: function () {
41 | var option = this.option;
42 | option.gridSize = Math.max(Math.floor(option.gridSize), 4);
43 | },
44 |
45 | getInitialData: function (option, ecModel) {
46 | var dimensions = external_echarts_.helper.createDimensions(option.data, {
47 | coordDimensions: ['value']
48 | });
49 | var list = new external_echarts_.List(dimensions, this);
50 | list.initData(option.data);
51 | return list;
52 | },
53 |
54 | // Most of options are from https://github.com/timdream/wordcloud2.js/blob/gh-pages/API.md
55 | defaultOption: {
56 | maskImage: null,
57 |
58 | // Shape can be 'circle', 'cardioid', 'diamond', 'triangle-forward', 'triangle', 'pentagon', 'star'
59 | shape: 'circle',
60 | keepAspect: false,
61 |
62 | left: 'center',
63 |
64 | top: 'center',
65 |
66 | width: '70%',
67 |
68 | height: '80%',
69 |
70 | sizeRange: [12, 60],
71 |
72 | rotationRange: [-90, 90],
73 |
74 | rotationStep: 45,
75 |
76 | gridSize: 8,
77 |
78 | drawOutOfBound: false,
79 | shrinkToFit: false,
80 |
81 | textStyle: {
82 | fontWeight: 'normal'
83 | }
84 | }
85 | });
86 |
87 | ;// CONCATENATED MODULE: ./src/WordCloudView.js
88 |
89 |
90 | external_echarts_.extendChartView({
91 | type: 'wordCloud',
92 |
93 | render: function (seriesModel, ecModel, api) {
94 | var group = this.group;
95 | group.removeAll();
96 |
97 | var data = seriesModel.getData();
98 |
99 | var gridSize = seriesModel.get('gridSize');
100 |
101 | seriesModel.layoutInstance.ondraw = function (text, size, dataIdx, drawn) {
102 | var itemModel = data.getItemModel(dataIdx);
103 | var textStyleModel = itemModel.getModel('textStyle');
104 |
105 | var textEl = new external_echarts_.graphic.Text({
106 | style: external_echarts_.helper.createTextStyle(textStyleModel),
107 | scaleX: 1 / drawn.info.mu,
108 | scaleY: 1 / drawn.info.mu,
109 | x: (drawn.gx + drawn.info.gw / 2) * gridSize,
110 | y: (drawn.gy + drawn.info.gh / 2) * gridSize,
111 | rotation: drawn.rot
112 | });
113 | textEl.setStyle({
114 | x: drawn.info.fillTextOffsetX,
115 | y: drawn.info.fillTextOffsetY + size * 0.5,
116 | text: text,
117 | verticalAlign: 'middle',
118 | fill: data.getItemVisual(dataIdx, 'style').fill,
119 | fontSize: size
120 | });
121 |
122 | group.add(textEl);
123 |
124 | data.setItemGraphicEl(dataIdx, textEl);
125 |
126 | textEl.ensureState('emphasis').style = external_echarts_.helper.createTextStyle(
127 | itemModel.getModel(['emphasis', 'textStyle']),
128 | {
129 | state: 'emphasis'
130 | }
131 | );
132 | textEl.ensureState('blur').style = external_echarts_.helper.createTextStyle(
133 | itemModel.getModel(['blur', 'textStyle']),
134 | {
135 | state: 'blur'
136 | }
137 | );
138 |
139 | external_echarts_.helper.enableHoverEmphasis(
140 | textEl,
141 | itemModel.get(['emphasis', 'focus']),
142 | itemModel.get(['emphasis', 'blurScope'])
143 | );
144 |
145 | textEl.stateTransition = {
146 | duration: seriesModel.get('animation')
147 | ? seriesModel.get(['stateAnimation', 'duration'])
148 | : 0,
149 | easing: seriesModel.get(['stateAnimation', 'easing'])
150 | };
151 | // TODO
152 | textEl.__highDownDispatcher = true;
153 | };
154 |
155 | this._model = seriesModel;
156 | },
157 |
158 | remove: function () {
159 | this.group.removeAll();
160 |
161 | this._model.layoutInstance.dispose();
162 | },
163 |
164 | dispose: function () {
165 | this._model.layoutInstance.dispose();
166 | }
167 | });
168 |
169 | ;// CONCATENATED MODULE: ./src/layout.js
170 | /*!
171 | * wordcloud2.js
172 | * http://timdream.org/wordcloud2.js/
173 | *
174 | * Copyright 2011 - 2019 Tim Guan-tin Chien and contributors.
175 | * Released under the MIT license
176 | */
177 |
178 |
179 |
180 | // setImmediate
181 | if (!window.setImmediate) {
182 | window.setImmediate = (function setupSetImmediate() {
183 | return (
184 | window.msSetImmediate ||
185 | window.webkitSetImmediate ||
186 | window.mozSetImmediate ||
187 | window.oSetImmediate ||
188 | (function setupSetZeroTimeout() {
189 | if (!window.postMessage || !window.addEventListener) {
190 | return null;
191 | }
192 |
193 | var callbacks = [undefined];
194 | var message = 'zero-timeout-message';
195 |
196 | // Like setTimeout, but only takes a function argument. There's
197 | // no time argument (always zero) and no arguments (you have to
198 | // use a closure).
199 | var setZeroTimeout = function setZeroTimeout(callback) {
200 | var id = callbacks.length;
201 | callbacks.push(callback);
202 | window.postMessage(message + id.toString(36), '*');
203 |
204 | return id;
205 | };
206 |
207 | window.addEventListener(
208 | 'message',
209 | function setZeroTimeoutMessage(evt) {
210 | // Skipping checking event source, retarded IE confused this window
211 | // object with another in the presence of iframe
212 | if (
213 | typeof evt.data !== 'string' ||
214 | evt.data.substr(0, message.length) !== message /* ||
215 | evt.source !== window */
216 | ) {
217 | return;
218 | }
219 |
220 | evt.stopImmediatePropagation();
221 |
222 | var id = parseInt(evt.data.substr(message.length), 36);
223 | if (!callbacks[id]) {
224 | return;
225 | }
226 |
227 | callbacks[id]();
228 | callbacks[id] = undefined;
229 | },
230 | true
231 | );
232 |
233 | /* specify clearImmediate() here since we need the scope */
234 | window.clearImmediate = function clearZeroTimeout(id) {
235 | if (!callbacks[id]) {
236 | return;
237 | }
238 |
239 | callbacks[id] = undefined;
240 | };
241 |
242 | return setZeroTimeout;
243 | })() ||
244 | // fallback
245 | function setImmediateFallback(fn) {
246 | window.setTimeout(fn, 0);
247 | }
248 | );
249 | })();
250 | }
251 |
252 | if (!window.clearImmediate) {
253 | window.clearImmediate = (function setupClearImmediate() {
254 | return (
255 | window.msClearImmediate ||
256 | window.webkitClearImmediate ||
257 | window.mozClearImmediate ||
258 | window.oClearImmediate ||
259 | // "clearZeroTimeout" is implement on the previous block ||
260 | // fallback
261 | function clearImmediateFallback(timer) {
262 | window.clearTimeout(timer);
263 | }
264 | );
265 | })();
266 | }
267 |
268 | // Check if WordCloud can run on this browser
269 | var isSupported = (function isSupported() {
270 | var canvas = document.createElement('canvas');
271 | if (!canvas || !canvas.getContext) {
272 | return false;
273 | }
274 |
275 | var ctx = canvas.getContext('2d');
276 | if (!ctx) {
277 | return false;
278 | }
279 | if (!ctx.getImageData) {
280 | return false;
281 | }
282 | if (!ctx.fillText) {
283 | return false;
284 | }
285 |
286 | if (!Array.prototype.some) {
287 | return false;
288 | }
289 | if (!Array.prototype.push) {
290 | return false;
291 | }
292 |
293 | return true;
294 | })();
295 |
296 | // Find out if the browser impose minium font size by
297 | // drawing small texts on a canvas and measure it's width.
298 | var minFontSize = (function getMinFontSize() {
299 | if (!isSupported) {
300 | return;
301 | }
302 |
303 | var ctx = document.createElement('canvas').getContext('2d');
304 |
305 | // start from 20
306 | var size = 20;
307 |
308 | // two sizes to measure
309 | var hanWidth, mWidth;
310 |
311 | while (size) {
312 | ctx.font = size.toString(10) + 'px sans-serif';
313 | if (
314 | ctx.measureText('\uFF37').width === hanWidth &&
315 | ctx.measureText('m').width === mWidth
316 | ) {
317 | return size + 1;
318 | }
319 |
320 | hanWidth = ctx.measureText('\uFF37').width;
321 | mWidth = ctx.measureText('m').width;
322 |
323 | size--;
324 | }
325 |
326 | return 0;
327 | })();
328 |
329 | var getItemExtraData = function (item) {
330 | if (Array.isArray(item)) {
331 | var itemCopy = item.slice();
332 | // remove data we already have (word and weight)
333 | itemCopy.splice(0, 2);
334 | return itemCopy;
335 | } else {
336 | return [];
337 | }
338 | };
339 |
340 | // Based on http://jsfromhell.com/array/shuffle
341 | var shuffleArray = function shuffleArray(arr) {
342 | for (var j, x, i = arr.length; i; ) {
343 | j = Math.floor(Math.random() * i);
344 | x = arr[--i];
345 | arr[i] = arr[j];
346 | arr[j] = x;
347 | }
348 | return arr;
349 | };
350 |
351 | var timer = {};
352 | var WordCloud = function WordCloud(elements, options) {
353 | if (!isSupported) {
354 | return;
355 | }
356 |
357 | var timerId = Math.floor(Math.random() * Date.now());
358 |
359 | if (!Array.isArray(elements)) {
360 | elements = [elements];
361 | }
362 |
363 | elements.forEach(function (el, i) {
364 | if (typeof el === 'string') {
365 | elements[i] = document.getElementById(el);
366 | if (!elements[i]) {
367 | throw new Error('The element id specified is not found.');
368 | }
369 | } else if (!el.tagName && !el.appendChild) {
370 | throw new Error(
371 | 'You must pass valid HTML elements, or ID of the element.'
372 | );
373 | }
374 | });
375 |
376 | /* Default values to be overwritten by options object */
377 | var settings = {
378 | list: [],
379 | fontFamily:
380 | '"Trebuchet MS", "Heiti TC", "微軟正黑體", ' +
381 | '"Arial Unicode MS", "Droid Fallback Sans", sans-serif',
382 | fontWeight: 'normal',
383 | color: 'random-dark',
384 | minSize: 0, // 0 to disable
385 | weightFactor: 1,
386 | clearCanvas: true,
387 | backgroundColor: '#fff', // opaque white = rgba(255, 255, 255, 1)
388 |
389 | gridSize: 8,
390 | drawOutOfBound: false,
391 | shrinkToFit: false,
392 | origin: null,
393 |
394 | drawMask: false,
395 | maskColor: 'rgba(255,0,0,0.3)',
396 | maskGapWidth: 0.3,
397 |
398 | layoutAnimation: true,
399 |
400 | wait: 0,
401 | abortThreshold: 0, // disabled
402 | abort: function noop() {},
403 |
404 | minRotation: -Math.PI / 2,
405 | maxRotation: Math.PI / 2,
406 | rotationStep: 0.1,
407 |
408 | shuffle: true,
409 | rotateRatio: 0.1,
410 |
411 | shape: 'circle',
412 | ellipticity: 0.65,
413 |
414 | classes: null,
415 |
416 | hover: null,
417 | click: null
418 | };
419 |
420 | if (options) {
421 | for (var key in options) {
422 | if (key in settings) {
423 | settings[key] = options[key];
424 | }
425 | }
426 | }
427 |
428 | /* Convert weightFactor into a function */
429 | if (typeof settings.weightFactor !== 'function') {
430 | var factor = settings.weightFactor;
431 | settings.weightFactor = function weightFactor(pt) {
432 | return pt * factor; // in px
433 | };
434 | }
435 |
436 | /* Convert shape into a function */
437 | if (typeof settings.shape !== 'function') {
438 | switch (settings.shape) {
439 | case 'circle':
440 | /* falls through */
441 | default:
442 | // 'circle' is the default and a shortcut in the code loop.
443 | settings.shape = 'circle';
444 | break;
445 |
446 | case 'cardioid':
447 | settings.shape = function shapeCardioid(theta) {
448 | return 1 - Math.sin(theta);
449 | };
450 | break;
451 |
452 | /*
453 | To work out an X-gon, one has to calculate "m",
454 | where 1/(cos(2*PI/X)+m*sin(2*PI/X)) = 1/(cos(0)+m*sin(0))
455 | http://www.wolframalpha.com/input/?i=1%2F%28cos%282*PI%2FX%29%2Bm*sin%28
456 | 2*PI%2FX%29%29+%3D+1%2F%28cos%280%29%2Bm*sin%280%29%29
457 | Copy the solution into polar equation r = 1/(cos(t') + m*sin(t'))
458 | where t' equals to mod(t, 2PI/X);
459 | */
460 |
461 | case 'diamond':
462 | // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
463 | // %28t%2C+PI%2F2%29%29%2Bsin%28mod+%28t%2C+PI%2F2%29%29%29%2C+t+%3D
464 | // +0+..+2*PI
465 | settings.shape = function shapeSquare(theta) {
466 | var thetaPrime = theta % ((2 * Math.PI) / 4);
467 | return 1 / (Math.cos(thetaPrime) + Math.sin(thetaPrime));
468 | };
469 | break;
470 |
471 | case 'square':
472 | // http://www.wolframalpha.com/input/?i=plot+r+%3D+min(1%2Fabs(cos(t
473 | // )),1%2Fabs(sin(t)))),+t+%3D+0+..+2*PI
474 | settings.shape = function shapeSquare(theta) {
475 | return Math.min(
476 | 1 / Math.abs(Math.cos(theta)),
477 | 1 / Math.abs(Math.sin(theta))
478 | );
479 | };
480 | break;
481 |
482 | case 'triangle-forward':
483 | // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
484 | // %28t%2C+2*PI%2F3%29%29%2Bsqrt%283%29sin%28mod+%28t%2C+2*PI%2F3%29
485 | // %29%29%2C+t+%3D+0+..+2*PI
486 | settings.shape = function shapeTriangle(theta) {
487 | var thetaPrime = theta % ((2 * Math.PI) / 3);
488 | return (
489 | 1 / (Math.cos(thetaPrime) + Math.sqrt(3) * Math.sin(thetaPrime))
490 | );
491 | };
492 | break;
493 |
494 | case 'triangle':
495 | case 'triangle-upright':
496 | settings.shape = function shapeTriangle(theta) {
497 | var thetaPrime = (theta + (Math.PI * 3) / 2) % ((2 * Math.PI) / 3);
498 | return (
499 | 1 / (Math.cos(thetaPrime) + Math.sqrt(3) * Math.sin(thetaPrime))
500 | );
501 | };
502 | break;
503 |
504 | case 'pentagon':
505 | settings.shape = function shapePentagon(theta) {
506 | var thetaPrime = (theta + 0.955) % ((2 * Math.PI) / 5);
507 | return 1 / (Math.cos(thetaPrime) + 0.726543 * Math.sin(thetaPrime));
508 | };
509 | break;
510 |
511 | case 'star':
512 | settings.shape = function shapeStar(theta) {
513 | var thetaPrime = (theta + 0.955) % ((2 * Math.PI) / 10);
514 | if (
515 | ((theta + 0.955) % ((2 * Math.PI) / 5)) - (2 * Math.PI) / 10 >=
516 | 0
517 | ) {
518 | return (
519 | 1 /
520 | (Math.cos((2 * Math.PI) / 10 - thetaPrime) +
521 | 3.07768 * Math.sin((2 * Math.PI) / 10 - thetaPrime))
522 | );
523 | } else {
524 | return 1 / (Math.cos(thetaPrime) + 3.07768 * Math.sin(thetaPrime));
525 | }
526 | };
527 | break;
528 | }
529 | }
530 |
531 | /* Make sure gridSize is a whole number and is not smaller than 4px */
532 | settings.gridSize = Math.max(Math.floor(settings.gridSize), 4);
533 |
534 | /* shorthand */
535 | var g = settings.gridSize;
536 | var maskRectWidth = g - settings.maskGapWidth;
537 |
538 | /* normalize rotation settings */
539 | var rotationRange = Math.abs(settings.maxRotation - settings.minRotation);
540 | var minRotation = Math.min(settings.maxRotation, settings.minRotation);
541 | var rotationStep = settings.rotationStep;
542 |
543 | /* information/object available to all functions, set when start() */
544 | var grid, // 2d array containing filling information
545 | ngx,
546 | ngy, // width and height of the grid
547 | center, // position of the center of the cloud
548 | maxRadius;
549 |
550 | /* timestamp for measuring each putWord() action */
551 | var escapeTime;
552 |
553 | /* function for getting the color of the text */
554 | var getTextColor;
555 | function randomHslColor(min, max) {
556 | return (
557 | 'hsl(' +
558 | (Math.random() * 360).toFixed() +
559 | ',' +
560 | (Math.random() * 30 + 70).toFixed() +
561 | '%,' +
562 | (Math.random() * (max - min) + min).toFixed() +
563 | '%)'
564 | );
565 | }
566 | switch (settings.color) {
567 | case 'random-dark':
568 | getTextColor = function getRandomDarkColor() {
569 | return randomHslColor(10, 50);
570 | };
571 | break;
572 |
573 | case 'random-light':
574 | getTextColor = function getRandomLightColor() {
575 | return randomHslColor(50, 90);
576 | };
577 | break;
578 |
579 | default:
580 | if (typeof settings.color === 'function') {
581 | getTextColor = settings.color;
582 | }
583 | break;
584 | }
585 |
586 | /* function for getting the font-weight of the text */
587 | var getTextFontWeight;
588 | if (typeof settings.fontWeight === 'function') {
589 | getTextFontWeight = settings.fontWeight;
590 | }
591 |
592 | /* function for getting the classes of the text */
593 | var getTextClasses = null;
594 | if (typeof settings.classes === 'function') {
595 | getTextClasses = settings.classes;
596 | }
597 |
598 | /* Interactive */
599 | var interactive = false;
600 | var infoGrid = [];
601 | var hovered;
602 |
603 | var getInfoGridFromMouseTouchEvent = function getInfoGridFromMouseTouchEvent(
604 | evt
605 | ) {
606 | var canvas = evt.currentTarget;
607 | var rect = canvas.getBoundingClientRect();
608 | var clientX;
609 | var clientY;
610 | /** Detect if touches are available */
611 | if (evt.touches) {
612 | clientX = evt.touches[0].clientX;
613 | clientY = evt.touches[0].clientY;
614 | } else {
615 | clientX = evt.clientX;
616 | clientY = evt.clientY;
617 | }
618 | var eventX = clientX - rect.left;
619 | var eventY = clientY - rect.top;
620 |
621 | var x = Math.floor((eventX * (canvas.width / rect.width || 1)) / g);
622 | var y = Math.floor((eventY * (canvas.height / rect.height || 1)) / g);
623 |
624 | if (!infoGrid[x]) {
625 | return null
626 | }
627 |
628 | return infoGrid[x][y];
629 | };
630 |
631 | var wordcloudhover = function wordcloudhover(evt) {
632 | var info = getInfoGridFromMouseTouchEvent(evt);
633 |
634 | if (hovered === info) {
635 | return;
636 | }
637 |
638 | hovered = info;
639 | if (!info) {
640 | settings.hover(undefined, undefined, evt);
641 |
642 | return;
643 | }
644 |
645 | settings.hover(info.item, info.dimension, evt);
646 | };
647 |
648 | var wordcloudclick = function wordcloudclick(evt) {
649 | var info = getInfoGridFromMouseTouchEvent(evt);
650 | if (!info) {
651 | return;
652 | }
653 |
654 | settings.click(info.item, info.dimension, evt);
655 | evt.preventDefault();
656 | };
657 |
658 | /* Get points on the grid for a given radius away from the center */
659 | var pointsAtRadius = [];
660 | var getPointsAtRadius = function getPointsAtRadius(radius) {
661 | if (pointsAtRadius[radius]) {
662 | return pointsAtRadius[radius];
663 | }
664 |
665 | // Look for these number of points on each radius
666 | var T = radius * 8;
667 |
668 | // Getting all the points at this radius
669 | var t = T;
670 | var points = [];
671 |
672 | if (radius === 0) {
673 | points.push([center[0], center[1], 0]);
674 | }
675 |
676 | while (t--) {
677 | // distort the radius to put the cloud in shape
678 | var rx = 1;
679 | if (settings.shape !== 'circle') {
680 | rx = settings.shape((t / T) * 2 * Math.PI); // 0 to 1
681 | }
682 |
683 | // Push [x, y, t]; t is used solely for getTextColor()
684 | points.push([
685 | center[0] + radius * rx * Math.cos((-t / T) * 2 * Math.PI),
686 | center[1] +
687 | radius * rx * Math.sin((-t / T) * 2 * Math.PI) * settings.ellipticity,
688 | (t / T) * 2 * Math.PI
689 | ]);
690 | }
691 |
692 | pointsAtRadius[radius] = points;
693 | return points;
694 | };
695 |
696 | /* Return true if we had spent too much time */
697 | var exceedTime = function exceedTime() {
698 | return (
699 | settings.abortThreshold > 0 &&
700 | new Date().getTime() - escapeTime > settings.abortThreshold
701 | );
702 | };
703 |
704 | /* Get the deg of rotation according to settings, and luck. */
705 | var getRotateDeg = function getRotateDeg() {
706 | if (settings.rotateRatio === 0) {
707 | return 0;
708 | }
709 |
710 | if (Math.random() > settings.rotateRatio) {
711 | return 0;
712 | }
713 |
714 | if (rotationRange === 0) {
715 | return minRotation;
716 | }
717 |
718 | return minRotation + Math.round(Math.random() * rotationRange / rotationStep) * rotationStep;
719 | };
720 |
721 | var getTextInfo = function getTextInfo(
722 | word,
723 | weight,
724 | rotateDeg,
725 | extraDataArray
726 | ) {
727 | // calculate the acutal font size
728 | // fontSize === 0 means weightFactor function wants the text skipped,
729 | // and size < minSize means we cannot draw the text.
730 | var debug = false;
731 | var fontSize = settings.weightFactor(weight);
732 | if (fontSize <= settings.minSize) {
733 | return false;
734 | }
735 |
736 | // Scale factor here is to make sure fillText is not limited by
737 | // the minium font size set by browser.
738 | // It will always be 1 or 2n.
739 | var mu = 1;
740 | if (fontSize < minFontSize) {
741 | mu = (function calculateScaleFactor() {
742 | var mu = 2;
743 | while (mu * fontSize < minFontSize) {
744 | mu += 2;
745 | }
746 | return mu;
747 | })();
748 | }
749 |
750 | // Get fontWeight that will be used to set fctx.font
751 | var fontWeight;
752 | if (getTextFontWeight) {
753 | fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray);
754 | } else {
755 | fontWeight = settings.fontWeight;
756 | }
757 |
758 | var fcanvas = document.createElement('canvas');
759 | var fctx = fcanvas.getContext('2d', { willReadFrequently: true });
760 |
761 | fctx.font =
762 | fontWeight +
763 | ' ' +
764 | (fontSize * mu).toString(10) +
765 | 'px ' +
766 | settings.fontFamily;
767 |
768 | // Estimate the dimension of the text with measureText().
769 | var fw = fctx.measureText(word).width / mu;
770 | var fh =
771 | Math.max(
772 | fontSize * mu,
773 | fctx.measureText('m').width,
774 | fctx.measureText('\uFF37').width
775 | ) / mu;
776 |
777 | // Create a boundary box that is larger than our estimates,
778 | // so text don't get cut of (it sill might)
779 | var boxWidth = fw + fh * 2;
780 | var boxHeight = fh * 3;
781 | var fgw = Math.ceil(boxWidth / g);
782 | var fgh = Math.ceil(boxHeight / g);
783 | boxWidth = fgw * g;
784 | boxHeight = fgh * g;
785 |
786 | // Calculate the proper offsets to make the text centered at
787 | // the preferred position.
788 |
789 | // This is simply half of the width.
790 | var fillTextOffsetX = -fw / 2;
791 | // Instead of moving the box to the exact middle of the preferred
792 | // position, for Y-offset we move 0.4 instead, so Latin alphabets look
793 | // vertical centered.
794 | var fillTextOffsetY = -fh * 0.4;
795 |
796 | // Calculate the actual dimension of the canvas, considering the rotation.
797 | var cgh = Math.ceil(
798 | (boxWidth * Math.abs(Math.sin(rotateDeg)) +
799 | boxHeight * Math.abs(Math.cos(rotateDeg))) /
800 | g
801 | );
802 | var cgw = Math.ceil(
803 | (boxWidth * Math.abs(Math.cos(rotateDeg)) +
804 | boxHeight * Math.abs(Math.sin(rotateDeg))) /
805 | g
806 | );
807 | var width = cgw * g;
808 | var height = cgh * g;
809 |
810 | fcanvas.setAttribute('width', width);
811 | fcanvas.setAttribute('height', height);
812 |
813 | if (debug) {
814 | // Attach fcanvas to the DOM
815 | document.body.appendChild(fcanvas);
816 | // Save it's state so that we could restore and draw the grid correctly.
817 | fctx.save();
818 | }
819 |
820 | // Scale the canvas with |mu|.
821 | fctx.scale(1 / mu, 1 / mu);
822 | fctx.translate((width * mu) / 2, (height * mu) / 2);
823 | fctx.rotate(-rotateDeg);
824 |
825 | // Once the width/height is set, ctx info will be reset.
826 | // Set it again here.
827 | fctx.font =
828 | fontWeight +
829 | ' ' +
830 | (fontSize * mu).toString(10) +
831 | 'px ' +
832 | settings.fontFamily;
833 |
834 | // Fill the text into the fcanvas.
835 | // XXX: We cannot because textBaseline = 'top' here because
836 | // Firefox and Chrome uses different default line-height for canvas.
837 | // Please read https://bugzil.la/737852#c6.
838 | // Here, we use textBaseline = 'middle' and draw the text at exactly
839 | // 0.5 * fontSize lower.
840 | fctx.fillStyle = '#000';
841 | fctx.textBaseline = 'middle';
842 | fctx.fillText(
843 | word,
844 | fillTextOffsetX * mu,
845 | (fillTextOffsetY + fontSize * 0.5) * mu
846 | );
847 |
848 | // Get the pixels of the text
849 | var imageData = fctx.getImageData(0, 0, width, height).data;
850 |
851 | if (exceedTime()) {
852 | return false;
853 | }
854 |
855 | if (debug) {
856 | // Draw the box of the original estimation
857 | fctx.strokeRect(fillTextOffsetX * mu, fillTextOffsetY, fw * mu, fh * mu);
858 | fctx.restore();
859 | }
860 |
861 | // Read the pixels and save the information to the occupied array
862 | var occupied = [];
863 | var gx = cgw;
864 | var gy, x, y;
865 | var bounds = [cgh / 2, cgw / 2, cgh / 2, cgw / 2];
866 | while (gx--) {
867 | gy = cgh;
868 | while (gy--) {
869 | y = g;
870 | /* eslint no-labels: ['error', { 'allowLoop': true }] */
871 | singleGridLoop: while (y--) {
872 | x = g;
873 | while (x--) {
874 | if (imageData[((gy * g + y) * width + (gx * g + x)) * 4 + 3]) {
875 | occupied.push([gx, gy]);
876 |
877 | if (gx < bounds[3]) {
878 | bounds[3] = gx;
879 | }
880 | if (gx > bounds[1]) {
881 | bounds[1] = gx;
882 | }
883 | if (gy < bounds[0]) {
884 | bounds[0] = gy;
885 | }
886 | if (gy > bounds[2]) {
887 | bounds[2] = gy;
888 | }
889 |
890 | if (debug) {
891 | fctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
892 | fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5);
893 | }
894 | break singleGridLoop;
895 | }
896 | }
897 | }
898 | if (debug) {
899 | fctx.fillStyle = 'rgba(0, 0, 255, 0.5)';
900 | fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5);
901 | }
902 | }
903 | }
904 |
905 | if (debug) {
906 | fctx.fillStyle = 'rgba(0, 255, 0, 0.5)';
907 | fctx.fillRect(
908 | bounds[3] * g,
909 | bounds[0] * g,
910 | (bounds[1] - bounds[3] + 1) * g,
911 | (bounds[2] - bounds[0] + 1) * g
912 | );
913 | }
914 |
915 | // Return information needed to create the text on the real canvas
916 | return {
917 | mu: mu,
918 | occupied: occupied,
919 | bounds: bounds,
920 | gw: cgw,
921 | gh: cgh,
922 | fillTextOffsetX: fillTextOffsetX,
923 | fillTextOffsetY: fillTextOffsetY,
924 | fillTextWidth: fw,
925 | fillTextHeight: fh,
926 | fontSize: fontSize
927 | };
928 | };
929 |
930 | /* Determine if there is room available in the given dimension */
931 | var canFitText = function canFitText(gx, gy, gw, gh, occupied) {
932 | // Go through the occupied points,
933 | // return false if the space is not available.
934 | var i = occupied.length;
935 | while (i--) {
936 | var px = gx + occupied[i][0];
937 | var py = gy + occupied[i][1];
938 |
939 | if (px >= ngx || py >= ngy || px < 0 || py < 0) {
940 | if (!settings.drawOutOfBound) {
941 | return false;
942 | }
943 | continue;
944 | }
945 |
946 | if (!grid[px][py]) {
947 | return false;
948 | }
949 | }
950 | return true;
951 | };
952 |
953 | /* Actually draw the text on the grid */
954 | var drawText = function drawText(
955 | gx,
956 | gy,
957 | info,
958 | word,
959 | weight,
960 | distance,
961 | theta,
962 | rotateDeg,
963 | attributes,
964 | extraDataArray
965 | ) {
966 | var fontSize = info.fontSize;
967 | var color;
968 | if (getTextColor) {
969 | color = getTextColor(
970 | word,
971 | weight,
972 | fontSize,
973 | distance,
974 | theta,
975 | extraDataArray
976 | );
977 | } else {
978 | color = settings.color;
979 | }
980 |
981 | // get fontWeight that will be used to set ctx.font and font style rule
982 | var fontWeight;
983 | if (getTextFontWeight) {
984 | fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray);
985 | } else {
986 | fontWeight = settings.fontWeight;
987 | }
988 |
989 | var classes;
990 | if (getTextClasses) {
991 | classes = getTextClasses(word, weight, fontSize, extraDataArray);
992 | } else {
993 | classes = settings.classes;
994 | }
995 |
996 | elements.forEach(function (el) {
997 | if (el.getContext) {
998 | var ctx = el.getContext('2d');
999 | var mu = info.mu;
1000 |
1001 | // Save the current state before messing it
1002 | ctx.save();
1003 | ctx.scale(1 / mu, 1 / mu);
1004 |
1005 | ctx.font =
1006 | fontWeight +
1007 | ' ' +
1008 | (fontSize * mu).toString(10) +
1009 | 'px ' +
1010 | settings.fontFamily;
1011 | ctx.fillStyle = color;
1012 |
1013 | // Translate the canvas position to the origin coordinate of where
1014 | // the text should be put.
1015 | ctx.translate((gx + info.gw / 2) * g * mu, (gy + info.gh / 2) * g * mu);
1016 |
1017 | if (rotateDeg !== 0) {
1018 | ctx.rotate(-rotateDeg);
1019 | }
1020 |
1021 | // Finally, fill the text.
1022 |
1023 | // XXX: We cannot because textBaseline = 'top' here because
1024 | // Firefox and Chrome uses different default line-height for canvas.
1025 | // Please read https://bugzil.la/737852#c6.
1026 | // Here, we use textBaseline = 'middle' and draw the text at exactly
1027 | // 0.5 * fontSize lower.
1028 | ctx.textBaseline = 'middle';
1029 | ctx.fillText(
1030 | word,
1031 | info.fillTextOffsetX * mu,
1032 | (info.fillTextOffsetY + fontSize * 0.5) * mu
1033 | );
1034 |
1035 | // The below box is always matches how s are positioned
1036 | /* ctx.strokeRect(info.fillTextOffsetX, info.fillTextOffsetY,
1037 | info.fillTextWidth, info.fillTextHeight); */
1038 |
1039 | // Restore the state.
1040 | ctx.restore();
1041 | } else {
1042 | // drawText on DIV element
1043 | var span = document.createElement('span');
1044 | var transformRule = '';
1045 | transformRule = 'rotate(' + (-rotateDeg / Math.PI) * 180 + 'deg) ';
1046 | if (info.mu !== 1) {
1047 | transformRule +=
1048 | 'translateX(-' +
1049 | info.fillTextWidth / 4 +
1050 | 'px) ' +
1051 | 'scale(' +
1052 | 1 / info.mu +
1053 | ')';
1054 | }
1055 | var styleRules = {
1056 | position: 'absolute',
1057 | display: 'block',
1058 | font:
1059 | fontWeight + ' ' + fontSize * info.mu + 'px ' + settings.fontFamily,
1060 | left: (gx + info.gw / 2) * g + info.fillTextOffsetX + 'px',
1061 | top: (gy + info.gh / 2) * g + info.fillTextOffsetY + 'px',
1062 | width: info.fillTextWidth + 'px',
1063 | height: info.fillTextHeight + 'px',
1064 | lineHeight: fontSize + 'px',
1065 | whiteSpace: 'nowrap',
1066 | transform: transformRule,
1067 | webkitTransform: transformRule,
1068 | msTransform: transformRule,
1069 | transformOrigin: '50% 40%',
1070 | webkitTransformOrigin: '50% 40%',
1071 | msTransformOrigin: '50% 40%'
1072 | };
1073 | if (color) {
1074 | styleRules.color = color;
1075 | }
1076 | span.textContent = word;
1077 | for (var cssProp in styleRules) {
1078 | span.style[cssProp] = styleRules[cssProp];
1079 | }
1080 | if (attributes) {
1081 | for (var attribute in attributes) {
1082 | span.setAttribute(attribute, attributes[attribute]);
1083 | }
1084 | }
1085 | if (classes) {
1086 | span.className += classes;
1087 | }
1088 | el.appendChild(span);
1089 | }
1090 | });
1091 | };
1092 |
1093 | /* Help function to updateGrid */
1094 | var fillGridAt = function fillGridAt(x, y, drawMask, dimension, item) {
1095 | if (x >= ngx || y >= ngy || x < 0 || y < 0) {
1096 | return;
1097 | }
1098 |
1099 | grid[x][y] = false;
1100 |
1101 | if (drawMask) {
1102 | var ctx = elements[0].getContext('2d');
1103 | ctx.fillRect(x * g, y * g, maskRectWidth, maskRectWidth);
1104 | }
1105 |
1106 | if (interactive) {
1107 | infoGrid[x][y] = { item: item, dimension: dimension };
1108 | }
1109 | };
1110 |
1111 | /* Update the filling information of the given space with occupied points.
1112 | Draw the mask on the canvas if necessary. */
1113 | var updateGrid = function updateGrid(gx, gy, gw, gh, info, item) {
1114 | var occupied = info.occupied;
1115 | var drawMask = settings.drawMask;
1116 | var ctx;
1117 | if (drawMask) {
1118 | ctx = elements[0].getContext('2d');
1119 | ctx.save();
1120 | ctx.fillStyle = settings.maskColor;
1121 | }
1122 |
1123 | var dimension;
1124 | if (interactive) {
1125 | var bounds = info.bounds;
1126 | dimension = {
1127 | x: (gx + bounds[3]) * g,
1128 | y: (gy + bounds[0]) * g,
1129 | w: (bounds[1] - bounds[3] + 1) * g,
1130 | h: (bounds[2] - bounds[0] + 1) * g
1131 | };
1132 | }
1133 |
1134 | var i = occupied.length;
1135 | while (i--) {
1136 | var px = gx + occupied[i][0];
1137 | var py = gy + occupied[i][1];
1138 |
1139 | if (px >= ngx || py >= ngy || px < 0 || py < 0) {
1140 | continue;
1141 | }
1142 |
1143 | fillGridAt(px, py, drawMask, dimension, item);
1144 | }
1145 |
1146 | if (drawMask) {
1147 | ctx.restore();
1148 | }
1149 | };
1150 |
1151 | /* putWord() processes each item on the list,
1152 | calculate it's size and determine it's position, and actually
1153 | put it on the canvas. */
1154 | var putWord = function putWord(item, loopIndex) {
1155 | if (loopIndex > 20) {
1156 | return null;
1157 | }
1158 |
1159 | var word, weight, attributes;
1160 | if (Array.isArray(item)) {
1161 | word = item[0];
1162 | weight = item[1];
1163 | } else {
1164 | word = item.word;
1165 | weight = item.weight;
1166 | attributes = item.attributes;
1167 | }
1168 | var rotateDeg = getRotateDeg();
1169 |
1170 | var extraDataArray = getItemExtraData(item);
1171 |
1172 | // get info needed to put the text onto the canvas
1173 | var info = getTextInfo(word, weight, rotateDeg, extraDataArray);
1174 |
1175 | // not getting the info means we shouldn't be drawing this one.
1176 | if (!info) {
1177 | return false;
1178 | }
1179 |
1180 | if (exceedTime()) {
1181 | return false;
1182 | }
1183 |
1184 | // If drawOutOfBound is set to false,
1185 | // skip the loop if we have already know the bounding box of
1186 | // word is larger than the canvas.
1187 | if (!settings.drawOutOfBound && !settings.shrinkToFit) {
1188 | var bounds = info.bounds;
1189 | if (bounds[1] - bounds[3] + 1 > ngx || bounds[2] - bounds[0] + 1 > ngy) {
1190 | return false;
1191 | }
1192 | }
1193 |
1194 | // Determine the position to put the text by
1195 | // start looking for the nearest points
1196 | var r = maxRadius + 1;
1197 |
1198 | var tryToPutWordAtPoint = function (gxy) {
1199 | var gx = Math.floor(gxy[0] - info.gw / 2);
1200 | var gy = Math.floor(gxy[1] - info.gh / 2);
1201 | var gw = info.gw;
1202 | var gh = info.gh;
1203 |
1204 | // If we cannot fit the text at this position, return false
1205 | // and go to the next position.
1206 | if (!canFitText(gx, gy, gw, gh, info.occupied)) {
1207 | return false;
1208 | }
1209 |
1210 | // Actually put the text on the canvas
1211 | drawText(
1212 | gx,
1213 | gy,
1214 | info,
1215 | word,
1216 | weight,
1217 | maxRadius - r,
1218 | gxy[2],
1219 | rotateDeg,
1220 | attributes,
1221 | extraDataArray
1222 | );
1223 |
1224 | // Mark the spaces on the grid as filled
1225 | updateGrid(gx, gy, gw, gh, info, item);
1226 |
1227 | return {
1228 | gx: gx,
1229 | gy: gy,
1230 | rot: rotateDeg,
1231 | info: info
1232 | };
1233 | };
1234 |
1235 | while (r--) {
1236 | var points = getPointsAtRadius(maxRadius - r);
1237 |
1238 | if (settings.shuffle) {
1239 | points = [].concat(points);
1240 | shuffleArray(points);
1241 | }
1242 |
1243 | // Try to fit the words by looking at each point.
1244 | // array.some() will stop and return true
1245 | // when putWordAtPoint() returns true.
1246 | for (var i = 0; i < points.length; i++) {
1247 | var res = tryToPutWordAtPoint(points[i]);
1248 | if (res) {
1249 | return res;
1250 | }
1251 | }
1252 |
1253 | // var drawn = points.some(tryToPutWordAtPoint);
1254 | // if (drawn) {
1255 | // // leave putWord() and return true
1256 | // return true;
1257 | // }
1258 | }
1259 |
1260 | if (settings.shrinkToFit) {
1261 | if (Array.isArray(item)) {
1262 | item[1] = (item[1] * 3) / 4;
1263 | } else {
1264 | item.weight = (item.weight * 3) / 4;
1265 | }
1266 | return putWord(item, loopIndex + 1);
1267 | }
1268 |
1269 | // we tried all distances but text won't fit, return null
1270 | return null;
1271 | };
1272 |
1273 | /* Send DOM event to all elements. Will stop sending event and return
1274 | if the previous one is canceled (for cancelable events). */
1275 | var sendEvent = function sendEvent(type, cancelable, details) {
1276 | if (cancelable) {
1277 | return !elements.some(function (el) {
1278 | var event = new CustomEvent(type, {
1279 | detail: details || {}
1280 | });
1281 | return !el.dispatchEvent(event);
1282 | }, this);
1283 | } else {
1284 | elements.forEach(function (el) {
1285 | var event = new CustomEvent(type, {
1286 | detail: details || {}
1287 | });
1288 | el.dispatchEvent(event);
1289 | }, this);
1290 | }
1291 | };
1292 |
1293 | /* Start drawing on a canvas */
1294 | var start = function start() {
1295 | // For dimensions, clearCanvas etc.,
1296 | // we only care about the first element.
1297 | var canvas = elements[0];
1298 |
1299 | if (canvas.getContext) {
1300 | ngx = Math.ceil(canvas.width / g);
1301 | ngy = Math.ceil(canvas.height / g);
1302 | } else {
1303 | var rect = canvas.getBoundingClientRect();
1304 | ngx = Math.ceil(rect.width / g);
1305 | ngy = Math.ceil(rect.height / g);
1306 | }
1307 |
1308 | // Sending a wordcloudstart event which cause the previous loop to stop.
1309 | // Do nothing if the event is canceled.
1310 | if (!sendEvent('wordcloudstart', true)) {
1311 | return;
1312 | }
1313 |
1314 | // Determine the center of the word cloud
1315 | center = settings.origin
1316 | ? [settings.origin[0] / g, settings.origin[1] / g]
1317 | : [ngx / 2, ngy / 2];
1318 |
1319 | // Maxium radius to look for space
1320 | maxRadius = Math.floor(Math.sqrt(ngx * ngx + ngy * ngy));
1321 |
1322 | /* Clear the canvas only if the clearCanvas is set,
1323 | if not, update the grid to the current canvas state */
1324 | grid = [];
1325 |
1326 | var gx, gy, i;
1327 | if (!canvas.getContext || settings.clearCanvas) {
1328 | elements.forEach(function (el) {
1329 | if (el.getContext) {
1330 | var ctx = el.getContext('2d');
1331 | ctx.fillStyle = settings.backgroundColor;
1332 | ctx.clearRect(0, 0, ngx * (g + 1), ngy * (g + 1));
1333 | ctx.fillRect(0, 0, ngx * (g + 1), ngy * (g + 1));
1334 | } else {
1335 | el.textContent = '';
1336 | el.style.backgroundColor = settings.backgroundColor;
1337 | el.style.position = 'relative';
1338 | }
1339 | });
1340 |
1341 | /* fill the grid with empty state */
1342 | gx = ngx;
1343 | while (gx--) {
1344 | grid[gx] = [];
1345 | gy = ngy;
1346 | while (gy--) {
1347 | grid[gx][gy] = true;
1348 | }
1349 | }
1350 | } else {
1351 | /* Determine bgPixel by creating
1352 | another canvas and fill the specified background color. */
1353 | var bctx = document.createElement('canvas').getContext('2d');
1354 |
1355 | bctx.fillStyle = settings.backgroundColor;
1356 | bctx.fillRect(0, 0, 1, 1);
1357 | var bgPixel = bctx.getImageData(0, 0, 1, 1).data;
1358 |
1359 | /* Read back the pixels of the canvas we got to tell which part of the
1360 | canvas is empty.
1361 | (no clearCanvas only works with a canvas, not divs) */
1362 | var imageData = canvas
1363 | .getContext('2d')
1364 | .getImageData(0, 0, ngx * g, ngy * g).data;
1365 |
1366 | gx = ngx;
1367 | var x, y;
1368 | while (gx--) {
1369 | grid[gx] = [];
1370 | gy = ngy;
1371 | while (gy--) {
1372 | y = g;
1373 | /* eslint no-labels: ['error', { 'allowLoop': true }] */
1374 | singleGridLoop: while (y--) {
1375 | x = g;
1376 | while (x--) {
1377 | i = 4;
1378 | while (i--) {
1379 | if (
1380 | imageData[((gy * g + y) * ngx * g + (gx * g + x)) * 4 + i] !==
1381 | bgPixel[i]
1382 | ) {
1383 | grid[gx][gy] = false;
1384 | break singleGridLoop;
1385 | }
1386 | }
1387 | }
1388 | }
1389 | if (grid[gx][gy] !== false) {
1390 | grid[gx][gy] = true;
1391 | }
1392 | }
1393 | }
1394 |
1395 | imageData = bctx = bgPixel = undefined;
1396 | }
1397 |
1398 | // fill the infoGrid with empty state if we need it
1399 | if (settings.hover || settings.click) {
1400 | interactive = true;
1401 |
1402 | /* fill the grid with empty state */
1403 | gx = ngx + 1;
1404 | while (gx--) {
1405 | infoGrid[gx] = [];
1406 | }
1407 |
1408 | if (settings.hover) {
1409 | canvas.addEventListener('mousemove', wordcloudhover);
1410 | }
1411 |
1412 | if (settings.click) {
1413 | canvas.addEventListener('click', wordcloudclick);
1414 | canvas.addEventListener('touchstart', wordcloudclick);
1415 | canvas.addEventListener('touchend', function (e) {
1416 | e.preventDefault();
1417 | });
1418 | canvas.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)';
1419 | }
1420 |
1421 | canvas.addEventListener('wordcloudstart', function stopInteraction() {
1422 | canvas.removeEventListener('wordcloudstart', stopInteraction);
1423 |
1424 | canvas.removeEventListener('mousemove', wordcloudhover);
1425 | canvas.removeEventListener('click', wordcloudclick);
1426 | hovered = undefined;
1427 | });
1428 | }
1429 |
1430 | i = 0;
1431 | var loopingFunction, stoppingFunction;
1432 | var layouting = true;
1433 | if (!settings.layoutAnimation) {
1434 | loopingFunction = function (cb) {
1435 | cb();
1436 | };
1437 | stoppingFunction = function () {
1438 | layouting = false;
1439 | };
1440 | } else if (settings.wait !== 0) {
1441 | loopingFunction = window.setTimeout;
1442 | stoppingFunction = window.clearTimeout;
1443 | } else {
1444 | loopingFunction = window.setImmediate;
1445 | stoppingFunction = window.clearImmediate;
1446 | }
1447 |
1448 | var addEventListener = function addEventListener(type, listener) {
1449 | elements.forEach(function (el) {
1450 | el.addEventListener(type, listener);
1451 | }, this);
1452 | };
1453 |
1454 | var removeEventListener = function removeEventListener(type, listener) {
1455 | elements.forEach(function (el) {
1456 | el.removeEventListener(type, listener);
1457 | }, this);
1458 | };
1459 |
1460 | var anotherWordCloudStart = function anotherWordCloudStart() {
1461 | removeEventListener('wordcloudstart', anotherWordCloudStart);
1462 | stoppingFunction(timer[timerId]);
1463 | };
1464 |
1465 | addEventListener('wordcloudstart', anotherWordCloudStart);
1466 |
1467 | // At least wait the following code before call the first iteration.
1468 | timer[timerId] = (settings.layoutAnimation ? loopingFunction : setTimeout)(
1469 | function loop() {
1470 | if (!layouting) {
1471 | return;
1472 | }
1473 | if (i >= settings.list.length) {
1474 | stoppingFunction(timer[timerId]);
1475 | sendEvent('wordcloudstop', false);
1476 | removeEventListener('wordcloudstart', anotherWordCloudStart);
1477 | delete timer[timerId];
1478 | return;
1479 | }
1480 | escapeTime = new Date().getTime();
1481 | var drawn = putWord(settings.list[i], 0);
1482 | var canceled = !sendEvent('wordclouddrawn', true, {
1483 | item: settings.list[i],
1484 | drawn: drawn
1485 | });
1486 | if (exceedTime() || canceled) {
1487 | stoppingFunction(timer[timerId]);
1488 | settings.abort();
1489 | sendEvent('wordcloudabort', false);
1490 | sendEvent('wordcloudstop', false);
1491 | removeEventListener('wordcloudstart', anotherWordCloudStart);
1492 | return;
1493 | }
1494 | i++;
1495 | timer[timerId] = loopingFunction(loop, settings.wait);
1496 | },
1497 | settings.wait
1498 | );
1499 | };
1500 |
1501 | // All set, start the drawing
1502 | start();
1503 | };
1504 |
1505 | WordCloud.isSupported = isSupported;
1506 | WordCloud.minFontSize = minFontSize;
1507 |
1508 | /* harmony default export */ const layout = (WordCloud);
1509 |
1510 | ;// CONCATENATED MODULE: ./src/wordCloud.js
1511 |
1512 |
1513 |
1514 |
1515 |
1516 |
1517 |
1518 | if (!layout.isSupported) {
1519 | throw new Error('Sorry your browser not support wordCloud');
1520 | }
1521 |
1522 | // https://github.com/timdream/wordcloud2.js/blob/c236bee60436e048949f9becc4f0f67bd832dc5c/index.js#L233
1523 | function updateCanvasMask(maskCanvas) {
1524 | var ctx = maskCanvas.getContext('2d');
1525 | var imageData = ctx.getImageData(0, 0, maskCanvas.width, maskCanvas.height);
1526 | var newImageData = ctx.createImageData(imageData);
1527 |
1528 | var toneSum = 0;
1529 | var toneCnt = 0;
1530 | for (var i = 0; i < imageData.data.length; i += 4) {
1531 | var alpha = imageData.data[i + 3];
1532 | if (alpha > 128) {
1533 | var tone =
1534 | imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2];
1535 | toneSum += tone;
1536 | ++toneCnt;
1537 | }
1538 | }
1539 | var threshold = toneSum / toneCnt;
1540 |
1541 | for (var i = 0; i < imageData.data.length; i += 4) {
1542 | var tone =
1543 | imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2];
1544 | var alpha = imageData.data[i + 3];
1545 |
1546 | if (alpha < 128 || tone > threshold) {
1547 | // Area not to draw
1548 | newImageData.data[i] = 0;
1549 | newImageData.data[i + 1] = 0;
1550 | newImageData.data[i + 2] = 0;
1551 | newImageData.data[i + 3] = 0;
1552 | } else {
1553 | // Area to draw
1554 | // The color must be same with backgroundColor
1555 | newImageData.data[i] = 255;
1556 | newImageData.data[i + 1] = 255;
1557 | newImageData.data[i + 2] = 255;
1558 | newImageData.data[i + 3] = 255;
1559 | }
1560 | }
1561 |
1562 | ctx.putImageData(newImageData, 0, 0);
1563 | }
1564 |
1565 | external_echarts_.registerLayout(function (ecModel, api) {
1566 | ecModel.eachSeriesByType('wordCloud', function (seriesModel) {
1567 | var gridRect = external_echarts_.helper.getLayoutRect(
1568 | seriesModel.getBoxLayoutParams(),
1569 | {
1570 | width: api.getWidth(),
1571 | height: api.getHeight()
1572 | }
1573 | );
1574 |
1575 | var keepAspect = seriesModel.get('keepAspect');
1576 | var maskImage = seriesModel.get('maskImage');
1577 | var ratio = maskImage ? maskImage.width / maskImage.height : 1;
1578 | keepAspect && adjustRectAspect(gridRect, ratio);
1579 |
1580 | var data = seriesModel.getData();
1581 |
1582 | var canvas = document.createElement('canvas');
1583 | canvas.width = gridRect.width;
1584 | canvas.height = gridRect.height;
1585 |
1586 | var ctx = canvas.getContext('2d');
1587 | if (maskImage) {
1588 | try {
1589 | ctx.drawImage(maskImage, 0, 0, canvas.width, canvas.height);
1590 | updateCanvasMask(canvas);
1591 | } catch (e) {
1592 | console.error('Invalid mask image');
1593 | console.error(e.toString());
1594 | }
1595 | }
1596 |
1597 | var sizeRange = seriesModel.get('sizeRange');
1598 | var rotationRange = seriesModel.get('rotationRange');
1599 | var valueExtent = data.getDataExtent('value');
1600 |
1601 | var DEGREE_TO_RAD = Math.PI / 180;
1602 | var gridSize = seriesModel.get('gridSize');
1603 | layout(canvas, {
1604 | list: data
1605 | .mapArray('value', function (value, idx) {
1606 | var itemModel = data.getItemModel(idx);
1607 | return [
1608 | data.getName(idx),
1609 | itemModel.get('textStyle.fontSize', true) ||
1610 | external_echarts_.number.linearMap(value, valueExtent, sizeRange),
1611 | idx
1612 | ];
1613 | })
1614 | .sort(function (a, b) {
1615 | // Sort from large to small in case there is no more room for more words
1616 | return b[1] - a[1];
1617 | }),
1618 | fontFamily:
1619 | seriesModel.get('textStyle.fontFamily') ||
1620 | seriesModel.get('emphasis.textStyle.fontFamily') ||
1621 | ecModel.get('textStyle.fontFamily'),
1622 | fontWeight:
1623 | seriesModel.get('textStyle.fontWeight') ||
1624 | seriesModel.get('emphasis.textStyle.fontWeight') ||
1625 | ecModel.get('textStyle.fontWeight'),
1626 |
1627 | gridSize: gridSize,
1628 |
1629 | ellipticity: gridRect.height / gridRect.width,
1630 |
1631 | minRotation: rotationRange[0] * DEGREE_TO_RAD,
1632 | maxRotation: rotationRange[1] * DEGREE_TO_RAD,
1633 |
1634 | clearCanvas: !maskImage,
1635 |
1636 | rotateRatio: 1,
1637 |
1638 | rotationStep: seriesModel.get('rotationStep') * DEGREE_TO_RAD,
1639 |
1640 | drawOutOfBound: seriesModel.get('drawOutOfBound'),
1641 | shrinkToFit: seriesModel.get('shrinkToFit'),
1642 |
1643 | layoutAnimation: seriesModel.get('layoutAnimation'),
1644 |
1645 | shuffle: false,
1646 |
1647 | shape: seriesModel.get('shape')
1648 | });
1649 |
1650 | function onWordCloudDrawn(e) {
1651 | var item = e.detail.item;
1652 | if (e.detail.drawn && seriesModel.layoutInstance.ondraw) {
1653 | e.detail.drawn.gx += gridRect.x / gridSize;
1654 | e.detail.drawn.gy += gridRect.y / gridSize;
1655 | seriesModel.layoutInstance.ondraw(
1656 | item[0],
1657 | item[1],
1658 | item[2],
1659 | e.detail.drawn
1660 | );
1661 | }
1662 | }
1663 |
1664 | canvas.addEventListener('wordclouddrawn', onWordCloudDrawn);
1665 |
1666 | if (seriesModel.layoutInstance) {
1667 | // Dispose previous
1668 | seriesModel.layoutInstance.dispose();
1669 | }
1670 |
1671 | seriesModel.layoutInstance = {
1672 | ondraw: null,
1673 |
1674 | dispose: function () {
1675 | canvas.removeEventListener('wordclouddrawn', onWordCloudDrawn);
1676 | // Abort
1677 | canvas.addEventListener('wordclouddrawn', function (e) {
1678 | // Prevent default to cancle the event and stop the loop
1679 | e.preventDefault();
1680 | });
1681 | }
1682 | };
1683 | });
1684 | });
1685 |
1686 | external_echarts_.registerPreprocessor(function (option) {
1687 | var series = (option || {}).series;
1688 | !external_echarts_.util.isArray(series) && (series = series ? [series] : []);
1689 |
1690 | var compats = ['shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY'];
1691 |
1692 | external_echarts_.util.each(series, function (seriesItem) {
1693 | if (seriesItem && seriesItem.type === 'wordCloud') {
1694 | var textStyle = seriesItem.textStyle || {};
1695 |
1696 | compatTextStyle(textStyle.normal);
1697 | compatTextStyle(textStyle.emphasis);
1698 | }
1699 | });
1700 |
1701 | function compatTextStyle(textStyle) {
1702 | textStyle &&
1703 | external_echarts_.util.each(compats, function (key) {
1704 | if (textStyle.hasOwnProperty(key)) {
1705 | textStyle['text' + external_echarts_.format.capitalFirst(key)] = textStyle[key];
1706 | }
1707 | });
1708 | }
1709 | });
1710 |
1711 | function adjustRectAspect(gridRect, aspect) {
1712 | // var outerWidth = gridRect.width + gridRect.x * 2;
1713 | // var outerHeight = gridRect.height + gridRect.y * 2;
1714 | var width = gridRect.width;
1715 | var height = gridRect.height;
1716 | if (width > height * aspect) {
1717 | gridRect.x += (width - height * aspect) / 2;
1718 | gridRect.width = height * aspect;
1719 | } else {
1720 | gridRect.y += (height - width / aspect) / 2;
1721 | gridRect.height = width / aspect;
1722 | }
1723 | }
1724 |
1725 | ;// CONCATENATED MODULE: ./index.js
1726 |
1727 |
1728 |
1729 | /***/ }),
1730 |
1731 | /***/ "echarts/lib/echarts":
1732 | /*!**************************!*\
1733 | !*** external "echarts" ***!
1734 | \**************************/
1735 | /***/ ((module) => {
1736 |
1737 | module.exports = __WEBPACK_EXTERNAL_MODULE_echarts_lib_echarts__;
1738 |
1739 | /***/ })
1740 |
1741 | /******/ });
1742 | /************************************************************************/
1743 | /******/ // The module cache
1744 | /******/ var __webpack_module_cache__ = {};
1745 | /******/
1746 | /******/ // The require function
1747 | /******/ function __webpack_require__(moduleId) {
1748 | /******/ // Check if module is in cache
1749 | /******/ if(__webpack_module_cache__[moduleId]) {
1750 | /******/ return __webpack_module_cache__[moduleId].exports;
1751 | /******/ }
1752 | /******/ // Create a new module (and put it into the cache)
1753 | /******/ var module = __webpack_module_cache__[moduleId] = {
1754 | /******/ // no module.id needed
1755 | /******/ // no module.loaded needed
1756 | /******/ exports: {}
1757 | /******/ };
1758 | /******/
1759 | /******/ // Execute the module function
1760 | /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
1761 | /******/
1762 | /******/ // Return the exports of the module
1763 | /******/ return module.exports;
1764 | /******/ }
1765 | /******/
1766 | /************************************************************************/
1767 | /******/ /* webpack/runtime/make namespace object */
1768 | /******/ (() => {
1769 | /******/ // define __esModule on exports
1770 | /******/ __webpack_require__.r = (exports) => {
1771 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
1772 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
1773 | /******/ }
1774 | /******/ Object.defineProperty(exports, '__esModule', { value: true });
1775 | /******/ };
1776 | /******/ })();
1777 | /******/
1778 | /************************************************************************/
1779 | /******/ // module exports must be returned from runtime so entry inlining is disabled
1780 | /******/ // startup
1781 | /******/ // Load entry module and return exports
1782 | /******/ return __webpack_require__("./index.js");
1783 | /******/ })()
1784 | ;
1785 | });
1786 | //# sourceMappingURL=echarts-wordcloud.js.map
--------------------------------------------------------------------------------
/dist/echarts-wordcloud.js.map:
--------------------------------------------------------------------------------
1 | {"version":3,"sources":["webpack://echarts-wordcloud/webpack/universalModuleDefinition","webpack://echarts-wordcloud/./src/WordCloudSeries.js","webpack://echarts-wordcloud/./src/WordCloudView.js","webpack://echarts-wordcloud/./src/layout.js","webpack://echarts-wordcloud/./src/wordCloud.js","webpack://echarts-wordcloud/./index.js","webpack://echarts-wordcloud/external \"echarts\"","webpack://echarts-wordcloud/webpack/bootstrap","webpack://echarts-wordcloud/webpack/runtime/make namespace object","webpack://echarts-wordcloud/webpack/startup"],"names":[],"mappings":"AAAA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,CAAC;AACD,O;;;;;;;;;;;;;;;;ACV+C;;AAE/C,mCAAyB;AACzB;;AAEA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;;AAEA;AACA;AACA;AACA,GAAG;;AAEH;AACA,qBAAqB,yCAA+B;AACpD;AACA,KAAK;AACL,mBAAmB,sBAAY;AAC/B;AACA;AACA,GAAG;;AAEH;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA,CAAC;;;AC1D8C;;AAE/C,iCAAuB;AACvB;;AAEA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;AACA;;AAEA,uBAAuB,8BAAoB;AAC3C,eAAe,wCAA8B;AAC7C;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;;AAEP;;AAEA;;AAEA,6CAA6C,wCAA8B;AAC3E;AACA;AACA;AACA;AACA;AACA,yCAAyC,wCAA8B;AACvE;AACA;AACA;AACA;AACA;;AAEA,MAAM,4CAAkC;AACxC;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,GAAG;;AAEH;AACA;;AAEA;AACA,GAAG;;AAEH;AACA;AACA;AACA,CAAC;;;AC/ED;AACA;AACA;AACA;AACA;AACA;AACA;;AAEa;;AAEb;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA,WAAW;AACX;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;;AAEA;AACA;AACA,gCAAgC,GAAG;AACnC;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA,GAAG;;AAEH;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA,6BAA6B;;AAE7B;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,yBAAyB;AACzB;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,WAAW;AACX;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,mDAAmD;AACnD;;AAEA,wBAAwB;AACxB;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;;AAEA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA,yCAAyC,2BAA2B;;AAEpE;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,wCAAwC,oBAAoB;AAC5D;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA,KAAK;AACL;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,qDAAqD;;AAErD;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;;AAEA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA,wBAAwB;AACxB;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,qBAAqB,mBAAmB;AACxC;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA,OAAO;AACP,KAAK;AACL;AACA;AACA;AACA,SAAS;AACT;AACA,OAAO;AACP;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA,OAAO;;AAEP;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,0CAA0C,oBAAoB;AAC9D;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA,OAAO;AACP;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA,KAAK;AACL;AACA;AACA;;AAEA;AACA;AACA;AACA,OAAO;AACP;;AAEA;AACA;AACA;AACA,OAAO;AACP;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;;AAEA,6CAAe,SAAS,EAAC;;;AC1zCsB;;AAEpB;AACF;;AAEoB;;AAE7C,KAAK,kBAAiC;AACtC;AACA;;AAEA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA,iBAAiB,2BAA2B;AAC5C;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA,iBAAiB,2BAA2B;AAC5C;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,KAAK;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;;AAEA,gCAAsB;AACtB;AACA,mBAAmB,sCAA4B;AAC/C;AACA;AACA;AACA;AACA;AACA;;AAEA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA,OAAO;AACP;AACA;AACA;AACA;;AAEA;AACA;AACA;;AAEA;AACA;AACA,IAAI,MAAqB;AACzB;AACA;AACA;AACA;AACA;AACA;AACA,cAAc,kCAAwB;AACtC;AACA;AACA,SAAS;AACT;AACA;AACA;AACA,SAAS;AACT;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;;AAEA;AACA;;AAEA;;AAEA;;AAEA;;AAEA;AACA;;AAEA;;AAEA;;AAEA;AACA,KAAK;;AAEL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;AAEA;;AAEA;AACA;AACA;AACA;;AAEA;AACA;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA,SAAS;AACT;AACA;AACA,GAAG;AACH,CAAC;;AAED,sCAA4B;AAC5B,4BAA4B;AAC5B,GAAG,8BAAoB;;AAEvB;;AAEA,EAAE,2BAAiB;AACnB;AACA;;AAEA;AACA;AACA;AACA,GAAG;;AAEH;AACA;AACA,MAAM,2BAAiB;AACvB;AACA,6BAA6B,qCAA2B;AACxD;AACA,OAAO;AACP;AACA,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,GAAG;AACH;AACA;AACA;AACA;;;ACpNyB;;;;;;;;;;;ACAzB,iE;;;;;;UCAA;UACA;;UAEA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;UACA;;UAEA;UACA;;UAEA;UACA;UACA;;;;;WCrBA;WACA;WACA;WACA,sDAAsD,kBAAkB;WACxE;WACA,+CAA+C,cAAc;WAC7D,E;;;;UCNA;UACA;UACA;UACA","file":"echarts-wordcloud.js","sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory(require(\"echarts\"));\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([\"echarts\"], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"echarts-wordcloud\"] = factory(require(\"echarts\"));\n\telse\n\t\troot[\"echarts-wordcloud\"] = factory(root[\"echarts\"]);\n})(self, function(__WEBPACK_EXTERNAL_MODULE_echarts_lib_echarts__) {\nreturn ","import * as echarts from 'echarts/lib/echarts';\n\necharts.extendSeriesModel({\n type: 'series.wordCloud',\n\n visualStyleAccessPath: 'textStyle',\n visualStyleMapper: function (model) {\n return {\n fill: model.get('color')\n };\n },\n visualDrawType: 'fill',\n\n optionUpdated: function () {\n var option = this.option;\n option.gridSize = Math.max(Math.floor(option.gridSize), 4);\n },\n\n getInitialData: function (option, ecModel) {\n var dimensions = echarts.helper.createDimensions(option.data, {\n coordDimensions: ['value']\n });\n var list = new echarts.List(dimensions, this);\n list.initData(option.data);\n return list;\n },\n\n // Most of options are from https://github.com/timdream/wordcloud2.js/blob/gh-pages/API.md\n defaultOption: {\n maskImage: null,\n\n // Shape can be 'circle', 'cardioid', 'diamond', 'triangle-forward', 'triangle', 'pentagon', 'star'\n shape: 'circle',\n keepAspect: false,\n\n left: 'center',\n\n top: 'center',\n\n width: '70%',\n\n height: '80%',\n\n sizeRange: [12, 60],\n\n rotationRange: [-90, 90],\n\n rotationStep: 45,\n\n gridSize: 8,\n\n drawOutOfBound: false,\n shrinkToFit: false,\n\n textStyle: {\n fontWeight: 'normal'\n }\n }\n});\n","import * as echarts from 'echarts/lib/echarts';\n\necharts.extendChartView({\n type: 'wordCloud',\n\n render: function (seriesModel, ecModel, api) {\n var group = this.group;\n group.removeAll();\n\n var data = seriesModel.getData();\n\n var gridSize = seriesModel.get('gridSize');\n\n seriesModel.layoutInstance.ondraw = function (text, size, dataIdx, drawn) {\n var itemModel = data.getItemModel(dataIdx);\n var textStyleModel = itemModel.getModel('textStyle');\n\n var textEl = new echarts.graphic.Text({\n style: echarts.helper.createTextStyle(textStyleModel),\n scaleX: 1 / drawn.info.mu,\n scaleY: 1 / drawn.info.mu,\n x: (drawn.gx + drawn.info.gw / 2) * gridSize,\n y: (drawn.gy + drawn.info.gh / 2) * gridSize,\n rotation: drawn.rot\n });\n textEl.setStyle({\n x: drawn.info.fillTextOffsetX,\n y: drawn.info.fillTextOffsetY + size * 0.5,\n text: text,\n verticalAlign: 'middle',\n fill: data.getItemVisual(dataIdx, 'style').fill,\n fontSize: size\n });\n\n group.add(textEl);\n\n data.setItemGraphicEl(dataIdx, textEl);\n\n textEl.ensureState('emphasis').style = echarts.helper.createTextStyle(\n itemModel.getModel(['emphasis', 'textStyle']),\n {\n state: 'emphasis'\n }\n );\n textEl.ensureState('blur').style = echarts.helper.createTextStyle(\n itemModel.getModel(['blur', 'textStyle']),\n {\n state: 'blur'\n }\n );\n\n echarts.helper.enableHoverEmphasis(\n textEl,\n itemModel.get(['emphasis', 'focus']),\n itemModel.get(['emphasis', 'blurScope'])\n );\n\n textEl.stateTransition = {\n duration: seriesModel.get('animation')\n ? seriesModel.get(['stateAnimation', 'duration'])\n : 0,\n easing: seriesModel.get(['stateAnimation', 'easing'])\n };\n // TODO\n textEl.__highDownDispatcher = true;\n };\n\n this._model = seriesModel;\n },\n\n remove: function () {\n this.group.removeAll();\n\n this._model.layoutInstance.dispose();\n },\n\n dispose: function () {\n this._model.layoutInstance.dispose();\n }\n});\n","/*!\n * wordcloud2.js\n * http://timdream.org/wordcloud2.js/\n *\n * Copyright 2011 - 2019 Tim Guan-tin Chien and contributors.\n * Released under the MIT license\n */\n\n'use strict';\n\n// setImmediate\nif (!window.setImmediate) {\n window.setImmediate = (function setupSetImmediate() {\n return (\n window.msSetImmediate ||\n window.webkitSetImmediate ||\n window.mozSetImmediate ||\n window.oSetImmediate ||\n (function setupSetZeroTimeout() {\n if (!window.postMessage || !window.addEventListener) {\n return null;\n }\n\n var callbacks = [undefined];\n var message = 'zero-timeout-message';\n\n // Like setTimeout, but only takes a function argument. There's\n // no time argument (always zero) and no arguments (you have to\n // use a closure).\n var setZeroTimeout = function setZeroTimeout(callback) {\n var id = callbacks.length;\n callbacks.push(callback);\n window.postMessage(message + id.toString(36), '*');\n\n return id;\n };\n\n window.addEventListener(\n 'message',\n function setZeroTimeoutMessage(evt) {\n // Skipping checking event source, retarded IE confused this window\n // object with another in the presence of iframe\n if (\n typeof evt.data !== 'string' ||\n evt.data.substr(0, message.length) !== message /* ||\n evt.source !== window */\n ) {\n return;\n }\n\n evt.stopImmediatePropagation();\n\n var id = parseInt(evt.data.substr(message.length), 36);\n if (!callbacks[id]) {\n return;\n }\n\n callbacks[id]();\n callbacks[id] = undefined;\n },\n true\n );\n\n /* specify clearImmediate() here since we need the scope */\n window.clearImmediate = function clearZeroTimeout(id) {\n if (!callbacks[id]) {\n return;\n }\n\n callbacks[id] = undefined;\n };\n\n return setZeroTimeout;\n })() ||\n // fallback\n function setImmediateFallback(fn) {\n window.setTimeout(fn, 0);\n }\n );\n })();\n}\n\nif (!window.clearImmediate) {\n window.clearImmediate = (function setupClearImmediate() {\n return (\n window.msClearImmediate ||\n window.webkitClearImmediate ||\n window.mozClearImmediate ||\n window.oClearImmediate ||\n // \"clearZeroTimeout\" is implement on the previous block ||\n // fallback\n function clearImmediateFallback(timer) {\n window.clearTimeout(timer);\n }\n );\n })();\n}\n\n// Check if WordCloud can run on this browser\nvar isSupported = (function isSupported() {\n var canvas = document.createElement('canvas');\n if (!canvas || !canvas.getContext) {\n return false;\n }\n\n var ctx = canvas.getContext('2d');\n if (!ctx) {\n return false;\n }\n if (!ctx.getImageData) {\n return false;\n }\n if (!ctx.fillText) {\n return false;\n }\n\n if (!Array.prototype.some) {\n return false;\n }\n if (!Array.prototype.push) {\n return false;\n }\n\n return true;\n})();\n\n// Find out if the browser impose minium font size by\n// drawing small texts on a canvas and measure it's width.\nvar minFontSize = (function getMinFontSize() {\n if (!isSupported) {\n return;\n }\n\n var ctx = document.createElement('canvas').getContext('2d');\n\n // start from 20\n var size = 20;\n\n // two sizes to measure\n var hanWidth, mWidth;\n\n while (size) {\n ctx.font = size.toString(10) + 'px sans-serif';\n if (\n ctx.measureText('\\uFF37').width === hanWidth &&\n ctx.measureText('m').width === mWidth\n ) {\n return size + 1;\n }\n\n hanWidth = ctx.measureText('\\uFF37').width;\n mWidth = ctx.measureText('m').width;\n\n size--;\n }\n\n return 0;\n})();\n\nvar getItemExtraData = function (item) {\n if (Array.isArray(item)) {\n var itemCopy = item.slice();\n // remove data we already have (word and weight)\n itemCopy.splice(0, 2);\n return itemCopy;\n } else {\n return [];\n }\n};\n\n// Based on http://jsfromhell.com/array/shuffle\nvar shuffleArray = function shuffleArray(arr) {\n for (var j, x, i = arr.length; i; ) {\n j = Math.floor(Math.random() * i);\n x = arr[--i];\n arr[i] = arr[j];\n arr[j] = x;\n }\n return arr;\n};\n\nvar timer = {};\nvar WordCloud = function WordCloud(elements, options) {\n if (!isSupported) {\n return;\n }\n\n var timerId = Math.floor(Math.random() * Date.now());\n\n if (!Array.isArray(elements)) {\n elements = [elements];\n }\n\n elements.forEach(function (el, i) {\n if (typeof el === 'string') {\n elements[i] = document.getElementById(el);\n if (!elements[i]) {\n throw new Error('The element id specified is not found.');\n }\n } else if (!el.tagName && !el.appendChild) {\n throw new Error(\n 'You must pass valid HTML elements, or ID of the element.'\n );\n }\n });\n\n /* Default values to be overwritten by options object */\n var settings = {\n list: [],\n fontFamily:\n '\"Trebuchet MS\", \"Heiti TC\", \"微軟正黑體\", ' +\n '\"Arial Unicode MS\", \"Droid Fallback Sans\", sans-serif',\n fontWeight: 'normal',\n color: 'random-dark',\n minSize: 0, // 0 to disable\n weightFactor: 1,\n clearCanvas: true,\n backgroundColor: '#fff', // opaque white = rgba(255, 255, 255, 1)\n\n gridSize: 8,\n drawOutOfBound: false,\n shrinkToFit: false,\n origin: null,\n\n drawMask: false,\n maskColor: 'rgba(255,0,0,0.3)',\n maskGapWidth: 0.3,\n\n layoutAnimation: true,\n\n wait: 0,\n abortThreshold: 0, // disabled\n abort: function noop() {},\n\n minRotation: -Math.PI / 2,\n maxRotation: Math.PI / 2,\n rotationStep: 0.1,\n\n shuffle: true,\n rotateRatio: 0.1,\n\n shape: 'circle',\n ellipticity: 0.65,\n\n classes: null,\n\n hover: null,\n click: null\n };\n\n if (options) {\n for (var key in options) {\n if (key in settings) {\n settings[key] = options[key];\n }\n }\n }\n\n /* Convert weightFactor into a function */\n if (typeof settings.weightFactor !== 'function') {\n var factor = settings.weightFactor;\n settings.weightFactor = function weightFactor(pt) {\n return pt * factor; // in px\n };\n }\n\n /* Convert shape into a function */\n if (typeof settings.shape !== 'function') {\n switch (settings.shape) {\n case 'circle':\n /* falls through */\n default:\n // 'circle' is the default and a shortcut in the code loop.\n settings.shape = 'circle';\n break;\n\n case 'cardioid':\n settings.shape = function shapeCardioid(theta) {\n return 1 - Math.sin(theta);\n };\n break;\n\n /*\n To work out an X-gon, one has to calculate \"m\",\n where 1/(cos(2*PI/X)+m*sin(2*PI/X)) = 1/(cos(0)+m*sin(0))\n http://www.wolframalpha.com/input/?i=1%2F%28cos%282*PI%2FX%29%2Bm*sin%28\n 2*PI%2FX%29%29+%3D+1%2F%28cos%280%29%2Bm*sin%280%29%29\n Copy the solution into polar equation r = 1/(cos(t') + m*sin(t'))\n where t' equals to mod(t, 2PI/X);\n */\n\n case 'diamond':\n // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+\n // %28t%2C+PI%2F2%29%29%2Bsin%28mod+%28t%2C+PI%2F2%29%29%29%2C+t+%3D\n // +0+..+2*PI\n settings.shape = function shapeSquare(theta) {\n var thetaPrime = theta % ((2 * Math.PI) / 4);\n return 1 / (Math.cos(thetaPrime) + Math.sin(thetaPrime));\n };\n break;\n\n case 'square':\n // http://www.wolframalpha.com/input/?i=plot+r+%3D+min(1%2Fabs(cos(t\n // )),1%2Fabs(sin(t)))),+t+%3D+0+..+2*PI\n settings.shape = function shapeSquare(theta) {\n return Math.min(\n 1 / Math.abs(Math.cos(theta)),\n 1 / Math.abs(Math.sin(theta))\n );\n };\n break;\n\n case 'triangle-forward':\n // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+\n // %28t%2C+2*PI%2F3%29%29%2Bsqrt%283%29sin%28mod+%28t%2C+2*PI%2F3%29\n // %29%29%2C+t+%3D+0+..+2*PI\n settings.shape = function shapeTriangle(theta) {\n var thetaPrime = theta % ((2 * Math.PI) / 3);\n return (\n 1 / (Math.cos(thetaPrime) + Math.sqrt(3) * Math.sin(thetaPrime))\n );\n };\n break;\n\n case 'triangle':\n case 'triangle-upright':\n settings.shape = function shapeTriangle(theta) {\n var thetaPrime = (theta + (Math.PI * 3) / 2) % ((2 * Math.PI) / 3);\n return (\n 1 / (Math.cos(thetaPrime) + Math.sqrt(3) * Math.sin(thetaPrime))\n );\n };\n break;\n\n case 'pentagon':\n settings.shape = function shapePentagon(theta) {\n var thetaPrime = (theta + 0.955) % ((2 * Math.PI) / 5);\n return 1 / (Math.cos(thetaPrime) + 0.726543 * Math.sin(thetaPrime));\n };\n break;\n\n case 'star':\n settings.shape = function shapeStar(theta) {\n var thetaPrime = (theta + 0.955) % ((2 * Math.PI) / 10);\n if (\n ((theta + 0.955) % ((2 * Math.PI) / 5)) - (2 * Math.PI) / 10 >=\n 0\n ) {\n return (\n 1 /\n (Math.cos((2 * Math.PI) / 10 - thetaPrime) +\n 3.07768 * Math.sin((2 * Math.PI) / 10 - thetaPrime))\n );\n } else {\n return 1 / (Math.cos(thetaPrime) + 3.07768 * Math.sin(thetaPrime));\n }\n };\n break;\n }\n }\n\n /* Make sure gridSize is a whole number and is not smaller than 4px */\n settings.gridSize = Math.max(Math.floor(settings.gridSize), 4);\n\n /* shorthand */\n var g = settings.gridSize;\n var maskRectWidth = g - settings.maskGapWidth;\n\n /* normalize rotation settings */\n var rotationRange = Math.abs(settings.maxRotation - settings.minRotation);\n var minRotation = Math.min(settings.maxRotation, settings.minRotation);\n var rotationStep = settings.rotationStep;\n\n /* information/object available to all functions, set when start() */\n var grid, // 2d array containing filling information\n ngx,\n ngy, // width and height of the grid\n center, // position of the center of the cloud\n maxRadius;\n\n /* timestamp for measuring each putWord() action */\n var escapeTime;\n\n /* function for getting the color of the text */\n var getTextColor;\n function randomHslColor(min, max) {\n return (\n 'hsl(' +\n (Math.random() * 360).toFixed() +\n ',' +\n (Math.random() * 30 + 70).toFixed() +\n '%,' +\n (Math.random() * (max - min) + min).toFixed() +\n '%)'\n );\n }\n switch (settings.color) {\n case 'random-dark':\n getTextColor = function getRandomDarkColor() {\n return randomHslColor(10, 50);\n };\n break;\n\n case 'random-light':\n getTextColor = function getRandomLightColor() {\n return randomHslColor(50, 90);\n };\n break;\n\n default:\n if (typeof settings.color === 'function') {\n getTextColor = settings.color;\n }\n break;\n }\n\n /* function for getting the font-weight of the text */\n var getTextFontWeight;\n if (typeof settings.fontWeight === 'function') {\n getTextFontWeight = settings.fontWeight;\n }\n\n /* function for getting the classes of the text */\n var getTextClasses = null;\n if (typeof settings.classes === 'function') {\n getTextClasses = settings.classes;\n }\n\n /* Interactive */\n var interactive = false;\n var infoGrid = [];\n var hovered;\n\n var getInfoGridFromMouseTouchEvent = function getInfoGridFromMouseTouchEvent(\n evt\n ) {\n var canvas = evt.currentTarget;\n var rect = canvas.getBoundingClientRect();\n var clientX;\n var clientY;\n /** Detect if touches are available */\n if (evt.touches) {\n clientX = evt.touches[0].clientX;\n clientY = evt.touches[0].clientY;\n } else {\n clientX = evt.clientX;\n clientY = evt.clientY;\n }\n var eventX = clientX - rect.left;\n var eventY = clientY - rect.top;\n\n var x = Math.floor((eventX * (canvas.width / rect.width || 1)) / g);\n var y = Math.floor((eventY * (canvas.height / rect.height || 1)) / g);\n\n if (!infoGrid[x]) {\n return null\n }\n\n return infoGrid[x][y];\n };\n\n var wordcloudhover = function wordcloudhover(evt) {\n var info = getInfoGridFromMouseTouchEvent(evt);\n\n if (hovered === info) {\n return;\n }\n\n hovered = info;\n if (!info) {\n settings.hover(undefined, undefined, evt);\n\n return;\n }\n\n settings.hover(info.item, info.dimension, evt);\n };\n\n var wordcloudclick = function wordcloudclick(evt) {\n var info = getInfoGridFromMouseTouchEvent(evt);\n if (!info) {\n return;\n }\n\n settings.click(info.item, info.dimension, evt);\n evt.preventDefault();\n };\n\n /* Get points on the grid for a given radius away from the center */\n var pointsAtRadius = [];\n var getPointsAtRadius = function getPointsAtRadius(radius) {\n if (pointsAtRadius[radius]) {\n return pointsAtRadius[radius];\n }\n\n // Look for these number of points on each radius\n var T = radius * 8;\n\n // Getting all the points at this radius\n var t = T;\n var points = [];\n\n if (radius === 0) {\n points.push([center[0], center[1], 0]);\n }\n\n while (t--) {\n // distort the radius to put the cloud in shape\n var rx = 1;\n if (settings.shape !== 'circle') {\n rx = settings.shape((t / T) * 2 * Math.PI); // 0 to 1\n }\n\n // Push [x, y, t]; t is used solely for getTextColor()\n points.push([\n center[0] + radius * rx * Math.cos((-t / T) * 2 * Math.PI),\n center[1] +\n radius * rx * Math.sin((-t / T) * 2 * Math.PI) * settings.ellipticity,\n (t / T) * 2 * Math.PI\n ]);\n }\n\n pointsAtRadius[radius] = points;\n return points;\n };\n\n /* Return true if we had spent too much time */\n var exceedTime = function exceedTime() {\n return (\n settings.abortThreshold > 0 &&\n new Date().getTime() - escapeTime > settings.abortThreshold\n );\n };\n\n /* Get the deg of rotation according to settings, and luck. */\n var getRotateDeg = function getRotateDeg() {\n if (settings.rotateRatio === 0) {\n return 0;\n }\n\n if (Math.random() > settings.rotateRatio) {\n return 0;\n }\n\n if (rotationRange === 0) {\n return minRotation;\n }\n\n return minRotation + Math.round(Math.random() * rotationRange / rotationStep) * rotationStep;\n };\n\n var getTextInfo = function getTextInfo(\n word,\n weight,\n rotateDeg,\n extraDataArray\n ) {\n // calculate the acutal font size\n // fontSize === 0 means weightFactor function wants the text skipped,\n // and size < minSize means we cannot draw the text.\n var debug = false;\n var fontSize = settings.weightFactor(weight);\n if (fontSize <= settings.minSize) {\n return false;\n }\n\n // Scale factor here is to make sure fillText is not limited by\n // the minium font size set by browser.\n // It will always be 1 or 2n.\n var mu = 1;\n if (fontSize < minFontSize) {\n mu = (function calculateScaleFactor() {\n var mu = 2;\n while (mu * fontSize < minFontSize) {\n mu += 2;\n }\n return mu;\n })();\n }\n\n // Get fontWeight that will be used to set fctx.font\n var fontWeight;\n if (getTextFontWeight) {\n fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray);\n } else {\n fontWeight = settings.fontWeight;\n }\n\n var fcanvas = document.createElement('canvas');\n var fctx = fcanvas.getContext('2d', { willReadFrequently: true });\n\n fctx.font =\n fontWeight +\n ' ' +\n (fontSize * mu).toString(10) +\n 'px ' +\n settings.fontFamily;\n\n // Estimate the dimension of the text with measureText().\n var fw = fctx.measureText(word).width / mu;\n var fh =\n Math.max(\n fontSize * mu,\n fctx.measureText('m').width,\n fctx.measureText('\\uFF37').width\n ) / mu;\n\n // Create a boundary box that is larger than our estimates,\n // so text don't get cut of (it sill might)\n var boxWidth = fw + fh * 2;\n var boxHeight = fh * 3;\n var fgw = Math.ceil(boxWidth / g);\n var fgh = Math.ceil(boxHeight / g);\n boxWidth = fgw * g;\n boxHeight = fgh * g;\n\n // Calculate the proper offsets to make the text centered at\n // the preferred position.\n\n // This is simply half of the width.\n var fillTextOffsetX = -fw / 2;\n // Instead of moving the box to the exact middle of the preferred\n // position, for Y-offset we move 0.4 instead, so Latin alphabets look\n // vertical centered.\n var fillTextOffsetY = -fh * 0.4;\n\n // Calculate the actual dimension of the canvas, considering the rotation.\n var cgh = Math.ceil(\n (boxWidth * Math.abs(Math.sin(rotateDeg)) +\n boxHeight * Math.abs(Math.cos(rotateDeg))) /\n g\n );\n var cgw = Math.ceil(\n (boxWidth * Math.abs(Math.cos(rotateDeg)) +\n boxHeight * Math.abs(Math.sin(rotateDeg))) /\n g\n );\n var width = cgw * g;\n var height = cgh * g;\n\n fcanvas.setAttribute('width', width);\n fcanvas.setAttribute('height', height);\n\n if (debug) {\n // Attach fcanvas to the DOM\n document.body.appendChild(fcanvas);\n // Save it's state so that we could restore and draw the grid correctly.\n fctx.save();\n }\n\n // Scale the canvas with |mu|.\n fctx.scale(1 / mu, 1 / mu);\n fctx.translate((width * mu) / 2, (height * mu) / 2);\n fctx.rotate(-rotateDeg);\n\n // Once the width/height is set, ctx info will be reset.\n // Set it again here.\n fctx.font =\n fontWeight +\n ' ' +\n (fontSize * mu).toString(10) +\n 'px ' +\n settings.fontFamily;\n\n // Fill the text into the fcanvas.\n // XXX: We cannot because textBaseline = 'top' here because\n // Firefox and Chrome uses different default line-height for canvas.\n // Please read https://bugzil.la/737852#c6.\n // Here, we use textBaseline = 'middle' and draw the text at exactly\n // 0.5 * fontSize lower.\n fctx.fillStyle = '#000';\n fctx.textBaseline = 'middle';\n fctx.fillText(\n word,\n fillTextOffsetX * mu,\n (fillTextOffsetY + fontSize * 0.5) * mu\n );\n\n // Get the pixels of the text\n var imageData = fctx.getImageData(0, 0, width, height).data;\n\n if (exceedTime()) {\n return false;\n }\n\n if (debug) {\n // Draw the box of the original estimation\n fctx.strokeRect(fillTextOffsetX * mu, fillTextOffsetY, fw * mu, fh * mu);\n fctx.restore();\n }\n\n // Read the pixels and save the information to the occupied array\n var occupied = [];\n var gx = cgw;\n var gy, x, y;\n var bounds = [cgh / 2, cgw / 2, cgh / 2, cgw / 2];\n while (gx--) {\n gy = cgh;\n while (gy--) {\n y = g;\n /* eslint no-labels: ['error', { 'allowLoop': true }] */\n singleGridLoop: while (y--) {\n x = g;\n while (x--) {\n if (imageData[((gy * g + y) * width + (gx * g + x)) * 4 + 3]) {\n occupied.push([gx, gy]);\n\n if (gx < bounds[3]) {\n bounds[3] = gx;\n }\n if (gx > bounds[1]) {\n bounds[1] = gx;\n }\n if (gy < bounds[0]) {\n bounds[0] = gy;\n }\n if (gy > bounds[2]) {\n bounds[2] = gy;\n }\n\n if (debug) {\n fctx.fillStyle = 'rgba(255, 0, 0, 0.5)';\n fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5);\n }\n break singleGridLoop;\n }\n }\n }\n if (debug) {\n fctx.fillStyle = 'rgba(0, 0, 255, 0.5)';\n fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5);\n }\n }\n }\n\n if (debug) {\n fctx.fillStyle = 'rgba(0, 255, 0, 0.5)';\n fctx.fillRect(\n bounds[3] * g,\n bounds[0] * g,\n (bounds[1] - bounds[3] + 1) * g,\n (bounds[2] - bounds[0] + 1) * g\n );\n }\n\n // Return information needed to create the text on the real canvas\n return {\n mu: mu,\n occupied: occupied,\n bounds: bounds,\n gw: cgw,\n gh: cgh,\n fillTextOffsetX: fillTextOffsetX,\n fillTextOffsetY: fillTextOffsetY,\n fillTextWidth: fw,\n fillTextHeight: fh,\n fontSize: fontSize\n };\n };\n\n /* Determine if there is room available in the given dimension */\n var canFitText = function canFitText(gx, gy, gw, gh, occupied) {\n // Go through the occupied points,\n // return false if the space is not available.\n var i = occupied.length;\n while (i--) {\n var px = gx + occupied[i][0];\n var py = gy + occupied[i][1];\n\n if (px >= ngx || py >= ngy || px < 0 || py < 0) {\n if (!settings.drawOutOfBound) {\n return false;\n }\n continue;\n }\n\n if (!grid[px][py]) {\n return false;\n }\n }\n return true;\n };\n\n /* Actually draw the text on the grid */\n var drawText = function drawText(\n gx,\n gy,\n info,\n word,\n weight,\n distance,\n theta,\n rotateDeg,\n attributes,\n extraDataArray\n ) {\n var fontSize = info.fontSize;\n var color;\n if (getTextColor) {\n color = getTextColor(\n word,\n weight,\n fontSize,\n distance,\n theta,\n extraDataArray\n );\n } else {\n color = settings.color;\n }\n\n // get fontWeight that will be used to set ctx.font and font style rule\n var fontWeight;\n if (getTextFontWeight) {\n fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray);\n } else {\n fontWeight = settings.fontWeight;\n }\n\n var classes;\n if (getTextClasses) {\n classes = getTextClasses(word, weight, fontSize, extraDataArray);\n } else {\n classes = settings.classes;\n }\n\n elements.forEach(function (el) {\n if (el.getContext) {\n var ctx = el.getContext('2d');\n var mu = info.mu;\n\n // Save the current state before messing it\n ctx.save();\n ctx.scale(1 / mu, 1 / mu);\n\n ctx.font =\n fontWeight +\n ' ' +\n (fontSize * mu).toString(10) +\n 'px ' +\n settings.fontFamily;\n ctx.fillStyle = color;\n\n // Translate the canvas position to the origin coordinate of where\n // the text should be put.\n ctx.translate((gx + info.gw / 2) * g * mu, (gy + info.gh / 2) * g * mu);\n\n if (rotateDeg !== 0) {\n ctx.rotate(-rotateDeg);\n }\n\n // Finally, fill the text.\n\n // XXX: We cannot because textBaseline = 'top' here because\n // Firefox and Chrome uses different default line-height for canvas.\n // Please read https://bugzil.la/737852#c6.\n // Here, we use textBaseline = 'middle' and draw the text at exactly\n // 0.5 * fontSize lower.\n ctx.textBaseline = 'middle';\n ctx.fillText(\n word,\n info.fillTextOffsetX * mu,\n (info.fillTextOffsetY + fontSize * 0.5) * mu\n );\n\n // The below box is always matches how s are positioned\n /* ctx.strokeRect(info.fillTextOffsetX, info.fillTextOffsetY,\n info.fillTextWidth, info.fillTextHeight); */\n\n // Restore the state.\n ctx.restore();\n } else {\n // drawText on DIV element\n var span = document.createElement('span');\n var transformRule = '';\n transformRule = 'rotate(' + (-rotateDeg / Math.PI) * 180 + 'deg) ';\n if (info.mu !== 1) {\n transformRule +=\n 'translateX(-' +\n info.fillTextWidth / 4 +\n 'px) ' +\n 'scale(' +\n 1 / info.mu +\n ')';\n }\n var styleRules = {\n position: 'absolute',\n display: 'block',\n font:\n fontWeight + ' ' + fontSize * info.mu + 'px ' + settings.fontFamily,\n left: (gx + info.gw / 2) * g + info.fillTextOffsetX + 'px',\n top: (gy + info.gh / 2) * g + info.fillTextOffsetY + 'px',\n width: info.fillTextWidth + 'px',\n height: info.fillTextHeight + 'px',\n lineHeight: fontSize + 'px',\n whiteSpace: 'nowrap',\n transform: transformRule,\n webkitTransform: transformRule,\n msTransform: transformRule,\n transformOrigin: '50% 40%',\n webkitTransformOrigin: '50% 40%',\n msTransformOrigin: '50% 40%'\n };\n if (color) {\n styleRules.color = color;\n }\n span.textContent = word;\n for (var cssProp in styleRules) {\n span.style[cssProp] = styleRules[cssProp];\n }\n if (attributes) {\n for (var attribute in attributes) {\n span.setAttribute(attribute, attributes[attribute]);\n }\n }\n if (classes) {\n span.className += classes;\n }\n el.appendChild(span);\n }\n });\n };\n\n /* Help function to updateGrid */\n var fillGridAt = function fillGridAt(x, y, drawMask, dimension, item) {\n if (x >= ngx || y >= ngy || x < 0 || y < 0) {\n return;\n }\n\n grid[x][y] = false;\n\n if (drawMask) {\n var ctx = elements[0].getContext('2d');\n ctx.fillRect(x * g, y * g, maskRectWidth, maskRectWidth);\n }\n\n if (interactive) {\n infoGrid[x][y] = { item: item, dimension: dimension };\n }\n };\n\n /* Update the filling information of the given space with occupied points.\n Draw the mask on the canvas if necessary. */\n var updateGrid = function updateGrid(gx, gy, gw, gh, info, item) {\n var occupied = info.occupied;\n var drawMask = settings.drawMask;\n var ctx;\n if (drawMask) {\n ctx = elements[0].getContext('2d');\n ctx.save();\n ctx.fillStyle = settings.maskColor;\n }\n\n var dimension;\n if (interactive) {\n var bounds = info.bounds;\n dimension = {\n x: (gx + bounds[3]) * g,\n y: (gy + bounds[0]) * g,\n w: (bounds[1] - bounds[3] + 1) * g,\n h: (bounds[2] - bounds[0] + 1) * g\n };\n }\n\n var i = occupied.length;\n while (i--) {\n var px = gx + occupied[i][0];\n var py = gy + occupied[i][1];\n\n if (px >= ngx || py >= ngy || px < 0 || py < 0) {\n continue;\n }\n\n fillGridAt(px, py, drawMask, dimension, item);\n }\n\n if (drawMask) {\n ctx.restore();\n }\n };\n\n /* putWord() processes each item on the list,\n calculate it's size and determine it's position, and actually\n put it on the canvas. */\n var putWord = function putWord(item, loopIndex) {\n if (loopIndex > 20) {\n return null;\n }\n\n var word, weight, attributes;\n if (Array.isArray(item)) {\n word = item[0];\n weight = item[1];\n } else {\n word = item.word;\n weight = item.weight;\n attributes = item.attributes;\n }\n var rotateDeg = getRotateDeg();\n\n var extraDataArray = getItemExtraData(item);\n\n // get info needed to put the text onto the canvas\n var info = getTextInfo(word, weight, rotateDeg, extraDataArray);\n\n // not getting the info means we shouldn't be drawing this one.\n if (!info) {\n return false;\n }\n\n if (exceedTime()) {\n return false;\n }\n\n // If drawOutOfBound is set to false,\n // skip the loop if we have already know the bounding box of\n // word is larger than the canvas.\n if (!settings.drawOutOfBound && !settings.shrinkToFit) {\n var bounds = info.bounds;\n if (bounds[1] - bounds[3] + 1 > ngx || bounds[2] - bounds[0] + 1 > ngy) {\n return false;\n }\n }\n\n // Determine the position to put the text by\n // start looking for the nearest points\n var r = maxRadius + 1;\n\n var tryToPutWordAtPoint = function (gxy) {\n var gx = Math.floor(gxy[0] - info.gw / 2);\n var gy = Math.floor(gxy[1] - info.gh / 2);\n var gw = info.gw;\n var gh = info.gh;\n\n // If we cannot fit the text at this position, return false\n // and go to the next position.\n if (!canFitText(gx, gy, gw, gh, info.occupied)) {\n return false;\n }\n\n // Actually put the text on the canvas\n drawText(\n gx,\n gy,\n info,\n word,\n weight,\n maxRadius - r,\n gxy[2],\n rotateDeg,\n attributes,\n extraDataArray\n );\n\n // Mark the spaces on the grid as filled\n updateGrid(gx, gy, gw, gh, info, item);\n\n return {\n gx: gx,\n gy: gy,\n rot: rotateDeg,\n info: info\n };\n };\n\n while (r--) {\n var points = getPointsAtRadius(maxRadius - r);\n\n if (settings.shuffle) {\n points = [].concat(points);\n shuffleArray(points);\n }\n\n // Try to fit the words by looking at each point.\n // array.some() will stop and return true\n // when putWordAtPoint() returns true.\n for (var i = 0; i < points.length; i++) {\n var res = tryToPutWordAtPoint(points[i]);\n if (res) {\n return res;\n }\n }\n\n // var drawn = points.some(tryToPutWordAtPoint);\n // if (drawn) {\n // // leave putWord() and return true\n // return true;\n // }\n }\n\n if (settings.shrinkToFit) {\n if (Array.isArray(item)) {\n item[1] = (item[1] * 3) / 4;\n } else {\n item.weight = (item.weight * 3) / 4;\n }\n return putWord(item, loopIndex + 1);\n }\n\n // we tried all distances but text won't fit, return null\n return null;\n };\n\n /* Send DOM event to all elements. Will stop sending event and return\n if the previous one is canceled (for cancelable events). */\n var sendEvent = function sendEvent(type, cancelable, details) {\n if (cancelable) {\n return !elements.some(function (el) {\n var event = new CustomEvent(type, {\n detail: details || {}\n });\n return !el.dispatchEvent(event);\n }, this);\n } else {\n elements.forEach(function (el) {\n var event = new CustomEvent(type, {\n detail: details || {}\n });\n el.dispatchEvent(event);\n }, this);\n }\n };\n\n /* Start drawing on a canvas */\n var start = function start() {\n // For dimensions, clearCanvas etc.,\n // we only care about the first element.\n var canvas = elements[0];\n\n if (canvas.getContext) {\n ngx = Math.ceil(canvas.width / g);\n ngy = Math.ceil(canvas.height / g);\n } else {\n var rect = canvas.getBoundingClientRect();\n ngx = Math.ceil(rect.width / g);\n ngy = Math.ceil(rect.height / g);\n }\n\n // Sending a wordcloudstart event which cause the previous loop to stop.\n // Do nothing if the event is canceled.\n if (!sendEvent('wordcloudstart', true)) {\n return;\n }\n\n // Determine the center of the word cloud\n center = settings.origin\n ? [settings.origin[0] / g, settings.origin[1] / g]\n : [ngx / 2, ngy / 2];\n\n // Maxium radius to look for space\n maxRadius = Math.floor(Math.sqrt(ngx * ngx + ngy * ngy));\n\n /* Clear the canvas only if the clearCanvas is set,\n if not, update the grid to the current canvas state */\n grid = [];\n\n var gx, gy, i;\n if (!canvas.getContext || settings.clearCanvas) {\n elements.forEach(function (el) {\n if (el.getContext) {\n var ctx = el.getContext('2d');\n ctx.fillStyle = settings.backgroundColor;\n ctx.clearRect(0, 0, ngx * (g + 1), ngy * (g + 1));\n ctx.fillRect(0, 0, ngx * (g + 1), ngy * (g + 1));\n } else {\n el.textContent = '';\n el.style.backgroundColor = settings.backgroundColor;\n el.style.position = 'relative';\n }\n });\n\n /* fill the grid with empty state */\n gx = ngx;\n while (gx--) {\n grid[gx] = [];\n gy = ngy;\n while (gy--) {\n grid[gx][gy] = true;\n }\n }\n } else {\n /* Determine bgPixel by creating\n another canvas and fill the specified background color. */\n var bctx = document.createElement('canvas').getContext('2d');\n\n bctx.fillStyle = settings.backgroundColor;\n bctx.fillRect(0, 0, 1, 1);\n var bgPixel = bctx.getImageData(0, 0, 1, 1).data;\n\n /* Read back the pixels of the canvas we got to tell which part of the\n canvas is empty.\n (no clearCanvas only works with a canvas, not divs) */\n var imageData = canvas\n .getContext('2d')\n .getImageData(0, 0, ngx * g, ngy * g).data;\n\n gx = ngx;\n var x, y;\n while (gx--) {\n grid[gx] = [];\n gy = ngy;\n while (gy--) {\n y = g;\n /* eslint no-labels: ['error', { 'allowLoop': true }] */\n singleGridLoop: while (y--) {\n x = g;\n while (x--) {\n i = 4;\n while (i--) {\n if (\n imageData[((gy * g + y) * ngx * g + (gx * g + x)) * 4 + i] !==\n bgPixel[i]\n ) {\n grid[gx][gy] = false;\n break singleGridLoop;\n }\n }\n }\n }\n if (grid[gx][gy] !== false) {\n grid[gx][gy] = true;\n }\n }\n }\n\n imageData = bctx = bgPixel = undefined;\n }\n\n // fill the infoGrid with empty state if we need it\n if (settings.hover || settings.click) {\n interactive = true;\n\n /* fill the grid with empty state */\n gx = ngx + 1;\n while (gx--) {\n infoGrid[gx] = [];\n }\n\n if (settings.hover) {\n canvas.addEventListener('mousemove', wordcloudhover);\n }\n\n if (settings.click) {\n canvas.addEventListener('click', wordcloudclick);\n canvas.addEventListener('touchstart', wordcloudclick);\n canvas.addEventListener('touchend', function (e) {\n e.preventDefault();\n });\n canvas.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)';\n }\n\n canvas.addEventListener('wordcloudstart', function stopInteraction() {\n canvas.removeEventListener('wordcloudstart', stopInteraction);\n\n canvas.removeEventListener('mousemove', wordcloudhover);\n canvas.removeEventListener('click', wordcloudclick);\n hovered = undefined;\n });\n }\n\n i = 0;\n var loopingFunction, stoppingFunction;\n var layouting = true;\n if (!settings.layoutAnimation) {\n loopingFunction = function (cb) {\n cb();\n };\n stoppingFunction = function () {\n layouting = false;\n };\n } else if (settings.wait !== 0) {\n loopingFunction = window.setTimeout;\n stoppingFunction = window.clearTimeout;\n } else {\n loopingFunction = window.setImmediate;\n stoppingFunction = window.clearImmediate;\n }\n\n var addEventListener = function addEventListener(type, listener) {\n elements.forEach(function (el) {\n el.addEventListener(type, listener);\n }, this);\n };\n\n var removeEventListener = function removeEventListener(type, listener) {\n elements.forEach(function (el) {\n el.removeEventListener(type, listener);\n }, this);\n };\n\n var anotherWordCloudStart = function anotherWordCloudStart() {\n removeEventListener('wordcloudstart', anotherWordCloudStart);\n stoppingFunction(timer[timerId]);\n };\n\n addEventListener('wordcloudstart', anotherWordCloudStart);\n\n // At least wait the following code before call the first iteration.\n timer[timerId] = (settings.layoutAnimation ? loopingFunction : setTimeout)(\n function loop() {\n if (!layouting) {\n return;\n }\n if (i >= settings.list.length) {\n stoppingFunction(timer[timerId]);\n sendEvent('wordcloudstop', false);\n removeEventListener('wordcloudstart', anotherWordCloudStart);\n delete timer[timerId];\n return;\n }\n escapeTime = new Date().getTime();\n var drawn = putWord(settings.list[i], 0);\n var canceled = !sendEvent('wordclouddrawn', true, {\n item: settings.list[i],\n drawn: drawn\n });\n if (exceedTime() || canceled) {\n stoppingFunction(timer[timerId]);\n settings.abort();\n sendEvent('wordcloudabort', false);\n sendEvent('wordcloudstop', false);\n removeEventListener('wordcloudstart', anotherWordCloudStart);\n return;\n }\n i++;\n timer[timerId] = loopingFunction(loop, settings.wait);\n },\n settings.wait\n );\n };\n\n // All set, start the drawing\n start();\n};\n\nWordCloud.isSupported = isSupported;\nWordCloud.minFontSize = minFontSize;\n\nexport default WordCloud;\n","import * as echarts from 'echarts/lib/echarts';\n\nimport './WordCloudSeries';\nimport './WordCloudView';\n\nimport wordCloudLayoutHelper from './layout';\n\nif (!wordCloudLayoutHelper.isSupported) {\n throw new Error('Sorry your browser not support wordCloud');\n}\n\n// https://github.com/timdream/wordcloud2.js/blob/c236bee60436e048949f9becc4f0f67bd832dc5c/index.js#L233\nfunction updateCanvasMask(maskCanvas) {\n var ctx = maskCanvas.getContext('2d');\n var imageData = ctx.getImageData(0, 0, maskCanvas.width, maskCanvas.height);\n var newImageData = ctx.createImageData(imageData);\n\n var toneSum = 0;\n var toneCnt = 0;\n for (var i = 0; i < imageData.data.length; i += 4) {\n var alpha = imageData.data[i + 3];\n if (alpha > 128) {\n var tone =\n imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2];\n toneSum += tone;\n ++toneCnt;\n }\n }\n var threshold = toneSum / toneCnt;\n\n for (var i = 0; i < imageData.data.length; i += 4) {\n var tone =\n imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2];\n var alpha = imageData.data[i + 3];\n\n if (alpha < 128 || tone > threshold) {\n // Area not to draw\n newImageData.data[i] = 0;\n newImageData.data[i + 1] = 0;\n newImageData.data[i + 2] = 0;\n newImageData.data[i + 3] = 0;\n } else {\n // Area to draw\n // The color must be same with backgroundColor\n newImageData.data[i] = 255;\n newImageData.data[i + 1] = 255;\n newImageData.data[i + 2] = 255;\n newImageData.data[i + 3] = 255;\n }\n }\n\n ctx.putImageData(newImageData, 0, 0);\n}\n\necharts.registerLayout(function (ecModel, api) {\n ecModel.eachSeriesByType('wordCloud', function (seriesModel) {\n var gridRect = echarts.helper.getLayoutRect(\n seriesModel.getBoxLayoutParams(),\n {\n width: api.getWidth(),\n height: api.getHeight()\n }\n );\n\n var keepAspect = seriesModel.get('keepAspect');\n var maskImage = seriesModel.get('maskImage');\n var ratio = maskImage ? maskImage.width / maskImage.height : 1;\n keepAspect && adjustRectAspect(gridRect, ratio);\n\n var data = seriesModel.getData();\n\n var canvas = document.createElement('canvas');\n canvas.width = gridRect.width;\n canvas.height = gridRect.height;\n\n var ctx = canvas.getContext('2d');\n if (maskImage) {\n try {\n ctx.drawImage(maskImage, 0, 0, canvas.width, canvas.height);\n updateCanvasMask(canvas);\n } catch (e) {\n console.error('Invalid mask image');\n console.error(e.toString());\n }\n }\n\n var sizeRange = seriesModel.get('sizeRange');\n var rotationRange = seriesModel.get('rotationRange');\n var valueExtent = data.getDataExtent('value');\n\n var DEGREE_TO_RAD = Math.PI / 180;\n var gridSize = seriesModel.get('gridSize');\n wordCloudLayoutHelper(canvas, {\n list: data\n .mapArray('value', function (value, idx) {\n var itemModel = data.getItemModel(idx);\n return [\n data.getName(idx),\n itemModel.get('textStyle.fontSize', true) ||\n echarts.number.linearMap(value, valueExtent, sizeRange),\n idx\n ];\n })\n .sort(function (a, b) {\n // Sort from large to small in case there is no more room for more words\n return b[1] - a[1];\n }),\n fontFamily:\n seriesModel.get('textStyle.fontFamily') ||\n seriesModel.get('emphasis.textStyle.fontFamily') ||\n ecModel.get('textStyle.fontFamily'),\n fontWeight:\n seriesModel.get('textStyle.fontWeight') ||\n seriesModel.get('emphasis.textStyle.fontWeight') ||\n ecModel.get('textStyle.fontWeight'),\n\n gridSize: gridSize,\n\n ellipticity: gridRect.height / gridRect.width,\n\n minRotation: rotationRange[0] * DEGREE_TO_RAD,\n maxRotation: rotationRange[1] * DEGREE_TO_RAD,\n\n clearCanvas: !maskImage,\n\n rotateRatio: 1,\n\n rotationStep: seriesModel.get('rotationStep') * DEGREE_TO_RAD,\n\n drawOutOfBound: seriesModel.get('drawOutOfBound'),\n shrinkToFit: seriesModel.get('shrinkToFit'),\n\n layoutAnimation: seriesModel.get('layoutAnimation'),\n\n shuffle: false,\n\n shape: seriesModel.get('shape')\n });\n\n function onWordCloudDrawn(e) {\n var item = e.detail.item;\n if (e.detail.drawn && seriesModel.layoutInstance.ondraw) {\n e.detail.drawn.gx += gridRect.x / gridSize;\n e.detail.drawn.gy += gridRect.y / gridSize;\n seriesModel.layoutInstance.ondraw(\n item[0],\n item[1],\n item[2],\n e.detail.drawn\n );\n }\n }\n\n canvas.addEventListener('wordclouddrawn', onWordCloudDrawn);\n\n if (seriesModel.layoutInstance) {\n // Dispose previous\n seriesModel.layoutInstance.dispose();\n }\n\n seriesModel.layoutInstance = {\n ondraw: null,\n\n dispose: function () {\n canvas.removeEventListener('wordclouddrawn', onWordCloudDrawn);\n // Abort\n canvas.addEventListener('wordclouddrawn', function (e) {\n // Prevent default to cancle the event and stop the loop\n e.preventDefault();\n });\n }\n };\n });\n});\n\necharts.registerPreprocessor(function (option) {\n var series = (option || {}).series;\n !echarts.util.isArray(series) && (series = series ? [series] : []);\n\n var compats = ['shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY'];\n\n echarts.util.each(series, function (seriesItem) {\n if (seriesItem && seriesItem.type === 'wordCloud') {\n var textStyle = seriesItem.textStyle || {};\n\n compatTextStyle(textStyle.normal);\n compatTextStyle(textStyle.emphasis);\n }\n });\n\n function compatTextStyle(textStyle) {\n textStyle &&\n echarts.util.each(compats, function (key) {\n if (textStyle.hasOwnProperty(key)) {\n textStyle['text' + echarts.format.capitalFirst(key)] = textStyle[key];\n }\n });\n }\n});\n\nfunction adjustRectAspect(gridRect, aspect) {\n // var outerWidth = gridRect.width + gridRect.x * 2;\n // var outerHeight = gridRect.height + gridRect.y * 2;\n var width = gridRect.width;\n var height = gridRect.height;\n if (width > height * aspect) {\n gridRect.x += (width - height * aspect) / 2;\n gridRect.width = height * aspect;\n } else {\n gridRect.y += (height - width / aspect) / 2;\n gridRect.height = width / aspect;\n }\n}\n","import './src/wordCloud';\n","module.exports = __WEBPACK_EXTERNAL_MODULE_echarts_lib_echarts__;","// The module cache\nvar __webpack_module_cache__ = {};\n\n// The require function\nfunction __webpack_require__(moduleId) {\n\t// Check if module is in cache\n\tif(__webpack_module_cache__[moduleId]) {\n\t\treturn __webpack_module_cache__[moduleId].exports;\n\t}\n\t// Create a new module (and put it into the cache)\n\tvar module = __webpack_module_cache__[moduleId] = {\n\t\t// no module.id needed\n\t\t// no module.loaded needed\n\t\texports: {}\n\t};\n\n\t// Execute the module function\n\t__webpack_modules__[moduleId](module, module.exports, __webpack_require__);\n\n\t// Return the exports of the module\n\treturn module.exports;\n}\n\n","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","// module exports must be returned from runtime so entry inlining is disabled\n// startup\n// Load entry module and return exports\nreturn __webpack_require__(\"./index.js\");\n"],"sourceRoot":""}
--------------------------------------------------------------------------------
/dist/echarts-wordcloud.min.js:
--------------------------------------------------------------------------------
1 | /*! For license information please see echarts-wordcloud.min.js.LICENSE.txt */
2 | !function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e(require("echarts")):"function"==typeof define&&define.amd?define(["echarts"],e):"object"==typeof exports?exports["echarts-wordcloud"]=e(require("echarts")):t["echarts-wordcloud"]=e(t.echarts)}(self,(function(t){return(()=>{"use strict";var e={638:(t,e,a)=>{a.r(e);var r=a(83);r.extendSeriesModel({type:"series.wordCloud",visualStyleAccessPath:"textStyle",visualStyleMapper:function(t){return{fill:t.get("color")}},visualDrawType:"fill",optionUpdated:function(){var t=this.option;t.gridSize=Math.max(Math.floor(t.gridSize),4)},getInitialData:function(t,e){var a=r.helper.createDimensions(t.data,{coordDimensions:["value"]}),i=new r.List(a,this);return i.initData(t.data),i},defaultOption:{maskImage:null,shape:"circle",keepAspect:!1,left:"center",top:"center",width:"70%",height:"80%",sizeRange:[12,60],rotationRange:[-90,90],rotationStep:45,gridSize:8,drawOutOfBound:!1,shrinkToFit:!1,textStyle:{fontWeight:"normal"}}}),r.extendChartView({type:"wordCloud",render:function(t,e,a){var i=this.group;i.removeAll();var o=t.getData(),n=t.get("gridSize");t.layoutInstance.ondraw=function(e,a,s,l){var d=o.getItemModel(s),u=d.getModel("textStyle"),f=new r.graphic.Text({style:r.helper.createTextStyle(u),scaleX:1/l.info.mu,scaleY:1/l.info.mu,x:(l.gx+l.info.gw/2)*n,y:(l.gy+l.info.gh/2)*n,rotation:l.rot});f.setStyle({x:l.info.fillTextOffsetX,y:l.info.fillTextOffsetY+.5*a,text:e,verticalAlign:"middle",fill:o.getItemVisual(s,"style").fill,fontSize:a}),i.add(f),o.setItemGraphicEl(s,f),f.ensureState("emphasis").style=r.helper.createTextStyle(d.getModel(["emphasis","textStyle"]),{state:"emphasis"}),f.ensureState("blur").style=r.helper.createTextStyle(d.getModel(["blur","textStyle"]),{state:"blur"}),r.helper.enableHoverEmphasis(f,d.get(["emphasis","focus"]),d.get(["emphasis","blurScope"])),f.stateTransition={duration:t.get("animation")?t.get(["stateAnimation","duration"]):0,easing:t.get(["stateAnimation","easing"])},f.__highDownDispatcher=!0},this._model=t},remove:function(){this.group.removeAll(),this._model.layoutInstance.dispose()},dispose:function(){this._model.layoutInstance.dispose()}}),window.setImmediate||(window.setImmediate=window.msSetImmediate||window.webkitSetImmediate||window.mozSetImmediate||window.oSetImmediate||function(){if(!window.postMessage||!window.addEventListener)return null;var t=[void 0],e="zero-timeout-message";return window.addEventListener("message",(function(a){if("string"==typeof a.data&&a.data.substr(0,e.length)===e){a.stopImmediatePropagation();var r=parseInt(a.data.substr(e.length),36);t[r]&&(t[r](),t[r]=void 0)}}),!0),window.clearImmediate=function(e){t[e]&&(t[e]=void 0)},function(a){var r=t.length;return t.push(a),window.postMessage(e+r.toString(36),"*"),r}}()||function(t){window.setTimeout(t,0)}),window.clearImmediate||(window.clearImmediate=window.msClearImmediate||window.webkitClearImmediate||window.mozClearImmediate||window.oClearImmediate||function(t){window.clearTimeout(t)});var i=function(){var t=document.createElement("canvas");if(!t||!t.getContext)return!1;var e=t.getContext("2d");return!!(e&&e.getImageData&&e.fillText&&Array.prototype.some&&Array.prototype.push)}(),o=function(){if(i){for(var t,e,a=document.createElement("canvas").getContext("2d"),r=20;r;){if(a.font=r.toString(10)+"px sans-serif",a.measureText("W").width===t&&a.measureText("m").width===e)return r+1;t=a.measureText("W").width,e=a.measureText("m").width,r--}return 0}}(),n=function(t){for(var e,a,r=t.length;r;)e=Math.floor(Math.random()*r),a=t[--r],t[r]=t[e],t[e]=a;return t},s={},l=function(t,e){if(i){var a=Math.floor(Math.random()*Date.now());Array.isArray(t)||(t=[t]),t.forEach((function(e,a){if("string"==typeof e){if(t[a]=document.getElementById(e),!t[a])throw new Error("The element id specified is not found.")}else if(!e.tagName&&!e.appendChild)throw new Error("You must pass valid HTML elements, or ID of the element.")}));var r={list:[],fontFamily:'"Trebuchet MS", "Heiti TC", "微軟正黑體", "Arial Unicode MS", "Droid Fallback Sans", sans-serif',fontWeight:"normal",color:"random-dark",minSize:0,weightFactor:1,clearCanvas:!0,backgroundColor:"#fff",gridSize:8,drawOutOfBound:!1,shrinkToFit:!1,origin:null,drawMask:!1,maskColor:"rgba(255,0,0,0.3)",maskGapWidth:.3,layoutAnimation:!0,wait:0,abortThreshold:0,abort:function(){},minRotation:-Math.PI/2,maxRotation:Math.PI/2,rotationStep:.1,shuffle:!0,rotateRatio:.1,shape:"circle",ellipticity:.65,classes:null,hover:null,click:null};if(e)for(var l in e)l in r&&(r[l]=e[l]);if("function"!=typeof r.weightFactor){var d=r.weightFactor;r.weightFactor=function(t){return t*d}}if("function"!=typeof r.shape)switch(r.shape){case"circle":default:r.shape="circle";break;case"cardioid":r.shape=function(t){return 1-Math.sin(t)};break;case"diamond":r.shape=function(t){var e=t%(2*Math.PI/4);return 1/(Math.cos(e)+Math.sin(e))};break;case"square":r.shape=function(t){return Math.min(1/Math.abs(Math.cos(t)),1/Math.abs(Math.sin(t)))};break;case"triangle-forward":r.shape=function(t){var e=t%(2*Math.PI/3);return 1/(Math.cos(e)+Math.sqrt(3)*Math.sin(e))};break;case"triangle":case"triangle-upright":r.shape=function(t){var e=(t+3*Math.PI/2)%(2*Math.PI/3);return 1/(Math.cos(e)+Math.sqrt(3)*Math.sin(e))};break;case"pentagon":r.shape=function(t){var e=(t+.955)%(2*Math.PI/5);return 1/(Math.cos(e)+.726543*Math.sin(e))};break;case"star":r.shape=function(t){var e=(t+.955)%(2*Math.PI/10);return(t+.955)%(2*Math.PI/5)-2*Math.PI/10>=0?1/(Math.cos(2*Math.PI/10-e)+3.07768*Math.sin(2*Math.PI/10-e)):1/(Math.cos(e)+3.07768*Math.sin(e))}}r.gridSize=Math.max(Math.floor(r.gridSize),4);var u,f,c,h,m,g,w,v,p=r.gridSize,y=p-r.maskGapWidth,x=Math.abs(r.maxRotation-r.minRotation),M=Math.min(r.maxRotation,r.minRotation),S=r.rotationStep;switch(r.color){case"random-dark":w=function(){return L(10,50)};break;case"random-light":w=function(){return L(50,90)};break;default:"function"==typeof r.color&&(w=r.color)}"function"==typeof r.fontWeight&&(v=r.fontWeight);var b=null;"function"==typeof r.classes&&(b=r.classes);var I,T=!1,k=[],C=function(t){var e,a,r=t.currentTarget,i=r.getBoundingClientRect();t.touches?(e=t.touches[0].clientX,a=t.touches[0].clientY):(e=t.clientX,a=t.clientY);var o=e-i.left,n=a-i.top,s=Math.floor(o*(r.width/i.width||1)/p),l=Math.floor(n*(r.height/i.height||1)/p);return k[s]?k[s][l]:null},E=function(t){var e=C(t);I!==e&&(I=e,e?r.hover(e.item,e.dimension,t):r.hover(void 0,void 0,t))},A=function(t){var e=C(t);e&&(r.click(e.item,e.dimension,t),t.preventDefault())},O=[],F=function(t){if(O[t])return O[t];var e=8*t,a=e,i=[];for(0===t&&i.push([h[0],h[1],0]);a--;){var o=1;"circle"!==r.shape&&(o=r.shape(a/e*2*Math.PI)),i.push([h[0]+t*o*Math.cos(-a/e*2*Math.PI),h[1]+t*o*Math.sin(-a/e*2*Math.PI)*r.ellipticity,a/e*2*Math.PI])}return O[t]=i,i},D=function(){return r.abortThreshold>0&&(new Date).getTime()-g>r.abortThreshold},P=function(e,a,r,i,o){e>=f||a>=c||e<0||a<0||(u[e][a]=!1,r&&t[0].getContext("2d").fillRect(e*p,a*p,y,y),T&&(k[e][a]={item:o,dimension:i}))},R=function e(a,i){if(i>20)return null;var s,l,d;Array.isArray(a)?(s=a[0],l=a[1]):(s=a.word,l=a.weight,d=a.attributes);var h,g,y,I=0===r.rotateRatio||Math.random()>r.rotateRatio?0:0===x?M:M+Math.round(Math.random()*x/S)*S,k=function(t){if(Array.isArray(t)){var e=t.slice();return e.splice(0,2),e}return[]}(a),C=function(t,e,a,i){var n=r.weightFactor(e);if(n<=r.minSize)return!1;var s,l=1;nF[1]&&(F[1]=O),kF[2]&&(F[2]=k);break t}}return{mu:l,occupied:A,bounds:F,gw:S,gh:M,fillTextOffsetX:y,fillTextOffsetY:x,fillTextWidth:f,fillTextHeight:c,fontSize:n}}(s,l,I,k);if(!C)return!1;if(D())return!1;if(!r.drawOutOfBound&&!r.shrinkToFit){var E=C.bounds;if(E[1]-E[3]+1>f||E[2]-E[0]+1>c)return!1}for(var A=m+1;A--;){var O=F(m-A);r.shuffle&&(O=[].concat(O),n(O));for(var R=0;R=f||l>=c||s<0||l<0){if(!r.drawOutOfBound)return!1}else if(!u[s][l])return!1}return!0}(g,y,0,0,C.occupied)&&(function(e,a,i,o,n,s,l,d,u,f){var c,h,m,g=i.fontSize;c=w?w(o,n,g,s,l,f):r.color,h=v?v(o,n,g,f):r.fontWeight,m=b?b(o,n,g,f):r.classes,t.forEach((function(t){if(t.getContext){var n=t.getContext("2d"),s=i.mu;n.save(),n.scale(1/s,1/s),n.font=h+" "+(g*s).toString(10)+"px "+r.fontFamily,n.fillStyle=c,n.translate((e+i.gw/2)*p*s,(a+i.gh/2)*p*s),0!==d&&n.rotate(-d),n.textBaseline="middle",n.fillText(o,i.fillTextOffsetX*s,(i.fillTextOffsetY+.5*g)*s),n.restore()}else{var l=document.createElement("span"),f="";f="rotate("+-d/Math.PI*180+"deg) ",1!==i.mu&&(f+="translateX(-"+i.fillTextWidth/4+"px) scale("+1/i.mu+")");var w={position:"absolute",display:"block",font:h+" "+g*i.mu+"px "+r.fontFamily,left:(e+i.gw/2)*p+i.fillTextOffsetX+"px",top:(a+i.gh/2)*p+i.fillTextOffsetY+"px",width:i.fillTextWidth+"px",height:i.fillTextHeight+"px",lineHeight:g+"px",whiteSpace:"nowrap",transform:f,webkitTransform:f,msTransform:f,transformOrigin:"50% 40%",webkitTransformOrigin:"50% 40%",msTransformOrigin:"50% 40%"};for(var v in c&&(w.color=c),l.textContent=o,w)l.style[v]=w[v];if(u)for(var y in u)l.setAttribute(y,u[y]);m&&(l.className+=m),t.appendChild(l)}}))}(g,y,C,s,l,m-A,h[2],I,d,k),function(e,a,i,o,n,s){var l,d,u=n.occupied,h=r.drawMask;if(h&&((l=t[0].getContext("2d")).save(),l.fillStyle=r.maskColor),T){var m=n.bounds;d={x:(e+m[3])*p,y:(a+m[0])*p,w:(m[1]-m[3]+1)*p,h:(m[2]-m[0]+1)*p}}for(var g=u.length;g--;){var w=e+u[g][0],v=a+u[g][1];w>=f||v>=c||w<0||v<0||P(w,v,h,d,s)}h&&l.restore()}(g,y,0,0,C,a),{gx:g,gy:y,rot:I,info:C}));if(z)return z}}return r.shrinkToFit?(Array.isArray(a)?a[1]=3*a[1]/4:a.weight=3*a.weight/4,e(a,i+1)):null},z=function(e,a,r){if(a)return!t.some((function(t){var a=new CustomEvent(e,{detail:r||{}});return!t.dispatchEvent(a)}),this);t.forEach((function(t){var a=new CustomEvent(e,{detail:r||{}});t.dispatchEvent(a)}),this)};!function(){var e=t[0];if(e.getContext)f=Math.ceil(e.width/p),c=Math.ceil(e.height/p);else{var i=e.getBoundingClientRect();f=Math.ceil(i.width/p),c=Math.ceil(i.height/p)}if(z("wordcloudstart",!0)){var o,n,l,d,w;if(h=r.origin?[r.origin[0]/p,r.origin[1]/p]:[f/2,c/2],m=Math.floor(Math.sqrt(f*f+c*c)),u=[],!e.getContext||r.clearCanvas)for(t.forEach((function(t){if(t.getContext){var e=t.getContext("2d");e.fillStyle=r.backgroundColor,e.clearRect(0,0,f*(p+1),c*(p+1)),e.fillRect(0,0,f*(p+1),c*(p+1))}else t.textContent="",t.style.backgroundColor=r.backgroundColor,t.style.position="relative"})),o=f;o--;)for(u[o]=[],n=c;n--;)u[o][n]=!0;else{var v=document.createElement("canvas").getContext("2d");v.fillStyle=r.backgroundColor,v.fillRect(0,0,1,1);var y,x,M=v.getImageData(0,0,1,1).data,S=e.getContext("2d").getImageData(0,0,f*p,c*p).data;for(o=f;o--;)for(u[o]=[],n=c;n--;){x=p;t:for(;x--;)for(y=p;y--;)for(l=4;l--;)if(S[4*((n*p+x)*f*p+(o*p+y))+l]!==M[l]){u[o][n]=!1;break t}!1!==u[o][n]&&(u[o][n]=!0)}S=v=M=void 0}if(r.hover||r.click){for(T=!0,o=f+1;o--;)k[o]=[];r.hover&&e.addEventListener("mousemove",E),r.click&&(e.addEventListener("click",A),e.addEventListener("touchstart",A),e.addEventListener("touchend",(function(t){t.preventDefault()})),e.style.webkitTapHighlightColor="rgba(0, 0, 0, 0)"),e.addEventListener("wordcloudstart",(function t(){e.removeEventListener("wordcloudstart",t),e.removeEventListener("mousemove",E),e.removeEventListener("click",A),I=void 0}))}l=0;var b=!0;r.layoutAnimation?0!==r.wait?(d=window.setTimeout,w=window.clearTimeout):(d=window.setImmediate,w=window.clearImmediate):(d=function(t){t()},w=function(){b=!1});var C=function(e,a){t.forEach((function(t){t.removeEventListener(e,a)}),this)},O=function t(){C("wordcloudstart",t),w(s[a])};!function(e,a){t.forEach((function(t){t.addEventListener("wordcloudstart",a)}),this)}(0,O),s[a]=(r.layoutAnimation?d:setTimeout)((function t(){if(b){if(l>=r.list.length)return w(s[a]),z("wordcloudstop",!1),C("wordcloudstart",O),void delete s[a];g=(new Date).getTime();var e=R(r.list[l],0),i=!z("wordclouddrawn",!0,{item:r.list[l],drawn:e});if(D()||i)return w(s[a]),r.abort(),z("wordcloudabort",!1),z("wordcloudstop",!1),void C("wordcloudstart",O);l++,s[a]=d(t,r.wait)}}),r.wait)}}()}function L(t,e){return"hsl("+(360*Math.random()).toFixed()+","+(30*Math.random()+70).toFixed()+"%,"+(Math.random()*(e-t)+t).toFixed()+"%)"}};l.isSupported=i,l.minFontSize=o;const d=l;if(!d.isSupported)throw new Error("Sorry your browser not support wordCloud");r.registerLayout((function(t,e){t.eachSeriesByType("wordCloud",(function(a){var i=r.helper.getLayoutRect(a.getBoxLayoutParams(),{width:e.getWidth(),height:e.getHeight()}),o=a.get("keepAspect"),n=a.get("maskImage"),s=n?n.width/n.height:1;o&&function(t,e){var a=t.width,r=t.height;a>r*e?(t.x+=(a-r*e)/2,t.width=r*e):(t.y+=(r-a/e)/2,t.height=a/e)}(i,s);var l=a.getData(),u=document.createElement("canvas");u.width=i.width,u.height=i.height;var f=u.getContext("2d");if(n)try{f.drawImage(n,0,0,u.width,u.height),function(t){for(var e=t.getContext("2d"),a=e.getImageData(0,0,t.width,t.height),r=e.createImageData(a),i=0,o=0,n=0;n128&&(i+=l=a.data[n]+a.data[n+1]+a.data[n+2],++o);var s=i/o;for(n=0;ns?(r.data[n]=0,r.data[n+1]=0,r.data[n+2]=0,r.data[n+3]=0):(r.data[n]=255,r.data[n+1]=255,r.data[n+2]=255,r.data[n+3]=255)}e.putImageData(r,0,0)}(u)}catch(t){console.error("Invalid mask image"),console.error(t.toString())}var c=a.get("sizeRange"),h=a.get("rotationRange"),m=l.getDataExtent("value"),g=Math.PI/180,w=a.get("gridSize");function v(t){var e=t.detail.item;t.detail.drawn&&a.layoutInstance.ondraw&&(t.detail.drawn.gx+=i.x/w,t.detail.drawn.gy+=i.y/w,a.layoutInstance.ondraw(e[0],e[1],e[2],t.detail.drawn))}d(u,{list:l.mapArray("value",(function(t,e){var a=l.getItemModel(e);return[l.getName(e),a.get("textStyle.fontSize",!0)||r.number.linearMap(t,m,c),e]})).sort((function(t,e){return e[1]-t[1]})),fontFamily:a.get("textStyle.fontFamily")||a.get("emphasis.textStyle.fontFamily")||t.get("textStyle.fontFamily"),fontWeight:a.get("textStyle.fontWeight")||a.get("emphasis.textStyle.fontWeight")||t.get("textStyle.fontWeight"),gridSize:w,ellipticity:i.height/i.width,minRotation:h[0]*g,maxRotation:h[1]*g,clearCanvas:!n,rotateRatio:1,rotationStep:a.get("rotationStep")*g,drawOutOfBound:a.get("drawOutOfBound"),shrinkToFit:a.get("shrinkToFit"),layoutAnimation:a.get("layoutAnimation"),shuffle:!1,shape:a.get("shape")}),u.addEventListener("wordclouddrawn",v),a.layoutInstance&&a.layoutInstance.dispose(),a.layoutInstance={ondraw:null,dispose:function(){u.removeEventListener("wordclouddrawn",v),u.addEventListener("wordclouddrawn",(function(t){t.preventDefault()}))}}}))})),r.registerPreprocessor((function(t){var e=(t||{}).series;!r.util.isArray(e)&&(e=e?[e]:[]);var a=["shadowColor","shadowBlur","shadowOffsetX","shadowOffsetY"];function i(t){t&&r.util.each(a,(function(e){t.hasOwnProperty(e)&&(t["text"+r.format.capitalFirst(e)]=t[e])}))}r.util.each(e,(function(t){if(t&&"wordCloud"===t.type){var e=t.textStyle||{};i(e.normal),i(e.emphasis)}}))}))},83:e=>{e.exports=t}},a={};function r(t){if(a[t])return a[t].exports;var i=a[t]={exports:{}};return e[t](i,i.exports,r),i.exports}return r.r=t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})},r(638)})()}));
3 | //# sourceMappingURL=echarts-wordcloud.min.js.map
--------------------------------------------------------------------------------
/dist/echarts-wordcloud.min.js.LICENSE.txt:
--------------------------------------------------------------------------------
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 |
--------------------------------------------------------------------------------
/example/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ecomfe/echarts-wordcloud/3aa04edd2b842977109efe5e9a20b3aab46caa18/example/logo.png
--------------------------------------------------------------------------------
/example/optionKeywords.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
17 |
18 |
415 |
416 |
--------------------------------------------------------------------------------
/example/test.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
29 |
30 |
31 | Expect text with 0 to 9 to be displayed all
32 |
65 |
66 |
67 |
--------------------------------------------------------------------------------
/example/typeTest.ts:
--------------------------------------------------------------------------------
1 | import echarts from 'echarts';
2 | import '../';
3 |
4 |
5 | const option: echarts.EChartsOption = {
6 | series: {
7 | type: 'wordCloud',
8 | shape: 'circle',
9 |
10 | // Folllowing left/top/width/height/right/bottom are used for positioning the word cloud
11 | // Default to be put in the center and has 75% x 80% size.
12 |
13 | left: 'center',
14 | top: 'center',
15 | width: '70%',
16 | height: '80%',
17 | right: null,
18 | bottom: null,
19 |
20 | // Text size range which the value in data will be mapped to.
21 | // Default to have minimum 12px and maximum 60px size.
22 |
23 | sizeRange: [12, 60],
24 |
25 | // Text rotation range and step in degree. Text will be rotated randomly in range [-90, 90] by rotationStep 45
26 |
27 | rotationRange: [-90, 90],
28 | rotationStep: 45,
29 |
30 | // size of the grid in pixels for marking the availability of the canvas
31 | // the larger the grid size, the bigger the gap between words.
32 |
33 | gridSize: 8,
34 |
35 | // set to true to allow word being draw partly outside of the canvas.
36 | // Allow word bigger than the size of the canvas to be drawn
37 | drawOutOfBound: false,
38 |
39 | // If perform layout animation.
40 | // NOTE disable it will lead to UI blocking when there is lots of words.
41 | layoutAnimation: true,
42 |
43 | // Global text style
44 | textStyle: {
45 | fontFamily: 'sans-serif',
46 | fontWeight: 'bold',
47 | // Color can be a callback function or a color string
48 | color: function () {
49 | // Random color
50 | return 'rgb(' + [
51 | Math.round(Math.random() * 160),
52 | Math.round(Math.random() * 160),
53 | Math.round(Math.random() * 160)
54 | ].join(',') + ')';
55 | }
56 | },
57 | emphasis: {
58 | focus: 'self',
59 |
60 | textStyle: {
61 | textShadowBlur: 10,
62 | textShadowColor: '#333'
63 | }
64 | },
65 |
66 | // Data is an array. Each array item must have name and value property.
67 | data: [{
68 | name: 'Farrah Abraham',
69 | value: 366,
70 | // Style of single text
71 | textStyle: {
72 | }
73 | }]
74 | }
75 | }
--------------------------------------------------------------------------------
/example/word-cloud.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ecomfe/echarts-wordcloud/3aa04edd2b842977109efe5e9a20b3aab46caa18/example/word-cloud.png
--------------------------------------------------------------------------------
/example/wordCloud.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
16 |
17 |
143 |
144 |
--------------------------------------------------------------------------------
/index.d.ts:
--------------------------------------------------------------------------------
1 | import echarts from 'echarts';
2 |
3 | interface WordCloudTextStyle {
4 | color?: string;
5 | fontStyle?: string;
6 | fontWeight?: string | number;
7 | fontFamily?: string;
8 | fontSize?: number | string;
9 | align?: string;
10 | verticalAlign?: string;
11 | // @deprecated
12 | baseline?: string;
13 |
14 | opacity?: number;
15 |
16 | lineHeight?: number;
17 | backgroundColor?:
18 | | string
19 | | {
20 | image: HTMLImageElement | HTMLCanvasElement | string;
21 | };
22 | borderColor?: string;
23 | borderWidth?: number;
24 | borderType?: string;
25 | borderDashOffset?: number;
26 | borderRadius?: number | number[];
27 | padding?: number | number[];
28 |
29 | width?: number | string; // Percent
30 | height?: number;
31 | textBorderColor?: string;
32 | textBorderWidth?: number;
33 | textBorderType?: string;
34 | textBorderDashOffset?: number;
35 |
36 | textShadowBlur?: number;
37 | textShadowColor?: string;
38 | textShadowOffsetX?: number;
39 | textShadowOffsetY?: number;
40 | }
41 |
42 | interface WorldCloudDataItem {
43 | name?: string;
44 | value?: number | number[];
45 | textStyle?: WordCloudTextStyle;
46 | emphasis?: {
47 | textStyle?: WordCloudTextStyle;
48 | };
49 | }
50 |
51 | declare module 'echarts/types/dist/echarts' {
52 | export interface WordCloudSeriesOption {
53 | mainType?: 'series';
54 | type?: 'wordCloud';
55 | silent?: boolean;
56 | blendMode?: string;
57 | /**
58 | * Cursor when mouse on the elements
59 | */
60 | cursor?: string;
61 |
62 | width?: number | string;
63 | height?: number | string;
64 | top?: number | string;
65 | right?: number | string;
66 | bottom?: number | string;
67 | left?: number | string;
68 |
69 | textStyle?:
70 | | WordCloudTextStyle
71 | | {
72 | color?: (params?: any) => string;
73 | };
74 | emphasis?: {
75 | focus?: 'self' | 'series' | 'none';
76 | blurScope?: 'coordinateSystem' | 'global' | 'series';
77 | textStyle?: WordCloudTextStyle;
78 | };
79 |
80 | shape?: string;
81 | maskImage?: HTMLImageElement | HTMLCanvasElement;
82 |
83 | sizeRange?: number[];
84 | rotationRange?: number[];
85 | rotationStep?: number;
86 |
87 | gridSize?: number;
88 |
89 | drawOutOfBound?: boolean;
90 | layoutAnimation?: boolean;
91 |
92 | data?: WorldCloudDataItem[];
93 | }
94 |
95 | interface RegisteredSeriesOption {
96 | wordCloud: WordCloudSeriesOption;
97 | }
98 | }
99 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | import './src/wordCloud';
2 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "echarts-wordcloud",
3 | "version": "2.1.0",
4 | "description": "ECharts wordcloud extension based on wordcloud2.js",
5 | "main": "index.js",
6 | "author": "",
7 | "license": "ISC",
8 | "scripts": {
9 | "dev": "npx webpack --mode development --watch",
10 | "build": "npx webpack --mode development",
11 | "release": "npx webpack --mode production && npx webpack --mode development"
12 | },
13 | "peerDependencies": {
14 | "echarts": "^5.0.1"
15 | },
16 | "repository": {
17 | "type": "git",
18 | "url": "https://github.com/ecomfe/echarts-wordcloud.git"
19 | },
20 | "devDependencies": {
21 | "echarts": "^5.4.0",
22 | "prettier": "^2.5.1",
23 | "webpack": "^5.11.1",
24 | "webpack-cli": "^4.3.1"
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/WordCloudSeries.js:
--------------------------------------------------------------------------------
1 | import * as echarts from 'echarts/lib/echarts';
2 |
3 | echarts.extendSeriesModel({
4 | type: 'series.wordCloud',
5 |
6 | visualStyleAccessPath: 'textStyle',
7 | visualStyleMapper: function (model) {
8 | return {
9 | fill: model.get('color')
10 | };
11 | },
12 | visualDrawType: 'fill',
13 |
14 | optionUpdated: function () {
15 | var option = this.option;
16 | option.gridSize = Math.max(Math.floor(option.gridSize), 4);
17 | },
18 |
19 | getInitialData: function (option, ecModel) {
20 | var dimensions = echarts.helper.createDimensions(option.data, {
21 | coordDimensions: ['value']
22 | });
23 | var list = new echarts.List(dimensions, this);
24 | list.initData(option.data);
25 | return list;
26 | },
27 |
28 | // Most of options are from https://github.com/timdream/wordcloud2.js/blob/gh-pages/API.md
29 | defaultOption: {
30 | maskImage: null,
31 |
32 | // Shape can be 'circle', 'cardioid', 'diamond', 'triangle-forward', 'triangle', 'pentagon', 'star'
33 | shape: 'circle',
34 | keepAspect: false,
35 |
36 | left: 'center',
37 |
38 | top: 'center',
39 |
40 | width: '70%',
41 |
42 | height: '80%',
43 |
44 | sizeRange: [12, 60],
45 |
46 | rotationRange: [-90, 90],
47 |
48 | rotationStep: 45,
49 |
50 | gridSize: 8,
51 |
52 | drawOutOfBound: false,
53 | shrinkToFit: false,
54 |
55 | textStyle: {
56 | fontWeight: 'normal'
57 | }
58 | }
59 | });
60 |
--------------------------------------------------------------------------------
/src/WordCloudView.js:
--------------------------------------------------------------------------------
1 | import * as echarts from 'echarts/lib/echarts';
2 |
3 | echarts.extendChartView({
4 | type: 'wordCloud',
5 |
6 | render: function (seriesModel, ecModel, api) {
7 | var group = this.group;
8 | group.removeAll();
9 |
10 | var data = seriesModel.getData();
11 |
12 | var gridSize = seriesModel.get('gridSize');
13 |
14 | seriesModel.layoutInstance.ondraw = function (text, size, dataIdx, drawn) {
15 | var itemModel = data.getItemModel(dataIdx);
16 | var textStyleModel = itemModel.getModel('textStyle');
17 |
18 | var textEl = new echarts.graphic.Text({
19 | style: echarts.helper.createTextStyle(textStyleModel),
20 | scaleX: 1 / drawn.info.mu,
21 | scaleY: 1 / drawn.info.mu,
22 | x: (drawn.gx + drawn.info.gw / 2) * gridSize,
23 | y: (drawn.gy + drawn.info.gh / 2) * gridSize,
24 | rotation: drawn.rot
25 | });
26 | textEl.setStyle({
27 | x: drawn.info.fillTextOffsetX,
28 | y: drawn.info.fillTextOffsetY + size * 0.5,
29 | text: text,
30 | verticalAlign: 'middle',
31 | fill: data.getItemVisual(dataIdx, 'style').fill,
32 | fontSize: size
33 | });
34 |
35 | group.add(textEl);
36 |
37 | data.setItemGraphicEl(dataIdx, textEl);
38 |
39 | textEl.ensureState('emphasis').style = echarts.helper.createTextStyle(
40 | itemModel.getModel(['emphasis', 'textStyle']),
41 | {
42 | state: 'emphasis'
43 | }
44 | );
45 | textEl.ensureState('blur').style = echarts.helper.createTextStyle(
46 | itemModel.getModel(['blur', 'textStyle']),
47 | {
48 | state: 'blur'
49 | }
50 | );
51 |
52 | echarts.helper.enableHoverEmphasis(
53 | textEl,
54 | itemModel.get(['emphasis', 'focus']),
55 | itemModel.get(['emphasis', 'blurScope'])
56 | );
57 |
58 | textEl.stateTransition = {
59 | duration: seriesModel.get('animation')
60 | ? seriesModel.get(['stateAnimation', 'duration'])
61 | : 0,
62 | easing: seriesModel.get(['stateAnimation', 'easing'])
63 | };
64 | // TODO
65 | textEl.__highDownDispatcher = true;
66 | };
67 |
68 | this._model = seriesModel;
69 | },
70 |
71 | remove: function () {
72 | this.group.removeAll();
73 |
74 | this._model.layoutInstance.dispose();
75 | },
76 |
77 | dispose: function () {
78 | this._model.layoutInstance.dispose();
79 | }
80 | });
81 |
--------------------------------------------------------------------------------
/src/layout.js:
--------------------------------------------------------------------------------
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 |
9 | 'use strict';
10 |
11 | // setImmediate
12 | if (!window.setImmediate) {
13 | window.setImmediate = (function setupSetImmediate() {
14 | return (
15 | window.msSetImmediate ||
16 | window.webkitSetImmediate ||
17 | window.mozSetImmediate ||
18 | window.oSetImmediate ||
19 | (function setupSetZeroTimeout() {
20 | if (!window.postMessage || !window.addEventListener) {
21 | return null;
22 | }
23 |
24 | var callbacks = [undefined];
25 | var message = 'zero-timeout-message';
26 |
27 | // Like setTimeout, but only takes a function argument. There's
28 | // no time argument (always zero) and no arguments (you have to
29 | // use a closure).
30 | var setZeroTimeout = function setZeroTimeout(callback) {
31 | var id = callbacks.length;
32 | callbacks.push(callback);
33 | window.postMessage(message + id.toString(36), '*');
34 |
35 | return id;
36 | };
37 |
38 | window.addEventListener(
39 | 'message',
40 | function setZeroTimeoutMessage(evt) {
41 | // Skipping checking event source, retarded IE confused this window
42 | // object with another in the presence of iframe
43 | if (
44 | typeof evt.data !== 'string' ||
45 | evt.data.substr(0, message.length) !== message /* ||
46 | evt.source !== window */
47 | ) {
48 | return;
49 | }
50 |
51 | evt.stopImmediatePropagation();
52 |
53 | var id = parseInt(evt.data.substr(message.length), 36);
54 | if (!callbacks[id]) {
55 | return;
56 | }
57 |
58 | callbacks[id]();
59 | callbacks[id] = undefined;
60 | },
61 | true
62 | );
63 |
64 | /* specify clearImmediate() here since we need the scope */
65 | window.clearImmediate = function clearZeroTimeout(id) {
66 | if (!callbacks[id]) {
67 | return;
68 | }
69 |
70 | callbacks[id] = undefined;
71 | };
72 |
73 | return setZeroTimeout;
74 | })() ||
75 | // fallback
76 | function setImmediateFallback(fn) {
77 | window.setTimeout(fn, 0);
78 | }
79 | );
80 | })();
81 | }
82 |
83 | if (!window.clearImmediate) {
84 | window.clearImmediate = (function setupClearImmediate() {
85 | return (
86 | window.msClearImmediate ||
87 | window.webkitClearImmediate ||
88 | window.mozClearImmediate ||
89 | window.oClearImmediate ||
90 | // "clearZeroTimeout" is implement on the previous block ||
91 | // fallback
92 | function clearImmediateFallback(timer) {
93 | window.clearTimeout(timer);
94 | }
95 | );
96 | })();
97 | }
98 |
99 | // Check if WordCloud can run on this browser
100 | var isSupported = (function isSupported() {
101 | var canvas = document.createElement('canvas');
102 | if (!canvas || !canvas.getContext) {
103 | return false;
104 | }
105 |
106 | var ctx = canvas.getContext('2d');
107 | if (!ctx) {
108 | return false;
109 | }
110 | if (!ctx.getImageData) {
111 | return false;
112 | }
113 | if (!ctx.fillText) {
114 | return false;
115 | }
116 |
117 | if (!Array.prototype.some) {
118 | return false;
119 | }
120 | if (!Array.prototype.push) {
121 | return false;
122 | }
123 |
124 | return true;
125 | })();
126 |
127 | // Find out if the browser impose minium font size by
128 | // drawing small texts on a canvas and measure it's width.
129 | var minFontSize = (function getMinFontSize() {
130 | if (!isSupported) {
131 | return;
132 | }
133 |
134 | var ctx = document.createElement('canvas').getContext('2d');
135 |
136 | // start from 20
137 | var size = 20;
138 |
139 | // two sizes to measure
140 | var hanWidth, mWidth;
141 |
142 | while (size) {
143 | ctx.font = size.toString(10) + 'px sans-serif';
144 | if (
145 | ctx.measureText('\uFF37').width === hanWidth &&
146 | ctx.measureText('m').width === mWidth
147 | ) {
148 | return size + 1;
149 | }
150 |
151 | hanWidth = ctx.measureText('\uFF37').width;
152 | mWidth = ctx.measureText('m').width;
153 |
154 | size--;
155 | }
156 |
157 | return 0;
158 | })();
159 |
160 | var getItemExtraData = function (item) {
161 | if (Array.isArray(item)) {
162 | var itemCopy = item.slice();
163 | // remove data we already have (word and weight)
164 | itemCopy.splice(0, 2);
165 | return itemCopy;
166 | } else {
167 | return [];
168 | }
169 | };
170 |
171 | // Based on http://jsfromhell.com/array/shuffle
172 | var shuffleArray = function shuffleArray(arr) {
173 | for (var j, x, i = arr.length; i; ) {
174 | j = Math.floor(Math.random() * i);
175 | x = arr[--i];
176 | arr[i] = arr[j];
177 | arr[j] = x;
178 | }
179 | return arr;
180 | };
181 |
182 | var timer = {};
183 | var WordCloud = function WordCloud(elements, options) {
184 | if (!isSupported) {
185 | return;
186 | }
187 |
188 | var timerId = Math.floor(Math.random() * Date.now());
189 |
190 | if (!Array.isArray(elements)) {
191 | elements = [elements];
192 | }
193 |
194 | elements.forEach(function (el, i) {
195 | if (typeof el === 'string') {
196 | elements[i] = document.getElementById(el);
197 | if (!elements[i]) {
198 | throw new Error('The element id specified is not found.');
199 | }
200 | } else if (!el.tagName && !el.appendChild) {
201 | throw new Error(
202 | 'You must pass valid HTML elements, or ID of the element.'
203 | );
204 | }
205 | });
206 |
207 | /* Default values to be overwritten by options object */
208 | var settings = {
209 | list: [],
210 | fontFamily:
211 | '"Trebuchet MS", "Heiti TC", "微軟正黑體", ' +
212 | '"Arial Unicode MS", "Droid Fallback Sans", sans-serif',
213 | fontWeight: 'normal',
214 | color: 'random-dark',
215 | minSize: 0, // 0 to disable
216 | weightFactor: 1,
217 | clearCanvas: true,
218 | backgroundColor: '#fff', // opaque white = rgba(255, 255, 255, 1)
219 |
220 | gridSize: 8,
221 | drawOutOfBound: false,
222 | shrinkToFit: false,
223 | origin: null,
224 |
225 | drawMask: false,
226 | maskColor: 'rgba(255,0,0,0.3)',
227 | maskGapWidth: 0.3,
228 |
229 | layoutAnimation: true,
230 |
231 | wait: 0,
232 | abortThreshold: 0, // disabled
233 | abort: function noop() {},
234 |
235 | minRotation: -Math.PI / 2,
236 | maxRotation: Math.PI / 2,
237 | rotationStep: 0.1,
238 |
239 | shuffle: true,
240 | rotateRatio: 0.1,
241 |
242 | shape: 'circle',
243 | ellipticity: 0.65,
244 |
245 | classes: null,
246 |
247 | hover: null,
248 | click: null
249 | };
250 |
251 | if (options) {
252 | for (var key in options) {
253 | if (key in settings) {
254 | settings[key] = options[key];
255 | }
256 | }
257 | }
258 |
259 | /* Convert weightFactor into a function */
260 | if (typeof settings.weightFactor !== 'function') {
261 | var factor = settings.weightFactor;
262 | settings.weightFactor = function weightFactor(pt) {
263 | return pt * factor; // in px
264 | };
265 | }
266 |
267 | /* Convert shape into a function */
268 | if (typeof settings.shape !== 'function') {
269 | switch (settings.shape) {
270 | case 'circle':
271 | /* falls through */
272 | default:
273 | // 'circle' is the default and a shortcut in the code loop.
274 | settings.shape = 'circle';
275 | break;
276 |
277 | case 'cardioid':
278 | settings.shape = function shapeCardioid(theta) {
279 | return 1 - Math.sin(theta);
280 | };
281 | break;
282 |
283 | /*
284 | To work out an X-gon, one has to calculate "m",
285 | where 1/(cos(2*PI/X)+m*sin(2*PI/X)) = 1/(cos(0)+m*sin(0))
286 | http://www.wolframalpha.com/input/?i=1%2F%28cos%282*PI%2FX%29%2Bm*sin%28
287 | 2*PI%2FX%29%29+%3D+1%2F%28cos%280%29%2Bm*sin%280%29%29
288 | Copy the solution into polar equation r = 1/(cos(t') + m*sin(t'))
289 | where t' equals to mod(t, 2PI/X);
290 | */
291 |
292 | case 'diamond':
293 | // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
294 | // %28t%2C+PI%2F2%29%29%2Bsin%28mod+%28t%2C+PI%2F2%29%29%29%2C+t+%3D
295 | // +0+..+2*PI
296 | settings.shape = function shapeSquare(theta) {
297 | var thetaPrime = theta % ((2 * Math.PI) / 4);
298 | return 1 / (Math.cos(thetaPrime) + Math.sin(thetaPrime));
299 | };
300 | break;
301 |
302 | case 'square':
303 | // http://www.wolframalpha.com/input/?i=plot+r+%3D+min(1%2Fabs(cos(t
304 | // )),1%2Fabs(sin(t)))),+t+%3D+0+..+2*PI
305 | settings.shape = function shapeSquare(theta) {
306 | return Math.min(
307 | 1 / Math.abs(Math.cos(theta)),
308 | 1 / Math.abs(Math.sin(theta))
309 | );
310 | };
311 | break;
312 |
313 | case 'triangle-forward':
314 | // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
315 | // %28t%2C+2*PI%2F3%29%29%2Bsqrt%283%29sin%28mod+%28t%2C+2*PI%2F3%29
316 | // %29%29%2C+t+%3D+0+..+2*PI
317 | settings.shape = function shapeTriangle(theta) {
318 | var thetaPrime = theta % ((2 * Math.PI) / 3);
319 | return (
320 | 1 / (Math.cos(thetaPrime) + Math.sqrt(3) * Math.sin(thetaPrime))
321 | );
322 | };
323 | break;
324 |
325 | case 'triangle':
326 | case 'triangle-upright':
327 | settings.shape = function shapeTriangle(theta) {
328 | var thetaPrime = (theta + (Math.PI * 3) / 2) % ((2 * Math.PI) / 3);
329 | return (
330 | 1 / (Math.cos(thetaPrime) + Math.sqrt(3) * Math.sin(thetaPrime))
331 | );
332 | };
333 | break;
334 |
335 | case 'pentagon':
336 | settings.shape = function shapePentagon(theta) {
337 | var thetaPrime = (theta + 0.955) % ((2 * Math.PI) / 5);
338 | return 1 / (Math.cos(thetaPrime) + 0.726543 * Math.sin(thetaPrime));
339 | };
340 | break;
341 |
342 | case 'star':
343 | settings.shape = function shapeStar(theta) {
344 | var thetaPrime = (theta + 0.955) % ((2 * Math.PI) / 10);
345 | if (
346 | ((theta + 0.955) % ((2 * Math.PI) / 5)) - (2 * Math.PI) / 10 >=
347 | 0
348 | ) {
349 | return (
350 | 1 /
351 | (Math.cos((2 * Math.PI) / 10 - thetaPrime) +
352 | 3.07768 * Math.sin((2 * Math.PI) / 10 - thetaPrime))
353 | );
354 | } else {
355 | return 1 / (Math.cos(thetaPrime) + 3.07768 * Math.sin(thetaPrime));
356 | }
357 | };
358 | break;
359 | }
360 | }
361 |
362 | /* Make sure gridSize is a whole number and is not smaller than 4px */
363 | settings.gridSize = Math.max(Math.floor(settings.gridSize), 4);
364 |
365 | /* shorthand */
366 | var g = settings.gridSize;
367 | var maskRectWidth = g - settings.maskGapWidth;
368 |
369 | /* normalize rotation settings */
370 | var rotationRange = Math.abs(settings.maxRotation - settings.minRotation);
371 | var minRotation = Math.min(settings.maxRotation, settings.minRotation);
372 | var rotationStep = settings.rotationStep;
373 |
374 | /* information/object available to all functions, set when start() */
375 | var grid, // 2d array containing filling information
376 | ngx,
377 | ngy, // width and height of the grid
378 | center, // position of the center of the cloud
379 | maxRadius;
380 |
381 | /* timestamp for measuring each putWord() action */
382 | var escapeTime;
383 |
384 | /* function for getting the color of the text */
385 | var getTextColor;
386 | function randomHslColor(min, max) {
387 | return (
388 | 'hsl(' +
389 | (Math.random() * 360).toFixed() +
390 | ',' +
391 | (Math.random() * 30 + 70).toFixed() +
392 | '%,' +
393 | (Math.random() * (max - min) + min).toFixed() +
394 | '%)'
395 | );
396 | }
397 | switch (settings.color) {
398 | case 'random-dark':
399 | getTextColor = function getRandomDarkColor() {
400 | return randomHslColor(10, 50);
401 | };
402 | break;
403 |
404 | case 'random-light':
405 | getTextColor = function getRandomLightColor() {
406 | return randomHslColor(50, 90);
407 | };
408 | break;
409 |
410 | default:
411 | if (typeof settings.color === 'function') {
412 | getTextColor = settings.color;
413 | }
414 | break;
415 | }
416 |
417 | /* function for getting the font-weight of the text */
418 | var getTextFontWeight;
419 | if (typeof settings.fontWeight === 'function') {
420 | getTextFontWeight = settings.fontWeight;
421 | }
422 |
423 | /* function for getting the classes of the text */
424 | var getTextClasses = null;
425 | if (typeof settings.classes === 'function') {
426 | getTextClasses = settings.classes;
427 | }
428 |
429 | /* Interactive */
430 | var interactive = false;
431 | var infoGrid = [];
432 | var hovered;
433 |
434 | var getInfoGridFromMouseTouchEvent = function getInfoGridFromMouseTouchEvent(
435 | evt
436 | ) {
437 | var canvas = evt.currentTarget;
438 | var rect = canvas.getBoundingClientRect();
439 | var clientX;
440 | var clientY;
441 | /** Detect if touches are available */
442 | if (evt.touches) {
443 | clientX = evt.touches[0].clientX;
444 | clientY = evt.touches[0].clientY;
445 | } else {
446 | clientX = evt.clientX;
447 | clientY = evt.clientY;
448 | }
449 | var eventX = clientX - rect.left;
450 | var eventY = clientY - rect.top;
451 |
452 | var x = Math.floor((eventX * (canvas.width / rect.width || 1)) / g);
453 | var y = Math.floor((eventY * (canvas.height / rect.height || 1)) / g);
454 |
455 | if (!infoGrid[x]) {
456 | return null
457 | }
458 |
459 | return infoGrid[x][y];
460 | };
461 |
462 | var wordcloudhover = function wordcloudhover(evt) {
463 | var info = getInfoGridFromMouseTouchEvent(evt);
464 |
465 | if (hovered === info) {
466 | return;
467 | }
468 |
469 | hovered = info;
470 | if (!info) {
471 | settings.hover(undefined, undefined, evt);
472 |
473 | return;
474 | }
475 |
476 | settings.hover(info.item, info.dimension, evt);
477 | };
478 |
479 | var wordcloudclick = function wordcloudclick(evt) {
480 | var info = getInfoGridFromMouseTouchEvent(evt);
481 | if (!info) {
482 | return;
483 | }
484 |
485 | settings.click(info.item, info.dimension, evt);
486 | evt.preventDefault();
487 | };
488 |
489 | /* Get points on the grid for a given radius away from the center */
490 | var pointsAtRadius = [];
491 | var getPointsAtRadius = function getPointsAtRadius(radius) {
492 | if (pointsAtRadius[radius]) {
493 | return pointsAtRadius[radius];
494 | }
495 |
496 | // Look for these number of points on each radius
497 | var T = radius * 8;
498 |
499 | // Getting all the points at this radius
500 | var t = T;
501 | var points = [];
502 |
503 | if (radius === 0) {
504 | points.push([center[0], center[1], 0]);
505 | }
506 |
507 | while (t--) {
508 | // distort the radius to put the cloud in shape
509 | var rx = 1;
510 | if (settings.shape !== 'circle') {
511 | rx = settings.shape((t / T) * 2 * Math.PI); // 0 to 1
512 | }
513 |
514 | // Push [x, y, t]; t is used solely for getTextColor()
515 | points.push([
516 | center[0] + radius * rx * Math.cos((-t / T) * 2 * Math.PI),
517 | center[1] +
518 | radius * rx * Math.sin((-t / T) * 2 * Math.PI) * settings.ellipticity,
519 | (t / T) * 2 * Math.PI
520 | ]);
521 | }
522 |
523 | pointsAtRadius[radius] = points;
524 | return points;
525 | };
526 |
527 | /* Return true if we had spent too much time */
528 | var exceedTime = function exceedTime() {
529 | return (
530 | settings.abortThreshold > 0 &&
531 | new Date().getTime() - escapeTime > settings.abortThreshold
532 | );
533 | };
534 |
535 | /* Get the deg of rotation according to settings, and luck. */
536 | var getRotateDeg = function getRotateDeg() {
537 | if (settings.rotateRatio === 0) {
538 | return 0;
539 | }
540 |
541 | if (Math.random() > settings.rotateRatio) {
542 | return 0;
543 | }
544 |
545 | if (rotationRange === 0) {
546 | return minRotation;
547 | }
548 |
549 | return minRotation + Math.round(Math.random() * rotationRange / rotationStep) * rotationStep;
550 | };
551 |
552 | var getTextInfo = function getTextInfo(
553 | word,
554 | weight,
555 | rotateDeg,
556 | extraDataArray
557 | ) {
558 | // calculate the acutal font size
559 | // fontSize === 0 means weightFactor function wants the text skipped,
560 | // and size < minSize means we cannot draw the text.
561 | var debug = false;
562 | var fontSize = settings.weightFactor(weight);
563 | if (fontSize <= settings.minSize) {
564 | return false;
565 | }
566 |
567 | // Scale factor here is to make sure fillText is not limited by
568 | // the minium font size set by browser.
569 | // It will always be 1 or 2n.
570 | var mu = 1;
571 | if (fontSize < minFontSize) {
572 | mu = (function calculateScaleFactor() {
573 | var mu = 2;
574 | while (mu * fontSize < minFontSize) {
575 | mu += 2;
576 | }
577 | return mu;
578 | })();
579 | }
580 |
581 | // Get fontWeight that will be used to set fctx.font
582 | var fontWeight;
583 | if (getTextFontWeight) {
584 | fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray);
585 | } else {
586 | fontWeight = settings.fontWeight;
587 | }
588 |
589 | var fcanvas = document.createElement('canvas');
590 | var fctx = fcanvas.getContext('2d', { willReadFrequently: true });
591 |
592 | fctx.font =
593 | fontWeight +
594 | ' ' +
595 | (fontSize * mu).toString(10) +
596 | 'px ' +
597 | settings.fontFamily;
598 |
599 | // Estimate the dimension of the text with measureText().
600 | var fw = fctx.measureText(word).width / mu;
601 | var fh =
602 | Math.max(
603 | fontSize * mu,
604 | fctx.measureText('m').width,
605 | fctx.measureText('\uFF37').width
606 | ) / mu;
607 |
608 | // Create a boundary box that is larger than our estimates,
609 | // so text don't get cut of (it sill might)
610 | var boxWidth = fw + fh * 2;
611 | var boxHeight = fh * 3;
612 | var fgw = Math.ceil(boxWidth / g);
613 | var fgh = Math.ceil(boxHeight / g);
614 | boxWidth = fgw * g;
615 | boxHeight = fgh * g;
616 |
617 | // Calculate the proper offsets to make the text centered at
618 | // the preferred position.
619 |
620 | // This is simply half of the width.
621 | var fillTextOffsetX = -fw / 2;
622 | // Instead of moving the box to the exact middle of the preferred
623 | // position, for Y-offset we move 0.4 instead, so Latin alphabets look
624 | // vertical centered.
625 | var fillTextOffsetY = -fh * 0.4;
626 |
627 | // Calculate the actual dimension of the canvas, considering the rotation.
628 | var cgh = Math.ceil(
629 | (boxWidth * Math.abs(Math.sin(rotateDeg)) +
630 | boxHeight * Math.abs(Math.cos(rotateDeg))) /
631 | g
632 | );
633 | var cgw = Math.ceil(
634 | (boxWidth * Math.abs(Math.cos(rotateDeg)) +
635 | boxHeight * Math.abs(Math.sin(rotateDeg))) /
636 | g
637 | );
638 | var width = cgw * g;
639 | var height = cgh * g;
640 |
641 | fcanvas.setAttribute('width', width);
642 | fcanvas.setAttribute('height', height);
643 |
644 | if (debug) {
645 | // Attach fcanvas to the DOM
646 | document.body.appendChild(fcanvas);
647 | // Save it's state so that we could restore and draw the grid correctly.
648 | fctx.save();
649 | }
650 |
651 | // Scale the canvas with |mu|.
652 | fctx.scale(1 / mu, 1 / mu);
653 | fctx.translate((width * mu) / 2, (height * mu) / 2);
654 | fctx.rotate(-rotateDeg);
655 |
656 | // Once the width/height is set, ctx info will be reset.
657 | // Set it again here.
658 | fctx.font =
659 | fontWeight +
660 | ' ' +
661 | (fontSize * mu).toString(10) +
662 | 'px ' +
663 | settings.fontFamily;
664 |
665 | // Fill the text into the fcanvas.
666 | // XXX: We cannot because textBaseline = 'top' here because
667 | // Firefox and Chrome uses different default line-height for canvas.
668 | // Please read https://bugzil.la/737852#c6.
669 | // Here, we use textBaseline = 'middle' and draw the text at exactly
670 | // 0.5 * fontSize lower.
671 | fctx.fillStyle = '#000';
672 | fctx.textBaseline = 'middle';
673 | fctx.fillText(
674 | word,
675 | fillTextOffsetX * mu,
676 | (fillTextOffsetY + fontSize * 0.5) * mu
677 | );
678 |
679 | // Get the pixels of the text
680 | var imageData = fctx.getImageData(0, 0, width, height).data;
681 |
682 | if (exceedTime()) {
683 | return false;
684 | }
685 |
686 | if (debug) {
687 | // Draw the box of the original estimation
688 | fctx.strokeRect(fillTextOffsetX * mu, fillTextOffsetY, fw * mu, fh * mu);
689 | fctx.restore();
690 | }
691 |
692 | // Read the pixels and save the information to the occupied array
693 | var occupied = [];
694 | var gx = cgw;
695 | var gy, x, y;
696 | var bounds = [cgh / 2, cgw / 2, cgh / 2, cgw / 2];
697 | while (gx--) {
698 | gy = cgh;
699 | while (gy--) {
700 | y = g;
701 | /* eslint no-labels: ['error', { 'allowLoop': true }] */
702 | singleGridLoop: while (y--) {
703 | x = g;
704 | while (x--) {
705 | if (imageData[((gy * g + y) * width + (gx * g + x)) * 4 + 3]) {
706 | occupied.push([gx, gy]);
707 |
708 | if (gx < bounds[3]) {
709 | bounds[3] = gx;
710 | }
711 | if (gx > bounds[1]) {
712 | bounds[1] = gx;
713 | }
714 | if (gy < bounds[0]) {
715 | bounds[0] = gy;
716 | }
717 | if (gy > bounds[2]) {
718 | bounds[2] = gy;
719 | }
720 |
721 | if (debug) {
722 | fctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
723 | fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5);
724 | }
725 | break singleGridLoop;
726 | }
727 | }
728 | }
729 | if (debug) {
730 | fctx.fillStyle = 'rgba(0, 0, 255, 0.5)';
731 | fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5);
732 | }
733 | }
734 | }
735 |
736 | if (debug) {
737 | fctx.fillStyle = 'rgba(0, 255, 0, 0.5)';
738 | fctx.fillRect(
739 | bounds[3] * g,
740 | bounds[0] * g,
741 | (bounds[1] - bounds[3] + 1) * g,
742 | (bounds[2] - bounds[0] + 1) * g
743 | );
744 | }
745 |
746 | // Return information needed to create the text on the real canvas
747 | return {
748 | mu: mu,
749 | occupied: occupied,
750 | bounds: bounds,
751 | gw: cgw,
752 | gh: cgh,
753 | fillTextOffsetX: fillTextOffsetX,
754 | fillTextOffsetY: fillTextOffsetY,
755 | fillTextWidth: fw,
756 | fillTextHeight: fh,
757 | fontSize: fontSize
758 | };
759 | };
760 |
761 | /* Determine if there is room available in the given dimension */
762 | var canFitText = function canFitText(gx, gy, gw, gh, occupied) {
763 | // Go through the occupied points,
764 | // return false if the space is not available.
765 | var i = occupied.length;
766 | while (i--) {
767 | var px = gx + occupied[i][0];
768 | var py = gy + occupied[i][1];
769 |
770 | if (px >= ngx || py >= ngy || px < 0 || py < 0) {
771 | if (!settings.drawOutOfBound) {
772 | return false;
773 | }
774 | continue;
775 | }
776 |
777 | if (!grid[px][py]) {
778 | return false;
779 | }
780 | }
781 | return true;
782 | };
783 |
784 | /* Actually draw the text on the grid */
785 | var drawText = function drawText(
786 | gx,
787 | gy,
788 | info,
789 | word,
790 | weight,
791 | distance,
792 | theta,
793 | rotateDeg,
794 | attributes,
795 | extraDataArray
796 | ) {
797 | var fontSize = info.fontSize;
798 | var color;
799 | if (getTextColor) {
800 | color = getTextColor(
801 | word,
802 | weight,
803 | fontSize,
804 | distance,
805 | theta,
806 | extraDataArray
807 | );
808 | } else {
809 | color = settings.color;
810 | }
811 |
812 | // get fontWeight that will be used to set ctx.font and font style rule
813 | var fontWeight;
814 | if (getTextFontWeight) {
815 | fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray);
816 | } else {
817 | fontWeight = settings.fontWeight;
818 | }
819 |
820 | var classes;
821 | if (getTextClasses) {
822 | classes = getTextClasses(word, weight, fontSize, extraDataArray);
823 | } else {
824 | classes = settings.classes;
825 | }
826 |
827 | elements.forEach(function (el) {
828 | if (el.getContext) {
829 | var ctx = el.getContext('2d');
830 | var mu = info.mu;
831 |
832 | // Save the current state before messing it
833 | ctx.save();
834 | ctx.scale(1 / mu, 1 / mu);
835 |
836 | ctx.font =
837 | fontWeight +
838 | ' ' +
839 | (fontSize * mu).toString(10) +
840 | 'px ' +
841 | settings.fontFamily;
842 | ctx.fillStyle = color;
843 |
844 | // Translate the canvas position to the origin coordinate of where
845 | // the text should be put.
846 | ctx.translate((gx + info.gw / 2) * g * mu, (gy + info.gh / 2) * g * mu);
847 |
848 | if (rotateDeg !== 0) {
849 | ctx.rotate(-rotateDeg);
850 | }
851 |
852 | // Finally, fill the text.
853 |
854 | // XXX: We cannot because textBaseline = 'top' here because
855 | // Firefox and Chrome uses different default line-height for canvas.
856 | // Please read https://bugzil.la/737852#c6.
857 | // Here, we use textBaseline = 'middle' and draw the text at exactly
858 | // 0.5 * fontSize lower.
859 | ctx.textBaseline = 'middle';
860 | ctx.fillText(
861 | word,
862 | info.fillTextOffsetX * mu,
863 | (info.fillTextOffsetY + fontSize * 0.5) * mu
864 | );
865 |
866 | // The below box is always matches how s are positioned
867 | /* ctx.strokeRect(info.fillTextOffsetX, info.fillTextOffsetY,
868 | info.fillTextWidth, info.fillTextHeight); */
869 |
870 | // Restore the state.
871 | ctx.restore();
872 | } else {
873 | // drawText on DIV element
874 | var span = document.createElement('span');
875 | var transformRule = '';
876 | transformRule = 'rotate(' + (-rotateDeg / Math.PI) * 180 + 'deg) ';
877 | if (info.mu !== 1) {
878 | transformRule +=
879 | 'translateX(-' +
880 | info.fillTextWidth / 4 +
881 | 'px) ' +
882 | 'scale(' +
883 | 1 / info.mu +
884 | ')';
885 | }
886 | var styleRules = {
887 | position: 'absolute',
888 | display: 'block',
889 | font:
890 | fontWeight + ' ' + fontSize * info.mu + 'px ' + settings.fontFamily,
891 | left: (gx + info.gw / 2) * g + info.fillTextOffsetX + 'px',
892 | top: (gy + info.gh / 2) * g + info.fillTextOffsetY + 'px',
893 | width: info.fillTextWidth + 'px',
894 | height: info.fillTextHeight + 'px',
895 | lineHeight: fontSize + 'px',
896 | whiteSpace: 'nowrap',
897 | transform: transformRule,
898 | webkitTransform: transformRule,
899 | msTransform: transformRule,
900 | transformOrigin: '50% 40%',
901 | webkitTransformOrigin: '50% 40%',
902 | msTransformOrigin: '50% 40%'
903 | };
904 | if (color) {
905 | styleRules.color = color;
906 | }
907 | span.textContent = word;
908 | for (var cssProp in styleRules) {
909 | span.style[cssProp] = styleRules[cssProp];
910 | }
911 | if (attributes) {
912 | for (var attribute in attributes) {
913 | span.setAttribute(attribute, attributes[attribute]);
914 | }
915 | }
916 | if (classes) {
917 | span.className += classes;
918 | }
919 | el.appendChild(span);
920 | }
921 | });
922 | };
923 |
924 | /* Help function to updateGrid */
925 | var fillGridAt = function fillGridAt(x, y, drawMask, dimension, item) {
926 | if (x >= ngx || y >= ngy || x < 0 || y < 0) {
927 | return;
928 | }
929 |
930 | grid[x][y] = false;
931 |
932 | if (drawMask) {
933 | var ctx = elements[0].getContext('2d');
934 | ctx.fillRect(x * g, y * g, maskRectWidth, maskRectWidth);
935 | }
936 |
937 | if (interactive) {
938 | infoGrid[x][y] = { item: item, dimension: dimension };
939 | }
940 | };
941 |
942 | /* Update the filling information of the given space with occupied points.
943 | Draw the mask on the canvas if necessary. */
944 | var updateGrid = function updateGrid(gx, gy, gw, gh, info, item) {
945 | var occupied = info.occupied;
946 | var drawMask = settings.drawMask;
947 | var ctx;
948 | if (drawMask) {
949 | ctx = elements[0].getContext('2d');
950 | ctx.save();
951 | ctx.fillStyle = settings.maskColor;
952 | }
953 |
954 | var dimension;
955 | if (interactive) {
956 | var bounds = info.bounds;
957 | dimension = {
958 | x: (gx + bounds[3]) * g,
959 | y: (gy + bounds[0]) * g,
960 | w: (bounds[1] - bounds[3] + 1) * g,
961 | h: (bounds[2] - bounds[0] + 1) * g
962 | };
963 | }
964 |
965 | var i = occupied.length;
966 | while (i--) {
967 | var px = gx + occupied[i][0];
968 | var py = gy + occupied[i][1];
969 |
970 | if (px >= ngx || py >= ngy || px < 0 || py < 0) {
971 | continue;
972 | }
973 |
974 | fillGridAt(px, py, drawMask, dimension, item);
975 | }
976 |
977 | if (drawMask) {
978 | ctx.restore();
979 | }
980 | };
981 |
982 | /* putWord() processes each item on the list,
983 | calculate it's size and determine it's position, and actually
984 | put it on the canvas. */
985 | var putWord = function putWord(item, loopIndex) {
986 | if (loopIndex > 20) {
987 | return null;
988 | }
989 |
990 | var word, weight, attributes;
991 | if (Array.isArray(item)) {
992 | word = item[0];
993 | weight = item[1];
994 | } else {
995 | word = item.word;
996 | weight = item.weight;
997 | attributes = item.attributes;
998 | }
999 | var rotateDeg = getRotateDeg();
1000 |
1001 | var extraDataArray = getItemExtraData(item);
1002 |
1003 | // get info needed to put the text onto the canvas
1004 | var info = getTextInfo(word, weight, rotateDeg, extraDataArray);
1005 |
1006 | // not getting the info means we shouldn't be drawing this one.
1007 | if (!info) {
1008 | return false;
1009 | }
1010 |
1011 | if (exceedTime()) {
1012 | return false;
1013 | }
1014 |
1015 | // If drawOutOfBound is set to false,
1016 | // skip the loop if we have already know the bounding box of
1017 | // word is larger than the canvas.
1018 | if (!settings.drawOutOfBound && !settings.shrinkToFit) {
1019 | var bounds = info.bounds;
1020 | if (bounds[1] - bounds[3] + 1 > ngx || bounds[2] - bounds[0] + 1 > ngy) {
1021 | return false;
1022 | }
1023 | }
1024 |
1025 | // Determine the position to put the text by
1026 | // start looking for the nearest points
1027 | var r = maxRadius + 1;
1028 |
1029 | var tryToPutWordAtPoint = function (gxy) {
1030 | var gx = Math.floor(gxy[0] - info.gw / 2);
1031 | var gy = Math.floor(gxy[1] - info.gh / 2);
1032 | var gw = info.gw;
1033 | var gh = info.gh;
1034 |
1035 | // If we cannot fit the text at this position, return false
1036 | // and go to the next position.
1037 | if (!canFitText(gx, gy, gw, gh, info.occupied)) {
1038 | return false;
1039 | }
1040 |
1041 | // Actually put the text on the canvas
1042 | drawText(
1043 | gx,
1044 | gy,
1045 | info,
1046 | word,
1047 | weight,
1048 | maxRadius - r,
1049 | gxy[2],
1050 | rotateDeg,
1051 | attributes,
1052 | extraDataArray
1053 | );
1054 |
1055 | // Mark the spaces on the grid as filled
1056 | updateGrid(gx, gy, gw, gh, info, item);
1057 |
1058 | return {
1059 | gx: gx,
1060 | gy: gy,
1061 | rot: rotateDeg,
1062 | info: info
1063 | };
1064 | };
1065 |
1066 | while (r--) {
1067 | var points = getPointsAtRadius(maxRadius - r);
1068 |
1069 | if (settings.shuffle) {
1070 | points = [].concat(points);
1071 | shuffleArray(points);
1072 | }
1073 |
1074 | // Try to fit the words by looking at each point.
1075 | // array.some() will stop and return true
1076 | // when putWordAtPoint() returns true.
1077 | for (var i = 0; i < points.length; i++) {
1078 | var res = tryToPutWordAtPoint(points[i]);
1079 | if (res) {
1080 | return res;
1081 | }
1082 | }
1083 |
1084 | // var drawn = points.some(tryToPutWordAtPoint);
1085 | // if (drawn) {
1086 | // // leave putWord() and return true
1087 | // return true;
1088 | // }
1089 | }
1090 |
1091 | if (settings.shrinkToFit) {
1092 | if (Array.isArray(item)) {
1093 | item[1] = (item[1] * 3) / 4;
1094 | } else {
1095 | item.weight = (item.weight * 3) / 4;
1096 | }
1097 | return putWord(item, loopIndex + 1);
1098 | }
1099 |
1100 | // we tried all distances but text won't fit, return null
1101 | return null;
1102 | };
1103 |
1104 | /* Send DOM event to all elements. Will stop sending event and return
1105 | if the previous one is canceled (for cancelable events). */
1106 | var sendEvent = function sendEvent(type, cancelable, details) {
1107 | if (cancelable) {
1108 | return !elements.some(function (el) {
1109 | var event = new CustomEvent(type, {
1110 | detail: details || {}
1111 | });
1112 | return !el.dispatchEvent(event);
1113 | }, this);
1114 | } else {
1115 | elements.forEach(function (el) {
1116 | var event = new CustomEvent(type, {
1117 | detail: details || {}
1118 | });
1119 | el.dispatchEvent(event);
1120 | }, this);
1121 | }
1122 | };
1123 |
1124 | /* Start drawing on a canvas */
1125 | var start = function start() {
1126 | // For dimensions, clearCanvas etc.,
1127 | // we only care about the first element.
1128 | var canvas = elements[0];
1129 |
1130 | if (canvas.getContext) {
1131 | ngx = Math.ceil(canvas.width / g);
1132 | ngy = Math.ceil(canvas.height / g);
1133 | } else {
1134 | var rect = canvas.getBoundingClientRect();
1135 | ngx = Math.ceil(rect.width / g);
1136 | ngy = Math.ceil(rect.height / g);
1137 | }
1138 |
1139 | // Sending a wordcloudstart event which cause the previous loop to stop.
1140 | // Do nothing if the event is canceled.
1141 | if (!sendEvent('wordcloudstart', true)) {
1142 | return;
1143 | }
1144 |
1145 | // Determine the center of the word cloud
1146 | center = settings.origin
1147 | ? [settings.origin[0] / g, settings.origin[1] / g]
1148 | : [ngx / 2, ngy / 2];
1149 |
1150 | // Maxium radius to look for space
1151 | maxRadius = Math.floor(Math.sqrt(ngx * ngx + ngy * ngy));
1152 |
1153 | /* Clear the canvas only if the clearCanvas is set,
1154 | if not, update the grid to the current canvas state */
1155 | grid = [];
1156 |
1157 | var gx, gy, i;
1158 | if (!canvas.getContext || settings.clearCanvas) {
1159 | elements.forEach(function (el) {
1160 | if (el.getContext) {
1161 | var ctx = el.getContext('2d');
1162 | ctx.fillStyle = settings.backgroundColor;
1163 | ctx.clearRect(0, 0, ngx * (g + 1), ngy * (g + 1));
1164 | ctx.fillRect(0, 0, ngx * (g + 1), ngy * (g + 1));
1165 | } else {
1166 | el.textContent = '';
1167 | el.style.backgroundColor = settings.backgroundColor;
1168 | el.style.position = 'relative';
1169 | }
1170 | });
1171 |
1172 | /* fill the grid with empty state */
1173 | gx = ngx;
1174 | while (gx--) {
1175 | grid[gx] = [];
1176 | gy = ngy;
1177 | while (gy--) {
1178 | grid[gx][gy] = true;
1179 | }
1180 | }
1181 | } else {
1182 | /* Determine bgPixel by creating
1183 | another canvas and fill the specified background color. */
1184 | var bctx = document.createElement('canvas').getContext('2d');
1185 |
1186 | bctx.fillStyle = settings.backgroundColor;
1187 | bctx.fillRect(0, 0, 1, 1);
1188 | var bgPixel = bctx.getImageData(0, 0, 1, 1).data;
1189 |
1190 | /* Read back the pixels of the canvas we got to tell which part of the
1191 | canvas is empty.
1192 | (no clearCanvas only works with a canvas, not divs) */
1193 | var imageData = canvas
1194 | .getContext('2d')
1195 | .getImageData(0, 0, ngx * g, ngy * g).data;
1196 |
1197 | gx = ngx;
1198 | var x, y;
1199 | while (gx--) {
1200 | grid[gx] = [];
1201 | gy = ngy;
1202 | while (gy--) {
1203 | y = g;
1204 | /* eslint no-labels: ['error', { 'allowLoop': true }] */
1205 | singleGridLoop: while (y--) {
1206 | x = g;
1207 | while (x--) {
1208 | i = 4;
1209 | while (i--) {
1210 | if (
1211 | imageData[((gy * g + y) * ngx * g + (gx * g + x)) * 4 + i] !==
1212 | bgPixel[i]
1213 | ) {
1214 | grid[gx][gy] = false;
1215 | break singleGridLoop;
1216 | }
1217 | }
1218 | }
1219 | }
1220 | if (grid[gx][gy] !== false) {
1221 | grid[gx][gy] = true;
1222 | }
1223 | }
1224 | }
1225 |
1226 | imageData = bctx = bgPixel = undefined;
1227 | }
1228 |
1229 | // fill the infoGrid with empty state if we need it
1230 | if (settings.hover || settings.click) {
1231 | interactive = true;
1232 |
1233 | /* fill the grid with empty state */
1234 | gx = ngx + 1;
1235 | while (gx--) {
1236 | infoGrid[gx] = [];
1237 | }
1238 |
1239 | if (settings.hover) {
1240 | canvas.addEventListener('mousemove', wordcloudhover);
1241 | }
1242 |
1243 | if (settings.click) {
1244 | canvas.addEventListener('click', wordcloudclick);
1245 | canvas.addEventListener('touchstart', wordcloudclick);
1246 | canvas.addEventListener('touchend', function (e) {
1247 | e.preventDefault();
1248 | });
1249 | canvas.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)';
1250 | }
1251 |
1252 | canvas.addEventListener('wordcloudstart', function stopInteraction() {
1253 | canvas.removeEventListener('wordcloudstart', stopInteraction);
1254 |
1255 | canvas.removeEventListener('mousemove', wordcloudhover);
1256 | canvas.removeEventListener('click', wordcloudclick);
1257 | hovered = undefined;
1258 | });
1259 | }
1260 |
1261 | i = 0;
1262 | var loopingFunction, stoppingFunction;
1263 | var layouting = true;
1264 | if (!settings.layoutAnimation) {
1265 | loopingFunction = function (cb) {
1266 | cb();
1267 | };
1268 | stoppingFunction = function () {
1269 | layouting = false;
1270 | };
1271 | } else if (settings.wait !== 0) {
1272 | loopingFunction = window.setTimeout;
1273 | stoppingFunction = window.clearTimeout;
1274 | } else {
1275 | loopingFunction = window.setImmediate;
1276 | stoppingFunction = window.clearImmediate;
1277 | }
1278 |
1279 | var addEventListener = function addEventListener(type, listener) {
1280 | elements.forEach(function (el) {
1281 | el.addEventListener(type, listener);
1282 | }, this);
1283 | };
1284 |
1285 | var removeEventListener = function removeEventListener(type, listener) {
1286 | elements.forEach(function (el) {
1287 | el.removeEventListener(type, listener);
1288 | }, this);
1289 | };
1290 |
1291 | var anotherWordCloudStart = function anotherWordCloudStart() {
1292 | removeEventListener('wordcloudstart', anotherWordCloudStart);
1293 | stoppingFunction(timer[timerId]);
1294 | };
1295 |
1296 | addEventListener('wordcloudstart', anotherWordCloudStart);
1297 |
1298 | // At least wait the following code before call the first iteration.
1299 | timer[timerId] = (settings.layoutAnimation ? loopingFunction : setTimeout)(
1300 | function loop() {
1301 | if (!layouting) {
1302 | return;
1303 | }
1304 | if (i >= settings.list.length) {
1305 | stoppingFunction(timer[timerId]);
1306 | sendEvent('wordcloudstop', false);
1307 | removeEventListener('wordcloudstart', anotherWordCloudStart);
1308 | delete timer[timerId];
1309 | return;
1310 | }
1311 | escapeTime = new Date().getTime();
1312 | var drawn = putWord(settings.list[i], 0);
1313 | var canceled = !sendEvent('wordclouddrawn', true, {
1314 | item: settings.list[i],
1315 | drawn: drawn
1316 | });
1317 | if (exceedTime() || canceled) {
1318 | stoppingFunction(timer[timerId]);
1319 | settings.abort();
1320 | sendEvent('wordcloudabort', false);
1321 | sendEvent('wordcloudstop', false);
1322 | removeEventListener('wordcloudstart', anotherWordCloudStart);
1323 | return;
1324 | }
1325 | i++;
1326 | timer[timerId] = loopingFunction(loop, settings.wait);
1327 | },
1328 | settings.wait
1329 | );
1330 | };
1331 |
1332 | // All set, start the drawing
1333 | start();
1334 | };
1335 |
1336 | WordCloud.isSupported = isSupported;
1337 | WordCloud.minFontSize = minFontSize;
1338 |
1339 | export default WordCloud;
1340 |
--------------------------------------------------------------------------------
/src/wordCloud.js:
--------------------------------------------------------------------------------
1 | import * as echarts from 'echarts/lib/echarts';
2 |
3 | import './WordCloudSeries';
4 | import './WordCloudView';
5 |
6 | import wordCloudLayoutHelper from './layout';
7 |
8 | if (!wordCloudLayoutHelper.isSupported) {
9 | throw new Error('Sorry your browser not support wordCloud');
10 | }
11 |
12 | // https://github.com/timdream/wordcloud2.js/blob/c236bee60436e048949f9becc4f0f67bd832dc5c/index.js#L233
13 | function updateCanvasMask(maskCanvas) {
14 | var ctx = maskCanvas.getContext('2d');
15 | var imageData = ctx.getImageData(0, 0, maskCanvas.width, maskCanvas.height);
16 | var newImageData = ctx.createImageData(imageData);
17 |
18 | var toneSum = 0;
19 | var toneCnt = 0;
20 | for (var i = 0; i < imageData.data.length; i += 4) {
21 | var alpha = imageData.data[i + 3];
22 | if (alpha > 128) {
23 | var tone =
24 | imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2];
25 | toneSum += tone;
26 | ++toneCnt;
27 | }
28 | }
29 | var threshold = toneSum / toneCnt;
30 |
31 | for (var i = 0; i < imageData.data.length; i += 4) {
32 | var tone =
33 | imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2];
34 | var alpha = imageData.data[i + 3];
35 |
36 | if (alpha < 128 || tone > threshold) {
37 | // Area not to draw
38 | newImageData.data[i] = 0;
39 | newImageData.data[i + 1] = 0;
40 | newImageData.data[i + 2] = 0;
41 | newImageData.data[i + 3] = 0;
42 | } else {
43 | // Area to draw
44 | // The color must be same with backgroundColor
45 | newImageData.data[i] = 255;
46 | newImageData.data[i + 1] = 255;
47 | newImageData.data[i + 2] = 255;
48 | newImageData.data[i + 3] = 255;
49 | }
50 | }
51 |
52 | ctx.putImageData(newImageData, 0, 0);
53 | }
54 |
55 | echarts.registerLayout(function (ecModel, api) {
56 | ecModel.eachSeriesByType('wordCloud', function (seriesModel) {
57 | var gridRect = echarts.helper.getLayoutRect(
58 | seriesModel.getBoxLayoutParams(),
59 | {
60 | width: api.getWidth(),
61 | height: api.getHeight()
62 | }
63 | );
64 |
65 | var keepAspect = seriesModel.get('keepAspect');
66 | var maskImage = seriesModel.get('maskImage');
67 | var ratio = maskImage ? maskImage.width / maskImage.height : 1;
68 | keepAspect && adjustRectAspect(gridRect, ratio);
69 |
70 | var data = seriesModel.getData();
71 |
72 | var canvas = document.createElement('canvas');
73 | canvas.width = gridRect.width;
74 | canvas.height = gridRect.height;
75 |
76 | var ctx = canvas.getContext('2d');
77 | if (maskImage) {
78 | try {
79 | ctx.drawImage(maskImage, 0, 0, canvas.width, canvas.height);
80 | updateCanvasMask(canvas);
81 | } catch (e) {
82 | console.error('Invalid mask image');
83 | console.error(e.toString());
84 | }
85 | }
86 |
87 | var sizeRange = seriesModel.get('sizeRange');
88 | var rotationRange = seriesModel.get('rotationRange');
89 | var valueExtent = data.getDataExtent('value');
90 |
91 | var DEGREE_TO_RAD = Math.PI / 180;
92 | var gridSize = seriesModel.get('gridSize');
93 | wordCloudLayoutHelper(canvas, {
94 | list: data
95 | .mapArray('value', function (value, idx) {
96 | var itemModel = data.getItemModel(idx);
97 | return [
98 | data.getName(idx),
99 | itemModel.get('textStyle.fontSize', true) ||
100 | echarts.number.linearMap(value, valueExtent, sizeRange),
101 | idx
102 | ];
103 | })
104 | .sort(function (a, b) {
105 | // Sort from large to small in case there is no more room for more words
106 | return b[1] - a[1];
107 | }),
108 | fontFamily:
109 | seriesModel.get('textStyle.fontFamily') ||
110 | seriesModel.get('emphasis.textStyle.fontFamily') ||
111 | ecModel.get('textStyle.fontFamily'),
112 | fontWeight:
113 | seriesModel.get('textStyle.fontWeight') ||
114 | seriesModel.get('emphasis.textStyle.fontWeight') ||
115 | ecModel.get('textStyle.fontWeight'),
116 |
117 | gridSize: gridSize,
118 |
119 | ellipticity: gridRect.height / gridRect.width,
120 |
121 | minRotation: rotationRange[0] * DEGREE_TO_RAD,
122 | maxRotation: rotationRange[1] * DEGREE_TO_RAD,
123 |
124 | clearCanvas: !maskImage,
125 |
126 | rotateRatio: 1,
127 |
128 | rotationStep: seriesModel.get('rotationStep') * DEGREE_TO_RAD,
129 |
130 | drawOutOfBound: seriesModel.get('drawOutOfBound'),
131 | shrinkToFit: seriesModel.get('shrinkToFit'),
132 |
133 | layoutAnimation: seriesModel.get('layoutAnimation'),
134 |
135 | shuffle: false,
136 |
137 | shape: seriesModel.get('shape')
138 | });
139 |
140 | function onWordCloudDrawn(e) {
141 | var item = e.detail.item;
142 | if (e.detail.drawn && seriesModel.layoutInstance.ondraw) {
143 | e.detail.drawn.gx += gridRect.x / gridSize;
144 | e.detail.drawn.gy += gridRect.y / gridSize;
145 | seriesModel.layoutInstance.ondraw(
146 | item[0],
147 | item[1],
148 | item[2],
149 | e.detail.drawn
150 | );
151 | }
152 | }
153 |
154 | canvas.addEventListener('wordclouddrawn', onWordCloudDrawn);
155 |
156 | if (seriesModel.layoutInstance) {
157 | // Dispose previous
158 | seriesModel.layoutInstance.dispose();
159 | }
160 |
161 | seriesModel.layoutInstance = {
162 | ondraw: null,
163 |
164 | dispose: function () {
165 | canvas.removeEventListener('wordclouddrawn', onWordCloudDrawn);
166 | // Abort
167 | canvas.addEventListener('wordclouddrawn', function (e) {
168 | // Prevent default to cancle the event and stop the loop
169 | e.preventDefault();
170 | });
171 | }
172 | };
173 | });
174 | });
175 |
176 | echarts.registerPreprocessor(function (option) {
177 | var series = (option || {}).series;
178 | !echarts.util.isArray(series) && (series = series ? [series] : []);
179 |
180 | var compats = ['shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY'];
181 |
182 | echarts.util.each(series, function (seriesItem) {
183 | if (seriesItem && seriesItem.type === 'wordCloud') {
184 | var textStyle = seriesItem.textStyle || {};
185 |
186 | compatTextStyle(textStyle.normal);
187 | compatTextStyle(textStyle.emphasis);
188 | }
189 | });
190 |
191 | function compatTextStyle(textStyle) {
192 | textStyle &&
193 | echarts.util.each(compats, function (key) {
194 | if (textStyle.hasOwnProperty(key)) {
195 | textStyle['text' + echarts.format.capitalFirst(key)] = textStyle[key];
196 | }
197 | });
198 | }
199 | });
200 |
201 | function adjustRectAspect(gridRect, aspect) {
202 | // var outerWidth = gridRect.width + gridRect.x * 2;
203 | // var outerHeight = gridRect.height + gridRect.y * 2;
204 | var width = gridRect.width;
205 | var height = gridRect.height;
206 | if (width > height * aspect) {
207 | gridRect.x += (width - height * aspect) / 2;
208 | gridRect.width = height * aspect;
209 | } else {
210 | gridRect.y += (height - width / aspect) / 2;
211 | gridRect.height = width / aspect;
212 | }
213 | }
214 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = (env, options) => {
2 | return {
3 | entry: {
4 | 'echarts-wordcloud': __dirname + '/index.js'
5 | },
6 | output: {
7 | libraryTarget: 'umd',
8 | library: ['echarts-wordcloud'],
9 | path: __dirname + '/dist',
10 | filename: options.mode === 'production' ? '[name].min.js' : '[name].js'
11 | },
12 | optimization: {
13 | concatenateModules: true
14 | },
15 | devtool: 'source-map',
16 | externals: {
17 | 'echarts/lib/echarts': 'echarts'
18 | }
19 | };
20 | };
21 |
--------------------------------------------------------------------------------