星火管控前端
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

echarts-wordcloud.js 48KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786
  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. /***/ "./index.js":
  15. /*!******************************!*\
  16. !*** ./index.js + 4 modules ***!
  17. \******************************/
  18. /***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
  19. // ESM COMPAT FLAG
  20. __webpack_require__.r(__webpack_exports__);
  21. // EXTERNAL MODULE: external "echarts"
  22. var external_echarts_ = __webpack_require__("echarts/lib/echarts");
  23. ;// CONCATENATED MODULE: ./src/WordCloudSeries.js
  24. external_echarts_.extendSeriesModel({
  25. type: 'series.wordCloud',
  26. visualStyleAccessPath: 'textStyle',
  27. visualStyleMapper: function (model) {
  28. return {
  29. fill: model.get('color')
  30. };
  31. },
  32. visualDrawType: 'fill',
  33. optionUpdated: function () {
  34. var option = this.option;
  35. option.gridSize = Math.max(Math.floor(option.gridSize), 4);
  36. },
  37. getInitialData: function (option, ecModel) {
  38. var dimensions = external_echarts_.helper.createDimensions(option.data, {
  39. coordDimensions: ['value']
  40. });
  41. var list = new external_echarts_.List(dimensions, this);
  42. list.initData(option.data);
  43. return list;
  44. },
  45. // Most of options are from https://github.com/timdream/wordcloud2.js/blob/gh-pages/API.md
  46. defaultOption: {
  47. maskImage: null,
  48. // Shape can be 'circle', 'cardioid', 'diamond', 'triangle-forward', 'triangle', 'pentagon', 'star'
  49. shape: 'circle',
  50. keepAspect: false,
  51. left: 'center',
  52. top: 'center',
  53. width: '70%',
  54. height: '80%',
  55. sizeRange: [12, 60],
  56. rotationRange: [-90, 90],
  57. rotationStep: 45,
  58. gridSize: 8,
  59. drawOutOfBound: false,
  60. shrinkToFit: false,
  61. textStyle: {
  62. fontWeight: 'normal'
  63. }
  64. }
  65. });
  66. ;// CONCATENATED MODULE: ./src/WordCloudView.js
  67. external_echarts_.extendChartView({
  68. type: 'wordCloud',
  69. render: function (seriesModel, ecModel, api) {
  70. var group = this.group;
  71. group.removeAll();
  72. var data = seriesModel.getData();
  73. var gridSize = seriesModel.get('gridSize');
  74. seriesModel.layoutInstance.ondraw = function (text, size, dataIdx, drawn) {
  75. var itemModel = data.getItemModel(dataIdx);
  76. var textStyleModel = itemModel.getModel('textStyle');
  77. var textEl = new external_echarts_.graphic.Text({
  78. style: external_echarts_.helper.createTextStyle(textStyleModel),
  79. scaleX: 1 / drawn.info.mu,
  80. scaleY: 1 / drawn.info.mu,
  81. x: (drawn.gx + drawn.info.gw / 2) * gridSize,
  82. y: (drawn.gy + drawn.info.gh / 2) * gridSize,
  83. rotation: drawn.rot
  84. });
  85. textEl.setStyle({
  86. x: drawn.info.fillTextOffsetX,
  87. y: drawn.info.fillTextOffsetY + size * 0.5,
  88. text: text,
  89. verticalAlign: 'middle',
  90. fill: data.getItemVisual(dataIdx, 'style').fill,
  91. fontSize: size
  92. });
  93. group.add(textEl);
  94. data.setItemGraphicEl(dataIdx, textEl);
  95. textEl.ensureState('emphasis').style = external_echarts_.helper.createTextStyle(
  96. itemModel.getModel(['emphasis', 'textStyle']),
  97. {
  98. state: 'emphasis'
  99. }
  100. );
  101. textEl.ensureState('blur').style = external_echarts_.helper.createTextStyle(
  102. itemModel.getModel(['blur', 'textStyle']),
  103. {
  104. state: 'blur'
  105. }
  106. );
  107. external_echarts_.helper.enableHoverEmphasis(
  108. textEl,
  109. itemModel.get(['emphasis', 'focus']),
  110. itemModel.get(['emphasis', 'blurScope'])
  111. );
  112. textEl.stateTransition = {
  113. duration: seriesModel.get('animation')
  114. ? seriesModel.get(['stateAnimation', 'duration'])
  115. : 0,
  116. easing: seriesModel.get(['stateAnimation', 'easing'])
  117. };
  118. // TODO
  119. textEl.__highDownDispatcher = true;
  120. };
  121. this._model = seriesModel;
  122. },
  123. remove: function () {
  124. this.group.removeAll();
  125. this._model.layoutInstance.dispose();
  126. },
  127. dispose: function () {
  128. this._model.layoutInstance.dispose();
  129. }
  130. });
  131. ;// CONCATENATED MODULE: ./src/layout.js
  132. /*!
  133. * wordcloud2.js
  134. * http://timdream.org/wordcloud2.js/
  135. *
  136. * Copyright 2011 - 2019 Tim Guan-tin Chien and contributors.
  137. * Released under the MIT license
  138. */
  139. // setImmediate
  140. if (!window.setImmediate) {
  141. window.setImmediate = (function setupSetImmediate() {
  142. return (
  143. window.msSetImmediate ||
  144. window.webkitSetImmediate ||
  145. window.mozSetImmediate ||
  146. window.oSetImmediate ||
  147. (function setupSetZeroTimeout() {
  148. if (!window.postMessage || !window.addEventListener) {
  149. return null;
  150. }
  151. var callbacks = [undefined];
  152. var message = 'zero-timeout-message';
  153. // Like setTimeout, but only takes a function argument. There's
  154. // no time argument (always zero) and no arguments (you have to
  155. // use a closure).
  156. var setZeroTimeout = function setZeroTimeout(callback) {
  157. var id = callbacks.length;
  158. callbacks.push(callback);
  159. window.postMessage(message + id.toString(36), '*');
  160. return id;
  161. };
  162. window.addEventListener(
  163. 'message',
  164. function setZeroTimeoutMessage(evt) {
  165. // Skipping checking event source, retarded IE confused this window
  166. // object with another in the presence of iframe
  167. if (
  168. typeof evt.data !== 'string' ||
  169. evt.data.substr(0, message.length) !== message /* ||
  170. evt.source !== window */
  171. ) {
  172. return;
  173. }
  174. evt.stopImmediatePropagation();
  175. var id = parseInt(evt.data.substr(message.length), 36);
  176. if (!callbacks[id]) {
  177. return;
  178. }
  179. callbacks[id]();
  180. callbacks[id] = undefined;
  181. },
  182. true
  183. );
  184. /* specify clearImmediate() here since we need the scope */
  185. window.clearImmediate = function clearZeroTimeout(id) {
  186. if (!callbacks[id]) {
  187. return;
  188. }
  189. callbacks[id] = undefined;
  190. };
  191. return setZeroTimeout;
  192. })() ||
  193. // fallback
  194. function setImmediateFallback(fn) {
  195. window.setTimeout(fn, 0);
  196. }
  197. );
  198. })();
  199. }
  200. if (!window.clearImmediate) {
  201. window.clearImmediate = (function setupClearImmediate() {
  202. return (
  203. window.msClearImmediate ||
  204. window.webkitClearImmediate ||
  205. window.mozClearImmediate ||
  206. window.oClearImmediate ||
  207. // "clearZeroTimeout" is implement on the previous block ||
  208. // fallback
  209. function clearImmediateFallback(timer) {
  210. window.clearTimeout(timer);
  211. }
  212. );
  213. })();
  214. }
  215. // Check if WordCloud can run on this browser
  216. var isSupported = (function isSupported() {
  217. var canvas = document.createElement('canvas');
  218. if (!canvas || !canvas.getContext) {
  219. return false;
  220. }
  221. var ctx = canvas.getContext('2d');
  222. if (!ctx) {
  223. return false;
  224. }
  225. if (!ctx.getImageData) {
  226. return false;
  227. }
  228. if (!ctx.fillText) {
  229. return false;
  230. }
  231. if (!Array.prototype.some) {
  232. return false;
  233. }
  234. if (!Array.prototype.push) {
  235. return false;
  236. }
  237. return true;
  238. })();
  239. // Find out if the browser impose minium font size by
  240. // drawing small texts on a canvas and measure it's width.
  241. var minFontSize = (function getMinFontSize() {
  242. if (!isSupported) {
  243. return;
  244. }
  245. var ctx = document.createElement('canvas').getContext('2d');
  246. // start from 20
  247. var size = 20;
  248. // two sizes to measure
  249. var hanWidth, mWidth;
  250. while (size) {
  251. ctx.font = size.toString(10) + 'px sans-serif';
  252. if (
  253. ctx.measureText('\uFF37').width === hanWidth &&
  254. ctx.measureText('m').width === mWidth
  255. ) {
  256. return size + 1;
  257. }
  258. hanWidth = ctx.measureText('\uFF37').width;
  259. mWidth = ctx.measureText('m').width;
  260. size--;
  261. }
  262. return 0;
  263. })();
  264. var getItemExtraData = function (item) {
  265. if (Array.isArray(item)) {
  266. var itemCopy = item.slice();
  267. // remove data we already have (word and weight)
  268. itemCopy.splice(0, 2);
  269. return itemCopy;
  270. } else {
  271. return [];
  272. }
  273. };
  274. // Based on http://jsfromhell.com/array/shuffle
  275. var shuffleArray = function shuffleArray(arr) {
  276. for (var j, x, i = arr.length; i; ) {
  277. j = Math.floor(Math.random() * i);
  278. x = arr[--i];
  279. arr[i] = arr[j];
  280. arr[j] = x;
  281. }
  282. return arr;
  283. };
  284. var timer = {};
  285. var WordCloud = function WordCloud(elements, options) {
  286. if (!isSupported) {
  287. return;
  288. }
  289. var timerId = Math.floor(Math.random() * Date.now());
  290. if (!Array.isArray(elements)) {
  291. elements = [elements];
  292. }
  293. elements.forEach(function (el, i) {
  294. if (typeof el === 'string') {
  295. elements[i] = document.getElementById(el);
  296. if (!elements[i]) {
  297. throw new Error('The element id specified is not found.');
  298. }
  299. } else if (!el.tagName && !el.appendChild) {
  300. throw new Error(
  301. 'You must pass valid HTML elements, or ID of the element.'
  302. );
  303. }
  304. });
  305. /* Default values to be overwritten by options object */
  306. var settings = {
  307. list: [],
  308. fontFamily:
  309. '"Trebuchet MS", "Heiti TC", "微軟正黑體", ' +
  310. '"Arial Unicode MS", "Droid Fallback Sans", sans-serif',
  311. fontWeight: 'normal',
  312. color: 'random-dark',
  313. minSize: 0, // 0 to disable
  314. weightFactor: 1,
  315. clearCanvas: true,
  316. backgroundColor: '#fff', // opaque white = rgba(255, 255, 255, 1)
  317. gridSize: 8,
  318. drawOutOfBound: false,
  319. shrinkToFit: false,
  320. origin: null,
  321. drawMask: false,
  322. maskColor: 'rgba(255,0,0,0.3)',
  323. maskGapWidth: 0.3,
  324. layoutAnimation: true,
  325. wait: 0,
  326. abortThreshold: 0, // disabled
  327. abort: function noop() {},
  328. minRotation: -Math.PI / 2,
  329. maxRotation: Math.PI / 2,
  330. rotationStep: 0.1,
  331. shuffle: true,
  332. rotateRatio: 0.1,
  333. shape: 'circle',
  334. ellipticity: 0.65,
  335. classes: null,
  336. hover: null,
  337. click: null
  338. };
  339. if (options) {
  340. for (var key in options) {
  341. if (key in settings) {
  342. settings[key] = options[key];
  343. }
  344. }
  345. }
  346. /* Convert weightFactor into a function */
  347. if (typeof settings.weightFactor !== 'function') {
  348. var factor = settings.weightFactor;
  349. settings.weightFactor = function weightFactor(pt) {
  350. return pt * factor; // in px
  351. };
  352. }
  353. /* Convert shape into a function */
  354. if (typeof settings.shape !== 'function') {
  355. switch (settings.shape) {
  356. case 'circle':
  357. /* falls through */
  358. default:
  359. // 'circle' is the default and a shortcut in the code loop.
  360. settings.shape = 'circle';
  361. break;
  362. case 'cardioid':
  363. settings.shape = function shapeCardioid(theta) {
  364. return 1 - Math.sin(theta);
  365. };
  366. break;
  367. /*
  368. To work out an X-gon, one has to calculate "m",
  369. where 1/(cos(2*PI/X)+m*sin(2*PI/X)) = 1/(cos(0)+m*sin(0))
  370. http://www.wolframalpha.com/input/?i=1%2F%28cos%282*PI%2FX%29%2Bm*sin%28
  371. 2*PI%2FX%29%29+%3D+1%2F%28cos%280%29%2Bm*sin%280%29%29
  372. Copy the solution into polar equation r = 1/(cos(t') + m*sin(t'))
  373. where t' equals to mod(t, 2PI/X);
  374. */
  375. case 'diamond':
  376. // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
  377. // %28t%2C+PI%2F2%29%29%2Bsin%28mod+%28t%2C+PI%2F2%29%29%29%2C+t+%3D
  378. // +0+..+2*PI
  379. settings.shape = function shapeSquare(theta) {
  380. var thetaPrime = theta % ((2 * Math.PI) / 4);
  381. return 1 / (Math.cos(thetaPrime) + Math.sin(thetaPrime));
  382. };
  383. break;
  384. case 'square':
  385. // http://www.wolframalpha.com/input/?i=plot+r+%3D+min(1%2Fabs(cos(t
  386. // )),1%2Fabs(sin(t)))),+t+%3D+0+..+2*PI
  387. settings.shape = function shapeSquare(theta) {
  388. return Math.min(
  389. 1 / Math.abs(Math.cos(theta)),
  390. 1 / Math.abs(Math.sin(theta))
  391. );
  392. };
  393. break;
  394. case 'triangle-forward':
  395. // http://www.wolframalpha.com/input/?i=plot+r+%3D+1%2F%28cos%28mod+
  396. // %28t%2C+2*PI%2F3%29%29%2Bsqrt%283%29sin%28mod+%28t%2C+2*PI%2F3%29
  397. // %29%29%2C+t+%3D+0+..+2*PI
  398. settings.shape = function shapeTriangle(theta) {
  399. var thetaPrime = theta % ((2 * Math.PI) / 3);
  400. return (
  401. 1 / (Math.cos(thetaPrime) + Math.sqrt(3) * Math.sin(thetaPrime))
  402. );
  403. };
  404. break;
  405. case 'triangle':
  406. case 'triangle-upright':
  407. settings.shape = function shapeTriangle(theta) {
  408. var thetaPrime = (theta + (Math.PI * 3) / 2) % ((2 * Math.PI) / 3);
  409. return (
  410. 1 / (Math.cos(thetaPrime) + Math.sqrt(3) * Math.sin(thetaPrime))
  411. );
  412. };
  413. break;
  414. case 'pentagon':
  415. settings.shape = function shapePentagon(theta) {
  416. var thetaPrime = (theta + 0.955) % ((2 * Math.PI) / 5);
  417. return 1 / (Math.cos(thetaPrime) + 0.726543 * Math.sin(thetaPrime));
  418. };
  419. break;
  420. case 'star':
  421. settings.shape = function shapeStar(theta) {
  422. var thetaPrime = (theta + 0.955) % ((2 * Math.PI) / 10);
  423. if (
  424. ((theta + 0.955) % ((2 * Math.PI) / 5)) - (2 * Math.PI) / 10 >=
  425. 0
  426. ) {
  427. return (
  428. 1 /
  429. (Math.cos((2 * Math.PI) / 10 - thetaPrime) +
  430. 3.07768 * Math.sin((2 * Math.PI) / 10 - thetaPrime))
  431. );
  432. } else {
  433. return 1 / (Math.cos(thetaPrime) + 3.07768 * Math.sin(thetaPrime));
  434. }
  435. };
  436. break;
  437. }
  438. }
  439. /* Make sure gridSize is a whole number and is not smaller than 4px */
  440. settings.gridSize = Math.max(Math.floor(settings.gridSize), 4);
  441. /* shorthand */
  442. var g = settings.gridSize;
  443. var maskRectWidth = g - settings.maskGapWidth;
  444. /* normalize rotation settings */
  445. var rotationRange = Math.abs(settings.maxRotation - settings.minRotation);
  446. var minRotation = Math.min(settings.maxRotation, settings.minRotation);
  447. var rotationStep = settings.rotationStep;
  448. /* information/object available to all functions, set when start() */
  449. var grid, // 2d array containing filling information
  450. ngx,
  451. ngy, // width and height of the grid
  452. center, // position of the center of the cloud
  453. maxRadius;
  454. /* timestamp for measuring each putWord() action */
  455. var escapeTime;
  456. /* function for getting the color of the text */
  457. var getTextColor;
  458. function randomHslColor(min, max) {
  459. return (
  460. 'hsl(' +
  461. (Math.random() * 360).toFixed() +
  462. ',' +
  463. (Math.random() * 30 + 70).toFixed() +
  464. '%,' +
  465. (Math.random() * (max - min) + min).toFixed() +
  466. '%)'
  467. );
  468. }
  469. switch (settings.color) {
  470. case 'random-dark':
  471. getTextColor = function getRandomDarkColor() {
  472. return randomHslColor(10, 50);
  473. };
  474. break;
  475. case 'random-light':
  476. getTextColor = function getRandomLightColor() {
  477. return randomHslColor(50, 90);
  478. };
  479. break;
  480. default:
  481. if (typeof settings.color === 'function') {
  482. getTextColor = settings.color;
  483. }
  484. break;
  485. }
  486. /* function for getting the font-weight of the text */
  487. var getTextFontWeight;
  488. if (typeof settings.fontWeight === 'function') {
  489. getTextFontWeight = settings.fontWeight;
  490. }
  491. /* function for getting the classes of the text */
  492. var getTextClasses = null;
  493. if (typeof settings.classes === 'function') {
  494. getTextClasses = settings.classes;
  495. }
  496. /* Interactive */
  497. var interactive = false;
  498. var infoGrid = [];
  499. var hovered;
  500. var getInfoGridFromMouseTouchEvent = function getInfoGridFromMouseTouchEvent(
  501. evt
  502. ) {
  503. var canvas = evt.currentTarget;
  504. var rect = canvas.getBoundingClientRect();
  505. var clientX;
  506. var clientY;
  507. /** Detect if touches are available */
  508. if (evt.touches) {
  509. clientX = evt.touches[0].clientX;
  510. clientY = evt.touches[0].clientY;
  511. } else {
  512. clientX = evt.clientX;
  513. clientY = evt.clientY;
  514. }
  515. var eventX = clientX - rect.left;
  516. var eventY = clientY - rect.top;
  517. var x = Math.floor((eventX * (canvas.width / rect.width || 1)) / g);
  518. var y = Math.floor((eventY * (canvas.height / rect.height || 1)) / g);
  519. if (!infoGrid[x]) {
  520. return null
  521. }
  522. return infoGrid[x][y];
  523. };
  524. var wordcloudhover = function wordcloudhover(evt) {
  525. var info = getInfoGridFromMouseTouchEvent(evt);
  526. if (hovered === info) {
  527. return;
  528. }
  529. hovered = info;
  530. if (!info) {
  531. settings.hover(undefined, undefined, evt);
  532. return;
  533. }
  534. settings.hover(info.item, info.dimension, evt);
  535. };
  536. var wordcloudclick = function wordcloudclick(evt) {
  537. var info = getInfoGridFromMouseTouchEvent(evt);
  538. if (!info) {
  539. return;
  540. }
  541. settings.click(info.item, info.dimension, evt);
  542. evt.preventDefault();
  543. };
  544. /* Get points on the grid for a given radius away from the center */
  545. var pointsAtRadius = [];
  546. var getPointsAtRadius = function getPointsAtRadius(radius) {
  547. if (pointsAtRadius[radius]) {
  548. return pointsAtRadius[radius];
  549. }
  550. // Look for these number of points on each radius
  551. var T = radius * 8;
  552. // Getting all the points at this radius
  553. var t = T;
  554. var points = [];
  555. if (radius === 0) {
  556. points.push([center[0], center[1], 0]);
  557. }
  558. while (t--) {
  559. // distort the radius to put the cloud in shape
  560. var rx = 1;
  561. if (settings.shape !== 'circle') {
  562. rx = settings.shape((t / T) * 2 * Math.PI); // 0 to 1
  563. }
  564. // Push [x, y, t]; t is used solely for getTextColor()
  565. points.push([
  566. center[0] + radius * rx * Math.cos((-t / T) * 2 * Math.PI),
  567. center[1] +
  568. radius * rx * Math.sin((-t / T) * 2 * Math.PI) * settings.ellipticity,
  569. (t / T) * 2 * Math.PI
  570. ]);
  571. }
  572. pointsAtRadius[radius] = points;
  573. return points;
  574. };
  575. /* Return true if we had spent too much time */
  576. var exceedTime = function exceedTime() {
  577. return (
  578. settings.abortThreshold > 0 &&
  579. new Date().getTime() - escapeTime > settings.abortThreshold
  580. );
  581. };
  582. /* Get the deg of rotation according to settings, and luck. */
  583. var getRotateDeg = function getRotateDeg() {
  584. if (settings.rotateRatio === 0) {
  585. return 0;
  586. }
  587. if (Math.random() > settings.rotateRatio) {
  588. return 0;
  589. }
  590. if (rotationRange === 0) {
  591. return minRotation;
  592. }
  593. return minRotation + Math.round(Math.random() * rotationRange / rotationStep) * rotationStep;
  594. };
  595. var getTextInfo = function getTextInfo(
  596. word,
  597. weight,
  598. rotateDeg,
  599. extraDataArray
  600. ) {
  601. // calculate the acutal font size
  602. // fontSize === 0 means weightFactor function wants the text skipped,
  603. // and size < minSize means we cannot draw the text.
  604. var debug = false;
  605. var fontSize = settings.weightFactor(weight);
  606. if (fontSize <= settings.minSize) {
  607. return false;
  608. }
  609. // Scale factor here is to make sure fillText is not limited by
  610. // the minium font size set by browser.
  611. // It will always be 1 or 2n.
  612. var mu = 1;
  613. if (fontSize < minFontSize) {
  614. mu = (function calculateScaleFactor() {
  615. var mu = 2;
  616. while (mu * fontSize < minFontSize) {
  617. mu += 2;
  618. }
  619. return mu;
  620. })();
  621. }
  622. // Get fontWeight that will be used to set fctx.font
  623. var fontWeight;
  624. if (getTextFontWeight) {
  625. fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray);
  626. } else {
  627. fontWeight = settings.fontWeight;
  628. }
  629. var fcanvas = document.createElement('canvas');
  630. var fctx = fcanvas.getContext('2d', { willReadFrequently: true });
  631. fctx.font =
  632. fontWeight +
  633. ' ' +
  634. (fontSize * mu).toString(10) +
  635. 'px ' +
  636. settings.fontFamily;
  637. // Estimate the dimension of the text with measureText().
  638. var fw = fctx.measureText(word).width / mu;
  639. var fh =
  640. Math.max(
  641. fontSize * mu,
  642. fctx.measureText('m').width,
  643. fctx.measureText('\uFF37').width
  644. ) / mu;
  645. // Create a boundary box that is larger than our estimates,
  646. // so text don't get cut of (it sill might)
  647. var boxWidth = fw + fh * 2;
  648. var boxHeight = fh * 3;
  649. var fgw = Math.ceil(boxWidth / g);
  650. var fgh = Math.ceil(boxHeight / g);
  651. boxWidth = fgw * g;
  652. boxHeight = fgh * g;
  653. // Calculate the proper offsets to make the text centered at
  654. // the preferred position.
  655. // This is simply half of the width.
  656. var fillTextOffsetX = -fw / 2;
  657. // Instead of moving the box to the exact middle of the preferred
  658. // position, for Y-offset we move 0.4 instead, so Latin alphabets look
  659. // vertical centered.
  660. var fillTextOffsetY = -fh * 0.4;
  661. // Calculate the actual dimension of the canvas, considering the rotation.
  662. var cgh = Math.ceil(
  663. (boxWidth * Math.abs(Math.sin(rotateDeg)) +
  664. boxHeight * Math.abs(Math.cos(rotateDeg))) /
  665. g
  666. );
  667. var cgw = Math.ceil(
  668. (boxWidth * Math.abs(Math.cos(rotateDeg)) +
  669. boxHeight * Math.abs(Math.sin(rotateDeg))) /
  670. g
  671. );
  672. var width = cgw * g;
  673. var height = cgh * g;
  674. fcanvas.setAttribute('width', width);
  675. fcanvas.setAttribute('height', height);
  676. if (debug) {
  677. // Attach fcanvas to the DOM
  678. document.body.appendChild(fcanvas);
  679. // Save it's state so that we could restore and draw the grid correctly.
  680. fctx.save();
  681. }
  682. // Scale the canvas with |mu|.
  683. fctx.scale(1 / mu, 1 / mu);
  684. fctx.translate((width * mu) / 2, (height * mu) / 2);
  685. fctx.rotate(-rotateDeg);
  686. // Once the width/height is set, ctx info will be reset.
  687. // Set it again here.
  688. fctx.font =
  689. fontWeight +
  690. ' ' +
  691. (fontSize * mu).toString(10) +
  692. 'px ' +
  693. settings.fontFamily;
  694. // Fill the text into the fcanvas.
  695. // XXX: We cannot because textBaseline = 'top' here because
  696. // Firefox and Chrome uses different default line-height for canvas.
  697. // Please read https://bugzil.la/737852#c6.
  698. // Here, we use textBaseline = 'middle' and draw the text at exactly
  699. // 0.5 * fontSize lower.
  700. fctx.fillStyle = '#000';
  701. fctx.textBaseline = 'middle';
  702. fctx.fillText(
  703. word,
  704. fillTextOffsetX * mu,
  705. (fillTextOffsetY + fontSize * 0.5) * mu
  706. );
  707. // Get the pixels of the text
  708. var imageData = fctx.getImageData(0, 0, width, height).data;
  709. if (exceedTime()) {
  710. return false;
  711. }
  712. if (debug) {
  713. // Draw the box of the original estimation
  714. fctx.strokeRect(fillTextOffsetX * mu, fillTextOffsetY, fw * mu, fh * mu);
  715. fctx.restore();
  716. }
  717. // Read the pixels and save the information to the occupied array
  718. var occupied = [];
  719. var gx = cgw;
  720. var gy, x, y;
  721. var bounds = [cgh / 2, cgw / 2, cgh / 2, cgw / 2];
  722. while (gx--) {
  723. gy = cgh;
  724. while (gy--) {
  725. y = g;
  726. /* eslint no-labels: ['error', { 'allowLoop': true }] */
  727. singleGridLoop: while (y--) {
  728. x = g;
  729. while (x--) {
  730. if (imageData[((gy * g + y) * width + (gx * g + x)) * 4 + 3]) {
  731. occupied.push([gx, gy]);
  732. if (gx < bounds[3]) {
  733. bounds[3] = gx;
  734. }
  735. if (gx > bounds[1]) {
  736. bounds[1] = gx;
  737. }
  738. if (gy < bounds[0]) {
  739. bounds[0] = gy;
  740. }
  741. if (gy > bounds[2]) {
  742. bounds[2] = gy;
  743. }
  744. if (debug) {
  745. fctx.fillStyle = 'rgba(255, 0, 0, 0.5)';
  746. fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5);
  747. }
  748. break singleGridLoop;
  749. }
  750. }
  751. }
  752. if (debug) {
  753. fctx.fillStyle = 'rgba(0, 0, 255, 0.5)';
  754. fctx.fillRect(gx * g, gy * g, g - 0.5, g - 0.5);
  755. }
  756. }
  757. }
  758. if (debug) {
  759. fctx.fillStyle = 'rgba(0, 255, 0, 0.5)';
  760. fctx.fillRect(
  761. bounds[3] * g,
  762. bounds[0] * g,
  763. (bounds[1] - bounds[3] + 1) * g,
  764. (bounds[2] - bounds[0] + 1) * g
  765. );
  766. }
  767. // Return information needed to create the text on the real canvas
  768. return {
  769. mu: mu,
  770. occupied: occupied,
  771. bounds: bounds,
  772. gw: cgw,
  773. gh: cgh,
  774. fillTextOffsetX: fillTextOffsetX,
  775. fillTextOffsetY: fillTextOffsetY,
  776. fillTextWidth: fw,
  777. fillTextHeight: fh,
  778. fontSize: fontSize
  779. };
  780. };
  781. /* Determine if there is room available in the given dimension */
  782. var canFitText = function canFitText(gx, gy, gw, gh, occupied) {
  783. // Go through the occupied points,
  784. // return false if the space is not available.
  785. var i = occupied.length;
  786. while (i--) {
  787. var px = gx + occupied[i][0];
  788. var py = gy + occupied[i][1];
  789. if (px >= ngx || py >= ngy || px < 0 || py < 0) {
  790. if (!settings.drawOutOfBound) {
  791. return false;
  792. }
  793. continue;
  794. }
  795. if (!grid[px][py]) {
  796. return false;
  797. }
  798. }
  799. return true;
  800. };
  801. /* Actually draw the text on the grid */
  802. var drawText = function drawText(
  803. gx,
  804. gy,
  805. info,
  806. word,
  807. weight,
  808. distance,
  809. theta,
  810. rotateDeg,
  811. attributes,
  812. extraDataArray
  813. ) {
  814. var fontSize = info.fontSize;
  815. var color;
  816. if (getTextColor) {
  817. color = getTextColor(
  818. word,
  819. weight,
  820. fontSize,
  821. distance,
  822. theta,
  823. extraDataArray
  824. );
  825. } else {
  826. color = settings.color;
  827. }
  828. // get fontWeight that will be used to set ctx.font and font style rule
  829. var fontWeight;
  830. if (getTextFontWeight) {
  831. fontWeight = getTextFontWeight(word, weight, fontSize, extraDataArray);
  832. } else {
  833. fontWeight = settings.fontWeight;
  834. }
  835. var classes;
  836. if (getTextClasses) {
  837. classes = getTextClasses(word, weight, fontSize, extraDataArray);
  838. } else {
  839. classes = settings.classes;
  840. }
  841. elements.forEach(function (el) {
  842. if (el.getContext) {
  843. var ctx = el.getContext('2d');
  844. var mu = info.mu;
  845. // Save the current state before messing it
  846. ctx.save();
  847. ctx.scale(1 / mu, 1 / mu);
  848. ctx.font =
  849. fontWeight +
  850. ' ' +
  851. (fontSize * mu).toString(10) +
  852. 'px ' +
  853. settings.fontFamily;
  854. ctx.fillStyle = color;
  855. // Translate the canvas position to the origin coordinate of where
  856. // the text should be put.
  857. ctx.translate((gx + info.gw / 2) * g * mu, (gy + info.gh / 2) * g * mu);
  858. if (rotateDeg !== 0) {
  859. ctx.rotate(-rotateDeg);
  860. }
  861. // Finally, fill the text.
  862. // XXX: We cannot because textBaseline = 'top' here because
  863. // Firefox and Chrome uses different default line-height for canvas.
  864. // Please read https://bugzil.la/737852#c6.
  865. // Here, we use textBaseline = 'middle' and draw the text at exactly
  866. // 0.5 * fontSize lower.
  867. ctx.textBaseline = 'middle';
  868. ctx.fillText(
  869. word,
  870. info.fillTextOffsetX * mu,
  871. (info.fillTextOffsetY + fontSize * 0.5) * mu
  872. );
  873. // The below box is always matches how <span>s are positioned
  874. /* ctx.strokeRect(info.fillTextOffsetX, info.fillTextOffsetY,
  875. info.fillTextWidth, info.fillTextHeight); */
  876. // Restore the state.
  877. ctx.restore();
  878. } else {
  879. // drawText on DIV element
  880. var span = document.createElement('span');
  881. var transformRule = '';
  882. transformRule = 'rotate(' + (-rotateDeg / Math.PI) * 180 + 'deg) ';
  883. if (info.mu !== 1) {
  884. transformRule +=
  885. 'translateX(-' +
  886. info.fillTextWidth / 4 +
  887. 'px) ' +
  888. 'scale(' +
  889. 1 / info.mu +
  890. ')';
  891. }
  892. var styleRules = {
  893. position: 'absolute',
  894. display: 'block',
  895. font:
  896. fontWeight + ' ' + fontSize * info.mu + 'px ' + settings.fontFamily,
  897. left: (gx + info.gw / 2) * g + info.fillTextOffsetX + 'px',
  898. top: (gy + info.gh / 2) * g + info.fillTextOffsetY + 'px',
  899. width: info.fillTextWidth + 'px',
  900. height: info.fillTextHeight + 'px',
  901. lineHeight: fontSize + 'px',
  902. whiteSpace: 'nowrap',
  903. transform: transformRule,
  904. webkitTransform: transformRule,
  905. msTransform: transformRule,
  906. transformOrigin: '50% 40%',
  907. webkitTransformOrigin: '50% 40%',
  908. msTransformOrigin: '50% 40%'
  909. };
  910. if (color) {
  911. styleRules.color = color;
  912. }
  913. span.textContent = word;
  914. for (var cssProp in styleRules) {
  915. span.style[cssProp] = styleRules[cssProp];
  916. }
  917. if (attributes) {
  918. for (var attribute in attributes) {
  919. span.setAttribute(attribute, attributes[attribute]);
  920. }
  921. }
  922. if (classes) {
  923. span.className += classes;
  924. }
  925. el.appendChild(span);
  926. }
  927. });
  928. };
  929. /* Help function to updateGrid */
  930. var fillGridAt = function fillGridAt(x, y, drawMask, dimension, item) {
  931. if (x >= ngx || y >= ngy || x < 0 || y < 0) {
  932. return;
  933. }
  934. grid[x][y] = false;
  935. if (drawMask) {
  936. var ctx = elements[0].getContext('2d');
  937. ctx.fillRect(x * g, y * g, maskRectWidth, maskRectWidth);
  938. }
  939. if (interactive) {
  940. infoGrid[x][y] = { item: item, dimension: dimension };
  941. }
  942. };
  943. /* Update the filling information of the given space with occupied points.
  944. Draw the mask on the canvas if necessary. */
  945. var updateGrid = function updateGrid(gx, gy, gw, gh, info, item) {
  946. var occupied = info.occupied;
  947. var drawMask = settings.drawMask;
  948. var ctx;
  949. if (drawMask) {
  950. ctx = elements[0].getContext('2d');
  951. ctx.save();
  952. ctx.fillStyle = settings.maskColor;
  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. var i = occupied.length;
  965. while (i--) {
  966. var px = gx + occupied[i][0];
  967. var py = gy + occupied[i][1];
  968. if (px >= ngx || py >= ngy || px < 0 || py < 0) {
  969. continue;
  970. }
  971. fillGridAt(px, py, drawMask, dimension, item);
  972. }
  973. if (drawMask) {
  974. ctx.restore();
  975. }
  976. };
  977. /* putWord() processes each item on the list,
  978. calculate it's size and determine it's position, and actually
  979. put it on the canvas. */
  980. var putWord = function putWord(item, loopIndex) {
  981. if (loopIndex > 20) {
  982. return null;
  983. }
  984. var word, weight, attributes;
  985. if (Array.isArray(item)) {
  986. word = item[0];
  987. weight = item[1];
  988. } else {
  989. word = item.word;
  990. weight = item.weight;
  991. attributes = item.attributes;
  992. }
  993. var rotateDeg = getRotateDeg();
  994. var extraDataArray = getItemExtraData(item);
  995. // get info needed to put the text onto the canvas
  996. var info = getTextInfo(word, weight, rotateDeg, extraDataArray);
  997. // not getting the info means we shouldn't be drawing this one.
  998. if (!info) {
  999. return false;
  1000. }
  1001. if (exceedTime()) {
  1002. return false;
  1003. }
  1004. // If drawOutOfBound is set to false,
  1005. // skip the loop if we have already know the bounding box of
  1006. // word is larger than the canvas.
  1007. if (!settings.drawOutOfBound && !settings.shrinkToFit) {
  1008. var bounds = info.bounds;
  1009. if (bounds[1] - bounds[3] + 1 > ngx || bounds[2] - bounds[0] + 1 > ngy) {
  1010. return false;
  1011. }
  1012. }
  1013. // Determine the position to put the text by
  1014. // start looking for the nearest points
  1015. var r = maxRadius + 1;
  1016. var tryToPutWordAtPoint = function (gxy) {
  1017. var gx = Math.floor(gxy[0] - info.gw / 2);
  1018. var gy = Math.floor(gxy[1] - info.gh / 2);
  1019. var gw = info.gw;
  1020. var gh = info.gh;
  1021. // If we cannot fit the text at this position, return false
  1022. // and go to the next position.
  1023. if (!canFitText(gx, gy, gw, gh, info.occupied)) {
  1024. return false;
  1025. }
  1026. // Actually put the text on the canvas
  1027. drawText(
  1028. gx,
  1029. gy,
  1030. info,
  1031. word,
  1032. weight,
  1033. maxRadius - r,
  1034. gxy[2],
  1035. rotateDeg,
  1036. attributes,
  1037. extraDataArray
  1038. );
  1039. // Mark the spaces on the grid as filled
  1040. updateGrid(gx, gy, gw, gh, info, item);
  1041. return {
  1042. gx: gx,
  1043. gy: gy,
  1044. rot: rotateDeg,
  1045. info: info
  1046. };
  1047. };
  1048. while (r--) {
  1049. var points = getPointsAtRadius(maxRadius - r);
  1050. if (settings.shuffle) {
  1051. points = [].concat(points);
  1052. shuffleArray(points);
  1053. }
  1054. // Try to fit the words by looking at each point.
  1055. // array.some() will stop and return true
  1056. // when putWordAtPoint() returns true.
  1057. for (var i = 0; i < points.length; i++) {
  1058. var res = tryToPutWordAtPoint(points[i]);
  1059. if (res) {
  1060. return res;
  1061. }
  1062. }
  1063. // var drawn = points.some(tryToPutWordAtPoint);
  1064. // if (drawn) {
  1065. // // leave putWord() and return true
  1066. // return true;
  1067. // }
  1068. }
  1069. if (settings.shrinkToFit) {
  1070. if (Array.isArray(item)) {
  1071. item[1] = (item[1] * 3) / 4;
  1072. } else {
  1073. item.weight = (item.weight * 3) / 4;
  1074. }
  1075. return putWord(item, loopIndex + 1);
  1076. }
  1077. // we tried all distances but text won't fit, return null
  1078. return null;
  1079. };
  1080. /* Send DOM event to all elements. Will stop sending event and return
  1081. if the previous one is canceled (for cancelable events). */
  1082. var sendEvent = function sendEvent(type, cancelable, details) {
  1083. if (cancelable) {
  1084. return !elements.some(function (el) {
  1085. var event = new CustomEvent(type, {
  1086. detail: details || {}
  1087. });
  1088. return !el.dispatchEvent(event);
  1089. }, this);
  1090. } else {
  1091. elements.forEach(function (el) {
  1092. var event = new CustomEvent(type, {
  1093. detail: details || {}
  1094. });
  1095. el.dispatchEvent(event);
  1096. }, this);
  1097. }
  1098. };
  1099. /* Start drawing on a canvas */
  1100. var start = function start() {
  1101. // For dimensions, clearCanvas etc.,
  1102. // we only care about the first element.
  1103. var canvas = elements[0];
  1104. if (canvas.getContext) {
  1105. ngx = Math.ceil(canvas.width / g);
  1106. ngy = Math.ceil(canvas.height / g);
  1107. } else {
  1108. var rect = canvas.getBoundingClientRect();
  1109. ngx = Math.ceil(rect.width / g);
  1110. ngy = Math.ceil(rect.height / g);
  1111. }
  1112. // Sending a wordcloudstart event which cause the previous loop to stop.
  1113. // Do nothing if the event is canceled.
  1114. if (!sendEvent('wordcloudstart', true)) {
  1115. return;
  1116. }
  1117. // Determine the center of the word cloud
  1118. center = settings.origin
  1119. ? [settings.origin[0] / g, settings.origin[1] / g]
  1120. : [ngx / 2, ngy / 2];
  1121. // Maxium radius to look for space
  1122. maxRadius = Math.floor(Math.sqrt(ngx * ngx + ngy * ngy));
  1123. /* Clear the canvas only if the clearCanvas is set,
  1124. if not, update the grid to the current canvas state */
  1125. grid = [];
  1126. var gx, gy, i;
  1127. if (!canvas.getContext || settings.clearCanvas) {
  1128. elements.forEach(function (el) {
  1129. if (el.getContext) {
  1130. var ctx = el.getContext('2d');
  1131. ctx.fillStyle = settings.backgroundColor;
  1132. ctx.clearRect(0, 0, ngx * (g + 1), ngy * (g + 1));
  1133. ctx.fillRect(0, 0, ngx * (g + 1), ngy * (g + 1));
  1134. } else {
  1135. el.textContent = '';
  1136. el.style.backgroundColor = settings.backgroundColor;
  1137. el.style.position = 'relative';
  1138. }
  1139. });
  1140. /* fill the grid with empty state */
  1141. gx = ngx;
  1142. while (gx--) {
  1143. grid[gx] = [];
  1144. gy = ngy;
  1145. while (gy--) {
  1146. grid[gx][gy] = true;
  1147. }
  1148. }
  1149. } else {
  1150. /* Determine bgPixel by creating
  1151. another canvas and fill the specified background color. */
  1152. var bctx = document.createElement('canvas').getContext('2d');
  1153. bctx.fillStyle = settings.backgroundColor;
  1154. bctx.fillRect(0, 0, 1, 1);
  1155. var bgPixel = bctx.getImageData(0, 0, 1, 1).data;
  1156. /* Read back the pixels of the canvas we got to tell which part of the
  1157. canvas is empty.
  1158. (no clearCanvas only works with a canvas, not divs) */
  1159. var imageData = canvas
  1160. .getContext('2d')
  1161. .getImageData(0, 0, ngx * g, ngy * g).data;
  1162. gx = ngx;
  1163. var x, y;
  1164. while (gx--) {
  1165. grid[gx] = [];
  1166. gy = ngy;
  1167. while (gy--) {
  1168. y = g;
  1169. /* eslint no-labels: ['error', { 'allowLoop': true }] */
  1170. singleGridLoop: while (y--) {
  1171. x = g;
  1172. while (x--) {
  1173. i = 4;
  1174. while (i--) {
  1175. if (
  1176. imageData[((gy * g + y) * ngx * g + (gx * g + x)) * 4 + i] !==
  1177. bgPixel[i]
  1178. ) {
  1179. grid[gx][gy] = false;
  1180. break singleGridLoop;
  1181. }
  1182. }
  1183. }
  1184. }
  1185. if (grid[gx][gy] !== false) {
  1186. grid[gx][gy] = true;
  1187. }
  1188. }
  1189. }
  1190. imageData = bctx = bgPixel = undefined;
  1191. }
  1192. // fill the infoGrid with empty state if we need it
  1193. if (settings.hover || settings.click) {
  1194. interactive = true;
  1195. /* fill the grid with empty state */
  1196. gx = ngx + 1;
  1197. while (gx--) {
  1198. infoGrid[gx] = [];
  1199. }
  1200. if (settings.hover) {
  1201. canvas.addEventListener('mousemove', wordcloudhover);
  1202. }
  1203. if (settings.click) {
  1204. canvas.addEventListener('click', wordcloudclick);
  1205. canvas.addEventListener('touchstart', wordcloudclick);
  1206. canvas.addEventListener('touchend', function (e) {
  1207. e.preventDefault();
  1208. });
  1209. canvas.style.webkitTapHighlightColor = 'rgba(0, 0, 0, 0)';
  1210. }
  1211. canvas.addEventListener('wordcloudstart', function stopInteraction() {
  1212. canvas.removeEventListener('wordcloudstart', stopInteraction);
  1213. canvas.removeEventListener('mousemove', wordcloudhover);
  1214. canvas.removeEventListener('click', wordcloudclick);
  1215. hovered = undefined;
  1216. });
  1217. }
  1218. i = 0;
  1219. var loopingFunction, stoppingFunction;
  1220. var layouting = true;
  1221. if (!settings.layoutAnimation) {
  1222. loopingFunction = function (cb) {
  1223. cb();
  1224. };
  1225. stoppingFunction = function () {
  1226. layouting = false;
  1227. };
  1228. } else if (settings.wait !== 0) {
  1229. loopingFunction = window.setTimeout;
  1230. stoppingFunction = window.clearTimeout;
  1231. } else {
  1232. loopingFunction = window.setImmediate;
  1233. stoppingFunction = window.clearImmediate;
  1234. }
  1235. var addEventListener = function addEventListener(type, listener) {
  1236. elements.forEach(function (el) {
  1237. el.addEventListener(type, listener);
  1238. }, this);
  1239. };
  1240. var removeEventListener = function removeEventListener(type, listener) {
  1241. elements.forEach(function (el) {
  1242. el.removeEventListener(type, listener);
  1243. }, this);
  1244. };
  1245. var anotherWordCloudStart = function anotherWordCloudStart() {
  1246. removeEventListener('wordcloudstart', anotherWordCloudStart);
  1247. stoppingFunction(timer[timerId]);
  1248. };
  1249. addEventListener('wordcloudstart', anotherWordCloudStart);
  1250. // At least wait the following code before call the first iteration.
  1251. timer[timerId] = (settings.layoutAnimation ? loopingFunction : setTimeout)(
  1252. function loop() {
  1253. if (!layouting) {
  1254. return;
  1255. }
  1256. if (i >= settings.list.length) {
  1257. stoppingFunction(timer[timerId]);
  1258. sendEvent('wordcloudstop', false);
  1259. removeEventListener('wordcloudstart', anotherWordCloudStart);
  1260. delete timer[timerId];
  1261. return;
  1262. }
  1263. escapeTime = new Date().getTime();
  1264. var drawn = putWord(settings.list[i], 0);
  1265. var canceled = !sendEvent('wordclouddrawn', true, {
  1266. item: settings.list[i],
  1267. drawn: drawn
  1268. });
  1269. if (exceedTime() || canceled) {
  1270. stoppingFunction(timer[timerId]);
  1271. settings.abort();
  1272. sendEvent('wordcloudabort', false);
  1273. sendEvent('wordcloudstop', false);
  1274. removeEventListener('wordcloudstart', anotherWordCloudStart);
  1275. return;
  1276. }
  1277. i++;
  1278. timer[timerId] = loopingFunction(loop, settings.wait);
  1279. },
  1280. settings.wait
  1281. );
  1282. };
  1283. // All set, start the drawing
  1284. start();
  1285. };
  1286. WordCloud.isSupported = isSupported;
  1287. WordCloud.minFontSize = minFontSize;
  1288. /* harmony default export */ const layout = (WordCloud);
  1289. ;// CONCATENATED MODULE: ./src/wordCloud.js
  1290. if (!layout.isSupported) {
  1291. throw new Error('Sorry your browser not support wordCloud');
  1292. }
  1293. // https://github.com/timdream/wordcloud2.js/blob/c236bee60436e048949f9becc4f0f67bd832dc5c/index.js#L233
  1294. function updateCanvasMask(maskCanvas) {
  1295. var ctx = maskCanvas.getContext('2d');
  1296. var imageData = ctx.getImageData(0, 0, maskCanvas.width, maskCanvas.height);
  1297. var newImageData = ctx.createImageData(imageData);
  1298. var toneSum = 0;
  1299. var toneCnt = 0;
  1300. for (var i = 0; i < imageData.data.length; i += 4) {
  1301. var alpha = imageData.data[i + 3];
  1302. if (alpha > 128) {
  1303. var tone =
  1304. imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2];
  1305. toneSum += tone;
  1306. ++toneCnt;
  1307. }
  1308. }
  1309. var threshold = toneSum / toneCnt;
  1310. for (var i = 0; i < imageData.data.length; i += 4) {
  1311. var tone =
  1312. imageData.data[i] + imageData.data[i + 1] + imageData.data[i + 2];
  1313. var alpha = imageData.data[i + 3];
  1314. if (alpha < 128 || tone > threshold) {
  1315. // Area not to draw
  1316. newImageData.data[i] = 0;
  1317. newImageData.data[i + 1] = 0;
  1318. newImageData.data[i + 2] = 0;
  1319. newImageData.data[i + 3] = 0;
  1320. } else {
  1321. // Area to draw
  1322. // The color must be same with backgroundColor
  1323. newImageData.data[i] = 255;
  1324. newImageData.data[i + 1] = 255;
  1325. newImageData.data[i + 2] = 255;
  1326. newImageData.data[i + 3] = 255;
  1327. }
  1328. }
  1329. ctx.putImageData(newImageData, 0, 0);
  1330. }
  1331. external_echarts_.registerLayout(function (ecModel, api) {
  1332. ecModel.eachSeriesByType('wordCloud', function (seriesModel) {
  1333. var gridRect = external_echarts_.helper.getLayoutRect(
  1334. seriesModel.getBoxLayoutParams(),
  1335. {
  1336. width: api.getWidth(),
  1337. height: api.getHeight()
  1338. }
  1339. );
  1340. var keepAspect = seriesModel.get('keepAspect');
  1341. var maskImage = seriesModel.get('maskImage');
  1342. var ratio = maskImage ? maskImage.width / maskImage.height : 1;
  1343. keepAspect && adjustRectAspect(gridRect, ratio);
  1344. var data = seriesModel.getData();
  1345. var canvas = document.createElement('canvas');
  1346. canvas.width = gridRect.width;
  1347. canvas.height = gridRect.height;
  1348. var ctx = canvas.getContext('2d');
  1349. if (maskImage) {
  1350. try {
  1351. ctx.drawImage(maskImage, 0, 0, canvas.width, canvas.height);
  1352. updateCanvasMask(canvas);
  1353. } catch (e) {
  1354. console.error('Invalid mask image');
  1355. console.error(e.toString());
  1356. }
  1357. }
  1358. var sizeRange = seriesModel.get('sizeRange');
  1359. var rotationRange = seriesModel.get('rotationRange');
  1360. var valueExtent = data.getDataExtent('value');
  1361. var DEGREE_TO_RAD = Math.PI / 180;
  1362. var gridSize = seriesModel.get('gridSize');
  1363. layout(canvas, {
  1364. list: data
  1365. .mapArray('value', function (value, idx) {
  1366. var itemModel = data.getItemModel(idx);
  1367. return [
  1368. data.getName(idx),
  1369. itemModel.get('textStyle.fontSize', true) ||
  1370. external_echarts_.number.linearMap(value, valueExtent, sizeRange),
  1371. idx
  1372. ];
  1373. })
  1374. .sort(function (a, b) {
  1375. // Sort from large to small in case there is no more room for more words
  1376. return b[1] - a[1];
  1377. }),
  1378. fontFamily:
  1379. seriesModel.get('textStyle.fontFamily') ||
  1380. seriesModel.get('emphasis.textStyle.fontFamily') ||
  1381. ecModel.get('textStyle.fontFamily'),
  1382. fontWeight:
  1383. seriesModel.get('textStyle.fontWeight') ||
  1384. seriesModel.get('emphasis.textStyle.fontWeight') ||
  1385. ecModel.get('textStyle.fontWeight'),
  1386. gridSize: gridSize,
  1387. ellipticity: gridRect.height / gridRect.width,
  1388. minRotation: rotationRange[0] * DEGREE_TO_RAD,
  1389. maxRotation: rotationRange[1] * DEGREE_TO_RAD,
  1390. clearCanvas: !maskImage,
  1391. rotateRatio: 1,
  1392. rotationStep: seriesModel.get('rotationStep') * DEGREE_TO_RAD,
  1393. drawOutOfBound: seriesModel.get('drawOutOfBound'),
  1394. shrinkToFit: seriesModel.get('shrinkToFit'),
  1395. layoutAnimation: seriesModel.get('layoutAnimation'),
  1396. shuffle: false,
  1397. shape: seriesModel.get('shape')
  1398. });
  1399. function onWordCloudDrawn(e) {
  1400. var item = e.detail.item;
  1401. if (e.detail.drawn && seriesModel.layoutInstance.ondraw) {
  1402. e.detail.drawn.gx += gridRect.x / gridSize;
  1403. e.detail.drawn.gy += gridRect.y / gridSize;
  1404. seriesModel.layoutInstance.ondraw(
  1405. item[0],
  1406. item[1],
  1407. item[2],
  1408. e.detail.drawn
  1409. );
  1410. }
  1411. }
  1412. canvas.addEventListener('wordclouddrawn', onWordCloudDrawn);
  1413. if (seriesModel.layoutInstance) {
  1414. // Dispose previous
  1415. seriesModel.layoutInstance.dispose();
  1416. }
  1417. seriesModel.layoutInstance = {
  1418. ondraw: null,
  1419. dispose: function () {
  1420. canvas.removeEventListener('wordclouddrawn', onWordCloudDrawn);
  1421. // Abort
  1422. canvas.addEventListener('wordclouddrawn', function (e) {
  1423. // Prevent default to cancle the event and stop the loop
  1424. e.preventDefault();
  1425. });
  1426. }
  1427. };
  1428. });
  1429. });
  1430. external_echarts_.registerPreprocessor(function (option) {
  1431. var series = (option || {}).series;
  1432. !external_echarts_.util.isArray(series) && (series = series ? [series] : []);
  1433. var compats = ['shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY'];
  1434. external_echarts_.util.each(series, function (seriesItem) {
  1435. if (seriesItem && seriesItem.type === 'wordCloud') {
  1436. var textStyle = seriesItem.textStyle || {};
  1437. compatTextStyle(textStyle.normal);
  1438. compatTextStyle(textStyle.emphasis);
  1439. }
  1440. });
  1441. function compatTextStyle(textStyle) {
  1442. textStyle &&
  1443. external_echarts_.util.each(compats, function (key) {
  1444. if (textStyle.hasOwnProperty(key)) {
  1445. textStyle['text' + external_echarts_.format.capitalFirst(key)] = textStyle[key];
  1446. }
  1447. });
  1448. }
  1449. });
  1450. function adjustRectAspect(gridRect, aspect) {
  1451. // var outerWidth = gridRect.width + gridRect.x * 2;
  1452. // var outerHeight = gridRect.height + gridRect.y * 2;
  1453. var width = gridRect.width;
  1454. var height = gridRect.height;
  1455. if (width > height * aspect) {
  1456. gridRect.x += (width - height * aspect) / 2;
  1457. gridRect.width = height * aspect;
  1458. } else {
  1459. gridRect.y += (height - width / aspect) / 2;
  1460. gridRect.height = width / aspect;
  1461. }
  1462. }
  1463. ;// CONCATENATED MODULE: ./index.js
  1464. /***/ }),
  1465. /***/ "echarts/lib/echarts":
  1466. /*!**************************!*\
  1467. !*** external "echarts" ***!
  1468. \**************************/
  1469. /***/ ((module) => {
  1470. module.exports = __WEBPACK_EXTERNAL_MODULE_echarts_lib_echarts__;
  1471. /***/ })
  1472. /******/ });
  1473. /************************************************************************/
  1474. /******/ // The module cache
  1475. /******/ var __webpack_module_cache__ = {};
  1476. /******/
  1477. /******/ // The require function
  1478. /******/ function __webpack_require__(moduleId) {
  1479. /******/ // Check if module is in cache
  1480. /******/ if(__webpack_module_cache__[moduleId]) {
  1481. /******/ return __webpack_module_cache__[moduleId].exports;
  1482. /******/ }
  1483. /******/ // Create a new module (and put it into the cache)
  1484. /******/ var module = __webpack_module_cache__[moduleId] = {
  1485. /******/ // no module.id needed
  1486. /******/ // no module.loaded needed
  1487. /******/ exports: {}
  1488. /******/ };
  1489. /******/
  1490. /******/ // Execute the module function
  1491. /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
  1492. /******/
  1493. /******/ // Return the exports of the module
  1494. /******/ return module.exports;
  1495. /******/ }
  1496. /******/
  1497. /************************************************************************/
  1498. /******/ /* webpack/runtime/make namespace object */
  1499. /******/ (() => {
  1500. /******/ // define __esModule on exports
  1501. /******/ __webpack_require__.r = (exports) => {
  1502. /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) {
  1503. /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
  1504. /******/ }
  1505. /******/ Object.defineProperty(exports, '__esModule', { value: true });
  1506. /******/ };
  1507. /******/ })();
  1508. /******/
  1509. /************************************************************************/
  1510. /******/ // module exports must be returned from runtime so entry inlining is disabled
  1511. /******/ // startup
  1512. /******/ // Load entry module and return exports
  1513. /******/ return __webpack_require__("./index.js");
  1514. /******/ })()
  1515. ;
  1516. });
  1517. //# sourceMappingURL=echarts-wordcloud.js.map