BubbleSeries.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  1. /* *
  2. *
  3. * (c) 2010-2020 Torstein Honsi
  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 H from '../../Core/Globals.js';
  12. /**
  13. * @typedef {"area"|"width"} Highcharts.BubbleSizeByValue
  14. */
  15. import Color from '../../Core/Color.js';
  16. var color = Color.parse;
  17. import Point from '../../Core/Series/Point.js';
  18. import U from '../../Core/Utilities.js';
  19. var arrayMax = U.arrayMax, arrayMin = U.arrayMin, clamp = U.clamp, extend = U.extend, isNumber = U.isNumber, pick = U.pick, pInt = U.pInt, seriesType = U.seriesType;
  20. import '../../Core/Axis/Axis.js';
  21. import '../../Core/Series/Series.js';
  22. import '../../Series/ScatterSeries.js';
  23. import './BubbleLegend.js';
  24. var Axis = H.Axis, noop = H.noop, Series = H.Series, seriesTypes = H.seriesTypes;
  25. /**
  26. * A bubble series is a three dimensional series type where each point renders
  27. * an X, Y and Z value. Each points is drawn as a bubble where the position
  28. * along the X and Y axes mark the X and Y values, and the size of the bubble
  29. * relates to the Z value.
  30. *
  31. * @sample {highcharts} highcharts/demo/bubble/
  32. * Bubble chart
  33. *
  34. * @extends plotOptions.scatter
  35. * @excluding cluster
  36. * @product highcharts highstock
  37. * @requires highcharts-more
  38. * @optionparent plotOptions.bubble
  39. */
  40. seriesType('bubble', 'scatter', {
  41. dataLabels: {
  42. formatter: function () {
  43. return this.point.z;
  44. },
  45. inside: true,
  46. verticalAlign: 'middle'
  47. },
  48. /**
  49. * If there are more points in the series than the `animationLimit`, the
  50. * animation won't run. Animation affects overall performance and doesn't
  51. * work well with heavy data series.
  52. *
  53. * @since 6.1.0
  54. */
  55. animationLimit: 250,
  56. /**
  57. * Whether to display negative sized bubbles. The threshold is given
  58. * by the [zThreshold](#plotOptions.bubble.zThreshold) option, and negative
  59. * bubbles can be visualized by setting
  60. * [negativeColor](#plotOptions.bubble.negativeColor).
  61. *
  62. * @sample {highcharts} highcharts/plotoptions/bubble-negative/
  63. * Negative bubbles
  64. *
  65. * @type {boolean}
  66. * @default true
  67. * @since 3.0
  68. * @apioption plotOptions.bubble.displayNegative
  69. */
  70. /**
  71. * @extends plotOptions.series.marker
  72. * @excluding enabled, enabledThreshold, height, radius, width
  73. */
  74. marker: {
  75. lineColor: null,
  76. lineWidth: 1,
  77. /**
  78. * The fill opacity of the bubble markers.
  79. */
  80. fillOpacity: 0.5,
  81. /**
  82. * In bubble charts, the radius is overridden and determined based on
  83. * the point's data value.
  84. *
  85. * @ignore-option
  86. */
  87. radius: null,
  88. states: {
  89. hover: {
  90. radiusPlus: 0
  91. }
  92. },
  93. /**
  94. * A predefined shape or symbol for the marker. Possible values are
  95. * "circle", "square", "diamond", "triangle" and "triangle-down".
  96. *
  97. * Additionally, the URL to a graphic can be given on the form
  98. * `url(graphic.png)`. Note that for the image to be applied to exported
  99. * charts, its URL needs to be accessible by the export server.
  100. *
  101. * Custom callbacks for symbol path generation can also be added to
  102. * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
  103. * used by its method name, as shown in the demo.
  104. *
  105. * @sample {highcharts} highcharts/plotoptions/bubble-symbol/
  106. * Bubble chart with various symbols
  107. * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
  108. * General chart with predefined, graphic and custom markers
  109. *
  110. * @type {Highcharts.SymbolKeyValue|string}
  111. * @since 5.0.11
  112. */
  113. symbol: 'circle'
  114. },
  115. /**
  116. * Minimum bubble size. Bubbles will automatically size between the
  117. * `minSize` and `maxSize` to reflect the `z` value of each bubble.
  118. * Can be either pixels (when no unit is given), or a percentage of
  119. * the smallest one of the plot width and height.
  120. *
  121. * @sample {highcharts} highcharts/plotoptions/bubble-size/
  122. * Bubble size
  123. *
  124. * @type {number|string}
  125. * @since 3.0
  126. * @product highcharts highstock
  127. */
  128. minSize: 8,
  129. /**
  130. * Maximum bubble size. Bubbles will automatically size between the
  131. * `minSize` and `maxSize` to reflect the `z` value of each bubble.
  132. * Can be either pixels (when no unit is given), or a percentage of
  133. * the smallest one of the plot width and height.
  134. *
  135. * @sample {highcharts} highcharts/plotoptions/bubble-size/
  136. * Bubble size
  137. *
  138. * @type {number|string}
  139. * @since 3.0
  140. * @product highcharts highstock
  141. */
  142. maxSize: '20%',
  143. /**
  144. * When a point's Z value is below the
  145. * [zThreshold](#plotOptions.bubble.zThreshold) setting, this color is used.
  146. *
  147. * @sample {highcharts} highcharts/plotoptions/bubble-negative/
  148. * Negative bubbles
  149. *
  150. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  151. * @since 3.0
  152. * @product highcharts
  153. * @apioption plotOptions.bubble.negativeColor
  154. */
  155. /**
  156. * Whether the bubble's value should be represented by the area or the
  157. * width of the bubble. The default, `area`, corresponds best to the
  158. * human perception of the size of each bubble.
  159. *
  160. * @sample {highcharts} highcharts/plotoptions/bubble-sizeby/
  161. * Comparison of area and size
  162. *
  163. * @type {Highcharts.BubbleSizeByValue}
  164. * @default area
  165. * @since 3.0.7
  166. * @apioption plotOptions.bubble.sizeBy
  167. */
  168. /**
  169. * When this is true, the absolute value of z determines the size of
  170. * the bubble. This means that with the default `zThreshold` of 0, a
  171. * bubble of value -1 will have the same size as a bubble of value 1,
  172. * while a bubble of value 0 will have a smaller size according to
  173. * `minSize`.
  174. *
  175. * @sample {highcharts} highcharts/plotoptions/bubble-sizebyabsolutevalue/
  176. * Size by absolute value, various thresholds
  177. *
  178. * @type {boolean}
  179. * @default false
  180. * @since 4.1.9
  181. * @product highcharts
  182. * @apioption plotOptions.bubble.sizeByAbsoluteValue
  183. */
  184. /**
  185. * When this is true, the series will not cause the Y axis to cross
  186. * the zero plane (or [threshold](#plotOptions.series.threshold) option)
  187. * unless the data actually crosses the plane.
  188. *
  189. * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
  190. * 3 will make the Y axis show negative values according to the `minPadding`
  191. * option. If `softThreshold` is `true`, the Y axis starts at 0.
  192. *
  193. * @since 4.1.9
  194. * @product highcharts
  195. */
  196. softThreshold: false,
  197. states: {
  198. hover: {
  199. halo: {
  200. size: 5
  201. }
  202. }
  203. },
  204. tooltip: {
  205. pointFormat: '({point.x}, {point.y}), Size: {point.z}'
  206. },
  207. turboThreshold: 0,
  208. /**
  209. * The minimum for the Z value range. Defaults to the highest Z value
  210. * in the data.
  211. *
  212. * @see [zMin](#plotOptions.bubble.zMin)
  213. *
  214. * @sample {highcharts} highcharts/plotoptions/bubble-zmin-zmax/
  215. * Z has a possible range of 0-100
  216. *
  217. * @type {number}
  218. * @since 4.0.3
  219. * @product highcharts
  220. * @apioption plotOptions.bubble.zMax
  221. */
  222. /**
  223. * @default z
  224. * @apioption plotOptions.bubble.colorKey
  225. */
  226. /**
  227. * The minimum for the Z value range. Defaults to the lowest Z value
  228. * in the data.
  229. *
  230. * @see [zMax](#plotOptions.bubble.zMax)
  231. *
  232. * @sample {highcharts} highcharts/plotoptions/bubble-zmin-zmax/
  233. * Z has a possible range of 0-100
  234. *
  235. * @type {number}
  236. * @since 4.0.3
  237. * @product highcharts
  238. * @apioption plotOptions.bubble.zMin
  239. */
  240. /**
  241. * When [displayNegative](#plotOptions.bubble.displayNegative) is `false`,
  242. * bubbles with lower Z values are skipped. When `displayNegative`
  243. * is `true` and a [negativeColor](#plotOptions.bubble.negativeColor)
  244. * is given, points with lower Z is colored.
  245. *
  246. * @sample {highcharts} highcharts/plotoptions/bubble-negative/
  247. * Negative bubbles
  248. *
  249. * @since 3.0
  250. * @product highcharts
  251. */
  252. zThreshold: 0,
  253. zoneAxis: 'z'
  254. // Prototype members
  255. }, {
  256. pointArrayMap: ['y', 'z'],
  257. parallelArrays: ['x', 'y', 'z'],
  258. trackerGroups: ['group', 'dataLabelsGroup'],
  259. specialGroup: 'group',
  260. bubblePadding: true,
  261. zoneAxis: 'z',
  262. directTouch: true,
  263. isBubble: true,
  264. /* eslint-disable valid-jsdoc */
  265. /**
  266. * @private
  267. */
  268. pointAttribs: function (point, state) {
  269. var markerOptions = this.options.marker, fillOpacity = markerOptions.fillOpacity, attr = Series.prototype.pointAttribs.call(this, point, state);
  270. if (fillOpacity !== 1) {
  271. attr.fill = color(attr.fill)
  272. .setOpacity(fillOpacity)
  273. .get('rgba');
  274. }
  275. return attr;
  276. },
  277. /**
  278. * Get the radius for each point based on the minSize, maxSize and each
  279. * point's Z value. This must be done prior to Series.translate because
  280. * the axis needs to add padding in accordance with the point sizes.
  281. * @private
  282. */
  283. getRadii: function (zMin, zMax, series) {
  284. var len, i, zData = this.zData, yData = this.yData, minSize = series.minPxSize, maxSize = series.maxPxSize, radii = [], value;
  285. // Set the shape type and arguments to be picked up in drawPoints
  286. for (i = 0, len = zData.length; i < len; i++) {
  287. value = zData[i];
  288. // Separate method to get individual radius for bubbleLegend
  289. radii.push(this.getRadius(zMin, zMax, minSize, maxSize, value, yData[i]));
  290. }
  291. this.radii = radii;
  292. },
  293. /**
  294. * Get the individual radius for one point.
  295. * @private
  296. */
  297. getRadius: function (zMin, zMax, minSize, maxSize, value, yValue) {
  298. var options = this.options, sizeByArea = options.sizeBy !== 'width', zThreshold = options.zThreshold, zRange = zMax - zMin, pos = 0.5;
  299. // #8608 - bubble should be visible when z is undefined
  300. if (yValue === null || value === null) {
  301. return null;
  302. }
  303. if (isNumber(value)) {
  304. // When sizing by threshold, the absolute value of z determines
  305. // the size of the bubble.
  306. if (options.sizeByAbsoluteValue) {
  307. value = Math.abs(value - zThreshold);
  308. zMax = zRange = Math.max(zMax - zThreshold, Math.abs(zMin - zThreshold));
  309. zMin = 0;
  310. }
  311. // Issue #4419 - if value is less than zMin, push a radius that's
  312. // always smaller than the minimum size
  313. if (value < zMin) {
  314. return minSize / 2 - 1;
  315. }
  316. // Relative size, a number between 0 and 1
  317. if (zRange > 0) {
  318. pos = (value - zMin) / zRange;
  319. }
  320. }
  321. if (sizeByArea && pos >= 0) {
  322. pos = Math.sqrt(pos);
  323. }
  324. return Math.ceil(minSize + pos * (maxSize - minSize)) / 2;
  325. },
  326. /**
  327. * Perform animation on the bubbles
  328. * @private
  329. */
  330. animate: function (init) {
  331. if (!init &&
  332. this.points.length < this.options.animationLimit // #8099
  333. ) {
  334. this.points.forEach(function (point) {
  335. var graphic = point.graphic;
  336. if (graphic && graphic.width) { // URL symbols don't have width
  337. // Start values
  338. if (!this.hasRendered) {
  339. graphic.attr({
  340. x: point.plotX,
  341. y: point.plotY,
  342. width: 1,
  343. height: 1
  344. });
  345. }
  346. // Run animation
  347. graphic.animate(this.markerAttribs(point), this.options.animation);
  348. }
  349. }, this);
  350. }
  351. },
  352. /**
  353. * Define hasData function for non-cartesian series.
  354. * Returns true if the series has points at all.
  355. * @private
  356. */
  357. hasData: function () {
  358. return !!this.processedXData.length; // != 0
  359. },
  360. /**
  361. * Extend the base translate method to handle bubble size
  362. * @private
  363. */
  364. translate: function () {
  365. var i, data = this.data, point, radius, radii = this.radii;
  366. // Run the parent method
  367. seriesTypes.scatter.prototype.translate.call(this);
  368. // Set the shape type and arguments to be picked up in drawPoints
  369. i = data.length;
  370. while (i--) {
  371. point = data[i];
  372. radius = radii ? radii[i] : 0; // #1737
  373. if (isNumber(radius) && radius >= this.minPxSize / 2) {
  374. // Shape arguments
  375. point.marker = extend(point.marker, {
  376. radius: radius,
  377. width: 2 * radius,
  378. height: 2 * radius
  379. });
  380. // Alignment box for the data label
  381. point.dlBox = {
  382. x: point.plotX - radius,
  383. y: point.plotY - radius,
  384. width: 2 * radius,
  385. height: 2 * radius
  386. };
  387. }
  388. else { // below zThreshold
  389. // #1691
  390. point.shapeArgs = point.plotY = point.dlBox = void 0;
  391. }
  392. }
  393. },
  394. alignDataLabel: seriesTypes.column.prototype.alignDataLabel,
  395. buildKDTree: noop,
  396. applyZones: noop
  397. // Point class
  398. }, {
  399. /**
  400. * @private
  401. */
  402. haloPath: function (size) {
  403. return Point.prototype.haloPath.call(this,
  404. // #6067
  405. size === 0 ? 0 : (this.marker ? this.marker.radius || 0 : 0) + size);
  406. },
  407. ttBelow: false
  408. });
  409. // Add logic to pad each axis with the amount of pixels necessary to avoid the
  410. // bubbles to overflow.
  411. Axis.prototype.beforePadding = function () {
  412. var axis = this, axisLength = this.len, chart = this.chart, pxMin = 0, pxMax = axisLength, isXAxis = this.isXAxis, dataKey = isXAxis ? 'xData' : 'yData', min = this.min, extremes = {}, smallestSize = Math.min(chart.plotWidth, chart.plotHeight), zMin = Number.MAX_VALUE, zMax = -Number.MAX_VALUE, range = this.max - min, transA = axisLength / range, activeSeries = [];
  413. // Handle padding on the second pass, or on redraw
  414. this.series.forEach(function (series) {
  415. var seriesOptions = series.options, zData;
  416. if (series.bubblePadding &&
  417. (series.visible || !chart.options.chart.ignoreHiddenSeries)) {
  418. // Correction for #1673
  419. axis.allowZoomOutside = true;
  420. // Cache it
  421. activeSeries.push(series);
  422. if (isXAxis) { // because X axis is evaluated first
  423. // For each series, translate the size extremes to pixel values
  424. ['minSize', 'maxSize'].forEach(function (prop) {
  425. var length = seriesOptions[prop], isPercent = /%$/.test(length);
  426. length = pInt(length);
  427. extremes[prop] = isPercent ?
  428. smallestSize * length / 100 :
  429. length;
  430. });
  431. series.minPxSize = extremes.minSize;
  432. // Prioritize min size if conflict to make sure bubbles are
  433. // always visible. #5873
  434. series.maxPxSize = Math.max(extremes.maxSize, extremes.minSize);
  435. // Find the min and max Z
  436. zData = series.zData.filter(isNumber);
  437. if (zData.length) { // #1735
  438. zMin = pick(seriesOptions.zMin, clamp(arrayMin(zData), seriesOptions.displayNegative === false ?
  439. seriesOptions.zThreshold :
  440. -Number.MAX_VALUE, zMin));
  441. zMax = pick(seriesOptions.zMax, Math.max(zMax, arrayMax(zData)));
  442. }
  443. }
  444. }
  445. });
  446. activeSeries.forEach(function (series) {
  447. var data = series[dataKey], i = data.length, radius;
  448. if (isXAxis) {
  449. series.getRadii(zMin, zMax, series);
  450. }
  451. if (range > 0) {
  452. while (i--) {
  453. if (isNumber(data[i]) &&
  454. axis.dataMin <= data[i] &&
  455. data[i] <= axis.max) {
  456. radius = series.radii ? series.radii[i] : 0;
  457. pxMin = Math.min(((data[i] - min) * transA) - radius, pxMin);
  458. pxMax = Math.max(((data[i] - min) * transA) + radius, pxMax);
  459. }
  460. }
  461. }
  462. });
  463. // Apply the padding to the min and max properties
  464. if (activeSeries.length && range > 0 && !this.logarithmic) {
  465. pxMax -= axisLength;
  466. transA *= (axisLength +
  467. Math.max(0, pxMin) - // #8901
  468. Math.min(pxMax, axisLength)) / axisLength;
  469. [
  470. ['min', 'userMin', pxMin],
  471. ['max', 'userMax', pxMax]
  472. ].forEach(function (keys) {
  473. if (typeof pick(axis.options[keys[0]], axis[keys[1]]) === 'undefined') {
  474. axis[keys[0]] += keys[2] / transA;
  475. }
  476. });
  477. }
  478. /* eslint-enable valid-jsdoc */
  479. };
  480. /**
  481. * A `bubble` series. If the [type](#series.bubble.type) option is
  482. * not specified, it is inherited from [chart.type](#chart.type).
  483. *
  484. * @extends series,plotOptions.bubble
  485. * @excluding dataParser, dataURL, stack
  486. * @product highcharts highstock
  487. * @requires highcharts-more
  488. * @apioption series.bubble
  489. */
  490. /**
  491. * An array of data points for the series. For the `bubble` series type,
  492. * points can be given in the following ways:
  493. *
  494. * 1. An array of arrays with 3 or 2 values. In this case, the values correspond
  495. * to `x,y,z`. If the first value is a string, it is applied as the name of
  496. * the point, and the `x` value is inferred. The `x` value can also be
  497. * omitted, in which case the inner arrays should be of length 2\. Then the
  498. * `x` value is automatically calculated, either starting at 0 and
  499. * incremented by 1, or from `pointStart` and `pointInterval` given in the
  500. * series options.
  501. * ```js
  502. * data: [
  503. * [0, 1, 2],
  504. * [1, 5, 5],
  505. * [2, 0, 2]
  506. * ]
  507. * ```
  508. *
  509. * 2. An array of objects with named values. The following snippet shows only a
  510. * few settings, see the complete options set below. If the total number of
  511. * data points exceeds the series'
  512. * [turboThreshold](#series.bubble.turboThreshold), this option is not
  513. * available.
  514. * ```js
  515. * data: [{
  516. * x: 1,
  517. * y: 1,
  518. * z: 1,
  519. * name: "Point2",
  520. * color: "#00FF00"
  521. * }, {
  522. * x: 1,
  523. * y: 5,
  524. * z: 4,
  525. * name: "Point1",
  526. * color: "#FF00FF"
  527. * }]
  528. * ```
  529. *
  530. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  531. * Arrays of numeric x and y
  532. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  533. * Arrays of datetime x and y
  534. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  535. * Arrays of point.name and y
  536. * @sample {highcharts} highcharts/series/data-array-of-objects/
  537. * Config objects
  538. *
  539. * @type {Array<Array<(number|string),number>|Array<(number|string),number,number>|*>}
  540. * @extends series.line.data
  541. * @product highcharts
  542. * @apioption series.bubble.data
  543. */
  544. /**
  545. * @extends series.line.data.marker
  546. * @excluding enabledThreshold, height, radius, width
  547. * @product highcharts
  548. * @apioption series.bubble.data.marker
  549. */
  550. /**
  551. * The size value for each bubble. The bubbles' diameters are computed
  552. * based on the `z`, and controlled by series options like `minSize`,
  553. * `maxSize`, `sizeBy`, `zMin` and `zMax`.
  554. *
  555. * @type {number|null}
  556. * @product highcharts
  557. * @apioption series.bubble.data.z
  558. */
  559. /**
  560. * @excluding enabled, enabledThreshold, height, radius, width
  561. * @apioption series.bubble.marker
  562. */
  563. ''; // adds doclets above to transpiled file