Column3DComposition.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. /* *
  2. *
  3. * (c) 2010-2021 Torstein Honsi
  4. *
  5. * License: www.highcharts.com/license
  6. *
  7. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  8. *
  9. * */
  10. 'use strict';
  11. import ColumnSeries from '../Column/ColumnSeries.js';
  12. var columnProto = ColumnSeries.prototype;
  13. import H from '../../Core/Globals.js';
  14. var svg = H.svg;
  15. import Series from '../../Core/Series/Series.js';
  16. import Math3D from '../../Extensions/Math3D.js';
  17. var perspective = Math3D.perspective;
  18. import SeriesRegistry from '../../Core/Series/SeriesRegistry.js';
  19. import StackItem from '../../Extensions/Stacking.js';
  20. import U from '../../Core/Utilities.js';
  21. var addEvent = U.addEvent, pick = U.pick, wrap = U.wrap;
  22. /* *
  23. *
  24. * Functions
  25. *
  26. * */
  27. /* eslint-disable no-invalid-this */
  28. /**
  29. * @private
  30. * @param {Highcharts.Chart} chart
  31. * Chart with stacks
  32. * @param {string} stacking
  33. * Stacking option
  34. * @return {Highcharts.Stack3DDictionary}
  35. */
  36. function retrieveStacks(chart, stacking) {
  37. var series = chart.series, stacks = {};
  38. var stackNumber, i = 1;
  39. series.forEach(function (s) {
  40. stackNumber = pick(s.options.stack, (stacking ? 0 : series.length - 1 - s.index)); // #3841, #4532
  41. if (!stacks[stackNumber]) {
  42. stacks[stackNumber] = { series: [s], position: i };
  43. i++;
  44. }
  45. else {
  46. stacks[stackNumber].series.push(s);
  47. }
  48. });
  49. stacks.totalStacks = i + 1;
  50. return stacks;
  51. }
  52. wrap(columnProto, 'translate', function (proceed) {
  53. proceed.apply(this, [].slice.call(arguments, 1));
  54. // Do not do this if the chart is not 3D
  55. if (this.chart.is3d()) {
  56. this.translate3dShapes();
  57. }
  58. });
  59. // Don't use justifyDataLabel when point is outsidePlot
  60. wrap(Series.prototype, 'justifyDataLabel', function (proceed) {
  61. return !(arguments[2].outside3dPlot) ?
  62. proceed.apply(this, [].slice.call(arguments, 1)) :
  63. false;
  64. });
  65. columnProto.translate3dPoints = function () { };
  66. columnProto.translate3dShapes = function () {
  67. var series = this, chart = series.chart, seriesOptions = series.options, depth = seriesOptions.depth, stack = seriesOptions.stacking ?
  68. (seriesOptions.stack || 0) :
  69. series.index, // #4743
  70. z = stack * (depth + (seriesOptions.groupZPadding || 1)), borderCrisp = series.borderWidth % 2 ? 0.5 : 0, point2dPos; // Position of point in 2D, used for 3D position calculation.
  71. if (chart.inverted && !series.yAxis.reversed) {
  72. borderCrisp *= -1;
  73. }
  74. if (seriesOptions.grouping !== false) {
  75. z = 0;
  76. }
  77. z += (seriesOptions.groupZPadding || 1);
  78. series.data.forEach(function (point) {
  79. // #7103 Reset outside3dPlot flag
  80. point.outside3dPlot = null;
  81. if (point.y !== null) {
  82. var shapeArgs = point.shapeArgs, tooltipPos = point.tooltipPos,
  83. // Array for final shapeArgs calculation.
  84. // We are checking two dimensions (x and y).
  85. dimensions = [['x', 'width'], ['y', 'height']], borderlessBase; // Crisped rects can have +/- 0.5 pixels offset.
  86. // #3131 We need to check if column is inside plotArea.
  87. dimensions.forEach(function (d) {
  88. borderlessBase = shapeArgs[d[0]] - borderCrisp;
  89. if (borderlessBase < 0) {
  90. // If borderLessBase is smaller than 0, it is needed to set
  91. // its value to 0 or 0.5 depending on borderWidth
  92. // borderWidth may be even or odd.
  93. shapeArgs[d[1]] +=
  94. shapeArgs[d[0]] + borderCrisp;
  95. shapeArgs[d[0]] = -borderCrisp;
  96. borderlessBase = 0;
  97. }
  98. if ((borderlessBase + shapeArgs[d[1]] >
  99. series[d[0] + 'Axis'].len) &&
  100. // Do not change height/width of column if 0 (#6708)
  101. shapeArgs[d[1]] !== 0) {
  102. shapeArgs[d[1]] =
  103. series[d[0] + 'Axis'].len -
  104. shapeArgs[d[0]];
  105. }
  106. if (
  107. // Do not remove columns with zero height/width.
  108. (shapeArgs[d[1]] !== 0) &&
  109. (shapeArgs[d[0]] >=
  110. series[d[0] + 'Axis'].len ||
  111. shapeArgs[d[0]] + shapeArgs[d[1]] <=
  112. borderCrisp)) {
  113. // Set args to 0 if column is outside the chart.
  114. for (var key in shapeArgs) { // eslint-disable-line guard-for-in
  115. shapeArgs[key] = 0;
  116. }
  117. // #7103 outside3dPlot flag is set on Points which are
  118. // currently outside of plot.
  119. point.outside3dPlot = true;
  120. }
  121. });
  122. // Change from 2d to 3d
  123. if (point.shapeType === 'rect') {
  124. point.shapeType = 'cuboid';
  125. }
  126. shapeArgs.z = z;
  127. shapeArgs.depth = depth;
  128. shapeArgs.insidePlotArea = true;
  129. // Point's position in 2D
  130. point2dPos = {
  131. x: shapeArgs.x + shapeArgs.width / 2,
  132. y: shapeArgs.y,
  133. z: z + depth / 2 // The center of column in Z dimension
  134. };
  135. // Recalculate point positions for inverted graphs
  136. if (chart.inverted) {
  137. point2dPos.x = shapeArgs.height;
  138. point2dPos.y = point.clientX;
  139. }
  140. // Calculate and store point's position in 3D,
  141. // using perspective method.
  142. point.plot3d = perspective([point2dPos], chart, true, false)[0];
  143. // Translate the tooltip position in 3d space
  144. tooltipPos = perspective([{
  145. x: tooltipPos[0],
  146. y: tooltipPos[1],
  147. z: z + depth / 2 // The center of column in Z dimension
  148. }], chart, true, false)[0];
  149. point.tooltipPos = [tooltipPos.x, tooltipPos.y];
  150. }
  151. });
  152. // store for later use #4067
  153. series.z = z;
  154. };
  155. wrap(columnProto, 'animate', function (proceed) {
  156. if (!this.chart.is3d()) {
  157. proceed.apply(this, [].slice.call(arguments, 1));
  158. }
  159. else {
  160. var args = arguments, init = args[1], yAxis = this.yAxis, series = this, reversed = this.yAxis.reversed;
  161. if (svg) { // VML is too slow anyway
  162. if (init) {
  163. series.data.forEach(function (point) {
  164. if (point.y !== null) {
  165. point.height = point.shapeArgs.height;
  166. point.shapey = point.shapeArgs.y; // #2968
  167. point.shapeArgs.height = 1;
  168. if (!reversed) {
  169. if (point.stackY) {
  170. point.shapeArgs.y =
  171. point.plotY +
  172. yAxis.translate(point.stackY);
  173. }
  174. else {
  175. point.shapeArgs.y =
  176. point.plotY +
  177. (point.negative ?
  178. -point.height :
  179. point.height);
  180. }
  181. }
  182. }
  183. });
  184. }
  185. else { // run the animation
  186. series.data.forEach(function (point) {
  187. if (point.y !== null) {
  188. point.shapeArgs.height = point.height;
  189. point.shapeArgs.y = point.shapey; // #2968
  190. // null value do not have a graphic
  191. if (point.graphic) {
  192. point.graphic.animate(point.shapeArgs, series.options.animation);
  193. }
  194. }
  195. });
  196. // redraw datalabels to the correct position
  197. this.drawDataLabels();
  198. }
  199. }
  200. }
  201. });
  202. // In case of 3d columns there is no sense to add this columns to a specific
  203. // series group - if series is added to a group all columns will have the same
  204. // zIndex in comparison with different series.
  205. wrap(columnProto, 'plotGroup', function (proceed, prop, _name, _visibility, _zIndex, parent) {
  206. if (prop !== 'dataLabelsGroup') {
  207. if (this.chart.is3d()) {
  208. if (this[prop]) {
  209. delete this[prop];
  210. }
  211. if (parent) {
  212. if (!this.chart.columnGroup) {
  213. this.chart.columnGroup =
  214. this.chart.renderer.g('columnGroup').add(parent);
  215. }
  216. this[prop] = this.chart.columnGroup;
  217. this.chart.columnGroup.attr(this.getPlotBox());
  218. this[prop].survive = true;
  219. if (prop === 'group' || prop === 'markerGroup') {
  220. arguments[3] = 'visible';
  221. // For 3D column group and markerGroup should be visible
  222. }
  223. }
  224. }
  225. }
  226. return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  227. });
  228. // When series is not added to group it is needed to change setVisible method to
  229. // allow correct Legend funcionality. This wrap is basing on pie chart series.
  230. wrap(columnProto, 'setVisible', function (proceed, vis) {
  231. var series = this, pointVis;
  232. if (series.chart.is3d()) {
  233. series.data.forEach(function (point) {
  234. point.visible = point.options.visible = vis =
  235. typeof vis === 'undefined' ?
  236. !pick(series.visible, point.visible) : vis;
  237. pointVis = vis ? 'visible' : 'hidden';
  238. series.options.data[series.data.indexOf(point)] =
  239. point.options;
  240. if (point.graphic) {
  241. point.graphic.attr({
  242. visibility: pointVis
  243. });
  244. }
  245. });
  246. }
  247. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  248. });
  249. addEvent(ColumnSeries, 'afterInit', function () {
  250. if (this.chart.is3d()) {
  251. var series = this, seriesOptions = this.options, grouping = seriesOptions.grouping, stacking = seriesOptions.stacking, reversedStacks = pick(this.yAxis.options.reversedStacks, true), z = 0;
  252. // @todo grouping === true ?
  253. if (!(typeof grouping !== 'undefined' && !grouping)) {
  254. var stacks = retrieveStacks(this.chart, stacking), stack = seriesOptions.stack || 0, i; // position within the stack
  255. for (i = 0; i < stacks[stack].series.length; i++) {
  256. if (stacks[stack].series[i] === this) {
  257. break;
  258. }
  259. }
  260. z = (10 * (stacks.totalStacks - stacks[stack].position)) +
  261. (reversedStacks ? i : -i); // #4369
  262. // In case when axis is reversed, columns are also reversed inside
  263. // the group (#3737)
  264. if (!this.xAxis.reversed) {
  265. z = (stacks.totalStacks * 10) - z;
  266. }
  267. }
  268. seriesOptions.depth = seriesOptions.depth || 25;
  269. series.z = series.z || 0;
  270. seriesOptions.zIndex = z;
  271. }
  272. });
  273. // eslint-disable-next-line valid-jsdoc
  274. /**
  275. * @private
  276. */
  277. function pointAttribs(proceed) {
  278. var attr = proceed.apply(this, [].slice.call(arguments, 1));
  279. if (this.chart.is3d && this.chart.is3d()) {
  280. // Set the fill color to the fill color to provide a smooth edge
  281. attr.stroke = this.options.edgeColor || attr.fill;
  282. attr['stroke-width'] = pick(this.options.edgeWidth, 1); // #4055
  283. }
  284. return attr;
  285. }
  286. // eslint-disable-next-line valid-jsdoc
  287. /**
  288. * In 3D mode, all column-series are rendered in one main group. Because of that
  289. * we need to apply inactive state on all points.
  290. * @private
  291. */
  292. function setState(proceed, state, inherit) {
  293. var is3d = this.chart.is3d && this.chart.is3d();
  294. if (is3d) {
  295. this.options.inactiveOtherPoints = true;
  296. }
  297. proceed.call(this, state, inherit);
  298. if (is3d) {
  299. this.options.inactiveOtherPoints = false;
  300. }
  301. }
  302. // eslint-disable-next-line valid-jsdoc
  303. /**
  304. * In 3D mode, simple checking for a new shape to animate is not enough.
  305. * Additionally check if graphic is a group of elements
  306. * @private
  307. */
  308. function hasNewShapeType(proceed) {
  309. var args = [];
  310. for (var _i = 1; _i < arguments.length; _i++) {
  311. args[_i - 1] = arguments[_i];
  312. }
  313. return this.series.chart.is3d() ?
  314. this.graphic && this.graphic.element.nodeName !== 'g' :
  315. proceed.apply(this, args);
  316. }
  317. wrap(columnProto, 'pointAttribs', pointAttribs);
  318. wrap(columnProto, 'setState', setState);
  319. wrap(columnProto.pointClass.prototype, 'hasNewShapeType', hasNewShapeType);
  320. if (SeriesRegistry.seriesTypes.columnRange) {
  321. var columnRangeProto = SeriesRegistry.seriesTypes.columnrange.prototype;
  322. wrap(columnRangeProto, 'pointAttribs', pointAttribs);
  323. wrap(columnRangeProto, 'setState', setState);
  324. wrap(columnRangeProto.pointClass.prototype, 'hasNewShapeType', hasNewShapeType);
  325. columnRangeProto.plotGroup = columnProto.plotGroup;
  326. columnRangeProto.setVisible = columnProto.setVisible;
  327. }
  328. wrap(Series.prototype, 'alignDataLabel', function (proceed, point, dataLabel, options, alignTo) {
  329. var chart = this.chart;
  330. // In 3D we need to pass point.outsidePlot option to the justifyDataLabel
  331. // method for disabling justifying dataLabels in columns outside plot
  332. options.outside3dPlot = point.outside3dPlot;
  333. // Only do this for 3D columns and it's derived series
  334. if (chart.is3d() &&
  335. this.is('column')) {
  336. var series = this, seriesOptions = series.options, inside = pick(options.inside, !!series.options.stacking), options3d = chart.options.chart.options3d, xOffset = point.pointWidth / 2 || 0;
  337. var dLPosition = {
  338. x: alignTo.x + xOffset,
  339. y: alignTo.y,
  340. z: series.z + seriesOptions.depth / 2
  341. };
  342. if (chart.inverted) {
  343. // Inside dataLabels are positioned according to above
  344. // logic and there is no need to position them using
  345. // non-3D algorighm (that use alignTo.width)
  346. if (inside) {
  347. alignTo.width = 0;
  348. dLPosition.x += point.shapeArgs.height / 2;
  349. }
  350. // When chart is upside down
  351. // (alpha angle between 180 and 360 degrees)
  352. // it is needed to add column width to calculated value.
  353. if (options3d.alpha >= 90 && options3d.alpha <= 270) {
  354. dLPosition.y += point.shapeArgs.width;
  355. }
  356. }
  357. // dLPosition is recalculated for 3D graphs
  358. dLPosition = perspective([dLPosition], chart, true, false)[0];
  359. alignTo.x = dLPosition.x - xOffset;
  360. // #7103 If point is outside of plotArea, hide data label.
  361. alignTo.y = point.outside3dPlot ? -9e9 : dLPosition.y;
  362. }
  363. proceed.apply(this, [].slice.call(arguments, 1));
  364. });
  365. // Added stackLabels position calculation for 3D charts.
  366. wrap(StackItem.prototype, 'getStackBox', function (proceed, chart, stackItem, x, y, xWidth, h, axis) {
  367. var stackBox = proceed.apply(this, [].slice.call(arguments, 1));
  368. // Only do this for 3D graph
  369. if (chart.is3d() && stackItem.base) {
  370. // First element of stackItem.base is an index of base series.
  371. var baseSeriesInd = +(stackItem.base).split(',')[0];
  372. var columnSeries = chart.series[baseSeriesInd];
  373. var options3d = chart.options.chart.options3d;
  374. // Only do this if base series is a column or inherited type,
  375. // use its barW, z and depth parameters
  376. // for correct stackLabels position calculation
  377. if (columnSeries &&
  378. columnSeries instanceof SeriesRegistry.seriesTypes.column) {
  379. var dLPosition = {
  380. x: stackBox.x + (chart.inverted ? h : xWidth / 2),
  381. y: stackBox.y,
  382. z: columnSeries.options.depth / 2
  383. };
  384. if (chart.inverted) {
  385. // Do not use default offset calculation logic
  386. // for 3D inverted stackLabels.
  387. stackBox.width = 0;
  388. // When chart is upside down
  389. // (alpha angle between 180 and 360 degrees)
  390. // it is needed to add column width to calculated value.
  391. if (options3d.alpha >= 90 && options3d.alpha <= 270) {
  392. dLPosition.y += xWidth;
  393. }
  394. }
  395. dLPosition = perspective([dLPosition], chart, true, false)[0];
  396. stackBox.x = dLPosition.x - xWidth / 2;
  397. stackBox.y = dLPosition.y;
  398. }
  399. }
  400. return stackBox;
  401. });
  402. /*
  403. @merge v6.2
  404. @todo
  405. EXTENSION FOR 3D CYLINDRICAL COLUMNS
  406. Not supported
  407. */
  408. /*
  409. var defaultOptions = H.getOptions();
  410. defaultOptions.plotOptions.cylinder =
  411. merge(defaultOptions.plotOptions.column);
  412. var CylinderSeries = extendClass(seriesTypes.column, {
  413. type: 'cylinder'
  414. });
  415. seriesTypes.cylinder = CylinderSeries;
  416. wrap(seriesTypes.cylinder.prototype, 'translate', function (proceed) {
  417. proceed.apply(this, [].slice.call(arguments, 1));
  418. // Do not do this if the chart is not 3D
  419. if (!this.chart.is3d()) {
  420. return;
  421. }
  422. var series = this,
  423. chart = series.chart,
  424. options = chart.options,
  425. cylOptions = options.plotOptions.cylinder,
  426. options3d = options.chart.options3d,
  427. depth = cylOptions.depth || 0,
  428. alpha = chart.alpha3d;
  429. var z = cylOptions.stacking ?
  430. (this.options.stack || 0) * depth :
  431. series._i * depth;
  432. z += depth / 2;
  433. if (cylOptions.grouping !== false) { z = 0; }
  434. each(series.data, function (point) {
  435. var shapeArgs = point.shapeArgs,
  436. deg2rad = H.deg2rad;
  437. point.shapeType = 'arc3d';
  438. shapeArgs.x += depth / 2;
  439. shapeArgs.z = z;
  440. shapeArgs.start = 0;
  441. shapeArgs.end = 2 * PI;
  442. shapeArgs.r = depth * 0.95;
  443. shapeArgs.innerR = 0;
  444. shapeArgs.depth =
  445. shapeArgs.height * (1 / sin((90 - alpha) * deg2rad)) - z;
  446. shapeArgs.alpha = 90 - alpha;
  447. shapeArgs.beta = 0;
  448. });
  449. });
  450. */
  451. /* *
  452. *
  453. * Default Export
  454. *
  455. * */
  456. export default ColumnSeries;
  457. /* *
  458. *
  459. * API Options
  460. *
  461. * */
  462. /**
  463. * Depth of the columns in a 3D column chart.
  464. *
  465. * @type {number}
  466. * @default 25
  467. * @since 4.0
  468. * @product highcharts
  469. * @requires highcharts-3d
  470. * @apioption plotOptions.column.depth
  471. */
  472. /**
  473. * 3D columns only. The color of the edges. Similar to `borderColor`, except it
  474. * defaults to the same color as the column.
  475. *
  476. * @type {Highcharts.ColorString}
  477. * @product highcharts
  478. * @requires highcharts-3d
  479. * @apioption plotOptions.column.edgeColor
  480. */
  481. /**
  482. * 3D columns only. The width of the colored edges.
  483. *
  484. * @type {number}
  485. * @default 1
  486. * @product highcharts
  487. * @requires highcharts-3d
  488. * @apioption plotOptions.column.edgeWidth
  489. */
  490. /**
  491. * The spacing between columns on the Z Axis in a 3D chart.
  492. *
  493. * @type {number}
  494. * @default 1
  495. * @since 4.0
  496. * @product highcharts
  497. * @requires highcharts-3d
  498. * @apioption plotOptions.column.groupZPadding
  499. */
  500. ''; // keeps doclets above in transpiled file