| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627162816291630163116321633163416351636163716381639164016411642164316441645164616471648164916501651165216531654165516561657165816591660166116621663166416651666166716681669167016711672167316741675167616771678167916801681168216831684168516861687168816891690169116921693169416951696169716981699170017011702170317041705170617071708170917101711171217131714171517161717171817191720172117221723172417251726172717281729173017311732173317341735173617371738173917401741174217431744174517461747174817491750175117521753175417551756175717581759176017611762176317641765176617671768176917701771177217731774177517761777177817791780178117821783178417851786178717881789179017911792179317941795179617971798179918001801180218031804180518061807180818091810181118121813181418151816181718181819182018211822182318241825182618271828182918301831183218331834183518361837183818391840184118421843184418451846184718481849185018511852185318541855185618571858185918601861186218631864186518661867186818691870187118721873187418751876187718781879188018811882188318841885188618871888188918901891189218931894189518961897189818991900190119021903190419051906190719081909191019111912191319141915191619171918191919201921192219231924192519261927192819291930193119321933193419351936193719381939194019411942 | /* * * (c) 2009-2017 Highsoft, Black Label * * License: www.highcharts.com/license *//** * @interface Highcharts.AnnotationAnchorObject *//** * Relative to the plot area position * @name Highcharts.AnnotationAnchorObject#relativePosition * @type {Highcharts.AnnotationAnchorPositionObject} *//** * Absolute position * @name Highcharts.AnnotationAnchorObject#absolutePosition * @type {Highcharts.AnnotationAnchorPositionObject} *//** * An object which denotes an anchor position * * @interface Highcharts.AnnotationAnchorPositionObject *//** * @name Highcharts.AnnotationAnchorPositionObject#x * @property {number} *//** * @name Highcharts.AnnotationAnchorPositionObject#y * @property {number} *//** * @name Highcharts.AnnotationAnchorPositionObject#height * @property {number} *//** * @name Highcharts.AnnotationAnchorPositionObject#width * @property {number} *//** * A mock point label configuration. * * @private * @interface Highcharts.MockLabelOptionsObject *//** * X value translated to x axis scale * @name Highcharts.MockLabelOptionsObject#x * @type {number|undefined} *//** * Y value translated to y axis scale * @name Highcharts.MockLabelOptionsObject#y * @type {number|undefined} *//** * @name Highcharts.MockLabelOptionsObject#point * @type {Highcharts.Point} *//** * A mock point configuration. * * @todo Make official * * @private * @interface Highcharts.MockPointOptionsObject *//** * x value for the point in xAxis scale or pixels * @name Highcharts.MockPointOptionsObject#x * @type {number} *//** * y value for the point in yAxis scale or pixels * @name Highcharts.MockPointOptionsObject#y * @type {number} *//** * xAxis index or id * @name Highcharts.MockPointOptionsObject#xAxis * @type {number|string|undefined} *//** * yAxis index or id * @name Highcharts.MockPointOptionsObject#yAxis * @property {number|string|undefined} */'use strict';import H from '../parts/Globals.js';import '../parts/Utilities.js';import '../parts/Chart.js';import '../parts/Series.js';import '../parts/Tooltip.js';var merge = H.merge,    addEvent = H.addEvent,    extend = H.extend,    isString = H.isString,    isNumber = H.isNumber,    defined = H.defined,    isObject = H.isObject,    erase = H.erase,    find = H.find,    format = H.format,    pick = H.pick,    objectEach = H.objectEach,    uniqueKey = H.uniqueKey,    destroyObjectProperties = H.destroyObjectProperties,    tooltipPrototype = H.Tooltip.prototype,    seriesPrototype = H.Series.prototype,    chartPrototype = H.Chart.prototype;/* ************************************************************************** * * *   MARKER SECTION *   Contains objects and functions for adding a marker element to a path *   element * * ************************************************************************** *//** * Options for configuring markers for annotations. * * An example of the arrow marker: * ``` * { *   arrow: { *     id: 'arrow', *     tagName: 'marker', *     refY: 5, *     refX: 5, *     markerWidth: 10, *     markerHeight: 10, *     children: [{ *       tagName: 'path', *       attrs: { *         d: 'M 0 0 L 10 5 L 0 10 Z', *         strokeWidth: 0 *       } *     }] *   } * } * ``` * * @sample highcharts/annotations/custom-markers/ *         Define a custom marker for annotations * @sample highcharts/css/annotations-markers/ *         Define markers in a styled mode * * @type      {Highcharts.Dictionary<Highcharts.SVGAttributes>} * @since     6.0.0 * @apioption defs */var defaultMarkers = {    arrow: {        tagName: 'marker',        render: false,        id: 'arrow',        refY: 5,        refX: 5,        markerWidth: 10,        markerHeight: 10,        children: [{            tagName: 'path',            d: 'M 0 0 L 10 5 L 0 10 Z', // triangle (used as an arrow)            strokeWidth: 0        }]    }};var MarkerMixin = {    markerSetter: function (markerType) {        return function (value) {            this.attr(markerType, 'url(#' + value + ')');        };    }};extend(MarkerMixin, {    markerEndSetter: MarkerMixin.markerSetter('marker-end'),    markerStartSetter: MarkerMixin.markerSetter('marker-start')});H.SVGRenderer.prototype.addMarker = function (id, markerOptions) {    var options = { id: id };    if (!this.styledMode) {        var attrs = {            stroke: markerOptions.color || 'none',            fill: markerOptions.color || 'rgba(0, 0, 0, 0.75)'        };        options.children = markerOptions.children.map(function (child) {            return merge(attrs, child);        });    }    var marker = this.definition(merge({        markerWidth: 20,        markerHeight: 20,        refX: 0,        refY: 0,        orient: 'auto'    }, markerOptions, options));    marker.id = id;    return marker;};/* ************************************************************************** * * *   MOCK POINT * * ************************************************************************** *//** * A trimmed point object which imitates {@link Highchart.Point} class. * It is created when there is a need of pointing to some chart's position * using axis values or pixel values * * @private * @class * @name Highcharts.MockPoint * * @param {Highcharts.Chart} chart *        The chart object * * @param {Highcharts.MockPointOptionsObject} options *        The options object */var MockPoint = H.MockPoint = function (chart, options) {    this.mock = true;    this.series = {        visible: true,        chart: chart,        getPlotBox: seriesPrototype.getPlotBox    };    // this.plotX    // this.plotY    /* Those might not exist if a specific axis was not found/defined */    // this.x?    // this.y?    this.init(chart, options);};/** * A factory function for creating a mock point object * * @function Highcharts.MockPoint#mockPoint * * @param {Highcharts.MockPointOptionsObject} mockPointOptions * * @return {Highcharts.MockPoint} *         A mock point */var mockPoint = H.mockPoint = function (chart, mockPointOptions) {    return new MockPoint(chart, mockPointOptions);};/** * @private * @class * @name Highcharts.MockPoint */MockPoint.prototype = {    /**     * Initialisation of the mock point     *     * @function Highcharts.MockPoint#init     *     * @param {Highcharts.Chart} chart     *        A chart object to which the mock point is attached     *     * @param {Highcharts.MockPointOptionsObject} options     *        A config for the mock point     */    init: function (chart, options) {        var xAxisId = options.xAxis,            xAxis = defined(xAxisId) ?                chart.xAxis[xAxisId] || chart.get(xAxisId) :                null,            yAxisId = options.yAxis,            yAxis = defined(yAxisId) ?                chart.yAxis[yAxisId] || chart.get(yAxisId) :                null;        if (xAxis) {            this.x = options.x;            this.series.xAxis = xAxis;        } else {            this.plotX = options.x;        }        if (yAxis) {            this.y = options.y;            this.series.yAxis = yAxis;        } else {            this.plotY = options.y;        }    },    /**     * Update of the point's coordinates (plotX/plotY)     *     * @function Highcharts.MockPoint#translate     */    translate: function () {        var series = this.series,            xAxis = series.xAxis,            yAxis = series.yAxis;        if (xAxis) {            this.plotX = xAxis.toPixels(this.x, true);        }        if (yAxis) {            this.plotY = yAxis.toPixels(this.y, true);        }        this.isInside = this.isInsidePane();    },    /**     * Returns a box to which an item can be aligned to     *     * @function Highcharts.MockPoint#alignToBox     *     * @param {boolean} [forceTranslate=false]     *        Whether to update the point's coordinates     *     * @return {Array<number,number,number,number>}     *         A quadruple of numbers which denotes x, y, width and height of     *         the box    **/    alignToBox: function (forceTranslate) {        if (forceTranslate) {            this.translate();        }        var x = this.plotX,            y = this.plotY,            temp;        if (this.series.chart.inverted) {            temp = x;            x = y;            y = temp;        }        return [x, y, 0, 0];    },    /**     * Returns a label config object -     * the same as Highcharts.Point.prototype.getLabelConfig     *     * @function Highcharts.MockPoint#getLabelConfig     *     * @return {Highcharts.MockLabelOptionsObject}     *         Label config object     */    getLabelConfig: function () {        return {            x: this.x,            y: this.y,            point: this        };    },    isInsidePane: function () {        var plotX = this.plotX,            plotY = this.plotY,            xAxis = this.series.xAxis,            yAxis = this.series.yAxis,            isInside = true;        if (xAxis) {            isInside = defined(plotX) && plotX >= 0 && plotX <= xAxis.len;        }        if (yAxis) {            isInside =                isInside &&                defined(plotY) &&                plotY >= 0 && plotY <= yAxis.len;        }        return isInside;    }};/* ************************************************************************** * * *   ANNOTATION * * ************************************************************************** */H.defaultOptions.annotations = [];/** * 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 *        The chart object * * @param {Highcharts.AnnotationsOptions} userOptions *        The options object */var Annotation = H.Annotation = function (chart, userOptions) {    /**     * The chart that the annotation belongs to.     *     * @name Highcharts.Annotation#chart     * @type {Highcharts.Chart}     */    this.chart = chart;    /**     * The array of labels which belong to the annotation.     *     * @name Highcharts.Annotation#labels     * @type {Array<Highcharts.SVGElement>}     */    this.labels = [];    /**     * The array of shapes which belong to the annotation.     *     * @name Highcharts.Annotation#shapes     * @type {Array<Highcharts.SVGElement>}     */    this.shapes = [];    /**     * The user options for the annotations.     *     * @name Highcharts.Annotation#userOptions     * @type {Highcharts.AnnotationsOptions}     */    this.userOptions = userOptions;    /**     * The options for the annotations. It contains user defined options     * merged with the default options.     *     * @name Highcharts.Annotation#options     * @type {Highcharts.AnnotationsOptions}     */    this.options = merge(this.defaultOptions, userOptions);    /**     * The callback that reports to the overlapping-labels module which     * labels it should account for.     *     * @todo Needs more specific function interface     *     * @private     * @name Highcharts.Annotation#labelCollector     * @type {Function}     */    /**     * The group element of the annotation.     *     * @private     * @name Highcharts.Annotation#group     * @type {Highcharts.SVGElement}     */    /**     * The group element of the annotation's shapes.     *     * @private     * @name Highcharts.Annotation#shapesGroup     * @type {Highcharts.SVGElement}     */    /**     * The group element of the annotation's labels.     *     * @private     * @name Highcharts.Annotation#labelsGroup     * @type {Highcharts.SVGElement}     */    this.init(chart, userOptions);};Annotation.prototype = {    /**     * Shapes which do not have background - the object is used for proper     * setting of the contrast color     *     * @private     * @name Highcharts.Annotations#shapesWithoutBackground     * @type {Array<string>}     */    shapesWithoutBackground: ['connector'],    /**     * Returns a map object which allows to map options attributes to element     * attributes.     *     * @private     * @function Highcharts.Annotations#getAttrsMap     *     * @return {object}     */    getAttrsMap: function () {        var attrsMap = {            zIndex: 'zIndex',            width: 'width',            height: 'height',            borderRadius: 'r',            r: 'r',            padding: 'padding'        };        if (!this.chart.styledMode) {            extend(attrsMap, {                backgroundColor: 'fill',                borderColor: 'stroke',                borderWidth: 'stroke-width',                dashStyle: 'dashstyle',                strokeWidth: 'stroke-width',                stroke: 'stroke',                fill: 'fill'            });        }        return attrsMap;    },    /**     * Options for configuring annotations, for example labels, arrows or     * shapes. Annotations 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 {highstock} stock/annotations/fibonacci-retracements     *         Custom annotation, Fibonacci retracement     *     * @type         {Array<*>}     * @since        6.0.0     * @optionparent annotations     */    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      {string}         * @apioption annotations.id         */        /**         * Whether the annotation is visible.         *         * @sample highcharts/annotations/visible/         *         Set annotation visibility         */        visible: true,        /**         * 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.         */        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.AlignType}             */            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';            },            /**             * 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             *             * @type {string}             */            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.VerticalAlignType}             */            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         * properies and optionally axes.         *         * @sample highcharts/annotations/mock-point/         *         Attach annotation to a mock point         *         * @type      {string|*}         * @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}         * @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}         * @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.         *         * @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)         *         * @type      {Array<*>}         * @extends   annotations.labels.point         * @apioption annotations.shapes.points         */        /**         * 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.         */        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 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 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 radius of the shape.             *             * @sample highcharts/annotations/shape/             *         Basic shape annotation             */            r: 0        },        /**         * The Z index of the annotation.         */        zIndex: 6    },    /**     * Initialize the annotation.     *     * @function Highcharts.Annotation#init     */    init: function () {        var anno = this;        (this.options.labels || []).forEach(this.initLabel, this);        (this.options.shapes || []).forEach(this.initShape, this);        this.labelCollector = function () {            return anno.labels.filter(function (label) {                return !label.options.allowOverlap;            });        };        this.chart.labelCollectors.push(this.labelCollector);    },    /**     * Main method for drawing an annotation.     *     * @function Highcharts.Annotation#redraw     */    redraw: function () {        if (!this.group) {            this.render();        }        this.redrawItems(this.shapes);        this.redrawItems(this.labels);    },    /**     * @private     * @function Highcharts.Annotation#redrawItems     *     * @param {Array<Highcharts.SVGElement>} items     */    redrawItems: function (items) {        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]);        }    },    /**     * Render the annotation.     *     * @function Highcharts.Annotation#render     */    render: function () {        var renderer = this.chart.renderer;        var group = this.group = renderer.g('annotation')            .attr({                zIndex: this.options.zIndex,                visibility: this.options.visible ? 'visible' : 'hidden'            })            .add();        this.shapesGroup = renderer.g('annotation-shapes').add(group);        this.labelsGroup = renderer.g('annotation-labels').attr({            // hideOverlappingLabels requires translation            translateX: 0,            translateY: 0        }).add(group);        this.shapesGroup.clip(this.chart.plotBoxClip);    },    /**     * Set the annotation's visibility.     *     * @function Highcharts.Annotation#setVisible     *     * @param {boolean} [visibility]     *        Whether to show or hide an annotation. If the param is omitted,     *        the annotation's visibility is toggled.     */    setVisible: function (visibility) {        var options = this.options,            visible = pick(visibility, !options.visible);        this.group.attr({            visibility: visible ? 'visible' : 'hidden'        });        options.visible = visible;    },    /**     * 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.     *     * @function Highcharts.Annotation#destroy     */    destroy: function () {        var chart = this.chart,            destroyItem = function (item) {                item.destroy();            };        erase(this.chart.labelCollectors, this.labelCollector);        this.labels.forEach(destroyItem);        this.shapes.forEach(destroyItem);        destroyObjectProperties(this, chart);    },    /* ********************************************************************** *     *     *   ITEM SECTION     *   Contains methods for handling a single item in an annotation     *     * ********************************************************************** */    /**     * Initialisation of a single shape     *     * @private     * @function Highcharts.Annotation#initShape     *     * @param {Highcharts.AnnotationsShapesOptions} shapeOptions     *        A confg object for a single shape     */    initShape: function (shapeOptions) {        var renderer = this.chart.renderer,            options = merge(this.options.shapeOptions, shapeOptions),            attr = this.attrsFromOptions(options),            type = renderer[options.type] ? options.type : 'rect',            shape = renderer[type](0, -9e9, 0, 0);        shape.points = [];        shape.type = type;        shape.options = options;        shape.itemType = 'shape';        if (type === 'path') {            extend(shape, {                markerStartSetter: MarkerMixin.markerStartSetter,                markerEndSetter: MarkerMixin.markerEndSetter,                markerStart: MarkerMixin.markerStart,                markerEnd: MarkerMixin.markerEnd            });        }        shape.attr(attr);        if (options.className) {            shape.addClass(options.className);        }        this.shapes.push(shape);    },    /**     * Initialisation of a single label     *     * @private     * @function Highcharts.Annotation#initLabel     *     * @param {Highcharts.AnnotationsLabelsOptions} labelOptions     */    initLabel: function (labelOptions) {        var options = merge(this.options.labelOptions, labelOptions),            attr = this.attrsFromOptions(options),            label = this.chart.renderer.label(                '',                0, -9e9,                options.shape,                null,                null,                options.useHTML,                null,                'annotation-label'            );        label.points = [];        label.options = options;        label.itemType = 'label';        // Labelrank required for hideOverlappingLabels()        label.labelrank = options.labelrank;        label.annotation = this;        label.attr(attr);        if (!this.chart.styledMode) {            var style = options.style;            if (style.color === 'contrast') {                style.color = this.chart.renderer.getContrast(                    this.shapesWithoutBackground.indexOf(options.shape) > -1 ?                        '#FFFFFF' :                        options.backgroundColor                );            }            label.css(style).shadow(options.shadow);        }        if (options.className) {            label.addClass(options.className);        }        this.labels.push(label);    },    /**     * Redrawing a single item     *     * @private     * @function Highcharts.Annotation#redrawItem     *     * @param {Highcharts.SVGElement} item     */    redrawItem: function (item) {        var points = this.linkPoints(item),            itemOptions = item.options,            text,            time = this.chart.time,            hasVisiblePoints = false;        if (!points.length) {            this.destroyItem(item);        } else {            if (!item.parentGroup) {                this.renderItem(item);            }            if (item.itemType === 'label') {                text = itemOptions.format || itemOptions.text;                item.attr({                    text: text ?                        format(text, points[0].getLabelConfig(), time) :                        itemOptions.formatter.call(points[0])                });            }            // Hide or show annotaiton attached to points (#9481)            points.forEach(function (point) {                if (                    point.series.visible !== false &&                    point.visible !== false                ) {                    hasVisiblePoints = true;                }            });            if (!hasVisiblePoints) {                item.hide();            } else if (item.visibility === 'hidden') {                item.show();            }            if (item.type === 'path') {                this.redrawPath(item);            } else {                this.alignItem(item, !item.placed);            }        }    },    /**     * Destroing a single item     *     * @private     * @function Highcharts.Annotation#destroyItem     *     * @param {Highcharts.SVGElement} item     */    destroyItem: function (item) {        // erase from shapes or labels array        erase(this[item.itemType + 's'], item);        item.destroy();    },    /**     * Returns a point object     *     * @private     * @function Highcharts.Annotation#pointItem     *     * @param {object} pointOptions     *     * @param {Highcharts.MockPoint|Highcharts.Point} point     *     * @return {Highcharts.MockPoint|Highcharts.Point|null}     *         If the point is found/exists returns this point, otherwise null     */    pointItem: function (pointOptions, point) {        if (!point || point.series === null) {            if (isObject(pointOptions)) {                point = mockPoint(this.chart, pointOptions);            } else if (isString(pointOptions)) {                point = this.chart.get(pointOptions) || null;            }        }        return point;    },    /**     * Linking item with the point or points and returning an array of linked     * points.     *     * @private     * @function Highcharts.Annotation#linkPoints     *     * @param {Highcharts.SVGElement} item     *     * @return {Highcharts.MockPoint|Highcharts.Point|Array<Highcharts.MockPoint|Highcharts.Point>}     */    linkPoints: function (item) {        var pointsOptions = (                item.options.points ||                (item.options.point && H.splat(item.options.point))            ),            points = item.points,            len = pointsOptions && pointsOptions.length,            i,            point;        for (i = 0; i < len; i++) {            point = this.pointItem(pointsOptions[i], points[i]);            if (!point) {                return (item.points = []);            }            points[i] = point;        }        return points;    },    /**     * Aligning the item and setting its anchor     *     * @private     * @function Highcharts.Annotation#alignItem     *     * @param {Highcharts.SVGElement} item     *     * @param {boolean} [isNew=false]     *        If the label is re-positioned (is not new) it is animated     */    alignItem: function (item, isNew) {        var anchor = this.itemAnchor(item, item.points[0]),            attrs = this.itemPosition(item, anchor);        if (attrs) {            item.alignAttr = attrs;            item.placed = true;            attrs.anchorX = anchor.absolutePosition.x;            attrs.anchorY = anchor.absolutePosition.y;            item[isNew ? 'attr' : 'animate'](attrs);        } else {            item.placed = false;            item.attr({                x: 0,                y: -9e9            });        }    },    redrawPath: function (pathItem, isNew) {        var points = pathItem.points,            strokeWidth = pathItem['stroke-width'] || 1,            d = ['M'],            pointIndex = 0,            dIndex = 0,            len = points && points.length,            crispSegmentIndex,            anchor,            point,            showPath;        if (len) {            do {                point = points[pointIndex];                anchor = this.itemAnchor(pathItem, point).absolutePosition;                d[++dIndex] = anchor.x;                d[++dIndex] = anchor.y;                // Crisping line, it might be replaced with                // Renderer.prototype.crispLine but it requires creating many                // temporary arrays                crispSegmentIndex = dIndex % 5;                if (crispSegmentIndex === 0) {                    if (d[crispSegmentIndex + 1] === d[crispSegmentIndex + 4]) {                        d[crispSegmentIndex + 1] = d[crispSegmentIndex + 4] =                            Math.round(d[crispSegmentIndex + 1]) -                            (strokeWidth % 2 / 2);                    }                    if (d[crispSegmentIndex + 2] === d[crispSegmentIndex + 5]) {                        d[crispSegmentIndex + 2] = d[crispSegmentIndex + 5] =                            Math.round(d[crispSegmentIndex + 2]) +                            (strokeWidth % 2 / 2);                    }                }                if (pointIndex < len - 1) {                    d[++dIndex] = 'L';                }                showPath = point.series.visible;            } while (++pointIndex < len && showPath);        }        if (showPath) {            pathItem[isNew ? 'attr' : 'animate']({                d: d            });        } else {            pathItem.attr({                d: 'M 0 ' + -9e9            });        }        pathItem.placed = showPath;    },    renderItem: function (item) {        item.add(            item.itemType === 'label' ?                this.labelsGroup :                this.shapesGroup        );        this.setItemMarkers(item);    },    setItemMarkers: function (item) {        var itemOptions = item.options,            chart = this.chart,            defs = chart.options.defs,            fill = itemOptions.fill,            color = defined(fill) && fill !== 'none' ?                fill :                itemOptions.stroke,            setMarker = function (markerType) {                var markerId = itemOptions[markerType],                    def,                    predefinedMarker,                    key,                    marker;                if (markerId) {                    for (key in defs) {                        def = defs[key];                        if (markerId === def.id && def.tagName === 'marker') {                            predefinedMarker = def;                            break;                        }                    }                    if (predefinedMarker) {                        marker = item[markerType] = chart.renderer.addMarker(                            (itemOptions.id || uniqueKey()) + '-' +                                predefinedMarker.id,                            merge(predefinedMarker, { color: color })                        );                        item.attr(markerType, marker.attr('id'));                    }                }            };        ['markerStart', 'markerEnd'].forEach(setMarker);    },    /**     * Returns object which denotes anchor position - relative and absolute     *     * @private     * @function Highcharts.Annotation#itemAnchor     *     * @param {Highcharts.SVGElement} item     *     * @param {Highcharts.MockPoint|Highcharts.Point} point     *     * @return {Highcharts.AnnotationAnchorObject}     */    itemAnchor: function (item, point) {        var plotBox = point.series.getPlotBox(),            box = point.mock ?                point.alignToBox(true) :                tooltipPrototype.getAnchor.call({                    chart: this.chart                }, point),            anchor = {                x: box[0],                y: box[1],                height: box[2] || 0,                width: box[3] || 0            };        return {            relativePosition: anchor,            absolutePosition: merge(anchor, {                x: anchor.x + plotBox.translateX,                y: anchor.y + plotBox.translateY            })        };    },    /**     * Returns the item position     *     * @private     * @function Highcharts.Annotation#itemPosition     *     * @param {Highcharts.SVGElement} item     *     * @param {Highcharts.AnnotationAnchorObject} anchor     *     * @return {Highcharts.AnnotationAnchorPositionObject}     */    itemPosition: function (item, anchor) {        var chart = this.chart,            point = item.points[0],            itemOptions = item.options,            anchorAbsolutePosition = anchor.absolutePosition,            anchorRelativePosition = anchor.relativePosition,            itemPosition,            alignTo,            itemPosRelativeX,            itemPosRelativeY,            showItem =                point.series.visible &&                MockPoint.prototype.isInsidePane.call(point);        if (showItem) {            if (defined(itemOptions.distance) || itemOptions.positioner) {                itemPosition = (                    itemOptions.positioner ||                    tooltipPrototype.getPosition                ).call(                    {                        chart: chart,                        distance: pick(itemOptions.distance, 16)                    },                    item.width,                    item.height,                    {                        plotX: anchorRelativePosition.x,                        plotY: anchorRelativePosition.y,                        negative: point.negative,                        ttBelow: point.ttBelow,                        h: anchorRelativePosition.height ||                            anchorRelativePosition.width                    }                );            } else {                alignTo = {                    x: anchorAbsolutePosition.x,                    y: anchorAbsolutePosition.y,                    width: 0,                    height: 0                };                itemPosition = this.alignedPosition(                    extend(itemOptions, {                        width: item.width,                        height: item.height                    }),                    alignTo                );                if (item.options.overflow === 'justify') {                    itemPosition = this.alignedPosition(                        this.justifiedOptions(item, itemOptions, itemPosition),                        alignTo                    );                }            }            if (itemOptions.crop) {                itemPosRelativeX = itemPosition.x - chart.plotLeft;                itemPosRelativeY = itemPosition.y - chart.plotTop;                showItem =                    chart.isInsidePlot(itemPosRelativeX, itemPosRelativeY) &&                    chart.isInsidePlot(                        itemPosRelativeX + item.width,                        itemPosRelativeY + item.height                    );            }        }        return showItem ? itemPosition : null;    },    /**     * Returns new aligned position based alignment options and box to align to.     * It is almost a one-to-one copy from SVGElement.prototype.align     * except it does not use and mutate an element     *     * @private     * @function Highcharts.Annotation#alignedPosition     *     * @param {Highcharts.AlignObject} alignOptions     *     * @param {Highcharts.BBoxObject} box     *     * @return {Highcharts.AlignObject}     */    alignedPosition: function (alignOptions, box) {        var align = alignOptions.align,            vAlign = alignOptions.verticalAlign,            x = (box.x || 0) + (alignOptions.x || 0),            y = (box.y || 0) + (alignOptions.y || 0),            alignFactor,            vAlignFactor;        if (align === 'right') {            alignFactor = 1;        } else if (align === 'center') {            alignFactor = 2;        }        if (alignFactor) {            x += (box.width - (alignOptions.width || 0)) / alignFactor;        }        if (vAlign === 'bottom') {            vAlignFactor = 1;        } else if (vAlign === 'middle') {            vAlignFactor = 2;        }        if (vAlignFactor) {            y += (box.height - (alignOptions.height || 0)) / vAlignFactor;        }        return {            x: Math.round(x),            y: Math.round(y)        };    },    /**     * Returns new alignment options for a label if the label is outside the     * plot area. It is almost a one-to-one copy from     * Series.prototype.justifyDataLabel except it does not mutate the label and     * it works with absolute instead of relative position.     *     * @private     * @function Highcharts.Annotation#justifiedOptions     *     * @param {Highcharts.SVGElement} label     *     * @param {Highcharts.AlignObject} alignOptions     *     * @param {Highcharts.SVGAttributes} alignAttr     *     * @return {Highcharts.AlignObject}     */    justifiedOptions: function (label, alignOptions, alignAttr) {        var chart = this.chart,            align = alignOptions.align,            verticalAlign = alignOptions.verticalAlign,            padding = label.box ? 0 : (label.padding || 0),            bBox = label.getBBox(),            off,            options = {                align: align,                verticalAlign: verticalAlign,                x: alignOptions.x,                y: alignOptions.y,                width: label.width,                height: label.height            },            x = alignAttr.x - chart.plotLeft,            y = alignAttr.y - chart.plotTop;        // Off left        off = x + padding;        if (off < 0) {            if (align === 'right') {                options.align = 'left';            } else {                options.x = -off;            }        }        // Off right        off = x + bBox.width - padding;        if (off > chart.plotWidth) {            if (align === 'left') {                options.align = 'right';            } else {                options.x = chart.plotWidth - off;            }        }        // Off top        off = y + padding;        if (off < 0) {            if (verticalAlign === 'bottom') {                options.verticalAlign = 'top';            } else {                options.y = -off;            }        }        // Off bottom        off = y + bBox.height - padding;        if (off > chart.plotHeight) {            if (verticalAlign === 'top') {                options.verticalAlign = 'bottom';            } else {                options.y = chart.plotHeight - off;            }        }        return options;    },    /**     * Utility function for mapping item's options to element's attribute     *     * @private     * @function Highcharts.Annotation#attrsFromOptions     *     * @param {object} options     *     * @return {object}     *         Mapped options     */    attrsFromOptions: function (options) {        var map = this.getAttrsMap(),            attrs = {},            key,            mappedKey;        for (key in options) {            mappedKey = map[key];            if (mappedKey) {                attrs[mappedKey] = options[key];            }        }        return attrs;    }};/* ************************************************************************** * * *   EXTENDING CHART PROTOTYPE * * ************************************************************************** */H.extend(chartPrototype, {    /**     * Add an annotation to the chart after render time.     *     * @function Highcharts.Chart#addAnnotation     *     * @param {Highcharts.AnnotationsOptions} options     *        The series options for the new, detailed series.     *     * @return {Highcharts.Annotation}     *         The newly generated annotation.     */    addAnnotation: function (userOptions, redraw) {        var annotation = new Annotation(this, userOptions);        this.annotations.push(annotation);        this.options.annotations.push(userOptions);        if (pick(redraw, true)) {            annotation.redraw();        }        return annotation;    },    /**     * Remove an annotation from the chart.     *     * @function Highcharts.Chart#removeAnnotation     *     * @param {string} id     *        The annotation's id.     */    removeAnnotation: function (id) {        var annotations = this.annotations,            annotation = find(annotations, function (annotation) {                return annotation.options.id === id;            });        if (annotation) {            erase(this.options.annotations, annotation.userOptions);            erase(annotations, annotation);            annotation.destroy();        }    },    drawAnnotations: function () {        var clip = this.plotBoxClip,            plotBox = this.plotBox;        if (clip) {            clip.attr(plotBox);        } else {            this.plotBoxClip = this.renderer.clipRect(plotBox);        }        this.annotations.forEach(function (annotation) {            annotation.redraw();        });    }});chartPrototype.callbacks.push(function (chart) {    chart.annotations = [];    chart.options.annotations.forEach(function (annotationOptions) {        chart.annotations.push(            new Annotation(chart, annotationOptions)        );    });    chart.drawAnnotations();    addEvent(chart, 'redraw', chart.drawAnnotations);    addEvent(chart, 'destroy', function () {        var plotBoxClip = chart.plotBoxClip;        if (plotBoxClip && plotBoxClip.destroy) {            plotBoxClip.destroy();        }    });});addEvent(H.Chart, 'afterGetContainer', function () {    this.options.defs = merge(defaultMarkers, this.options.defs || {});    if (!this.styledMode) {        objectEach(this.options.defs, function (def) {            if (def.tagName === 'marker' && def.render !== false) {                this.renderer.addMarker(def.id, def);            }        }, this);    }});/* ************************************************************************** */// General symbol definition for labels with connectorH.SVGRenderer.prototype.symbols.connector = function (x, y, w, h, options) {    var anchorX = options && options.anchorX,        anchorY = options && options.anchorY,        path,        yOffset,        lateral = w / 2;    if (isNumber(anchorX) && isNumber(anchorY)) {        path = ['M', anchorX, anchorY];        // Prefer 45 deg connectors        yOffset = y - anchorY;        if (yOffset < 0) {            yOffset = -h - yOffset;        }        if (yOffset < w) {            lateral = anchorX < x + (w / 2) ? yOffset : w - yOffset;        }        // Anchor below label        if (anchorY > y + h) {            path.push('L', x + lateral, y + h);        // Anchor above label        } else if (anchorY < y) {            path.push('L', x + lateral, y);        // Anchor left of label        } else if (anchorX < x) {            path.push('L', x, y + h / 2);        // Anchor right of label        } else if (anchorX > x + w) {            path.push('L', x + w, y + h / 2);        }    }    return path || [];};
 |