funnel3d.src.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809
  1. /**
  2. * @license Highcharts JS v8.2.0 (2020-08-20)
  3. *
  4. * Highcharts funnel module
  5. *
  6. * (c) 2010-2019 Kacper Madej
  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/funnel3d', ['highcharts', 'highcharts/highcharts-3d', 'highcharts/modules/cylinder'], 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, 'Series/Funnel3DSeries.js', [_modules['Core/Globals.js'], _modules['Extensions/Math3D.js'], _modules['Core/Color.js'], _modules['Core/Utilities.js']], function (H, Math3D, Color, U) {
  32. /* *
  33. *
  34. * Highcharts funnel3d series module
  35. *
  36. * (c) 2010-2020 Highsoft AS
  37. *
  38. * Author: Kacper Madej
  39. *
  40. * License: www.highcharts.com/license
  41. *
  42. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  43. *
  44. * */
  45. var perspective = Math3D.perspective;
  46. var color = Color.parse;
  47. var error = U.error,
  48. extend = U.extend,
  49. merge = U.merge,
  50. pick = U.pick,
  51. relativeLength = U.relativeLength,
  52. seriesType = U.seriesType;
  53. var charts = H.charts,
  54. seriesTypes = H.seriesTypes,
  55. // Use H.Renderer instead of SVGRenderer for VML support.
  56. RendererProto = H.Renderer.prototype,
  57. //
  58. cuboidPath = RendererProto.cuboidPath,
  59. funnel3dMethods;
  60. /**
  61. * The funnel3d series type.
  62. *
  63. * @constructor seriesTypes.funnel3d
  64. * @augments seriesTypes.column
  65. * @requires highcharts-3d
  66. * @requires modules/cylinder
  67. * @requires modules/funnel3d
  68. */
  69. seriesType('funnel3d', 'column',
  70. /**
  71. * A funnel3d is a 3d version of funnel series type. Funnel charts are
  72. * a type of chart often used to visualize stages in a sales project,
  73. * where the top are the initial stages with the most clients.
  74. *
  75. * It requires that the `highcharts-3d.js`, `cylinder.js` and
  76. * `funnel3d.js` module are loaded.
  77. *
  78. * @sample highcharts/demo/funnel3d/
  79. * Funnel3d
  80. *
  81. * @extends plotOptions.column
  82. * @excluding allAreas, boostThreshold, colorAxis, compare, compareBase,
  83. * dataSorting, boostBlending
  84. * @product highcharts
  85. * @since 7.1.0
  86. * @requires highcharts-3d
  87. * @requires modules/cylinder
  88. * @requires modules/funnel3d
  89. * @optionparent plotOptions.funnel3d
  90. */
  91. {
  92. /** @ignore-option */
  93. center: ['50%', '50%'],
  94. /**
  95. * The max width of the series compared to the width of the plot area,
  96. * or the pixel width if it is a number.
  97. *
  98. * @type {number|string}
  99. * @sample {highcharts} highcharts/demo/funnel3d/ Funnel3d demo
  100. * @product highcharts
  101. */
  102. width: '90%',
  103. /**
  104. * The width of the neck, the lower part of the funnel. A number defines
  105. * pixel width, a percentage string defines a percentage of the plot
  106. * area width.
  107. *
  108. * @type {number|string}
  109. * @sample {highcharts} highcharts/demo/funnel3d/ Funnel3d demo
  110. * @product highcharts
  111. */
  112. neckWidth: '30%',
  113. /**
  114. * The height of the series. If it is a number it defines
  115. * the pixel height, if it is a percentage string it is the percentage
  116. * of the plot area height.
  117. *
  118. * @type {number|string}
  119. * @sample {highcharts} highcharts/demo/funnel3d/ Funnel3d demo
  120. * @product highcharts
  121. */
  122. height: '100%',
  123. /**
  124. * The height of the neck, the lower part of the funnel. A number
  125. * defines pixel width, a percentage string defines a percentage
  126. * of the plot area height.
  127. *
  128. * @type {number|string}
  129. * @sample {highcharts} highcharts/demo/funnel3d/ Funnel3d demo
  130. * @product highcharts
  131. */
  132. neckHeight: '25%',
  133. /**
  134. * A reversed funnel has the widest area down. A reversed funnel with
  135. * no neck width and neck height is a pyramid.
  136. *
  137. * @product highcharts
  138. */
  139. reversed: false,
  140. /**
  141. * By deafult sides fill is set to a gradient through this option being
  142. * set to `true`. Set to `false` to get solid color for the sides.
  143. *
  144. * @product highcharts
  145. */
  146. gradientForSides: true,
  147. animation: false,
  148. edgeWidth: 0,
  149. colorByPoint: true,
  150. showInLegend: false,
  151. dataLabels: {
  152. align: 'right',
  153. crop: false,
  154. inside: false,
  155. overflow: 'allow'
  156. }
  157. }, {
  158. // Override default axis options with series required options for axes
  159. bindAxes: function () {
  160. H.Series.prototype.bindAxes.apply(this, arguments);
  161. extend(this.xAxis.options, {
  162. gridLineWidth: 0,
  163. lineWidth: 0,
  164. title: null,
  165. tickPositions: []
  166. });
  167. extend(this.yAxis.options, {
  168. gridLineWidth: 0,
  169. title: null,
  170. labels: {
  171. enabled: false
  172. }
  173. });
  174. },
  175. translate3dShapes: H.noop,
  176. translate: function () {
  177. H.Series.prototype.translate.apply(this, arguments);
  178. var sum = 0,
  179. series = this,
  180. chart = series.chart,
  181. options = series.options,
  182. reversed = options.reversed,
  183. ignoreHiddenPoint = options.ignoreHiddenPoint,
  184. plotWidth = chart.plotWidth,
  185. plotHeight = chart.plotHeight,
  186. cumulative = 0, // start at top
  187. center = options.center,
  188. centerX = relativeLength(center[0],
  189. plotWidth),
  190. centerY = relativeLength(center[1],
  191. plotHeight),
  192. width = relativeLength(options.width,
  193. plotWidth),
  194. tempWidth,
  195. getWidthAt,
  196. height = relativeLength(options.height,
  197. plotHeight),
  198. neckWidth = relativeLength(options.neckWidth,
  199. plotWidth),
  200. neckHeight = relativeLength(options.neckHeight,
  201. plotHeight),
  202. neckY = (centerY - height / 2) + height - neckHeight,
  203. data = series.data,
  204. fraction,
  205. tooltipPos,
  206. //
  207. y1,
  208. y3,
  209. y5,
  210. //
  211. h,
  212. shapeArgs;
  213. // Return the width at a specific y coordinate
  214. series.getWidthAt = getWidthAt = function (y) {
  215. var top = (centerY - height / 2);
  216. return (y > neckY || height === neckHeight) ?
  217. neckWidth :
  218. neckWidth + (width - neckWidth) *
  219. (1 - (y - top) / (height - neckHeight));
  220. };
  221. // Expose
  222. series.center = [centerX, centerY, height];
  223. series.centerX = centerX;
  224. /*
  225. * Individual point coordinate naming:
  226. *
  227. * _________centerX,y1________
  228. * \ /
  229. * \ /
  230. * \ /
  231. * \ /
  232. * \ /
  233. * ___centerX,y3___
  234. *
  235. * Additional for the base of the neck:
  236. *
  237. * | |
  238. * | |
  239. * | |
  240. * ___centerX,y5___
  241. */
  242. // get the total sum
  243. data.forEach(function (point) {
  244. if (!ignoreHiddenPoint || point.visible !== false) {
  245. sum += point.y;
  246. }
  247. });
  248. data.forEach(function (point) {
  249. // set start and end positions
  250. y5 = null;
  251. fraction = sum ? point.y / sum : 0;
  252. y1 = centerY - height / 2 + cumulative * height;
  253. y3 = y1 + fraction * height;
  254. tempWidth = getWidthAt(y1);
  255. h = y3 - y1;
  256. shapeArgs = {
  257. // for fill setter
  258. gradientForSides: pick(point.options.gradientForSides, options.gradientForSides),
  259. x: centerX,
  260. y: y1,
  261. height: h,
  262. width: tempWidth,
  263. z: 1,
  264. top: {
  265. width: tempWidth
  266. }
  267. };
  268. tempWidth = getWidthAt(y3);
  269. shapeArgs.bottom = {
  270. fraction: fraction,
  271. width: tempWidth
  272. };
  273. // the entire point is within the neck
  274. if (y1 >= neckY) {
  275. shapeArgs.isCylinder = true;
  276. }
  277. else if (y3 > neckY) {
  278. // the base of the neck
  279. y5 = y3;
  280. tempWidth = getWidthAt(neckY);
  281. y3 = neckY;
  282. shapeArgs.bottom.width = tempWidth;
  283. shapeArgs.middle = {
  284. fraction: h ? (neckY - y1) / h : 0,
  285. width: tempWidth
  286. };
  287. }
  288. if (reversed) {
  289. shapeArgs.y = y1 = centerY + height / 2 -
  290. (cumulative + fraction) * height;
  291. if (shapeArgs.middle) {
  292. shapeArgs.middle.fraction = 1 -
  293. (h ? shapeArgs.middle.fraction : 0);
  294. }
  295. tempWidth = shapeArgs.width;
  296. shapeArgs.width = shapeArgs.bottom.width;
  297. shapeArgs.bottom.width = tempWidth;
  298. }
  299. point.shapeArgs = extend(point.shapeArgs, shapeArgs);
  300. // for tooltips and data labels context
  301. point.percentage = fraction * 100;
  302. point.plotX = centerX;
  303. if (reversed) {
  304. point.plotY = centerY + height / 2 -
  305. (cumulative + fraction / 2) * height;
  306. }
  307. else {
  308. point.plotY = (y1 + (y5 || y3)) / 2;
  309. }
  310. // Placement of tooltips and data labels in 3D
  311. tooltipPos = perspective([{
  312. x: centerX,
  313. y: point.plotY,
  314. z: reversed ?
  315. -(width - getWidthAt(point.plotY)) / 2 :
  316. -(getWidthAt(point.plotY)) / 2
  317. }], chart, true)[0];
  318. point.tooltipPos = [tooltipPos.x, tooltipPos.y];
  319. // base to be used when alignment options are known
  320. point.dlBoxRaw = {
  321. x: centerX,
  322. width: getWidthAt(point.plotY),
  323. y: y1,
  324. bottom: shapeArgs.height,
  325. fullWidth: width
  326. };
  327. if (!ignoreHiddenPoint || point.visible !== false) {
  328. cumulative += fraction;
  329. }
  330. });
  331. },
  332. alignDataLabel: function (point, dataLabel, options) {
  333. var series = this,
  334. dlBoxRaw = point.dlBoxRaw,
  335. inverted = series.chart.inverted,
  336. below = point.plotY > pick(series.translatedThreshold,
  337. series.yAxis.len),
  338. inside = pick(options.inside, !!series.options.stacking),
  339. dlBox = {
  340. x: dlBoxRaw.x,
  341. y: dlBoxRaw.y,
  342. height: 0
  343. };
  344. options.align = pick(options.align, !inverted || inside ? 'center' : below ? 'right' : 'left');
  345. options.verticalAlign = pick(options.verticalAlign, inverted || inside ? 'middle' : below ? 'top' : 'bottom');
  346. if (options.verticalAlign !== 'top') {
  347. dlBox.y += dlBoxRaw.bottom /
  348. (options.verticalAlign === 'bottom' ? 1 : 2);
  349. }
  350. dlBox.width = series.getWidthAt(dlBox.y);
  351. if (series.options.reversed) {
  352. dlBox.width = dlBoxRaw.fullWidth - dlBox.width;
  353. }
  354. if (inside) {
  355. dlBox.x -= dlBox.width / 2;
  356. }
  357. else {
  358. // swap for inside
  359. if (options.align === 'left') {
  360. options.align = 'right';
  361. dlBox.x -= dlBox.width * 1.5;
  362. }
  363. else if (options.align === 'right') {
  364. options.align = 'left';
  365. dlBox.x += dlBox.width / 2;
  366. }
  367. else {
  368. dlBox.x -= dlBox.width / 2;
  369. }
  370. }
  371. point.dlBox = dlBox;
  372. seriesTypes.column.prototype.alignDataLabel.apply(series, arguments);
  373. }
  374. }, /** @lends seriesTypes.funnel3d.prototype.pointClass.prototype */ {
  375. shapeType: 'funnel3d',
  376. hasNewShapeType: H
  377. .seriesTypes.column.prototype
  378. .pointClass.prototype
  379. .hasNewShapeType
  380. });
  381. /**
  382. * A `funnel3d` series. If the [type](#series.funnel3d.type) option is
  383. * not specified, it is inherited from [chart.type](#chart.type).
  384. *
  385. * @sample {highcharts} highcharts/demo/funnel3d/
  386. * Funnel3d demo
  387. *
  388. * @since 7.1.0
  389. * @extends series.funnel,plotOptions.funnel3d
  390. * @excluding allAreas,boostThreshold,colorAxis,compare,compareBase
  391. * @product highcharts
  392. * @requires highcharts-3d
  393. * @requires modules/cylinder
  394. * @requires modules/funnel3d
  395. * @apioption series.funnel3d
  396. */
  397. /**
  398. * An array of data points for the series. For the `funnel3d` series
  399. * type, points can be given in the following ways:
  400. *
  401. * 1. An array of numerical values. In this case, the numerical values
  402. * will be interpreted as `y` options. The `x` values will be automatically
  403. * calculated, either starting at 0 and incremented by 1, or from `pointStart`
  404. * and `pointInterval` given in the series options. If the axis has
  405. * categories, these will be used. Example:
  406. *
  407. * ```js
  408. * data: [0, 5, 3, 5]
  409. * ```
  410. *
  411. * 2. An array of objects with named values. The following snippet shows only a
  412. * few settings, see the complete options set below. If the total number of data
  413. * points exceeds the series' [turboThreshold](#series.funnel3d.turboThreshold),
  414. * this option is not available.
  415. *
  416. * ```js
  417. * data: [{
  418. * y: 2,
  419. * name: "Point2",
  420. * color: "#00FF00"
  421. * }, {
  422. * y: 4,
  423. * name: "Point1",
  424. * color: "#FF00FF"
  425. * }]
  426. * ```
  427. *
  428. * @sample {highcharts} highcharts/chart/reflow-true/
  429. * Numerical values
  430. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  431. * Arrays of numeric x and y
  432. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  433. * Arrays of datetime x and y
  434. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  435. * Arrays of point.name and y
  436. * @sample {highcharts} highcharts/series/data-array-of-objects/
  437. * Config objects
  438. *
  439. * @type {Array<number|Array<number>|*>}
  440. * @extends series.column.data
  441. * @product highcharts
  442. * @apioption series.funnel3d.data
  443. */
  444. /**
  445. * By deafult sides fill is set to a gradient through this option being
  446. * set to `true`. Set to `false` to get solid color for the sides.
  447. *
  448. * @type {boolean}
  449. * @product highcharts
  450. * @apioption series.funnel3d.data.gradientForSides
  451. */
  452. funnel3dMethods = merge(RendererProto.elements3d.cuboid, {
  453. parts: [
  454. 'top', 'bottom',
  455. 'frontUpper', 'backUpper',
  456. 'frontLower', 'backLower',
  457. 'rightUpper', 'rightLower'
  458. ],
  459. mainParts: ['top', 'bottom'],
  460. sideGroups: [
  461. 'upperGroup', 'lowerGroup'
  462. ],
  463. sideParts: {
  464. upperGroup: ['frontUpper', 'backUpper', 'rightUpper'],
  465. lowerGroup: ['frontLower', 'backLower', 'rightLower']
  466. },
  467. pathType: 'funnel3d',
  468. // override opacity and color setters to control opacity
  469. opacitySetter: function (opacity) {
  470. var funnel3d = this,
  471. parts = funnel3d.parts,
  472. chart = H.charts[funnel3d.renderer.chartIndex],
  473. filterId = 'group-opacity-' + opacity + '-' + chart.index;
  474. // use default for top and bottom
  475. funnel3d.parts = funnel3d.mainParts;
  476. funnel3d.singleSetterForParts('opacity', opacity);
  477. // restore
  478. funnel3d.parts = parts;
  479. if (!chart.renderer.filterId) {
  480. chart.renderer.definition({
  481. tagName: 'filter',
  482. id: filterId,
  483. children: [{
  484. tagName: 'feComponentTransfer',
  485. children: [{
  486. tagName: 'feFuncA',
  487. type: 'table',
  488. tableValues: '0 ' + opacity
  489. }]
  490. }]
  491. });
  492. funnel3d.sideGroups.forEach(function (groupName) {
  493. funnel3d[groupName].attr({
  494. filter: 'url(#' + filterId + ')'
  495. });
  496. });
  497. // styled mode
  498. if (funnel3d.renderer.styledMode) {
  499. chart.renderer.definition({
  500. tagName: 'style',
  501. textContent: '.highcharts-' + filterId +
  502. ' {filter:url(#' + filterId + ')}'
  503. });
  504. funnel3d.sideGroups.forEach(function (group) {
  505. group.addClass('highcharts-' + filterId);
  506. });
  507. }
  508. }
  509. return funnel3d;
  510. },
  511. fillSetter: function (fill) {
  512. // extract alpha channel to use the opacitySetter
  513. var funnel3d = this,
  514. fillColor = color(fill),
  515. alpha = fillColor.rgba[3],
  516. partsWithColor = {
  517. // standard color for top and bottom
  518. top: color(fill).brighten(0.1).get(),
  519. bottom: color(fill).brighten(-0.2).get()
  520. };
  521. if (alpha < 1) {
  522. fillColor.rgba[3] = 1;
  523. fillColor = fillColor.get('rgb');
  524. // set opacity through the opacitySetter
  525. funnel3d.attr({
  526. opacity: alpha
  527. });
  528. }
  529. else {
  530. // use default for full opacity
  531. fillColor = fill;
  532. }
  533. // add gradient for sides
  534. if (!fillColor.linearGradient &&
  535. !fillColor.radialGradient &&
  536. funnel3d.gradientForSides) {
  537. fillColor = {
  538. linearGradient: { x1: 0, x2: 1, y1: 1, y2: 1 },
  539. stops: [
  540. [0, color(fill).brighten(-0.2).get()],
  541. [0.5, fill],
  542. [1, color(fill).brighten(-0.2).get()]
  543. ]
  544. };
  545. }
  546. // gradient support
  547. if (fillColor.linearGradient) {
  548. // color in steps, as each gradient will generate a key
  549. funnel3d.sideGroups.forEach(function (sideGroupName) {
  550. var box = funnel3d[sideGroupName].gradientBox,
  551. gradient = fillColor.linearGradient,
  552. alteredGradient = merge(fillColor, {
  553. linearGradient: {
  554. x1: box.x + gradient.x1 * box.width,
  555. y1: box.y + gradient.y1 * box.height,
  556. x2: box.x + gradient.x2 * box.width,
  557. y2: box.y + gradient.y2 * box.height
  558. }
  559. });
  560. funnel3d.sideParts[sideGroupName].forEach(function (partName) {
  561. partsWithColor[partName] = alteredGradient;
  562. });
  563. });
  564. }
  565. else {
  566. merge(true, partsWithColor, {
  567. frontUpper: fillColor,
  568. backUpper: fillColor,
  569. rightUpper: fillColor,
  570. frontLower: fillColor,
  571. backLower: fillColor,
  572. rightLower: fillColor
  573. });
  574. if (fillColor.radialGradient) {
  575. funnel3d.sideGroups.forEach(function (sideGroupName) {
  576. var gradBox = funnel3d[sideGroupName].gradientBox,
  577. centerX = gradBox.x + gradBox.width / 2,
  578. centerY = gradBox.y + gradBox.height / 2,
  579. diameter = Math.min(gradBox.width,
  580. gradBox.height);
  581. funnel3d.sideParts[sideGroupName].forEach(function (partName) {
  582. funnel3d[partName].setRadialReference([
  583. centerX, centerY, diameter
  584. ]);
  585. });
  586. });
  587. }
  588. }
  589. funnel3d.singleSetterForParts('fill', null, partsWithColor);
  590. // fill for animation getter (#6776)
  591. funnel3d.color = funnel3d.fill = fill;
  592. // change gradientUnits to userSpaceOnUse for linearGradient
  593. if (fillColor.linearGradient) {
  594. [funnel3d.frontLower, funnel3d.frontUpper].forEach(function (part) {
  595. var elem = part.element,
  596. grad = elem && funnel3d.renderer.gradients[elem.gradient];
  597. if (grad && grad.attr('gradientUnits') !== 'userSpaceOnUse') {
  598. grad.attr({
  599. gradientUnits: 'userSpaceOnUse'
  600. });
  601. }
  602. });
  603. }
  604. return funnel3d;
  605. },
  606. adjustForGradient: function () {
  607. var funnel3d = this,
  608. bbox;
  609. funnel3d.sideGroups.forEach(function (sideGroupName) {
  610. // use common extremes for groups for matching gradients
  611. var topLeftEdge = {
  612. x: Number.MAX_VALUE,
  613. y: Number.MAX_VALUE
  614. },
  615. bottomRightEdge = {
  616. x: -Number.MAX_VALUE,
  617. y: -Number.MAX_VALUE
  618. };
  619. // get extremes
  620. funnel3d.sideParts[sideGroupName].forEach(function (partName) {
  621. var part = funnel3d[partName];
  622. bbox = part.getBBox(true);
  623. topLeftEdge = {
  624. x: Math.min(topLeftEdge.x, bbox.x),
  625. y: Math.min(topLeftEdge.y, bbox.y)
  626. };
  627. bottomRightEdge = {
  628. x: Math.max(bottomRightEdge.x, bbox.x + bbox.width),
  629. y: Math.max(bottomRightEdge.y, bbox.y + bbox.height)
  630. };
  631. });
  632. // store for color fillSetter
  633. funnel3d[sideGroupName].gradientBox = {
  634. x: topLeftEdge.x,
  635. width: bottomRightEdge.x - topLeftEdge.x,
  636. y: topLeftEdge.y,
  637. height: bottomRightEdge.y - topLeftEdge.y
  638. };
  639. });
  640. },
  641. zIndexSetter: function () {
  642. // this.added won't work, because zIndex is set after the prop is set,
  643. // but before the graphic is really added
  644. if (this.finishedOnAdd) {
  645. this.adjustForGradient();
  646. }
  647. // run default
  648. return this.renderer.Element.prototype.zIndexSetter.apply(this, arguments);
  649. },
  650. onAdd: function () {
  651. this.adjustForGradient();
  652. this.finishedOnAdd = true;
  653. }
  654. });
  655. RendererProto.elements3d.funnel3d = funnel3dMethods;
  656. RendererProto.funnel3d = function (shapeArgs) {
  657. var renderer = this,
  658. funnel3d = renderer.element3d('funnel3d',
  659. shapeArgs),
  660. styledMode = renderer.styledMode,
  661. // hide stroke for Firefox
  662. strokeAttrs = {
  663. 'stroke-width': 1,
  664. stroke: 'none'
  665. };
  666. // create groups for sides for oppacity setter
  667. funnel3d.upperGroup = renderer.g('funnel3d-upper-group').attr({
  668. zIndex: funnel3d.frontUpper.zIndex
  669. }).add(funnel3d);
  670. [
  671. funnel3d.frontUpper,
  672. funnel3d.backUpper,
  673. funnel3d.rightUpper
  674. ].forEach(function (upperElem) {
  675. if (!styledMode) {
  676. upperElem.attr(strokeAttrs);
  677. }
  678. upperElem.add(funnel3d.upperGroup);
  679. });
  680. funnel3d.lowerGroup = renderer.g('funnel3d-lower-group').attr({
  681. zIndex: funnel3d.frontLower.zIndex
  682. }).add(funnel3d);
  683. [
  684. funnel3d.frontLower,
  685. funnel3d.backLower,
  686. funnel3d.rightLower
  687. ].forEach(function (lowerElem) {
  688. if (!styledMode) {
  689. lowerElem.attr(strokeAttrs);
  690. }
  691. lowerElem.add(funnel3d.lowerGroup);
  692. });
  693. funnel3d.gradientForSides = shapeArgs.gradientForSides;
  694. return funnel3d;
  695. };
  696. // eslint-disable-next-line valid-jsdoc
  697. /**
  698. * Generates paths and zIndexes.
  699. * @private
  700. */
  701. RendererProto.funnel3dPath = function (shapeArgs) {
  702. // Check getCylinderEnd for better error message if
  703. // the cylinder module is missing
  704. if (!this.getCylinderEnd) {
  705. error('A required Highcharts module is missing: cylinder.js', true, charts[this.chartIndex]);
  706. }
  707. var renderer = this,
  708. chart = charts[renderer.chartIndex],
  709. // adjust angles for visible edges
  710. // based on alpha, selected through visual tests
  711. alphaCorrection = shapeArgs.alphaCorrection = 90 -
  712. Math.abs((chart.options.chart.options3d.alpha % 180) - 90),
  713. // set zIndexes of parts based on cubiod logic, for consistency
  714. cuboidData = cuboidPath.call(renderer,
  715. merge(shapeArgs, {
  716. depth: shapeArgs.width,
  717. width: (shapeArgs.width + shapeArgs.bottom.width) / 2
  718. })),
  719. isTopFirst = cuboidData.isTop,
  720. isFrontFirst = !cuboidData.isFront,
  721. hasMiddle = !!shapeArgs.middle,
  722. //
  723. top = renderer.getCylinderEnd(chart,
  724. merge(shapeArgs, {
  725. x: shapeArgs.x - shapeArgs.width / 2,
  726. z: shapeArgs.z - shapeArgs.width / 2,
  727. alphaCorrection: alphaCorrection
  728. })),
  729. bottomWidth = shapeArgs.bottom.width,
  730. bottomArgs = merge(shapeArgs, {
  731. width: bottomWidth,
  732. x: shapeArgs.x - bottomWidth / 2,
  733. z: shapeArgs.z - bottomWidth / 2,
  734. alphaCorrection: alphaCorrection
  735. }),
  736. bottom = renderer.getCylinderEnd(chart,
  737. bottomArgs,
  738. true),
  739. //
  740. middleWidth = bottomWidth,
  741. middleTopArgs = bottomArgs,
  742. middleTop = bottom,
  743. middleBottom = bottom,
  744. ret,
  745. // masking for cylinders or a missing part of a side shape
  746. useAlphaCorrection;
  747. if (hasMiddle) {
  748. middleWidth = shapeArgs.middle.width;
  749. middleTopArgs = merge(shapeArgs, {
  750. y: shapeArgs.y + shapeArgs.middle.fraction * shapeArgs.height,
  751. width: middleWidth,
  752. x: shapeArgs.x - middleWidth / 2,
  753. z: shapeArgs.z - middleWidth / 2
  754. });
  755. middleTop = renderer.getCylinderEnd(chart, middleTopArgs, false);
  756. middleBottom = renderer.getCylinderEnd(chart, middleTopArgs, false);
  757. }
  758. ret = {
  759. top: top,
  760. bottom: bottom,
  761. frontUpper: renderer.getCylinderFront(top, middleTop),
  762. zIndexes: {
  763. group: cuboidData.zIndexes.group,
  764. top: isTopFirst !== 0 ? 0 : 3,
  765. bottom: isTopFirst !== 1 ? 0 : 3,
  766. frontUpper: isFrontFirst ? 2 : 1,
  767. backUpper: isFrontFirst ? 1 : 2,
  768. rightUpper: isFrontFirst ? 2 : 1
  769. }
  770. };
  771. ret.backUpper = renderer.getCylinderBack(top, middleTop);
  772. useAlphaCorrection = (Math.min(middleWidth, shapeArgs.width) /
  773. Math.max(middleWidth, shapeArgs.width)) !== 1;
  774. ret.rightUpper = renderer.getCylinderFront(renderer.getCylinderEnd(chart, merge(shapeArgs, {
  775. x: shapeArgs.x - shapeArgs.width / 2,
  776. z: shapeArgs.z - shapeArgs.width / 2,
  777. alphaCorrection: useAlphaCorrection ? -alphaCorrection : 0
  778. }), false), renderer.getCylinderEnd(chart, merge(middleTopArgs, {
  779. alphaCorrection: useAlphaCorrection ? -alphaCorrection : 0
  780. }), !hasMiddle));
  781. if (hasMiddle) {
  782. useAlphaCorrection = (Math.min(middleWidth, bottomWidth) /
  783. Math.max(middleWidth, bottomWidth)) !== 1;
  784. merge(true, ret, {
  785. frontLower: renderer.getCylinderFront(middleBottom, bottom),
  786. backLower: renderer.getCylinderBack(middleBottom, bottom),
  787. rightLower: renderer.getCylinderFront(renderer.getCylinderEnd(chart, merge(bottomArgs, {
  788. alphaCorrection: useAlphaCorrection ?
  789. -alphaCorrection : 0
  790. }), true), renderer.getCylinderEnd(chart, merge(middleTopArgs, {
  791. alphaCorrection: useAlphaCorrection ?
  792. -alphaCorrection : 0
  793. }), false)),
  794. zIndexes: {
  795. frontLower: isFrontFirst ? 2 : 1,
  796. backLower: isFrontFirst ? 1 : 2,
  797. rightLower: isFrontFirst ? 1 : 2
  798. }
  799. });
  800. }
  801. return ret;
  802. };
  803. });
  804. _registerModule(_modules, 'masters/modules/funnel3d.src.js', [], function () {
  805. });
  806. }));