wordcloud.src.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893
  1. /**
  2. * @license Highcharts JS v6.1.0 (2018-04-13)
  3. *
  4. * (c) 2016 Highsoft AS
  5. * Authors: Jon Arild Nygard
  6. *
  7. * License: www.highcharts.com/license
  8. */
  9. 'use strict';
  10. (function (factory) {
  11. if (typeof module === 'object' && module.exports) {
  12. module.exports = factory;
  13. } else {
  14. factory(Highcharts);
  15. }
  16. }(function (Highcharts) {
  17. var draw = (function () {
  18. var isFn = function (x) {
  19. return typeof x === 'function';
  20. };
  21. /**
  22. * draw - Handles the drawing of a point.
  23. * TODO: add type checking.
  24. *
  25. * @param {object} params Parameters.
  26. * @return {undefined} Returns undefined.
  27. */
  28. var draw = function draw(params) {
  29. var point = this,
  30. graphic = point.graphic,
  31. animate = params.animate,
  32. attr = params.attr,
  33. onComplete = params.onComplete,
  34. css = params.css,
  35. group = params.group,
  36. renderer = params.renderer,
  37. shape = params.shapeArgs,
  38. type = params.shapeType;
  39. if (point.shouldDraw()) {
  40. if (!graphic) {
  41. point.graphic = graphic = renderer[type](shape).add(group);
  42. }
  43. graphic.css(css).attr(attr).animate(animate, undefined, onComplete);
  44. } else if (graphic) {
  45. graphic.animate(animate, undefined, function () {
  46. point.graphic = graphic = graphic.destroy();
  47. if (isFn(onComplete)) {
  48. onComplete();
  49. }
  50. });
  51. }
  52. if (graphic) {
  53. graphic.addClass(point.getClassName(), true);
  54. }
  55. };
  56. return draw;
  57. }());
  58. (function (H, drawPoint) {
  59. /**
  60. * (c) 2016 Highsoft AS
  61. * Authors: Jon Arild Nygard
  62. *
  63. * License: www.highcharts.com/license
  64. *
  65. * This is an experimental Highcharts module which enables visualization
  66. * of a word cloud.
  67. */
  68. var each = H.each,
  69. extend = H.extend,
  70. isArray = H.isArray,
  71. isNumber = H.isNumber,
  72. isObject = H.isObject,
  73. reduce = H.reduce,
  74. Series = H.Series;
  75. /**
  76. * isRectanglesIntersecting - Detects if there is a collision between two
  77. * rectangles.
  78. *
  79. * @param {object} r1 First rectangle.
  80. * @param {object} r2 Second rectangle.
  81. * @return {boolean} Returns true if the rectangles overlap.
  82. */
  83. var isRectanglesIntersecting = function isRectanglesIntersecting(r1, r2) {
  84. return !(
  85. r2.left > r1.right ||
  86. r2.right < r1.left ||
  87. r2.top > r1.bottom ||
  88. r2.bottom < r1.top
  89. );
  90. };
  91. /**
  92. * intersectsAnyWord - Detects if a word collides with any previously placed
  93. * words.
  94. *
  95. * @param {Point} point Point which the word is connected to.
  96. * @param {Array} points Previously placed points to check against.
  97. * @return {boolean} Returns true if there is collision.
  98. */
  99. var intersectsAnyWord = function intersectsAnyWord(point, points) {
  100. var intersects = false,
  101. rect1 = point.rect,
  102. rect2;
  103. if (point.lastCollidedWith) {
  104. rect2 = point.lastCollidedWith.rect;
  105. intersects = isRectanglesIntersecting(rect1, rect2);
  106. // If they no longer intersects, remove the cache from the point.
  107. if (!intersects) {
  108. delete point.lastCollidedWith;
  109. }
  110. }
  111. if (!intersects) {
  112. intersects = !!H.find(points, function (p) {
  113. var result;
  114. rect2 = p.rect;
  115. result = isRectanglesIntersecting(rect1, rect2);
  116. if (result) {
  117. point.lastCollidedWith = p;
  118. }
  119. return result;
  120. });
  121. }
  122. return intersects;
  123. };
  124. /**
  125. * archimedeanSpiral - Gives a set of cordinates for an Archimedian Spiral.
  126. *
  127. * @param {number} attempt How far along the spiral we have traversed.
  128. * @param {object} params Additional parameters.
  129. * @param {object} params.field Size of field.
  130. * @return {boolean|object} Resulting coordinates, x and y. False if the word
  131. * should be dropped from the visualization.
  132. */
  133. var archimedeanSpiral = function archimedeanSpiral(attempt, params) {
  134. var field = params.field,
  135. result = false,
  136. maxDelta = (field.width * field.width) + (field.height * field.height),
  137. t = attempt * 0.2;
  138. // Emergency brake. TODO make spiralling logic more foolproof.
  139. if (attempt <= 10000) {
  140. result = {
  141. x: t * Math.cos(t),
  142. y: t * Math.sin(t)
  143. };
  144. if (!(Math.min(Math.abs(result.x), Math.abs(result.y)) < maxDelta)) {
  145. result = false;
  146. }
  147. }
  148. return result;
  149. };
  150. /**
  151. * squareSpiral - Gives a set of cordinates for an rectangular spiral.
  152. *
  153. * @param {number} attempt How far along the spiral we have traversed.
  154. * @param {object} params Additional parameters.
  155. * @return {boolean|object} Resulting coordinates, x and y. False if the word
  156. * should be dropped from the visualization.
  157. */
  158. var squareSpiral = function squareSpiral(attempt) {
  159. var k = Math.ceil((Math.sqrt(attempt) - 1) / 2),
  160. t = 2 * k + 1,
  161. m = Math.pow(t, 2),
  162. isBoolean = function (x) {
  163. return typeof x === 'boolean';
  164. },
  165. result = false;
  166. t -= 1;
  167. if (attempt <= 10000) {
  168. if (isBoolean(result) && attempt >= m - t) {
  169. result = {
  170. x: k - (m - attempt),
  171. y: -k
  172. };
  173. }
  174. m -= t;
  175. if (isBoolean(result) && attempt >= m - t) {
  176. result = {
  177. x: -k,
  178. y: -k + (m - attempt)
  179. };
  180. }
  181. m -= t;
  182. if (isBoolean(result)) {
  183. if (attempt >= m - t) {
  184. result = {
  185. x: -k + (m - attempt),
  186. y: k
  187. };
  188. } else {
  189. result = {
  190. x: k,
  191. y: k - (m - attempt - t)
  192. };
  193. }
  194. }
  195. result.x *= 5;
  196. result.y *= 5;
  197. }
  198. return result;
  199. };
  200. /**
  201. * rectangularSpiral - Gives a set of cordinates for an rectangular spiral.
  202. *
  203. * @param {number} attempt How far along the spiral we have traversed.
  204. * @param {object} params Additional parameters.
  205. * @return {boolean|object} Resulting coordinates, x and y. False if the word
  206. * should be dropped from the visualization.
  207. */
  208. var rectangularSpiral = function rectangularSpiral(attempt, params) {
  209. var result = squareSpiral(attempt, params),
  210. field = params.field;
  211. if (result) {
  212. result.x *= field.ratio;
  213. }
  214. return result;
  215. };
  216. /**
  217. * getRandomPosition
  218. *
  219. * @param {number} size
  220. * @return {number}
  221. */
  222. var getRandomPosition = function getRandomPosition(size) {
  223. return Math.round((size * (Math.random() + 0.5)) / 2);
  224. };
  225. /**
  226. * getScale - Calculates the proper scale to fit the cloud inside the plotting
  227. * area.
  228. *
  229. * @param {number} targetWidth Width of target area.
  230. * @param {number} targetHeight Height of target area.
  231. * @param {object} field The playing field.
  232. * @param {Series} series Series object.
  233. * @return {number} Returns the value to scale the playing field up to the size
  234. * of the target area.
  235. */
  236. var getScale = function getScale(targetWidth, targetHeight, field) {
  237. var height = Math.max(Math.abs(field.top), Math.abs(field.bottom)) * 2,
  238. width = Math.max(Math.abs(field.left), Math.abs(field.right)) * 2,
  239. scaleX = 1 / width * targetWidth,
  240. scaleY = 1 / height * targetHeight;
  241. return Math.min(scaleX, scaleY);
  242. };
  243. /**
  244. * getPlayingField - Calculates what is called the playing field.
  245. * The field is the area which all the words are allowed to be positioned
  246. * within. The area is proportioned to match the target aspect ratio.
  247. *
  248. * @param {number} targetWidth Width of the target area.
  249. * @param {number} targetHeight Height of the target area.
  250. * @param {array} data Array of {@link Point} objects.
  251. * @param {object} data.dimensions The height and width of the word.
  252. * @return {object} The width and height of the playing field.
  253. */
  254. var getPlayingField = function getPlayingField(
  255. targetWidth,
  256. targetHeight,
  257. data
  258. ) {
  259. var ratio = targetWidth / targetHeight,
  260. info = reduce(data, function (obj, point) {
  261. var dimensions = point.dimensions;
  262. // Find largest height.
  263. obj.maxHeight = Math.max(obj.maxHeight, dimensions.height);
  264. // Find largest width.
  265. obj.maxWidth = Math.max(obj.maxWidth, dimensions.width);
  266. // Sum up the total area of all the words.
  267. obj.area += dimensions.width * dimensions.height;
  268. return obj;
  269. }, {
  270. maxHeight: 0,
  271. maxWidth: 0,
  272. area: 0
  273. }),
  274. /**
  275. * Use largest width, largest height, or root of total area to give size
  276. * to the playing field.
  277. * Add extra 10 percentage to ensure enough space.
  278. */
  279. x = 1.1 * Math.max(info.maxHeight, info.maxWidth, Math.sqrt(info.area));
  280. return {
  281. width: x * ratio,
  282. height: x,
  283. ratio: ratio
  284. };
  285. };
  286. /**
  287. * getRotation - Calculates a number of degrees to rotate, based upon a number
  288. * of orientations within a range from-to.
  289. *
  290. * @param {number} orientations Number of orientations.
  291. * @param {number} index Index of point, used to decide orientation.
  292. * @param {number} from The smallest degree of rotation.
  293. * @param {number} to The largest degree of rotation.
  294. * @return {boolean|number} Returns the resulting rotation for the word. Returns
  295. * false if invalid input parameters.
  296. */
  297. var getRotation = function getRotation(orientations, index, from, to) {
  298. var result = false, // Default to false
  299. range,
  300. intervals,
  301. orientation;
  302. // Check if we have valid input parameters.
  303. if (
  304. isNumber(orientations) &&
  305. isNumber(index) &&
  306. isNumber(from) &&
  307. isNumber(to) &&
  308. orientations > -1 &&
  309. index > -1 &&
  310. to > from
  311. ) {
  312. range = to - from;
  313. intervals = range / (orientations - 1);
  314. orientation = index % orientations;
  315. result = from + (orientation * intervals);
  316. }
  317. return result;
  318. };
  319. /**
  320. * outsidePlayingField - Detects if a word is placed outside the playing field.
  321. *
  322. * @param {Point} point Point which the word is connected to.
  323. * @param {object} field The width and height of the playing field.
  324. * @return {boolean} Returns true if the word is placed outside the field.
  325. */
  326. var outsidePlayingField = function outsidePlayingField(wrapper, field) {
  327. var rect = wrapper.getBBox(),
  328. playingField = {
  329. left: -(field.width / 2),
  330. right: field.width / 2,
  331. top: -(field.height / 2),
  332. bottom: field.height / 2
  333. };
  334. return !(
  335. playingField.left < (rect.x - rect.width / 2) &&
  336. playingField.right > (rect.x + rect.width / 2) &&
  337. playingField.top < (rect.y - rect.height / 2) &&
  338. playingField.bottom > (rect.y + rect.height / 2)
  339. );
  340. };
  341. /**
  342. * intersectionTesting - Check if a point intersects with previously placed
  343. * words, or if it goes outside the field boundaries. If a collision, then try
  344. * to adjusts the position.
  345. *
  346. * @param {object} point Point to test for intersections.
  347. * @param {object} options Options object.
  348. * @return {boolean|object} Returns an object with how much to correct the
  349. * positions. Returns false if the word should not be placed at all.
  350. */
  351. var intersectionTesting = function intersectionTesting(point, options) {
  352. var placed = options.placed,
  353. element = options.element,
  354. field = options.field,
  355. clientRect = options.clientRect,
  356. spiral = options.spiral,
  357. attempt = 1,
  358. delta = {
  359. x: 0,
  360. y: 0
  361. },
  362. rect = point.rect = extend({}, clientRect);
  363. /**
  364. * while w intersects any previously placed words:
  365. * do {
  366. * move w a little bit along a spiral path
  367. * } while any part of w is outside the playing field and
  368. * the spiral radius is still smallish
  369. */
  370. while (
  371. (
  372. intersectsAnyWord(point, placed) ||
  373. outsidePlayingField(element, field)
  374. ) && delta !== false
  375. ) {
  376. delta = spiral(attempt, {
  377. field: field
  378. });
  379. if (isObject(delta)) {
  380. // Update the DOMRect with new positions.
  381. rect.left = clientRect.left + delta.x;
  382. rect.right = rect.left + rect.width;
  383. rect.top = clientRect.top + delta.y;
  384. rect.bottom = rect.top + rect.height;
  385. }
  386. attempt++;
  387. }
  388. return delta;
  389. };
  390. /**
  391. * updateFieldBoundaries - If a rectangle is outside a give field, then the
  392. * boundaries of the field is adjusted accordingly. Modifies the field object
  393. * which is passed as the first parameter.
  394. *
  395. * @param {object} field The bounding box of a playing field.
  396. * @param {object} placement The bounding box for a placed point.
  397. * @return {object} Returns a modified field object.
  398. */
  399. var updateFieldBoundaries = function updateFieldBoundaries(field, rectangle) {
  400. // TODO improve type checking.
  401. if (!isNumber(field.left) || field.left > rectangle.left) {
  402. field.left = rectangle.left;
  403. }
  404. if (!isNumber(field.right) || field.right < rectangle.right) {
  405. field.right = rectangle.right;
  406. }
  407. if (!isNumber(field.top) || field.top > rectangle.top) {
  408. field.top = rectangle.top;
  409. }
  410. if (!isNumber(field.bottom) || field.bottom < rectangle.bottom) {
  411. field.bottom = rectangle.bottom;
  412. }
  413. return field;
  414. };
  415. /**
  416. * A word cloud is a visualization of a set of words, where the size and
  417. * placement of a word is determined by how it is weighted.
  418. *
  419. * @extends {plotOptions.column}
  420. * @sample highcharts/demo/wordcloud Word Cloud chart
  421. * @excluding allAreas, boostThreshold, clip, colorAxis, compare, compareBase,
  422. * crisp, cropTreshold, dataGrouping, dataLabels, depth, edgeColor,
  423. * findNearestPointBy, getExtremesFromAll, grouping, groupPadding,
  424. * groupZPadding, joinBy, maxPointWidth, minPointLength,
  425. * navigatorOptions, negativeColor, pointInterval, pointIntervalUnit,
  426. * pointPadding, pointPlacement, pointRange, pointStart, pointWidth,
  427. * pointStart, pointWidth, shadow, showCheckbox, showInNavigator,
  428. * softThreshold, stacking, threshold, zoneAxis, zones
  429. * @product highcharts
  430. * @since 6.0.0
  431. * @optionparent plotOptions.wordcloud
  432. */
  433. var wordCloudOptions = {
  434. animation: {
  435. duration: 500
  436. },
  437. borderWidth: 0,
  438. clip: false, // Something goes wrong with clip. // TODO fix this
  439. /**
  440. * When using automatic point colors pulled from the `options.colors`
  441. * collection, this option determines whether the chart should receive
  442. * one color per series or one color per point.
  443. *
  444. * @see [series colors](#plotOptions.column.colors)
  445. */
  446. colorByPoint: true,
  447. /**
  448. * A threshold determining the minimum font size that can be applied to a
  449. * word.
  450. */
  451. minFontSize: 1,
  452. /**
  453. * The word with the largest weight will have a font size equal to this
  454. * value. The font size of a word is the ratio between its weight and the
  455. * largest occuring weight, multiplied with the value of maxFontSize.
  456. */
  457. maxFontSize: 25,
  458. /**
  459. * This option decides which algorithm is used for placement, and rotation
  460. * of a word. The choice of algorith is therefore a crucial part of the
  461. * resulting layout of the wordcloud.
  462. * It is possible for users to add their own custom placement strategies
  463. * for use in word cloud. Read more about it in our
  464. * [documentation](https://www.highcharts.com/docs/chart-and-series-types/word-cloud-series#custom-placement-strategies)
  465. *
  466. * @validvalue: ["center", "random"]
  467. */
  468. placementStrategy: 'center',
  469. /**
  470. * Rotation options for the words in the wordcloud.
  471. * @sample highcharts/plotoptions/wordcloud-rotation
  472. * Word cloud with rotation
  473. */
  474. rotation: {
  475. /**
  476. * The smallest degree of rotation for a word.
  477. */
  478. from: 0,
  479. /**
  480. * The number of possible orientations for a word, within the range of
  481. * `rotation.from` and `rotation.to`.
  482. */
  483. orientations: 2,
  484. /**
  485. * The largest degree of rotation for a word.
  486. */
  487. to: 90
  488. },
  489. showInLegend: false,
  490. /**
  491. * Spiral used for placing a word after the inital position experienced a
  492. * collision with either another word or the borders.
  493. * It is possible for users to add their own custom spiralling algorithms
  494. * for use in word cloud. Read more about it in our
  495. * [documentation](https://www.highcharts.com/docs/chart-and-series-types/word-cloud-series#custom-spiralling-algorithm)
  496. *
  497. * @validvalue: ["archimedean", "rectangular", "square"]
  498. */
  499. spiral: 'rectangular',
  500. /**
  501. * CSS styles for the words.
  502. *
  503. * @type {CSSObject}
  504. * @default {"fontFamily":"sans-serif", "fontWeight": "900"}
  505. */
  506. style: {
  507. fontFamily: 'sans-serif',
  508. fontWeight: '900'
  509. },
  510. tooltip: {
  511. followPointer: true,
  512. pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.weight}</b><br/>'
  513. }
  514. };
  515. /**
  516. * Properties of the WordCloud series.
  517. */
  518. var wordCloudSeries = {
  519. animate: Series.prototype.animate,
  520. bindAxes: function () {
  521. var wordcloudAxis = {
  522. endOnTick: false,
  523. gridLineWidth: 0,
  524. lineWidth: 0,
  525. maxPadding: 0,
  526. startOnTick: false,
  527. title: null,
  528. tickPositions: []
  529. };
  530. Series.prototype.bindAxes.call(this);
  531. extend(this.yAxis.options, wordcloudAxis);
  532. extend(this.xAxis.options, wordcloudAxis);
  533. },
  534. /**
  535. * deriveFontSize - Calculates the fontSize of a word based on its weight.
  536. *
  537. * @param {number} [relativeWeight] The weight of the word, on a scale 0-1.
  538. * Defaults to 0.
  539. * @param {number} [maxFontSize] The maximum font size of a word. Defaults
  540. * to 1.
  541. * @param {number} [minFontSize] The minimum font size of a word. Defaults
  542. * to 1.
  543. * @returns {number} Returns the resulting fontSize of a word. If
  544. * minFontSize is larger then maxFontSize the result will equal minFontSize.
  545. */
  546. deriveFontSize: function deriveFontSize(
  547. relativeWeight,
  548. maxFontSize,
  549. minFontSize
  550. ) {
  551. var weight = isNumber(relativeWeight) ? relativeWeight : 0,
  552. max = isNumber(maxFontSize) ? maxFontSize : 1,
  553. min = isNumber(minFontSize) ? minFontSize : 1;
  554. return Math.floor(Math.max(min, weight * max));
  555. },
  556. drawPoints: function () {
  557. var series = this,
  558. hasRendered = series.hasRendered,
  559. xAxis = series.xAxis,
  560. yAxis = series.yAxis,
  561. chart = series.chart,
  562. group = series.group,
  563. options = series.options,
  564. animation = options.animation,
  565. renderer = chart.renderer,
  566. testElement = renderer.text().add(group),
  567. placed = [],
  568. placementStrategy = series.placementStrategy[
  569. options.placementStrategy
  570. ],
  571. spiral = series.spirals[options.spiral],
  572. rotation = options.rotation,
  573. scale,
  574. weights = series.points
  575. .map(function (p) {
  576. return p.weight;
  577. }),
  578. maxWeight = Math.max.apply(null, weights),
  579. data = series.points
  580. .sort(function (a, b) {
  581. return b.weight - a.weight; // Sort descending
  582. }),
  583. field;
  584. // Get the dimensions for each word.
  585. // Used in calculating the playing field.
  586. each(data, function (point) {
  587. var relativeWeight = 1 / maxWeight * point.weight,
  588. fontSize = series.deriveFontSize(
  589. relativeWeight,
  590. options.maxFontSize,
  591. options.minFontSize
  592. ),
  593. css = extend({
  594. fontSize: fontSize + 'px'
  595. }, options.style),
  596. bBox;
  597. testElement.css(css).attr({
  598. x: 0,
  599. y: 0,
  600. text: point.name
  601. });
  602. // TODO Replace all use of clientRect with bBox.
  603. bBox = testElement.getBBox(true);
  604. point.dimensions = {
  605. height: bBox.height,
  606. width: bBox.width
  607. };
  608. });
  609. // Calculate the playing field.
  610. field = getPlayingField(xAxis.len, yAxis.len, data);
  611. // Draw all the points.
  612. each(data, function (point) {
  613. var relativeWeight = 1 / maxWeight * point.weight,
  614. fontSize = series.deriveFontSize(
  615. relativeWeight,
  616. options.maxFontSize,
  617. options.minFontSize
  618. ),
  619. css = extend({
  620. fontSize: fontSize + 'px',
  621. fill: point.color
  622. }, options.style),
  623. placement = placementStrategy(point, {
  624. data: data,
  625. field: field,
  626. placed: placed,
  627. rotation: rotation
  628. }),
  629. attr = {
  630. align: 'center',
  631. x: placement.x,
  632. y: placement.y,
  633. text: point.name,
  634. rotation: placement.rotation
  635. },
  636. animate,
  637. delta,
  638. clientRect;
  639. testElement.css(css).attr(attr);
  640. // Cache the original DOMRect values for later calculations.
  641. point.clientRect = clientRect = extend(
  642. {},
  643. testElement.element.getBoundingClientRect()
  644. );
  645. delta = intersectionTesting(point, {
  646. clientRect: clientRect,
  647. element: testElement,
  648. field: field,
  649. placed: placed,
  650. spiral: spiral
  651. });
  652. /**
  653. * Check if point was placed, if so delete it,
  654. * otherwise place it on the correct positions.
  655. */
  656. if (isObject(delta)) {
  657. attr.x += delta.x;
  658. attr.y += delta.y;
  659. extend(placement, {
  660. left: attr.x - (clientRect.width / 2),
  661. right: attr.x + (clientRect.width / 2),
  662. top: attr.y - (clientRect.height / 2),
  663. bottom: attr.y + (clientRect.height / 2)
  664. });
  665. field = updateFieldBoundaries(field, placement);
  666. placed.push(point);
  667. point.isNull = false;
  668. } else {
  669. point.isNull = true;
  670. }
  671. if (animation) {
  672. // Animate to new positions
  673. animate = {
  674. x: attr.x,
  675. y: attr.y
  676. };
  677. // Animate from center of chart
  678. if (!hasRendered) {
  679. attr.x = 0;
  680. attr.y = 0;
  681. // or animate from previous position
  682. } else {
  683. delete attr.x;
  684. delete attr.y;
  685. }
  686. }
  687. point.draw({
  688. animate: animate,
  689. attr: attr,
  690. css: css,
  691. group: group,
  692. renderer: renderer,
  693. shapeArgs: undefined,
  694. shapeType: 'text'
  695. });
  696. });
  697. // Destroy the element after use.
  698. testElement = testElement.destroy();
  699. /**
  700. * Scale the series group to fit within the plotArea.
  701. */
  702. scale = getScale(xAxis.len, yAxis.len, field);
  703. series.group.attr({
  704. scaleX: scale,
  705. scaleY: scale
  706. });
  707. },
  708. hasData: function () {
  709. var series = this;
  710. return (
  711. isObject(series) &&
  712. series.visible === true &&
  713. isArray(series.points) &&
  714. series.points.length > 0
  715. );
  716. },
  717. /**
  718. * Strategies used for deciding rotation and initial position of a word.
  719. * To implement a custom strategy, have a look at the function
  720. * randomPlacement for example.
  721. */
  722. placementStrategy: {
  723. random: function randomPlacement(point, options) {
  724. var field = options.field,
  725. r = options.rotation;
  726. return {
  727. x: getRandomPosition(field.width) - (field.width / 2),
  728. y: getRandomPosition(field.height) - (field.height / 2),
  729. rotation: getRotation(r.orientations, point.index, r.from, r.to)
  730. };
  731. },
  732. center: function centerPlacement(point, options) {
  733. var r = options.rotation;
  734. return {
  735. x: 0,
  736. y: 0,
  737. rotation: getRotation(r.orientations, point.index, r.from, r.to)
  738. };
  739. }
  740. },
  741. pointArrayMap: ['weight'],
  742. /**
  743. * Spirals used for placing a word after the inital position experienced a
  744. * collision with either another word or the borders.
  745. * To implement a custom spiral, look at the function archimedeanSpiral for
  746. * example.
  747. */
  748. spirals: {
  749. 'archimedean': archimedeanSpiral,
  750. 'rectangular': rectangularSpiral,
  751. 'square': squareSpiral
  752. },
  753. utils: {
  754. getRotation: getRotation
  755. },
  756. getPlotBox: function () {
  757. var series = this,
  758. chart = series.chart,
  759. inverted = chart.inverted,
  760. // Swap axes for inverted (#2339)
  761. xAxis = series[(inverted ? 'yAxis' : 'xAxis')],
  762. yAxis = series[(inverted ? 'xAxis' : 'yAxis')],
  763. width = xAxis ? xAxis.len : chart.plotWidth,
  764. height = yAxis ? yAxis.len : chart.plotHeight,
  765. x = xAxis ? xAxis.left : chart.plotLeft,
  766. y = yAxis ? yAxis.top : chart.plotTop;
  767. return {
  768. translateX: x + (width / 2),
  769. translateY: y + (height / 2),
  770. scaleX: 1, // #1623
  771. scaleY: 1
  772. };
  773. }
  774. };
  775. /**
  776. * Properties of the Sunburst series.
  777. */
  778. var wordCloudPoint = {
  779. draw: drawPoint,
  780. shouldDraw: function shouldDraw() {
  781. var point = this;
  782. return !point.isNull;
  783. }
  784. };
  785. /**
  786. * A `wordcloud` series. If the [type](#series.wordcloud.type) option is
  787. * not specified, it is inherited from [chart.type](#chart.type).
  788. *
  789. * @type {Object}
  790. * @extends series,plotOptions.wordcloud
  791. * @product highcharts
  792. * @apioption series.wordcloud
  793. */
  794. /**
  795. * An array of data points for the series. For the `wordcloud` series
  796. * type, points can be given in the following ways:
  797. *
  798. * 1. An array of arrays with 2 values. In this case, the values
  799. * correspond to `name,weight`.
  800. *
  801. * ```js
  802. * data: [
  803. * ['Lorem', 4],
  804. * ['Ipsum', 1]
  805. * ]
  806. * ```
  807. *
  808. * 2. An array of objects with named values. The objects are point
  809. * configuration objects as seen below. If the total number of data
  810. * points exceeds the series'
  811. * [turboThreshold](#series.arearange.turboThreshold), this option is not
  812. * available.
  813. *
  814. * ```js
  815. * data: [{
  816. * name: "Lorem",
  817. * weight: 4
  818. * }, {
  819. * name: "Ipsum",
  820. * weight: 1
  821. * }]
  822. * ```
  823. *
  824. * @type {Array<Object|Array>}
  825. * @extends series.line.data
  826. * @excluding drilldown,marker,x,y
  827. * @product highcharts
  828. * @apioption series.wordcloud.data
  829. */
  830. /**
  831. * The name decides the text for a word.
  832. *
  833. * @type {String}
  834. * @default undefined
  835. * @since 6.0.0
  836. * @product highcharts
  837. * @apioption series.sunburst.data.name
  838. */
  839. /**
  840. * The weighting of a word. The weight decides the relative size of a word
  841. * compared to the rest of the collection.
  842. *
  843. * @type {Number}
  844. * @default undefined
  845. * @since 6.0.0
  846. * @product highcharts
  847. * @apioption series.sunburst.data.weight
  848. */
  849. H.seriesType(
  850. 'wordcloud',
  851. 'column',
  852. wordCloudOptions,
  853. wordCloudSeries,
  854. wordCloudPoint
  855. );
  856. }(Highcharts, draw));
  857. }));