/** * @license Highcharts JS v11.4.1 (2024-04-04) * * Timeline series * * (c) 2010-2024 Highsoft AS * Author: Daniel Studencki * * License: www.highcharts.com/license */ (function (factory) { if (typeof module === 'object' && module.exports) { factory['default'] = factory; module.exports = factory; } else if (typeof define === 'function' && define.amd) { define('highcharts/modules/timeline', ['highcharts'], function (Highcharts) { factory(Highcharts); factory.Highcharts = Highcharts; return factory; }); } else { factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined); } }(function (Highcharts) { 'use strict'; var _modules = Highcharts ? Highcharts._modules : {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); if (typeof CustomEvent === 'function') { window.dispatchEvent(new CustomEvent( 'HighchartsModuleLoaded', { detail: { path: path, module: obj[path] } } )); } } } _registerModule(_modules, 'Series/Timeline/TimelinePoint.js', [_modules['Core/Series/Point.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (Point, SeriesRegistry, U) { /* * * * Timeline Series. * * (c) 2010-2024 Highsoft AS * * Author: Daniel Studencki * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { line: { prototype: { pointClass: LinePoint } }, pie: { prototype: { pointClass: PiePoint } } } = SeriesRegistry.seriesTypes; const { defined, isNumber, merge, objectEach, pick } = U; /* * * * Class * * */ class TimelinePoint extends LinePoint { /* * * * Functions * * */ alignConnector() { const point = this, series = point.series, dataLabel = point.dataLabel, connector = dataLabel.connector, dlOptions = (dataLabel.options || {}), connectorWidth = dlOptions.connectorWidth || 0, chart = point.series.chart, bBox = connector.getBBox(), plotPos = { x: bBox.x + (dataLabel.translateX || 0), y: bBox.y + (dataLabel.translateY || 0) }; // Include a half of connector width in order to run animation, // when connectors are aligned to the plot area edge. if (chart.inverted) { plotPos.y -= connectorWidth / 2; } else { plotPos.x += connectorWidth / 2; } const isVisible = chart.isInsidePlot(plotPos.x, plotPos.y); connector[isVisible ? 'animate' : 'attr']({ d: point.getConnectorPath() }); connector.addClass('highcharts-color-' + point.colorIndex); if (!series.chart.styledMode) { connector.attr({ stroke: dlOptions.connectorColor || point.color, 'stroke-width': dlOptions.connectorWidth, opacity: dataLabel[defined(dataLabel.newOpacity) ? 'newOpacity' : 'opacity'] }); } } drawConnector() { const point = this, { dataLabel, series } = point; if (dataLabel) { if (!dataLabel.connector) { dataLabel.connector = series.chart.renderer .path(point.getConnectorPath()) .attr({ zIndex: -1 }) .add(dataLabel); } if (point.series.chart.isInsidePlot(// #10507 dataLabel.x || 0, dataLabel.y || 0)) { point.alignConnector(); } } } getConnectorPath() { const { plotX = 0, plotY = 0, series, dataLabel } = this, chart = series.chart, xAxisLen = series.xAxis.len, inverted = chart.inverted, direction = inverted ? 'x2' : 'y2'; if (dataLabel) { const targetDLPos = dataLabel.targetPosition, negativeDistance = ((dataLabel.alignAttr || dataLabel)[direction[0]] < series.yAxis.len / 2); let coords = { x1: plotX, y1: plotY, x2: plotX, y2: isNumber(targetDLPos.y) ? targetDLPos.y : dataLabel.y }; // Recalculate coords when the chart is inverted. if (inverted) { coords = { x1: plotY, y1: xAxisLen - plotX, x2: targetDLPos.x || dataLabel.x, y2: xAxisLen - plotX }; } // Subtract data label width or height from expected coordinate so // that the connector would start from the appropriate edge. if (negativeDistance) { coords[direction] += dataLabel[inverted ? 'width' : 'height'] || 0; } // Change coordinates so that they will be relative to data label. objectEach(coords, (_coord, i) => { coords[i] -= (dataLabel.alignAttr || dataLabel)[i[0]]; }); return chart.renderer.crispLine([ ['M', coords.x1, coords.y1], ['L', coords.x2, coords.y2] ], dataLabel.options?.connectorWidth || 0); } return []; } constructor(series, options) { super(series, options); this.name ?? (this.name = 'Event'); this.y = 1; } isValid() { return this.options.y !== null; } setState() { const proceed = super.setState; // Prevent triggering the setState method on null points. if (!this.isNull) { proceed.apply(this, arguments); } } setVisible(visible, redraw) { const point = this, series = point.series; redraw = pick(redraw, series.options.ignoreHiddenPoint); PiePoint.prototype.setVisible.call(point, visible, false); // Process new data series.processData(); if (redraw) { series.chart.redraw(); } } applyOptions(options, x) { options = Point.prototype.optionsToObject.call(this, options); this.userDLOptions = merge(this.userDLOptions, options.dataLabels); return super.applyOptions(options, x); } } /* * * * Default Export * * */ return TimelinePoint; }); _registerModule(_modules, 'Series/Timeline/TimelineSeriesDefaults.js', [], function () { /* * * * Timeline Series. * * (c) 2010-2024 Highsoft AS * * Author: Daniel Studencki * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * API Options * * */ /** * The timeline series presents given events along a drawn line. * * @sample highcharts/series-timeline/alternate-labels * Timeline series * @sample highcharts/series-timeline/inverted * Inverted timeline * @sample highcharts/series-timeline/datetime-axis * With true datetime axis * * @extends plotOptions.line * @excluding animationLimit, boostThreshold, connectEnds, connectNulls, * cropThreshold, dashStyle, findNearestPointBy, * getExtremesFromAll, negativeColor, pointInterval, * pointIntervalUnit, pointPlacement, pointStart, * softThreshold, stacking, step, threshold, turboThreshold, * zoneAxis, zones, dataSorting, boostBlending * @product highcharts * @since 7.0.0 * @requires modules/timeline * @optionparent plotOptions.timeline */ const TimelineSeriesDefaults = { colorByPoint: true, stickyTracking: false, ignoreHiddenPoint: true, /** * @ignore */ legendType: 'point', /** * Pixel width of the graph line. */ lineWidth: 4, tooltip: { headerFormat: '\u25CF ' + ' {point.key}
', pointFormat: '{point.description}' }, states: { hover: { lineWidthPlus: 0 } }, /** * @declare Highcharts.TimelineDataLabelsOptionsObject */ dataLabels: { enabled: true, allowOverlap: true, /** * Whether to position data labels alternately. For example, if * [distance](#plotOptions.timeline.dataLabels.distance) * is set equal to `100`, then data labels will be positioned * alternately (on both sides of the point) at a distance of 100px. * * @sample {highcharts} highcharts/series-timeline/alternate-disabled * Alternate disabled */ alternate: true, backgroundColor: "#ffffff" /* Palette.backgroundColor */, borderWidth: 1, borderColor: "#999999" /* Palette.neutralColor40 */, borderRadius: 3, color: "#333333" /* Palette.neutralColor80 */, /** * The color of the line connecting the data label to the point. * The default color is the same as the point's color. * * In styled mode, the connector stroke is given in the * `.highcharts-data-label-connector` class. * * @sample {highcharts} highcharts/series-timeline/connector-styles * Custom connector width and color * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @apioption plotOptions.timeline.dataLabels.connectorColor */ /** * The width of the line connecting the data label to the point. * * In styled mode, the connector stroke width is given in the * `.highcharts-data-label-connector` class. * * @sample {highcharts} highcharts/series-timeline/connector-styles * Custom connector width and color */ connectorWidth: 1, /** * A pixel value defining the distance between the data label and * the point. Negative numbers puts the label on top of the point in a * non-inverted chart. Defaults to 100 for horizontal and 20 for * vertical timeline (`chart.inverted: true`). */ distance: void 0, // eslint-disable-next-line jsdoc/require-description /** * @type {Highcharts.TimelineDataLabelsFormatterCallbackFunction} * @default function () { * let format; * * if (!this.series.chart.styledMode) { * format = ''; * } else { * format = ''; * } * format += '' + (this.key || '') + '
' + * (this.point.label || ''); * return format; * } */ formatter: function () { let format; if (!this.series.chart.styledMode) { format = ''; } else { format = ''; } format += '' + (this.key || '') + '
' + (this.point.label || ''); return format; }, style: { /** @internal */ textOutline: 'none', /** @internal */ fontWeight: 'normal', /** @internal */ fontSize: '0.8em' }, /** * Shadow options for the data label. * * @type {boolean|Highcharts.CSSObject} */ shadow: false, /** * @type {number} * @apioption plotOptions.timeline.dataLabels.width */ verticalAlign: 'middle' }, marker: { enabledThreshold: 0, symbol: 'square', radius: 6, lineWidth: 2, height: 15 }, showInLegend: false, colorKey: 'x', legendSymbol: 'rectangle' }; /** * The `timeline` series. If the [type](#series.timeline.type) option is * not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.timeline * @excluding animationLimit, boostThreshold, connectEnds, connectNulls, * cropThreshold, dashStyle, dataParser, dataURL, findNearestPointBy, * getExtremesFromAll, negativeColor, pointInterval, * pointIntervalUnit, pointPlacement, pointStart, softThreshold, * stacking, stack, step, threshold, turboThreshold, zoneAxis, zones, * dataSorting, boostBlending * @product highcharts * @requires modules/timeline * @apioption series.timeline */ /** * An array of data points for the series. For the `timeline` series type, * points can be given with three general parameters, `name`, `label`, * and `description`: * * Example: * * ```js * series: [{ * type: 'timeline', * data: [{ * name: 'Jan 2018', * label: 'Some event label', * description: 'Description to show in tooltip' * }] * }] * ``` * If all points additionally have the `x` values, and xAxis type is set to * `datetime`, then events are laid out on a true time axis, where their * placement reflects the actual time between them. * * @sample {highcharts} highcharts/series-timeline/alternate-labels * Alternate labels * @sample {highcharts} highcharts/series-timeline/datetime-axis * Real time intervals * * @type {Array<*>} * @extends series.line.data * @excluding marker, y * @product highcharts * @apioption series.timeline.data */ /** * The name of event. * * @type {string} * @product highcharts * @apioption series.timeline.data.name */ /** * The label of event. * * @type {string} * @product highcharts * @apioption series.timeline.data.label */ /** * The description of event. This description will be shown in tooltip. * * @type {string} * @product highcharts * @apioption series.timeline.data.description */ ''; // Adds doclets above to transpiled file /* * * * Default Export * * */ return TimelineSeriesDefaults; }); _registerModule(_modules, 'Series/Timeline/TimelineSeries.js', [_modules['Core/Series/SeriesRegistry.js'], _modules['Series/Timeline/TimelinePoint.js'], _modules['Series/Timeline/TimelineSeriesDefaults.js'], _modules['Core/Utilities.js']], function (SeriesRegistry, TimelinePoint, TimelineSeriesDefaults, U) { /* * * * Timeline Series. * * (c) 2010-2024 Highsoft AS * * Author: Daniel Studencki * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { column: ColumnSeries, line: LineSeries } = SeriesRegistry.seriesTypes; const { addEvent, arrayMax, arrayMin, defined, extend, merge, pick } = U; /* * * * Class * * */ /** * The timeline series type. * * @private * @class * @name Highcharts.seriesTypes.timeline * * @augments Highcharts.Series */ class TimelineSeries extends LineSeries { /* * * * Functions * * */ alignDataLabel(point, dataLabel, _options, _alignTo) { const series = this, isInverted = series.chart.inverted, visiblePoints = series.visibilityMap.filter((point) => !!point), visiblePointsCount = series.visiblePointsCount || 0, pointIndex = visiblePoints.indexOf(point), isFirstOrLast = (!pointIndex || pointIndex === visiblePointsCount - 1), dataLabelsOptions = series.options.dataLabels, userDLOptions = point.userDLOptions || {}, // Define multiplier which is used to calculate data label // width. If data labels are alternate, they have two times more // space to adapt (excepting first and last ones, which has only // one and half), than in case of placing all data labels side // by side. multiplier = dataLabelsOptions.alternate ? (isFirstOrLast ? 1.5 : 2) : 1, availableSpace = Math.floor(series.xAxis.len / visiblePointsCount), pad = dataLabel.padding; let distance, targetDLWidth, styles; // Adjust data label width to the currently available space. if (point.visible) { distance = Math.abs(userDLOptions.x || point.options.dataLabels.x); if (isInverted) { targetDLWidth = ((distance - pad) * 2 - ((point.itemHeight || 0) / 2)); styles = { width: pick(dataLabelsOptions.style?.width, `${series.yAxis.len * 0.4}px`), // Apply ellipsis when data label height is exceeded. textOverflow: (dataLabel.width || 0) / targetDLWidth * (dataLabel.height || 0) / 2 > availableSpace * multiplier ? 'ellipsis' : 'none' }; } else { styles = { width: (userDLOptions.width || dataLabelsOptions.width || availableSpace * multiplier - (pad * 2)) + 'px' }; } dataLabel.css(styles); if (!series.chart.styledMode) { dataLabel.shadow(dataLabelsOptions.shadow); } } super.alignDataLabel.apply(series, arguments); } bindAxes() { const series = this; super.bindAxes(); // Initially set the linked xAxis type to category. if (!series.xAxis.userOptions.type) { series.xAxis.categories = series.xAxis.hasNames = true; } } distributeDL() { const series = this, dataLabelsOptions = series.options.dataLabels, inverted = series.chart.inverted; let visibilityIndex = 1; if (dataLabelsOptions) { const distance = pick(dataLabelsOptions.distance, inverted ? 20 : 100); for (const point of series.points) { const defaults = { [inverted ? 'x' : 'y']: dataLabelsOptions.alternate && visibilityIndex % 2 ? -distance : distance }; if (inverted) { defaults.align = (dataLabelsOptions.alternate && visibilityIndex % 2) ? 'right' : 'left'; } point.options.dataLabels = merge(defaults, point.userDLOptions); visibilityIndex++; } } } generatePoints() { super.generatePoints(); const series = this, points = series.points; for (let i = 0, iEnd = points.length; i < iEnd; ++i) { points[i].applyOptions({ x: series.xData[i] }, series.xData[i]); } } getVisibilityMap() { const series = this, map = (series.data.length ? series.data : series.userOptions.data || []).map((point) => (point && point.visible !== false && !point.isNull ? point : false)); return map; } getXExtremes(xData) { const series = this, filteredData = xData.filter((_x, i) => (series.points[i].isValid() && series.points[i].visible)); return { min: arrayMin(filteredData), max: arrayMax(filteredData) }; } init() { const series = this; super.init.apply(series, arguments); series.eventsToUnbind.push(addEvent(series, 'afterTranslate', function () { let lastPlotX, closestPointRangePx = Number.MAX_VALUE; for (const point of series.points) { // Set the isInside parameter basing also on the real point // visibility, in order to avoid showing hidden points // in drawPoints method. point.isInside = point.isInside && point.visible; // New way of calculating closestPointRangePx value, which // respects the real point visibility is needed. if (point.visible && !point.isNull) { if (defined(lastPlotX)) { closestPointRangePx = Math.min(closestPointRangePx, Math.abs(point.plotX - lastPlotX)); } lastPlotX = point.plotX; } } series.closestPointRangePx = closestPointRangePx; })); // Distribute data labels before rendering them. Distribution is // based on the 'dataLabels.distance' and 'dataLabels.alternate' // property. series.eventsToUnbind.push(addEvent(series, 'drawDataLabels', function () { // Distribute data labels basing on defined algorithm. series.distributeDL(); // @todo use this scope for series })); series.eventsToUnbind.push(addEvent(series, 'afterDrawDataLabels', function () { let dataLabel; // @todo use this scope for series // Draw or align connector for each point. for (const point of series.points) { dataLabel = point.dataLabel; if (dataLabel) { // Within this wrap method is necessary to save the // current animation params, because the data label // target position (after animation) is needed to align // connectors. dataLabel.animate = function (params) { if (this.targetPosition) { this.targetPosition = params; } return this.renderer.Element.prototype .animate.apply(this, arguments); }; // Initialize the targetPosition field within data label // object. It's necessary because there is need to know // expected position of specific data label, when // aligning connectors. This field is overridden inside // of SVGElement.animate() wrapped method. if (!dataLabel.targetPosition) { dataLabel.targetPosition = {}; } point.drawConnector(); } } })); series.eventsToUnbind.push(addEvent(series.chart, 'afterHideOverlappingLabel', function () { for (const p of series.points) { if (p.dataLabel && p.dataLabel.connector && p.dataLabel.oldOpacity !== p.dataLabel.newOpacity) { p.alignConnector(); } } })); } markerAttribs(point, state) { const series = this, seriesMarkerOptions = series.options.marker, pointMarkerOptions = point.marker || {}, symbol = (pointMarkerOptions.symbol || seriesMarkerOptions.symbol), width = pick(pointMarkerOptions.width, seriesMarkerOptions.width, series.closestPointRangePx), height = pick(pointMarkerOptions.height, seriesMarkerOptions.height); let seriesStateOptions, pointStateOptions, radius = 0; // Call default markerAttribs method, when the xAxis type // is set to datetime. if (series.xAxis.dateTime) { return super.markerAttribs(point, state); } // Handle hover and select states if (state) { seriesStateOptions = seriesMarkerOptions.states[state] || {}; pointStateOptions = pointMarkerOptions.states && pointMarkerOptions.states[state] || {}; radius = pick(pointStateOptions.radius, seriesStateOptions.radius, radius + (seriesStateOptions.radiusPlus || 0)); } point.hasImage = (symbol && symbol.indexOf('url') === 0); const attribs = { x: Math.floor(point.plotX) - (width / 2) - (radius / 2), y: point.plotY - (height / 2) - (radius / 2), width: width + radius, height: height + radius }; return (series.chart.inverted) ? { y: (attribs.x && attribs.width) && series.xAxis.len - attribs.x - attribs.width, x: attribs.y && attribs.y, width: attribs.height, height: attribs.width } : attribs; } processData() { const series = this; let visiblePoints = 0, i; series.visibilityMap = series.getVisibilityMap(); // Calculate currently visible points. for (const point of series.visibilityMap) { if (point) { visiblePoints++; } } series.visiblePointsCount = visiblePoints; for (i = 0; i < series.xData.length; i++) { series.yData[i] = 1; } super.processData.call(this, arguments); return; } } /* * * * Static Properties * * */ TimelineSeries.defaultOptions = merge(LineSeries.defaultOptions, TimelineSeriesDefaults); extend(TimelineSeries.prototype, { // Use a group of trackers from TrackerMixin drawTracker: ColumnSeries.prototype.drawTracker, pointClass: TimelinePoint, trackerGroups: ['markerGroup', 'dataLabelsGroup'] }); SeriesRegistry.registerSeriesType('timeline', TimelineSeries); /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * Callback JavaScript function to format the data label as a string. Note that * if a `format` is defined, the format takes precedence and the formatter is * ignored. * * @callback Highcharts.TimelineDataLabelsFormatterCallbackFunction * * @param {Highcharts.PointLabelObject|Highcharts.TimelineDataLabelsFormatterContextObject} this * Data label context to format * * @return {number|string|null|undefined} * Formatted data label text */ /** * @interface Highcharts.TimelineDataLabelsFormatterContextObject * @extends Highcharts.PointLabelObject */ /** * @name Highcharts.TimelineDataLabelsFormatterContextObject#key * @type {string|undefined} */ /** * @name Highcharts.TimelineDataLabelsFormatterContextObject#point * @type {Highcharts.Point} */ /** * @name Highcharts.TimelineDataLabelsFormatterContextObject#series * @type {Highcharts.Series} */ ''; // Dettach doclets above return TimelineSeries; }); _registerModule(_modules, 'masters/modules/timeline.src.js', [_modules['Core/Globals.js']], function (Highcharts) { return Highcharts; }); }));