highcharts
Version:
JavaScript charting framework
1,318 lines (1,317 loc) • 50.3 kB
JavaScript
/* *
*
* (c) 2009-2021 Highsoft, Black Label
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* */
'use strict';
import A from '../../Core/Animation/AnimationUtilities.js';
var getDeferredAnimation = A.getDeferredAnimation;
import Chart from '../../Core/Chart/Chart.js';
var chartProto = Chart.prototype;
import ControllableMixin from './Mixins/ControllableMixin.js';
import ControllableRect from './Controllables/ControllableRect.js';
import ControllableCircle from './Controllables/ControllableCircle.js';
import ControllablePath from './Controllables/ControllablePath.js';
import ControllableImage from './Controllables/ControllableImage.js';
import ControllableLabel from './Controllables/ControllableLabel.js';
import ControlPoint from './ControlPoint.js';
import EventEmitterMixin from './Mixins/EventEmitterMixin.js';
import H from '../../Core/Globals.js';
import MockPoint from './MockPoint.js';
import Pointer from '../../Core/Pointer.js';
import U from '../../Core/Utilities.js';
var addEvent = U.addEvent, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, erase = U.erase, extend = U.extend, find = U.find, fireEvent = U.fireEvent, merge = U.merge, pick = U.pick, splat = U.splat, wrap = U.wrap;
/* *********************************************************************
*
* ANNOTATION
*
******************************************************************** */
/**
* Possible directions for draggable annotations. An empty string (`''`)
* makes the annotation undraggable.
*
* @typedef {''|'x'|'xy'|'y'} Highcharts.AnnotationDraggableValue
*/
/**
* @private
* @typedef {
* Highcharts.AnnotationControllableCircle|
* Highcharts.AnnotationControllableImage|
* Highcharts.AnnotationControllablePath|
* Highcharts.AnnotationControllableRect
* }
* Highcharts.AnnotationShapeType
* @requires modules/annotations
*/
/**
* @private
* @typedef {
* Highcharts.AnnotationControllableLabel
* }
* Highcharts.AnnotationLabelType
* @requires modules/annotations
*/
/**
* A point-like object, a mock point or a point used in series.
* @private
* @typedef {Highcharts.AnnotationMockPoint|Highcharts.Point} Highcharts.AnnotationPointType
* @requires modules/annotations
*/
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
* An annotation class which serves as a container for items like labels or
* shapes. Created items are positioned on the chart either by linking them to
* existing points or created mock points
*
* @class
* @name Highcharts.Annotation
*
* @param {Highcharts.Chart} chart a chart instance
* @param {Highcharts.AnnotationsOptions} userOptions the options object
*/
var Annotation = /** @class */ (function () {
/* *
*
* Constructors
*
* */
function Annotation(chart, userOptions) {
/* *
*
* Properties
*
* */
this.annotation = void 0;
this.coll = 'annotations';
this.collection = void 0;
this.animationConfig = void 0;
this.graphic = void 0;
this.group = void 0;
this.labelCollector = void 0;
this.labelsGroup = void 0;
this.shapesGroup = void 0;
var labelsAndShapes;
/**
* The chart that the annotation belongs to.
*
* @type {Highcharts.Chart}
*/
this.chart = chart;
/**
* The array of points which defines the annotation.
*
* @type {Array<Highcharts.Point>}
*/
this.points = [];
/**
* The array of control points.
*
* @private
* @name Highcharts.Annotation#controlPoints
* @type {Array<Annotation.ControlPoint>}
*/
this.controlPoints = [];
this.coll = 'annotations';
/**
* The array of labels which belong to the annotation.
*
* @private
* @name Highcharts.Annotation#labels
* @type {Array<Highcharts.AnnotationLabelType>}
*/
this.labels = [];
/**
* The array of shapes which belong to the annotation.
*
* @private
* @name Highcharts.Annotation#shapes
* @type {Array<Highcharts.AnnotationShapeType>}
*/
this.shapes = [];
/**
* The options for the annotations.
*
* @name Highcharts.Annotation#options
* @type {Highcharts.AnnotationsOptions}
*/
this.options = merge(this.defaultOptions, userOptions);
/**
* The user options for the annotations.
*
* @name Highcharts.Annotation#userOptions
* @type {Highcharts.AnnotationsOptions}
*/
this.userOptions = userOptions;
// Handle labels and shapes - those are arrays
// Merging does not work with arrays (stores reference)
labelsAndShapes = this.getLabelsAndShapesOptions(this.options, userOptions);
this.options.labels = labelsAndShapes.labels;
this.options.shapes = labelsAndShapes.shapes;
/**
* The callback that reports to the overlapping-labels module which
* labels it should account for.
* @private
* @name Highcharts.Annotation#labelCollector
* @type {Function}
*/
/**
* The group svg element.
*
* @name Highcharts.Annotation#group
* @type {Highcharts.SVGElement}
*/
/**
* The group svg element of the annotation's shapes.
*
* @name Highcharts.Annotation#shapesGroup
* @type {Highcharts.SVGElement}
*/
/**
* The group svg element of the annotation's labels.
*
* @name Highcharts.Annotation#labelsGroup
* @type {Highcharts.SVGElement}
*/
this.init(chart, this.options);
}
/**
* Initialize the annotation.
* @private
*/
Annotation.prototype.init = function () {
var chart = this.chart, animOptions = this.options.animation;
this.linkPoints();
this.addControlPoints();
this.addShapes();
this.addLabels();
this.setLabelCollector();
this.animationConfig = getDeferredAnimation(chart, animOptions);
};
Annotation.prototype.getLabelsAndShapesOptions = function (baseOptions, newOptions) {
var mergedOptions = {};
['labels', 'shapes'].forEach(function (name) {
if (baseOptions[name]) {
mergedOptions[name] = splat(newOptions[name]).map(function (basicOptions, i) {
return merge(baseOptions[name][i], basicOptions);
});
}
});
return mergedOptions;
};
Annotation.prototype.addShapes = function () {
(this.options.shapes || []).forEach(function (shapeOptions, i) {
var shape = this.initShape(shapeOptions, i);
merge(true, this.options.shapes[i], shape.options);
}, this);
};
Annotation.prototype.addLabels = function () {
(this.options.labels || []).forEach(function (labelsOptions, i) {
var labels = this.initLabel(labelsOptions, i);
merge(true, this.options.labels[i], labels.options);
}, this);
};
Annotation.prototype.addClipPaths = function () {
this.setClipAxes();
if (this.clipXAxis && this.clipYAxis) {
this.clipRect = this.chart.renderer.clipRect(this.getClipBox());
}
};
Annotation.prototype.setClipAxes = function () {
var xAxes = this.chart.xAxis, yAxes = this.chart.yAxis, linkedAxes = (this.options.labels || [])
.concat(this.options.shapes || [])
.reduce(function (axes, labelOrShape) {
return [
xAxes[labelOrShape &&
labelOrShape.point &&
labelOrShape.point.xAxis] || axes[0],
yAxes[labelOrShape &&
labelOrShape.point &&
labelOrShape.point.yAxis] || axes[1]
];
}, []);
this.clipXAxis = linkedAxes[0];
this.clipYAxis = linkedAxes[1];
};
Annotation.prototype.getClipBox = function () {
if (this.clipXAxis && this.clipYAxis) {
return {
x: this.clipXAxis.left,
y: this.clipYAxis.top,
width: this.clipXAxis.width,
height: this.clipYAxis.height
};
}
};
Annotation.prototype.setLabelCollector = function () {
var annotation = this;
annotation.labelCollector = function () {
return annotation.labels.reduce(function (labels, label) {
if (!label.options.allowOverlap) {
labels.push(label.graphic);
}
return labels;
}, []);
};
annotation.chart.labelCollectors.push(annotation.labelCollector);
};
/**
* Set an annotation options.
* @private
* @param {Highcharts.AnnotationsOptions} - user options for an annotation
*/
Annotation.prototype.setOptions = function (userOptions) {
this.options = merge(this.defaultOptions, userOptions);
};
Annotation.prototype.redraw = function (animation) {
this.linkPoints();
if (!this.graphic) {
this.render();
}
if (this.clipRect) {
this.clipRect.animate(this.getClipBox());
}
this.redrawItems(this.shapes, animation);
this.redrawItems(this.labels, animation);
ControllableMixin.redraw.call(this, animation);
};
/**
* @private
* @param {Array<Highcharts.AnnotationControllable>} items
* @param {boolean} [animation]
*/
Annotation.prototype.redrawItems = function (items, animation) {
var i = items.length;
// needs a backward loop
// labels/shapes array might be modified
// due to destruction of the item
while (i--) {
this.redrawItem(items[i], animation);
}
};
/**
* @private
* @param {Array<Highcharts.AnnotationControllable>} items
*/
Annotation.prototype.renderItems = function (items) {
var i = items.length;
while (i--) {
this.renderItem(items[i]);
}
};
Annotation.prototype.render = function () {
var renderer = this.chart.renderer;
this.graphic = renderer
.g('annotation')
.attr({
opacity: 0,
zIndex: this.options.zIndex,
visibility: this.options.visible ?
'visible' :
'hidden'
})
.add();
this.shapesGroup = renderer
.g('annotation-shapes')
.add(this.graphic)
.clip(this.chart.plotBoxClip);
this.labelsGroup = renderer
.g('annotation-labels')
.attr({
// hideOverlappingLabels requires translation
translateX: 0,
translateY: 0
})
.add(this.graphic);
this.addClipPaths();
if (this.clipRect) {
this.graphic.clip(this.clipRect);
}
// Render shapes and labels before adding events (#13070).
this.renderItems(this.shapes);
this.renderItems(this.labels);
this.addEvents();
ControllableMixin.render.call(this);
};
/**
* Set the annotation's visibility.
* @private
* @param {boolean} [visible]
* Whether to show or hide an annotation. If the param is omitted, the
* annotation's visibility is toggled.
*/
Annotation.prototype.setVisibility = function (visible) {
var options = this.options, visibility = pick(visible, !options.visible);
this.graphic.attr('visibility', visibility ? 'visible' : 'hidden');
if (!visibility) {
this.setControlPointsVisibility(false);
}
options.visible = visibility;
};
Annotation.prototype.setControlPointsVisibility = function (visible) {
var setItemControlPointsVisibility = function (item) {
item.setControlPointsVisibility(visible);
};
ControllableMixin.setControlPointsVisibility.call(this, visible);
this.shapes.forEach(setItemControlPointsVisibility);
this.labels.forEach(setItemControlPointsVisibility);
};
/**
* Destroy the annotation. This function does not touch the chart
* that the annotation belongs to (all annotations are kept in
* the chart.annotations array) - it is recommended to use
* {@link Highcharts.Chart#removeAnnotation} instead.
* @private
*/
Annotation.prototype.destroy = function () {
var chart = this.chart, destroyItem = function (item) {
item.destroy();
};
this.labels.forEach(destroyItem);
this.shapes.forEach(destroyItem);
this.clipXAxis = null;
this.clipYAxis = null;
erase(chart.labelCollectors, this.labelCollector);
EventEmitterMixin.destroy.call(this);
ControllableMixin.destroy.call(this);
destroyObjectProperties(this, chart);
};
/**
* See {@link Highcharts.Chart#removeAnnotation}.
* @private
*/
Annotation.prototype.remove = function () {
// Let chart.update() remove annoations on demand
return this.chart.removeAnnotation(this);
};
/**
* Updates an annotation.
*
* @function Highcharts.Annotation#update
*
* @param {Partial<Highcharts.AnnotationsOptions>} userOptions
* New user options for the annotation.
*
* @return {void}
*/
Annotation.prototype.update = function (userOptions, redraw) {
var chart = this.chart, labelsAndShapes = this.getLabelsAndShapesOptions(this.userOptions, userOptions), userOptionsIndex = chart.annotations.indexOf(this), options = merge(true, this.userOptions, userOptions);
options.labels = labelsAndShapes.labels;
options.shapes = labelsAndShapes.shapes;
this.destroy();
this.constructor(chart, options);
// Update options in chart options, used in exporting (#9767):
chart.options.annotations[userOptionsIndex] = options;
this.isUpdating = true;
if (pick(redraw, true)) {
chart.redraw();
}
fireEvent(this, 'afterUpdate');
this.isUpdating = false;
};
/* *************************************************************
* ITEM SECTION
* Contains methods for handling a single item in an annotation
**************************************************************** */
/**
* Initialisation of a single shape
* @private
* @param {Object} shapeOptions - a confg object for a single shape
*/
Annotation.prototype.initShape = function (shapeOptions, index) {
var options = merge(this.options.shapeOptions, {
controlPointOptions: this.options.controlPointOptions
}, shapeOptions), shape = new Annotation.shapesMap[options.type](this, options, index);
shape.itemType = 'shape';
this.shapes.push(shape);
return shape;
};
/**
* Initialisation of a single label
* @private
*/
Annotation.prototype.initLabel = function (labelOptions, index) {
var options = merge(this.options.labelOptions, {
controlPointOptions: this.options.controlPointOptions
}, labelOptions), label = new ControllableLabel(this, options, index);
label.itemType = 'label';
this.labels.push(label);
return label;
};
/**
* Redraw a single item.
* @private
* @param {Annotation.Label|Annotation.Shape} item
* @param {boolean} [animation]
*/
Annotation.prototype.redrawItem = function (item, animation) {
item.linkPoints();
if (!item.shouldBeDrawn()) {
this.destroyItem(item);
}
else {
if (!item.graphic) {
this.renderItem(item);
}
item.redraw(pick(animation, true) && item.graphic.placed);
if (item.points.length) {
this.adjustVisibility(item);
}
}
};
/**
* Hide or show annotaiton attached to points.
* @private
* @param {Annotation.Label|Annotation.Shape} item
*/
Annotation.prototype.adjustVisibility = function (item) {
var hasVisiblePoints = false, label = item.graphic;
item.points.forEach(function (point) {
if (point.series.visible !== false &&
point.visible !== false) {
hasVisiblePoints = true;
}
});
if (!hasVisiblePoints) {
label.hide();
}
else if (label.visibility === 'hidden') {
label.show();
}
};
/**
* Destroy a single item.
* @private
* @param {Annotation.Label|Annotation.Shape} item
*/
Annotation.prototype.destroyItem = function (item) {
// erase from shapes or labels array
erase(this[item.itemType + 's'], item);
item.destroy();
};
/**
* @private
*/
Annotation.prototype.renderItem = function (item) {
item.render(item.itemType === 'label' ?
this.labelsGroup :
this.shapesGroup);
};
/**
* @private
*/
Annotation.ControlPoint = ControlPoint;
/**
* @private
*/
Annotation.MockPoint = MockPoint;
/**
* An object uses for mapping between a shape type and a constructor.
* To add a new shape type extend this object with type name as a key
* and a constructor as its value.
*/
Annotation.shapesMap = {
'rect': ControllableRect,
'circle': ControllableCircle,
'path': ControllablePath,
'image': ControllableImage
};
/**
* @private
*/
Annotation.types = {};
return Annotation;
}());
merge(true, Annotation.prototype, ControllableMixin, EventEmitterMixin,
// restore original Annotation implementation after mixin overwrite
merge(Annotation.prototype,
/** @lends Highcharts.Annotation# */
{
/**
* List of events for `annotation.options.events` that should not be
* added to `annotation.graphic` but to the `annotation`.
*
* @private
* @type {Array<string>}
*/
nonDOMEvents: ['add', 'afterUpdate', 'drag', 'remove'],
/**
* A basic type of an annotation. It allows to add custom labels
* or shapes. The items can be tied to points, axis coordinates
* or chart pixel coordinates.
*
* @sample highcharts/annotations/basic/
* Basic annotations
* @sample highcharts/demo/annotations/
* Advanced annotations
* @sample highcharts/css/annotations
* Styled mode
* @sample highcharts/annotations-advanced/controllable
* Controllable items
* @sample {highstock} stock/annotations/fibonacci-retracements
* Custom annotation, Fibonacci retracement
*
* @type {Array<*>}
* @since 6.0.0
* @requires modules/annotations
* @optionparent annotations
*
* @private
*/
defaultOptions: {
/**
* Sets an ID for an annotation. Can be user later when
* removing an annotation in [Chart#removeAnnotation(id)](
* /class-reference/Highcharts.Chart#removeAnnotation) method.
*
* @type {number|string}
* @apioption annotations.id
*/
/**
* Whether the annotation is visible.
*
* @sample highcharts/annotations/visible/
* Set annotation visibility
*/
visible: true,
/**
* Enable or disable the initial animation when a series is
* displayed for the `annotation`. The animation can also be set
* as a configuration object. Please note that this option only
* applies to the initial animation.
* For other animations, see [chart.animation](#chart.animation)
* and the animation parameter under the API methods.
* The following properties are supported:
*
* - `defer`: The animation delay time in milliseconds.
*
* @sample {highcharts} highcharts/annotations/defer/
* Animation defer settings
* @type {boolean|Partial<Highcharts.AnimationOptionsObject>}
* @since 8.2.0
* @apioption annotations.animation
*/
animation: {},
/**
* The animation delay time in milliseconds.
* Set to `0` renders annotation immediately.
* As `undefined` inherits defer time from the [series.animation.defer](#plotOptions.series.animation.defer).
*
* @type {number}
* @since 8.2.0
* @apioption annotations.animation.defer
*/
/**
* Allow an annotation to be draggable by a user. Possible
* values are `'x'`, `'xy'`, `'y'` and `''` (disabled).
*
* @sample highcharts/annotations/draggable/
* Annotations draggable: 'xy'
*
* @type {Highcharts.AnnotationDraggableValue}
*/
draggable: 'xy',
/**
* Options for annotation's labels. Each label inherits options
* from the labelOptions object. An option from the labelOptions
* can be overwritten by config for a specific label.
*
* @requires modules/annotations
*/
labelOptions: {
/**
* The alignment of the annotation's label. If right,
* the right side of the label should be touching the point.
*
* @sample highcharts/annotations/label-position/
* Set labels position
*
* @type {Highcharts.AlignValue}
*/
align: 'center',
/**
* Whether to allow the annotation's labels to overlap.
* To make the labels less sensitive for overlapping,
* the can be set to 0.
*
* @sample highcharts/annotations/tooltip-like/
* Hide overlapping labels
*/
allowOverlap: false,
/**
* The background color or gradient for the annotation's
* label.
*
* @sample highcharts/annotations/label-presentation/
* Set labels graphic options
*
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
*/
backgroundColor: 'rgba(0, 0, 0, 0.75)',
/**
* The border color for the annotation's label.
*
* @sample highcharts/annotations/label-presentation/
* Set labels graphic options
*
* @type {Highcharts.ColorString}
*/
borderColor: 'black',
/**
* The border radius in pixels for the annotaiton's label.
*
* @sample highcharts/annotations/label-presentation/
* Set labels graphic options
*/
borderRadius: 3,
/**
* The border width in pixels for the annotation's label
*
* @sample highcharts/annotations/label-presentation/
* Set labels graphic options
*/
borderWidth: 1,
/**
* A class name for styling by CSS.
*
* @sample highcharts/css/annotations
* Styled mode annotations
*
* @since 6.0.5
*/
className: '',
/**
* Whether to hide the annotation's label
* that is outside the plot area.
*
* @sample highcharts/annotations/label-crop-overflow/
* Crop or justify labels
*/
crop: false,
/**
* The label's pixel distance from the point.
*
* @sample highcharts/annotations/label-position/
* Set labels position
*
* @type {number}
* @apioption annotations.labelOptions.distance
*/
/**
* A
* [format](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
* string for the data label.
*
* @see [plotOptions.series.dataLabels.format](plotOptions.series.dataLabels.format.html)
*
* @sample highcharts/annotations/label-text/
* Set labels text
*
* @type {string}
* @apioption annotations.labelOptions.format
*/
/**
* Alias for the format option.
*
* @see [format](annotations.labelOptions.format.html)
*
* @sample highcharts/annotations/label-text/
* Set labels text
*
* @type {string}
* @apioption annotations.labelOptions.text
*/
/**
* Callback JavaScript function to format the annotation's
* label. Note that if a `format` or `text` are defined,
* the format or text take precedence and the formatter is
* ignored. `This` refers to a point object.
*
* @sample highcharts/annotations/label-text/
* Set labels text
*
* @type {Highcharts.FormatterCallbackFunction<Highcharts.Point>}
* @default function () { return defined(this.y) ? this.y : 'Annotation label'; }
*/
formatter: function () {
return defined(this.y) ? this.y : 'Annotation label';
},
/**
* Whether the annotation is visible in the exported data
* table.
*
* @sample highcharts/annotations/include-in-data-export/
* Do not include in the data export
*
* @since 8.2.0
* @requires modules/export-data
*/
includeInDataExport: true,
/**
* How to handle the annotation's label that flow outside
* the plot area. The justify option aligns the label inside
* the plot area.
*
* @sample highcharts/annotations/label-crop-overflow/
* Crop or justify labels
*
* @validvalue ["allow", "justify"]
*/
overflow: 'justify',
/**
* When either the borderWidth or the backgroundColor is
* set, this is the padding within the box.
*
* @sample highcharts/annotations/label-presentation/
* Set labels graphic options
*/
padding: 5,
/**
* The shadow of the box. The shadow can be an object
* configuration containing `color`, `offsetX`, `offsetY`,
* `opacity` and `width`.
*
* @sample highcharts/annotations/label-presentation/
* Set labels graphic options
*
* @type {boolean|Highcharts.ShadowOptionsObject}
*/
shadow: false,
/**
* The name of a symbol to use for the border around the
* label. Symbols are predefined functions on the Renderer
* object.
*
* @sample highcharts/annotations/shapes/
* Available shapes for labels
*/
shape: 'callout',
/**
* Styles for the annotation's label.
*
* @see [plotOptions.series.dataLabels.style](plotOptions.series.dataLabels.style.html)
*
* @sample highcharts/annotations/label-presentation/
* Set labels graphic options
*
* @type {Highcharts.CSSObject}
*/
style: {
/** @ignore */
fontSize: '11px',
/** @ignore */
fontWeight: 'normal',
/** @ignore */
color: 'contrast'
},
/**
* Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
* to render the annotation's label.
*/
useHTML: false,
/**
* The vertical alignment of the annotation's label.
*
* @sample highcharts/annotations/label-position/
* Set labels position
*
* @type {Highcharts.VerticalAlignValue}
*/
verticalAlign: 'bottom',
/**
* The x position offset of the label relative to the point.
* Note that if a `distance` is defined, the distance takes
* precedence over `x` and `y` options.
*
* @sample highcharts/annotations/label-position/
* Set labels position
*/
x: 0,
/**
* The y position offset of the label relative to the point.
* Note that if a `distance` is defined, the distance takes
* precedence over `x` and `y` options.
*
* @sample highcharts/annotations/label-position/
* Set labels position
*/
y: -16
},
/**
* An array of labels for the annotation. For options that apply
* to multiple labels, they can be added to the
* [labelOptions](annotations.labelOptions.html).
*
* @type {Array<*>}
* @extends annotations.labelOptions
* @apioption annotations.labels
*/
/**
* This option defines the point to which the label will be
* connected. It can be either the point which exists in the
* series - it is referenced by the point's id - or a new point
* with defined x, y properties and optionally axes.
*
* @sample highcharts/annotations/mock-point/
* Attach annotation to a mock point
*
* @declare Highcharts.AnnotationMockPointOptionsObject
* @type {string|*}
* @requires modules/annotations
* @apioption annotations.labels.point
*/
/**
* The x position of the point. Units can be either in axis
* or chart pixel coordinates.
*
* @type {number}
* @apioption annotations.labels.point.x
*/
/**
* The y position of the point. Units can be either in axis
* or chart pixel coordinates.
*
* @type {number}
* @apioption annotations.labels.point.y
*/
/**
* This number defines which xAxis the point is connected to.
* It refers to either the axis id or the index of the axis in
* the xAxis array. If the option is not configured or the axis
* is not found the point's x coordinate refers to the chart
* pixels.
*
* @type {number|string|null}
* @apioption annotations.labels.point.xAxis
*/
/**
* This number defines which yAxis the point is connected to.
* It refers to either the axis id or the index of the axis in
* the yAxis array. If the option is not configured or the axis
* is not found the point's y coordinate refers to the chart
* pixels.
*
* @type {number|string|null}
* @apioption annotations.labels.point.yAxis
*/
/**
* An array of shapes for the annotation. For options that apply
* to multiple shapes, then can be added to the
* [shapeOptions](annotations.shapeOptions.html).
*
* @type {Array<*>}
* @extends annotations.shapeOptions
* @apioption annotations.shapes
*/
/**
* This option defines the point to which the shape will be
* connected. It can be either the point which exists in the
* series - it is referenced by the point's id - or a new point
* with defined x, y properties and optionally axes.
*
* @declare Highcharts.AnnotationMockPointOptionsObject
* @type {string|Highcharts.AnnotationMockPointOptionsObject}
* @extends annotations.labels.point
* @apioption annotations.shapes.point
*/
/**
* An array of points for the shape. This option is available
* for shapes which can use multiple points such as path. A
* point can be either a point object or a point's id.
*
* @see [annotations.shapes.point](annotations.shapes.point.html)
*
* @declare Highcharts.AnnotationMockPointOptionsObject
* @type {Array<string|*>}
* @extends annotations.labels.point
* @apioption annotations.shapes.points
*/
/**
* The URL for an image to use as the annotation shape. Note,
* type has to be set to `'image'`.
*
* @see [annotations.shapes.type](annotations.shapes.type)
* @sample highcharts/annotations/shape-src/
* Define a marker image url for annotations
*
* @type {string}
* @apioption annotations.shapes.src
*/
/**
* Id of the marker which will be drawn at the final vertex of
* the path. Custom markers can be defined in defs property.
*
* @see [defs.markers](defs.markers.html)
*
* @sample highcharts/annotations/custom-markers/
* Define a custom marker for annotations
*
* @type {string}
* @apioption annotations.shapes.markerEnd
*/
/**
* Id of the marker which will be drawn at the first vertex of
* the path. Custom markers can be defined in defs property.
*
* @see [defs.markers](defs.markers.html)
*
* @sample {highcharts} highcharts/annotations/custom-markers/
* Define a custom marker for annotations
*
* @type {string}
* @apioption annotations.shapes.markerStart
*/
/**
* Options for annotation's shapes. Each shape inherits options
* from the shapeOptions object. An option from the shapeOptions
* can be overwritten by config for a specific shape.
*
* @requires modules/annotations
*/
shapeOptions: {
/**
* The width of the shape.
*
* @sample highcharts/annotations/shape/
* Basic shape annotation
*
* @type {number}
* @apioption annotations.shapeOptions.width
**/
/**
* The height of the shape.
*
* @sample highcharts/annotations/shape/
* Basic shape annotation
*
* @type {number}
* @apioption annotations.shapeOptions.height
*/
/**
* The type of the shape, e.g. circle or rectangle.
*
* @sample highcharts/annotations/shape/
* Basic shape annotation
*
* @type {string}
* @default rect
* @apioption annotations.shapeOptions.type
*/
/**
* The URL for an image to use as the annotation shape.
* Note, type has to be set to `'image'`.
*
* @see [annotations.shapeOptions.type](annotations.shapeOptions.type)
* @sample highcharts/annotations/shape-src/
* Define a marker image url for annotations
*
* @type {string}
* @apioption annotations.shapeOptions.src
*/
/**
* Name of the dash style to use for the shape's stroke.
*
* @sample {highcharts} highcharts/plotoptions/series-dashstyle-all/
* Possible values demonstrated
*
* @type {Highcharts.DashStyleValue}
* @apioption annotations.shapeOptions.dashStyle
*/
/**
* The color of the shape's stroke.
*
* @sample highcharts/annotations/shape/
* Basic shape annotation
*
* @type {Highcharts.ColorString}
*/
stroke: 'rgba(0, 0, 0, 0.75)',
/**
* The pixel stroke width of the shape.
*
* @sample highcharts/annotations/shape/
* Basic shape annotation
*/
strokeWidth: 1,
/**
* The color of the shape's fill.
*
* @sample highcharts/annotations/shape/
* Basic shape annotation
*
* @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
*/
fill: 'rgba(0, 0, 0, 0.75)',
/**
* The radius of the shape.
*
* @sample highcharts/annotations/shape/
* Basic shape annotation
*/
r: 0,
/**
* Defines additional snapping area around an annotation
* making this annotation to focus. Defined in pixels.
*/
snap: 2
},
/**
* Options for annotation's control points. Each control point
* inherits options from controlPointOptions object.
* Options from the controlPointOptions can be overwritten
* by options in a specific control point.
*
* @declare Highcharts.AnnotationControlPointOptionsObject
* @requires modules/annotations
* @apioption annotations.controlPointOptions
*/
controlPointOptions: {
/**
* @type {Highcharts.AnnotationControlPointPositionerFunction}
* @apioption annotations.controlPointOptions.positioner
*/
symbol: 'circle',
width: 10,
height: 10,
style: {
stroke: 'black',
'stroke-width': 2,
fill: 'white'
},
visible: false,
events: {}
},
/**
* Event callback when annotation is added to the chart.
*
* @type {Highcharts.EventCallbackFunction<Highcharts.Annotation>}
* @since 7.1.0
* @apioption annotations.events.add
*/
/**
* Event callback when annotation is updated (e.g. drag and
* droppped or resized by control points).
*
* @type {Highcharts.EventCallbackFunction<Highcharts.Annotation>}
* @since 7.1.0
* @apioption annotations.events.afterUpdate
*/
/**
* Event callback when annotation is removed from the chart.
*
* @type {Highcharts.EventCallbackFunction<Highcharts.Annotation>}
* @since 7.1.0
* @apioption annotations.events.remove
*/
/**
* Events available in annotations.
*
* @requires modules/annotations
*/
events: {},
/**
* The Z index of the annotation.
*/
zIndex: 6
}
}));
H.extendAnnotation = function (Constructor, BaseConstructor, prototype, defaultOptions) {
BaseConstructor = BaseConstructor || Annotation;
merge(true, Constructor.prototype, BaseConstructor.prototype, prototype);
Constructor.prototype.defaultOptions = merge(Constructor.prototype.defaultOptions, defaultOptions || {});
};
/* *********************************************************************
*
* EXTENDING CHART PROTOTYPE
*
******************************************************************** */
extend(chartProto, /** @lends Highcharts.Chart# */ {
initAnnotation: function (userOptions) {
var Constructor = Annotation.types[userOptions.type] || Annotation, annotation = new Constructor(this, userOptions);
this.annotations.push(annotation);
return annotation;
},
/**
* Add an annotation to the chart after render time.
*
* @param {Highcharts.AnnotationsOptions} options
* The annotation options for the new, detailed annotation.
* @param {boolean} [redraw]
*
* @return {Highcharts.Annotation} - The newly generated annotation.
*/
addAnnotation: function (userOptions, redraw) {
var annotation = this.initAnnotation(userOptions);
this.options.annotations.push(annotation.options);
if (pick(redraw, true)) {
annotation.redraw();
annotation.graphic.attr({
opacity: 1
});
}
return annotation;
},
/**
* Remove an annotation from the chart.
*
* @param {number|string|Highcharts.Annotation} idOrAnnotation
* The annotation's id or direct annotation object.
*/
removeAnnotation: function (idOrAnnotation) {
var annotations = this.annotations, annotation = idOrAnnotation.coll === 'annotations' ?
idOrAnnotation :
find(annotations, function (annotation) {
return annotation.options.id === idOrAnnotation;
});
if (annotation) {
fireEvent(annotation, 'remove');
erase(this.options.annotations, annotation.options);
erase(annotations, annotation);
annotation.destroy();
}
},
drawAnnotations: function () {
this.plotBoxClip.attr(this.plotBox);
this.annotations.forEach(function (annotation) {
annotation.redraw();
annotation.graphic.animate({
opacity: 1
}, annotation.animationConfig);
});
}
});
// Let chart.update() update annotations
chartProto.collectionsWithUpdate.push('annotations');
// Let chart.update() create annoations on demand
chartProto.collectionsWithInit.annotations = [chartProto.addAnnotation];
// Create lookups initially
addEvent(Chart, 'afterInit', function () {
this.annotations = [];
if (!this.options.annotations) {
this.options.annotations = [];
}
});
chartProto.callbacks.push(function (chart) {
chart.plotBoxClip = this.renderer.clipRect(this.plotBox);
chart.controlPointsGroup = chart.renderer
.g('control-points')
.attr({ zIndex: 99 })
.clip(chart.plotBoxClip)
.add();
chart.options.annotations.forEach(function (annotationOptions, i) {
if (
// Verify that it has not been previously added in a responsive rule
!chart.annotations.some(function (annotation) {
return annotation.options === annotationOptions;
})) {
var annotation = chart.initAnnotation(annotationOptions);
chart.options.annotations[i] = annotation.options;
}
});
chart.drawAnnotations();
addEvent(chart, 'redraw', chart.drawAnnotations);
addEvent(chart, 'destroy', function () {
chart.plotBoxClip.destroy();
chart.controlPointsGroup.destroy();
});
addEvent(chart, 'exportData', function (event) {
var _a, _b, _c, _d, _e, _f, _g, _h;
var annotations = chart.annotations, csvColumnHeaderFormatter = ((this.options.exporting &&
this.options.exporting.csv) ||
{}).columnHeaderFormatter,
// If second row doesn't have xValues
// then it is a title row thus multiple level header is in use.
multiLevelHeaders = !event.dataRows[1].xValues, annotationHeader = (_b = (_a = chart.options.lang) === null || _a === void 0 ? void 0 : _a.exportData) === null || _b === void 0 ? void 0 : _b.annotationHeader, columnHeaderFormatter = function (index) {
var s;
if (csvColumnHeaderFormatter) {
s = csvColumnHeaderFormatter(index);
if (s !== false) {
return s;
}
}
s = annotationHeader + ' ' + index;
if (multiLevelHeaders) {
return {
columnTitle: s,
topLevelColumnTitle: s
};
}
return s;
}, startRowLength = event.dataRows[0].length, annotationSeparator = (_e = (_d = (_c = chart.options.exporting) === null || _c === void 0 ? void 0 : _c.csv) === null || _d === void 0 ? void 0 : _d.annotations) === null || _e === void 0 ? void 0 : _e.itemDelimiter, joinAnnotations = (_h = (_g = (_f = chart.options.exporting) === null || _f === void 0 ? void 0 : _f.csv) === null || _g === void 0 ? void 0 : _g.annotations) === null || _h === void 0 ? void 0 : _h.join;
annotations.forEach(function (annotation) {
if (annotation.options.labelOptions.includeInDataExport) {
annotation.labels.forEach(function (label) {
if (label.options.text) {
var annotationText_1 = label.options.text;
label.points.forEach(function (points) {
var annotationX = points.x, xAxisIndex = points.series.xAxis ?
points.series.xAxis.options.index :
-1;
var wasAdded = false;
// Annotation not connected to any xAxis -
// add new row.
if (xAxisIndex === -1) {
var n = event.dataRows[0].length, newRow = new Array(n);
for (var i = 0; i < n; ++i) {
newRow[i] = '';
}
newRow.push(annotationText_1);
newRow.xValues = [];
newRow.xValues[xAxisIndex] = annotationX;
event.dataRows.push(newRow);
wasAdded = true;
}
// Annotation placed on a exported data point
// - add new column
if (!wasAdded) {
event.dataRows.forEach(function (row, rowIndex) {
if (!wasAdded &&
row.xValues &&
xAxisIndex !== void 0 &&
annotationX === row.xValues[xAxisIndex]) {
if (joinAnnotations &&
row.length > startRowLength) {
row[row.length - 1] +=
annotationSeparator + annotationText_1;
}
else {
row.push(annotationText_1);
}
wasAdded = true;
}
});
}
// Annotation not placed on any exported data point,
// but connected to the xAxis - add new row
if (!wasAdded) {
var n = event.dataRows[0].length, newRow = new Array(n);
for (var i = 0; i < n; ++i) {
newRow[i] = '';
}
newRow[0] = annotationX;
newRow.push(annotationText_1);
newRow.xValues = [];
if (xAxisIndex !== void 0) {
newRow.xValues[xAxisIndex] = annotationX;
}
event.dataRows.push(newRow);
}
});
}
});
}
});
var maxRowLen = 0;
event.dataRows.forEach(function (row) {
maxRowLen = Math.max(maxRowLen, row.length);
});
var newRows = maxRowLen - event.dataRows[0].length;
for (var i = 0; i < newRows; i++) {
var header = columnHeaderFormatter(i + 1);
if (multiLevelHeaders) {
event.dataRows[0].push(header.topLevelColumnTitle);
event.dataRows[1].push(header.columnTitle);
}