Column.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437
  1. /* *
  2. * (c) 2010-2019 Torstein Honsi
  3. *
  4. * License: www.highcharts.com/license
  5. */
  6. 'use strict';
  7. import H from '../parts/Globals.js';
  8. import '../parts/Utilities.js';
  9. import '../parts/Series.js';
  10. var addEvent = H.addEvent,
  11. perspective = H.perspective,
  12. pick = H.pick,
  13. Series = H.Series,
  14. seriesTypes = H.seriesTypes,
  15. svg = H.svg,
  16. wrap = H.wrap;
  17. /**
  18. * Depth of the columns in a 3D column chart. Requires `highcharts-3d.js`.
  19. *
  20. * @type {number}
  21. * @default 25
  22. * @since 4.0
  23. * @product highcharts
  24. * @apioption plotOptions.column.depth
  25. */
  26. /**
  27. * 3D columns only. The color of the edges. Similar to `borderColor`,
  28. * except it defaults to the same color as the column.
  29. *
  30. * @type {Highcharts.ColorString}
  31. * @product highcharts
  32. * @apioption plotOptions.column.edgeColor
  33. */
  34. /**
  35. * 3D columns only. The width of the colored edges.
  36. *
  37. * @type {number}
  38. * @default 1
  39. * @product highcharts
  40. * @apioption plotOptions.column.edgeWidth
  41. */
  42. /**
  43. * The spacing between columns on the Z Axis in a 3D chart. Requires
  44. * `highcharts-3d.js`.
  45. *
  46. * @type {number}
  47. * @default 1
  48. * @since 4.0
  49. * @product highcharts
  50. * @apioption plotOptions.column.groupZPadding
  51. */
  52. wrap(seriesTypes.column.prototype, '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. // In 3D we need to pass point.outsidePlot option to the justifyDataLabel
  60. // method for disabling justifying dataLabels in columns outside plot
  61. wrap(H.Series.prototype, 'alignDataLabel', function (proceed) {
  62. arguments[3].outside3dPlot = arguments[1].outside3dPlot;
  63. proceed.apply(this, [].slice.call(arguments, 1));
  64. });
  65. // Don't use justifyDataLabel when point is outsidePlot
  66. wrap(H.Series.prototype, 'justifyDataLabel', function (proceed) {
  67. return !(arguments[2].outside3dPlot) ?
  68. proceed.apply(this, [].slice.call(arguments, 1)) :
  69. false;
  70. });
  71. seriesTypes.column.prototype.translate3dPoints = function () {};
  72. seriesTypes.column.prototype.translate3dShapes = function () {
  73. var series = this,
  74. chart = series.chart,
  75. seriesOptions = series.options,
  76. depth = seriesOptions.depth || 25,
  77. stack = seriesOptions.stacking ?
  78. (seriesOptions.stack || 0) :
  79. series.index, // #4743
  80. z = stack * (depth + (seriesOptions.groupZPadding || 1)),
  81. borderCrisp = series.borderWidth % 2 ? 0.5 : 0;
  82. if (chart.inverted && !series.yAxis.reversed) {
  83. borderCrisp *= -1;
  84. }
  85. if (seriesOptions.grouping !== false) {
  86. z = 0;
  87. }
  88. z += (seriesOptions.groupZPadding || 1);
  89. series.data.forEach(function (point) {
  90. // #7103 Reset outside3dPlot flag
  91. point.outside3dPlot = null;
  92. if (point.y !== null) {
  93. var shapeArgs = point.shapeArgs,
  94. tooltipPos = point.tooltipPos,
  95. // Array for final shapeArgs calculation.
  96. // We are checking two dimensions (x and y).
  97. dimensions = [['x', 'width'], ['y', 'height']],
  98. borderlessBase; // Crisped rects can have +/- 0.5 pixels offset.
  99. // #3131 We need to check if column is inside plotArea.
  100. dimensions.forEach(function (d) {
  101. borderlessBase = shapeArgs[d[0]] - borderCrisp;
  102. if (borderlessBase < 0) {
  103. // If borderLessBase is smaller than 0, it is needed to set
  104. // its value to 0 or 0.5 depending on borderWidth
  105. // borderWidth may be even or odd.
  106. shapeArgs[d[1]] += shapeArgs[d[0]] + borderCrisp;
  107. shapeArgs[d[0]] = -borderCrisp;
  108. borderlessBase = 0;
  109. }
  110. if (
  111. (
  112. borderlessBase + shapeArgs[d[1]] >
  113. series[d[0] + 'Axis'].len
  114. ) &&
  115. // Do not change height/width of column if 0 (#6708)
  116. shapeArgs[d[1]] !== 0
  117. ) {
  118. shapeArgs[d[1]] =
  119. series[d[0] + 'Axis'].len - shapeArgs[d[0]];
  120. }
  121. if (
  122. // Do not remove columns with zero height/width.
  123. (shapeArgs[d[1]] !== 0) &&
  124. (
  125. shapeArgs[d[0]] >= series[d[0] + 'Axis'].len ||
  126. shapeArgs[d[0]] + shapeArgs[d[1]] <= borderCrisp
  127. )
  128. ) {
  129. // Set args to 0 if column is outside the chart.
  130. for (var key in shapeArgs) {
  131. shapeArgs[key] = 0;
  132. }
  133. // #7103 outside3dPlot flag is set on Points which are
  134. // currently outside of plot.
  135. point.outside3dPlot = true;
  136. }
  137. });
  138. // Change from 2d to 3d
  139. if (point.shapeType === 'rect') {
  140. point.shapeType = 'cuboid';
  141. }
  142. shapeArgs.z = z;
  143. shapeArgs.depth = depth;
  144. shapeArgs.insidePlotArea = true;
  145. // Translate the tooltip position in 3d space
  146. tooltipPos = perspective(
  147. [{ x: tooltipPos[0], y: tooltipPos[1], z: z }],
  148. chart,
  149. true
  150. )[0];
  151. point.tooltipPos = [tooltipPos.x, tooltipPos.y];
  152. }
  153. });
  154. // store for later use #4067
  155. series.z = z;
  156. };
  157. wrap(seriesTypes.column.prototype, 'animate', function (proceed) {
  158. if (!this.chart.is3d()) {
  159. proceed.apply(this, [].slice.call(arguments, 1));
  160. } else {
  161. var args = arguments,
  162. init = args[1],
  163. yAxis = this.yAxis,
  164. series = this,
  165. reversed = this.yAxis.reversed;
  166. if (svg) { // VML is too slow anyway
  167. if (init) {
  168. series.data.forEach(function (point) {
  169. if (point.y !== null) {
  170. point.height = point.shapeArgs.height;
  171. point.shapey = point.shapeArgs.y; // #2968
  172. point.shapeArgs.height = 1;
  173. if (!reversed) {
  174. if (point.stackY) {
  175. point.shapeArgs.y =
  176. point.plotY + yAxis.translate(point.stackY);
  177. } else {
  178. point.shapeArgs.y =
  179. point.plotY +
  180. (
  181. point.negative ?
  182. -point.height :
  183. point.height
  184. );
  185. }
  186. }
  187. }
  188. });
  189. } else { // run the animation
  190. series.data.forEach(function (point) {
  191. if (point.y !== null) {
  192. point.shapeArgs.height = point.height;
  193. point.shapeArgs.y = point.shapey; // #2968
  194. // null value do not have a graphic
  195. if (point.graphic) {
  196. point.graphic.animate(
  197. point.shapeArgs,
  198. series.options.animation
  199. );
  200. }
  201. }
  202. });
  203. // redraw datalabels to the correct position
  204. this.drawDataLabels();
  205. // delete this function to allow it only once
  206. series.animate = null;
  207. }
  208. }
  209. }
  210. });
  211. // In case of 3d columns there is no sense to add this columns to a specific
  212. // series group - if series is added to a group all columns will have the same
  213. // zIndex in comparison with different series.
  214. wrap(
  215. seriesTypes.column.prototype,
  216. 'plotGroup',
  217. function (proceed, prop, name, visibility, zIndex, parent) {
  218. if (this.chart.is3d() && parent && !this[prop]) {
  219. if (!this.chart.columnGroup) {
  220. this.chart.columnGroup =
  221. this.chart.renderer.g('columnGroup').add(parent);
  222. }
  223. this[prop] = this.chart.columnGroup;
  224. this.chart.columnGroup.attr(this.getPlotBox());
  225. this[prop].survive = true;
  226. }
  227. return proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  228. }
  229. );
  230. // When series is not added to group it is needed to change setVisible method to
  231. // allow correct Legend funcionality. This wrap is basing on pie chart series.
  232. wrap(
  233. seriesTypes.column.prototype,
  234. 'setVisible',
  235. function (proceed, vis) {
  236. var series = this,
  237. pointVis;
  238. if (series.chart.is3d()) {
  239. series.data.forEach(function (point) {
  240. point.visible = point.options.visible = vis =
  241. vis === undefined ? !point.visible : vis;
  242. pointVis = vis ? 'visible' : 'hidden';
  243. series.options.data[series.data.indexOf(point)] =
  244. point.options;
  245. if (point.graphic) {
  246. point.graphic.attr({
  247. visibility: pointVis
  248. });
  249. }
  250. });
  251. }
  252. proceed.apply(this, Array.prototype.slice.call(arguments, 1));
  253. }
  254. );
  255. seriesTypes.column.prototype.handle3dGrouping = true;
  256. addEvent(Series, 'afterInit', function () {
  257. if (this.chart.is3d() && this.handle3dGrouping) {
  258. var seriesOptions = this.options,
  259. grouping = seriesOptions.grouping,
  260. stacking = seriesOptions.stacking,
  261. reversedStacks = pick(this.yAxis.options.reversedStacks, true),
  262. z = 0;
  263. if (!(grouping !== undefined && !grouping)) {
  264. var stacks = this.chart.retrieveStacks(stacking),
  265. stack = seriesOptions.stack || 0,
  266. i; // position within the stack
  267. for (i = 0; i < stacks[stack].series.length; i++) {
  268. if (stacks[stack].series[i] === this) {
  269. break;
  270. }
  271. }
  272. z = (10 * (stacks.totalStacks - stacks[stack].position)) +
  273. (reversedStacks ? i : -i); // #4369
  274. // In case when axis is reversed, columns are also reversed inside
  275. // the group (#3737)
  276. if (!this.xAxis.reversed) {
  277. z = (stacks.totalStacks * 10) - z;
  278. }
  279. }
  280. seriesOptions.zIndex = z;
  281. }
  282. });
  283. function pointAttribs(proceed) {
  284. var attr = proceed.apply(this, [].slice.call(arguments, 1));
  285. if (this.chart.is3d && this.chart.is3d()) {
  286. // Set the fill color to the fill color to provide a smooth edge
  287. attr.stroke = this.options.edgeColor || attr.fill;
  288. attr['stroke-width'] = pick(this.options.edgeWidth, 1); // #4055
  289. }
  290. return attr;
  291. }
  292. wrap(seriesTypes.column.prototype, 'pointAttribs', pointAttribs);
  293. if (seriesTypes.columnrange) {
  294. wrap(seriesTypes.columnrange.prototype, 'pointAttribs', pointAttribs);
  295. seriesTypes.columnrange.prototype.plotGroup =
  296. seriesTypes.column.prototype.plotGroup;
  297. seriesTypes.columnrange.prototype.setVisible =
  298. seriesTypes.column.prototype.setVisible;
  299. }
  300. wrap(Series.prototype, 'alignDataLabel', function (proceed) {
  301. // Only do this for 3D columns and it's derived series
  302. if (
  303. this.chart.is3d() &&
  304. this instanceof seriesTypes.column
  305. ) {
  306. var series = this,
  307. chart = series.chart;
  308. var args = arguments,
  309. alignTo = args[4],
  310. point = args[1];
  311. var pos = ({ x: alignTo.x, y: alignTo.y, z: series.z });
  312. pos = perspective([pos], chart, true)[0];
  313. alignTo.x = pos.x;
  314. // #7103 If point is outside of plotArea, hide data label.
  315. alignTo.y = point.outside3dPlot ? -9e9 : pos.y;
  316. }
  317. proceed.apply(this, [].slice.call(arguments, 1));
  318. });
  319. // Added stackLabels position calculation for 3D charts.
  320. wrap(H.StackItem.prototype, 'getStackBox', function (proceed, chart) { // #3946
  321. var stackBox = proceed.apply(this, [].slice.call(arguments, 1));
  322. // Only do this for 3D chart.
  323. if (chart.is3d()) {
  324. var pos = ({
  325. x: stackBox.x,
  326. y: stackBox.y,
  327. z: 0
  328. });
  329. pos = H.perspective([pos], chart, true)[0];
  330. stackBox.x = pos.x;
  331. stackBox.y = pos.y;
  332. }
  333. return stackBox;
  334. });
  335. /*
  336. @merge v6.2
  337. @todo
  338. EXTENSION FOR 3D CYLINDRICAL COLUMNS
  339. Not supported
  340. */
  341. /*
  342. var defaultOptions = H.getOptions();
  343. defaultOptions.plotOptions.cylinder =
  344. H.merge(defaultOptions.plotOptions.column);
  345. var CylinderSeries = H.extendClass(seriesTypes.column, {
  346. type: 'cylinder'
  347. });
  348. seriesTypes.cylinder = CylinderSeries;
  349. wrap(seriesTypes.cylinder.prototype, 'translate', function (proceed) {
  350. proceed.apply(this, [].slice.call(arguments, 1));
  351. // Do not do this if the chart is not 3D
  352. if (!this.chart.is3d()) {
  353. return;
  354. }
  355. var series = this,
  356. chart = series.chart,
  357. options = chart.options,
  358. cylOptions = options.plotOptions.cylinder,
  359. options3d = options.chart.options3d,
  360. depth = cylOptions.depth || 0,
  361. alpha = chart.alpha3d;
  362. var z = cylOptions.stacking ?
  363. (this.options.stack || 0) * depth :
  364. series._i * depth;
  365. z += depth / 2;
  366. if (cylOptions.grouping !== false) { z = 0; }
  367. each(series.data, function (point) {
  368. var shapeArgs = point.shapeArgs,
  369. deg2rad = H.deg2rad;
  370. point.shapeType = 'arc3d';
  371. shapeArgs.x += depth / 2;
  372. shapeArgs.z = z;
  373. shapeArgs.start = 0;
  374. shapeArgs.end = 2 * PI;
  375. shapeArgs.r = depth * 0.95;
  376. shapeArgs.innerR = 0;
  377. shapeArgs.depth =
  378. shapeArgs.height * (1 / sin((90 - alpha) * deg2rad)) - z;
  379. shapeArgs.alpha = 90 - alpha;
  380. shapeArgs.beta = 0;
  381. });
  382. });
  383. */