funnel3d.src.js 32 KB

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