funnel.src.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. /**
  2. * @license Highcharts JS v8.1.2 (2020-06-16)
  3. *
  4. * Highcharts funnel module
  5. *
  6. * (c) 2010-2019 Torstein Honsi
  7. *
  8. * License: www.highcharts.com/license
  9. */
  10. 'use strict';
  11. (function (factory) {
  12. if (typeof module === 'object' && module.exports) {
  13. factory['default'] = factory;
  14. module.exports = factory;
  15. } else if (typeof define === 'function' && define.amd) {
  16. define('highcharts/modules/funnel', ['highcharts'], function (Highcharts) {
  17. factory(Highcharts);
  18. factory.Highcharts = Highcharts;
  19. return factory;
  20. });
  21. } else {
  22. factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
  23. }
  24. }(function (Highcharts) {
  25. var _modules = Highcharts ? Highcharts._modules : {};
  26. function _registerModule(obj, path, args, fn) {
  27. if (!obj.hasOwnProperty(path)) {
  28. obj[path] = fn.apply(null, args);
  29. }
  30. }
  31. _registerModule(_modules, 'modules/funnel.src.js', [_modules['parts/Chart.js'], _modules['parts/Globals.js'], _modules['parts/Utilities.js']], function (Chart, H, U) {
  32. /* *
  33. *
  34. * Highcharts funnel module
  35. *
  36. * (c) 2010-2020 Torstein Honsi
  37. *
  38. * License: www.highcharts.com/license
  39. *
  40. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  41. *
  42. * */
  43. /* eslint indent: 0 */
  44. var noop = H.noop, seriesType = H.seriesType, seriesTypes = H.seriesTypes;
  45. var addEvent = U.addEvent, fireEvent = U.fireEvent, isArray = U.isArray, pick = U.pick;
  46. /**
  47. * @private
  48. * @class
  49. * @name Highcharts.seriesTypes.funnel
  50. *
  51. * @augments Highcharts.Series
  52. */
  53. seriesType('funnel', 'pie',
  54. /**
  55. * Funnel charts are a type of chart often used to visualize stages in a
  56. * sales project, where the top are the initial stages with the most
  57. * clients. It requires that the modules/funnel.js file is loaded.
  58. *
  59. * @sample highcharts/demo/funnel/
  60. * Funnel demo
  61. *
  62. * @extends plotOptions.pie
  63. * @excluding innerSize,size,dataSorting
  64. * @product highcharts
  65. * @requires modules/funnel
  66. * @optionparent plotOptions.funnel
  67. */
  68. {
  69. /**
  70. * Initial animation is by default disabled for the funnel chart.
  71. */
  72. animation: false,
  73. /**
  74. * The center of the series. By default, it is centered in the middle
  75. * of the plot area, so it fills the plot area height.
  76. *
  77. * @type {Array<number|string>}
  78. * @default ["50%", "50%"]
  79. * @since 3.0
  80. */
  81. center: ['50%', '50%'],
  82. /**
  83. * The width of the funnel compared to the width of the plot area,
  84. * or the pixel width if it is a number.
  85. *
  86. * @type {number|string}
  87. * @since 3.0
  88. */
  89. width: '90%',
  90. /**
  91. * The width of the neck, the lower part of the funnel. A number defines
  92. * pixel width, a percentage string defines a percentage of the plot
  93. * area width.
  94. *
  95. * @sample {highcharts} highcharts/demo/funnel/
  96. * Funnel demo
  97. *
  98. * @type {number|string}
  99. * @since 3.0
  100. */
  101. neckWidth: '30%',
  102. /**
  103. * The height of the funnel or pyramid. If it is a number it defines
  104. * the pixel height, if it is a percentage string it is the percentage
  105. * of the plot area height.
  106. *
  107. * @sample {highcharts} highcharts/demo/funnel/
  108. * Funnel demo
  109. *
  110. * @type {number|string}
  111. * @since 3.0
  112. */
  113. height: '100%',
  114. /**
  115. * The height of the neck, the lower part of the funnel. A number
  116. * defines pixel width, a percentage string defines a percentage of the
  117. * plot area height.
  118. *
  119. * @type {number|string}
  120. */
  121. neckHeight: '25%',
  122. /**
  123. * A reversed funnel has the widest area down. A reversed funnel with
  124. * no neck width and neck height is a pyramid.
  125. *
  126. * @since 3.0.10
  127. */
  128. reversed: false,
  129. /**
  130. * To avoid adapting the data label size in Pie.drawDataLabels.
  131. * @ignore-option
  132. */
  133. size: true,
  134. dataLabels: {
  135. connectorWidth: 1,
  136. verticalAlign: 'middle'
  137. },
  138. /**
  139. * Options for the series states.
  140. */
  141. states: {
  142. /**
  143. * @excluding halo, marker, lineWidth, lineWidthPlus
  144. * @apioption plotOptions.funnel.states.hover
  145. */
  146. /**
  147. * Options for a selected funnel item.
  148. *
  149. * @excluding halo, marker, lineWidth, lineWidthPlus
  150. */
  151. select: {
  152. /**
  153. * A specific color for the selected point.
  154. *
  155. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  156. */
  157. color: '#cccccc',
  158. /**
  159. * A specific border color for the selected point.
  160. *
  161. * @type {Highcharts.ColorString}
  162. */
  163. borderColor: '#000000'
  164. }
  165. }
  166. },
  167. // Properties
  168. {
  169. animate: noop,
  170. // Overrides the pie translate method
  171. translate: function () {
  172. var sum = 0, series = this, chart = series.chart, options = series.options, reversed = options.reversed, ignoreHiddenPoint = options.ignoreHiddenPoint, plotWidth = chart.plotWidth, plotHeight = chart.plotHeight, cumulative = 0, // start at top
  173. center = options.center, centerX = getLength(center[0], plotWidth), centerY = getLength(center[1], plotHeight), width = getLength(options.width, plotWidth), tempWidth, height = getLength(options.height, plotHeight), neckWidth = getLength(options.neckWidth, plotWidth), neckHeight = getLength(options.neckHeight, plotHeight), neckY = (centerY - height / 2) + height - neckHeight, data = series.data, path, fraction, half = (options.dataLabels.position === 'left' ?
  174. 1 :
  175. 0), x1, y1, x2, x3, y3, x4, y5;
  176. /**
  177. * Get positions - either an integer or a percentage string must be
  178. * given.
  179. * @private
  180. * @param {number|string|undefined} length
  181. * Length
  182. * @param {number} relativeTo
  183. * Relative factor
  184. * @return {number}
  185. * Relative position
  186. */
  187. function getLength(length, relativeTo) {
  188. return (/%$/).test(length) ?
  189. relativeTo * parseInt(length, 10) / 100 :
  190. parseInt(length, 10);
  191. }
  192. series.getWidthAt = function (y) {
  193. var top = (centerY - height / 2);
  194. return (y > neckY || height === neckHeight) ?
  195. neckWidth :
  196. neckWidth + (width - neckWidth) *
  197. (1 - (y - top) / (height - neckHeight));
  198. };
  199. series.getX = function (y, half, point) {
  200. return centerX + (half ? -1 : 1) *
  201. ((series.getWidthAt(reversed ? 2 * centerY - y : y) / 2) +
  202. point.labelDistance);
  203. };
  204. // Expose
  205. series.center = [centerX, centerY, height];
  206. series.centerX = centerX;
  207. /*
  208. Individual point coordinate naming:
  209. x1,y1 _________________ x2,y1
  210. \ /
  211. \ /
  212. \ /
  213. \ /
  214. \ /
  215. x3,y3 _________ x4,y3
  216. Additional for the base of the neck:
  217. | |
  218. | |
  219. | |
  220. x3,y5 _________ x4,y5
  221. */
  222. // get the total sum
  223. data.forEach(function (point) {
  224. if (!ignoreHiddenPoint || point.visible !== false) {
  225. sum += point.y;
  226. }
  227. });
  228. data.forEach(function (point) {
  229. // set start and end positions
  230. y5 = null;
  231. fraction = sum ? point.y / sum : 0;
  232. y1 = centerY - height / 2 + cumulative * height;
  233. y3 = y1 + fraction * height;
  234. tempWidth = series.getWidthAt(y1);
  235. x1 = centerX - tempWidth / 2;
  236. x2 = x1 + tempWidth;
  237. tempWidth = series.getWidthAt(y3);
  238. x3 = centerX - tempWidth / 2;
  239. x4 = x3 + tempWidth;
  240. // the entire point is within the neck
  241. if (y1 > neckY) {
  242. x1 = x3 = centerX - neckWidth / 2;
  243. x2 = x4 = centerX + neckWidth / 2;
  244. // the base of the neck
  245. }
  246. else if (y3 > neckY) {
  247. y5 = y3;
  248. tempWidth = series.getWidthAt(neckY);
  249. x3 = centerX - tempWidth / 2;
  250. x4 = x3 + tempWidth;
  251. y3 = neckY;
  252. }
  253. if (reversed) {
  254. y1 = 2 * centerY - y1;
  255. y3 = 2 * centerY - y3;
  256. if (y5 !== null) {
  257. y5 = 2 * centerY - y5;
  258. }
  259. }
  260. // save the path
  261. path = [
  262. ['M', x1, y1],
  263. ['L', x2, y1],
  264. ['L', x4, y3]
  265. ];
  266. if (y5 !== null) {
  267. path.push(['L', x4, y5], ['L', x3, y5]);
  268. }
  269. path.push(['L', x3, y3], ['Z']);
  270. // prepare for using shared dr
  271. point.shapeType = 'path';
  272. point.shapeArgs = { d: path };
  273. // for tooltips and data labels
  274. point.percentage = fraction * 100;
  275. point.plotX = centerX;
  276. point.plotY = (y1 + (y5 || y3)) / 2;
  277. // Placement of tooltips and data labels
  278. point.tooltipPos = [
  279. centerX,
  280. point.plotY
  281. ];
  282. point.dlBox = {
  283. x: x3,
  284. y: y1,
  285. topWidth: x2 - x1,
  286. bottomWidth: x4 - x3,
  287. height: Math.abs(pick(y5, y3) - y1),
  288. width: NaN
  289. };
  290. // Slice is a noop on funnel points
  291. point.slice = noop;
  292. // Mimicking pie data label placement logic
  293. point.half = half;
  294. if (!ignoreHiddenPoint || point.visible !== false) {
  295. cumulative += fraction;
  296. }
  297. });
  298. fireEvent(series, 'afterTranslate');
  299. },
  300. // Funnel items don't have angles (#2289)
  301. sortByAngle: function (points) {
  302. points.sort(function (a, b) {
  303. return a.plotY - b.plotY;
  304. });
  305. },
  306. // Extend the pie data label method
  307. drawDataLabels: function () {
  308. var series = this, data = series.data, labelDistance = series.options.dataLabels.distance, leftSide, sign, point, i = data.length, x, y;
  309. // In the original pie label anticollision logic, the slots are
  310. // distributed from one labelDistance above to one labelDistance
  311. // below the pie. In funnels we don't want this.
  312. series.center[2] -= 2 * labelDistance;
  313. // Set the label position array for each point.
  314. while (i--) {
  315. point = data[i];
  316. leftSide = point.half;
  317. sign = leftSide ? 1 : -1;
  318. y = point.plotY;
  319. point.labelDistance = pick(point.options.dataLabels &&
  320. point.options.dataLabels.distance, labelDistance);
  321. series.maxLabelDistance = Math.max(point.labelDistance, series.maxLabelDistance || 0);
  322. x = series.getX(y, leftSide, point);
  323. // set the anchor point for data labels
  324. point.labelPosition = {
  325. // initial position of the data label - it's utilized for
  326. // finding the final position for the label
  327. natural: {
  328. x: 0,
  329. y: y
  330. },
  331. 'final': {
  332. // used for generating connector path -
  333. // initialized later in drawDataLabels function
  334. // x: undefined,
  335. // y: undefined
  336. },
  337. // left - funnel on the left side of the data label
  338. // right - funnel on the right side of the data label
  339. alignment: leftSide ? 'right' : 'left',
  340. connectorPosition: {
  341. breakAt: {
  342. x: x + (point.labelDistance - 5) * sign,
  343. y: y
  344. },
  345. touchingSliceAt: {
  346. x: x + point.labelDistance * sign,
  347. y: y
  348. }
  349. }
  350. };
  351. }
  352. seriesTypes[series.options.dataLabels.inside ? 'column' : 'pie'].prototype.drawDataLabels.call(this);
  353. },
  354. alignDataLabel: function (point, dataLabel, options, alignTo, isNew) {
  355. var series = point.series, reversed = series.options.reversed, dlBox = point.dlBox || point.shapeArgs, align = options.align, verticalAlign = options.verticalAlign, inside = ((series.options || {}).dataLabels || {}).inside, centerY = series.center[1], pointPlotY = (reversed ?
  356. 2 * centerY - point.plotY :
  357. point.plotY), widthAtLabel = series.getWidthAt(pointPlotY - dlBox.height / 2 +
  358. dataLabel.height), offset = verticalAlign === 'middle' ?
  359. (dlBox.topWidth - dlBox.bottomWidth) / 4 :
  360. (widthAtLabel - dlBox.bottomWidth) / 2, y = dlBox.y, x = dlBox.x;
  361. if (verticalAlign === 'middle') {
  362. y = dlBox.y - dlBox.height / 2 + dataLabel.height / 2;
  363. }
  364. else if (verticalAlign === 'top') {
  365. y = dlBox.y - dlBox.height + dataLabel.height +
  366. options.padding;
  367. }
  368. if (verticalAlign === 'top' && !reversed ||
  369. verticalAlign === 'bottom' && reversed ||
  370. verticalAlign === 'middle') {
  371. if (align === 'right') {
  372. x = dlBox.x - options.padding + offset;
  373. }
  374. else if (align === 'left') {
  375. x = dlBox.x + options.padding - offset;
  376. }
  377. }
  378. alignTo = {
  379. x: x,
  380. y: reversed ? y - dlBox.height : y,
  381. width: dlBox.bottomWidth,
  382. height: dlBox.height
  383. };
  384. options.verticalAlign = 'bottom';
  385. // Call the parent method
  386. if (!inside || point.visible) {
  387. Highcharts.Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew);
  388. }
  389. if (inside) {
  390. if (!point.visible && point.dataLabel) {
  391. // Avoid animation from top
  392. point.dataLabel.placed = false;
  393. }
  394. // If label is inside and we have contrast, set it:
  395. if (point.contrastColor) {
  396. dataLabel.css({
  397. color: point.contrastColor
  398. });
  399. }
  400. }
  401. }
  402. });
  403. /* eslint-disable no-invalid-this */
  404. addEvent(Chart, 'afterHideAllOverlappingLabels', function () {
  405. this.series.forEach(function (series) {
  406. var dataLabelsOptions = series.options && series.options.dataLabels;
  407. if (isArray(dataLabelsOptions)) {
  408. dataLabelsOptions = dataLabelsOptions[0];
  409. }
  410. if (series.is('pie') &&
  411. series.placeDataLabels &&
  412. dataLabelsOptions &&
  413. !dataLabelsOptions.inside) {
  414. series.placeDataLabels();
  415. }
  416. });
  417. });
  418. /**
  419. * A `funnel` series. If the [type](#series.funnel.type) option is
  420. * not specified, it is inherited from [chart.type](#chart.type).
  421. *
  422. * @extends series,plotOptions.funnel
  423. * @excluding dataParser, dataURL, stack, xAxis, yAxis, dataSorting
  424. * @product highcharts
  425. * @requires modules/funnel
  426. * @apioption series.funnel
  427. */
  428. /**
  429. * An array of data points for the series. For the `funnel` series type,
  430. * points can be given in the following ways:
  431. *
  432. * 1. An array of numerical values. In this case, the numerical values
  433. * will be interpreted as `y` options. Example:
  434. *
  435. * ```js
  436. * data: [0, 5, 3, 5]
  437. * ```
  438. *
  439. * 2. An array of objects with named values. The following snippet shows only a
  440. * few settings, see the complete options set below. If the total number of data
  441. * points exceeds the series' [turboThreshold](#series.funnel.turboThreshold),
  442. * this option is not available.
  443. *
  444. * ```js
  445. * data: [{
  446. * y: 3,
  447. * name: "Point2",
  448. * color: "#00FF00"
  449. * }, {
  450. * y: 1,
  451. * name: "Point1",
  452. * color: "#FF00FF"
  453. * }]
  454. * ```
  455. *
  456. * @sample {highcharts} highcharts/chart/reflow-true/
  457. * Numerical values
  458. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  459. * Arrays of numeric x and y
  460. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  461. * Arrays of datetime x and y
  462. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  463. * Arrays of point.name and y
  464. * @sample {highcharts} highcharts/series/data-array-of-objects/
  465. * Config objects
  466. *
  467. * @type {Array<number|null|*>}
  468. * @extends series.pie.data
  469. * @excluding sliced
  470. * @product highcharts
  471. * @apioption series.funnel.data
  472. */
  473. /**
  474. * Pyramid series type.
  475. *
  476. * @private
  477. * @class
  478. * @name Highcharts.seriesTypes.pyramid
  479. *
  480. * @augments Highcharts.Series
  481. */
  482. seriesType('pyramid', 'funnel',
  483. /**
  484. * A pyramid series is a special type of funnel, without neck and reversed
  485. * by default.
  486. *
  487. * @sample highcharts/demo/pyramid/
  488. * Pyramid chart
  489. *
  490. * @extends plotOptions.funnel
  491. * @product highcharts
  492. * @requires modules/funnel
  493. * @optionparent plotOptions.pyramid
  494. */
  495. {
  496. /**
  497. * The pyramid neck width is zero by default, as opposed to the funnel,
  498. * which shares the same layout logic.
  499. *
  500. * @since 3.0.10
  501. */
  502. neckWidth: '0%',
  503. /**
  504. * The pyramid neck width is zero by default, as opposed to the funnel,
  505. * which shares the same layout logic.
  506. *
  507. * @since 3.0.10
  508. */
  509. neckHeight: '0%',
  510. /**
  511. * The pyramid is reversed by default, as opposed to the funnel, which
  512. * shares the layout engine, and is not reversed.
  513. *
  514. * @since 3.0.10
  515. */
  516. reversed: true
  517. });
  518. /**
  519. * A `pyramid` series. If the [type](#series.pyramid.type) option is
  520. * not specified, it is inherited from [chart.type](#chart.type).
  521. *
  522. * @extends series,plotOptions.pyramid
  523. * @excluding dataParser, dataURL, stack, xAxis, yAxis, dataSorting
  524. * @product highcharts
  525. * @requires modules/funnel
  526. * @apioption series.pyramid
  527. */
  528. /**
  529. * An array of data points for the series. For the `pyramid` series
  530. * type, points can be given in the following ways:
  531. *
  532. * 1. An array of numerical values. In this case, the numerical values will be
  533. * interpreted as `y` options. Example:
  534. * ```js
  535. * data: [0, 5, 3, 5]
  536. * ```
  537. *
  538. * 2. An array of objects with named values. The following snippet shows only a
  539. * few settings, see the complete options set below. If the total number of
  540. * data points exceeds the series'
  541. * [turboThreshold](#series.pyramid.turboThreshold), this option is not
  542. * available.
  543. * ```js
  544. * data: [{
  545. * y: 9,
  546. * name: "Point2",
  547. * color: "#00FF00"
  548. * }, {
  549. * y: 6,
  550. * name: "Point1",
  551. * color: "#FF00FF"
  552. * }]
  553. * ```
  554. *
  555. * @sample {highcharts} highcharts/chart/reflow-true/
  556. * Numerical values
  557. * @sample {highcharts} highcharts/series/data-array-of-objects/
  558. * Config objects
  559. *
  560. * @type {Array<number|null|*>}
  561. * @extends series.pie.data
  562. * @excluding sliced
  563. * @product highcharts
  564. * @apioption series.pyramid.data
  565. */
  566. ''; // adds doclets above into transpiled file
  567. });
  568. _registerModule(_modules, 'masters/modules/funnel.src.js', [], function () {
  569. });
  570. }));