UNPKG

highcharts

Version:
1,321 lines (1,289 loc) 211 kB
/** * @license Highstock JS v12.1.2 (2024-12-21) * @module highcharts/modules/stock-tools * @requires highcharts * @requires highcharts/modules/stock * * Advanced Highcharts Stock tools * * (c) 2010-2024 Highsoft AS * Author: Torstein Honsi * * License: www.highcharts.com/license */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(root["_Highcharts"], root["_Highcharts"]["Templating"], root["_Highcharts"]["Series"], root["_Highcharts"]["AST"]); else if(typeof define === 'function' && define.amd) define("highcharts/modules/stock-tools", ["highcharts/highcharts"], function (amd1) {return factory(amd1,amd1["Templating"],amd1["Series"],amd1["AST"]);}); else if(typeof exports === 'object') exports["highcharts/modules/stock-tools"] = factory(root["_Highcharts"], root["_Highcharts"]["Templating"], root["_Highcharts"]["Series"], root["_Highcharts"]["AST"]); else root["Highcharts"] = factory(root["Highcharts"], root["Highcharts"]["Templating"], root["Highcharts"]["Series"], root["Highcharts"]["AST"]); })(typeof window === 'undefined' ? this : window, (__WEBPACK_EXTERNAL_MODULE__944__, __WEBPACK_EXTERNAL_MODULE__984__, __WEBPACK_EXTERNAL_MODULE__820__, __WEBPACK_EXTERNAL_MODULE__660__) => { return /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ 660: /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_MODULE__660__; /***/ }), /***/ 820: /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_MODULE__820__; /***/ }), /***/ 984: /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_MODULE__984__; /***/ }), /***/ 944: /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_MODULE__944__; /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/compat get default export */ /******/ (() => { /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = (module) => { /******/ var getter = module && module.__esModule ? /******/ () => (module['default']) : /******/ () => (module); /******/ __webpack_require__.d(getter, { a: getter }); /******/ return getter; /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // EXPORTS __webpack_require__.d(__webpack_exports__, { "default": () => (/* binding */ stock_tools_src) }); // EXTERNAL MODULE: external {"amd":["highcharts/highcharts"],"commonjs":["highcharts"],"commonjs2":["highcharts"],"root":["Highcharts"]} var highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_ = __webpack_require__(944); var highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default = /*#__PURE__*/__webpack_require__.n(highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_); ;// ./code/es-modules/Core/Chart/ChartNavigationComposition.js /** * * (c) 2010-2024 Paweł Fus * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Composition * * */ var ChartNavigationComposition; (function (ChartNavigationComposition) { /* * * * Declarations * * */ /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * @private */ function compose(chart) { if (!chart.navigation) { chart.navigation = new Additions(chart); } return chart; } ChartNavigationComposition.compose = compose; /* * * * Class * * */ /** * Initializes `chart.navigation` object which delegates `update()` methods * to all other common classes (used in exporting and navigationBindings). * @private */ class Additions { /* * * * Constructor * * */ constructor(chart) { this.updates = []; this.chart = chart; } /* * * * Functions * * */ /** * Registers an `update()` method in the `chart.navigation` object. * * @private * @param {UpdateFunction} updateFn * The `update()` method that will be called in `chart.update()`. */ addUpdate(updateFn) { this.chart.navigation.updates.push(updateFn); } /** * @private */ update(options, redraw) { this.updates.forEach((updateFn) => { updateFn.call(this.chart, options, redraw); }); } } ChartNavigationComposition.Additions = Additions; })(ChartNavigationComposition || (ChartNavigationComposition = {})); /* * * * Default Export * * */ /* harmony default export */ const Chart_ChartNavigationComposition = (ChartNavigationComposition); // EXTERNAL MODULE: external {"amd":["highcharts/highcharts","Templating"],"commonjs":["highcharts","Templating"],"commonjs2":["highcharts","Templating"],"root":["Highcharts","Templating"]} var highcharts_Templating_commonjs_highcharts_Templating_commonjs2_highcharts_Templating_root_Highcharts_Templating_ = __webpack_require__(984); var highcharts_Templating_commonjs_highcharts_Templating_commonjs2_highcharts_Templating_root_Highcharts_Templating_default = /*#__PURE__*/__webpack_require__.n(highcharts_Templating_commonjs_highcharts_Templating_commonjs2_highcharts_Templating_root_Highcharts_Templating_); ;// ./code/es-modules/Extensions/Annotations/NavigationBindingsUtilities.js /* * * * (c) 2009-2024 Highsoft, Black Label * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { defined, isNumber, pick } = (highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default()); /* * * * Constants * * */ /** * Define types for editable fields per annotation. There is no need to define * numbers, because they won't change their type to string. * @private */ const annotationsFieldsTypes = { backgroundColor: 'string', borderColor: 'string', borderRadius: 'string', color: 'string', fill: 'string', fontSize: 'string', labels: 'string', name: 'string', stroke: 'string', title: 'string' }; /* * * * Functions * * */ /** * Returns the first xAxis or yAxis that was clicked with its value. * * @private * * @param {Array<Highcharts.PointerAxisCoordinateObject>} coords * All the chart's x or y axes with a current pointer's axis value. * * @return {Highcharts.PointerAxisCoordinateObject} * Object with a first found axis and its value that pointer * is currently pointing. */ function getAssignedAxis(coords) { return coords.filter((coord) => { const extremes = coord.axis.getExtremes(), axisMin = extremes.min, axisMax = extremes.max, // Correct axis edges when axis has series // with pointRange (like column) minPointOffset = pick(coord.axis.minPointOffset, 0); return isNumber(axisMin) && isNumber(axisMax) && coord.value >= (axisMin - minPointOffset) && coord.value <= (axisMax + minPointOffset) && // Don't count navigator axis !coord.axis.options.isInternal; })[0]; // If the axes overlap, return the first axis that was found. } /** * Get field type according to value * * @private * * @param {'boolean'|'number'|'string'} value * Atomic type (one of: string, number, boolean) * * @return {'checkbox'|'number'|'text'} * Field type (one of: text, number, checkbox) */ function getFieldType(key, value) { const predefinedType = annotationsFieldsTypes[key]; let fieldType = typeof value; if (defined(predefinedType)) { fieldType = predefinedType; } return { 'string': 'text', 'number': 'number', 'boolean': 'checkbox' }[fieldType]; } /* * * * Default Export * * */ const NavigationBindingUtilities = { annotationsFieldsTypes, getAssignedAxis, getFieldType }; /* harmony default export */ const NavigationBindingsUtilities = (NavigationBindingUtilities); ;// ./code/es-modules/Extensions/Annotations/NavigationBindingsDefaults.js /* * * * (c) 2009-2024 Highsoft, Black Label * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { getAssignedAxis: NavigationBindingsDefaults_getAssignedAxis } = NavigationBindingsUtilities; const { isNumber: NavigationBindingsDefaults_isNumber, merge } = (highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default()); /* * * * Constants * * */ /** * @optionparent lang */ const 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 * @product highcharts highstock */ navigation: { /** * Translations for all field names used in popup. * * @product highcharts highstock */ popup: { simpleShapes: 'Simple shapes', lines: 'Lines', circle: 'Circle', ellipse: 'Ellipse', 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 */ const 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. * * Default value of versions < 7.0.4 `highcharts-bindings-wrapper` * * @since 7.0.0 * @type {string} */ bindingsClassName: 'highcharts-bindings-container', /** * 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.NavigationBindingsOptionsObject>|*} * * @sample {highstock} stock/stocktools/stocktools-thresholds * Custom bindings * @sample {highcharts} highcharts/annotations/bindings/ * Simple binding * @sample {highcharts} highcharts/annotations/bindings-custom-annotation/ * Custom annotation binding * * @since 7.0.0 * @requires modules/annotations * @product highcharts highstock */ bindings: { /** * A circle annotation bindings. Includes `start` and one event in * `steps` array. * * @type {Highcharts.NavigationBindingsOptionsObject} * @default {"className": "highcharts-circle-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}} */ circleAnnotation: { /** @ignore-option */ className: 'highcharts-circle-annotation', /** @ignore-option */ start: function (e) { const coords = this.chart.pointer?.getCoordinates(e), coordsX = coords && NavigationBindingsDefaults_getAssignedAxis(coords.xAxis), coordsY = coords && NavigationBindingsDefaults_getAssignedAxis(coords.yAxis), navigation = this.chart.options.navigation; // Exit if clicked out of axes area if (!coordsX || !coordsY) { return; } return this.chart.addAnnotation(merge({ langKey: 'circle', type: 'basicAnnotation', shapes: [{ type: 'circle', point: { x: coordsX.value, y: coordsY.value, xAxis: coordsX.axis.index, yAxis: coordsY.axis.index }, r: 5 }] }, navigation.annotationsOptions, navigation.bindings.circleAnnotation .annotationsOptions)); }, /** @ignore-option */ steps: [ function (e, annotation) { const shapes = annotation.options.shapes, mockPointOpts = ((shapes && shapes[0] && shapes[0].point) || {}); let distance; if (NavigationBindingsDefaults_isNumber(mockPointOpts.xAxis) && NavigationBindingsDefaults_isNumber(mockPointOpts.yAxis)) { const inverted = this.chart.inverted, x = this.chart.xAxis[mockPointOpts.xAxis] .toPixels(mockPointOpts.x), y = this.chart.yAxis[mockPointOpts.yAxis] .toPixels(mockPointOpts.y); distance = Math.max(Math.sqrt(Math.pow(inverted ? y - e.chartX : x - e.chartX, 2) + Math.pow(inverted ? x - e.chartY : y - e.chartY, 2)), 5); } annotation.update({ shapes: [{ r: distance }] }); } ] }, /** * A ellipse annotation bindings. Includes `start` and two events in * `steps` array. First updates the second point, responsible for a * rx width, and second updates the ry width. * * @type {Highcharts.NavigationBindingsOptionsObject} * @default {"className": "highcharts-ellipse-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}} */ ellipseAnnotation: { className: 'highcharts-ellipse-annotation', start: function (e) { const coords = this.chart.pointer?.getCoordinates(e), coordsX = coords && NavigationBindingsDefaults_getAssignedAxis(coords.xAxis), coordsY = coords && NavigationBindingsDefaults_getAssignedAxis(coords.yAxis), navigation = this.chart.options.navigation; if (!coordsX || !coordsY) { return; } return this.chart.addAnnotation(merge({ langKey: 'ellipse', type: 'basicAnnotation', shapes: [ { type: 'ellipse', xAxis: coordsX.axis.index, yAxis: coordsY.axis.index, points: [{ x: coordsX.value, y: coordsY.value }, { x: coordsX.value, y: coordsY.value }], ry: 1 } ] }, navigation.annotationsOptions, navigation.bindings.ellipseAnnotation .annotationOptions)); }, steps: [ function (e, annotation) { const target = annotation.shapes[0], position = target.getAbsolutePosition(target.points[1]); target.translatePoint(e.chartX - position.x, e.chartY - position.y, 1); target.redraw(false); }, function (e, annotation) { const target = annotation.shapes[0], position = target.getAbsolutePosition(target.points[0]), position2 = target.getAbsolutePosition(target.points[1]), newR = target.getDistanceFromLine(position, position2, e.chartX, e.chartY), yAxis = target.getYAxis(), newRY = Math.abs(yAxis.toValue(0) - yAxis.toValue(newR)); target.setYRadius(newRY); target.redraw(false); } ] }, /** * A rectangle annotation bindings. Includes `start` and one event * in `steps` array. * * @type {Highcharts.NavigationBindingsOptionsObject} * @default {"className": "highcharts-rectangle-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}} */ rectangleAnnotation: { /** @ignore-option */ className: 'highcharts-rectangle-annotation', /** @ignore-option */ start: function (e) { const coords = this.chart.pointer?.getCoordinates(e), coordsX = coords && NavigationBindingsDefaults_getAssignedAxis(coords.xAxis), coordsY = coords && NavigationBindingsDefaults_getAssignedAxis(coords.yAxis); // Exit if clicked out of axes area if (!coordsX || !coordsY) { return; } const x = coordsX.value, y = coordsY.value, xAxis = coordsX.axis.index, yAxis = coordsY.axis.index, navigation = this.chart.options.navigation; return this.chart.addAnnotation(merge({ langKey: 'rectangle', type: 'basicAnnotation', shapes: [{ type: 'path', points: [ { xAxis, yAxis, x, y }, { xAxis, yAxis, x, y }, { xAxis, yAxis, x, y }, { xAxis, yAxis, x, y }, { command: 'Z' } ] }] }, navigation .annotationsOptions, navigation .bindings .rectangleAnnotation .annotationsOptions)); }, /** @ignore-option */ steps: [ function (e, annotation) { const shapes = annotation.options.shapes, points = ((shapes && shapes[0] && shapes[0].points) || []), coords = this.chart.pointer?.getCoordinates(e), coordsX = coords && NavigationBindingsDefaults_getAssignedAxis(coords.xAxis), coordsY = coords && NavigationBindingsDefaults_getAssignedAxis(coords.yAxis); if (coordsX && coordsY) { const x = coordsX.value, y = coordsY.value; // Top right point points[1].x = x; // Bottom right point (cursor position) points[2].x = x; points[2].y = y; // Bottom left points[3].y = y; annotation.update({ shapes: [{ points: points }] }); } } ] }, /** * A label annotation bindings. Includes `start` event only. * * @type {Highcharts.NavigationBindingsOptionsObject} * @default {"className": "highcharts-label-annotation", "start": function() {}, "steps": [function() {}], "annotationsOptions": {}} */ labelAnnotation: { /** @ignore-option */ className: 'highcharts-label-annotation', /** @ignore-option */ start: function (e) { const coords = this.chart.pointer?.getCoordinates(e), coordsX = coords && NavigationBindingsDefaults_getAssignedAxis(coords.xAxis), coordsY = coords && NavigationBindingsDefaults_getAssignedAxis(coords.yAxis), navigation = this.chart.options.navigation; // Exit if clicked out of axes area if (!coordsX || !coordsY) { return; } return this.chart.addAnnotation(merge({ langKey: 'label', type: 'basicAnnotation', labelOptions: { format: '{y:.2f}', overflow: 'none', crop: true }, labels: [{ point: { xAxis: coordsX.axis.index, yAxis: coordsY.axis.index, x: coordsX.value, y: coordsY.value } }] }, navigation .annotationsOptions, navigation .bindings .labelAnnotation .annotationsOptions)); } } }, /** * Path where Highcharts will look for icons. Change this to use icons * from a different server. * * @type {string} * @default https://code.highcharts.com/12.1.2/gfx/stock-icons/ * @since 7.1.3 * @apioption navigation.iconsURL */ /** * A `showPopup` event. Fired when selecting for example an annotation. * * @type {Function} * @apioption navigation.events.showPopup */ /** * A `closePopup` event. Fired when Popup should be hidden, for example * when clicking on an annotation again. * * @type {Function} * @apioption navigation.events.closePopup */ /** * 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: {}, /** * Additional options to be merged into all annotations. * * @sample stock/stocktools/navigation-annotation-options * Set red color of all line annotations * * @type {Highcharts.AnnotationsOptions} * @extends annotations * @exclude crookedLine, elliottWave, fibonacci, infinityLine, * measure, pitchfork, tunnel, verticalLine, basicAnnotation * @requires modules/annotations * @apioption navigation.annotationsOptions */ annotationsOptions: { animation: { defer: 0 } } }; /* * * * Default Export * * */ const NavigationBindingDefaults = { lang, navigation }; /* harmony default export */ const NavigationBindingsDefaults = (NavigationBindingDefaults); ;// ./code/es-modules/Extensions/Annotations/NavigationBindings.js /* * * * (c) 2009-2024 Highsoft, Black Label * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { setOptions } = (highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default()); const { format } = (highcharts_Templating_commonjs_highcharts_Templating_commonjs2_highcharts_Templating_root_Highcharts_Templating_default()); const { composed, doc, win } = (highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default()); const { getAssignedAxis: NavigationBindings_getAssignedAxis, getFieldType: NavigationBindings_getFieldType } = NavigationBindingsUtilities; const { addEvent, attr, defined: NavigationBindings_defined, fireEvent, isArray, isFunction, isNumber: NavigationBindings_isNumber, isObject, merge: NavigationBindings_merge, objectEach, pick: NavigationBindings_pick, pushUnique } = (highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default()); /* * * * Functions * * */ /** * IE 9-11 polyfill for Element.closest(): * @private */ function closestPolyfill(el, s) { const ElementProto = win.Element.prototype, elementMatches = ElementProto.matches || ElementProto.msMatchesSelector || ElementProto.webkitMatchesSelector; let ret = null; if (ElementProto.closest) { ret = ElementProto.closest.call(el, s); } else { do { if (elementMatches.call(el, s)) { return el; } el = el.parentElement || el.parentNode; } while (el !== null && el.nodeType === 1); } return ret; } /** * @private */ function onAnnotationRemove() { if (this.chart.navigationBindings) { this.chart.navigationBindings.deselectAnnotation(); } } /** * @private */ function onChartDestroy() { if (this.navigationBindings) { this.navigationBindings.destroy(); } } /** * @private */ function onChartLoad() { const options = this.options; if (options && options.navigation && options.navigation.bindings) { this.navigationBindings = new NavigationBindings(this, options.navigation); this.navigationBindings.initEvents(); this.navigationBindings.initUpdate(); } } /** * @private */ function onChartRender() { const navigationBindings = this.navigationBindings, disabledClassName = 'highcharts-disabled-btn'; if (this && navigationBindings) { // Check if the buttons should be enabled/disabled based on // visible series. let buttonsEnabled = false; this.series.forEach((series) => { if (!series.options.isInternal && series.visible) { buttonsEnabled = true; } }); if (this.navigationBindings && this.navigationBindings.container && this.navigationBindings.container[0]) { const container = this.navigationBindings.container[0]; objectEach(navigationBindings.boundClassNames, (value, key) => { // Get the HTML element corresponding to the className taken // from StockToolsBindings. const buttonNode = container.querySelectorAll('.' + key); if (buttonNode) { for (let i = 0; i < buttonNode.length; i++) { const button = buttonNode[i], cls = button.className; if (value.noDataState === 'normal') { // If button has noDataState: 'normal', and has // disabledClassName, remove this className. if (cls.indexOf(disabledClassName) !== -1) { button.classList.remove(disabledClassName); } } else if (!buttonsEnabled) { if (cls.indexOf(disabledClassName) === -1) { button.className += ' ' + disabledClassName; } } else { // Enable all buttons by deleting the className. if (cls.indexOf(disabledClassName) !== -1) { button.classList.remove(disabledClassName); } } } } }); } } } /** * @private */ function onNavigationBindingsClosePopup() { this.deselectAnnotation(); } /** * @private */ function onNavigationBindingsDeselectButton() { this.selectedButtonElement = null; } /** * Show edit-annotation form: * @private */ function selectableAnnotation(annotationType) { const originalClick = annotationType.prototype.defaultOptions.events && annotationType.prototype.defaultOptions.events.click; /** * Select and show popup * @private */ function selectAndShowPopup(eventArguments) { const annotation = this, navigation = annotation.chart.navigationBindings, prevAnnotation = navigation.activeAnnotation; if (originalClick) { originalClick.call(annotation, eventArguments); } 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) { if (data.actionType === 'remove') { navigation.activeAnnotation = false; navigation.chart.removeAnnotation(annotation); } else { const config = {}; navigation.fieldsToOptions(data.fields, config); navigation.deselectAnnotation(); const 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: fireEvent(navigation, 'closePopup'); } // Let bubble event to chart.click: eventArguments.activeAnnotation = true; } // #18276, show popup on touchend, but not on touchmove let touchStartX, touchStartY; /** * */ function saveCoords(e) { touchStartX = e.touches[0].clientX; touchStartY = e.touches[0].clientY; } /** * */ function checkForTouchmove(e) { const hasMoved = touchStartX ? Math.sqrt(Math.pow(touchStartX - e.changedTouches[0].clientX, 2) + Math.pow(touchStartY - e.changedTouches[0].clientY, 2)) >= 4 : false; if (!hasMoved) { selectAndShowPopup.call(this, e); } } NavigationBindings_merge(true, annotationType.prototype.defaultOptions.events, { click: selectAndShowPopup, touchstart: saveCoords, touchend: checkForTouchmove }); } /* * * * Class * * */ /** * @private */ class NavigationBindings { /* * * * Static Functions * * */ static compose(AnnotationClass, ChartClass) { if (pushUnique(composed, 'NavigationBindings')) { addEvent(AnnotationClass, 'remove', onAnnotationRemove); // Basic shapes: selectableAnnotation(AnnotationClass); // Advanced annotations: objectEach(AnnotationClass.types, (annotationType) => { selectableAnnotation(annotationType); }); addEvent(ChartClass, 'destroy', onChartDestroy); addEvent(ChartClass, 'load', onChartLoad); addEvent(ChartClass, 'render', onChartRender); addEvent(NavigationBindings, 'closePopup', onNavigationBindingsClosePopup); addEvent(NavigationBindings, 'deselectButton', onNavigationBindingsDeselectButton); setOptions(NavigationBindingsDefaults); } } /* * * * Constructor * * */ constructor(chart, options) { this.boundClassNames = void 0; this.chart = chart; this.options = options; this.eventsToUnbind = []; this.container = this.chart.container.getElementsByClassName(this.options.bindingsClassName || ''); if (!this.container.length) { this.container = doc.getElementsByClassName(this.options.bindingsClassName || ''); } } /* * * * Functions * * */ getCoords(e) { const coords = this.chart.pointer?.getCoordinates(e); return [ coords && NavigationBindings_getAssignedAxis(coords.xAxis), coords && NavigationBindings_getAssignedAxis(coords.yAxis) ]; } /** * Init all events connected to NavigationBindings. * * @private * @function Highcharts.NavigationBindings#initEvents */ initEvents() { const navigation = this, chart = navigation.chart, bindingsContainer = navigation.container, options = navigation.options; // Shorthand object for getting events for buttons: navigation.boundClassNames = {}; objectEach((options.bindings || {}), (value) => { navigation.boundClassNames[value.className] = value; }); // Handle multiple containers with the same class names: [].forEach.call(bindingsContainer, (subContainer) => { navigation.eventsToUnbind.push(addEvent(subContainer, 'click', (event) => { const bindings = navigation.getButtonEvents(subContainer, event); if (bindings && (!bindings.button.classList .contains('highcharts-disabled-btn'))) { navigation.bindingsButtonClick(bindings.button, bindings.events, event); } })); }); objectEach((options.events || {}), (callback, eventName) => { if (isFunction(callback)) { navigation.eventsToUnbind.push(addEvent(navigation, eventName, callback, { passive: false })); } }); navigation.eventsToUnbind.push(addEvent(chart.container, 'click', function (e) { if (!chart.cancelClick && chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop, { visiblePlotOnly: true })) { navigation.bindingsChartClick(this, e); } })); navigation.eventsToUnbind.push(addEvent(chart.container, (highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default()).isTouchDevice ? 'touchmove' : 'mousemove', function (e) { navigation.bindingsContainerMouseMove(this, e); }, (highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default()).isTouchDevice ? { passive: false } : void 0)); } /** * Common chart.update() delegation, shared between bindings and exporting. * * @private * @function Highcharts.NavigationBindings#initUpdate */ initUpdate() { const navigation = this; Chart_ChartNavigationComposition .compose(this.chart).navigation .addUpdate((options) => { navigation.update(options); }); } /** * Hook for click on a button, method selects/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 {Highcharts.PointerEventObject} clickEvent * Browser's click event */ bindingsButtonClick(button, events, clickEvent) { const navigation = this, chart = navigation.chart, svgContainer = chart.renderer.boxWrapper; let shouldEventBeFired = true; if (navigation.selectedButtonElement) { if (navigation.selectedButtonElement.classList === button.classList) { shouldEventBeFired = false; } 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; } } if (shouldEventBeFired) { 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('highcharts-draw-mode'); } } else { chart.stockTools && button.classList.remove('highcharts-active'); svgContainer.removeClass('highcharts-draw-mode'); navigation.nextEvent = false; navigation.mouseMoveEvent = false; navigation.selectedButton = null; } } /** * 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 {Highcharts.PointerEventObject} clickEvent * Browser's click event. */ bindingsChartClick(chart, clickEvent) { chart = this.chart; const navigation = this, activeAnnotation = navigation.activeAnnotation, selectedButton = navigation.selectedButton, svgContainer = chart.renderer.boxWrapper; if (activeAnnotation) { // Click outside popups, should close them and deselect the // annotation if (!activeAnnotation.cancelClick && // #15729 !clickEvent.activeAnnotation && // Element could be removed in the child action, e.g. button clickEvent.target.parentNode && // TO DO: Polyfill for IE11? !closestPolyfill(clickEvent.target, '.highcharts-popup')) { fireEvent(navigation, 'closePopup'); } else if (activeAnnotation.cancelClick) { // Reset cancelClick after the other event handlers have run setTimeout(() => { activeAnnotation.cancelClick = false; }, 0); } } 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 (navigation.currentUserDetails && 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('highcharts-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('highcharts-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(_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<T> * * @param {Highcharts.Dictionary<string>} fields * Fields from popup form. * * @param {T} config * Default config to be modified. * * @return {T} * Modified config */ fieldsToOptions(fields, config) { objectEach(fields, (value, field) => { const parsedValue = parseFloat(value), path = field.split('.'), pathLength = path.length - 1; // If it's a number (not "format" options), parse it: if (NavigationBindings_isNumber(parsedValue) && !value.match(/px|em/g) && !field.match(/format/g)) { value = parsedValue; } // Remove values like 0 if (value !== 'undefined') { let parent = config; path.forEach((name, index) => { if (name !== '__proto__' && name !== 'constructor') { const nextName = NavigationBindings_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() { 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 {Highcharts.Dictionary<string>} * Annotation options to be displayed in popup box */ annotationToFields(annotation) { const options = annotation.options, editables = NavigationBindings.annotationsEditable, nestedEditables = editables.nestedOptions, type = NavigationBindings_pick(options.type, options.shapes && options.shapes[0] && options.shapes[0].type, options.labels && options.labels[0] && options.labels[0].type, 'label'), nonEditables = 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 * * @param {*} option * Atomic type or object/array * * @param {string} key * Option name, for example "visible" or "x", "y" * * @param {Object} parentEditables * Editables from NavigationBindings.annotationsEditable * * @param {Object} parent * Where new options will be assigned */ function traverse(option, key, parentEditables, parent, parentKey) { let nextParent; if (parentEditables && NavigationBindings_defined(option) && 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((arrayOption, i) => { if (!isObject(arrayOption)) { // Simple arrays, e.g. [String, Number, Boolean] traverse(arrayOption, 0, nestedEditables[key], parent[key], key); } else { // Advanced arrays, e.g. [Object, Object] parent[key][i] = {}; objectEach