funnel3d.src.js 26 KB

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