AreaRangeSeries.js 23 KB

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