| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283 | /** * (c) 2009-2017 Highsoft, Black Label * * License: www.highcharts.com/license */'use strict';import H from '../parts/Globals.js';import chartNavigationMixin from '../mixins/navigation.js';var doc = H.doc,    addEvent = H.addEvent,    pick = H.pick,    merge = H.merge,    extend = H.extend,    isNumber = H.isNumber,    fireEvent = H.fireEvent,    isArray = H.isArray,    isObject = H.isObject,    objectEach = H.objectEach,    PREFIX = 'highcharts-';/** * @private * @interface bindingsUtils */var bindingsUtils = {    /**     * Update size of background (rect) in some annotations: Measure, Simple     * Rect.     *     * @private     * @function bindingsUtils.updateRectSize     *     * @param {global.Event} event     *        Normalized browser event     *     * @param {Highcharts.Annotation} annotation     *        Annotation to be updated     */    updateRectSize: function (event, annotation) {        var options = annotation.options.typeOptions,            x = this.chart.xAxis[0].toValue(event.chartX),            y = this.chart.yAxis[0].toValue(event.chartY),            width = x - options.point.x,            height = options.point.y - y;        annotation.update({            typeOptions: {                background: {                    width: width,                    height: height                }            }        });    },    /**     * Get field type according to value     *     * @private     * @function bindingsUtils.getFieldType     *     * @param {*} value     *        Atomic type (one of: string, number, boolean)     *     * @return {string}     *         Field type (one of: text, number, checkbox)     */    getFieldType: function (value) {        return {            'string': 'text',            'number': 'number',            'boolean': 'checkbox'        }[typeof value];    }};H.NavigationBindings = function (chart, options) {    this.chart = chart;    this.options = options;    this.eventsToUnbind = [];    this.container = doc.getElementsByClassName(        this.options.bindingsClassName    );};// Define which options from annotations should show up in edit box:H.NavigationBindings.annotationsEditable = {    // `typeOptions` are always available    // Nested and shared options:    nestedOptions: {        labelOptions: ['style', 'format', 'backgroundColor'],        labels: ['style'],        label: ['style'],        style: ['fontSize', 'color'],        background: ['fill', 'strokeWidth', 'stroke'],        innerBackground: ['fill', 'strokeWidth', 'stroke'],        outerBackground: ['fill', 'strokeWidth', 'stroke'],        shapeOptions: ['fill', 'strokeWidth', 'stroke'],        shapes: ['fill', 'strokeWidth', 'stroke'],        line: ['strokeWidth', 'stroke'],        backgroundColors: [true],        connector: ['fill', 'strokeWidth', 'stroke'],        crosshairX: ['strokeWidth', 'stroke'],        crosshairY: ['strokeWidth', 'stroke']    },    // Simple shapes:    circle: ['shapes'],    verticalLine: [],    label: ['labelOptions'],    // Measure    measure: ['background', 'crosshairY', 'crosshairX'],    // Others:    fibonacci: [],    tunnel: ['background', 'line', 'height'],    pitchfork: ['innerBackground', 'outerBackground'],    rect: ['shapes'],    // Crooked lines, elliots, arrows etc:    crookedLine: []};// Define non editable fields per annotation, for example Rectangle inherits// options from Measure, but crosshairs are not availableH.NavigationBindings.annotationsNonEditable = {    rectangle: ['crosshairX', 'crosshairY', 'label']};extend(H.NavigationBindings.prototype, {    // Private properties added by bindings:    // Active (selected) annotation that is editted through popup/forms    // activeAnnotation: Annotation    // Holder for current step, used on mouse move to update bound object    // mouseMoveEvent: function () {}    // Next event in `step` array to be called on chart's click    // nextEvent: function () {}    // Index in the `step` array of the current event    // stepIndex: 0    // Flag to determine if current binding has steps    // steps: true|false    // Bindings holder for all events    // selectedButton: {}    // Holder for user options, returned from `start` event, and passed on to    // `step`'s' and `end`.    // currentUserDetails: {}    /**     * Initi all events conencted to NavigationBindings.     *     * @private     * @function Highcharts.NavigationBindings#initEvents     */    initEvents: function () {        var navigation = this,            chart = navigation.chart,            bindingsContainer = navigation.container,            options = navigation.options;        // Shorthand object for getting events for buttons:        navigation.boundClassNames = {};        objectEach(options.bindings, function (value) {            navigation.boundClassNames[value.className] = value;        });        // Handle multiple containers with the same class names:        [].forEach.call(bindingsContainer, function (subContainer) {            navigation.eventsToUnbind.push(                addEvent(                    subContainer,                    'click',                    function (event) {                        var bindings = navigation.getButtonEvents(                            bindingsContainer,                            event                        );                        if (bindings) {                            navigation.bindingsButtonClick(                                bindings.button,                                bindings.events,                                event                            );                        }                    }                )            );        });        objectEach(options.events || {}, function (callback, eventName) {            navigation.eventsToUnbind.push(                addEvent(                    navigation,                    eventName,                    callback                )            );        });        navigation.eventsToUnbind.push(            addEvent(chart.container, 'click', function (e) {                if (                    !chart.cancelClick &&                    chart.isInsidePlot(                        e.chartX - chart.plotLeft,                        e.chartY - chart.plotTop                    )                ) {                    navigation.bindingsChartClick(this, e);                }            })        );        navigation.eventsToUnbind.push(            addEvent(chart.container, 'mousemove', function (e) {                navigation.bindingsContainerMouseMove(this, e);            })        );    },    /**     * Common chart.update() delegation, shared between bindings and exporting.     *     * @private     * @function Highcharts.NavigationBindings#initUpdate     */    initUpdate: function () {        var navigation = this;        chartNavigationMixin.addUpdate(            function (options) {                navigation.update(options);            },            this.chart        );    },    /**     * Hook for click on a button, method selcts/unselects buttons,     * then calls `bindings.init` callback.     *     * @private     * @function Highcharts.NavigationBindings#bindingsButtonClick     *     * @param {Highcharts.HTMLDOMElement} [button]     *        Clicked button     *     * @param {object} [events]     *        Events passed down from bindings (`init`, `start`, `step`, `end`)     *     * @param {global.Event} [clickEvent]     *        Browser's click event     */    bindingsButtonClick: function (button, events, clickEvent) {        var navigation = this,            chart = navigation.chart;        if (navigation.selectedButtonElement) {            fireEvent(                navigation,                'deselectButton',                { button: navigation.selectedButtonElement }            );            if (navigation.nextEvent) {                // Remove in-progress annotations adders:                if (                    navigation.currentUserDetails &&                    navigation.currentUserDetails.coll === 'annotations'                ) {                    chart.removeAnnotation(navigation.currentUserDetails);                }                navigation.mouseMoveEvent = navigation.nextEvent = false;            }        }        navigation.selectedButton = events;        navigation.selectedButtonElement = button;        fireEvent(navigation, 'selectButton', { button: button });        // Call "init" event, for example to open modal window        if (events.init) {            events.init.call(navigation, button, clickEvent);        }        if (events.start || events.steps) {            chart.renderer.boxWrapper.addClass(PREFIX + 'draw-mode');        }    },    /**     * Hook for click on a chart, first click on a chart calls `start` event,     * then on all subsequent clicks iterate over `steps` array.     * When finished, calls `end` event.     *     * @private     * @function Highcharts.NavigationBindings#bindingsChartClick     *     * @param {Highcharts.Chart} chart     *        Chart that click was performed on.     *     * @param {global.Event} clickEvent     *        Browser's click event.     */    bindingsChartClick: function (chartContainer, clickEvent) {        var navigation = this,            chart = navigation.chart,            selectedButton = navigation.selectedButton,            svgContainer = chart.renderer.boxWrapper;        if (            navigation.activeAnnotation &&            !clickEvent.activeAnnotation &&            // Element could be removed in the child action, e.g. button            clickEvent.target.parentNode &&            // TO DO: Polyfill for IE11?            !clickEvent.target.closest('.' + PREFIX + 'popup')        ) {            fireEvent(navigation, 'closePopup');            navigation.deselectAnnotation();        }        if (!selectedButton || !selectedButton.start) {            return;        }        if (!navigation.nextEvent) {            // Call init method:            navigation.currentUserDetails = selectedButton.start.call(                navigation,                clickEvent            );            // If steps exists (e.g. Annotations), bind them:            if (selectedButton.steps) {                navigation.stepIndex = 0;                navigation.steps = true;                navigation.mouseMoveEvent = navigation.nextEvent =                    selectedButton.steps[navigation.stepIndex];            } else {                fireEvent(                    navigation,                    'deselectButton',                    { button: navigation.selectedButtonElement }                );                svgContainer.removeClass(PREFIX + 'draw-mode');                navigation.steps = false;                navigation.selectedButton = null;                // First click is also the last one:                if (selectedButton.end) {                    selectedButton.end.call(                        navigation,                        clickEvent,                        navigation.currentUserDetails                    );                }            }        } else {            navigation.nextEvent(                clickEvent,                navigation.currentUserDetails            );            if (navigation.steps) {                navigation.stepIndex++;                if (selectedButton.steps[navigation.stepIndex]) {                    // If we have more steps, bind them one by one:                    navigation.mouseMoveEvent = navigation.nextEvent =                        selectedButton.steps[navigation.stepIndex];                } else {                    fireEvent(                        navigation,                        'deselectButton',                        { button: navigation.selectedButtonElement }                    );                    svgContainer.removeClass(PREFIX + 'draw-mode');                    // That was the last step, call end():                    if (selectedButton.end) {                        selectedButton.end.call(                            navigation,                            clickEvent,                            navigation.currentUserDetails                        );                    }                    navigation.nextEvent = false;                    navigation.mouseMoveEvent = false;                    navigation.selectedButton = null;                }            }        }    },    /**     * Hook for mouse move on a chart's container. It calls current step.     *     * @private     * @function Highcharts.NavigationBindings#bindingsContainerMouseMove     *     * @param {Highcharts.HTMLDOMElement} container     *        Chart's container.     *     * @param {global.Event} moveEvent     *        Browser's move event.     */    bindingsContainerMouseMove: function (container, moveEvent) {        if (this.mouseMoveEvent) {            this.mouseMoveEvent(                moveEvent,                this.currentUserDetails            );        }    },    /**     * Translate fields (e.g. `params.period` or `marker.styles.color`) to     * Highcharts options object (e.g. `{ params: { period } }`).     *     * @private     * @function Highcharts.NavigationBindings#fieldsToOptions     *     * @param {object} fields     *        Fields from popup form.     *     * @param {object} config     *        Default config to be modified.     *     * @return {object}     *         Modified config     */    fieldsToOptions: function (fields, config) {        objectEach(fields, function (value, field) {            var parsedValue = parseFloat(value),                path = field.split('.'),                parent = config,                pathLength = path.length - 1;            // If it's a number (not "forma" options), parse it:            if (                isNumber(parsedValue) &&                !value.match(/px/g) &&                !field.match(/format/g)            ) {                value = parsedValue;            }            // Remove empty strings or values like 0            if (value !== '' && value !== 'undefined') {                path.forEach(function (name, index) {                    var nextName = pick(path[index + 1], '');                    if (pathLength === index) {                        // Last index, put value:                        parent[name] = value;                    } else if (!parent[name]) {                        // Create middle property:                        parent[name] = nextName.match(/\d/g) ? [] : {};                        parent = parent[name];                    } else {                        // Jump into next property                        parent = parent[name];                    }                });            }        });        return config;    },    /**     * Shorthand method to deselect an annotation.     *     * @function Highcharts.NavigationBindings#deselectAnnotation     */    deselectAnnotation: function () {        if (this.activeAnnotation) {            this.activeAnnotation.setControlPointsVisibility(false);            this.activeAnnotation = false;        }    },    /**     * Generates API config for popup in the same format as options for     * Annotation object.     *     * @function Highcharts.NavigationBindings#annotationToFields     *     * @param {Highcharts.Annotation} annotation     *        Annotations object     *     * @return {object}     *         Annotation options to be displayed in popup box     */    annotationToFields: function (annotation) {        var options = annotation.options,            editables = H.NavigationBindings.annotationsEditable,            nestedEditables = editables.nestedOptions,            getFieldType = this.utils.getFieldType,            type = pick(                options.type,                options.shapes && options.shapes[0] &&                    options.shapes[0].type,                options.labels && options.labels[0] &&                    options.labels[0].itemType,                'label'            ),            nonEditables = H.NavigationBindings                .annotationsNonEditable[options.langKey] || [],            visualOptions = {                langKey: options.langKey,                type: type            };        /**         * Nested options traversing. Method goes down to the options and copies         * allowed options (with values) to new object, which is last parameter:         * "parent".         *         * @private         * @function Highcharts.NavigationBindings#annotationToFields.traverse         *         * @param {*} option         *        Atomic type or object/array         *         * @param {string} key         *        Option name, for example "visible" or "x", "y"         *         * @param {object} allowed         *        Editables from H.NavigationBindings.annotationsEditable         *         * @param {object} parent         *        Where new options will be assigned         */        function traverse(option, key, parentEditables, parent) {            var nextParent;            if (                parentEditables &&                nonEditables.indexOf(key) === -1 &&                (                    (                        parentEditables.indexOf &&                        parentEditables.indexOf(key)                    ) >= 0 ||                    parentEditables[key] || // nested array                    parentEditables === true // simple array                )            ) {                // Roots:                if (isArray(option)) {                    parent[key] = [];                    option.forEach(function (arrayOption, i) {                        if (!isObject(arrayOption)) {                            // Simple arrays, e.g. [String, Number, Boolean]                            traverse(                                arrayOption,                                0,                                nestedEditables[key],                                parent[key]                            );                        } else {                            // Advanced arrays, e.g. [Object, Object]                            parent[key][i] = {};                            objectEach(                                arrayOption,                                function (nestedOption, nestedKey) {                                    traverse(                                        nestedOption,                                        nestedKey,                                        nestedEditables[key],                                        parent[key][i]                                    );                                }                            );                        }                    });                } else if (isObject(option)) {                    nextParent = {};                    if (isArray(parent)) {                        parent.push(nextParent);                        nextParent[key] = {};                        nextParent = nextParent[key];                    } else {                        parent[key] = nextParent;                    }                    objectEach(option, function (nestedOption, nestedKey) {                        traverse(                            nestedOption,                            nestedKey,                            key === 0 ? parentEditables : nestedEditables[key],                            nextParent                        );                    });                } else {                    // Leaf:                    if (key === 'format') {                        parent[key] = [                            H.format(                                option,                                annotation.labels[0].points[0]                            ).toString(),                            'text'                        ];                    } else if (isArray(parent)) {                        parent.push([option, getFieldType(option)]);                    } else {                        parent[key] = [option, getFieldType(option)];                    }                }            }        }        objectEach(options, function (option, key) {            if (key === 'typeOptions') {                visualOptions[key] = {};                objectEach(options[key], function (typeOption, typeKey) {                    traverse(                        typeOption,                        typeKey,                        nestedEditables,                        visualOptions[key],                        true                    );                });            } else {                traverse(option, key, editables[type], visualOptions);            }        });        return visualOptions;    },    /**     * Get all class names for all parents in the element. Iterates until finds     * main container.     *     * @function Highcharts.NavigationBindings#getClickedClassNames     *     * @param {Highcharts.HTMLDOMElement}     *        Container that event is bound to.     *     * @param {global.Event} event     *        Browser's event.     *     * @return {Array<string>}     *         Array of class names with corresponding elements     */    getClickedClassNames: function (container, event) {        var element = event.target,            classNames = [],            elemClassName;        while (element) {            elemClassName = H.attr(element, 'class');            if (elemClassName) {                classNames = classNames.concat(                    elemClassName.split(' ').map(                        function (name) { // eslint-disable-line no-loop-func                            return [                                name,                                element                            ];                        }                    )                );            }            element = element.parentNode;            if (element === container) {                return classNames;            }        }        return classNames;    },    /**     * Get events bound to a button. It's a custom event delegation to find all     * events connected to the element.     *     * @function Highcharts.NavigationBindings#getButtonEvents     *     * @param {Highcharts.HTMLDOMElement}     *        Container that event is bound to.     *     * @param {global.Event} event     *        Browser's event.     *     * @return {object}     *         Oject with events (init, start, steps, and end)     */    getButtonEvents: function (container, event) {        var navigation = this,            classNames = this.getClickedClassNames(container, event),            bindings;        classNames.forEach(function (className) {            if (navigation.boundClassNames[className[0]] && !bindings) {                bindings = {                    events: navigation.boundClassNames[className[0]],                    button: className[1]                };            }        });        return bindings;    },    /**     * Bindings are just events, so the whole update process is simply     * removing old events and adding new ones.     *     * @private     * @function Highcharts.NavigationBindings#update     */    update: function (options) {        this.options = merge(true, this.options, options);        this.removeEvents();        this.initEvents();    },    /**     * Remove all events created in the navigation.     *     * @private     * @function Highcharts.NavigationBindings#removeEvents     */    removeEvents: function () {        this.eventsToUnbind.forEach(function (unbinder) {            unbinder();        });    },    destroy: function () {        this.removeEvents();    },    /**     * General utils for bindings     *     * @private     * @name Highcharts.NavigationBindings#utils     * @type {bindingsUtils}     */    utils: bindingsUtils});H.Chart.prototype.initNavigationBindings = function () {    var chart = this,        options = chart.options;    if (options && options.navigation && options.navigation.bindings) {        chart.navigationBindings = new H.NavigationBindings(            chart,            options.navigation        );        chart.navigationBindings.initEvents();        chart.navigationBindings.initUpdate();    }};addEvent(H.Chart, 'load', function () {    this.initNavigationBindings();});addEvent(H.Chart, 'destroy', function () {    if (this.navigationBindings) {        this.navigationBindings.destroy();    }});addEvent(H.NavigationBindings, 'deselectButton', function () {    this.selectedButtonElement = null;});// Show edit-annotation form:function selectableAnnotation(annotationType) {    var originalClick = annotationType.prototype.defaultOptions.events &&            annotationType.prototype.defaultOptions.events.click;    function selectAndshowPopup(event) {        var annotation = this,            navigation = annotation.chart.navigationBindings,            prevAnnotation = navigation.activeAnnotation;        if (originalClick) {            originalClick.click.call(annotation, event);        }        if (prevAnnotation !== annotation) {            // Select current:            navigation.deselectAnnotation();            navigation.activeAnnotation = annotation;            annotation.setControlPointsVisibility(true);            fireEvent(                navigation,                'showPopup',                {                    annotation: annotation,                    formType: 'annotation-toolbar',                    options: navigation.annotationToFields(annotation),                    onSubmit: function (data) {                        var config = {},                            typeOptions;                        if (data.actionType === 'remove') {                            navigation.activeAnnotation = false;                            navigation.chart.removeAnnotation(annotation);                        } else {                            navigation.fieldsToOptions(data.fields, config);                            navigation.deselectAnnotation();                            typeOptions = config.typeOptions;                            if (annotation.options.type === 'measure') {                                // Manually disable crooshars according to                                // stroke width of the shape:                                typeOptions.crosshairY.enabled =                                    typeOptions.crosshairY.strokeWidth !== 0;                                typeOptions.crosshairX.enabled =                                    typeOptions.crosshairX.strokeWidth !== 0;                            }                            annotation.update(config);                        }                    }                }            );        } else {            // Deselect current:            navigation.deselectAnnotation();            fireEvent(navigation, 'closePopup');        }        // Let bubble event to chart.click:        event.activeAnnotation = true;    }    H.merge(        true,        annotationType.prototype.defaultOptions.events,        {            click: selectAndshowPopup        }    );}if (H.Annotation) {    // Basic shapes:    selectableAnnotation(H.Annotation);    // Advanced annotations:    H.objectEach(H.Annotation.types, function (annotationType) {        selectableAnnotation(annotationType);    });}H.setOptions({    /**     * @optionparent lang     */    lang: {        /**         * Configure the Popup strings in the chart. Requires the         * `annotations.js` or `annotations-advanced.src.js` module to be         * loaded.         *         * @since           7.0.0         * @type            {Object}         * @product         highcharts highstock         */        navigation: {            /**             * Translations for all field names used in popup.             *             * @product         highcharts highstock             * @type            {Object}             */            popup: {                simpleShapes: 'Simple shapes',                lines: 'Lines',                circle: 'Circle',                rectangle: 'Rectangle',                label: 'Label',                shapeOptions: 'Shape options',                typeOptions: 'Details',                fill: 'Fill',                format: 'Text',                strokeWidth: 'Line width',                stroke: 'Line color',                title: 'Title',                name: 'Name',                labelOptions: 'Label options',                labels: 'Labels',                backgroundColor: 'Background color',                backgroundColors: 'Background colors',                borderColor: 'Border color',                borderRadius: 'Border radius',                borderWidth: 'Border width',                style: 'Style',                padding: 'Padding',                fontSize: 'Font size',                color: 'Color',                height: 'Height',                shapes: 'Shape options'            }        }    },    /**     * @optionparent navigation     * @product      highcharts highstock     */    navigation: {        /**         * A CSS class name where all bindings will be attached to. Multiple         * charts on the same page should have separate class names to prevent         * duplicating events.         *         * @since     7.0.0         * @type      {string}         */        bindingsClassName: 'highcharts-bindings-wrapper',        /**         * Bindings definitions for custom HTML buttons. Each binding implements         * simple event-driven interface:         *         * - `className`: classname used to bind event to         *         * - `init`: initial event, fired on button click         *         * - `start`: fired on first click on a chart         *         * - `steps`: array of sequential events fired one after another on each         *   of users clicks         *         * - `end`: last event to be called after last step event         *         * @type         {Highcharts.Dictionary<Highcharts.StockToolsBindingsObject>|*}         * @sample       stock/stocktools/stocktools-thresholds         *               Custom bindings in Highstock         * @since        7.0.0         * @product      highcharts highstock         */        bindings: {            /**             * A circle annotation bindings. Includes `start` and one event in             * `steps` array.             *             * @type    {Highcharts.StockToolsBindingsObject}             * @default {"className": "highcharts-circle-annotation", "start": function() {}, "steps": [function() {}]}             */            circleAnnotation: {                /** @ignore */                className: 'highcharts-circle-annotation',                /** @ignore */                start: function (e) {                    var x = this.chart.xAxis[0].toValue(e.chartX),                        y = this.chart.yAxis[0].toValue(e.chartY),                        annotation;                    annotation = this.chart.addAnnotation({                        langKey: 'circle',                        shapes: [{                            type: 'circle',                            point: {                                xAxis: 0,                                yAxis: 0,                                x: x,                                y: y                            },                            r: 5,                            controlPoints: [{                                positioner: function (target) {                                    var xy = H.Annotation.MockPoint                                            .pointToPixels(                                                target.points[0]                                            ),                                        r = target.options.r;                                    return {                                        x: xy.x + r * Math.cos(Math.PI / 4) -                                            this.graphic.width / 2,                                        y: xy.y + r * Math.sin(Math.PI / 4) -                                            this.graphic.height / 2                                    };                                },                                events: {                                    // TRANSFORM RADIUS ACCORDING TO Y                                    // TRANSLATION                                    drag: function (e, target) {                                        var annotation = target.annotation,                                            position = this                                                .mouseMoveToTranslation(e);                                        target.setRadius(                                            Math.max(                                                target.options.r +                                                    position.y /                                                    Math.sin(Math.PI / 4),                                                5                                            )                                        );                                        annotation.options.shapes[0] =                                            annotation.userOptions.shapes[0] =                                            target.options;                                        target.redraw(false);                                    }                                }                            }]                        }]                    });                    return annotation;                },                /** @ignore */                steps: [                    function (e, annotation) {                        var point = annotation.options.shapes[0].point,                            x = this.chart.xAxis[0].toPixels(point.x),                            y = this.chart.yAxis[0].toPixels(point.y),                            distance = Math.max(                                Math.sqrt(                                    Math.pow(x - e.chartX, 2) +                                    Math.pow(y - e.chartY, 2)                                ),                                5                            );                        annotation.update({                            shapes: [{                                r: distance                            }]                        });                    }                ]            },            /**             * A rectangle annotation bindings. Includes `start` and one event             * in `steps` array.             *             * @type    {Highcharts.StockToolsBindingsObject}             * @default {"className": "highcharts-rectangle-annotation", "start": function() {}, "steps": [function() {}]}             */            rectangleAnnotation: {                /** @ignore */                className: 'highcharts-rectangle-annotation',                /** @ignore */                start: function (e) {                    var x = this.chart.xAxis[0].toValue(e.chartX),                        y = this.chart.yAxis[0].toValue(e.chartY),                        options = {                            langKey: 'rectangle',                            shapes: [{                                type: 'rect',                                point: {                                    x: x,                                    y: y,                                    xAxis: 0,                                    yAxis: 0                                },                                width: 5,                                height: 5,                                controlPoints: [{                                    positioner: function (target) {                                        var xy = H.Annotation.MockPoint                                            .pointToPixels(                                                target.points[0]                                            );                                        return {                                            x: xy.x + target.options.width - 4,                                            y: xy.y + target.options.height - 4                                        };                                    },                                    events: {                                        drag: function (e, target) {                                            var annotation = target.annotation,                                                xy = this                                                    .mouseMoveToTranslation(e);                                            target.options.width = Math.max(                                                target.options.width + xy.x,                                                5                                            );                                            target.options.height = Math.max(                                                target.options.height + xy.y,                                                5                                            );                                            annotation.options.shapes[0] =                                                target.options;                                            annotation.userOptions.shapes[0] =                                                target.options;                                            target.redraw(false);                                        }                                    }                                }]                            }]                        };                    return this.chart.addAnnotation(options);                },                /** @ignore */                steps: [                    function (e, annotation) {                        var xAxis = this.chart.xAxis[0],                            yAxis = this.chart.yAxis[0],                            point = annotation.options.shapes[0].point,                            x = xAxis.toPixels(point.x),                            y = yAxis.toPixels(point.y),                            width = Math.max(e.chartX - x, 5),                            height = Math.max(e.chartY - y, 5);                        annotation.update({                            shapes: [{                                width: width,                                height: height,                                point: {                                    x: point.x,                                    y: point.y                                }                            }]                        });                    }                ]            },            /**             * A label annotation bindings. Includes `start` event only.             *             * @type    {Highcharts.StockToolsBindingsObject}             * @default {"className": "highcharts-label-annotation", "start": function() {}, "steps": [function() {}]}             */            labelAnnotation: {                /** @ignore */                className: 'highcharts-label-annotation',                /** @ignore */                start: function (e) {                    var x = this.chart.xAxis[0].toValue(e.chartX),                        y = this.chart.yAxis[0].toValue(e.chartY);                    this.chart.addAnnotation({                        langKey: 'label',                        labelOptions: {                            format: '{y:.2f}'                        },                        labels: [{                            point: {                                x: x,                                y: y,                                xAxis: 0,                                yAxis: 0                            },                            controlPoints: [{                                symbol: 'triangle-down',                                positioner: function (target) {                                    if (!target.graphic.placed) {                                        return {                                            x: 0,                                            y: -9e7                                        };                                    }                                    var xy = H.Annotation.MockPoint                                        .pointToPixels(                                            target.points[0]                                        );                                    return {                                        x: xy.x - this.graphic.width / 2,                                        y: xy.y - this.graphic.height / 2                                    };                                },                                // TRANSLATE POINT/ANCHOR                                events: {                                    drag: function (e, target) {                                        var xy = this.mouseMoveToTranslation(e);                                        target.translatePoint(xy.x, xy.y);                                        target.annotation.labels[0].options =                                            target.options;                                        target.redraw(false);                                    }                                }                            }, {                                symbol: 'square',                                positioner: function (target) {                                    if (!target.graphic.placed) {                                        return {                                            x: 0,                                            y: -9e7                                        };                                    }                                    return {                                        x: target.graphic.alignAttr.x -                                            this.graphic.width / 2,                                        y: target.graphic.alignAttr.y -                                            this.graphic.height / 2                                    };                                },                                // TRANSLATE POSITION WITHOUT CHANGING THE                                // ANCHOR                                events: {                                    drag: function (e, target) {                                        var xy = this.mouseMoveToTranslation(e);                                        target.translate(xy.x, xy.y);                                        target.annotation.labels[0].options =                                            target.options;                                        target.redraw(false);                                    }                                }                            }],                            overflow: 'none',                            crop: true                        }]                    });                }            }        },        /**         * A `showPopup` event. Fired when selecting for example an annotation.         *         * @type      {Function}         * @apioption navigation.events.showPopup         */        /**         * A `hidePopop` event. Fired when Popup should be hidden, for exampole         * when clicking on an annotation again.         *         * @type      {Function}         * @apioption navigation.events.hidePopup         */        /**         * Event fired on a button click.         *         * @type      {Function}         * @sample    highcharts/annotations/gui/         *            Change icon in a dropddown on event         * @sample    highcharts/annotations/gui-buttons/         *            Change button class on event         * @apioption navigation.events.selectButton         */        /**         * Event fired when button state should change, for example after         * adding an annotation.         *         * @type      {Function}         * @sample    highcharts/annotations/gui/         *            Change icon in a dropddown on event         * @sample    highcharts/annotations/gui-buttons/         *            Change button class on event         * @apioption navigation.events.deselectButton         */        /**         * Events to communicate between Stock Tools and custom GUI.         *         * @since        7.0.0         * @product      highcharts highstock         * @optionparent navigation.events         */        events: {}    }});
 |