PackedBubbleSeries.js 52 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339
  1. /* *
  2. *
  3. * (c) 2010-2018 Grzegorz Blachlinski, Sebastian Bochan
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8. *
  9. * */
  10. 'use strict';
  11. import Chart from '../Core/Chart/Chart.js';
  12. import Color from '../Core/Color.js';
  13. var color = Color.parse;
  14. import H from '../Core/Globals.js';
  15. import Point from '../Core/Series/Point.js';
  16. import U from '../Core/Utilities.js';
  17. var addEvent = U.addEvent, clamp = U.clamp, defined = U.defined, extend = U.extend, extendClass = U.extendClass, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, merge = U.merge, pick = U.pick, seriesType = U.seriesType;
  18. /**
  19. * Formatter callback function.
  20. *
  21. * @callback Highcharts.SeriesPackedBubbleDataLabelsFormatterCallbackFunction
  22. *
  23. * @param {Highcharts.SeriesPackedBubbleDataLabelsFormatterContextObject} this
  24. * Data label context to format
  25. *
  26. * @return {string}
  27. * Formatted data label text
  28. */
  29. /**
  30. * Context for the formatter function.
  31. *
  32. * @interface Highcharts.SeriesPackedBubbleDataLabelsFormatterContextObject
  33. * @extends Highcharts.PointLabelObject
  34. * @since 7.0.0
  35. */ /**
  36. * The color of the node.
  37. * @name Highcharts.SeriesPackedBubbleDataLabelsFormatterContextObject#color
  38. * @type {Highcharts.ColorString}
  39. * @since 7.0.0
  40. */ /**
  41. * The point (node) object. The node name, if defined, is available through
  42. * `this.point.name`. Arrays: `this.point.linksFrom` and `this.point.linksTo`
  43. * contains all nodes connected to this point.
  44. * @name Highcharts.SeriesPackedBubbleDataLabelsFormatterContextObject#point
  45. * @type {Highcharts.Point}
  46. * @since 7.0.0
  47. */ /**
  48. * The ID of the node.
  49. * @name Highcharts.SeriesPackedBubbleDataLabelsFormatterContextObject#key
  50. * @type {string}
  51. * @since 7.0.0
  52. */
  53. import '../Core/Axis/Axis.js';
  54. import './Bubble/BubbleSeries.js';
  55. import '../Series/Networkgraph/DraggableNodes.js';
  56. import '../Series/Networkgraph/Layouts.js';
  57. var Series = H.Series, Reingold = H.layouts['reingold-fruchterman'], dragNodesMixin = H.dragNodesMixin;
  58. Chart.prototype.getSelectedParentNodes = function () {
  59. var chart = this, series = chart.series, selectedParentsNodes = [];
  60. series.forEach(function (series) {
  61. if (series.parentNode && series.parentNode.selected) {
  62. selectedParentsNodes.push(series.parentNode);
  63. }
  64. });
  65. return selectedParentsNodes;
  66. };
  67. H.networkgraphIntegrations.packedbubble = {
  68. repulsiveForceFunction: function (d, k, node, repNode) {
  69. return Math.min(d, (node.marker.radius + repNode.marker.radius) / 2);
  70. },
  71. barycenter: function () {
  72. var layout = this, gravitationalConstant = layout.options.gravitationalConstant, box = layout.box, nodes = layout.nodes, centerX, centerY;
  73. nodes.forEach(function (node) {
  74. if (layout.options.splitSeries && !node.isParentNode) {
  75. centerX = node.series.parentNode.plotX;
  76. centerY = node.series.parentNode.plotY;
  77. }
  78. else {
  79. centerX = box.width / 2;
  80. centerY = box.height / 2;
  81. }
  82. if (!node.fixedPosition) {
  83. node.plotX -=
  84. (node.plotX - centerX) *
  85. gravitationalConstant /
  86. (node.mass * Math.sqrt(nodes.length));
  87. node.plotY -=
  88. (node.plotY - centerY) *
  89. gravitationalConstant /
  90. (node.mass * Math.sqrt(nodes.length));
  91. }
  92. });
  93. },
  94. repulsive: function (node, force, distanceXY, repNode) {
  95. var factor = (force * this.diffTemperature / node.mass /
  96. node.degree), x = distanceXY.x * factor, y = distanceXY.y * factor;
  97. if (!node.fixedPosition) {
  98. node.plotX += x;
  99. node.plotY += y;
  100. }
  101. if (!repNode.fixedPosition) {
  102. repNode.plotX -= x;
  103. repNode.plotY -= y;
  104. }
  105. },
  106. integrate: H.networkgraphIntegrations.verlet.integrate,
  107. getK: H.noop
  108. };
  109. H.layouts.packedbubble = extendClass(Reingold, {
  110. beforeStep: function () {
  111. if (this.options.marker) {
  112. this.series.forEach(function (series) {
  113. if (series) {
  114. series.calculateParentRadius();
  115. }
  116. });
  117. }
  118. },
  119. setCircularPositions: function () {
  120. var layout = this, box = layout.box, nodes = layout.nodes, nodesLength = nodes.length + 1, angle = 2 * Math.PI / nodesLength, centerX, centerY, radius = layout.options.initialPositionRadius;
  121. nodes.forEach(function (node, index) {
  122. if (layout.options.splitSeries &&
  123. !node.isParentNode) {
  124. centerX = node.series.parentNode.plotX;
  125. centerY = node.series.parentNode.plotY;
  126. }
  127. else {
  128. centerX = box.width / 2;
  129. centerY = box.height / 2;
  130. }
  131. node.plotX = node.prevX = pick(node.plotX, centerX +
  132. radius * Math.cos(node.index || index * angle));
  133. node.plotY = node.prevY = pick(node.plotY, centerY +
  134. radius * Math.sin(node.index || index * angle));
  135. node.dispX = 0;
  136. node.dispY = 0;
  137. });
  138. },
  139. repulsiveForces: function () {
  140. var layout = this, force, distanceR, distanceXY, bubblePadding = layout.options.bubblePadding;
  141. layout.nodes.forEach(function (node) {
  142. node.degree = node.mass;
  143. node.neighbours = 0;
  144. layout.nodes.forEach(function (repNode) {
  145. force = 0;
  146. if (
  147. // Node can not repulse itself:
  148. node !== repNode &&
  149. // Only close nodes affect each other:
  150. // Not dragged:
  151. !node.fixedPosition &&
  152. (layout.options.seriesInteraction ||
  153. node.series === repNode.series)) {
  154. distanceXY = layout.getDistXY(node, repNode);
  155. distanceR = (layout.vectorLength(distanceXY) -
  156. (node.marker.radius +
  157. repNode.marker.radius +
  158. bubblePadding));
  159. // TODO padding configurable
  160. if (distanceR < 0) {
  161. node.degree += 0.01;
  162. node.neighbours++;
  163. force = layout.repulsiveForce(-distanceR / Math.sqrt(node.neighbours), layout.k, node, repNode);
  164. }
  165. layout.force('repulsive', node, force * repNode.mass, distanceXY, repNode, distanceR);
  166. }
  167. });
  168. });
  169. },
  170. applyLimitBox: function (node) {
  171. var layout = this, distanceXY, distanceR, factor = 0.01;
  172. // parentNodeLimit should be used together
  173. // with seriesInteraction: false
  174. if (layout.options.splitSeries &&
  175. !node.isParentNode &&
  176. layout.options.parentNodeLimit) {
  177. distanceXY = layout.getDistXY(node, node.series.parentNode);
  178. distanceR = (node.series.parentNodeRadius -
  179. node.marker.radius -
  180. layout.vectorLength(distanceXY));
  181. if (distanceR < 0 &&
  182. distanceR > -2 * node.marker.radius) {
  183. node.plotX -= distanceXY.x * factor;
  184. node.plotY -= distanceXY.y * factor;
  185. }
  186. }
  187. Reingold.prototype.applyLimitBox.apply(this, arguments);
  188. }
  189. });
  190. /**
  191. * @private
  192. * @class
  193. * @name Highcharts.seriesTypes.packedbubble
  194. *
  195. * @extends Highcharts.Series
  196. */
  197. seriesType('packedbubble', 'bubble',
  198. /**
  199. * A packed bubble series is a two dimensional series type, where each point
  200. * renders a value in X, Y position. Each point is drawn as a bubble
  201. * where the bubbles don't overlap with each other and the radius
  202. * of the bubble relates to the value.
  203. *
  204. * @sample highcharts/demo/packed-bubble/
  205. * Packed bubble chart
  206. * @sample highcharts/demo/packed-bubble-split/
  207. * Split packed bubble chart
  208. * @extends plotOptions.bubble
  209. * @excluding connectEnds, connectNulls, cropThreshold, dragDrop, jitter,
  210. * keys, pointPlacement, sizeByAbsoluteValue, step, xAxis,
  211. * yAxis, zMax, zMin, dataSorting, boostThreshold,
  212. * boostBlending
  213. * @product highcharts
  214. * @since 7.0.0
  215. * @requires highcharts-more
  216. * @optionparent plotOptions.packedbubble
  217. */
  218. {
  219. /**
  220. * Minimum bubble size. Bubbles will automatically size between the
  221. * `minSize` and `maxSize` to reflect the value of each bubble.
  222. * Can be either pixels (when no unit is given), or a percentage of
  223. * the smallest one of the plot width and height, divided by the square
  224. * root of total number of points.
  225. *
  226. * @sample highcharts/plotoptions/bubble-size/
  227. * Bubble size
  228. *
  229. * @type {number|string}
  230. *
  231. * @private
  232. */
  233. minSize: '10%',
  234. /**
  235. * Maximum bubble size. Bubbles will automatically size between the
  236. * `minSize` and `maxSize` to reflect the value of each bubble.
  237. * Can be either pixels (when no unit is given), or a percentage of
  238. * the smallest one of the plot width and height, divided by the square
  239. * root of total number of points.
  240. *
  241. * @sample highcharts/plotoptions/bubble-size/
  242. * Bubble size
  243. *
  244. * @type {number|string}
  245. *
  246. * @private
  247. */
  248. maxSize: '50%',
  249. sizeBy: 'area',
  250. zoneAxis: 'y',
  251. crisp: false,
  252. tooltip: {
  253. pointFormat: 'Value: {point.value}'
  254. },
  255. /**
  256. * Flag to determine if nodes are draggable or not. Available for
  257. * graph with useSimulation set to true only.
  258. *
  259. * @since 7.1.0
  260. *
  261. * @private
  262. */
  263. draggable: true,
  264. /**
  265. * An option is giving a possibility to choose between using simulation
  266. * for calculating bubble positions. These reflects in both animation
  267. * and final position of bubbles. Simulation is also adding options to
  268. * the series graph based on used layout. In case of big data sets, with
  269. * any performance issues, it is possible to disable animation and pack
  270. * bubble in a simple circular way.
  271. *
  272. * @sample highcharts/series-packedbubble/spiral/
  273. * useSimulation set to false
  274. *
  275. * @since 7.1.0
  276. *
  277. * @private
  278. */
  279. useSimulation: true,
  280. /**
  281. * Series options for parent nodes.
  282. *
  283. * @since 8.1.1
  284. *
  285. * @private
  286. */
  287. parentNode: {
  288. /**
  289. * Allow this series' parent nodes to be selected
  290. * by clicking on the graph.
  291. *
  292. * @since 8.1.1
  293. */
  294. allowPointSelect: false
  295. },
  296. /**
  297. /**
  298. *
  299. * @declare Highcharts.SeriesPackedBubbleDataLabelsOptionsObject
  300. *
  301. * @private
  302. */
  303. dataLabels: {
  304. /**
  305. * The
  306. * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
  307. * specifying what to show for _node_ in the networkgraph. In v7.0
  308. * defaults to `{key}`, since v7.1 defaults to `undefined` and
  309. * `formatter` is used instead.
  310. *
  311. * @type {string}
  312. * @since 7.0.0
  313. * @apioption plotOptions.packedbubble.dataLabels.format
  314. */
  315. // eslint-disable-next-line valid-jsdoc
  316. /**
  317. * Callback JavaScript function to format the data label for a node.
  318. * Note that if a `format` is defined, the format takes precedence
  319. * and the formatter is ignored.
  320. *
  321. * @type {Highcharts.SeriesPackedBubbleDataLabelsFormatterCallbackFunction}
  322. * @since 7.0.0
  323. */
  324. formatter: function () {
  325. return this.point.value;
  326. },
  327. /**
  328. * @type {string}
  329. * @since 7.1.0
  330. * @apioption plotOptions.packedbubble.dataLabels.parentNodeFormat
  331. */
  332. // eslint-disable-next-line valid-jsdoc
  333. /**
  334. * @type {Highcharts.SeriesPackedBubbleDataLabelsFormatterCallbackFunction}
  335. * @since 7.1.0
  336. */
  337. parentNodeFormatter: function () {
  338. return this.name;
  339. },
  340. /**
  341. * @sample {highcharts} highcharts/series-packedbubble/packed-dashboard
  342. * Dashboard with dataLabels on parentNodes
  343. *
  344. * @declare Highcharts.SeriesPackedBubbleDataLabelsTextPathOptionsObject
  345. * @since 7.1.0
  346. */
  347. parentNodeTextPath: {
  348. /**
  349. * Presentation attributes for the text path.
  350. *
  351. * @type {Highcharts.SVGAttributes}
  352. * @since 7.1.0
  353. * @apioption plotOptions.packedbubble.dataLabels.attributes
  354. */
  355. /**
  356. * Enable or disable `textPath` option for link's or marker's
  357. * data labels.
  358. *
  359. * @since 7.1.0
  360. */
  361. enabled: true
  362. },
  363. /**
  364. * Options for a _node_ label text which should follow marker's
  365. * shape.
  366. *
  367. * **Note:** Only SVG-based renderer supports this option.
  368. *
  369. * @extends plotOptions.series.dataLabels.textPath
  370. * @apioption plotOptions.packedbubble.dataLabels.textPath
  371. */
  372. padding: 0,
  373. style: {
  374. transition: 'opacity 2000ms'
  375. }
  376. },
  377. /**
  378. * Options for layout algorithm when simulation is enabled. Inside there
  379. * are options to change the speed, padding, initial bubbles positions
  380. * and more.
  381. *
  382. * @extends plotOptions.networkgraph.layoutAlgorithm
  383. * @excluding approximation, attractiveForce, repulsiveForce, theta
  384. * @since 7.1.0
  385. *
  386. * @private
  387. */
  388. layoutAlgorithm: {
  389. /**
  390. * Initial layout algorithm for positioning nodes. Can be one of
  391. * the built-in options ("circle", "random") or a function where
  392. * positions should be set on each node (`this.nodes`) as
  393. * `node.plotX` and `node.plotY`.
  394. *
  395. * @sample highcharts/series-networkgraph/initial-positions/
  396. * Initial positions with callback
  397. *
  398. * @type {"circle"|"random"|Function}
  399. */
  400. initialPositions: 'circle',
  401. /**
  402. * @sample highcharts/series-packedbubble/initial-radius/
  403. * Initial radius set to 200
  404. *
  405. * @extends plotOptions.networkgraph.layoutAlgorithm.initialPositionRadius
  406. * @excluding states
  407. */
  408. initialPositionRadius: 20,
  409. /**
  410. * The distance between two bubbles, when the algorithm starts to
  411. * treat two bubbles as overlapping. The `bubblePadding` is also the
  412. * expected distance between all the bubbles on simulation end.
  413. */
  414. bubblePadding: 5,
  415. /**
  416. * Whether bubbles should interact with their parentNode to keep
  417. * them inside.
  418. */
  419. parentNodeLimit: false,
  420. /**
  421. * Whether series should interact with each other or not. When
  422. * `parentNodeLimit` is set to true, thi option should be set to
  423. * false to avoid sticking points in wrong series parentNode.
  424. */
  425. seriesInteraction: true,
  426. /**
  427. * In case of split series, this option allows user to drag and
  428. * drop points between series, for changing point related series.
  429. *
  430. * @sample highcharts/series-packedbubble/packed-dashboard/
  431. * Example of drag'n drop bubbles for bubble kanban
  432. */
  433. dragBetweenSeries: false,
  434. /**
  435. * Layout algorithm options for parent nodes.
  436. *
  437. * @extends plotOptions.networkgraph.layoutAlgorithm
  438. * @excluding approximation, attractiveForce, enableSimulation,
  439. * repulsiveForce, theta
  440. */
  441. parentNodeOptions: {
  442. maxIterations: 400,
  443. gravitationalConstant: 0.03,
  444. maxSpeed: 50,
  445. initialPositionRadius: 100,
  446. seriesInteraction: true,
  447. /**
  448. * Styling options for parentNodes markers. Similar to
  449. * line.marker options.
  450. *
  451. * @sample highcharts/series-packedbubble/parentnode-style/
  452. * Bubble size
  453. *
  454. * @extends plotOptions.series.marker
  455. * @excluding states
  456. */
  457. marker: {
  458. fillColor: null,
  459. fillOpacity: 1,
  460. lineWidth: 1,
  461. lineColor: null,
  462. symbol: 'circle'
  463. }
  464. },
  465. enableSimulation: true,
  466. /**
  467. * Type of the algorithm used when positioning bubbles.
  468. * @ignore-option
  469. */
  470. type: 'packedbubble',
  471. /**
  472. * Integration type. Integration determines how forces are applied
  473. * on particles. The `packedbubble` integration is based on
  474. * the networkgraph `verlet` integration, where the new position
  475. * is based on a previous position without velocity:
  476. * `newPosition += previousPosition - newPosition`.
  477. *
  478. * @sample highcharts/series-networkgraph/forces/
  479. *
  480. * @ignore-option
  481. */
  482. integration: 'packedbubble',
  483. maxIterations: 1000,
  484. /**
  485. * Whether to split series into individual groups or to mix all
  486. * series together.
  487. *
  488. * @since 7.1.0
  489. * @default false
  490. */
  491. splitSeries: false,
  492. /**
  493. * Max speed that node can get in one iteration. In terms of
  494. * simulation, it's a maximum translation (in pixels) that a node
  495. * can move (in both, x and y, dimensions). While `friction` is
  496. * applied on all nodes, max speed is applied only for nodes that
  497. * move very fast, for example small or disconnected ones.
  498. *
  499. * @see [layoutAlgorithm.integration](#series.networkgraph.layoutAlgorithm.integration)
  500. *
  501. * @see [layoutAlgorithm.friction](#series.networkgraph.layoutAlgorithm.friction)
  502. */
  503. maxSpeed: 5,
  504. gravitationalConstant: 0.01,
  505. friction: -0.981
  506. }
  507. }, {
  508. /**
  509. * An internal option used for allowing nodes dragging.
  510. * @private
  511. */
  512. hasDraggableNodes: true,
  513. /**
  514. * Array of internal forces. Each force should be later defined in
  515. * integrations.js.
  516. * @private
  517. */
  518. forces: ['barycenter', 'repulsive'],
  519. pointArrayMap: ['value'],
  520. trackerGroups: ['group', 'dataLabelsGroup', 'parentNodesGroup'],
  521. pointValKey: 'value',
  522. isCartesian: false,
  523. requireSorting: false,
  524. directTouch: true,
  525. axisTypes: [],
  526. noSharedTooltip: true,
  527. // solving #12287
  528. searchPoint: H.noop,
  529. /* eslint-disable no-invalid-this, valid-jsdoc */
  530. /**
  531. * Create a single array of all points from all series
  532. * @private
  533. * @param {Highcharts.Series} series Array of all series objects
  534. * @return {Array<Highcharts.PackedBubbleData>} Returns the array of all points.
  535. */
  536. accumulateAllPoints: function (series) {
  537. var chart = series.chart, allDataPoints = [], i, j;
  538. for (i = 0; i < chart.series.length; i++) {
  539. series = chart.series[i];
  540. if (series.is('packedbubble') && // #13574
  541. series.visible ||
  542. !chart.options.chart.ignoreHiddenSeries) {
  543. // add data to array only if series is visible
  544. for (j = 0; j < series.yData.length; j++) {
  545. allDataPoints.push([
  546. null, null,
  547. series.yData[j],
  548. series.index,
  549. j,
  550. {
  551. id: j,
  552. marker: {
  553. radius: 0
  554. }
  555. }
  556. ]);
  557. }
  558. }
  559. }
  560. return allDataPoints;
  561. },
  562. init: function () {
  563. Series.prototype.init.apply(this, arguments);
  564. // When one series is modified, the others need to be recomputed
  565. addEvent(this, 'updatedData', function () {
  566. this.chart.series.forEach(function (s) {
  567. if (s.type === this.type) {
  568. s.isDirty = true;
  569. }
  570. }, this);
  571. });
  572. return this;
  573. },
  574. render: function () {
  575. var series = this, dataLabels = [];
  576. Series.prototype.render.apply(this, arguments);
  577. // #10823 - dataLabels should stay visible
  578. // when enabled allowOverlap.
  579. if (!series.options.dataLabels.allowOverlap) {
  580. series.data.forEach(function (point) {
  581. if (isArray(point.dataLabels)) {
  582. point.dataLabels.forEach(function (dataLabel) {
  583. dataLabels.push(dataLabel);
  584. });
  585. }
  586. });
  587. // Only hide overlapping dataLabels for layouts that
  588. // use simulation. Spiral packedbubble don't need
  589. // additional dataLabel hiding on every simulation step
  590. if (series.options.useSimulation) {
  591. series.chart.hideOverlappingLabels(dataLabels);
  592. }
  593. }
  594. },
  595. // Needed because of z-indexing issue if point is added in series.group
  596. setVisible: function () {
  597. var series = this;
  598. Series.prototype.setVisible.apply(series, arguments);
  599. if (series.parentNodeLayout && series.graph) {
  600. if (series.visible) {
  601. series.graph.show();
  602. if (series.parentNode.dataLabel) {
  603. series.parentNode.dataLabel.show();
  604. }
  605. }
  606. else {
  607. series.graph.hide();
  608. series.parentNodeLayout
  609. .removeElementFromCollection(series.parentNode, series.parentNodeLayout.nodes);
  610. if (series.parentNode.dataLabel) {
  611. series.parentNode.dataLabel.hide();
  612. }
  613. }
  614. }
  615. else if (series.layout) {
  616. if (series.visible) {
  617. series.layout.addElementsToCollection(series.points, series.layout.nodes);
  618. }
  619. else {
  620. series.points.forEach(function (node) {
  621. series.layout.removeElementFromCollection(node, series.layout.nodes);
  622. });
  623. }
  624. }
  625. },
  626. // Packedbubble has two separate collecions of nodes if split, render
  627. // dataLabels for both sets:
  628. drawDataLabels: function () {
  629. var textPath = this.options.dataLabels.textPath, points = this.points;
  630. // Render node labels:
  631. Series.prototype.drawDataLabels.apply(this, arguments);
  632. // Render parentNode labels:
  633. if (this.parentNode) {
  634. this.parentNode.formatPrefix = 'parentNode';
  635. this.points = [this.parentNode];
  636. this.options.dataLabels.textPath =
  637. this.options.dataLabels.parentNodeTextPath;
  638. Series.prototype.drawDataLabels.apply(this, arguments);
  639. // Restore nodes
  640. this.points = points;
  641. this.options.dataLabels.textPath = textPath;
  642. }
  643. },
  644. /**
  645. * The function responsible for calculating series bubble' s bBox.
  646. * Needed because of exporting failure when useSimulation
  647. * is set to false
  648. * @private
  649. */
  650. seriesBox: function () {
  651. var series = this, chart = series.chart, data = series.data, max = Math.max, min = Math.min, radius,
  652. // bBox = [xMin, xMax, yMin, yMax]
  653. bBox = [
  654. chart.plotLeft,
  655. chart.plotLeft + chart.plotWidth,
  656. chart.plotTop,
  657. chart.plotTop + chart.plotHeight
  658. ];
  659. data.forEach(function (p) {
  660. if (defined(p.plotX) &&
  661. defined(p.plotY) &&
  662. p.marker.radius) {
  663. radius = p.marker.radius;
  664. bBox[0] = min(bBox[0], p.plotX - radius);
  665. bBox[1] = max(bBox[1], p.plotX + radius);
  666. bBox[2] = min(bBox[2], p.plotY - radius);
  667. bBox[3] = max(bBox[3], p.plotY + radius);
  668. }
  669. });
  670. return isNumber(bBox.width / bBox.height) ?
  671. bBox :
  672. null;
  673. },
  674. /**
  675. * The function responsible for calculating the parent node radius
  676. * based on the total surface of iniside-bubbles and the group BBox
  677. * @private
  678. */
  679. calculateParentRadius: function () {
  680. var series = this, bBox, parentPadding = 20, minParentRadius = 20;
  681. bBox = series.seriesBox();
  682. series.parentNodeRadius = clamp(Math.sqrt(2 * series.parentNodeMass / Math.PI) + parentPadding, minParentRadius, bBox ?
  683. Math.max(Math.sqrt(Math.pow(bBox.width, 2) +
  684. Math.pow(bBox.height, 2)) / 2 + parentPadding, minParentRadius) :
  685. Math.sqrt(2 * series.parentNodeMass / Math.PI) + parentPadding);
  686. if (series.parentNode) {
  687. series.parentNode.marker.radius =
  688. series.parentNode.radius = series.parentNodeRadius;
  689. }
  690. },
  691. // Create Background/Parent Nodes for split series.
  692. drawGraph: function () {
  693. // if the series is not using layout, don't add parent nodes
  694. if (!this.layout || !this.layout.options.splitSeries) {
  695. return;
  696. }
  697. var series = this, chart = series.chart, parentAttribs = {}, nodeMarker = this.layout.options.parentNodeOptions.marker, parentOptions = {
  698. fill: nodeMarker.fillColor || color(series.color).brighten(0.4).get(),
  699. opacity: nodeMarker.fillOpacity,
  700. stroke: nodeMarker.lineColor || series.color,
  701. 'stroke-width': nodeMarker.lineWidth
  702. }, visibility = series.visible ? 'inherit' : 'hidden';
  703. // create the group for parent Nodes if doesn't exist
  704. if (!this.parentNodesGroup) {
  705. series.parentNodesGroup = series.plotGroup('parentNodesGroup', 'parentNode', visibility, 0.1, chart.seriesGroup);
  706. series.group.attr({
  707. zIndex: 2
  708. });
  709. }
  710. this.calculateParentRadius();
  711. parentAttribs = merge({
  712. x: series.parentNode.plotX -
  713. series.parentNodeRadius,
  714. y: series.parentNode.plotY -
  715. series.parentNodeRadius,
  716. width: series.parentNodeRadius * 2,
  717. height: series.parentNodeRadius * 2
  718. }, parentOptions);
  719. if (!series.parentNode.graphic) {
  720. series.graph = series.parentNode.graphic =
  721. chart.renderer.symbol(parentOptions.symbol)
  722. .add(series.parentNodesGroup);
  723. }
  724. series.parentNode.graphic.attr(parentAttribs);
  725. },
  726. /**
  727. * Creating parent nodes for split series, in which all the bubbles
  728. * are rendered.
  729. * @private
  730. */
  731. createParentNodes: function () {
  732. var series = this, chart = series.chart, parentNodeLayout = series.parentNodeLayout, nodeAdded, parentNode = series.parentNode, PackedBubblePoint = series.pointClass;
  733. series.parentNodeMass = 0;
  734. series.points.forEach(function (p) {
  735. series.parentNodeMass +=
  736. Math.PI * Math.pow(p.marker.radius, 2);
  737. });
  738. series.calculateParentRadius();
  739. parentNodeLayout.nodes.forEach(function (node) {
  740. if (node.seriesIndex === series.index) {
  741. nodeAdded = true;
  742. }
  743. });
  744. parentNodeLayout.setArea(0, 0, chart.plotWidth, chart.plotHeight);
  745. if (!nodeAdded) {
  746. if (!parentNode) {
  747. parentNode = (new PackedBubblePoint()).init(this, {
  748. mass: series.parentNodeRadius / 2,
  749. marker: {
  750. radius: series.parentNodeRadius
  751. },
  752. dataLabels: {
  753. inside: false
  754. },
  755. dataLabelOnNull: true,
  756. degree: series.parentNodeRadius,
  757. isParentNode: true,
  758. seriesIndex: series.index
  759. });
  760. }
  761. if (series.parentNode) {
  762. parentNode.plotX = series.parentNode.plotX;
  763. parentNode.plotY = series.parentNode.plotY;
  764. }
  765. series.parentNode = parentNode;
  766. parentNodeLayout.addElementsToCollection([series], parentNodeLayout.series);
  767. parentNodeLayout.addElementsToCollection([parentNode], parentNodeLayout.nodes);
  768. }
  769. },
  770. drawTracker: function () {
  771. var series = this, chart = series.chart, pointer = chart.pointer, onMouseOver = function (e) {
  772. var point = pointer.getPointFromEvent(e);
  773. // undefined on graph in scatterchart
  774. if (typeof point !== 'undefined') {
  775. pointer.isDirectTouch = true;
  776. point.onMouseOver(e);
  777. }
  778. }, parentNode = series.parentNode;
  779. var dataLabels;
  780. H.TrackerMixin.drawTrackerPoint.call(this);
  781. // Add reference to the point
  782. if (parentNode) {
  783. dataLabels = (isArray(parentNode.dataLabels) ?
  784. parentNode.dataLabels :
  785. (parentNode.dataLabel ? [parentNode.dataLabel] : []));
  786. if (parentNode.graphic) {
  787. parentNode.graphic.element.point = parentNode;
  788. }
  789. dataLabels.forEach(function (dataLabel) {
  790. if (dataLabel.div) {
  791. dataLabel.div.point = parentNode;
  792. }
  793. else {
  794. dataLabel.element.point = parentNode;
  795. }
  796. });
  797. }
  798. },
  799. /**
  800. * Function responsible for adding series layout, used for parent nodes.
  801. * @private
  802. */
  803. addSeriesLayout: function () {
  804. var series = this, layoutOptions = series.options.layoutAlgorithm, graphLayoutsStorage = series.chart.graphLayoutsStorage, graphLayoutsLookup = series.chart.graphLayoutsLookup, parentNodeOptions = merge(layoutOptions, layoutOptions.parentNodeOptions, {
  805. enableSimulation: series.layout.options.enableSimulation
  806. }), parentNodeLayout;
  807. parentNodeLayout = graphLayoutsStorage[layoutOptions.type + '-series'];
  808. if (!parentNodeLayout) {
  809. graphLayoutsStorage[layoutOptions.type + '-series'] =
  810. parentNodeLayout =
  811. new H.layouts[layoutOptions.type]();
  812. parentNodeLayout.init(parentNodeOptions);
  813. graphLayoutsLookup.splice(parentNodeLayout.index, 0, parentNodeLayout);
  814. }
  815. series.parentNodeLayout = parentNodeLayout;
  816. this.createParentNodes();
  817. },
  818. /**
  819. * Adding the basic layout to series points.
  820. * @private
  821. */
  822. addLayout: function () {
  823. var series = this, layoutOptions = series.options.layoutAlgorithm, graphLayoutsStorage = series.chart.graphLayoutsStorage, graphLayoutsLookup = series.chart.graphLayoutsLookup, chartOptions = series.chart.options.chart, layout;
  824. if (!graphLayoutsStorage) {
  825. series.chart.graphLayoutsStorage = graphLayoutsStorage = {};
  826. series.chart.graphLayoutsLookup = graphLayoutsLookup = [];
  827. }
  828. layout = graphLayoutsStorage[layoutOptions.type];
  829. if (!layout) {
  830. layoutOptions.enableSimulation =
  831. !defined(chartOptions.forExport) ?
  832. layoutOptions.enableSimulation :
  833. !chartOptions.forExport;
  834. graphLayoutsStorage[layoutOptions.type] = layout =
  835. new H.layouts[layoutOptions.type]();
  836. layout.init(layoutOptions);
  837. graphLayoutsLookup.splice(layout.index, 0, layout);
  838. }
  839. series.layout = layout;
  840. series.points.forEach(function (node) {
  841. node.mass = 2;
  842. node.degree = 1;
  843. node.collisionNmb = 1;
  844. });
  845. layout.setArea(0, 0, series.chart.plotWidth, series.chart.plotHeight);
  846. layout.addElementsToCollection([series], layout.series);
  847. layout.addElementsToCollection(series.points, layout.nodes);
  848. },
  849. /**
  850. * Function responsible for adding all the layouts to the chart.
  851. * @private
  852. */
  853. deferLayout: function () {
  854. // TODO split layouts to independent methods
  855. var series = this, layoutOptions = series.options.layoutAlgorithm;
  856. if (!series.visible) {
  857. return;
  858. }
  859. // layout is using nodes for position calculation
  860. series.addLayout();
  861. if (layoutOptions.splitSeries) {
  862. series.addSeriesLayout();
  863. }
  864. },
  865. /**
  866. * Extend the base translate method to handle bubble size,
  867. * and correct positioning them.
  868. * @private
  869. */
  870. translate: function () {
  871. var series = this, chart = series.chart, data = series.data, index = series.index, point, radius, positions, i, useSimulation = series.options.useSimulation;
  872. series.processedXData = series.xData;
  873. series.generatePoints();
  874. // merged data is an array with all of the data from all series
  875. if (!defined(chart.allDataPoints)) {
  876. chart.allDataPoints = series.accumulateAllPoints(series);
  877. // calculate radius for all added data
  878. series.getPointRadius();
  879. }
  880. // after getting initial radius, calculate bubble positions
  881. if (useSimulation) {
  882. positions = chart.allDataPoints;
  883. }
  884. else {
  885. positions = series.placeBubbles(chart.allDataPoints);
  886. series.options.draggable = false;
  887. }
  888. // Set the shape and arguments to be picked up in drawPoints
  889. for (i = 0; i < positions.length; i++) {
  890. if (positions[i][3] === index) {
  891. // update the series points with the val from positions
  892. // array
  893. point = data[positions[i][4]];
  894. radius = positions[i][2];
  895. if (!useSimulation) {
  896. point.plotX = (positions[i][0] - chart.plotLeft +
  897. chart.diffX);
  898. point.plotY = (positions[i][1] - chart.plotTop +
  899. chart.diffY);
  900. }
  901. point.marker = extend(point.marker, {
  902. radius: radius,
  903. width: 2 * radius,
  904. height: 2 * radius
  905. });
  906. point.radius = radius;
  907. }
  908. }
  909. if (useSimulation) {
  910. series.deferLayout();
  911. }
  912. fireEvent(series, 'afterTranslate');
  913. },
  914. /**
  915. * Check if two bubbles overlaps.
  916. * @private
  917. * @param {Array} first bubble
  918. * @param {Array} second bubble
  919. * @return {Boolean} overlap or not
  920. */
  921. checkOverlap: function (bubble1, bubble2) {
  922. var diffX = bubble1[0] - bubble2[0], // diff of X center values
  923. diffY = bubble1[1] - bubble2[1], // diff of Y center values
  924. sumRad = bubble1[2] + bubble2[2]; // sum of bubble radius
  925. return (Math.sqrt(diffX * diffX + diffY * diffY) -
  926. Math.abs(sumRad)) < -0.001;
  927. },
  928. /**
  929. * Function that is adding one bubble based on positions and sizes of
  930. * two other bubbles, lastBubble is the last added bubble, newOrigin is
  931. * the bubble for positioning new bubbles. nextBubble is the curently
  932. * added bubble for which we are calculating positions
  933. * @private
  934. * @param {Array<number>} lastBubble The closest last bubble
  935. * @param {Array<number>} newOrigin New bubble
  936. * @param {Array<number>} nextBubble The closest next bubble
  937. * @return {Array<number>} Bubble with correct positions
  938. */
  939. positionBubble: function (lastBubble, newOrigin, nextBubble) {
  940. var sqrt = Math.sqrt, asin = Math.asin, acos = Math.acos, pow = Math.pow, abs = Math.abs, distance = sqrt(// dist between lastBubble and newOrigin
  941. pow((lastBubble[0] - newOrigin[0]), 2) +
  942. pow((lastBubble[1] - newOrigin[1]), 2)), alfa = acos(
  943. // from cosinus theorem: alfa is an angle used for
  944. // calculating correct position
  945. (pow(distance, 2) +
  946. pow(nextBubble[2] + newOrigin[2], 2) -
  947. pow(nextBubble[2] + lastBubble[2], 2)) / (2 * (nextBubble[2] + newOrigin[2]) * distance)), beta = asin(// from sinus theorem.
  948. abs(lastBubble[0] - newOrigin[0]) /
  949. distance),
  950. // providing helping variables, related to angle between
  951. // lastBubble and newOrigin
  952. gamma = (lastBubble[1] - newOrigin[1]) < 0 ? 0 : Math.PI,
  953. // if new origin y is smaller than last bubble y value
  954. // (2 and 3 quarter),
  955. // add Math.PI to final angle
  956. delta = (lastBubble[0] - newOrigin[0]) *
  957. (lastBubble[1] - newOrigin[1]) < 0 ?
  958. 1 : -1, // (1st and 3rd quarter)
  959. finalAngle = gamma + alfa + beta * delta, cosA = Math.cos(finalAngle), sinA = Math.sin(finalAngle), posX = newOrigin[0] + (newOrigin[2] + nextBubble[2]) * sinA,
  960. // center of new origin + (radius1 + radius2) * sinus A
  961. posY = newOrigin[1] - (newOrigin[2] + nextBubble[2]) * cosA;
  962. return [
  963. posX,
  964. posY,
  965. nextBubble[2],
  966. nextBubble[3],
  967. nextBubble[4]
  968. ]; // the same as described before
  969. },
  970. /**
  971. * This is the main function responsible
  972. * for positioning all of the bubbles
  973. * allDataPoints - bubble array, in format [pixel x value,
  974. * pixel y value, radius,
  975. * related series index, related point index]
  976. * @private
  977. * @param {Array<Highcharts.PackedBubbleData>} allDataPoints All points from all series
  978. * @return {Array<Highcharts.PackedBubbleData>} Positions of all bubbles
  979. */
  980. placeBubbles: function (allDataPoints) {
  981. var series = this, checkOverlap = series.checkOverlap, positionBubble = series.positionBubble, bubblePos = [], stage = 1, j = 0, k = 0, calculatedBubble, sortedArr, arr = [], i;
  982. // sort all points
  983. sortedArr = allDataPoints.sort(function (a, b) {
  984. return b[2] - a[2];
  985. });
  986. if (sortedArr.length) {
  987. // create first bubble in the middle of the chart
  988. bubblePos.push([
  989. [
  990. 0,
  991. 0,
  992. sortedArr[0][2],
  993. sortedArr[0][3],
  994. sortedArr[0][4]
  995. ] // point index
  996. ]); // 0 level bubble
  997. if (sortedArr.length > 1) {
  998. bubblePos.push([
  999. [
  1000. 0,
  1001. (0 - sortedArr[1][2] -
  1002. sortedArr[0][2]),
  1003. // move bubble above first one
  1004. sortedArr[1][2],
  1005. sortedArr[1][3],
  1006. sortedArr[1][4]
  1007. ]
  1008. ]); // 1 level 1st bubble
  1009. // first two already positioned so starting from 2
  1010. for (i = 2; i < sortedArr.length; i++) {
  1011. sortedArr[i][2] = sortedArr[i][2] || 1;
  1012. // in case if radius is calculated as 0.
  1013. calculatedBubble = positionBubble(bubblePos[stage][j], bubblePos[stage - 1][k], sortedArr[i]); // calculate initial bubble position
  1014. if (checkOverlap(calculatedBubble, bubblePos[stage][0])) {
  1015. /* if new bubble is overlapping with first bubble
  1016. * in current level (stage)
  1017. */
  1018. bubblePos.push([]);
  1019. k = 0;
  1020. /* reset index of bubble, used for
  1021. * positioning the bubbles around it,
  1022. * we are starting from first bubble in next
  1023. * stage because we are changing level to higher
  1024. */
  1025. bubblePos[stage + 1].push(positionBubble(bubblePos[stage][j], bubblePos[stage][0], sortedArr[i]));
  1026. // (last bubble, 1. from curr stage, new bubble)
  1027. stage++; // the new level is created, above current
  1028. j = 0; // set the index of bubble in curr level to 0
  1029. }
  1030. else if (stage > 1 &&
  1031. bubblePos[stage - 1][k + 1] &&
  1032. checkOverlap(calculatedBubble, bubblePos[stage - 1][k + 1])) {
  1033. /* if new bubble is overlapping with one of the prev
  1034. * stage bubbles, it means that - bubble, used for
  1035. * positioning the bubbles around it has changed
  1036. * so we need to recalculate it
  1037. */
  1038. k++;
  1039. bubblePos[stage].push(positionBubble(bubblePos[stage][j], bubblePos[stage - 1][k], sortedArr[i]));
  1040. // (last bubble, prev stage bubble, new bubble)
  1041. j++;
  1042. }
  1043. else { // simply add calculated bubble
  1044. j++;
  1045. bubblePos[stage].push(calculatedBubble);
  1046. }
  1047. }
  1048. }
  1049. series.chart.stages = bubblePos;
  1050. // it may not be necessary but adding it just in case -
  1051. // it is containing all of the bubble levels
  1052. series.chart.rawPositions =
  1053. []
  1054. .concat.apply([], bubblePos);
  1055. // bubble positions merged into one array
  1056. series.resizeRadius();
  1057. arr = series.chart.rawPositions;
  1058. }
  1059. return arr;
  1060. },
  1061. /**
  1062. * The function responsible for resizing the bubble radius.
  1063. * In shortcut: it is taking the initially
  1064. * calculated positions of bubbles. Then it is calculating the min max
  1065. * of both dimensions, creating something in shape of bBox.
  1066. * The comparison of bBox and the size of plotArea
  1067. * (later it may be also the size set by customer) is giving the
  1068. * value how to recalculate the radius so it will match the size
  1069. * @private
  1070. */
  1071. resizeRadius: function () {
  1072. var chart = this.chart, positions = chart.rawPositions, min = Math.min, max = Math.max, plotLeft = chart.plotLeft, plotTop = chart.plotTop, chartHeight = chart.plotHeight, chartWidth = chart.plotWidth, minX, maxX, minY, maxY, radius, bBox, spaceRatio, smallerDimension, i;
  1073. minX = minY = Number.POSITIVE_INFINITY; // set initial values
  1074. maxX = maxY = Number.NEGATIVE_INFINITY;
  1075. for (i = 0; i < positions.length; i++) {
  1076. radius = positions[i][2];
  1077. minX = min(minX, positions[i][0] - radius);
  1078. // (x center-radius) is the min x value used by specific bubble
  1079. maxX = max(maxX, positions[i][0] + radius);
  1080. minY = min(minY, positions[i][1] - radius);
  1081. maxY = max(maxY, positions[i][1] + radius);
  1082. }
  1083. bBox = [maxX - minX, maxY - minY];
  1084. spaceRatio = [
  1085. (chartWidth - plotLeft) / bBox[0],
  1086. (chartHeight - plotTop) / bBox[1]
  1087. ];
  1088. smallerDimension = min.apply([], spaceRatio);
  1089. if (Math.abs(smallerDimension - 1) > 1e-10) {
  1090. // if bBox is considered not the same width as possible size
  1091. for (i = 0; i < positions.length; i++) {
  1092. positions[i][2] *= smallerDimension;
  1093. }
  1094. this.placeBubbles(positions);
  1095. }
  1096. else {
  1097. /** if no radius recalculation is needed, we need to position
  1098. * the whole bubbles in center of chart plotarea
  1099. * for this, we are adding two parameters,
  1100. * diffY and diffX, that are related to differences
  1101. * between the initial center and the bounding box
  1102. */
  1103. chart.diffY = chartHeight / 2 +
  1104. plotTop - minY - (maxY - minY) / 2;
  1105. chart.diffX = chartWidth / 2 +
  1106. plotLeft - minX - (maxX - minX) / 2;
  1107. }
  1108. },
  1109. /**
  1110. * Calculate min and max bubble value for radius calculation.
  1111. * @private
  1112. */
  1113. calculateZExtremes: function () {
  1114. var chart = this.chart, zMin = this.options.zMin, zMax = this.options.zMax, valMin = Infinity, valMax = -Infinity;
  1115. if (zMin && zMax) {
  1116. return [zMin, zMax];
  1117. }
  1118. // it is needed to deal with null
  1119. // and undefined values
  1120. chart.series.forEach(function (s) {
  1121. s.yData.forEach(function (p) {
  1122. if (defined(p)) {
  1123. if (p > valMax) {
  1124. valMax = p;
  1125. }
  1126. if (p < valMin) {
  1127. valMin = p;
  1128. }
  1129. }
  1130. });
  1131. });
  1132. zMin = pick(zMin, valMin);
  1133. zMax = pick(zMax, valMax);
  1134. return [zMin, zMax];
  1135. },
  1136. /**
  1137. * Calculate radius of bubbles in series.
  1138. * @private
  1139. */
  1140. getPointRadius: function () {
  1141. var series = this, chart = series.chart, plotWidth = chart.plotWidth, plotHeight = chart.plotHeight, seriesOptions = series.options, useSimulation = seriesOptions.useSimulation, smallestSize = Math.min(plotWidth, plotHeight), extremes = {}, radii = [], allDataPoints = chart.allDataPoints, minSize, maxSize, value, radius, zExtremes;
  1142. ['minSize', 'maxSize'].forEach(function (prop) {
  1143. var length = parseInt(seriesOptions[prop], 10), isPercent = /%$/.test(seriesOptions[prop]);
  1144. extremes[prop] = isPercent ?
  1145. smallestSize * length / 100 :
  1146. length * Math.sqrt(allDataPoints.length);
  1147. });
  1148. chart.minRadius = minSize = extremes.minSize /
  1149. Math.sqrt(allDataPoints.length);
  1150. chart.maxRadius = maxSize = extremes.maxSize /
  1151. Math.sqrt(allDataPoints.length);
  1152. zExtremes = useSimulation ?
  1153. series.calculateZExtremes() :
  1154. [minSize, maxSize];
  1155. (allDataPoints || []).forEach(function (point, i) {
  1156. value = useSimulation ?
  1157. clamp(point[2], zExtremes[0], zExtremes[1]) :
  1158. point[2];
  1159. radius = series.getRadius(zExtremes[0], zExtremes[1], minSize, maxSize, value);
  1160. if (radius === 0) {
  1161. radius = null;
  1162. }
  1163. allDataPoints[i][2] = radius;
  1164. radii.push(radius);
  1165. });
  1166. series.radii = radii;
  1167. },
  1168. // Draggable mode:
  1169. /**
  1170. * Redraw halo on mousemove during the drag&drop action.
  1171. * @private
  1172. * @param {Highcharts.Point} point The point that should show halo.
  1173. */
  1174. redrawHalo: dragNodesMixin.redrawHalo,
  1175. /**
  1176. * Mouse down action, initializing drag&drop mode.
  1177. * @private
  1178. * @param {global.Event} event Browser event, before normalization.
  1179. * @param {Highcharts.Point} point The point that event occured.
  1180. */
  1181. onMouseDown: dragNodesMixin.onMouseDown,
  1182. /**
  1183. * Mouse move action during drag&drop.
  1184. * @private
  1185. * @param {global.Event} event Browser event, before normalization.
  1186. * @param {Highcharts.Point} point The point that event occured.
  1187. */
  1188. onMouseMove: dragNodesMixin.onMouseMove,
  1189. /**
  1190. * Mouse up action, finalizing drag&drop.
  1191. * @private
  1192. * @param {Highcharts.Point} point The point that event occured.
  1193. */
  1194. onMouseUp: function (point) {
  1195. if (point.fixedPosition && !point.removed) {
  1196. var distanceXY, distanceR, layout = this.layout, parentNodeLayout = this.parentNodeLayout;
  1197. if (parentNodeLayout && layout.options.dragBetweenSeries) {
  1198. parentNodeLayout.nodes.forEach(function (node) {
  1199. if (point && point.marker &&
  1200. node !== point.series.parentNode) {
  1201. distanceXY = layout.getDistXY(point, node);
  1202. distanceR = (layout.vectorLength(distanceXY) -
  1203. node.marker.radius -
  1204. point.marker.radius);
  1205. if (distanceR < 0) {
  1206. node.series.addPoint(merge(point.options, {
  1207. plotX: point.plotX,
  1208. plotY: point.plotY
  1209. }), false);
  1210. layout.removeElementFromCollection(point, layout.nodes);
  1211. point.remove();
  1212. }
  1213. }
  1214. });
  1215. }
  1216. dragNodesMixin.onMouseUp.apply(this, arguments);
  1217. }
  1218. },
  1219. destroy: function () {
  1220. // Remove the series from all layouts series collections #11469
  1221. if (this.chart.graphLayoutsLookup) {
  1222. this.chart.graphLayoutsLookup.forEach(function (layout) {
  1223. layout.removeElementFromCollection(this, layout.series);
  1224. }, this);
  1225. }
  1226. if (this.parentNode) {
  1227. this.parentNodeLayout.removeElementFromCollection(this.parentNode, this.parentNodeLayout.nodes);
  1228. if (this.parentNode.dataLabel) {
  1229. this.parentNode.dataLabel =
  1230. this.parentNode.dataLabel.destroy();
  1231. }
  1232. }
  1233. H.Series.prototype.destroy.apply(this, arguments);
  1234. },
  1235. alignDataLabel: H.Series.prototype.alignDataLabel
  1236. }, {
  1237. /**
  1238. * Destroy point.
  1239. * Then remove point from the layout.
  1240. * @private
  1241. * @return {undefined}
  1242. */
  1243. destroy: function () {
  1244. if (this.series.layout) {
  1245. this.series.layout.removeElementFromCollection(this, this.series.layout.nodes);
  1246. }
  1247. return Point.prototype.destroy.apply(this, arguments);
  1248. },
  1249. firePointEvent: function (eventType, eventArgs, defaultFunction) {
  1250. var point = this, series = this.series, seriesOptions = series.options;
  1251. if (this.isParentNode && seriesOptions.parentNode) {
  1252. var temp = seriesOptions.allowPointSelect;
  1253. seriesOptions.allowPointSelect = seriesOptions.parentNode.allowPointSelect;
  1254. Point.prototype.firePointEvent.apply(this, arguments);
  1255. seriesOptions.allowPointSelect = temp;
  1256. }
  1257. else {
  1258. Point.prototype.firePointEvent.apply(this, arguments);
  1259. }
  1260. },
  1261. select: function (selected, accumulate) {
  1262. var point = this, series = this.series, chart = series.chart;
  1263. if (point.isParentNode) {
  1264. chart.getSelectedPoints = chart.getSelectedParentNodes;
  1265. Point.prototype.select.apply(this, arguments);
  1266. chart.getSelectedPoints = H.Chart.prototype.getSelectedPoints;
  1267. }
  1268. else {
  1269. Point.prototype.select.apply(this, arguments);
  1270. }
  1271. }
  1272. });
  1273. // Remove accumulated data points to redistribute all of them again
  1274. // (i.e after hiding series by legend)
  1275. addEvent(Chart, 'beforeRedraw', function () {
  1276. if (this.allDataPoints) {
  1277. delete this.allDataPoints;
  1278. }
  1279. });
  1280. /* eslint-enable no-invalid-this, valid-jsdoc */
  1281. /**
  1282. * A `packedbubble` series. If the [type](#series.packedbubble.type) option is
  1283. * not specified, it is inherited from [chart.type](#chart.type).
  1284. *
  1285. * @type {Object}
  1286. * @extends series,plotOptions.packedbubble
  1287. * @excluding cropThreshold, dataParser, dataSorting, dataURL, dragDrop, stack,
  1288. * boostThreshold, boostBlending
  1289. * @product highcharts
  1290. * @requires highcharts-more
  1291. * @apioption series.packedbubble
  1292. */
  1293. /**
  1294. * An array of data points for the series. For the `packedbubble` series type,
  1295. * points can be given in the following ways:
  1296. *
  1297. * 1. An array of `values`.
  1298. *
  1299. * ```js
  1300. * data: [5, 1, 20]
  1301. * ```
  1302. *
  1303. * 2. An array of objects with named values. The objects are point
  1304. * configuration objects as seen below. If the total number of data points
  1305. * exceeds the series' [turboThreshold](#series.packedbubble.turboThreshold),
  1306. * this option is not available.
  1307. *
  1308. * ```js
  1309. * data: [{
  1310. * value: 1,
  1311. * name: "Point2",
  1312. * color: "#00FF00"
  1313. * }, {
  1314. * value: 5,
  1315. * name: "Point1",
  1316. * color: "#FF00FF"
  1317. * }]
  1318. * ```
  1319. *
  1320. * @type {Array<Object|Array>}
  1321. * @extends series.line.data
  1322. * @excluding marker, x, y
  1323. * @sample {highcharts} highcharts/series/data-array-of-objects/
  1324. * Config objects
  1325. * @product highcharts
  1326. * @apioption series.packedbubble.data
  1327. */
  1328. /**
  1329. * @type {Highcharts.SeriesPackedBubbleDataLabelsOptionsObject|Array<Highcharts.SeriesPackedBubbleDataLabelsOptionsObject>}
  1330. * @product highcharts
  1331. * @apioption series.packedbubble.data.dataLabels
  1332. */
  1333. /**
  1334. * @excluding enabled,enabledThreshold,height,radius,width
  1335. * @product highcharts
  1336. * @apioption series.packedbubble.marker
  1337. */
  1338. ''; // adds doclets above to transpiled file