package.modules.timeline.src.js Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of highcharts Show documentation
Show all versions of highcharts Show documentation
JavaScript charting framework
The newest version!
/**
* @license Highcharts JS v11.4.8 (2024-08-29)
*
* 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') {
Highcharts.win.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;
});
}));