TimelineSeries.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556
  1. /* *
  2. *
  3. * Timeline Series.
  4. *
  5. * (c) 2010-2021 Highsoft AS
  6. *
  7. * Author: Daniel Studencki
  8. *
  9. * License: www.highcharts.com/license
  10. *
  11. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  12. *
  13. * */
  14. 'use strict';
  15. var __extends = (this && this.__extends) || (function () {
  16. var extendStatics = function (d, b) {
  17. extendStatics = Object.setPrototypeOf ||
  18. ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
  19. function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; };
  20. return extendStatics(d, b);
  21. };
  22. return function (d, b) {
  23. extendStatics(d, b);
  24. function __() { this.constructor = d; }
  25. d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
  26. };
  27. })();
  28. import LegendSymbolMixin from '../../Mixins/LegendSymbol.js';
  29. import palette from '../../Core/Color/Palette.js';
  30. import SeriesRegistry from '../../Core/Series/SeriesRegistry.js';
  31. var _a = SeriesRegistry.seriesTypes, ColumnSeries = _a.column, LineSeries = _a.line;
  32. import SVGElement from '../../Core/Renderer/SVG/SVGElement.js';
  33. import TimelinePoint from './TimelinePoint.js';
  34. import U from '../../Core/Utilities.js';
  35. var addEvent = U.addEvent, arrayMax = U.arrayMax, arrayMin = U.arrayMin, defined = U.defined, extend = U.extend, merge = U.merge, pick = U.pick;
  36. /* *
  37. *
  38. * Class
  39. *
  40. * */
  41. /**
  42. * The timeline series type.
  43. *
  44. * @private
  45. * @class
  46. * @name Highcharts.seriesTypes.timeline
  47. *
  48. * @augments Highcharts.Series
  49. */
  50. var TimelineSeries = /** @class */ (function (_super) {
  51. __extends(TimelineSeries, _super);
  52. function TimelineSeries() {
  53. /* *
  54. *
  55. * Static Properties
  56. *
  57. * */
  58. var _this = _super !== null && _super.apply(this, arguments) || this;
  59. /* *
  60. *
  61. * Properties
  62. *
  63. * */
  64. _this.data = void 0;
  65. _this.options = void 0;
  66. _this.points = void 0;
  67. _this.userOptions = void 0;
  68. _this.visibilityMap = void 0;
  69. return _this;
  70. /* eslint-enable valid-jsdoc */
  71. }
  72. /* *
  73. *
  74. * Functions
  75. *
  76. * */
  77. /* eslint-disable valid-jsdoc */
  78. TimelineSeries.prototype.alignDataLabel = function (point, dataLabel, _options, _alignTo) {
  79. var series = this, isInverted = series.chart.inverted, visiblePoints = series.visibilityMap.filter(function (point) {
  80. return point;
  81. }), visiblePointsCount = series.visiblePointsCount, pointIndex = visiblePoints.indexOf(point), isFirstOrLast = (!pointIndex || pointIndex === visiblePointsCount - 1), dataLabelsOptions = series.options.dataLabels, userDLOptions = point.userDLOptions || {},
  82. // Define multiplier which is used to calculate data label
  83. // width. If data labels are alternate, they have two times more
  84. // space to adapt (excepting first and last ones, which has only
  85. // one and half), than in case of placing all data labels side
  86. // by side.
  87. multiplier = dataLabelsOptions.alternate ?
  88. (isFirstOrLast ? 1.5 : 2) :
  89. 1, distance, availableSpace = Math.floor(series.xAxis.len / visiblePointsCount), pad = dataLabel.padding, targetDLWidth, styles;
  90. // Adjust data label width to the currently available space.
  91. if (point.visible) {
  92. distance = Math.abs(userDLOptions.x || point.options.dataLabels.x);
  93. if (isInverted) {
  94. targetDLWidth = ((distance - pad) * 2 - (point.itemHeight / 2));
  95. styles = {
  96. width: targetDLWidth + 'px',
  97. // Apply ellipsis when data label height is exceeded.
  98. textOverflow: dataLabel.width / targetDLWidth *
  99. dataLabel.height / 2 > availableSpace * multiplier ?
  100. 'ellipsis' : 'none'
  101. };
  102. }
  103. else {
  104. styles = {
  105. width: (userDLOptions.width ||
  106. dataLabelsOptions.width ||
  107. availableSpace * multiplier - (pad * 2)) + 'px'
  108. };
  109. }
  110. dataLabel.css(styles);
  111. if (!series.chart.styledMode) {
  112. dataLabel.shadow(dataLabelsOptions.shadow);
  113. }
  114. }
  115. _super.prototype.alignDataLabel.apply(series, arguments);
  116. };
  117. TimelineSeries.prototype.bindAxes = function () {
  118. var series = this;
  119. _super.prototype.bindAxes.call(series);
  120. ['xAxis', 'yAxis'].forEach(function (axis) {
  121. // Initially set the linked xAxis type to category.
  122. if (axis === 'xAxis' && !series[axis].userOptions.type) {
  123. series[axis].categories = series[axis].hasNames = true;
  124. }
  125. });
  126. };
  127. TimelineSeries.prototype.distributeDL = function () {
  128. var series = this, dataLabelsOptions = series.options.dataLabels, options, pointDLOptions, newOptions = {}, visibilityIndex = 1, distance = dataLabelsOptions.distance;
  129. series.points.forEach(function (point) {
  130. if (point.visible && !point.isNull) {
  131. options = point.options;
  132. pointDLOptions = point.options.dataLabels;
  133. if (!series.hasRendered) {
  134. point.userDLOptions =
  135. merge({}, pointDLOptions);
  136. }
  137. newOptions[series.chart.inverted ? 'x' : 'y'] =
  138. dataLabelsOptions.alternate && visibilityIndex % 2 ?
  139. -distance : distance;
  140. options.dataLabels = merge(newOptions, point.userDLOptions);
  141. visibilityIndex++;
  142. }
  143. });
  144. };
  145. TimelineSeries.prototype.generatePoints = function () {
  146. var series = this;
  147. _super.prototype.generatePoints.apply(series);
  148. series.points.forEach(function (point, i) {
  149. point.applyOptions({
  150. x: series.xData[i]
  151. }, series.xData[i]);
  152. });
  153. };
  154. TimelineSeries.prototype.getVisibilityMap = function () {
  155. var series = this, map = (series.data.length ?
  156. series.data : series.userOptions.data).map(function (point) {
  157. return (point &&
  158. point.visible !== false &&
  159. !point.isNull) ? point : false;
  160. });
  161. return map;
  162. };
  163. TimelineSeries.prototype.getXExtremes = function (xData) {
  164. var series = this, filteredData = xData.filter(function (x, i) {
  165. return series.points[i].isValid() &&
  166. series.points[i].visible;
  167. });
  168. return {
  169. min: arrayMin(filteredData),
  170. max: arrayMax(filteredData)
  171. };
  172. };
  173. TimelineSeries.prototype.init = function () {
  174. var series = this;
  175. _super.prototype.init.apply(series, arguments);
  176. series.eventsToUnbind.push(addEvent(series, 'afterTranslate', function () {
  177. var lastPlotX, closestPointRangePx = Number.MAX_VALUE;
  178. series.points.forEach(function (point) {
  179. // Set the isInside parameter basing also on the real point
  180. // visibility, in order to avoid showing hidden points
  181. // in drawPoints method.
  182. point.isInside = point.isInside && point.visible;
  183. // New way of calculating closestPointRangePx value, which
  184. // respects the real point visibility is needed.
  185. if (point.visible && !point.isNull) {
  186. if (defined(lastPlotX)) {
  187. closestPointRangePx = Math.min(closestPointRangePx, Math.abs(point.plotX - lastPlotX));
  188. }
  189. lastPlotX = point.plotX;
  190. }
  191. });
  192. series.closestPointRangePx = closestPointRangePx;
  193. }));
  194. // Distribute data labels before rendering them. Distribution is
  195. // based on the 'dataLabels.distance' and 'dataLabels.alternate'
  196. // property.
  197. series.eventsToUnbind.push(addEvent(series, 'drawDataLabels', function () {
  198. // Distribute data labels basing on defined algorithm.
  199. series.distributeDL(); // @todo use this scope for series
  200. }));
  201. series.eventsToUnbind.push(addEvent(series, 'afterDrawDataLabels', function () {
  202. var dataLabel; // @todo use this scope for series
  203. // Draw or align connector for each point.
  204. series.points.forEach(function (point) {
  205. dataLabel = point.dataLabel;
  206. if (dataLabel) {
  207. // Within this wrap method is necessary to save the
  208. // current animation params, because the data label
  209. // target position (after animation) is needed to align
  210. // connectors.
  211. dataLabel.animate = function (params) {
  212. if (this.targetPosition) {
  213. this.targetPosition = params;
  214. }
  215. return SVGElement.prototype.animate.apply(this, arguments);
  216. };
  217. // Initialize the targetPosition field within data label
  218. // object. It's necessary because there is need to know
  219. // expected position of specific data label, when
  220. // aligning connectors. This field is overrided inside
  221. // of SVGElement.animate() wrapped method.
  222. if (!dataLabel.targetPosition) {
  223. dataLabel.targetPosition = {};
  224. }
  225. return point.drawConnector();
  226. }
  227. });
  228. }));
  229. series.eventsToUnbind.push(addEvent(series.chart, 'afterHideOverlappingLabel', function () {
  230. series.points.forEach(function (p) {
  231. if (p.connector &&
  232. p.dataLabel &&
  233. p.dataLabel.oldOpacity !== p.dataLabel.newOpacity) {
  234. p.alignConnector();
  235. }
  236. });
  237. }));
  238. };
  239. TimelineSeries.prototype.markerAttribs = function (point, state) {
  240. var series = this, seriesMarkerOptions = series.options.marker, seriesStateOptions, pointMarkerOptions = point.marker || {}, symbol = (pointMarkerOptions.symbol || seriesMarkerOptions.symbol), pointStateOptions, width = pick(pointMarkerOptions.width, seriesMarkerOptions.width, series.closestPointRangePx), height = pick(pointMarkerOptions.height, seriesMarkerOptions.height), radius = 0, attribs;
  241. // Call default markerAttribs method, when the xAxis type
  242. // is set to datetime.
  243. if (series.xAxis.dateTime) {
  244. return _super.prototype.markerAttribs.call(this, point, state);
  245. }
  246. // Handle hover and select states
  247. if (state) {
  248. seriesStateOptions =
  249. seriesMarkerOptions.states[state] || {};
  250. pointStateOptions = pointMarkerOptions.states &&
  251. pointMarkerOptions.states[state] || {};
  252. radius = pick(pointStateOptions.radius, seriesStateOptions.radius, radius + (seriesStateOptions.radiusPlus || 0));
  253. }
  254. point.hasImage = (symbol && symbol.indexOf('url') === 0);
  255. attribs = {
  256. x: Math.floor(point.plotX) - (width / 2) - (radius / 2),
  257. y: point.plotY - (height / 2) - (radius / 2),
  258. width: width + radius,
  259. height: height + radius
  260. };
  261. return attribs;
  262. };
  263. TimelineSeries.prototype.processData = function () {
  264. var series = this, visiblePoints = 0, i;
  265. series.visibilityMap = series.getVisibilityMap();
  266. // Calculate currently visible points.
  267. series.visibilityMap.forEach(function (point) {
  268. if (point) {
  269. visiblePoints++;
  270. }
  271. });
  272. series.visiblePointsCount = visiblePoints;
  273. for (i = 0; i < series.xData.length; i++) {
  274. series.yData[i] = 1;
  275. }
  276. _super.prototype.processData.call(this, arguments);
  277. return;
  278. };
  279. /**
  280. * The timeline series presents given events along a drawn line.
  281. *
  282. * @sample highcharts/series-timeline/alternate-labels
  283. * Timeline series
  284. * @sample highcharts/series-timeline/inverted
  285. * Inverted timeline
  286. * @sample highcharts/series-timeline/datetime-axis
  287. * With true datetime axis
  288. *
  289. * @extends plotOptions.line
  290. * @since 7.0.0
  291. * @product highcharts
  292. * @excluding animationLimit, boostThreshold, connectEnds, connectNulls,
  293. * cropThreshold, dashStyle, findNearestPointBy,
  294. * getExtremesFromAll, lineWidth, negativeColor,
  295. * pointInterval, pointIntervalUnit, pointPlacement,
  296. * pointStart, softThreshold, stacking, step, threshold,
  297. * turboThreshold, zoneAxis, zones, dataSorting,
  298. * boostBlending
  299. * @requires modules/timeline
  300. * @optionparent plotOptions.timeline
  301. */
  302. TimelineSeries.defaultOptions = merge(LineSeries.defaultOptions, {
  303. colorByPoint: true,
  304. stickyTracking: false,
  305. ignoreHiddenPoint: true,
  306. /**
  307. * @ignore
  308. * @private
  309. */
  310. legendType: 'point',
  311. lineWidth: 4,
  312. tooltip: {
  313. headerFormat: '<span style="color:{point.color}">\u25CF</span> ' +
  314. '<span style="font-size: 10px"> {point.key}</span><br/>',
  315. pointFormat: '{point.description}'
  316. },
  317. states: {
  318. hover: {
  319. lineWidthPlus: 0
  320. }
  321. },
  322. /**
  323. * @declare Highcharts.TimelineDataLabelsOptionsObject
  324. *
  325. * @private
  326. */
  327. dataLabels: {
  328. enabled: true,
  329. allowOverlap: true,
  330. /**
  331. * Whether to position data labels alternately. For example, if
  332. * [distance](#plotOptions.timeline.dataLabels.distance)
  333. * is set equal to `100`, then data labels will be positioned
  334. * alternately (on both sides of the point) at a distance of 100px.
  335. *
  336. * @sample {highcharts} highcharts/series-timeline/alternate-disabled
  337. * Alternate disabled
  338. */
  339. alternate: true,
  340. backgroundColor: palette.backgroundColor,
  341. borderWidth: 1,
  342. borderColor: palette.neutralColor40,
  343. borderRadius: 3,
  344. color: palette.neutralColor80,
  345. /**
  346. * The color of the line connecting the data label to the point.
  347. * The default color is the same as the point's color.
  348. *
  349. * In styled mode, the connector stroke is given in the
  350. * `.highcharts-data-label-connector` class.
  351. *
  352. * @sample {highcharts} highcharts/series-timeline/connector-styles
  353. * Custom connector width and color
  354. *
  355. * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
  356. * @apioption plotOptions.timeline.dataLabels.connectorColor
  357. */
  358. /**
  359. * The width of the line connecting the data label to the point.
  360. *
  361. * In styled mode, the connector stroke width is given in the
  362. * `.highcharts-data-label-connector` class.
  363. *
  364. * @sample {highcharts} highcharts/series-timeline/connector-styles
  365. * Custom connector width and color
  366. */
  367. connectorWidth: 1,
  368. /**
  369. * A pixel value defining the distance between the data label and
  370. * the point. Negative numbers puts the label on top of the point.
  371. */
  372. distance: 100,
  373. // eslint-disable-next-line valid-jsdoc
  374. /**
  375. * @type {Highcharts.TimelineDataLabelsFormatterCallbackFunction}
  376. * @default function () {
  377. * var format;
  378. *
  379. * if (!this.series.chart.styledMode) {
  380. * format = '<span style="color:' + this.point.color +
  381. * '">● </span>';
  382. * } else {
  383. * format = '<span>● </span>';
  384. * }
  385. * format += '<span>' + (this.key || '') + '</span><br/>' +
  386. * (this.point.label || '');
  387. * return format;
  388. * }
  389. */
  390. formatter: function () {
  391. var format;
  392. if (!this.series.chart.styledMode) {
  393. format = '<span style="color:' + this.point.color +
  394. '">● </span>';
  395. }
  396. else {
  397. format = '<span>● </span>';
  398. }
  399. format += '<span class="highcharts-strong">' +
  400. (this.key || '') + '</span><br/>' +
  401. (this.point.label || '');
  402. return format;
  403. },
  404. style: {
  405. /** @internal */
  406. textOutline: 'none',
  407. /** @internal */
  408. fontWeight: 'normal',
  409. /** @internal */
  410. fontSize: '12px'
  411. },
  412. /**
  413. * Shadow options for the data label.
  414. *
  415. * @type {boolean|Highcharts.CSSObject}
  416. */
  417. shadow: false,
  418. /**
  419. * @type {number}
  420. * @apioption plotOptions.timeline.dataLabels.width
  421. */
  422. verticalAlign: 'middle'
  423. },
  424. marker: {
  425. enabledThreshold: 0,
  426. symbol: 'square',
  427. radius: 6,
  428. lineWidth: 2,
  429. height: 15
  430. },
  431. showInLegend: false,
  432. colorKey: 'x'
  433. });
  434. return TimelineSeries;
  435. }(LineSeries));
  436. extend(TimelineSeries.prototype, {
  437. // Use a simple symbol from LegendSymbolMixin
  438. drawLegendSymbol: LegendSymbolMixin.drawRectangle,
  439. // Use a group of trackers from TrackerMixin
  440. drawTracker: ColumnSeries.prototype.drawTracker,
  441. pointClass: TimelinePoint,
  442. trackerGroups: ['markerGroup', 'dataLabelsGroup']
  443. });
  444. SeriesRegistry.registerSeriesType('timeline', TimelineSeries);
  445. /* *
  446. *
  447. * Default Export
  448. *
  449. * */
  450. export default TimelineSeries;
  451. /* *
  452. *
  453. * API Declarations
  454. *
  455. * */
  456. /**
  457. * Callback JavaScript function to format the data label as a string. Note that
  458. * if a `format` is defined, the format takes precedence and the formatter is
  459. * ignored.
  460. *
  461. * @callback Highcharts.TimelineDataLabelsFormatterCallbackFunction
  462. *
  463. * @param {Highcharts.PointLabelObject|Highcharts.TimelineDataLabelsFormatterContextObject} this
  464. * Data label context to format
  465. *
  466. * @return {number|string|null|undefined}
  467. * Formatted data label text
  468. */
  469. /**
  470. * @interface Highcharts.TimelineDataLabelsFormatterContextObject
  471. * @extends Highcharts.PointLabelObject
  472. */ /**
  473. * @name Highcharts.TimelineDataLabelsFormatterContextObject#key
  474. * @type {string|undefined}
  475. */ /**
  476. * @name Highcharts.TimelineDataLabelsFormatterContextObject#point
  477. * @type {Highcharts.Point}
  478. */ /**
  479. * @name Highcharts.TimelineDataLabelsFormatterContextObject#series
  480. * @type {Highcharts.Series}
  481. */
  482. ''; // dettach doclets above
  483. /* *
  484. *
  485. * API Options
  486. *
  487. * */
  488. /**
  489. * The `timeline` series. If the [type](#series.timeline.type) option is
  490. * not specified, it is inherited from [chart.type](#chart.type).
  491. *
  492. * @extends series,plotOptions.timeline
  493. * @excluding animationLimit, boostThreshold, connectEnds, connectNulls,
  494. * cropThreshold, dashStyle, dataParser, dataURL, findNearestPointBy,
  495. * getExtremesFromAll, lineWidth, negativeColor,
  496. * pointInterval, pointIntervalUnit, pointPlacement, pointStart,
  497. * softThreshold, stacking, stack, step, threshold, turboThreshold,
  498. * zoneAxis, zones, dataSorting, boostBlending
  499. * @product highcharts
  500. * @requires modules/timeline
  501. * @apioption series.timeline
  502. */
  503. /**
  504. * An array of data points for the series. For the `timeline` series type,
  505. * points can be given with three general parameters, `name`, `label`,
  506. * and `description`:
  507. *
  508. * Example:
  509. *
  510. * ```js
  511. * series: [{
  512. * type: 'timeline',
  513. * data: [{
  514. * name: 'Jan 2018',
  515. * label: 'Some event label',
  516. * description: 'Description to show in tooltip'
  517. * }]
  518. * }]
  519. * ```
  520. * If all points additionally have the `x` values, and xAxis type is set to
  521. * `datetime`, then events are laid out on a true time axis, where their
  522. * placement reflects the actual time between them.
  523. *
  524. * @sample {highcharts} highcharts/series-timeline/alternate-labels
  525. * Alternate labels
  526. * @sample {highcharts} highcharts/series-timeline/datetime-axis
  527. * Real time intervals
  528. *
  529. * @type {Array<*>}
  530. * @extends series.line.data
  531. * @excluding marker, y
  532. * @product highcharts
  533. * @apioption series.timeline.data
  534. */
  535. /**
  536. * The name of event.
  537. *
  538. * @type {string}
  539. * @product highcharts
  540. * @apioption series.timeline.data.name
  541. */
  542. /**
  543. * The label of event.
  544. *
  545. * @type {string}
  546. * @product highcharts
  547. * @apioption series.timeline.data.label
  548. */
  549. /**
  550. * The description of event. This description will be shown in tooltip.
  551. *
  552. * @type {string}
  553. * @product highcharts
  554. * @apioption series.timeline.data.description
  555. */
  556. ''; // adds doclets above to transpiled file