WaterfallSeries.js 31 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811
  1. /* *
  2. *
  3. * (c) 2010-2020 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 Axis from '../Core/Axis/Axis.js';
  12. import Chart from '../Core/Chart/Chart.js';
  13. import H from '../Core/Globals.js';
  14. import Point from '../Core/Series/Point.js';
  15. import StackItem from '../Extensions/Stacking.js';
  16. import U from '../Core/Utilities.js';
  17. var addEvent = U.addEvent, arrayMax = U.arrayMax, arrayMin = U.arrayMin, correctFloat = U.correctFloat, isNumber = U.isNumber, objectEach = U.objectEach, pick = U.pick, seriesType = U.seriesType;
  18. import '../Core/Options.js';
  19. import '../Core/Series/Series.js';
  20. var Series = H.Series, seriesTypes = H.seriesTypes;
  21. /**
  22. * Returns true if the key is a direct property of the object.
  23. * @private
  24. * @param {*} obj - Object with property to test
  25. * @param {string} key - Property key to test
  26. * @return {boolean} - Whether it is a direct property
  27. */
  28. function ownProp(obj, key) {
  29. return Object.hasOwnProperty.call(obj, key);
  30. }
  31. /**
  32. * @private
  33. */
  34. var WaterfallAxis;
  35. (function (WaterfallAxis) {
  36. /* *
  37. *
  38. * Interfaces
  39. *
  40. * */
  41. /* *
  42. *
  43. * Classes
  44. *
  45. * */
  46. /**
  47. * @private
  48. */
  49. var Composition = /** @class */ (function () {
  50. /* *
  51. *
  52. * Constructors
  53. *
  54. * */
  55. /**
  56. * @private
  57. */
  58. function Composition(axis) {
  59. this.axis = axis;
  60. this.stacks = {
  61. changed: false
  62. };
  63. }
  64. /* *
  65. *
  66. * Functions
  67. *
  68. * */
  69. /**
  70. * Calls StackItem.prototype.render function that creates and renders
  71. * stack total label for each waterfall stack item.
  72. *
  73. * @private
  74. * @function Highcharts.Axis#renderWaterfallStackTotals
  75. */
  76. Composition.prototype.renderStackTotals = function () {
  77. var yAxis = this.axis, waterfallStacks = yAxis.waterfall.stacks, stackTotalGroup = yAxis.stacking && yAxis.stacking.stackTotalGroup, dummyStackItem = new StackItem(yAxis, yAxis.options.stackLabels, false, 0, void 0);
  78. this.dummyStackItem = dummyStackItem;
  79. // Render each waterfall stack total
  80. objectEach(waterfallStacks, function (type) {
  81. objectEach(type, function (stackItem) {
  82. dummyStackItem.total = stackItem.stackTotal;
  83. if (stackItem.label) {
  84. dummyStackItem.label = stackItem.label;
  85. }
  86. StackItem.prototype.render.call(dummyStackItem, stackTotalGroup);
  87. stackItem.label = dummyStackItem.label;
  88. delete dummyStackItem.label;
  89. });
  90. });
  91. dummyStackItem.total = null;
  92. };
  93. return Composition;
  94. }());
  95. WaterfallAxis.Composition = Composition;
  96. /* *
  97. *
  98. * Functions
  99. *
  100. * */
  101. /**
  102. * @private
  103. */
  104. function compose(AxisClass, ChartClass) {
  105. addEvent(AxisClass, 'init', onInit);
  106. addEvent(AxisClass, 'afterBuildStacks', onAfterBuildStacks);
  107. addEvent(AxisClass, 'afterRender', onAfterRender);
  108. addEvent(ChartClass, 'beforeRedraw', onBeforeRedraw);
  109. }
  110. WaterfallAxis.compose = compose;
  111. /**
  112. * @private
  113. */
  114. function onAfterBuildStacks() {
  115. var axis = this;
  116. var stacks = axis.waterfall.stacks;
  117. if (stacks) {
  118. stacks.changed = false;
  119. delete stacks.alreadyChanged;
  120. }
  121. }
  122. /**
  123. * @private
  124. */
  125. function onAfterRender() {
  126. var axis = this;
  127. var stackLabelOptions = axis.options.stackLabels;
  128. if (stackLabelOptions && stackLabelOptions.enabled &&
  129. axis.waterfall.stacks) {
  130. axis.waterfall.renderStackTotals();
  131. }
  132. }
  133. /**
  134. * @private
  135. */
  136. function onBeforeRedraw() {
  137. var axes = this.axes, series = this.series, i = series.length;
  138. while (i--) {
  139. if (series[i].options.stacking) {
  140. axes.forEach(function (axis) {
  141. if (!axis.isXAxis) {
  142. axis.waterfall.stacks.changed = true;
  143. }
  144. });
  145. i = 0;
  146. }
  147. }
  148. }
  149. /**
  150. * @private
  151. */
  152. function onInit() {
  153. var axis = this;
  154. if (!axis.waterfall) {
  155. axis.waterfall = new Composition(axis);
  156. }
  157. }
  158. })(WaterfallAxis || (WaterfallAxis = {}));
  159. // eslint-disable-next-line valid-jsdoc
  160. /**
  161. * A waterfall chart displays sequentially introduced positive or negative
  162. * values in cumulative columns.
  163. *
  164. * @sample highcharts/demo/waterfall/
  165. * Waterfall chart
  166. * @sample highcharts/plotoptions/waterfall-inverted/
  167. * Horizontal (inverted) waterfall
  168. * @sample highcharts/plotoptions/waterfall-stacked/
  169. * Stacked waterfall chart
  170. *
  171. * @extends plotOptions.column
  172. * @excluding boostThreshold, boostBlending
  173. * @product highcharts
  174. * @requires highcharts-more
  175. * @optionparent plotOptions.waterfall
  176. */
  177. seriesType('waterfall', 'column', {
  178. /**
  179. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  180. * @apioption plotOptions.waterfall.color
  181. */
  182. /**
  183. * The color used specifically for positive point columns. When not
  184. * specified, the general series color is used.
  185. *
  186. * In styled mode, the waterfall colors can be set with the
  187. * `.highcharts-point-negative`, `.highcharts-sum` and
  188. * `.highcharts-intermediate-sum` classes.
  189. *
  190. * @sample {highcharts} highcharts/demo/waterfall/
  191. * Waterfall
  192. *
  193. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  194. * @product highcharts
  195. * @apioption plotOptions.waterfall.upColor
  196. */
  197. dataLabels: {
  198. inside: true
  199. },
  200. /**
  201. * The width of the line connecting waterfall columns.
  202. *
  203. * @product highcharts
  204. */
  205. lineWidth: 1,
  206. /**
  207. * The color of the line that connects columns in a waterfall series.
  208. *
  209. * In styled mode, the stroke can be set with the `.highcharts-graph` class.
  210. *
  211. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  212. * @since 3.0
  213. * @product highcharts
  214. */
  215. lineColor: '#333333',
  216. /**
  217. * A name for the dash style to use for the line connecting the columns
  218. * of the waterfall series. Possible values: Dash, DashDot, Dot, LongDash,
  219. * LongDashDot, LongDashDotDot, ShortDash, ShortDashDot, ShortDashDotDot,
  220. * ShortDot, Solid
  221. *
  222. * In styled mode, the stroke dash-array can be set with the
  223. * `.highcharts-graph` class.
  224. *
  225. * @type {Highcharts.DashStyleValue}
  226. * @since 3.0
  227. * @product highcharts
  228. */
  229. dashStyle: 'Dot',
  230. /**
  231. * The color of the border of each waterfall column.
  232. *
  233. * In styled mode, the border stroke can be set with the
  234. * `.highcharts-point` class.
  235. *
  236. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  237. * @since 3.0
  238. * @product highcharts
  239. */
  240. borderColor: '#333333',
  241. states: {
  242. hover: {
  243. lineWidthPlus: 0 // #3126
  244. }
  245. }
  246. // Prototype members
  247. }, {
  248. pointValKey: 'y',
  249. // Property needed to prevent lines between the columns from disappearing
  250. // when negativeColor is used.
  251. showLine: true,
  252. // After generating points, set y-values for all sums.
  253. generatePoints: function () {
  254. var point, len, i, y;
  255. // Parent call:
  256. seriesTypes.column.prototype.generatePoints.apply(this);
  257. for (i = 0, len = this.points.length; i < len; i++) {
  258. point = this.points[i];
  259. y = this.processedYData[i];
  260. // override point value for sums
  261. // #3710 Update point does not propagate to sum
  262. if (point.isIntermediateSum || point.isSum) {
  263. point.y = correctFloat(y);
  264. }
  265. }
  266. },
  267. // Translate data points from raw values
  268. translate: function () {
  269. var series = this, options = series.options, yAxis = series.yAxis, len, i, points, point, shapeArgs, y, yValue, previousY, previousIntermediate, range, minPointLength = pick(options.minPointLength, 5), halfMinPointLength = minPointLength / 2, threshold = options.threshold, stacking = options.stacking, tooltipY, actualStack = yAxis.waterfall.stacks[series.stackKey], actualStackX, dummyStackItem, total, pointY, yPos, hPos;
  270. // run column series translate
  271. seriesTypes.column.prototype.translate.apply(series);
  272. previousY = previousIntermediate = threshold;
  273. points = series.points;
  274. for (i = 0, len = points.length; i < len; i++) {
  275. // cache current point object
  276. point = points[i];
  277. yValue = series.processedYData[i];
  278. shapeArgs = point.shapeArgs;
  279. range = [0, yValue];
  280. pointY = point.y;
  281. // code responsible for correct positions of stacked points
  282. // starts here
  283. if (stacking) {
  284. if (actualStack) {
  285. actualStackX = actualStack[i];
  286. if (stacking === 'overlap') {
  287. total =
  288. actualStackX.stackState[actualStackX.stateIndex--];
  289. y = pointY >= 0 ? total : total - pointY;
  290. if (ownProp(actualStackX, 'absolutePos')) {
  291. delete actualStackX.absolutePos;
  292. }
  293. if (ownProp(actualStackX, 'absoluteNeg')) {
  294. delete actualStackX.absoluteNeg;
  295. }
  296. }
  297. else {
  298. if (pointY >= 0) {
  299. total = actualStackX.threshold +
  300. actualStackX.posTotal;
  301. actualStackX.posTotal -= pointY;
  302. y = total;
  303. }
  304. else {
  305. total = actualStackX.threshold +
  306. actualStackX.negTotal;
  307. actualStackX.negTotal -= pointY;
  308. y = total - pointY;
  309. }
  310. if (!actualStackX.posTotal) {
  311. if (ownProp(actualStackX, 'absolutePos')) {
  312. actualStackX.posTotal =
  313. actualStackX.absolutePos;
  314. delete actualStackX.absolutePos;
  315. }
  316. }
  317. if (!actualStackX.negTotal) {
  318. if (ownProp(actualStackX, 'absoluteNeg')) {
  319. actualStackX.negTotal =
  320. actualStackX.absoluteNeg;
  321. delete actualStackX.absoluteNeg;
  322. }
  323. }
  324. }
  325. if (!point.isSum) {
  326. // the connectorThreshold property is later used in
  327. // getCrispPath function to draw a connector line in a
  328. // correct place
  329. actualStackX.connectorThreshold =
  330. actualStackX.threshold + actualStackX.stackTotal;
  331. }
  332. if (yAxis.reversed) {
  333. yPos = (pointY >= 0) ? (y - pointY) : (y + pointY);
  334. hPos = y;
  335. }
  336. else {
  337. yPos = y;
  338. hPos = y - pointY;
  339. }
  340. point.below = yPos <= pick(threshold, 0);
  341. shapeArgs.y = yAxis.translate(yPos, 0, 1, 0, 1);
  342. shapeArgs.height = Math.abs(shapeArgs.y -
  343. yAxis.translate(hPos, 0, 1, 0, 1));
  344. }
  345. dummyStackItem = yAxis.waterfall.dummyStackItem;
  346. if (dummyStackItem) {
  347. dummyStackItem.x = i;
  348. dummyStackItem.label = actualStack[i].label;
  349. dummyStackItem.setOffset(series.pointXOffset || 0, series.barW || 0, series.stackedYNeg[i], series.stackedYPos[i]);
  350. }
  351. }
  352. else {
  353. // up points
  354. y =
  355. Math.max(previousY, previousY + pointY) + range[0];
  356. shapeArgs.y =
  357. yAxis.translate(y, 0, 1, 0, 1);
  358. // sum points
  359. if (point.isSum) {
  360. shapeArgs.y = yAxis.translate(range[1], 0, 1, 0, 1);
  361. shapeArgs.height = Math.min(yAxis.translate(range[0], 0, 1, 0, 1), yAxis.len) - shapeArgs.y; // #4256
  362. }
  363. else if (point.isIntermediateSum) {
  364. if (pointY >= 0) {
  365. yPos = range[1] + previousIntermediate;
  366. hPos = previousIntermediate;
  367. }
  368. else {
  369. yPos = previousIntermediate;
  370. hPos = range[1] + previousIntermediate;
  371. }
  372. if (yAxis.reversed) {
  373. // swapping values
  374. yPos ^= hPos;
  375. hPos ^= yPos;
  376. yPos ^= hPos;
  377. }
  378. shapeArgs.y = yAxis.translate(yPos, 0, 1, 0, 1);
  379. shapeArgs.height = Math.abs(shapeArgs.y -
  380. Math.min(yAxis.translate(hPos, 0, 1, 0, 1), yAxis.len));
  381. previousIntermediate += range[1];
  382. // If it's not the sum point, update previous stack end position
  383. // and get shape height (#3886)
  384. }
  385. else {
  386. shapeArgs.height = yValue > 0 ?
  387. yAxis.translate(previousY, 0, 1, 0, 1) - shapeArgs.y :
  388. yAxis.translate(previousY, 0, 1, 0, 1) - yAxis.translate(previousY - yValue, 0, 1, 0, 1);
  389. previousY += yValue;
  390. point.below = previousY < pick(threshold, 0);
  391. }
  392. // #3952 Negative sum or intermediate sum not rendered correctly
  393. if (shapeArgs.height < 0) {
  394. shapeArgs.y += shapeArgs.height;
  395. shapeArgs.height *= -1;
  396. }
  397. }
  398. point.plotY = shapeArgs.y =
  399. Math.round(shapeArgs.y) - (series.borderWidth % 2) / 2;
  400. // #3151
  401. shapeArgs.height =
  402. Math.max(Math.round(shapeArgs.height), 0.001);
  403. point.yBottom = shapeArgs.y + shapeArgs.height;
  404. if (shapeArgs.height <= minPointLength && !point.isNull) {
  405. shapeArgs.height = minPointLength;
  406. shapeArgs.y -= halfMinPointLength;
  407. point.plotY = shapeArgs.y;
  408. if (point.y < 0) {
  409. point.minPointLengthOffset = -halfMinPointLength;
  410. }
  411. else {
  412. point.minPointLengthOffset = halfMinPointLength;
  413. }
  414. }
  415. else {
  416. if (point.isNull) {
  417. shapeArgs.width = 0;
  418. }
  419. point.minPointLengthOffset = 0;
  420. }
  421. // Correct tooltip placement (#3014)
  422. tooltipY =
  423. point.plotY + (point.negative ? shapeArgs.height : 0);
  424. if (series.chart.inverted) {
  425. point.tooltipPos[0] = yAxis.len - tooltipY;
  426. }
  427. else {
  428. point.tooltipPos[1] = tooltipY;
  429. }
  430. }
  431. },
  432. // Call default processData then override yData to reflect waterfall's
  433. // extremes on yAxis
  434. processData: function (force) {
  435. var series = this, options = series.options, yData = series.yData,
  436. // #3710 Update point does not propagate to sum
  437. points = options.data, point, dataLength = yData.length, threshold = options.threshold || 0, subSum, sum, dataMin, dataMax, y, i;
  438. sum = subSum = dataMin = dataMax = 0;
  439. for (i = 0; i < dataLength; i++) {
  440. y = yData[i];
  441. point = points && points[i] ? points[i] : {};
  442. if (y === 'sum' || point.isSum) {
  443. yData[i] = correctFloat(sum);
  444. }
  445. else if (y === 'intermediateSum' ||
  446. point.isIntermediateSum) {
  447. yData[i] = correctFloat(subSum);
  448. subSum = 0;
  449. }
  450. else {
  451. sum += y;
  452. subSum += y;
  453. }
  454. dataMin = Math.min(sum, dataMin);
  455. dataMax = Math.max(sum, dataMax);
  456. }
  457. Series.prototype.processData.call(this, force);
  458. // Record extremes only if stacking was not set:
  459. if (!options.stacking) {
  460. series.dataMin = dataMin + threshold;
  461. series.dataMax = dataMax;
  462. }
  463. return;
  464. },
  465. // Return y value or string if point is sum
  466. toYData: function (pt) {
  467. if (pt.isSum) {
  468. return 'sum';
  469. }
  470. if (pt.isIntermediateSum) {
  471. return 'intermediateSum';
  472. }
  473. return pt.y;
  474. },
  475. updateParallelArrays: function (point, i) {
  476. Series.prototype.updateParallelArrays.call(this, point, i);
  477. // Prevent initial sums from triggering an error (#3245, #7559)
  478. if (this.yData[0] === 'sum' || this.yData[0] === 'intermediateSum') {
  479. this.yData[0] = null;
  480. }
  481. },
  482. // Postprocess mapping between options and SVG attributes
  483. pointAttribs: function (point, state) {
  484. var upColor = this.options.upColor, attr;
  485. // Set or reset up color (#3710, update to negative)
  486. if (upColor && !point.options.color) {
  487. point.color = point.y > 0 ? upColor : null;
  488. }
  489. attr = seriesTypes.column.prototype.pointAttribs.call(this, point, state);
  490. // The dashStyle option in waterfall applies to the graph, not
  491. // the points
  492. delete attr.dashstyle;
  493. return attr;
  494. },
  495. // Return an empty path initially, because we need to know the stroke-width
  496. // in order to set the final path.
  497. getGraphPath: function () {
  498. return [['M', 0, 0]];
  499. },
  500. // Draw columns' connector lines
  501. getCrispPath: function () {
  502. var data = this.data, yAxis = this.yAxis, length = data.length, graphNormalizer = Math.round(this.graph.strokeWidth()) % 2 / 2, borderNormalizer = Math.round(this.borderWidth) % 2 / 2, reversedXAxis = this.xAxis.reversed, reversedYAxis = this.yAxis.reversed, stacking = this.options.stacking, path = [], connectorThreshold, prevStack, prevStackX, prevPoint, yPos, isPos, prevArgs, pointArgs, i;
  503. for (i = 1; i < length; i++) {
  504. pointArgs = data[i].shapeArgs;
  505. prevPoint = data[i - 1];
  506. prevArgs = data[i - 1].shapeArgs;
  507. prevStack = yAxis.waterfall.stacks[this.stackKey];
  508. isPos = prevPoint.y > 0 ? -prevArgs.height : 0;
  509. if (prevStack && prevArgs && pointArgs) {
  510. prevStackX = prevStack[i - 1];
  511. // y position of the connector is different when series are
  512. // stacked, yAxis is reversed and it also depends on point's
  513. // value
  514. if (stacking) {
  515. connectorThreshold = prevStackX.connectorThreshold;
  516. yPos = Math.round((yAxis.translate(connectorThreshold, 0, 1, 0, 1) +
  517. (reversedYAxis ? isPos : 0))) - graphNormalizer;
  518. }
  519. else {
  520. yPos =
  521. prevArgs.y + prevPoint.minPointLengthOffset +
  522. borderNormalizer - graphNormalizer;
  523. }
  524. path.push([
  525. 'M',
  526. (prevArgs.x || 0) + (reversedXAxis ?
  527. 0 :
  528. (prevArgs.width || 0)),
  529. yPos
  530. ], [
  531. 'L',
  532. (pointArgs.x || 0) + (reversedXAxis ?
  533. (pointArgs.width || 0) :
  534. 0),
  535. yPos
  536. ]);
  537. }
  538. if (!stacking &&
  539. path.length &&
  540. prevArgs &&
  541. ((prevPoint.y < 0 && !reversedYAxis) ||
  542. (prevPoint.y > 0 && reversedYAxis))) {
  543. path[path.length - 2][2] += prevArgs.height;
  544. path[path.length - 1][2] += prevArgs.height;
  545. }
  546. }
  547. return path;
  548. },
  549. // The graph is initially drawn with an empty definition, then updated with
  550. // crisp rendering.
  551. drawGraph: function () {
  552. Series.prototype.drawGraph.call(this);
  553. this.graph.attr({
  554. d: this.getCrispPath()
  555. });
  556. },
  557. // Waterfall has stacking along the x-values too.
  558. setStackedPoints: function () {
  559. var series = this, options = series.options, waterfallStacks = series.yAxis.waterfall.stacks, seriesThreshold = options.threshold, stackThreshold = seriesThreshold || 0, interSum = stackThreshold, stackKey = series.stackKey, xData = series.xData, xLength = xData.length, actualStack, actualStackX, totalYVal, actualSum, prevSum, statesLen, posTotal, negTotal, xPoint, yVal, x, alreadyChanged, changed;
  560. // function responsible for calculating correct values for stackState
  561. // array of each stack item. The arguments are: firstS - the value for
  562. // the first state, nextS - the difference between the previous and the
  563. // newest state, sInx - counter used in the for that updates each state
  564. // when necessary, sOff - offset that must be added to each state when
  565. // they need to be updated (if point isn't a total sum)
  566. // eslint-disable-next-line require-jsdoc
  567. function calculateStackState(firstS, nextS, sInx, sOff) {
  568. if (!statesLen) {
  569. actualStackX.stackState[0] = firstS;
  570. statesLen = actualStackX.stackState.length;
  571. }
  572. else {
  573. for (sInx; sInx < statesLen; sInx++) {
  574. actualStackX.stackState[sInx] += sOff;
  575. }
  576. }
  577. actualStackX.stackState.push(actualStackX.stackState[statesLen - 1] + nextS);
  578. }
  579. series.yAxis.stacking.usePercentage = false;
  580. totalYVal = actualSum = prevSum = stackThreshold;
  581. // code responsible for creating stacks for waterfall series
  582. if (series.visible ||
  583. !series.chart.options.chart.ignoreHiddenSeries) {
  584. changed = waterfallStacks.changed;
  585. alreadyChanged = waterfallStacks.alreadyChanged;
  586. // in case of a redraw, stack for each x value must be
  587. // emptied (only for the first series in a specific stack)
  588. // and recalculated once more
  589. if (alreadyChanged &&
  590. alreadyChanged.indexOf(stackKey) < 0) {
  591. changed = true;
  592. }
  593. if (!waterfallStacks[stackKey]) {
  594. waterfallStacks[stackKey] = {};
  595. }
  596. actualStack = waterfallStacks[stackKey];
  597. for (var i = 0; i < xLength; i++) {
  598. x = xData[i];
  599. if (!actualStack[x] || changed) {
  600. actualStack[x] = {
  601. negTotal: 0,
  602. posTotal: 0,
  603. stackTotal: 0,
  604. threshold: 0,
  605. stateIndex: 0,
  606. stackState: [],
  607. label: ((changed &&
  608. actualStack[x]) ?
  609. actualStack[x].label :
  610. void 0)
  611. };
  612. }
  613. actualStackX = actualStack[x];
  614. yVal = series.yData[i];
  615. if (yVal >= 0) {
  616. actualStackX.posTotal += yVal;
  617. }
  618. else {
  619. actualStackX.negTotal += yVal;
  620. }
  621. // points do not exist yet, so raw data is used
  622. xPoint = options.data[i];
  623. posTotal = actualStackX.absolutePos =
  624. actualStackX.posTotal;
  625. negTotal = actualStackX.absoluteNeg =
  626. actualStackX.negTotal;
  627. actualStackX.stackTotal = posTotal + negTotal;
  628. statesLen = actualStackX.stackState.length;
  629. if (xPoint && xPoint.isIntermediateSum) {
  630. calculateStackState(prevSum, actualSum, 0, prevSum);
  631. prevSum = actualSum;
  632. actualSum = seriesThreshold;
  633. // swapping values
  634. stackThreshold ^= interSum;
  635. interSum ^= stackThreshold;
  636. stackThreshold ^= interSum;
  637. }
  638. else if (xPoint && xPoint.isSum) {
  639. calculateStackState(seriesThreshold, totalYVal, statesLen);
  640. stackThreshold = seriesThreshold;
  641. }
  642. else {
  643. calculateStackState(stackThreshold, yVal, 0, totalYVal);
  644. if (xPoint) {
  645. totalYVal += yVal;
  646. actualSum += yVal;
  647. }
  648. }
  649. actualStackX.stateIndex++;
  650. actualStackX.threshold = stackThreshold;
  651. stackThreshold += actualStackX.stackTotal;
  652. }
  653. waterfallStacks.changed = false;
  654. if (!waterfallStacks.alreadyChanged) {
  655. waterfallStacks.alreadyChanged = [];
  656. }
  657. waterfallStacks.alreadyChanged.push(stackKey);
  658. }
  659. },
  660. // Extremes for a non-stacked series are recorded in processData.
  661. // In case of stacking, use Series.stackedYData to calculate extremes.
  662. getExtremes: function () {
  663. var stacking = this.options.stacking, yAxis, waterfallStacks, stackedYNeg, stackedYPos;
  664. if (stacking) {
  665. yAxis = this.yAxis;
  666. waterfallStacks = yAxis.waterfall.stacks;
  667. stackedYNeg = this.stackedYNeg = [];
  668. stackedYPos = this.stackedYPos = [];
  669. // the visible y range can be different when stacking is set to
  670. // overlap and different when it's set to normal
  671. if (stacking === 'overlap') {
  672. objectEach(waterfallStacks[this.stackKey], function (stackX) {
  673. stackedYNeg.push(arrayMin(stackX.stackState));
  674. stackedYPos.push(arrayMax(stackX.stackState));
  675. });
  676. }
  677. else {
  678. objectEach(waterfallStacks[this.stackKey], function (stackX) {
  679. stackedYNeg.push(stackX.negTotal + stackX.threshold);
  680. stackedYPos.push(stackX.posTotal + stackX.threshold);
  681. });
  682. }
  683. return {
  684. dataMin: arrayMin(stackedYNeg),
  685. dataMax: arrayMax(stackedYPos)
  686. };
  687. }
  688. // When not stacking, data extremes have already been computed in the
  689. // processData function.
  690. return {
  691. dataMin: this.dataMin,
  692. dataMax: this.dataMax
  693. };
  694. }
  695. // Point members
  696. }, {
  697. getClassName: function () {
  698. var className = Point.prototype.getClassName.call(this);
  699. if (this.isSum) {
  700. className += ' highcharts-sum';
  701. }
  702. else if (this.isIntermediateSum) {
  703. className += ' highcharts-intermediate-sum';
  704. }
  705. return className;
  706. },
  707. // Pass the null test in ColumnSeries.translate.
  708. isValid: function () {
  709. return (isNumber(this.y) ||
  710. this.isSum ||
  711. Boolean(this.isIntermediateSum));
  712. }
  713. });
  714. /**
  715. * A `waterfall` series. If the [type](#series.waterfall.type) option
  716. * is not specified, it is inherited from [chart.type](#chart.type).
  717. *
  718. * @extends series,plotOptions.waterfall
  719. * @excluding dataParser, dataURL, boostThreshold, boostBlending
  720. * @product highcharts
  721. * @requires highcharts-more
  722. * @apioption series.waterfall
  723. */
  724. /**
  725. * An array of data points for the series. For the `waterfall` series
  726. * type, points can be given in the following ways:
  727. *
  728. * 1. An array of numerical values. In this case, the numerical values will be
  729. * interpreted as `y` options. The `x` values will be automatically
  730. * calculated, either starting at 0 and incremented by 1, or from
  731. * `pointStart` and `pointInterval` given in the series options. If the axis
  732. * has categories, these will be used. Example:
  733. * ```js
  734. * data: [0, 5, 3, 5]
  735. * ```
  736. *
  737. * 2. An array of arrays with 2 values. In this case, the values correspond to
  738. * `x,y`. If the first value is a string, it is applied as the name of the
  739. * point, and the `x` value is inferred.
  740. * ```js
  741. * data: [
  742. * [0, 7],
  743. * [1, 8],
  744. * [2, 3]
  745. * ]
  746. * ```
  747. *
  748. * 3. An array of objects with named values. The following snippet shows only a
  749. * few settings, see the complete options set below. If the total number of
  750. * data points exceeds the series'
  751. * [turboThreshold](#series.waterfall.turboThreshold), this option is not
  752. * available.
  753. * ```js
  754. * data: [{
  755. * x: 1,
  756. * y: 8,
  757. * name: "Point2",
  758. * color: "#00FF00"
  759. * }, {
  760. * x: 1,
  761. * y: 8,
  762. * name: "Point1",
  763. * color: "#FF00FF"
  764. * }]
  765. * ```
  766. *
  767. * @sample {highcharts} highcharts/chart/reflow-true/
  768. * Numerical values
  769. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  770. * Arrays of numeric x and y
  771. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  772. * Arrays of datetime x and y
  773. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  774. * Arrays of point.name and y
  775. * @sample {highcharts} highcharts/series/data-array-of-objects/
  776. * Config objects
  777. *
  778. * @type {Array<number|Array<(number|string),(number|null)>|null|*>}
  779. * @extends series.line.data
  780. * @excluding marker
  781. * @product highcharts
  782. * @apioption series.waterfall.data
  783. */
  784. /**
  785. * When this property is true, the points acts as a summary column for
  786. * the values added or substracted since the last intermediate sum,
  787. * or since the start of the series. The `y` value is ignored.
  788. *
  789. * @sample {highcharts} highcharts/demo/waterfall/
  790. * Waterfall
  791. *
  792. * @type {boolean}
  793. * @default false
  794. * @product highcharts
  795. * @apioption series.waterfall.data.isIntermediateSum
  796. */
  797. /**
  798. * When this property is true, the point display the total sum across
  799. * the entire series. The `y` value is ignored.
  800. *
  801. * @sample {highcharts} highcharts/demo/waterfall/
  802. * Waterfall
  803. *
  804. * @type {boolean}
  805. * @default false
  806. * @product highcharts
  807. * @apioption series.waterfall.data.isSum
  808. */
  809. ''; // adds doclets above to transpiled file
  810. WaterfallAxis.compose(Axis, Chart);
  811. export default WaterfallAxis;