AreaRangeSeries.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624
  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 '../Core/Globals.js';
  12. import Point from '../Core/Series/Point.js';
  13. import U from '../Core/Utilities.js';
  14. var defined = U.defined, extend = U.extend, isArray = U.isArray, isNumber = U.isNumber, pick = U.pick, seriesType = U.seriesType;
  15. import '../Core/Options.js';
  16. import '../Core/Series/Series.js';
  17. var noop = H.noop, Series = H.Series, seriesTypes = H.seriesTypes, seriesProto = Series.prototype, pointProto = Point.prototype;
  18. /**
  19. * The area range series is a carteseian series with higher and lower values for
  20. * each point along an X axis, where the area between the values is shaded.
  21. *
  22. * @sample {highcharts} highcharts/demo/arearange/
  23. * Area range chart
  24. * @sample {highstock} stock/demo/arearange/
  25. * Area range chart
  26. *
  27. * @extends plotOptions.area
  28. * @product highcharts highstock
  29. * @excluding stack, stacking
  30. * @requires highcharts-more
  31. * @optionparent plotOptions.arearange
  32. */
  33. seriesType('arearange', 'area', {
  34. /**
  35. * Whether to apply a drop shadow to the graph line. Since 2.3 the shadow
  36. * can be an object configuration containing `color`, `offsetX`, `offsetY`,
  37. * `opacity` and `width`.
  38. *
  39. * @type {boolean|Highcharts.ShadowOptionsObject}
  40. * @product highcharts
  41. * @apioption plotOptions.arearange.shadow
  42. */
  43. /**
  44. * @default low
  45. * @apioption plotOptions.arearange.colorKey
  46. */
  47. /**
  48. * Pixel width of the arearange graph line.
  49. *
  50. * @since 2.3.0
  51. *
  52. * @private
  53. */
  54. lineWidth: 1,
  55. threshold: null,
  56. tooltip: {
  57. pointFormat: '<span style="color:{series.color}">\u25CF</span> ' +
  58. '{series.name}: <b>{point.low}</b> - <b>{point.high}</b><br/>'
  59. },
  60. /**
  61. * Whether the whole area or just the line should respond to mouseover
  62. * tooltips and other mouse or touch events.
  63. *
  64. * @since 2.3.0
  65. *
  66. * @private
  67. */
  68. trackByArea: true,
  69. /**
  70. * Extended data labels for range series types. Range series data labels use
  71. * no `x` and `y` options. Instead, they have `xLow`, `xHigh`, `yLow` and
  72. * `yHigh` options to allow the higher and lower data label sets
  73. * individually.
  74. *
  75. * @declare Highcharts.SeriesAreaRangeDataLabelsOptionsObject
  76. * @exclude x, y
  77. * @since 2.3.0
  78. * @product highcharts highstock
  79. *
  80. * @private
  81. */
  82. dataLabels: {
  83. align: void 0,
  84. verticalAlign: void 0,
  85. /**
  86. * X offset of the lower data labels relative to the point value.
  87. *
  88. * @sample highcharts/plotoptions/arearange-datalabels/
  89. * Data labels on range series
  90. * @sample highcharts/plotoptions/arearange-datalabels/
  91. * Data labels on range series
  92. */
  93. xLow: 0,
  94. /**
  95. * X offset of the higher data labels relative to the point value.
  96. *
  97. * @sample highcharts/plotoptions/arearange-datalabels/
  98. * Data labels on range series
  99. */
  100. xHigh: 0,
  101. /**
  102. * Y offset of the lower data labels relative to the point value.
  103. *
  104. * @sample highcharts/plotoptions/arearange-datalabels/
  105. * Data labels on range series
  106. */
  107. yLow: 0,
  108. /**
  109. * Y offset of the higher data labels relative to the point value.
  110. *
  111. * @sample highcharts/plotoptions/arearange-datalabels/
  112. * Data labels on range series
  113. */
  114. yHigh: 0
  115. }
  116. // Prototype members
  117. }, {
  118. pointArrayMap: ['low', 'high'],
  119. pointValKey: 'low',
  120. deferTranslatePolar: true,
  121. /* eslint-disable valid-jsdoc */
  122. /**
  123. * @private
  124. */
  125. toYData: function (point) {
  126. return [point.low, point.high];
  127. },
  128. /**
  129. * Translate a point's plotHigh from the internal angle and radius measures
  130. * to true plotHigh coordinates. This is an addition of the toXY method
  131. * found in Polar.js, because it runs too early for arearanges to be
  132. * considered (#3419).
  133. * @private
  134. */
  135. highToXY: function (point) {
  136. // Find the polar plotX and plotY
  137. var chart = this.chart, xy = this.xAxis.postTranslate(point.rectPlotX, this.yAxis.len - point.plotHigh);
  138. point.plotHighX = xy.x - chart.plotLeft;
  139. point.plotHigh = xy.y - chart.plotTop;
  140. point.plotLowX = point.plotX;
  141. },
  142. /**
  143. * Translate data points from raw values x and y to plotX and plotY.
  144. * @private
  145. */
  146. translate: function () {
  147. var series = this, yAxis = series.yAxis, hasModifyValue = !!series.modifyValue;
  148. seriesTypes.area.prototype.translate.apply(series);
  149. // Set plotLow and plotHigh
  150. series.points.forEach(function (point) {
  151. var high = point.high, plotY = point.plotY;
  152. if (point.isNull) {
  153. point.plotY = null;
  154. }
  155. else {
  156. point.plotLow = plotY;
  157. point.plotHigh = yAxis.translate(hasModifyValue ?
  158. series.modifyValue(high, point) :
  159. high, 0, 1, 0, 1);
  160. if (hasModifyValue) {
  161. point.yBottom = point.plotHigh;
  162. }
  163. }
  164. });
  165. // Postprocess plotHigh
  166. if (this.chart.polar) {
  167. this.points.forEach(function (point) {
  168. series.highToXY(point);
  169. point.tooltipPos = [
  170. (point.plotHighX + point.plotLowX) / 2,
  171. (point.plotHigh + point.plotLow) / 2
  172. ];
  173. });
  174. }
  175. },
  176. /**
  177. * Extend the line series' getSegmentPath method by applying the segment
  178. * path to both lower and higher values of the range.
  179. * @private
  180. */
  181. getGraphPath: function (points) {
  182. var highPoints = [], highAreaPoints = [], i, getGraphPath = seriesTypes.area.prototype.getGraphPath, point, pointShim, linePath, lowerPath, options = this.options, polar = this.chart.polar, connectEnds = polar && options.connectEnds !== false, connectNulls = options.connectNulls, step = options.step, higherPath, higherAreaPath;
  183. points = points || this.points;
  184. // Create the top line and the top part of the area fill. The area fill
  185. // compensates for null points by drawing down to the lower graph,
  186. // moving across the null gap and starting again at the lower graph.
  187. i = points.length;
  188. while (i--) {
  189. point = points[i];
  190. // Support for polar
  191. var highAreaPoint = polar ? {
  192. plotX: point.rectPlotX,
  193. plotY: point.yBottom,
  194. doCurve: false // #5186, gaps in areasplinerange fill
  195. } : {
  196. plotX: point.plotX,
  197. plotY: point.plotY,
  198. doCurve: false // #5186, gaps in areasplinerange fill
  199. };
  200. if (!point.isNull &&
  201. !connectEnds &&
  202. !connectNulls &&
  203. (!points[i + 1] || points[i + 1].isNull)) {
  204. highAreaPoints.push(highAreaPoint);
  205. }
  206. pointShim = {
  207. polarPlotY: point.polarPlotY,
  208. rectPlotX: point.rectPlotX,
  209. yBottom: point.yBottom,
  210. // plotHighX is for polar charts
  211. plotX: pick(point.plotHighX, point.plotX),
  212. plotY: point.plotHigh,
  213. isNull: point.isNull
  214. };
  215. highAreaPoints.push(pointShim);
  216. highPoints.push(pointShim);
  217. if (!point.isNull &&
  218. !connectEnds &&
  219. !connectNulls &&
  220. (!points[i - 1] || points[i - 1].isNull)) {
  221. highAreaPoints.push(highAreaPoint);
  222. }
  223. }
  224. // Get the paths
  225. lowerPath = getGraphPath.call(this, points);
  226. if (step) {
  227. if (step === true) {
  228. step = 'left';
  229. }
  230. options.step = {
  231. left: 'right',
  232. center: 'center',
  233. right: 'left'
  234. }[step]; // swap for reading in getGraphPath
  235. }
  236. higherPath = getGraphPath.call(this, highPoints);
  237. higherAreaPath = getGraphPath.call(this, highAreaPoints);
  238. options.step = step;
  239. // Create a line on both top and bottom of the range
  240. linePath = []
  241. .concat(lowerPath, higherPath);
  242. // For the area path, we need to change the 'move' statement
  243. // into 'lineTo'
  244. if (!this.chart.polar && higherAreaPath[0] && higherAreaPath[0][0] === 'M') {
  245. // This probably doesn't work for spline
  246. higherAreaPath[0] = ['L', higherAreaPath[0][1], higherAreaPath[0][2]];
  247. }
  248. this.graphPath = linePath;
  249. this.areaPath = lowerPath.concat(higherAreaPath);
  250. // Prepare for sideways animation
  251. linePath.isArea = true;
  252. linePath.xMap = lowerPath.xMap;
  253. this.areaPath.xMap = lowerPath.xMap;
  254. return linePath;
  255. },
  256. /**
  257. * Extend the basic drawDataLabels method by running it for both lower and
  258. * higher values.
  259. * @private
  260. */
  261. drawDataLabels: function () {
  262. var data = this.points, length = data.length, i, originalDataLabels = [], dataLabelOptions = this.options.dataLabels, point, up, inverted = this.chart.inverted, upperDataLabelOptions, lowerDataLabelOptions;
  263. // Split into upper and lower options. If data labels is an array, the
  264. // first element is the upper label, the second is the lower.
  265. //
  266. // TODO: We want to change this and allow multiple labels for both upper
  267. // and lower values in the future - introducing some options for which
  268. // point value to use as Y for the dataLabel, so that this could be
  269. // handled in Series.drawDataLabels. This would also improve performance
  270. // since we now have to loop over all the points multiple times to work
  271. // around the data label logic.
  272. if (isArray(dataLabelOptions)) {
  273. if (dataLabelOptions.length > 1) {
  274. upperDataLabelOptions = dataLabelOptions[0];
  275. lowerDataLabelOptions = dataLabelOptions[1];
  276. }
  277. else {
  278. upperDataLabelOptions = dataLabelOptions[0];
  279. lowerDataLabelOptions = { enabled: false };
  280. }
  281. }
  282. else {
  283. // Make copies
  284. upperDataLabelOptions = extend({}, dataLabelOptions);
  285. upperDataLabelOptions.x = dataLabelOptions.xHigh;
  286. upperDataLabelOptions.y = dataLabelOptions.yHigh;
  287. lowerDataLabelOptions = extend({}, dataLabelOptions);
  288. lowerDataLabelOptions.x = dataLabelOptions.xLow;
  289. lowerDataLabelOptions.y = dataLabelOptions.yLow;
  290. }
  291. // Draw upper labels
  292. if (upperDataLabelOptions.enabled || this._hasPointLabels) {
  293. // Set preliminary values for plotY and dataLabel
  294. // and draw the upper labels
  295. i = length;
  296. while (i--) {
  297. point = data[i];
  298. if (point) {
  299. up = upperDataLabelOptions.inside ?
  300. point.plotHigh < point.plotLow :
  301. point.plotHigh > point.plotLow;
  302. point.y = point.high;
  303. point._plotY = point.plotY;
  304. point.plotY = point.plotHigh;
  305. // Store original data labels and set preliminary label
  306. // objects to be picked up in the uber method
  307. originalDataLabels[i] = point.dataLabel;
  308. point.dataLabel = point.dataLabelUpper;
  309. // Set the default offset
  310. point.below = up;
  311. if (inverted) {
  312. if (!upperDataLabelOptions.align) {
  313. upperDataLabelOptions.align = up ? 'right' : 'left';
  314. }
  315. }
  316. else {
  317. if (!upperDataLabelOptions.verticalAlign) {
  318. upperDataLabelOptions.verticalAlign = up ?
  319. 'top' :
  320. 'bottom';
  321. }
  322. }
  323. }
  324. }
  325. this.options.dataLabels = upperDataLabelOptions;
  326. if (seriesProto.drawDataLabels) {
  327. // #1209:
  328. seriesProto.drawDataLabels.apply(this, arguments);
  329. }
  330. // Reset state after the upper labels were created. Move
  331. // it to point.dataLabelUpper and reassign the originals.
  332. // We do this here to support not drawing a lower label.
  333. i = length;
  334. while (i--) {
  335. point = data[i];
  336. if (point) {
  337. point.dataLabelUpper = point.dataLabel;
  338. point.dataLabel = originalDataLabels[i];
  339. delete point.dataLabels;
  340. point.y = point.low;
  341. point.plotY = point._plotY;
  342. }
  343. }
  344. }
  345. // Draw lower labels
  346. if (lowerDataLabelOptions.enabled || this._hasPointLabels) {
  347. i = length;
  348. while (i--) {
  349. point = data[i];
  350. if (point) {
  351. up = lowerDataLabelOptions.inside ?
  352. point.plotHigh < point.plotLow :
  353. point.plotHigh > point.plotLow;
  354. // Set the default offset
  355. point.below = !up;
  356. if (inverted) {
  357. if (!lowerDataLabelOptions.align) {
  358. lowerDataLabelOptions.align = up ? 'left' : 'right';
  359. }
  360. }
  361. else {
  362. if (!lowerDataLabelOptions.verticalAlign) {
  363. lowerDataLabelOptions.verticalAlign = up ?
  364. 'bottom' :
  365. 'top';
  366. }
  367. }
  368. }
  369. }
  370. this.options.dataLabels = lowerDataLabelOptions;
  371. if (seriesProto.drawDataLabels) {
  372. seriesProto.drawDataLabels.apply(this, arguments);
  373. }
  374. }
  375. // Merge upper and lower into point.dataLabels for later destroying
  376. if (upperDataLabelOptions.enabled) {
  377. i = length;
  378. while (i--) {
  379. point = data[i];
  380. if (point) {
  381. point.dataLabels = [
  382. point.dataLabelUpper,
  383. point.dataLabel
  384. ].filter(function (label) {
  385. return !!label;
  386. });
  387. }
  388. }
  389. }
  390. // Reset options
  391. this.options.dataLabels = dataLabelOptions;
  392. },
  393. alignDataLabel: function () {
  394. seriesTypes.column.prototype.alignDataLabel
  395. .apply(this, arguments);
  396. },
  397. drawPoints: function () {
  398. var series = this, pointLength = series.points.length, point, i;
  399. // Draw bottom points
  400. seriesProto.drawPoints
  401. .apply(series, arguments);
  402. // Prepare drawing top points
  403. i = 0;
  404. while (i < pointLength) {
  405. point = series.points[i];
  406. // Save original props to be overridden by temporary props for top
  407. // points
  408. point.origProps = {
  409. plotY: point.plotY,
  410. plotX: point.plotX,
  411. isInside: point.isInside,
  412. negative: point.negative,
  413. zone: point.zone,
  414. y: point.y
  415. };
  416. point.lowerGraphic = point.graphic;
  417. point.graphic = point.upperGraphic;
  418. point.plotY = point.plotHigh;
  419. if (defined(point.plotHighX)) {
  420. point.plotX = point.plotHighX;
  421. }
  422. point.y = point.high;
  423. point.negative = point.high < (series.options.threshold || 0);
  424. point.zone = (series.zones.length && point.getZone());
  425. if (!series.chart.polar) {
  426. point.isInside = point.isTopInside = (typeof point.plotY !== 'undefined' &&
  427. point.plotY >= 0 &&
  428. point.plotY <= series.yAxis.len && // #3519
  429. point.plotX >= 0 &&
  430. point.plotX <= series.xAxis.len);
  431. }
  432. i++;
  433. }
  434. // Draw top points
  435. seriesProto.drawPoints.apply(series, arguments);
  436. // Reset top points preliminary modifications
  437. i = 0;
  438. while (i < pointLength) {
  439. point = series.points[i];
  440. point.upperGraphic = point.graphic;
  441. point.graphic = point.lowerGraphic;
  442. extend(point, point.origProps);
  443. delete point.origProps;
  444. i++;
  445. }
  446. },
  447. /* eslint-enable valid-jsdoc */
  448. setStackedPoints: noop
  449. }, {
  450. /**
  451. * Range series only. The high or maximum value for each data point.
  452. * @name Highcharts.Point#high
  453. * @type {number|undefined}
  454. */
  455. /**
  456. * Range series only. The low or minimum value for each data point.
  457. * @name Highcharts.Point#low
  458. * @type {number|undefined}
  459. */
  460. /* eslint-disable valid-jsdoc */
  461. /**
  462. * @private
  463. */
  464. setState: function () {
  465. var prevState = this.state, series = this.series, isPolar = series.chart.polar;
  466. if (!defined(this.plotHigh)) {
  467. // Boost doesn't calculate plotHigh
  468. this.plotHigh = series.yAxis.toPixels(this.high, true);
  469. }
  470. if (!defined(this.plotLow)) {
  471. // Boost doesn't calculate plotLow
  472. this.plotLow = this.plotY = series.yAxis.toPixels(this.low, true);
  473. }
  474. if (series.stateMarkerGraphic) {
  475. series.lowerStateMarkerGraphic = series.stateMarkerGraphic;
  476. series.stateMarkerGraphic = series.upperStateMarkerGraphic;
  477. }
  478. // Change state also for the top marker
  479. this.graphic = this.upperGraphic;
  480. this.plotY = this.plotHigh;
  481. if (isPolar) {
  482. this.plotX = this.plotHighX;
  483. }
  484. // Top state:
  485. pointProto.setState.apply(this, arguments);
  486. this.state = prevState;
  487. // Now restore defaults
  488. this.plotY = this.plotLow;
  489. this.graphic = this.lowerGraphic;
  490. if (isPolar) {
  491. this.plotX = this.plotLowX;
  492. }
  493. if (series.stateMarkerGraphic) {
  494. series.upperStateMarkerGraphic = series.stateMarkerGraphic;
  495. series.stateMarkerGraphic = series.lowerStateMarkerGraphic;
  496. // Lower marker is stored at stateMarkerGraphic
  497. // to avoid reference duplication (#7021)
  498. series.lowerStateMarkerGraphic = void 0;
  499. }
  500. pointProto.setState.apply(this, arguments);
  501. },
  502. haloPath: function () {
  503. var isPolar = this.series.chart.polar, path = [];
  504. // Bottom halo
  505. this.plotY = this.plotLow;
  506. if (isPolar) {
  507. this.plotX = this.plotLowX;
  508. }
  509. if (this.isInside) {
  510. path = pointProto.haloPath.apply(this, arguments);
  511. }
  512. // Top halo
  513. this.plotY = this.plotHigh;
  514. if (isPolar) {
  515. this.plotX = this.plotHighX;
  516. }
  517. if (this.isTopInside) {
  518. path = path.concat(pointProto.haloPath.apply(this, arguments));
  519. }
  520. return path;
  521. },
  522. destroyElements: function () {
  523. var graphics = ['lowerGraphic', 'upperGraphic'];
  524. graphics.forEach(function (graphicName) {
  525. if (this[graphicName]) {
  526. this[graphicName] =
  527. this[graphicName].destroy();
  528. }
  529. }, this);
  530. // Clear graphic for states, removed in the above each:
  531. this.graphic = null;
  532. return pointProto.destroyElements.apply(this, arguments);
  533. },
  534. isValid: function () {
  535. return isNumber(this.low) && isNumber(this.high);
  536. }
  537. /* eslint-enable valid-jsdoc */
  538. });
  539. /**
  540. * A `arearange` series. If the [type](#series.arearange.type) option is not
  541. * specified, it is inherited from [chart.type](#chart.type).
  542. *
  543. *
  544. * @extends series,plotOptions.arearange
  545. * @excluding dataParser, dataURL, stack, stacking
  546. * @product highcharts highstock
  547. * @requires highcharts-more
  548. * @apioption series.arearange
  549. */
  550. /**
  551. * An array of data points for the series. For the `arearange` series type,
  552. * points can be given in the following ways:
  553. *
  554. * 1. An array of arrays with 3 or 2 values. In this case, the values
  555. * correspond to `x,low,high`. If the first value is a string, it is
  556. * applied as the name of the point, and the `x` value is inferred.
  557. * The `x` value can also be omitted, in which case the inner arrays
  558. * should be of length 2\. Then the `x` value is automatically calculated,
  559. * either starting at 0 and incremented by 1, or from `pointStart`
  560. * and `pointInterval` given in the series options.
  561. * ```js
  562. * data: [
  563. * [0, 8, 3],
  564. * [1, 1, 1],
  565. * [2, 6, 8]
  566. * ]
  567. * ```
  568. *
  569. * 2. An array of objects with named values. The following snippet shows only a
  570. * few settings, see the complete options set below. If the total number of
  571. * data points exceeds the series'
  572. * [turboThreshold](#series.arearange.turboThreshold),
  573. * this option is not available.
  574. * ```js
  575. * data: [{
  576. * x: 1,
  577. * low: 9,
  578. * high: 0,
  579. * name: "Point2",
  580. * color: "#00FF00"
  581. * }, {
  582. * x: 1,
  583. * low: 3,
  584. * high: 4,
  585. * name: "Point1",
  586. * color: "#FF00FF"
  587. * }]
  588. * ```
  589. *
  590. * @sample {highcharts} highcharts/series/data-array-of-arrays/
  591. * Arrays of numeric x and y
  592. * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
  593. * Arrays of datetime x and y
  594. * @sample {highcharts} highcharts/series/data-array-of-name-value/
  595. * Arrays of point.name and y
  596. * @sample {highcharts} highcharts/series/data-array-of-objects/
  597. * Config objects
  598. *
  599. * @type {Array<Array<(number|string),number>|Array<(number|string),number,number>|*>}
  600. * @extends series.line.data
  601. * @excluding marker, y
  602. * @product highcharts highstock
  603. * @apioption series.arearange.data
  604. */
  605. /**
  606. * @extends series.arearange.dataLabels
  607. * @product highcharts highstock
  608. * @apioption series.arearange.data.dataLabels
  609. */
  610. /**
  611. * The high or maximum value for each data point.
  612. *
  613. * @type {number}
  614. * @product highcharts highstock
  615. * @apioption series.arearange.data.high
  616. */
  617. /**
  618. * The low or minimum value for each data point.
  619. *
  620. * @type {number}
  621. * @product highcharts highstock
  622. * @apioption series.arearange.data.low
  623. */
  624. ''; // adds doclets above to tranpiled file