AreaSeries.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499
  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 H from './Globals.js';
  12. import Color from './Color.js';
  13. var color = Color.parse;
  14. import LegendSymbolMixin from '../mixins/legend-symbol.js';
  15. import U from './Utilities.js';
  16. var objectEach = U.objectEach, pick = U.pick, seriesType = U.seriesType;
  17. import './Series.js';
  18. import './Options.js';
  19. var Series = H.Series;
  20. /**
  21. * Area series type.
  22. *
  23. * @private
  24. * @class
  25. * @name Highcharts.seriesTypes.area
  26. *
  27. * @augments Highcharts.Series
  28. */
  29. seriesType('area', 'line',
  30. /**
  31. * The area series type.
  32. *
  33. * @sample {highcharts} highcharts/demo/area-basic/
  34. * Area chart
  35. * @sample {highstock} stock/demo/area/
  36. * Area chart
  37. *
  38. * @extends plotOptions.line
  39. * @excluding useOhlcData
  40. * @product highcharts highstock
  41. * @optionparent plotOptions.area
  42. */
  43. {
  44. /**
  45. * Fill color or gradient for the area. When `null`, the series' `color`
  46. * is used with the series' `fillOpacity`.
  47. *
  48. * In styled mode, the fill color can be set with the `.highcharts-area`
  49. * class name.
  50. *
  51. * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/
  52. * Null by default
  53. * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/
  54. * Gradient
  55. *
  56. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  57. * @product highcharts highstock
  58. * @apioption plotOptions.area.fillColor
  59. */
  60. /**
  61. * Fill opacity for the area. When you set an explicit `fillColor`,
  62. * the `fillOpacity` is not applied. Instead, you should define the
  63. * opacity in the `fillColor` with an rgba color definition. The
  64. * `fillOpacity` setting, also the default setting, overrides the alpha
  65. * component of the `color` setting.
  66. *
  67. * In styled mode, the fill opacity can be set with the
  68. * `.highcharts-area` class name.
  69. *
  70. * @sample {highcharts} highcharts/plotoptions/area-fillopacity/
  71. * Automatic fill color and fill opacity of 0.1
  72. *
  73. * @type {number}
  74. * @default {highcharts} 0.75
  75. * @default {highstock} 0.75
  76. * @product highcharts highstock
  77. * @apioption plotOptions.area.fillOpacity
  78. */
  79. /**
  80. * A separate color for the graph line. By default the line takes the
  81. * `color` of the series, but the lineColor setting allows setting a
  82. * separate color for the line without altering the `fillColor`.
  83. *
  84. * In styled mode, the line stroke can be set with the
  85. * `.highcharts-graph` class name.
  86. *
  87. * @sample {highcharts} highcharts/plotoptions/area-linecolor/
  88. * Dark gray line
  89. *
  90. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  91. * @product highcharts highstock
  92. * @apioption plotOptions.area.lineColor
  93. */
  94. /**
  95. * A separate color for the negative part of the area.
  96. *
  97. * In styled mode, a negative color is set with the
  98. * `.highcharts-negative` class name.
  99. *
  100. * @see [negativeColor](#plotOptions.area.negativeColor)
  101. *
  102. * @sample {highcharts} highcharts/css/series-negative-color/
  103. * Negative color in styled mode
  104. *
  105. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  106. * @since 3.0
  107. * @product highcharts
  108. * @apioption plotOptions.area.negativeFillColor
  109. */
  110. /**
  111. * Whether the whole area or just the line should respond to mouseover
  112. * tooltips and other mouse or touch events.
  113. *
  114. * @sample {highcharts|highstock} highcharts/plotoptions/area-trackbyarea/
  115. * Display the tooltip when the area is hovered
  116. *
  117. * @type {boolean}
  118. * @default false
  119. * @since 1.1.6
  120. * @product highcharts highstock
  121. * @apioption plotOptions.area.trackByArea
  122. */
  123. /**
  124. * When this is true, the series will not cause the Y axis to cross
  125. * the zero plane (or [threshold](#plotOptions.series.threshold) option)
  126. * unless the data actually crosses the plane.
  127. *
  128. * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
  129. * 3 will make the Y axis show negative values according to the
  130. * `minPadding` option. If `softThreshold` is `true`, the Y axis starts
  131. * at 0.
  132. *
  133. * @since 4.1.9
  134. * @product highcharts highstock
  135. */
  136. softThreshold: false,
  137. /**
  138. * The Y axis value to serve as the base for the area, for
  139. * distinguishing between values above and below a threshold. The area
  140. * between the graph and the threshold is filled.
  141. *
  142. * * If a number is given, the Y axis will scale to the threshold.
  143. * * If `null`, the scaling behaves like a line series with fill between
  144. * the graph and the Y axis minimum.
  145. * * If `Infinity` or `-Infinity`, the area between the graph and the
  146. * corresponding Y axis extreme is filled (since v6.1.0).
  147. *
  148. * @sample {highcharts} highcharts/plotoptions/area-threshold/
  149. * A threshold of 100
  150. * @sample {highcharts} highcharts/plotoptions/area-threshold-infinity/
  151. * A threshold of Infinity
  152. *
  153. * @type {number|null}
  154. * @since 2.0
  155. * @product highcharts highstock
  156. */
  157. threshold: 0
  158. },
  159. /* eslint-disable valid-jsdoc */
  160. /**
  161. * @lends seriesTypes.area.prototype
  162. */
  163. {
  164. singleStacks: false,
  165. /**
  166. * Return an array of stacked points, where null and missing points are
  167. * replaced by dummy points in order for gaps to be drawn correctly in
  168. * stacks.
  169. * @private
  170. */
  171. getStackPoints: function (points) {
  172. var series = this, segment = [], keys = [], xAxis = this.xAxis, yAxis = this.yAxis, stack = yAxis.stacking.stacks[this.stackKey], pointMap = {}, seriesIndex = series.index, yAxisSeries = yAxis.series, seriesLength = yAxisSeries.length, visibleSeries, upOrDown = pick(yAxis.options.reversedStacks, true) ? 1 : -1, i;
  173. points = points || this.points;
  174. if (this.options.stacking) {
  175. for (i = 0; i < points.length; i++) {
  176. // Reset after point update (#7326)
  177. points[i].leftNull = points[i].rightNull = void 0;
  178. // Create a map where we can quickly look up the points by
  179. // their X values.
  180. pointMap[points[i].x] = points[i];
  181. }
  182. // Sort the keys (#1651)
  183. objectEach(stack, function (stackX, x) {
  184. // nulled after switching between
  185. // grouping and not (#1651, #2336)
  186. if (stackX.total !== null) {
  187. keys.push(x);
  188. }
  189. });
  190. keys.sort(function (a, b) {
  191. return a - b;
  192. });
  193. visibleSeries = yAxisSeries.map(function (s) {
  194. return s.visible;
  195. });
  196. keys.forEach(function (x, idx) {
  197. var y = 0, stackPoint, stackedValues;
  198. if (pointMap[x] && !pointMap[x].isNull) {
  199. segment.push(pointMap[x]);
  200. // Find left and right cliff. -1 goes left, 1 goes
  201. // right.
  202. [-1, 1].forEach(function (direction) {
  203. var nullName = direction === 1 ?
  204. 'rightNull' :
  205. 'leftNull', cliffName = direction === 1 ?
  206. 'rightCliff' :
  207. 'leftCliff', cliff = 0, otherStack = stack[keys[idx + direction]];
  208. // If there is a stack next to this one,
  209. // to the left or to the right...
  210. if (otherStack) {
  211. i = seriesIndex;
  212. // Can go either up or down,
  213. // depending on reversedStacks
  214. while (i >= 0 && i < seriesLength) {
  215. stackPoint = otherStack.points[i];
  216. if (!stackPoint) {
  217. // If the next point in this series
  218. // is missing, mark the point
  219. // with point.leftNull or
  220. // point.rightNull = true.
  221. if (i === seriesIndex) {
  222. pointMap[x][nullName] =
  223. true;
  224. // If there are missing points in
  225. // the next stack in any of the
  226. // series below this one, we need
  227. // to substract the missing values
  228. // and add a hiatus to the left or
  229. // right.
  230. }
  231. else if (visibleSeries[i]) {
  232. stackedValues =
  233. stack[x].points[i];
  234. if (stackedValues) {
  235. cliff -=
  236. stackedValues[1] -
  237. stackedValues[0];
  238. }
  239. }
  240. }
  241. // When reversedStacks is true, loop up,
  242. // else loop down
  243. i += upOrDown;
  244. }
  245. }
  246. pointMap[x][cliffName] = cliff;
  247. });
  248. // There is no point for this X value in this series, so we
  249. // insert a dummy point in order for the areas to be drawn
  250. // correctly.
  251. }
  252. else {
  253. // Loop down the stack to find the series below this
  254. // one that has a value (#1991)
  255. i = seriesIndex;
  256. while (i >= 0 && i < seriesLength) {
  257. stackPoint = stack[x].points[i];
  258. if (stackPoint) {
  259. y = stackPoint[1];
  260. break;
  261. }
  262. // When reversedStacks is true, loop up, else loop
  263. // down
  264. i += upOrDown;
  265. }
  266. y = yAxis.translate(// #6272
  267. y, 0, 1, 0, 1);
  268. segment.push({
  269. isNull: true,
  270. plotX: xAxis.translate(// #6272
  271. x, 0, 0, 0, 1),
  272. x: x,
  273. plotY: y,
  274. yBottom: y
  275. });
  276. }
  277. });
  278. }
  279. return segment;
  280. },
  281. /**
  282. * @private
  283. */
  284. getGraphPath: function (points) {
  285. var getGraphPath = Series.prototype.getGraphPath, graphPath, options = this.options, stacking = options.stacking, yAxis = this.yAxis, topPath, bottomPath, bottomPoints = [], graphPoints = [], seriesIndex = this.index, i, areaPath, plotX, stacks = yAxis.stacking.stacks[this.stackKey], threshold = options.threshold, translatedThreshold = Math.round(// #10909
  286. yAxis.getThreshold(options.threshold)), isNull, yBottom, connectNulls = pick(// #10574
  287. options.connectNulls, stacking === 'percent'),
  288. // To display null points in underlying stacked series, this
  289. // series graph must be broken, and the area also fall down to
  290. // fill the gap left by the null point. #2069
  291. addDummyPoints = function (i, otherI, side) {
  292. var point = points[i], stackedValues = stacking &&
  293. stacks[point.x].points[seriesIndex], nullVal = point[side + 'Null'] || 0, cliffVal = point[side + 'Cliff'] || 0, top, bottom, isNull = true;
  294. if (cliffVal || nullVal) {
  295. top = (nullVal ?
  296. stackedValues[0] :
  297. stackedValues[1]) + cliffVal;
  298. bottom = stackedValues[0] + cliffVal;
  299. isNull = !!nullVal;
  300. }
  301. else if (!stacking &&
  302. points[otherI] &&
  303. points[otherI].isNull) {
  304. top = bottom = threshold;
  305. }
  306. // Add to the top and bottom line of the area
  307. if (typeof top !== 'undefined') {
  308. graphPoints.push({
  309. plotX: plotX,
  310. plotY: top === null ?
  311. translatedThreshold :
  312. yAxis.getThreshold(top),
  313. isNull: isNull,
  314. isCliff: true
  315. });
  316. bottomPoints.push({
  317. plotX: plotX,
  318. plotY: bottom === null ?
  319. translatedThreshold :
  320. yAxis.getThreshold(bottom),
  321. doCurve: false // #1041, gaps in areaspline areas
  322. });
  323. }
  324. };
  325. // Find what points to use
  326. points = points || this.points;
  327. // Fill in missing points
  328. if (stacking) {
  329. points = this.getStackPoints(points);
  330. }
  331. for (i = 0; i < points.length; i++) {
  332. // Reset after series.update of stacking property (#12033)
  333. if (!stacking) {
  334. points[i].leftCliff = points[i].rightCliff =
  335. points[i].leftNull = points[i].rightNull = void 0;
  336. }
  337. isNull = points[i].isNull;
  338. plotX = pick(points[i].rectPlotX, points[i].plotX);
  339. yBottom = pick(points[i].yBottom, translatedThreshold);
  340. if (!isNull || connectNulls) {
  341. if (!connectNulls) {
  342. addDummyPoints(i, i - 1, 'left');
  343. }
  344. // Skip null point when stacking is false and connectNulls
  345. // true
  346. if (!(isNull && !stacking && connectNulls)) {
  347. graphPoints.push(points[i]);
  348. bottomPoints.push({
  349. x: i,
  350. plotX: plotX,
  351. plotY: yBottom
  352. });
  353. }
  354. if (!connectNulls) {
  355. addDummyPoints(i, i + 1, 'right');
  356. }
  357. }
  358. }
  359. topPath = getGraphPath.call(this, graphPoints, true, true);
  360. bottomPoints.reversed = true;
  361. bottomPath = getGraphPath.call(this, bottomPoints, true, true);
  362. var firstBottomPoint = bottomPath[0];
  363. if (firstBottomPoint && firstBottomPoint[0] === 'M') {
  364. bottomPath[0] = ['L', firstBottomPoint[1], firstBottomPoint[2]];
  365. }
  366. areaPath = topPath.concat(bottomPath);
  367. // TODO: don't set leftCliff and rightCliff when connectNulls?
  368. graphPath = getGraphPath
  369. .call(this, graphPoints, false, connectNulls);
  370. areaPath.xMap = topPath.xMap;
  371. this.areaPath = areaPath;
  372. return graphPath;
  373. },
  374. /**
  375. * Draw the graph and the underlying area. This method calls the Series
  376. * base function and adds the area. The areaPath is calculated in the
  377. * getSegmentPath method called from Series.prototype.drawGraph.
  378. * @private
  379. */
  380. drawGraph: function () {
  381. // Define or reset areaPath
  382. this.areaPath = [];
  383. // Call the base method
  384. Series.prototype.drawGraph.apply(this);
  385. // Define local variables
  386. var series = this, areaPath = this.areaPath, options = this.options, zones = this.zones, props = [[
  387. 'area',
  388. 'highcharts-area',
  389. this.color,
  390. options.fillColor
  391. ]]; // area name, main color, fill color
  392. zones.forEach(function (zone, i) {
  393. props.push([
  394. 'zone-area-' + i,
  395. 'highcharts-area highcharts-zone-area-' + i + ' ' +
  396. zone.className,
  397. zone.color || series.color,
  398. zone.fillColor || options.fillColor
  399. ]);
  400. });
  401. props.forEach(function (prop) {
  402. var areaKey = prop[0], area = series[areaKey], verb = area ? 'animate' : 'attr', attribs = {};
  403. // Create or update the area
  404. if (area) { // update
  405. area.endX = series.preventGraphAnimation ?
  406. null :
  407. areaPath.xMap;
  408. area.animate({ d: areaPath });
  409. }
  410. else { // create
  411. attribs.zIndex = 0; // #1069
  412. area = series[areaKey] = series.chart.renderer
  413. .path(areaPath)
  414. .addClass(prop[1])
  415. .add(series.group);
  416. area.isArea = true;
  417. }
  418. if (!series.chart.styledMode) {
  419. attribs.fill = pick(prop[3], color(prop[2])
  420. .setOpacity(pick(options.fillOpacity, 0.75))
  421. .get());
  422. }
  423. area[verb](attribs);
  424. area.startX = areaPath.xMap;
  425. area.shiftUnit = options.step ? 2 : 1;
  426. });
  427. },
  428. drawLegendSymbol: LegendSymbolMixin.drawRectangle
  429. });
  430. /* eslint-enable valid-jsdoc */
  431. /**
  432. * A `area` series. If the [type](#series.area.type) option is not
  433. * specified, it is inherited from [chart.type](#chart.type).
  434. *
  435. * @extends series,plotOptions.area
  436. * @excluding dataParser, dataURL, useOhlcData
  437. * @product highcharts highstock
  438. * @apioption series.area
  439. */
  440. /**
  441. * An array of data points for the series. For the `area` series type,
  442. * points can be given in the following ways:
  443. *
  444. * 1. An array of numerical values. In this case, the numerical values will be
  445. * interpreted as `y` options. The `x` values will be automatically
  446. * calculated, either starting at 0 and incremented by 1, or from
  447. * `pointStart` * and `pointInterval` given in the series options. If the
  448. * axis has categories, these will be used. Example:
  449. * ```js
  450. * data: [0, 5, 3, 5]
  451. * ```
  452. *
  453. * 2. An array of arrays with 2 values. In this case, the values correspond to
  454. * `x,y`. If the first value is a string, it is applied as the name of the
  455. * point, and the `x` value is inferred.
  456. * ```js
  457. * data: [
  458. * [0, 9],
  459. * [1, 7],
  460. * [2, 6]
  461. * ]
  462. * ```
  463. *
  464. * 3. An array of objects with named values. The following snippet shows only a
  465. * few settings, see the complete options set below. If the total number of
  466. * data points exceeds the series'
  467. * [turboThreshold](#series.area.turboThreshold), this option is not
  468. * available.
  469. * ```js
  470. * data: [{
  471. * x: 1,
  472. * y: 9,
  473. * name: "Point2",
  474. * color: "#00FF00"
  475. * }, {
  476. * x: 1,
  477. * y: 6,
  478. * name: "Point1",
  479. * color: "#FF00FF"
  480. * }]
  481. * ```
  482. *
  483. * @sample {highcharts} highcharts/chart/reflow-true/
  484. * Numerical values
  485. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  486. * Arrays of numeric x and y
  487. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  488. * Arrays of datetime x and y
  489. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  490. * Arrays of point.name and y
  491. * @sample {highcharts} highcharts/series/data-array-of-objects/
  492. * Config objects
  493. *
  494. * @type {Array<number|Array<(number|string),(number|null)>|null|*>}
  495. * @extends series.line.data
  496. * @product highcharts highstock
  497. * @apioption series.area.data
  498. */
  499. ''; // adds doclets above to transpilat