AreaSeries.js 21 KB

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