/** * @license Highstock JS v9.1.1 (2021-06-04) * * (c) 2009-2021 Torstein Honsi * * License: www.highcharts.com/license */ 'use strict'; (function (root, factory) { if (typeof module === 'object' && module.exports) { factory['default'] = factory; module.exports = root.document ? factory(root) : factory; } else if (typeof define === 'function' && define.amd) { define('highcharts/highstock', function () { return factory(root); }); } else { if (root.Highcharts) { root.Highcharts.error(16, true); } root.Highcharts = factory(root); } }(typeof window !== 'undefined' ? window : this, function (win) { var _modules = {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); } } _registerModule(_modules, 'Core/Globals.js', [], function () { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Constants * * */ /** * @private * @deprecated * @todo Rename UMD argument `win` to `window`; move code to `Globals.win` */ var w = (typeof win !== 'undefined' ? win : typeof window !== 'undefined' ? window : {} // eslint-disable-next-line node/no-unsupported-features/es-builtins ); /* * * * Namespace * * */ /** * Shared Highcharts properties. */ var Globals; (function (Globals) { /* * * * Constants * * */ Globals.SVG_NS = 'http://www.w3.org/2000/svg', Globals.product = 'Highcharts', Globals.version = '9.1.1', Globals.win = w, Globals.doc = Globals.win.document, Globals.svg = (Globals.doc && Globals.doc.createElementNS && !!Globals.doc.createElementNS(Globals.SVG_NS, 'svg').createSVGRect), Globals.userAgent = (Globals.win.navigator && Globals.win.navigator.userAgent) || '', Globals.isChrome = Globals.userAgent.indexOf('Chrome') !== -1, Globals.isFirefox = Globals.userAgent.indexOf('Firefox') !== -1, Globals.isMS = /(edge|msie|trident)/i.test(Globals.userAgent) && !Globals.win.opera, Globals.isSafari = !Globals.isChrome && Globals.userAgent.indexOf('Safari') !== -1, Globals.isTouchDevice = /(Mobile|Android|Windows Phone)/.test(Globals.userAgent), Globals.isWebKit = Globals.userAgent.indexOf('AppleWebKit') !== -1, Globals.deg2rad = Math.PI * 2 / 360, Globals.hasBidiBug = (Globals.isFirefox && parseInt(Globals.userAgent.split('Firefox/')[1], 10) < 4 // issue #38 ), Globals.hasTouch = !!Globals.win.TouchEvent, Globals.marginNames = [ 'plotTop', 'marginRight', 'marginBottom', 'plotLeft' ], Globals.noop = function () { }, Globals.supportsPassiveEvents = (function () { // Checks whether the browser supports passive events, (#11353). var supportsPassive = false; // Object.defineProperty doesn't work on IE as well as passive // events - instead of using polyfill, we can exclude IE totally. if (!Globals.isMS) { var opts = Object.defineProperty({}, 'passive', { get: function () { supportsPassive = true; } }); if (Globals.win.addEventListener && Globals.win.removeEventListener) { Globals.win.addEventListener('testPassive', Globals.noop, opts); Globals.win.removeEventListener('testPassive', Globals.noop, opts); } } return supportsPassive; }()); /** * An array containing the current chart objects in the page. A chart's * position in the array is preserved throughout the page's lifetime. When * a chart is destroyed, the array item becomes `undefined`. * * @name Highcharts.charts * @type {Array} */ Globals.charts = []; /** * A hook for defining additional date format specifiers. New * specifiers are defined as key-value pairs by using the * specifier as key, and a function which takes the timestamp as * value. This function returns the formatted portion of the * date. * * @sample highcharts/global/dateformats/ * Adding support for week number * * @name Highcharts.dateFormats * @type {Record} */ Globals.dateFormats = {}; /** * @private * @deprecated * @todo Use only `Core/Series/SeriesRegistry.seriesTypes` */ Globals.seriesTypes = {}; /** * @private */ Globals.symbolSizes = {}; })(Globals || (Globals = {})); /* * * * Default Export * * */ return Globals; }); _registerModule(_modules, 'Core/Utilities.js', [_modules['Core/Globals.js']], function (H) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var charts = H.charts, doc = H.doc, win = H.win; /** * An animation configuration. Animation configurations can also be defined as * booleans, where `false` turns off animation and `true` defaults to a duration * of 500ms and defer of 0ms. * * @interface Highcharts.AnimationOptionsObject */ /** * A callback function to exectute when the animation finishes. * @name Highcharts.AnimationOptionsObject#complete * @type {Function|undefined} */ /** * The animation defer in milliseconds. * @name Highcharts.AnimationOptionsObject#defer * @type {number|undefined} */ /** * The animation duration in milliseconds. * @name Highcharts.AnimationOptionsObject#duration * @type {number|undefined} */ /** * The name of an easing function as defined on the `Math` object. * @name Highcharts.AnimationOptionsObject#easing * @type {string|Function|undefined} */ /** * A callback function to execute on each step of each attribute or CSS property * that's being animated. The first argument contains information about the * animation and progress. * @name Highcharts.AnimationOptionsObject#step * @type {Function|undefined} */ /** * Creates a frame for the animated SVG element. * * @callback Highcharts.AnimationStepCallbackFunction * * @param {Highcharts.SVGElement} this * The SVG element to animate. * * @return {void} */ /** * Interface description for a class. * * @interface Highcharts.Class * @extends Function */ /** * Class costructor. * @function Highcharts.Class#new * @param {...Array<*>} args * Constructor arguments. * @return {T} * Class instance. */ /** * A style object with camel case property names to define visual appearance of * a SVG element or HTML element. The properties can be whatever styles are * supported on the given SVG or HTML element. * * @example * { * fontFamily: 'monospace', * fontSize: '1.2em' * } * * @interface Highcharts.CSSObject */ /** * @name Highcharts.CSSObject#[key:string] * @type {boolean|number|string|undefined} */ /** * Background style for the element. * @name Highcharts.CSSObject#background * @type {string|undefined} */ /** * Background color of the element. * @name Highcharts.CSSObject#backgroundColor * @type {Highcharts.ColorString|undefined} */ /** * Border style for the element. * @name Highcharts.CSSObject#border * @type {string|undefined} */ /** * Radius of the element border. * @name Highcharts.CSSObject#borderRadius * @type {number|undefined} */ /** * Color used in the element. The 'contrast' option is a Highcharts custom * property that results in black or white, depending on the background of the * element. * @name Highcharts.CSSObject#color * @type {'contrast'|Highcharts.ColorString|undefined} */ /** * Style of the mouse cursor when resting over the element. * @name Highcharts.CSSObject#cursor * @type {Highcharts.CursorValue|undefined} */ /** * Font family of the element text. Multiple values have to be in decreasing * preference order and separated by comma. * @name Highcharts.CSSObject#fontFamily * @type {string|undefined} */ /** * Font size of the element text. * @name Highcharts.CSSObject#fontSize * @type {string|undefined} */ /** * Font weight of the element text. * @name Highcharts.CSSObject#fontWeight * @type {string|undefined} */ /** * Height of the element. * @name Highcharts.CSSObject#height * @type {number|undefined} */ /** * Width of the element border. * @name Highcharts.CSSObject#lineWidth * @type {number|undefined} */ /** * Opacity of the element. * @name Highcharts.CSSObject#opacity * @type {number|undefined} */ /** * Space around the element content. * @name Highcharts.CSSObject#padding * @type {string|undefined} */ /** * Behaviour of the element when the mouse cursor rests over it. * @name Highcharts.CSSObject#pointerEvents * @type {string|undefined} */ /** * Positioning of the element. * @name Highcharts.CSSObject#position * @type {string|undefined} */ /** * Alignment of the element text. * @name Highcharts.CSSObject#textAlign * @type {string|undefined} */ /** * Additional decoration of the element text. * @name Highcharts.CSSObject#textDecoration * @type {string|undefined} */ /** * Outline style of the element text. * @name Highcharts.CSSObject#textOutline * @type {string|undefined} */ /** * Line break style of the element text. Highcharts SVG elements support * `ellipsis` when a `width` is set. * @name Highcharts.CSSObject#textOverflow * @type {string|undefined} */ /** * Top spacing of the element relative to the parent element. * @name Highcharts.CSSObject#top * @type {string|undefined} */ /** * Animated transition of selected element properties. * @name Highcharts.CSSObject#transition * @type {string|undefined} */ /** * Line break style of the element text. * @name Highcharts.CSSObject#whiteSpace * @type {string|undefined} */ /** * Width of the element. * @name Highcharts.CSSObject#width * @type {number|undefined} */ /** * All possible cursor styles. * * @typedef {'alias'|'all-scroll'|'auto'|'cell'|'col-resize'|'context-menu'|'copy'|'crosshair'|'default'|'e-resize'|'ew-resize'|'grab'|'grabbing'|'help'|'move'|'n-resize'|'ne-resize'|'nesw-resize'|'no-drop'|'none'|'not-allowed'|'ns-resize'|'nw-resize'|'nwse-resize'|'pointer'|'progress'|'row-resize'|'s-resize'|'se-resize'|'sw-resize'|'text'|'vertical-text'|'w-resize'|'wait'|'zoom-in'|'zoom-out'} Highcharts.CursorValue */ /** * All possible dash styles. * * @typedef {'Dash'|'DashDot'|'Dot'|'LongDash'|'LongDashDot'|'LongDashDotDot'|'ShortDash'|'ShortDashDot'|'ShortDashDotDot'|'ShortDot'|'Solid'} Highcharts.DashStyleValue */ /** * Generic dictionary in TypeScript notation. * Use the native `AnyRecord` instead. * * @deprecated * @interface Highcharts.Dictionary */ /** * @name Highcharts.Dictionary#[key:string] * @type {T} */ /** * The function callback to execute when the event is fired. The `this` context * contains the instance, that fired the event. * * @callback Highcharts.EventCallbackFunction * * @param {T} this * * @param {Highcharts.Dictionary<*>|Event} [eventArguments] * Event arguments. * * @return {boolean|void} */ /** * The event options for adding function callback. * * @interface Highcharts.EventOptionsObject */ /** * The order the event handler should be called. This opens for having one * handler be called before another, independent of in which order they were * added. * @name Highcharts.EventOptionsObject#order * @type {number} */ /** * Whether an event should be passive or not. * When set to `true`, the function specified by listener will never call * `preventDefault()`. * @name Highcharts.EventOptionsObject#passive * @type boolean */ /** * Formats data as a string. Usually the data is accessible throught the `this` * keyword. * * @callback Highcharts.FormatterCallbackFunction * * @param {T} this * Context to format * * @return {string} * Formatted text */ /** * An object of key-value pairs for HTML attributes. * * @typedef {Highcharts.Dictionary} Highcharts.HTMLAttributes */ /** * An HTML DOM element. The type is a reference to the regular HTMLElement in * the global scope. * * @typedef {global.HTMLElement} Highcharts.HTMLDOMElement * * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement */ /** * The iterator callback. * * @callback Highcharts.ObjectEachCallbackFunction * * @param {T} this * The context. * * @param {*} value * The property value. * * @param {string} key * The property key. * * @param {*} obj * The object that objectEach is being applied to. */ /** * An object containing `left` and `top` properties for the position in the * page. * * @interface Highcharts.OffsetObject */ /** * Left distance to the page border. * @name Highcharts.OffsetObject#left * @type {number} */ /** * Top distance to the page border. * @name Highcharts.OffsetObject#top * @type {number} */ /** * Describes a range. * * @interface Highcharts.RangeObject */ /** * Maximum number of the range. * @name Highcharts.RangeObject#max * @type {number} */ /** * Minimum number of the range. * @name Highcharts.RangeObject#min * @type {number} */ /** * If a number is given, it defines the pixel length. If a percentage string is * given, like for example `'50%'`, the setting defines a length relative to a * base size, for example the size of a container. * * @typedef {number|string} Highcharts.RelativeSize */ /** * Proceed function to call original (wrapped) function. * * @callback Highcharts.WrapProceedFunction * * @param {*} [arg1] * Optional argument. Without any arguments defaults to first argument of * the wrapping function. * * @param {*} [arg2] * Optional argument. Without any arguments defaults to second argument * of the wrapping function. * * @param {*} [arg3] * Optional argument. Without any arguments defaults to third argument of * the wrapping function. * * @return {*} * Return value of the original function. */ /** * The Highcharts object is the placeholder for all other members, and various * utility functions. The most important member of the namespace would be the * chart constructor. * * @example * let chart = Highcharts.chart('container', { ... }); * * @namespace Highcharts */ ''; // detach doclets above /** * Provide error messages for debugging, with links to online explanation. This * function can be overridden to provide custom error handling. * * @sample highcharts/chart/highcharts-error/ * Custom error handler * * @function Highcharts.error * * @param {number|string} code * The error code. See * [errors.xml](https://github.com/highcharts/highcharts/blob/master/errors/errors.xml) * for available codes. If it is a string, the error message is printed * directly in the console. * * @param {boolean} [stop=false] * Whether to throw an error or just log a warning in the console. * * @param {Highcharts.Chart} [chart] * Reference to the chart that causes the error. Used in 'debugger' * module to display errors directly on the chart. * Important note: This argument is undefined for errors that lack * access to the Chart instance. In such case, the error will be * displayed on the last created chart. * * @param {Highcharts.Dictionary} [params] * Additional parameters for the generated message. * * @return {void} */ function error(code, stop, chart, params) { var severity = stop ? 'Highcharts error' : 'Highcharts warning'; if (code === 32) { code = severity + ": Deprecated member"; } var isCode = isNumber(code); var message = isCode ? severity + " #" + code + ": www.highcharts.com/errors/" + code + "/" : code.toString(); var defaultHandler = function () { if (stop) { throw new Error(message); } // else ... if (win.console && error.messages.indexOf(message) === -1 // prevent console flooting ) { console.warn(message); // eslint-disable-line no-console } }; if (typeof params !== 'undefined') { var additionalMessages_1 = ''; if (isCode) { message += '?'; } objectEach(params, function (value, key) { additionalMessages_1 += "\n - " + key + ": " + value; if (isCode) { message += encodeURI(key) + '=' + encodeURI(value); } }); message += additionalMessages_1; } fireEvent(H, 'displayError', { chart: chart, code: code, message: message, params: params }, defaultHandler); error.messages.push(message); } (function (error) { error.messages = []; })(error || (error = {})); /* eslint-disable valid-jsdoc */ /** * Utility function to deep merge two or more objects and return a third object. * If the first argument is true, the contents of the second object is copied * into the first object. The merge function can also be used with a single * object argument to create a deep copy of an object. * * @function Highcharts.merge * * @param {boolean} extend * Whether to extend the left-side object (a) or return a whole new * object. * * @param {T|undefined} a * The first object to extend. When only this is given, the function * returns a deep copy. * * @param {...Array} [n] * An object to merge into the previous one. * * @return {T} * The merged object. If the first argument is true, the return is the * same as the second argument. */ /** * Utility function to deep merge two or more objects and return a third object. * The merge function can also be used with a single object argument to create a * deep copy of an object. * * @function Highcharts.merge * * @param {T|undefined} a * The first object to extend. When only this is given, the function * returns a deep copy. * * @param {...Array} [n] * An object to merge into the previous one. * * @return {T} * The merged object. If the first argument is true, the return is the * same as the second argument. */ function merge() { /* eslint-enable valid-jsdoc */ var i, args = arguments, ret = {}; var doCopy = function (copy, original) { // An object is replacing a primitive if (typeof copy !== 'object') { copy = {}; } objectEach(original, function (value, key) { // Prototype pollution (#14883) if (key === '__proto__' || key === 'constructor') { return; } // Copy the contents of objects, but not arrays or DOM nodes if (isObject(value, true) && !isClass(value) && !isDOMElement(value)) { copy[key] = doCopy(copy[key] || {}, value); // Primitives and arrays are copied over directly } else { copy[key] = original[key]; } }); return copy; }; // If first argument is true, copy into the existing object. Used in // setOptions. if (args[0] === true) { ret = args[1]; args = Array.prototype.slice.call(args, 2); } // For each argument, extend the return var len = args.length; for (i = 0; i < len; i++) { ret = doCopy(ret, args[i]); } return ret; } /** * Constrain a value to within a lower and upper threshold. * * @private * @param {number} value The initial value * @param {number} min The lower threshold * @param {number} max The upper threshold * @return {number} Returns a number value within min and max. */ function clamp(value, min, max) { return value > min ? value < max ? value : max : min; } // eslint-disable-next-line valid-jsdoc /** * Remove settings that have not changed, to avoid unnecessary rendering or * computing (#9197). * @private */ function cleanRecursively(newer, older) { var result = {}; objectEach(newer, function (_val, key) { var ob; // Dive into objects (except DOM nodes) if (isObject(newer[key], true) && !newer.nodeType && // #10044 older[key]) { ob = cleanRecursively(newer[key], older[key]); if (Object.keys(ob).length) { result[key] = ob; } // Arrays, primitives and DOM nodes are copied directly } else if (isObject(newer[key]) || newer[key] !== older[key]) { result[key] = newer[key]; } }); return result; } /** * Shortcut for parseInt * * @private * @function Highcharts.pInt * * @param {*} s * any * * @param {number} [mag] * Magnitude * * @return {number} * number */ function pInt(s, mag) { return parseInt(s, mag || 10); } /** * Utility function to check for string type. * * @function Highcharts.isString * * @param {*} s * The item to check. * * @return {boolean} * True if the argument is a string. */ function isString(s) { return typeof s === 'string'; } /** * Utility function to check if an item is an array. * * @function Highcharts.isArray * * @param {*} obj * The item to check. * * @return {boolean} * True if the argument is an array. */ function isArray(obj) { var str = Object.prototype.toString.call(obj); return str === '[object Array]' || str === '[object Array Iterator]'; } /** * Utility function to check if an item is of type object. * * @function Highcharts.isObject * * @param {*} obj * The item to check. * * @param {boolean} [strict=false] * Also checks that the object is not an array. * * @return {boolean} * True if the argument is an object. */ function isObject(obj, strict) { return (!!obj && typeof obj === 'object' && (!strict || !isArray(obj))); // eslint-disable-line @typescript-eslint/no-explicit-any } /** * Utility function to check if an Object is a HTML Element. * * @function Highcharts.isDOMElement * * @param {*} obj * The item to check. * * @return {boolean} * True if the argument is a HTML Element. */ function isDOMElement(obj) { return isObject(obj) && typeof obj.nodeType === 'number'; } /** * Utility function to check if an Object is a class. * * @function Highcharts.isClass * * @param {object|undefined} obj * The item to check. * * @return {boolean} * True if the argument is a class. */ function isClass(obj) { var c = obj && obj.constructor; return !!(isObject(obj, true) && !isDOMElement(obj) && (c && c.name && c.name !== 'Object')); } /** * Utility function to check if an item is a number and it is finite (not NaN, * Infinity or -Infinity). * * @function Highcharts.isNumber * * @param {*} n * The item to check. * * @return {boolean} * True if the item is a finite number */ function isNumber(n) { return typeof n === 'number' && !isNaN(n) && n < Infinity && n > -Infinity; } /** * Remove the last occurence of an item from an array. * * @function Highcharts.erase * * @param {Array<*>} arr * The array. * * @param {*} item * The item to remove. * * @return {void} */ function erase(arr, item) { var i = arr.length; while (i--) { if (arr[i] === item) { arr.splice(i, 1); break; } } } /** * Check if an object is null or undefined. * * @function Highcharts.defined * * @param {*} obj * The object to check. * * @return {boolean} * False if the object is null or undefined, otherwise true. */ function defined(obj) { return typeof obj !== 'undefined' && obj !== null; } /** * Set or get an attribute or an object of attributes. To use as a setter, pass * a key and a value, or let the second argument be a collection of keys and * values. To use as a getter, pass only a string as the second argument. * * @function Highcharts.attr * * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} elem * The DOM element to receive the attribute(s). * * @param {string|Highcharts.HTMLAttributes|Highcharts.SVGAttributes} [prop] * The property or an object of key-value pairs. * * @param {number|string} [value] * The value if a single property is set. * * @return {string|null|undefined} * When used as a getter, return the value. */ function attr(elem, prop, value) { var ret; // if the prop is a string if (isString(prop)) { // set the value if (defined(value)) { elem.setAttribute(prop, value); // get the value } else if (elem && elem.getAttribute) { ret = elem.getAttribute(prop); // IE7 and below cannot get class through getAttribute (#7850) if (!ret && prop === 'class') { ret = elem.getAttribute(prop + 'Name'); } } // else if prop is defined, it is a hash of key/value pairs } else { objectEach(prop, function (val, key) { elem.setAttribute(key, val); }); } return ret; } /** * Check if an element is an array, and if not, make it into an array. * * @function Highcharts.splat * * @param {*} obj * The object to splat. * * @return {Array} * The produced or original array. */ function splat(obj) { return isArray(obj) ? obj : [obj]; } /** * Set a timeout if the delay is given, otherwise perform the function * synchronously. * * @function Highcharts.syncTimeout * * @param {Function} fn * The function callback. * * @param {number} delay * Delay in milliseconds. * * @param {*} [context] * An optional context to send to the function callback. * * @return {number} * An identifier for the timeout that can later be cleared with * Highcharts.clearTimeout. Returns -1 if there is no timeout. */ function syncTimeout(fn, delay, context) { if (delay > 0) { return setTimeout(fn, delay, context); } fn.call(0, context); return -1; } /** * Internal clear timeout. The function checks that the `id` was not removed * (e.g. by `chart.destroy()`). For the details see * [issue #7901](https://github.com/highcharts/highcharts/issues/7901). * * @function Highcharts.clearTimeout * * @param {number} id * Id of a timeout. * * @return {void} */ function internalClearTimeout(id) { if (defined(id)) { clearTimeout(id); } } /* eslint-disable valid-jsdoc */ /** * Utility function to extend an object with the members of another. * * @function Highcharts.extend * * @param {T|undefined} a * The object to be extended. * * @param {Partial} b * The object to add to the first one. * * @return {T} * Object a, the original object. */ function extend(a, b) { /* eslint-enable valid-jsdoc */ var n; if (!a) { a = {}; } for (n in b) { // eslint-disable-line guard-for-in a[n] = b[n]; } return a; } /* eslint-disable valid-jsdoc */ /** * Return the first value that is not null or undefined. * * @function Highcharts.pick * * @param {...Array} items * Variable number of arguments to inspect. * * @return {T} * The value of the first argument that is not null or undefined. */ function pick() { var args = arguments; var length = args.length; for (var i = 0; i < length; i++) { var arg = args[i]; if (typeof arg !== 'undefined' && arg !== null) { return arg; } } } /** * Set CSS on a given element. * * @function Highcharts.css * * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} el * An HTML DOM element. * * @param {Highcharts.CSSObject} styles * Style object with camel case property names. * * @return {void} */ function css(el, styles) { if (H.isMS && !H.svg) { // #2686 if (styles && typeof styles.opacity !== 'undefined') { styles.filter = 'alpha(opacity=' + (styles.opacity * 100) + ')'; } } extend(el.style, styles); } /** * Utility function to create an HTML element with attributes and styles. * * @function Highcharts.createElement * * @param {string} tag * The HTML tag. * * @param {Highcharts.HTMLAttributes} [attribs] * Attributes as an object of key-value pairs. * * @param {Highcharts.CSSObject} [styles] * Styles as an object of key-value pairs. * * @param {Highcharts.HTMLDOMElement} [parent] * The parent HTML object. * * @param {boolean} [nopad=false] * If true, remove all padding, border and margin. * * @return {Highcharts.HTMLDOMElement} * The created DOM element. */ function createElement(tag, attribs, styles, parent, nopad) { var el = doc.createElement(tag); if (attribs) { extend(el, attribs); } if (nopad) { css(el, { padding: '0', border: 'none', margin: '0' }); } if (styles) { css(el, styles); } if (parent) { parent.appendChild(el); } return el; } // eslint-disable-next-line valid-jsdoc /** * Extend a prototyped class by new members. * * @function Highcharts.extendClass * * @param {Highcharts.Class} parent * The parent prototype to inherit. * * @param {Highcharts.Dictionary<*>} members * A collection of prototype members to add or override compared to the * parent prototype. * * @return {Highcharts.Class} * A new prototype. */ function extendClass(parent, members) { var obj = (function () { }); obj.prototype = new parent(); // eslint-disable-line new-cap extend(obj.prototype, members); return obj; } /** * Left-pad a string to a given length by adding a character repetetively. * * @function Highcharts.pad * * @param {number} number * The input string or number. * * @param {number} [length] * The desired string length. * * @param {string} [padder=0] * The character to pad with. * * @return {string} * The padded string. */ function pad(number, length, padder) { return new Array((length || 2) + 1 - String(number) .replace('-', '') .length).join(padder || '0') + number; } /** * Return a length based on either the integer value, or a percentage of a base. * * @function Highcharts.relativeLength * * @param {Highcharts.RelativeSize} value * A percentage string or a number. * * @param {number} base * The full length that represents 100%. * * @param {number} [offset=0] * A pixel offset to apply for percentage values. Used internally in * axis positioning. * * @return {number} * The computed length. */ function relativeLength(value, base, offset) { return (/%$/).test(value) ? (base * parseFloat(value) / 100) + (offset || 0) : parseFloat(value); } /** * Wrap a method with extended functionality, preserving the original function. * * @function Highcharts.wrap * * @param {*} obj * The context object that the method belongs to. In real cases, this is * often a prototype. * * @param {string} method * The name of the method to extend. * * @param {Highcharts.WrapProceedFunction} func * A wrapper function callback. This function is called with the same * arguments as the original function, except that the original function * is unshifted and passed as the first argument. */ function wrap(obj, method, func) { var proceed = obj[method]; obj[method] = function () { var args = Array.prototype.slice.call(arguments), outerArgs = arguments, ctx = this; ctx.proceed = function () { proceed.apply(ctx, arguments.length ? arguments : outerArgs); }; args.unshift(proceed); var ret = func.apply(this, args); ctx.proceed = null; return ret; }; } /** * Get the magnitude of a number. * * @function Highcharts.getMagnitude * * @param {number} num * The number. * * @return {number} * The magnitude, where 1-9 are magnitude 1, 10-99 magnitude 2 etc. */ function getMagnitude(num) { return Math.pow(10, Math.floor(Math.log(num) / Math.LN10)); } /** * Take an interval and normalize it to multiples of round numbers. * * @deprecated * @function Highcharts.normalizeTickInterval * * @param {number} interval * The raw, un-rounded interval. * * @param {Array<*>} [multiples] * Allowed multiples. * * @param {number} [magnitude] * The magnitude of the number. * * @param {boolean} [allowDecimals] * Whether to allow decimals. * * @param {boolean} [hasTickAmount] * If it has tickAmount, avoid landing on tick intervals lower than * original. * * @return {number} * The normalized interval. * * @todo * Move this function to the Axis prototype. It is here only for historical * reasons. */ function normalizeTickInterval(interval, multiples, magnitude, allowDecimals, hasTickAmount) { var i, retInterval = interval; // round to a tenfold of 1, 2, 2.5 or 5 magnitude = pick(magnitude, 1); var normalized = interval / magnitude; // multiples for a linear scale if (!multiples) { multiples = hasTickAmount ? // Finer grained ticks when the tick amount is hard set, including // when alignTicks is true on multiple axes (#4580). [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10] : // Else, let ticks fall on rounder numbers [1, 2, 2.5, 5, 10]; // the allowDecimals option if (allowDecimals === false) { if (magnitude === 1) { multiples = multiples.filter(function (num) { return num % 1 === 0; }); } else if (magnitude <= 0.1) { multiples = [1 / magnitude]; } } } // normalize the interval to the nearest multiple for (i = 0; i < multiples.length; i++) { retInterval = multiples[i]; // only allow tick amounts smaller than natural if ((hasTickAmount && retInterval * magnitude >= interval) || (!hasTickAmount && (normalized <= (multiples[i] + (multiples[i + 1] || multiples[i])) / 2))) { break; } } // Multiply back to the correct magnitude. Correct floats to appropriate // precision (#6085). retInterval = correctFloat(retInterval * magnitude, -Math.round(Math.log(0.001) / Math.LN10)); return retInterval; } /** * Sort an object array and keep the order of equal items. The ECMAScript * standard does not specify the behaviour when items are equal. * * @function Highcharts.stableSort * * @param {Array<*>} arr * The array to sort. * * @param {Function} sortFunction * The function to sort it with, like with regular Array.prototype.sort. * * @return {void} */ function stableSort(arr, sortFunction) { // @todo It seems like Chrome since v70 sorts in a stable way internally, // plus all other browsers do it, so over time we may be able to remove this // function var length = arr.length; var sortValue, i; // Add index to each item for (i = 0; i < length; i++) { arr[i].safeI = i; // stable sort index } arr.sort(function (a, b) { sortValue = sortFunction(a, b); return sortValue === 0 ? a.safeI - b.safeI : sortValue; }); // Remove index from items for (i = 0; i < length; i++) { delete arr[i].safeI; // stable sort index } } /** * Non-recursive method to find the lowest member of an array. `Math.min` raises * a maximum call stack size exceeded error in Chrome when trying to apply more * than 150.000 points. This method is slightly slower, but safe. * * @function Highcharts.arrayMin * * @param {Array<*>} data * An array of numbers. * * @return {number} * The lowest number. */ function arrayMin(data) { var i = data.length, min = data[0]; while (i--) { if (data[i] < min) { min = data[i]; } } return min; } /** * Non-recursive method to find the lowest member of an array. `Math.max` raises * a maximum call stack size exceeded error in Chrome when trying to apply more * than 150.000 points. This method is slightly slower, but safe. * * @function Highcharts.arrayMax * * @param {Array<*>} data * An array of numbers. * * @return {number} * The highest number. */ function arrayMax(data) { var i = data.length, max = data[0]; while (i--) { if (data[i] > max) { max = data[i]; } } return max; } /** * Utility method that destroys any SVGElement instances that are properties on * the given object. It loops all properties and invokes destroy if there is a * destroy method. The property is then delete. * * @function Highcharts.destroyObjectProperties * * @param {*} obj * The object to destroy properties on. * * @param {*} [except] * Exception, do not destroy this property, only delete it. */ function destroyObjectProperties(obj, except) { objectEach(obj, function (val, n) { // If the object is non-null and destroy is defined if (val && val !== except && val.destroy) { // Invoke the destroy val.destroy(); } // Delete the property from the object. delete obj[n]; }); } /** * Discard a HTML element by moving it to the bin and delete. * * @function Highcharts.discardElement * * @param {Highcharts.HTMLDOMElement} element * The HTML node to discard. */ function discardElement(element) { // create a garbage bin element, not part of the DOM if (!garbageBin) { garbageBin = createElement('div'); } // move the node and empty bin if (element) { garbageBin.appendChild(element); } garbageBin.innerHTML = ''; } var garbageBin; /** * Fix JS round off float errors. * * @function Highcharts.correctFloat * * @param {number} num * A float number to fix. * * @param {number} [prec=14] * The precision. * * @return {number} * The corrected float number. */ function correctFloat(num, prec) { return parseFloat(num.toPrecision(prec || 14)); } /** * The time unit lookup * * @ignore */ var timeUnits = { millisecond: 1, second: 1000, minute: 60000, hour: 3600000, day: 24 * 3600000, week: 7 * 24 * 3600000, month: 28 * 24 * 3600000, year: 364 * 24 * 3600000 }; /** * Easing definition * * @private * @function Math.easeInOutSine * * @param {number} pos * Current position, ranging from 0 to 1. * * @return {number} * Ease result */ Math.easeInOutSine = function (pos) { return -0.5 * (Math.cos(Math.PI * pos) - 1); }; /** * Returns the value of a property path on a given object. * * @private * @function getNestedProperty * * @param {string} path * Path to the property, for example `custom.myValue`. * * @param {unknown} obj * Instance containing the property on the specific path. * * @return {unknown} * The unknown property value. */ function getNestedProperty(path, parent) { var pathElements = path.split('.'); while (pathElements.length && defined(parent)) { var pathElement = pathElements.shift(); // Filter on the key if (typeof pathElement === 'undefined' || pathElement === '__proto__') { return; // undefined } var child = parent[pathElement]; // Filter on the child if (!defined(child) || typeof child === 'function' || typeof child.nodeType === 'number' || child === win) { return; // undefined } // Else, proceed parent = child; } return parent; } /** * Get the computed CSS value for given element and property, only for numerical * properties. For width and height, the dimension of the inner box (excluding * padding) is returned. Used for fitting the chart within the container. * * @function Highcharts.getStyle * * @param {Highcharts.HTMLDOMElement} el * An HTML element. * * @param {string} prop * The property name. * * @param {boolean} [toInt=true] * Parse to integer. * * @return {number|string|undefined} * The style value. */ function getStyle(el, prop, toInt) { var customGetStyle = (H.getStyle || // oldie getStyle getStyle); var style; // For width and height, return the actual inner pixel size (#4913) if (prop === 'width') { var offsetWidth = Math.min(el.offsetWidth, el.scrollWidth); // In flex boxes, we need to use getBoundingClientRect and floor it, // because scrollWidth doesn't support subpixel precision (#6427) ... var boundingClientRectWidth = el.getBoundingClientRect && el.getBoundingClientRect().width; // ...unless if the containing div or its parents are transform-scaled // down, in which case the boundingClientRect can't be used as it is // also scaled down (#9871, #10498). if (boundingClientRectWidth < offsetWidth && boundingClientRectWidth >= offsetWidth - 1) { offsetWidth = Math.floor(boundingClientRectWidth); } return Math.max(0, // #8377 (offsetWidth - (customGetStyle(el, 'padding-left', true) || 0) - (customGetStyle(el, 'padding-right', true) || 0))); } if (prop === 'height') { return Math.max(0, // #8377 (Math.min(el.offsetHeight, el.scrollHeight) - (customGetStyle(el, 'padding-top', true) || 0) - (customGetStyle(el, 'padding-bottom', true) || 0))); } if (!win.getComputedStyle) { // SVG not supported, forgot to load oldie.js? error(27, true); } // Otherwise, get the computed style var css = win.getComputedStyle(el, undefined); // eslint-disable-line no-undefined if (css) { style = css.getPropertyValue(prop); if (pick(toInt, prop !== 'opacity')) { style = pInt(style); } } return style; } /** * Search for an item in an array. * * @function Highcharts.inArray * * @deprecated * * @param {*} item * The item to search for. * * @param {Array<*>} arr * The array or node collection to search in. * * @param {number} [fromIndex=0] * The index to start searching from. * * @return {number} * The index within the array, or -1 if not found. */ function inArray(item, arr, fromIndex) { error(32, false, void 0, { 'Highcharts.inArray': 'use Array.indexOf' }); return arr.indexOf(item, fromIndex); } /** * Return the value of the first element in the array that satisfies the * provided testing function. * * @function Highcharts.find * * @param {Array} arr * The array to test. * * @param {Function} callback * The callback function. The function receives the item as the first * argument. Return `true` if this item satisfies the condition. * * @return {T|undefined} * The value of the element. */ var find = Array.prototype.find ? function (arr, callback) { return arr.find(callback); } : // Legacy implementation. PhantomJS, IE <= 11 etc. #7223. function (arr, callback) { var i; var length = arr.length; for (i = 0; i < length; i++) { if (callback(arr[i], i)) { // eslint-disable-line callback-return return arr[i]; } } }; /** * Returns an array of a given object's own properties. * * @function Highcharts.keys * @deprecated * * @param {*} obj * The object of which the properties are to be returned. * * @return {Array} * An array of strings that represents all the properties. */ function keys(obj) { error(32, false, void 0, { 'Highcharts.keys': 'use Object.keys' }); return Object.keys(obj); } /** * Get the element's offset position, corrected for `overflow: auto`. * * @function Highcharts.offset * * @param {global.Element} el * The DOM element. * * @return {Highcharts.OffsetObject} * An object containing `left` and `top` properties for the position in * the page. */ function offset(el) { var docElem = doc.documentElement, box = (el.parentElement || el.parentNode) ? el.getBoundingClientRect() : { top: 0, left: 0, width: 0, height: 0 }; return { top: box.top + (win.pageYOffset || docElem.scrollTop) - (docElem.clientTop || 0), left: box.left + (win.pageXOffset || docElem.scrollLeft) - (docElem.clientLeft || 0), width: box.width, height: box.height }; } /* eslint-disable valid-jsdoc */ /** * Iterate over object key pairs in an object. * * @function Highcharts.objectEach * * @param {*} obj * The object to iterate over. * * @param {Highcharts.ObjectEachCallbackFunction} fn * The iterator callback. It passes three arguments: * * value - The property value. * * key - The property key. * * obj - The object that objectEach is being applied to. * * @param {T} [ctx] * The context. * * @return {void} */ function objectEach(obj, fn, ctx) { /* eslint-enable valid-jsdoc */ for (var key in obj) { if (Object.hasOwnProperty.call(obj, key)) { fn.call(ctx || obj[key], obj[key], key, obj); } } } /** * Iterate over an array. * * @deprecated * @function Highcharts.each * * @param {Array<*>} arr * The array to iterate over. * * @param {Function} fn * The iterator callback. It passes three arguments: * - `item`: The array item. * - `index`: The item's index in the array. * - `arr`: The array that each is being applied to. * * @param {*} [ctx] * The context. * * @return {void} */ /** * Filter an array by a callback. * * @deprecated * @function Highcharts.grep * * @param {Array<*>} arr * The array to filter. * * @param {Function} callback * The callback function. The function receives the item as the first * argument. Return `true` if the item is to be preserved. * * @return {Array<*>} * A new, filtered array. */ /** * Map an array by a callback. * * @deprecated * @function Highcharts.map * * @param {Array<*>} arr * The array to map. * * @param {Function} fn * The callback function. Return the new value for the new array. * * @return {Array<*>} * A new array item with modified items. */ /** * Reduce an array to a single value. * * @deprecated * @function Highcharts.reduce * * @param {Array<*>} arr * The array to reduce. * * @param {Function} fn * The callback function. Return the reduced value. Receives 4 * arguments: Accumulated/reduced value, current value, current array * index, and the array. * * @param {*} initialValue * The initial value of the accumulator. * * @return {*} * The reduced value. */ /** * Test whether at least one element in the array passes the test implemented by * the provided function. * * @deprecated * @function Highcharts.some * * @param {Array<*>} arr * The array to test * * @param {Function} fn * The function to run on each item. Return truty to pass the test. * Receives arguments `currentValue`, `index` and `array`. * * @param {*} ctx * The context. * * @return {boolean} */ objectEach({ map: 'map', each: 'forEach', grep: 'filter', reduce: 'reduce', some: 'some' }, function (val, key) { H[key] = function (arr) { var _a; error(32, false, void 0, (_a = {}, _a["Highcharts." + key] = "use Array." + val, _a)); return Array.prototype[val].apply(arr, [].slice.call(arguments, 1)); }; }); /* eslint-disable valid-jsdoc */ /** * Add an event listener. * * @function Highcharts.addEvent * * @param {Highcharts.Class|T} el * The element or object to add a listener to. It can be a * {@link HTMLDOMElement}, an {@link SVGElement} or any other object. * * @param {string} type * The event type. * * @param {Highcharts.EventCallbackFunction|Function} fn * The function callback to execute when the event is fired. * * @param {Highcharts.EventOptionsObject} [options] * Options for adding the event. * * @return {Function} * A callback function to remove the added event. */ function addEvent(el, type, fn, options) { /* eslint-enable valid-jsdoc */ if (options === void 0) { options = {}; } // Add hcEvents to either the prototype (in case we're running addEvent on a // class) or the instance. If hasOwnProperty('hcEvents') is false, it is // inherited down the prototype chain, in which case we need to set the // property on this instance (which may itself be a prototype). var owner = typeof el === 'function' && el.prototype || el; if (!Object.hasOwnProperty.call(owner, 'hcEvents')) { owner.hcEvents = {}; } var events = owner.hcEvents; // Allow click events added to points, otherwise they will be prevented by // the TouchPointer.pinch function after a pinch zoom operation (#7091). if (H.Point && // without H a dependency loop occurs el instanceof H.Point && el.series && el.series.chart) { el.series.chart.runTrackerClick = true; } // Handle DOM events // If the browser supports passive events, add it to improve performance // on touch events (#11353). var addEventListener = (el.addEventListener || H.addEventListenerPolyfill); if (addEventListener) { addEventListener.call(el, type, fn, H.supportsPassiveEvents ? { passive: options.passive === void 0 ? type.indexOf('touch') !== -1 : options.passive, capture: false } : false); } if (!events[type]) { events[type] = []; } var eventObject = { fn: fn, order: typeof options.order === 'number' ? options.order : Infinity }; events[type].push(eventObject); // Order the calls events[type].sort(function (a, b) { return a.order - b.order; }); // Return a function that can be called to remove this event. return function () { removeEvent(el, type, fn); }; } /* eslint-disable valid-jsdoc */ /** * Remove an event that was added with {@link Highcharts#addEvent}. * * @function Highcharts.removeEvent * * @param {Highcharts.Class|T} el * The element to remove events on. * * @param {string} [type] * The type of events to remove. If undefined, all events are removed * from the element. * * @param {Highcharts.EventCallbackFunction} [fn] * The specific callback to remove. If undefined, all events that match * the element and optionally the type are removed. * * @return {void} */ function removeEvent(el, type, fn) { /* eslint-enable valid-jsdoc */ /** * @private * @param {string} type - event type * @param {Highcharts.EventCallbackFunction} fn - callback * @return {void} */ function removeOneEvent(type, fn) { var removeEventListener = (el.removeEventListener || H.removeEventListenerPolyfill); if (removeEventListener) { removeEventListener.call(el, type, fn, false); } } /** * @private * @param {any} eventCollection - collection * @return {void} */ function removeAllEvents(eventCollection) { var types, len; if (!el.nodeName) { return; // break on non-DOM events } if (type) { types = {}; types[type] = true; } else { types = eventCollection; } objectEach(types, function (_val, n) { if (eventCollection[n]) { len = eventCollection[n].length; while (len--) { removeOneEvent(n, eventCollection[n][len].fn); } } }); } var owner = typeof el === 'function' && el.prototype || el; if (Object.hasOwnProperty.call(owner, 'hcEvents')) { var events = owner.hcEvents; if (type) { var typeEvents = (events[type] || []); if (fn) { events[type] = typeEvents.filter(function (obj) { return fn !== obj.fn; }); removeOneEvent(type, fn); } else { removeAllEvents(events); events[type] = []; } } else { removeAllEvents(events); delete owner.hcEvents; } } } /* eslint-disable valid-jsdoc */ /** * Fire an event that was registered with {@link Highcharts#addEvent}. * * @function Highcharts.fireEvent * * @param {T} el * The object to fire the event on. It can be a {@link HTMLDOMElement}, * an {@link SVGElement} or any other object. * * @param {string} type * The type of event. * * @param {Highcharts.Dictionary<*>|Event} [eventArguments] * Custom event arguments that are passed on as an argument to the event * handler. * * @param {Highcharts.EventCallbackFunction|Function} [defaultFunction] * The default function to execute if the other listeners haven't * returned false. * * @return {void} */ function fireEvent(el, type, eventArguments, defaultFunction) { /* eslint-enable valid-jsdoc */ var e, i; eventArguments = eventArguments || {}; if (doc.createEvent && (el.dispatchEvent || (el.fireEvent && // Enable firing events on Highcharts instance. el !== H))) { e = doc.createEvent('Events'); e.initEvent(type, true, true); eventArguments = extend(e, eventArguments); if (el.dispatchEvent) { el.dispatchEvent(eventArguments); } else { el.fireEvent(type, eventArguments); } } else if (el.hcEvents) { if (!eventArguments.target) { // We're running a custom event extend(eventArguments, { // Attach a simple preventDefault function to skip // default handler if called. The built-in // defaultPrevented property is not overwritable (#5112) preventDefault: function () { eventArguments.defaultPrevented = true; }, // Setting target to native events fails with clicking // the zoom-out button in Chrome. target: el, // If the type is not set, we're running a custom event // (#2297). If it is set, we're running a browser event, // and setting it will cause en error in IE8 (#2465). type: type }); } var events = []; var object = el; var multilevel = false; // Recurse up the inheritance chain and collect hcEvents set as own // objects on the prototypes. while (object.hcEvents) { if (Object.hasOwnProperty.call(object, 'hcEvents') && object.hcEvents[type]) { if (events.length) { multilevel = true; } events.unshift.apply(events, object.hcEvents[type]); } object = Object.getPrototypeOf(object); } // For performance reasons, only sort the event handlers in case we are // dealing with multiple levels in the prototype chain. Otherwise, the // events are already sorted in the addEvent function. if (multilevel) { // Order the calls events.sort(function (a, b) { return a.order - b.order; }); } // Call the collected event handlers events.forEach(function (obj) { // If the event handler returns false, prevent the default handler // from executing if (obj.fn.call(el, eventArguments) === false) { eventArguments.preventDefault(); } }); } // Run the default if not prevented if (defaultFunction && !eventArguments.defaultPrevented) { defaultFunction.call(el, eventArguments); } } var serialMode; /** * Get a unique key for using in internal element id's and pointers. The key is * composed of a random hash specific to this Highcharts instance, and a * counter. * * @example * let id = uniqueKey(); // => 'highcharts-x45f6hp-0' * * @function Highcharts.uniqueKey * * @return {string} * A unique key. */ var uniqueKey = (function () { var hash = Math.random().toString(36).substring(2, 9) + '-'; var id = 0; return function () { return 'highcharts-' + (serialMode ? '' : hash) + id++; }; }()); /** * Activates a serial mode for element IDs provided by * {@link Highcharts.uniqueKey}. This mode can be used in automated tests, where * a simple comparison of two rendered SVG graphics is needed. * * **Note:** This is only for testing purposes and will break functionality in * webpages with multiple charts. * * @example * if ( * process && * process.env.NODE_ENV === 'development' * ) { * Highcharts.useSerialIds(true); * } * * @function Highcharts.useSerialIds * * @param {boolean} [mode] * Changes the state of serial mode. * * @return {boolean|undefined} * State of the serial mode. */ function useSerialIds(mode) { return (serialMode = pick(mode, serialMode)); } function isFunction(obj) { return typeof obj === 'function'; } // Register Highcharts as a plugin in jQuery if (win.jQuery) { /** * Highcharts-extended JQuery. * * @external JQuery */ /** * Helper function to return the chart of the current JQuery selector * element. * * @function external:JQuery#highcharts * * @return {Highcharts.Chart} * The chart that is linked to the JQuery selector element. */ /** * Factory function to create a chart in the current JQuery selector * element. * * @function external:JQuery#highcharts * * @param {'Chart'|'Map'|'StockChart'|string} [className] * Name of the factory class in the Highcharts namespace. * * @param {Highcharts.Options} [options] * The chart options structure. * * @param {Highcharts.ChartCallbackFunction} [callback] * Function to run when the chart has loaded and and all external * images are loaded. Defining a * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) * handler is equivalent. * * @return {JQuery} * The current JQuery selector. */ win.jQuery.fn.highcharts = function () { var args = [].slice.call(arguments); if (this[0]) { // this[0] is the renderTo div // Create the chart if (args[0]) { new H[ // eslint-disable-line computed-property-spacing, no-new // Constructor defaults to Chart isString(args[0]) ? args.shift() : 'Chart'](this[0], args[0], args[1]); return this; } // When called without parameters or with the return argument, // return an existing chart return charts[attr(this[0], 'data-highcharts-chart')]; } }; } // TODO use named exports when supported. var Utilities = { addEvent: addEvent, arrayMax: arrayMax, arrayMin: arrayMin, attr: attr, clamp: clamp, cleanRecursively: cleanRecursively, clearTimeout: internalClearTimeout, correctFloat: correctFloat, createElement: createElement, css: css, defined: defined, destroyObjectProperties: destroyObjectProperties, discardElement: discardElement, erase: erase, error: error, extend: extend, extendClass: extendClass, find: find, fireEvent: fireEvent, getMagnitude: getMagnitude, getNestedProperty: getNestedProperty, getStyle: getStyle, inArray: inArray, isArray: isArray, isClass: isClass, isDOMElement: isDOMElement, isFunction: isFunction, isNumber: isNumber, isObject: isObject, isString: isString, keys: keys, merge: merge, normalizeTickInterval: normalizeTickInterval, objectEach: objectEach, offset: offset, pad: pad, pick: pick, pInt: pInt, relativeLength: relativeLength, removeEvent: removeEvent, splat: splat, stableSort: stableSort, syncTimeout: syncTimeout, timeUnits: timeUnits, uniqueKey: uniqueKey, useSerialIds: useSerialIds, wrap: wrap }; return Utilities; }); _registerModule(_modules, 'Core/Color/Palette.js', [], function () { var palette = { /** * Colors for data series and points. */ colors: [ '#7cb5ec', '#434348', '#90ed7d', '#f7a35c', '#8085e9', '#f15c80', '#e4d354', '#2b908f', '#f45b5b', '#91e8e1' ], /** * Chart background, point stroke for markers and columns etc */ backgroundColor: '#ffffff', /** * Strong text. */ neutralColor100: '#000000', /** * Main text and some strokes. */ neutralColor80: '#333333', /** * Axis labels, axis title, connector fallback. */ neutralColor60: '#666666', /** * Credits text, export menu stroke. */ neutralColor40: '#999999', /** * Disabled texts, button strokes, crosshair etc. */ neutralColor20: '#cccccc', /** * Grid lines etc. */ neutralColor10: '#e6e6e6', /** * Minor grid lines etc. */ neutralColor5: '#f2f2f2', /** * Tooltip backgroud, button fills, map null points. */ neutralColor3: '#f7f7f7', /** * Drilldown clickable labels, color axis max color. */ highlightColor100: '#003399', /** * Selection marker, menu hover, button hover, chart border, navigator series. */ highlightColor80: '#335cad', /** * Navigator mask fill. */ highlightColor60: '#6685c2', /** * Ticks and axis line. */ highlightColor20: '#ccd6eb', /** * Pressed button, color axis min color. */ highlightColor10: '#e6ebf5', /** * Positive indicator color */ positiveColor: '#06b535', /** * Negative indicator color */ negativeColor: '#f21313' }; return palette; }); _registerModule(_modules, 'Core/Chart/ChartDefaults.js', [_modules['Core/Color/Palette.js']], function (Palette) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Constants * * */ /** * General options for the chart. * * @optionparent chart */ var ChartDefaults = { /** * Default `mapData` for all series. If set to a string, it functions * as an index into the `Highcharts.maps` array. Otherwise it is * interpreted as map data. * * @see [mapData](#series.map.mapData) * * @sample maps/demo/geojson * Loading geoJSON data * @sample maps/chart/topojson * Loading topoJSON converted to geoJSON * * @type {string|Array<*>|Highcharts.GeoJSON} * @since 5.0.0 * @product highmaps * @apioption chart.map */ /** * Set lat/lon transformation definitions for the chart. If not defined, * these are extracted from the map data. * * @type {*} * @since 5.0.0 * @product highmaps * @apioption chart.mapTransforms */ /** * When using multiple axis, the ticks of two or more opposite axes * will automatically be aligned by adding ticks to the axis or axes * with the least ticks, as if `tickAmount` were specified. * * This can be prevented by setting `alignTicks` to false. If the grid * lines look messy, it's a good idea to hide them for the secondary * axis by setting `gridLineWidth` to 0. * * If `startOnTick` or `endOnTick` in an Axis options are set to false, * then the `alignTicks ` will be disabled for the Axis. * * Disabled for logarithmic axes. * * @sample {highcharts} highcharts/chart/alignticks-true/ * True by default * @sample {highcharts} highcharts/chart/alignticks-false/ * False * @sample {highstock} stock/chart/alignticks-true/ * True by default * @sample {highstock} stock/chart/alignticks-false/ * False * * @type {boolean} * @default true * @product highcharts highstock gantt * @apioption chart.alignTicks */ /** * Set the overall animation for all chart updating. Animation can be * disabled throughout the chart by setting it to false here. It can * be overridden for each individual API method as a function parameter. * The only animation not affected by this option is the initial series * animation, see [plotOptions.series.animation]( * #plotOptions.series.animation). * * The animation can either be set as a boolean or a configuration * object. If `true`, it will use the 'swing' jQuery easing and a * duration of 500 ms. If used as a configuration object, the following * properties are supported: * * - `defer`: The animation delay time in milliseconds. * * - `duration`: The duration of the animation in milliseconds. * * - `easing`: A string reference to an easing function set on the * `Math` object. See * [the easing demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-animation-easing/). * * When zooming on a series with less than 100 points, the chart redraw * will be done with animation, but in case of more data points, it is * necessary to set this option to ensure animation on zoom. * * @sample {highcharts} highcharts/chart/animation-none/ * Updating with no animation * @sample {highcharts} highcharts/chart/animation-duration/ * With a longer duration * @sample {highcharts} highcharts/chart/animation-easing/ * With a jQuery UI easing * @sample {highmaps} maps/chart/animation-none/ * Updating with no animation * @sample {highmaps} maps/chart/animation-duration/ * With a longer duration * * @type {boolean|Partial} * @default undefined * @apioption chart.animation */ /** * A CSS class name to apply to the charts container `div`, allowing * unique CSS styling for each chart. * * @type {string} * @apioption chart.className */ /** * Event listeners for the chart. * * @apioption chart.events */ /** * Fires when a series is added to the chart after load time, using the * `addSeries` method. One parameter, `event`, is passed to the * function, containing common event information. Through * `event.options` you can access the series options that were passed to * the `addSeries` method. Returning false prevents the series from * being added. * * @sample {highcharts} highcharts/chart/events-addseries/ * Alert on add series * @sample {highstock} stock/chart/events-addseries/ * Alert on add series * * @type {Highcharts.ChartAddSeriesCallbackFunction} * @since 1.2.0 * @context Highcharts.Chart * @apioption chart.events.addSeries */ /** * Fires when clicking on the plot background. One parameter, `event`, * is passed to the function, containing common event information. * * Information on the clicked spot can be found through `event.xAxis` * and `event.yAxis`, which are arrays containing the axes of each * dimension and each axis' value at the clicked spot. The primary axes * are `event.xAxis[0]` and `event.yAxis[0]`. Remember the unit of a * datetime axis is milliseconds since 1970-01-01 00:00:00. * * ```js * click: function(e) { * console.log( * Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', e.xAxis[0].value), * e.yAxis[0].value * ) * } * ``` * * @sample {highcharts} highcharts/chart/events-click/ * Alert coordinates on click * @sample {highcharts} highcharts/chart/events-container/ * Alternatively, attach event to container * @sample {highstock} stock/chart/events-click/ * Alert coordinates on click * @sample {highstock} highcharts/chart/events-container/ * Alternatively, attach event to container * @sample {highmaps} maps/chart/events-click/ * Record coordinates on click * @sample {highmaps} highcharts/chart/events-container/ * Alternatively, attach event to container * * @type {Highcharts.ChartClickCallbackFunction} * @since 1.2.0 * @context Highcharts.Chart * @apioption chart.events.click */ /** * Fires when the chart is finished loading. Since v4.2.2, it also waits * for images to be loaded, for example from point markers. One * parameter, `event`, is passed to the function, containing common * event information. * * There is also a second parameter to the chart constructor where a * callback function can be passed to be executed on chart.load. * * @sample {highcharts} highcharts/chart/events-load/ * Alert on chart load * @sample {highstock} stock/chart/events-load/ * Alert on chart load * @sample {highmaps} maps/chart/events-load/ * Add series on chart load * * @type {Highcharts.ChartLoadCallbackFunction} * @context Highcharts.Chart * @apioption chart.events.load */ /** * Fires when the chart is redrawn, either after a call to * `chart.redraw()` or after an axis, series or point is modified with * the `redraw` option set to `true`. One parameter, `event`, is passed * to the function, containing common event information. * * @sample {highcharts} highcharts/chart/events-redraw/ * Alert on chart redraw * @sample {highstock} stock/chart/events-redraw/ * Alert on chart redraw when adding a series or moving the * zoomed range * @sample {highmaps} maps/chart/events-redraw/ * Set subtitle on chart redraw * * @type {Highcharts.ChartRedrawCallbackFunction} * @since 1.2.0 * @context Highcharts.Chart * @apioption chart.events.redraw */ /** * Fires after initial load of the chart (directly after the `load` * event), and after each redraw (directly after the `redraw` event). * * @type {Highcharts.ChartRenderCallbackFunction} * @since 5.0.7 * @context Highcharts.Chart * @apioption chart.events.render */ /** * Fires when an area of the chart has been selected. Selection is * enabled by setting the chart's zoomType. One parameter, `event`, is * passed to the function, containing common event information. The * default action for the selection event is to zoom the chart to the * selected area. It can be prevented by calling * `event.preventDefault()` or return false. * * Information on the selected area can be found through `event.xAxis` * and `event.yAxis`, which are arrays containing the axes of each * dimension and each axis' min and max values. The primary axes are * `event.xAxis[0]` and `event.yAxis[0]`. Remember the unit of a * datetime axis is milliseconds since 1970-01-01 00:00:00. * * ```js * selection: function(event) { * // log the min and max of the primary, datetime x-axis * console.log( * Highcharts.dateFormat( * '%Y-%m-%d %H:%M:%S', * event.xAxis[0].min * ), * Highcharts.dateFormat( * '%Y-%m-%d %H:%M:%S', * event.xAxis[0].max * ) * ); * // log the min and max of the y axis * console.log(event.yAxis[0].min, event.yAxis[0].max); * } * ``` * * @sample {highcharts} highcharts/chart/events-selection/ * Report on selection and reset * @sample {highcharts} highcharts/chart/events-selection-points/ * Select a range of points through a drag selection * @sample {highstock} stock/chart/events-selection/ * Report on selection and reset * @sample {highstock} highcharts/chart/events-selection-points/ * Select a range of points through a drag selection * (Highcharts) * * @type {Highcharts.ChartSelectionCallbackFunction} * @apioption chart.events.selection */ /** * The margin between the outer edge of the chart and the plot area. * The numbers in the array designate top, right, bottom and left * respectively. Use the options `marginTop`, `marginRight`, * `marginBottom` and `marginLeft` for shorthand setting of one option. * * By default there is no margin. The actual space is dynamically * calculated from the offset of axis labels, axis title, title, * subtitle and legend in addition to the `spacingTop`, `spacingRight`, * `spacingBottom` and `spacingLeft` options. * * @sample {highcharts} highcharts/chart/margins-zero/ * Zero margins * @sample {highstock} stock/chart/margin-zero/ * Zero margins * * @type {number|Array} * @apioption chart.margin */ /** * The margin between the bottom outer edge of the chart and the plot * area. Use this to set a fixed pixel value for the margin as opposed * to the default dynamic margin. See also `spacingBottom`. * * @sample {highcharts} highcharts/chart/marginbottom/ * 100px bottom margin * @sample {highstock} stock/chart/marginbottom/ * 100px bottom margin * @sample {highmaps} maps/chart/margin/ * 100px margins * * @type {number} * @since 2.0 * @apioption chart.marginBottom */ /** * The margin between the left outer edge of the chart and the plot * area. Use this to set a fixed pixel value for the margin as opposed * to the default dynamic margin. See also `spacingLeft`. * * @sample {highcharts} highcharts/chart/marginleft/ * 150px left margin * @sample {highstock} stock/chart/marginleft/ * 150px left margin * @sample {highmaps} maps/chart/margin/ * 100px margins * * @type {number} * @since 2.0 * @apioption chart.marginLeft */ /** * The margin between the right outer edge of the chart and the plot * area. Use this to set a fixed pixel value for the margin as opposed * to the default dynamic margin. See also `spacingRight`. * * @sample {highcharts} highcharts/chart/marginright/ * 100px right margin * @sample {highstock} stock/chart/marginright/ * 100px right margin * @sample {highmaps} maps/chart/margin/ * 100px margins * * @type {number} * @since 2.0 * @apioption chart.marginRight */ /** * The margin between the top outer edge of the chart and the plot area. * Use this to set a fixed pixel value for the margin as opposed to * the default dynamic margin. See also `spacingTop`. * * @sample {highcharts} highcharts/chart/margintop/ 100px top margin * @sample {highstock} stock/chart/margintop/ * 100px top margin * @sample {highmaps} maps/chart/margin/ * 100px margins * * @type {number} * @since 2.0 * @apioption chart.marginTop */ /** * Callback function to override the default function that formats all * the numbers in the chart. Returns a string with the formatted number. * * @sample highcharts/members/highcharts-numberformat * Arabic digits in Highcharts * @type {Highcharts.NumberFormatterCallbackFunction} * @since 8.0.0 * @apioption chart.numberFormatter */ /** * Allows setting a key to switch between zooming and panning. Can be * one of `alt`, `ctrl`, `meta` (the command key on Mac and Windows * key on Windows) or `shift`. The keys are mapped directly to the key * properties of the click event argument (`event.altKey`, * `event.ctrlKey`, `event.metaKey` and `event.shiftKey`). * * @type {string} * @since 4.0.3 * @product highcharts gantt * @validvalue ["alt", "ctrl", "meta", "shift"] * @apioption chart.panKey */ /** * Allow panning in a chart. Best used with [panKey](#chart.panKey) * to combine zooming and panning. * * On touch devices, when the [tooltip.followTouchMove]( * #tooltip.followTouchMove) option is `true` (default), panning * requires two fingers. To allow panning with one finger, set * `followTouchMove` to `false`. * * @sample {highcharts} highcharts/chart/pankey/ Zooming and panning * @sample {highstock} stock/chart/panning/ Zooming and xy panning */ panning: { /** * Enable or disable chart panning. * * @type {boolean} * @default {highcharts} false * @default {highstock|highmaps} true */ enabled: false, /** * Decides in what dimensions the user can pan the chart. Can be * one of `x`, `y`, or `xy`. * * @sample {highcharts} highcharts/chart/panning-type * Zooming and xy panning * * @type {string} * @validvalue ["x", "y", "xy"] * @default {highcharts|highstock} x * @default {highmaps} xy */ type: 'x' }, /** * Equivalent to [zoomType](#chart.zoomType), but for multitouch * gestures only. By default, the `pinchType` is the same as the * `zoomType` setting. However, pinching can be enabled separately in * some cases, for example in stock charts where a mouse drag pans the * chart, while pinching is enabled. When [tooltip.followTouchMove]( * #tooltip.followTouchMove) is true, pinchType only applies to * two-finger touches. * * @type {string} * @default {highcharts} undefined * @default {highstock} x * @since 3.0 * @product highcharts highstock gantt * @validvalue ["x", "y", "xy"] * @apioption chart.pinchType */ /** * Whether to apply styled mode. When in styled mode, no presentational * attributes or CSS are applied to the chart SVG. Instead, CSS rules * are required to style the chart. The default style sheet is * available from `https://code.highcharts.com/css/highcharts.css`. * * @type {boolean} * @default false * @since 7.0 * @apioption chart.styledMode */ styledMode: false, /** * The corner radius of the outer chart border. * * @sample {highcharts} highcharts/chart/borderradius/ * 20px radius * @sample {highstock} stock/chart/border/ * 10px radius * @sample {highmaps} maps/chart/border/ * Border options * */ borderRadius: 0, /** * In styled mode, this sets how many colors the class names * should rotate between. With ten colors, series (or points) are * given class names like `highcharts-color-0`, `highcharts-color-0` * [...] `highcharts-color-9`. The equivalent in non-styled mode * is to set colors using the [colors](#colors) setting. * * @since 5.0.0 */ colorCount: 10, /** * Alias of `type`. * * @sample {highcharts} highcharts/chart/defaultseriestype/ * Bar * * @deprecated * * @product highcharts */ defaultSeriesType: 'line', /** * If true, the axes will scale to the remaining visible series once * one series is hidden. If false, hiding and showing a series will * not affect the axes or the other series. For stacks, once one series * within the stack is hidden, the rest of the stack will close in * around it even if the axis is not affected. * * @sample {highcharts} highcharts/chart/ignorehiddenseries-true/ * True by default * @sample {highcharts} highcharts/chart/ignorehiddenseries-false/ * False * @sample {highcharts} highcharts/chart/ignorehiddenseries-true-stacked/ * True with stack * @sample {highstock} stock/chart/ignorehiddenseries-true/ * True by default * @sample {highstock} stock/chart/ignorehiddenseries-false/ * False * * @since 1.2.0 * @product highcharts highstock gantt */ ignoreHiddenSeries: true, /** * Whether to invert the axes so that the x axis is vertical and y axis * is horizontal. When `true`, the x axis is [reversed](#xAxis.reversed) * by default. * * @productdesc {highcharts} * If a bar series is present in the chart, it will be inverted * automatically. Inverting the chart doesn't have an effect if there * are no cartesian series in the chart, or if the chart is * [polar](#chart.polar). * * @sample {highcharts} highcharts/chart/inverted/ * Inverted line * @sample {highstock} stock/navigator/inverted/ * Inverted stock chart * * @type {boolean} * @default false * @product highcharts highstock gantt * @apioption chart.inverted */ /** * The distance between the outer edge of the chart and the content, * like title or legend, or axis title and labels if present. The * numbers in the array designate top, right, bottom and left * respectively. Use the options spacingTop, spacingRight, spacingBottom * and spacingLeft options for shorthand setting of one option. * * @type {Array} * @see [chart.margin](#chart.margin) * @default [10, 10, 15, 10] * @since 3.0.6 */ spacing: [10, 10, 15, 10], /** * The button that appears after a selection zoom, allowing the user * to reset zoom. */ resetZoomButton: { /** * What frame the button placement should be related to. Can be * either `plotBox` or `spacingBox`. * * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/ * Relative to the chart * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/ * Relative to the chart * * @type {Highcharts.ButtonRelativeToValue} * @default plot * @since 2.2 * @apioption chart.resetZoomButton.relativeTo */ /** * A collection of attributes for the button. The object takes SVG * attributes like `fill`, `stroke`, `stroke-width` or `r`, the * border radius. The theme also supports `style`, a collection of * CSS properties for the text. Equivalent attributes for the hover * state are given in `theme.states.hover`. * * @sample {highcharts} highcharts/chart/resetzoombutton-theme/ * Theming the button * @sample {highstock} highcharts/chart/resetzoombutton-theme/ * Theming the button * * @type {Highcharts.SVGAttributes} * @since 2.2 */ theme: { /** @internal */ zIndex: 6 }, /** * The position of the button. * * @sample {highcharts} highcharts/chart/resetzoombutton-position/ * Above the plot area * @sample {highstock} highcharts/chart/resetzoombutton-position/ * Above the plot area * @sample {highmaps} highcharts/chart/resetzoombutton-position/ * Above the plot area * * @type {Highcharts.AlignObject} * @since 2.2 */ position: { /** * The horizontal alignment of the button. */ align: 'right', /** * The horizontal offset of the button. */ x: -10, /** * The vertical alignment of the button. * * @type {Highcharts.VerticalAlignValue} * @default top * @apioption chart.resetZoomButton.position.verticalAlign */ /** * The vertical offset of the button. */ y: 10 } }, /** * The pixel width of the plot area border. * * @sample {highcharts} highcharts/chart/plotborderwidth/ * 1px border * @sample {highstock} stock/chart/plotborder/ * 2px border * @sample {highmaps} maps/chart/plotborder/ * Plot border options * * @type {number} * @default 0 * @apioption chart.plotBorderWidth */ /** * Whether to apply a drop shadow to the plot area. Requires that * plotBackgroundColor be set. The shadow can be an object configuration * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`. * * @sample {highcharts} highcharts/chart/plotshadow/ * Plot shadow * @sample {highstock} stock/chart/plotshadow/ * Plot shadow * @sample {highmaps} maps/chart/plotborder/ * Plot border options * * @type {boolean|Highcharts.CSSObject} * @default false * @apioption chart.plotShadow */ /** * When true, cartesian charts like line, spline, area and column are * transformed into the polar coordinate system. This produces _polar * charts_, also known as _radar charts_. * * @sample {highcharts} highcharts/demo/polar/ * Polar chart * @sample {highcharts} highcharts/demo/polar-wind-rose/ * Wind rose, stacked polar column chart * @sample {highcharts} highcharts/demo/polar-spider/ * Spider web chart * @sample {highcharts} highcharts/parallel-coordinates/polar/ * Star plot, multivariate data in a polar chart * * @type {boolean} * @default false * @since 2.3.0 * @product highcharts * @requires highcharts-more * @apioption chart.polar */ /** * Whether to reflow the chart to fit the width of the container div * on resizing the window. * * @sample {highcharts} highcharts/chart/reflow-true/ * True by default * @sample {highcharts} highcharts/chart/reflow-false/ * False * @sample {highstock} stock/chart/reflow-true/ * True by default * @sample {highstock} stock/chart/reflow-false/ * False * @sample {highmaps} maps/chart/reflow-true/ * True by default * @sample {highmaps} maps/chart/reflow-false/ * False * * @type {boolean} * @default true * @since 2.1 * @apioption chart.reflow */ /** * The HTML element where the chart will be rendered. If it is a string, * the element by that id is used. The HTML element can also be passed * by direct reference, or as the first argument of the chart * constructor, in which case the option is not needed. * * @sample {highcharts} highcharts/chart/reflow-true/ * String * @sample {highcharts} highcharts/chart/renderto-object/ * Object reference * @sample {highstock} stock/chart/renderto-string/ * String * @sample {highstock} stock/chart/renderto-object/ * Object reference * * @type {string|Highcharts.HTMLDOMElement} * @apioption chart.renderTo */ /** * The background color of the marker square when selecting (zooming * in on) an area of the chart. * * @see In styled mode, the selection marker fill is set with the * `.highcharts-selection-marker` class. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default rgba(51,92,173,0.25) * @since 2.1.7 * @apioption chart.selectionMarkerFill */ /** * Whether to apply a drop shadow to the outer chart area. Requires * that backgroundColor be set. The shadow can be an object * configuration containing `color`, `offsetX`, `offsetY`, `opacity` and * `width`. * * @sample {highcharts} highcharts/chart/shadow/ * Shadow * @sample {highstock} stock/chart/shadow/ * Shadow * @sample {highmaps} maps/chart/border/ * Chart border and shadow * * @type {boolean|Highcharts.CSSObject} * @default false * @apioption chart.shadow */ /** * Whether to show the axes initially. This only applies to empty charts * where series are added dynamically, as axes are automatically added * to cartesian series. * * @sample {highcharts} highcharts/chart/showaxes-false/ * False by default * @sample {highcharts} highcharts/chart/showaxes-true/ * True * * @type {boolean} * @since 1.2.5 * @product highcharts gantt * @apioption chart.showAxes */ /** * The space between the bottom edge of the chart and the content (plot * area, axis title and labels, title, subtitle or legend in top * position). * * @sample {highcharts} highcharts/chart/spacingbottom/ * Spacing bottom set to 100 * @sample {highstock} stock/chart/spacingbottom/ * Spacing bottom set to 100 * @sample {highmaps} maps/chart/spacing/ * Spacing 100 all around * * @type {number} * @default 15 * @since 2.1 * @apioption chart.spacingBottom */ /** * The space between the left edge of the chart and the content (plot * area, axis title and labels, title, subtitle or legend in top * position). * * @sample {highcharts} highcharts/chart/spacingleft/ * Spacing left set to 100 * @sample {highstock} stock/chart/spacingleft/ * Spacing left set to 100 * @sample {highmaps} maps/chart/spacing/ * Spacing 100 all around * * @type {number} * @default 10 * @since 2.1 * @apioption chart.spacingLeft */ /** * The space between the right edge of the chart and the content (plot * area, axis title and labels, title, subtitle or legend in top * position). * * @sample {highcharts} highcharts/chart/spacingright-100/ * Spacing set to 100 * @sample {highcharts} highcharts/chart/spacingright-legend/ * Legend in right position with default spacing * @sample {highstock} stock/chart/spacingright/ * Spacing set to 100 * @sample {highmaps} maps/chart/spacing/ * Spacing 100 all around * * @type {number} * @default 10 * @since 2.1 * @apioption chart.spacingRight */ /** * The space between the top edge of the chart and the content (plot * area, axis title and labels, title, subtitle or legend in top * position). * * @sample {highcharts} highcharts/chart/spacingtop-100/ * A top spacing of 100 * @sample {highcharts} highcharts/chart/spacingtop-10/ * Floating chart title makes the plot area align to the default * spacingTop of 10. * @sample {highstock} stock/chart/spacingtop/ * A top spacing of 100 * @sample {highmaps} maps/chart/spacing/ * Spacing 100 all around * * @type {number} * @default 10 * @since 2.1 * @apioption chart.spacingTop */ /** * Additional CSS styles to apply inline to the container `div`. Note * that since the default font styles are applied in the renderer, it * is ignorant of the individual chart options and must be set globally. * * @see In styled mode, general chart styles can be set with the * `.highcharts-root` class. * @sample {highcharts} highcharts/chart/style-serif-font/ * Using a serif type font * @sample {highcharts} highcharts/css/em/ * Styled mode with relative font sizes * @sample {highstock} stock/chart/style/ * Using a serif type font * @sample {highmaps} maps/chart/style-serif-font/ * Using a serif type font * * @type {Highcharts.CSSObject} * @default {"fontFamily": "\"Lucida Grande\", \"Lucida Sans Unicode\", Verdana, Arial, Helvetica, sans-serif","fontSize":"12px"} * @apioption chart.style */ /** * The default series type for the chart. Can be any of the chart types * listed under [plotOptions](#plotOptions) and [series](#series) or can * be a series provided by an additional module. * * In TypeScript this option has no effect in sense of typing and * instead the `type` option must always be set in the series. * * @sample {highcharts} highcharts/chart/type-bar/ * Bar * @sample {highstock} stock/chart/type/ * Areaspline * @sample {highmaps} maps/chart/type-mapline/ * Mapline * * @type {string} * @default {highcharts} line * @default {highstock} line * @default {highmaps} map * @since 2.1.0 * @apioption chart.type */ /** * Decides in what dimensions the user can zoom by dragging the mouse. * Can be one of `x`, `y` or `xy`. * * @see [panKey](#chart.panKey) * * @sample {highcharts} highcharts/chart/zoomtype-none/ * None by default * @sample {highcharts} highcharts/chart/zoomtype-x/ * X * @sample {highcharts} highcharts/chart/zoomtype-y/ * Y * @sample {highcharts} highcharts/chart/zoomtype-xy/ * Xy * @sample {highstock} stock/demo/basic-line/ * None by default * @sample {highstock} stock/chart/zoomtype-x/ * X * @sample {highstock} stock/chart/zoomtype-y/ * Y * @sample {highstock} stock/chart/zoomtype-xy/ * Xy * * @type {string} * @product highcharts highstock gantt * @validvalue ["x", "y", "xy"] * @apioption chart.zoomType */ /** * Enables zooming by a single touch, in combination with * [chart.zoomType](#chart.zoomType). When enabled, two-finger pinch * will still work as set up by [chart.pinchType](#chart.pinchType). * However, `zoomBySingleTouch` will interfere with touch-dragging the * chart to read the tooltip. And especially when vertical zooming is * enabled, it will make it hard to scroll vertically on the page. * @since 9.0.0 * @sample highcharts/chart/zoombysingletouch * Zoom by single touch enabled, with buttons to toggle * @product highcharts highstock gantt */ zoomBySingleTouch: false, /** * An explicit width for the chart. By default (when `null`) the width * is calculated from the offset width of the containing element. * * @sample {highcharts} highcharts/chart/width/ * 800px wide * @sample {highstock} stock/chart/width/ * 800px wide * @sample {highmaps} maps/chart/size/ * Chart with explicit size * * @type {null|number|string} */ width: null, /** * An explicit height for the chart. If a _number_, the height is * given in pixels. If given a _percentage string_ (for example * `'56%'`), the height is given as the percentage of the actual chart * width. This allows for preserving the aspect ratio across responsive * sizes. * * By default (when `null`) the height is calculated from the offset * height of the containing element, or 400 pixels if the containing * element's height is 0. * * @sample {highcharts} highcharts/chart/height/ * 500px height * @sample {highstock} stock/chart/height/ * 300px height * @sample {highmaps} maps/chart/size/ * Chart with explicit size * @sample highcharts/chart/height-percent/ * Highcharts with percentage height * * @type {null|number|string} */ height: null, /** * The color of the outer chart border. * * @see In styled mode, the stroke is set with the * `.highcharts-background` class. * * @sample {highcharts} highcharts/chart/bordercolor/ * Brown border * @sample {highstock} stock/chart/border/ * Brown border * @sample {highmaps} maps/chart/border/ * Border options * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ borderColor: Palette.highlightColor80, /** * The pixel width of the outer chart border. * * @see In styled mode, the stroke is set with the * `.highcharts-background` class. * * @sample {highcharts} highcharts/chart/borderwidth/ * 5px border * @sample {highstock} stock/chart/border/ * 2px border * @sample {highmaps} maps/chart/border/ * Border options * * @type {number} * @default 0 * @apioption chart.borderWidth */ /** * The background color or gradient for the outer chart area. * * @see In styled mode, the background is set with the * `.highcharts-background` class. * * @sample {highcharts} highcharts/chart/backgroundcolor-color/ * Color * @sample {highcharts} highcharts/chart/backgroundcolor-gradient/ * Gradient * @sample {highstock} stock/chart/backgroundcolor-color/ * Color * @sample {highstock} stock/chart/backgroundcolor-gradient/ * Gradient * @sample {highmaps} maps/chart/backgroundcolor-color/ * Color * @sample {highmaps} maps/chart/backgroundcolor-gradient/ * Gradient * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ backgroundColor: Palette.backgroundColor, /** * The background color or gradient for the plot area. * * @see In styled mode, the plot background is set with the * `.highcharts-plot-background` class. * * @sample {highcharts} highcharts/chart/plotbackgroundcolor-color/ * Color * @sample {highcharts} highcharts/chart/plotbackgroundcolor-gradient/ * Gradient * @sample {highstock} stock/chart/plotbackgroundcolor-color/ * Color * @sample {highstock} stock/chart/plotbackgroundcolor-gradient/ * Gradient * @sample {highmaps} maps/chart/plotbackgroundcolor-color/ * Color * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/ * Gradient * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @apioption chart.plotBackgroundColor */ /** * The URL for an image to use as the plot background. To set an image * as the background for the entire chart, set a CSS background image * to the container element. Note that for the image to be applied to * exported charts, its URL needs to be accessible by the export server. * * @see In styled mode, a plot background image can be set with the * `.highcharts-plot-background` class and a [custom pattern]( * https://www.highcharts.com/docs/chart-design-and-style/ * gradients-shadows-and-patterns). * * @sample {highcharts} highcharts/chart/plotbackgroundimage/ * Skies * @sample {highstock} stock/chart/plotbackgroundimage/ * Skies * * @type {string} * @apioption chart.plotBackgroundImage */ /** * The color of the inner chart or plot area border. * * @see In styled mode, a plot border stroke can be set with the * `.highcharts-plot-border` class. * * @sample {highcharts} highcharts/chart/plotbordercolor/ * Blue border * @sample {highstock} stock/chart/plotborder/ * Blue border * @sample {highmaps} maps/chart/plotborder/ * Plot border options * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ plotBorderColor: Palette.neutralColor20 }; /* * * * Default Export * * */ return ChartDefaults; }); _registerModule(_modules, 'Core/Color/Color.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var isNumber = U.isNumber, merge = U.merge, pInt = U.pInt; /* * * * Class * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * Handle color operations. Some object methods are chainable. * * @class * @name Highcharts.Color * * @param {Highcharts.ColorType} input * The input color in either rbga or hex format */ var Color = /** @class */ (function () { /* * * * Constructors * * */ function Color(input) { // Collection of parsers. This can be extended from the outside by pushing // parsers to Highcharts.Color.prototype.parsers. this.parsers = [{ // RGBA color // eslint-disable-next-line max-len regex: /rgba\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]?(?:\.[0-9]+)?)\s*\)/, parse: function (result) { return [ pInt(result[1]), pInt(result[2]), pInt(result[3]), parseFloat(result[4], 10) ]; } }, { // RGB color regex: /rgb\(\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*,\s*([0-9]{1,3})\s*\)/, parse: function (result) { return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1]; } }]; this.rgba = []; var GlobalColor = H.Color; // Backwards compatibility, allow class overwrite if (GlobalColor && GlobalColor !== Color) { return new GlobalColor(input); } // Backwards compatibility, allow instanciation without new (#13053) if (!(this instanceof Color)) { return new Color(input); } this.init(input); } /* * * * Static Functions * * */ /** * Creates a color instance out of a color string or object. * * @function Highcharts.Color.parse * * @param {Highcharts.ColorType} input * The input color in either rbga or hex format. * * @return {Highcharts.Color} * Color instance. */ Color.parse = function (input) { return new Color(input); }; /* * * * Functions * * */ /** * Parse the input color to rgba array * * @private * @function Highcharts.Color#init * * @param {Highcharts.ColorType} input * The input color in either rbga or hex format * * @return {void} */ Color.prototype.init = function (input) { var result, rgba, i, parser, len; this.input = input = Color.names[input && input.toLowerCase ? input.toLowerCase() : ''] || input; // Gradients if (input && input.stops) { this.stops = input.stops.map(function (stop) { return new Color(stop[1]); }); // Solid colors } else { // Bitmasking as input[0] is not working for legacy IE. if (input && input.charAt && input.charAt() === '#') { len = input.length; input = parseInt(input.substr(1), 16); // Handle long-form, e.g. #AABBCC if (len === 7) { rgba = [ (input & 0xFF0000) >> 16, (input & 0xFF00) >> 8, (input & 0xFF), 1 ]; // Handle short-form, e.g. #ABC // In short form, the value is assumed to be the same // for both nibbles for each component. e.g. #ABC = #AABBCC } else if (len === 4) { rgba = [ (((input & 0xF00) >> 4) | (input & 0xF00) >> 8), (((input & 0xF0) >> 4) | (input & 0xF0)), ((input & 0xF) << 4) | (input & 0xF), 1 ]; } } // Otherwise, check regex parsers if (!rgba) { i = this.parsers.length; while (i-- && !rgba) { parser = this.parsers[i]; result = parser.regex.exec(input); if (result) { rgba = parser.parse(result); } } } } this.rgba = rgba || []; }; /** * Return the color or gradient stops in the specified format * * @function Highcharts.Color#get * * @param {string} [format] * Possible values are 'a', 'rgb', 'rgba' (default). * * @return {Highcharts.ColorType} * This color as a string or gradient stops. */ Color.prototype.get = function (format) { var input = this.input, rgba = this.rgba, ret; if (typeof this.stops !== 'undefined') { ret = merge(input); ret.stops = [].concat(ret.stops); this.stops.forEach(function (stop, i) { ret.stops[i] = [ ret.stops[i][0], stop.get(format) ]; }); // it's NaN if gradient colors on a column chart } else if (rgba && isNumber(rgba[0])) { if (format === 'rgb' || (!format && rgba[3] === 1)) { ret = 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')'; } else if (format === 'a') { ret = rgba[3]; } else { ret = 'rgba(' + rgba.join(',') + ')'; } } else { ret = input; } return ret; }; /** * Brighten the color instance. * * @function Highcharts.Color#brighten * * @param {number} alpha * The alpha value. * * @return {Highcharts.Color} * This color with modifications. */ Color.prototype.brighten = function (alpha) { var i, rgba = this.rgba; if (this.stops) { this.stops.forEach(function (stop) { stop.brighten(alpha); }); } else if (isNumber(alpha) && alpha !== 0) { for (i = 0; i < 3; i++) { rgba[i] += pInt(alpha * 255); if (rgba[i] < 0) { rgba[i] = 0; } if (rgba[i] > 255) { rgba[i] = 255; } } } return this; }; /** * Set the color's opacity to a given alpha value. * * @function Highcharts.Color#setOpacity * * @param {number} alpha * Opacity between 0 and 1. * * @return {Highcharts.Color} * Color with modifications. */ Color.prototype.setOpacity = function (alpha) { this.rgba[3] = alpha; return this; }; /** * Return an intermediate color between two colors. * * @function Highcharts.Color#tweenTo * * @param {Highcharts.Color} to * The color object to tween to. * * @param {number} pos * The intermediate position, where 0 is the from color (current * color item), and 1 is the `to` color. * * @return {Highcharts.ColorString} * The intermediate color in rgba notation. */ Color.prototype.tweenTo = function (to, pos) { // Check for has alpha, because rgba colors perform worse due to lack of // support in WebKit. var fromRgba = this.rgba, toRgba = to.rgba, hasAlpha, ret; // Unsupported color, return to-color (#3920, #7034) if (!toRgba.length || !fromRgba || !fromRgba.length) { ret = to.input || 'none'; // Interpolate } else { hasAlpha = (toRgba[3] !== 1 || fromRgba[3] !== 1); ret = (hasAlpha ? 'rgba(' : 'rgb(') + Math.round(toRgba[0] + (fromRgba[0] - toRgba[0]) * (1 - pos)) + ',' + Math.round(toRgba[1] + (fromRgba[1] - toRgba[1]) * (1 - pos)) + ',' + Math.round(toRgba[2] + (fromRgba[2] - toRgba[2]) * (1 - pos)) + (hasAlpha ? (',' + (toRgba[3] + (fromRgba[3] - toRgba[3]) * (1 - pos))) : '') + ')'; } return ret; }; /* * * * Static Properties * * */ /** * Collection of named colors. Can be extended from the outside by adding * colors to Highcharts.Color.names. * @private */ Color.names = { white: '#ffffff', black: '#000000' }; return Color; }()); /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * A valid color to be parsed and handled by Highcharts. Highcharts internally * supports hex colors like `#ffffff`, rgb colors like `rgb(255,255,255)` and * rgba colors like `rgba(255,255,255,1)`. Other colors may be supported by the * browsers and displayed correctly, but Highcharts is not able to process them * and apply concepts like opacity and brightening. * * @typedef {string} Highcharts.ColorString */ /** * A valid color type than can be parsed and handled by Highcharts. It can be a * color string, a gradient object, or a pattern object. * * @typedef {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} Highcharts.ColorType */ /** * Gradient options instead of a solid color. * * @example * // Linear gradient used as a color option * color: { * linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 }, * stops: [ * [0, '#003399'], // start * [0.5, '#ffffff'], // middle * [1, '#3366AA'] // end * ] * } * * @interface Highcharts.GradientColorObject */ /** * Holds an object that defines the start position and the end position relative * to the shape. * @name Highcharts.GradientColorObject#linearGradient * @type {Highcharts.LinearGradientColorObject|undefined} */ /** * Holds an object that defines the center position and the radius. * @name Highcharts.GradientColorObject#radialGradient * @type {Highcharts.RadialGradientColorObject|undefined} */ /** * The first item in each tuple is the position in the gradient, where 0 is the * start of the gradient and 1 is the end of the gradient. Multiple stops can be * applied. The second item is the color for each stop. This color can also be * given in the rgba format. * @name Highcharts.GradientColorObject#stops * @type {Array} */ /** * Color stop tuple. * * @see Highcharts.GradientColorObject * * @interface Highcharts.GradientColorStopObject */ /** * @name Highcharts.GradientColorStopObject#0 * @type {number} */ /** * @name Highcharts.GradientColorStopObject#1 * @type {Highcharts.ColorString} */ /** * @name Highcharts.GradientColorStopObject#color * @type {Highcharts.Color|undefined} */ /** * Defines the start position and the end position for a gradient relative * to the shape. Start position (x1, y1) and end position (x2, y2) are relative * to the shape, where 0 means top/left and 1 is bottom/right. * * @interface Highcharts.LinearGradientColorObject */ /** * Start horizontal position of the gradient. Float ranges 0-1. * @name Highcharts.LinearGradientColorObject#x1 * @type {number} */ /** * End horizontal position of the gradient. Float ranges 0-1. * @name Highcharts.LinearGradientColorObject#x2 * @type {number} */ /** * Start vertical position of the gradient. Float ranges 0-1. * @name Highcharts.LinearGradientColorObject#y1 * @type {number} */ /** * End vertical position of the gradient. Float ranges 0-1. * @name Highcharts.LinearGradientColorObject#y2 * @type {number} */ /** * Defines the center position and the radius for a gradient. * * @interface Highcharts.RadialGradientColorObject */ /** * Center horizontal position relative to the shape. Float ranges 0-1. * @name Highcharts.RadialGradientColorObject#cx * @type {number} */ /** * Center vertical position relative to the shape. Float ranges 0-1. * @name Highcharts.RadialGradientColorObject#cy * @type {number} */ /** * Radius relative to the shape. Float ranges 0-1. * @name Highcharts.RadialGradientColorObject#r * @type {number} */ /** * Creates a color instance out of a color string. * * @function Highcharts.color * * @param {Highcharts.ColorType} input * The input color in either rbga or hex format * * @return {Highcharts.Color} * Color instance */ (''); // detach doclets above return Color; }); _registerModule(_modules, 'Core/Time.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var win = H.win; var defined = U.defined, error = U.error, extend = U.extend, isObject = U.isObject, merge = U.merge, objectEach = U.objectEach, pad = U.pad, pick = U.pick, splat = U.splat, timeUnits = U.timeUnits; /* * * * Constants * * */ var hasNewSafariBug = (H.isSafari && Intl.DateTimeFormat.prototype.formatRange); // To do: Remove this when we no longer need support for Safari < v14.1 var hasOldSafariBug = (H.isSafari && !Intl.DateTimeFormat.prototype.formatRange); /* * * * Class * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The Time class. Time settings are applied in general for each page using * `Highcharts.setOptions`, or individually for each Chart item through the * [time](https://api.highcharts.com/highcharts/time) options set. * * The Time object is available from {@link Highcharts.Chart#time}, * which refers to `Highcharts.time` if no individual time settings are * applied. * * @example * // Apply time settings globally * Highcharts.setOptions({ * time: { * timezone: 'Europe/London' * } * }); * * // Apply time settings by instance * let chart = Highcharts.chart('container', { * time: { * timezone: 'America/New_York' * }, * series: [{ * data: [1, 4, 3, 5] * }] * }); * * // Use the Time object * console.log( * 'Current time in New York', * chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now()) * ); * * @since 6.0.5 * * @class * @name Highcharts.Time * * @param {Highcharts.TimeOptions} options * Time options as defined in [chart.options.time](/highcharts/time). */ var Time = /** @class */ (function () { /* * * * Constructors * * */ function Time(options) { /* * * * Properties * * */ this.options = {}; this.useUTC = false; this.variableTimezone = false; this.Date = win.Date; /** * Get the time zone offset based on the current timezone information as * set in the global options. * * @function Highcharts.Time#getTimezoneOffset * * @param {number} timestamp * The JavaScript timestamp to inspect. * * @return {number} * The timezone offset in minutes compared to UTC. */ this.getTimezoneOffset = this.timezoneOffsetFunction(); this.update(options); } /* * * * Functions * * */ /** * Time units used in `Time.get` and `Time.set` * * @typedef {"Date"|"Day"|"FullYear"|"Hours"|"Milliseconds"|"Minutes"|"Month"|"Seconds"} Highcharts.TimeUnitValue */ /** * Get the value of a date object in given units, and subject to the Time * object's current timezone settings. This function corresponds directly to * JavaScripts `Date.getXXX / Date.getUTCXXX`, so instead of calling * `date.getHours()` or `date.getUTCHours()` we will call * `time.get('Hours')`. * * @function Highcharts.Time#get * * @param {Highcharts.TimeUnitValue} unit * @param {Date} date * * @return {number} * The given time unit */ Time.prototype.get = function (unit, date) { if (this.variableTimezone || this.timezoneOffset) { var realMs = date.getTime(); var ms = realMs - this.getTimezoneOffset(date); date.setTime(ms); // Temporary adjust to timezone var ret = date['getUTC' + unit](); date.setTime(realMs); // Reset return ret; } // UTC time with no timezone handling if (this.useUTC) { return date['getUTC' + unit](); } // Else, local time return date['get' + unit](); }; /** * Set the value of a date object in given units, and subject to the Time * object's current timezone settings. This function corresponds directly to * JavaScripts `Date.setXXX / Date.setUTCXXX`, so instead of calling * `date.setHours(0)` or `date.setUTCHours(0)` we will call * `time.set('Hours', 0)`. * * @function Highcharts.Time#set * * @param {Highcharts.TimeUnitValue} unit * @param {Date} date * @param {number} value * * @return {number} * The epoch milliseconds of the updated date */ Time.prototype.set = function (unit, date, value) { // UTC time with timezone handling if (this.variableTimezone || this.timezoneOffset) { // For lower order time units, just set it directly using UTC // time if (unit === 'Milliseconds' || unit === 'Seconds' || (unit === 'Minutes' && this.getTimezoneOffset(date) % 3600000 === 0) // #13961 ) { return date['setUTC' + unit](value); } // Higher order time units need to take the time zone into // account // Adjust by timezone var offset = this.getTimezoneOffset(date); var ms = date.getTime() - offset; date.setTime(ms); date['setUTC' + unit](value); var newOffset = this.getTimezoneOffset(date); ms = date.getTime() + newOffset; return date.setTime(ms); } // UTC time with no timezone handling if (this.useUTC || (hasNewSafariBug && unit === 'FullYear') // leap calculation in UTC only ) { return date['setUTC' + unit](value); } // Else, local time return date['set' + unit](value); }; /** * Update the Time object with current options. It is called internally on * initializing Highcharts, after running `Highcharts.setOptions` and on * `Chart.update`. * * @private * @function Highcharts.Time#update * * @param {Highcharts.TimeOptions} options * * @return {void} */ Time.prototype.update = function (options) { var useUTC = pick(options && options.useUTC, true), time = this; this.options = options = merge(true, this.options || {}, options); // Allow using a different Date class this.Date = options.Date || win.Date || Date; this.useUTC = useUTC; this.timezoneOffset = (useUTC && options.timezoneOffset); this.getTimezoneOffset = this.timezoneOffsetFunction(); /* * The time object has options allowing for variable time zones, meaning * the axis ticks or series data needs to consider this. */ this.variableTimezone = useUTC && !!(options.getTimezoneOffset || options.timezone); }; /** * Make a time and returns milliseconds. Interprets the inputs as UTC time, * local time or a specific timezone time depending on the current time * settings. * * @function Highcharts.Time#makeTime * * @param {number} year * The year * * @param {number} month * The month. Zero-based, so January is 0. * * @param {number} [date=1] * The day of the month * * @param {number} [hours=0] * The hour of the day, 0-23. * * @param {number} [minutes=0] * The minutes * * @param {number} [seconds=0] * The seconds * * @return {number} * The time in milliseconds since January 1st 1970. */ Time.prototype.makeTime = function (year, month, date, hours, minutes, seconds) { var d, offset, newOffset; if (this.useUTC) { d = this.Date.UTC.apply(0, arguments); offset = this.getTimezoneOffset(d); d += offset; newOffset = this.getTimezoneOffset(d); if (offset !== newOffset) { d += newOffset - offset; // A special case for transitioning from summer time to winter time. // When the clock is set back, the same time is repeated twice, i.e. // 02:30 am is repeated since the clock is set back from 3 am to // 2 am. We need to make the same time as local Date does. } else if (offset - 36e5 === this.getTimezoneOffset(d - 36e5) && !hasOldSafariBug) { d -= 36e5; } } else { d = new this.Date(year, month, pick(date, 1), pick(hours, 0), pick(minutes, 0), pick(seconds, 0)).getTime(); } return d; }; /** * Sets the getTimezoneOffset function. If the `timezone` option is set, a * default getTimezoneOffset function with that timezone is returned. If * a `getTimezoneOffset` option is defined, it is returned. If neither are * specified, the function using the `timezoneOffset` option or 0 offset is * returned. * * @private * @function Highcharts.Time#timezoneOffsetFunction * * @return {Function} * A getTimezoneOffset function */ Time.prototype.timezoneOffsetFunction = function () { var time = this, options = this.options, moment = options.moment || win.moment; if (!this.useUTC) { return function (timestamp) { return new Date(timestamp.toString()).getTimezoneOffset() * 60000; }; } if (options.timezone) { if (!moment) { // getTimezoneOffset-function stays undefined because it depends // on Moment.js error(25); } else { return function (timestamp) { return -moment.tz(timestamp, options.timezone).utcOffset() * 60000; }; } } // If not timezone is set, look for the getTimezoneOffset callback if (this.useUTC && options.getTimezoneOffset) { return function (timestamp) { return options.getTimezoneOffset(timestamp.valueOf()) * 60000; }; } // Last, use the `timezoneOffset` option if set return function () { return (time.timezoneOffset || 0) * 60000; }; }; /** * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970) * into a human readable date string. The available format keys are listed * below. Additional formats can be given in the * {@link Highcharts.dateFormats} hook. * * Supported format keys: * - `%a`: Short weekday, like 'Mon' * - `%A`: Long weekday, like 'Monday' * - `%d`: Two digit day of the month, 01 to 31 * - `%e`: Day of the month, 1 through 31 * - `%w`: Day of the week, 0 through 6 * - `%b`: Short month, like 'Jan' * - `%B`: Long month, like 'January' * - `%m`: Two digit month number, 01 through 12 * - `%y`: Two digits year, like 09 for 2009 * - `%Y`: Four digits year, like 2009 * - `%H`: Two digits hours in 24h format, 00 through 23 * - `%k`: Hours in 24h format, 0 through 23 * - `%I`: Two digits hours in 12h format, 00 through 11 * - `%l`: Hours in 12h format, 1 through 12 * - `%M`: Two digits minutes, 00 through 59 * - `%p`: Upper case AM or PM * - `%P`: Lower case AM or PM * - `%S`: Two digits seconds, 00 through 59 * - `%L`: Milliseconds (naming from Ruby) * * @example * const time = new Highcharts.Time(); * const s = time.dateFormat('%Y-%m-%d %H:%M:%S', Date.UTC(2020, 0, 1)); * console.log(s); // => 2020-01-01 00:00:00 * * @function Highcharts.Time#dateFormat * * @param {string} format * The desired format where various time representations are * prefixed with %. * * @param {number} [timestamp] * The JavaScript timestamp. * * @param {boolean} [capitalize=false] * Upper case first letter in the return. * * @return {string} * The formatted date. */ Time.prototype.dateFormat = function (format, timestamp, capitalize) { if (!defined(timestamp) || isNaN(timestamp)) { return (H.defaultOptions.lang && H.defaultOptions.lang.invalidDate || ''); } format = pick(format, '%Y-%m-%d %H:%M:%S'); var time = this, date = new this.Date(timestamp), // get the basic time values hours = this.get('Hours', date), day = this.get('Day', date), dayOfMonth = this.get('Date', date), month = this.get('Month', date), fullYear = this.get('FullYear', date), lang = H.defaultOptions.lang, langWeekdays = (lang && lang.weekdays), shortWeekdays = (lang && lang.shortWeekdays), // List all format keys. Custom formats can be added from the // outside. replacements = extend({ // Day // Short weekday, like 'Mon' a: shortWeekdays ? shortWeekdays[day] : langWeekdays[day].substr(0, 3), // Long weekday, like 'Monday' A: langWeekdays[day], // Two digit day of the month, 01 to 31 d: pad(dayOfMonth), // Day of the month, 1 through 31 e: pad(dayOfMonth, 2, ' '), // Day of the week, 0 through 6 w: day, // Week (none implemented) // 'W': weekNumber(), // Month // Short month, like 'Jan' b: lang.shortMonths[month], // Long month, like 'January' B: lang.months[month], // Two digit month number, 01 through 12 m: pad(month + 1), // Month number, 1 through 12 (#8150) o: month + 1, // Year // Two digits year, like 09 for 2009 y: fullYear.toString().substr(2, 2), // Four digits year, like 2009 Y: fullYear, // Time // Two digits hours in 24h format, 00 through 23 H: pad(hours), // Hours in 24h format, 0 through 23 k: hours, // Two digits hours in 12h format, 00 through 11 I: pad((hours % 12) || 12), // Hours in 12h format, 1 through 12 l: (hours % 12) || 12, // Two digits minutes, 00 through 59 M: pad(this.get('Minutes', date)), // Upper case AM or PM p: hours < 12 ? 'AM' : 'PM', // Lower case AM or PM P: hours < 12 ? 'am' : 'pm', // Two digits seconds, 00 through 59 S: pad(date.getSeconds()), // Milliseconds (naming from Ruby) L: pad(Math.floor(timestamp % 1000), 3) }, H.dateFormats); // Do the replaces objectEach(replacements, function (val, key) { // Regex would do it in one line, but this is faster while (format.indexOf('%' + key) !== -1) { format = format.replace('%' + key, typeof val === 'function' ? val.call(time, timestamp) : val); } }); // Optionally capitalize the string and return return capitalize ? (format.substr(0, 1).toUpperCase() + format.substr(1)) : format; }; /** * Resolve legacy formats of dateTimeLabelFormats (strings and arrays) into * an object. * @private * @param {string|Array|Highcharts.Dictionary} f - General format description * @return {Highcharts.Dictionary} - The object definition */ Time.prototype.resolveDTLFormat = function (f) { if (!isObject(f, true)) { // check for string or array f = splat(f); return { main: f[0], from: f[1], to: f[2] }; } return f; }; /** * Return an array with time positions distributed on round time values * right and right after min and max. Used in datetime axes as well as for * grouping data on a datetime axis. * * @function Highcharts.Time#getTimeTicks * * @param {Highcharts.TimeNormalizedObject} normalizedInterval * The interval in axis values (ms) and the count * * @param {number} [min] * The minimum in axis values * * @param {number} [max] * The maximum in axis values * * @param {number} [startOfWeek=1] * * @return {Highcharts.AxisTickPositionsArray} */ Time.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek) { var time = this, Date = time.Date, tickPositions = [], higherRanks = {}, // When crossing DST, use the max. Resolves #6278. minDate = new Date(min), interval = normalizedInterval.unitRange, count = normalizedInterval.count || 1; var i, minYear, // used in months and years as a basis for Date.UTC() variableDayLength, minDay; startOfWeek = pick(startOfWeek, 1); if (defined(min)) { // #1300 time.set('Milliseconds', minDate, interval >= timeUnits.second ? 0 : // #3935 count * Math.floor(time.get('Milliseconds', minDate) / count)); // #3652, #3654 if (interval >= timeUnits.second) { // second time.set('Seconds', minDate, interval >= timeUnits.minute ? 0 : // #3935 count * Math.floor(time.get('Seconds', minDate) / count)); } if (interval >= timeUnits.minute) { // minute time.set('Minutes', minDate, interval >= timeUnits.hour ? 0 : count * Math.floor(time.get('Minutes', minDate) / count)); } if (interval >= timeUnits.hour) { // hour time.set('Hours', minDate, interval >= timeUnits.day ? 0 : count * Math.floor(time.get('Hours', minDate) / count)); } if (interval >= timeUnits.day) { // day time.set('Date', minDate, interval >= timeUnits.month ? 1 : Math.max(1, count * Math.floor(time.get('Date', minDate) / count))); } if (interval >= timeUnits.month) { // month time.set('Month', minDate, interval >= timeUnits.year ? 0 : count * Math.floor(time.get('Month', minDate) / count)); minYear = time.get('FullYear', minDate); } if (interval >= timeUnits.year) { // year minYear -= minYear % count; time.set('FullYear', minDate, minYear); } // week is a special case that runs outside the hierarchy if (interval === timeUnits.week) { // get start of current week, independent of count minDay = time.get('Day', minDate); time.set('Date', minDate, (time.get('Date', minDate) - minDay + startOfWeek + // We don't want to skip days that are before // startOfWeek (#7051) (minDay < startOfWeek ? -7 : 0))); } // Get basics for variable time spans minYear = time.get('FullYear', minDate); var minMonth = time.get('Month', minDate), minDateDate = time.get('Date', minDate), minHours = time.get('Hours', minDate); // Redefine min to the floored/rounded minimum time (#7432) min = minDate.getTime(); // Handle local timezone offset if ((time.variableTimezone || !time.useUTC) && defined(max)) { // Detect whether we need to take the DST crossover into // consideration. If we're crossing over DST, the day length may // be 23h or 25h and we need to compute the exact clock time for // each tick instead of just adding hours. This comes at a cost, // so first we find out if it is needed (#4951). variableDayLength = ( // Long range, assume we're crossing over. max - min > 4 * timeUnits.month || // Short range, check if min and max are in different time // zones. time.getTimezoneOffset(min) !== time.getTimezoneOffset(max)); } // Iterate and add tick positions at appropriate values var t = minDate.getTime(); i = 1; while (t < max) { tickPositions.push(t); // if the interval is years, use Date.UTC to increase years if (interval === timeUnits.year) { t = time.makeTime(minYear + i * count, 0); // if the interval is months, use Date.UTC to increase months } else if (interval === timeUnits.month) { t = time.makeTime(minYear, minMonth + i * count); // if we're using global time, the interval is not fixed as it // jumps one hour at the DST crossover } else if (variableDayLength && (interval === timeUnits.day || interval === timeUnits.week)) { t = time.makeTime(minYear, minMonth, minDateDate + i * count * (interval === timeUnits.day ? 1 : 7)); } else if (variableDayLength && interval === timeUnits.hour && count > 1) { // make sure higher ranks are preserved across DST (#6797, // #7621) t = time.makeTime(minYear, minMonth, minDateDate, minHours + i * count); // else, the interval is fixed and we use simple addition } else { t += interval * count; } i++; } // push the last time tickPositions.push(t); // Handle higher ranks. Mark new days if the time is on midnight // (#950, #1649, #1760, #3349). Use a reasonable dropout threshold // to prevent looping over dense data grouping (#6156). if (interval <= timeUnits.hour && tickPositions.length < 10000) { tickPositions.forEach(function (t) { if ( // Speed optimization, no need to run dateFormat unless // we're on a full or half hour t % 1800000 === 0 && // Check for local or global midnight time.dateFormat('%H%M%S%L', t) === '000000000') { higherRanks[t] = 'day'; } }); } } // record information on the chosen unit - for dynamic label formatter tickPositions.info = extend(normalizedInterval, { higherRanks: higherRanks, totalRange: interval * count }); return tickPositions; }; return Time; }()); /** * Normalized interval. * * @interface Highcharts.TimeNormalizedObject */ /** * The count. * * @name Highcharts.TimeNormalizedObject#count * @type {number} */ /** * The interval in axis values (ms). * * @name Highcharts.TimeNormalizedObject#unitRange * @type {number} */ /** * Function of an additional date format specifier. * * @callback Highcharts.TimeFormatCallbackFunction * * @param {number} timestamp * The time to format. * * @return {string} * The formatted portion of the date. */ /** * Time ticks. * * @interface Highcharts.AxisTickPositionsArray * @extends global.Array */ /** * @name Highcharts.AxisTickPositionsArray#info * @type {Highcharts.TimeTicksInfoObject|undefined} */ /** * A callback to return the time zone offset for a given datetime. It * takes the timestamp in terms of milliseconds since January 1 1970, * and returns the timezone offset in minutes. This provides a hook * for drawing time based charts in specific time zones using their * local DST crossover dates, with the help of external libraries. * * @callback Highcharts.TimezoneOffsetCallbackFunction * * @param {number} timestamp * Timestamp in terms of milliseconds since January 1 1970. * * @return {number} * Timezone offset in minutes. */ /** * Allows to manually load the `moment.js` library from Highcharts options * instead of the `window`. * In case of loading the library from a `script` tag, * this option is not needed, it will be loaded from there by default. * * @type {function} * @since 8.2.0 * @apioption time.moment */ ''; // keeps doclets above in JS file return Time; }); _registerModule(_modules, 'Core/DefaultOptions.js', [_modules['Core/Globals.js'], _modules['Core/Chart/ChartDefaults.js'], _modules['Core/Color/Color.js'], _modules['Core/Color/Palette.js'], _modules['Core/Time.js'], _modules['Core/Utilities.js']], function (H, ChartDefaults, Color, palette, Time, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var isTouchDevice = H.isTouchDevice, svg = H.svg; var color = Color.parse; var merge = U.merge; /* * * * API Declarations * * */ /** * @typedef {"plotBox"|"spacingBox"} Highcharts.ButtonRelativeToValue */ /** * Gets fired when a series is added to the chart after load time, using the * `addSeries` method. Returning `false` prevents the series from being added. * * @callback Highcharts.ChartAddSeriesCallbackFunction * * @param {Highcharts.Chart} this * The chart on which the event occured. * * @param {Highcharts.ChartAddSeriesEventObject} event * The event that occured. */ /** * Contains common event information. Through the `options` property you can * access the series options that were passed to the `addSeries` method. * * @interface Highcharts.ChartAddSeriesEventObject */ /** * The series options that were passed to the `addSeries` method. * @name Highcharts.ChartAddSeriesEventObject#options * @type {Highcharts.SeriesOptionsType} */ /** * Prevents the default behaviour of the event. * @name Highcharts.ChartAddSeriesEventObject#preventDefault * @type {Function} */ /** * The event target. * @name Highcharts.ChartAddSeriesEventObject#target * @type {Highcharts.Chart} */ /** * The event type. * @name Highcharts.ChartAddSeriesEventObject#type * @type {"addSeries"} */ /** * Gets fired when clicking on the plot background. * * @callback Highcharts.ChartClickCallbackFunction * * @param {Highcharts.Chart} this * The chart on which the event occured. * * @param {Highcharts.PointerEventObject} event * The event that occured. */ /** * Contains an axes of the clicked spot. * * @interface Highcharts.ChartClickEventAxisObject */ /** * Axis at the clicked spot. * @name Highcharts.ChartClickEventAxisObject#axis * @type {Highcharts.Axis} */ /** * Axis value at the clicked spot. * @name Highcharts.ChartClickEventAxisObject#value * @type {number} */ /** * Contains information about the clicked spot on the chart. Remember the unit * of a datetime axis is milliseconds since 1970-01-01 00:00:00. * * @interface Highcharts.ChartClickEventObject * @extends Highcharts.PointerEventObject */ /** * Information about the x-axis on the clicked spot. * @name Highcharts.ChartClickEventObject#xAxis * @type {Array} */ /** * Information about the y-axis on the clicked spot. * @name Highcharts.ChartClickEventObject#yAxis * @type {Array} */ /** * Information about the z-axis on the clicked spot. * @name Highcharts.ChartClickEventObject#zAxis * @type {Array|undefined} */ /** * Gets fired when the chart is finished loading. * * @callback Highcharts.ChartLoadCallbackFunction * * @param {Highcharts.Chart} this * The chart on which the event occured. * * @param {global.Event} event * The event that occured. */ /** * Fires when the chart is redrawn, either after a call to `chart.redraw()` or * after an axis, series or point is modified with the `redraw` option set to * `true`. * * @callback Highcharts.ChartRedrawCallbackFunction * * @param {Highcharts.Chart} this * The chart on which the event occured. * * @param {global.Event} event * The event that occured. */ /** * Gets fired after initial load of the chart (directly after the `load` event), * and after each redraw (directly after the `redraw` event). * * @callback Highcharts.ChartRenderCallbackFunction * * @param {Highcharts.Chart} this * The chart on which the event occured. * * @param {global.Event} event * The event that occured. */ /** * Gets fired when an area of the chart has been selected. The default action * for the selection event is to zoom the chart to the selected area. It can be * prevented by calling `event.preventDefault()` or return false. * * @callback Highcharts.ChartSelectionCallbackFunction * * @param {Highcharts.Chart} this * The chart on which the event occured. * * @param {global.ChartSelectionContextObject} event * Event informations * * @return {boolean|undefined} * Return false to prevent the default action, usually zoom. */ /** * The primary axes are `xAxis[0]` and `yAxis[0]`. Remember the unit of a * datetime axis is milliseconds since 1970-01-01 00:00:00. * * @interface Highcharts.ChartSelectionContextObject * @extends global.Event */ /** * Arrays containing the axes of each dimension and each axis' min and max * values. * @name Highcharts.ChartSelectionContextObject#xAxis * @type {Array} */ /** * Arrays containing the axes of each dimension and each axis' min and max * values. * @name Highcharts.ChartSelectionContextObject#yAxis * @type {Array} */ /** * Axis context of the selection. * * @interface Highcharts.ChartSelectionAxisContextObject */ /** * The selected Axis. * @name Highcharts.ChartSelectionAxisContextObject#axis * @type {Highcharts.Axis} */ /** * The maximum axis value, either automatic or set manually. * @name Highcharts.ChartSelectionAxisContextObject#max * @type {number} */ /** * The minimum axis value, either automatic or set manually. * @name Highcharts.ChartSelectionAxisContextObject#min * @type {number} */ (''); // detach doclets above /* * * * API Options * * */ /** * Global default settings. * * @name Highcharts.defaultOptions * @type {Highcharts.Options} */ /** * @optionparent */ var defaultOptions = { /** * An array containing the default colors for the chart's series. When * all colors are used, new colors are pulled from the start again. * * Default colors can also be set on a series or series.type basis, * see [column.colors](#plotOptions.column.colors), * [pie.colors](#plotOptions.pie.colors). * * In styled mode, the colors option doesn't exist. Instead, colors * are defined in CSS and applied either through series or point class * names, or through the [chart.colorCount](#chart.colorCount) option. * * * ### Legacy * * In Highcharts 3.x, the default colors were: * ```js * colors: ['#2f7ed8', '#0d233a', '#8bbc21', '#910000', '#1aadce', * '#492970', '#f28f43', '#77a1e5', '#c42525', '#a6c96a'] * ``` * * In Highcharts 2.x, the default colors were: * ```js * colors: ['#4572A7', '#AA4643', '#89A54E', '#80699B', '#3D96AE', * '#DB843D', '#92A8CD', '#A47D7C', '#B5CA92'] * ``` * * @sample {highcharts} highcharts/chart/colors/ * Assign a global color theme * * @type {Array} * @default ["#7cb5ec", "#434348", "#90ed7d", "#f7a35c", "#8085e9", * "#f15c80", "#e4d354", "#2b908f", "#f45b5b", "#91e8e1"] */ colors: palette.colors, /** * Styled mode only. Configuration object for adding SVG definitions for * reusable elements. See [gradients, shadows and * patterns](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns) * for more information and code examples. * * @type {*} * @since 5.0.0 * @apioption defs */ /** * @ignore-option */ symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'], /** * The language object is global and it can't be set on each chart * initialization. Instead, use `Highcharts.setOptions` to set it before any * chart is initialized. * * ```js * Highcharts.setOptions({ * lang: { * months: [ * 'Janvier', 'Février', 'Mars', 'Avril', * 'Mai', 'Juin', 'Juillet', 'Août', * 'Septembre', 'Octobre', 'Novembre', 'Décembre' * ], * weekdays: [ * 'Dimanche', 'Lundi', 'Mardi', 'Mercredi', * 'Jeudi', 'Vendredi', 'Samedi' * ] * } * }); * ``` */ lang: { /** * The loading text that appears when the chart is set into the loading * state following a call to `chart.showLoading`. */ loading: 'Loading...', /** * An array containing the months names. Corresponds to the `%B` format * in `Highcharts.dateFormat()`. * * @type {Array} * @default ["January", "February", "March", "April", "May", "June", * "July", "August", "September", "October", "November", * "December"] */ months: [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December' ], /** * An array containing the months names in abbreviated form. Corresponds * to the `%b` format in `Highcharts.dateFormat()`. * * @type {Array} * @default ["Jan", "Feb", "Mar", "Apr", "May", "Jun", * "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"] */ shortMonths: [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec' ], /** * An array containing the weekday names. * * @type {Array} * @default ["Sunday", "Monday", "Tuesday", "Wednesday", "Thursday", * "Friday", "Saturday"] */ weekdays: [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday' ], /** * Short week days, starting Sunday. If not specified, Highcharts uses * the first three letters of the `lang.weekdays` option. * * @sample highcharts/lang/shortweekdays/ * Finnish two-letter abbreviations * * @type {Array} * @since 4.2.4 * @apioption lang.shortWeekdays */ /** * What to show in a date field for invalid dates. Defaults to an empty * string. * * @type {string} * @since 4.1.8 * @product highcharts highstock * @apioption lang.invalidDate */ /** * The title appearing on hovering the zoom in button. The text itself * defaults to "+" and can be changed in the button options. * * @type {string} * @default Zoom in * @product highmaps * @apioption lang.zoomIn */ /** * The title appearing on hovering the zoom out button. The text itself * defaults to "-" and can be changed in the button options. * * @type {string} * @default Zoom out * @product highmaps * @apioption lang.zoomOut */ /** * The default decimal point used in the `Highcharts.numberFormat` * method unless otherwise specified in the function arguments. * * @since 1.2.2 */ decimalPoint: '.', /** * [Metric prefixes](https://en.wikipedia.org/wiki/Metric_prefix) used * to shorten high numbers in axis labels. Replacing any of the * positions with `null` causes the full number to be written. Setting * `numericSymbols` to `null` disables shortening altogether. * * @sample {highcharts} highcharts/lang/numericsymbols/ * Replacing the symbols with text * @sample {highstock} highcharts/lang/numericsymbols/ * Replacing the symbols with text * * @type {Array} * @default ["k", "M", "G", "T", "P", "E"] * @since 2.3.0 */ numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'], /** * The magnitude of [numericSymbols](#lang.numericSymbol) replacements. * Use 10000 for Japanese, Korean and various Chinese locales, which * use symbols for 10^4, 10^8 and 10^12. * * @sample highcharts/lang/numericsymbolmagnitude/ * 10000 magnitude for Japanese * * @type {number} * @default 1000 * @since 5.0.3 * @apioption lang.numericSymbolMagnitude */ /** * The text for the label appearing when a chart is zoomed. * * @since 1.2.4 */ resetZoom: 'Reset zoom', /** * The tooltip title for the label appearing when a chart is zoomed. * * @since 1.2.4 */ resetZoomTitle: 'Reset zoom level 1:1', /** * The default thousands separator used in the `Highcharts.numberFormat` * method unless otherwise specified in the function arguments. Defaults * to a single space character, which is recommended in * [ISO 31-0](https://en.wikipedia.org/wiki/ISO_31-0#Numbers) and works * across Anglo-American and continental European languages. * * @default \u0020 * @since 1.2.2 */ thousandsSep: ' ' }, /** * Global options that don't apply to each chart. These options, like * the `lang` options, must be set using the `Highcharts.setOptions` * method. * * ```js * Highcharts.setOptions({ * global: { * useUTC: false * } * }); * ``` */ /** * _Canvg rendering for Android 2.x is removed as of Highcharts 5.0\. * Use the [libURL](#exporting.libURL) option to configure exporting._ * * The URL to the additional file to lazy load for Android 2.x devices. * These devices don't support SVG, so we download a helper file that * contains [canvg](https://github.com/canvg/canvg), its dependency * rbcolor, and our own CanVG Renderer class. To avoid hotlinking to * our site, you can install canvas-tools.js on your own server and * change this option accordingly. * * @deprecated * * @type {string} * @default https://code.highcharts.com/{version}/modules/canvas-tools.js * @product highcharts highmaps * @apioption global.canvasToolsURL */ /** * This option is deprecated since v6.0.5. Instead, use * [time.useUTC](#time.useUTC) that supports individual time settings * per chart. * * @deprecated * * @type {boolean} * @apioption global.useUTC */ /** * This option is deprecated since v6.0.5. Instead, use * [time.Date](#time.Date) that supports individual time settings * per chart. * * @deprecated * * @type {Function} * @product highcharts highstock * @apioption global.Date */ /** * This option is deprecated since v6.0.5. Instead, use * [time.getTimezoneOffset](#time.getTimezoneOffset) that supports * individual time settings per chart. * * @deprecated * * @type {Function} * @product highcharts highstock * @apioption global.getTimezoneOffset */ /** * This option is deprecated since v6.0.5. Instead, use * [time.timezone](#time.timezone) that supports individual time * settings per chart. * * @deprecated * * @type {string} * @product highcharts highstock * @apioption global.timezone */ /** * This option is deprecated since v6.0.5. Instead, use * [time.timezoneOffset](#time.timezoneOffset) that supports individual * time settings per chart. * * @deprecated * * @type {number} * @product highcharts highstock * @apioption global.timezoneOffset */ global: {}, /** * Time options that can apply globally or to individual charts. These * settings affect how `datetime` axes are laid out, how tooltips are * formatted, how series * [pointIntervalUnit](#plotOptions.series.pointIntervalUnit) works and how * the Highcharts Stock range selector handles time. * * The common use case is that all charts in the same Highcharts object * share the same time settings, in which case the global settings are set * using `setOptions`. * * ```js * // Apply time settings globally * Highcharts.setOptions({ * time: { * timezone: 'Europe/London' * } * }); * // Apply time settings by instance * let chart = Highcharts.chart('container', { * time: { * timezone: 'America/New_York' * }, * series: [{ * data: [1, 4, 3, 5] * }] * }); * * // Use the Time object * console.log( * 'Current time in New York', * chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now()) * ); * ``` * * Since v6.0.5, the time options were moved from the `global` obect to the * `time` object, and time options can be set on each individual chart. * * @sample {highcharts|highstock} * highcharts/time/timezone/ * Set the timezone globally * @sample {highcharts} * highcharts/time/individual/ * Set the timezone per chart instance * @sample {highstock} * stock/time/individual/ * Set the timezone per chart instance * * @since 6.0.5 * @optionparent time */ time: { /** * A custom `Date` class for advanced date handling. For example, * [JDate](https://github.com/tahajahangir/jdate) can be hooked in to * handle Jalali dates. * * @type {*} * @since 4.0.4 * @product highcharts highstock gantt */ Date: void 0, /** * A callback to return the time zone offset for a given datetime. It * takes the timestamp in terms of milliseconds since January 1 1970, * and returns the timezone offset in minutes. This provides a hook * for drawing time based charts in specific time zones using their * local DST crossover dates, with the help of external libraries. * * @see [global.timezoneOffset](#global.timezoneOffset) * * @sample {highcharts|highstock} highcharts/time/gettimezoneoffset/ * Use moment.js to draw Oslo time regardless of browser locale * * @type {Highcharts.TimezoneOffsetCallbackFunction} * @since 4.1.0 * @product highcharts highstock gantt */ getTimezoneOffset: void 0, /** * Requires [moment.js](https://momentjs.com/). If the timezone option * is specified, it creates a default * [getTimezoneOffset](#time.getTimezoneOffset) function that looks * up the specified timezone in moment.js. If moment.js is not included, * this throws a Highcharts error in the console, but does not crash the * chart. * * @see [getTimezoneOffset](#time.getTimezoneOffset) * * @sample {highcharts|highstock} highcharts/time/timezone/ * Europe/Oslo * * @type {string} * @since 5.0.7 * @product highcharts highstock gantt */ timezone: void 0, /** * The timezone offset in minutes. Positive values are west, negative * values are east of UTC, as in the ECMAScript * [getTimezoneOffset](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset) * method. Use this to display UTC based data in a predefined time zone. * * @see [time.getTimezoneOffset](#time.getTimezoneOffset) * * @sample {highcharts|highstock} highcharts/time/timezoneoffset/ * Timezone offset * * @since 3.0.8 * @product highcharts highstock gantt */ timezoneOffset: 0, /** * Whether to use UTC time for axis scaling, tickmark placement and * time display in `Highcharts.dateFormat`. Advantages of using UTC * is that the time displays equally regardless of the user agent's * time zone settings. Local time can be used when the data is loaded * in real time or when correct Daylight Saving Time transitions are * required. * * @sample {highcharts} highcharts/time/useutc-true/ * True by default * @sample {highcharts} highcharts/time/useutc-false/ * False */ useUTC: true }, chart: ChartDefaults, /** * The chart's main title. * * @sample {highmaps} maps/title/title/ * Title options demonstrated */ title: { /** * When the title is floating, the plot area will not move to make space * for it. * * @sample {highcharts} highcharts/chart/zoomtype-none/ * False by default * @sample {highcharts} highcharts/title/floating/ * True - title on top of the plot area * @sample {highstock} stock/chart/title-floating/ * True - title on top of the plot area * * @type {boolean} * @default false * @since 2.1 * @apioption title.floating */ /** * CSS styles for the title. Use this for font styling, but use `align`, * `x` and `y` for text alignment. * * In styled mode, the title style is given in the `.highcharts-title` * class. * * @sample {highcharts} highcharts/title/style/ * Custom color and weight * @sample {highstock} stock/chart/title-style/ * Custom color and weight * @sample highcharts/css/titles/ * Styled mode * * @type {Highcharts.CSSObject} * @default {highcharts|highmaps} { "color": "#333333", "fontSize": "18px" } * @default {highstock} { "color": "#333333", "fontSize": "16px" } * @apioption title.style */ /** * Whether to * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) * to render the text. * * @type {boolean} * @default false * @apioption title.useHTML */ /** * The vertical alignment of the title. Can be one of `"top"`, * `"middle"` and `"bottom"`. When a value is given, the title behaves * as if [floating](#title.floating) were `true`. * * @sample {highcharts} highcharts/title/verticalalign/ * Chart title in bottom right corner * @sample {highstock} stock/chart/title-verticalalign/ * Chart title in bottom right corner * * @type {Highcharts.VerticalAlignValue} * @since 2.1 * @apioption title.verticalAlign */ /** * The x position of the title relative to the alignment within * `chart.spacingLeft` and `chart.spacingRight`. * * @sample {highcharts} highcharts/title/align/ * Aligned to the plot area (x = 70px = margin left - spacing * left) * @sample {highstock} stock/chart/title-align/ * Aligned to the plot area (x = 50px = margin left - spacing * left) * * @type {number} * @default 0 * @since 2.0 * @apioption title.x */ /** * The y position of the title relative to the alignment within * [chart.spacingTop](#chart.spacingTop) and [chart.spacingBottom]( * #chart.spacingBottom). By default it depends on the font size. * * @sample {highcharts} highcharts/title/y/ * Title inside the plot area * @sample {highstock} stock/chart/title-verticalalign/ * Chart title in bottom right corner * * @type {number} * @since 2.0 * @apioption title.y */ /** * The title of the chart. To disable the title, set the `text` to * `undefined`. * * @sample {highcharts} highcharts/title/text/ * Custom title * @sample {highstock} stock/chart/title-text/ * Custom title * * @default {highcharts|highmaps} Chart title * @default {highstock} undefined */ text: 'Chart title', /** * The horizontal alignment of the title. Can be one of "left", "center" * and "right". * * @sample {highcharts} highcharts/title/align/ * Aligned to the plot area (x = 70px = margin left - spacing * left) * @sample {highstock} stock/chart/title-align/ * Aligned to the plot area (x = 50px = margin left - spacing * left) * * @type {Highcharts.AlignValue} * @since 2.0 */ align: 'center', /** * The margin between the title and the plot area, or if a subtitle * is present, the margin between the subtitle and the plot area. * * @sample {highcharts} highcharts/title/margin-50/ * A chart title margin of 50 * @sample {highcharts} highcharts/title/margin-subtitle/ * The same margin applied with a subtitle * @sample {highstock} stock/chart/title-margin/ * A chart title margin of 50 * * @since 2.1 */ margin: 15, /** * Adjustment made to the title width, normally to reserve space for * the exporting burger menu. * * @sample highcharts/title/widthadjust/ * Wider menu, greater padding * * @since 4.2.5 */ widthAdjust: -44 }, /** * The chart's subtitle. This can be used both to display a subtitle below * the main title, and to display random text anywhere in the chart. The * subtitle can be updated after chart initialization through the * `Chart.setTitle` method. * * @sample {highmaps} maps/title/subtitle/ * Subtitle options demonstrated */ subtitle: { /** * When the subtitle is floating, the plot area will not move to make * space for it. * * @sample {highcharts} highcharts/subtitle/floating/ * Floating title and subtitle * @sample {highstock} stock/chart/subtitle-footnote * Footnote floating at bottom right of plot area * * @type {boolean} * @default false * @since 2.1 * @apioption subtitle.floating */ /** * CSS styles for the title. * * In styled mode, the subtitle style is given in the * `.highcharts-subtitle` class. * * @sample {highcharts} highcharts/subtitle/style/ * Custom color and weight * @sample {highcharts} highcharts/css/titles/ * Styled mode * @sample {highstock} stock/chart/subtitle-style * Custom color and weight * @sample {highstock} highcharts/css/titles/ * Styled mode * @sample {highmaps} highcharts/css/titles/ * Styled mode * * @type {Highcharts.CSSObject} * @default {"color": "#666666"} * @apioption subtitle.style */ /** * Whether to * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) * to render the text. * * @type {boolean} * @default false * @apioption subtitle.useHTML */ /** * The vertical alignment of the title. Can be one of `"top"`, * `"middle"` and `"bottom"`. When middle, the subtitle behaves as * floating. * * @sample {highcharts} highcharts/subtitle/verticalalign/ * Footnote at the bottom right of plot area * @sample {highstock} stock/chart/subtitle-footnote * Footnote at the bottom right of plot area * * @type {Highcharts.VerticalAlignValue} * @since 2.1 * @apioption subtitle.verticalAlign */ /** * The x position of the subtitle relative to the alignment within * `chart.spacingLeft` and `chart.spacingRight`. * * @sample {highcharts} highcharts/subtitle/align/ * Footnote at right of plot area * @sample {highstock} stock/chart/subtitle-footnote * Footnote at the bottom right of plot area * * @type {number} * @default 0 * @since 2.0 * @apioption subtitle.x */ /** * The y position of the subtitle relative to the alignment within * `chart.spacingTop` and `chart.spacingBottom`. By default the subtitle * is laid out below the title unless the title is floating. * * @sample {highcharts} highcharts/subtitle/verticalalign/ * Footnote at the bottom right of plot area * @sample {highstock} stock/chart/subtitle-footnote * Footnote at the bottom right of plot area * * @type {number} * @since 2.0 * @apioption subtitle.y */ /** * The subtitle of the chart. * * @sample {highcharts|highstock} highcharts/subtitle/text/ * Custom subtitle * @sample {highcharts|highstock} highcharts/subtitle/text-formatted/ * Formatted and linked text. */ text: '', /** * The horizontal alignment of the subtitle. Can be one of "left", * "center" and "right". * * @sample {highcharts} highcharts/subtitle/align/ * Footnote at right of plot area * @sample {highstock} stock/chart/subtitle-footnote * Footnote at bottom right of plot area * * @type {Highcharts.AlignValue} * @since 2.0 */ align: 'center', /** * Adjustment made to the subtitle width, normally to reserve space * for the exporting burger menu. * * @see [title.widthAdjust](#title.widthAdjust) * * @sample highcharts/title/widthadjust/ * Wider menu, greater padding * * @since 4.2.5 */ widthAdjust: -44 }, /** * The chart's caption, which will render below the chart and will be part * of exported charts. The caption can be updated after chart initialization * through the `Chart.update` or `Chart.caption.update` methods. * * @sample highcharts/caption/text/ * A chart with a caption * @since 7.2.0 */ caption: { /** * When the caption is floating, the plot area will not move to make * space for it. * * @type {boolean} * @default false * @apioption caption.floating */ /** * The margin between the caption and the plot area. */ margin: 15, /** * CSS styles for the caption. * * In styled mode, the caption style is given in the * `.highcharts-caption` class. * * @sample {highcharts} highcharts/css/titles/ * Styled mode * * @type {Highcharts.CSSObject} * @default {"color": "#666666"} * @apioption caption.style */ /** * Whether to * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) * to render the text. * * @type {boolean} * @default false * @apioption caption.useHTML */ /** * The x position of the caption relative to the alignment within * `chart.spacingLeft` and `chart.spacingRight`. * * @type {number} * @default 0 * @apioption caption.x */ /** * The y position of the caption relative to the alignment within * `chart.spacingTop` and `chart.spacingBottom`. * * @type {number} * @apioption caption.y */ /** * The caption text of the chart. * * @sample {highcharts} highcharts/caption/text/ * Custom caption */ text: '', /** * The horizontal alignment of the caption. Can be one of "left", * "center" and "right". * * @type {Highcharts.AlignValue} */ align: 'left', /** * The vertical alignment of the caption. Can be one of `"top"`, * `"middle"` and `"bottom"`. When middle, the caption behaves as * floating. * * @type {Highcharts.VerticalAlignValue} */ verticalAlign: 'bottom' }, /** * The plotOptions is a wrapper object for config objects for each series * type. The config objects for each series can also be overridden for * each series item as given in the series array. * * Configuration options for the series are given in three levels. Options * for all series in a chart are given in the [plotOptions.series]( * #plotOptions.series) object. Then options for all series of a specific * type are given in the plotOptions of that type, for example * `plotOptions.line`. Next, options for one single series are given in * [the series array](#series). */ plotOptions: {}, /** * HTML labels that can be positioned anywhere in the chart area. * * This option is deprecated since v7.1.2. Instead, use * [annotations](#annotations) that support labels. * * @deprecated * @product highcharts highstock */ labels: { /** * An HTML label that can be positioned anywhere in the chart area. * * @deprecated * @type {Array<*>} * @apioption labels.items */ /** * Inner HTML or text for the label. * * @deprecated * @type {string} * @apioption labels.items.html */ /** * CSS styles for each label. To position the label, use left and top * like this: * ```js * style: { * left: '100px', * top: '100px' * } * ``` * * @deprecated * @type {Highcharts.CSSObject} * @apioption labels.items.style */ /** * Shared CSS styles for all labels. * * @deprecated * @type {Highcharts.CSSObject} * @default {"color": "#333333", "position": "absolute"} */ style: { /** * @ignore-option */ position: 'absolute', /** * @ignore-option */ color: palette.neutralColor80 } }, /** * The legend is a box containing a symbol and name for each series * item or point item in the chart. Each series (or points in case * of pie charts) is represented by a symbol and its name in the legend. * * It is possible to override the symbol creator function and create * [custom legend symbols](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/studies/legend-custom-symbol/). * * @productdesc {highmaps} * A Highmaps legend by default contains one legend item per series, but if * a `colorAxis` is defined, the axis will be displayed in the legend. * Either as a gradient, or as multiple legend items for `dataClasses`. */ legend: { /** * The background color of the legend. * * @see In styled mode, the legend background fill can be applied with * the `.highcharts-legend-box` class. * * @sample {highcharts} highcharts/legend/backgroundcolor/ * Yellowish background * @sample {highstock} stock/legend/align/ * Various legend options * @sample {highmaps} maps/legend/border-background/ * Border and background options * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @apioption legend.backgroundColor */ /** * The width of the drawn border around the legend. * * @see In styled mode, the legend border stroke width can be applied * with the `.highcharts-legend-box` class. * * @sample {highcharts} highcharts/legend/borderwidth/ * 2px border width * @sample {highstock} stock/legend/align/ * Various legend options * @sample {highmaps} maps/legend/border-background/ * Border and background options * * @type {number} * @default 0 * @apioption legend.borderWidth */ /** * Enable or disable the legend. There is also a series-specific option, * [showInLegend](#plotOptions.series.showInLegend), that can hide the * series from the legend. In some series types this is `false` by * default, so it must set to `true` in order to show the legend for the * series. * * @sample {highcharts} highcharts/legend/enabled-false/ Legend disabled * @sample {highstock} stock/legend/align/ Various legend options * @sample {highmaps} maps/legend/enabled-false/ Legend disabled * * @default {highstock} false * @default {highmaps} true * @default {gantt} false */ enabled: true, /** * The horizontal alignment of the legend box within the chart area. * Valid values are `left`, `center` and `right`. * * In the case that the legend is aligned in a corner position, the * `layout` option will determine whether to place it above/below * or on the side of the plot area. * * @sample {highcharts} highcharts/legend/align/ * Legend at the right of the chart * @sample {highstock} stock/legend/align/ * Various legend options * @sample {highmaps} maps/legend/alignment/ * Legend alignment * * @type {Highcharts.AlignValue} * @since 2.0 */ align: 'center', /** * If the [layout](legend.layout) is `horizontal` and the legend items * span over two lines or more, whether to align the items into vertical * columns. Setting this to `false` makes room for more items, but will * look more messy. * * @since 6.1.0 */ alignColumns: true, /** * A CSS class name to apply to the legend group. */ className: 'highcharts-no-tooltip', /** * When the legend is floating, the plot area ignores it and is allowed * to be placed below it. * * @sample {highcharts} highcharts/legend/floating-false/ * False by default * @sample {highcharts} highcharts/legend/floating-true/ * True * @sample {highmaps} maps/legend/alignment/ * Floating legend * * @type {boolean} * @default false * @since 2.1 * @apioption legend.floating */ /** * The layout of the legend items. Can be one of `horizontal` or * `vertical` or `proximate`. When `proximate`, the legend items will be * placed as close as possible to the graphs they're representing, * except in inverted charts or when the legend position doesn't allow * it. * * @sample {highcharts} highcharts/legend/layout-horizontal/ * Horizontal by default * @sample {highcharts} highcharts/legend/layout-vertical/ * Vertical * @sample highcharts/legend/layout-proximate * Labels proximate to the data * @sample {highstock} stock/legend/layout-horizontal/ * Horizontal by default * @sample {highmaps} maps/legend/padding-itemmargin/ * Vertical with data classes * @sample {highmaps} maps/legend/layout-vertical/ * Vertical with color axis gradient * * @validvalue ["horizontal", "vertical", "proximate"] */ layout: 'horizontal', /** * In a legend with horizontal layout, the itemDistance defines the * pixel distance between each item. * * @sample {highcharts} highcharts/legend/layout-horizontal/ * 50px item distance * @sample {highstock} highcharts/legend/layout-horizontal/ * 50px item distance * * @type {number} * @default {highcharts} 20 * @default {highstock} 20 * @default {highmaps} 8 * @since 3.0.3 * @apioption legend.itemDistance */ /** * The pixel bottom margin for each legend item. * * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/ * Padding and item margins demonstrated * @sample {highmaps} maps/legend/padding-itemmargin/ * Padding and item margins demonstrated * * @type {number} * @default 0 * @since 2.2.0 * @apioption legend.itemMarginBottom */ /** * The pixel top margin for each legend item. * * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/ * Padding and item margins demonstrated * @sample {highmaps} maps/legend/padding-itemmargin/ * Padding and item margins demonstrated * * @type {number} * @default 0 * @since 2.2.0 * @apioption legend.itemMarginTop */ /** * The width for each legend item. By default the items are laid out * successively. In a [horizontal layout](legend.layout), if the items * are laid out across two rows or more, they will be vertically aligned * depending on the [legend.alignColumns](legend.alignColumns) option. * * @sample {highcharts} highcharts/legend/itemwidth-default/ * Undefined by default * @sample {highcharts} highcharts/legend/itemwidth-80/ * 80 for aligned legend items * * @type {number} * @since 2.0 * @apioption legend.itemWidth */ /** * A [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) * for each legend label. Available variables relates to properties on * the series, or the point in case of pies. * * @type {string} * @default {name} * @since 1.3 * @apioption legend.labelFormat */ /* eslint-disable valid-jsdoc */ /** * Callback function to format each of the series' labels. The `this` * keyword refers to the series object, or the point object in case of * pie charts. By default the series or point name is printed. * * @productdesc {highmaps} * In Highmaps the context can also be a data class in case of a * `colorAxis`. * * @sample {highcharts} highcharts/legend/labelformatter/ * Add text * @sample {highmaps} maps/legend/labelformatter/ * Data classes with label formatter * * @type {Highcharts.FormatterCallbackFunction} */ labelFormatter: function () { /** eslint-enable valid-jsdoc */ return this.name; }, /** * Line height for the legend items. Deprecated as of 2.1\. Instead, * the line height for each item can be set using * `itemStyle.lineHeight`, and the padding between items using * `itemMarginTop` and `itemMarginBottom`. * * @sample {highcharts} highcharts/legend/lineheight/ * Setting padding * * @deprecated * * @type {number} * @default 16 * @since 2.0 * @product highcharts gantt * @apioption legend.lineHeight */ /** * If the plot area sized is calculated automatically and the legend is * not floating, the legend margin is the space between the legend and * the axis labels or plot area. * * @sample {highcharts} highcharts/legend/margin-default/ * 12 pixels by default * @sample {highcharts} highcharts/legend/margin-30/ * 30 pixels * * @type {number} * @default 12 * @since 2.1 * @apioption legend.margin */ /** * Maximum pixel height for the legend. When the maximum height is * extended, navigation will show. * * @type {number} * @since 2.3.0 * @apioption legend.maxHeight */ /** * The color of the drawn border around the legend. * * @see In styled mode, the legend border stroke can be applied with the * `.highcharts-legend-box` class. * * @sample {highcharts} highcharts/legend/bordercolor/ * Brown border * @sample {highstock} stock/legend/align/ * Various legend options * @sample {highmaps} maps/legend/border-background/ * Border and background options * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ borderColor: palette.neutralColor40, /** * The border corner radius of the legend. * * @sample {highcharts} highcharts/legend/borderradius-default/ * Square by default * @sample {highcharts} highcharts/legend/borderradius-round/ * 5px rounded * @sample {highmaps} maps/legend/border-background/ * Border and background options */ borderRadius: 0, /** * Options for the paging or navigation appearing when the legend is * overflown. Navigation works well on screen, but not in static * exported images. One way of working around that is to * [increase the chart height in * export](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/legend/navigation-enabled-false/). */ navigation: { /** * How to animate the pages when navigating up or down. A value of * `true` applies the default navigation given in the * `chart.animation` option. Additional options can be given as an * object containing values for easing and duration. * * @sample {highcharts} highcharts/legend/navigation/ * Legend page navigation demonstrated * @sample {highstock} highcharts/legend/navigation/ * Legend page navigation demonstrated * * @type {boolean|Partial} * @default true * @since 2.2.4 * @apioption legend.navigation.animation */ /** * The pixel size of the up and down arrows in the legend paging * navigation. * * @sample {highcharts} highcharts/legend/navigation/ * Legend page navigation demonstrated * @sample {highstock} highcharts/legend/navigation/ * Legend page navigation demonstrated * * @type {number} * @default 12 * @since 2.2.4 * @apioption legend.navigation.arrowSize */ /** * Whether to enable the legend navigation. In most cases, disabling * the navigation results in an unwanted overflow. * * See also the [adapt chart to legend]( * https://www.highcharts.com/products/plugin-registry/single/8/Adapt-Chart-To-Legend) * plugin for a solution to extend the chart height to make room for * the legend, optionally in exported charts only. * * @type {boolean} * @default true * @since 4.2.4 * @apioption legend.navigation.enabled */ /** * Text styles for the legend page navigation. * * @see In styled mode, the navigation items are styled with the * `.highcharts-legend-navigation` class. * * @sample {highcharts} highcharts/legend/navigation/ * Legend page navigation demonstrated * @sample {highstock} highcharts/legend/navigation/ * Legend page navigation demonstrated * * @type {Highcharts.CSSObject} * @since 2.2.4 * @apioption legend.navigation.style */ /** * The color for the active up or down arrow in the legend page * navigation. * * @see In styled mode, the active arrow be styled with the * `.highcharts-legend-nav-active` class. * * @sample {highcharts} highcharts/legend/navigation/ * Legend page navigation demonstrated * @sample {highstock} highcharts/legend/navigation/ * Legend page navigation demonstrated * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 2.2.4 */ activeColor: palette.highlightColor100, /** * The color of the inactive up or down arrow in the legend page * navigation. . * * @see In styled mode, the inactive arrow be styled with the * `.highcharts-legend-nav-inactive` class. * * @sample {highcharts} highcharts/legend/navigation/ * Legend page navigation demonstrated * @sample {highstock} highcharts/legend/navigation/ * Legend page navigation demonstrated * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 2.2.4 */ inactiveColor: palette.neutralColor20 }, /** * The inner padding of the legend box. * * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/ * Padding and item margins demonstrated * @sample {highmaps} maps/legend/padding-itemmargin/ * Padding and item margins demonstrated * * @type {number} * @default 8 * @since 2.2.0 * @apioption legend.padding */ /** * Whether to reverse the order of the legend items compared to the * order of the series or points as defined in the configuration object. * * @see [yAxis.reversedStacks](#yAxis.reversedStacks), * [series.legendIndex](#series.legendIndex) * * @sample {highcharts} highcharts/legend/reversed/ * Stacked bar with reversed legend * * @type {boolean} * @default false * @since 1.2.5 * @apioption legend.reversed */ /** * Whether to show the symbol on the right side of the text rather than * the left side. This is common in Arabic and Hebrew. * * @sample {highcharts} highcharts/legend/rtl/ * Symbol to the right * * @type {boolean} * @default false * @since 2.2 * @apioption legend.rtl */ /** * CSS styles for the legend area. In the 1.x versions the position * of the legend area was determined by CSS. In 2.x, the position is * determined by properties like `align`, `verticalAlign`, `x` and `y`, * but the styles are still parsed for backwards compatibility. * * @deprecated * * @type {Highcharts.CSSObject} * @product highcharts highstock * @apioption legend.style */ /** * CSS styles for each legend item. Only a subset of CSS is supported, * notably those options related to text. The default `textOverflow` * property makes long texts truncate. Set it to `undefined` to wrap * text instead. A `width` property can be added to control the text * width. * * @see In styled mode, the legend items can be styled with the * `.highcharts-legend-item` class. * * @sample {highcharts} highcharts/legend/itemstyle/ * Bold black text * @sample {highmaps} maps/legend/itemstyle/ * Item text styles * * @type {Highcharts.CSSObject} * @default {"color": "#333333", "cursor": "pointer", "fontSize": "12px", "fontWeight": "bold", "textOverflow": "ellipsis"} */ itemStyle: { /** * @ignore */ color: palette.neutralColor80, /** * @ignore */ cursor: 'pointer', /** * @ignore */ fontSize: '12px', /** * @ignore */ fontWeight: 'bold', /** * @ignore */ textOverflow: 'ellipsis' }, /** * CSS styles for each legend item in hover mode. Only a subset of * CSS is supported, notably those options related to text. Properties * are inherited from `style` unless overridden here. * * @see In styled mode, the hovered legend items can be styled with * the `.highcharts-legend-item:hover` pesudo-class. * * @sample {highcharts} highcharts/legend/itemhoverstyle/ * Red on hover * @sample {highmaps} maps/legend/itemstyle/ * Item text styles * * @type {Highcharts.CSSObject} * @default {"color": "#000000"} */ itemHoverStyle: { /** * @ignore */ color: palette.neutralColor100 }, /** * CSS styles for each legend item when the corresponding series or * point is hidden. Only a subset of CSS is supported, notably those * options related to text. Properties are inherited from `style` * unless overridden here. * * @see In styled mode, the hidden legend items can be styled with * the `.highcharts-legend-item-hidden` class. * * @sample {highcharts} highcharts/legend/itemhiddenstyle/ * Darker gray color * * @type {Highcharts.CSSObject} * @default {"color": "#cccccc"} */ itemHiddenStyle: { /** * @ignore */ color: palette.neutralColor20 }, /** * Whether to apply a drop shadow to the legend. A `backgroundColor` * also needs to be applied for this to take effect. The shadow can be * an object configuration containing `color`, `offsetX`, `offsetY`, * `opacity` and `width`. * * @sample {highcharts} highcharts/legend/shadow/ * White background and drop shadow * @sample {highstock} stock/legend/align/ * Various legend options * @sample {highmaps} maps/legend/border-background/ * Border and background options * * @type {boolean|Highcharts.CSSObject} */ shadow: false, /** * Default styling for the checkbox next to a legend item when * `showCheckbox` is true. * * @type {Highcharts.CSSObject} * @default {"width": "13px", "height": "13px", "position":"absolute"} */ itemCheckboxStyle: { /** * @ignore */ position: 'absolute', /** * @ignore */ width: '13px', /** * @ignore */ height: '13px' }, // itemWidth: undefined, /** * When this is true, the legend symbol width will be the same as * the symbol height, which in turn defaults to the font size of the * legend items. * * @since 5.0.0 */ squareSymbol: true, /** * The pixel height of the symbol for series types that use a rectangle * in the legend. Defaults to the font size of legend items. * * @productdesc {highmaps} * In Highmaps, when the symbol is the gradient of a vertical color * axis, the height defaults to 200. * * @sample {highmaps} maps/legend/layout-vertical-sized/ * Sized vertical gradient * @sample {highmaps} maps/legend/padding-itemmargin/ * No distance between data classes * * @type {number} * @since 3.0.8 * @apioption legend.symbolHeight */ /** * The border radius of the symbol for series types that use a rectangle * in the legend. Defaults to half the `symbolHeight`. * * @sample {highcharts} highcharts/legend/symbolradius/ * Round symbols * @sample {highstock} highcharts/legend/symbolradius/ * Round symbols * @sample {highmaps} highcharts/legend/symbolradius/ * Round symbols * * @type {number} * @since 3.0.8 * @apioption legend.symbolRadius */ /** * The pixel width of the legend item symbol. When the `squareSymbol` * option is set, this defaults to the `symbolHeight`, otherwise 16. * * @productdesc {highmaps} * In Highmaps, when the symbol is the gradient of a horizontal color * axis, the width defaults to 200. * * @sample {highcharts} highcharts/legend/symbolwidth/ * Greater symbol width and padding * @sample {highmaps} maps/legend/padding-itemmargin/ * Padding and item margins demonstrated * @sample {highmaps} maps/legend/layout-vertical-sized/ * Sized vertical gradient * * @type {number} * @apioption legend.symbolWidth */ /** * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) * to render the legend item texts. * * Prior to 4.1.7, when using HTML, [legend.navigation]( * #legend.navigation) was disabled. * * @type {boolean} * @default false * @apioption legend.useHTML */ /** * The width of the legend box. If a number is set, it translates to * pixels. Since v7.0.2 it allows setting a percent string of the full * chart width, for example `40%`. * * Defaults to the full chart width for legends below or above the * chart, half the chart width for legends to the left and right. * * @sample {highcharts} highcharts/legend/width/ * Aligned to the plot area * @sample {highcharts} highcharts/legend/width-percent/ * A percent of the chart width * * @type {number|string} * @since 2.0 * @apioption legend.width */ /** * The pixel padding between the legend item symbol and the legend * item text. * * @sample {highcharts} highcharts/legend/symbolpadding/ * Greater symbol width and padding */ symbolPadding: 5, /** * The vertical alignment of the legend box. Can be one of `top`, * `middle` or `bottom`. Vertical position can be further determined * by the `y` option. * * In the case that the legend is aligned in a corner position, the * `layout` option will determine whether to place it above/below * or on the side of the plot area. * * When the [layout](#legend.layout) option is `proximate`, the * `verticalAlign` option doesn't apply. * * @sample {highcharts} highcharts/legend/verticalalign/ * Legend 100px from the top of the chart * @sample {highstock} stock/legend/align/ * Various legend options * @sample {highmaps} maps/legend/alignment/ * Legend alignment * * @type {Highcharts.VerticalAlignValue} * @since 2.0 */ verticalAlign: 'bottom', // width: undefined, /** * The x offset of the legend relative to its horizontal alignment * `align` within chart.spacingLeft and chart.spacingRight. Negative * x moves it to the left, positive x moves it to the right. * * @sample {highcharts} highcharts/legend/width/ * Aligned to the plot area * * @since 2.0 */ x: 0, /** * The vertical offset of the legend relative to it's vertical alignment * `verticalAlign` within chart.spacingTop and chart.spacingBottom. * Negative y moves it up, positive y moves it down. * * @sample {highcharts} highcharts/legend/verticalalign/ * Legend 100px from the top of the chart * @sample {highstock} stock/legend/align/ * Various legend options * @sample {highmaps} maps/legend/alignment/ * Legend alignment * * @since 2.0 */ y: 0, /** * A title to be added on top of the legend. * * @sample {highcharts} highcharts/legend/title/ * Legend title * @sample {highmaps} maps/legend/alignment/ * Legend with title * * @since 3.0 */ title: { /** * A text or HTML string for the title. * * @type {string} * @since 3.0 * @apioption legend.title.text */ /** * Generic CSS styles for the legend title. * * @see In styled mode, the legend title is styled with the * `.highcharts-legend-title` class. * * @type {Highcharts.CSSObject} * @default {"fontWeight": "bold"} * @since 3.0 */ style: { /** * @ignore */ fontWeight: 'bold' } } }, /** * The loading options control the appearance of the loading screen * that covers the plot area on chart operations. This screen only * appears after an explicit call to `chart.showLoading()`. It is a * utility for developers to communicate to the end user that something * is going on, for example while retrieving new data via an XHR connection. * The "Loading..." text itself is not part of this configuration * object, but part of the `lang` object. */ loading: { /** * The duration in milliseconds of the fade out effect. * * @sample highcharts/loading/hideduration/ * Fade in and out over a second * * @type {number} * @default 100 * @since 1.2.0 * @apioption loading.hideDuration */ /** * The duration in milliseconds of the fade in effect. * * @sample highcharts/loading/hideduration/ * Fade in and out over a second * * @type {number} * @default 100 * @since 1.2.0 * @apioption loading.showDuration */ /** * CSS styles for the loading label `span`. * * @see In styled mode, the loading label is styled with the * `.highcharts-loading-inner` class. * * @sample {highcharts|highmaps} highcharts/loading/labelstyle/ * Vertically centered * @sample {highstock} stock/loading/general/ * Label styles * * @type {Highcharts.CSSObject} * @default {"fontWeight": "bold", "position": "relative", "top": "45%"} * @since 1.2.0 */ labelStyle: { /** * @ignore */ fontWeight: 'bold', /** * @ignore */ position: 'relative', /** * @ignore */ top: '45%' }, /** * CSS styles for the loading screen that covers the plot area. * * In styled mode, the loading label is styled with the * `.highcharts-loading` class. * * @sample {highcharts|highmaps} highcharts/loading/style/ * Gray plot area, white text * @sample {highstock} stock/loading/general/ * Gray plot area, white text * * @type {Highcharts.CSSObject} * @default {"position": "absolute", "backgroundColor": "#ffffff", "opacity": 0.5, "textAlign": "center"} * @since 1.2.0 */ style: { /** * @ignore */ position: 'absolute', /** * @ignore */ backgroundColor: palette.backgroundColor, /** * @ignore */ opacity: 0.5, /** * @ignore */ textAlign: 'center' } }, /** * Options for the tooltip that appears when the user hovers over a * series or point. * * @declare Highcharts.TooltipOptions */ tooltip: { /** * The color of the tooltip border. When `undefined`, the border takes * the color of the corresponding series or point. * * @sample {highcharts} highcharts/tooltip/bordercolor-default/ * Follow series by default * @sample {highcharts} highcharts/tooltip/bordercolor-black/ * Black border * @sample {highstock} stock/tooltip/general/ * Styled tooltip * @sample {highmaps} maps/tooltip/background-border/ * Background and border demo * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @apioption tooltip.borderColor */ /** * A CSS class name to apply to the tooltip's container div, * allowing unique CSS styling for each chart. * * @type {string} * @apioption tooltip.className */ /** * Since 4.1, the crosshair definitions are moved to the Axis object * in order for a better separation from the tooltip. See * [xAxis.crosshair](#xAxis.crosshair). * * @sample {highcharts} highcharts/tooltip/crosshairs-x/ * Enable a crosshair for the x value * * @deprecated * * @type {*} * @default true * @apioption tooltip.crosshairs */ /** * Distance from point to tooltip in pixels. * * @type {number} * @default 16 * @apioption tooltip.distance */ /** * Whether the tooltip should follow the mouse as it moves across * columns, pie slices and other point types with an extent. * By default it behaves this way for pie, polygon, map, sankey * and wordcloud series by override in the `plotOptions` * for those series types. * * Does not apply if [split](#tooltip.split) is `true`. * * For touch moves to behave the same way, [followTouchMove]( * #tooltip.followTouchMove) must be `true` also. * * @type {boolean} * @default {highcharts} false * @default {highstock} false * @default {highmaps} true * @since 3.0 * @apioption tooltip.followPointer */ /** * Whether the tooltip should update as the finger moves on a touch * device. If this is `true` and [chart.panning](#chart.panning) is * set,`followTouchMove` will take over one-finger touches, so the user * needs to use two fingers for zooming and panning. * * Note the difference to [followPointer](#tooltip.followPointer) that * only defines the _position_ of the tooltip. If `followPointer` is * false in for example a column series, the tooltip will show above or * below the column, but as `followTouchMove` is true, the tooltip will * jump from column to column as the user swipes across the plot area. * * @type {boolean} * @default {highcharts} true * @default {highstock} true * @default {highmaps} false * @since 3.0.1 * @apioption tooltip.followTouchMove */ /** * Callback function to format the text of the tooltip from scratch. In * case of single or [shared](#tooltip.shared) tooltips, a string should * be returned. In case of [split](#tooltip.split) tooltips, it should * return an array where the first item is the header, and subsequent * items are mapped to the points. Return `false` to disable tooltip for * a specific point on series. * * A subset of HTML is supported. Unless `useHTML` is true, the HTML of * the tooltip is parsed and converted to SVG, therefore this isn't a * complete HTML renderer. The following HTML tags are supported: `b`, * `br`, `em`, `i`, `span`, `strong`. Spans can be styled with a `style` * attribute, but only text-related CSS, that is shared with SVG, is * handled. * * The available data in the formatter differ a bit depending on whether * the tooltip is shared or split, or belongs to a single point. In a * shared/split tooltip, all properties except `x`, which is common for * all points, are kept in an array, `this.points`. * * Available data are: * * - **this.percentage (not shared) /** * **this.points[i].percentage (shared)**: * Stacked series and pies only. The point's percentage of the total. * * - **this.point (not shared) / this.points[i].point (shared)**: * The point object. The point name, if defined, is available through * `this.point.name`. * * - **this.points**: * In a shared tooltip, this is an array containing all other * properties for each point. * * - **this.series (not shared) / this.points[i].series (shared)**: * The series object. The series name is available through * `this.series.name`. * * - **this.total (not shared) / this.points[i].total (shared)**: * Stacked series only. The total value at this point's x value. * * - **this.x**: * The x value. This property is the same regardless of the tooltip * being shared or not. * * - **this.y (not shared) / this.points[i].y (shared)**: * The y value. * * @sample {highcharts} highcharts/tooltip/formatter-simple/ * Simple string formatting * @sample {highcharts} highcharts/tooltip/formatter-shared/ * Formatting with shared tooltip * @sample {highcharts|highstock} highcharts/tooltip/formatter-split/ * Formatting with split tooltip * @sample highcharts/tooltip/formatter-conditional-default/ * Extending default formatter * @sample {highstock} stock/tooltip/formatter/ * Formatting with shared tooltip * @sample {highmaps} maps/tooltip/formatter/ * String formatting * * @type {Highcharts.TooltipFormatterCallbackFunction} * @apioption tooltip.formatter */ /** * Callback function to format the text of the tooltip for * visible null points. * Works analogously to [formatter](#tooltip.formatter). * * @sample highcharts/plotoptions/series-nullformat * Format data label and tooltip for null point. * * @type {Highcharts.TooltipFormatterCallbackFunction} * @apioption tooltip.nullFormatter */ /** * The number of milliseconds to wait until the tooltip is hidden when * mouse out from a point or chart. * * @type {number} * @default 500 * @since 3.0 * @apioption tooltip.hideDelay */ /** * Whether to allow the tooltip to render outside the chart's SVG * element box. By default (`false`), the tooltip is rendered within the * chart's SVG element, which results in the tooltip being aligned * inside the chart area. For small charts, this may result in clipping * or overlapping. When `true`, a separate SVG element is created and * overlaid on the page, allowing the tooltip to be aligned inside the * page itself. * * Defaults to `true` if `chart.scrollablePlotArea` is activated, * otherwise `false`. * * @sample highcharts/tooltip/outside * Small charts with tooltips outside * * @type {boolean|undefined} * @default undefined * @since 6.1.1 * @apioption tooltip.outside */ /** * A callback function for formatting the HTML output for a single point * in the tooltip. Like the `pointFormat` string, but with more * flexibility. * * @type {Highcharts.FormatterCallbackFunction} * @since 4.1.0 * @context Highcharts.Point * @apioption tooltip.pointFormatter */ /** * A callback function to place the tooltip in a default position. The * callback receives three parameters: `labelWidth`, `labelHeight` and * `point`, where point contains values for `plotX` and `plotY` telling * where the reference point is in the plot area. Add `chart.plotLeft` * and `chart.plotTop` to get the full coordinates. * * Since v7, when [tooltip.split](#tooltip.split) option is enabled, * positioner is called for each of the boxes separately, including * xAxis header. xAxis header is not a point, instead `point` argument * contains info: * `{ plotX: Number, plotY: Number, isHeader: Boolean }` * * * The return should be an object containing x and y values, for example * `{ x: 100, y: 100 }`. * * @sample {highcharts} highcharts/tooltip/positioner/ * A fixed tooltip position * @sample {highstock} stock/tooltip/positioner/ * A fixed tooltip position on top of the chart * @sample {highmaps} maps/tooltip/positioner/ * A fixed tooltip position * @sample {highstock} stock/tooltip/split-positioner/ * Split tooltip with fixed positions * @sample {highstock} stock/tooltip/positioner-scrollable-plotarea/ * Scrollable plot area combined with tooltip positioner * * @type {Highcharts.TooltipPositionerCallbackFunction} * @since 2.2.4 * @apioption tooltip.positioner */ /** * The name of a symbol to use for the border around the tooltip. Can * be one of: `"callout"`, `"circle"` or `"rect"`. When * [tooltip.split](#tooltip.split) * option is enabled, shape is applied to all boxes except header, which * is controlled by * [tooltip.headerShape](#tooltip.headerShape). * * Custom callbacks for symbol path generation can also be added to * `Highcharts.SVGRenderer.prototype.symbols` the same way as for * [series.marker.symbol](plotOptions.line.marker.symbol). * * @type {Highcharts.TooltipShapeValue} * @default callout * @since 4.0 * @apioption tooltip.shape */ /** * The name of a symbol to use for the border around the tooltip * header. Applies only when [tooltip.split](#tooltip.split) is * enabled. * * Custom callbacks for symbol path generation can also be added to * `Highcharts.SVGRenderer.prototype.symbols` the same way as for * [series.marker.symbol](plotOptions.line.marker.symbol). * * @see [tooltip.shape](#tooltip.shape) * * @sample {highstock} stock/tooltip/split-positioner/ * Different shapes for header and split boxes * * @type {Highcharts.TooltipShapeValue} * @default callout * @validvalue ["callout", "square"] * @since 7.0 * @apioption tooltip.headerShape */ /** * When the tooltip is shared, the entire plot area will capture mouse * movement or touch events. Tooltip texts for series types with ordered * data (not pie, scatter, flags etc) will be shown in a single bubble. * This is recommended for single series charts and for tablet/mobile * optimized charts. * * See also [tooltip.split](#tooltip.split), that is better suited for * charts with many series, especially line-type series. The * `tooltip.split` option takes precedence over `tooltip.shared`. * * @sample {highcharts} highcharts/tooltip/shared-false/ * False by default * @sample {highcharts} highcharts/tooltip/shared-true/ * True * @sample {highcharts} highcharts/tooltip/shared-x-crosshair/ * True with x axis crosshair * @sample {highcharts} highcharts/tooltip/shared-true-mixed-types/ * True with mixed series types * * @type {boolean} * @default false * @since 2.1 * @product highcharts highstock * @apioption tooltip.shared */ /** * Split the tooltip into one label per series, with the header close * to the axis. This is recommended over [shared](#tooltip.shared) * tooltips for charts with multiple line series, generally making them * easier to read. This option takes precedence over `tooltip.shared`. * * @productdesc {highstock} In Highcharts Stock, tooltips are split * by default since v6.0.0. Stock charts typically contain * multi-dimension points and multiple panes, making split tooltips * the preferred layout over * the previous `shared` tooltip. * * @sample highcharts/tooltip/split/ * Split tooltip * @sample {highcharts|highstock} highcharts/tooltip/formatter-split/ * Split tooltip and custom formatter callback * * @type {boolean} * @default {highcharts} false * @default {highstock} true * @since 5.0.0 * @product highcharts highstock * @apioption tooltip.split */ /** * Prevents the tooltip from switching or closing, when touched or * pointed. * * @sample highcharts/tooltip/stickoncontact/ * Tooltip sticks on pointer contact * * @type {boolean} * @since 8.0.1 * @apioption tooltip.stickOnContact */ /** * Use HTML to render the contents of the tooltip instead of SVG. Using * HTML allows advanced formatting like tables and images in the * tooltip. It is also recommended for rtl languages as it works around * rtl bugs in early Firefox. * * @sample {highcharts|highstock} highcharts/tooltip/footerformat/ * A table for value alignment * @sample {highcharts|highstock} highcharts/tooltip/fullhtml/ * Full HTML tooltip * @sample {highmaps} maps/tooltip/usehtml/ * Pure HTML tooltip * * @type {boolean} * @default false * @since 2.2 * @apioption tooltip.useHTML */ /** * How many decimals to show in each series' y value. This is * overridable in each series' tooltip options object. The default is to * preserve all decimals. * * @sample {highcharts|highstock} highcharts/tooltip/valuedecimals/ * Set decimals, prefix and suffix for the value * @sample {highmaps} maps/tooltip/valuedecimals/ * Set decimals, prefix and suffix for the value * * @type {number} * @since 2.2 * @apioption tooltip.valueDecimals */ /** * A string to prepend to each series' y value. Overridable in each * series' tooltip options object. * * @sample {highcharts|highstock} highcharts/tooltip/valuedecimals/ * Set decimals, prefix and suffix for the value * @sample {highmaps} maps/tooltip/valuedecimals/ * Set decimals, prefix and suffix for the value * * @type {string} * @since 2.2 * @apioption tooltip.valuePrefix */ /** * A string to append to each series' y value. Overridable in each * series' tooltip options object. * * @sample {highcharts|highstock} highcharts/tooltip/valuedecimals/ * Set decimals, prefix and suffix for the value * @sample {highmaps} maps/tooltip/valuedecimals/ * Set decimals, prefix and suffix for the value * * @type {string} * @since 2.2 * @apioption tooltip.valueSuffix */ /** * The format for the date in the tooltip header if the X axis is a * datetime axis. The default is a best guess based on the smallest * distance between points in the chart. * * @sample {highcharts} highcharts/tooltip/xdateformat/ * A different format * * @type {string} * @product highcharts highstock gantt * @apioption tooltip.xDateFormat */ /** * How many decimals to show for the `point.change` value when the * `series.compare` option is set. This is overridable in each series' * tooltip options object. The default is to preserve all decimals. * * @type {number} * @since 1.0.1 * @product highstock * @apioption tooltip.changeDecimals */ /** * Enable or disable the tooltip. * * @sample {highcharts} highcharts/tooltip/enabled/ * Disabled * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ * Disable tooltip and show values on chart instead */ enabled: true, /** * Enable or disable animation of the tooltip. * * @type {boolean} * @default true * @since 2.3.0 */ animation: svg, /** * The radius of the rounded border corners. * * @sample {highcharts} highcharts/tooltip/bordercolor-default/ * 5px by default * @sample {highcharts} highcharts/tooltip/borderradius-0/ * Square borders * @sample {highmaps} maps/tooltip/background-border/ * Background and border demo */ borderRadius: 3, /** * For series on datetime axes, the date format in the tooltip's * header will by default be guessed based on the closest data points. * This member gives the default string representations used for * each unit. For an overview of the replacement codes, see * [dateFormat](/class-reference/Highcharts#.dateFormat). * * @see [xAxis.dateTimeLabelFormats](#xAxis.dateTimeLabelFormats) * * @type {Highcharts.Dictionary} * @product highcharts highstock gantt */ dateTimeLabelFormats: { /** @internal */ millisecond: '%A, %b %e, %H:%M:%S.%L', /** @internal */ second: '%A, %b %e, %H:%M:%S', /** @internal */ minute: '%A, %b %e, %H:%M', /** @internal */ hour: '%A, %b %e, %H:%M', /** @internal */ day: '%A, %b %e, %Y', /** @internal */ week: 'Week from %A, %b %e, %Y', /** @internal */ month: '%B %Y', /** @internal */ year: '%Y' }, /** * A string to append to the tooltip format. * * @sample {highcharts} highcharts/tooltip/footerformat/ * A table for value alignment * @sample {highmaps} maps/tooltip/format/ * Format demo * * @since 2.2 */ footerFormat: '', /** * Padding inside the tooltip, in pixels. * * @since 5.0.0 */ padding: 8, /** * Proximity snap for graphs or single points. It defaults to 10 for * mouse-powered devices and 25 for touch devices. * * Note that in most cases the whole plot area captures the mouse * movement, and in these cases `tooltip.snap` doesn't make sense. This * applies when [stickyTracking](#plotOptions.series.stickyTracking) * is `true` (default) and when the tooltip is [shared](#tooltip.shared) * or [split](#tooltip.split). * * @sample {highcharts} highcharts/tooltip/bordercolor-default/ * 10 px by default * @sample {highcharts} highcharts/tooltip/snap-50/ * 50 px on graph * * @type {number} * @default 10/25 * @since 1.2.0 * @product highcharts highstock */ snap: isTouchDevice ? 25 : 10, /** * The HTML of the tooltip header line. Variables are enclosed by * curly brackets. Available variables are `point.key`, `series.name`, * `series.color` and other members from the `point` and `series` * objects. The `point.key` variable contains the category name, x * value or datetime string depending on the type of axis. For datetime * axes, the `point.key` date format can be set using * `tooltip.xDateFormat`. * * @sample {highcharts} highcharts/tooltip/footerformat/ * An HTML table in the tooltip * @sample {highstock} highcharts/tooltip/footerformat/ * An HTML table in the tooltip * @sample {highmaps} maps/tooltip/format/ * Format demo * * @type {string} * @apioption tooltip.headerFormat */ headerFormat: '{point.key}
', /** * The HTML of the null point's line in the tooltip. Works analogously * to [pointFormat](#tooltip.pointFormat). * * @sample {highcharts} highcharts/plotoptions/series-nullformat * Format data label and tooltip for null point. * * @type {string} * @apioption tooltip.nullFormat */ /** * The HTML of the point's line in the tooltip. Variables are enclosed * by curly brackets. Available variables are `point.x`, `point.y`, * `series.name` and `series.color` and other properties on the same * form. Furthermore, `point.y` can be extended by the * `tooltip.valuePrefix` and `tooltip.valueSuffix` variables. This can * also be overridden for each series, which makes it a good hook for * displaying units. * * In styled mode, the dot is colored by a class name rather * than the point color. * * @sample {highcharts} highcharts/tooltip/pointformat/ * A different point format with value suffix * @sample {highmaps} maps/tooltip/format/ * Format demo * * @type {string} * @since 2.2 * @apioption tooltip.pointFormat */ pointFormat: '\u25CF {series.name}: {point.y}
', /** * The background color or gradient for the tooltip. * * In styled mode, the stroke width is set in the * `.highcharts-tooltip-box` class. * * @sample {highcharts} highcharts/tooltip/backgroundcolor-solid/ * Yellowish background * @sample {highcharts} highcharts/tooltip/backgroundcolor-gradient/ * Gradient * @sample {highcharts} highcharts/css/tooltip-border-background/ * Tooltip in styled mode * @sample {highstock} stock/tooltip/general/ * Custom tooltip * @sample {highstock} highcharts/css/tooltip-border-background/ * Tooltip in styled mode * @sample {highmaps} maps/tooltip/background-border/ * Background and border demo * @sample {highmaps} highcharts/css/tooltip-border-background/ * Tooltip in styled mode * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ backgroundColor: color(palette.neutralColor3) .setOpacity(0.85).get(), /** * The pixel width of the tooltip border. * * In styled mode, the stroke width is set in the * `.highcharts-tooltip-box` class. * * @sample {highcharts} highcharts/tooltip/bordercolor-default/ * 2px by default * @sample {highcharts} highcharts/tooltip/borderwidth/ * No border (shadow only) * @sample {highcharts} highcharts/css/tooltip-border-background/ * Tooltip in styled mode * @sample {highstock} stock/tooltip/general/ * Custom tooltip * @sample {highstock} highcharts/css/tooltip-border-background/ * Tooltip in styled mode * @sample {highmaps} maps/tooltip/background-border/ * Background and border demo * @sample {highmaps} highcharts/css/tooltip-border-background/ * Tooltip in styled mode */ borderWidth: 1, /** * Whether to apply a drop shadow to the tooltip. * * @sample {highcharts} highcharts/tooltip/bordercolor-default/ * True by default * @sample {highcharts} highcharts/tooltip/shadow/ * False * @sample {highmaps} maps/tooltip/positioner/ * Fixed tooltip position, border and shadow disabled * * @type {boolean|Highcharts.ShadowOptionsObject} */ shadow: true, /** * CSS styles for the tooltip. The tooltip can also be styled through * the CSS class `.highcharts-tooltip`. * * Note that the default `pointerEvents` style makes the tooltip ignore * mouse events, so in order to use clickable tooltips, this value must * be set to `auto`. * * @sample {highcharts} highcharts/tooltip/style/ * Greater padding, bold text * * @type {Highcharts.CSSObject} */ style: { /** @internal */ color: palette.neutralColor80, /** @internal */ cursor: 'default', /** @internal */ fontSize: '12px', /** @internal */ whiteSpace: 'nowrap' } }, /** * Highchart by default puts a credits label in the lower right corner * of the chart. This can be changed using these options. */ credits: { /** * Credits for map source to be concatenated with conventional credit * text. By default this is a format string that collects copyright * information from the map if available. * * @see [mapTextFull](#credits.mapTextFull) * @see [text](#credits.text) * * @type {string} * @default \u00a9 {geojson.copyrightShort} * @since 4.2.2 * @product highmaps * @apioption credits.mapText */ /** * Detailed credits for map source to be displayed on hover of credits * text. By default this is a format string that collects copyright * information from the map if available. * * @see [mapText](#credits.mapText) * @see [text](#credits.text) * * @type {string} * @default {geojson.copyright} * @since 4.2.2 * @product highmaps * @apioption credits.mapTextFull */ /** * Whether to show the credits text. * * @sample {highcharts} highcharts/credits/enabled-false/ * Credits disabled * @sample {highstock} stock/credits/enabled/ * Credits disabled * @sample {highmaps} maps/credits/enabled-false/ * Credits disabled */ enabled: true, /** * The URL for the credits label. * * @sample {highcharts} highcharts/credits/href/ * Custom URL and text * @sample {highmaps} maps/credits/customized/ * Custom URL and text */ href: 'https://www.highcharts.com?credits', /** * Position configuration for the credits label. * * @sample {highcharts} highcharts/credits/position-left/ * Left aligned * @sample {highcharts} highcharts/credits/position-left/ * Left aligned * @sample {highmaps} maps/credits/customized/ * Left aligned * @sample {highmaps} maps/credits/customized/ * Left aligned * * @type {Highcharts.AlignObject} * @since 2.1 */ position: { /** @internal */ align: 'right', /** @internal */ x: -10, /** @internal */ verticalAlign: 'bottom', /** @internal */ y: -5 }, /** * CSS styles for the credits label. * * @see In styled mode, credits styles can be set with the * `.highcharts-credits` class. * * @type {Highcharts.CSSObject} */ style: { /** @internal */ cursor: 'pointer', /** @internal */ color: palette.neutralColor40, /** @internal */ fontSize: '9px' }, /** * The text for the credits label. * * @productdesc {highmaps} * If a map is loaded as GeoJSON, the text defaults to * `Highcharts @ {map-credits}`. Otherwise, it defaults to * `Highcharts.com`. * * @sample {highcharts} highcharts/credits/href/ * Custom URL and text * @sample {highmaps} maps/credits/customized/ * Custom URL and text */ text: 'Highcharts.com' } }; /* eslint-disable spaced-comment */ defaultOptions.chart.styledMode = false; ''; var defaultTime = new Time(merge(defaultOptions.global, defaultOptions.time)); /** * Get the updated default options. Until 3.0.7, merely exposing defaultOptions * for outside modules wasn't enough because the setOptions method created a new * object. * * @function Highcharts.getOptions * * @return {Highcharts.Options} */ function getOptions() { return defaultOptions; } /** * Merge the default options with custom options and return the new options * structure. Commonly used for defining reusable templates. * * @sample highcharts/global/useutc-false Setting a global option * @sample highcharts/members/setoptions Applying a global theme * * @function Highcharts.setOptions * * @param {Highcharts.Options} options * The new custom chart options. * * @return {Highcharts.Options} * Updated options. */ function setOptions(options) { // Copy in the default options merge(true, defaultOptions, options); // Update the time object if (options.time || options.global) { if (H.time) { H.time.update(merge(defaultOptions.global, defaultOptions.time, options.global, options.time)); } else { /** * Global `Time` object with default options. Since v6.0.5, time * settings can be applied individually for each chart. If no * individual settings apply, this `Time` object is shared by all * instances. * * @name Highcharts.time * @type {Highcharts.Time} */ H.time = defaultTime; } } return defaultOptions; } /* * * * Default Export * * */ var DefaultOptions = { defaultOptions: defaultOptions, defaultTime: defaultTime, getOptions: getOptions, setOptions: setOptions }; return DefaultOptions; }); _registerModule(_modules, 'Core/Animation/Fx.js', [_modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (Color, H, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var color = Color.parse; var win = H.win; var isNumber = U.isNumber, objectEach = U.objectEach; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * An animator object used internally. One instance applies to one property * (attribute or style prop) on one element. Animation is always initiated * through {@link SVGElement#animate}. * * @example * let rect = renderer.rect(0, 0, 10, 10).add(); * rect.animate({ width: 100 }); * * @private * @class * @name Highcharts.Fx */ var Fx = /** @class */ (function () { /* * * * Constructors * * */ /** * * @param {Highcharts.HTMLDOMElement|Highcharts.SVGElement} elem * The element to animate. * * @param {Partial} options * Animation options. * * @param {string} prop * The single attribute or CSS property to animate. */ function Fx(elem, options, prop) { this.pos = NaN; this.options = options; this.elem = elem; this.prop = prop; } /* * * * Functions * * */ /** * Set the current step of a path definition on SVGElement. * * @function Highcharts.Fx#dSetter * * @return {void} */ Fx.prototype.dSetter = function () { var paths = this.paths, start = paths && paths[0], end = paths && paths[1], now = this.now || 0; var path = []; // Land on the final path without adjustment points appended in the ends if (now === 1 || !start || !end) { path = this.toD || []; } else if (start.length === end.length && now < 1) { for (var i = 0; i < end.length; i++) { // Tween between the start segment and the end segment. Start // with a copy of the end segment and tween the appropriate // numerics var startSeg = start[i]; var endSeg = end[i]; var tweenSeg = []; for (var j = 0; j < endSeg.length; j++) { var startItem = startSeg[j]; var endItem = endSeg[j]; // Tween numbers if (isNumber(startItem) && isNumber(endItem) && // Arc boolean flags !(endSeg[0] === 'A' && (j === 4 || j === 5))) { tweenSeg[j] = startItem + now * (endItem - startItem); // Strings, take directly from the end segment } else { tweenSeg[j] = endItem; } } path.push(tweenSeg); } // If animation is finished or length not matching, land on right value } else { path = end; } this.elem.attr('d', path, void 0, true); }; /** * Update the element with the current animation step. * * @function Highcharts.Fx#update * * @return {void} */ Fx.prototype.update = function () { var elem = this.elem, prop = this.prop, // if destroyed, it is null now = this.now, step = this.options.step; // Animation setter defined from outside if (this[prop + 'Setter']) { this[prop + 'Setter'](); // Other animations on SVGElement } else if (elem.attr) { if (elem.element) { elem.attr(prop, now, null, true); } // HTML styles, raw HTML content like container size } else { elem.style[prop] = now + this.unit; } if (step) { step.call(elem, now, this); } }; /** * Run an animation. * * @function Highcharts.Fx#run * * @param {number} from * The current value, value to start from. * * @param {number} to * The end value, value to land on. * * @param {string} unit * The property unit, for example `px`. * * @return {void} */ Fx.prototype.run = function (from, to, unit) { var self = this, options = self.options, timer = function (gotoEnd) { return timer.stopped ? false : self.step(gotoEnd); }, requestAnimationFrame = win.requestAnimationFrame || function (step) { setTimeout(step, 13); }, step = function () { for (var i = 0; i < Fx.timers.length; i++) { if (!Fx.timers[i]()) { Fx.timers.splice(i--, 1); } } if (Fx.timers.length) { requestAnimationFrame(step); } }; if (from === to && !this.elem['forceAnimate:' + this.prop]) { delete options.curAnim[this.prop]; if (options.complete && Object.keys(options.curAnim).length === 0) { options.complete.call(this.elem); } } else { // #7166 this.startTime = +new Date(); this.start = from; this.end = to; this.unit = unit; this.now = this.start; this.pos = 0; timer.elem = this.elem; timer.prop = this.prop; if (timer() && Fx.timers.push(timer) === 1) { requestAnimationFrame(step); } } }; /** * Run a single step in the animation. * * @function Highcharts.Fx#step * * @param {boolean} [gotoEnd] * Whether to go to the endpoint of the animation after abort. * * @return {boolean} * Returns `true` if animation continues. */ Fx.prototype.step = function (gotoEnd) { var t = +new Date(), options = this.options, elem = this.elem, complete = options.complete, duration = options.duration, curAnim = options.curAnim; var ret, done; if (elem.attr && !elem.element) { // #2616, element is destroyed ret = false; } else if (gotoEnd || t >= duration + this.startTime) { this.now = this.end; this.pos = 1; this.update(); curAnim[this.prop] = true; done = true; objectEach(curAnim, function (val) { if (val !== true) { done = false; } }); if (done && complete) { complete.call(elem); } ret = false; } else { this.pos = options.easing((t - this.startTime) / duration); this.now = this.start + ((this.end - this.start) * this.pos); this.update(); ret = true; } return ret; }; /** * Prepare start and end values so that the path can be animated one to one. * * @function Highcharts.Fx#initPath * * @param {Highcharts.SVGElement} elem * The SVGElement item. * * @param {Highcharts.SVGPathArray|undefined} fromD * Starting path definition. * * @param {Highcharts.SVGPathArray} toD * Ending path definition. * * @return {Array} * An array containing start and end paths in array form so that * they can be animated in parallel. */ Fx.prototype.initPath = function (elem, fromD, toD) { var startX = elem.startX, endX = elem.endX, end = toD.slice(), // copy isArea = elem.isArea, positionFactor = isArea ? 2 : 1; var shift, fullLength, i, reverse, start = fromD && fromD.slice(); // copy if (!start) { return [end, end]; } /** * If shifting points, prepend a dummy point to the end path. * @private * @param {Highcharts.SVGPathArray} arr - array * @param {Highcharts.SVGPathArray} other - array * @return {void} */ function prepend(arr, other) { while (arr.length < fullLength) { // Move to, line to or curve to? var moveSegment = arr[0], otherSegment = other[fullLength - arr.length]; if (otherSegment && moveSegment[0] === 'M') { if (otherSegment[0] === 'C') { arr[0] = [ 'C', moveSegment[1], moveSegment[2], moveSegment[1], moveSegment[2], moveSegment[1], moveSegment[2] ]; } else { arr[0] = ['L', moveSegment[1], moveSegment[2]]; } } // Prepend a copy of the first point arr.unshift(moveSegment); // For areas, the bottom path goes back again to the left, so we // need to append a copy of the last point. if (isArea) { var z = arr.pop(); arr.push(arr[arr.length - 1], z); // append point and the Z } } } /** * Copy and append last point until the length matches the end length. * @private * @param {Highcharts.SVGPathArray} arr - array * @param {Highcharts.SVGPathArray} other - array * @return {void} */ function append(arr, other) { while (arr.length < fullLength) { // Pull out the slice that is going to be appended or inserted. // In a line graph, the positionFactor is 1, and the last point // is sliced out. In an area graph, the positionFactor is 2, // causing the middle two points to be sliced out, since an area // path starts at left, follows the upper path then turns and // follows the bottom back. var segmentToAdd = arr[Math.floor(arr.length / positionFactor) - 1].slice(); // Disable the first control point of curve segments if (segmentToAdd[0] === 'C') { segmentToAdd[1] = segmentToAdd[5]; segmentToAdd[2] = segmentToAdd[6]; } if (!isArea) { arr.push(segmentToAdd); } else { var lowerSegmentToAdd = arr[Math.floor(arr.length / positionFactor)].slice(); arr.splice(arr.length / 2, 0, segmentToAdd, lowerSegmentToAdd); } } } // For sideways animation, find out how much we need to shift to get the // start path Xs to match the end path Xs. if (startX && endX && endX.length) { for (i = 0; i < startX.length; i++) { // Moving left, new points coming in on right if (startX[i] === endX[0]) { shift = i; break; // Moving right } else if (startX[0] === endX[endX.length - startX.length + i]) { shift = i; reverse = true; break; // Fixed from the right side, "scaling" left } else if (startX[startX.length - 1] === endX[endX.length - startX.length + i]) { shift = startX.length - i; break; } } if (typeof shift === 'undefined') { start = []; } } if (start.length && isNumber(shift)) { // The common target length for the start and end array, where both // arrays are padded in opposite ends fullLength = end.length + shift * positionFactor; if (!reverse) { prepend(end, start); append(start, end); } else { prepend(start, end); append(end, start); } } return [start, end]; }; /** * Handle animation of the color attributes directly. * * @function Highcharts.Fx#fillSetter * * @return {void} */ Fx.prototype.fillSetter = function () { Fx.prototype.strokeSetter.apply(this, arguments); }; /** * Handle animation of the color attributes directly. * * @function Highcharts.Fx#strokeSetter * * @return {void} */ Fx.prototype.strokeSetter = function () { this.elem.attr(this.prop, color(this.start).tweenTo(color(this.end), this.pos), null, true); }; /* * * * Static properties * * */ Fx.timers = []; return Fx; }()); /* * * * Default Export * * */ return Fx; }); _registerModule(_modules, 'Core/Animation/AnimationUtilities.js', [_modules['Core/Animation/Fx.js'], _modules['Core/Utilities.js']], function (Fx, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var defined = U.defined, getStyle = U.getStyle, isArray = U.isArray, isNumber = U.isNumber, isObject = U.isObject, merge = U.merge, objectEach = U.objectEach, pick = U.pick; /** * Set the global animation to either a given value, or fall back to the given * chart's animation option. * * @function Highcharts.setAnimation * * @param {boolean|Partial|undefined} animation * The animation object. * * @param {Highcharts.Chart} chart * The chart instance. * * @todo * This function always relates to a chart, and sets a property on the renderer, * so it should be moved to the SVGRenderer. */ function setAnimation(animation, chart) { chart.renderer.globalAnimation = pick(animation, chart.options.chart.animation, true); } /** * Get the animation in object form, where a disabled animation is always * returned as `{ duration: 0 }`. * * @function Highcharts.animObject * * @param {boolean|Highcharts.AnimationOptionsObject} [animation=0] * An animation setting. Can be an object with duration, complete and * easing properties, or a boolean to enable or disable. * * @return {Highcharts.AnimationOptionsObject} * An object with at least a duration property. */ function animObject(animation) { return isObject(animation) ? merge({ duration: 500, defer: 0 }, animation) : { duration: animation ? 500 : 0, defer: 0 }; } /** * Get the defer as a number value from series animation options. * * @function Highcharts.getDeferredAnimation * * @param {Highcharts.Chart} chart * The chart instance. * * @param {boolean|Highcharts.AnimationOptionsObject} animation * An animation setting. Can be an object with duration, complete and * easing properties, or a boolean to enable or disable. * * @param {Highcharts.Series} [series] * Series to defer animation. * * @return {number} * The numeric value. */ function getDeferredAnimation(chart, animation, series) { var labelAnimation = animObject(animation); var s = series ? [series] : chart.series; var defer = 0; var duration = 0; s.forEach(function (series) { var seriesAnim = animObject(series.options.animation); defer = animation && defined(animation.defer) ? labelAnimation.defer : Math.max(defer, seriesAnim.duration + seriesAnim.defer); duration = Math.min(labelAnimation.duration, seriesAnim.duration); }); // Disable defer for exporting if (chart.renderer.forExport) { defer = 0; } var anim = { defer: Math.max(0, defer - duration), duration: Math.min(defer, duration) }; return anim; } /** * The global animate method, which uses Fx to create individual animators. * * @function Highcharts.animate * * @param {Highcharts.HTMLDOMElement|Highcharts.SVGElement} el * The element to animate. * * @param {Highcharts.CSSObject|Highcharts.SVGAttributes} params * An object containing key-value pairs of the properties to animate. * Supports numeric as pixel-based CSS properties for HTML objects and * attributes for SVGElements. * * @param {Partial} [opt] * Animation options. * * @return {void} */ function animate(el, params, opt) { var start, unit = '', end, fx, args; if (!isObject(opt)) { // Number or undefined/null args = arguments; opt = { duration: args[2], easing: args[3], complete: args[4] }; } if (!isNumber(opt.duration)) { opt.duration = 400; } opt.easing = typeof opt.easing === 'function' ? opt.easing : (Math[opt.easing] || Math.easeInOutSine); opt.curAnim = merge(params); objectEach(params, function (val, prop) { // Stop current running animation of this property stop(el, prop); fx = new Fx(el, opt, prop); end = void 0; if (prop === 'd' && isArray(params.d)) { fx.paths = fx.initPath(el, el.pathArray, params.d); fx.toD = params.d; start = 0; end = 1; } else if (el.attr) { start = el.attr(prop); } else { start = parseFloat(getStyle(el, prop)) || 0; if (prop !== 'opacity') { unit = 'px'; } } if (!end) { end = val; } if (typeof end === 'string' && end.match('px')) { end = end.replace(/px/g, ''); // #4351 } fx.run(start, end, unit); }); } /** * Stop running animation. * * @function Highcharts.stop * * @param {Highcharts.SVGElement} el * The SVGElement to stop animation on. * * @param {string} [prop] * The property to stop animating. If given, the stop method will stop a * single property from animating, while others continue. * * @return {void} * * @todo * A possible extension to this would be to stop a single property, when * we want to continue animating others. Then assign the prop to the timer * in the Fx.run method, and check for the prop here. This would be an * improvement in all cases where we stop the animation from .attr. Instead of * stopping everything, we can just stop the actual attributes we're setting. */ function stop(el, prop) { var i = Fx.timers.length; // Remove timers related to this element (#4519) while (i--) { if (Fx.timers[i].elem === el && (!prop || prop === Fx.timers[i].prop)) { Fx.timers[i].stopped = true; // #4667 } } } var animationExports = { animate: animate, animObject: animObject, getDeferredAnimation: getDeferredAnimation, setAnimation: setAnimation, stop: stop }; return animationExports; }); _registerModule(_modules, 'Core/Renderer/HTML/AST.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { /* * * * (c) 2010-2020 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var SVG_NS = H.SVG_NS; var attr = U.attr, createElement = U.createElement, discardElement = U.discardElement, error = U.error, isString = U.isString, objectEach = U.objectEach, splat = U.splat; /* * * * Constants * * */ // In IE8, DOMParser is undefined. IE9 and PhantomJS are only able to parse XML. var hasValidDOMParser = (function () { try { return Boolean(new DOMParser().parseFromString('', 'text/html')); } catch (e) { return false; } }()); /* * * * Class * * */ /** * The AST class represents an abstract syntax tree of HTML or SVG content. It * can take HTML as an argument, parse it, optionally transform it to SVG, then * perform sanitation before inserting it into the DOM. * * @class * @name Highcharts.AST * * @param {string|Array} source * Either an HTML string or an ASTNode list to populate the tree. */ var AST = /** @class */ (function () { // Construct an AST from HTML markup, or wrap an array of existing AST nodes function AST(source) { this.nodes = typeof source === 'string' ? this.parseMarkup(source) : source; } /** * Filter an object of SVG or HTML attributes against the allow list. * * @static * * @function Highcharts.AST#filterUserAttributes * * @param {Highcharts.SVGAttributes} attributes The attributes to filter * * @return {Highcharts.SVGAttributes} * The filtered attributes */ AST.filterUserAttributes = function (attributes) { objectEach(attributes, function (val, key) { var valid = true; if (AST.allowedAttributes.indexOf(key) === -1) { valid = false; } if (['background', 'dynsrc', 'href', 'lowsrc', 'src'] .indexOf(key) !== -1) { valid = isString(val) && AST.allowedReferences.some(function (ref) { return val.indexOf(ref) === 0; }); } if (!valid) { error("Highcharts warning: Invalid attribute '" + key + "' in config"); delete attributes[key]; } }); return attributes; }; /** * Utility function to set html content for an element by passing in a * markup string. The markup is safely parsed by the AST class to avoid * XSS vulnerabilities. This function should be used instead of setting * `innerHTML` in all cases where the content is not fully trusted. * * @static * * @function Highcharts.AST#setElementHTML * * @param {SVGDOMElement|HTMLDOMElement} el The node to set content of * @param {string} html The markup string */ AST.setElementHTML = function (el, html) { el.innerHTML = ''; // Clear previous if (html) { var ast = new AST(html); ast.addToDOM(el); } }; /** * Add the tree defined as a hierarchical JS structure to the DOM * * @function Highcharts.AST#addToDOM * * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} parent * The node where it should be added * * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} * The inserted node. */ AST.prototype.addToDOM = function (parent) { /** * @private * @param {Highcharts.ASTNode} subtree - HTML/SVG definition * @param {Element} [subParent] - parent node * @return {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} The inserted node. */ function recurse(subtree, subParent) { var ret; splat(subtree).forEach(function (item) { var tagName = item.tagName; var textNode = item.textContent ? H.doc.createTextNode(item.textContent) : void 0; var node; if (tagName) { if (tagName === '#text') { node = textNode; } else if (AST.allowedTags.indexOf(tagName) !== -1) { var NS = tagName === 'svg' ? SVG_NS : (subParent.namespaceURI || SVG_NS); var element = H.doc.createElementNS(NS, tagName); var attributes_1 = item.attributes || {}; // Apply attributes from root of AST node, legacy from // from before TextBuilder objectEach(item, function (val, key) { if (key !== 'tagName' && key !== 'attributes' && key !== 'children' && key !== 'textContent') { attributes_1[key] = val; } }); attr(element, AST.filterUserAttributes(attributes_1)); // Add text content if (textNode) { element.appendChild(textNode); } // Recurse recurse(item.children || [], element); node = element; } else { error("Highcharts warning: Invalid tagName '" + tagName + "' in config"); } } // Add to the tree if (node) { subParent.appendChild(node); } ret = node; }); // Return last node added (on top level it's the only one) return ret; } return recurse(this.nodes, parent); }; /** * Parse HTML/SVG markup into AST Node objects. Used internally from the * constructor. * * @private * * @function Highcharts.AST#getNodesFromMarkup * * @param {string} markup The markup string. * * @return {Array} The parsed nodes. */ AST.prototype.parseMarkup = function (markup) { var nodes = []; var doc; var body; if (hasValidDOMParser) { doc = new DOMParser().parseFromString(markup, 'text/html'); } else { body = createElement('div'); body.innerHTML = markup; doc = { body: body }; } var appendChildNodes = function (node, addTo) { var tagName = node.nodeName.toLowerCase(); // Add allowed tags var astNode = { tagName: tagName }; if (tagName === '#text') { var textContent = node.textContent || ''; // Whitespace text node, don't append it to the AST if (/^[\s]*$/.test(textContent)) { return; } astNode.textContent = textContent; } var parsedAttributes = node.attributes; // Add attributes if (parsedAttributes) { var attributes_2 = {}; [].forEach.call(parsedAttributes, function (attrib) { attributes_2[attrib.name] = attrib.value; }); astNode.attributes = attributes_2; } // Handle children if (node.childNodes.length) { var children_1 = []; [].forEach.call(node.childNodes, function (childNode) { appendChildNodes(childNode, children_1); }); if (children_1.length) { astNode.children = children_1; } } addTo.push(astNode); }; [].forEach.call(doc.body.childNodes, function (childNode) { return appendChildNodes(childNode, nodes); }); if (body) { discardElement(body); } return nodes; }; /** * The list of allowed SVG or HTML tags, used for sanitizing potentially * harmful content from the chart configuration before adding to the DOM. * * @example * // Allow a custom, trusted tag * Highcharts.AST.allowedTags.push('blink'); // ;) * * @name Highcharts.AST.allowedTags * @static */ AST.allowedTags = [ 'a', 'b', 'br', 'button', 'caption', 'circle', 'clipPath', 'code', 'dd', 'defs', 'div', 'dl', 'dt', 'em', 'feComponentTransfer', 'feFuncA', 'feFuncB', 'feFuncG', 'feFuncR', 'feGaussianBlur', 'feOffset', 'feMerge', 'feMergeNode', 'filter', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i', 'img', 'li', 'linearGradient', 'marker', 'ol', 'p', 'path', 'pattern', 'pre', 'rect', 'small', 'span', 'stop', 'strong', 'style', 'sub', 'sup', 'svg', 'table', 'text', 'thead', 'tbody', 'tspan', 'td', 'th', 'tr', 'u', 'ul', '#text' ]; /** * The list of allowed SVG or HTML attributes, used for sanitizing * potentially harmful content from the chart configuration before adding to * the DOM. * * @example * // Allow a custom, trusted attribute * Highcharts.AST.allowedAttributes.push('data-value'); * * @name Highcharts.AST.allowedAttributes * @static */ AST.allowedAttributes = [ 'aria-controls', 'aria-describedby', 'aria-expanded', 'aria-haspopup', 'aria-hidden', 'aria-label', 'aria-labelledby', 'aria-live', 'aria-pressed', 'aria-readonly', 'aria-roledescription', 'aria-selected', 'class', 'clip-path', 'color', 'colspan', 'cx', 'cy', 'd', 'dx', 'dy', 'disabled', 'fill', 'height', 'href', 'id', 'in', 'markerHeight', 'markerWidth', 'offset', 'opacity', 'orient', 'padding', 'paddingLeft', 'paddingRight', 'patternUnits', 'r', 'refX', 'refY', 'role', 'scope', 'slope', 'src', 'startOffset', 'stdDeviation', 'stroke', 'stroke-linecap', 'stroke-width', 'style', 'tableValues', 'result', 'rowspan', 'summary', 'target', 'tabindex', 'text-align', 'textAnchor', 'textLength', 'type', 'valign', 'width', 'x', 'x1', 'x2', 'y', 'y1', 'y2', 'zIndex' ]; /** * The list of allowed references for referring attributes like `href` and * `src`. Attribute values will only be allowed if they start with one of * these strings. * * @example * // Allow tel: * Highcharts.AST.allowedReferences.push('tel:'); * * @name Highcharts.AST.allowedReferences * @static */ AST.allowedReferences = [ 'https://', 'http://', 'mailto:', '/', '../', './', '#' ]; return AST; }()); /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * Serialized form of an SVG/HTML definition, including children. * * @interface Highcharts.ASTNode */ /** * @name Highcharts.ASTNode#attributes * @type {Highcharts.SVGAttributes|undefined} */ /** * @name Highcharts.ASTNode#children * @type {Array|undefined} */ /** * @name Highcharts.ASTNode#tagName * @type {string|undefined} */ /** * @name Highcharts.ASTNode#textContent * @type {string|undefined} */ ''; // detach doclets above return AST; }); _registerModule(_modules, 'Core/FormatUtilities.js', [_modules['Core/DefaultOptions.js'], _modules['Core/Utilities.js']], function (D, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var defaultOptions = D.defaultOptions, defaultTime = D.defaultTime; var getNestedProperty = U.getNestedProperty, isNumber = U.isNumber, pick = U.pick, pInt = U.pInt; /* * * * Functions * * */ /** * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970) into a * human readable date string. The format is a subset of the formats for PHP's * [strftime](https://www.php.net/manual/en/function.strftime.php) function. * Additional formats can be given in the {@link Highcharts.dateFormats} hook. * * Since v6.0.5, all internal dates are formatted through the * {@link Highcharts.Chart#time} instance to respect chart-level time settings. * The `Highcharts.dateFormat` function only reflects global time settings set * with `setOptions`. * * Supported format keys: * - `%a`: Short weekday, like 'Mon' * - `%A`: Long weekday, like 'Monday' * - `%d`: Two digit day of the month, 01 to 31 * - `%e`: Day of the month, 1 through 31 * - `%w`: Day of the week, 0 through 6 * - `%b`: Short month, like 'Jan' * - `%B`: Long month, like 'January' * - `%m`: Two digit month number, 01 through 12 * - `%y`: Two digits year, like 09 for 2009 * - `%Y`: Four digits year, like 2009 * - `%H`: Two digits hours in 24h format, 00 through 23 * - `%k`: Hours in 24h format, 0 through 23 * - `%I`: Two digits hours in 12h format, 00 through 11 * - `%l`: Hours in 12h format, 1 through 12 * - `%M`: Two digits minutes, 00 through 59 * - `%p`: Upper case AM or PM * - `%P`: Lower case AM or PM * - `%S`: Two digits seconds, 00 through 59 * - `%L`: Milliseconds (naming from Ruby) * * @function Highcharts.dateFormat * * @param {string} format * The desired format where various time representations are prefixed * with `%`. * * @param {number} timestamp * The JavaScript timestamp. * * @param {boolean} [capitalize=false] * Upper case first letter in the return. * * @return {string} * The formatted date. */ function dateFormat(format, timestamp, capitalize) { return defaultTime.dateFormat(format, timestamp, capitalize); } /** * Format a string according to a subset of the rules of Python's String.format * method. * * @example * let s = Highcharts.format( * 'The {color} fox was {len:.2f} feet long', * { color: 'red', len: Math.PI } * ); * // => The red fox was 3.14 feet long * * @function Highcharts.format * * @param {string} str * The string to format. * * @param {Record} ctx * The context, a collection of key-value pairs where each key is * replaced by its value. * * @param {Highcharts.Chart} [chart] * A `Chart` instance used to get numberFormatter and time. * * @return {string} * The formatted string. */ function format(str, ctx, chart) { var splitter = '{', isInside = false, segment, valueAndFormat, val, index; var floatRegex = /f$/; var decRegex = /\.([0-9])/; var lang = defaultOptions.lang; var time = chart && chart.time || defaultTime; var numberFormatter = chart && chart.numberFormatter || numberFormat; var ret = []; while (str) { index = str.indexOf(splitter); if (index === -1) { break; } segment = str.slice(0, index); if (isInside) { // we're on the closing bracket looking back valueAndFormat = segment.split(':'); val = getNestedProperty(valueAndFormat.shift() || '', ctx); // Format the replacement if (valueAndFormat.length && typeof val === 'number') { segment = valueAndFormat.join(':'); if (floatRegex.test(segment)) { // float var decimals = parseInt((segment.match(decRegex) || ['', '-1'])[1], 10); if (val !== null) { val = numberFormatter(val, decimals, lang.decimalPoint, segment.indexOf(',') > -1 ? lang.thousandsSep : ''); } } else { val = time.dateFormat(segment, val); } } // Push the result and advance the cursor ret.push(val); } else { ret.push(segment); } str = str.slice(index + 1); // the rest isInside = !isInside; // toggle splitter = isInside ? '}' : '{'; // now look for next matching bracket } ret.push(str); return ret.join(''); } /** * Format a number and return a string based on input settings. * * @sample highcharts/members/highcharts-numberformat/ * Custom number format * * @function Highcharts.numberFormat * * @param {number} number * The input number to format. * * @param {number} decimals * The amount of decimals. A value of -1 preserves the amount in the * input number. * * @param {string} [decimalPoint] * The decimal point, defaults to the one given in the lang options, or * a dot. * * @param {string} [thousandsSep] * The thousands separator, defaults to the one given in the lang * options, or a space character. * * @return {string} * The formatted number. */ function numberFormat(number, decimals, decimalPoint, thousandsSep) { number = +number || 0; decimals = +decimals; var ret, fractionDigits; var lang = defaultOptions.lang, origDec = (number.toString().split('.')[1] || '').split('e')[0].length, exponent = number.toString().split('e'), firstDecimals = decimals; if (decimals === -1) { // Preserve decimals. Not huge numbers (#3793). decimals = Math.min(origDec, 20); } else if (!isNumber(decimals)) { decimals = 2; } else if (decimals && exponent[1] && exponent[1] < 0) { // Expose decimals from exponential notation (#7042) fractionDigits = decimals + +exponent[1]; if (fractionDigits >= 0) { // remove too small part of the number while keeping the notation exponent[0] = (+exponent[0]).toExponential(fractionDigits) .split('e')[0]; decimals = fractionDigits; } else { // fractionDigits < 0 exponent[0] = exponent[0].split('.')[0] || 0; if (decimals < 20) { // use number instead of exponential notation (#7405) number = (exponent[0] * Math.pow(10, exponent[1])) .toFixed(decimals); } else { // or zero number = 0; } exponent[1] = 0; } } // Add another decimal to avoid rounding errors of float numbers. (#4573) // Then use toFixed to handle rounding. var roundedNumber = (Math.abs(exponent[1] ? exponent[0] : number) + Math.pow(10, -Math.max(decimals, origDec) - 1)).toFixed(decimals); // A string containing the positive integer component of the number var strinteger = String(pInt(roundedNumber)); // Leftover after grouping into thousands. Can be 0, 1 or 2. var thousands = strinteger.length > 3 ? strinteger.length % 3 : 0; // Language decimalPoint = pick(decimalPoint, lang.decimalPoint); thousandsSep = pick(thousandsSep, lang.thousandsSep); // Start building the return ret = number < 0 ? '-' : ''; // Add the leftover after grouping into thousands. For example, in the // number 42 000 000, this line adds 42. ret += thousands ? strinteger.substr(0, thousands) + thousandsSep : ''; if (+exponent[1] < 0 && !firstDecimals) { ret = '0'; } else { // Add the remaining thousands groups, joined by the thousands separator ret += strinteger .substr(thousands) .replace(/(\d{3})(?=\d)/g, '$1' + thousandsSep); } // Add the decimal point and the decimal component if (decimals) { // Get the decimal component ret += decimalPoint + roundedNumber.slice(-decimals); } if (exponent[1] && +ret !== 0) { ret += 'e' + exponent[1]; } return ret; } /* * * * Default Export * * */ var FormatUtilities = { dateFormat: dateFormat, format: format, numberFormat: numberFormat }; return FormatUtilities; }); _registerModule(_modules, 'Core/Renderer/SVG/SVGElement.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Renderer/HTML/AST.js'], _modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Core/Color/Palette.js'], _modules['Core/Utilities.js']], function (A, AST, Color, H, palette, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var animate = A.animate, animObject = A.animObject, stop = A.stop; var deg2rad = H.deg2rad, doc = H.doc, hasTouch = H.hasTouch, noop = H.noop, svg = H.svg, SVG_NS = H.SVG_NS, win = H.win; var addEvent = U.addEvent, attr = U.attr, createElement = U.createElement, css = U.css, defined = U.defined, erase = U.erase, extend = U.extend, fireEvent = U.fireEvent, isArray = U.isArray, isFunction = U.isFunction, isNumber = U.isNumber, isString = U.isString, merge = U.merge, objectEach = U.objectEach, pick = U.pick, pInt = U.pInt, syncTimeout = U.syncTimeout, uniqueKey = U.uniqueKey; /* * * * Class * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The SVGElement prototype is a JavaScript wrapper for SVG elements used in the * rendering layer of Highcharts. Combined with the * {@link Highcharts.SVGRenderer} * object, these prototypes allow freeform annotation in the charts or even in * HTML pages without instanciating a chart. The SVGElement can also wrap HTML * labels, when `text` or `label` elements are created with the `useHTML` * parameter. * * The SVGElement instances are created through factory functions on the * {@link Highcharts.SVGRenderer} * object, like * {@link Highcharts.SVGRenderer#rect|rect}, * {@link Highcharts.SVGRenderer#path|path}, * {@link Highcharts.SVGRenderer#text|text}, * {@link Highcharts.SVGRenderer#label|label}, * {@link Highcharts.SVGRenderer#g|g} * and more. * * @class * @name Highcharts.SVGElement */ var SVGElement = /** @class */ (function () { function SVGElement() { /* * * * Properties * * */ this.element = void 0; this.onEvents = {}; this.opacity = 1; // Default base for animation this.renderer = void 0; this.SVG_NS = SVG_NS; // Custom attributes used for symbols, these should be filtered out when // setting SVGElement attributes (#9375). this.symbolCustomAttribs = [ 'x', 'y', 'width', 'height', 'r', 'start', 'end', 'innerR', 'anchorX', 'anchorY', 'rounded' ]; } // @todo public zIndex?: number; /* * * * Functions * * */ /** * Get the current value of an attribute or pseudo attribute, * used mainly for animation. Called internally from * the {@link Highcharts.SVGRenderer#attr} function. * * @private * @function Highcharts.SVGElement#_defaultGetter * * @param {string} key * Property key. * * @return {number|string} * Property value. */ SVGElement.prototype._defaultGetter = function (key) { var ret = pick(this[key + 'Value'], // align getter this[key], this.element ? this.element.getAttribute(key) : null, 0); if (/^[\-0-9\.]+$/.test(ret)) { // is numerical ret = parseFloat(ret); } return ret; }; /** * @private * @function Highcharts.SVGElement#_defaultSetter * * @param {string} value * * @param {string} key * * @param {Highcharts.SVGDOMElement} element * * @return {void} */ SVGElement.prototype._defaultSetter = function (value, key, element) { element.setAttribute(key, value); }; /** * Add the element to the DOM. All elements must be added this way. * * @sample highcharts/members/renderer-g * Elements added to a group * * @function Highcharts.SVGElement#add * * @param {Highcharts.SVGElement} [parent] * The parent item to add it to. If undefined, the element is added * to the {@link Highcharts.SVGRenderer.box}. * * @return {Highcharts.SVGElement} * Returns the SVGElement for chaining. */ SVGElement.prototype.add = function (parent) { var renderer = this.renderer, element = this.element; var inserted; if (parent) { this.parentGroup = parent; } // Mark as inverted this.parentInverted = parent && parent.inverted; // Build formatted text if (typeof this.textStr !== 'undefined' && this.element.nodeName === 'text' // Not for SVGLabel instances ) { renderer.buildText(this); } // Mark as added this.added = true; // If we're adding to renderer root, or other elements in the group // have a z index, we need to handle it if (!parent || parent.handleZ || this.zIndex) { inserted = this.zIndexSetter(); } // If zIndex is not handled, append at the end if (!inserted) { (parent ? parent.element : renderer.box).appendChild(element); } // fire an event for internal hooks if (this.onAdd) { this.onAdd(); } return this; }; /** * Add a class name to an element. * * @function Highcharts.SVGElement#addClass * * @param {string} className * The new class name to add. * * @param {boolean} [replace=false] * When true, the existing class name(s) will be overwritten with the new * one. When false, the new one is added. * * @return {Highcharts.SVGElement} * Return the SVG element for chainability. */ SVGElement.prototype.addClass = function (className, replace) { var currentClassName = replace ? '' : (this.attr('class') || ''); // Trim the string and remove duplicates className = (className || '') .split(/ /g) .reduce(function (newClassName, name) { if (currentClassName.indexOf(name) === -1) { newClassName.push(name); } return newClassName; }, (currentClassName ? [currentClassName] : [])) .join(' '); if (className !== currentClassName) { this.attr('class', className); } return this; }; /** * This method is executed in the end of `attr()`, after setting all * attributes in the hash. In can be used to efficiently consolidate * multiple attributes in one SVG property -- e.g., translate, rotate and * scale are merged in one "transform" attribute in the SVG node. * * @private * @function Highcharts.SVGElement#afterSetters */ SVGElement.prototype.afterSetters = function () { // Update transform. Do this outside the loop to prevent redundant // updating for batch setting of attributes. if (this.doTransform) { this.updateTransform(); this.doTransform = false; } }; /** * Align the element relative to the chart or another box. * * @function Highcharts.SVGElement#align * * @param {Highcharts.AlignObject} [alignOptions] * The alignment options. The function can be called without this * parameter in order to re-align an element after the box has been * updated. * * @param {boolean} [alignByTranslate] * Align element by translation. * * @param {string|Highcharts.BBoxObject} [box] * The box to align to, needs a width and height. When the box is a * string, it refers to an object in the Renderer. For example, when * box is `spacingBox`, it refers to `Renderer.spacingBox` which * holds `width`, `height`, `x` and `y` properties. * * @return {Highcharts.SVGElement} Returns the SVGElement for chaining. */ SVGElement.prototype.align = function (alignOptions, alignByTranslate, box) { var attribs = {}, renderer = this.renderer, alignedObjects = renderer.alignedObjects; var x, y, alignTo, alignFactor, vAlignFactor; // First call on instanciate if (alignOptions) { this.alignOptions = alignOptions; this.alignByTranslate = alignByTranslate; if (!box || isString(box)) { this.alignTo = alignTo = box || 'renderer'; // prevent duplicates, like legendGroup after resize erase(alignedObjects, this); alignedObjects.push(this); box = void 0; // reassign it below } // When called on resize, no arguments are supplied } else { alignOptions = this.alignOptions; alignByTranslate = this.alignByTranslate; alignTo = this.alignTo; } box = pick(box, renderer[alignTo], alignTo === 'scrollablePlotBox' ? renderer.plotBox : void 0, renderer); // Assign variables var align = alignOptions.align, vAlign = alignOptions.verticalAlign; // default: left align x = (box.x || 0) + (alignOptions.x || 0); // default: top align y = (box.y || 0) + (alignOptions.y || 0); // Align if (align === 'right') { alignFactor = 1; } else if (align === 'center') { alignFactor = 2; } if (alignFactor) { x += (box.width - (alignOptions.width || 0)) / alignFactor; } attribs[alignByTranslate ? 'translateX' : 'x'] = Math.round(x); // Vertical align if (vAlign === 'bottom') { vAlignFactor = 1; } else if (vAlign === 'middle') { vAlignFactor = 2; } if (vAlignFactor) { y += (box.height - (alignOptions.height || 0)) / vAlignFactor; } attribs[alignByTranslate ? 'translateY' : 'y'] = Math.round(y); // Animate only if already placed this[this.placed ? 'animate' : 'attr'](attribs); this.placed = true; this.alignAttr = attribs; return this; }; /** * @private * @function Highcharts.SVGElement#alignSetter * @param {"left"|"center"|"right"} value */ SVGElement.prototype.alignSetter = function (value) { var convert = { left: 'start', center: 'middle', right: 'end' }; if (convert[value]) { this.alignValue = value; this.element.setAttribute('text-anchor', convert[value]); } }; /** * Animate to given attributes or CSS properties. * * @sample highcharts/members/element-on/ * Setting some attributes by animation * * @function Highcharts.SVGElement#animate * * @param {Highcharts.SVGAttributes} params * SVG attributes or CSS to animate. * * @param {boolean|Partial} [options] * Animation options. * * @param {Function} [complete] * Function to perform at the end of animation. * * @return {Highcharts.SVGElement} * Returns the SVGElement for chaining. */ SVGElement.prototype.animate = function (params, options, complete) { var _this = this; var animOptions = animObject(pick(options, this.renderer.globalAnimation, true)), deferTime = animOptions.defer; // When the page is hidden save resources in the background by not // running animation at all (#9749). if (pick(doc.hidden, doc.msHidden, doc.webkitHidden, false)) { animOptions.duration = 0; } if (animOptions.duration !== 0) { // allows using a callback with the global animation without // overwriting it if (complete) { animOptions.complete = complete; } // If defer option is defined delay the animation #12901 syncTimeout(function () { if (_this.element) { animate(_this, params, animOptions); } }, deferTime); } else { this.attr(params, void 0, complete); // Call the end step synchronously objectEach(params, function (val, prop) { if (animOptions.step) { animOptions.step.call(this, val, { prop: prop, pos: 1, elem: this }); } }, this); } return this; }; /** * Apply a text outline through a custom CSS property, by copying the text * element and apply stroke to the copy. Used internally. Contrast checks at * [example](https://jsfiddle.net/highcharts/43soe9m1/2/). * * @example * // Specific color * text.css({ * textOutline: '1px black' * }); * // Automatic contrast * text.css({ * color: '#000000', // black text * textOutline: '1px contrast' // => white outline * }); * * @private * @function Highcharts.SVGElement#applyTextOutline * * @param {string} textOutline * A custom CSS `text-outline` setting, defined by `width color`. */ SVGElement.prototype.applyTextOutline = function (textOutline) { var elem = this.element, hasContrast = textOutline.indexOf('contrast') !== -1, styles = {}; // When the text shadow is set to contrast, use dark stroke for light // text and vice versa. if (hasContrast) { styles.textOutline = textOutline = textOutline.replace(/contrast/g, this.renderer.getContrast(elem.style.fill)); } // Extract the stroke width and color var parts = textOutline.split(' '); var color = parts[parts.length - 1]; var strokeWidth = parts[0]; if (strokeWidth && strokeWidth !== 'none' && H.svg) { this.fakeTS = true; // Fake text shadow // In order to get the right y position of the clone, // copy over the y setter this.ySetter = this.xSetter; // Since the stroke is applied on center of the actual outline, we // need to double it to get the correct stroke-width outside the // glyphs. strokeWidth = strokeWidth.replace(/(^[\d\.]+)(.*?)$/g, function (match, digit, unit) { return (2 * Number(digit)) + unit; }); // Remove shadows from previous runs. this.removeTextOutline(); var outline_1 = doc.createElementNS(SVG_NS, 'tspan'); attr(outline_1, { 'class': 'highcharts-text-outline', fill: color, stroke: color, 'stroke-width': strokeWidth, 'stroke-linejoin': 'round' }); // For each of the tspans and text nodes, create a copy in the // outline. [].forEach.call(elem.childNodes, function (childNode) { var clone = childNode.cloneNode(true); if (clone.removeAttribute) { ['fill', 'stroke', 'stroke-width', 'stroke'].forEach(function (prop) { return clone.removeAttribute(prop); }); } outline_1.appendChild(clone); }); // Insert an absolutely positioned break before the original text // to keep it in place var br_1 = doc.createElementNS(SVG_NS, 'tspan'); br_1.textContent = '\u200B'; // Copy x and y if not null ['x', 'y'].forEach(function (key) { var value = elem.getAttribute(key); if (value) { br_1.setAttribute(key, value); } }); // Insert the outline outline_1.appendChild(br_1); elem.insertBefore(outline_1, elem.firstChild); } }; /** * @function Highcharts.SVGElement#attr * @param {string} key * @return {number|string} */ /** * Apply native and custom attributes to the SVG elements. * * In order to set the rotation center for rotation, set x and y to 0 and * use `translateX` and `translateY` attributes to position the element * instead. * * Attributes frequently used in Highcharts are `fill`, `stroke`, * `stroke-width`. * * @sample highcharts/members/renderer-rect/ * Setting some attributes * * @example * // Set multiple attributes * element.attr({ * stroke: 'red', * fill: 'blue', * x: 10, * y: 10 * }); * * // Set a single attribute * element.attr('stroke', 'red'); * * // Get an attribute * element.attr('stroke'); // => 'red' * * @function Highcharts.SVGElement#attr * * @param {string|Highcharts.SVGAttributes} [hash] * The native and custom SVG attributes. * * @param {number|string|Highcharts.SVGPathArray} [val] * If the type of the first argument is `string`, the second can be a * value, which will serve as a single attribute setter. If the first * argument is a string and the second is undefined, the function * serves as a getter and the current value of the property is * returned. * * @param {Function} [complete] * A callback function to execute after setting the attributes. This * makes the function compliant and interchangeable with the * {@link SVGElement#animate} function. * * @param {boolean} [continueAnimation=true] * Used internally when `.attr` is called as part of an animation * step. Otherwise, calling `.attr` for an attribute will stop * animation for that attribute. * * @return {Highcharts.SVGElement} * If used as a setter, it returns the current * {@link Highcharts.SVGElement} so the calls can be chained. If * used as a getter, the current value of the attribute is returned. */ SVGElement.prototype.attr = function (hash, val, complete, continueAnimation) { var element = this.element, symbolCustomAttribs = this.symbolCustomAttribs; var key, hasSetSymbolSize, ret = this, skipAttr, setter; // single key-value pair if (typeof hash === 'string' && typeof val !== 'undefined') { key = hash; hash = {}; hash[key] = val; } // used as a getter: first argument is a string, second is undefined if (typeof hash === 'string') { ret = (this[hash + 'Getter'] || this._defaultGetter).call(this, hash, element); // setter } else { objectEach(hash, function eachAttribute(val, key) { skipAttr = false; // Unless .attr is from the animator update, stop current // running animation of this property if (!continueAnimation) { stop(this, key); } // Special handling of symbol attributes if (this.symbolName && symbolCustomAttribs.indexOf(key) !== -1) { if (!hasSetSymbolSize) { this.symbolAttr(hash); hasSetSymbolSize = true; } skipAttr = true; } if (this.rotation && (key === 'x' || key === 'y')) { this.doTransform = true; } if (!skipAttr) { setter = (this[key + 'Setter'] || this._defaultSetter); setter.call(this, val, key, element); // Let the shadow follow the main element if (!this.styledMode && this.shadows && /^(width|height|visibility|x|y|d|transform|cx|cy|r)$/.test(key)) { this.updateShadows(key, val, setter); } } }, this); this.afterSetters(); } // In accordance with animate, run a complete callback if (complete) { complete.call(this); } return ret; }; /** * Apply a clipping rectangle to this element. * * @function Highcharts.SVGElement#clip * * @param {Highcharts.ClipRectElement} [clipRect] * The clipping rectangle. If skipped, the current clip is removed. * * @return {Highcharts.SVGElement} * Returns the SVG element to allow chaining. */ SVGElement.prototype.clip = function (clipRect) { return this.attr('clip-path', clipRect ? 'url(' + this.renderer.url + '#' + clipRect.id + ')' : 'none'); }; /** * Calculate the coordinates needed for drawing a rectangle crisply and * return the calculated attributes. * * @function Highcharts.SVGElement#crisp * * @param {Highcharts.RectangleObject} rect * Rectangle to crisp. * * @param {number} [strokeWidth] * The stroke width to consider when computing crisp positioning. It can * also be set directly on the rect parameter. * * @return {Highcharts.RectangleObject} * The modified rectangle arguments. */ SVGElement.prototype.crisp = function (rect, strokeWidth) { var wrapper = this; strokeWidth = strokeWidth || rect.strokeWidth || 0; // Math.round because strokeWidth can sometimes have roundoff errors var normalizer = Math.round(strokeWidth) % 2 / 2; // normalize for crisp edges rect.x = Math.floor(rect.x || wrapper.x || 0) + normalizer; rect.y = Math.floor(rect.y || wrapper.y || 0) + normalizer; rect.width = Math.floor((rect.width || wrapper.width || 0) - 2 * normalizer); rect.height = Math.floor((rect.height || wrapper.height || 0) - 2 * normalizer); if (defined(rect.strokeWidth)) { rect.strokeWidth = strokeWidth; } return rect; }; /** * Build and apply an SVG gradient out of a common JavaScript configuration * object. This function is called from the attribute setters. An event * hook is added for supporting other complex color types. * * @private * @function Highcharts.SVGElement#complexColor * * @param {Highcharts.GradientColorObject|Highcharts.PatternObject} colorOptions * The gradient or pattern options structure. * * @param {string} prop * The property to apply, can either be `fill` or `stroke`. * * @param {Highcharts.SVGDOMElement} elem * SVG element to apply the gradient on. */ SVGElement.prototype.complexColor = function (colorOptions, prop, elem) { var renderer = this.renderer; var colorObject, gradName, gradAttr, radAttr, gradients, stops, stopColor, stopOpacity, radialReference, id, key = [], value; fireEvent(this.renderer, 'complexColor', { args: arguments }, function () { // Apply linear or radial gradients if (colorOptions.radialGradient) { gradName = 'radialGradient'; } else if (colorOptions.linearGradient) { gradName = 'linearGradient'; } if (gradName) { gradAttr = colorOptions[gradName]; gradients = renderer.gradients; stops = colorOptions.stops; radialReference = elem.radialReference; // Keep < 2.2 kompatibility if (isArray(gradAttr)) { colorOptions[gradName] = gradAttr = { x1: gradAttr[0], y1: gradAttr[1], x2: gradAttr[2], y2: gradAttr[3], gradientUnits: 'userSpaceOnUse' }; } // Correct the radial gradient for the radial reference system if (gradName === 'radialGradient' && radialReference && !defined(gradAttr.gradientUnits)) { // Save the radial attributes for updating radAttr = gradAttr; gradAttr = merge(gradAttr, renderer.getRadialAttr(radialReference, radAttr), { gradientUnits: 'userSpaceOnUse' }); } // Build the unique key to detect whether we need to create a // new element (#1282) objectEach(gradAttr, function (value, n) { if (n !== 'id') { key.push(n, value); } }); objectEach(stops, function (val) { key.push(val); }); key = key.join(','); // Check if a gradient object with the same config object is // created within this renderer if (gradients[key]) { id = gradients[key].attr('id'); } else { // Set the id and create the element gradAttr.id = id = uniqueKey(); var gradientObject_1 = gradients[key] = renderer.createElement(gradName) .attr(gradAttr) .add(renderer.defs); gradientObject_1.radAttr = radAttr; // The gradient needs to keep a list of stops to be able to // destroy them gradientObject_1.stops = []; stops.forEach(function (stop) { if (stop[1].indexOf('rgba') === 0) { colorObject = Color.parse(stop[1]); stopColor = colorObject.get('rgb'); stopOpacity = colorObject.get('a'); } else { stopColor = stop[1]; stopOpacity = 1; } var stopObject = renderer.createElement('stop').attr({ offset: stop[0], 'stop-color': stopColor, 'stop-opacity': stopOpacity }).add(gradientObject_1); // Add the stop element to the gradient gradientObject_1.stops.push(stopObject); }); } // Set the reference to the gradient object value = 'url(' + renderer.url + '#' + id + ')'; elem.setAttribute(prop, value); elem.gradient = key; // Allow the color to be concatenated into tooltips formatters // etc. (#2995) colorOptions.toString = function () { return value; }; } }); }; /** * Set styles for the element. In addition to CSS styles supported by * native SVG and HTML elements, there are also some custom made for * Highcharts, like `width`, `ellipsis` and `textOverflow` for SVG text * elements. * * @sample highcharts/members/renderer-text-on-chart/ * Styled text * * @function Highcharts.SVGElement#css * * @param {Highcharts.CSSObject} styles * The new CSS styles. * * @return {Highcharts.SVGElement} * Return the SVG element for chaining. */ SVGElement.prototype.css = function (styles) { var oldStyles = this.styles, newStyles = {}, elem = this.element, // These CSS properties are interpreted internally by the SVG // renderer, but are not supported by SVG and should not be added to // the DOM. In styled mode, no CSS should find its way to the DOM // whatsoever (#6173, #6474). svgPseudoProps = ['textOutline', 'textOverflow', 'width']; var textWidth, serializedCss = '', hyphenate, hasNew = !oldStyles; // convert legacy if (styles && styles.color) { styles.fill = styles.color; } // Filter out existing styles to increase performance (#2640) if (oldStyles) { objectEach(styles, function (style, n) { if (oldStyles && oldStyles[n] !== style) { newStyles[n] = style; hasNew = true; } }); } if (hasNew) { // Merge the new styles with the old ones if (oldStyles) { styles = extend(oldStyles, newStyles); } // Get the text width from style if (styles) { // Previously set, unset it (#8234) if (styles.width === null || styles.width === 'auto') { delete this.textWidth; // Apply new } else if (elem.nodeName.toLowerCase() === 'text' && styles.width) { textWidth = this.textWidth = pInt(styles.width); } } // store object this.styles = styles; if (textWidth && (!svg && this.renderer.forExport)) { delete styles.width; } // Serialize and set style attribute if (elem.namespaceURI === this.SVG_NS) { // #7633 hyphenate = function (a, b) { return '-' + b.toLowerCase(); }; objectEach(styles, function (style, n) { if (svgPseudoProps.indexOf(n) === -1) { serializedCss += n.replace(/([A-Z])/g, hyphenate) + ':' + style + ';'; } }); if (serializedCss) { attr(elem, 'style', serializedCss); // #1881 } } else { css(elem, styles); } if (this.added) { // Rebuild text after added. Cache mechanisms in the buildText // will prevent building if there are no significant changes. if (this.element.nodeName === 'text') { this.renderer.buildText(this); } // Apply text outline after added if (styles && styles.textOutline) { this.applyTextOutline(styles.textOutline); } } } return this; }; /** * @private * @function Highcharts.SVGElement#dashstyleSetter * @param {string} value */ SVGElement.prototype.dashstyleSetter = function (value) { var i, strokeWidth = this['stroke-width']; // If "inherit", like maps in IE, assume 1 (#4981). With HC5 and the new // strokeWidth function, we should be able to use that instead. if (strokeWidth === 'inherit') { strokeWidth = 1; } value = value && value.toLowerCase(); if (value) { var v = value .replace('shortdashdotdot', '3,1,1,1,1,1,') .replace('shortdashdot', '3,1,1,1') .replace('shortdot', '1,1,') .replace('shortdash', '3,1,') .replace('longdash', '8,3,') .replace(/dot/g, '1,3,') .replace('dash', '4,3,') .replace(/,$/, '') .split(','); // ending comma i = v.length; while (i--) { v[i] = '' + (pInt(v[i]) * pick(strokeWidth, NaN)); } value = v.join(',').replace(/NaN/g, 'none'); // #3226 this.element.setAttribute('stroke-dasharray', value); } }; /** * Destroy the element and element wrapper and clear up the DOM and event * hooks. * * @function Highcharts.SVGElement#destroy */ SVGElement.prototype.destroy = function () { var wrapper = this, element = wrapper.element || {}, renderer = wrapper.renderer, ownerSVGElement = element.ownerSVGElement; var parentToClean = (renderer.isSVG && element.nodeName === 'SPAN' && wrapper.parentGroup || void 0), grandParent, i; // remove events element.onclick = element.onmouseout = element.onmouseover = element.onmousemove = element.point = null; stop(wrapper); // stop running animations if (wrapper.clipPath && ownerSVGElement) { var clipPath_1 = wrapper.clipPath; // Look for existing references to this clipPath and remove them // before destroying the element (#6196). // The upper case version is for Edge [].forEach.call(ownerSVGElement.querySelectorAll('[clip-path],[CLIP-PATH]'), function (el) { if (el.getAttribute('clip-path').indexOf(clipPath_1.element.id) > -1) { el.removeAttribute('clip-path'); } }); wrapper.clipPath = clipPath_1.destroy(); } // Destroy stops in case this is a gradient object @todo old code? if (wrapper.stops) { for (i = 0; i < wrapper.stops.length; i++) { wrapper.stops[i].destroy(); } wrapper.stops.length = 0; wrapper.stops = void 0; } // remove element wrapper.safeRemoveChild(element); if (!renderer.styledMode) { wrapper.destroyShadows(); } // In case of useHTML, clean up empty containers emulating SVG groups // (#1960, #2393, #2697). while (parentToClean && parentToClean.div && parentToClean.div.childNodes.length === 0) { grandParent = parentToClean.parentGroup; wrapper.safeRemoveChild(parentToClean.div); delete parentToClean.div; parentToClean = grandParent; } // remove from alignObjects if (wrapper.alignTo) { erase(renderer.alignedObjects, wrapper); } objectEach(wrapper, function (val, key) { // Destroy child elements of a group if (wrapper[key] && wrapper[key].parentGroup === wrapper && wrapper[key].destroy) { wrapper[key].destroy(); } // Delete all properties delete wrapper[key]; }); return; }; /** * Destroy shadows on the element. * * @private * @function Highcharts.SVGElement#destroyShadows * * @return {void} */ SVGElement.prototype.destroyShadows = function () { (this.shadows || []).forEach(function (shadow) { this.safeRemoveChild(shadow); }, this); this.shadows = void 0; }; /** * @private */ SVGElement.prototype.destroyTextPath = function (elem, path) { var textElement = elem.getElementsByTagName('text')[0]; var childNodes; if (textElement) { // Remove textPath attributes textElement.removeAttribute('dx'); textElement.removeAttribute('dy'); // Remove ID's: path.element.setAttribute('id', ''); // Check if textElement includes textPath, if (this.textPathWrapper && textElement.getElementsByTagName('textPath').length) { // Move nodes to childNodes = this.textPathWrapper.element.childNodes; // Now move all 's and text nodes to the node while (childNodes.length) { textElement.appendChild(childNodes[0]); } // Remove from the DOM textElement.removeChild(this.textPathWrapper.element); } } else if (elem.getAttribute('dx') || elem.getAttribute('dy')) { // Remove textPath attributes from elem // to get correct text-outline position elem.removeAttribute('dx'); elem.removeAttribute('dy'); } if (this.textPathWrapper) { // Set textPathWrapper to undefined and destroy it this.textPathWrapper = this.textPathWrapper.destroy(); } }; /** * @private * @function Highcharts.SVGElement#dSettter * @param {number|string|Highcharts.SVGPathArray} value * @param {string} key * @param {Highcharts.SVGDOMElement} element */ SVGElement.prototype.dSetter = function (value, key, element) { if (isArray(value)) { // Backwards compatibility, convert one-dimensional array into an // array of segments if (typeof value[0] === 'string') { value = this.renderer.pathToSegments(value); } this.pathArray = value; value = value.reduce(function (acc, seg, i) { if (!seg || !seg.join) { return (seg || '').toString(); } return (i ? acc + ' ' : '') + seg.join(' '); }, ''); } if (/(NaN| {2}|^$)/.test(value)) { value = 'M 0 0'; } // Check for cache before resetting. Resetting causes disturbance in the // DOM, causing flickering in some cases in Edge/IE (#6747). Also // possible performance gain. if (this[key] !== value) { element.setAttribute(key, value); this[key] = value; } }; /** * Fade out an element by animating its opacity down to 0, and hide it on * complete. Used internally for the tooltip. * * @function Highcharts.SVGElement#fadeOut * * @param {number} [duration=150] * The fade duration in milliseconds. */ SVGElement.prototype.fadeOut = function (duration) { var elemWrapper = this; elemWrapper.animate({ opacity: 0 }, { duration: pick(duration, 150), complete: function () { // #3088, assuming we're only using this for tooltips elemWrapper.attr({ y: -9999 }).hide(); } }); }; /** * @private * @function Highcharts.SVGElement#fillSetter * @param {Highcharts.ColorType} value * @param {string} key * @param {Highcharts.SVGDOMElement} element */ SVGElement.prototype.fillSetter = function (value, key, element) { if (typeof value === 'string') { element.setAttribute(key, value); } else if (value) { this.complexColor(value, key, element); } }; /** * Get the bounding box (width, height, x and y) for the element. Generally * used to get rendered text size. Since this is called a lot in charts, * the results are cached based on text properties, in order to save DOM * traffic. The returned bounding box includes the rotation, so for example * a single text line of rotation 90 will report a greater height, and a * width corresponding to the line-height. * * @sample highcharts/members/renderer-on-chart/ * Draw a rectangle based on a text's bounding box * * @function Highcharts.SVGElement#getBBox * * @param {boolean} [reload] * Skip the cache and get the updated DOM bouding box. * * @param {number} [rot] * Override the element's rotation. This is internally used on axis * labels with a value of 0 to find out what the bounding box would * be have been if it were not rotated. * * @return {Highcharts.BBoxObject} * The bounding box with `x`, `y`, `width` and `height` properties. */ SVGElement.prototype.getBBox = function (reload, rot) { var wrapper = this, renderer = wrapper.renderer, element = wrapper.element, styles = wrapper.styles, textStr = wrapper.textStr, cache = renderer.cache, cacheKeys = renderer.cacheKeys, isSVG = element.namespaceURI === wrapper.SVG_NS, rotation = pick(rot, wrapper.rotation, 0), fontSize = renderer.styledMode ? (element && SVGElement.prototype.getStyle.call(element, 'font-size')) : (styles && styles.fontSize); var bBox, // = wrapper.bBox, width, height, toggleTextShadowShim, cacheKey; // Avoid undefined and null (#7316) if (defined(textStr)) { cacheKey = textStr.toString(); // Since numbers are monospaced, and numerical labels appear a lot // in a chart, we assume that a label of n characters has the same // bounding box as others of the same length. Unless there is inner // HTML in the label. In that case, leave the numbers as is (#5899). if (cacheKey.indexOf('<') === -1) { cacheKey = cacheKey.replace(/[0-9]/g, '0'); } // Properties that affect bounding box cacheKey += [ '', rotation, fontSize, wrapper.textWidth, styles && styles.textOverflow, styles && styles.fontWeight // #12163 ].join(','); } if (cacheKey && !reload) { bBox = cache[cacheKey]; } // No cache found if (!bBox) { // SVG elements if (isSVG || renderer.forExport) { try { // Fails in Firefox if the container has display: none. // When the text shadow shim is used, we need to hide the // fake shadows to get the correct bounding box (#3872) toggleTextShadowShim = this.fakeTS && function (display) { var outline = element.querySelector('.highcharts-text-outline'); if (outline) { css(outline, { display: display }); } }; // Workaround for #3842, Firefox reporting wrong bounding // box for shadows if (isFunction(toggleTextShadowShim)) { toggleTextShadowShim('none'); } bBox = element.getBBox ? // SVG: use extend because IE9 is not allowed to change // width and height in case of rotation (below) extend({}, element.getBBox()) : { // Legacy IE in export mode width: element.offsetWidth, height: element.offsetHeight }; // #3842 if (isFunction(toggleTextShadowShim)) { toggleTextShadowShim(''); } } catch (e) { ''; } // If the bBox is not set, the try-catch block above failed. The // other condition is for Opera that returns a width of // -Infinity on hidden elements. if (!bBox || bBox.width < 0) { bBox = { width: 0, height: 0 }; } // VML Renderer or useHTML within SVG } else { bBox = wrapper.htmlGetBBox(); } // True SVG elements as well as HTML elements in modern browsers // using the .useHTML option need to compensated for rotation if (renderer.isSVG) { width = bBox.width; height = bBox.height; // Workaround for wrong bounding box in IE, Edge and Chrome on // Windows. With Highcharts' default font, IE and Edge report // a box height of 16.899 and Chrome rounds it to 17. If this // stands uncorrected, it results in more padding added below // the text than above when adding a label border or background. // Also vertical positioning is affected. // https://jsfiddle.net/highcharts/em37nvuj/ // (#1101, #1505, #1669, #2568, #6213). if (isSVG) { bBox.height = height = ({ '11px,17': 14, '13px,20': 16 }[styles && styles.fontSize + ',' + Math.round(height)] || height); } // Adjust for rotated text if (rotation) { var rad = rotation * deg2rad; bBox.width = Math.abs(height * Math.sin(rad)) + Math.abs(width * Math.cos(rad)); bBox.height = Math.abs(height * Math.cos(rad)) + Math.abs(width * Math.sin(rad)); } } // Cache it. When loading a chart in a hidden iframe in Firefox and // IE/Edge, the bounding box height is 0, so don't cache it (#5620). if (cacheKey && bBox.height > 0) { // Rotate (#4681) while (cacheKeys.length > 250) { delete cache[cacheKeys.shift()]; } if (!cache[cacheKey]) { cacheKeys.push(cacheKey); } cache[cacheKey] = bBox; } } return bBox; }; /** * Get the computed style. Only in styled mode. * * @example * chart.series[0].points[0].graphic.getStyle('stroke-width'); // => '1px' * * @function Highcharts.SVGElement#getStyle * * @param {string} prop * The property name to check for. * * @return {string} * The current computed value. */ SVGElement.prototype.getStyle = function (prop) { return win .getComputedStyle(this.element || this, '') .getPropertyValue(prop); }; /** * Check if an element has the given class name. * * @function Highcharts.SVGElement#hasClass * * @param {string} className * The class name to check for. * * @return {boolean} * Whether the class name is found. */ SVGElement.prototype.hasClass = function (className) { return ('' + this.attr('class')) .split(' ') .indexOf(className) !== -1; }; /** * Hide the element, similar to setting the `visibility` attribute to * `hidden`. * * @function Highcharts.SVGElement#hide * * @param {boolean} [hideByTranslation=false] * The flag to determine if element should be hidden by moving out * of the viewport. Used for example for dataLabels. * * @return {Highcharts.SVGElement} * Returns the SVGElement for chaining. */ SVGElement.prototype.hide = function (hideByTranslation) { if (hideByTranslation) { this.attr({ y: -9999 }); } else { this.attr({ visibility: 'hidden' }); } return this; }; /** * @private */ SVGElement.prototype.htmlGetBBox = function () { return { height: 0, width: 0, x: 0, y: 0 }; }; /** * Initialize the SVG element. This function only exists to make the * initialization process overridable. It should not be called directly. * * @function Highcharts.SVGElement#init * * @param {Highcharts.SVGRenderer} renderer * The SVGRenderer instance to initialize to. * * @param {string} nodeName * The SVG node name. */ SVGElement.prototype.init = function (renderer, nodeName) { /** * The primary DOM node. Each `SVGElement` instance wraps a main DOM * node, but may also represent more nodes. * * @name Highcharts.SVGElement#element * @type {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} */ this.element = nodeName === 'span' ? createElement(nodeName) : doc.createElementNS(this.SVG_NS, nodeName); /** * The renderer that the SVGElement belongs to. * * @name Highcharts.SVGElement#renderer * @type {Highcharts.SVGRenderer} */ this.renderer = renderer; fireEvent(this, 'afterInit'); }; /** * Invert a group, rotate and flip. This is used internally on inverted * charts, where the points and graphs are drawn as if not inverted, then * the series group elements are inverted. * * @function Highcharts.SVGElement#invert * * @param {boolean} inverted * Whether to invert or not. An inverted shape can be un-inverted by * setting it to false. * * @return {Highcharts.SVGElement} * Return the SVGElement for chaining. */ SVGElement.prototype.invert = function (inverted) { this.inverted = inverted; this.updateTransform(); return this; }; /** * Add an event listener. This is a simple setter that replaces the * previous event of the same type added by this function, as opposed to * the {@link Highcharts#addEvent} function. * * @sample highcharts/members/element-on/ * A clickable rectangle * * @function Highcharts.SVGElement#on * * @param {string} eventType * The event type. * * @param {Function} handler * The handler callback. * * @return {Highcharts.SVGElement} * The SVGElement for chaining. */ SVGElement.prototype.on = function (eventType, handler) { var onEvents = this.onEvents; if (onEvents[eventType]) { // Unbind existing event onEvents[eventType](); } onEvents[eventType] = addEvent(this.element, eventType, handler); return this; }; /** * @private * @function Highcharts.SVGElement#opacitySetter * @param {string} value * @param {string} key * @param {Highcharts.SVGDOMElement} element */ SVGElement.prototype.opacitySetter = function (value, key, element) { // Round off to avoid float errors, like tests where opacity lands on // 9.86957e-06 instead of 0 var opacity = Number(Number(value).toFixed(3)); this.opacity = opacity; element.setAttribute(key, opacity); }; /** * Remove a class name from the element. * * @function Highcharts.SVGElement#removeClass * * @param {string|RegExp} className * The class name to remove. * * @return {Highcharts.SVGElement} Returns the SVG element for chainability. */ SVGElement.prototype.removeClass = function (className) { return this.attr('class', ('' + this.attr('class')) .replace(isString(className) ? new RegExp("(^| )" + className + "( |$)") : // #12064, #13590 className, ' ') .replace(/ +/g, ' ') .trim()); }; /** * * @private */ SVGElement.prototype.removeTextOutline = function () { var outline = this.element .querySelector('tspan.highcharts-text-outline'); if (outline) { this.safeRemoveChild(outline); } }; /** * Removes an element from the DOM. * * @private * @function Highcharts.SVGElement#safeRemoveChild * * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element * The DOM node to remove. */ SVGElement.prototype.safeRemoveChild = function (element) { var parentNode = element.parentNode; if (parentNode) { parentNode.removeChild(element); } }; /** * Set the coordinates needed to draw a consistent radial gradient across * a shape regardless of positioning inside the chart. Used on pie slices * to make all the slices have the same radial reference point. * * @function Highcharts.SVGElement#setRadialReference * * @param {Array} coordinates * The center reference. The format is `[centerX, centerY, diameter]` in * pixels. * * @return {Highcharts.SVGElement} * Returns the SVGElement for chaining. */ SVGElement.prototype.setRadialReference = function (coordinates) { var existingGradient = (this.element.gradient && this.renderer.gradients[this.element.gradient]); this.element.radialReference = coordinates; // On redrawing objects with an existing gradient, the gradient needs // to be repositioned (#3801) if (existingGradient && existingGradient.radAttr) { existingGradient.animate(this.renderer.getRadialAttr(coordinates, existingGradient.radAttr)); } return this; }; /** * @private * @function Highcharts.SVGElement#setTextPath * @param {Highcharts.SVGElement} path * Path to follow. * @param {Highcharts.DataLabelsTextPathOptionsObject} textPathOptions * Options. * @return {Highcharts.SVGElement} * Returns the SVGElement for chaining. */ SVGElement.prototype.setTextPath = function (path, textPathOptions) { var elem = this.element, textNode = this.text ? this.text.element : elem, attribsMap = { textAnchor: 'text-anchor' }; var adder = false, textPathElement, textPathId, textPathWrapper = this.textPathWrapper, firstTime = !textPathWrapper; // Defaults textPathOptions = merge(true, { enabled: true, attributes: { dy: -5, startOffset: '50%', textAnchor: 'middle' } }, textPathOptions); var attrs = AST.filterUserAttributes(textPathOptions.attributes); if (path && textPathOptions && textPathOptions.enabled) { // In case of fixed width for a text, string is rebuilt // (e.g. ellipsis is applied), so we need to rebuild textPath too if (textPathWrapper && textPathWrapper.element.parentNode === null) { // When buildText functionality was triggered again // and deletes textPathWrapper parentNode firstTime = true; textPathWrapper = textPathWrapper.destroy(); } else if (textPathWrapper) { // Case after drillup when spans were added into // the DOM outside the textPathWrapper parentGroup this.removeTextOutline.call(textPathWrapper.parentGroup); } // label() has padding, text() doesn't if (this.options && this.options.padding) { attrs.dx = -this.options.padding; } if (!textPathWrapper) { // Create , defer the DOM adder this.textPathWrapper = textPathWrapper = this.renderer.createElement('textPath'); adder = true; } textPathElement = textPathWrapper.element; // Set ID for the path textPathId = path.element.getAttribute('id'); if (!textPathId) { path.element.setAttribute('id', textPathId = uniqueKey()); } // Change DOM structure, by placing tag in if (firstTime) { // Adjust the position textNode.setAttribute('y', 0); // Firefox if (isNumber(attrs.dx)) { textNode.setAttribute('x', -attrs.dx); } // Move all 's and text nodes to the node. Do // not move other elements like or <path> var childNodes = [].slice.call(textNode.childNodes); for (var i = 0; i < childNodes.length; i++) { var childNode = childNodes[i]; if (childNode.nodeType === Node.TEXT_NODE || childNode.nodeName === 'tspan') { textPathElement.appendChild(childNode); } } } // Add <textPath> to the DOM if (adder && textPathWrapper) { textPathWrapper.add({ element: textNode }); } // Set basic options: // Use `setAttributeNS` because Safari needs this.. textPathElement.setAttributeNS('http://www.w3.org/1999/xlink', 'href', this.renderer.url + '#' + textPathId); // Presentation attributes: // dx/dy options must by set on <text> (parent), // the rest should be set on <textPath> if (defined(attrs.dy)) { textPathElement.parentNode .setAttribute('dy', attrs.dy); delete attrs.dy; } if (defined(attrs.dx)) { textPathElement.parentNode .setAttribute('dx', attrs.dx); delete attrs.dx; } // Additional attributes objectEach(attrs, function (val, key) { textPathElement.setAttribute(attribsMap[key] || key, val); }); // Remove translation, text that follows path does not need that elem.removeAttribute('transform'); // Remove shadows and text outlines this.removeTextOutline.call(textPathWrapper); // Remove background and border for label(), see #10545 // Alternatively, we can disable setting background rects in // series.drawDataLabels() if (this.text && !this.renderer.styledMode) { this.attr({ fill: 'none', 'stroke-width': 0 }); } // Disable some functions this.updateTransform = noop; this.applyTextOutline = noop; } else if (textPathWrapper) { // Reset to prototype delete this.updateTransform; delete this.applyTextOutline; // Restore DOM structure: this.destroyTextPath(elem, path); // Bring attributes back this.updateTransform(); // Set textOutline back for text() if (this.options && this.options.rotation) { this.applyTextOutline(this.options.style.textOutline); } } return this; }; /** * Add a shadow to the element. Must be called after the element is added to * the DOM. In styled mode, this method is not used, instead use `defs` and * filters. * * @example * renderer.rect(10, 100, 100, 100) * .attr({ fill: 'red' }) * .shadow(true); * * @function Highcharts.SVGElement#shadow * * @param {boolean|Highcharts.ShadowOptionsObject} [shadowOptions] * The shadow options. If `true`, the default options are applied. If * `false`, the current shadow will be removed. * * @param {Highcharts.SVGElement} [group] * The SVG group element where the shadows will be applied. The * default is to add it to the same parent as the current element. * Internally, this is ised for pie slices, where all the shadows are * added to an element behind all the slices. * * @param {boolean} [cutOff] * Used internally for column shadows. * * @return {Highcharts.SVGElement} * Returns the SVGElement for chaining. */ SVGElement.prototype.shadow = function (shadowOptions, group, cutOff) { var shadows = [], element = this.element, oldShadowOptions = this.oldShadowOptions, defaultShadowOptions = { color: palette.neutralColor100, offsetX: this.parentInverted ? -1 : 1, offsetY: this.parentInverted ? -1 : 1, opacity: 0.15, width: 3 }; var i, shadow, strokeWidth, shadowElementOpacity, update = false, // compensate for inverted plot area transform, options; if (shadowOptions === true) { options = defaultShadowOptions; } else if (typeof shadowOptions === 'object') { options = extend(defaultShadowOptions, shadowOptions); } // Update shadow when options change (#12091). if (options) { // Go over each key to look for change if (options && oldShadowOptions) { objectEach(options, function (value, key) { if (value !== oldShadowOptions[key]) { update = true; } }); } if (update) { this.destroyShadows(); } this.oldShadowOptions = options; } if (!options) { this.destroyShadows(); } else if (!this.shadows) { shadowElementOpacity = options.opacity / options.width; transform = this.parentInverted ? "translate(" + options.offsetY + ", " + options.offsetX + ")" : "translate(" + options.offsetX + ", " + options.offsetY + ")"; for (i = 1; i <= options.width; i++) { shadow = element.cloneNode(false); strokeWidth = (options.width * 2) + 1 - (2 * i); attr(shadow, { stroke: (shadowOptions.color || palette.neutralColor100), 'stroke-opacity': shadowElementOpacity * i, 'stroke-width': strokeWidth, transform: transform, fill: 'none' }); shadow.setAttribute('class', (shadow.getAttribute('class') || '') + ' highcharts-shadow'); if (cutOff) { attr(shadow, 'height', Math.max(attr(shadow, 'height') - strokeWidth, 0)); shadow.cutHeight = strokeWidth; } if (group) { group.element.appendChild(shadow); } else if (element.parentNode) { element.parentNode.insertBefore(shadow, element); } shadows.push(shadow); } this.shadows = shadows; } return this; }; /** * Show the element after it has been hidden. * * @function Highcharts.SVGElement#show * * @param {boolean} [inherit=false] * Set the visibility attribute to `inherit` rather than `visible`. * The difference is that an element with `visibility="visible"` * will be visible even if the parent is hidden. * * @return {Highcharts.SVGElement} * Returns the SVGElement for chaining. */ SVGElement.prototype.show = function (inherit) { return this.attr({ visibility: inherit ? 'inherit' : 'visible' }); }; /** * WebKit and Batik have problems with a stroke-width of zero, so in this * case we remove the stroke attribute altogether. #1270, #1369, #3065, * #3072. * * @private * @function Highcharts.SVGElement#strokeSetter * @param {number|string|ColorType} value * @param {string} key * @param {Highcharts.SVGDOMElement} element */ SVGElement.prototype.strokeSetter = function (value, key, element) { this[key] = value; // Only apply the stroke attribute if the stroke width is defined and // larger than 0 if (this.stroke && this['stroke-width']) { // Use prototype as instance may be overridden SVGElement.prototype.fillSetter.call(this, this.stroke, 'stroke', element); element.setAttribute('stroke-width', this['stroke-width']); this.hasStroke = true; } else if (key === 'stroke-width' && value === 0 && this.hasStroke) { element.removeAttribute('stroke'); this.hasStroke = false; } else if (this.renderer.styledMode && this['stroke-width']) { element.setAttribute('stroke-width', this['stroke-width']); this.hasStroke = true; } }; /** * Get the computed stroke width in pixel values. This is used extensively * when drawing shapes to ensure the shapes are rendered crisp and * positioned correctly relative to each other. Using * `shape-rendering: crispEdges` leaves us less control over positioning, * for example when we want to stack columns next to each other, or position * things pixel-perfectly within the plot box. * * The common pattern when placing a shape is: * - Create the SVGElement and add it to the DOM. In styled mode, it will * now receive a stroke width from the style sheet. In classic mode we * will add the `stroke-width` attribute. * - Read the computed `elem.strokeWidth()`. * - Place it based on the stroke width. * * @function Highcharts.SVGElement#strokeWidth * * @return {number} * The stroke width in pixels. Even if the given stroke widtch (in CSS or by * attributes) is based on `em` or other units, the pixel size is returned. */ SVGElement.prototype.strokeWidth = function () { // In non-styled mode, read the stroke width as set by .attr if (!this.renderer.styledMode) { return this['stroke-width'] || 0; } // In styled mode, read computed stroke width var val = this.getStyle('stroke-width'); var ret = 0, dummy; // Read pixel values directly if (val.indexOf('px') === val.length - 2) { ret = pInt(val); // Other values like em, pt etc need to be measured } else if (val !== '') { dummy = doc.createElementNS(SVG_NS, 'rect'); attr(dummy, { width: val, 'stroke-width': 0 }); this.element.parentNode.appendChild(dummy); ret = dummy.getBBox().width; dummy.parentNode.removeChild(dummy); } return ret; }; /** * If one of the symbol size affecting parameters are changed, * check all the others only once for each call to an element's * .attr() method * * @private * @function Highcharts.SVGElement#symbolAttr * * @param {Highcharts.SVGAttributes} hash * The attributes to set. */ SVGElement.prototype.symbolAttr = function (hash) { var wrapper = this; [ 'x', 'y', 'r', 'start', 'end', 'width', 'height', 'innerR', 'anchorX', 'anchorY', 'clockwise' ].forEach(function (key) { wrapper[key] = pick(hash[key], wrapper[key]); }); wrapper.attr({ d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper) }); }; /** * @private * @function Highcharts.SVGElement#textSetter * @param {string} value */ SVGElement.prototype.textSetter = function (value) { if (value !== this.textStr) { // Delete size caches when the text changes // delete this.bBox; // old code in series-label delete this.textPxLength; this.textStr = value; if (this.added) { this.renderer.buildText(this); } } }; /** * @private * @function Highcharts.SVGElement#titleSetter * @param {string} value */ SVGElement.prototype.titleSetter = function (value) { var el = this.element; var titleNode = el.getElementsByTagName('title')[0] || doc.createElementNS(this.SVG_NS, 'title'); // Move to first child if (el.insertBefore) { el.insertBefore(titleNode, el.firstChild); } else { el.appendChild(titleNode); } // Replace text content and escape markup titleNode.textContent = // #3276, #3895 String(pick(value, '')) .replace(/<[^>]*>/g, '') .replace(/</g, '<') .replace(/>/g, '>'); }; /** * Bring the element to the front. Alternatively, a new zIndex can be set. * * @sample highcharts/members/element-tofront/ * Click an element to bring it to front * * @function Highcharts.SVGElement#toFront * * @return {Highcharts.SVGElement} * Returns the SVGElement for chaining. */ SVGElement.prototype.toFront = function () { var element = this.element; element.parentNode.appendChild(element); return this; }; /** * Move an object and its children by x and y values. * * @function Highcharts.SVGElement#translate * * @param {number} x * The x value. * * @param {number} y * The y value. * * @return {Highcharts.SVGElement} */ SVGElement.prototype.translate = function (x, y) { return this.attr({ translateX: x, translateY: y }); }; /** * Update the shadow elements with new attributes. * * @private * @function Highcharts.SVGElement#updateShadows * * @param {string} key * The attribute name. * * @param {number} value * The value of the attribute. * * @param {Function} setter * The setter function, inherited from the parent wrapper. */ SVGElement.prototype.updateShadows = function (key, value, setter) { var shadows = this.shadows; if (shadows) { var i = shadows.length; while (i--) { setter.call(shadows[i], key === 'height' ? Math.max(value - (shadows[i].cutHeight || 0), 0) : key === 'd' ? this.d : value, key, shadows[i]); } } }; /** * Update the transform attribute based on internal properties. Deals with * the custom `translateX`, `translateY`, `rotation`, `scaleX` and `scaleY` * attributes and updates the SVG `transform` attribute. * * @private * @function Highcharts.SVGElement#updateTransform */ SVGElement.prototype.updateTransform = function () { var wrapper = this, scaleX = wrapper.scaleX, scaleY = wrapper.scaleY, inverted = wrapper.inverted, rotation = wrapper.rotation, matrix = wrapper.matrix, element = wrapper.element; var translateX = wrapper.translateX || 0, translateY = wrapper.translateY || 0; // Flipping affects translate as adjustment for flipping around the // group's axis if (inverted) { translateX += wrapper.width; translateY += wrapper.height; } // Apply translate. Nearly all transformed elements have translation, // so instead of checking for translate = 0, do it always (#1767, // #1846). var transform = ['translate(' + translateX + ',' + translateY + ')']; // apply matrix if (defined(matrix)) { transform.push('matrix(' + matrix.join(',') + ')'); } // apply rotation if (inverted) { transform.push('rotate(90) scale(-1,1)'); } else if (rotation) { // text rotation transform.push('rotate(' + rotation + ' ' + pick(this.rotationOriginX, element.getAttribute('x'), 0) + ' ' + pick(this.rotationOriginY, element.getAttribute('y') || 0) + ')'); } // apply scale if (defined(scaleX) || defined(scaleY)) { transform.push('scale(' + pick(scaleX, 1) + ' ' + pick(scaleY, 1) + ')'); } if (transform.length) { element.setAttribute('transform', transform.join(' ')); } }; /** * @private * @function Highcharts.SVGElement#visibilitySetter * * @param {string} value * * @param {string} key * * @param {Highcharts.SVGDOMElement} element * * @return {void} */ SVGElement.prototype.visibilitySetter = function (value, key, element) { // IE9-11 doesn't handle visibilty:inherit well, so we remove the // attribute instead (#2881, #3909) if (value === 'inherit') { element.removeAttribute(key); } else if (this[key] !== value) { // #6747 element.setAttribute(key, value); } this[key] = value; }; /** * @private * @function Highcharts.SVGElement#xGetter * * @param {string} key * * @return {number|string|null} */ SVGElement.prototype.xGetter = function (key) { if (this.element.nodeName === 'circle') { if (key === 'x') { key = 'cx'; } else if (key === 'y') { key = 'cy'; } } return this._defaultGetter(key); }; /** * @private * @function Highcharts.SVGElement#zIndexSetter * @param {number} [value] * @param {string} [key] * @return {boolean} */ SVGElement.prototype.zIndexSetter = function (value, key) { var renderer = this.renderer, parentGroup = this.parentGroup, parentWrapper = parentGroup || renderer, parentNode = parentWrapper.element || renderer.box, element = this.element, svgParent = parentNode === renderer.box; var childNodes, otherElement, otherZIndex, inserted = false, undefinedOtherZIndex, run = this.added, i; if (defined(value)) { // So we can read it for other elements in the group element.setAttribute('data-z-index', value); value = +value; if (this[key] === value) { // Only update when needed (#3865) run = false; } } else if (defined(this[key])) { element.removeAttribute('data-z-index'); } this[key] = value; // Insert according to this and other elements' zIndex. Before .add() is // called, nothing is done. Then on add, or by later calls to // zIndexSetter, the node is placed on the right place in the DOM. if (run) { value = this.zIndex; if (value && parentGroup) { parentGroup.handleZ = true; } childNodes = parentNode.childNodes; for (i = childNodes.length - 1; i >= 0 && !inserted; i--) { otherElement = childNodes[i]; otherZIndex = otherElement.getAttribute('data-z-index'); undefinedOtherZIndex = !defined(otherZIndex); if (otherElement !== element) { if ( // Negative zIndex versus no zIndex: // On all levels except the highest. If the parent is // <svg>, then we don't want to put items before <desc> // or <defs> value < 0 && undefinedOtherZIndex && !svgParent && !i) { parentNode.insertBefore(element, childNodes[i]); inserted = true; } else if ( // Insert after the first element with a lower zIndex pInt(otherZIndex) <= value || // If negative zIndex, add this before first undefined // zIndex element (undefinedOtherZIndex && (!defined(value) || value >= 0))) { parentNode.insertBefore(element, childNodes[i + 1] || null // null for oldIE export ); inserted = true; } } } if (!inserted) { parentNode.insertBefore(element, childNodes[svgParent ? 3 : 0] || null // null for oldIE ); inserted = true; } } return inserted; }; return SVGElement; }()); // Some shared setters and getters SVGElement.prototype['stroke-widthSetter'] = SVGElement.prototype.strokeSetter; SVGElement.prototype.yGetter = SVGElement.prototype.xGetter; SVGElement.prototype.matrixSetter = SVGElement.prototype.rotationOriginXSetter = SVGElement.prototype.rotationOriginYSetter = SVGElement.prototype.rotationSetter = SVGElement.prototype.scaleXSetter = SVGElement.prototype.scaleYSetter = SVGElement.prototype.translateXSetter = SVGElement.prototype.translateYSetter = SVGElement.prototype.verticalAlignSetter = function (value, key) { this[key] = value; this.doTransform = true; }; /* * * * API Declarations * * */ /** * Reference to the global SVGElement class as a workaround for a name conflict * in the Highcharts namespace. * * @global * @typedef {global.SVGElement} GlobalSVGElement * * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement */ /** * The horizontal alignment of an element. * * @typedef {"center"|"left"|"right"} Highcharts.AlignValue */ /** * Options to align the element relative to the chart or another box. * * @interface Highcharts.AlignObject */ /** * Horizontal alignment. Can be one of `left`, `center` and `right`. * * @name Highcharts.AlignObject#align * @type {Highcharts.AlignValue|undefined} * * @default left */ /** * Vertical alignment. Can be one of `top`, `middle` and `bottom`. * * @name Highcharts.AlignObject#verticalAlign * @type {Highcharts.VerticalAlignValue|undefined} * * @default top */ /** * Horizontal pixel offset from alignment. * * @name Highcharts.AlignObject#x * @type {number|undefined} * * @default 0 */ /** * Vertical pixel offset from alignment. * * @name Highcharts.AlignObject#y * @type {number|undefined} * * @default 0 */ /** * Use the `transform` attribute with translateX and translateY custom * attributes to align this elements rather than `x` and `y` attributes. * * @name Highcharts.AlignObject#alignByTranslate * @type {boolean|undefined} * * @default false */ /** * Bounding box of an element. * * @interface Highcharts.BBoxObject * @extends Highcharts.PositionObject */ /** * Height of the bounding box. * * @name Highcharts.BBoxObject#height * @type {number} */ /** * Width of the bounding box. * * @name Highcharts.BBoxObject#width * @type {number} */ /** * Horizontal position of the bounding box. * * @name Highcharts.BBoxObject#x * @type {number} */ /** * Vertical position of the bounding box. * * @name Highcharts.BBoxObject#y * @type {number} */ /** * An object of key-value pairs for SVG attributes. Attributes in Highcharts * elements for the most parts correspond to SVG, but some are specific to * Highcharts, like `zIndex`, `rotation`, `rotationOriginX`, * `rotationOriginY`, `translateX`, `translateY`, `scaleX` and `scaleY`. SVG * attributes containing a hyphen are _not_ camel-cased, they should be * quoted to preserve the hyphen. * * @example * { * 'stroke': '#ff0000', // basic * 'stroke-width': 2, // hyphenated * 'rotation': 45 // custom * 'd': ['M', 10, 10, 'L', 30, 30, 'z'] // path definition, note format * } * * @interface Highcharts.SVGAttributes */ /** * @name Highcharts.SVGAttributes#[key:string] * @type {*} */ /** * @name Highcharts.SVGAttributes#d * @type {string|Highcharts.SVGPathArray|undefined} */ /** * @name Highcharts.SVGAttributes#fill * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} */ /** * @name Highcharts.SVGAttributes#inverted * @type {boolean|undefined} */ /** * @name Highcharts.SVGAttributes#matrix * @type {Array<number>|undefined} */ /** * @name Highcharts.SVGAttributes#rotation * @type {number|undefined} */ /** * @name Highcharts.SVGAttributes#rotationOriginX * @type {number|undefined} */ /** * @name Highcharts.SVGAttributes#rotationOriginY * @type {number|undefined} */ /** * @name Highcharts.SVGAttributes#scaleX * @type {number|undefined} */ /** * @name Highcharts.SVGAttributes#scaleY * @type {number|undefined} */ /** * @name Highcharts.SVGAttributes#stroke * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} */ /** * @name Highcharts.SVGAttributes#style * @type {string|Highcharts.CSSObject|undefined} */ /** * @name Highcharts.SVGAttributes#translateX * @type {number|undefined} */ /** * @name Highcharts.SVGAttributes#translateY * @type {number|undefined} */ /** * @name Highcharts.SVGAttributes#zIndex * @type {number|undefined} */ /** * An SVG DOM element. The type is a reference to the regular SVGElement in the * global scope. * * @typedef {globals.GlobalSVGElement} Highcharts.SVGDOMElement * * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement */ /** * The vertical alignment of an element. * * @typedef {"bottom"|"middle"|"top"} Highcharts.VerticalAlignValue */ ''; // detach doclets above return SVGElement; }); _registerModule(_modules, 'Core/Renderer/RendererRegistry.js', [_modules['Core/Globals.js']], function (H) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Namespace * * */ var RendererRegistry; (function (RendererRegistry) { /* * * * Static Properties * * */ var defaultRenderer; RendererRegistry.rendererTypes = {}; /* * * * Static Functions * * */ /** * Gets a registered renderer class. If no renderer type is provided or the * requested renderer was not founded, the default renderer is returned. * * @param {string} [rendererType] * Renderer type or the default renderer. * * @return {Highcharts.Class<Highcharts.SVGRenderer>} * Returns the requested renderer class or the default renderer class. */ function getRendererType(rendererType) { if (rendererType === void 0) { rendererType = defaultRenderer; } return (RendererRegistry.rendererTypes[rendererType] || RendererRegistry.rendererTypes[defaultRenderer]); } RendererRegistry.getRendererType = getRendererType; /** * Register a renderer class. * * @param {string} rendererType * Renderer type to register. * * @param {Highcharts.Class<Highcharts.SVGRenderer>} rendererClass * Returns the requested renderer class or the default renderer class. * * @param {boolean} setAsDefault * Sets the renderer class as the default renderer. */ function registerRendererType(rendererType, rendererClass, setAsDefault) { RendererRegistry.rendererTypes[rendererType] = rendererClass; if (!defaultRenderer || setAsDefault) { defaultRenderer = rendererType; H.Renderer = rendererClass; // compatibility } } RendererRegistry.registerRendererType = registerRendererType; })(RendererRegistry || (RendererRegistry = {})); /* * * * Export * * */ return RendererRegistry; }); _registerModule(_modules, 'Core/Renderer/SVG/SVGLabel.js', [_modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (SVGElement, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var defined = U.defined, extend = U.extend, isNumber = U.isNumber, merge = U.merge, pick = U.pick, removeEvent = U.removeEvent; /* * * * Class * * */ /** * SVG label to render text. * @private * @class * @name Highcharts.SVGLabel * @augments Highcharts.SVGElement */ var SVGLabel = /** @class */ (function (_super) { __extends(SVGLabel, _super); /* * * * Constructors * * */ function SVGLabel(renderer, str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { var _this = _super.call(this) || this; _this.paddingLeftSetter = _this.paddingSetter; _this.paddingRightSetter = _this.paddingSetter; _this.init(renderer, 'g'); _this.textStr = str; _this.x = x; _this.y = y; _this.anchorX = anchorX; _this.anchorY = anchorY; _this.baseline = baseline; _this.className = className; _this.addClass(className === 'button' ? 'highcharts-no-tooltip' : 'highcharts-label'); if (className) { _this.addClass('highcharts-' + className); } _this.text = renderer.text('', 0, 0, useHTML).attr({ zIndex: 1 }); // Validate the shape argument var hasBGImage; if (typeof shape === 'string') { hasBGImage = /^url\((.*?)\)$/.test(shape); if (hasBGImage || _this.renderer.symbols[shape]) { _this.symbolKey = shape; } } _this.bBox = SVGLabel.emptyBBox; _this.padding = 3; _this.baselineOffset = 0; _this.needsBox = renderer.styledMode || hasBGImage; _this.deferredAttr = {}; _this.alignFactor = 0; return _this; } /* * * * Functions * * */ SVGLabel.prototype.alignSetter = function (value) { var alignFactor = ({ left: 0, center: 0.5, right: 1 })[value]; if (alignFactor !== this.alignFactor) { this.alignFactor = alignFactor; // Bounding box exists, means we're dynamically changing if (this.bBox && isNumber(this.xSetting)) { this.attr({ x: this.xSetting }); // #5134 } } }; SVGLabel.prototype.anchorXSetter = function (value, key) { this.anchorX = value; this.boxAttr(key, Math.round(value) - this.getCrispAdjust() - this.xSetting); }; SVGLabel.prototype.anchorYSetter = function (value, key) { this.anchorY = value; this.boxAttr(key, value - this.ySetting); }; /* * Set a box attribute, or defer it if the box is not yet created */ SVGLabel.prototype.boxAttr = function (key, value) { if (this.box) { this.box.attr(key, value); } else { this.deferredAttr[key] = value; } }; /* * Pick up some properties and apply them to the text instead of the * wrapper. */ SVGLabel.prototype.css = function (styles) { if (styles) { var textStyles_1 = {}; // Create a copy to avoid altering the original object // (#537) styles = merge(styles); SVGLabel.textProps.forEach(function (prop) { if (typeof styles[prop] !== 'undefined') { textStyles_1[prop] = styles[prop]; delete styles[prop]; } }); this.text.css(textStyles_1); var isWidth = 'width' in textStyles_1, isFontStyle = ('fontSize' in textStyles_1 || 'fontWeight' in textStyles_1); // Update existing text, box (#9400, #12163) if (isFontStyle) { this.updateTextPadding(); } else if (isWidth) { this.updateBoxSize(); } } return SVGElement.prototype.css.call(this, styles); }; /* * Destroy and release memory. */ SVGLabel.prototype.destroy = function () { // Added by button implementation removeEvent(this.element, 'mouseenter'); removeEvent(this.element, 'mouseleave'); if (this.text) { this.text.destroy(); } if (this.box) { this.box = this.box.destroy(); } // Call base implementation to destroy the rest SVGElement.prototype.destroy.call(this); return void 0; }; SVGLabel.prototype.fillSetter = function (value, key) { if (value) { this.needsBox = true; } // for animation getter (#6776) this.fill = value; this.boxAttr(key, value); }; /* * Return the bounding box of the box, not the group. */ SVGLabel.prototype.getBBox = function () { // If we have a text string and the DOM bBox was 0, it typically means // that the label was first rendered hidden, so we need to update the // bBox (#15246) if (this.textStr && this.bBox.width === 0 && this.bBox.height === 0) { this.updateBoxSize(); } var padding = this.padding; var paddingLeft = pick(this.paddingLeft, padding); return { width: this.width, height: this.height, x: this.bBox.x - paddingLeft, y: this.bBox.y - padding }; }; SVGLabel.prototype.getCrispAdjust = function () { return this.renderer.styledMode && this.box ? this.box.strokeWidth() % 2 / 2 : (this['stroke-width'] ? parseInt(this['stroke-width'], 10) : 0) % 2 / 2; }; SVGLabel.prototype.heightSetter = function (value) { this.heightSetting = value; }; // Event handling. In case of useHTML, we need to make sure that events // are captured on the span as well, and that mouseenter/mouseleave // between the SVG group and the HTML span are not treated as real // enter/leave events. #13310. SVGLabel.prototype.on = function (eventType, handler) { var label = this; var text = label.text; var span = text && text.element.tagName === 'SPAN' ? text : void 0; var selectiveHandler; if (span) { selectiveHandler = function (e) { if ((eventType === 'mouseenter' || eventType === 'mouseleave') && e.relatedTarget instanceof Element && ( // #14110 label.element.compareDocumentPosition(e.relatedTarget) & Node.DOCUMENT_POSITION_CONTAINED_BY || span.element.compareDocumentPosition(e.relatedTarget) & Node.DOCUMENT_POSITION_CONTAINED_BY)) { return; } handler.call(label.element, e); }; span.on(eventType, selectiveHandler); } SVGElement.prototype.on.call(label, eventType, selectiveHandler || handler); return label; }; /* * After the text element is added, get the desired size of the border * box and add it before the text in the DOM. */ SVGLabel.prototype.onAdd = function () { var str = this.textStr; this.text.add(this); this.attr({ // Alignment is available now (#3295, 0 not rendered if given // as a value) text: (defined(str) ? str : ''), x: this.x, y: this.y }); if (this.box && defined(this.anchorX)) { this.attr({ anchorX: this.anchorX, anchorY: this.anchorY }); } }; SVGLabel.prototype.paddingSetter = function (value, key) { if (!isNumber(value)) { this[key] = void 0; } else if (value !== this[key]) { this[key] = value; this.updateTextPadding(); } }; SVGLabel.prototype.rSetter = function (value, key) { this.boxAttr(key, value); }; SVGLabel.prototype.shadow = function (b) { if (b && !this.renderer.styledMode) { this.updateBoxSize(); if (this.box) { this.box.shadow(b); } } return this; }; SVGLabel.prototype.strokeSetter = function (value, key) { // for animation getter (#6776) this.stroke = value; this.boxAttr(key, value); }; SVGLabel.prototype['stroke-widthSetter'] = function (value, key) { if (value) { this.needsBox = true; } this['stroke-width'] = value; this.boxAttr(key, value); }; SVGLabel.prototype['text-alignSetter'] = function (value) { this.textAlign = value; }; SVGLabel.prototype.textSetter = function (text) { if (typeof text !== 'undefined') { // Must use .attr to ensure transforms are done (#10009) this.text.attr({ text: text }); } this.updateTextPadding(); }; /* * This function runs after the label is added to the DOM (when the bounding * box is available), and after the text of the label is updated to detect * the new bounding box and reflect it in the border box. */ SVGLabel.prototype.updateBoxSize = function () { var style = this.text.element.style, attribs = {}, padding = this.padding, // #12165 error when width is null (auto) // #12163 when fontweight: bold, recalculate bBox withot cache // #3295 && 3514 box failure when string equals 0 bBox = this.bBox = (((!isNumber(this.widthSetting) || !isNumber(this.heightSetting) || this.textAlign) && defined(this.text.textStr)) ? this.text.getBBox() : SVGLabel.emptyBBox); var crispAdjust; this.width = this.getPaddedWidth(); this.height = (this.heightSetting || bBox.height || 0) + 2 * padding; var metrics = this.renderer.fontMetrics(style && style.fontSize, this.text); // Update the label-scoped y offset. Math.min because of inline // style (#9400) this.baselineOffset = padding + Math.min( // When applicable, use the font size of the first line (#15707) (this.text.firstLineMetrics || metrics).b, // When the height is 0, there is no bBox, so go with the font // metrics. Highmaps CSS demos. bBox.height || Infinity); // #15491: Vertical centering if (this.heightSetting) { this.baselineOffset += (this.heightSetting - metrics.h) / 2; } if (this.needsBox) { // Create the border box if it is not already present if (!this.box) { // Symbol definition exists (#5324) var box = this.box = this.symbolKey ? this.renderer.symbol(this.symbolKey) : this.renderer.rect(); box.addClass(// Don't use label className for buttons (this.className === 'button' ? '' : 'highcharts-label-box') + (this.className ? ' highcharts-' + this.className + '-box' : '')); box.add(this); } crispAdjust = this.getCrispAdjust(); attribs.x = crispAdjust; attribs.y = (this.baseline ? -this.baselineOffset : 0) + crispAdjust; // Apply the box attributes attribs.width = Math.round(this.width); attribs.height = Math.round(this.height); this.box.attr(extend(attribs, this.deferredAttr)); this.deferredAttr = {}; } }; /* * This function runs after setting text or padding, but only if padding * is changed. */ SVGLabel.prototype.updateTextPadding = function () { var text = this.text; this.updateBoxSize(); // Determine y based on the baseline var textY = this.baseline ? 0 : this.baselineOffset; var textX = pick(this.paddingLeft, this.padding); // compensate for alignment if (defined(this.widthSetting) && this.bBox && (this.textAlign === 'center' || this.textAlign === 'right')) { textX += { center: 0.5, right: 1 }[this.textAlign] * (this.widthSetting - this.bBox.width); } // update if anything changed if (textX !== text.x || textY !== text.y) { text.attr('x', textX); // #8159 - prevent misplaced data labels in treemap // (useHTML: true) if (text.hasBoxWidthChanged) { this.bBox = text.getBBox(true); } if (typeof textY !== 'undefined') { text.attr('y', textY); } } // record current values text.x = textX; text.y = textY; }; SVGLabel.prototype.widthSetter = function (value) { // width:auto => null this.widthSetting = isNumber(value) ? value : void 0; }; SVGLabel.prototype.getPaddedWidth = function () { var padding = this.padding; var paddingLeft = pick(this.paddingLeft, padding); var paddingRight = pick(this.paddingRight, padding); return (this.widthSetting || this.bBox.width || 0) + paddingLeft + paddingRight; }; SVGLabel.prototype.xSetter = function (value) { this.x = value; // for animation getter if (this.alignFactor) { value -= this.alignFactor * this.getPaddedWidth(); // Force animation even when setting to the same value (#7898) this['forceAnimate:x'] = true; } this.xSetting = Math.round(value); this.attr('translateX', this.xSetting); }; SVGLabel.prototype.ySetter = function (value) { this.ySetting = this.y = Math.round(value); this.attr('translateY', this.ySetting); }; /* * * * Static Properties * * */ SVGLabel.emptyBBox = { width: 0, height: 0, x: 0, y: 0 }; /** * For labels, these CSS properties are applied to the `text` node directly. * * @private * @name Highcharts.SVGLabel#textProps * @type {Array<string>} */ SVGLabel.textProps = [ 'color', 'direction', 'fontFamily', 'fontSize', 'fontStyle', 'fontWeight', 'lineHeight', 'textAlign', 'textDecoration', 'textOutline', 'textOverflow', 'width' ]; return SVGLabel; }(SVGElement)); return SVGLabel; }); _registerModule(_modules, 'Core/Renderer/SVG/Symbols.js', [_modules['Core/Utilities.js']], function (U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var defined = U.defined, isNumber = U.isNumber, pick = U.pick; /* * * * Functions * * */ /* eslint-disable require-jsdoc, valid-jsdoc */ function arc(x, y, w, h, options) { var arc = []; if (options) { var start = options.start || 0, rx = pick(options.r, w), ry = pick(options.r, h || w), proximity = 0.001, fullCircle = (Math.abs((options.end || 0) - start - 2 * Math.PI) < proximity), // Substract a small number to prevent cos and sin of start // and end from becoming equal on 360 arcs (related: #1561) end = (options.end || 0) - proximity, innerRadius = options.innerR, open_1 = pick(options.open, fullCircle), cosStart = Math.cos(start), sinStart = Math.sin(start), cosEnd = Math.cos(end), sinEnd = Math.sin(end), // Proximity takes care of rounding errors around PI (#6971) longArc = pick(options.longArc, end - start - Math.PI < proximity ? 0 : 1); arc.push([ 'M', x + rx * cosStart, y + ry * sinStart ], [ 'A', rx, ry, 0, longArc, pick(options.clockwise, 1), x + rx * cosEnd, y + ry * sinEnd ]); if (defined(innerRadius)) { arc.push(open_1 ? [ 'M', x + innerRadius * cosEnd, y + innerRadius * sinEnd ] : [ 'L', x + innerRadius * cosEnd, y + innerRadius * sinEnd ], [ 'A', innerRadius, innerRadius, 0, longArc, // Clockwise - opposite to the outer arc clockwise defined(options.clockwise) ? 1 - options.clockwise : 0, x + innerRadius * cosStart, y + innerRadius * sinStart ]); } if (!open_1) { arc.push(['Z']); } } return arc; } /** * Callout shape used for default tooltips, also used for rounded * rectangles in VML */ function callout(x, y, w, h, options) { var arrowLength = 6, halfDistance = 6, r = Math.min((options && options.r) || 0, w, h), safeDistance = r + halfDistance, anchorX = options && options.anchorX, anchorY = options && options.anchorY || 0; var path = roundedRect(x, y, w, h, { r: r }); if (!isNumber(anchorX)) { return path; } // Anchor on right side if (x + anchorX >= w) { // Chevron if (anchorY > y + safeDistance && anchorY < y + h - safeDistance) { path.splice(3, 1, ['L', x + w, anchorY - halfDistance], ['L', x + w + arrowLength, anchorY], ['L', x + w, anchorY + halfDistance], ['L', x + w, y + h - r]); // Simple connector } else { path.splice(3, 1, ['L', x + w, h / 2], ['L', anchorX, anchorY], ['L', x + w, h / 2], ['L', x + w, y + h - r]); } // Anchor on left side } else if (x + anchorX <= 0) { // Chevron if (anchorY > y + safeDistance && anchorY < y + h - safeDistance) { path.splice(7, 1, ['L', x, anchorY + halfDistance], ['L', x - arrowLength, anchorY], ['L', x, anchorY - halfDistance], ['L', x, y + r]); // Simple connector } else { path.splice(7, 1, ['L', x, h / 2], ['L', anchorX, anchorY], ['L', x, h / 2], ['L', x, y + r]); } } else if ( // replace bottom anchorY && anchorY > h && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { path.splice(5, 1, ['L', anchorX + halfDistance, y + h], ['L', anchorX, y + h + arrowLength], ['L', anchorX - halfDistance, y + h], ['L', x + r, y + h]); } else if ( // replace top anchorY && anchorY < 0 && anchorX > x + safeDistance && anchorX < x + w - safeDistance) { path.splice(1, 1, ['L', anchorX - halfDistance, y], ['L', anchorX, y - arrowLength], ['L', anchorX + halfDistance, y], ['L', w - r, y]); } return path; } function circle(x, y, w, h) { // Return a full arc return arc(x + w / 2, y + h / 2, w / 2, h / 2, { start: Math.PI * 0.5, end: Math.PI * 2.5, open: false }); } function diamond(x, y, w, h) { return [ ['M', x + w / 2, y], ['L', x + w, y + h / 2], ['L', x + w / 2, y + h], ['L', x, y + h / 2], ['Z'] ]; } // #15291 function rect(x, y, w, h, options) { if (options && options.r) { return roundedRect(x, y, w, h, options); } return [ ['M', x, y], ['L', x + w, y], ['L', x + w, y + h], ['L', x, y + h], ['Z'] ]; } function roundedRect(x, y, w, h, options) { var r = (options && options.r) || 0; return [ ['M', x + r, y], ['L', x + w - r, y], ['C', x + w, y, x + w, y, x + w, y + r], ['L', x + w, y + h - r], ['C', x + w, y + h, x + w, y + h, x + w - r, y + h], ['L', x + r, y + h], ['C', x, y + h, x, y + h, x, y + h - r], ['L', x, y + r], ['C', x, y, x, y, x + r, y] // top-left corner ]; } function triangle(x, y, w, h) { return [ ['M', x + w / 2, y], ['L', x + w, y + h], ['L', x, y + h], ['Z'] ]; } function triangleDown(x, y, w, h) { return [ ['M', x, y], ['L', x + w, y], ['L', x + w / 2, y + h], ['Z'] ]; } var Symbols = { arc: arc, callout: callout, circle: circle, diamond: diamond, rect: rect, roundedRect: roundedRect, square: rect, triangle: triangle, 'triangle-down': triangleDown }; /* * * * Default Export * * */ return Symbols; }); _registerModule(_modules, 'Core/Renderer/SVG/TextBuilder.js', [_modules['Core/Renderer/HTML/AST.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (AST, H, U) { /* * * * (c) 2010-2020 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var doc = H.doc, SVG_NS = H.SVG_NS; var attr = U.attr, isString = U.isString, objectEach = U.objectEach, pick = U.pick; /* * * * Class * * */ /** * SVG Text Builder * @private * @class * @name Highcharts.TextBuilder */ var TextBuilder = /** @class */ (function () { function TextBuilder(svgElement) { var textStyles = svgElement.styles; this.renderer = svgElement.renderer; this.svgElement = svgElement; this.width = svgElement.textWidth; this.textLineHeight = textStyles && textStyles.lineHeight; this.textOutline = textStyles && textStyles.textOutline; this.ellipsis = Boolean(textStyles && textStyles.textOverflow === 'ellipsis'); this.noWrap = Boolean(textStyles && textStyles.whiteSpace === 'nowrap'); this.fontSize = textStyles && textStyles.fontSize; } /** * Build an SVG representation of the pseudo HTML given in the object's * svgElement. * * @private * * @return {void}. */ TextBuilder.prototype.buildSVG = function () { var wrapper = this.svgElement; var textNode = wrapper.element, renderer = wrapper.renderer, textStr = pick(wrapper.textStr, '').toString(), hasMarkup = textStr.indexOf('<') !== -1, childNodes = textNode.childNodes, textCache, i = childNodes.length, tempParent = this.width && !wrapper.added && renderer.box; var regexMatchBreaks = /<br.*?>/g; // The buildText code is quite heavy, so if we're not changing something // that affects the text, skip it (#6113). textCache = [ textStr, this.ellipsis, this.noWrap, this.textLineHeight, this.textOutline, this.fontSize, this.width ].join(','); if (textCache === wrapper.textCache) { return; } wrapper.textCache = textCache; delete wrapper.actualWidth; // Remove old text while (i--) { textNode.removeChild(childNodes[i]); } // Simple strings, add text directly and return if (!hasMarkup && !this.ellipsis && !this.width && (textStr.indexOf(' ') === -1 || (this.noWrap && !regexMatchBreaks.test(textStr)))) { textNode.appendChild(doc.createTextNode(this.unescapeEntities(textStr))); // Complex strings, add more logic } else if (textStr !== '') { if (tempParent) { // attach it to the DOM to read offset width tempParent.appendChild(textNode); } // Step 1. Parse the markup safely and directly into a tree // structure. var ast = new AST(textStr); // Step 2. Do as many as we can of the modifications to the tree // structure before it is added to the DOM this.modifyTree(ast.nodes); ast.addToDOM(wrapper.element); // Step 3. Some modifications can't be done until the structure is // in the DOM, because we need to read computed metrics. this.modifyDOM(); // Add title if an ellipsis was added if (this.ellipsis && (textNode.textContent || '').indexOf('\u2026') !== -1) { wrapper.attr('title', this.unescapeEntities(wrapper.textStr || '', ['<', '>']) // #7179 ); } if (tempParent) { tempParent.removeChild(textNode); } } // Apply the text outline if (isString(this.textOutline) && wrapper.applyTextOutline) { wrapper.applyTextOutline(this.textOutline); } }; /** * Modify the DOM of the generated SVG structure. This function only does * operations that cannot be done until the elements are attached to the * DOM, like doing layout based on rendered metrics of the added elements. * * @private * * @return {void} */ TextBuilder.prototype.modifyDOM = function () { var _this = this; var wrapper = this.svgElement; var x = attr(wrapper.element, 'x'); wrapper.firstLineMetrics = void 0; // Modify hard line breaks by applying the rendered line height [].forEach.call(wrapper.element.querySelectorAll('tspan.highcharts-br'), function (br, i) { if (br.nextSibling && br.previousSibling) { // #5261 if (i === 0 && br.previousSibling.nodeType === 1) { wrapper.firstLineMetrics = wrapper.renderer .fontMetrics(void 0, br.previousSibling); } attr(br, { // Since the break is inserted in front of the next // line, we need to use the next sibling for the line // height dy: _this.getLineHeight(br.nextSibling), x: x }); } }); // Constrain the line width, either by ellipsis or wrapping var width = this.width || 0; if (!width) { return; } // Insert soft line breaks into each text node var modifyTextNode = function (textNode, parentElement) { var text = textNode.textContent || ''; var words = text .replace(/([^\^])-/g, '$1- ') // Split on hyphens // .trim() .split(' '); // #1273 var hasWhiteSpace = !_this.noWrap && (words.length > 1 || wrapper.element.childNodes.length > 1); var dy = _this.getLineHeight(parentElement); var lineNo = 0; var startAt = wrapper.actualWidth; if (_this.ellipsis) { if (text) { _this.truncate(textNode, text, void 0, 0, // Target width Math.max(0, // Substract the font face to make room for the // ellipsis itself width - parseInt(_this.fontSize || 12, 10)), // Build the text to test for function (text, currentIndex) { return text.substring(0, currentIndex) + '\u2026'; }); } } else if (hasWhiteSpace) { var lines = []; // Remove preceding siblings in order to make the text length // calculation correct in the truncate function var precedingSiblings = []; while (parentElement.firstChild && parentElement.firstChild !== textNode) { precedingSiblings.push(parentElement.firstChild); parentElement.removeChild(parentElement.firstChild); } while (words.length) { // Apply the previous line if (words.length && !_this.noWrap && lineNo > 0) { lines.push(textNode.textContent || ''); textNode.textContent = words.join(' ') .replace(/- /g, '-'); } // For each line, truncate the remaining // words into the line length. _this.truncate(textNode, void 0, words, lineNo === 0 ? (startAt || 0) : 0, width, // Build the text to test for function (t, currentIndex) { return words .slice(0, currentIndex) .join(' ') .replace(/- /g, '-'); }); startAt = wrapper.actualWidth; lineNo++; } // Reinsert the preceding child nodes precedingSiblings.forEach(function (childNode) { parentElement.insertBefore(childNode, textNode); }); // Insert the previous lines before the original text node lines.forEach(function (line) { // Insert the line parentElement.insertBefore(doc.createTextNode(line), textNode); // Insert a break var br = doc.createElementNS(SVG_NS, 'tspan'); br.textContent = '\u200B'; // zero-width space attr(br, { dy: dy, x: x }); parentElement.insertBefore(br, textNode); }); } }; // Recurse down the DOM tree and handle line breaks for each text node var modifyChildren = (function (node) { var childNodes = [].slice.call(node.childNodes); childNodes.forEach(function (childNode) { if (childNode.nodeType === Node.TEXT_NODE) { modifyTextNode(childNode, node); } else { // Reset word-wrap width readings after hard breaks if (childNode.className.baseVal .indexOf('highcharts-br') !== -1) { wrapper.actualWidth = 0; } // Recurse down to child node modifyChildren(childNode); } }); }); modifyChildren(wrapper.element); }; /** * Get the rendered line height of a <text>, <tspan> or pure text node. * * @param {DOMElementType|Text} node The node to check for * * @return {number} The rendered line height */ TextBuilder.prototype.getLineHeight = function (node) { var fontSizeStyle; // If the node is a text node, use its parent var element = node.nodeType === Node.TEXT_NODE ? node.parentElement : node; if (!this.renderer.styledMode) { fontSizeStyle = element && /(px|em)$/.test(element.style.fontSize) ? element.style.fontSize : (this.fontSize || this.renderer.style.fontSize || 12); } return this.textLineHeight ? parseInt(this.textLineHeight.toString(), 10) : this.renderer.fontMetrics(fontSizeStyle, element || this.svgElement.element).h; }; /** * Transform a pseudo HTML AST node tree into an SVG structure. We do as * much heavy lifting as we can here, before doing the final processing in * the modifyDOM function. The original data is mutated. * * @private * * @param {ASTNode[]} nodes The AST nodes * * @return {void} */ TextBuilder.prototype.modifyTree = function (nodes) { var _this = this; var modifyChild = function (node, i) { var tagName = node.tagName; var styledMode = _this.renderer.styledMode; var attributes = node.attributes || {}; // Apply styling to text tags if (tagName === 'b' || tagName === 'strong') { if (styledMode) { attributes['class'] = 'highcharts-strong'; // eslint-disable-line dot-notation } else { attributes.style = 'font-weight:bold;' + (attributes.style || ''); } } else if (tagName === 'i' || tagName === 'em') { if (styledMode) { attributes['class'] = 'highcharts-emphasized'; // eslint-disable-line dot-notation } else { attributes.style = 'font-style:italic;' + (attributes.style || ''); } } // Modify attributes if (isString(attributes.style)) { attributes.style = attributes.style.replace(/(;| |^)color([ :])/, '$1fill$2'); } if (tagName === 'br') { attributes['class'] = 'highcharts-br'; // eslint-disable-line dot-notation node.textContent = '\u200B'; // zero-width space // Trim whitespace off the beginning of new lines var nextNode = nodes[i + 1]; if (nextNode && nextNode.textContent) { nextNode.textContent = nextNode.textContent.replace(/^ +/gm, ''); } } if (tagName !== '#text' && tagName !== 'a') { node.tagName = 'tspan'; } node.attributes = attributes; // Recurse if (node.children) { node.children .filter(function (c) { return c.tagName !== '#text'; }) .forEach(modifyChild); } }; nodes.forEach(modifyChild); // Remove empty spans from the beginning because SVG's getBBox doesn't // count empty lines. The use case is tooltip where the header is empty. while (nodes[0]) { if (nodes[0].tagName === 'tspan' && !nodes[0].children) { nodes.splice(0, 1); } else { break; } } }; /* * Truncate the text node contents to a given length. Used when the css * width is set. If the `textOverflow` is `ellipsis`, the text is truncated * character by character to the given length. If not, the text is * word-wrapped line by line. */ TextBuilder.prototype.truncate = function (textNode, text, words, startAt, width, getString) { var svgElement = this.svgElement; var renderer = svgElement.renderer, rotation = svgElement.rotation; // Cache the lengths to avoid checking the same twice var lengths = []; // Word wrap can not be truncated to shorter than one word, ellipsis // text can be completely blank. var minIndex = words ? 1 : 0; var maxIndex = (text || words || '').length; var currentIndex = maxIndex; var str; var actualWidth; var getSubStringLength = function (charEnd, concatenatedEnd) { // charEnd is used when finding the character-by-character // break for ellipsis, concatenatedEnd is used for word-by-word // break for word wrapping. var end = concatenatedEnd || charEnd; var parentNode = textNode.parentNode; if (parentNode && typeof lengths[end] === 'undefined') { // Modern browsers if (parentNode.getSubStringLength) { // Fails with DOM exception on unit-tests/legend/members // of unknown reason. Desired width is 0, text content // is "5" and end is 1. try { lengths[end] = startAt + parentNode.getSubStringLength(0, words ? end + 1 : end); } catch (e) { ''; } // Legacy } else if (renderer.getSpanWidth) { // #9058 jsdom textNode.textContent = getString(text || words, charEnd); lengths[end] = startAt + renderer.getSpanWidth(svgElement, textNode); } } return lengths[end]; }; svgElement.rotation = 0; // discard rotation when computing box actualWidth = getSubStringLength(textNode.textContent.length); if (startAt + actualWidth > width) { // Do a binary search for the index where to truncate the text while (minIndex <= maxIndex) { currentIndex = Math.ceil((minIndex + maxIndex) / 2); // When checking words for word-wrap, we need to build the // string and measure the subStringLength at the concatenated // word length. if (words) { str = getString(words, currentIndex); } actualWidth = getSubStringLength(currentIndex, str && str.length - 1); if (minIndex === maxIndex) { // Complete minIndex = maxIndex + 1; } else if (actualWidth > width) { // Too large. Set max index to current. maxIndex = currentIndex - 1; } else { // Within width. Set min index to current. minIndex = currentIndex; } } // If max index was 0 it means the shortest possible text was also // too large. For ellipsis that means only the ellipsis, while for // word wrap it means the whole first word. if (maxIndex === 0) { // Remove ellipsis textNode.textContent = ''; // If the new text length is one less than the original, we don't // need the ellipsis } else if (!(text && maxIndex === text.length - 1)) { textNode.textContent = str || getString(text || words, currentIndex); } } // When doing line wrapping, prepare for the next line by removing the // items from this line. if (words) { words.splice(0, currentIndex); } svgElement.actualWidth = actualWidth; svgElement.rotation = rotation; // Apply rotation again. }; /* * Un-escape HTML entities based on the public `renderer.escapes` list * * @private * * @param {string} inputStr The string to unescape * @param {Array<string>} [except] Exceptions * * @return {string} The processed string */ TextBuilder.prototype.unescapeEntities = function (inputStr, except) { objectEach(this.renderer.escapes, function (value, key) { if (!except || except.indexOf(value) === -1) { inputStr = inputStr.toString().replace(new RegExp(value, 'g'), key); } }); return inputStr; }; return TextBuilder; }()); return TextBuilder; }); _registerModule(_modules, 'Core/Renderer/SVG/SVGRenderer.js', [_modules['Core/Renderer/HTML/AST.js'], _modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Core/Color/Palette.js'], _modules['Core/Renderer/RendererRegistry.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Renderer/SVG/SVGLabel.js'], _modules['Core/Renderer/SVG/Symbols.js'], _modules['Core/Renderer/SVG/TextBuilder.js'], _modules['Core/Utilities.js']], function (AST, Color, H, Palette, RendererRegistry, SVGElement, SVGLabel, Symbols, TextBuilder, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var charts = H.charts, deg2rad = H.deg2rad, doc = H.doc, isFirefox = H.isFirefox, isMS = H.isMS, isWebKit = H.isWebKit, noop = H.noop, SVG_NS = H.SVG_NS, symbolSizes = H.symbolSizes, win = H.win; var addEvent = U.addEvent, attr = U.attr, createElement = U.createElement, css = U.css, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, extend = U.extend, isArray = U.isArray, isNumber = U.isNumber, isObject = U.isObject, isString = U.isString, merge = U.merge, pick = U.pick, pInt = U.pInt, uniqueKey = U.uniqueKey; /* * * * Variables * * */ var hasInternalReferenceBug; /* * * * Class * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * Allows direct access to the Highcharts rendering layer in order to draw * primitive shapes like circles, rectangles, paths or text directly on a chart, * or independent from any chart. The SVGRenderer represents a wrapper object * for SVG in modern browsers. Through the VMLRenderer, part of the `oldie.js` * module, it also brings vector graphics to IE <= 8. * * An existing chart's renderer can be accessed through {@link Chart.renderer}. * The renderer can also be used completely decoupled from a chart. * * @sample highcharts/members/renderer-on-chart * Annotating a chart programmatically. * @sample highcharts/members/renderer-basic * Independent SVG drawing. * * @example * // Use directly without a chart object. * let renderer = new Highcharts.Renderer(parentNode, 600, 400); * * @class * @name Highcharts.SVGRenderer * * @param {Highcharts.HTMLDOMElement} container * Where to put the SVG in the web page. * * @param {number} width * The width of the SVG. * * @param {number} height * The height of the SVG. * * @param {Highcharts.CSSObject} [style] * The box style, if not in styleMode * * @param {boolean} [forExport=false] * Whether the rendered content is intended for export. * * @param {boolean} [allowHTML=true] * Whether the renderer is allowed to include HTML text, which will be * projected on top of the SVG. * * @param {boolean} [styledMode=false] * Whether the renderer belongs to a chart that is in styled mode. * If it does, it will avoid setting presentational attributes in * some cases, but not when set explicitly through `.attr` and `.css` * etc. */ var SVGRenderer = /** @class */ (function () { /* * * * Constructors * * */ function SVGRenderer(container, width, height, style, forExport, allowHTML, styledMode) { /* * * * Properties * * */ this.alignedObjects = void 0; /** * The root `svg` node of the renderer. * * @name Highcharts.SVGRenderer#box * @type {Highcharts.SVGDOMElement} */ this.box = void 0; /** * The wrapper for the root `svg` node of the renderer. * * @name Highcharts.SVGRenderer#boxWrapper * @type {Highcharts.SVGElement} */ this.boxWrapper = void 0; this.cache = void 0; this.cacheKeys = void 0; this.chartIndex = void 0; /** * A pointer to the `defs` node of the root SVG. * * @name Highcharts.SVGRenderer#defs * @type {Highcharts.SVGElement} */ this.defs = void 0; this.globalAnimation = void 0; this.gradients = void 0; this.height = void 0; this.imgCount = void 0; this.isSVG = void 0; this.style = void 0; /** * Page url used for internal references. * * @private * @name Highcharts.SVGRenderer#url * @type {string} */ this.url = void 0; this.width = void 0; this.init(container, width, height, style, forExport, allowHTML, styledMode); } /* * * * Functions * * */ /** * Initialize the SVGRenderer. Overridable initializer function that takes * the same parameters as the constructor. * * @function Highcharts.SVGRenderer#init * * @param {Highcharts.HTMLDOMElement} container * Where to put the SVG in the web page. * * @param {number} width * The width of the SVG. * * @param {number} height * The height of the SVG. * * @param {Highcharts.CSSObject} [style] * The box style, if not in styleMode * * @param {boolean} [forExport=false] * Whether the rendered content is intended for export. * * @param {boolean} [allowHTML=true] * Whether the renderer is allowed to include HTML text, which will be * projected on top of the SVG. * * @param {boolean} [styledMode=false] * Whether the renderer belongs to a chart that is in styled mode. If it * does, it will avoid setting presentational attributes in some cases, but * not when set explicitly through `.attr` and `.css` etc. */ SVGRenderer.prototype.init = function (container, width, height, style, forExport, allowHTML, styledMode) { var renderer = this, boxWrapper = renderer .createElement('svg') .attr({ version: '1.1', 'class': 'highcharts-root' }), element = boxWrapper.element; if (!styledMode) { boxWrapper.css(this.getStyle(style)); } container.appendChild(element); // Always use ltr on the container, otherwise text-anchor will be // flipped and text appear outside labels, buttons, tooltip etc (#3482) attr(container, 'dir', 'ltr'); // For browsers other than IE, add the namespace attribute (#1978) if (container.innerHTML.indexOf('xmlns') === -1) { attr(element, 'xmlns', this.SVG_NS); } // object properties renderer.isSVG = true; this.box = element; this.boxWrapper = boxWrapper; renderer.alignedObjects = []; this.url = this.getReferenceURL(); // Add description var desc = this.createElement('desc').add(); desc.element.appendChild(doc.createTextNode('Created with Highcharts 9.1.1')); renderer.defs = this.createElement('defs').add(); renderer.allowHTML = allowHTML; renderer.forExport = forExport; renderer.styledMode = styledMode; renderer.gradients = {}; // Object where gradient SvgElements are stored renderer.cache = {}; // Cache for numerical bounding boxes renderer.cacheKeys = []; renderer.imgCount = 0; renderer.setSize(width, height, false); // Issue 110 workaround: // In Firefox, if a div is positioned by percentage, its pixel position // may land between pixels. The container itself doesn't display this, // but an SVG element inside this container will be drawn at subpixel // precision. In order to draw sharp lines, this must be compensated // for. This doesn't seem to work inside iframes though (like in // jsFiddle). var subPixelFix, rect; if (isFirefox && container.getBoundingClientRect) { subPixelFix = function () { css(container, { left: 0, top: 0 }); rect = container.getBoundingClientRect(); css(container, { left: (Math.ceil(rect.left) - rect.left) + 'px', top: (Math.ceil(rect.top) - rect.top) + 'px' }); }; // run the fix now subPixelFix(); // run it on resize renderer.unSubPixelFix = addEvent(win, 'resize', subPixelFix); } }; /** * General method for adding a definition to the SVG `defs` tag. Can be used * for gradients, fills, filters etc. Styled mode only. A hook for adding * general definitions to the SVG's defs tag. Definitions can be referenced * from the CSS by its `id`. Read more in * [gradients, shadows and patterns](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns). * Styled mode only. * * @function Highcharts.SVGRenderer#definition * * @param {Highcharts.ASTNode} def * A serialized form of an SVG definition, including children. * * @return {Highcharts.SVGElement} * The inserted node. */ SVGRenderer.prototype.definition = function (def) { var ast = new AST([def]); return ast.addToDOM(this.defs.element); }; /** * Get the prefix needed for internal URL references to work in certain * cases. Some older browser versions had a bug where internal url * references in SVG attributes, on the form `url(#some-id)`, would fail if * a base tag was present in the page. There were also issues with * `history.pushState` related to this prefix. * * Related issues: #24, #672, #1070, #5244. * * The affected browsers are: * - Chrome <= 53 (May 2018) * - Firefox <= 51 (January 2017) * - Safari/Mac <= 12.1 (2018 or 2019) * - Safari/iOS <= 13 * * @todo Remove this hack when time has passed. All the affected browsers * are evergreens, so it is increasingly unlikely that users are affected by * the bug. * * @return {string} * The prefix to use. An empty string for modern browsers. */ SVGRenderer.prototype.getReferenceURL = function () { if ((isFirefox || isWebKit) && doc.getElementsByTagName('base').length) { // Detect if a clip path is taking effect by performing a hit test // outside the clipped area. If the hit element is the rectangle // that was supposed to be clipped, the bug is present. This only // has to be performed once per page load, so we store the result // locally in the module. if (!defined(hasInternalReferenceBug)) { var id = uniqueKey(); var ast = new AST([{ tagName: 'svg', attributes: { width: 8, height: 8 }, children: [{ tagName: 'defs', children: [{ tagName: 'clipPath', attributes: { id: id }, children: [{ tagName: 'rect', attributes: { width: 4, height: 4 } }] }] }, { tagName: 'rect', attributes: { id: 'hitme', width: 8, height: 8, 'clip-path': "url(#" + id + ")", fill: 'rgba(0,0,0,0.001)' } }] }]); var svg = ast.addToDOM(doc.body); css(svg, { position: 'fixed', top: 0, left: 0, zIndex: 9e5 }); var hitElement = doc.elementFromPoint(6, 6); hasInternalReferenceBug = (hitElement && hitElement.id) === 'hitme'; doc.body.removeChild(svg); } if (hasInternalReferenceBug) { return win.location.href .split('#')[0] // remove the hash .replace(/<[^>]*>/g, '') // wing cut HTML // escape parantheses and quotes .replace(/([\('\)])/g, '\\$1') // replace spaces (needed for Safari only) .replace(/ /g, '%20'); } } return ''; }; /** * Get the global style setting for the renderer. * * @private * @function Highcharts.SVGRenderer#getStyle * * @param {Highcharts.CSSObject} style * Style settings. * * @return {Highcharts.CSSObject} * The style settings mixed with defaults. */ SVGRenderer.prototype.getStyle = function (style) { this.style = extend({ fontFamily: '"Lucida Grande", "Lucida Sans Unicode", ' + 'Arial, Helvetica, sans-serif', fontSize: '12px' }, style); return this.style; }; /** * Apply the global style on the renderer, mixed with the default styles. * * @function Highcharts.SVGRenderer#setStyle * * @param {Highcharts.CSSObject} style * CSS to apply. */ SVGRenderer.prototype.setStyle = function (style) { this.boxWrapper.css(this.getStyle(style)); }; /** * Detect whether the renderer is hidden. This happens when one of the * parent elements has `display: none`. Used internally to detect when we * needto render preliminarily in another div to get the text bounding boxes * right. * * @function Highcharts.SVGRenderer#isHidden * * @return {boolean} * True if it is hidden. */ SVGRenderer.prototype.isHidden = function () { return !this.boxWrapper.getBBox().width; }; /** * Destroys the renderer and its allocated members. * * @function Highcharts.SVGRenderer#destroy * * @return {null} */ SVGRenderer.prototype.destroy = function () { var renderer = this, rendererDefs = renderer.defs; renderer.box = null; renderer.boxWrapper = renderer.boxWrapper.destroy(); // Call destroy on all gradient elements destroyObjectProperties(renderer.gradients || {}); renderer.gradients = null; // Defs are null in VMLRenderer // Otherwise, destroy them here. if (rendererDefs) { renderer.defs = rendererDefs.destroy(); } // Remove sub pixel fix handler (#982) if (renderer.unSubPixelFix) { renderer.unSubPixelFix(); } renderer.alignedObjects = null; return null; }; /** * Create a wrapper for an SVG element. Serves as a factory for * {@link SVGElement}, but this function is itself mostly called from * primitive factories like {@link SVGRenderer#path}, {@link * SVGRenderer#rect} or {@link SVGRenderer#text}. * * @function Highcharts.SVGRenderer#createElement * * @param {string} nodeName * The node name, for example `rect`, `g` etc. * * @return {Highcharts.SVGElement} * The generated SVGElement. */ SVGRenderer.prototype.createElement = function (nodeName) { var wrapper = new this.Element(); wrapper.init(this, nodeName); return wrapper; }; /** * Get converted radial gradient attributes according to the radial * reference. Used internally from the {@link SVGElement#colorGradient} * function. * * @private * @function Highcharts.SVGRenderer#getRadialAttr */ SVGRenderer.prototype.getRadialAttr = function (radialReference, gradAttr) { return { cx: (radialReference[0] - radialReference[2] / 2) + (gradAttr.cx || 0) * radialReference[2], cy: (radialReference[1] - radialReference[2] / 2) + (gradAttr.cy || 0) * radialReference[2], r: (gradAttr.r || 0) * radialReference[2] }; }; /** * Parse a simple HTML string into SVG tspans. Called internally when text * is set on an SVGElement. The function supports a subset of HTML tags, CSS * text features like `width`, `text-overflow`, `white-space`, and also * attributes like `href` and `style`. * * @private * @function Highcharts.SVGRenderer#buildText * * @param {Highcharts.SVGElement} wrapper * The parent SVGElement. */ SVGRenderer.prototype.buildText = function (wrapper) { new TextBuilder(wrapper).buildSVG(); }; /** * Returns white for dark colors and black for bright colors. * * @function Highcharts.SVGRenderer#getContrast * * @param {Highcharts.ColorString} rgba * The color to get the contrast for. * * @return {Highcharts.ColorString} * The contrast color, either `#000000` or `#FFFFFF`. */ SVGRenderer.prototype.getContrast = function (rgba) { rgba = Color.parse(rgba).rgba; // The threshold may be discussed. Here's a proposal for adding // different weight to the color channels (#6216) rgba[0] *= 1; // red rgba[1] *= 1.2; // green rgba[2] *= 0.5; // blue return rgba[0] + rgba[1] + rgba[2] > 1.8 * 255 ? '#000000' : '#FFFFFF'; }; /** * Create a button with preset states. * * @function Highcharts.SVGRenderer#button * * @param {string} text * The text or HTML to draw. * * @param {number} x * The x position of the button's left side. * * @param {number} y * The y position of the button's top side. * * @param {Highcharts.EventCallbackFunction<Highcharts.SVGElement>} callback * The function to execute on button click or touch. * * @param {Highcharts.SVGAttributes} [theme] * SVG attributes for the normal state. * * @param {Highcharts.SVGAttributes} [hoverState] * SVG attributes for the hover state. * * @param {Highcharts.SVGAttributes} [pressedState] * SVG attributes for the pressed state. * * @param {Highcharts.SVGAttributes} [disabledState] * SVG attributes for the disabled state. * * @param {Highcharts.SymbolKeyValue} [shape=rect] * The shape type. * * @param {boolean} [useHTML=false] * Wether to use HTML to render the label. * * @return {Highcharts.SVGElement} * The button element. */ SVGRenderer.prototype.button = function (text, x, y, callback, theme, hoverState, pressedState, disabledState, shape, useHTML) { var label = this.label(text, x, y, shape, void 0, void 0, useHTML, void 0, 'button'), styledMode = this.styledMode; var curState = 0, // Make a copy of normalState (#13798) // (reference to options.rangeSelector.buttonTheme) normalState = theme ? merge(theme) : {}; var userNormalStyle = normalState && normalState.style || {}; // Remove stylable attributes normalState = AST.filterUserAttributes(normalState); // Default, non-stylable attributes label.attr(merge({ padding: 8, r: 2 }, normalState)); // Presentational var normalStyle, hoverStyle, pressedStyle, disabledStyle; if (!styledMode) { // Normal state - prepare the attributes normalState = merge({ fill: Palette.neutralColor3, stroke: Palette.neutralColor20, 'stroke-width': 1, style: { color: Palette.neutralColor80, cursor: 'pointer', fontWeight: 'normal' } }, { style: userNormalStyle }, normalState); normalStyle = normalState.style; delete normalState.style; // Hover state hoverState = merge(normalState, { fill: Palette.neutralColor10 }, AST.filterUserAttributes(hoverState || {})); hoverStyle = hoverState.style; delete hoverState.style; // Pressed state pressedState = merge(normalState, { fill: Palette.highlightColor10, style: { color: Palette.neutralColor100, fontWeight: 'bold' } }, AST.filterUserAttributes(pressedState || {})); pressedStyle = pressedState.style; delete pressedState.style; // Disabled state disabledState = merge(normalState, { style: { color: Palette.neutralColor20 } }, AST.filterUserAttributes(disabledState || {})); disabledStyle = disabledState.style; delete disabledState.style; } // Add the events. IE9 and IE10 need mouseover and mouseout to funciton // (#667). addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function () { if (curState !== 3) { label.setState(1); } }); addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function () { if (curState !== 3) { label.setState(curState); } }); label.setState = function (state) { // Hover state is temporary, don't record it if (state !== 1) { label.state = curState = state; } // Update visuals label .removeClass(/highcharts-button-(normal|hover|pressed|disabled)/) .addClass('highcharts-button-' + ['normal', 'hover', 'pressed', 'disabled'][state || 0]); if (!styledMode) { label .attr([ normalState, hoverState, pressedState, disabledState ][state || 0]) .css([ normalStyle, hoverStyle, pressedStyle, disabledStyle ][state || 0]); } }; // Presentational attributes if (!styledMode) { label .attr(normalState) .css(extend({ cursor: 'default' }, normalStyle)); } return label .on('touchstart', function (e) { return e.stopPropagation(); }) .on('click', function (e) { if (curState !== 3) { callback.call(label, e); } }); }; /** * Make a straight line crisper by not spilling out to neighbour pixels. * * @function Highcharts.SVGRenderer#crispLine * * @param {Highcharts.SVGPathArray} points * The original points on the format `[['M', 0, 0], ['L', 100, 0]]`. * * @param {number} width * The width of the line. * * @param {string} [roundingFunction=round] * The rounding function name on the `Math` object, can be one of * `round`, `floor` or `ceil`. * * @return {Highcharts.SVGPathArray} * The original points array, but modified to render crisply. */ SVGRenderer.prototype.crispLine = function (points, width, roundingFunction) { if (roundingFunction === void 0) { roundingFunction = 'round'; } var start = points[0]; var end = points[1]; // Normalize to a crisp line if (defined(start[1]) && start[1] === end[1]) { // Substract due to #1129. Now bottom and left axis gridlines behave // the same. start[1] = end[1] = Math[roundingFunction](start[1]) - (width % 2 / 2); } if (defined(start[2]) && start[2] === end[2]) { start[2] = end[2] = Math[roundingFunction](start[2]) + (width % 2 / 2); } return points; }; /** * Draw a path, wraps the SVG `path` element. * * @sample highcharts/members/renderer-path-on-chart/ * Draw a path in a chart * @sample highcharts/members/renderer-path/ * Draw a path independent from a chart * * @example * let path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z']) * .attr({ stroke: '#ff00ff' }) * .add(); * * @function Highcharts.SVGRenderer#path * * @param {Highcharts.SVGPathArray} [path] * An SVG path definition in array form. * * @return {Highcharts.SVGElement} * The generated wrapper element. * */ /** * Draw a path, wraps the SVG `path` element. * * @function Highcharts.SVGRenderer#path * * @param {Highcharts.SVGAttributes} [attribs] * The initial attributes. * * @return {Highcharts.SVGElement} * The generated wrapper element. */ SVGRenderer.prototype.path = function (path) { var attribs = (this.styledMode ? {} : { fill: 'none' }); if (isArray(path)) { attribs.d = path; } else if (isObject(path)) { // attributes extend(attribs, path); } return this.createElement('path').attr(attribs); }; /** * Draw a circle, wraps the SVG `circle` element. * * @sample highcharts/members/renderer-circle/ * Drawing a circle * * @function Highcharts.SVGRenderer#circle * * @param {number} [x] * The center x position. * * @param {number} [y] * The center y position. * * @param {number} [r] * The radius. * * @return {Highcharts.SVGElement} * The generated wrapper element. */ /** * Draw a circle, wraps the SVG `circle` element. * * @function Highcharts.SVGRenderer#circle * * @param {Highcharts.SVGAttributes} [attribs] * The initial attributes. * * @return {Highcharts.SVGElement} * The generated wrapper element. */ SVGRenderer.prototype.circle = function (x, y, r) { var attribs = (isObject(x) ? x : typeof x === 'undefined' ? {} : { x: x, y: y, r: r }), wrapper = this.createElement('circle'); // Setting x or y translates to cx and cy wrapper.xSetter = wrapper.ySetter = function (value, key, element) { element.setAttribute('c' + key, value); }; return wrapper.attr(attribs); }; /** * Draw and return an arc. * * @sample highcharts/members/renderer-arc/ * Drawing an arc * * @function Highcharts.SVGRenderer#arc * * @param {number} [x=0] * Center X position. * * @param {number} [y=0] * Center Y position. * * @param {number} [r=0] * The outer radius' of the arc. * * @param {number} [innerR=0] * Inner radius like used in donut charts. * * @param {number} [start=0] * The starting angle of the arc in radians, where 0 is to the right and * `-Math.PI/2` is up. * * @param {number} [end=0] * The ending angle of the arc in radians, where 0 is to the right and * `-Math.PI/2` is up. * * @return {Highcharts.SVGElement} * The generated wrapper element. */ /** * Draw and return an arc. Overloaded function that takes arguments object. * * @function Highcharts.SVGRenderer#arc * * @param {Highcharts.SVGAttributes} attribs * Initial SVG attributes. * * @return {Highcharts.SVGElement} * The generated wrapper element. */ SVGRenderer.prototype.arc = function (x, y, r, innerR, start, end) { var options; if (isObject(x)) { options = x; y = options.y; r = options.r; innerR = options.innerR; start = options.start; end = options.end; x = options.x; } else { options = { innerR: innerR, start: start, end: end }; } // Arcs are defined as symbols for the ability to set // attributes in attr and animate var arc = this.symbol('arc', x, y, r, r, options); arc.r = r; // #959 return arc; }; /** * Draw and return a rectangle. * * @function Highcharts.SVGRenderer#rect * * @param {number} [x] * Left position. * * @param {number} [y] * Top position. * * @param {number} [width] * Width of the rectangle. * * @param {number} [height] * Height of the rectangle. * * @param {number} [r] * Border corner radius. * * @param {number} [strokeWidth] * A stroke width can be supplied to allow crisp drawing. * * @return {Highcharts.SVGElement} * The generated wrapper element. */ /** * Draw and return a rectangle. * * @sample highcharts/members/renderer-rect-on-chart/ * Draw a rectangle in a chart * @sample highcharts/members/renderer-rect/ * Draw a rectangle independent from a chart * * @function Highcharts.SVGRenderer#rect * * @param {Highcharts.SVGAttributes} [attributes] * General SVG attributes for the rectangle. * * @return {Highcharts.SVGElement} * The generated wrapper element. */ SVGRenderer.prototype.rect = function (x, y, width, height, r, strokeWidth) { r = isObject(x) ? x.r : r; var wrapper = this.createElement('rect'); var attribs = (isObject(x) ? x : typeof x === 'undefined' ? {} : { x: x, y: y, width: Math.max(width, 0), height: Math.max(height, 0) }); if (!this.styledMode) { if (typeof strokeWidth !== 'undefined') { attribs['stroke-width'] = strokeWidth; attribs = wrapper.crisp(attribs); } attribs.fill = 'none'; } if (r) { attribs.r = r; } wrapper.rSetter = function (value, _key, element) { wrapper.r = value; attr(element, { rx: value, ry: value }); }; wrapper.rGetter = function () { return wrapper.r || 0; }; return wrapper.attr(attribs); }; /** * Resize the {@link SVGRenderer#box} and re-align all aligned child * elements. * * @sample highcharts/members/renderer-g/ * Show and hide grouped objects * * @function Highcharts.SVGRenderer#setSize * * @param {number} width * The new pixel width. * * @param {number} height * The new pixel height. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animate=true] * Whether and how to animate. */ SVGRenderer.prototype.setSize = function (width, height, animate) { var renderer = this; renderer.width = width; renderer.height = height; renderer.boxWrapper.animate({ width: width, height: height }, { step: function () { this.attr({ viewBox: '0 0 ' + this.attr('width') + ' ' + this.attr('height') }); }, duration: pick(animate, true) ? void 0 : 0 }); renderer.alignElements(); }; /** * Create and return an svg group element. Child * {@link Highcharts.SVGElement} objects are added to the group by using the * group as the first parameter in {@link Highcharts.SVGElement#add|add()}. * * @function Highcharts.SVGRenderer#g * * @param {string} [name] * The group will be given a class name of `highcharts-{name}`. This * can be used for styling and scripting. * * @return {Highcharts.SVGElement} * The generated wrapper element. */ SVGRenderer.prototype.g = function (name) { var elem = this.createElement('g'); return name ? elem.attr({ 'class': 'highcharts-' + name }) : elem; }; /** * Display an image. * * @sample highcharts/members/renderer-image-on-chart/ * Add an image in a chart * @sample highcharts/members/renderer-image/ * Add an image independent of a chart * * @function Highcharts.SVGRenderer#image * * @param {string} src * The image source. * * @param {number} [x] * The X position. * * @param {number} [y] * The Y position. * * @param {number} [width] * The image width. If omitted, it defaults to the image file width. * * @param {number} [height] * The image height. If omitted it defaults to the image file * height. * * @param {Function} [onload] * Event handler for image load. * * @return {Highcharts.SVGElement} * The generated wrapper element. */ SVGRenderer.prototype.image = function (src, x, y, width, height, onload) { var attribs = { preserveAspectRatio: 'none' }, setSVGImageSource = function (el, src) { // Set the href in the xlink namespace if (el.setAttributeNS) { el.setAttributeNS('http://www.w3.org/1999/xlink', 'href', src); } else { // could be exporting in IE // using href throws "not supported" in ie7 and under, // requries regex shim to fix later el.setAttribute('hc-svg-href', src); } }; // optional properties if (arguments.length > 1) { extend(attribs, { x: x, y: y, width: width, height: height }); } var elemWrapper = this.createElement('image').attr(attribs), onDummyLoad = function (e) { setSVGImageSource(elemWrapper.element, src); onload.call(elemWrapper, e); }; // Add load event if supplied if (onload) { // We have to use a dummy HTML image since IE support for SVG image // load events is very buggy. First set a transparent src, wait for // dummy to load, and then add the real src to the SVG image. setSVGImageSource(elemWrapper.element, 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw==' /* eslint-disable-line */); var dummy = new win.Image(); addEvent(dummy, 'load', onDummyLoad); dummy.src = src; if (dummy.complete) { onDummyLoad({}); } } else { setSVGImageSource(elemWrapper.element, src); } return elemWrapper; }; /** * Draw a symbol out of pre-defined shape paths from * {@link SVGRenderer#symbols}. * It is used in Highcharts for point makers, which cake a `symbol` option, * and label and button backgrounds like in the tooltip and stock flags. * * @function Highcharts.SVGRenderer#symbol * * @param {string} symbol * The symbol name. * * @param {number} [x] * The X coordinate for the top left position. * * @param {number} [y] * The Y coordinate for the top left position. * * @param {number} [width] * The pixel width. * * @param {number} [height] * The pixel height. * * @param {Highcharts.SymbolOptionsObject} [options] * Additional options, depending on the actual symbol drawn. * * @return {Highcharts.SVGElement} */ SVGRenderer.prototype.symbol = function (symbol, x, y, width, height, options) { var ren = this, imageRegex = /^url\((.*?)\)$/, isImage = imageRegex.test(symbol), sym = (!isImage && (this.symbols[symbol] ? symbol : 'circle')), // get the symbol definition function symbolFn = (sym && this.symbols[sym]); var obj, path, imageSrc, centerImage; if (symbolFn) { // Check if there's a path defined for this symbol if (typeof x === 'number') { path = symbolFn.call(this.symbols, Math.round(x || 0), Math.round(y || 0), width || 0, height || 0, options); } obj = this.path(path); if (!ren.styledMode) { obj.attr('fill', 'none'); } // expando properties for use in animate and attr extend(obj, { symbolName: (sym || void 0), x: x, y: y, width: width, height: height }); if (options) { extend(obj, options); } // Image symbols } else if (isImage) { imageSrc = symbol.match(imageRegex)[1]; // Create the image synchronously, add attribs async var img_1 = obj = this.image(imageSrc); // The image width is not always the same as the symbol width. The // image may be centered within the symbol, as is the case when // image shapes are used as label backgrounds, for example in flags. img_1.imgwidth = pick(symbolSizes[imageSrc] && symbolSizes[imageSrc].width, options && options.width); img_1.imgheight = pick(symbolSizes[imageSrc] && symbolSizes[imageSrc].height, options && options.height); /** * Set the size and position */ centerImage = function (obj) { return obj.attr({ width: obj.width, height: obj.height }); }; /** * Width and height setters that take both the image's physical size * and the label size into consideration, and translates the image * to center within the label. */ ['width', 'height'].forEach(function (key) { img_1[key + 'Setter'] = function (value, key) { var imgSize = this['img' + key]; this[key] = value; if (defined(imgSize)) { // Scale and center the image within its container. // The name `backgroundSize` is taken from the CSS spec, // but the value `within` is made up. Other possible // values in the spec, `cover` and `contain`, can be // implemented if needed. if (options && options.backgroundSize === 'within' && this.width && this.height) { imgSize = Math.round(imgSize * Math.min(this.width / this.imgwidth, this.height / this.imgheight)); } if (this.element) { this.element.setAttribute(key, imgSize); } if (!this.alignByTranslate) { var translate = ((this[key] || 0) - imgSize) / 2; var attribs = key === 'width' ? { translateX: translate } : { translateY: translate }; this.attr(attribs); } } }; }); if (defined(x)) { img_1.attr({ x: x, y: y }); } img_1.isImg = true; if (defined(img_1.imgwidth) && defined(img_1.imgheight)) { centerImage(img_1); } else { // Initialize image to be 0 size so export will still function // if there's no cached sizes. img_1.attr({ width: 0, height: 0 }); // Create a dummy JavaScript image to get the width and height. createElement('img', { onload: function () { var chart = charts[ren.chartIndex]; // Special case for SVGs on IE11, the width is not // accessible until the image is part of the DOM // (#2854). if (this.width === 0) { css(this, { position: 'absolute', top: '-999em' }); doc.body.appendChild(this); } // Center the image symbolSizes[imageSrc] = { width: this.width, height: this.height }; img_1.imgwidth = this.width; img_1.imgheight = this.height; if (img_1.element) { centerImage(img_1); } // Clean up after #2854 workaround. if (this.parentNode) { this.parentNode.removeChild(this); } // Fire the load event when all external images are // loaded ren.imgCount--; if (!ren.imgCount && chart && !chart.hasLoaded) { chart.onload(); } }, src: imageSrc }); this.imgCount++; } } return obj; }; /** * Define a clipping rectangle. The clipping rectangle is later applied * to {@link SVGElement} objects through the {@link SVGElement#clip} * function. * * @example * let circle = renderer.circle(100, 100, 100) * .attr({ fill: 'red' }) * .add(); * let clipRect = renderer.clipRect(100, 100, 100, 100); * * // Leave only the lower right quarter visible * circle.clip(clipRect); * * @function Highcharts.SVGRenderer#clipRect * * @param {number} [x] * * @param {number} [y] * * @param {number} [width] * * @param {number} [height] * * @return {Highcharts.ClipRectElement} * A clipping rectangle. */ SVGRenderer.prototype.clipRect = function (x, y, width, height) { var // Add a hyphen at the end to avoid confusion in testing indexes // -1 and -10, -11 etc (#6550) id = uniqueKey() + '-', clipPath = this.createElement('clipPath').attr({ id: id }).add(this.defs), wrapper = this.rect(x, y, width, height, 0).add(clipPath); wrapper.id = id; wrapper.clipPath = clipPath; wrapper.count = 0; return wrapper; }; /** * Draw text. The text can contain a subset of HTML, like spans and anchors * and some basic text styling of these. For more advanced features like * border and background, use {@link Highcharts.SVGRenderer#label} instead. * To update the text after render, run `text.attr({ text: 'New text' })`. * * @sample highcharts/members/renderer-text-on-chart/ * Annotate the chart freely * @sample highcharts/members/renderer-on-chart/ * Annotate with a border and in response to the data * @sample highcharts/members/renderer-text/ * Formatted text * * @function Highcharts.SVGRenderer#text * * @param {string} [str] * The text of (subset) HTML to draw. * * @param {number} [x] * The x position of the text's lower left corner. * * @param {number} [y] * The y position of the text's lower left corner. * * @param {boolean} [useHTML=false] * Use HTML to render the text. * * @return {Highcharts.SVGElement} * The text object. */ SVGRenderer.prototype.text = function (str, x, y, useHTML) { var renderer = this, attribs = {}; if (useHTML && (renderer.allowHTML || !renderer.forExport)) { return renderer.html(str, x, y); } attribs.x = Math.round(x || 0); // X always needed for line-wrap logic if (y) { attribs.y = Math.round(y); } if (defined(str)) { attribs.text = str; } var wrapper = renderer.createElement('text').attr(attribs); if (!useHTML) { wrapper.xSetter = function (value, key, element) { var tspans = element.getElementsByTagName('tspan'), parentVal = element.getAttribute(key); for (var i = 0, tspan = void 0; i < tspans.length; i++) { tspan = tspans[i]; // If the x values are equal, the tspan represents a // linebreak if (tspan.getAttribute(key) === parentVal) { tspan.setAttribute(key, value); } } element.setAttribute(key, value); }; } return wrapper; }; /** * Utility to return the baseline offset and total line height from the font * size. * * @function Highcharts.SVGRenderer#fontMetrics * * @param {number|string} [fontSize] * The current font size to inspect. If not given, the font size * will be found from the DOM element. * * @param {Highcharts.SVGElement|Highcharts.SVGDOMElement} [elem] * The element to inspect for a current font size. * * @return {Highcharts.FontMetricsObject} * The font metrics. */ SVGRenderer.prototype.fontMetrics = function (fontSize, elem) { if ((this.styledMode || !/px/.test(fontSize)) && win.getComputedStyle // old IE doesn't support it ) { fontSize = elem && SVGElement.prototype.getStyle.call(elem, 'font-size'); } else { fontSize = fontSize || // When the elem is a DOM element (#5932) (elem && elem.style && elem.style.fontSize) || // Fall back on the renderer style default (this.style && this.style.fontSize); } // Handle different units if (/px/.test(fontSize)) { fontSize = pInt(fontSize); } else { fontSize = 12; } // Empirical values found by comparing font size and bounding box // height. Applies to the default font family. // https://jsfiddle.net/highcharts/7xvn7/ var lineHeight = (fontSize < 24 ? fontSize + 3 : Math.round(fontSize * 1.2)), baseline = Math.round(lineHeight * 0.8); return { h: lineHeight, b: baseline, f: fontSize }; }; /** * Correct X and Y positioning of a label for rotation (#1764). * * @private * @function Highcharts.SVGRenderer#rotCorr * * @param {number} baseline * * @param {number} rotation * * @param {boolean} [alterY] * * @param {Highcharts.PositionObject} */ SVGRenderer.prototype.rotCorr = function (baseline, rotation, alterY) { var y = baseline; if (rotation && alterY) { y = Math.max(y * Math.cos(rotation * deg2rad), 4); } return { x: (-baseline / 3) * Math.sin(rotation * deg2rad), y: y }; }; /** * Compatibility function to convert the legacy one-dimensional path array * into an array of segments. * * It is used in maps to parse the `path` option, and in SVGRenderer.dSetter * to support legacy paths from demos. * * @private * @function Highcharts.SVGRenderer#pathToSegments */ SVGRenderer.prototype.pathToSegments = function (path) { var ret = []; var segment = []; var commandLength = { A: 8, C: 7, H: 2, L: 3, M: 3, Q: 5, S: 5, T: 3, V: 2 }; // Short, non-typesafe parsing of the one-dimensional array. It splits // the path on any string. This is not type checked against the tuple // types, but is shorter, and doesn't require specific checks for any // command type in SVG. for (var i = 0; i < path.length; i++) { // Command skipped, repeat previous or insert L/l for M/m if (isString(segment[0]) && isNumber(path[i]) && segment.length === commandLength[(segment[0].toUpperCase())]) { path.splice(i, 0, segment[0].replace('M', 'L').replace('m', 'l')); } // Split on string if (typeof path[i] === 'string') { if (segment.length) { ret.push(segment.slice(0)); } segment.length = 0; } segment.push(path[i]); } ret.push(segment.slice(0)); return ret; /* // Fully type-safe version where each tuple type is checked. The // downside is filesize and a lack of flexibility for unsupported // commands const ret: SVGPath = [], commands = { A: 7, C: 6, H: 1, L: 2, M: 2, Q: 4, S: 4, T: 2, V: 1, Z: 0 }; let i = 0, lastI = 0, lastCommand; while (i < path.length) { const item = path[i]; let command; if (typeof item === 'string') { command = item; i += 1; } else { command = lastCommand || 'M'; } // Upper case const commandUC = command.toUpperCase(); if (commandUC in commands) { // No numeric parameters if (command === 'Z' || command === 'z') { ret.push([command]); // One numeric parameter } else { const val0 = path[i]; if (typeof val0 === 'number') { // Horizontal line to if (command === 'H' || command === 'h') { ret.push([command, val0]); i += 1; // Vertical line to } else if (command === 'V' || command === 'v') { ret.push([command, val0]); i += 1; // Two numeric parameters } else { const val1 = path[i + 1]; if (typeof val1 === 'number') { // lineTo if (command === 'L' || command === 'l') { ret.push([command, val0, val1]); i += 2; // moveTo } else if (command === 'M' || command === 'm') { ret.push([command, val0, val1]); i += 2; // Smooth quadratic bezier } else if (command === 'T' || command === 't') { ret.push([command, val0, val1]); i += 2; // Four numeric parameters } else { const val2 = path[i + 2], val3 = path[i + 3]; if ( typeof val2 === 'number' && typeof val3 === 'number' ) { // Quadratic bezier to if ( command === 'Q' || command === 'q' ) { ret.push([ command, val0, val1, val2, val3 ]); i += 4; // Smooth cubic bezier to } else if ( command === 'S' || command === 's' ) { ret.push([ command, val0, val1, val2, val3 ]); i += 4; // Six numeric parameters } else { const val4 = path[i + 4], val5 = path[i + 5]; if ( typeof val4 === 'number' && typeof val5 === 'number' ) { // Curve to if ( command === 'C' || command === 'c' ) { ret.push([ command, val0, val1, val2, val3, val4, val5 ]); i += 6; // Seven numeric parameters } else { const val6 = path[i + 6]; // Arc to if ( typeof val6 === 'number' && ( command === 'A' || command === 'a' ) ) { ret.push([ command, val0, val1, val2, val3, val4, val5, val6 ]); i += 7; } } } } } } } } } } } // An unmarked command following a moveTo is a lineTo lastCommand = command === 'M' ? 'L' : command; if (i === lastI) { break; } lastI = i; } return ret; */ }; /** * Draw a label, which is an extended text element with support for border * and background. Highcharts creates a `g` element with a text and a `path` * or `rect` inside, to make it behave somewhat like a HTML div. Border and * background are set through `stroke`, `stroke-width` and `fill` attributes * using the {@link Highcharts.SVGElement#attr|attr} method. To update the * text after render, run `label.attr({ text: 'New text' })`. * * @sample highcharts/members/renderer-label-on-chart/ * A label on the chart * * @function Highcharts.SVGRenderer#label * * @param {string} str * The initial text string or (subset) HTML to render. * * @param {number} x * The x position of the label's left side. * * @param {number} [y] * The y position of the label's top side or baseline, depending on * the `baseline` parameter. * * @param {string} [shape='rect'] * The shape of the label's border/background, if any. Defaults to * `rect`. Other possible values are `callout` or other shapes * defined in {@link Highcharts.SVGRenderer#symbols}. * * @param {number} [anchorX] * In case the `shape` has a pointer, like a flag, this is the * coordinates it should be pinned to. * * @param {number} [anchorY] * In case the `shape` has a pointer, like a flag, this is the * coordinates it should be pinned to. * * @param {boolean} [useHTML=false] * Wether to use HTML to render the label. * * @param {boolean} [baseline=false] * Whether to position the label relative to the text baseline, * like {@link Highcharts.SVGRenderer#text|renderer.text}, or to the * upper border of the rectangle. * * @param {string} [className] * Class name for the group. * * @return {Highcharts.SVGElement} * The generated label. */ SVGRenderer.prototype.label = function (str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) { return new SVGLabel(this, str, x, y, shape, anchorX, anchorY, useHTML, baseline, className); }; /** * Re-align all aligned elements. * * @private * @function Highcharts.SVGRenderer#alignElements * @return {void} */ SVGRenderer.prototype.alignElements = function () { this.alignedObjects.forEach(function (el) { return el.align(); }); }; return SVGRenderer; }()); extend(SVGRenderer.prototype, { /** * A pointer to the renderer's associated Element class. The VMLRenderer * will have a pointer to VMLElement here. * * @name Highcharts.SVGRenderer#Element * @type {Highcharts.SVGElement} */ Element: SVGElement, SVG_NS: SVG_NS, /** * A collection of characters mapped to HTML entities. When `useHTML` on an * element is true, these entities will be rendered correctly by HTML. In * the SVG pseudo-HTML, they need to be unescaped back to simple characters, * so for example `<` will render as `<`. * * @example * // Add support for unescaping quotes * Highcharts.SVGRenderer.prototype.escapes['"'] = '"'; * * @name Highcharts.SVGRenderer#escapes * @type {Highcharts.Dictionary<string>} */ escapes: { '&': '&', '<': '<', '>': '>', "'": ''', '"': '"' }, /** * An extendable collection of functions for defining symbol paths. * * @name Highcharts.SVGRenderer#symbols * @type {Highcharts.SymbolDictionary} */ symbols: Symbols, /** * Dummy function for plugins, called every time the renderer is updated. * Prior to Highcharts 5, this was used for the canvg renderer. * * @deprecated * @function Highcharts.SVGRenderer#draw */ draw: noop }); /* * * * Registry * * */ RendererRegistry.registerRendererType('svg', SVGRenderer, true); /* * * * Export Default * * */ /* * * * API Declarations * * */ /** * A clipping rectangle that can be applied to one or more {@link SVGElement} * instances. It is instanciated with the {@link SVGRenderer#clipRect} function * and applied with the {@link SVGElement#clip} function. * * @example * let circle = renderer.circle(100, 100, 100) * .attr({ fill: 'red' }) * .add(); * let clipRect = renderer.clipRect(100, 100, 100, 100); * * // Leave only the lower right quarter visible * circle.clip(clipRect); * * @typedef {Highcharts.SVGElement} Highcharts.ClipRectElement */ /** * The font metrics. * * @interface Highcharts.FontMetricsObject */ /** * The baseline relative to the top of the box. * * @name Highcharts.FontMetricsObject#b * @type {number} */ /** * The font size. * * @name Highcharts.FontMetricsObject#f * @type {number} */ /** * The line height. * * @name Highcharts.FontMetricsObject#h * @type {number} */ /** * An object containing `x` and `y` properties for the position of an element. * * @interface Highcharts.PositionObject */ /** * X position of the element. * @name Highcharts.PositionObject#x * @type {number} */ /** * Y position of the element. * @name Highcharts.PositionObject#y * @type {number} */ /** * A rectangle. * * @interface Highcharts.RectangleObject */ /** * Height of the rectangle. * @name Highcharts.RectangleObject#height * @type {number} */ /** * Width of the rectangle. * @name Highcharts.RectangleObject#width * @type {number} */ /** * Horizontal position of the rectangle. * @name Highcharts.RectangleObject#x * @type {number} */ /** * Vertical position of the rectangle. * @name Highcharts.RectangleObject#y * @type {number} */ /** * The shadow options. * * @interface Highcharts.ShadowOptionsObject */ /** * The shadow color. * @name Highcharts.ShadowOptionsObject#color * @type {Highcharts.ColorString|undefined} * @default ${palette.neutralColor100} */ /** * The horizontal offset from the element. * * @name Highcharts.ShadowOptionsObject#offsetX * @type {number|undefined} * @default 1 */ /** * The vertical offset from the element. * @name Highcharts.ShadowOptionsObject#offsetY * @type {number|undefined} * @default 1 */ /** * The shadow opacity. * * @name Highcharts.ShadowOptionsObject#opacity * @type {number|undefined} * @default 0.15 */ /** * The shadow width or distance from the element. * @name Highcharts.ShadowOptionsObject#width * @type {number|undefined} * @default 3 */ /** * @interface Highcharts.SizeObject */ /** * @name Highcharts.SizeObject#height * @type {number} */ /** * @name Highcharts.SizeObject#width * @type {number} */ /** * Array of path commands, that will go into the `d` attribute of an SVG * element. * * @typedef {Array<(Array<Highcharts.SVGPathCommand>|Array<Highcharts.SVGPathCommand,number>|Array<Highcharts.SVGPathCommand,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number,number,number,number>)>} Highcharts.SVGPathArray */ /** * Possible path commands in an SVG path array. Valid values are `A`, `C`, `H`, * `L`, `M`, `Q`, `S`, `T`, `V`, `Z`. * * @typedef {string} Highcharts.SVGPathCommand * @validvalue ["a","c","h","l","m","q","s","t","v","z","A","C","H","L","M","Q","S","T","V","Z"] */ /** * An extendable collection of functions for defining symbol paths. Symbols are * used internally for point markers, button and label borders and backgrounds, * or custom shapes. Extendable by adding to {@link SVGRenderer#symbols}. * * @interface Highcharts.SymbolDictionary */ /** * @name Highcharts.SymbolDictionary#[key:string] * @type {Function|undefined} */ /** * @name Highcharts.SymbolDictionary#arc * @type {Function|undefined} */ /** * @name Highcharts.SymbolDictionary#callout * @type {Function|undefined} */ /** * @name Highcharts.SymbolDictionary#circle * @type {Function|undefined} */ /** * @name Highcharts.SymbolDictionary#diamond * @type {Function|undefined} */ /** * @name Highcharts.SymbolDictionary#square * @type {Function|undefined} */ /** * @name Highcharts.SymbolDictionary#triangle * @type {Function|undefined} */ /** * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`, `triangle`, * and `triangle-down`. Symbols are used internally for point markers, button * and label borders and backgrounds, or custom shapes. Extendable by adding to * {@link SVGRenderer#symbols}. * * @typedef {"arc"|"callout"|"circle"|"diamond"|"square"|"triangle"|"triangle-down"} Highcharts.SymbolKeyValue */ /** * Additional options, depending on the actual symbol drawn. * * @interface Highcharts.SymbolOptionsObject */ /** * The anchor X position for the `callout` symbol. This is where the chevron * points to. * * @name Highcharts.SymbolOptionsObject#anchorX * @type {number|undefined} */ /** * The anchor Y position for the `callout` symbol. This is where the chevron * points to. * * @name Highcharts.SymbolOptionsObject#anchorY * @type {number|undefined} */ /** * The end angle of an `arc` symbol. * * @name Highcharts.SymbolOptionsObject#end * @type {number|undefined} */ /** * Whether to draw `arc` symbol open or closed. * * @name Highcharts.SymbolOptionsObject#open * @type {boolean|undefined} */ /** * The radius of an `arc` symbol, or the border radius for the `callout` symbol. * * @name Highcharts.SymbolOptionsObject#r * @type {number|undefined} */ /** * The start angle of an `arc` symbol. * * @name Highcharts.SymbolOptionsObject#start * @type {number|undefined} */ (''); // keeps doclets above in transpiled file return SVGRenderer; }); _registerModule(_modules, 'Core/Renderer/HTML/HTMLElement.js', [_modules['Core/Globals.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (H, SVGElement, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var isFirefox = H.isFirefox, isMS = H.isMS, isWebKit = H.isWebKit, win = H.win; var css = U.css, defined = U.defined, extend = U.extend, pick = U.pick, pInt = U.pInt; /* * * * Composition * * */ /* eslint-disable valid-jsdoc */ var HTMLElement = /** @class */ (function (_super) { __extends(HTMLElement, _super); function HTMLElement() { return _super !== null && _super.apply(this, arguments) || this; } /* * * * Static Functions * * */ /** * Modifies SVGElement to support HTML elements. * @private */ HTMLElement.compose = function (SVGElementClass) { var svgElementProto = SVGElementClass.prototype, htmlElementProto = HTMLElement.prototype; svgElementProto.getSpanCorrection = htmlElementProto.getSpanCorrection; svgElementProto.htmlCss = htmlElementProto.htmlCss; svgElementProto.htmlGetBBox = htmlElementProto.htmlGetBBox; svgElementProto.htmlUpdateTransform = htmlElementProto.htmlUpdateTransform; svgElementProto.setSpanRotation = htmlElementProto.setSpanRotation; }; /* * * * Functions * * */ /** * Get the correction in X and Y positioning as the element is rotated. * @private */ HTMLElement.prototype.getSpanCorrection = function (width, baseline, alignCorrection) { this.xCorr = -width * alignCorrection; this.yCorr = -baseline; }; /** * Apply CSS to HTML elements. This is used in text within SVG rendering and * by the VML renderer * @private */ HTMLElement.prototype.htmlCss = function (styles) { var wrapper = this, element = wrapper.element, // When setting or unsetting the width style, we need to update // transform (#8809) isSettingWidth = (element.tagName === 'SPAN' && styles && 'width' in styles), textWidth = pick(isSettingWidth && styles.width, void 0); var doTransform; if (isSettingWidth) { delete styles.width; wrapper.textWidth = textWidth; doTransform = true; } if (styles && styles.textOverflow === 'ellipsis') { styles.whiteSpace = 'nowrap'; styles.overflow = 'hidden'; } wrapper.styles = extend(wrapper.styles, styles); css(wrapper.element, styles); // Now that all styles are applied, to the transform if (doTransform) { wrapper.htmlUpdateTransform(); } return wrapper; }; /** * VML and useHTML method for calculating the bounding box based on offsets. */ HTMLElement.prototype.htmlGetBBox = function () { var wrapper = this, element = wrapper.element; return { x: element.offsetLeft, y: element.offsetTop, width: element.offsetWidth, height: element.offsetHeight }; }; /** * VML override private method to update elements based on internal * properties based on SVG transform. * @private */ HTMLElement.prototype.htmlUpdateTransform = function () { // aligning non added elements is expensive if (!this.added) { this.alignOnAdd = true; return; } var wrapper = this, renderer = wrapper.renderer, elem = wrapper.element, translateX = wrapper.translateX || 0, translateY = wrapper.translateY || 0, x = wrapper.x || 0, y = wrapper.y || 0, align = wrapper.textAlign || 'left', alignCorrection = { left: 0, center: 0.5, right: 1 }[align], styles = wrapper.styles, whiteSpace = styles && styles.whiteSpace; /** @private */ function getTextPxLength() { // Reset multiline/ellipsis in order to read width (#4928, // #5417) css(elem, { width: '', whiteSpace: whiteSpace || 'nowrap' }); return elem.offsetWidth; } // apply translate css(elem, { marginLeft: translateX, marginTop: translateY }); if (!renderer.styledMode && wrapper.shadows) { // used in labels/tooltip wrapper.shadows.forEach(function (shadow) { css(shadow, { marginLeft: translateX + 1, marginTop: translateY + 1 }); }); } // apply inversion if (wrapper.inverted) { // wrapper is a group [].forEach.call(elem.childNodes, function (child) { renderer.invertChild(child, elem); }); } if (elem.tagName === 'SPAN') { var rotation = wrapper.rotation, textWidth = wrapper.textWidth && pInt(wrapper.textWidth), currentTextTransform = [ rotation, align, elem.innerHTML, wrapper.textWidth, wrapper.textAlign ].join(','); var baseline = void 0; // Update textWidth. Use the memoized textPxLength if possible, to // avoid the getTextPxLength function using elem.offsetWidth. // Calling offsetWidth affects rendering time as it forces layout // (#7656). if (textWidth !== wrapper.oldTextWidth && ((textWidth > wrapper.oldTextWidth) || (wrapper.textPxLength || getTextPxLength()) > textWidth) && ( // Only set the width if the text is able to word-wrap, or // text-overflow is ellipsis (#9537) /[ \-]/.test(elem.textContent || elem.innerText) || elem.style.textOverflow === 'ellipsis')) { // #983, #1254 css(elem, { width: textWidth + 'px', display: 'block', whiteSpace: whiteSpace || 'normal' // #3331 }); wrapper.oldTextWidth = textWidth; wrapper.hasBoxWidthChanged = true; // #8159 } else { wrapper.hasBoxWidthChanged = false; // #8159 } // Do the calculations and DOM access only if properties changed if (currentTextTransform !== wrapper.cTT) { baseline = renderer.fontMetrics(elem.style.fontSize, elem).b; // Renderer specific handling of span rotation, but only if we // have something to update. if (defined(rotation) && ((rotation !== (wrapper.oldRotation || 0)) || (align !== wrapper.oldAlign))) { wrapper.setSpanRotation(rotation, alignCorrection, baseline); } wrapper.getSpanCorrection( // Avoid elem.offsetWidth if we can, it affects rendering // time heavily (#7656) ((!defined(rotation) && wrapper.textPxLength) || // #7920 elem.offsetWidth), baseline, alignCorrection, rotation, align); } // apply position with correction css(elem, { left: (x + (wrapper.xCorr || 0)) + 'px', top: (y + (wrapper.yCorr || 0)) + 'px' }); // record current text transform wrapper.cTT = currentTextTransform; wrapper.oldRotation = rotation; wrapper.oldAlign = align; } }; /** * Set the rotation of an individual HTML span. * @private */ HTMLElement.prototype.setSpanRotation = function (rotation, alignCorrection, baseline) { var getTransformKey = function () { return (isMS && !/Edge/.test(win.navigator.userAgent) ? '-ms-transform' : isWebKit ? '-webkit-transform' : isFirefox ? 'MozTransform' : win.opera ? '-o-transform' : void 0); }; var rotationStyle = {}, cssTransformKey = getTransformKey(); if (cssTransformKey) { rotationStyle[cssTransformKey] = rotationStyle.transform = 'rotate(' + rotation + 'deg)'; rotationStyle[cssTransformKey + (isFirefox ? 'Origin' : '-origin')] = rotationStyle.transformOrigin = (alignCorrection * 100) + '% ' + baseline + 'px'; css(this.element, rotationStyle); } }; return HTMLElement; }(SVGElement)); /* * * * Default Export * * */ return HTMLElement; }); _registerModule(_modules, 'Core/Renderer/HTML/HTMLRenderer.js', [_modules['Core/Renderer/HTML/AST.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Renderer/SVG/SVGRenderer.js'], _modules['Core/Utilities.js']], function (AST, SVGElement, SVGRenderer, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var attr = U.attr, createElement = U.createElement, extend = U.extend, pick = U.pick; /* * * * Composition * * */ /* eslint-disable valid-jsdoc */ // Extend SvgRenderer for useHTML option. var HTMLRenderer = /** @class */ (function (_super) { __extends(HTMLRenderer, _super); function HTMLRenderer() { return _super !== null && _super.apply(this, arguments) || this; } /* * * * Functions * * */ /** @private */ HTMLRenderer.compose = function (SVGRendererClass) { var svgRendererProto = SVGRendererClass.prototype, htmlRendererProto = HTMLRenderer.prototype; svgRendererProto.html = htmlRendererProto.html; }; /** * Create HTML text node. This is used by the VML renderer as well as the * SVG renderer through the useHTML option. * * @private * @function Highcharts.SVGRenderer#html * * @param {string} str * The text of (subset) HTML to draw. * * @param {number} x * The x position of the text's lower left corner. * * @param {number} y * The y position of the text's lower left corner. * * @return {Highcharts.HTMLDOMElement} */ HTMLRenderer.prototype.html = function (str, x, y) { var wrapper = this.createElement('span'), element = wrapper.element, renderer = wrapper.renderer, isSVG = renderer.isSVG, addSetters = function (gWrapper, style) { // These properties are set as attributes on the SVG group, and // as identical CSS properties on the div. (#3542) ['opacity', 'visibility'].forEach(function (prop) { gWrapper[prop + 'Setter'] = function (value, key, elem) { var styleObject = gWrapper.div ? gWrapper.div.style : style; SVGElement.prototype[prop + 'Setter'] .call(this, value, key, elem); if (styleObject) { styleObject[key] = value; } }; }); gWrapper.addedSetters = true; }; // Text setter wrapper.textSetter = function (value) { if (value !== this.textStr) { delete this.bBox; delete this.oldTextWidth; AST.setElementHTML(this.element, pick(value, '')); this.textStr = value; wrapper.doTransform = true; } }; // Add setters for the element itself (#4938) if (isSVG) { // #4938, only for HTML within SVG addSetters(wrapper, wrapper.element.style); } // Various setters which rely on update transform wrapper.xSetter = wrapper.ySetter = wrapper.alignSetter = wrapper.rotationSetter = function (value, key) { if (key === 'align') { // Do not overwrite the SVGElement.align method. Same as VML. wrapper.alignValue = wrapper.textAlign = value; } else { wrapper[key] = value; } wrapper.doTransform = true; }; // Runs at the end of .attr() wrapper.afterSetters = function () { // Update transform. Do this outside the loop to prevent redundant // updating for batch setting of attributes. if (this.doTransform) { this.htmlUpdateTransform(); this.doTransform = false; } }; // Set the default attributes wrapper .attr({ text: str, x: Math.round(x), y: Math.round(y) }) .css({ position: 'absolute' }); if (!renderer.styledMode) { wrapper.css({ fontFamily: this.style.fontFamily, fontSize: this.style.fontSize }); } // Keep the whiteSpace style outside the wrapper.styles collection element.style.whiteSpace = 'nowrap'; // Use the HTML specific .css method wrapper.css = wrapper.htmlCss; // This is specific for HTML within SVG if (isSVG) { wrapper.add = function (svgGroupWrapper) { var htmlGroup, container = renderer.box.parentNode, parentGroup, parents = []; this.parentGroup = svgGroupWrapper; // Create a mock group to hold the HTML elements if (svgGroupWrapper) { htmlGroup = svgGroupWrapper.div; if (!htmlGroup) { // Read the parent chain into an array and read from top // down parentGroup = svgGroupWrapper; while (parentGroup) { parents.push(parentGroup); // Move up to the next parent group parentGroup = parentGroup.parentGroup; } // Ensure dynamically updating position when any parent // is translated parents.reverse().forEach(function (parentGroup) { var htmlGroupStyle, cls = attr(parentGroup.element, 'class'); /** * Common translate setter for X and Y on the HTML * group. Reverted the fix for #6957 du to * positioning problems and offline export (#7254, * #7280, #7529) * @private * @param {*} value * @param {string} key * @return {void} */ function translateSetter(value, key) { parentGroup[key] = value; if (key === 'translateX') { htmlGroupStyle.left = value + 'px'; } else { htmlGroupStyle.top = value + 'px'; } parentGroup.doTransform = true; } // Create a HTML div and append it to the parent div // to emulate the SVG group structure var parentGroupStyles = parentGroup.styles || {}; htmlGroup = parentGroup.div = parentGroup.div || createElement('div', cls ? { className: cls } : void 0, { position: 'absolute', left: (parentGroup.translateX || 0) + 'px', top: (parentGroup.translateY || 0) + 'px', display: parentGroup.display, opacity: parentGroup.opacity, cursor: parentGroupStyles.cursor, pointerEvents: parentGroupStyles.pointerEvents // #5595 // the top group is appended to container }, htmlGroup || container); // Shortcut htmlGroupStyle = htmlGroup.style; // Set listeners to update the HTML div's position // whenever the SVG group position is changed. extend(parentGroup, { // (#7287) Pass htmlGroup to use // the related group classSetter: (function (htmlGroup) { return function (value) { this.element.setAttribute('class', value); htmlGroup.className = value; }; }(htmlGroup)), on: function () { if (parents[0].div) { // #6418 wrapper.on.apply({ element: parents[0].div, onEvents: wrapper.onEvents }, arguments); } return parentGroup; }, translateXSetter: translateSetter, translateYSetter: translateSetter }); if (!parentGroup.addedSetters) { addSetters(parentGroup); } }); } } else { htmlGroup = container; } htmlGroup.appendChild(element); // Shared with VML: wrapper.added = true; if (wrapper.alignOnAdd) { wrapper.htmlUpdateTransform(); } return wrapper; }; } return wrapper; }; return HTMLRenderer; }(SVGRenderer)); /* * * * Default Export * * */ return HTMLRenderer; }); _registerModule(_modules, 'Core/Axis/AxisDefaults.js', [_modules['Core/Color/Palette.js']], function (Palette) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Namespace * * */ var AxisDefaults; (function (AxisDefaults) { /* * * * Constants * * */ /** * The X axis or category axis. Normally this is the horizontal axis, * though if the chart is inverted this is the vertical axis. In case of * multiple axes, the xAxis node is an array of configuration objects. * * See the [Axis class](/class-reference/Highcharts.Axis) for programmatic * access to the axis. * * @productdesc {highmaps} * In Highmaps, the axis is hidden, but it is used behind the scenes to * control features like zooming and panning. Zooming is in effect the same * as setting the extremes of one of the exes. * * @type {*|Array<*>} * @optionparent xAxis */ AxisDefaults.defaultXAxisOptions = { /** * When using multiple axis, the ticks of two or more opposite axes * will automatically be aligned by adding ticks to the axis or axes * with the least ticks, as if `tickAmount` were specified. * * This can be prevented by setting `alignTicks` to false. If the grid * lines look messy, it's a good idea to hide them for the secondary * axis by setting `gridLineWidth` to 0. * * If `startOnTick` or `endOnTick` in an Axis options are set to false, * then the `alignTicks ` will be disabled for the Axis. * * Disabled for logarithmic axes. * * @product highcharts highstock gantt */ alignTicks: true, /** * Whether to allow decimals in this axis' ticks. When counting * integers, like persons or hits on a web page, decimals should * be avoided in the labels. By default, decimals are allowed on small * scale axes. * * @see [minTickInterval](#xAxis.minTickInterval) * * @sample {highcharts|highstock} highcharts/yaxis/allowdecimals-true/ * True by default * @sample {highcharts|highstock} highcharts/yaxis/allowdecimals-false/ * False * * @type {boolean|undefined} * @default undefined * @since 2.0 */ allowDecimals: void 0, /** * When using an alternate grid color, a band is painted across the * plot area between every other grid line. * * @sample {highcharts} highcharts/yaxis/alternategridcolor/ * Alternate grid color on the Y axis * @sample {highstock} stock/xaxis/alternategridcolor/ * Alternate grid color on the Y axis * * @type {Highcharts.ColorType} * @apioption xAxis.alternateGridColor */ /** * An array defining breaks in the axis, the sections defined will be * left out and all the points shifted closer to each other. * * @productdesc {highcharts} * Requires that the broken-axis.js module is loaded. * * @sample {highcharts} highcharts/axisbreak/break-simple/ * Simple break * @sample {highcharts|highstock} highcharts/axisbreak/break-visualized/ * Advanced with callback * @sample {highstock} stock/demo/intraday-breaks/ * Break on nights and weekends * * @type {Array<*>} * @since 4.1.0 * @product highcharts highstock gantt * @apioption xAxis.breaks */ /** * A number indicating how much space should be left between the start * and the end of the break. The break size is given in axis units, * so for instance on a `datetime` axis, a break size of 3600000 would * indicate the equivalent of an hour. * * @type {number} * @default 0 * @since 4.1.0 * @product highcharts highstock gantt * @apioption xAxis.breaks.breakSize */ /** * The point where the break starts. * * @type {number} * @since 4.1.0 * @product highcharts highstock gantt * @apioption xAxis.breaks.from */ /** * Defines an interval after which the break appears again. By default * the breaks do not repeat. * * @type {number} * @default 0 * @since 4.1.0 * @product highcharts highstock gantt * @apioption xAxis.breaks.repeat */ /** * The point where the break ends. * * @type {number} * @since 4.1.0 * @product highcharts highstock gantt * @apioption xAxis.breaks.to */ /** * If categories are present for the xAxis, names are used instead of * numbers for that axis. * * Since Highcharts 3.0, categories can also * be extracted by giving each point a [name](#series.data) and setting * axis [type](#xAxis.type) to `category`. However, if you have multiple * series, best practice remains defining the `categories` array. * * Example: `categories: ['Apples', 'Bananas', 'Oranges']` * * @sample {highcharts} highcharts/demo/line-labels/ * With * @sample {highcharts} highcharts/xaxis/categories/ * Without * * @type {Array<string>} * @product highcharts gantt * @apioption xAxis.categories */ /** * The highest allowed value for automatically computed axis extremes. * * @see [floor](#xAxis.floor) * * @sample {highcharts|highstock} highcharts/yaxis/floor-ceiling/ * Floor and ceiling * * @type {number} * @since 4.0 * @product highcharts highstock gantt * @apioption xAxis.ceiling */ /** * A class name that opens for styling the axis by CSS, especially in * Highcharts styled mode. The class name is applied to group elements * for the grid, axis elements and labels. * * @sample {highcharts|highstock|highmaps} highcharts/css/axis/ * Multiple axes with separate styling * * @type {string} * @since 5.0.0 * @apioption xAxis.className */ /** * Configure a crosshair that follows either the mouse pointer or the * hovered point. * * In styled mode, the crosshairs are styled in the * `.highcharts-crosshair`, `.highcharts-crosshair-thin` or * `.highcharts-xaxis-category` classes. * * @productdesc {highstock} * In Highcharts stock, by default, the crosshair is enabled on the * X axis and disabled on the Y axis. * * @sample {highcharts} highcharts/xaxis/crosshair-both/ * Crosshair on both axes * @sample {highstock} stock/xaxis/crosshairs-xy/ * Crosshair on both axes * @sample {highmaps} highcharts/xaxis/crosshair-both/ * Crosshair on both axes * * @declare Highcharts.AxisCrosshairOptions * @type {boolean|*} * @default false * @since 4.1 * @apioption xAxis.crosshair */ /** * A class name for the crosshair, especially as a hook for styling. * * @type {string} * @since 5.0.0 * @apioption xAxis.crosshair.className */ /** * The color of the crosshair. Defaults to `#cccccc` for numeric and * datetime axes, and `rgba(204,214,235,0.25)` for category axes, where * the crosshair by default highlights the whole category. * * @sample {highcharts|highstock|highmaps} highcharts/xaxis/crosshair-customized/ * Customized crosshairs * * @type {Highcharts.ColorType} * @default #cccccc * @since 4.1 * @apioption xAxis.crosshair.color */ /** * The dash style for the crosshair. See * [plotOptions.series.dashStyle](#plotOptions.series.dashStyle) * for possible values. * * @sample {highcharts|highmaps} highcharts/xaxis/crosshair-dotted/ * Dotted crosshair * @sample {highstock} stock/xaxis/crosshair-dashed/ * Dashed X axis crosshair * * @type {Highcharts.DashStyleValue} * @default Solid * @since 4.1 * @apioption xAxis.crosshair.dashStyle */ /** * A label on the axis next to the crosshair. * * In styled mode, the label is styled with the * `.highcharts-crosshair-label` class. * * @sample {highstock} stock/xaxis/crosshair-label/ * Crosshair labels * @sample {highstock} highcharts/css/crosshair-label/ * Style mode * * @declare Highcharts.AxisCrosshairLabelOptions * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label */ /** * Alignment of the label compared to the axis. Defaults to `"left"` for * right-side axes, `"right"` for left-side axes and `"center"` for * horizontal axes. * * @type {Highcharts.AlignValue} * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label.align */ /** * The background color for the label. Defaults to the related series * color, or `#666666` if that is not available. * * @type {Highcharts.ColorType} * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label.backgroundColor */ /** * The border color for the crosshair label * * @type {Highcharts.ColorType} * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label.borderColor */ /** * The border corner radius of the crosshair label. * * @type {number} * @default 3 * @since 2.1.10 * @product highstock * @apioption xAxis.crosshair.label.borderRadius */ /** * The border width for the crosshair label. * * @type {number} * @default 0 * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label.borderWidth */ /** * Flag to enable crosshair's label. * * @sample {highstock} stock/xaxis/crosshairs-xy/ * Enabled label for yAxis' crosshair * * @type {boolean} * @default false * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label.enabled */ /** * A format string for the crosshair label. Defaults to `{value}` for * numeric axes and `{value:%b %d, %Y}` for datetime axes. * * @type {string} * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label.format */ /** * Formatter function for the label text. * * @type {Highcharts.XAxisCrosshairLabelFormatterCallbackFunction} * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label.formatter */ /** * Padding inside the crosshair label. * * @type {number} * @default 8 * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label.padding */ /** * The shape to use for the label box. * * @type {string} * @default callout * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label.shape */ /** * Text styles for the crosshair label. * * @type {Highcharts.CSSObject} * @default {"color": "white", "fontWeight": "normal", "fontSize": "11px", "textAlign": "center"} * @since 2.1 * @product highstock * @apioption xAxis.crosshair.label.style */ /** * Whether the crosshair should snap to the point or follow the pointer * independent of points. * * @sample {highcharts|highstock} highcharts/xaxis/crosshair-snap-false/ * True by default * @sample {highmaps} maps/demo/latlon-advanced/ * Snap is false * * @type {boolean} * @default true * @since 4.1 * @apioption xAxis.crosshair.snap */ /** * The pixel width of the crosshair. Defaults to 1 for numeric or * datetime axes, and for one category width for category axes. * * @sample {highcharts} highcharts/xaxis/crosshair-customized/ * Customized crosshairs * @sample {highstock} highcharts/xaxis/crosshair-customized/ * Customized crosshairs * @sample {highmaps} highcharts/xaxis/crosshair-customized/ * Customized crosshairs * * @type {number} * @default 1 * @since 4.1 * @apioption xAxis.crosshair.width */ /** * The Z index of the crosshair. Higher Z indices allow drawing the * crosshair on top of the series or behind the grid lines. * * @type {number} * @default 2 * @since 4.1 * @apioption xAxis.crosshair.zIndex */ /** * Whether to pan axis. If `chart.panning` is enabled, the option * allows to disable panning on an individual axis. */ panningEnabled: true, /** * The Z index for the axis group. */ zIndex: 2, /** * Whether to zoom axis. If `chart.zoomType` is set, the option allows * to disable zooming on an individual axis. * * @sample {highcharts} highcharts/xaxis/zoomenabled/ * Zoom enabled is false */ zoomEnabled: true, /** * For a datetime axis, the scale will automatically adjust to the * appropriate unit. This member gives the default string * representations used for each unit. For intermediate values, * different units may be used, for example the `day` unit can be used * on midnight and `hour` unit be used for intermediate values on the * same axis. * * For an overview of the replacement codes, see * [dateFormat](/class-reference/Highcharts#.dateFormat). * * Defaults to: * ```js * { * millisecond: '%H:%M:%S.%L', * second: '%H:%M:%S', * minute: '%H:%M', * hour: '%H:%M', * day: '%e. %b', * week: '%e. %b', * month: '%b \'%y', * year: '%Y' * } * ``` * * @sample {highcharts} highcharts/xaxis/datetimelabelformats/ * Different day format on X axis * @sample {highstock} stock/xaxis/datetimelabelformats/ * More information in x axis labels * * @declare Highcharts.AxisDateTimeLabelFormatsOptions * @product highcharts highstock gantt */ dateTimeLabelFormats: { /** * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject * @type {string|*} */ millisecond: { main: '%H:%M:%S.%L', range: false }, /** * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject * @type {string|*} */ second: { main: '%H:%M:%S', range: false }, /** * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject * @type {string|*} */ minute: { main: '%H:%M', range: false }, /** * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject * @type {string|*} */ hour: { main: '%H:%M', range: false }, /** * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject * @type {string|*} */ day: { main: '%e. %b' }, /** * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject * @type {string|*} */ week: { main: '%e. %b' }, /** * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject * @type {string|*} */ month: { main: '%b \'%y' }, /** * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject * @type {string|*} */ year: { main: '%Y' } }, /** * Whether to force the axis to end on a tick. Use this option with * the `maxPadding` option to control the axis end. * * @productdesc {highstock} * In Highcharts Stock, `endOnTick` is always `false` when the navigator * is enabled, to prevent jumpy scrolling. * * @sample {highcharts} highcharts/chart/reflow-true/ * True by default * @sample {highcharts} highcharts/yaxis/endontick/ * False * @sample {highstock} stock/demo/basic-line/ * True by default * @sample {highstock} stock/xaxis/endontick/ * False * * @since 1.2.0 */ endOnTick: false, /** * Event handlers for the axis. * * @type {*} * @apioption xAxis.events */ /** * An event fired after the breaks have rendered. * * @see [breaks](#xAxis.breaks) * * @sample {highcharts} highcharts/axisbreak/break-event/ * AfterBreak Event * * @type {Highcharts.AxisEventCallbackFunction} * @since 4.1.0 * @product highcharts gantt * @apioption xAxis.events.afterBreaks */ /** * As opposed to the `setExtremes` event, this event fires after the * final min and max values are computed and corrected for `minRange`. * * Fires when the minimum and maximum is set for the axis, either by * calling the `.setExtremes()` method or by selecting an area in the * chart. One parameter, `event`, is passed to the function, containing * common event information. * * The new user set minimum and maximum values can be found by * `event.min` and `event.max`. These reflect the axis minimum and * maximum in axis values. The actual data extremes are found in * `event.dataMin` and `event.dataMax`. * * @type {Highcharts.AxisSetExtremesEventCallbackFunction} * @since 2.3 * @context Highcharts.Axis * @apioption xAxis.events.afterSetExtremes */ /** * An event fired when a break from this axis occurs on a point. * * @see [breaks](#xAxis.breaks) * * @sample {highcharts} highcharts/axisbreak/break-visualized/ * Visualization of a Break * * @type {Highcharts.AxisPointBreakEventCallbackFunction} * @since 4.1.0 * @product highcharts gantt * @context Highcharts.Axis * @apioption xAxis.events.pointBreak */ /** * An event fired when a point falls inside a break from this axis. * * @type {Highcharts.AxisPointBreakEventCallbackFunction} * @product highcharts highstock gantt * @context Highcharts.Axis * @apioption xAxis.events.pointInBreak */ /** * Fires when the minimum and maximum is set for the axis, either by * calling the `.setExtremes()` method or by selecting an area in the * chart. One parameter, `event`, is passed to the function, * containing common event information. * * The new user set minimum and maximum values can be found by * `event.min` and `event.max`. These reflect the axis minimum and * maximum in data values. When an axis is zoomed all the way out from * the "Reset zoom" button, `event.min` and `event.max` are null, and * the new extremes are set based on `this.dataMin` and `this.dataMax`. * * @sample {highstock} stock/xaxis/events-setextremes/ * Log new extremes on x axis * * @type {Highcharts.AxisSetExtremesEventCallbackFunction} * @since 1.2.0 * @context Highcharts.Axis * @apioption xAxis.events.setExtremes */ /** * The lowest allowed value for automatically computed axis extremes. * * @see [ceiling](#yAxis.ceiling) * * @sample {highcharts} highcharts/yaxis/floor-ceiling/ * Floor and ceiling * @sample {highstock} stock/demo/lazy-loading/ * Prevent negative stock price on Y axis * * @type {number} * @since 4.0 * @product highcharts highstock gantt * @apioption xAxis.floor */ /** * The dash or dot style of the grid lines. For possible values, see * [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/). * * @sample {highcharts} highcharts/yaxis/gridlinedashstyle/ * Long dashes * @sample {highstock} stock/xaxis/gridlinedashstyle/ * Long dashes * * @type {Highcharts.DashStyleValue} * @since 1.2 */ gridLineDashStyle: 'Solid', /** * The Z index of the grid lines. * * @sample {highcharts|highstock} highcharts/xaxis/gridzindex/ * A Z index of 4 renders the grid above the graph * * @product highcharts highstock gantt */ gridZIndex: 1, /** * An id for the axis. This can be used after render time to get * a pointer to the axis object through `chart.get()`. * * @sample {highcharts} highcharts/xaxis/id/ * Get the object * @sample {highstock} stock/xaxis/id/ * Get the object * * @type {string} * @since 1.2.0 * @apioption xAxis.id */ /** * The axis labels show the number or category for each tick. * * Since v8.0.0: Labels are animated in categorized x-axis with * updating data if `tickInterval` and `step` is set to 1. * * @productdesc {highmaps} * X and Y axis labels are by default disabled in Highmaps, but the * functionality is inherited from Highcharts and used on `colorAxis`, * and can be enabled on X and Y axes too. */ labels: { /** * What part of the string the given position is anchored to. * If `left`, the left side of the string is at the axis position. * Can be one of `"left"`, `"center"` or `"right"`. Defaults to * an intelligent guess based on which side of the chart the axis * is on and the rotation of the label. * * @see [reserveSpace](#xAxis.labels.reserveSpace) * * @sample {highcharts} highcharts/xaxis/labels-align-left/ * Left * @sample {highcharts} highcharts/xaxis/labels-align-right/ * Right * @sample {highcharts} highcharts/xaxis/labels-reservespace-true/ * Left-aligned labels on a vertical category axis * * @type {Highcharts.AlignValue} * @apioption xAxis.labels.align */ /** * Whether to allow the axis labels to overlap. * When false, overlapping labels are hidden. * * @sample {highcharts} highcharts/xaxis/labels-allowoverlap-true/ * X axis labels overlap enabled * * @type {boolean} * @default false * @apioption xAxis.labels.allowOverlap * */ /** * For horizontal axes, the allowed degrees of label rotation * to prevent overlapping labels. If there is enough space, * labels are not rotated. As the chart gets narrower, it * will start rotating the labels -45 degrees, then remove * every second label and try again with rotations 0 and -45 etc. * Set it to `undefined` to disable rotation, which will * cause the labels to word-wrap if possible. Defaults to `[-45]`` * on bottom and top axes, `undefined` on left and right axes. * * @sample {highcharts|highstock} highcharts/xaxis/labels-autorotation-default/ * Default auto rotation of 0 or -45 * @sample {highcharts|highstock} highcharts/xaxis/labels-autorotation-0-90/ * Custom graded auto rotation * * @type {Array<number>} * @default undefined * @since 4.1.0 * @product highcharts highstock gantt * @apioption xAxis.labels.autoRotation */ autoRotation: void 0, /** * When each category width is more than this many pixels, we don't * apply auto rotation. Instead, we lay out the axis label with word * wrap. A lower limit makes sense when the label contains multiple * short words that don't extend the available horizontal space for * each label. * * @sample {highcharts} highcharts/xaxis/labels-autorotationlimit/ * Lower limit * * @since 4.1.5 * @product highcharts gantt */ autoRotationLimit: 80, /** * Polar charts only. The label's pixel distance from the perimeter * of the plot area. * * @type {number} * @default undefined * @product highcharts gantt */ distance: void 0, /** * Enable or disable the axis labels. * * @sample {highcharts} highcharts/xaxis/labels-enabled/ * X axis labels disabled * @sample {highstock} stock/xaxis/labels-enabled/ * X axis labels disabled * * @default {highcharts|highstock|gantt} true * @default {highmaps} false */ enabled: true, /** * A format string for the axis label. The context is available as * format string variables. For example, you can use `{text}` to * insert the default formatted text. The recommended way of adding * units for the label is using `text`, for example `{text} km`. * * To add custom numeric or datetime formatting, use `{value}` with * formatting, for example `{value:.1f}` or `{value:%Y-%m-%d}`. * * See * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) * for more examples of formatting. * * The default value is not specified due to the dynamic * nature of the default implementation. * * @sample {highcharts|highstock} highcharts/yaxis/labels-format/ * Add units to Y axis label * @sample {highcharts} highcharts/xaxis/labels-format-linked/ * Linked category names * @sample {highcharts} highcharts/xaxis/labels-format-custom/ * Custom number format * * @type {string} * @since 3.0 * @apioption xAxis.labels.format */ /** * Callback JavaScript function to format the label. The value * is given by `this.value`. Additional properties for `this` are * `axis`, `chart`, `isFirst`, `isLast` and `text` which holds the * value of the default formatter. * * Defaults to a built in function returning a formatted string * depending on whether the axis is `category`, `datetime`, * `numeric` or other. * * @sample {highcharts} highcharts/xaxis/labels-formatter-linked/ * Linked category names * @sample {highcharts} highcharts/xaxis/labels-formatter-extended/ * Modified numeric labels * @sample {highstock} stock/xaxis/labels-formatter/ * Added units on Y axis * * @type {Highcharts.AxisLabelsFormatterCallbackFunction} * @apioption xAxis.labels.formatter */ /** * The number of pixels to indent the labels per level in a treegrid * axis. * * @sample gantt/treegrid-axis/demo * Indentation 10px by default. * @sample gantt/treegrid-axis/indentation-0px * Indentation set to 0px. * * @product gantt */ indentation: 10, /** * Horizontal axis only. When `staggerLines` is not set, * `maxStaggerLines` defines how many lines the axis is allowed to * add to automatically avoid overlapping X labels. Set to `1` to * disable overlap detection. * * @deprecated * @type {number} * @default 5 * @since 1.3.3 * @apioption xAxis.labels.maxStaggerLines */ /** * How to handle overflowing labels on horizontal axis. If set to * `"allow"`, it will not be aligned at all. By default it * `"justify"` labels inside the chart area. If there is room to * move it, it will be aligned to the edge, else it will be removed. * * @since 2.2.5 * @validvalue ["allow", "justify"] */ overflow: 'justify', /** * The pixel padding for axis labels, to ensure white space between * them. * * @product highcharts gantt */ padding: 5, /** * Whether to reserve space for the labels. By default, space is * reserved for the labels in these cases: * * * On all horizontal axes. * * On vertical axes if `label.align` is `right` on a left-side * axis or `left` on a right-side axis. * * On vertical axes if `label.align` is `center`. * * This can be turned off when for example the labels are rendered * inside the plot area instead of outside. * * @see [labels.align](#xAxis.labels.align) * * @sample {highcharts} highcharts/xaxis/labels-reservespace/ * No reserved space, labels inside plot * @sample {highcharts} highcharts/xaxis/labels-reservespace-true/ * Left-aligned labels on a vertical category axis * * @type {boolean} * @since 4.1.10 * @product highcharts gantt * @apioption xAxis.labels.reserveSpace */ reserveSpace: void 0, /** * Rotation of the labels in degrees. When `undefined`, the * `autoRotation` option takes precedence. * * @sample {highcharts} highcharts/xaxis/labels-rotation/ * X axis labels rotated 90° * * @type {number} * @default 0 * @apioption xAxis.labels.rotation */ rotation: void 0, /** * Horizontal axes only. The number of lines to spread the labels * over to make room or tighter labels. 0 disables staggering. * * @sample {highcharts} highcharts/xaxis/labels-staggerlines/ * Show labels over two lines * @sample {highstock} stock/xaxis/labels-staggerlines/ * Show labels over two lines * * @since 2.1 * @apioption xAxis.labels.staggerLines */ staggerLines: 0, /** * To show only every _n_'th label on the axis, set the step to _n_. * Setting the step to 2 shows every other label. * * By default, when 0, the step is calculated automatically to avoid * overlap. To prevent this, set it to 1\. This usually only * happens on a category axis, and is often a sign that you have * chosen the wrong axis type. * * Read more at * [Axis docs](https://www.highcharts.com/docs/chart-concepts/axes) * => What axis should I use? * * @sample {highcharts} highcharts/xaxis/labels-step/ * Showing only every other axis label on a categorized * x-axis * @sample {highcharts} highcharts/xaxis/labels-step-auto/ * Auto steps on a category axis * * @since 2.1 */ step: 0, /** * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) * to render the labels. */ useHTML: false, /** * The x position offset of all labels relative to the tick * positions on the axis. * * @sample {highcharts} highcharts/xaxis/labels-x/ * Y axis labels placed on grid lines */ x: 0, /** * The y position offset of all labels relative to the tick * positions on the axis. The default makes it adapt to the font * size of the bottom axis. * * @sample {highcharts} highcharts/xaxis/labels-x/ * Y axis labels placed on grid lines * * @type {number} * @apioption xAxis.labels.y */ /** * The Z index for the axis labels. */ zIndex: 7, /** * CSS styles for the label. Use `whiteSpace: 'nowrap'` to prevent * wrapping of category labels. Use `textOverflow: 'none'` to * prevent ellipsis (dots). * * In styled mode, the labels are styled with the * `.highcharts-axis-labels` class. * * @sample {highcharts} highcharts/xaxis/labels-style/ * Red X axis labels * * @type {Highcharts.CSSObject} */ style: { /** @internal */ color: Palette.neutralColor60, /** @internal */ cursor: 'default', /** @internal */ fontSize: '11px' } }, /** * The left position as the horizontal axis. If it's a number, it is * interpreted as pixel position relative to the chart. * * Since Highcharts v5.0.13: If it's a percentage string, it is * interpreted as percentages of the plot width, offset from plot area * left. * * @type {number|string} * @product highcharts highstock * @apioption xAxis.left */ /** * The top position as the vertical axis. If it's a number, it is * interpreted as pixel position relative to the chart. * * Since Highcharts 2: If it's a percentage string, it is interpreted * as percentages of the plot height, offset from plot area top. * * @type {number|string} * @product highcharts highstock * @apioption xAxis.top */ /** * Index of another axis that this axis is linked to. When an axis is * linked to a master axis, it will take the same extremes as * the master, but as assigned by min or max or by setExtremes. * It can be used to show additional info, or to ease reading the * chart by duplicating the scales. * * @sample {highcharts} highcharts/xaxis/linkedto/ * Different string formats of the same date * @sample {highcharts} highcharts/yaxis/linkedto/ * Y values on both sides * * @type {number} * @since 2.0.2 * @product highcharts highstock gantt * @apioption xAxis.linkedTo */ /** * The maximum value of the axis. If `null`, the max value is * automatically calculated. * * If the [endOnTick](#yAxis.endOnTick) option is true, the `max` value * might be rounded up. * * If a [tickAmount](#yAxis.tickAmount) is set, the axis may be extended * beyond the set max in order to reach the given number of ticks. The * same may happen in a chart with multiple axes, determined by [chart. * alignTicks](#chart), where a `tickAmount` is applied internally. * * @sample {highcharts} highcharts/yaxis/max-200/ * Y axis max of 200 * @sample {highcharts} highcharts/yaxis/max-logarithmic/ * Y axis max on logarithmic axis * @sample {highstock} stock/xaxis/min-max/ * Fixed min and max on X axis * @sample {highmaps} maps/axis/min-max/ * Pre-zoomed to a specific area * * @type {number|null} * @apioption xAxis.max */ /** * Padding of the max value relative to the length of the axis. A * padding of 0.05 will make a 100px axis 5px longer. This is useful * when you don't want the highest data value to appear on the edge * of the plot area. When the axis' `max` option is set or a max extreme * is set using `axis.setExtremes()`, the maxPadding will be ignored. * * @productdesc {highstock} * For an [ordinal](#xAxis.ordinal) axis, `minPadding` and `maxPadding` * are ignored. Use [overscroll](#xAxis.overscroll) instead. * * @sample {highcharts} highcharts/yaxis/maxpadding/ * Max padding of 0.25 on y axis * @sample {highstock} stock/xaxis/minpadding-maxpadding/ * Greater min- and maxPadding * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/ * Add some padding * * @default {highcharts} 0.01 * @default {highstock|highmaps} 0 * @since 1.2.0 */ maxPadding: 0.01, /** * Deprecated. Use `minRange` instead. * * @deprecated * @type {number} * @product highcharts highstock * @apioption xAxis.maxZoom */ /** * The minimum value of the axis. If `null` the min value is * automatically calculated. * * If the [startOnTick](#yAxis.startOnTick) option is true (default), * the `min` value might be rounded down. * * The automatically calculated minimum value is also affected by * [floor](#yAxis.floor), [softMin](#yAxis.softMin), * [minPadding](#yAxis.minPadding), [minRange](#yAxis.minRange) * as well as [series.threshold](#plotOptions.series.threshold) * and [series.softThreshold](#plotOptions.series.softThreshold). * * @sample {highcharts} highcharts/yaxis/min-startontick-false/ * -50 with startOnTick to false * @sample {highcharts} highcharts/yaxis/min-startontick-true/ * -50 with startOnTick true by default * @sample {highstock} stock/xaxis/min-max/ * Set min and max on X axis * @sample {highmaps} maps/axis/min-max/ * Pre-zoomed to a specific area * * @type {number|null} * @apioption xAxis.min */ /** * The dash or dot style of the minor grid lines. For possible values, * see [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/). * * @sample {highcharts} highcharts/yaxis/minorgridlinedashstyle/ * Long dashes on minor grid lines * @sample {highstock} stock/xaxis/minorgridlinedashstyle/ * Long dashes on minor grid lines * * @type {Highcharts.DashStyleValue} * @since 1.2 */ minorGridLineDashStyle: 'Solid', /** * Specific tick interval in axis units for the minor ticks. On a linear * axis, if `"auto"`, the minor tick interval is calculated as a fifth * of the tickInterval. If `null` or `undefined`, minor ticks are not * shown. * * On logarithmic axes, the unit is the power of the value. For example, * setting the minorTickInterval to 1 puts one tick on each of 0.1, 1, * 10, 100 etc. Setting the minorTickInterval to 0.1 produces 9 ticks * between 1 and 10, 10 and 100 etc. * * If user settings dictate minor ticks to become too dense, they don't * make sense, and will be ignored to prevent performance problems. * * @sample {highcharts} highcharts/yaxis/minortickinterval-null/ * Null by default * @sample {highcharts} highcharts/yaxis/minortickinterval-5/ * 5 units * @sample {highcharts} highcharts/yaxis/minortickinterval-log-auto/ * "auto" * @sample {highcharts} highcharts/yaxis/minortickinterval-log/ * 0.1 * @sample {highstock} stock/demo/basic-line/ * Null by default * @sample {highstock} stock/xaxis/minortickinterval-auto/ * "auto" * * @type {number|string|null} * @apioption xAxis.minorTickInterval */ /** * The pixel length of the minor tick marks. * * @sample {highcharts} highcharts/yaxis/minorticklength/ * 10px on Y axis * @sample {highstock} stock/xaxis/minorticks/ * 10px on Y axis */ minorTickLength: 2, /** * The position of the minor tick marks relative to the axis line. * Can be one of `inside` and `outside`. * * @sample {highcharts} highcharts/yaxis/minortickposition-outside/ * Outside by default * @sample {highcharts} highcharts/yaxis/minortickposition-inside/ * Inside * @sample {highstock} stock/xaxis/minorticks/ * Inside * * @validvalue ["inside", "outside"] */ minorTickPosition: 'outside', /** * Enable or disable minor ticks. Unless * [minorTickInterval](#xAxis.minorTickInterval) is set, the tick * interval is calculated as a fifth of the `tickInterval`. * * On a logarithmic axis, minor ticks are laid out based on a best * guess, attempting to enter approximately 5 minor ticks between * each major tick. * * Prior to v6.0.0, ticks were unabled in auto layout by setting * `minorTickInterval` to `"auto"`. * * @productdesc {highcharts} * On axes using [categories](#xAxis.categories), minor ticks are not * supported. * * @sample {highcharts} highcharts/yaxis/minorticks-true/ * Enabled on linear Y axis * * @type {boolean} * @default false * @since 6.0.0 * @apioption xAxis.minorTicks */ /** * The pixel width of the minor tick mark. * * @sample {highcharts} highcharts/yaxis/minortickwidth/ * 3px width * @sample {highstock} stock/xaxis/minorticks/ * 1px width * * @type {number} * @default 0 * @apioption xAxis.minorTickWidth */ /** * Padding of the min value relative to the length of the axis. A * padding of 0.05 will make a 100px axis 5px longer. This is useful * when you don't want the lowest data value to appear on the edge * of the plot area. When the axis' `min` option is set or a min extreme * is set using `axis.setExtremes()`, the minPadding will be ignored. * * @productdesc {highstock} * For an [ordinal](#xAxis.ordinal) axis, `minPadding` and `maxPadding` * are ignored. Use [overscroll](#xAxis.overscroll) instead. * * @sample {highcharts} highcharts/yaxis/minpadding/ * Min padding of 0.2 * @sample {highstock} stock/xaxis/minpadding-maxpadding/ * Greater min- and maxPadding * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/ * Add some padding * * @default {highcharts} 0.01 * @default {highstock|highmaps} 0 * @since 1.2.0 * @product highcharts highstock gantt */ minPadding: 0.01, /** * The minimum range to display on this axis. The entire axis will not * be allowed to span over a smaller interval than this. For example, * for a datetime axis the main unit is milliseconds. If minRange is * set to 3600000, you can't zoom in more than to one hour. * * The default minRange for the x axis is five times the smallest * interval between any of the data points. * * On a logarithmic axis, the unit for the minimum range is the power. * So a minRange of 1 means that the axis can be zoomed to 10-100, * 100-1000, 1000-10000 etc. * * **Note**: The `minPadding`, `maxPadding`, `startOnTick` and * `endOnTick` settings also affect how the extremes of the axis * are computed. * * @sample {highcharts} highcharts/xaxis/minrange/ * Minimum range of 5 * @sample {highstock} stock/xaxis/minrange/ * Max zoom of 6 months overrides user selections * @sample {highmaps} maps/axis/minrange/ * Minimum range of 1000 * * @type {number} * @apioption xAxis.minRange */ /** * The minimum tick interval allowed in axis values. For example on * zooming in on an axis with daily data, this can be used to prevent * the axis from showing hours. Defaults to the closest distance between * two points on the axis. * * @type {number} * @since 2.3.0 * @apioption xAxis.minTickInterval */ /** * The distance in pixels from the plot area to the axis line. * A positive offset moves the axis with it's line, labels and ticks * away from the plot area. This is typically used when two or more * axes are displayed on the same side of the plot. With multiple * axes the offset is dynamically adjusted to avoid collision, this * can be overridden by setting offset explicitly. * * @sample {highcharts} highcharts/yaxis/offset/ * Y axis offset of 70 * @sample {highcharts} highcharts/yaxis/offset-centered/ * Axes positioned in the center of the plot * @sample {highstock} stock/xaxis/offset/ * Y axis offset by 70 px * * @type {number} */ offset: void 0, /** * Whether to display the axis on the opposite side of the normal. The * normal is on the left side for vertical axes and bottom for * horizontal, so the opposite sides will be right and top respectively. * This is typically used with dual or multiple axes. * * @sample {highcharts} highcharts/yaxis/opposite/ * Secondary Y axis opposite * @sample {highstock} stock/xaxis/opposite/ * Y axis on left side * * @default {highcharts|highstock|highmaps} false * @default {gantt} true */ opposite: false, /** * In an ordinal axis, the points are equally spaced in the chart * regardless of the actual time or x distance between them. This means * that missing data periods (e.g. nights or weekends for a stock chart) * will not take up space in the chart. * Having `ordinal: false` will show any gaps created by the `gapSize` * setting proportionate to their duration. * * In stock charts the X axis is ordinal by default, unless * the boost module is used and at least one of the series' data length * exceeds the [boostThreshold](#series.line.boostThreshold). * * For an ordinal axis, `minPadding` and `maxPadding` are ignored. Use * [overscroll](#xAxis.overscroll) instead. * * @sample {highstock} stock/xaxis/ordinal-true/ * True by default * @sample {highstock} stock/xaxis/ordinal-false/ * False * * @see [overscroll](#xAxis.overscroll) * * @type {boolean} * @default true * @since 1.1 * @product highstock * @apioption xAxis.ordinal */ /** * Additional range on the right side of the xAxis. Works similar to * `xAxis.maxPadding`, but value is set in milliseconds. Can be set for * both main `xAxis` and the navigator's `xAxis`. * * @sample {highstock} stock/xaxis/overscroll/ * One minute overscroll with live data * * @type {number} * @default 0 * @since 6.0.0 * @product highstock * @apioption xAxis.overscroll */ /** * Refers to the index in the [panes](#panes) array. Used for circular * gauges and polar charts. When the option is not set then first pane * will be used. * * @sample highcharts/demo/gauge-vu-meter * Two gauges with different center * * @type {number} * @product highcharts * @apioption xAxis.pane */ /** * The zoomed range to display when only defining one or none of `min` * or `max`. For example, to show the latest month, a range of one month * can be set. * * @sample {highstock} stock/xaxis/range/ * Setting a zoomed range when the rangeSelector is disabled * * @type {number} * @product highstock * @apioption xAxis.range */ /** * Whether to reverse the axis so that the highest number is closest * to the origin. If the chart is inverted, the x axis is reversed by * default. * * @sample {highcharts} highcharts/yaxis/reversed/ * Reversed Y axis * @sample {highstock} stock/xaxis/reversed/ * Reversed Y axis * * @type {boolean} * @default undefined * @apioption xAxis.reversed */ reversed: void 0, /** * This option determines how stacks should be ordered within a group. * For example reversed xAxis also reverses stacks, so first series * comes last in a group. To keep order like for non-reversed xAxis * enable this option. * * @sample {highcharts} highcharts/xaxis/reversedstacks/ * Reversed stacks comparison * @sample {highstock} highcharts/xaxis/reversedstacks/ * Reversed stacks comparison * * @since 6.1.1 * @product highcharts highstock */ reversedStacks: false, /** * An optional scrollbar to display on the X axis in response to * limiting the minimum and maximum of the axis values. * * In styled mode, all the presentational options for the scrollbar are * replaced by the classes `.highcharts-scrollbar-thumb`, * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`, * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`. * * @sample {highstock} stock/yaxis/heatmap-scrollbars/ * Heatmap with both scrollbars * * @extends scrollbar * @since 4.2.6 * @product highstock * @apioption xAxis.scrollbar */ /** * Whether to show the axis line and title when the axis has no data. * * @sample {highcharts} highcharts/yaxis/showempty/ * When clicking the legend to hide series, one axis preserves * line and title, the other doesn't * @sample {highstock} highcharts/yaxis/showempty/ * When clicking the legend to hide series, one axis preserves * line and title, the other doesn't * * @since 1.1 */ showEmpty: true, /** * Whether to show the first tick label. * * @sample {highcharts} highcharts/xaxis/showfirstlabel-false/ * Set to false on X axis * @sample {highstock} stock/xaxis/showfirstlabel/ * Labels below plot lines on Y axis */ showFirstLabel: true, /** * Whether to show the last tick label. Defaults to `true` on cartesian * charts, and `false` on polar charts. * * @sample {highcharts} highcharts/xaxis/showlastlabel-true/ * Set to true on X axis * @sample {highstock} stock/xaxis/showfirstlabel/ * Labels below plot lines on Y axis * * @product highcharts highstock gantt */ showLastLabel: true, /** * A soft maximum for the axis. If the series data maximum is less than * this, the axis will stay at this maximum, but if the series data * maximum is higher, the axis will flex to show all data. * * @sample highcharts/yaxis/softmin-softmax/ * Soft min and max * * @type {number} * @since 5.0.1 * @product highcharts highstock gantt * @apioption xAxis.softMax */ /** * A soft minimum for the axis. If the series data minimum is greater * than this, the axis will stay at this minimum, but if the series * data minimum is lower, the axis will flex to show all data. * * @sample highcharts/yaxis/softmin-softmax/ * Soft min and max * * @type {number} * @since 5.0.1 * @product highcharts highstock gantt * @apioption xAxis.softMin */ /** * For datetime axes, this decides where to put the tick between weeks. * 0 = Sunday, 1 = Monday. * * @sample {highcharts} highcharts/xaxis/startofweek-monday/ * Monday by default * @sample {highcharts} highcharts/xaxis/startofweek-sunday/ * Sunday * @sample {highstock} stock/xaxis/startofweek-1 * Monday by default * @sample {highstock} stock/xaxis/startofweek-0 * Sunday * * @product highcharts highstock gantt */ startOfWeek: 1, /** * Whether to force the axis to start on a tick. Use this option with * the `minPadding` option to control the axis start. * * @productdesc {highstock} * In Highcharts Stock, `startOnTick` is always `false` when * the navigator is enabled, to prevent jumpy scrolling. * * @sample {highcharts} highcharts/xaxis/startontick-false/ * False by default * @sample {highcharts} highcharts/xaxis/startontick-true/ * True * * @since 1.2.0 */ startOnTick: false, /** * The amount of ticks to draw on the axis. This opens up for aligning * the ticks of multiple charts or panes within a chart. This option * overrides the `tickPixelInterval` option. * * This option only has an effect on linear axes. Datetime, logarithmic * or category axes are not affected. * * @sample {highcharts} highcharts/yaxis/tickamount/ * 8 ticks on Y axis * @sample {highstock} highcharts/yaxis/tickamount/ * 8 ticks on Y axis * * @type {number} * @since 4.1.0 * @product highcharts highstock gantt * @apioption xAxis.tickAmount */ /** * The interval of the tick marks in axis units. When `undefined`, the * tick interval is computed to approximately follow the * [tickPixelInterval](#xAxis.tickPixelInterval) on linear and datetime * axes. On categorized axes, a `undefined` tickInterval will default to * 1, one category. Note that datetime axes are based on milliseconds, * so for example an interval of one day is expressed as * `24 * 3600 * 1000`. * * On logarithmic axes, the tickInterval is based on powers, so a * tickInterval of 1 means one tick on each of 0.1, 1, 10, 100 etc. A * tickInterval of 2 means a tick of 0.1, 10, 1000 etc. A tickInterval * of 0.2 puts a tick on 0.1, 0.2, 0.4, 0.6, 0.8, 1, 2, 4, 6, 8, 10, 20, * 40 etc. * * * If the tickInterval is too dense for labels to be drawn, Highcharts * may remove ticks. * * If the chart has multiple axes, the [alignTicks](#chart.alignTicks) * option may interfere with the `tickInterval` setting. * * @see [tickPixelInterval](#xAxis.tickPixelInterval) * @see [tickPositions](#xAxis.tickPositions) * @see [tickPositioner](#xAxis.tickPositioner) * * @sample {highcharts} highcharts/xaxis/tickinterval-5/ * Tick interval of 5 on a linear axis * @sample {highstock} stock/xaxis/tickinterval/ * Tick interval of 0.01 on Y axis * * @type {number} * @apioption xAxis.tickInterval */ /** * The pixel length of the main tick marks. * * @sample {highcharts} highcharts/xaxis/ticklength/ * 20 px tick length on the X axis * @sample {highstock} stock/xaxis/ticks/ * Formatted ticks on X axis */ tickLength: 10, /** * If tickInterval is `null` this option sets the approximate pixel * interval of the tick marks. Not applicable to categorized axis. * * The tick interval is also influenced by the [minTickInterval]( * #xAxis.minTickInterval) option, that, by default prevents ticks from * being denser than the data points. * * @see [tickInterval](#xAxis.tickInterval) * @see [tickPositioner](#xAxis.tickPositioner) * @see [tickPositions](#xAxis.tickPositions) * * @sample {highcharts} highcharts/xaxis/tickpixelinterval-50/ * 50 px on X axis * @sample {highstock} stock/xaxis/tickpixelinterval/ * 200 px on X axis */ tickPixelInterval: 100, /** * For categorized axes only. If `on` the tick mark is placed in the * center of the category, if `between` the tick mark is placed between * categories. The default is `between` if the `tickInterval` is 1, else * `on`. * * @sample {highcharts} highcharts/xaxis/tickmarkplacement-between/ * "between" by default * @sample {highcharts} highcharts/xaxis/tickmarkplacement-on/ * "on" * * @product highcharts gantt * @validvalue ["on", "between"] */ tickmarkPlacement: 'between', /** * The position of the major tick marks relative to the axis line. * Can be one of `inside` and `outside`. * * @sample {highcharts} highcharts/xaxis/tickposition-outside/ * "outside" by default * @sample {highcharts} highcharts/xaxis/tickposition-inside/ * "inside" * @sample {highstock} stock/xaxis/ticks/ * Formatted ticks on X axis * * @validvalue ["inside", "outside"] */ tickPosition: 'outside', /** * A callback function returning array defining where the ticks are * laid out on the axis. This overrides the default behaviour of * [tickPixelInterval](#xAxis.tickPixelInterval) and [tickInterval]( * #xAxis.tickInterval). The automatic tick positions are accessible * through `this.tickPositions` and can be modified by the callback. * * @see [tickPositions](#xAxis.tickPositions) * * @sample {highcharts} highcharts/xaxis/tickpositions-tickpositioner/ * Demo of tickPositions and tickPositioner * @sample {highstock} highcharts/xaxis/tickpositions-tickpositioner/ * Demo of tickPositions and tickPositioner * * @type {Highcharts.AxisTickPositionerCallbackFunction} * @apioption xAxis.tickPositioner */ /** * An array defining where the ticks are laid out on the axis. This * overrides the default behaviour of [tickPixelInterval]( * #xAxis.tickPixelInterval) and [tickInterval](#xAxis.tickInterval). * * @see [tickPositioner](#xAxis.tickPositioner) * * @sample {highcharts} highcharts/xaxis/tickpositions-tickpositioner/ * Demo of tickPositions and tickPositioner * @sample {highstock} highcharts/xaxis/tickpositions-tickpositioner/ * Demo of tickPositions and tickPositioner * * @type {Array<number>} * @apioption xAxis.tickPositions */ /** * The pixel width of the major tick marks. Defaults to 0 on category * axes, otherwise 1. * * In styled mode, the stroke width is given in the `.highcharts-tick` * class, but in order for the element to be generated on category axes, * the option must be explicitly set to 1. * * @sample {highcharts} highcharts/xaxis/tickwidth/ * 10 px width * @sample {highcharts} highcharts/css/axis-grid/ * Styled mode * @sample {highstock} stock/xaxis/ticks/ * Formatted ticks on X axis * @sample {highstock} highcharts/css/axis-grid/ * Styled mode * * @type {undefined|number} * @default {highstock} 1 * @default {highmaps} 0 * @apioption xAxis.tickWidth */ /** * The axis title, showing next to the axis line. * * @productdesc {highmaps} * In Highmaps, the axis is hidden by default, but adding an axis title * is still possible. X axis and Y axis titles will appear at the bottom * and left by default. */ title: { /** * Alignment of the title relative to the axis values. Possible * values are "low", "middle" or "high". * * @sample {highcharts} highcharts/xaxis/title-align-low/ * "low" * @sample {highcharts} highcharts/xaxis/title-align-center/ * "middle" by default * @sample {highcharts} highcharts/xaxis/title-align-high/ * "high" * @sample {highcharts} highcharts/yaxis/title-offset/ * Place the Y axis title on top of the axis * @sample {highstock} stock/xaxis/title-align/ * Aligned to "high" value * * @type {Highcharts.AxisTitleAlignValue} */ align: 'middle', /** * Deprecated. Set the `text` to `undefined` to disable the title. * * @deprecated * @type {boolean} * @product highcharts * @apioption xAxis.title.enabled */ /** * The pixel distance between the axis labels or line and the title. * Defaults to 0 for horizontal axes, 10 for vertical * * @sample {highcharts} highcharts/xaxis/title-margin/ * Y axis title margin of 60 * * @type {number} * @apioption xAxis.title.margin */ /** * The distance of the axis title from the axis line. By default, * this distance is computed from the offset width of the labels, * the labels' distance from the axis and the title's margin. * However when the offset option is set, it overrides all this. * * @sample {highcharts} highcharts/yaxis/title-offset/ * Place the axis title on top of the axis * @sample {highstock} highcharts/yaxis/title-offset/ * Place the axis title on top of the Y axis * * @type {number} * @since 2.2.0 * @apioption xAxis.title.offset */ /** * Whether to reserve space for the title when laying out the axis. * * @type {boolean} * @default true * @since 5.0.11 * @product highcharts highstock gantt * @apioption xAxis.title.reserveSpace */ /** * The rotation of the text in degrees. 0 is horizontal, 270 is * vertical reading from bottom to top. * * @sample {highcharts} highcharts/yaxis/title-offset/ * Horizontal */ rotation: 0, /** * The actual text of the axis title. It can contain basic HTML tags * like `b`, `i` and `span` with style. * * @sample {highcharts} highcharts/xaxis/title-text/ * Custom HTML * @sample {highstock} stock/xaxis/title-text/ * Titles for both axes * * @type {string|null} * @apioption xAxis.title.text */ /** * Alignment of the text, can be `"left"`, `"right"` or `"center"`. * Default alignment depends on the * [title.align](xAxis.title.align): * * Horizontal axes: * - for `align` = `"low"`, `textAlign` is set to `left` * - for `align` = `"middle"`, `textAlign` is set to `center` * - for `align` = `"high"`, `textAlign` is set to `right` * * Vertical axes: * - for `align` = `"low"` and `opposite` = `true`, `textAlign` is * set to `right` * - for `align` = `"low"` and `opposite` = `false`, `textAlign` is * set to `left` * - for `align` = `"middle"`, `textAlign` is set to `center` * - for `align` = `"high"` and `opposite` = `true` `textAlign` is * set to `left` * - for `align` = `"high"` and `opposite` = `false` `textAlign` is * set to `right` * * @type {Highcharts.AlignValue} * @apioption xAxis.title.textAlign */ /** * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) * to render the axis title. * * @product highcharts highstock gantt */ useHTML: false, /** * Horizontal pixel offset of the title position. * * @since 4.1.6 * @product highcharts highstock gantt */ x: 0, /** * Vertical pixel offset of the title position. * * @product highcharts highstock gantt */ y: 0, /** * CSS styles for the title. If the title text is longer than the * axis length, it will wrap to multiple lines by default. This can * be customized by setting `textOverflow: 'ellipsis'`, by * setting a specific `width` or by setting `whiteSpace: 'nowrap'`. * * In styled mode, the stroke width is given in the * `.highcharts-axis-title` class. * * @sample {highcharts} highcharts/xaxis/title-style/ * Red * @sample {highcharts} highcharts/css/axis/ * Styled mode * * @type {Highcharts.CSSObject} */ style: { /** @internal */ color: Palette.neutralColor60 } }, /** * The type of axis. Can be one of `linear`, `logarithmic`, `datetime` * or `category`. In a datetime axis, the numbers are given in * milliseconds, and tick marks are placed on appropriate values like * full hours or days. In a category axis, the * [point names](#series.line.data.name) of the chart's series are used * for categories, if not a [categories](#xAxis.categories) array is * defined. * * @sample {highcharts} highcharts/xaxis/type-linear/ * Linear * @sample {highcharts} highcharts/yaxis/type-log/ * Logarithmic * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/ * Logarithmic with minor grid lines * @sample {highcharts} highcharts/xaxis/type-log-both/ * Logarithmic on two axes * @sample {highcharts} highcharts/yaxis/type-log-negative/ * Logarithmic with extension to emulate negative values * * @type {Highcharts.AxisTypeValue} * @product highcharts gantt */ type: 'linear', /** * If there are multiple axes on the same side of the chart, the pixel * margin between the axes. Defaults to 0 on vertical axes, 15 on * horizontal axes. * * @type {number} * @since 7.0.3 * @apioption xAxis.margin */ /** * Applies only when the axis `type` is `category`. When `uniqueNames` * is true, points are placed on the X axis according to their names. * If the same point name is repeated in the same or another series, * the point is placed on the same X position as other points of the * same name. When `uniqueNames` is false, the points are laid out in * increasing X positions regardless of their names, and the X axis * category will take the name of the last point in each position. * * @sample {highcharts} highcharts/xaxis/uniquenames-true/ * True by default * @sample {highcharts} highcharts/xaxis/uniquenames-false/ * False * * @since 4.2.7 * @product highcharts gantt */ uniqueNames: true, /** * Datetime axis only. An array determining what time intervals the * ticks are allowed to fall on. Each array item is an array where the * first value is the time unit and the second value another array of * allowed multiples. * * Defaults to: * ```js * units: [[ * 'millisecond', // unit name * [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples * ], [ * 'second', * [1, 2, 5, 10, 15, 30] * ], [ * 'minute', * [1, 2, 5, 10, 15, 30] * ], [ * 'hour', * [1, 2, 3, 4, 6, 8, 12] * ], [ * 'day', * [1, 2] * ], [ * 'week', * [1, 2] * ], [ * 'month', * [1, 2, 3, 4, 6] * ], [ * 'year', * null * ]] * ``` * * @type {Array<Array<string,(Array<number>|null)>>} * @product highcharts highstock gantt * @apioption xAxis.units */ /** * Whether axis, including axis title, line, ticks and labels, should * be visible. * * @since 4.1.9 * @product highcharts highstock gantt */ visible: true, /** * Color of the minor, secondary grid lines. * * In styled mode, the stroke width is given in the * `.highcharts-minor-grid-line` class. * * @sample {highcharts} highcharts/yaxis/minorgridlinecolor/ * Bright grey lines from Y axis * @sample {highcharts|highstock} highcharts/css/axis-grid/ * Styled mode * @sample {highstock} stock/xaxis/minorgridlinecolor/ * Bright grey lines from Y axis * * @type {Highcharts.ColorType} * @default #f2f2f2 */ minorGridLineColor: Palette.neutralColor5, /** * Width of the minor, secondary grid lines. * * In styled mode, the stroke width is given in the * `.highcharts-grid-line` class. * * @sample {highcharts} highcharts/yaxis/minorgridlinewidth/ * 2px lines from Y axis * @sample {highcharts|highstock} highcharts/css/axis-grid/ * Styled mode * @sample {highstock} stock/xaxis/minorgridlinewidth/ * 2px lines from Y axis */ minorGridLineWidth: 1, /** * Color for the minor tick marks. * * @sample {highcharts} highcharts/yaxis/minortickcolor/ * Black tick marks on Y axis * @sample {highstock} stock/xaxis/minorticks/ * Black tick marks on Y axis * * @type {Highcharts.ColorType} * @default #999999 */ minorTickColor: Palette.neutralColor40, /** * The color of the line marking the axis itself. * * In styled mode, the line stroke is given in the * `.highcharts-axis-line` or `.highcharts-xaxis-line` class. * * @productdesc {highmaps} * In Highmaps, the axis line is hidden by default, because the axis is * not visible by default. * * @sample {highcharts} highcharts/yaxis/linecolor/ * A red line on Y axis * @sample {highcharts|highstock} highcharts/css/axis/ * Axes in styled mode * @sample {highstock} stock/xaxis/linecolor/ * A red line on X axis * * @type {Highcharts.ColorType} * @default #ccd6eb */ lineColor: Palette.highlightColor20, /** * The width of the line marking the axis itself. * * In styled mode, the stroke width is given in the * `.highcharts-axis-line` or `.highcharts-xaxis-line` class. * * @sample {highcharts} highcharts/yaxis/linecolor/ * A 1px line on Y axis * @sample {highcharts|highstock} highcharts/css/axis/ * Axes in styled mode * @sample {highstock} stock/xaxis/linewidth/ * A 2px line on X axis * * @default {highcharts|highstock} 1 * @default {highmaps} 0 */ lineWidth: 1, /** * Color of the grid lines extending the ticks across the plot area. * * In styled mode, the stroke is given in the `.highcharts-grid-line` * class. * * @productdesc {highmaps} * In Highmaps, the grid lines are hidden by default. * * @sample {highcharts} highcharts/yaxis/gridlinecolor/ * Green lines * @sample {highcharts|highstock} highcharts/css/axis-grid/ * Styled mode * @sample {highstock} stock/xaxis/gridlinecolor/ * Green lines * * @type {Highcharts.ColorType} * @default #e6e6e6 */ gridLineColor: Palette.neutralColor10, /** * The width of the grid lines extending the ticks across the plot area. * Defaults to 1 on the Y axis and 0 on the X axis, except for 3d * charts. * * In styled mode, the stroke width is given in the * `.highcharts-grid-line` class. * * @sample {highcharts} highcharts/yaxis/gridlinewidth/ * 2px lines * @sample {highcharts|highstock} highcharts/css/axis-grid/ * Styled mode * @sample {highstock} stock/xaxis/gridlinewidth/ * 2px lines * * @type {number} * @apioption xAxis.gridLineWidth */ gridLineWidth: void 0, /** * The height as the vertical axis. If it's a number, it is * interpreted as pixels. * * Since Highcharts 2: If it's a percentage string, it is interpreted * as percentages of the total plot height. * * @type {number|string} * @product highcharts highstock * @apioption xAxis.height */ /** * The width as the horizontal axis. If it's a number, it is interpreted * as pixels. * * Since Highcharts v5.0.13: If it's a percentage string, it is * interpreted as percentages of the total plot width. * * @type {number|string} * @product highcharts highstock * @apioption xAxis.width */ /** * Color for the main tick marks. * * In styled mode, the stroke is given in the `.highcharts-tick` * class. * * @sample {highcharts} highcharts/xaxis/tickcolor/ * Red ticks on X axis * @sample {highcharts|highstock} highcharts/css/axis-grid/ * Styled mode * @sample {highstock} stock/xaxis/ticks/ * Formatted ticks on X axis * * @type {Highcharts.ColorType} * @default #ccd6eb */ tickColor: Palette.highlightColor20 // tickWidth: 1 }; /** * The Y axis or value axis. Normally this is the vertical axis, * though if the chart is inverted this is the horizontal axis. * In case of multiple axes, the yAxis node is an array of * configuration objects. * * See [the Axis object](/class-reference/Highcharts.Axis) for programmatic * access to the axis. * * @type {*|Array<*>} * @extends xAxis * @excluding currentDateIndicator,ordinal,overscroll * @optionparent yAxis */ AxisDefaults.defaultYAxisOptions = { /** * The type of axis. Can be one of `linear`, `logarithmic`, `datetime`, * `category` or `treegrid`. Defaults to `treegrid` for Gantt charts, * `linear` for other chart types. * * In a datetime axis, the numbers are given in milliseconds, and tick * marks are placed on appropriate values, like full hours or days. In a * category or treegrid axis, the [point names](#series.line.data.name) * of the chart's series are used for categories, if a * [categories](#xAxis.categories) array is not defined. * * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/ * Logarithmic with minor grid lines * @sample {highcharts} highcharts/yaxis/type-log-negative/ * Logarithmic with extension to emulate negative values * @sample {gantt} gantt/treegrid-axis/demo * Treegrid axis * * @type {Highcharts.AxisTypeValue} * @default {highcharts} linear * @default {gantt} treegrid * @product highcharts gantt * @apioption yAxis.type */ /** * The height of the Y axis. If it's a number, it is interpreted as * pixels. * * Since Highcharts 2: If it's a percentage string, it is interpreted as * percentages of the total plot height. * * @see [yAxis.top](#yAxis.top) * * @sample {highstock} stock/demo/candlestick-and-volume/ * Percentage height panes * * @type {number|string} * @product highcharts highstock * @apioption yAxis.height */ /** * Solid gauge only. Unless [stops](#yAxis.stops) are set, the color * to represent the maximum value of the Y axis. * * @sample {highcharts} highcharts/yaxis/mincolor-maxcolor/ * Min and max colors * * @type {Highcharts.ColorType} * @default #003399 * @since 4.0 * @product highcharts * @apioption yAxis.maxColor */ /** * Solid gauge only. Unless [stops](#yAxis.stops) are set, the color * to represent the minimum value of the Y axis. * * @sample {highcharts} highcharts/yaxis/mincolor-maxcolor/ * Min and max color * * @type {Highcharts.ColorType} * @default #e6ebf5 * @since 4.0 * @product highcharts * @apioption yAxis.minColor */ /** * Whether to reverse the axis so that the highest number is closest * to the origin. * * @sample {highcharts} highcharts/yaxis/reversed/ * Reversed Y axis * @sample {highstock} stock/xaxis/reversed/ * Reversed Y axis * * @type {boolean} * @default {highcharts} false * @default {highstock} false * @default {highmaps} true * @default {gantt} true * @apioption yAxis.reversed */ /** * If `true`, the first series in a stack will be drawn on top in a * positive, non-reversed Y axis. If `false`, the first series is in * the base of the stack. * * @sample {highcharts} highcharts/yaxis/reversedstacks-false/ * Non-reversed stacks * @sample {highstock} highcharts/yaxis/reversedstacks-false/ * Non-reversed stacks * * @type {boolean} * @default true * @since 3.0.10 * @product highcharts highstock * @apioption yAxis.reversedStacks */ reversedStacks: true, /** * Solid gauge series only. Color stops for the solid gauge. Use this * in cases where a linear gradient between a `minColor` and `maxColor` * is not sufficient. The stops is an array of tuples, where the first * item is a float between 0 and 1 assigning the relative position in * the gradient, and the second item is the color. * * For solid gauges, the Y axis also inherits the concept of * [data classes](https://api.highcharts.com/highmaps#colorAxis.dataClasses) * from the Highmaps color axis. * * @see [minColor](#yAxis.minColor) * @see [maxColor](#yAxis.maxColor) * * @sample {highcharts} highcharts/demo/gauge-solid/ * True by default * * @type {Array<Array<number,Highcharts.ColorType>>} * @since 4.0 * @product highcharts * @apioption yAxis.stops */ /** * The pixel width of the major tick marks. * * @sample {highcharts} highcharts/xaxis/tickwidth/ 10 px width * @sample {highstock} stock/xaxis/ticks/ Formatted ticks on X axis * * @type {number} * @default 0 * @product highcharts highstock gantt * @apioption yAxis.tickWidth */ /** * Whether to force the axis to end on a tick. Use this option with * the `maxPadding` option to control the axis end. * * This option is always disabled, when panning type is * either `y` or `xy`. * * @see [type](#chart.panning.type) * * * @sample {highcharts} highcharts/chart/reflow-true/ * True by default * @sample {highcharts} highcharts/yaxis/endontick/ * False * @sample {highstock} stock/demo/basic-line/ * True by default * @sample {highstock} stock/xaxis/endontick/ * False for Y axis * * @since 1.2.0 */ endOnTick: true, /** * Padding of the max value relative to the length of the axis. A * padding of 0.05 will make a 100px axis 5px longer. This is useful * when you don't want the highest data value to appear on the edge * of the plot area. When the axis' `max` option is set or a max extreme * is set using `axis.setExtremes()`, the maxPadding will be ignored. * * Also the `softThreshold` option takes precedence over `maxPadding`, * so if the data is tangent to the threshold, `maxPadding` may not * apply unless `softThreshold` is set to false. * * @sample {highcharts} highcharts/yaxis/maxpadding-02/ * Max padding of 0.2 * @sample {highstock} stock/xaxis/minpadding-maxpadding/ * Greater min- and maxPadding * * @since 1.2.0 * @product highcharts highstock gantt */ maxPadding: 0.05, /** * Padding of the min value relative to the length of the axis. A * padding of 0.05 will make a 100px axis 5px longer. This is useful * when you don't want the lowest data value to appear on the edge * of the plot area. When the axis' `min` option is set or a max extreme * is set using `axis.setExtremes()`, the maxPadding will be ignored. * * Also the `softThreshold` option takes precedence over `minPadding`, * so if the data is tangent to the threshold, `minPadding` may not * apply unless `softThreshold` is set to false. * * @sample {highcharts} highcharts/yaxis/minpadding/ * Min padding of 0.2 * @sample {highstock} stock/xaxis/minpadding-maxpadding/ * Greater min- and maxPadding * * @since 1.2.0 * @product highcharts highstock gantt */ minPadding: 0.05, /** * @productdesc {highstock} * In Highcharts Stock 1.x, the Y axis was placed * on the left side by default. * * @sample {highcharts} highcharts/yaxis/opposite/ * Secondary Y axis opposite * @sample {highstock} stock/xaxis/opposite/ * Y axis on left side * * @type {boolean} * @default {highstock} true * @default {highcharts} false * @product highstock highcharts gantt * @apioption yAxis.opposite */ /** * @see [tickInterval](#xAxis.tickInterval) * @see [tickPositioner](#xAxis.tickPositioner) * @see [tickPositions](#xAxis.tickPositions) */ tickPixelInterval: 72, showLastLabel: true, /** * @extends xAxis.labels */ labels: { /** * Angular gauges and solid gauges only. * The label's pixel distance from the perimeter of the plot area. * * Since v7.1.2: If it's a percentage string, it is interpreted the * same as [series.radius](#plotOptions.gauge.radius), so label can be * aligned under the gauge's shape. * * @sample {highcharts} highcharts/yaxis/labels-distance/ * Labels centered under the arc * * @type {number|string} * @default -25 * @product highcharts * @apioption yAxis.labels.distance */ /** * The y position offset of all labels relative to the tick * positions on the axis. For polar and radial axis consider the use * of the [distance](#yAxis.labels.distance) option. * * @sample {highcharts} highcharts/xaxis/labels-x/ * Y axis labels placed on grid lines * * @type {number} * @default {highcharts} 3 * @default {highstock} -2 * @default {highmaps} 3 * @apioption yAxis.labels.y */ /** * What part of the string the given position is anchored to. Can * be one of `"left"`, `"center"` or `"right"`. The exact position * also depends on the `labels.x` setting. * * Angular gauges and solid gauges defaults to `"center"`. * Solid gauges with two labels have additional option `"auto"` * for automatic horizontal and vertical alignment. * * @see [yAxis.labels.distance](#yAxis.labels.distance) * * @sample {highcharts} highcharts/yaxis/labels-align-left/ * Left * @sample {highcharts} highcharts/series-solidgauge/labels-auto-aligned/ * Solid gauge labels auto aligned * * @type {Highcharts.AlignValue} * @default {highcharts|highmaps} right * @default {highstock} left * @apioption yAxis.labels.align */ /** * The x position offset of all labels relative to the tick * positions on the axis. Defaults to -15 for left axis, 15 for * right axis. * * @sample {highcharts} highcharts/xaxis/labels-x/ * Y axis labels placed on grid lines */ x: -8 }, /** * @productdesc {highmaps} * In Highmaps, the axis line is hidden by default, because the axis is * not visible by default. * * @type {Highcharts.ColorType} * @apioption yAxis.lineColor */ /** * @sample {highcharts} highcharts/yaxis/max-200/ * Y axis max of 200 * @sample {highcharts} highcharts/yaxis/max-logarithmic/ * Y axis max on logarithmic axis * @sample {highstock} stock/yaxis/min-max/ * Fixed min and max on Y axis * @sample {highmaps} maps/axis/min-max/ * Pre-zoomed to a specific area * * @apioption yAxis.max */ /** * @sample {highcharts} highcharts/yaxis/min-startontick-false/ * -50 with startOnTick to false * @sample {highcharts} highcharts/yaxis/min-startontick-true/ * -50 with startOnTick true by default * @sample {highstock} stock/yaxis/min-max/ * Fixed min and max on Y axis * @sample {highmaps} maps/axis/min-max/ * Pre-zoomed to a specific area * * @apioption yAxis.min */ /** * An optional scrollbar to display on the Y axis in response to * limiting the minimum an maximum of the axis values. * * In styled mode, all the presentational options for the scrollbar * are replaced by the classes `.highcharts-scrollbar-thumb`, * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`, * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`. * * @sample {highstock} stock/yaxis/scrollbar/ * Scrollbar on the Y axis * * @extends scrollbar * @since 4.2.6 * @product highstock * @excluding height * @apioption yAxis.scrollbar */ /** * Enable the scrollbar on the Y axis. * * @sample {highstock} stock/yaxis/scrollbar/ * Enabled on Y axis * * @type {boolean} * @default false * @since 4.2.6 * @product highstock * @apioption yAxis.scrollbar.enabled */ /** * Pixel margin between the scrollbar and the axis elements. * * @type {number} * @default 10 * @since 4.2.6 * @product highstock * @apioption yAxis.scrollbar.margin */ /** * Whether to show the scrollbar when it is fully zoomed out at max * range. Setting it to `false` on the Y axis makes the scrollbar stay * hidden until the user zooms in, like common in browsers. * * @type {boolean} * @default true * @since 4.2.6 * @product highstock * @apioption yAxis.scrollbar.showFull */ /** * The width of a vertical scrollbar or height of a horizontal * scrollbar. Defaults to 20 on touch devices. * * @type {number} * @default 14 * @since 4.2.6 * @product highstock * @apioption yAxis.scrollbar.size */ /** * Z index of the scrollbar elements. * * @type {number} * @default 3 * @since 4.2.6 * @product highstock * @apioption yAxis.scrollbar.zIndex */ /** * A soft maximum for the axis. If the series data maximum is less * than this, the axis will stay at this maximum, but if the series * data maximum is higher, the axis will flex to show all data. * * **Note**: The [series.softThreshold]( * #plotOptions.series.softThreshold) option takes precedence over this * option. * * @sample highcharts/yaxis/softmin-softmax/ * Soft min and max * * @type {number} * @since 5.0.1 * @product highcharts highstock gantt * @apioption yAxis.softMax */ /** * A soft minimum for the axis. If the series data minimum is greater * than this, the axis will stay at this minimum, but if the series * data minimum is lower, the axis will flex to show all data. * * **Note**: The [series.softThreshold]( * #plotOptions.series.softThreshold) option takes precedence over this * option. * * @sample highcharts/yaxis/softmin-softmax/ * Soft min and max * * @type {number} * @since 5.0.1 * @product highcharts highstock gantt * @apioption yAxis.softMin */ /** * Defines the horizontal alignment of the stack total label. Can be one * of `"left"`, `"center"` or `"right"`. The default value is calculated * at runtime and depends on orientation and whether the stack is * positive or negative. * * @sample {highcharts} highcharts/yaxis/stacklabels-align-left/ * Aligned to the left * @sample {highcharts} highcharts/yaxis/stacklabels-align-center/ * Aligned in center * @sample {highcharts} highcharts/yaxis/stacklabels-align-right/ * Aligned to the right * * @type {Highcharts.AlignValue} * @since 2.1.5 * @product highcharts * @apioption yAxis.stackLabels.align */ /** * A format string for the data label. Available variables are the same * as for `formatter`. * * @type {string} * @default {total} * @since 3.0.2 * @product highcharts highstock * @apioption yAxis.stackLabels.format */ /** * Rotation of the labels in degrees. * * @sample {highcharts} highcharts/yaxis/stacklabels-rotation/ * Labels rotated 45° * * @type {number} * @default 0 * @since 2.1.5 * @product highcharts * @apioption yAxis.stackLabels.rotation */ /** * The text alignment for the label. While `align` determines where the * texts anchor point is placed with regards to the stack, `textAlign` * determines how the text is aligned against its anchor point. Possible * values are `"left"`, `"center"` and `"right"`. The default value is * calculated at runtime and depends on orientation and whether the * stack is positive or negative. * * @sample {highcharts} highcharts/yaxis/stacklabels-textalign-left/ * Label in center position but text-aligned left * * @type {Highcharts.AlignValue} * @since 2.1.5 * @product highcharts * @apioption yAxis.stackLabels.textAlign */ /** * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) * to render the labels. * * @type {boolean} * @default false * @since 3.0 * @product highcharts highstock * @apioption yAxis.stackLabels.useHTML */ /** * Defines the vertical alignment of the stack total label. Can be one * of `"top"`, `"middle"` or `"bottom"`. The default value is calculated * at runtime and depends on orientation and whether the stack is * positive or negative. * * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-top/ * Vertically aligned top * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-middle/ * Vertically aligned middle * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-bottom/ * Vertically aligned bottom * * @type {Highcharts.VerticalAlignValue} * @since 2.1.5 * @product highcharts * @apioption yAxis.stackLabels.verticalAlign */ /** * The x position offset of the label relative to the left of the * stacked bar. The default value is calculated at runtime and depends * on orientation and whether the stack is positive or negative. * * @sample {highcharts} highcharts/yaxis/stacklabels-x/ * Stack total labels with x offset * * @type {number} * @since 2.1.5 * @product highcharts * @apioption yAxis.stackLabels.x */ /** * The y position offset of the label relative to the tick position * on the axis. The default value is calculated at runtime and depends * on orientation and whether the stack is positive or negative. * * @sample {highcharts} highcharts/yaxis/stacklabels-y/ * Stack total labels with y offset * * @type {number} * @since 2.1.5 * @product highcharts * @apioption yAxis.stackLabels.y */ /** * Whether to force the axis to start on a tick. Use this option with * the `maxPadding` option to control the axis start. * * This option is always disabled, when panning type is * either `y` or `xy`. * * @see [type](#chart.panning.type) * * @sample {highcharts} highcharts/xaxis/startontick-false/ * False by default * @sample {highcharts} highcharts/xaxis/startontick-true/ * True * @sample {highstock} stock/xaxis/endontick/ * False for Y axis * * @since 1.2.0 * @product highcharts highstock gantt */ startOnTick: true, title: { /** * The pixel distance between the axis labels and the title. * Positive values are outside the axis line, negative are inside. * * @sample {highcharts} highcharts/xaxis/title-margin/ * Y axis title margin of 60 * * @type {number} * @default 40 * @apioption yAxis.title.margin */ /** * The rotation of the text in degrees. 0 is horizontal, 270 is * vertical reading from bottom to top. * * @sample {highcharts} highcharts/yaxis/title-offset/ * Horizontal */ rotation: 270, /** * The actual text of the axis title. Horizontal texts can contain * HTML, but rotated texts are painted using vector techniques and * must be clean text. The Y axis title is disabled by setting the * `text` option to `undefined`. * * @sample {highcharts} highcharts/xaxis/title-text/ * Custom HTML * * @type {string|null} * @default {highcharts} Values * @default {highstock} undefined * @product highcharts highstock gantt */ text: 'Values' }, /** * The top position of the Y axis. If it's a number, it is interpreted * as pixel position relative to the chart. * * Since Highcharts 2: If it's a percentage string, it is interpreted as * percentages of the plot height, offset from plot area top. * * @see [yAxis.height](#yAxis.height) * * @sample {highstock} stock/demo/candlestick-and-volume/ * Percentage height panes * * @type {number|string} * @product highcharts highstock * @apioption yAxis.top */ /** * The stack labels show the total value for each bar in a stacked * column or bar chart. The label will be placed on top of positive * columns and below negative columns. In case of an inverted column * chart or a bar chart the label is placed to the right of positive * bars and to the left of negative bars. * * @product highcharts */ stackLabels: { /** * Enable or disable the initial animation when a series is * displayed for the `stackLabels`. The animation can also be set as * a configuration object. Please note that this option only * applies to the initial animation. * For other animations, see [chart.animation](#chart.animation) * and the animation parameter under the API methods. * The following properties are supported: * * - `defer`: The animation delay time in milliseconds. * * @sample {highcharts} highcharts/plotoptions/animation-defer/ * Animation defer settings * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} * @since 8.2.0 * @apioption yAxis.stackLabels.animation */ animation: {}, /** * The animation delay time in milliseconds. * Set to `0` renders stackLabel immediately. * As `undefined` inherits defer time from the [series.animation.defer](#plotOptions.series.animation.defer). * * @type {number} * @since 8.2.0 * @apioption yAxis.stackLabels.animation.defer */ /** * Allow the stack labels to overlap. * * @sample {highcharts} highcharts/yaxis/stacklabels-allowoverlap-false/ * Default false * * @since 5.0.13 * @product highcharts */ allowOverlap: false, /** * The background color or gradient for the stack label. * * @sample {highcharts} highcharts/yaxis/stacklabels-box/ * Stack labels box options * @type {Highcharts.ColorType} * @since 8.1.0 * @apioption yAxis.stackLabels.backgroundColor */ /** * The border color for the stack label. Defaults to `undefined`. * * @sample {highcharts} highcharts/yaxis/stacklabels-box/ * Stack labels box options * @type {Highcharts.ColorType} * @since 8.1.0 * @apioption yAxis.stackLabels.borderColor */ /** * The border radius in pixels for the stack label. * * @sample {highcharts} highcharts/yaxis/stacklabels-box/ * Stack labels box options * @type {number} * @default 0 * @since 8.1.0 * @apioption yAxis.stackLabels.borderRadius */ /** * The border width in pixels for the stack label. * * @sample {highcharts} highcharts/yaxis/stacklabels-box/ * Stack labels box options * @type {number} * @default 0 * @since 8.1.0 * @apioption yAxis.stackLabels.borderWidth */ /** * Enable or disable the stack total labels. * * @sample {highcharts} highcharts/yaxis/stacklabels-enabled/ * Enabled stack total labels * @sample {highcharts} highcharts/yaxis/stacklabels-enabled-waterfall/ * Enabled stack labels in waterfall chart * * @since 2.1.5 * @product highcharts */ enabled: false, /** * Whether to hide stack labels that are outside the plot area. * By default, the stack label is moved * inside the plot area according to the * [overflow](/highcharts/#yAxis/stackLabels/overflow) * option. * * @type {boolean} * @since 7.1.3 */ crop: true, /** * How to handle stack total labels that flow outside the plot area. * The default is set to `"justify"`, * which aligns them inside the plot area. * For columns and bars, this means it will be moved inside the bar. * To display stack labels outside the plot area, * set `crop` to `false` and `overflow` to `"allow"`. * * @sample highcharts/yaxis/stacklabels-overflow/ * Stack labels flows outside the plot area. * * @type {Highcharts.DataLabelsOverflowValue} * @since 7.1.3 */ overflow: 'justify', /* eslint-disable valid-jsdoc */ /** * Callback JavaScript function to format the label. The value is * given by `this.total`. * * @sample {highcharts} highcharts/yaxis/stacklabels-formatter/ * Added units to stack total value * * @type {Highcharts.FormatterCallbackFunction<Highcharts.StackItemObject>} * @since 2.1.5 * @product highcharts */ formatter: function () { var numberFormatter = this.axis.chart.numberFormatter; /* eslint-enable valid-jsdoc */ return numberFormatter(this.total, -1); }, /** * CSS styles for the label. * * In styled mode, the styles are set in the * `.highcharts-stack-label` class. * * @sample {highcharts} highcharts/yaxis/stacklabels-style/ * Red stack total labels * * @type {Highcharts.CSSObject} * @since 2.1.5 * @product highcharts */ style: { /** @internal */ color: Palette.neutralColor100, /** @internal */ fontSize: '11px', /** @internal */ fontWeight: 'bold', /** @internal */ textOutline: '1px contrast' } }, gridLineWidth: 1, lineWidth: 0 // tickWidth: 0 }; /** * The Z axis or depth axis for 3D plots. * * See the [Axis class](/class-reference/Highcharts.Axis) for programmatic * access to the axis. * * @sample {highcharts} highcharts/3d/scatter-zaxis-categories/ * Z-Axis with Categories * @sample {highcharts} highcharts/3d/scatter-zaxis-grid/ * Z-Axis with styling * * @type {*|Array<*>} * @extends xAxis * @since 5.0.0 * @product highcharts * @excluding breaks, crosshair, height, left, lineColor, lineWidth, * nameToX, showEmpty, top, width * @apioption zAxis */ // This variable extends the defaultOptions for left axes. AxisDefaults.defaultLeftAxisOptions = { labels: { x: -15 }, title: { rotation: 270 } }; // This variable extends the defaultOptions for right axes. AxisDefaults.defaultRightAxisOptions = { labels: { x: 15 }, title: { rotation: 90 } }; // This variable extends the defaultOptions for bottom axes. AxisDefaults.defaultBottomAxisOptions = { labels: { autoRotation: [-45], x: 0 // overflow: undefined, // staggerLines: null }, margin: 15, title: { rotation: 0 } }; // This variable extends the defaultOptions for top axes. AxisDefaults.defaultTopAxisOptions = { labels: { autoRotation: [-45], x: 0 // overflow: undefined // staggerLines: null }, margin: 15, title: { rotation: 0 } }; })(AxisDefaults || (AxisDefaults = {})); /* * * * Default Export * * */ return AxisDefaults; }); _registerModule(_modules, 'Core/Foundation.js', [_modules['Core/Utilities.js']], function (U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var addEvent = U.addEvent, isFunction = U.isFunction, objectEach = U.objectEach, removeEvent = U.removeEvent; /* * * * Functions * * */ /* * Register event options. If an event handler is set on the options, it should * be subject to Chart.update, Axis.update and Series.update. This is contrary * to general handlers that are set directly using addEvent either on the class * or on the instance. #6538, #6943, #10861. */ var registerEventOptions = function (component, options) { // A lookup over those events that are added by _options_ (not // programmatically). These are updated through .update() component.eventOptions = component.eventOptions || {}; // Register event listeners objectEach(options.events, function (event, eventType) { if (isFunction(event)) { // If event does not exist, or is changed by the .update() // function if (component.eventOptions[eventType] !== event) { // Remove existing if set by option if (isFunction(component.eventOptions[eventType])) { removeEvent(component, eventType, component.eventOptions[eventType]); } component.eventOptions[eventType] = event; addEvent(component, eventType, event); } } }); }; /* * * * Default Export * * */ var exports = { registerEventOptions: registerEventOptions }; return exports; }); _registerModule(_modules, 'Core/Axis/Tick.js', [_modules['Core/FormatUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (F, H, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var deg2rad = H.deg2rad; var clamp = U.clamp, correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, extend = U.extend, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach, pick = U.pick; /* * * * Class * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The Tick class. * * @class * @name Highcharts.Tick * * @param {Highcharts.Axis} axis * The axis of the tick. * * @param {number} pos * The position of the tick on the axis in terms of axis values. * * @param {string} [type] * The type of tick, either 'minor' or an empty string * * @param {boolean} [noLabel=false] * Whether to disable the label or not. Defaults to false. * * @param {object} [parameters] * Optional parameters for the tick. */ var Tick = /** @class */ (function () { /* * * * Constructors * * */ function Tick(axis, pos, type, noLabel, parameters) { this.isNew = true; this.isNewLabel = true; /** * The related axis of the tick. * @name Highcharts.Tick#axis * @type {Highcharts.Axis} */ this.axis = axis; /** * The logical position of the tick on the axis in terms of axis values. * @name Highcharts.Tick#pos * @type {number} */ this.pos = pos; /** * The tick type, which can be `"minor"`, or an empty string. * @name Highcharts.Tick#type * @type {string} */ this.type = type || ''; this.parameters = parameters || {}; /** * The mark offset of the tick on the axis. Usually `undefined`, numeric * for grid axes. * @name Highcharts.Tick#tickmarkOffset * @type {number|undefined} */ this.tickmarkOffset = this.parameters.tickmarkOffset; this.options = this.parameters.options; fireEvent(this, 'init'); if (!type && !noLabel) { this.addLabel(); } } /* * * * Functions * * */ /** * Write the tick label. * * @private * @function Highcharts.Tick#addLabel * @return {void} */ Tick.prototype.addLabel = function () { var tick = this, axis = tick.axis, options = axis.options, chart = axis.chart, categories = axis.categories, log = axis.logarithmic, names = axis.names, pos = tick.pos, labelOptions = pick(tick.options && tick.options.labels, options.labels), tickPositions = axis.tickPositions, isFirst = pos === tickPositions[0], isLast = pos === tickPositions[tickPositions.length - 1], animateLabels = (!labelOptions.step || labelOptions.step === 1) && axis.tickInterval === 1, tickPositionInfo = tickPositions.info; var label = tick.label, dateTimeLabelFormat, dateTimeLabelFormats, i; // The context value var value = this.parameters.category || (categories ? pick(categories[pos], names[pos], pos) : pos); if (log && isNumber(value)) { value = correctFloat(log.lin2log(value)); } // Set the datetime label format. If a higher rank is set for this // position, use that. If not, use the general format. if (axis.dateTime && tickPositionInfo) { dateTimeLabelFormats = chart.time.resolveDTLFormat(options.dateTimeLabelFormats[(!options.grid && tickPositionInfo.higherRanks[pos]) || tickPositionInfo.unitName]); dateTimeLabelFormat = dateTimeLabelFormats.main; } // set properties for access in render method /** * True if the tick is the first one on the axis. * @name Highcharts.Tick#isFirst * @readonly * @type {boolean|undefined} */ tick.isFirst = isFirst; /** * True if the tick is the last one on the axis. * @name Highcharts.Tick#isLast * @readonly * @type {boolean|undefined} */ tick.isLast = isLast; // Get the string var ctx = { axis: axis, chart: chart, dateTimeLabelFormat: dateTimeLabelFormat, isFirst: isFirst, isLast: isLast, pos: pos, tick: tick, tickPositionInfo: tickPositionInfo, value: value }; // Fire an event that allows modifying the context for use in // `labels.format` and `labels.formatter`. fireEvent(this, 'labelFormat', ctx); // Label formatting. When `labels.format` is given, we first run the // defaultFormatter and append the result to the context as `text`. // Handy for adding prefix or suffix while keeping default number // formatting. var labelFormatter = function (ctx) { if (labelOptions.formatter) { return labelOptions.formatter.call(ctx, ctx); } if (labelOptions.format) { ctx.text = axis.defaultLabelFormatter.call(ctx); return F.format(labelOptions.format, ctx, chart); } return axis.defaultLabelFormatter.call(ctx, ctx); }; var str = labelFormatter.call(ctx, ctx); // Set up conditional formatting based on the format list if existing. var list = dateTimeLabelFormats && dateTimeLabelFormats.list; if (list) { tick.shortenLabel = function () { for (i = 0; i < list.length; i++) { extend(ctx, { dateTimeLabelFormat: list[i] }); label.attr({ text: labelFormatter.call(ctx, ctx) }); if (label.getBBox().width < axis.getSlotWidth(tick) - 2 * labelOptions.padding) { return; } } label.attr({ text: '' }); }; } else { // #15692 tick.shortenLabel = void 0; } // Call only after first render if (animateLabels && axis._addedPlotLB) { tick.moveLabel(str, labelOptions); } // First call if (!defined(label) && !tick.movedLabel) { /** * The rendered text label of the tick. * @name Highcharts.Tick#label * @type {Highcharts.SVGElement|undefined} */ tick.label = label = tick.createLabel({ x: 0, y: 0 }, str, labelOptions); // Base value to detect change for new calls to getBBox tick.rotation = 0; // update } else if (label && label.textStr !== str && !animateLabels) { // When resetting text, also reset the width if dynamically set // (#8809) if (label.textWidth && !labelOptions.style.width && !label.styles.width) { label.css({ width: null }); } label.attr({ text: str }); label.textPxLength = label.getBBox().width; } }; /** * Render and return the label of the tick. * * @private * @function Highcharts.Tick#createLabel * @param {Highcharts.PositionObject} xy * @param {string} str * @param {Highcharts.XAxisLabelsOptions} labelOptions * @return {Highcharts.SVGElement|undefined} */ Tick.prototype.createLabel = function (xy, str, labelOptions) { var axis = this.axis, chart = axis.chart, label = defined(str) && labelOptions.enabled ? chart.renderer .text(str, xy.x, xy.y, labelOptions.useHTML) .add(axis.labelGroup) : null; // Un-rotated length if (label) { // Without position absolute, IE export sometimes is wrong if (!chart.styledMode) { label.css(merge(labelOptions.style)); } label.textPxLength = label.getBBox().width; } return label; }; /** * Destructor for the tick prototype * * @private * @function Highcharts.Tick#destroy * @return {void} */ Tick.prototype.destroy = function () { destroyObjectProperties(this, this.axis); }; /** * Gets the x and y positions for ticks in terms of pixels. * * @private * @function Highcharts.Tick#getPosition * * @param {boolean} horiz * Whether the tick is on an horizontal axis or not. * * @param {number} tickPos * Position of the tick. * * @param {number} tickmarkOffset * Tickmark offset for all ticks. * * @param {boolean} [old] * Whether the axis has changed or not. * * @return {Highcharts.PositionObject} * The tick position. * * @fires Highcharts.Tick#event:afterGetPosition */ Tick.prototype.getPosition = function (horiz, tickPos, tickmarkOffset, old) { var axis = this.axis, chart = axis.chart, cHeight = (old && chart.oldChartHeight) || chart.chartHeight, pos = { x: horiz ? correctFloat(axis.translate(tickPos + tickmarkOffset, null, null, old) + axis.transB) : (axis.left + axis.offset + (axis.opposite ? (((old && chart.oldChartWidth) || chart.chartWidth) - axis.right - axis.left) : 0)), y: horiz ? (cHeight - axis.bottom + axis.offset - (axis.opposite ? axis.height : 0)) : correctFloat(cHeight - axis.translate(tickPos + tickmarkOffset, null, null, old) - axis.transB) }; // Chrome workaround for #10516 pos.y = clamp(pos.y, -1e5, 1e5); fireEvent(this, 'afterGetPosition', { pos: pos }); return pos; }; /** * Get the x, y position of the tick label * * @private * @return {Highcharts.PositionObject} */ Tick.prototype.getLabelPosition = function (x, y, label, horiz, labelOptions, tickmarkOffset, index, step) { var axis = this.axis, transA = axis.transA, reversed = ( // #7911 axis.isLinked && axis.linkedParent ? axis.linkedParent.reversed : axis.reversed), staggerLines = axis.staggerLines, rotCorr = axis.tickRotCorr || { x: 0, y: 0 }, // Adjust for label alignment if we use reserveSpace: true (#5286) labelOffsetCorrection = (!horiz && !axis.reserveSpaceDefault ? -axis.labelOffset * (axis.labelAlign === 'center' ? 0.5 : 1) : 0), pos = {}; var yOffset = labelOptions.y, line; if (!defined(yOffset)) { if (axis.side === 0) { yOffset = label.rotation ? -8 : -label.getBBox().height; } else if (axis.side === 2) { yOffset = rotCorr.y + 8; } else { // #3140, #3140 yOffset = Math.cos(label.rotation * deg2rad) * (rotCorr.y - label.getBBox(false, 0).height / 2); } } x = x + labelOptions.x + labelOffsetCorrection + rotCorr.x - (tickmarkOffset && horiz ? tickmarkOffset * transA * (reversed ? -1 : 1) : 0); y = y + yOffset - (tickmarkOffset && !horiz ? tickmarkOffset * transA * (reversed ? 1 : -1) : 0); // Correct for staggered labels if (staggerLines) { line = (index / (step || 1) % staggerLines); if (axis.opposite) { line = staggerLines - line - 1; } y += line * (axis.labelOffset / staggerLines); } pos.x = x; pos.y = Math.round(y); fireEvent(this, 'afterGetLabelPosition', { pos: pos, tickmarkOffset: tickmarkOffset, index: index }); return pos; }; /** * Get the offset height or width of the label * * @private * @function Highcharts.Tick#getLabelSize * @return {number} */ Tick.prototype.getLabelSize = function () { return this.label ? this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] : 0; }; /** * Extendible method to return the path of the marker * * @private * */ Tick.prototype.getMarkPath = function (x, y, tickLength, tickWidth, horiz, renderer) { return renderer.crispLine([[ 'M', x, y ], [ 'L', x + (horiz ? 0 : -tickLength), y + (horiz ? tickLength : 0) ]], tickWidth); }; /** * Handle the label overflow by adjusting the labels to the left and right * edge, or hide them if they collide into the neighbour label. * * @private * @function Highcharts.Tick#handleOverflow * @param {Highcharts.PositionObject} xy * @return {void} */ Tick.prototype.handleOverflow = function (xy) { var tick = this, axis = this.axis, labelOptions = axis.options.labels, pxPos = xy.x, chartWidth = axis.chart.chartWidth, spacing = axis.chart.spacing, leftBound = pick(axis.labelLeft, Math.min(axis.pos, spacing[3])), rightBound = pick(axis.labelRight, Math.max(!axis.isRadial ? axis.pos + axis.len : 0, chartWidth - spacing[1])), label = this.label, rotation = this.rotation, factor = { left: 0, center: 0.5, right: 1 }[axis.labelAlign || label.attr('align')], labelWidth = label.getBBox().width, slotWidth = axis.getSlotWidth(tick), xCorrection = factor, css = {}; var modifiedSlotWidth = slotWidth, goRight = 1, leftPos, rightPos, textWidth; // Check if the label overshoots the chart spacing box. If it does, move // it. If it now overshoots the slotWidth, add ellipsis. if (!rotation && labelOptions.overflow === 'justify') { leftPos = pxPos - factor * labelWidth; rightPos = pxPos + (1 - factor) * labelWidth; if (leftPos < leftBound) { modifiedSlotWidth = xy.x + modifiedSlotWidth * (1 - factor) - leftBound; } else if (rightPos > rightBound) { modifiedSlotWidth = rightBound - xy.x + modifiedSlotWidth * factor; goRight = -1; } modifiedSlotWidth = Math.min(slotWidth, modifiedSlotWidth); // #4177 if (modifiedSlotWidth < slotWidth && axis.labelAlign === 'center') { xy.x += (goRight * (slotWidth - modifiedSlotWidth - xCorrection * (slotWidth - Math.min(labelWidth, modifiedSlotWidth)))); } // If the label width exceeds the available space, set a text width // to be picked up below. Also, if a width has been set before, we // need to set a new one because the reported labelWidth will be // limited by the box (#3938). if (labelWidth > modifiedSlotWidth || (axis.autoRotation && (label.styles || {}).width)) { textWidth = modifiedSlotWidth; } // Add ellipsis to prevent rotated labels to be clipped against the edge // of the chart } else if (rotation < 0 && pxPos - factor * labelWidth < leftBound) { textWidth = Math.round(pxPos / Math.cos(rotation * deg2rad) - leftBound); } else if (rotation > 0 && pxPos + factor * labelWidth > rightBound) { textWidth = Math.round((chartWidth - pxPos) / Math.cos(rotation * deg2rad)); } if (textWidth) { if (tick.shortenLabel) { tick.shortenLabel(); } else { css.width = Math.floor(textWidth) + 'px'; if (!(labelOptions.style || {}).textOverflow) { css.textOverflow = 'ellipsis'; } label.css(css); } } }; /** * Try to replace the label if the same one already exists. * * @private * @function Highcharts.Tick#moveLabel * @param {string} str * @param {Highcharts.XAxisLabelsOptions} labelOptions * * @return {void} */ Tick.prototype.moveLabel = function (str, labelOptions) { var tick = this, label = tick.label, axis = tick.axis, reversed = axis.reversed; var moved = false, labelPos, xPos, yPos; if (label && label.textStr === str) { tick.movedLabel = label; moved = true; delete tick.label; } else { // Find a label with the same string objectEach(axis.ticks, function (currentTick) { if (!moved && !currentTick.isNew && currentTick !== tick && currentTick.label && currentTick.label.textStr === str) { tick.movedLabel = currentTick.label; moved = true; currentTick.labelPos = tick.movedLabel.xy; delete currentTick.label; } }); } // Create new label if the actual one is moved if (!moved && (tick.labelPos || label)) { labelPos = tick.labelPos || label.xy; xPos = axis.horiz ? (reversed ? 0 : axis.width + axis.left) : labelPos.x; yPos = axis.horiz ? labelPos.y : (reversed ? (axis.width + axis.left) : 0); tick.movedLabel = tick.createLabel({ x: xPos, y: yPos }, str, labelOptions); if (tick.movedLabel) { tick.movedLabel.attr({ opacity: 0 }); } } }; /** * Put everything in place * * @private * @param {number} index * @param {boolean} [old] * Use old coordinates to prepare an animation into new position * @param {number} [opacity] * @return {voids} */ Tick.prototype.render = function (index, old, opacity) { var tick = this, axis = tick.axis, horiz = axis.horiz, pos = tick.pos, tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset), xy = tick.getPosition(horiz, pos, tickmarkOffset, old), x = xy.x, y = xy.y, reverseCrisp = ((horiz && x === axis.pos + axis.len) || (!horiz && y === axis.pos)) ? -1 : 1; // #1480, #1687 var labelOpacity = pick(opacity, tick.label && tick.label.newOpacity, // #15528 1); opacity = pick(opacity, 1); this.isActive = true; // Create the grid line this.renderGridLine(old, opacity, reverseCrisp); // create the tick mark this.renderMark(xy, opacity, reverseCrisp); // the label is created on init - now move it into place this.renderLabel(xy, old, labelOpacity, index); tick.isNew = false; fireEvent(this, 'afterRender'); }; /** * Renders the gridLine. * * @private * @param {boolean} old Whether or not the tick is old * @param {number} opacity The opacity of the grid line * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1 * @return {void} */ Tick.prototype.renderGridLine = function (old, opacity, reverseCrisp) { var tick = this, axis = tick.axis, options = axis.options, attribs = {}, pos = tick.pos, type = tick.type, tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset), renderer = axis.chart.renderer; var gridLine = tick.gridLine, gridLinePath, gridLineWidth = options.gridLineWidth, gridLineColor = options.gridLineColor, dashStyle = options.gridLineDashStyle; if (tick.type === 'minor') { gridLineWidth = options.minorGridLineWidth; gridLineColor = options.minorGridLineColor; dashStyle = options.minorGridLineDashStyle; } if (!gridLine) { if (!axis.chart.styledMode) { attribs.stroke = gridLineColor; attribs['stroke-width'] = gridLineWidth || 0; attribs.dashstyle = dashStyle; } if (!type) { attribs.zIndex = 1; } if (old) { opacity = 0; } /** * The rendered grid line of the tick. * @name Highcharts.Tick#gridLine * @type {Highcharts.SVGElement|undefined} */ tick.gridLine = gridLine = renderer.path() .attr(attribs) .addClass('highcharts-' + (type ? type + '-' : '') + 'grid-line') .add(axis.gridGroup); } if (gridLine) { gridLinePath = axis.getPlotLinePath({ value: pos + tickmarkOffset, lineWidth: gridLine.strokeWidth() * reverseCrisp, force: 'pass', old: old }); // If the parameter 'old' is set, the current call will be followed // by another call, therefore do not do any animations this time if (gridLinePath) { gridLine[old || tick.isNew ? 'attr' : 'animate']({ d: gridLinePath, opacity: opacity }); } } }; /** * Renders the tick mark. * * @private * @param {Highcharts.PositionObject} xy The position vector of the mark * @param {number} opacity The opacity of the mark * @param {number} reverseCrisp Modifier for avoiding overlapping 1 or -1 * @return {void} */ Tick.prototype.renderMark = function (xy, opacity, reverseCrisp) { var tick = this, axis = tick.axis, options = axis.options, renderer = axis.chart.renderer, type = tick.type, tickSize = axis.tickSize(type ? type + 'Tick' : 'tick'), x = xy.x, y = xy.y, tickWidth = pick(options[type !== 'minor' ? 'tickWidth' : 'minorTickWidth'], !type && axis.isXAxis ? 1 : 0), // X axis defaults to 1 tickColor = options[type !== 'minor' ? 'tickColor' : 'minorTickColor']; var mark = tick.mark; var isNewMark = !mark; if (tickSize) { // negate the length if (axis.opposite) { tickSize[0] = -tickSize[0]; } // First time, create it if (!mark) { /** * The rendered mark of the tick. * @name Highcharts.Tick#mark * @type {Highcharts.SVGElement|undefined} */ tick.mark = mark = renderer.path() .addClass('highcharts-' + (type ? type + '-' : '') + 'tick') .add(axis.axisGroup); if (!axis.chart.styledMode) { mark.attr({ stroke: tickColor, 'stroke-width': tickWidth }); } } mark[isNewMark ? 'attr' : 'animate']({ d: tick.getMarkPath(x, y, tickSize[0], mark.strokeWidth() * reverseCrisp, axis.horiz, renderer), opacity: opacity }); } }; /** * Renders the tick label. * Note: The label should already be created in init(), so it should only * have to be moved into place. * * @private * @param {Highcharts.PositionObject} xy The position vector of the label * @param {boolean} old Whether or not the tick is old * @param {number} opacity The opacity of the label * @param {number} index The index of the tick * @return {void} */ Tick.prototype.renderLabel = function (xy, old, opacity, index) { var tick = this, axis = tick.axis, horiz = axis.horiz, options = axis.options, label = tick.label, labelOptions = options.labels, step = labelOptions.step, tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset), x = xy.x, y = xy.y; var show = true; if (label && isNumber(x)) { label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step); // Apply show first and show last. If the tick is both first and // last, it is a single centered tick, in which case we show the // label anyway (#2100). if ((tick.isFirst && !tick.isLast && !options.showFirstLabel) || (tick.isLast && !tick.isFirst && !options.showLastLabel)) { show = false; // Handle label overflow and show or hide accordingly } else if (horiz && !labelOptions.step && !labelOptions.rotation && !old && opacity !== 0) { tick.handleOverflow(xy); } // apply step if (step && index % step) { // show those indices dividable by step show = false; } // Set the new position, and show or hide if (show && isNumber(xy.y)) { xy.opacity = opacity; label[tick.isNewLabel ? 'attr' : 'animate'](xy); tick.isNewLabel = false; } else { label.attr('y', -9999); // #1338 tick.isNewLabel = true; } } }; /** * Replace labels with the moved ones to perform animation. Additionally * destroy unused labels. * * @private * @function Highcharts.Tick#replaceMovedLabel * @return {void} */ Tick.prototype.replaceMovedLabel = function () { var tick = this, label = tick.label, axis = tick.axis, reversed = axis.reversed; var x, y; // Animate and destroy if (label && !tick.isNew) { x = axis.horiz ? (reversed ? axis.left : axis.width + axis.left) : label.xy.x; y = axis.horiz ? label.xy.y : (reversed ? axis.width + axis.top : axis.top); label.animate({ x: x, y: y, opacity: 0 }, void 0, label.destroy); delete tick.label; } axis.isDirty = true; tick.label = tick.movedLabel; delete tick.movedLabel; }; return Tick; }()); /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * Optional parameters for the tick. * @private * @interface Highcharts.TickParametersObject */ /** * Set category for the tick. * @name Highcharts.TickParametersObject#category * @type {string|undefined} */ /** * @name Highcharts.TickParametersObject#options * @type {Highcharts.Dictionary<any>|undefined} */ /** * Set tickmarkOffset for the tick. * @name Highcharts.TickParametersObject#tickmarkOffset * @type {number|undefined} */ /** * Additonal time tick information. * * @interface Highcharts.TimeTicksInfoObject * @extends Highcharts.TimeNormalizedObject */ /** * @name Highcharts.TimeTicksInfoObject#higherRanks * @type {Array<string>} */ /** * @name Highcharts.TimeTicksInfoObject#totalRange * @type {number} */ ''; // detach doclets above return Tick; }); _registerModule(_modules, 'Core/Axis/Axis.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Axis/AxisDefaults.js'], _modules['Core/Color/Color.js'], _modules['Core/Foundation.js'], _modules['Core/Globals.js'], _modules['Core/Color/Palette.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Axis/Tick.js'], _modules['Core/Utilities.js']], function (A, AxisDefaults, Color, F, H, Palette, D, Tick, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var animObject = A.animObject; var registerEventOptions = F.registerEventOptions; var deg2rad = H.deg2rad; var defaultOptions = D.defaultOptions; var arrayMax = U.arrayMax, arrayMin = U.arrayMin, clamp = U.clamp, correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, erase = U.erase, error = U.error, extend = U.extend, fireEvent = U.fireEvent, getMagnitude = U.getMagnitude, isArray = U.isArray, isNumber = U.isNumber, isString = U.isString, merge = U.merge, normalizeTickInterval = U.normalizeTickInterval, objectEach = U.objectEach, pick = U.pick, relativeLength = U.relativeLength, removeEvent = U.removeEvent, splat = U.splat, syncTimeout = U.syncTimeout; /* * * * Class * * */ /** * Create a new axis object. Called internally when instanciating a new chart or * adding axes by {@link Highcharts.Chart#addAxis}. * * A chart can have from 0 axes (pie chart) to multiples. In a normal, single * series cartesian chart, there is one X axis and one Y axis. * * The X axis or axes are referenced by {@link Highcharts.Chart.xAxis}, which is * an array of Axis objects. If there is only one axis, it can be referenced * through `chart.xAxis[0]`, and multiple axes have increasing indices. The same * pattern goes for Y axes. * * If you need to get the axes from a series object, use the `series.xAxis` and * `series.yAxis` properties. These are not arrays, as one series can only be * associated to one X and one Y axis. * * A third way to reference the axis programmatically is by `id`. Add an `id` in * the axis configuration options, and get the axis by * {@link Highcharts.Chart#get}. * * Configuration options for the axes are given in options.xAxis and * options.yAxis. * * @class * @name Highcharts.Axis * * @param {Highcharts.Chart} chart * The Chart instance to apply the axis on. * * @param {Highcharts.AxisOptions} userOptions * Axis options. */ var Axis = /** @class */ (function () { /* * * * Constructors * * */ function Axis(chart, userOptions) { this.alternateBands = void 0; this.bottom = void 0; this.categories = void 0; this.chart = void 0; this.closestPointRange = void 0; this.coll = void 0; this.eventOptions = void 0; this.hasNames = void 0; this.hasVisibleSeries = void 0; this.height = void 0; this.isLinked = void 0; this.labelEdge = void 0; // @todo this.labelFormatter = void 0; this.left = void 0; this.len = void 0; this.max = void 0; this.maxLabelLength = void 0; this.min = void 0; this.minorTickInterval = void 0; this.minorTicks = void 0; this.minPixelPadding = void 0; this.names = void 0; this.offset = void 0; this.options = void 0; this.overlap = void 0; this.paddedTicks = void 0; this.plotLinesAndBands = void 0; this.plotLinesAndBandsGroups = void 0; this.pointRange = void 0; this.pointRangePadding = void 0; this.pos = void 0; this.positiveValuesOnly = void 0; this.right = void 0; this.series = void 0; this.side = void 0; this.tickAmount = void 0; this.tickInterval = void 0; this.tickmarkOffset = void 0; this.tickPositions = void 0; this.tickRotCorr = void 0; this.ticks = void 0; this.top = void 0; this.transA = void 0; this.transB = void 0; this.translationSlope = void 0; this.userOptions = void 0; this.visible = void 0; this.width = void 0; this.zoomEnabled = void 0; this.init(chart, userOptions); } /* * * * Functions * * */ /** * Overrideable function to initialize the axis. * * @see {@link Axis} * * @function Highcharts.Axis#init * * @param {Highcharts.Chart} chart * The Chart instance to apply the axis on. * * @param {AxisOptions} userOptions * Axis options. * * @fires Highcharts.Axis#event:afterInit * @fires Highcharts.Axis#event:init */ Axis.prototype.init = function (chart, userOptions) { var isXAxis = userOptions.isX, axis = this; /** * The Chart that the axis belongs to. * * @name Highcharts.Axis#chart * @type {Highcharts.Chart} */ axis.chart = chart; /** * Whether the axis is horizontal. * * @name Highcharts.Axis#horiz * @type {boolean|undefined} */ axis.horiz = chart.inverted && !axis.isZAxis ? !isXAxis : isXAxis; /** * Whether the axis is the x-axis. * * @name Highcharts.Axis#isXAxis * @type {boolean|undefined} */ axis.isXAxis = isXAxis; /** * The collection where the axis belongs, for example `xAxis`, `yAxis` * or `colorAxis`. Corresponds to properties on Chart, for example * {@link Chart.xAxis}. * * @name Highcharts.Axis#coll * @type {string} */ axis.coll = axis.coll || (isXAxis ? 'xAxis' : 'yAxis'); fireEvent(this, 'init', { userOptions: userOptions }); axis.opposite = pick(userOptions.opposite, axis.opposite); // needed in setOptions /** * The side on which the axis is rendered. 0 is top, 1 is right, 2 * is bottom and 3 is left. * * @name Highcharts.Axis#side * @type {number} */ axis.side = pick(userOptions.side, axis.side, (axis.horiz ? (axis.opposite ? 0 : 2) : // top : bottom (axis.opposite ? 1 : 3)) // right : left ); /** * Current options for the axis after merge of defaults and user's * options. * * @name Highcharts.Axis#options * @type {Highcharts.AxisOptions} */ axis.setOptions(userOptions); var options = this.options, labelsOptions = options.labels, type = options.type; /** * User's options for this axis without defaults. * * @name Highcharts.Axis#userOptions * @type {Highcharts.AxisOptions} */ axis.userOptions = userOptions; axis.minPixelPadding = 0; /** * Whether the axis is reversed. Based on the `axis.reversed`, * option, but inverted charts have reversed xAxis by default. * * @name Highcharts.Axis#reversed * @type {boolean} */ axis.reversed = pick(options.reversed, axis.reversed); axis.visible = options.visible; axis.zoomEnabled = options.zoomEnabled; // Initial categories axis.hasNames = type === 'category' || options.categories === true; /** * If categories are present for the axis, names are used instead of * numbers for that axis. * * Since Highcharts 3.0, categories can also be extracted by giving each * point a name and setting axis type to `category`. However, if you * have multiple series, best practice remains defining the `categories` * array. * * @see [xAxis.categories](/highcharts/xAxis.categories) * * @name Highcharts.Axis#categories * @type {Array<string>} * @readonly */ axis.categories = options.categories || axis.hasNames; if (!axis.names) { // Preserve on update (#3830) axis.names = []; axis.names.keys = {}; } // Placeholder for plotlines and plotbands groups axis.plotLinesAndBandsGroups = {}; // Shorthand types axis.positiveValuesOnly = !!axis.logarithmic; // Flag, if axis is linked to another axis axis.isLinked = defined(options.linkedTo); /** * List of major ticks mapped by postition on axis. * * @see {@link Highcharts.Tick} * * @name Highcharts.Axis#ticks * @type {Highcharts.Dictionary<Highcharts.Tick>} */ axis.ticks = {}; axis.labelEdge = []; /** * List of minor ticks mapped by position on the axis. * * @see {@link Highcharts.Tick} * * @name Highcharts.Axis#minorTicks * @type {Highcharts.Dictionary<Highcharts.Tick>} */ axis.minorTicks = {}; // List of plotLines/Bands axis.plotLinesAndBands = []; // Alternate bands axis.alternateBands = {}; // Axis metrics axis.len = 0; axis.minRange = axis.userMinRange = options.minRange || options.maxZoom; axis.range = options.range; axis.offset = options.offset || 0; /** * The maximum value of the axis. In a logarithmic axis, this is the * logarithm of the real value, and the real value can be obtained from * {@link Axis#getExtremes}. * * @name Highcharts.Axis#max * @type {number|null} */ axis.max = null; /** * The minimum value of the axis. In a logarithmic axis, this is the * logarithm of the real value, and the real value can be obtained from * {@link Axis#getExtremes}. * * @name Highcharts.Axis#min * @type {number|null} */ axis.min = null; /** * The processed crosshair options. * * @name Highcharts.Axis#crosshair * @type {boolean|Highcharts.AxisCrosshairOptions} */ var crosshair = pick(options.crosshair, splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1]); axis.crosshair = crosshair === true ? {} : crosshair; // Register. Don't add it again on Axis.update(). if (chart.axes.indexOf(axis) === -1) { // if (isXAxis) { // #2713 chart.axes.splice(chart.xAxis.length, 0, axis); } else { chart.axes.push(axis); } chart[axis.coll].push(axis); } /** * All series associated to the axis. * * @name Highcharts.Axis#series * @type {Array<Highcharts.Series>} */ axis.series = axis.series || []; // populated by Series // Reversed axis if (chart.inverted && !axis.isZAxis && isXAxis && typeof axis.reversed === 'undefined') { axis.reversed = true; } axis.labelRotation = isNumber(labelsOptions.rotation) ? labelsOptions.rotation : void 0; // Register event listeners registerEventOptions(axis, options); fireEvent(this, 'afterInit'); }; /** * Merge and set options. * * @private * @function Highcharts.Axis#setOptions * * @param {Highcharts.AxisOptions} userOptions * Axis options. * * @fires Highcharts.Axis#event:afterSetOptions */ Axis.prototype.setOptions = function (userOptions) { this.options = merge(AxisDefaults.defaultXAxisOptions, (this.coll === 'yAxis') && AxisDefaults.defaultYAxisOptions, [ AxisDefaults.defaultTopAxisOptions, AxisDefaults.defaultRightAxisOptions, AxisDefaults.defaultBottomAxisOptions, AxisDefaults.defaultLeftAxisOptions ][this.side], merge( // if set in setOptions (#1053): defaultOptions[this.coll], userOptions)); fireEvent(this, 'afterSetOptions', { userOptions: userOptions }); }; /** * The default label formatter. The context is a special config object for * the label. In apps, use the * [labels.formatter](https://api.highcharts.com/highcharts/xAxis.labels.formatter) * instead, except when a modification is needed. * * @function Highcharts.Axis#defaultLabelFormatter * * @param {Highcharts.AxisLabelsFormatterContextObject} this * Formatter context of axis label. * * @param {Highcharts.AxisLabelsFormatterContextObject} [ctx] * Formatter context of axis label. * * @return {string} * The formatted label content. */ Axis.prototype.defaultLabelFormatter = function (ctx) { var axis = this.axis, chart = this.chart, numberFormatter = chart.numberFormatter, value = isNumber(this.value) ? this.value : NaN, time = axis.chart.time, categories = axis.categories, dateTimeLabelFormat = this.dateTimeLabelFormat, lang = defaultOptions.lang, numericSymbols = lang.numericSymbols, numSymMagnitude = lang.numericSymbolMagnitude || 1000, // make sure the same symbol is added for all labels on a linear // axis numericSymbolDetector = axis.logarithmic ? Math.abs(value) : axis.tickInterval; var i = numericSymbols && numericSymbols.length, multi, ret; if (categories) { ret = "" + this.value; } else if (dateTimeLabelFormat) { // datetime axis ret = time.dateFormat(dateTimeLabelFormat, value); } else if (i && numericSymbolDetector >= 1000) { // Decide whether we should add a numeric symbol like k (thousands) // or M (millions). If we are to enable this in tooltip or other // places as well, we can move this logic to the numberFormatter and // enable it by a parameter. while (i-- && typeof ret === 'undefined') { multi = Math.pow(numSymMagnitude, i + 1); if ( // Only accept a numeric symbol when the distance is more // than a full unit. So for example if the symbol is k, we // don't accept numbers like 0.5k. numericSymbolDetector >= multi && // Accept one decimal before the symbol. Accepts 0.5k but // not 0.25k. How does this work with the previous? (value * 10) % multi === 0 && numericSymbols[i] !== null && value !== 0) { // #5480 ret = numberFormatter(value / multi, -1) + numericSymbols[i]; } } } if (typeof ret === 'undefined') { if (Math.abs(value) >= 10000) { // add thousands separators ret = numberFormatter(value, -1); } else { // small numbers ret = numberFormatter(value, -1, void 0, ''); // #2466 } } return ret; }; /** * Get the minimum and maximum for the series of each axis. The function * analyzes the axis series and updates `this.dataMin` and `this.dataMax`. * * @private * @function Highcharts.Axis#getSeriesExtremes * * @fires Highcharts.Axis#event:afterGetSeriesExtremes * @fires Highcharts.Axis#event:getSeriesExtremes */ Axis.prototype.getSeriesExtremes = function () { var axis = this, chart = axis.chart; var xExtremes; fireEvent(this, 'getSeriesExtremes', null, function () { axis.hasVisibleSeries = false; // Reset properties in case we're redrawing (#3353) axis.dataMin = axis.dataMax = axis.threshold = null; axis.softThreshold = !axis.isXAxis; if (axis.stacking) { axis.stacking.buildStacks(); } // loop through this axis' series axis.series.forEach(function (series) { if (series.visible || !chart.options.chart.ignoreHiddenSeries) { var seriesOptions = series.options; var xData = void 0, threshold = seriesOptions.threshold, seriesDataMin = void 0, seriesDataMax = void 0; axis.hasVisibleSeries = true; // Validate threshold in logarithmic axes if (axis.positiveValuesOnly && threshold <= 0) { threshold = null; } // Get dataMin and dataMax for X axes if (axis.isXAxis) { xData = series.xData; if (xData.length) { var isPositive = function (number) { return number > 0; }; xData = axis.logarithmic ? xData.filter(axis.validatePositiveValue) : xData; xExtremes = series.getXExtremes(xData); // If xData contains values which is not numbers, // then filter them out. To prevent performance hit, // we only do this after we have already found // seriesDataMin because in most cases all data is // valid. #5234. seriesDataMin = xExtremes.min; seriesDataMax = xExtremes.max; if (!isNumber(seriesDataMin) && // #5010: !(seriesDataMin instanceof Date)) { xData = xData.filter(isNumber); xExtremes = series.getXExtremes(xData); // Do it again with valid data seriesDataMin = xExtremes.min; seriesDataMax = xExtremes.max; } if (xData.length) { axis.dataMin = Math.min(pick(axis.dataMin, seriesDataMin), seriesDataMin); axis.dataMax = Math.max(pick(axis.dataMax, seriesDataMax), seriesDataMax); } } // Get dataMin and dataMax for Y axes, as well as handle // stacking and processed data } else { // Get this particular series extremes var dataExtremes = series.applyExtremes(); // Get the dataMin and dataMax so far. If percentage is // used, the min and max are always 0 and 100. If // seriesDataMin and seriesDataMax is null, then series // doesn't have active y data, we continue with nulls if (isNumber(dataExtremes.dataMin)) { seriesDataMin = dataExtremes.dataMin; axis.dataMin = Math.min(pick(axis.dataMin, seriesDataMin), seriesDataMin); } if (isNumber(dataExtremes.dataMax)) { seriesDataMax = dataExtremes.dataMax; axis.dataMax = Math.max(pick(axis.dataMax, seriesDataMax), seriesDataMax); } // Adjust to threshold if (defined(threshold)) { axis.threshold = threshold; } // If any series has a hard threshold, it takes // precedence if (!seriesOptions.softThreshold || axis.positiveValuesOnly) { axis.softThreshold = false; } } } }); }); fireEvent(this, 'afterGetSeriesExtremes'); }; /** * Translate from axis value to pixel position on the chart, or back. Use * the `toPixels` and `toValue` functions in applications. * * @private * @function Highcharts.Axis#translate * * @param {number} val * TO-DO: parameter description * * @param {boolean|null} [backwards] * TO-DO: parameter description * * @param {boolean|null} [cvsCoord] * TO-DO: parameter description * * @param {boolean|null} [old] * TO-DO: parameter description * * @param {boolean} [handleLog] * TO-DO: parameter description * * @param {number} [pointPlacement] * TO-DO: parameter description * * @return {number|undefined} */ Axis.prototype.translate = function (val, backwards, cvsCoord, old, handleLog, pointPlacement) { var axis = (this.linkedParent || this), // #1417 localMin = old && axis.old ? axis.old.min : axis.min, minPixelPadding = axis.minPixelPadding, doPostTranslate = (axis.isOrdinal || axis.brokenAxis && axis.brokenAxis.hasBreaks || (axis.logarithmic && handleLog)) && axis.lin2val; var sign = 1, cvsOffset = 0, localA = old && axis.old ? axis.old.transA : axis.transA, returnValue = 0; if (!localA) { localA = axis.transA; } // In vertical axes, the canvas coordinates start from 0 at the top like // in SVG. if (cvsCoord) { sign *= -1; // canvas coordinates inverts the value cvsOffset = axis.len; } // Handle reversed axis if (axis.reversed) { sign *= -1; cvsOffset -= sign * (axis.sector || axis.len); } // From pixels to value if (backwards) { // reverse translation val = val * sign + cvsOffset; val -= minPixelPadding; // from chart pixel to value: returnValue = val / localA + localMin; if (doPostTranslate) { // log and ordinal axes returnValue = axis.lin2val(returnValue); } // From value to pixels } else { if (doPostTranslate) { // log and ordinal axes val = axis.val2lin(val); } returnValue = isNumber(localMin) ? (sign * (val - localMin) * localA + cvsOffset + (sign * minPixelPadding) + (isNumber(pointPlacement) ? localA * pointPlacement : 0)) : void 0; } return returnValue; }; /** * Translate a value in terms of axis units into pixels within the chart. * * @function Highcharts.Axis#toPixels * * @param {number} value * A value in terms of axis units. * * @param {boolean} paneCoordinates * Whether to return the pixel coordinate relative to the chart or just the * axis/pane itself. * * @return {number} * Pixel position of the value on the chart or axis. */ Axis.prototype.toPixels = function (value, paneCoordinates) { return this.translate(value, false, !this.horiz, null, true) + (paneCoordinates ? 0 : this.pos); }; /** * Translate a pixel position along the axis to a value in terms of axis * units. * * @function Highcharts.Axis#toValue * * @param {number} pixel * The pixel value coordinate. * * @param {boolean} [paneCoordinates=false] * Whether the input pixel is relative to the chart or just the axis/pane * itself. * * @return {number} * The axis value. */ Axis.prototype.toValue = function (pixel, paneCoordinates) { return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, null, true); }; /** * Create the path for a plot line that goes from the given value on * this axis, across the plot to the opposite side. Also used internally for * grid lines and crosshairs. * * @function Highcharts.Axis#getPlotLinePath * * @param {Highcharts.AxisPlotLinePathOptionsObject} options * Options for the path. * * @return {Highcharts.SVGPathArray|null} * The SVG path definition for the plot line. */ Axis.prototype.getPlotLinePath = function (options) { var axis = this, chart = axis.chart, axisLeft = axis.left, axisTop = axis.top, old = options.old, value = options.value, lineWidth = options.lineWidth, cHeight = (old && chart.oldChartHeight) || chart.chartHeight, cWidth = (old && chart.oldChartWidth) || chart.chartWidth, transB = axis.transB; var translatedValue = options.translatedValue, force = options.force, x1, y1, x2, y2, skip; // eslint-disable-next-line valid-jsdoc /** * Check if x is between a and b. If not, either move to a/b * or skip, depending on the force parameter. * @private */ function between(x, a, b) { if (force !== 'pass' && x < a || x > b) { if (force) { x = clamp(x, a, b); } else { skip = true; } } return x; } var evt = { value: value, lineWidth: lineWidth, old: old, force: force, acrossPanes: options.acrossPanes, translatedValue: translatedValue }; fireEvent(this, 'getPlotLinePath', evt, function (e) { translatedValue = pick(translatedValue, axis.translate(value, null, null, old)); // Keep the translated value within sane bounds, and avoid Infinity // to fail the isNumber test (#7709). translatedValue = clamp(translatedValue, -1e5, 1e5); x1 = x2 = Math.round(translatedValue + transB); y1 = y2 = Math.round(cHeight - translatedValue - transB); if (!isNumber(translatedValue)) { // no min or max skip = true; force = false; // #7175, don't force it when path is invalid } else if (axis.horiz) { y1 = axisTop; y2 = cHeight - axis.bottom; x1 = x2 = between(x1, axisLeft, axisLeft + axis.width); } else { x1 = axisLeft; x2 = cWidth - axis.right; y1 = y2 = between(y1, axisTop, axisTop + axis.height); } e.path = skip && !force ? null : chart.renderer.crispLine([['M', x1, y1], ['L', x2, y2]], lineWidth || 1); }); return evt.path; }; /** * Internal function to get the tick positions of a linear axis to round * values like whole tens or every five. * * @function Highcharts.Axis#getLinearTickPositions * * @param {number} tickInterval * The normalized tick interval. * * @param {number} min * Axis minimum. * * @param {number} max * Axis maximum. * * @return {Array<number>} * An array of axis values where ticks should be placed. */ Axis.prototype.getLinearTickPositions = function (tickInterval, min, max) { var roundedMin = correctFloat(Math.floor(min / tickInterval) * tickInterval), roundedMax = correctFloat(Math.ceil(max / tickInterval) * tickInterval), tickPositions = []; var pos, lastPos, precision; // When the precision is higher than what we filter out in // correctFloat, skip it (#6183). if (correctFloat(roundedMin + tickInterval) === roundedMin) { precision = 20; } // For single points, add a tick regardless of the relative position // (#2662, #6274) if (this.single) { return [min]; } // Populate the intermediate values pos = roundedMin; while (pos <= roundedMax) { // Place the tick on the rounded value tickPositions.push(pos); // Always add the raw tickInterval, not the corrected one. pos = correctFloat(pos + tickInterval, precision); // If the interval is not big enough in the current min - max range // to actually increase the loop variable, we need to break out to // prevent endless loop. Issue #619 if (pos === lastPos) { break; } // Record the last value lastPos = pos; } return tickPositions; }; /** * Resolve the new minorTicks/minorTickInterval options into the legacy * loosely typed minorTickInterval option. * * @function Highcharts.Axis#getMinorTickInterval * * @return {number|"auto"|null} */ Axis.prototype.getMinorTickInterval = function () { var options = this.options; if (options.minorTicks === true) { return pick(options.minorTickInterval, 'auto'); } if (options.minorTicks === false) { return null; } return options.minorTickInterval; }; /** * Internal function to return the minor tick positions. For logarithmic * axes, the same logic as for major ticks is reused. * * @function Highcharts.Axis#getMinorTickPositions * * @return {Array<number>} * An array of axis values where ticks should be placed. */ Axis.prototype.getMinorTickPositions = function () { var axis = this, options = axis.options, tickPositions = axis.tickPositions, minorTickInterval = axis.minorTickInterval, pointRangePadding = axis.pointRangePadding || 0, min = axis.min - pointRangePadding, // #1498 max = axis.max + pointRangePadding, // #1498 range = max - min; var minorTickPositions = [], pos; // If minor ticks get too dense, they are hard to read, and may cause // long running script. So we don't draw them. if (range && range / minorTickInterval < axis.len / 3) { // #3875 var logarithmic_1 = axis.logarithmic; if (logarithmic_1) { // For each interval in the major ticks, compute the minor ticks // separately. this.paddedTicks.forEach(function (_pos, i, paddedTicks) { if (i) { minorTickPositions.push.apply(minorTickPositions, logarithmic_1.getLogTickPositions(minorTickInterval, paddedTicks[i - 1], paddedTicks[i], true)); } }); } else if (axis.dateTime && this.getMinorTickInterval() === 'auto') { // #1314 minorTickPositions = minorTickPositions.concat(axis.getTimeTicks(axis.dateTime.normalizeTimeTickInterval(minorTickInterval), min, max, options.startOfWeek)); } else { for (pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval) { // Very, very, tight grid lines (#5771) if (pos === minorTickPositions[0]) { break; } minorTickPositions.push(pos); } } } if (minorTickPositions.length !== 0) { axis.trimTicks(minorTickPositions); // #3652 #3743 #1498 #6330 } return minorTickPositions; }; /** * Adjust the min and max for the minimum range. Keep in mind that the * series data is not yet processed, so we don't have information on data * cropping and grouping, or updated `axis.pointRange` or * `series.pointRange`. The data can't be processed until we have finally * established min and max. * * @private * @function Highcharts.Axis#adjustForMinRange */ Axis.prototype.adjustForMinRange = function () { var axis = this, options = axis.options, log = axis.logarithmic; var min = axis.min, max = axis.max, zoomOffset, spaceAvailable, closestDataRange = 0, i, distance, xData, loopLength, minArgs, maxArgs, minRange; // Set the automatic minimum range based on the closest point distance if (axis.isXAxis && typeof axis.minRange === 'undefined' && !log) { if (defined(options.min) || defined(options.max)) { axis.minRange = null; // don't do this again } else { // Find the closest distance between raw data points, as opposed // to closestPointRange that applies to processed points // (cropped and grouped) axis.series.forEach(function (series) { xData = series.xData; loopLength = series.xIncrement ? 1 : xData.length - 1; if (xData.length > 1) { for (i = loopLength; i > 0; i--) { distance = xData[i] - xData[i - 1]; if (!closestDataRange || distance < closestDataRange) { closestDataRange = distance; } } } }); axis.minRange = Math.min(closestDataRange * 5, axis.dataMax - axis.dataMin); } } // if minRange is exceeded, adjust if (max - min < axis.minRange) { spaceAvailable = axis.dataMax - axis.dataMin >= axis.minRange; minRange = axis.minRange; zoomOffset = (minRange - max + min) / 2; // if min and max options have been set, don't go beyond it minArgs = [ min - zoomOffset, pick(options.min, min - zoomOffset) ]; // If space is available, stay within the data range if (spaceAvailable) { minArgs[2] = axis.logarithmic ? axis.logarithmic.log2lin(axis.dataMin) : axis.dataMin; } min = arrayMax(minArgs); maxArgs = [ min + minRange, pick(options.max, min + minRange) ]; // If space is availabe, stay within the data range if (spaceAvailable) { maxArgs[2] = log ? log.log2lin(axis.dataMax) : axis.dataMax; } max = arrayMin(maxArgs); // now if the max is adjusted, adjust the min back if (max - min < minRange) { minArgs[0] = max - minRange; minArgs[1] = pick(options.min, max - minRange); min = arrayMax(minArgs); } } // Record modified extremes axis.min = min; axis.max = max; }; /** * Find the closestPointRange across all series. * * @private * @function Highcharts.Axis#getClosest * * @return {number} */ Axis.prototype.getClosest = function () { var ret; if (this.categories) { ret = 1; } else { this.series.forEach(function (series) { var seriesClosest = series.closestPointRange, visible = series.visible || !series.chart.options.chart.ignoreHiddenSeries; if (!series.noSharedTooltip && defined(seriesClosest) && visible) { ret = defined(ret) ? Math.min(ret, seriesClosest) : seriesClosest; } }); } return ret; }; /** * When a point name is given and no x, search for the name in the existing * categories, or if categories aren't provided, search names or create a * new category (#2522). * * @private * @function Highcharts.Axis#nameToX * * @param {Highcharts.Point} point * The point to inspect. * * @return {number} * The X value that the point is given. */ Axis.prototype.nameToX = function (point) { var explicitCategories = isArray(this.categories), names = explicitCategories ? this.categories : this.names; var nameX = point.options.x, x; point.series.requireSorting = false; if (!defined(nameX)) { nameX = this.options.uniqueNames ? (explicitCategories ? names.indexOf(point.name) : pick(names.keys[point.name], -1)) : point.series.autoIncrement(); } if (nameX === -1) { // Not found in currenct categories if (!explicitCategories) { x = names.length; } } else { x = nameX; } // Write the last point's name to the names array if (typeof x !== 'undefined') { this.names[x] = point.name; // Backwards mapping is much faster than array searching (#7725) this.names.keys[point.name] = x; } return x; }; /** * When changes have been done to series data, update the axis.names. * * @private * @function Highcharts.Axis#updateNames */ Axis.prototype.updateNames = function () { var axis = this, names = this.names, i = names.length; if (i > 0) { Object.keys(names.keys).forEach(function (key) { delete (names.keys)[key]; }); names.length = 0; this.minRange = this.userMinRange; // Reset (this.series || []).forEach(function (series) { // Reset incrementer (#5928) series.xIncrement = null; // When adding a series, points are not yet generated if (!series.points || series.isDirtyData) { // When we're updating the series with data that is longer // than it was, and cropThreshold is passed, we need to make // sure that the axis.max is increased _before_ running the // premature processData. Otherwise this early iteration of // processData will crop the points to axis.max, and the // names array will be too short (#5857). axis.max = Math.max(axis.max, series.xData.length - 1); series.processData(); series.generatePoints(); } series.data.forEach(function (point, i) { var x; if (point && point.options && typeof point.name !== 'undefined' // #9562 ) { x = axis.nameToX(point); if (typeof x !== 'undefined' && x !== point.x) { point.x = x; series.xData[i] = x; } } }); }); } }; /** * Update translation information. * * @private * @function Highcharts.Axis#setAxisTranslation * * @fires Highcharts.Axis#event:afterSetAxisTranslation */ Axis.prototype.setAxisTranslation = function () { var axis = this, range = axis.max - axis.min, linkedParent = axis.linkedParent, hasCategories = !!axis.categories, isXAxis = axis.isXAxis; var pointRange = axis.axisPointRange || 0, closestPointRange, minPointOffset = 0, pointRangePadding = 0, ordinalCorrection, transA = axis.transA; // Adjust translation for padding. Y axis with categories need to go // through the same (#1784). if (isXAxis || hasCategories || pointRange) { // Get the closest points closestPointRange = axis.getClosest(); if (linkedParent) { minPointOffset = linkedParent.minPointOffset; pointRangePadding = linkedParent.pointRangePadding; } else { axis.series.forEach(function (series) { var seriesPointRange = hasCategories ? 1 : (isXAxis ? pick(series.options.pointRange, closestPointRange, 0) : (axis.axisPointRange || 0)), // #2806 pointPlacement = series.options.pointPlacement; pointRange = Math.max(pointRange, seriesPointRange); if (!axis.single || hasCategories) { // TODO: series should internally set x- and y- // pointPlacement to simplify this logic. var isPointPlacementAxis = series.is('xrange') ? !isXAxis : isXAxis; // minPointOffset is the value padding to the left of // the axis in order to make room for points with a // pointRange, typically columns. When the // pointPlacement option is 'between' or 'on', this // padding does not apply. minPointOffset = Math.max(minPointOffset, isPointPlacementAxis && isString(pointPlacement) ? 0 : seriesPointRange / 2); // Determine the total padding needed to the length of // the axis to make room for the pointRange. If the // series' pointPlacement is 'on', no padding is added. pointRangePadding = Math.max(pointRangePadding, isPointPlacementAxis && pointPlacement === 'on' ? 0 : seriesPointRange); } }); } // Record minPointOffset and pointRangePadding ordinalCorrection = axis.ordinal && axis.ordinal.slope && closestPointRange ? axis.ordinal.slope / closestPointRange : 1; // #988, #1853 axis.minPointOffset = minPointOffset = minPointOffset * ordinalCorrection; axis.pointRangePadding = pointRangePadding = pointRangePadding * ordinalCorrection; // pointRange means the width reserved for each point, like in a // column chart axis.pointRange = Math.min(pointRange, axis.single && hasCategories ? 1 : range); // closestPointRange means the closest distance between points. In // columns it is mostly equal to pointRange, but in lines pointRange // is 0 while closestPointRange is some other value if (isXAxis) { axis.closestPointRange = closestPointRange; } } // Secondary values axis.translationSlope = axis.transA = transA = axis.staticScale || axis.len / ((range + pointRangePadding) || 1); // Translation addend axis.transB = axis.horiz ? axis.left : axis.bottom; axis.minPixelPadding = transA * minPointOffset; fireEvent(this, 'afterSetAxisTranslation'); }; /** * @private * @function Highcharts.Axis#minFromRange * * @return {number|undefined} */ Axis.prototype.minFromRange = function () { var axis = this; return axis.max - axis.range; }; /** * Set the tick positions to round values and optionally extend the extremes * to the nearest tick. * * @private * @function Highcharts.Axis#setTickInterval * * @param {boolean} secondPass * TO-DO: parameter description * * @fires Highcharts.Axis#event:foundExtremes */ Axis.prototype.setTickInterval = function (secondPass) { var axis = this, chart = axis.chart, log = axis.logarithmic, options = axis.options, isXAxis = axis.isXAxis, isLinked = axis.isLinked, tickPixelIntervalOption = options.tickPixelInterval, categories = axis.categories, softThreshold = axis.softThreshold; var maxPadding = options.maxPadding, minPadding = options.minPadding, length, linkedParentExtremes, tickIntervalOption = options.tickInterval, threshold = isNumber(axis.threshold) ? axis.threshold : null, thresholdMin, thresholdMax, hardMin, hardMax; if (!axis.dateTime && !categories && !isLinked) { this.getTickAmount(); } // Min or max set either by zooming/setExtremes or initial options hardMin = pick(axis.userMin, options.min); hardMax = pick(axis.userMax, options.max); // Linked axis gets the extremes from the parent axis if (isLinked) { axis.linkedParent = chart[axis.coll][options.linkedTo]; linkedParentExtremes = axis.linkedParent.getExtremes(); axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin); axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax); if (options.type !== axis.linkedParent.options.type) { // Can't link axes of different type error(11, 1, chart); } // Initial min and max from the extreme data values } else { // Adjust to hard threshold if (softThreshold && defined(threshold)) { if (axis.dataMin >= threshold) { thresholdMin = threshold; minPadding = 0; } else if (axis.dataMax <= threshold) { thresholdMax = threshold; maxPadding = 0; } } axis.min = pick(hardMin, thresholdMin, axis.dataMin); axis.max = pick(hardMax, thresholdMax, axis.dataMax); } if (log) { if (axis.positiveValuesOnly && !secondPass && Math.min(axis.min, pick(axis.dataMin, axis.min)) <= 0) { // #978 // Can't plot negative values on log axis error(10, 1, chart); } // The correctFloat cures #934, float errors on full tens. But it // was too aggressive for #4360 because of conversion back to lin, // therefore use precision 15. axis.min = correctFloat(log.log2lin(axis.min), 16); axis.max = correctFloat(log.log2lin(axis.max), 16); } // handle zoomed range if (axis.range && defined(axis.max)) { // #618, #6773: axis.userMin = axis.min = hardMin = Math.max(axis.dataMin, axis.minFromRange()); axis.userMax = hardMax = axis.max; axis.range = null; // don't use it when running setExtremes } // Hook for Highcharts Stock Scroller. // Consider combining with beforePadding. fireEvent(axis, 'foundExtremes'); // Hook for adjusting this.min and this.max. Used by bubble series. if (axis.beforePadding) { axis.beforePadding(); } // adjust min and max for the minimum range axis.adjustForMinRange(); // Pad the values to get clear of the chart's edges. To avoid // tickInterval taking the padding into account, we do this after // computing tick interval (#1337). if (!categories && !axis.axisPointRange && !(axis.stacking && axis.stacking.usePercentage) && !isLinked && defined(axis.min) && defined(axis.max)) { length = axis.max - axis.min; if (length) { if (!defined(hardMin) && minPadding) { axis.min -= length * minPadding; } if (!defined(hardMax) && maxPadding) { axis.max += length * maxPadding; } } } // Handle options for floor, ceiling, softMin and softMax (#6359) if (!isNumber(axis.userMin)) { if (isNumber(options.softMin) && options.softMin < axis.min) { axis.min = hardMin = options.softMin; // #6894 } if (isNumber(options.floor)) { axis.min = Math.max(axis.min, options.floor); } } if (!isNumber(axis.userMax)) { if (isNumber(options.softMax) && options.softMax > axis.max) { axis.max = hardMax = options.softMax; // #6894 } if (isNumber(options.ceiling)) { axis.max = Math.min(axis.max, options.ceiling); } } // When the threshold is soft, adjust the extreme value only if the data // extreme and the padded extreme land on either side of the threshold. // For example, a series of [0, 1, 2, 3] would make the yAxis add a tick // for -1 because of the default minPadding and startOnTick options. // This is prevented by the softThreshold option. if (softThreshold && defined(axis.dataMin)) { threshold = threshold || 0; if (!defined(hardMin) && axis.min < threshold && axis.dataMin >= threshold) { axis.min = axis.options.minRange ? Math.min(threshold, axis.max - axis.minRange) : threshold; } else if (!defined(hardMax) && axis.max > threshold && axis.dataMax <= threshold) { axis.max = axis.options.minRange ? Math.max(threshold, axis.min + axis.minRange) : threshold; } } // If min is bigger than highest, or if max less than lowest value, the // chart should not render points. (#14417) if (isNumber(axis.min) && isNumber(axis.max) && !this.chart.polar && (axis.min > axis.max)) { if (defined(axis.options.min)) { axis.max = axis.min; } else if (defined(axis.options.max)) { axis.min = axis.max; } } // get tickInterval if (axis.min === axis.max || typeof axis.min === 'undefined' || typeof axis.max === 'undefined') { axis.tickInterval = 1; } else if (isLinked && axis.linkedParent && !tickIntervalOption && tickPixelIntervalOption === axis.linkedParent.options.tickPixelInterval) { axis.tickInterval = tickIntervalOption = axis.linkedParent.tickInterval; } else { axis.tickInterval = pick(tickIntervalOption, this.tickAmount ? ((axis.max - axis.min) / Math.max(this.tickAmount - 1, 1)) : void 0, // For categoried axis, 1 is default, for linear axis use // tickPix categories ? 1 : // don't let it be more than the data range (axis.max - axis.min) * tickPixelIntervalOption / Math.max(axis.len, tickPixelIntervalOption)); } // Now we're finished detecting min and max, crop and group series data. // This is in turn needed in order to find tick positions in ordinal // axes. if (isXAxis && !secondPass) { axis.series.forEach(function (series) { series.processData(axis.min !== (axis.old && axis.old.min) || axis.max !== (axis.old && axis.old.max)); }); } // set the translation factor used in translate function axis.setAxisTranslation(); // hook for ordinal axes and radial axes fireEvent(this, 'initialAxisTranslation'); // In column-like charts, don't cramp in more ticks than there are // points (#1943, #4184) if (axis.pointRange && !tickIntervalOption) { axis.tickInterval = Math.max(axis.pointRange, axis.tickInterval); } // Before normalizing the tick interval, handle minimum tick interval. // This applies only if tickInterval is not defined. var minTickInterval = pick(options.minTickInterval, // In datetime axes, don't go below the data interval, except when // there are scatter-like series involved (#13369). axis.dateTime && !axis.series.some(function (s) { return s.noSharedTooltip; }) ? axis.closestPointRange : 0); if (!tickIntervalOption && axis.tickInterval < minTickInterval) { axis.tickInterval = minTickInterval; } // for linear axes, get magnitude and normalize the interval if (!axis.dateTime && !axis.logarithmic && !tickIntervalOption) { axis.tickInterval = normalizeTickInterval(axis.tickInterval, void 0, getMagnitude(axis.tickInterval), pick(options.allowDecimals, // If the tick interval is greather than 0.5, avoid // decimals, as linear axes are often used to render // discrete values. #3363. If a tick amount is set, allow // decimals by default, as it increases the chances for a // good fit. axis.tickInterval < 0.5 || this.tickAmount !== void 0), !!this.tickAmount); } // Prevent ticks from getting so close that we can't draw the labels if (!this.tickAmount) { axis.tickInterval = axis.unsquish(); } this.setTickPositions(); }; /** * Now we have computed the normalized tickInterval, get the tick positions. * * @private * @function Highcharts.Axis#setTickPositions * * @fires Highcharts.Axis#event:afterSetTickPositions */ Axis.prototype.setTickPositions = function () { var axis = this, options = this.options, tickPositionsOption = options.tickPositions, minorTickIntervalOption = this.getMinorTickInterval(), hasVerticalPanning = this.hasVerticalPanning(), isColorAxis = this.coll === 'colorAxis', startOnTick = (isColorAxis || !hasVerticalPanning) && options.startOnTick, endOnTick = (isColorAxis || !hasVerticalPanning) && options.endOnTick; var tickPositions, tickPositioner = options.tickPositioner; // Set the tickmarkOffset this.tickmarkOffset = (this.categories && options.tickmarkPlacement === 'between' && this.tickInterval === 1) ? 0.5 : 0; // #3202 // get minorTickInterval this.minorTickInterval = minorTickIntervalOption === 'auto' && this.tickInterval ? this.tickInterval / 5 : minorTickIntervalOption; // When there is only one point, or all points have the same value on // this axis, then min and max are equal and tickPositions.length is 0 // or 1. In this case, add some padding in order to center the point, // but leave it with one tick. #1337. this.single = this.min === this.max && defined(this.min) && !this.tickAmount && ( // Data is on integer (#6563) parseInt(this.min, 10) === this.min || // Between integers and decimals are not allowed (#6274) options.allowDecimals !== false); /** * Contains the current positions that are laid out on the axis. The * positions are numbers in terms of axis values. In a category axis * they are integers, in a datetime axis they are also integers, but * designating milliseconds. * * This property is read only - for modifying the tick positions, use * the `tickPositioner` callback or [axis.tickPositions( * https://api.highcharts.com/highcharts/xAxis.tickPositions) option * instead. * * @name Highcharts.Axis#tickPositions * @type {Highcharts.AxisTickPositionsArray|undefined} */ this.tickPositions = // Find the tick positions. Work on a copy (#1565) tickPositions = (tickPositionsOption && tickPositionsOption.slice()); if (!tickPositions) { // Too many ticks (#6405). Create a friendly warning and provide two // ticks so at least we can show the data series. if ((!axis.ordinal || !axis.ordinal.positions) && ((this.max - this.min) / this.tickInterval > Math.max(2 * this.len, 200))) { tickPositions = [this.min, this.max]; error(19, false, this.chart); } else if (axis.dateTime) { tickPositions = axis.getTimeTicks(axis.dateTime.normalizeTimeTickInterval(this.tickInterval, options.units), this.min, this.max, options.startOfWeek, axis.ordinal && axis.ordinal.positions, this.closestPointRange, true); } else if (axis.logarithmic) { tickPositions = axis.logarithmic.getLogTickPositions(this.tickInterval, this.min, this.max); } else { tickPositions = this.getLinearTickPositions(this.tickInterval, this.min, this.max); } // Too dense ticks, keep only the first and last (#4477) if (tickPositions.length > this.len) { tickPositions = [tickPositions[0], tickPositions.pop()]; // Reduce doubled value (#7339) if (tickPositions[0] === tickPositions[1]) { tickPositions.length = 1; } } this.tickPositions = tickPositions; // Run the tick positioner callback, that allows modifying auto tick // positions. if (tickPositioner) { tickPositioner = tickPositioner.apply(axis, [this.min, this.max]); if (tickPositioner) { this.tickPositions = tickPositions = tickPositioner; } } } // Reset min/max or remove extremes based on start/end on tick this.paddedTicks = tickPositions.slice(0); // Used for logarithmic minor this.trimTicks(tickPositions, startOnTick, endOnTick); if (!this.isLinked) { // Substract half a unit (#2619, #2846, #2515, #3390), // but not in case of multiple ticks (#6897) if (this.single && tickPositions.length < 2 && !this.categories && !this.series.some(function (s) { return (s.is('heatmap') && s.options.pointPlacement === 'between'); })) { this.min -= 0.5; this.max += 0.5; } if (!tickPositionsOption && !tickPositioner) { this.adjustTickAmount(); } } fireEvent(this, 'afterSetTickPositions'); }; /** * Handle startOnTick and endOnTick by either adapting to padding min/max or * rounded min/max. Also handle single data points. * * @private * @function Highcharts.Axis#trimTicks * * @param {Array<number>} tickPositions * TO-DO: parameter description * * @param {boolean} [startOnTick] * TO-DO: parameter description * * @param {boolean} [endOnTick] * TO-DO: parameter description */ Axis.prototype.trimTicks = function (tickPositions, startOnTick, endOnTick) { var roundedMin = tickPositions[0], roundedMax = tickPositions[tickPositions.length - 1], minPointOffset = (!this.isOrdinal && this.minPointOffset) || 0; // (#12716) fireEvent(this, 'trimTicks'); if (!this.isLinked) { if (startOnTick && roundedMin !== -Infinity) { // #6502 this.min = roundedMin; } else { while (this.min - minPointOffset > tickPositions[0]) { tickPositions.shift(); } } if (endOnTick) { this.max = roundedMax; } else { while (this.max + minPointOffset < tickPositions[tickPositions.length - 1]) { tickPositions.pop(); } } // If no tick are left, set one tick in the middle (#3195) if (tickPositions.length === 0 && defined(roundedMin) && !this.options.tickPositions) { tickPositions.push((roundedMax + roundedMin) / 2); } } }; /** * Check if there are multiple axes in the same pane. * * @private * @function Highcharts.Axis#alignToOthers * * @return {boolean|undefined} * True if there are other axes. */ Axis.prototype.alignToOthers = function () { var axis = this, others = // Whether there is another axis to pair with this one {}, options = axis.options; var hasOther; if ( // Only if alignTicks is true this.chart.options.chart.alignTicks !== false && options.alignTicks && // Disabled when startOnTick or endOnTick are false (#7604) options.startOnTick !== false && options.endOnTick !== false && // Don't try to align ticks on a log axis, they are not evenly // spaced (#6021) !axis.logarithmic) { this.chart[this.coll].forEach(function (axis) { var otherOptions = axis.options, horiz = axis.horiz, key = [ horiz ? otherOptions.left : otherOptions.top, otherOptions.width, otherOptions.height, otherOptions.pane ].join(','); if (axis.series.length) { // #4442 if (others[key]) { hasOther = true; // #4201 } else { others[key] = 1; } } }); } return hasOther; }; /** * Find the max ticks of either the x and y axis collection, and record it * in `this.tickAmount`. * * @private * @function Highcharts.Axis#getTickAmount */ Axis.prototype.getTickAmount = function () { var axis = this, options = this.options, tickPixelInterval = options.tickPixelInterval; var tickAmount = options.tickAmount; if (!defined(options.tickInterval) && !tickAmount && this.len < tickPixelInterval && !this.isRadial && !axis.logarithmic && options.startOnTick && options.endOnTick) { tickAmount = 2; } if (!tickAmount && this.alignToOthers()) { // Add 1 because 4 tick intervals require 5 ticks (including first // and last) tickAmount = Math.ceil(this.len / tickPixelInterval) + 1; } // For tick amounts of 2 and 3, compute five ticks and remove the // intermediate ones. This prevents the axis from adding ticks that are // too far away from the data extremes. if (tickAmount < 4) { this.finalTickAmt = tickAmount; tickAmount = 5; } this.tickAmount = tickAmount; }; /** * When using multiple axes, adjust the number of ticks to match the highest * number of ticks in that group. * * @private * @function Highcharts.Axis#adjustTickAmount */ Axis.prototype.adjustTickAmount = function () { var axis = this, axisOptions = axis.options, tickInterval = axis.tickInterval, tickPositions = axis.tickPositions, tickAmount = axis.tickAmount, finalTickAmt = axis.finalTickAmt, currentTickAmount = tickPositions && tickPositions.length, threshold = pick(axis.threshold, axis.softThreshold ? 0 : null); var len, i; if (axis.hasData() && isNumber(axis.min) && isNumber(axis.max)) { // #14769 if (currentTickAmount < tickAmount) { while (tickPositions.length < tickAmount) { // Extend evenly for both sides unless we're on the // threshold (#3965) if (tickPositions.length % 2 || axis.min === threshold) { // to the end tickPositions.push(correctFloat(tickPositions[tickPositions.length - 1] + tickInterval)); } else { // to the start tickPositions.unshift(correctFloat(tickPositions[0] - tickInterval)); } } axis.transA *= (currentTickAmount - 1) / (tickAmount - 1); // Do not crop when ticks are not extremes (#9841) axis.min = axisOptions.startOnTick ? tickPositions[0] : Math.min(axis.min, tickPositions[0]); axis.max = axisOptions.endOnTick ? tickPositions[tickPositions.length - 1] : Math.max(axis.max, tickPositions[tickPositions.length - 1]); // We have too many ticks, run second pass to try to reduce ticks } else if (currentTickAmount > tickAmount) { axis.tickInterval *= 2; axis.setTickPositions(); } // The finalTickAmt property is set in getTickAmount if (defined(finalTickAmt)) { i = len = tickPositions.length; while (i--) { if ( // Remove every other tick (finalTickAmt === 3 && i % 2 === 1) || // Remove all but first and last (finalTickAmt <= 2 && i > 0 && i < len - 1)) { tickPositions.splice(i, 1); } } axis.finalTickAmt = void 0; } } }; /** * Set the scale based on data min and max, user set min and max or options. * * @private * @function Highcharts.Axis#setScale * * @fires Highcharts.Axis#event:afterSetScale */ Axis.prototype.setScale = function () { var axis = this; var isDirtyData = false, isXAxisDirty = false; axis.series.forEach(function (series) { isDirtyData = isDirtyData || series.isDirtyData || series.isDirty; // When x axis is dirty, we need new data extremes for y as // well: isXAxisDirty = (isXAxisDirty || (series.xAxis && series.xAxis.isDirty) || false); }); // set the new axisLength axis.setAxisSize(); var isDirtyAxisLength = axis.len !== (axis.old && axis.old.len); // do we really need to go through all this? if (isDirtyAxisLength || isDirtyData || isXAxisDirty || axis.isLinked || axis.forceRedraw || axis.userMin !== (axis.old && axis.old.userMin) || axis.userMax !== (axis.old && axis.old.userMax) || axis.alignToOthers()) { if (axis.stacking) { axis.stacking.resetStacks(); } axis.forceRedraw = false; // get data extremes if needed axis.getSeriesExtremes(); // get fixed positions based on tickInterval axis.setTickInterval(); // Mark as dirty if it is not already set to dirty and extremes have // changed. #595. if (!axis.isDirty) { axis.isDirty = isDirtyAxisLength || axis.min !== (axis.old && axis.old.min) || axis.max !== (axis.old && axis.old.max); } } else if (axis.stacking) { axis.stacking.cleanStacks(); } // Recalculate panning state object, when the data // has changed. It is required when vertical panning is enabled. if (isDirtyData && axis.panningState) { axis.panningState.isDirty = true; } fireEvent(this, 'afterSetScale'); }; /** * Set the minimum and maximum of the axes after render time. If the * `startOnTick` and `endOnTick` options are true, the minimum and maximum * values are rounded off to the nearest tick. To prevent this, these * options can be set to false before calling setExtremes. Also, setExtremes * will not allow a range lower than the `minRange` option, which by default * is the range of five points. * * @sample highcharts/members/axis-setextremes/ * Set extremes from a button * @sample highcharts/members/axis-setextremes-datetime/ * Set extremes on a datetime axis * @sample highcharts/members/axis-setextremes-off-ticks/ * Set extremes off ticks * @sample stock/members/axis-setextremes/ * Set extremes in Highcharts Stock * @sample maps/members/axis-setextremes/ * Set extremes in Highmaps * * @function Highcharts.Axis#setExtremes * * @param {number} [newMin] * The new minimum value. * * @param {number} [newMax] * The new maximum value. * * @param {boolean} [redraw=true] * Whether to redraw the chart or wait for an explicit call to * {@link Highcharts.Chart#redraw} * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] * Enable or modify animations. * * @param {*} [eventArguments] * Arguments to be accessed in event handler. * * @fires Highcharts.Axis#event:setExtremes */ Axis.prototype.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) { var axis = this, chart = axis.chart; redraw = pick(redraw, true); // defaults to true axis.series.forEach(function (serie) { delete serie.kdTree; }); // Extend the arguments with min and max eventArguments = extend(eventArguments, { min: newMin, max: newMax }); // Fire the event fireEvent(axis, 'setExtremes', eventArguments, function () { axis.userMin = newMin; axis.userMax = newMax; axis.eventArgs = eventArguments; if (redraw) { chart.redraw(animation); } }); }; /** * Overridable method for zooming chart. Pulled out in a separate method to * allow overriding in stock charts. * * @private * @function Highcharts.Axis#zoom * * @param {number} newMin * TO-DO: parameter description * * @param {number} newMax * TO-DO: parameter description * * @return {boolean} */ Axis.prototype.zoom = function (newMin, newMax) { var axis = this, dataMin = this.dataMin, dataMax = this.dataMax, options = this.options, min = Math.min(dataMin, pick(options.min, dataMin)), max = Math.max(dataMax, pick(options.max, dataMax)), evt = { newMin: newMin, newMax: newMax }; fireEvent(this, 'zoom', evt, function (e) { // Use e.newMin and e.newMax - event handlers may have altered them var newMin = e.newMin, newMax = e.newMax; if (newMin !== axis.min || newMax !== axis.max) { // #5790 // Prevent pinch zooming out of range. Check for defined is for // #1946. #1734. if (!axis.allowZoomOutside) { // #6014, sometimes newMax will be smaller than min (or // newMin will be larger than max). if (defined(dataMin)) { if (newMin < min) { newMin = min; } if (newMin > max) { newMin = max; } } if (defined(dataMax)) { if (newMax < min) { newMax = min; } if (newMax > max) { newMax = max; } } } // In full view, displaying the reset zoom button is not // required axis.displayBtn = (typeof newMin !== 'undefined' || typeof newMax !== 'undefined'); // Do it axis.setExtremes(newMin, newMax, false, void 0, { trigger: 'zoom' }); } e.zoomed = true; }); return evt.zoomed; }; /** * Update the axis metrics. * * @private * @function Highcharts.Axis#setAxisSize */ Axis.prototype.setAxisSize = function () { var chart = this.chart, options = this.options, // [top, right, bottom, left] offsets = options.offsets || [0, 0, 0, 0], horiz = this.horiz, // Check for percentage based input values. Rounding fixes problems // with column overflow and plot line filtering (#4898, #4899) width = this.width = Math.round(relativeLength(pick(options.width, chart.plotWidth - offsets[3] + offsets[1]), chart.plotWidth)), height = this.height = Math.round(relativeLength(pick(options.height, chart.plotHeight - offsets[0] + offsets[2]), chart.plotHeight)), top = this.top = Math.round(relativeLength(pick(options.top, chart.plotTop + offsets[0]), chart.plotHeight, chart.plotTop)), left = this.left = Math.round(relativeLength(pick(options.left, chart.plotLeft + offsets[3]), chart.plotWidth, chart.plotLeft)); // Expose basic values to use in Series object and navigator this.bottom = chart.chartHeight - height - top; this.right = chart.chartWidth - width - left; // Direction agnostic properties this.len = Math.max(horiz ? width : height, 0); // Math.max fixes #905 this.pos = horiz ? left : top; // distance from SVG origin }; /** * Get the current extremes for the axis. * * @sample highcharts/members/axis-getextremes/ * Report extremes by click on a button * @sample maps/members/axis-getextremes/ * Get extremes in Highmaps * * @function Highcharts.Axis#getExtremes * * @return {Highcharts.ExtremesObject} * An object containing extremes information. */ Axis.prototype.getExtremes = function () { var axis = this, log = axis.logarithmic; return { min: log ? correctFloat(log.lin2log(axis.min)) : axis.min, max: log ? correctFloat(log.lin2log(axis.max)) : axis.max, dataMin: axis.dataMin, dataMax: axis.dataMax, userMin: axis.userMin, userMax: axis.userMax }; }; /** * Get the zero plane either based on zero or on the min or max value. * Used in bar and area plots. * * @function Highcharts.Axis#getThreshold * * @param {number} threshold * The threshold in axis values. * * @return {number|undefined} * The translated threshold position in terms of pixels, and corrected to * stay within the axis bounds. */ Axis.prototype.getThreshold = function (threshold) { var axis = this, log = axis.logarithmic, realMin = log ? log.lin2log(axis.min) : axis.min, realMax = log ? log.lin2log(axis.max) : axis.max; if (threshold === null || threshold === -Infinity) { threshold = realMin; } else if (threshold === Infinity) { threshold = realMax; } else if (realMin > threshold) { threshold = realMin; } else if (realMax < threshold) { threshold = realMax; } return axis.translate(threshold, 0, 1, 0, 1); }; /** * Compute auto alignment for the axis label based on which side the axis is * on and the given rotation for the label. * * @private * @function Highcharts.Axis#autoLabelAlign * * @param {number} rotation * The rotation in degrees as set by either the `rotation` or `autoRotation` * options. * * @return {Highcharts.AlignValue} * Can be `"center"`, `"left"` or `"right"`. */ Axis.prototype.autoLabelAlign = function (rotation) { var angle = (pick(rotation, 0) - (this.side * 90) + 720) % 360, evt = { align: 'center' }; fireEvent(this, 'autoLabelAlign', evt, function (e) { if (angle > 15 && angle < 165) { e.align = 'right'; } else if (angle > 195 && angle < 345) { e.align = 'left'; } }); return evt.align; }; /** * Get the tick length and width for the axis based on axis options. * * @private * @function Highcharts.Axis#tickSize * * @param {string} [prefix] * 'tick' or 'minorTick' * * @return {Array<number,number>|undefined} * An array of tickLength and tickWidth */ Axis.prototype.tickSize = function (prefix) { var options = this.options, tickWidth = pick(options[prefix === 'tick' ? 'tickWidth' : 'minorTickWidth'], // Default to 1 on linear and datetime X axes prefix === 'tick' && this.isXAxis && !this.categories ? 1 : 0); var tickLength = options[prefix === 'tick' ? 'tickLength' : 'minorTickLength'], tickSize; if (tickWidth && tickLength) { // Negate the length if (options[prefix + 'Position'] === 'inside') { tickLength = -tickLength; } tickSize = [tickLength, tickWidth]; } var e = { tickSize: tickSize }; fireEvent(this, 'afterTickSize', e); return e.tickSize; }; /** * Return the size of the labels. * * @private * @function Highcharts.Axis#labelMetrics * * @return {Highcharts.FontMetricsObject} */ Axis.prototype.labelMetrics = function () { var index = this.tickPositions && this.tickPositions[0] || 0; return this.chart.renderer.fontMetrics(this.options.labels.style.fontSize, this.ticks[index] && this.ticks[index].label); }; /** * Prevent the ticks from getting so close we can't draw the labels. On a * horizontal axis, this is handled by rotating the labels, removing ticks * and adding ellipsis. On a vertical axis remove ticks and add ellipsis. * * @private * @function Highcharts.Axis#unsquish * * @return {number} */ Axis.prototype.unsquish = function () { var labelOptions = this.options.labels, horiz = this.horiz, tickInterval = this.tickInterval, slotSize = this.len / (((this.categories ? 1 : 0) + this.max - this.min) / tickInterval), rotationOption = labelOptions.rotation, labelMetrics = this.labelMetrics(), range = Math.max(this.max - this.min, 0), // Return the multiple of tickInterval that is needed to avoid // collision getStep = function (spaceNeeded) { var step = spaceNeeded / (slotSize || 1); step = step > 1 ? Math.ceil(step) : 1; // Guard for very small or negative angles (#9835) if (step * tickInterval > range && spaceNeeded !== Infinity && slotSize !== Infinity && range) { step = Math.ceil(range / tickInterval); } return correctFloat(step * tickInterval); }; var newTickInterval = tickInterval, rotation, step, bestScore = Number.MAX_VALUE, autoRotation; if (horiz) { if (!labelOptions.staggerLines && !labelOptions.step) { if (isNumber(rotationOption)) { autoRotation = [rotationOption]; } else if (slotSize < labelOptions.autoRotationLimit) { autoRotation = labelOptions.autoRotation; } } if (autoRotation) { // Loop over the given autoRotation options, and determine // which gives the best score. The best score is that with // the lowest number of steps and a rotation closest // to horizontal. autoRotation.forEach(function (rot) { var score; if (rot === rotationOption || (rot && rot >= -90 && rot <= 90)) { // #3891 step = getStep(Math.abs(labelMetrics.h / Math.sin(deg2rad * rot))); score = step + Math.abs(rot / 360); if (score < bestScore) { bestScore = score; rotation = rot; newTickInterval = step; } } }); } } else if (!labelOptions.step) { // #4411 newTickInterval = getStep(labelMetrics.h); } this.autoRotation = autoRotation; this.labelRotation = pick(rotation, isNumber(rotationOption) ? rotationOption : 0); return newTickInterval; }; /** * Get the general slot width for labels/categories on this axis. This may * change between the pre-render (from Axis.getOffset) and the final tick * rendering and placement. * * @private * @function Highcharts.Axis#getSlotWidth * * @param {Highcharts.Tick} [tick] Optionally, calculate the slot width * basing on tick label. It is used in highcharts-3d module, where the slots * has different widths depending on perspective angles. * * @return {number} * The pixel width allocated to each axis label. */ Axis.prototype.getSlotWidth = function (tick) { // #5086, #1580, #1931 var chart = this.chart, horiz = this.horiz, labelOptions = this.options.labels, slotCount = Math.max(this.tickPositions.length - (this.categories ? 0 : 1), 1), marginLeft = chart.margin[3]; // Used by grid axis if (tick && isNumber(tick.slotWidth)) { // #13221, can be 0 return tick.slotWidth; } if (horiz && labelOptions.step < 2) { if (labelOptions.rotation) { // #4415 return 0; } return ((this.staggerLines || 1) * this.len) / slotCount; } if (!horiz) { // #7028 var cssWidth = labelOptions.style.width; if (cssWidth !== void 0) { return parseInt(String(cssWidth), 10); } if (marginLeft) { return marginLeft - chart.spacing[3]; } } // Last resort, a fraction of the available size return chart.chartWidth * 0.33; }; /** * Render the axis labels and determine whether ellipsis or rotation need to * be applied. * * @private * @function Highcharts.Axis#renderUnsquish */ Axis.prototype.renderUnsquish = function () { var chart = this.chart, renderer = chart.renderer, tickPositions = this.tickPositions, ticks = this.ticks, labelOptions = this.options.labels, labelStyleOptions = labelOptions.style, horiz = this.horiz, slotWidth = this.getSlotWidth(), innerWidth = Math.max(1, Math.round(slotWidth - 2 * labelOptions.padding)), attr = {}, labelMetrics = this.labelMetrics(), textOverflowOption = labelStyleOptions.textOverflow; var commonWidth, commonTextOverflow, maxLabelLength = 0, label, i, pos; // Set rotation option unless it is "auto", like in gauges if (!isString(labelOptions.rotation)) { // #4443 attr.rotation = labelOptions.rotation || 0; } // Get the longest label length tickPositions.forEach(function (tickPosition) { var tick = ticks[tickPosition]; // Replace label - sorting animation if (tick.movedLabel) { tick.replaceMovedLabel(); } if (tick && tick.label && tick.label.textPxLength > maxLabelLength) { maxLabelLength = tick.label.textPxLength; } }); this.maxLabelLength = maxLabelLength; // Handle auto rotation on horizontal axis if (this.autoRotation) { // Apply rotation only if the label is too wide for the slot, and // the label is wider than its height. if (maxLabelLength > innerWidth && maxLabelLength > labelMetrics.h) { attr.rotation = this.labelRotation; } else { this.labelRotation = 0; } // Handle word-wrap or ellipsis on vertical axis } else if (slotWidth) { // For word-wrap or ellipsis commonWidth = innerWidth; if (!textOverflowOption) { commonTextOverflow = 'clip'; // On vertical axis, only allow word wrap if there is room // for more lines. i = tickPositions.length; while (!horiz && i--) { pos = tickPositions[i]; label = ticks[pos].label; if (label) { // Reset ellipsis in order to get the correct // bounding box (#4070) if (label.styles && label.styles.textOverflow === 'ellipsis') { label.css({ textOverflow: 'clip' }); // Set the correct width in order to read // the bounding box height (#4678, #5034) } else if (label.textPxLength > slotWidth) { label.css({ width: slotWidth + 'px' }); } if (label.getBBox().height > (this.len / tickPositions.length - (labelMetrics.h - labelMetrics.f))) { label.specificTextOverflow = 'ellipsis'; } } } } } // Add ellipsis if the label length is significantly longer than ideal if (attr.rotation) { commonWidth = (maxLabelLength > chart.chartHeight * 0.5 ? chart.chartHeight * 0.33 : maxLabelLength); if (!textOverflowOption) { commonTextOverflow = 'ellipsis'; } } // Set the explicit or automatic label alignment this.labelAlign = labelOptions.align || this.autoLabelAlign(this.labelRotation); if (this.labelAlign) { attr.align = this.labelAlign; } // Apply general and specific CSS tickPositions.forEach(function (pos) { var tick = ticks[pos], label = tick && tick.label, widthOption = labelStyleOptions.width, css = {}; if (label) { // This needs to go before the CSS in old IE (#4502) label.attr(attr); if (tick.shortenLabel) { tick.shortenLabel(); } else if (commonWidth && !widthOption && // Setting width in this case messes with the bounding box // (#7975) labelStyleOptions.whiteSpace !== 'nowrap' && ( // Speed optimizing, #7656 commonWidth < label.textPxLength || // Resetting CSS, #4928 label.element.tagName === 'SPAN')) { css.width = commonWidth + 'px'; if (!textOverflowOption) { css.textOverflow = (label.specificTextOverflow || commonTextOverflow); } label.css(css); // Reset previously shortened label (#8210) } else if (label.styles && label.styles.width && !css.width && !widthOption) { label.css({ width: null }); } delete label.specificTextOverflow; tick.rotation = attr.rotation; } }, this); // Note: Why is this not part of getLabelPosition? this.tickRotCorr = renderer.rotCorr(labelMetrics.b, this.labelRotation || 0, this.side !== 0); }; /** * Return true if the axis has associated data. * * @function Highcharts.Axis#hasData * * @return {boolean} * True if the axis has associated visible series and those series have * either valid data points or explicit `min` and `max` settings. */ Axis.prototype.hasData = function () { return this.series.some(function (s) { return s.hasData(); }) || (this.options.showEmpty && defined(this.min) && defined(this.max)); }; /** * Adds the title defined in axis.options.title. * * @function Highcharts.Axis#addTitle * * @param {boolean} [display] * Whether or not to display the title. */ Axis.prototype.addTitle = function (display) { var axis = this, renderer = axis.chart.renderer, horiz = axis.horiz, opposite = axis.opposite, options = axis.options, axisTitleOptions = options.title, styledMode = axis.chart.styledMode; var textAlign; if (!axis.axisTitle) { textAlign = axisTitleOptions.textAlign; if (!textAlign) { textAlign = (horiz ? { low: 'left', middle: 'center', high: 'right' } : { low: opposite ? 'right' : 'left', middle: 'center', high: opposite ? 'left' : 'right' })[axisTitleOptions.align]; } axis.axisTitle = renderer .text(axisTitleOptions.text || '', 0, 0, axisTitleOptions.useHTML) .attr({ zIndex: 7, rotation: axisTitleOptions.rotation, align: textAlign }) .addClass('highcharts-axis-title'); // #7814, don't mutate style option if (!styledMode) { axis.axisTitle.css(merge(axisTitleOptions.style)); } axis.axisTitle.add(axis.axisGroup); axis.axisTitle.isNew = true; } // Max width defaults to the length of the axis if (!styledMode && !axisTitleOptions.style.width && !axis.isRadial) { axis.axisTitle.css({ width: axis.len + 'px' }); } // hide or show the title depending on whether showEmpty is set axis.axisTitle[display ? 'show' : 'hide'](display); }; /** * Generates a tick for initial positioning. * * @private * @function Highcharts.Axis#generateTick * * @param {number} pos * The tick position in axis values. * * @param {number} [i] * The index of the tick in {@link Axis.tickPositions}. */ Axis.prototype.generateTick = function (pos) { var axis = this, ticks = axis.ticks; if (!ticks[pos]) { ticks[pos] = new Tick(axis, pos); } else { ticks[pos].addLabel(); // update labels depending on tick interval } }; /** * Render the tick labels to a preliminary position to get their sizes * * @private * @function Highcharts.Axis#getOffset * * @fires Highcharts.Axis#event:afterGetOffset */ Axis.prototype.getOffset = function () { var _this = this; var axis = this, chart = axis.chart, renderer = chart.renderer, options = axis.options, tickPositions = axis.tickPositions, ticks = axis.ticks, horiz = axis.horiz, side = axis.side, invertedSide = (chart.inverted && !axis.isZAxis ? [1, 0, 3, 2][side] : side), hasData = axis.hasData(), axisTitleOptions = options.title, labelOptions = options.labels, axisOffset = chart.axisOffset, clipOffset = chart.clipOffset, directionFactor = [-1, 1, 1, -1][side], className = options.className, axisParent = axis.axisParent; // Used in color axis var showAxis, titleOffset = 0, titleOffsetOption, titleMargin = 0, labelOffset = 0, // reset labelOffsetPadded, lineHeightCorrection; // For reuse in Axis.render axis.showAxis = showAxis = hasData || options.showEmpty; // Set/reset staggerLines axis.staggerLines = (axis.horiz && labelOptions.staggerLines) || void 0; // Create the axisGroup and gridGroup elements on first iteration if (!axis.axisGroup) { var createGroup = function (name, suffix, zIndex) { return renderer.g(name) .attr({ zIndex: zIndex }) .addClass("highcharts-" + _this.coll.toLowerCase() + suffix + " " + (_this.isRadial ? "highcharts-radial-axis" + suffix + " " : '') + (className || '')) .add(axisParent); }; axis.gridGroup = createGroup('grid', '-grid', options.gridZIndex); axis.axisGroup = createGroup('axis', '', options.zIndex); axis.labelGroup = createGroup('axis-labels', '-labels', labelOptions.zIndex); } if (hasData || axis.isLinked) { // Generate ticks tickPositions.forEach(function (pos) { // i is not used here, but may be used in overrides axis.generateTick(pos); }); axis.renderUnsquish(); // Left side must be align: right and right side must // have align: left for labels axis.reserveSpaceDefault = (side === 0 || side === 2 || { 1: 'left', 3: 'right' }[side] === axis.labelAlign); if (pick(labelOptions.reserveSpace, axis.labelAlign === 'center' ? true : null, axis.reserveSpaceDefault)) { tickPositions.forEach(function (pos) { // get the highest offset labelOffset = Math.max(ticks[pos].getLabelSize(), labelOffset); }); } if (axis.staggerLines) { labelOffset *= axis.staggerLines; } axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1); } else { // doesn't have data objectEach(ticks, function (tick, n) { tick.destroy(); delete ticks[n]; }); } if (axisTitleOptions && axisTitleOptions.text && axisTitleOptions.enabled !== false) { axis.addTitle(showAxis); if (showAxis && axisTitleOptions.reserveSpace !== false) { axis.titleOffset = titleOffset = axis.axisTitle.getBBox()[horiz ? 'height' : 'width']; titleOffsetOption = axisTitleOptions.offset; titleMargin = defined(titleOffsetOption) ? 0 : pick(axisTitleOptions.margin, horiz ? 5 : 10); } } // Render the axis line axis.renderLine(); // handle automatic or user set offset axis.offset = directionFactor * pick(options.offset, axisOffset[side] ? axisOffset[side] + (options.margin || 0) : 0); axis.tickRotCorr = axis.tickRotCorr || { x: 0, y: 0 }; // polar if (side === 0) { lineHeightCorrection = -axis.labelMetrics().h; } else if (side === 2) { lineHeightCorrection = axis.tickRotCorr.y; } else { lineHeightCorrection = 0; } // Find the padded label offset labelOffsetPadded = Math.abs(labelOffset) + titleMargin; if (labelOffset) { labelOffsetPadded -= lineHeightCorrection; labelOffsetPadded += directionFactor * (horiz ? pick(labelOptions.y, axis.tickRotCorr.y + directionFactor * 8) : labelOptions.x); } axis.axisTitleMargin = pick(titleOffsetOption, labelOffsetPadded); if (axis.getMaxLabelDimensions) { axis.maxLabelDimensions = axis.getMaxLabelDimensions(ticks, tickPositions); } // Due to GridAxis.tickSize, tickSize should be calculated after ticks // has rendered. var tickSize = this.tickSize('tick'); axisOffset[side] = Math.max(axisOffset[side], (axis.axisTitleMargin || 0) + titleOffset + directionFactor * axis.offset, labelOffsetPadded, // #3027 tickPositions && tickPositions.length && tickSize ? tickSize[0] + directionFactor * axis.offset : 0 // #4866 ); // Decide the clipping needed to keep the graph inside // the plot area and axis lines var clip = options.offset ? 0 : // #4308, #4371: Math.floor(axis.axisLine.strokeWidth() / 2) * 2; clipOffset[invertedSide] = Math.max(clipOffset[invertedSide], clip); fireEvent(this, 'afterGetOffset'); }; /** * Internal function to get the path for the axis line. Extended for polar * charts. * * @function Highcharts.Axis#getLinePath * * @param {number} lineWidth * The line width in pixels. * * @return {Highcharts.SVGPathArray} * The SVG path definition in array form. */ Axis.prototype.getLinePath = function (lineWidth) { var chart = this.chart, opposite = this.opposite, offset = this.offset, horiz = this.horiz, lineLeft = this.left + (opposite ? this.width : 0) + offset, lineTop = chart.chartHeight - this.bottom - (opposite ? this.height : 0) + offset; if (opposite) { lineWidth *= -1; // crispify the other way - #1480, #1687 } return chart.renderer .crispLine([ [ 'M', horiz ? this.left : lineLeft, horiz ? lineTop : this.top ], [ 'L', horiz ? chart.chartWidth - this.right : lineLeft, horiz ? lineTop : chart.chartHeight - this.bottom ] ], lineWidth); }; /** * Render the axis line. Called internally when rendering and redrawing the * axis. * * @function Highcharts.Axis#renderLine */ Axis.prototype.renderLine = function () { if (!this.axisLine) { this.axisLine = this.chart.renderer.path() .addClass('highcharts-axis-line') .add(this.axisGroup); if (!this.chart.styledMode) { this.axisLine.attr({ stroke: this.options.lineColor, 'stroke-width': this.options.lineWidth, zIndex: 7 }); } } }; /** * Position the axis title. * * @private * @function Highcharts.Axis#getTitlePosition * * @return {Highcharts.PositionObject} * X and Y positions for the title. */ Axis.prototype.getTitlePosition = function () { // compute anchor points for each of the title align options var horiz = this.horiz, axisLeft = this.left, axisTop = this.top, axisLength = this.len, axisTitleOptions = this.options.title, margin = horiz ? axisLeft : axisTop, opposite = this.opposite, offset = this.offset, xOption = axisTitleOptions.x, yOption = axisTitleOptions.y, axisTitle = this.axisTitle, fontMetrics = this.chart.renderer.fontMetrics(axisTitleOptions.style.fontSize, axisTitle), // The part of a multiline text that is below the baseline of the // first line. Subtract 1 to preserve pixel-perfectness from the // old behaviour (v5.0.12), where only one line was allowed. textHeightOvershoot = Math.max(axisTitle.getBBox(null, 0).height - fontMetrics.h - 1, 0), // the position in the length direction of the axis alongAxis = { low: margin + (horiz ? 0 : axisLength), middle: margin + axisLength / 2, high: margin + (horiz ? axisLength : 0) }[axisTitleOptions.align], // the position in the perpendicular direction of the axis offAxis = (horiz ? axisTop + this.height : axisLeft) + (horiz ? 1 : -1) * // horizontal axis reverses the margin (opposite ? -1 : 1) * // so does opposite axes this.axisTitleMargin + [ -textHeightOvershoot, textHeightOvershoot, fontMetrics.f, -textHeightOvershoot // left ][this.side], titlePosition = { x: horiz ? alongAxis + xOption : offAxis + (opposite ? this.width : 0) + offset + xOption, y: horiz ? offAxis + yOption - (opposite ? this.height : 0) + offset : alongAxis + yOption }; fireEvent(this, 'afterGetTitlePosition', { titlePosition: titlePosition }); return titlePosition; }; /** * Render a minor tick into the given position. If a minor tick already * exists in this position, move it. * * @function Highcharts.Axis#renderMinorTick * * @param {number} pos * The position in axis values. */ Axis.prototype.renderMinorTick = function (pos) { var axis = this; var slideInTicks = axis.chart.hasRendered && axis.old; var minorTicks = axis.minorTicks; if (!minorTicks[pos]) { minorTicks[pos] = new Tick(axis, pos, 'minor'); } // Render new ticks in old position if (slideInTicks && minorTicks[pos].isNew) { minorTicks[pos].render(null, true); } minorTicks[pos].render(null, false, 1); }; /** * Render a major tick into the given position. If a tick already exists * in this position, move it. * * @function Highcharts.Axis#renderTick * * @param {number} pos * The position in axis values. * * @param {number} i * The tick index. */ Axis.prototype.renderTick = function (pos, i) { var axis = this, isLinked = axis.isLinked, ticks = axis.ticks, slideInTicks = axis.chart.hasRendered && axis.old; // Linked axes need an extra check to find out if if (!isLinked || (pos >= axis.min && pos <= axis.max) || (axis.grid && axis.grid.isColumn)) { if (!ticks[pos]) { ticks[pos] = new Tick(axis, pos); } // NOTE this seems like overkill. Could be handled in tick.render by // setting old position in attr, then set new position in animate. // render new ticks in old position if (slideInTicks && ticks[pos].isNew) { // Start with negative opacity so that it is visible from // halfway into the animation ticks[pos].render(i, true, -1); } ticks[pos].render(i); } }; /** * Render the axis. * * @private * @function Highcharts.Axis#render * * @fires Highcharts.Axis#event:afterRender */ Axis.prototype.render = function () { var axis = this, chart = axis.chart, log = axis.logarithmic, renderer = chart.renderer, options = axis.options, isLinked = axis.isLinked, tickPositions = axis.tickPositions, axisTitle = axis.axisTitle, ticks = axis.ticks, minorTicks = axis.minorTicks, alternateBands = axis.alternateBands, stackLabelOptions = options.stackLabels, alternateGridColor = options.alternateGridColor, tickmarkOffset = axis.tickmarkOffset, axisLine = axis.axisLine, showAxis = axis.showAxis, animation = animObject(renderer.globalAnimation); var from, to; // Reset axis.labelEdge.length = 0; axis.overlap = false; // Mark all elements inActive before we go over and mark the active ones [ticks, minorTicks, alternateBands].forEach(function (coll) { objectEach(coll, function (tick) { tick.isActive = false; }); }); // If the series has data draw the ticks. Else only the line and title if (axis.hasData() || isLinked) { // minor ticks if (axis.minorTickInterval && !axis.categories) { axis.getMinorTickPositions().forEach(function (pos) { axis.renderMinorTick(pos); }); } // Major ticks. Pull out the first item and render it last so that // we can get the position of the neighbour label. #808. if (tickPositions.length) { // #1300 tickPositions.forEach(function (pos, i) { axis.renderTick(pos, i); }); // In a categorized axis, the tick marks are displayed // between labels. So we need to add a tick mark and // grid line at the left edge of the X axis. if (tickmarkOffset && (axis.min === 0 || axis.single)) { if (!ticks[-1]) { ticks[-1] = new Tick(axis, -1, null, true); } ticks[-1].render(-1); } } // alternate grid color if (alternateGridColor) { tickPositions.forEach(function (pos, i) { to = typeof tickPositions[i + 1] !== 'undefined' ? tickPositions[i + 1] + tickmarkOffset : axis.max - tickmarkOffset; if (i % 2 === 0 && pos < axis.max && to <= axis.max + (chart.polar ? -tickmarkOffset : tickmarkOffset)) { // #2248, #4660 if (!alternateBands[pos]) { // Should be imported from PlotLineOrBand.js, but // the dependency cycle with axis is a problem alternateBands[pos] = new H.PlotLineOrBand(axis); } from = pos + tickmarkOffset; // #949 alternateBands[pos].options = { from: log ? log.lin2log(from) : from, to: log ? log.lin2log(to) : to, color: alternateGridColor, className: 'highcharts-alternate-grid' }; alternateBands[pos].render(); alternateBands[pos].isActive = true; } }); } // custom plot lines and bands if (!axis._addedPlotLB) { // only first time axis._addedPlotLB = true; (options.plotLines || []) .concat(options.plotBands || []) .forEach(function (plotLineOptions) { axis.addPlotBandOrLine(plotLineOptions); }); } } // end if hasData // Remove inactive ticks [ticks, minorTicks, alternateBands].forEach(function (coll) { var forDestruction = [], delay = animation.duration, destroyInactiveItems = function () { var i = forDestruction.length; while (i--) { // When resizing rapidly, the same items // may be destroyed in different timeouts, // or the may be reactivated if (coll[forDestruction[i]] && !coll[forDestruction[i]].isActive) { coll[forDestruction[i]].destroy(); delete coll[forDestruction[i]]; } } }; objectEach(coll, function (tick, pos) { if (!tick.isActive) { // Render to zero opacity tick.render(pos, false, 0); tick.isActive = false; forDestruction.push(pos); } }); // When the objects are finished fading out, destroy them syncTimeout(destroyInactiveItems, coll === alternateBands || !chart.hasRendered || !delay ? 0 : delay); }); // Set the axis line path if (axisLine) { axisLine[axisLine.isPlaced ? 'animate' : 'attr']({ d: this.getLinePath(axisLine.strokeWidth()) }); axisLine.isPlaced = true; // Show or hide the line depending on options.showEmpty axisLine[showAxis ? 'show' : 'hide'](showAxis); } if (axisTitle && showAxis) { var titleXy = axis.getTitlePosition(); if (isNumber(titleXy.y)) { axisTitle[axisTitle.isNew ? 'attr' : 'animate'](titleXy); axisTitle.isNew = false; } else { axisTitle.attr('y', -9999); axisTitle.isNew = true; } } // Stacked totals: if (stackLabelOptions && stackLabelOptions.enabled && axis.stacking) { axis.stacking.renderStackTotals(); } // End stacked totals // Record old scaling for updating/animation axis.old = { len: axis.len, max: axis.max, min: axis.min, transA: axis.transA, userMax: axis.userMax, userMin: axis.userMin }; axis.isDirty = false; fireEvent(this, 'afterRender'); }; /** * Redraw the axis to reflect changes in the data or axis extremes. Called * internally from Highcharts.Chart#redraw. * * @private * @function Highcharts.Axis#redraw */ Axis.prototype.redraw = function () { if (this.visible) { // render the axis this.render(); // move plot lines and bands this.plotLinesAndBands.forEach(function (plotLine) { plotLine.render(); }); } // mark associated series as dirty and ready for redraw this.series.forEach(function (series) { series.isDirty = true; }); }; /** * Returns an array of axis properties, that should be untouched during * reinitialization. * * @private * @function Highcharts.Axis#getKeepProps * * @return {Array<string>} */ Axis.prototype.getKeepProps = function () { return (this.keepProps || Axis.keepProps); }; /** * Destroys an Axis instance. See {@link Axis#remove} for the API endpoint * to fully remove the axis. * * @private * @function Highcharts.Axis#destroy * * @param {boolean} [keepEvents] * Whether to preserve events, used internally in Axis.update. */ Axis.prototype.destroy = function (keepEvents) { var axis = this, plotLinesAndBands = axis.plotLinesAndBands, eventOptions = this.eventOptions; fireEvent(this, 'destroy', { keepEvents: keepEvents }); // Remove the events if (!keepEvents) { removeEvent(axis); } // Destroy collections [axis.ticks, axis.minorTicks, axis.alternateBands].forEach(function (coll) { destroyObjectProperties(coll); }); if (plotLinesAndBands) { var i = plotLinesAndBands.length; while (i--) { // #1975 plotLinesAndBands[i].destroy(); } } // Destroy elements ['axisLine', 'axisTitle', 'axisGroup', 'gridGroup', 'labelGroup', 'cross', 'scrollbar'].forEach(function (prop) { if (axis[prop]) { axis[prop] = axis[prop].destroy(); } }); // Destroy each generated group for plotlines and plotbands for (var plotGroup in axis.plotLinesAndBandsGroups) { // eslint-disable-line guard-for-in axis.plotLinesAndBandsGroups[plotGroup] = axis.plotLinesAndBandsGroups[plotGroup].destroy(); } // Delete all properties and fall back to the prototype. objectEach(axis, function (val, key) { if (axis.getKeepProps().indexOf(key) === -1) { delete axis[key]; } }); this.eventOptions = eventOptions; }; /** * Internal function to draw a crosshair. * * @function Highcharts.Axis#drawCrosshair * * @param {Highcharts.PointerEventObject} [e] * The event arguments from the modified pointer event, extended with * `chartX` and `chartY` * * @param {Highcharts.Point} [point] * The Point object if the crosshair snaps to points. * * @fires Highcharts.Axis#event:afterDrawCrosshair * @fires Highcharts.Axis#event:drawCrosshair */ Axis.prototype.drawCrosshair = function (e, point) { var options = this.crosshair, snap = pick(options && options.snap, true), chart = this.chart; var path, pos, categorized, graphic = this.cross, crossOptions; fireEvent(this, 'drawCrosshair', { e: e, point: point }); // Use last available event when updating non-snapped crosshairs without // mouse interaction (#5287) if (!e) { e = this.cross && this.cross.e; } if ( // Disabled in options !options || // Snap ((defined(point) || !snap) === false)) { this.hideCrosshair(); } else { // Get the path if (!snap) { pos = e && (this.horiz ? e.chartX - this.pos : this.len - e.chartY + this.pos); } else if (defined(point)) { // #3834 pos = pick(this.coll !== 'colorAxis' ? point.crosshairPos : // 3D axis extension null, this.isXAxis ? point.plotX : this.len - point.plotY); } if (defined(pos)) { crossOptions = { // value, only used on radial value: point && (this.isXAxis ? point.x : pick(point.stackY, point.y)), translatedValue: pos }; if (chart.polar) { // Additional information required for crosshairs in // polar chart extend(crossOptions, { isCrosshair: true, chartX: e && e.chartX, chartY: e && e.chartY, point: point }); } path = this.getPlotLinePath(crossOptions) || null; // #3189 } if (!defined(path)) { this.hideCrosshair(); return; } categorized = this.categories && !this.isRadial; // Draw the cross if (!graphic) { this.cross = graphic = chart.renderer .path() .addClass('highcharts-crosshair highcharts-crosshair-' + (categorized ? 'category ' : 'thin ') + (options.className || '')) .attr({ zIndex: pick(options.zIndex, 2) }) .add(); // Presentational attributes if (!chart.styledMode) { graphic.attr({ stroke: options.color || (categorized ? Color .parse(Palette.highlightColor20) .setOpacity(0.25) .get() : Palette.neutralColor20), 'stroke-width': pick(options.width, 1) }).css({ 'pointer-events': 'none' }); if (options.dashStyle) { graphic.attr({ dashstyle: options.dashStyle }); } } } graphic.show().attr({ d: path }); if (categorized && !options.width) { graphic.attr({ 'stroke-width': this.transA }); } this.cross.e = e; } fireEvent(this, 'afterDrawCrosshair', { e: e, point: point }); }; /** * Hide the crosshair if visible. * * @function Highcharts.Axis#hideCrosshair */ Axis.prototype.hideCrosshair = function () { if (this.cross) { this.cross.hide(); } fireEvent(this, 'afterHideCrosshair'); }; /** * Check whether the chart has vertical panning ('y' or 'xy' type). * * @private * @function Highcharts.Axis#hasVerticalPanning * * @return {boolean} */ Axis.prototype.hasVerticalPanning = function () { var panningOptions = this.chart.options.chart.panning; return Boolean(panningOptions && panningOptions.enabled && // #14624 /y/.test(panningOptions.type)); }; /** * Check whether the given value is a positive valid axis value. * * @private * @function Highcharts.Axis#validatePositiveValue * * @param {unknown} value * The axis value * * @return {boolean} */ Axis.prototype.validatePositiveValue = function (value) { return isNumber(value) && value > 0; }; /** * Update an axis object with a new set of options. The options are merged * with the existing options, so only new or altered options need to be * specified. * * @sample highcharts/members/axis-update/ * Axis update demo * * @function Highcharts.Axis#update * * @param {Highcharts.AxisOptions} options * The new options that will be merged in with existing options on the axis. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the axis is altered. If doing more * operations on the chart, it is a good idea to set redraw to false and * call {@link Chart#redraw} after. */ Axis.prototype.update = function (options, redraw) { var chart = this.chart; options = merge(this.userOptions, options); this.destroy(true); this.init(chart, options); chart.isDirtyBox = true; if (pick(redraw, true)) { chart.redraw(); } }; /** * Remove the axis from the chart. * * @sample highcharts/members/chart-addaxis/ * Add and remove axes * * @function Highcharts.Axis#remove * * @param {boolean} [redraw=true] * Whether to redraw the chart following the remove. */ Axis.prototype.remove = function (redraw) { var chart = this.chart, key = this.coll, // xAxis or yAxis axisSeries = this.series; var i = axisSeries.length; // Remove associated series (#2687) while (i--) { if (axisSeries[i]) { axisSeries[i].remove(false); } } // Remove the axis erase(chart.axes, this); erase(chart[key], this); chart[key].forEach(function (axis, i) { // Re-index, #1706, #8075 axis.options.index = axis.userOptions.index = i; }); this.destroy(); chart.isDirtyBox = true; if (pick(redraw, true)) { chart.redraw(); } }; /** * Update the axis title by options after render time. * * @sample highcharts/members/axis-settitle/ * Set a new Y axis title * * @function Highcharts.Axis#setTitle * * @param {Highcharts.AxisTitleOptions} titleOptions * The additional title options. * * @param {boolean} [redraw=true] * Whether to redraw the chart after setting the title. */ Axis.prototype.setTitle = function (titleOptions, redraw) { this.update({ title: titleOptions }, redraw); }; /** * Set new axis categories and optionally redraw. * * @sample highcharts/members/axis-setcategories/ * Set categories by click on a button * * @function Highcharts.Axis#setCategories * * @param {Array<string>} categories * The new categories. * * @param {boolean} [redraw=true] * Whether to redraw the chart. */ Axis.prototype.setCategories = function (categories, redraw) { this.update({ categories: categories }, redraw); }; /* * * * Static Properties * * */ Axis.defaultOptions = AxisDefaults.defaultXAxisOptions; // Properties to survive after destroy, needed for Axis.update (#4317, // #5773, #5881). Axis.keepProps = ['extKey', 'hcEvents', 'names', 'series', 'userMax', 'userMin']; return Axis; }()); /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * Options for the path on the Axis to be calculated. * @interface Highcharts.AxisPlotLinePathOptionsObject */ /** * Axis value. * @name Highcharts.AxisPlotLinePathOptionsObject#value * @type {number|undefined} */ /** * Line width used for calculation crisp line coordinates. Defaults to 1. * @name Highcharts.AxisPlotLinePathOptionsObject#lineWidth * @type {number|undefined} */ /** * If `false`, the function will return null when it falls outside the axis * bounds. If `true`, the function will return a path aligned to the plot area * sides if it falls outside. If `pass`, it will return a path outside. * @name Highcharts.AxisPlotLinePathOptionsObject#force * @type {string|boolean|undefined} */ /** * Used in Highcharts Stock. When `true`, plot paths * (crosshair, plotLines, gridLines) * will be rendered on all axes when defined on the first axis. * @name Highcharts.AxisPlotLinePathOptionsObject#acrossPanes * @type {boolean|undefined} */ /** * Use old coordinates (for resizing and rescaling). * If not set, defaults to `false`. * @name Highcharts.AxisPlotLinePathOptionsObject#old * @type {boolean|undefined} */ /** * If given, return the plot line path of a pixel position on the axis. * @name Highcharts.AxisPlotLinePathOptionsObject#translatedValue * @type {number|undefined} */ /** * Used in Polar axes. Reverse the positions for concatenation of polygonal * plot bands * @name Highcharts.AxisPlotLinePathOptionsObject#reverse * @type {boolean|undefined} */ /** * Options for crosshairs on axes. * * @product highstock * * @typedef {Highcharts.XAxisCrosshairOptions|Highcharts.YAxisCrosshairOptions} Highcharts.AxisCrosshairOptions */ /** * @typedef {"navigator"|"pan"|"rangeSelectorButton"|"rangeSelectorInput"|"scrollbar"|"traverseUpButton"|"zoom"} Highcharts.AxisExtremesTriggerValue */ /** * @callback Highcharts.AxisEventCallbackFunction * * @param {Highcharts.Axis} this */ /** * @callback Highcharts.AxisLabelsFormatterCallbackFunction * * @param {Highcharts.AxisLabelsFormatterContextObject} this * * @param {Highcharts.AxisLabelsFormatterContextObject} ctx * * @return {string} */ /** * @interface Highcharts.AxisLabelsFormatterContextObject */ /** * The axis item of the label * @name Highcharts.AxisLabelsFormatterContextObject#axis * @type {Highcharts.Axis} */ /** * The chart instance. * @name Highcharts.AxisLabelsFormatterContextObject#chart * @type {Highcharts.Chart} */ /** * Whether the label belongs to the first tick on the axis. * @name Highcharts.AxisLabelsFormatterContextObject#isFirst * @type {boolean} */ /** * Whether the label belongs to the last tick on the axis. * @name Highcharts.AxisLabelsFormatterContextObject#isLast * @type {boolean} */ /** * The position on the axis in terms of axis values. For category axes, a * zero-based index. For datetime axes, the JavaScript time in milliseconds * since 1970. * @name Highcharts.AxisLabelsFormatterContextObject#pos * @type {number} */ /** * The preformatted text as the result of the default formatting. For example * dates will be formatted as strings, and numbers with language-specific comma * separators, thousands separators and numeric symbols like `k` or `M`. * @name Highcharts.AxisLabelsFormatterContextObject#text * @type {string} */ /** * The Tick instance. * @name Highcharts.AxisLabelsFormatterContextObject#tick * @type {Highcharts.Tick} */ /** * This can be either a numeric value or a category string. * @name Highcharts.AxisLabelsFormatterContextObject#value * @type {number|string} */ /** * Options for axes. * * @typedef {Highcharts.XAxisOptions|Highcharts.YAxisOptions|Highcharts.ZAxisOptions} Highcharts.AxisOptions */ /** * @callback Highcharts.AxisPointBreakEventCallbackFunction * * @param {Highcharts.Axis} this * * @param {Highcharts.AxisPointBreakEventObject} evt */ /** * @interface Highcharts.AxisPointBreakEventObject */ /** * @name Highcharts.AxisPointBreakEventObject#brk * @type {Highcharts.Dictionary<number>} */ /** * @name Highcharts.AxisPointBreakEventObject#point * @type {Highcharts.Point} */ /** * @name Highcharts.AxisPointBreakEventObject#preventDefault * @type {Function} */ /** * @name Highcharts.AxisPointBreakEventObject#target * @type {Highcharts.SVGElement} */ /** * @name Highcharts.AxisPointBreakEventObject#type * @type {"pointBreak"|"pointInBreak"} */ /** * @callback Highcharts.AxisSetExtremesEventCallbackFunction * * @param {Highcharts.Axis} this * * @param {Highcharts.AxisSetExtremesEventObject} evt */ /** * @interface Highcharts.AxisSetExtremesEventObject * @extends Highcharts.ExtremesObject */ /** * @name Highcharts.AxisSetExtremesEventObject#preventDefault * @type {Function} */ /** * @name Highcharts.AxisSetExtremesEventObject#target * @type {Highcharts.SVGElement} */ /** * @name Highcharts.AxisSetExtremesEventObject#trigger * @type {Highcharts.AxisExtremesTriggerValue|string} */ /** * @name Highcharts.AxisSetExtremesEventObject#type * @type {"setExtremes"} */ /** * @callback Highcharts.AxisTickPositionerCallbackFunction * * @param {Highcharts.Axis} this * * @return {Highcharts.AxisTickPositionsArray} */ /** * @interface Highcharts.AxisTickPositionsArray * @augments Array<number> */ /** * @typedef {"high"|"low"|"middle"} Highcharts.AxisTitleAlignValue */ /** * @typedef {Highcharts.XAxisTitleOptions|Highcharts.YAxisTitleOptions|Highcharts.ZAxisTitleOptions} Highcharts.AxisTitleOptions */ /** * @typedef {"linear"|"logarithmic"|"datetime"|"category"|"treegrid"} Highcharts.AxisTypeValue */ /** * The returned object literal from the {@link Highcharts.Axis#getExtremes} * function. * * @interface Highcharts.ExtremesObject */ /** * The maximum value of the axis' associated series. * @name Highcharts.ExtremesObject#dataMax * @type {number} */ /** * The minimum value of the axis' associated series. * @name Highcharts.ExtremesObject#dataMin * @type {number} */ /** * The maximum axis value, either automatic or set manually. If the `max` option * is not set, `maxPadding` is 0 and `endOnTick` is false, this value will be * the same as `dataMax`. * @name Highcharts.ExtremesObject#max * @type {number} */ /** * The minimum axis value, either automatic or set manually. If the `min` option * is not set, `minPadding` is 0 and `startOnTick` is false, this value will be * the same as `dataMin`. * @name Highcharts.ExtremesObject#min * @type {number} */ /** * The user defined maximum, either from the `max` option or from a zoom or * `setExtremes` action. * @name Highcharts.ExtremesObject#userMax * @type {number} */ /** * The user defined minimum, either from the `min` option or from a zoom or * `setExtremes` action. * @name Highcharts.ExtremesObject#userMin * @type {number} */ /** * Formatter function for the text of a crosshair label. * * @callback Highcharts.XAxisCrosshairLabelFormatterCallbackFunction * * @param {Highcharts.Axis} this * Axis context * * @param {number} value * Y value of the data point * * @return {string} */ ''; // keeps doclets above in JS file return Axis; }); _registerModule(_modules, 'Core/Axis/DateTimeAxis.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Utilities.js']], function (Axis, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var addEvent = U.addEvent, getMagnitude = U.getMagnitude, normalizeTickInterval = U.normalizeTickInterval, timeUnits = U.timeUnits; /* eslint-disable valid-jsdoc */ var DateTimeAxisAdditions = /** @class */ (function () { /* * * * Constructors * * */ function DateTimeAxisAdditions(axis) { this.axis = axis; } /* * * * Functions * * */ /** * Get a normalized tick interval for dates. Returns a configuration object * with unit range (interval), count and name. Used to prepare data for * `getTimeTicks`. Previously this logic was part of getTimeTicks, but as * `getTimeTicks` now runs of segments in stock charts, the normalizing * logic was extracted in order to prevent it for running over again for * each segment having the same interval. #662, #697. * @private */ /** * Get a normalized tick interval for dates. Returns a configuration object * with unit range (interval), count and name. Used to prepare data for * `getTimeTicks`. Previously this logic was part of getTimeTicks, but as * `getTimeTicks` now runs of segments in stock charts, the normalizing * logic was extracted in order to prevent it for running over again for * each segment having the same interval. #662, #697. * @private */ DateTimeAxisAdditions.prototype.normalizeTimeTickInterval = function (tickInterval, unitsOption) { var units = unitsOption || [[ 'millisecond', [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples ], [ 'second', [1, 2, 5, 10, 15, 30] ], [ 'minute', [1, 2, 5, 10, 15, 30] ], [ 'hour', [1, 2, 3, 4, 6, 8, 12] ], [ 'day', [1, 2] ], [ 'week', [1, 2] ], [ 'month', [1, 2, 3, 4, 6] ], [ 'year', null ]], unit = units[units.length - 1], // default unit is years interval = timeUnits[unit[0]], multiples = unit[1], count, i; // loop through the units to find the one that best fits the // tickInterval for (i = 0; i < units.length; i++) { unit = units[i]; interval = timeUnits[unit[0]]; multiples = unit[1]; if (units[i + 1]) { // lessThan is in the middle between the highest multiple and // the next unit. var lessThan = (interval * multiples[multiples.length - 1] + timeUnits[units[i + 1][0]]) / 2; // break and keep the current unit if (tickInterval <= lessThan) { break; } } } // prevent 2.5 years intervals, though 25, 250 etc. are allowed if (interval === timeUnits.year && tickInterval < 5 * interval) { multiples = [1, 2, 5]; } // get the count count = normalizeTickInterval(tickInterval / interval, multiples, unit[0] === 'year' ? // #1913, #2360 Math.max(getMagnitude(tickInterval / interval), 1) : 1); return { unitRange: interval, count: count, unitName: unit[0] }; }; return DateTimeAxisAdditions; }()); /** * Date and time support for axes. * * @private * @class */ var DateTimeAxis = /** @class */ (function () { function DateTimeAxis() { } /* * * * Static Functions * * */ /** * Extends axis class with date and time support. * @private */ DateTimeAxis.compose = function (AxisClass) { AxisClass.keepProps.push('dateTime'); var axisProto = AxisClass.prototype; /** * Set the tick positions to a time unit that makes sense, for example * on the first of each month or on every Monday. Return an array with * the time positions. Used in datetime axes as well as for grouping * data on a datetime axis. * * @private * @function Highcharts.Axis#getTimeTicks * * @param {Highcharts.TimeNormalizeObject} normalizedInterval * The interval in axis values (ms) and thecount. * * @param {number} min * The minimum in axis values. * * @param {number} max * The maximum in axis values. * * @param {number} startOfWeek * * @return {Highcharts.AxisTickPositionsArray} */ axisProto.getTimeTicks = function () { return this.chart.time.getTimeTicks.apply(this.chart.time, arguments); }; /* eslint-disable no-invalid-this */ addEvent(AxisClass, 'init', function (e) { var axis = this; var options = e.userOptions; if (options.type !== 'datetime') { axis.dateTime = void 0; return; } if (!axis.dateTime) { axis.dateTime = new DateTimeAxisAdditions(axis); } }); /* eslint-enable no-invalid-this */ }; /* * * * Static Properties * * */ DateTimeAxis.AdditionsClass = DateTimeAxisAdditions; return DateTimeAxis; }()); DateTimeAxis.compose(Axis); return DateTimeAxis; }); _registerModule(_modules, 'Core/Axis/LogarithmicAxis.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Utilities.js']], function (Axis, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var addEvent = U.addEvent, getMagnitude = U.getMagnitude, normalizeTickInterval = U.normalizeTickInterval, pick = U.pick; /* eslint-disable valid-jsdoc */ /** * Provides logarithmic support for axes. * * @private * @class */ var LogarithmicAxisAdditions = /** @class */ (function () { /* * * * Constructors * * */ function LogarithmicAxisAdditions(axis) { this.axis = axis; } /* * * * Functions * * */ /** * Set the tick positions of a logarithmic axis. */ LogarithmicAxisAdditions.prototype.getLogTickPositions = function (interval, min, max, minor) { var log = this; var axis = log.axis; var axisLength = axis.len; var options = axis.options; // Since we use this method for both major and minor ticks, // use a local variable and return the result var positions = []; // Reset if (!minor) { log.minorAutoInterval = void 0; } // First case: All ticks fall on whole logarithms: 1, 10, 100 etc. if (interval >= 0.5) { interval = Math.round(interval); positions = axis.getLinearTickPositions(interval, min, max); // Second case: We need intermediary ticks. For example // 1, 2, 4, 6, 8, 10, 20, 40 etc. } else if (interval >= 0.08) { var roundedMin = Math.floor(min), intermediate = void 0, i = void 0, j = void 0, len = void 0, pos = void 0, lastPos = void 0, break2 = void 0; if (interval > 0.3) { intermediate = [1, 2, 4]; // 0.2 equals five minor ticks per 1, 10, 100 etc } else if (interval > 0.15) { intermediate = [1, 2, 4, 6, 8]; } else { // 0.1 equals ten minor ticks per 1, 10, 100 etc intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9]; } for (i = roundedMin; i < max + 1 && !break2; i++) { len = intermediate.length; for (j = 0; j < len && !break2; j++) { pos = log.log2lin(log.lin2log(i) * intermediate[j]); // #1670, lastPos is #3113 if (pos > min && (!minor || lastPos <= max) && typeof lastPos !== 'undefined') { positions.push(lastPos); } if (lastPos > max) { break2 = true; } lastPos = pos; } } // Third case: We are so deep in between whole logarithmic values that // we might as well handle the tick positions like a linear axis. For // example 1.01, 1.02, 1.03, 1.04. } else { var realMin = log.lin2log(min), realMax = log.lin2log(max), tickIntervalOption = minor ? axis.getMinorTickInterval() : options.tickInterval, filteredTickIntervalOption = tickIntervalOption === 'auto' ? null : tickIntervalOption, tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1), totalPixelLength = minor ? axisLength / axis.tickPositions.length : axisLength; interval = pick(filteredTickIntervalOption, log.minorAutoInterval, (realMax - realMin) * tickPixelIntervalOption / (totalPixelLength || 1)); interval = normalizeTickInterval(interval, void 0, getMagnitude(interval)); positions = axis.getLinearTickPositions(interval, realMin, realMax).map(log.log2lin); if (!minor) { log.minorAutoInterval = interval / 5; } } // Set the axis-level tickInterval variable if (!minor) { axis.tickInterval = interval; } return positions; }; LogarithmicAxisAdditions.prototype.lin2log = function (num) { return Math.pow(10, num); }; LogarithmicAxisAdditions.prototype.log2lin = function (num) { return Math.log(num) / Math.LN10; }; return LogarithmicAxisAdditions; }()); var LogarithmicAxis = /** @class */ (function () { function LogarithmicAxis() { } /** * Provides logarithmic support for axes. * * @private */ LogarithmicAxis.compose = function (AxisClass) { AxisClass.keepProps.push('logarithmic'); /* eslint-disable no-invalid-this */ addEvent(AxisClass, 'init', function (e) { var axis = this; var options = e.userOptions; var logarithmic = axis.logarithmic; if (options.type !== 'logarithmic') { axis.logarithmic = void 0; } else { if (!logarithmic) { logarithmic = axis.logarithmic = new LogarithmicAxisAdditions(axis); } } }); addEvent(AxisClass, 'afterInit', function () { var axis = this; var log = axis.logarithmic; // extend logarithmic axis if (log) { axis.lin2val = function (num) { return log.lin2log(num); }; axis.val2lin = function (num) { return log.log2lin(num); }; } }); }; return LogarithmicAxis; }()); LogarithmicAxis.compose(Axis); // @todo move to factory functions return LogarithmicAxis; }); _registerModule(_modules, 'Core/Axis/PlotLineOrBand.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Color/Palette.js'], _modules['Core/Utilities.js']], function (Axis, palette, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /** * Options for plot bands on axes. * * @typedef {Highcharts.XAxisPlotBandsOptions|Highcharts.YAxisPlotBandsOptions|Highcharts.ZAxisPlotBandsOptions} Highcharts.AxisPlotBandsOptions */ /** * Options for plot band labels on axes. * * @typedef {Highcharts.XAxisPlotBandsLabelOptions|Highcharts.YAxisPlotBandsLabelOptions|Highcharts.ZAxisPlotBandsLabelOptions} Highcharts.AxisPlotBandsLabelOptions */ /** * Options for plot lines on axes. * * @typedef {Highcharts.XAxisPlotLinesOptions|Highcharts.YAxisPlotLinesOptions|Highcharts.ZAxisPlotLinesOptions} Highcharts.AxisPlotLinesOptions */ /** * Options for plot line labels on axes. * * @typedef {Highcharts.XAxisPlotLinesLabelOptions|Highcharts.YAxisPlotLinesLabelOptions|Highcharts.ZAxisPlotLinesLabelOptions} Highcharts.AxisPlotLinesLabelOptions */ var arrayMax = U.arrayMax, arrayMin = U.arrayMin, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, erase = U.erase, extend = U.extend, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach, pick = U.pick; /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The object wrapper for plot lines and plot bands * * @class * @name Highcharts.PlotLineOrBand * * @param {Highcharts.Axis} axis * * @param {Highcharts.AxisPlotLinesOptions|Highcharts.AxisPlotBandsOptions} [options] */ var PlotLineOrBand = /** @class */ (function () { function PlotLineOrBand(axis, options) { this.axis = axis; if (options) { this.options = options; this.id = options.id; } } /** * Render the plot line or plot band. If it is already existing, * move it. * * @private * @function Highcharts.PlotLineOrBand#render * @return {Highcharts.PlotLineOrBand|undefined} */ PlotLineOrBand.prototype.render = function () { fireEvent(this, 'render'); var plotLine = this, axis = plotLine.axis, horiz = axis.horiz, log = axis.logarithmic, options = plotLine.options, optionsLabel = options.label, label = plotLine.label, to = options.to, from = options.from, value = options.value, isBand = defined(from) && defined(to), isLine = defined(value), svgElem = plotLine.svgElem, isNew = !svgElem, path = [], color = options.color, zIndex = pick(options.zIndex, 0), events = options.events, attribs = { 'class': 'highcharts-plot-' + (isBand ? 'band ' : 'line ') + (options.className || '') }, groupAttribs = {}, renderer = axis.chart.renderer, groupName = isBand ? 'bands' : 'lines', group; // logarithmic conversion if (log) { from = log.log2lin(from); to = log.log2lin(to); value = log.log2lin(value); } // Set the presentational attributes if (!axis.chart.styledMode) { if (isLine) { attribs.stroke = color || palette.neutralColor40; attribs['stroke-width'] = pick(options.width, 1); if (options.dashStyle) { attribs.dashstyle = options.dashStyle; } } else if (isBand) { // plot band attribs.fill = color || palette.highlightColor10; if (options.borderWidth) { attribs.stroke = options.borderColor; attribs['stroke-width'] = options.borderWidth; } } } // Grouping and zIndex groupAttribs.zIndex = zIndex; groupName += '-' + zIndex; group = axis.plotLinesAndBandsGroups[groupName]; if (!group) { axis.plotLinesAndBandsGroups[groupName] = group = renderer.g('plot-' + groupName) .attr(groupAttribs).add(); } // Create the path if (isNew) { /** * SVG element of the plot line or band. * * @name Highcharts.PlotLineOrBand#svgElement * @type {Highcharts.SVGElement} */ plotLine.svgElem = svgElem = renderer .path() .attr(attribs) .add(group); } // Set the path or return if (isLine) { path = axis.getPlotLinePath({ value: value, lineWidth: svgElem.strokeWidth(), acrossPanes: options.acrossPanes }); } else if (isBand) { // plot band path = axis.getPlotBandPath(from, to, options); } else { return; } // common for lines and bands // Add events only if they were not added before. if (!plotLine.eventsAdded && events) { objectEach(events, function (event, eventType) { svgElem.on(eventType, function (e) { events[eventType].apply(plotLine, [e]); }); }); plotLine.eventsAdded = true; } if ((isNew || !svgElem.d) && path && path.length) { svgElem.attr({ d: path }); } else if (svgElem) { if (path) { svgElem.show(true); svgElem.animate({ d: path }); } else if (svgElem.d) { svgElem.hide(); if (label) { plotLine.label = label = label.destroy(); } } } // the plot band/line label if (optionsLabel && (defined(optionsLabel.text) || defined(optionsLabel.formatter)) && path && path.length && axis.width > 0 && axis.height > 0 && !path.isFlat) { // apply defaults optionsLabel = merge({ align: horiz && isBand && 'center', x: horiz ? !isBand && 4 : 10, verticalAlign: !horiz && isBand && 'middle', y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4, rotation: horiz && !isBand && 90 }, optionsLabel); this.renderLabel(optionsLabel, path, isBand, zIndex); } else if (label) { // move out of sight label.hide(); } // chainable return plotLine; }; /** * Render and align label for plot line or band. * * @private * @function Highcharts.PlotLineOrBand#renderLabel * @param {Highcharts.AxisPlotLinesLabelOptions|Highcharts.AxisPlotBandsLabelOptions} optionsLabel * @param {Highcharts.SVGPathArray} path * @param {boolean} [isBand] * @param {number} [zIndex] * @return {void} */ PlotLineOrBand.prototype.renderLabel = function (optionsLabel, path, isBand, zIndex) { var plotLine = this, label = plotLine.label, renderer = plotLine.axis.chart.renderer, attribs, xBounds, yBounds, x, y, labelText; // add the SVG element if (!label) { attribs = { align: optionsLabel.textAlign || optionsLabel.align, rotation: optionsLabel.rotation, 'class': 'highcharts-plot-' + (isBand ? 'band' : 'line') + '-label ' + (optionsLabel.className || '') }; attribs.zIndex = zIndex; labelText = this.getLabelText(optionsLabel); /** * SVG element of the label. * * @name Highcharts.PlotLineOrBand#label * @type {Highcharts.SVGElement} */ plotLine.label = label = renderer .text(labelText, 0, 0, optionsLabel.useHTML) .attr(attribs) .add(); if (!this.axis.chart.styledMode) { label.css(optionsLabel.style); } } // get the bounding box and align the label // #3000 changed to better handle choice between plotband or plotline xBounds = path.xBounds || [path[0][1], path[1][1], (isBand ? path[2][1] : path[0][1])]; yBounds = path.yBounds || [path[0][2], path[1][2], (isBand ? path[2][2] : path[0][2])]; x = arrayMin(xBounds); y = arrayMin(yBounds); label.align(optionsLabel, false, { x: x, y: y, width: arrayMax(xBounds) - x, height: arrayMax(yBounds) - y }); label.show(true); }; /** * Get label's text content. * * @private * @function Highcharts.PlotLineOrBand#getLabelText * @param {Highcharts.AxisPlotLinesLabelOptions|Highcharts.AxisPlotBandsLabelOptions} optionsLabel * @return {string} */ PlotLineOrBand.prototype.getLabelText = function (optionsLabel) { return defined(optionsLabel.formatter) ? optionsLabel.formatter .call(this) : optionsLabel.text; }; /** * Remove the plot line or band. * * @function Highcharts.PlotLineOrBand#destroy * @return {void} */ PlotLineOrBand.prototype.destroy = function () { // remove it from the lookup erase(this.axis.plotLinesAndBands, this); delete this.axis; destroyObjectProperties(this); }; return PlotLineOrBand; }()); /* eslint-enable no-invalid-this, valid-jsdoc */ // Object with members for extending the Axis prototype extend(Axis.prototype, /** @lends Highcharts.Axis.prototype */ { /** * An array of colored bands stretching across the plot area marking an * interval on the axis. * * In styled mode, the plot bands are styled by the `.highcharts-plot-band` * class in addition to the `className` option. * * @productdesc {highcharts} * In a gauge, a plot band on the Y axis (value axis) will stretch along the * perimeter of the gauge. * * @type {Array<*>} * @product highcharts highstock gantt * @apioption xAxis.plotBands */ /** * Flag to decide if plotBand should be rendered across all panes. * * @since 7.1.2 * @product highstock * @type {boolean} * @default true * @apioption xAxis.plotBands.acrossPanes */ /** * Border color for the plot band. Also requires `borderWidth` to be set. * * @type {Highcharts.ColorString} * @apioption xAxis.plotBands.borderColor */ /** * Border width for the plot band. Also requires `borderColor` to be set. * * @type {number} * @default 0 * @apioption xAxis.plotBands.borderWidth */ /** * A custom class name, in addition to the default `highcharts-plot-band`, * to apply to each individual band. * * @type {string} * @since 5.0.0 * @apioption xAxis.plotBands.className */ /** * The color of the plot band. * * @sample {highcharts} highcharts/xaxis/plotbands-color/ * Color band * @sample {highstock} stock/xaxis/plotbands/ * Plot band on Y axis * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default ${palette.highlightColor10} * @apioption xAxis.plotBands.color */ /** * An object defining mouse events for the plot band. Supported properties * are `click`, `mouseover`, `mouseout`, `mousemove`. * * @sample {highcharts} highcharts/xaxis/plotbands-events/ * Mouse events demonstrated * * @since 1.2 * @apioption xAxis.plotBands.events */ /** * Click event on a plot band. * * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} * @apioption xAxis.plotBands.events.click */ /** * Mouse move event on a plot band. * * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} * @apioption xAxis.plotBands.events.mousemove */ /** * Mouse out event on the corner of a plot band. * * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} * @apioption xAxis.plotBands.events.mouseout */ /** * Mouse over event on a plot band. * * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} * @apioption xAxis.plotBands.events.mouseover */ /** * The start position of the plot band in axis units. * * @sample {highcharts} highcharts/xaxis/plotbands-color/ * Datetime axis * @sample {highcharts} highcharts/xaxis/plotbands-from/ * Categorized axis * @sample {highstock} stock/xaxis/plotbands/ * Plot band on Y axis * * @type {number} * @apioption xAxis.plotBands.from */ /** * An id used for identifying the plot band in Axis.removePlotBand. * * @sample {highcharts} highcharts/xaxis/plotbands-id/ * Remove plot band by id * @sample {highstock} highcharts/xaxis/plotbands-id/ * Remove plot band by id * * @type {string} * @apioption xAxis.plotBands.id */ /** * The end position of the plot band in axis units. * * @sample {highcharts} highcharts/xaxis/plotbands-color/ * Datetime axis * @sample {highcharts} highcharts/xaxis/plotbands-from/ * Categorized axis * @sample {highstock} stock/xaxis/plotbands/ * Plot band on Y axis * * @type {number} * @apioption xAxis.plotBands.to */ /** * The z index of the plot band within the chart, relative to other * elements. Using the same z index as another element may give * unpredictable results, as the last rendered element will be on top. * Values from 0 to 20 make sense. * * @sample {highcharts} highcharts/xaxis/plotbands-color/ * Behind plot lines by default * @sample {highcharts} highcharts/xaxis/plotbands-zindex/ * Above plot lines * @sample {highcharts} highcharts/xaxis/plotbands-zindex-above-series/ * Above plot lines and series * * @type {number} * @since 1.2 * @apioption xAxis.plotBands.zIndex */ /** * Text labels for the plot bands * * @product highcharts highstock gantt * @apioption xAxis.plotBands.label */ /** * Horizontal alignment of the label. Can be one of "left", "center" or * "right". * * @sample {highcharts} highcharts/xaxis/plotbands-label-align/ * Aligned to the right * @sample {highstock} stock/xaxis/plotbands-label/ * Plot band with labels * * @type {Highcharts.AlignValue} * @default center * @since 2.1 * @apioption xAxis.plotBands.label.align */ /** * Rotation of the text label in degrees . * * @sample {highcharts} highcharts/xaxis/plotbands-label-rotation/ * Vertical text * * @type {number} * @default 0 * @since 2.1 * @apioption xAxis.plotBands.label.rotation */ /** * CSS styles for the text label. * * In styled mode, the labels are styled by the * `.highcharts-plot-band-label` class. * * @sample {highcharts} highcharts/xaxis/plotbands-label-style/ * Blue and bold label * * @type {Highcharts.CSSObject} * @since 2.1 * @apioption xAxis.plotBands.label.style */ /** * The string text itself. A subset of HTML is supported. * * @type {string} * @since 2.1 * @apioption xAxis.plotBands.label.text */ /** * The text alignment for the label. While `align` determines where the * texts anchor point is placed within the plot band, `textAlign` determines * how the text is aligned against its anchor point. Possible values are * "left", "center" and "right". Defaults to the same as the `align` option. * * @sample {highcharts} highcharts/xaxis/plotbands-label-rotation/ * Vertical text in center position but text-aligned left * * @type {Highcharts.AlignValue} * @since 2.1 * @apioption xAxis.plotBands.label.textAlign */ /** * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) * to render the labels. * * @type {boolean} * @default false * @since 3.0.3 * @apioption xAxis.plotBands.label.useHTML */ /** * Vertical alignment of the label relative to the plot band. Can be one of * "top", "middle" or "bottom". * * @sample {highcharts} highcharts/xaxis/plotbands-label-verticalalign/ * Vertically centered label * @sample {highstock} stock/xaxis/plotbands-label/ * Plot band with labels * * @type {Highcharts.VerticalAlignValue} * @default top * @since 2.1 * @apioption xAxis.plotBands.label.verticalAlign */ /** * Horizontal position relative the alignment. Default varies by * orientation. * * @sample {highcharts} highcharts/xaxis/plotbands-label-align/ * Aligned 10px from the right edge * @sample {highstock} stock/xaxis/plotbands-label/ * Plot band with labels * * @type {number} * @since 2.1 * @apioption xAxis.plotBands.label.x */ /** * Vertical position of the text baseline relative to the alignment. Default * varies by orientation. * * @sample {highcharts} highcharts/xaxis/plotbands-label-y/ * Label on x axis * @sample {highstock} stock/xaxis/plotbands-label/ * Plot band with labels * * @type {number} * @since 2.1 * @apioption xAxis.plotBands.label.y */ /** * An array of lines stretching across the plot area, marking a specific * value on one of the axes. * * In styled mode, the plot lines are styled by the * `.highcharts-plot-line` class in addition to the `className` option. * * @type {Array<*>} * @product highcharts highstock gantt * @sample {highcharts} highcharts/xaxis/plotlines-color/ * Basic plot line * @sample {highcharts} highcharts/series-solidgauge/labels-auto-aligned/ * Solid gauge plot line * @apioption xAxis.plotLines */ /** * Flag to decide if plotLine should be rendered across all panes. * * @sample {highstock} stock/xaxis/plotlines-acrosspanes/ * Plot lines on different panes * * @since 7.1.2 * @product highstock * @type {boolean} * @default true * @apioption xAxis.plotLines.acrossPanes */ /** * A custom class name, in addition to the default `highcharts-plot-line`, * to apply to each individual line. * * @type {string} * @since 5.0.0 * @apioption xAxis.plotLines.className */ /** * The color of the line. * * @sample {highcharts} highcharts/xaxis/plotlines-color/ * A red line from X axis * @sample {highstock} stock/xaxis/plotlines/ * Plot line on Y axis * * @type {Highcharts.ColorString} * @default ${palette.neutralColor40} * @apioption xAxis.plotLines.color */ /** * The dashing or dot style for the plot line. For possible values see * [this overview](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/). * * @sample {highcharts} highcharts/xaxis/plotlines-dashstyle/ * Dash and dot pattern * @sample {highstock} stock/xaxis/plotlines/ * Plot line on Y axis * * @type {Highcharts.DashStyleValue} * @default Solid * @since 1.2 * @apioption xAxis.plotLines.dashStyle */ /** * An object defining mouse events for the plot line. Supported * properties are `click`, `mouseover`, `mouseout`, `mousemove`. * * @sample {highcharts} highcharts/xaxis/plotlines-events/ * Mouse events demonstrated * * @since 1.2 * @apioption xAxis.plotLines.events */ /** * Click event on a plot band. * * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} * @apioption xAxis.plotLines.events.click */ /** * Mouse move event on a plot band. * * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} * @apioption xAxis.plotLines.events.mousemove */ /** * Mouse out event on the corner of a plot band. * * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} * @apioption xAxis.plotLines.events.mouseout */ /** * Mouse over event on a plot band. * * @type {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>} * @apioption xAxis.plotLines.events.mouseover */ /** * An id used for identifying the plot line in Axis.removePlotLine. * * @sample {highcharts} highcharts/xaxis/plotlines-id/ * Remove plot line by id * * @type {string} * @apioption xAxis.plotLines.id */ /** * The position of the line in axis units. * * @sample {highcharts} highcharts/xaxis/plotlines-color/ * Between two categories on X axis * @sample {highstock} stock/xaxis/plotlines/ * Plot line on Y axis * * @type {number} * @apioption xAxis.plotLines.value */ /** * The width or thickness of the plot line. * * @sample {highcharts} highcharts/xaxis/plotlines-color/ * 2px wide line from X axis * @sample {highstock} stock/xaxis/plotlines/ * Plot line on Y axis * * @type {number} * @default 2 * @apioption xAxis.plotLines.width */ /** * The z index of the plot line within the chart. * * @sample {highcharts} highcharts/xaxis/plotlines-zindex-behind/ * Behind plot lines by default * @sample {highcharts} highcharts/xaxis/plotlines-zindex-above/ * Above plot lines * @sample {highcharts} highcharts/xaxis/plotlines-zindex-above-all/ * Above plot lines and series * * @type {number} * @since 1.2 * @apioption xAxis.plotLines.zIndex */ /** * Text labels for the plot bands * * @apioption xAxis.plotLines.label */ /** * Horizontal alignment of the label. Can be one of "left", "center" or * "right". * * @sample {highcharts} highcharts/xaxis/plotlines-label-align-right/ * Aligned to the right * @sample {highstock} stock/xaxis/plotlines/ * Plot line on Y axis * * @type {Highcharts.AlignValue} * @default left * @since 2.1 * @apioption xAxis.plotLines.label.align */ /** * Callback JavaScript function to format the label. Useful properties like * the value of plot line or the range of plot band (`from` & `to` * properties) can be found in `this.options` object. * * @sample {highcharts} highcharts/xaxis/plotlines-plotbands-label-formatter * Label formatters for plot line and plot band. * @type {Highcharts.FormatterCallbackFunction<Highcharts.PlotLineOrBand>} * @apioption xAxis.plotLines.label.formatter */ /** * Rotation of the text label in degrees. Defaults to 0 for horizontal plot * lines and 90 for vertical lines. * * @sample {highcharts} highcharts/xaxis/plotlines-label-verticalalign-middle/ * Slanted text * * @type {number} * @since 2.1 * @apioption xAxis.plotLines.label.rotation */ /** * CSS styles for the text label. * * In styled mode, the labels are styled by the * `.highcharts-plot-line-label` class. * * @sample {highcharts} highcharts/xaxis/plotlines-label-style/ * Blue and bold label * * @type {Highcharts.CSSObject} * @since 2.1 * @apioption xAxis.plotLines.label.style */ /** * The text itself. A subset of HTML is supported. * * @type {string} * @since 2.1 * @apioption xAxis.plotLines.label.text */ /** * The text alignment for the label. While `align` determines where the * texts anchor point is placed within the plot band, `textAlign` determines * how the text is aligned against its anchor point. Possible values are * "left", "center" and "right". Defaults to the same as the `align` option. * * @sample {highcharts} highcharts/xaxis/plotlines-label-textalign/ * Text label in bottom position * * @type {Highcharts.AlignValue} * @since 2.1 * @apioption xAxis.plotLines.label.textAlign */ /** * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) * to render the labels. * * @type {boolean} * @default false * @since 3.0.3 * @apioption xAxis.plotLines.label.useHTML */ /** * Vertical alignment of the label relative to the plot line. Can be * one of "top", "middle" or "bottom". * * @sample {highcharts} highcharts/xaxis/plotlines-label-verticalalign-middle/ * Vertically centered label * * @type {Highcharts.VerticalAlignValue} * @default {highcharts} top * @default {highstock} top * @since 2.1 * @apioption xAxis.plotLines.label.verticalAlign */ /** * Horizontal position relative the alignment. Default varies by * orientation. * * @sample {highcharts} highcharts/xaxis/plotlines-label-align-right/ * Aligned 10px from the right edge * @sample {highstock} stock/xaxis/plotlines/ * Plot line on Y axis * * @type {number} * @since 2.1 * @apioption xAxis.plotLines.label.x */ /** * Vertical position of the text baseline relative to the alignment. Default * varies by orientation. * * @sample {highcharts} highcharts/xaxis/plotlines-label-y/ * Label below the plot line * @sample {highstock} stock/xaxis/plotlines/ * Plot line on Y axis * * @type {number} * @since 2.1 * @apioption xAxis.plotLines.label.y */ /** * * @type {Array<*>} * @extends xAxis.plotBands * @apioption yAxis.plotBands */ /** * In a gauge chart, this option determines the inner radius of the * plot band that stretches along the perimeter. It can be given as * a percentage string, like `"100%"`, or as a pixel number, like `100`. * By default, the inner radius is controlled by the [thickness]( * #yAxis.plotBands.thickness) option. * * @sample {highcharts} highcharts/xaxis/plotbands-gauge * Gauge plot band * * @type {number|string} * @since 2.3 * @product highcharts * @apioption yAxis.plotBands.innerRadius */ /** * In a gauge chart, this option determines the outer radius of the * plot band that stretches along the perimeter. It can be given as * a percentage string, like `"100%"`, or as a pixel number, like `100`. * * @sample {highcharts} highcharts/xaxis/plotbands-gauge * Gauge plot band * * @type {number|string} * @default 100% * @since 2.3 * @product highcharts * @apioption yAxis.plotBands.outerRadius */ /** * In a gauge chart, this option sets the width of the plot band * stretching along the perimeter. It can be given as a percentage * string, like `"10%"`, or as a pixel number, like `10`. The default * value 10 is the same as the default [tickLength](#yAxis.tickLength), * thus making the plot band act as a background for the tick markers. * * @sample {highcharts} highcharts/xaxis/plotbands-gauge * Gauge plot band * * @type {number|string} * @default 10 * @since 2.3 * @product highcharts * @apioption yAxis.plotBands.thickness */ /** * @type {Array<*>} * @extends xAxis.plotLines * @apioption yAxis.plotLines */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * Internal function to create the SVG path definition for a plot band. * * @function Highcharts.Axis#getPlotBandPath * * @param {number} from * The axis value to start from. * * @param {number} to * The axis value to end on. * * @param {Highcharts.AxisPlotBandsOptions|Highcharts.AxisPlotLinesOptions} options * The plotBand or plotLine configuration object. * * @return {Highcharts.SVGPathArray} * The SVG path definition in array form. */ getPlotBandPath: function (from, to, options) { if (options === void 0) { options = this.options; } var toPath = this.getPlotLinePath({ value: to, force: true, acrossPanes: options.acrossPanes }), path = this.getPlotLinePath({ value: from, force: true, acrossPanes: options.acrossPanes }), result = [], i, // #4964 check if chart is inverted or plotband is on yAxis horiz = this.horiz, plus = 1, isFlat, outside = !isNumber(this.min) || !isNumber(this.max) || (from < this.min && to < this.min) || (from > this.max && to > this.max); if (path && toPath) { // Flat paths don't need labels (#3836) if (outside) { isFlat = path.toString() === toPath.toString(); plus = 0; } // Go over each subpath - for panes in Highcharts Stock for (i = 0; i < path.length; i += 2) { var pathStart = path[i], pathEnd = path[i + 1], toPathStart = toPath[i], toPathEnd = toPath[i + 1]; // Type checking all affected path segments. Consider something // smarter. if ((pathStart[0] === 'M' || pathStart[0] === 'L') && (pathEnd[0] === 'M' || pathEnd[0] === 'L') && (toPathStart[0] === 'M' || toPathStart[0] === 'L') && (toPathEnd[0] === 'M' || toPathEnd[0] === 'L')) { // Add 1 pixel when coordinates are the same if (horiz && toPathStart[1] === pathStart[1]) { toPathStart[1] += plus; toPathEnd[1] += plus; } else if (!horiz && toPathStart[2] === pathStart[2]) { toPathStart[2] += plus; toPathEnd[2] += plus; } result.push(['M', pathStart[1], pathStart[2]], ['L', pathEnd[1], pathEnd[2]], ['L', toPathEnd[1], toPathEnd[2]], ['L', toPathStart[1], toPathStart[2]], ['Z']); } result.isFlat = isFlat; } } else { // outside the axis area path = null; } return result; }, /** * Add a plot band after render time. * * @sample highcharts/members/axis-addplotband/ * Toggle the plot band from a button * * @function Highcharts.Axis#addPlotBand * * @param {Highcharts.AxisPlotBandsOptions} options * A configuration object for the plot band, as defined in * [xAxis.plotBands](https://api.highcharts.com/highcharts/xAxis.plotBands). * * @return {Highcharts.PlotLineOrBand|undefined} * The added plot band. */ addPlotBand: function (options) { return this.addPlotBandOrLine(options, 'plotBands'); }, /** * Add a plot line after render time. * * @sample highcharts/members/axis-addplotline/ * Toggle the plot line from a button * * @function Highcharts.Axis#addPlotLine * * @param {Highcharts.AxisPlotLinesOptions} options * A configuration object for the plot line, as defined in * [xAxis.plotLines](https://api.highcharts.com/highcharts/xAxis.plotLines). * * @return {Highcharts.PlotLineOrBand|undefined} * The added plot line. */ addPlotLine: function (options) { return this.addPlotBandOrLine(options, 'plotLines'); }, /** * Add a plot band or plot line after render time. Called from addPlotBand * and addPlotLine internally. * * @private * @function Highcharts.Axis#addPlotBandOrLine * * @param {Highcharts.AxisPlotBandsOptions|Highcharts.AxisPlotLinesOptions} options * The plotBand or plotLine configuration object. * * @param {"plotBands"|"plotLines"} [coll] * * @return {Highcharts.PlotLineOrBand|undefined} */ addPlotBandOrLine: function (options, coll) { var _this = this; var obj = new PlotLineOrBand(this, options), userOptions = this.userOptions; if (this.visible) { obj = obj.render(); } if (obj) { // #2189 if (!this._addedPlotLB) { this._addedPlotLB = true; (userOptions.plotLines || []) .concat(userOptions.plotBands || []) .forEach(function (plotLineOptions) { _this.addPlotBandOrLine(plotLineOptions); }); } // Add it to the user options for exporting and Axis.update if (coll) { // Workaround Microsoft/TypeScript issue #32693 var updatedOptions = (userOptions[coll] || []); updatedOptions.push(options); userOptions[coll] = updatedOptions; } this.plotLinesAndBands.push(obj); } return obj; }, /** * Remove a plot band or plot line from the chart by id. Called internally * from `removePlotBand` and `removePlotLine`. * * @private * @function Highcharts.Axis#removePlotBandOrLine * @param {string} id * @return {void} */ removePlotBandOrLine: function (id) { var plotLinesAndBands = this.plotLinesAndBands, options = this.options, userOptions = this.userOptions; if (plotLinesAndBands) { // #15639 var i_1 = plotLinesAndBands.length; while (i_1--) { if (plotLinesAndBands[i_1].id === id) { plotLinesAndBands[i_1].destroy(); } } ([ options.plotLines || [], userOptions.plotLines || [], options.plotBands || [], userOptions.plotBands || [] ]).forEach(function (arr) { i_1 = arr.length; while (i_1--) { if ((arr[i_1] || {}).id === id) { erase(arr, arr[i_1]); } } }); } }, /** * Remove a plot band by its id. * * @sample highcharts/members/axis-removeplotband/ * Remove plot band by id * @sample highcharts/members/axis-addplotband/ * Toggle the plot band from a button * * @function Highcharts.Axis#removePlotBand * * @param {string} id * The plot band's `id` as given in the original configuration * object or in the `addPlotBand` option. * * @return {void} */ removePlotBand: function (id) { this.removePlotBandOrLine(id); }, /** * Remove a plot line by its id. * * @sample highcharts/xaxis/plotlines-id/ * Remove plot line by id * @sample highcharts/members/axis-addplotline/ * Toggle the plot line from a button * * @function Highcharts.Axis#removePlotLine * * @param {string} id * The plot line's `id` as given in the original configuration * object or in the `addPlotLine` option. */ removePlotLine: function (id) { this.removePlotBandOrLine(id); } }); return PlotLineOrBand; }); _registerModule(_modules, 'Core/Tooltip.js', [_modules['Core/FormatUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Color/Palette.js'], _modules['Core/Renderer/RendererRegistry.js'], _modules['Core/Utilities.js']], function (F, H, palette, RendererRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var format = F.format; var doc = H.doc; var clamp = U.clamp, css = U.css, defined = U.defined, discardElement = U.discardElement, extend = U.extend, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, isString = U.isString, merge = U.merge, pick = U.pick, splat = U.splat, syncTimeout = U.syncTimeout, timeUnits = U.timeUnits; /** * Callback function to format the text of the tooltip from scratch. * * In case of single or shared tooltips, a string should be be returned. In case * of splitted tooltips, it should return an array where the first item is the * header, and subsequent items are mapped to the points. Return `false` to * disable tooltip for a specific point on series. * * @callback Highcharts.TooltipFormatterCallbackFunction * * @param {Highcharts.TooltipFormatterContextObject} this * Context to format * * @param {Highcharts.Tooltip} tooltip * The tooltip instance * * @return {false|string|Array<(string|null|undefined)>|null|undefined} * Formatted text or false */ /** * @interface Highcharts.TooltipFormatterContextObject */ /** * @name Highcharts.TooltipFormatterContextObject#color * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ /** * @name Highcharts.TooltipFormatterContextObject#colorIndex * @type {number|undefined} */ /** * @name Highcharts.TooltipFormatterContextObject#key * @type {number} */ /** * @name Highcharts.TooltipFormatterContextObject#percentage * @type {number|undefined} */ /** * @name Highcharts.TooltipFormatterContextObject#point * @type {Highcharts.Point} */ /** * @name Highcharts.TooltipFormatterContextObject#points * @type {Array<Highcharts.TooltipFormatterContextObject>|undefined} */ /** * @name Highcharts.TooltipFormatterContextObject#series * @type {Highcharts.Series} */ /** * @name Highcharts.TooltipFormatterContextObject#total * @type {number|undefined} */ /** * @name Highcharts.TooltipFormatterContextObject#x * @type {number} */ /** * @name Highcharts.TooltipFormatterContextObject#y * @type {number} */ /** * A callback function to place the tooltip in a specific position. * * @callback Highcharts.TooltipPositionerCallbackFunction * * @param {Highcharts.Tooltip} this * Tooltip context of the callback. * * @param {number} labelWidth * Width of the tooltip. * * @param {number} labelHeight * Height of the tooltip. * * @param {Highcharts.TooltipPositionerPointObject} point * Point information for positioning a tooltip. * * @return {Highcharts.PositionObject} * New position for the tooltip. */ /** * Point information for positioning a tooltip. * * @interface Highcharts.TooltipPositionerPointObject * @extends Highcharts.Point */ /** * If `tooltip.split` option is enabled and positioner is called for each of the * boxes separately, this property indicates the call on the xAxis header, which * is not a point itself. * @name Highcharts.TooltipPositionerPointObject#isHeader * @type {boolean} */ /** * The reference point relative to the plot area. Add chart.plotLeft to get the * full coordinates. * @name Highcharts.TooltipPositionerPointObject#plotX * @type {number} */ /** * The reference point relative to the plot area. Add chart.plotTop to get the * full coordinates. * @name Highcharts.TooltipPositionerPointObject#plotY * @type {number} */ /** * @typedef {"callout"|"circle"|"square"} Highcharts.TooltipShapeValue */ ''; // separates doclets above from variables below /* eslint-disable no-invalid-this, valid-jsdoc */ /** * Tooltip of a chart. * * @class * @name Highcharts.Tooltip * * @param {Highcharts.Chart} chart * The chart instance. * * @param {Highcharts.TooltipOptions} options * Tooltip options. */ var Tooltip = /** @class */ (function () { /* * * * Constructors * * */ function Tooltip(chart, options) { this.container = void 0; this.crosshairs = []; this.distance = 0; this.isHidden = true; this.isSticky = false; this.now = {}; this.options = {}; this.outside = false; this.chart = chart; this.init(chart, options); } /* * * * Functions * * */ /** * In styled mode, apply the default filter for the tooltip drop-shadow. It * needs to have an id specific to the chart, otherwise there will be issues * when one tooltip adopts the filter of a different chart, specifically one * where the container is hidden. * * @private * @function Highcharts.Tooltip#applyFilter */ Tooltip.prototype.applyFilter = function () { var chart = this.chart; chart.renderer.definition({ tagName: 'filter', attributes: { id: 'drop-shadow-' + chart.index, opacity: 0.5 }, children: [{ tagName: 'feGaussianBlur', attributes: { 'in': 'SourceAlpha', stdDeviation: 1 } }, { tagName: 'feOffset', attributes: { dx: 1, dy: 1 } }, { tagName: 'feComponentTransfer', children: [{ tagName: 'feFuncA', attributes: { type: 'linear', slope: 0.3 } }] }, { tagName: 'feMerge', children: [{ tagName: 'feMergeNode' }, { tagName: 'feMergeNode', attributes: { 'in': 'SourceGraphic' } }] }] }); chart.renderer.definition({ tagName: 'style', textContent: '.highcharts-tooltip-' + chart.index + '{' + 'filter:url(#drop-shadow-' + chart.index + ')' + '}' }); }; /** * Build the body (lines) of the tooltip by iterating over the items and * returning one entry for each item, abstracting this functionality allows * to easily overwrite and extend it. * * @private * @function Highcharts.Tooltip#bodyFormatter * @param {Array<(Highcharts.Point|Highcharts.Series)>} items * @return {Array<string>} */ Tooltip.prototype.bodyFormatter = function (items) { return items.map(function (item) { var tooltipOptions = item.series.tooltipOptions; return (tooltipOptions[(item.point.formatPrefix || 'point') + 'Formatter'] || item.point.tooltipFormatter).call(item.point, tooltipOptions[(item.point.formatPrefix || 'point') + 'Format'] || ''); }); }; /** * Destroy the single tooltips in a split tooltip. * If the tooltip is active then it is not destroyed, unless forced to. * * @private * @function Highcharts.Tooltip#cleanSplit * * @param {boolean} [force] * Force destroy all tooltips. */ Tooltip.prototype.cleanSplit = function (force) { this.chart.series.forEach(function (series) { var tt = series && series.tt; if (tt) { if (!tt.isActive || force) { series.tt = tt.destroy(); } else { tt.isActive = false; } } }); }; /** * In case no user defined formatter is given, this will be used. Note that * the context here is an object holding point, series, x, y etc. * * @function Highcharts.Tooltip#defaultFormatter * * @param {Highcharts.Tooltip} tooltip * * @return {Array<string>} */ Tooltip.prototype.defaultFormatter = function (tooltip) { var items = this.points || splat(this), s; // Build the header s = [tooltip.tooltipFooterHeaderFormatter(items[0])]; // build the values s = s.concat(tooltip.bodyFormatter(items)); // footer s.push(tooltip.tooltipFooterHeaderFormatter(items[0], true)); return s; }; /** * Removes and destroys the tooltip and its elements. * * @function Highcharts.Tooltip#destroy */ Tooltip.prototype.destroy = function () { // Destroy and clear local variables if (this.label) { this.label = this.label.destroy(); } if (this.split && this.tt) { this.cleanSplit(this.chart, true); this.tt = this.tt.destroy(); } if (this.renderer) { this.renderer = this.renderer.destroy(); discardElement(this.container); } U.clearTimeout(this.hideTimer); U.clearTimeout(this.tooltipTimeout); }; /** * Extendable method to get the anchor position of the tooltip * from a point or set of points * * @private * @function Highcharts.Tooltip#getAnchor * * @param {Highcharts.Point|Array<Highcharts.Point>} points * * @param {Highcharts.PointerEventObject} [mouseEvent] * * @return {Array<number>} */ Tooltip.prototype.getAnchor = function (points, mouseEvent) { var ret, chart = this.chart, pointer = chart.pointer, inverted = chart.inverted, plotTop = chart.plotTop, plotLeft = chart.plotLeft, plotX = 0, plotY = 0, yAxis, xAxis; points = splat(points); // When tooltip follows mouse, relate the position to the mouse if (this.followPointer && mouseEvent) { if (typeof mouseEvent.chartX === 'undefined') { mouseEvent = pointer.normalize(mouseEvent); } ret = [ mouseEvent.chartX - plotLeft, mouseEvent.chartY - plotTop ]; // Some series types use a specificly calculated tooltip position for // each point } else if (points[0].tooltipPos) { ret = points[0].tooltipPos; // Calculate the average position and adjust for axis positions } else { points.forEach(function (point) { yAxis = point.series.yAxis; xAxis = point.series.xAxis; plotX += point.plotX || 0; plotY += (point.plotLow ? (point.plotLow + (point.plotHigh || 0)) / 2 : (point.plotY || 0)); // Adjust position for positioned axes (top/left settings) if (xAxis && yAxis) { if (!inverted) { // #1151 plotX += xAxis.pos - plotLeft; plotY += yAxis.pos - plotTop; } else { // #14771 plotX += plotTop + chart.plotHeight - xAxis.len - xAxis.pos; plotY += plotLeft + chart.plotWidth - yAxis.len - yAxis.pos; } } }); plotX /= points.length; plotY /= points.length; // Use the average position for multiple points ret = [ inverted ? chart.plotWidth - plotY : plotX, inverted ? chart.plotHeight - plotX : plotY ]; // When shared, place the tooltip next to the mouse (#424) if (this.shared && points.length > 1 && mouseEvent) { if (inverted) { ret[0] = mouseEvent.chartX - plotLeft; } else { ret[1] = mouseEvent.chartY - plotTop; } } } return ret.map(Math.round); }; /** * Get the optimal date format for a point, based on a range. * * @private * @function Highcharts.Tooltip#getDateFormat * * @param {number} range * The time range * * @param {number} date * The date of the point in question * * @param {number} startOfWeek * An integer representing the first day of the week, where 0 is * Sunday. * * @param {Highcharts.Dictionary<string>} dateTimeLabelFormats * A map of time units to formats. * * @return {string} * The optimal date format for a point. */ Tooltip.prototype.getDateFormat = function (range, date, startOfWeek, dateTimeLabelFormats) { var time = this.chart.time, dateStr = time.dateFormat('%m-%d %H:%M:%S.%L', date), format, n, blank = '01-01 00:00:00.000', strpos = { millisecond: 15, second: 12, minute: 9, hour: 6, day: 3 }, lastN = 'millisecond'; // for sub-millisecond data, #4223 for (n in timeUnits) { // eslint-disable-line guard-for-in // If the range is exactly one week and we're looking at a // Sunday/Monday, go for the week format if (range === timeUnits.week && +time.dateFormat('%w', date) === startOfWeek && dateStr.substr(6) === blank.substr(6)) { n = 'week'; break; } // The first format that is too great for the range if (timeUnits[n] > range) { n = lastN; break; } // If the point is placed every day at 23:59, we need to show // the minutes as well. #2637. if (strpos[n] && dateStr.substr(strpos[n]) !== blank.substr(strpos[n])) { break; } // Weeks are outside the hierarchy, only apply them on // Mondays/Sundays like in the first condition if (n !== 'week') { lastN = n; } } if (n) { format = time.resolveDTLFormat(dateTimeLabelFormats[n]).main; } return format; }; /** * Creates the Tooltip label element if it does not exist, then returns it. * * @function Highcharts.Tooltip#getLabel * @return {Highcharts.SVGElement} */ Tooltip.prototype.getLabel = function () { var tooltip = this, renderer = this.chart.renderer, styledMode = this.chart.styledMode, options = this.options, className = ('tooltip' + (defined(options.className) ? ' ' + options.className : '')), pointerEvents = ((options.style && options.style.pointerEvents) || (!this.followPointer && options.stickOnContact ? 'auto' : 'none')), container, onMouseEnter = function () { tooltip.inContact = true; }, onMouseLeave = function () { var series = tooltip.chart.hoverSeries; tooltip.inContact = false; if (series && series.onMouseOut) { series.onMouseOut(); } }; if (!this.label) { if (this.outside) { var chartStyle = this.chart.options.chart.style, Renderer = RendererRegistry.getRendererType(); /** * Reference to the tooltip's container, when * [Highcharts.Tooltip#outside] is set to true, otherwise * it's undefined. * * @name Highcharts.Tooltip#container * @type {Highcharts.HTMLDOMElement|undefined} */ this.container = container = H.doc.createElement('div'); container.className = 'highcharts-tooltip-container'; css(container, { position: 'absolute', top: '1px', pointerEvents: pointerEvents, zIndex: Math.max((this.options.style && this.options.style.zIndex || 0), (chartStyle && chartStyle.zIndex || 0) + 3) }); H.doc.body.appendChild(container); /** * Reference to the tooltip's renderer, when * [Highcharts.Tooltip#outside] is set to true, otherwise * it's undefined. * * @name Highcharts.Tooltip#renderer * @type {Highcharts.SVGRenderer|undefined} */ this.renderer = renderer = new Renderer(container, 0, 0, chartStyle, void 0, void 0, renderer.styledMode); } // Create the label if (this.split) { this.label = renderer.g(className); } else { this.label = renderer .label('', 0, 0, options.shape || 'callout', null, null, options.useHTML, null, className) .attr({ padding: options.padding, r: options.borderRadius }); if (!styledMode) { this.label .attr({ fill: options.backgroundColor, 'stroke-width': options.borderWidth }) // #2301, #2657 .css(options.style) .css({ pointerEvents: pointerEvents }) .shadow(options.shadow); } } if (styledMode) { // Apply the drop-shadow filter this.applyFilter(); this.label.addClass('highcharts-tooltip-' + this.chart.index); } // Split tooltip use updateTooltipContainer to position the tooltip // container. if (tooltip.outside && !tooltip.split) { var label_1 = this.label; var xSetter_1 = label_1.xSetter, ySetter_1 = label_1.ySetter; label_1.xSetter = function (value) { xSetter_1.call(label_1, tooltip.distance); container.style.left = value + 'px'; }; label_1.ySetter = function (value) { ySetter_1.call(label_1, tooltip.distance); container.style.top = value + 'px'; }; } this.label .on('mouseenter', onMouseEnter) .on('mouseleave', onMouseLeave) .attr({ zIndex: 8 }) .add(); } return this.label; }; /** * Place the tooltip in a chart without spilling over * and not covering the point it self. * * @private * @function Highcharts.Tooltip#getPosition * * @param {number} boxWidth * * @param {number} boxHeight * * @param {Highcharts.Point} point * * @return {Highcharts.PositionObject} */ Tooltip.prototype.getPosition = function (boxWidth, boxHeight, point) { var chart = this.chart, distance = this.distance, ret = {}, // Don't use h if chart isn't inverted (#7242) ??? h = (chart.inverted && point.h) || 0, // #4117 ??? swapped, outside = this.outside, outerWidth = outside ? // substract distance to prevent scrollbars doc.documentElement.clientWidth - 2 * distance : chart.chartWidth, outerHeight = outside ? Math.max(doc.body.scrollHeight, doc.documentElement.scrollHeight, doc.body.offsetHeight, doc.documentElement.offsetHeight, doc.documentElement.clientHeight) : chart.chartHeight, chartPosition = chart.pointer.getChartPosition(), scaleX = function (val) { return ( // eslint-disable-line no-confusing-arrow val * chartPosition.scaleX); }, scaleY = function (val) { return ( // eslint-disable-line no-confusing-arrow val * chartPosition.scaleY); }, // Build parameter arrays for firstDimension()/secondDimension() buildDimensionArray = function (dim) { var isX = dim === 'x'; return [ dim, isX ? outerWidth : outerHeight, isX ? boxWidth : boxHeight ].concat(outside ? [ // If we are using tooltip.outside, we need to scale the // position to match scaling of the container in case there // is a transform/zoom on the container. #11329 isX ? scaleX(boxWidth) : scaleY(boxHeight), isX ? chartPosition.left - distance + scaleX(point.plotX + chart.plotLeft) : chartPosition.top - distance + scaleY(point.plotY + chart.plotTop), 0, isX ? outerWidth : outerHeight ] : [ // Not outside, no scaling is needed isX ? boxWidth : boxHeight, isX ? point.plotX + chart.plotLeft : point.plotY + chart.plotTop, isX ? chart.plotLeft : chart.plotTop, isX ? chart.plotLeft + chart.plotWidth : chart.plotTop + chart.plotHeight ]); }, first = buildDimensionArray('y'), second = buildDimensionArray('x'), // The far side is right or bottom preferFarSide = !this.followPointer && pick(point.ttBelow, !chart.inverted === !!point.negative), // #4984 /* * Handle the preferred dimension. When the preferred dimension is * tooltip on top or bottom of the point, it will look for space * there. * * @private */ firstDimension = function (dim, outerSize, innerSize, scaledInnerSize, // #11329 point, min, max) { var scaledDist = outside ? (dim === 'y' ? scaleY(distance) : scaleX(distance)) : distance, scaleDiff = (innerSize - scaledInnerSize) / 2, roomLeft = scaledInnerSize < point - distance, roomRight = point + distance + scaledInnerSize < outerSize, alignedLeft = point - scaledDist - innerSize + scaleDiff, alignedRight = point + scaledDist - scaleDiff; if (preferFarSide && roomRight) { ret[dim] = alignedRight; } else if (!preferFarSide && roomLeft) { ret[dim] = alignedLeft; } else if (roomLeft) { ret[dim] = Math.min(max - scaledInnerSize, alignedLeft - h < 0 ? alignedLeft : alignedLeft - h); } else if (roomRight) { ret[dim] = Math.max(min, alignedRight + h + innerSize > outerSize ? alignedRight : alignedRight + h); } else { return false; } }, /* * Handle the secondary dimension. If the preferred dimension is * tooltip on top or bottom of the point, the second dimension is to * align the tooltip above the point, trying to align center but * allowing left or right align within the chart box. * * @private */ secondDimension = function (dim, outerSize, innerSize, scaledInnerSize, // #11329 point) { var retVal; // Too close to the edge, return false and swap dimensions if (point < distance || point > outerSize - distance) { retVal = false; // Align left/top } else if (point < innerSize / 2) { ret[dim] = 1; // Align right/bottom } else if (point > outerSize - scaledInnerSize / 2) { ret[dim] = outerSize - scaledInnerSize - 2; // Align center } else { ret[dim] = point - innerSize / 2; } return retVal; }, /* * Swap the dimensions */ swap = function (count) { var temp = first; first = second; second = temp; swapped = count; }, run = function () { if (firstDimension.apply(0, first) !== false) { if (secondDimension.apply(0, second) === false && !swapped) { swap(true); run(); } } else if (!swapped) { swap(true); run(); } else { ret.x = ret.y = 0; } }; // Under these conditions, prefer the tooltip on the side of the point if (chart.inverted || this.len > 1) { swap(); } run(); return ret; }; /** * Get the best X date format based on the closest point range on the axis. * * @private * @function Highcharts.Tooltip#getXDateFormat * * @param {Highcharts.Point} point * * @param {Highcharts.TooltipOptions} options * * @param {Highcharts.Axis} xAxis * * @return {string} */ Tooltip.prototype.getXDateFormat = function (point, options, xAxis) { var xDateFormat, dateTimeLabelFormats = options.dateTimeLabelFormats, closestPointRange = xAxis && xAxis.closestPointRange; if (closestPointRange) { xDateFormat = this.getDateFormat(closestPointRange, point.x, xAxis.options.startOfWeek, dateTimeLabelFormats); } else { xDateFormat = dateTimeLabelFormats.day; } return xDateFormat || dateTimeLabelFormats.year; // #2546, 2581 }; /** * Hides the tooltip with a fade out animation. * * @function Highcharts.Tooltip#hide * * @param {number} [delay] * The fade out in milliseconds. If no value is provided the value * of the tooltip.hideDelay option is used. A value of 0 disables * the fade out animation. */ Tooltip.prototype.hide = function (delay) { var tooltip = this; // disallow duplicate timers (#1728, #1766) U.clearTimeout(this.hideTimer); delay = pick(delay, this.options.hideDelay, 500); if (!this.isHidden) { this.hideTimer = syncTimeout(function () { // If there is a delay, do fadeOut with the default duration. If // the hideDelay is 0, we assume no animation is wanted, so we // pass 0 duration. #12994. tooltip.getLabel().fadeOut(delay ? void 0 : delay); tooltip.isHidden = true; }, delay); } }; /** * @private * @function Highcharts.Tooltip#init * * @param {Highcharts.Chart} chart * The chart instance. * * @param {Highcharts.TooltipOptions} options * Tooltip options. */ Tooltip.prototype.init = function (chart, options) { /** * Chart of the tooltip. * * @readonly * @name Highcharts.Tooltip#chart * @type {Highcharts.Chart} */ this.chart = chart; /** * Used tooltip options. * * @readonly * @name Highcharts.Tooltip#options * @type {Highcharts.TooltipOptions} */ this.options = options; /** * List of crosshairs. * * @private * @readonly * @name Highcharts.Tooltip#crosshairs * @type {Array<null>} */ this.crosshairs = []; /** * Current values of x and y when animating. * * @private * @readonly * @name Highcharts.Tooltip#now * @type {Highcharts.PositionObject} */ this.now = { x: 0, y: 0 }; /** * Tooltips are initially hidden. * * @private * @readonly * @name Highcharts.Tooltip#isHidden * @type {boolean} */ this.isHidden = true; /** * True, if the tooltip is split into one label per series, with the * header close to the axis. * * @readonly * @name Highcharts.Tooltip#split * @type {boolean|undefined} */ this.split = options.split && !chart.inverted && !chart.polar; /** * When the tooltip is shared, the entire plot area will capture mouse * movement or touch events. * * @readonly * @name Highcharts.Tooltip#shared * @type {boolean|undefined} */ this.shared = options.shared || this.split; /** * Whether to allow the tooltip to render outside the chart's SVG * element box. By default (false), the tooltip is rendered within the * chart's SVG element, which results in the tooltip being aligned * inside the chart area. * * @readonly * @name Highcharts.Tooltip#outside * @type {boolean} * * @todo * Split tooltip does not support outside in the first iteration. Should * not be too complicated to implement. */ this.outside = pick(options.outside, Boolean(chart.scrollablePixelsX || chart.scrollablePixelsY)); }; /** * Returns true, if the pointer is in contact with the tooltip tracker. */ Tooltip.prototype.isStickyOnContact = function () { return !!(!this.followPointer && this.options.stickOnContact && this.inContact); }; /** * Moves the tooltip with a soft animation to a new position. * * @private * @function Highcharts.Tooltip#move * * @param {number} x * * @param {number} y * * @param {number} anchorX * * @param {number} anchorY */ Tooltip.prototype.move = function (x, y, anchorX, anchorY) { var tooltip = this, now = tooltip.now, animate = tooltip.options.animation !== false && !tooltip.isHidden && // When we get close to the target position, abort animation and // land on the right place (#3056) (Math.abs(x - now.x) > 1 || Math.abs(y - now.y) > 1), skipAnchor = tooltip.followPointer || tooltip.len > 1; // Get intermediate values for animation extend(now, { x: animate ? (2 * now.x + x) / 3 : x, y: animate ? (now.y + y) / 2 : y, anchorX: skipAnchor ? void 0 : animate ? (2 * now.anchorX + anchorX) / 3 : anchorX, anchorY: skipAnchor ? void 0 : animate ? (now.anchorY + anchorY) / 2 : anchorY }); // Move to the intermediate value tooltip.getLabel().attr(now); tooltip.drawTracker(); // Run on next tick of the mouse tracker if (animate) { // Never allow two timeouts U.clearTimeout(this.tooltipTimeout); // Set the fixed interval ticking for the smooth tooltip this.tooltipTimeout = setTimeout(function () { // The interval function may still be running during destroy, // so check that the chart is really there before calling. if (tooltip) { tooltip.move(x, y, anchorX, anchorY); } }, 32); } }; /** * Refresh the tooltip's text and position. * * @function Highcharts.Tooltip#refresh * * @param {Highcharts.Point|Array<Highcharts.Point>} pointOrPoints * Either a point or an array of points. * * @param {Highcharts.PointerEventObject} [mouseEvent] * Mouse event, that is responsible for the refresh and should be * used for the tooltip update. */ Tooltip.prototype.refresh = function (pointOrPoints, mouseEvent) { var tooltip = this, chart = this.chart, options = tooltip.options, x, y, points = splat(pointOrPoints), point = points[0], anchor, textConfig = {}, text, pointConfig = [], formatter = options.formatter || tooltip.defaultFormatter, shared = tooltip.shared, styledMode = chart.styledMode; if (!options.enabled) { return; } U.clearTimeout(this.hideTimer); // get the reference point coordinates (pie charts use tooltipPos) tooltip.followPointer = !tooltip.split && point.series.tooltipOptions.followPointer; anchor = tooltip.getAnchor(pointOrPoints, mouseEvent); x = anchor[0]; y = anchor[1]; // shared tooltip, array is sent over if (shared && !(!isArray(pointOrPoints) && pointOrPoints.series && pointOrPoints.series.noSharedTooltip)) { chart.pointer.applyInactiveState(points); // Now set hover state for the choosen ones: points.forEach(function (item) { item.setState('hover'); pointConfig.push(item.getLabelConfig()); }); textConfig = { x: point.category, y: point.y }; textConfig.points = pointConfig; // single point tooltip } else { textConfig = point.getLabelConfig(); } this.len = pointConfig.length; // #6128 text = formatter.call(textConfig, tooltip); // register the current series var currentSeries = point.series; this.distance = pick(currentSeries.tooltipOptions.distance, 16); // update the inner HTML if (text === false) { this.hide(); } else { // update text if (tooltip.split) { this.renderSplit(text, points); } else { var checkX = x; var checkY = y; if (mouseEvent && chart.pointer.isDirectTouch) { checkX = mouseEvent.chartX - chart.plotLeft; checkY = mouseEvent.chartY - chart.plotTop; } // #11493, #13095 if (chart.polar || currentSeries.options.clip === false || currentSeries.shouldShowTooltip(checkX, checkY)) { var label = tooltip.getLabel(); // Prevent the tooltip from flowing over the chart box // (#6659) if (!options.style.width || styledMode) { label.css({ width: this.chart.spacingBox.width + 'px' }); } label.attr({ text: text && text.join ? text.join('') : text }); // Set the stroke color of the box to reflect the point label.removeClass(/highcharts-color-[\d]+/g) .addClass('highcharts-color-' + pick(point.colorIndex, currentSeries.colorIndex)); if (!styledMode) { label.attr({ stroke: (options.borderColor || point.color || currentSeries.color || palette.neutralColor60) }); } tooltip.updatePosition({ plotX: x, plotY: y, negative: point.negative, ttBelow: point.ttBelow, h: anchor[2] || 0 }); } else { tooltip.hide(); return; } } // show it if (tooltip.isHidden && tooltip.label) { tooltip.label.attr({ opacity: 1 }).show(); } tooltip.isHidden = false; } fireEvent(this, 'refresh'); }; /** * Render the split tooltip. Loops over each point's text and adds * a label next to the point, then uses the distribute function to * find best non-overlapping positions. * * @private * @function Highcharts.Tooltip#renderSplit * * @param {string|Array<(boolean|string)>} labels * * @param {Array<Highcharts.Point>} points */ Tooltip.prototype.renderSplit = function (labels, points) { var tooltip = this; var chart = tooltip.chart, _a = tooltip.chart, chartWidth = _a.chartWidth, chartHeight = _a.chartHeight, plotHeight = _a.plotHeight, plotLeft = _a.plotLeft, plotTop = _a.plotTop, pointer = _a.pointer, _b = _a.scrollablePixelsY, scrollablePixelsY = _b === void 0 ? 0 : _b, scrollablePixelsX = _a.scrollablePixelsX, _c = _a.scrollingContainer, _d = _c === void 0 ? { scrollLeft: 0, scrollTop: 0 } : _c, scrollLeft = _d.scrollLeft, scrollTop = _d.scrollTop, styledMode = _a.styledMode, distance = tooltip.distance, options = tooltip.options, positioner = tooltip.options.positioner; // The area which the tooltip should be limited to. Limit to scrollable // plot area if enabled, otherwise limit to the chart container. // If outside is true it should be the whole viewport var bounds = tooltip.outside && typeof scrollablePixelsX !== 'number' ? doc.documentElement.getBoundingClientRect() : { left: scrollLeft, right: scrollLeft + chartWidth, top: scrollTop, bottom: scrollTop + chartHeight }; var tooltipLabel = tooltip.getLabel(); var ren = this.renderer || chart.renderer; var headerTop = Boolean(chart.xAxis[0] && chart.xAxis[0].opposite); var _e = pointer.getChartPosition(), chartLeft = _e.left, chartTop = _e.top; var distributionBoxTop = plotTop + scrollTop; var headerHeight = 0; var adjustedPlotHeight = plotHeight - scrollablePixelsY; /** * Calculates the anchor position for the partial tooltip * * @private * @param {Highcharts.Point} point The point related to the tooltip * @return {object} Returns an object with anchorX and anchorY */ function getAnchor(point) { var isHeader = point.isHeader, _a = point.plotX, plotX = _a === void 0 ? 0 : _a, _b = point.plotY, plotY = _b === void 0 ? 0 : _b, series = point.series; var anchorX; var anchorY; if (isHeader) { // Set anchorX to plotX anchorX = plotLeft + plotX; // Set anchorY to center of visible plot area. anchorY = plotTop + plotHeight / 2; } else { var xAxis = series.xAxis, yAxis = series.yAxis; // Set anchorX to plotX. Limit to within xAxis. anchorX = xAxis.pos + clamp(plotX, -distance, xAxis.len + distance); // Set anchorY, limit to the scrollable plot area if (series.shouldShowTooltip(0, yAxis.pos - plotTop + plotY, { ignoreX: true })) { anchorY = yAxis.pos + plotY; } } // Limit values to plot area anchorX = clamp(anchorX, bounds.left - distance, bounds.right + distance); return { anchorX: anchorX, anchorY: anchorY }; } /** * Calculates the position of the partial tooltip * * @private * @param {number} anchorX The partial tooltip anchor x position * @param {number} anchorY The partial tooltip anchor y position * @param {boolean} isHeader Whether the partial tooltip is a header * @param {number} boxWidth Width of the partial tooltip * @return {Highcharts.PositionObject} Returns the partial tooltip x and * y position */ function defaultPositioner(anchorX, anchorY, isHeader, boxWidth, alignedLeft) { if (alignedLeft === void 0) { alignedLeft = true; } var y; var x; if (isHeader) { y = headerTop ? 0 : adjustedPlotHeight; x = clamp(anchorX - (boxWidth / 2), bounds.left, bounds.right - boxWidth - (tooltip.outside ? chartLeft : 0)); } else { y = anchorY - distributionBoxTop; x = alignedLeft ? anchorX - boxWidth - distance : anchorX + distance; x = clamp(x, alignedLeft ? x : bounds.left, bounds.right); } // NOTE: y is relative to distributionBoxTop return { x: x, y: y }; } /** * Updates the attributes and styling of the partial tooltip. Creates a * new partial tooltip if it does not exists. * * @private * @param {Highcharts.SVGElement|undefined} partialTooltip * The partial tooltip to update * @param {Highcharts.Point} point * The point related to the partial tooltip * @param {boolean|string} str The text for the partial tooltip * @return {Highcharts.SVGElement} Returns the updated partial tooltip */ function updatePartialTooltip(partialTooltip, point, str) { var tt = partialTooltip; var isHeader = point.isHeader, series = point.series; var colorClass = 'highcharts-color-' + pick(point.colorIndex, series.colorIndex, 'none'); if (!tt) { var attribs = { padding: options.padding, r: options.borderRadius }; if (!styledMode) { attribs.fill = options.backgroundColor; attribs['stroke-width'] = options.borderWidth; } tt = ren .label('', 0, 0, (options[isHeader ? 'headerShape' : 'shape']) || 'callout', void 0, void 0, options.useHTML) .addClass((isHeader ? 'highcharts-tooltip-header ' : '') + 'highcharts-tooltip-box ' + colorClass) .attr(attribs) .add(tooltipLabel); } tt.isActive = true; tt.attr({ text: str }); if (!styledMode) { tt.css(options.style) .shadow(options.shadow) .attr({ stroke: (options.borderColor || point.color || series.color || palette.neutralColor80) }); } return tt; } // Graceful degradation for legacy formatters if (isString(labels)) { labels = [false, labels]; } // Create the individual labels for header and points, ignore footer var boxes = labels.slice(0, points.length + 1).reduce(function (boxes, str, i) { if (str !== false && str !== '') { var point = (points[i - 1] || { // Item 0 is the header. Instead of this, we could also // use the crosshair label isHeader: true, plotX: points[0].plotX, plotY: plotHeight, series: {} }); var isHeader = point.isHeader; // Store the tooltip label referance on the series var owner = isHeader ? tooltip : point.series; var tt = owner.tt = updatePartialTooltip(owner.tt, point, str.toString()); // Get X position now, so we can move all to the other side in // case of overflow var bBox = tt.getBBox(); var boxWidth = bBox.width + tt.strokeWidth(); if (isHeader) { headerHeight = bBox.height; adjustedPlotHeight += headerHeight; if (headerTop) { distributionBoxTop -= headerHeight; } } var _a = getAnchor(point), anchorX = _a.anchorX, anchorY = _a.anchorY; if (typeof anchorY === 'number') { var size = bBox.height + 1; var boxPosition = (positioner ? positioner.call(tooltip, boxWidth, size, point) : defaultPositioner(anchorX, anchorY, isHeader, boxWidth)); boxes.push({ // 0-align to the top, 1-align to the bottom align: positioner ? 0 : void 0, anchorX: anchorX, anchorY: anchorY, boxWidth: boxWidth, point: point, rank: pick(boxPosition.rank, isHeader ? 1 : 0), size: size, target: boxPosition.y, tt: tt, x: boxPosition.x }); } else { // Hide tooltips which anchorY is outside the visible plot // area tt.isActive = false; } } return boxes; }, []); // Realign the tooltips towards the right if there is not enough // space to the left and there is space to to the right if (!positioner && boxes.some(function (box) { // Always realign if the beginning of a label is outside bounds var outside = tooltip.outside; var boxStart = (outside ? chartLeft : 0) + box.anchorX; if (boxStart < bounds.left && boxStart + box.boxWidth < bounds.right) { return true; } // Otherwise, check if there is more space available to the right return boxStart < (chartLeft - bounds.left) + box.boxWidth && bounds.right - boxStart > boxStart; })) { boxes = boxes.map(function (box) { var _a = defaultPositioner(box.anchorX, box.anchorY, box.point.isHeader, box.boxWidth, false), x = _a.x, y = _a.y; return extend(box, { target: y, x: x }); }); } // Clean previous run (for missing points) tooltip.cleanSplit(); // Distribute and put in place H.distribute(boxes, adjustedPlotHeight); var boxExtremes = { left: chartLeft, right: chartLeft }; // Get the extremes from series tooltips boxes.forEach(function (box) { var x = box.x, boxWidth = box.boxWidth, isHeader = box.isHeader; if (!isHeader) { if (tooltip.outside && chartLeft + x < boxExtremes.left) { boxExtremes.left = chartLeft + x; } if (!isHeader && tooltip.outside && boxExtremes.left + boxWidth > boxExtremes.right) { boxExtremes.right = chartLeft + x; } } }); boxes.forEach(function (box) { var x = box.x, anchorX = box.anchorX, anchorY = box.anchorY, pos = box.pos, isHeader = box.point.isHeader; var attributes = { visibility: typeof pos === 'undefined' ? 'hidden' : 'inherit', x: x, /* NOTE: y should equal pos to be consistent with !split * tooltip, but is currently relative to plotTop. Is left as is * to avoid breaking change. Remove distributionBoxTop to make * it consistent. */ y: pos + distributionBoxTop, anchorX: anchorX, anchorY: anchorY }; // Handle left-aligned tooltips overflowing the chart area if (tooltip.outside && x < anchorX) { var offset = chartLeft - boxExtremes.left; // Skip this if there is no overflow if (offset > 0) { if (!isHeader) { attributes.x = x + offset; attributes.anchorX = anchorX + offset; } if (isHeader) { attributes.x = (boxExtremes.right - boxExtremes.left) / 2; attributes.anchorX = anchorX + offset; } } } // Put the label in place box.tt.attr(attributes); }); /* If we have a seperate tooltip container, then update the necessary * container properties. * Test that tooltip has its own container and renderer before executing * the operation. */ var container = tooltip.container, outside = tooltip.outside, renderer = tooltip.renderer; if (outside && container && renderer) { // Set container size to fit the bounds var _f = tooltipLabel.getBBox(), width = _f.width, height = _f.height, x = _f.x, y = _f.y; renderer.setSize(width + x, height + y, false); // Position the tooltip container to the chart container container.style.left = boxExtremes.left + 'px'; container.style.top = chartTop + 'px'; } }; /** * If the `stickOnContact` option is active, this will add a tracker shape. * * @private * @function Highcharts.Tooltip#drawTracker */ Tooltip.prototype.drawTracker = function () { var tooltip = this; if (tooltip.followPointer || !tooltip.options.stickOnContact) { if (tooltip.tracker) { tooltip.tracker.destroy(); } return; } var chart = tooltip.chart; var label = tooltip.label; var point = chart.hoverPoint; if (!label || !point) { return; } var box = { x: 0, y: 0, width: 0, height: 0 }; // Combine anchor and tooltip var anchorPos = this.getAnchor(point); var labelBBox = label.getBBox(); anchorPos[0] += chart.plotLeft - label.translateX; anchorPos[1] += chart.plotTop - label.translateY; // When the mouse pointer is between the anchor point and the label, // the label should stick. box.x = Math.min(0, anchorPos[0]); box.y = Math.min(0, anchorPos[1]); box.width = (anchorPos[0] < 0 ? Math.max(Math.abs(anchorPos[0]), (labelBBox.width - anchorPos[0])) : Math.max(Math.abs(anchorPos[0]), labelBBox.width)); box.height = (anchorPos[1] < 0 ? Math.max(Math.abs(anchorPos[1]), (labelBBox.height - Math.abs(anchorPos[1]))) : Math.max(Math.abs(anchorPos[1]), labelBBox.height)); if (tooltip.tracker) { tooltip.tracker.attr(box); } else { tooltip.tracker = label.renderer .rect(box) .addClass('highcharts-tracker') .add(label); if (!chart.styledMode) { tooltip.tracker.attr({ fill: 'rgba(0,0,0,0)' }); } } }; /** * @private */ Tooltip.prototype.styledModeFormat = function (formatString) { return formatString .replace('style="font-size: 10px"', 'class="highcharts-header"') .replace(/style="color:{(point|series)\.color}"/g, 'class="highcharts-color-{$1.colorIndex}"'); }; /** * Format the footer/header of the tooltip * #3397: abstraction to enable formatting of footer and header * * @private * @function Highcharts.Tooltip#tooltipFooterHeaderFormatter * @param {Highcharts.PointLabelObject} labelConfig * @param {boolean} [isFooter] * @return {string} */ Tooltip.prototype.tooltipFooterHeaderFormatter = function (labelConfig, isFooter) { var footOrHead = isFooter ? 'footer' : 'header', series = labelConfig.series, tooltipOptions = series.tooltipOptions, xDateFormat = tooltipOptions.xDateFormat, xAxis = series.xAxis, isDateTime = (xAxis && xAxis.options.type === 'datetime' && isNumber(labelConfig.key)), formatString = tooltipOptions[footOrHead + 'Format'], e = { isFooter: isFooter, labelConfig: labelConfig }; fireEvent(this, 'headerFormatter', e, function (e) { // Guess the best date format based on the closest point distance // (#568, #3418) if (isDateTime && !xDateFormat) { xDateFormat = this.getXDateFormat(labelConfig, tooltipOptions, xAxis); } // Insert the footer date format if any if (isDateTime && xDateFormat) { ((labelConfig.point && labelConfig.point.tooltipDateKeys) || ['key']).forEach(function (key) { formatString = formatString.replace('{point.' + key + '}', '{point.' + key + ':' + xDateFormat + '}'); }); } // Replace default header style with class name if (series.chart.styledMode) { formatString = this.styledModeFormat(formatString); } e.text = format(formatString, { point: labelConfig, series: series }, this.chart); }); return e.text; }; /** * Updates the tooltip with the provided tooltip options. * * @function Highcharts.Tooltip#update * * @param {Highcharts.TooltipOptions} options * The tooltip options to update. */ Tooltip.prototype.update = function (options) { this.destroy(); // Update user options (#6218) merge(true, this.chart.options.tooltip.userOptions, options); this.init(this.chart, merge(true, this.options, options)); }; /** * Find the new position and perform the move * * @private * @function Highcharts.Tooltip#updatePosition * * @param {Highcharts.Point} point */ Tooltip.prototype.updatePosition = function (point) { var chart = this.chart, pointer = chart.pointer, label = this.getLabel(), pos, anchorX = point.plotX + chart.plotLeft, anchorY = point.plotY + chart.plotTop, pad; // Needed for outside: true (#11688) var chartPosition = pointer.getChartPosition(); pos = (this.options.positioner || this.getPosition).call(this, label.width, label.height, point); // Set the renderer size dynamically to prevent document size to change if (this.outside) { pad = (this.options.borderWidth || 0) + 2 * this.distance; this.renderer.setSize(label.width + pad, label.height + pad, false); // Anchor and tooltip container need scaling if chart container has // scale transform/css zoom. #11329. if (chartPosition.scaleX !== 1 || chartPosition.scaleY !== 1) { css(this.container, { transform: "scale(" + chartPosition.scaleX + ", " + chartPosition.scaleY + ")" }); anchorX *= chartPosition.scaleX; anchorY *= chartPosition.scaleY; } anchorX += chartPosition.left - pos.x; anchorY += chartPosition.top - pos.y; } // do the move this.move(Math.round(pos.x), Math.round(pos.y || 0), // can be undefined (#3977) anchorX, anchorY); }; return Tooltip; }()); H.Tooltip = Tooltip; return H.Tooltip; }); _registerModule(_modules, 'Core/Pointer.js', [_modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Core/Color/Palette.js'], _modules['Core/Tooltip.js'], _modules['Core/Utilities.js']], function (Color, H, Palette, Tooltip, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var color = Color.parse; var charts = H.charts, noop = H.noop; var addEvent = U.addEvent, attr = U.attr, css = U.css, defined = U.defined, extend = U.extend, find = U.find, fireEvent = U.fireEvent, isNumber = U.isNumber, isObject = U.isObject, objectEach = U.objectEach, offset = U.offset, pick = U.pick, splat = U.splat; /* * * * Class * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The mouse and touch tracker object. Each {@link Chart} item has one * assosiated Pointer item that can be accessed from the {@link Chart.pointer} * property. * * @class * @name Highcharts.Pointer * * @param {Highcharts.Chart} chart * The chart instance. * * @param {Highcharts.Options} options * The root options object. The pointer uses options from the chart and tooltip * structures. */ var Pointer = /** @class */ (function () { /* * * * Constructors * * */ function Pointer(chart, options) { this.lastValidTouch = {}; this.pinchDown = []; this.runChartClick = false; this.eventsToUnbind = []; this.chart = chart; this.hasDragged = false; this.options = options; this.init(chart, options); } /* * * * Functions * * */ /** * Set inactive state to all series that are not currently hovered, * or, if `inactiveOtherPoints` is set to true, set inactive state to * all points within that series. * * @private * @function Highcharts.Pointer#applyInactiveState * * @param {Array<Highcharts.Point>} points * Currently hovered points */ Pointer.prototype.applyInactiveState = function (points) { var activeSeries = [], series; // Get all active series from the hovered points (points || []).forEach(function (item) { series = item.series; // Include itself activeSeries.push(series); // Include parent series if (series.linkedParent) { activeSeries.push(series.linkedParent); } // Include all child series if (series.linkedSeries) { activeSeries = activeSeries.concat(series.linkedSeries); } // Include navigator series if (series.navigatorSeries) { activeSeries.push(series.navigatorSeries); } }); // Now loop over all series, filtering out active series this.chart.series.forEach(function (inactiveSeries) { if (activeSeries.indexOf(inactiveSeries) === -1) { // Inactive series inactiveSeries.setState('inactive', true); } else if (inactiveSeries.options.inactiveOtherPoints) { // Active series, but other points should be inactivated inactiveSeries.setAllPointsToState('inactive'); } }); }; /** * Destroys the Pointer object and disconnects DOM events. * * @function Highcharts.Pointer#destroy */ Pointer.prototype.destroy = function () { var pointer = this; this.eventsToUnbind.forEach(function (unbind) { return unbind(); }); this.eventsToUnbind = []; if (!H.chartCount) { if (Pointer.unbindDocumentMouseUp) { Pointer.unbindDocumentMouseUp = Pointer.unbindDocumentMouseUp(); } if (Pointer.unbindDocumentTouchEnd) { Pointer.unbindDocumentTouchEnd = Pointer.unbindDocumentTouchEnd(); } } // memory and CPU leak clearInterval(pointer.tooltipTimeout); objectEach(pointer, function (_val, prop) { pointer[prop] = void 0; }); }; /** * Perform a drag operation in response to a mousemove event while the mouse * is down. * @private * @function Highcharts.Pointer#drag */ Pointer.prototype.drag = function (e) { var chart = this.chart, chartOptions = chart.options.chart, zoomHor = this.zoomHor, zoomVert = this.zoomVert, plotLeft = chart.plotLeft, plotTop = chart.plotTop, plotWidth = chart.plotWidth, plotHeight = chart.plotHeight, mouseDownX = (this.mouseDownX || 0), mouseDownY = (this.mouseDownY || 0), panningEnabled = isObject(chartOptions.panning) ? chartOptions.panning && chartOptions.panning.enabled : chartOptions.panning, panKey = (chartOptions.panKey && e[chartOptions.panKey + 'Key']); var chartX = e.chartX, chartY = e.chartY, clickedInside, size, selectionMarker = this.selectionMarker; // If the device supports both touch and mouse (like IE11), and we are // touch-dragging inside the plot area, don't handle the mouse event. // #4339. if (selectionMarker && selectionMarker.touch) { return; } // If the mouse is outside the plot area, adjust to cooordinates // inside to prevent the selection marker from going outside if (chartX < plotLeft) { chartX = plotLeft; } else if (chartX > plotLeft + plotWidth) { chartX = plotLeft + plotWidth; } if (chartY < plotTop) { chartY = plotTop; } else if (chartY > plotTop + plotHeight) { chartY = plotTop + plotHeight; } // determine if the mouse has moved more than 10px this.hasDragged = Math.sqrt(Math.pow(mouseDownX - chartX, 2) + Math.pow(mouseDownY - chartY, 2)); if (this.hasDragged > 10) { clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop, { visiblePlotOnly: true }); // make a selection if (chart.hasCartesianSeries && (this.zoomX || this.zoomY) && clickedInside && !panKey) { if (!selectionMarker) { this.selectionMarker = selectionMarker = chart.renderer.rect(plotLeft, plotTop, zoomHor ? 1 : plotWidth, zoomVert ? 1 : plotHeight, 0) .attr({ 'class': 'highcharts-selection-marker', zIndex: 7 }) .add(); if (!chart.styledMode) { selectionMarker.attr({ fill: (chartOptions.selectionMarkerFill || color(Palette.highlightColor80) .setOpacity(0.25).get()) }); } } } // adjust the width of the selection marker if (selectionMarker && zoomHor) { size = chartX - mouseDownX; selectionMarker.attr({ width: Math.abs(size), x: (size > 0 ? 0 : size) + mouseDownX }); } // adjust the height of the selection marker if (selectionMarker && zoomVert) { size = chartY - mouseDownY; selectionMarker.attr({ height: Math.abs(size), y: (size > 0 ? 0 : size) + mouseDownY }); } // panning if (clickedInside && !selectionMarker && panningEnabled) { chart.pan(e, chartOptions.panning); } } }; /** * Start a drag operation. * @private * @function Highcharts.Pointer#dragStart */ Pointer.prototype.dragStart = function (e) { var chart = this.chart; // Record the start position chart.mouseIsDown = e.type; chart.cancelClick = false; chart.mouseDownX = this.mouseDownX = e.chartX; chart.mouseDownY = this.mouseDownY = e.chartY; }; /** * On mouse up or touch end across the entire document, drop the selection. * @private * @function Highcharts.Pointer#drop */ Pointer.prototype.drop = function (e) { var pointer = this, chart = this.chart, hasPinched = this.hasPinched; if (this.selectionMarker) { var selectionData_1 = { originalEvent: e, xAxis: [], yAxis: [] }, selectionBox = this.selectionMarker, selectionLeft_1 = selectionBox.attr ? selectionBox.attr('x') : selectionBox.x, selectionTop_1 = selectionBox.attr ? selectionBox.attr('y') : selectionBox.y, selectionWidth_1 = selectionBox.attr ? selectionBox.attr('width') : selectionBox.width, selectionHeight_1 = selectionBox.attr ? selectionBox.attr('height') : selectionBox.height; var runZoom_1; // a selection has been made if (this.hasDragged || hasPinched) { // record each axis' min and max chart.axes.forEach(function (axis) { if (axis.zoomEnabled && defined(axis.min) && (hasPinched || pointer[{ xAxis: 'zoomX', yAxis: 'zoomY' }[axis.coll]]) && isNumber(selectionLeft_1) && isNumber(selectionTop_1)) { // #859, #3569 var horiz = axis.horiz, minPixelPadding = e.type === 'touchend' ? axis.minPixelPadding : 0, // #1207, #3075 selectionMin = axis.toValue((horiz ? selectionLeft_1 : selectionTop_1) + minPixelPadding), selectionMax = axis.toValue((horiz ? selectionLeft_1 + selectionWidth_1 : selectionTop_1 + selectionHeight_1) - minPixelPadding); selectionData_1[axis.coll].push({ axis: axis, // Min/max for reversed axes min: Math.min(selectionMin, selectionMax), max: Math.max(selectionMin, selectionMax) }); runZoom_1 = true; } }); if (runZoom_1) { fireEvent(chart, 'selection', selectionData_1, function (args) { chart.zoom(extend(args, hasPinched ? { animation: false } : null)); }); } } if (isNumber(chart.index)) { this.selectionMarker = this.selectionMarker.destroy(); } // Reset scaling preview if (hasPinched) { this.scaleGroups(); } } // Reset all. Check isNumber because it may be destroyed on mouse up // (#877) if (chart && isNumber(chart.index)) { css(chart.container, { cursor: chart._cursor }); chart.cancelClick = this.hasDragged > 10; // #370 chart.mouseIsDown = this.hasDragged = this.hasPinched = false; this.pinchDown = []; } }; /** * Finds the closest point to a set of coordinates, using the k-d-tree * algorithm. * * @function Highcharts.Pointer#findNearestKDPoint * * @param {Array<Highcharts.Series>} series * All the series to search in. * * @param {boolean|undefined} shared * Whether it is a shared tooltip or not. * * @param {Highcharts.PointerEventObject} e * The pointer event object, containing chart coordinates of the pointer. * * @return {Highcharts.Point|undefined} * The point closest to given coordinates. */ Pointer.prototype.findNearestKDPoint = function (series, shared, e) { var chart = this.chart; var hoverPoint = chart.hoverPoint; var tooltip = chart.tooltip; if (hoverPoint && tooltip && tooltip.isStickyOnContact()) { return hoverPoint; } var closest; /** @private */ function sort(p1, p2) { var isCloserX = p1.distX - p2.distX, isCloser = p1.dist - p2.dist, isAbove = ((p2.series.group && p2.series.group.zIndex) - (p1.series.group && p1.series.group.zIndex)); var result; // We have two points which are not in the same place on xAxis // and shared tooltip: if (isCloserX !== 0 && shared) { // #5721 result = isCloserX; // Points are not exactly in the same place on x/yAxis: } else if (isCloser !== 0) { result = isCloser; // The same xAxis and yAxis position, sort by z-index: } else if (isAbove !== 0) { result = isAbove; // The same zIndex, sort by array index: } else { result = p1.series.index > p2.series.index ? -1 : 1; } return result; } series.forEach(function (s) { var noSharedTooltip = s.noSharedTooltip && shared, compareX = (!noSharedTooltip && s.options.findNearestPointBy.indexOf('y') < 0), point = s.searchPoint(e, compareX); if ( // Check that we actually found a point on the series. isObject(point, true) && point.series && // Use the new point if it is closer. (!isObject(closest, true) || (sort(closest, point) > 0))) { closest = point; } }); return closest; }; /** * @private * @function Highcharts.Pointer#getChartCoordinatesFromPoint */ Pointer.prototype.getChartCoordinatesFromPoint = function (point, inverted) { var series = point.series, xAxis = series.xAxis, yAxis = series.yAxis, shapeArgs = point.shapeArgs; if (xAxis && yAxis) { var x = pick(point.clientX, point.plotX); var y = point.plotY || 0; if (point.isNode && shapeArgs && isNumber(shapeArgs.x) && isNumber(shapeArgs.y)) { x = shapeArgs.x; y = shapeArgs.y; } return inverted ? { chartX: yAxis.len + yAxis.pos - y, chartY: xAxis.len + xAxis.pos - x } : { chartX: x + xAxis.pos, chartY: y + yAxis.pos }; } if (shapeArgs && shapeArgs.x && shapeArgs.y) { // E.g. pies do not have axes return { chartX: shapeArgs.x, chartY: shapeArgs.y }; } }; /** * Return the cached chartPosition if it is available on the Pointer, * otherwise find it. Running offset is quite expensive, so it should be * avoided when we know the chart hasn't moved. * * @function Highcharts.Pointer#getChartPosition * * @return {Highcharts.ChartPositionObject} * The offset of the chart container within the page */ Pointer.prototype.getChartPosition = function () { if (this.chartPosition) { return this.chartPosition; } var container = this.chart.container; var pos = offset(container); this.chartPosition = { left: pos.left, top: pos.top, scaleX: 1, scaleY: 1 }; var offsetWidth = container.offsetWidth; var offsetHeight = container.offsetHeight; // #13342 - tooltip was not visible in Chrome, when chart // updates height. if (offsetWidth > 2 && // #13342 offsetHeight > 2 // #13342 ) { this.chartPosition.scaleX = pos.width / offsetWidth; this.chartPosition.scaleY = pos.height / offsetHeight; } return this.chartPosition; }; /** * Get the click position in terms of axis values. * * @function Highcharts.Pointer#getCoordinates * * @param {Highcharts.PointerEventObject} e * Pointer event, extended with `chartX` and `chartY` properties. * * @return {Highcharts.PointerAxisCoordinatesObject} */ Pointer.prototype.getCoordinates = function (e) { var coordinates = { xAxis: [], yAxis: [] }; this.chart.axes.forEach(function (axis) { coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({ axis: axis, value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY']) }); }); return coordinates; }; /** * Calculates what is the current hovered point/points and series. * * @private * @function Highcharts.Pointer#getHoverData * * @param {Highcharts.Point|undefined} existingHoverPoint * The point currrently beeing hovered. * * @param {Highcharts.Series|undefined} existingHoverSeries * The series currently beeing hovered. * * @param {Array<Highcharts.Series>} series * All the series in the chart. * * @param {boolean} isDirectTouch * Is the pointer directly hovering the point. * * @param {boolean|undefined} shared * Whether it is a shared tooltip or not. * * @param {Highcharts.PointerEventObject} [e] * The triggering event, containing chart coordinates of the pointer. * * @return {object} * Object containing resulting hover data: hoverPoint, hoverSeries, and * hoverPoints. */ Pointer.prototype.getHoverData = function (existingHoverPoint, existingHoverSeries, series, isDirectTouch, shared, e) { var hoverPoints = [], useExisting = !!(isDirectTouch && existingHoverPoint), filter = function (s) { return (s.visible && !(!shared && s.directTouch) && // #3821 pick(s.options.enableMouseTracking, true)); }; var hoverSeries = existingHoverSeries, // Which series to look in for the hover point searchSeries, // Parameters needed for beforeGetHoverData event. eventArgs = { chartX: e ? e.chartX : void 0, chartY: e ? e.chartY : void 0, shared: shared }; // Find chart.hoverPane and update filter method in polar. fireEvent(this, 'beforeGetHoverData', eventArgs); var notSticky = hoverSeries && !hoverSeries.stickyTracking; searchSeries = notSticky ? // Only search on hovered series if it has stickyTracking false [hoverSeries] : // Filter what series to look in. series.filter(function (s) { return eventArgs.filter ? eventArgs.filter(s) : filter(s) && s.stickyTracking; }); // Use existing hovered point or find the one closest to coordinates. var hoverPoint = useExisting || !e ? existingHoverPoint : this.findNearestKDPoint(searchSeries, shared, e); // Assign hover series hoverSeries = hoverPoint && hoverPoint.series; // If we have a hoverPoint, assign hoverPoints. if (hoverPoint) { // When tooltip is shared, it displays more than one point if (shared && !hoverSeries.noSharedTooltip) { searchSeries = series.filter(function (s) { return eventArgs.filter ? eventArgs.filter(s) : filter(s) && !s.noSharedTooltip; }); // Get all points with the same x value as the hoverPoint searchSeries.forEach(function (s) { var point = find(s.points, function (p) { return p.x === hoverPoint.x && !p.isNull; }); if (isObject(point)) { /* * Boost returns a minimal point. Convert it to a usable * point for tooltip and states. */ if (s.chart.isBoosting) { point = s.getPoint(point); } hoverPoints.push(point); } }); } else { hoverPoints.push(hoverPoint); } } // Check whether the hoverPoint is inside pane we are hovering over. eventArgs = { hoverPoint: hoverPoint }; fireEvent(this, 'afterGetHoverData', eventArgs); return { hoverPoint: eventArgs.hoverPoint, hoverSeries: hoverSeries, hoverPoints: hoverPoints }; }; /** * @private * @function Highcharts.Pointer#getPointFromEvent */ Pointer.prototype.getPointFromEvent = function (e) { var target = e.target, point; while (target && !point) { point = target.point; target = target.parentNode; } return point; }; /** * @private * @function Highcharts.Pointer#onTrackerMouseOut */ Pointer.prototype.onTrackerMouseOut = function (e) { var chart = this.chart; var relatedTarget = e.relatedTarget || e.toElement; var series = chart.hoverSeries; this.isDirectTouch = false; if (series && relatedTarget && !series.stickyTracking && !this.inClass(relatedTarget, 'highcharts-tooltip') && (!this.inClass(relatedTarget, 'highcharts-series-' + series.index) || // #2499, #4465, #5553 !this.inClass(relatedTarget, 'highcharts-tracker'))) { series.onMouseOut(); } }; /** * Utility to detect whether an element has, or has a parent with, a * specificclass name. Used on detection of tracker objects and on deciding * whether hovering the tooltip should cause the active series to mouse out. * * @function Highcharts.Pointer#inClass * * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element * The element to investigate. * * @param {string} className * The class name to look for. * * @return {boolean|undefined} * True if either the element or one of its parents has the given class * name. */ Pointer.prototype.inClass = function (element, className) { var elemClassName; while (element) { elemClassName = attr(element, 'class'); if (elemClassName) { if (elemClassName.indexOf(className) !== -1) { return true; } if (elemClassName.indexOf('highcharts-container') !== -1) { return false; } } element = element.parentNode; } }; /** * Initialize the Pointer. * * @private * @function Highcharts.Pointer#init * * @param {Highcharts.Chart} chart * The Chart instance. * * @param {Highcharts.Options} options * The root options object. The pointer uses options from the chart and * tooltip structures. */ Pointer.prototype.init = function (chart, options) { // Store references this.options = options; this.chart = chart; // Do we need to handle click on a touch device? this.runChartClick = Boolean(options.chart.events && options.chart.events.click); this.pinchDown = []; this.lastValidTouch = {}; if (Tooltip) { /** * Tooltip object for points of series. * * @name Highcharts.Chart#tooltip * @type {Highcharts.Tooltip} */ chart.tooltip = new Tooltip(chart, options.tooltip); this.followTouchMove = pick(options.tooltip.followTouchMove, true); } this.setDOMEvents(); }; /** * Takes a browser event object and extends it with custom Highcharts * properties `chartX` and `chartY` in order to work on the internal * coordinate system. * * @function Highcharts.Pointer#normalize * * @param {global.MouseEvent|global.PointerEvent|global.TouchEvent} e * Event object in standard browsers. * * @param {Highcharts.OffsetObject} [chartPosition] * Additional chart offset. * * @return {Highcharts.PointerEventObject} * A browser event with extended properties `chartX` and `chartY`. */ Pointer.prototype.normalize = function (e, chartPosition) { var touches = e.touches; // iOS (#2757) var ePos = (touches ? touches.length ? touches.item(0) : (pick(// #13534 touches.changedTouches, e.changedTouches))[0] : e); // Get mouse position if (!chartPosition) { chartPosition = this.getChartPosition(); } var chartX = ePos.pageX - chartPosition.left, chartY = ePos.pageY - chartPosition.top; // #11329 - when there is scaling on a parent element, we need to take // this into account chartX /= chartPosition.scaleX; chartY /= chartPosition.scaleY; return extend(e, { chartX: Math.round(chartX), chartY: Math.round(chartY) }); }; /** * @private * @function Highcharts.Pointer#onContainerClick */ Pointer.prototype.onContainerClick = function (e) { var chart = this.chart; var hoverPoint = chart.hoverPoint; var pEvt = this.normalize(e); var plotLeft = chart.plotLeft; var plotTop = chart.plotTop; if (!chart.cancelClick) { // On tracker click, fire the series and point events. #783, #1583 if (hoverPoint && this.inClass(pEvt.target, 'highcharts-tracker')) { // the series click event fireEvent(hoverPoint.series, 'click', extend(pEvt, { point: hoverPoint })); // the point click event if (chart.hoverPoint) { // it may be destroyed (#1844) hoverPoint.firePointEvent('click', pEvt); } // When clicking outside a tracker, fire a chart event } else { extend(pEvt, this.getCoordinates(pEvt)); // fire a click event in the chart if (chart.isInsidePlot(pEvt.chartX - plotLeft, pEvt.chartY - plotTop, { visiblePlotOnly: true })) { fireEvent(chart, 'click', pEvt); } } } }; /** * @private * @function Highcharts.Pointer#onContainerMouseDown */ Pointer.prototype.onContainerMouseDown = function (e) { var isPrimaryButton = ((e.buttons || e.button) & 1) === 1; // Normalize before the 'if' for the legacy IE (#7850) e = this.normalize(e); // #11635, Firefox does not reliable fire move event after click scroll if (H.isFirefox && e.button !== 0) { this.onContainerMouseMove(e); } // #11635, limiting to primary button (incl. IE 8 support) if (typeof e.button === 'undefined' || isPrimaryButton) { this.zoomOption(e); // #295, #13737 solve conflict between container drag and chart zoom if (isPrimaryButton && e.preventDefault) { e.preventDefault(); } this.dragStart(e); } }; /** * When mouse leaves the container, hide the tooltip. * @private * @function Highcharts.Pointer#onContainerMouseLeave */ Pointer.prototype.onContainerMouseLeave = function (e) { var chart = charts[pick(Pointer.hoverChartIndex, -1)]; var tooltip = this.chart.tooltip; e = this.normalize(e); // #4886, MS Touch end fires mouseleave but with no related target if (chart && (e.relatedTarget || e.toElement)) { chart.pointer.reset(); // Also reset the chart position, used in #149 fix chart.pointer.chartPosition = void 0; } if ( // #11635, Firefox wheel scroll does not fire out events consistently tooltip && !tooltip.isHidden) { this.reset(); } }; /** * When mouse enters the container, delete pointer's chartPosition. * @private * @function Highcharts.Pointer#onContainerMouseEnter */ Pointer.prototype.onContainerMouseEnter = function (e) { delete this.chartPosition; }; /** * The mousemove, touchmove and touchstart event handler * @private * @function Highcharts.Pointer#onContainerMouseMove */ Pointer.prototype.onContainerMouseMove = function (e) { var chart = this.chart; var pEvt = this.normalize(e); this.setHoverChartIndex(); // In IE8 we apparently need this returnValue set to false in order to // avoid text being selected. But in Chrome, e.returnValue is prevented, // plus we don't need to run e.preventDefault to prevent selected text // in modern browsers. So we set it conditionally. Remove it when IE8 is // no longer needed. #2251, #3224. if (!pEvt.preventDefault) { pEvt.returnValue = false; } if (chart.mouseIsDown === 'mousedown' || this.touchSelect(pEvt)) { this.drag(pEvt); } // Show the tooltip and run mouse over events (#977) if (!chart.openMenu && (this.inClass(pEvt.target, 'highcharts-tracker') || chart.isInsidePlot(pEvt.chartX - chart.plotLeft, pEvt.chartY - chart.plotTop, { visiblePlotOnly: true }))) { if (this.inClass(pEvt.target, 'highcharts-no-tooltip')) { this.reset(false, 0); } else { this.runPointActions(pEvt); } } }; /** * @private * @function Highcharts.Pointer#onDocumentTouchEnd */ Pointer.prototype.onDocumentTouchEnd = function (e) { var hoverChart = charts[pick(Pointer.hoverChartIndex, -1)]; if (hoverChart) { hoverChart.pointer.drop(e); } }; /** * @private * @function Highcharts.Pointer#onContainerTouchMove */ Pointer.prototype.onContainerTouchMove = function (e) { if (this.touchSelect(e)) { this.onContainerMouseMove(e); } else { this.touch(e); } }; /** * @private * @function Highcharts.Pointer#onContainerTouchStart */ Pointer.prototype.onContainerTouchStart = function (e) { if (this.touchSelect(e)) { this.onContainerMouseDown(e); } else { this.zoomOption(e); this.touch(e, true); } }; /** * Special handler for mouse move that will hide the tooltip when the mouse * leaves the plotarea. Issue #149 workaround. The mouseleave event does not * always fire. * @private * @function Highcharts.Pointer#onDocumentMouseMove */ Pointer.prototype.onDocumentMouseMove = function (e) { var chart = this.chart; var chartPosition = this.chartPosition; var pEvt = this.normalize(e, chartPosition); var tooltip = chart.tooltip; // If we're outside, hide the tooltip if (chartPosition && (!tooltip || !tooltip.isStickyOnContact()) && !chart.isInsidePlot(pEvt.chartX - chart.plotLeft, pEvt.chartY - chart.plotTop, { visiblePlotOnly: true }) && !this.inClass(pEvt.target, 'highcharts-tracker')) { this.reset(); } }; /** * @private * @function Highcharts.Pointer#onDocumentMouseUp */ Pointer.prototype.onDocumentMouseUp = function (e) { var chart = charts[pick(Pointer.hoverChartIndex, -1)]; if (chart) { chart.pointer.drop(e); } }; /** * Handle touch events with two touches * @private * @function Highcharts.Pointer#pinch */ Pointer.prototype.pinch = function (e) { var self = this, chart = self.chart, pinchDown = self.pinchDown, touches = (e.touches || []), touchesLength = touches.length, lastValidTouch = self.lastValidTouch, hasZoom = self.hasZoom, transform = {}, fireClickEvent = touchesLength === 1 && ((self.inClass(e.target, 'highcharts-tracker') && chart.runTrackerClick) || self.runChartClick), clip = {}; var selectionMarker = self.selectionMarker; // Don't initiate panning until the user has pinched. This prevents us // from blocking page scrolling as users scroll down a long page // (#4210). if (touchesLength > 1) { self.initiated = true; } // On touch devices, only proceed to trigger click if a handler is // defined if (hasZoom && self.initiated && !fireClickEvent && e.cancelable !== false) { e.preventDefault(); } // Normalize each touch [].map.call(touches, function (e) { return self.normalize(e); }); // Register the touch start position if (e.type === 'touchstart') { [].forEach.call(touches, function (e, i) { pinchDown[i] = { chartX: e.chartX, chartY: e.chartY }; }); lastValidTouch.x = [pinchDown[0].chartX, pinchDown[1] && pinchDown[1].chartX]; lastValidTouch.y = [pinchDown[0].chartY, pinchDown[1] && pinchDown[1].chartY]; // Identify the data bounds in pixels chart.axes.forEach(function (axis) { if (axis.zoomEnabled) { var bounds = chart.bounds[axis.horiz ? 'h' : 'v'], minPixelPadding = axis.minPixelPadding, min = axis.toPixels(Math.min(pick(axis.options.min, axis.dataMin), axis.dataMin)), max = axis.toPixels(Math.max(pick(axis.options.max, axis.dataMax), axis.dataMax)), absMin = Math.min(min, max), absMax = Math.max(min, max); // Store the bounds for use in the touchmove handler bounds.min = Math.min(axis.pos, absMin - minPixelPadding); bounds.max = Math.max(axis.pos + axis.len, absMax + minPixelPadding); } }); self.res = true; // reset on next move // Optionally move the tooltip on touchmove } else if (self.followTouchMove && touchesLength === 1) { this.runPointActions(self.normalize(e)); // Event type is touchmove, handle panning and pinching } else if (pinchDown.length) { // can be 0 when releasing, if touchend // fires first // Set the marker if (!selectionMarker) { // @todo It's a mock object, so maybe we need a separate // interface self.selectionMarker = selectionMarker = extend({ destroy: noop, touch: true }, chart.plotBox); } self.pinchTranslate(pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); self.hasPinched = hasZoom; // Scale and translate the groups to provide visual feedback during // pinching self.scaleGroups(transform, clip); if (self.res) { self.res = false; this.reset(false, 0); } } }; /** * Run translation operations * @private * @function Highcharts.Pointer#pinchTranslate */ Pointer.prototype.pinchTranslate = function (pinchDown, touches, transform, selectionMarker, clip, lastValidTouch) { if (this.zoomHor) { this.pinchTranslateDirection(true, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); } if (this.zoomVert) { this.pinchTranslateDirection(false, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch); } }; /** * Run translation operations for each direction (horizontal and vertical) * independently. * @private * @function Highcharts.Pointer#pinchTranslateDirection */ Pointer.prototype.pinchTranslateDirection = function (horiz, pinchDown, touches, transform, selectionMarker, clip, lastValidTouch, forcedScale) { var chart = this.chart, xy = horiz ? 'x' : 'y', XY = horiz ? 'X' : 'Y', sChartXY = ('chart' + XY), wh = horiz ? 'width' : 'height', plotLeftTop = chart['plot' + (horiz ? 'Left' : 'Top')], inverted = chart.inverted, bounds = chart.bounds[horiz ? 'h' : 'v'], singleTouch = pinchDown.length === 1, touch0Start = pinchDown[0][sChartXY], touch1Start = !singleTouch && pinchDown[1][sChartXY], setScale = function () { // Don't zoom if fingers are too close on this axis if (typeof touch1Now === 'number' && Math.abs(touch0Start - touch1Start) > 20) { scale = forcedScale || Math.abs(touch0Now - touch1Now) / Math.abs(touch0Start - touch1Start); } clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start; selectionWH = chart['plot' + (horiz ? 'Width' : 'Height')] / scale; }; var selectionWH, selectionXY, clipXY, scale = forcedScale || 1, touch0Now = touches[0][sChartXY], touch1Now = !singleTouch && touches[1][sChartXY], outOfBounds; // Set the scale, first pass setScale(); // The clip position (x or y) is altered if out of bounds, the selection // position is not selectionXY = clipXY; // Out of bounds if (selectionXY < bounds.min) { selectionXY = bounds.min; outOfBounds = true; } else if (selectionXY + selectionWH > bounds.max) { selectionXY = bounds.max - selectionWH; outOfBounds = true; } // Is the chart dragged off its bounds, determined by dataMin and // dataMax? if (outOfBounds) { // Modify the touchNow position in order to create an elastic drag // movement. This indicates to the user that the chart is responsive // but can't be dragged further. touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]); if (typeof touch1Now === 'number') { touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]); } // Set the scale, second pass to adapt to the modified touchNow // positions setScale(); } else { lastValidTouch[xy] = [touch0Now, touch1Now]; } // Set geometry for clipping, selection and transformation if (!inverted) { clip[xy] = clipXY - plotLeftTop; clip[wh] = selectionWH; } var scaleKey = inverted ? (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY; var transformScale = inverted ? 1 / scale : scale; selectionMarker[wh] = selectionWH; selectionMarker[xy] = selectionXY; transform[scaleKey] = scale; transform['translate' + XY] = (transformScale * plotLeftTop) + (touch0Now - (transformScale * touch0Start)); }; /** * Reset the tracking by hiding the tooltip, the hover series state and the * hover point * * @function Highcharts.Pointer#reset * * @param {boolean} [allowMove] * Instead of destroying the tooltip altogether, allow moving it if * possible. * * @param {number} [delay] */ Pointer.prototype.reset = function (allowMove, delay) { var pointer = this, chart = pointer.chart, hoverSeries = chart.hoverSeries, hoverPoint = chart.hoverPoint, hoverPoints = chart.hoverPoints, tooltip = chart.tooltip, tooltipPoints = tooltip && tooltip.shared ? hoverPoints : hoverPoint; // Check if the points have moved outside the plot area (#1003, #4736, // #5101) if (allowMove && tooltipPoints) { splat(tooltipPoints).forEach(function (point) { if (point.series.isCartesian && typeof point.plotX === 'undefined') { allowMove = false; } }); } // Just move the tooltip, #349 if (allowMove) { if (tooltip && tooltipPoints && splat(tooltipPoints).length) { tooltip.refresh(tooltipPoints); if (tooltip.shared && hoverPoints) { // #8284 hoverPoints.forEach(function (point) { point.setState(point.state, true); if (point.series.isCartesian) { if (point.series.xAxis.crosshair) { point.series.xAxis .drawCrosshair(null, point); } if (point.series.yAxis.crosshair) { point.series.yAxis .drawCrosshair(null, point); } } }); } else if (hoverPoint) { // #2500 hoverPoint.setState(hoverPoint.state, true); chart.axes.forEach(function (axis) { if (axis.crosshair && hoverPoint.series[axis.coll] === axis) { axis.drawCrosshair(null, hoverPoint); } }); } } // Full reset } else { if (hoverPoint) { hoverPoint.onMouseOut(); } if (hoverPoints) { hoverPoints.forEach(function (point) { point.setState(); }); } if (hoverSeries) { hoverSeries.onMouseOut(); } if (tooltip) { tooltip.hide(delay); } if (pointer.unDocMouseMove) { pointer.unDocMouseMove = pointer.unDocMouseMove(); } // Remove crosshairs chart.axes.forEach(function (axis) { axis.hideCrosshair(); }); pointer.hoverX = chart.hoverPoints = chart.hoverPoint = null; } }; /** * With line type charts with a single tracker, get the point closest to the * mouse. Run Point.onMouseOver and display tooltip for the point or points. * * @private * @function Highcharts.Pointer#runPointActions * * @fires Highcharts.Point#event:mouseOut * @fires Highcharts.Point#event:mouseOver */ Pointer.prototype.runPointActions = function (e, p) { var pointer = this, chart = pointer.chart, series = chart.series, tooltip = (chart.tooltip && chart.tooltip.options.enabled ? chart.tooltip : void 0), shared = (tooltip ? tooltip.shared : false); var hoverPoint = p || chart.hoverPoint, hoverSeries = hoverPoint && hoverPoint.series || chart.hoverSeries; var // onMouseOver or already hovering a series with directTouch isDirectTouch = (!e || e.type !== 'touchmove') && (!!p || ((hoverSeries && hoverSeries.directTouch) && pointer.isDirectTouch)), hoverData = this.getHoverData(hoverPoint, hoverSeries, series, isDirectTouch, shared, e); // Update variables from hoverData. hoverPoint = hoverData.hoverPoint; hoverSeries = hoverData.hoverSeries; var points = hoverData.hoverPoints, followPointer = hoverSeries && hoverSeries.tooltipOptions.followPointer && !hoverSeries.tooltipOptions.split, useSharedTooltip = (shared && hoverSeries && !hoverSeries.noSharedTooltip); // Refresh tooltip for kdpoint if new hover point or tooltip was hidden // #3926, #4200 if (hoverPoint && // !(hoverSeries && hoverSeries.directTouch) && (hoverPoint !== chart.hoverPoint || (tooltip && tooltip.isHidden))) { (chart.hoverPoints || []).forEach(function (p) { if (points.indexOf(p) === -1) { p.setState(); } }); // Set normal state to previous series if (chart.hoverSeries !== hoverSeries) { hoverSeries.onMouseOver(); } pointer.applyInactiveState(points); // Do mouseover on all points (#3919, #3985, #4410, #5622) (points || []).forEach(function (p) { p.setState('hover'); }); // If tracking is on series in stead of on each point, // fire mouseOver on hover point. // #4448 if (chart.hoverPoint) { chart.hoverPoint.firePointEvent('mouseOut'); } // Hover point may have been destroyed in the event handlers (#7127) if (!hoverPoint.series) { return; } /** * Contains all hovered points. * * @name Highcharts.Chart#hoverPoints * @type {Array<Highcharts.Point>|null} */ chart.hoverPoints = points; /** * Contains the original hovered point. * * @name Highcharts.Chart#hoverPoint * @type {Highcharts.Point|null} */ chart.hoverPoint = hoverPoint; /** * Hover state should not be lost when axis is updated (#12569) * Axis.update runs pointer.reset which uses chart.hoverPoint.state * to apply state which does not exist in hoverPoint yet. * The mouseOver event should be triggered when hoverPoint * is correct. */ hoverPoint.firePointEvent('mouseOver'); // Draw tooltip if necessary if (tooltip) { tooltip.refresh(useSharedTooltip ? points : hoverPoint, e); } // Update positions (regardless of kdpoint or hoverPoint) } else if (followPointer && tooltip && !tooltip.isHidden) { var anchor = tooltip.getAnchor([{}], e); if (chart.isInsidePlot(anchor[0], anchor[1], { visiblePlotOnly: true })) { tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] }); } } // Start the event listener to pick up the tooltip and crosshairs if (!pointer.unDocMouseMove) { pointer.unDocMouseMove = addEvent(chart.container.ownerDocument, 'mousemove', function (e) { var chart = charts[Pointer.hoverChartIndex]; if (chart) { chart.pointer.onDocumentMouseMove(e); } }); pointer.eventsToUnbind.push(pointer.unDocMouseMove); } // Issues related to crosshair #4927, #5269 #5066, #5658 chart.axes.forEach(function drawAxisCrosshair(axis) { var snap = pick((axis.crosshair || {}).snap, true); var point; if (snap) { point = chart.hoverPoint; // #13002 if (!point || point.series[axis.coll] !== axis) { point = find(points, function (p) { return p.series[axis.coll] === axis; }); } } // Axis has snapping crosshairs, and one of the hover points belongs // to axis. Always call drawCrosshair when it is not snap. if (point || !snap) { axis.drawCrosshair(e, point); // Axis has snapping crosshairs, but no hover point belongs to axis } else { axis.hideCrosshair(); } }); }; /** * Scale series groups to a certain scale and translation. * @private * @function Highcharts.Pointer#scaleGroups */ Pointer.prototype.scaleGroups = function (attribs, clip) { var chart = this.chart; // Scale each series chart.series.forEach(function (series) { var seriesAttribs = attribs || series.getPlotBox(); // #1701 if (series.xAxis && series.xAxis.zoomEnabled && series.group) { series.group.attr(seriesAttribs); if (series.markerGroup) { series.markerGroup.attr(seriesAttribs); series.markerGroup.clip(clip ? chart.clipRect : null); } if (series.dataLabelsGroup) { series.dataLabelsGroup.attr(seriesAttribs); } } }); // Clip chart.clipRect.attr(clip || chart.clipBox); }; /** * Set the JS DOM events on the container and document. This method should * contain a one-to-one assignment between methods and their handlers. Any * advanced logic should be moved to the handler reflecting the event's * name. * @private * @function Highcharts.Pointer#setDOMEvents */ Pointer.prototype.setDOMEvents = function () { var _this = this; var container = this.chart.container, ownerDoc = container.ownerDocument; container.onmousedown = this.onContainerMouseDown.bind(this); container.onmousemove = this.onContainerMouseMove.bind(this); container.onclick = this.onContainerClick.bind(this); this.eventsToUnbind.push(addEvent(container, 'mouseenter', this.onContainerMouseEnter.bind(this))); this.eventsToUnbind.push(addEvent(container, 'mouseleave', this.onContainerMouseLeave.bind(this))); if (!Pointer.unbindDocumentMouseUp) { Pointer.unbindDocumentMouseUp = addEvent(ownerDoc, 'mouseup', this.onDocumentMouseUp.bind(this)); } // In case we are dealing with overflow, reset the chart position when // scrolling parent elements var parent = this.chart.renderTo.parentElement; while (parent && parent.tagName !== 'BODY') { this.eventsToUnbind.push(addEvent(parent, 'scroll', function () { delete _this.chartPosition; })); parent = parent.parentElement; } if (H.hasTouch) { this.eventsToUnbind.push(addEvent(container, 'touchstart', this.onContainerTouchStart.bind(this), { passive: false })); this.eventsToUnbind.push(addEvent(container, 'touchmove', this.onContainerTouchMove.bind(this), { passive: false })); if (!Pointer.unbindDocumentTouchEnd) { Pointer.unbindDocumentTouchEnd = addEvent(ownerDoc, 'touchend', this.onDocumentTouchEnd.bind(this), { passive: false }); } } }; /** * Sets the index of the hovered chart and leaves the previous hovered * chart, to reset states like tooltip. * @private * @function Highcharts.Pointer#setHoverChartIndex */ Pointer.prototype.setHoverChartIndex = function () { var chart = this.chart; var hoverChart = H.charts[pick(Pointer.hoverChartIndex, -1)]; if (hoverChart && hoverChart !== chart) { hoverChart.pointer.onContainerMouseLeave({ relatedTarget: true }); } if (!hoverChart || !hoverChart.mouseIsDown) { Pointer.hoverChartIndex = chart.index; } }; /** * General touch handler shared by touchstart and touchmove. * @private * @function Highcharts.Pointer#touch */ Pointer.prototype.touch = function (e, start) { var chart = this.chart; var hasMoved, pinchDown, isInside; this.setHoverChartIndex(); if (e.touches.length === 1) { e = this.normalize(e); isInside = chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop, { visiblePlotOnly: true }); if (isInside && !chart.openMenu) { // Run mouse events and display tooltip etc if (start) { this.runPointActions(e); } // Android fires touchmove events after the touchstart even if // the finger hasn't moved, or moved only a pixel or two. In iOS // however, the touchmove doesn't fire unless the finger moves // more than ~4px. So we emulate this behaviour in Android by // checking how much it moved, and cancelling on small // distances. #3450. if (e.type === 'touchmove') { pinchDown = this.pinchDown; hasMoved = pinchDown[0] ? Math.sqrt(// #5266 Math.pow(pinchDown[0].chartX - e.chartX, 2) + Math.pow(pinchDown[0].chartY - e.chartY, 2)) >= 4 : false; } if (pick(hasMoved, true)) { this.pinch(e); } } else if (start) { // Hide the tooltip on touching outside the plot area (#1203) this.reset(); } } else if (e.touches.length === 2) { this.pinch(e); } }; /** * Returns true if the chart is set up for zooming by single touch and the * event is capable * @private * @function Highcharts.Pointer#touchSelect */ Pointer.prototype.touchSelect = function (e) { return Boolean(this.chart.options.chart.zoomBySingleTouch && e.touches && e.touches.length === 1); }; /** * Resolve the zoomType option, this is reset on all touch start and mouse * down events. * @private * @function Highcharts.Pointer#zoomOption */ Pointer.prototype.zoomOption = function (e) { var chart = this.chart, options = chart.options.chart, inverted = chart.inverted; var zoomType = options.zoomType || '', zoomX, zoomY; // Look for the pinchType option if (/touch/.test(e.type)) { zoomType = pick(options.pinchType, zoomType); } this.zoomX = zoomX = /x/.test(zoomType); this.zoomY = zoomY = /y/.test(zoomType); this.zoomHor = (zoomX && !inverted) || (zoomY && inverted); this.zoomVert = (zoomY && !inverted) || (zoomX && inverted); this.hasZoom = zoomX || zoomY; }; return Pointer; }()); /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * One position in relation to an axis. * * @interface Highcharts.PointerAxisCoordinateObject */ /** * Related axis. * * @name Highcharts.PointerAxisCoordinateObject#axis * @type {Highcharts.Axis} */ /** * Axis value. * * @name Highcharts.PointerAxisCoordinateObject#value * @type {number} */ /** * Positions in terms of axis values. * * @interface Highcharts.PointerAxisCoordinatesObject */ /** * Positions on the x-axis. * @name Highcharts.PointerAxisCoordinatesObject#xAxis * @type {Array<Highcharts.PointerAxisCoordinateObject>} */ /** * Positions on the y-axis. * @name Highcharts.PointerAxisCoordinatesObject#yAxis * @type {Array<Highcharts.PointerAxisCoordinateObject>} */ /** * Pointer coordinates. * * @interface Highcharts.PointerCoordinatesObject */ /** * @name Highcharts.PointerCoordinatesObject#chartX * @type {number} */ /** * @name Highcharts.PointerCoordinatesObject#chartY * @type {number} */ /** * A native browser mouse or touch event, extended with position information * relative to the {@link Chart.container}. * * @interface Highcharts.PointerEventObject * @extends global.PointerEvent */ /** * The X coordinate of the pointer interaction relative to the chart. * * @name Highcharts.PointerEventObject#chartX * @type {number} */ /** * The Y coordinate of the pointer interaction relative to the chart. * * @name Highcharts.PointerEventObject#chartY * @type {number} */ /** * Axis-specific data of a selection. * * @interface Highcharts.SelectDataObject */ /** * @name Highcharts.SelectDataObject#axis * @type {Highcharts.Axis} */ /** * @name Highcharts.SelectDataObject#max * @type {number} */ /** * @name Highcharts.SelectDataObject#min * @type {number} */ /** * Object for select events. * * @interface Highcharts.SelectEventObject */ /** * @name Highcharts.SelectEventObject#originalEvent * @type {global.Event} */ /** * @name Highcharts.SelectEventObject#xAxis * @type {Array<Highcharts.SelectDataObject>} */ /** * @name Highcharts.SelectEventObject#yAxis * @type {Array<Highcharts.SelectDataObject>} */ /** * Chart position and scale. * * @interface Highcharts.ChartPositionObject */ /** * @name Highcharts.ChartPositionObject#left * @type {number} */ /** * @name Highcharts.ChartPositionObject#scaleX * @type {number} */ /** * @name Highcharts.ChartPositionObject#scaleY * @type {number} */ /** * @name Highcharts.ChartPositionObject#top * @type {number} */ ''; // keeps doclets above in JS file return Pointer; }); _registerModule(_modules, 'Core/MSPointer.js', [_modules['Core/Globals.js'], _modules['Core/Pointer.js'], _modules['Core/Utilities.js']], function (H, Pointer, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var charts = H.charts, doc = H.doc, noop = H.noop, win = H.win; var addEvent = U.addEvent, css = U.css, objectEach = U.objectEach, removeEvent = U.removeEvent; /* * * * Constants * * */ // The touches object keeps track of the points being touched at all times var touches = {}; var hasPointerEvent = !!win.PointerEvent; /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** @private */ function getWebkitTouches() { var fake = []; fake.item = function (i) { return this[i]; }; objectEach(touches, function (touch) { fake.push({ pageX: touch.pageX, pageY: touch.pageY, target: touch.target }); }); return fake; } /** @private */ function translateMSPointer(e, method, wktype, func) { var chart = charts[Pointer.hoverChartIndex || NaN]; if ((e.pointerType === 'touch' || e.pointerType === e.MSPOINTER_TYPE_TOUCH) && chart) { var p = chart.pointer; func(e); p[method]({ type: wktype, target: e.currentTarget, preventDefault: noop, touches: getWebkitTouches() }); } } /* * * * Class * * */ /** @private */ var MSPointer = /** @class */ (function (_super) { __extends(MSPointer, _super); function MSPointer() { return _super !== null && _super.apply(this, arguments) || this; } /* * * * Static Functions * * */ MSPointer.isRequired = function () { return !!(!H.hasTouch && (win.PointerEvent || win.MSPointerEvent)); }; /* * * * Functions * * */ /** * Add or remove the MS Pointer specific events * @private * @function Highcharts.Pointer#batchMSEvents */ MSPointer.prototype.batchMSEvents = function (fn) { fn(this.chart.container, hasPointerEvent ? 'pointerdown' : 'MSPointerDown', this.onContainerPointerDown); fn(this.chart.container, hasPointerEvent ? 'pointermove' : 'MSPointerMove', this.onContainerPointerMove); fn(doc, hasPointerEvent ? 'pointerup' : 'MSPointerUp', this.onDocumentPointerUp); }; // Destroy MS events also MSPointer.prototype.destroy = function () { this.batchMSEvents(removeEvent); _super.prototype.destroy.call(this); }; // Disable default IE actions for pinch and such on chart element MSPointer.prototype.init = function (chart, options) { _super.prototype.init.call(this, chart, options); if (this.hasZoom) { // #4014 css(chart.container, { '-ms-touch-action': 'none', 'touch-action': 'none' }); } }; /** * @private * @function Highcharts.Pointer#onContainerPointerDown */ MSPointer.prototype.onContainerPointerDown = function (e) { translateMSPointer(e, 'onContainerTouchStart', 'touchstart', function (e) { touches[e.pointerId] = { pageX: e.pageX, pageY: e.pageY, target: e.currentTarget }; }); }; /** * @private * @function Highcharts.Pointer#onContainerPointerMove */ MSPointer.prototype.onContainerPointerMove = function (e) { translateMSPointer(e, 'onContainerTouchMove', 'touchmove', function (e) { touches[e.pointerId] = ({ pageX: e.pageX, pageY: e.pageY }); if (!touches[e.pointerId].target) { touches[e.pointerId].target = e.currentTarget; } }); }; /** * @private * @function Highcharts.Pointer#onDocumentPointerUp */ MSPointer.prototype.onDocumentPointerUp = function (e) { translateMSPointer(e, 'onDocumentTouchEnd', 'touchend', function (e) { delete touches[e.pointerId]; }); }; // Add IE specific touch events to chart MSPointer.prototype.setDOMEvents = function () { _super.prototype.setDOMEvents.call(this); if (this.hasZoom || this.followTouchMove) { this.batchMSEvents(addEvent); } }; return MSPointer; }(Pointer)); /* * * * Default Export * * */ return MSPointer; }); _registerModule(_modules, 'Core/Series/Point.js', [_modules['Core/Renderer/HTML/AST.js'], _modules['Core/Animation/AnimationUtilities.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Globals.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Utilities.js']], function (AST, A, F, H, D, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var animObject = A.animObject; var format = F.format; var defaultOptions = D.defaultOptions; var addEvent = U.addEvent, defined = U.defined, erase = U.erase, extend = U.extend, fireEvent = U.fireEvent, getNestedProperty = U.getNestedProperty, isArray = U.isArray, isFunction = U.isFunction, isNumber = U.isNumber, isObject = U.isObject, merge = U.merge, objectEach = U.objectEach, pick = U.pick, syncTimeout = U.syncTimeout, removeEvent = U.removeEvent, uniqueKey = U.uniqueKey; /** * Function callback when a series point is clicked. Return false to cancel the * action. * * @callback Highcharts.PointClickCallbackFunction * * @param {Highcharts.Point} this * The point where the event occured. * * @param {Highcharts.PointClickEventObject} event * Event arguments. */ /** * Common information for a click event on a series point. * * @interface Highcharts.PointClickEventObject * @extends Highcharts.PointerEventObject */ /** * Clicked point. * @name Highcharts.PointClickEventObject#point * @type {Highcharts.Point} */ /** * Configuration hash for the data label and tooltip formatters. * * @interface Highcharts.PointLabelObject */ /** * The point's current color. * @name Highcharts.PointLabelObject#color * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} */ /** * The point's current color index, used in styled mode instead of `color`. The * color index is inserted in class names used for styling. * @name Highcharts.PointLabelObject#colorIndex * @type {number} */ /** * The name of the related point. * @name Highcharts.PointLabelObject#key * @type {string|undefined} */ /** * The percentage for related points in a stacked series or pies. * @name Highcharts.PointLabelObject#percentage * @type {number} */ /** * The related point. The point name, if defined, is available through * `this.point.name`. * @name Highcharts.PointLabelObject#point * @type {Highcharts.Point} */ /** * The related series. The series name is available through `this.series.name`. * @name Highcharts.PointLabelObject#series * @type {Highcharts.Series} */ /** * The total of values in either a stack for stacked series, or a pie in a pie * series. * @name Highcharts.PointLabelObject#total * @type {number|undefined} */ /** * For categorized axes this property holds the category name for the point. For * other axes it holds the X value. * @name Highcharts.PointLabelObject#x * @type {number|string|undefined} */ /** * The y value of the point. * @name Highcharts.PointLabelObject#y * @type {number|undefined} */ /** * Gets fired when the mouse leaves the area close to the point. * * @callback Highcharts.PointMouseOutCallbackFunction * * @param {Highcharts.Point} this * Point where the event occured. * * @param {global.PointerEvent} event * Event that occured. */ /** * Gets fired when the mouse enters the area close to the point. * * @callback Highcharts.PointMouseOverCallbackFunction * * @param {Highcharts.Point} this * Point where the event occured. * * @param {global.Event} event * Event that occured. */ /** * The generic point options for all series. * * In TypeScript you have to extend `PointOptionsObject` with an additional * declaration to allow custom data options: * * ``` * declare interface PointOptionsObject { * customProperty: string; * } * ``` * * @interface Highcharts.PointOptionsObject */ /** * Possible option types for a data point. Use `null` to indicate a gap. * * @typedef {number|string|Highcharts.PointOptionsObject|Array<(number|string|null)>|null} Highcharts.PointOptionsType */ /** * Gets fired when the point is removed using the `.remove()` method. * * @callback Highcharts.PointRemoveCallbackFunction * * @param {Highcharts.Point} this * Point where the event occured. * * @param {global.Event} event * Event that occured. */ /** * Possible key values for the point state options. * * @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.PointStateValue */ /** * Gets fired when the point is updated programmatically through the `.update()` * method. * * @callback Highcharts.PointUpdateCallbackFunction * * @param {Highcharts.Point} this * Point where the event occured. * * @param {Highcharts.PointUpdateEventObject} event * Event that occured. */ /** * Information about the update event. * * @interface Highcharts.PointUpdateEventObject * @extends global.Event */ /** * Options data of the update event. * @name Highcharts.PointUpdateEventObject#options * @type {Highcharts.PointOptionsType} */ /** * @interface Highcharts.PointEventsOptionsObject */ /** * Fires when the point is selected either programmatically or following a click * on the point. One parameter, `event`, is passed to the function. Returning * `false` cancels the operation. * @name Highcharts.PointEventsOptionsObject#select * @type {Highcharts.PointSelectCallbackFunction|undefined} */ /** * Fires when the point is unselected either programmatically or following a * click on the point. One parameter, `event`, is passed to the function. * Returning `false` cancels the operation. * @name Highcharts.PointEventsOptionsObject#unselect * @type {Highcharts.PointUnselectCallbackFunction|undefined} */ /** * Information about the select/unselect event. * * @interface Highcharts.PointInteractionEventObject * @extends global.Event */ /** * @name Highcharts.PointInteractionEventObject#accumulate * @type {boolean} */ /** * Gets fired when the point is selected either programmatically or following a * click on the point. * * @callback Highcharts.PointSelectCallbackFunction * * @param {Highcharts.Point} this * Point where the event occured. * * @param {Highcharts.PointInteractionEventObject} event * Event that occured. */ /** * Fires when the point is unselected either programmatically or following a * click on the point. * * @callback Highcharts.PointUnselectCallbackFunction * * @param {Highcharts.Point} this * Point where the event occured. * * @param {Highcharts.PointInteractionEventObject} event * Event that occured. */ ''; // detach doclet above /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The Point object. The point objects are generated from the `series.data` * configuration objects or raw numbers. They can be accessed from the * `Series.points` array. Other ways to instantiate points are through {@link * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}. * * @class * @name Highcharts.Point */ var Point = /** @class */ (function () { function Point() { /* * * * Properties * * */ /** * For categorized axes this property holds the category name for the * point. For other axes it holds the X value. * * @name Highcharts.Point#category * @type {string} */ this.category = void 0; /** * The point's current color index, used in styled mode instead of * `color`. The color index is inserted in class names used for styling. * * @name Highcharts.Point#colorIndex * @type {number} */ this.colorIndex = void 0; this.formatPrefix = 'point'; this.id = void 0; this.isNull = false; /** * The name of the point. The name can be given as the first position of the * point configuration array, or as a `name` property in the configuration: * * @example * // Array config * data: [ * ['John', 1], * ['Jane', 2] * ] * * // Object config * data: [{ * name: 'John', * y: 1 * }, { * name: 'Jane', * y: 2 * }] * * @name Highcharts.Point#name * @type {string} */ this.name = void 0; /** * The point's options as applied in the initial configuration, or * extended through `Point.update`. * * In TypeScript you have to extend `PointOptionsObject` via an * additional interface to allow custom data options: * * ``` * declare interface PointOptionsObject { * customProperty: string; * } * ``` * * @name Highcharts.Point#options * @type {Highcharts.PointOptionsObject} */ this.options = void 0; /** * The percentage for points in a stacked series or pies. * * @name Highcharts.Point#percentage * @type {number|undefined} */ this.percentage = void 0; this.selected = false; /** * The series object associated with the point. * * @name Highcharts.Point#series * @type {Highcharts.Series} */ this.series = void 0; /** * The total of values in either a stack for stacked series, or a pie in a * pie series. * * @name Highcharts.Point#total * @type {number|undefined} */ this.total = void 0; /** * For certain series types, like pie charts, where individual points can * be shown or hidden. * * @name Highcharts.Point#visible * @type {boolean} * @default true */ this.visible = true; this.x = void 0; } /* * * * Functions * * */ /** * Animate SVG elements associated with the point. * * @private * @function Highcharts.Point#animateBeforeDestroy */ Point.prototype.animateBeforeDestroy = function () { var point = this, animateParams = { x: point.startXPos, opacity: 0 }, isDataLabel, graphicalProps = point.getGraphicalProps(); graphicalProps.singular.forEach(function (prop) { isDataLabel = prop === 'dataLabel'; point[prop] = point[prop].animate(isDataLabel ? { x: point[prop].startXPos, y: point[prop].startYPos, opacity: 0 } : animateParams); }); graphicalProps.plural.forEach(function (plural) { point[plural].forEach(function (item) { if (item.element) { item.animate(extend({ x: point.startXPos }, (item.startYPos ? { x: item.startXPos, y: item.startYPos } : {}))); } }); }); }; /** * Apply the options containing the x and y data and possible some extra * properties. Called on point init or from point.update. * * @private * @function Highcharts.Point#applyOptions * * @param {Highcharts.PointOptionsType} options * The point options as defined in series.data. * * @param {number} [x] * Optionally, the x value. * * @return {Highcharts.Point} * The Point instance. */ Point.prototype.applyOptions = function (options, x) { var point = this, series = point.series, pointValKey = series.options.pointValKey || series.pointValKey; options = Point.prototype.optionsToObject.call(this, options); // copy options directly to point extend(point, options); point.options = point.options ? extend(point.options, options) : options; // Since options are copied into the Point instance, some accidental // options must be shielded (#5681) if (options.group) { delete point.group; } if (options.dataLabels) { delete point.dataLabels; } /** * The y value of the point. * @name Highcharts.Point#y * @type {number|undefined} */ // For higher dimension series types. For instance, for ranges, point.y // is mapped to point.low. if (pointValKey) { point.y = Point.prototype.getNestedProperty.call(point, pointValKey); } point.isNull = pick(point.isValid && !point.isValid(), point.x === null || !isNumber(point.y)); // #3571, check for NaN point.formatPrefix = point.isNull ? 'null' : 'point'; // #9233, #10874 // The point is initially selected by options (#5777) if (point.selected) { point.state = 'select'; } /** * The x value of the point. * @name Highcharts.Point#x * @type {number} */ // If no x is set by now, get auto incremented value. All points must // have an x value, however the y value can be null to create a gap in // the series if ('name' in point && typeof x === 'undefined' && series.xAxis && series.xAxis.hasNames) { point.x = series.xAxis.nameToX(point); } if (typeof point.x === 'undefined' && series) { if (typeof x === 'undefined') { point.x = series.autoIncrement(point); } else { point.x = x; } } return point; }; /** * Destroy a point to clear memory. Its reference still stays in * `series.data`. * * @private * @function Highcharts.Point#destroy */ Point.prototype.destroy = function () { var point = this, series = point.series, chart = series.chart, dataSorting = series.options.dataSorting, hoverPoints = chart.hoverPoints, globalAnimation = point.series.chart.renderer.globalAnimation, animation = animObject(globalAnimation), prop; /** * Allow to call after animation. * @private */ function destroyPoint() { // Remove all events and elements if (point.graphic || point.dataLabel || point.dataLabels) { removeEvent(point); point.destroyElements(); } for (prop in point) { // eslint-disable-line guard-for-in point[prop] = null; } } if (point.legendItem) { // pies have legend items chart.legend.destroyItem(point); } if (hoverPoints) { point.setState(); erase(hoverPoints, point); if (!hoverPoints.length) { chart.hoverPoints = null; } } if (point === chart.hoverPoint) { point.onMouseOut(); } // Remove properties after animation if (!dataSorting || !dataSorting.enabled) { destroyPoint(); } else { this.animateBeforeDestroy(); syncTimeout(destroyPoint, animation.duration); } chart.pointCount--; }; /** * Destroy SVG elements associated with the point. * * @private * @function Highcharts.Point#destroyElements * @param {Highcharts.Dictionary<number>} [kinds] */ Point.prototype.destroyElements = function (kinds) { var point = this, props = point.getGraphicalProps(kinds); props.singular.forEach(function (prop) { point[prop] = point[prop].destroy(); }); props.plural.forEach(function (plural) { point[plural].forEach(function (item) { if (item.element) { item.destroy(); } }); delete point[plural]; }); }; /** * Fire an event on the Point object. * * @private * @function Highcharts.Point#firePointEvent * * @param {string} eventType * Type of the event. * * @param {Highcharts.Dictionary<any>|Event} [eventArgs] * Additional event arguments. * * @param {Highcharts.EventCallbackFunction<Highcharts.Point>|Function} [defaultFunction] * Default event handler. * * @fires Highcharts.Point#event:* */ Point.prototype.firePointEvent = function (eventType, eventArgs, defaultFunction) { var point = this, series = this.series, seriesOptions = series.options; // load event handlers on demand to save time on mouseover/out if (seriesOptions.point.events[eventType] || (point.options && point.options.events && point.options.events[eventType])) { point.importEvents(); } // add default handler if in selection mode if (eventType === 'click' && seriesOptions.allowPointSelect) { defaultFunction = function (event) { // Control key is for Windows, meta (= Cmd key) for Mac, Shift // for Opera. if (point.select) { // #2911 point.select(null, event.ctrlKey || event.metaKey || event.shiftKey); } }; } fireEvent(point, eventType, eventArgs, defaultFunction); }; /** * Get the CSS class names for individual points. Used internally where the * returned value is set on every point. * * @function Highcharts.Point#getClassName * * @return {string} * The class names. */ Point.prototype.getClassName = function () { var point = this; return 'highcharts-point' + (point.selected ? ' highcharts-point-select' : '') + (point.negative ? ' highcharts-negative' : '') + (point.isNull ? ' highcharts-null-point' : '') + (typeof point.colorIndex !== 'undefined' ? ' highcharts-color-' + point.colorIndex : '') + (point.options.className ? ' ' + point.options.className : '') + (point.zone && point.zone.className ? ' ' + point.zone.className.replace('highcharts-negative', '') : ''); }; /** * Get props of all existing graphical point elements. * * @private * @function Highcharts.Point#getGraphicalProps * @param {Highcharts.Dictionary<number>} [kinds] * @return {Highcharts.PointGraphicalProps} */ Point.prototype.getGraphicalProps = function (kinds) { var point = this, props = [], prop, i, graphicalProps = { singular: [], plural: [] }; kinds = kinds || { graphic: 1, dataLabel: 1 }; if (kinds.graphic) { props.push('graphic', 'upperGraphic', 'shadowGroup'); } if (kinds.dataLabel) { props.push('dataLabel', 'dataLabelUpper', 'connector'); } i = props.length; while (i--) { prop = props[i]; if (point[prop]) { graphicalProps.singular.push(prop); } } ['dataLabel', 'connector'].forEach(function (prop) { var plural = prop + 's'; if (kinds[prop] && point[plural]) { graphicalProps.plural.push(plural); } }); return graphicalProps; }; /** * Return the configuration hash needed for the data label and tooltip * formatters. * * @function Highcharts.Point#getLabelConfig * * @return {Highcharts.PointLabelObject} * Abstract object used in formatters and formats. */ Point.prototype.getLabelConfig = function () { return { x: this.category, y: this.y, color: this.color, colorIndex: this.colorIndex, key: this.name || this.category, series: this.series, point: this, percentage: this.percentage, total: this.total || this.stackTotal }; }; /** * Returns the value of the point property for a given value. * @private */ Point.prototype.getNestedProperty = function (key) { if (!key) { return; } if (key.indexOf('custom.') === 0) { return getNestedProperty(key, this.options); } return this[key]; }; /** * In a series with `zones`, return the zone that the point belongs to. * * @function Highcharts.Point#getZone * * @return {Highcharts.SeriesZonesOptionsObject} * The zone item. */ Point.prototype.getZone = function () { var series = this.series, zones = series.zones, zoneAxis = series.zoneAxis || 'y', i = 0, zone; zone = zones[i]; while (this[zoneAxis] >= zone.value) { zone = zones[++i]; } // For resetting or reusing the point (#8100) if (!this.nonZonedColor) { this.nonZonedColor = this.color; } if (zone && zone.color && !this.options.color) { this.color = zone.color; } else { this.color = this.nonZonedColor; } return zone; }; /** * Utility to check if point has new shape type. Used in column series and * all others that are based on column series. * * @return boolean|undefined */ Point.prototype.hasNewShapeType = function () { var point = this; var oldShapeType = point.graphic && (point.graphic.symbolName || point.graphic.element.nodeName); return oldShapeType !== this.shapeType; }; /** * Initialize the point. Called internally based on the `series.data` * option. * * @function Highcharts.Point#init * * @param {Highcharts.Series} series * The series object containing this point. * * @param {Highcharts.PointOptionsType} options * The data in either number, array or object format. * * @param {number} [x] * Optionally, the X value of the point. * * @return {Highcharts.Point} * The Point instance. * * @fires Highcharts.Point#event:afterInit */ Point.prototype.init = function (series, options, x) { this.series = series; this.applyOptions(options, x); // Add a unique ID to the point if none is assigned this.id = defined(this.id) ? this.id : uniqueKey(); this.resolveColor(); series.chart.pointCount++; fireEvent(this, 'afterInit'); return this; }; /** * Transform number or array configs into objects. Also called for object * configs. Used internally to unify the different configuration formats for * points. For example, a simple number `10` in a line series will be * transformed to `{ y: 10 }`, and an array config like `[1, 10]` in a * scatter series will be transformed to `{ x: 1, y: 10 }`. * * @function Highcharts.Point#optionsToObject * * @param {Highcharts.PointOptionsType} options * The input option. * * @return {Highcharts.Dictionary<*>} * Transformed options. */ Point.prototype.optionsToObject = function (options) { var ret = {}, series = this.series, keys = series.options.keys, pointArrayMap = keys || series.pointArrayMap || ['y'], valueCount = pointArrayMap.length, firstItemType, i = 0, j = 0; if (isNumber(options) || options === null) { ret[pointArrayMap[0]] = options; } else if (isArray(options)) { // with leading x value if (!keys && options.length > valueCount) { firstItemType = typeof options[0]; if (firstItemType === 'string') { ret.name = options[0]; } else if (firstItemType === 'number') { ret.x = options[0]; } i++; } while (j < valueCount) { // Skip undefined positions for keys if (!keys || typeof options[i] !== 'undefined') { if (pointArrayMap[j].indexOf('.') > 0) { // Handle nested keys, e.g. ['color.pattern.image'] // Avoid function call unless necessary. Point.prototype.setNestedProperty(ret, options[i], pointArrayMap[j]); } else { ret[pointArrayMap[j]] = options[i]; } } i++; j++; } } else if (typeof options === 'object') { ret = options; // This is the fastest way to detect if there are individual point // dataLabels that need to be considered in drawDataLabels. These // can only occur in object configs. if (options.dataLabels) { series._hasPointLabels = true; } // Same approach as above for markers if (options.marker) { series._hasPointMarkers = true; } } return ret; }; /** * @private * @function Highcharts.Point#resolveColor * @return {void} */ Point.prototype.resolveColor = function () { var series = this.series, colors, optionsChart = series.chart.options.chart, colorCount = optionsChart.colorCount, styledMode = series.chart.styledMode, colorIndex, color; // remove points nonZonedColor for later recalculation delete this.nonZonedColor; if (series.options.colorByPoint) { if (!styledMode) { colors = series.options.colors || series.chart.options.colors; color = colors[series.colorCounter]; colorCount = colors.length; } colorIndex = series.colorCounter; series.colorCounter++; // loop back to zero if (series.colorCounter === colorCount) { series.colorCounter = 0; } } else { if (!styledMode) { color = series.color; } colorIndex = series.colorIndex; } this.colorIndex = pick(this.options.colorIndex, colorIndex); /** * The point's current color. * * @name Highcharts.Point#color * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject|undefined} */ this.color = pick(this.options.color, color); }; /** * Set a value in an object, on the property defined by key. The key * supports nested properties using dot notation. The function modifies the * input object and does not make a copy. * * @function Highcharts.Point#setNestedProperty<T> * * @param {T} object * The object to set the value on. * * @param {*} value * The value to set. * * @param {string} key * Key to the property to set. * * @return {T} * The modified object. */ Point.prototype.setNestedProperty = function (object, value, key) { var nestedKeys = key.split('.'); nestedKeys.reduce(function (result, key, i, arr) { var isLastKey = arr.length - 1 === i; result[key] = (isLastKey ? value : isObject(result[key], true) ? result[key] : {}); return result[key]; }, object); return object; }; /** * Extendable method for formatting each point's tooltip line. * * @function Highcharts.Point#tooltipFormatter * * @param {string} pointFormat * The point format. * * @return {string} * A string to be concatenated in to the common tooltip text. */ Point.prototype.tooltipFormatter = function (pointFormat) { // Insert options for valueDecimals, valuePrefix, and valueSuffix var series = this.series, seriesTooltipOptions = series.tooltipOptions, valueDecimals = pick(seriesTooltipOptions.valueDecimals, ''), valuePrefix = seriesTooltipOptions.valuePrefix || '', valueSuffix = seriesTooltipOptions.valueSuffix || ''; // Replace default point style with class name if (series.chart.styledMode) { pointFormat = series.chart.tooltip.styledModeFormat(pointFormat); } // Loop over the point array map and replace unformatted values with // sprintf formatting markup (series.pointArrayMap || ['y']).forEach(function (key) { key = '{point.' + key; // without the closing bracket if (valuePrefix || valueSuffix) { pointFormat = pointFormat.replace(RegExp(key + '}', 'g'), valuePrefix + key + '}' + valueSuffix); } pointFormat = pointFormat.replace(RegExp(key + '}', 'g'), key + ':,.' + valueDecimals + 'f}'); }); return format(pointFormat, { point: this, series: this.series }, series.chart); }; /** * Update point with new options (typically x/y data) and optionally redraw * the series. * * @sample highcharts/members/point-update-column/ * Update column value * @sample highcharts/members/point-update-pie/ * Update pie slice * @sample maps/members/point-update/ * Update map area value in Highmaps * * @function Highcharts.Point#update * * @param {Highcharts.PointOptionsType} options * The point options. Point options are handled as described under * the `series.type.data` item for each series type. For example * for a line series, if options is a single number, the point will * be given that number as the marin y value. If it is an array, it * will be interpreted as x and y values respectively. If it is an * object, advanced options are applied. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the point is updated. If doing * more operations on the chart, it is best practice to set * `redraw` to false and call `chart.redraw()` after. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] * Whether to apply animation, and optionally animation * configuration. * * @fires Highcharts.Point#event:update */ Point.prototype.update = function (options, redraw, animation, runEvent) { var point = this, series = point.series, graphic = point.graphic, i, chart = series.chart, seriesOptions = series.options; redraw = pick(redraw, true); /** * @private */ function update() { point.applyOptions(options); // Update visuals, #4146 // Handle dummy graphic elements for a11y, #12718 var hasDummyGraphic = graphic && point.hasDummyGraphic; var shouldDestroyGraphic = point.y === null ? !hasDummyGraphic : hasDummyGraphic; if (graphic && shouldDestroyGraphic) { point.graphic = graphic.destroy(); delete point.hasDummyGraphic; } if (isObject(options, true)) { // Destroy so we can get new elements if (graphic && graphic.element) { // "null" is also a valid symbol if (options && options.marker && typeof options.marker.symbol !== 'undefined') { point.graphic = graphic.destroy(); } } if (options && options.dataLabels && point.dataLabel) { point.dataLabel = point.dataLabel.destroy(); // #2468 } if (point.connector) { point.connector = point.connector.destroy(); // #7243 } } // record changes in the parallel arrays i = point.index; series.updateParallelArrays(point, i); // Record the options to options.data. If the old or the new config // is an object, use point options, otherwise use raw options // (#4701, #4916). seriesOptions.data[i] = (isObject(seriesOptions.data[i], true) || isObject(options, true)) ? point.options : pick(options, seriesOptions.data[i]); // redraw series.isDirty = series.isDirtyData = true; if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320 chart.isDirtyBox = true; } if (seriesOptions.legendType === 'point') { // #1831, #1885 chart.isDirtyLegend = true; } if (redraw) { chart.redraw(animation); } } // Fire the event with a default handler of doing the update if (runEvent === false) { // When called from setData update(); } else { point.firePointEvent('update', { options: options }, update); } }; /** * Remove a point and optionally redraw the series and if necessary the axes * * @sample highcharts/plotoptions/series-point-events-remove/ * Remove point and confirm * @sample highcharts/members/point-remove/ * Remove pie slice * @sample maps/members/point-remove/ * Remove selected points in Highmaps * * @function Highcharts.Point#remove * * @param {boolean} [redraw=true] * Whether to redraw the chart or wait for an explicit call. When * doing more operations on the chart, for example running * `point.remove()` in a loop, it is best practice to set `redraw` * to false and call `chart.redraw()` after. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=false] * Whether to apply animation, and optionally animation * configuration. */ Point.prototype.remove = function (redraw, animation) { this.series.removePoint(this.series.data.indexOf(this), redraw, animation); }; /** * Toggle the selection status of a point. * * @see Highcharts.Chart#getSelectedPoints * * @sample highcharts/members/point-select/ * Select a point from a button * @sample highcharts/chart/events-selection-points/ * Select a range of points through a drag selection * @sample maps/series/data-id/ * Select a point in Highmaps * * @function Highcharts.Point#select * * @param {boolean} [selected] * When `true`, the point is selected. When `false`, the point is * unselected. When `null` or `undefined`, the selection state is toggled. * * @param {boolean} [accumulate=false] * When `true`, the selection is added to other selected points. * When `false`, other selected points are deselected. Internally in * Highcharts, when * [allowPointSelect](https://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect) * is `true`, selected points are accumulated on Control, Shift or Cmd * clicking the point. * * @fires Highcharts.Point#event:select * @fires Highcharts.Point#event:unselect */ Point.prototype.select = function (selected, accumulate) { var point = this, series = point.series, chart = series.chart; selected = pick(selected, !point.selected); this.selectedStaging = selected; // fire the event with the default handler point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () { /** * Whether the point is selected or not. * * @see Point#select * @see Chart#getSelectedPoints * * @name Highcharts.Point#selected * @type {boolean} */ point.selected = point.options.selected = selected; series.options.data[series.data.indexOf(point)] = point.options; point.setState(selected && 'select'); // unselect all other points unless Ctrl or Cmd + click if (!accumulate) { chart.getSelectedPoints().forEach(function (loopPoint) { var loopSeries = loopPoint.series; if (loopPoint.selected && loopPoint !== point) { loopPoint.selected = loopPoint.options.selected = false; loopSeries.options.data[loopSeries.data.indexOf(loopPoint)] = loopPoint.options; // Programatically selecting a point should restore // normal state, but when click happened on other // point, set inactive state to match other points loopPoint.setState(chart.hoverPoints && loopSeries.options.inactiveOtherPoints ? 'inactive' : ''); loopPoint.firePointEvent('unselect'); } }); } }); delete this.selectedStaging; }; /** * Runs on mouse over the point. Called internally from mouse and touch * events. * * @function Highcharts.Point#onMouseOver * * @param {Highcharts.PointerEventObject} [e] * The event arguments. */ Point.prototype.onMouseOver = function (e) { var point = this, series = point.series, chart = series.chart, pointer = chart.pointer; e = e ? pointer.normalize(e) : // In cases where onMouseOver is called directly without an event pointer.getChartCoordinatesFromPoint(point, chart.inverted); pointer.runPointActions(e, point); }; /** * Runs on mouse out from the point. Called internally from mouse and touch * events. * * @function Highcharts.Point#onMouseOut * @fires Highcharts.Point#event:mouseOut */ Point.prototype.onMouseOut = function () { var point = this, chart = point.series.chart; point.firePointEvent('mouseOut'); if (!point.series.options.inactiveOtherPoints) { (chart.hoverPoints || []).forEach(function (p) { p.setState(); }); } chart.hoverPoints = chart.hoverPoint = null; }; /** * Import events from the series' and point's options. Only do it on * demand, to save processing time on hovering. * * @private * @function Highcharts.Point#importEvents */ Point.prototype.importEvents = function () { if (!this.hasImportedEvents) { var point_1 = this, options = merge(point_1.series.options.point, point_1.options), events = options.events; point_1.events = events; objectEach(events, function (event, eventType) { if (isFunction(event)) { addEvent(point_1, eventType, event); } }); this.hasImportedEvents = true; } }; /** * Set the point's state. * * @function Highcharts.Point#setState * * @param {Highcharts.PointStateValue|""} [state] * The new state, can be one of `'hover'`, `'select'`, `'inactive'`, * or `''` (an empty string), `'normal'` or `undefined` to set to * normal state. * @param {boolean} [move] * State for animation. * * @fires Highcharts.Point#event:afterSetState */ Point.prototype.setState = function (state, move) { var point = this, series = point.series, previousState = point.state, stateOptions = (series.options.states[state || 'normal'] || {}), markerOptions = (defaultOptions.plotOptions[series.type].marker && series.options.marker), normalDisabled = (markerOptions && markerOptions.enabled === false), markerStateOptions = ((markerOptions && markerOptions.states && markerOptions.states[state || 'normal']) || {}), stateDisabled = markerStateOptions.enabled === false, stateMarkerGraphic = series.stateMarkerGraphic, pointMarker = point.marker || {}, chart = series.chart, halo = series.halo, haloOptions, markerAttribs, pointAttribs, pointAttribsAnimation, hasMarkers = (markerOptions && series.markerAttribs), newSymbol; state = state || ''; // empty string if ( // already has this state (state === point.state && !move) || // selected points don't respond to hover (point.selected && state !== 'select') || // series' state options is disabled (stateOptions.enabled === false) || // general point marker's state options is disabled (state && (stateDisabled || (normalDisabled && markerStateOptions.enabled === false))) || // individual point marker's state options is disabled (state && pointMarker.states && pointMarker.states[state] && pointMarker.states[state].enabled === false) // #1610 ) { return; } point.state = state; if (hasMarkers) { markerAttribs = series.markerAttribs(point, state); } // Apply hover styles to the existing point // Prevent from dummy null points (#14966) if (point.graphic && !point.hasDummyGraphic) { if (previousState) { point.graphic.removeClass('highcharts-point-' + previousState); } if (state) { point.graphic.addClass('highcharts-point-' + state); } if (!chart.styledMode) { pointAttribs = series.pointAttribs(point, state); pointAttribsAnimation = pick(chart.options.chart.animation, stateOptions.animation); // Some inactive points (e.g. slices in pie) should apply // oppacity also for it's labels if (series.options.inactiveOtherPoints && isNumber(pointAttribs.opacity)) { (point.dataLabels || []).forEach(function (label) { if (label) { label.animate({ opacity: pointAttribs.opacity }, pointAttribsAnimation); } }); if (point.connector) { point.connector.animate({ opacity: pointAttribs.opacity }, pointAttribsAnimation); } } point.graphic.animate(pointAttribs, pointAttribsAnimation); } if (markerAttribs) { point.graphic.animate(markerAttribs, pick( // Turn off globally: chart.options.chart.animation, markerStateOptions.animation, markerOptions.animation)); } // Zooming in from a range with no markers to a range with markers if (stateMarkerGraphic) { stateMarkerGraphic.hide(); } } else { // if a graphic is not applied to each point in the normal state, // create a shared graphic for the hover state if (state && markerStateOptions) { newSymbol = pointMarker.symbol || series.symbol; // If the point has another symbol than the previous one, throw // away the state marker graphic and force a new one (#1459) if (stateMarkerGraphic && stateMarkerGraphic.currentSymbol !== newSymbol) { stateMarkerGraphic = stateMarkerGraphic.destroy(); } // Add a new state marker graphic if (markerAttribs) { if (!stateMarkerGraphic) { if (newSymbol) { series.stateMarkerGraphic = stateMarkerGraphic = chart.renderer .symbol(newSymbol, markerAttribs.x, markerAttribs.y, markerAttribs.width, markerAttribs.height) .add(series.markerGroup); stateMarkerGraphic.currentSymbol = newSymbol; } // Move the existing graphic } else { stateMarkerGraphic[move ? 'animate' : 'attr']({ x: markerAttribs.x, y: markerAttribs.y }); } } if (!chart.styledMode && stateMarkerGraphic) { stateMarkerGraphic.attr(series.pointAttribs(point, state)); } } if (stateMarkerGraphic) { stateMarkerGraphic[state && point.isInside ? 'show' : 'hide'](); // #2450 stateMarkerGraphic.element.point = point; // #4310 } } // Show me your halo haloOptions = stateOptions.halo; var markerGraphic = (point.graphic || stateMarkerGraphic); var markerVisibility = (markerGraphic && markerGraphic.visibility || 'inherit'); if (haloOptions && haloOptions.size && markerGraphic && markerVisibility !== 'hidden' && !point.isCluster) { if (!halo) { series.halo = halo = chart.renderer.path() // #5818, #5903, #6705 .add(markerGraphic.parentGroup); } halo.show()[move ? 'animate' : 'attr']({ d: point.haloPath(haloOptions.size) }); halo.attr({ 'class': 'highcharts-halo highcharts-color-' + pick(point.colorIndex, series.colorIndex) + (point.className ? ' ' + point.className : ''), 'visibility': markerVisibility, 'zIndex': -1 // #4929, #8276 }); halo.point = point; // #6055 if (!chart.styledMode) { halo.attr(extend({ 'fill': point.color || series.color, 'fill-opacity': haloOptions.opacity }, AST.filterUserAttributes(haloOptions.attributes || {}))); } } else if (halo && halo.point && halo.point.haloPath) { // Animate back to 0 on the current halo point (#6055) halo.animate({ d: halo.point.haloPath(0) }, null, // Hide after unhovering. The `complete` callback runs in the // halo's context (#7681). halo.hide); } fireEvent(point, 'afterSetState', { state: state }); }; /** * Get the path definition for the halo, which is usually a shadow-like * circle around the currently hovered point. * * @function Highcharts.Point#haloPath * * @param {number} size * The radius of the circular halo. * * @return {Highcharts.SVGPathArray} * The path definition. */ Point.prototype.haloPath = function (size) { var series = this.series, chart = series.chart; return chart.renderer.symbols.circle(Math.floor(this.plotX) - size, this.plotY - size, size * 2, size * 2); }; return Point; }()); H.Point = Point; return Point; }); _registerModule(_modules, 'Core/Legend.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Series/Point.js'], _modules['Core/Utilities.js']], function (A, F, H, Point, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var animObject = A.animObject, setAnimation = A.setAnimation; var format = F.format; var isFirefox = H.isFirefox, marginNames = H.marginNames, win = H.win; var addEvent = U.addEvent, createElement = U.createElement, css = U.css, defined = U.defined, discardElement = U.discardElement, find = U.find, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, pick = U.pick, relativeLength = U.relativeLength, stableSort = U.stableSort, syncTimeout = U.syncTimeout, wrap = U.wrap; /** * Gets fired when the legend item belonging to a point is clicked. The default * action is to toggle the visibility of the point. This can be prevented by * returning `false` or calling `event.preventDefault()`. * * @callback Highcharts.PointLegendItemClickCallbackFunction * * @param {Highcharts.Point} this * The point on which the event occured. * * @param {Highcharts.PointLegendItemClickEventObject} event * The event that occured. */ /** * Information about the legend click event. * * @interface Highcharts.PointLegendItemClickEventObject */ /** * Related browser event. * @name Highcharts.PointLegendItemClickEventObject#browserEvent * @type {Highcharts.PointerEvent} */ /** * Prevent the default action of toggle the visibility of the point. * @name Highcharts.PointLegendItemClickEventObject#preventDefault * @type {Function} */ /** * Related point. * @name Highcharts.PointLegendItemClickEventObject#target * @type {Highcharts.Point} */ /** * Event type. * @name Highcharts.PointLegendItemClickEventObject#type * @type {"legendItemClick"} */ /** * Gets fired when the legend item belonging to a series is clicked. The default * action is to toggle the visibility of the series. This can be prevented by * returning `false` or calling `event.preventDefault()`. * * @callback Highcharts.SeriesLegendItemClickCallbackFunction * * @param {Highcharts.Series} this * The series where the event occured. * * @param {Highcharts.SeriesLegendItemClickEventObject} event * The event that occured. */ /** * Information about the legend click event. * * @interface Highcharts.SeriesLegendItemClickEventObject */ /** * Related browser event. * @name Highcharts.SeriesLegendItemClickEventObject#browserEvent * @type {Highcharts.PointerEvent} */ /** * Prevent the default action of toggle the visibility of the series. * @name Highcharts.SeriesLegendItemClickEventObject#preventDefault * @type {Function} */ /** * Related series. * @name Highcharts.SeriesLegendItemClickEventObject#target * @type {Highcharts.Series} */ /** * Event type. * @name Highcharts.SeriesLegendItemClickEventObject#type * @type {"legendItemClick"} */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The overview of the chart's series. The legend object is instanciated * internally in the chart constructor, and is available from the `chart.legend` * property. Each chart has only one legend. * * @class * @name Highcharts.Legend * * @param {Highcharts.Chart} chart * The chart instance. * * @param {Highcharts.LegendOptions} options * Legend options. */ var Legend = /** @class */ (function () { /* * * * Constructors * * */ function Legend(chart, options) { /* * * * Properties * * */ this.allItems = []; this.box = void 0; this.contentGroup = void 0; this.display = false; this.group = void 0; this.initialItemY = 0; this.itemHeight = 0; this.itemMarginBottom = 0; this.itemMarginTop = 0; this.itemX = 0; this.itemY = 0; this.lastItemY = 0; this.lastLineHeight = 0; this.legendHeight = 0; this.legendWidth = 0; this.maxItemWidth = 0; this.maxLegendWidth = 0; this.offsetWidth = 0; this.options = {}; this.padding = 0; this.pages = []; this.proximate = false; this.scrollGroup = void 0; this.symbolHeight = 0; this.symbolWidth = 0; this.titleHeight = 0; this.totalItemWidth = 0; this.widthOption = 0; this.chart = chart; this.init(chart, options); } /* * * * Functions * * */ /** * Initialize the legend. * * @private * @function Highcharts.Legend#init * * @param {Highcharts.Chart} chart * The chart instance. * * @param {Highcharts.LegendOptions} options * Legend options. */ Legend.prototype.init = function (chart, options) { /** * Chart of this legend. * * @readonly * @name Highcharts.Legend#chart * @type {Highcharts.Chart} */ this.chart = chart; this.setOptions(options); if (options.enabled) { // Render it this.render(); // move checkboxes addEvent(this.chart, 'endResize', function () { this.legend.positionCheckboxes(); }); if (this.proximate) { this.unchartrender = addEvent(this.chart, 'render', function () { this.legend.proximatePositions(); this.legend.positionItems(); }); } else if (this.unchartrender) { this.unchartrender(); } } }; /** * @private * @function Highcharts.Legend#setOptions * @param {Highcharts.LegendOptions} options */ Legend.prototype.setOptions = function (options) { var padding = pick(options.padding, 8); /** * Legend options. * * @readonly * @name Highcharts.Legend#options * @type {Highcharts.LegendOptions} */ this.options = options; if (!this.chart.styledMode) { this.itemStyle = options.itemStyle; this.itemHiddenStyle = merge(this.itemStyle, options.itemHiddenStyle); } this.itemMarginTop = options.itemMarginTop || 0; this.itemMarginBottom = options.itemMarginBottom || 0; this.padding = padding; this.initialItemY = padding - 5; // 5 is pixels above the text this.symbolWidth = pick(options.symbolWidth, 16); this.pages = []; this.proximate = options.layout === 'proximate' && !this.chart.inverted; this.baseline = void 0; // #12705: baseline has to be reset on every update }; /** * Update the legend with new options. Equivalent to running `chart.update` * with a legend configuration option. * * @sample highcharts/legend/legend-update/ * Legend update * * @function Highcharts.Legend#update * * @param {Highcharts.LegendOptions} options * Legend options. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the axis is altered. If doing more * operations on the chart, it is a good idea to set redraw to false and * call {@link Chart#redraw} after. Whether to redraw the chart. * * @fires Highcharts.Legends#event:afterUpdate */ Legend.prototype.update = function (options, redraw) { var chart = this.chart; this.setOptions(merge(true, this.options, options)); this.destroy(); chart.isDirtyLegend = chart.isDirtyBox = true; if (pick(redraw, true)) { chart.redraw(); } fireEvent(this, 'afterUpdate'); }; /** * Set the colors for the legend item. * * @private * @function Highcharts.Legend#colorizeItem * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item * A Series or Point instance * @param {boolean} [visible=false] * Dimmed or colored * * @todo * Make events official: Fires the event `afterColorizeItem`. */ Legend.prototype.colorizeItem = function (item, visible) { item.legendGroup[visible ? 'removeClass' : 'addClass']('highcharts-legend-item-hidden'); if (!this.chart.styledMode) { var legend = this, options = legend.options, legendItem = item.legendItem, legendLine = item.legendLine, legendSymbol = item.legendSymbol, hiddenColor = legend.itemHiddenStyle.color, textColor = visible ? options.itemStyle.color : hiddenColor, symbolColor = visible ? (item.color || hiddenColor) : hiddenColor, markerOptions = item.options && item.options.marker, symbolAttr = { fill: symbolColor }; if (legendItem) { legendItem.css({ fill: textColor, color: textColor // #1553, oldIE }); } if (legendLine) { legendLine.attr({ stroke: symbolColor }); } if (legendSymbol) { // Apply marker options if (markerOptions && legendSymbol.isMarker) { // #585 symbolAttr = item.pointAttribs(); if (!visible) { // #6769 symbolAttr.stroke = symbolAttr.fill = hiddenColor; } } legendSymbol.attr(symbolAttr); } } fireEvent(this, 'afterColorizeItem', { item: item, visible: visible }); }; /** * @private * @function Highcharts.Legend#positionItems */ Legend.prototype.positionItems = function () { // Now that the legend width and height are established, put the items // in the final position this.allItems.forEach(this.positionItem, this); if (!this.chart.isResizing) { this.positionCheckboxes(); } }; /** * Position the legend item. * * @private * @function Highcharts.Legend#positionItem * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item * The item to position */ Legend.prototype.positionItem = function (item) { var _this = this; var legend = this, options = legend.options, symbolPadding = options.symbolPadding, ltr = !options.rtl, legendItemPos = item._legendItemPos, itemX = legendItemPos[0], itemY = legendItemPos[1], checkbox = item.checkbox, legendGroup = item.legendGroup; if (legendGroup && legendGroup.element) { var attribs = { translateX: ltr ? itemX : legend.legendWidth - itemX - 2 * symbolPadding - 4, translateY: itemY }; var complete = function () { fireEvent(_this, 'afterPositionItem', { item: item }); }; if (defined(legendGroup.translateY)) { legendGroup.animate(attribs, void 0, complete); } else { legendGroup.attr(attribs); complete(); } } if (checkbox) { checkbox.x = itemX; checkbox.y = itemY; } }; /** * Destroy a single legend item, used internally on removing series items. * * @private * @function Highcharts.Legend#destroyItem * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item * The item to remove */ Legend.prototype.destroyItem = function (item) { var checkbox = item.checkbox; // destroy SVG elements ['legendItem', 'legendLine', 'legendSymbol', 'legendGroup'].forEach(function (key) { if (item[key]) { item[key] = item[key].destroy(); } }); if (checkbox) { discardElement(item.checkbox); } }; /** * Destroy the legend. Used internally. To reflow objects, `chart.redraw` * must be called after destruction. * * @private * @function Highcharts.Legend#destroy */ Legend.prototype.destroy = function () { /** * @private * @param {string} key * @return {void} */ function destroyItems(key) { if (this[key]) { this[key] = this[key].destroy(); } } // Destroy items this.getAllItems().forEach(function (item) { ['legendItem', 'legendGroup'].forEach(destroyItems, item); }); // Destroy legend elements [ 'clipRect', 'up', 'down', 'pager', 'nav', 'box', 'title', 'group' ].forEach(destroyItems, this); this.display = null; // Reset in .render on update. }; /** * Position the checkboxes after the width is determined. * * @private * @function Highcharts.Legend#positionCheckboxes */ Legend.prototype.positionCheckboxes = function () { var alignAttr = this.group && this.group.alignAttr, translateY, clipHeight = this.clipHeight || this.legendHeight, titleHeight = this.titleHeight; if (alignAttr) { translateY = alignAttr.translateY; this.allItems.forEach(function (item) { var checkbox = item.checkbox, top; if (checkbox) { top = translateY + titleHeight + checkbox.y + (this.scrollOffset || 0) + 3; css(checkbox, { left: (alignAttr.translateX + item.checkboxOffset + checkbox.x - 20) + 'px', top: top + 'px', display: this.proximate || (top > translateY - 6 && top < translateY + clipHeight - 6) ? '' : 'none' }); } }, this); } }; /** * Render the legend title on top of the legend. * * @private * @function Highcharts.Legend#renderTitle */ Legend.prototype.renderTitle = function () { var options = this.options, padding = this.padding, titleOptions = options.title, titleHeight = 0, bBox; if (titleOptions.text) { if (!this.title) { /** * SVG element of the legend title. * * @readonly * @name Highcharts.Legend#title * @type {Highcharts.SVGElement} */ this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, null, null, null, options.useHTML, null, 'legend-title') .attr({ zIndex: 1 }); if (!this.chart.styledMode) { this.title.css(titleOptions.style); } this.title.add(this.group); } // Set the max title width (#7253) if (!titleOptions.width) { this.title.css({ width: this.maxLegendWidth + 'px' }); } bBox = this.title.getBBox(); titleHeight = bBox.height; this.offsetWidth = bBox.width; // #1717 this.contentGroup.attr({ translateY: titleHeight }); } this.titleHeight = titleHeight; }; /** * Set the legend item text. * * @function Highcharts.Legend#setText * @param {Highcharts.Point|Highcharts.Series} item * The item for which to update the text in the legend. */ Legend.prototype.setText = function (item) { var options = this.options; item.legendItem.attr({ text: options.labelFormat ? format(options.labelFormat, item, this.chart) : options.labelFormatter.call(item) }); }; /** * Render a single specific legend item. Called internally from the `render` * function. * * @private * @function Highcharts.Legend#renderItem * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item * The item to render. */ Legend.prototype.renderItem = function (item) { var legend = this, chart = legend.chart, renderer = chart.renderer, options = legend.options, horizontal = options.layout === 'horizontal', symbolWidth = legend.symbolWidth, symbolPadding = options.symbolPadding || 0, itemStyle = legend.itemStyle, itemHiddenStyle = legend.itemHiddenStyle, itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, ltr = !options.rtl, bBox, li = item.legendItem, isSeries = !item.series, series = !isSeries && item.series.drawLegendSymbol ? item.series : item, seriesOptions = series.options, showCheckbox = legend.createCheckboxForItem && seriesOptions && seriesOptions.showCheckbox, // full width minus text width itemExtraWidth = symbolWidth + symbolPadding + itemDistance + (showCheckbox ? 20 : 0), useHTML = options.useHTML, itemClassName = item.options.className; if (!li) { // generate it once, later move it // Generate the group box, a group to hold the symbol and text. Text // is to be appended in Legend class. item.legendGroup = renderer .g('legend-item') .addClass('highcharts-' + series.type + '-series ' + 'highcharts-color-' + item.colorIndex + (itemClassName ? ' ' + itemClassName : '') + (isSeries ? ' highcharts-series-' + item.index : '')) .attr({ zIndex: 1 }) .add(legend.scrollGroup); // Generate the list item text and add it to the group item.legendItem = li = renderer.text('', ltr ? symbolWidth + symbolPadding : -symbolPadding, legend.baseline || 0, useHTML); if (!chart.styledMode) { // merge to prevent modifying original (#1021) li.css(merge(item.visible ? itemStyle : itemHiddenStyle)); } li .attr({ align: ltr ? 'left' : 'right', zIndex: 2 }) .add(item.legendGroup); // Get the baseline for the first item - the font size is equal for // all if (!legend.baseline) { legend.fontMetrics = renderer.fontMetrics(chart.styledMode ? 12 : itemStyle.fontSize, li); legend.baseline = legend.fontMetrics.f + 3 + legend.itemMarginTop; li.attr('y', legend.baseline); legend.symbolHeight = options.symbolHeight || legend.fontMetrics.f; if (options.squareSymbol) { legend.symbolWidth = pick(options.symbolWidth, Math.max(legend.symbolHeight, 16)); itemExtraWidth = legend.symbolWidth + symbolPadding + itemDistance + (showCheckbox ? 20 : 0); if (ltr) { li.attr('x', legend.symbolWidth + symbolPadding); } } } // Draw the legend symbol inside the group box series.drawLegendSymbol(legend, item); if (legend.setItemEvents) { legend.setItemEvents(item, li, useHTML); } } // Add the HTML checkbox on top if (showCheckbox && !item.checkbox && legend.createCheckboxForItem) { legend.createCheckboxForItem(item); } // Colorize the items legend.colorizeItem(item, item.visible); // Take care of max width and text overflow (#6659) if (chart.styledMode || !itemStyle.width) { li.css({ width: ((options.itemWidth || legend.widthOption || chart.spacingBox.width) - itemExtraWidth) + 'px' }); } // Always update the text legend.setText(item); // calculate the positions for the next line bBox = li.getBBox(); item.itemWidth = item.checkboxOffset = options.itemWidth || item.legendItemWidth || bBox.width + itemExtraWidth; legend.maxItemWidth = Math.max(legend.maxItemWidth, item.itemWidth); legend.totalItemWidth += item.itemWidth; legend.itemHeight = item.itemHeight = Math.round(item.legendItemHeight || bBox.height || legend.symbolHeight); }; /** * Get the position of the item in the layout. We now know the * maxItemWidth from the previous loop. * * @private * @function Highcharts.Legend#layoutItem * @param {Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series} item */ Legend.prototype.layoutItem = function (item) { var options = this.options, padding = this.padding, horizontal = options.layout === 'horizontal', itemHeight = item.itemHeight, itemMarginBottom = this.itemMarginBottom, itemMarginTop = this.itemMarginTop, itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, maxLegendWidth = this.maxLegendWidth, itemWidth = (options.alignColumns && this.totalItemWidth > maxLegendWidth) ? this.maxItemWidth : item.itemWidth; // If the item exceeds the width, start a new line if (horizontal && this.itemX - padding + itemWidth > maxLegendWidth) { this.itemX = padding; if (this.lastLineHeight) { // Not for the first line (#10167) this.itemY += (itemMarginTop + this.lastLineHeight + itemMarginBottom); } this.lastLineHeight = 0; // reset for next line (#915, #3976) } // Set the edge positions this.lastItemY = itemMarginTop + this.itemY + itemMarginBottom; this.lastLineHeight = Math.max(// #915 itemHeight, this.lastLineHeight); // cache the position of the newly generated or reordered items item._legendItemPos = [this.itemX, this.itemY]; // advance if (horizontal) { this.itemX += itemWidth; } else { this.itemY += itemMarginTop + itemHeight + itemMarginBottom; this.lastLineHeight = itemHeight; } // the width of the widest item this.offsetWidth = this.widthOption || Math.max((horizontal ? this.itemX - padding - (item.checkbox ? // decrease by itemDistance only when no checkbox #4853 0 : itemDistance) : itemWidth) + padding, this.offsetWidth); }; /** * Get all items, which is one item per series for most series and one * item per point for pie series and its derivatives. Fires the event * `afterGetAllItems`. * * @private * @function Highcharts.Legend#getAllItems * @return {Array<(Highcharts.BubbleLegend|Highcharts.Point|Highcharts.Series)>} * The current items in the legend. * @fires Highcharts.Legend#event:afterGetAllItems */ Legend.prototype.getAllItems = function () { var allItems = []; this.chart.series.forEach(function (series) { var seriesOptions = series && series.options; // Handle showInLegend. If the series is linked to another series, // defaults to false. if (series && pick(seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? void 0 : false, true)) { // Use points or series for the legend item depending on // legendType allItems = allItems.concat(series.legendItems || (seriesOptions.legendType === 'point' ? series.data : series)); } }); fireEvent(this, 'afterGetAllItems', { allItems: allItems }); return allItems; }; /** * Get a short, three letter string reflecting the alignment and layout. * * @private * @function Highcharts.Legend#getAlignment * @return {string} * The alignment, empty string if floating */ Legend.prototype.getAlignment = function () { var options = this.options; // Use the first letter of each alignment option in order to detect // the side. (#4189 - use charAt(x) notation instead of [x] for IE7) if (this.proximate) { return options.align.charAt(0) + 'tv'; } return options.floating ? '' : (options.align.charAt(0) + options.verticalAlign.charAt(0) + options.layout.charAt(0)); }; /** * Adjust the chart margins by reserving space for the legend on only one * side of the chart. If the position is set to a corner, top or bottom is * reserved for horizontal legends and left or right for vertical ones. * * @private * @function Highcharts.Legend#adjustMargins * @param {Array<number>} margin * @param {Array<number>} spacing */ Legend.prototype.adjustMargins = function (margin, spacing) { var chart = this.chart, options = this.options, alignment = this.getAlignment(); if (alignment) { ([ /(lth|ct|rth)/, /(rtv|rm|rbv)/, /(rbh|cb|lbh)/, /(lbv|lm|ltv)/ ]).forEach(function (alignments, side) { if (alignments.test(alignment) && !defined(margin[side])) { // Now we have detected on which side of the chart we should // reserve space for the legend chart[marginNames[side]] = Math.max(chart[marginNames[side]], (chart.legend[(side + 1) % 2 ? 'legendHeight' : 'legendWidth'] + [1, -1, -1, 1][side] * options[(side % 2) ? 'x' : 'y'] + pick(options.margin, 12) + spacing[side] + (chart.titleOffset[side] || 0))); } }); } }; /** * @private * @function Highcharts.Legend#proximatePositions */ Legend.prototype.proximatePositions = function () { var chart = this.chart, boxes = [], alignLeft = this.options.align === 'left'; this.allItems.forEach(function (item) { var lastPoint, height, useFirstPoint = alignLeft, target, top; if (item.yAxis) { if (item.xAxis.options.reversed) { useFirstPoint = !useFirstPoint; } if (item.points) { lastPoint = find(useFirstPoint ? item.points : item.points.slice(0).reverse(), function (item) { return isNumber(item.plotY); }); } height = this.itemMarginTop + item.legendItem.getBBox().height + this.itemMarginBottom; top = item.yAxis.top - chart.plotTop; if (item.visible) { target = lastPoint ? lastPoint.plotY : item.yAxis.height; target += top - 0.3 * height; } else { target = top + item.yAxis.height; } boxes.push({ target: target, size: height, item: item }); } }, this); H.distribute(boxes, chart.plotHeight); boxes.forEach(function (box) { box.item._legendItemPos[1] = chart.plotTop - chart.spacing[0] + box.pos; }); }; /** * Render the legend. This method can be called both before and after * `chart.render`. If called after, it will only rearrange items instead * of creating new ones. Called internally on initial render and after * redraws. * * @private * @function Highcharts.Legend#render */ Legend.prototype.render = function () { var legend = this, chart = legend.chart, renderer = chart.renderer, legendGroup = legend.group, allItems, display, legendWidth, legendHeight, box = legend.box, options = legend.options, padding = legend.padding, allowedWidth; legend.itemX = padding; legend.itemY = legend.initialItemY; legend.offsetWidth = 0; legend.lastItemY = 0; legend.widthOption = relativeLength(options.width, chart.spacingBox.width - padding); // Compute how wide the legend is allowed to be allowedWidth = chart.spacingBox.width - 2 * padding - options.x; if (['rm', 'lm'].indexOf(legend.getAlignment().substring(0, 2)) > -1) { allowedWidth /= 2; } legend.maxLegendWidth = legend.widthOption || allowedWidth; if (!legendGroup) { /** * SVG group of the legend. * * @readonly * @name Highcharts.Legend#group * @type {Highcharts.SVGElement} */ legend.group = legendGroup = renderer.g('legend') .addClass(options.className || '') .attr({ zIndex: 7 }) .add(); legend.contentGroup = renderer.g() .attr({ zIndex: 1 }) // above background .add(legendGroup); legend.scrollGroup = renderer.g() .add(legend.contentGroup); } legend.renderTitle(); // add each series or point allItems = legend.getAllItems(); // sort by legendIndex stableSort(allItems, function (a, b) { return ((a.options && a.options.legendIndex) || 0) - ((b.options && b.options.legendIndex) || 0); }); // reversed legend if (options.reversed) { allItems.reverse(); } /** * All items for the legend, which is an array of series for most series * and an array of points for pie series and its derivatives. * * @readonly * @name Highcharts.Legend#allItems * @type {Array<(Highcharts.Point|Highcharts.Series)>} */ legend.allItems = allItems; legend.display = display = !!allItems.length; // Render the items. First we run a loop to set the text and properties // and read all the bounding boxes. The next loop computes the item // positions based on the bounding boxes. legend.lastLineHeight = 0; legend.maxItemWidth = 0; legend.totalItemWidth = 0; legend.itemHeight = 0; allItems.forEach(legend.renderItem, legend); allItems.forEach(legend.layoutItem, legend); // Get the box legendWidth = (legend.widthOption || legend.offsetWidth) + padding; legendHeight = legend.lastItemY + legend.lastLineHeight + legend.titleHeight; legendHeight = legend.handleOverflow(legendHeight); legendHeight += padding; // Draw the border and/or background if (!box) { /** * SVG element of the legend box. * * @readonly * @name Highcharts.Legend#box * @type {Highcharts.SVGElement} */ legend.box = box = renderer.rect() .addClass('highcharts-legend-box') .attr({ r: options.borderRadius }) .add(legendGroup); box.isNew = true; } // Presentational if (!chart.styledMode) { box .attr({ stroke: options.borderColor, 'stroke-width': options.borderWidth || 0, fill: options.backgroundColor || 'none' }) .shadow(options.shadow); } if (legendWidth > 0 && legendHeight > 0) { box[box.isNew ? 'attr' : 'animate'](box.crisp.call({}, { x: 0, y: 0, width: legendWidth, height: legendHeight }, box.strokeWidth())); box.isNew = false; } // hide the border if no items box[display ? 'show' : 'hide'](); // Open for responsiveness if (chart.styledMode && legendGroup.getStyle('display') === 'none') { legendWidth = legendHeight = 0; } legend.legendWidth = legendWidth; legend.legendHeight = legendHeight; if (display) { legend.align(); } if (!this.proximate) { this.positionItems(); } fireEvent(this, 'afterRender'); }; /** * Align the legend to chart's box. * * @private * @function Highcharts.align * @param {Highcharts.BBoxObject} alignTo * @return {void} */ Legend.prototype.align = function (alignTo) { if (alignTo === void 0) { alignTo = this.chart.spacingBox; } var chart = this.chart, options = this.options; // If aligning to the top and the layout is horizontal, adjust for // the title (#7428) var y = alignTo.y; if (/(lth|ct|rth)/.test(this.getAlignment()) && chart.titleOffset[0] > 0) { y += chart.titleOffset[0]; } else if (/(lbh|cb|rbh)/.test(this.getAlignment()) && chart.titleOffset[2] > 0) { y -= chart.titleOffset[2]; } if (y !== alignTo.y) { alignTo = merge(alignTo, { y: y }); } this.group.align(merge(options, { width: this.legendWidth, height: this.legendHeight, verticalAlign: this.proximate ? 'top' : options.verticalAlign }), true, alignTo); }; /** * Set up the overflow handling by adding navigation with up and down arrows * below the legend. * * @private * @function Highcharts.Legend#handleOverflow * @param {number} legendHeight * @return {number} */ Legend.prototype.handleOverflow = function (legendHeight) { var legend = this, chart = this.chart, renderer = chart.renderer, options = this.options, optionsY = options.y, alignTop = options.verticalAlign === 'top', padding = this.padding, spaceHeight = (chart.spacingBox.height + (alignTop ? -optionsY : optionsY) - padding), maxHeight = options.maxHeight, clipHeight, clipRect = this.clipRect, navOptions = options.navigation, animation = pick(navOptions.animation, true), arrowSize = navOptions.arrowSize || 12, nav = this.nav, pages = this.pages, lastY, allItems = this.allItems, clipToHeight = function (height) { if (typeof height === 'number') { clipRect.attr({ height: height }); } else if (clipRect) { // Reset (#5912) legend.clipRect = clipRect.destroy(); legend.contentGroup.clip(); } // useHTML if (legend.contentGroup.div) { legend.contentGroup.div.style.clip = height ? 'rect(' + padding + 'px,9999px,' + (padding + height) + 'px,0)' : 'auto'; } }, addTracker = function (key) { legend[key] = renderer .circle(0, 0, arrowSize * 1.3) .translate(arrowSize / 2, arrowSize / 2) .add(nav); if (!chart.styledMode) { legend[key].attr('fill', 'rgba(0,0,0,0.0001)'); } return legend[key]; }; // Adjust the height if (options.layout === 'horizontal' && options.verticalAlign !== 'middle' && !options.floating) { spaceHeight /= 2; } if (maxHeight) { spaceHeight = Math.min(spaceHeight, maxHeight); } // Reset the legend height and adjust the clipping rectangle pages.length = 0; if (legendHeight && spaceHeight > 0 && legendHeight > spaceHeight && navOptions.enabled !== false) { this.clipHeight = clipHeight = Math.max(spaceHeight - 20 - this.titleHeight - padding, 0); this.currentPage = pick(this.currentPage, 1); this.fullHeight = legendHeight; // Fill pages with Y positions so that the top of each a legend item // defines the scroll top for each page (#2098) allItems.forEach(function (item, i) { var y = item._legendItemPos[1], h = Math.round(item.legendItem.getBBox().height), len = pages.length; if (!len || (y - pages[len - 1] > clipHeight && (lastY || y) !== pages[len - 1])) { pages.push(lastY || y); len++; } // Keep track of which page each item is on item.pageIx = len - 1; if (lastY) { allItems[i - 1].pageIx = len - 1; } if (i === allItems.length - 1 && y + h - pages[len - 1] > clipHeight && y !== lastY // #2617 ) { pages.push(y); item.pageIx = len; } if (y !== lastY) { lastY = y; } }); // Only apply clipping if needed. Clipping causes blurred legend in // PDF export (#1787) if (!clipRect) { clipRect = legend.clipRect = renderer.clipRect(0, padding, 9999, 0); legend.contentGroup.clip(clipRect); } clipToHeight(clipHeight); // Add navigation elements if (!nav) { this.nav = nav = renderer.g() .attr({ zIndex: 1 }) .add(this.group); this.up = renderer .symbol('triangle', 0, 0, arrowSize, arrowSize) .add(nav); addTracker('upTracker') .on('click', function () { legend.scroll(-1, animation); }); this.pager = renderer.text('', 15, 10) .addClass('highcharts-legend-navigation'); if (!chart.styledMode) { this.pager.css(navOptions.style); } this.pager.add(nav); this.down = renderer .symbol('triangle-down', 0, 0, arrowSize, arrowSize) .add(nav); addTracker('downTracker') .on('click', function () { legend.scroll(1, animation); }); } // Set initial position legend.scroll(0); legendHeight = spaceHeight; // Reset } else if (nav) { clipToHeight(); this.nav = nav.destroy(); // #6322 this.scrollGroup.attr({ translateY: 1 }); this.clipHeight = 0; // #1379 } return legendHeight; }; /** * Scroll the legend by a number of pages. * * @private * @function Highcharts.Legend#scroll * * @param {number} scrollBy * The number of pages to scroll. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] * Whether and how to apply animation. * * @return {void} */ Legend.prototype.scroll = function (scrollBy, animation) { var _this = this; var chart = this.chart, pages = this.pages, pageCount = pages.length, currentPage = this.currentPage + scrollBy, clipHeight = this.clipHeight, navOptions = this.options.navigation, pager = this.pager, padding = this.padding; // When resizing while looking at the last page if (currentPage > pageCount) { currentPage = pageCount; } if (currentPage > 0) { if (typeof animation !== 'undefined') { setAnimation(animation, chart); } this.nav.attr({ translateX: padding, translateY: clipHeight + this.padding + 7 + this.titleHeight, visibility: 'visible' }); [this.up, this.upTracker].forEach(function (elem) { elem.attr({ 'class': currentPage === 1 ? 'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active' }); }); pager.attr({ text: currentPage + '/' + pageCount }); [this.down, this.downTracker].forEach(function (elem) { elem.attr({ // adjust to text width x: 18 + this.pager.getBBox().width, 'class': currentPage === pageCount ? 'highcharts-legend-nav-inactive' : 'highcharts-legend-nav-active' }); }, this); if (!chart.styledMode) { this.up .attr({ fill: currentPage === 1 ? navOptions.inactiveColor : navOptions.activeColor }); this.upTracker .css({ cursor: currentPage === 1 ? 'default' : 'pointer' }); this.down .attr({ fill: currentPage === pageCount ? navOptions.inactiveColor : navOptions.activeColor }); this.downTracker .css({ cursor: currentPage === pageCount ? 'default' : 'pointer' }); } this.scrollOffset = -pages[currentPage - 1] + this.initialItemY; this.scrollGroup.animate({ translateY: this.scrollOffset }); this.currentPage = currentPage; this.positionCheckboxes(); // Fire event after scroll animation is complete var animOptions = animObject(pick(animation, chart.renderer.globalAnimation, true)); syncTimeout(function () { fireEvent(_this, 'afterScroll', { currentPage: currentPage }); }, animOptions.duration); } }; /** * @private * @function Highcharts.Legend#setItemEvents * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item * @param {Highcharts.SVGElement} legendItem * @param {boolean} [useHTML=false] * @fires Highcharts.Point#event:legendItemClick * @fires Highcharts.Series#event:legendItemClick */ Legend.prototype.setItemEvents = function (item, legendItem, useHTML) { var legend = this, boxWrapper = legend.chart.renderer.boxWrapper, isPoint = item instanceof Point, activeClass = 'highcharts-legend-' + (isPoint ? 'point' : 'series') + '-active', styledMode = legend.chart.styledMode, // When `useHTML`, the symbol is rendered in other group, so // we need to apply events listeners to both places legendItems = useHTML ? [legendItem, item.legendSymbol] : [item.legendGroup]; // Set the events on the item group, or in case of useHTML, the item // itself (#1249) legendItems.forEach(function (element) { if (element) { element .on('mouseover', function () { if (item.visible) { legend.allItems.forEach(function (inactiveItem) { if (item !== inactiveItem) { inactiveItem.setState('inactive', !isPoint); } }); } item.setState('hover'); // A CSS class to dim or hide other than the hovered // series. // Works only if hovered series is visible (#10071). if (item.visible) { boxWrapper.addClass(activeClass); } if (!styledMode) { legendItem.css(legend.options.itemHoverStyle); } }) .on('mouseout', function () { if (!legend.chart.styledMode) { legendItem.css(merge(item.visible ? legend.itemStyle : legend.itemHiddenStyle)); } legend.allItems.forEach(function (inactiveItem) { if (item !== inactiveItem) { inactiveItem.setState('', !isPoint); } }); // A CSS class to dim or hide other than the hovered // series. boxWrapper.removeClass(activeClass); item.setState(); }) .on('click', function (event) { var strLegendItemClick = 'legendItemClick', fnLegendItemClick = function () { if (item.setVisible) { item.setVisible(); } // Reset inactive state legend.allItems.forEach(function (inactiveItem) { if (item !== inactiveItem) { inactiveItem.setState(item.visible ? 'inactive' : '', !isPoint); } }); }; // A CSS class to dim or hide other than the hovered // series. Event handling in iOS causes the activeClass // to be added prior to click in some cases (#7418). boxWrapper.removeClass(activeClass); // Pass over the click/touch event. #4. event = { browserEvent: event }; // click the name or symbol if (item.firePointEvent) { // point item.firePointEvent(strLegendItemClick, event, fnLegendItemClick); } else { fireEvent(item, strLegendItemClick, event, fnLegendItemClick); } }); } }); }; /** * @private * @function Highcharts.Legend#createCheckboxForItem * @param {Highcharts.BubbleLegend|Point|Highcharts.Series} item * @fires Highcharts.Series#event:checkboxClick */ Legend.prototype.createCheckboxForItem = function (item) { var legend = this; item.checkbox = createElement('input', { type: 'checkbox', className: 'highcharts-legend-checkbox', checked: item.selected, defaultChecked: item.selected // required by IE7 }, legend.options.itemCheckboxStyle, legend.chart.container); addEvent(item.checkbox, 'click', function (event) { var target = event.target; fireEvent(item.series || item, 'checkboxClick', { checked: target.checked, item: item }, function () { item.select(); }); }); }; return Legend; }()); // Workaround for #2030, horizontal legend items not displaying in IE11 Preview, // and for #2580, a similar drawing flaw in Firefox 26. // Explore if there's a general cause for this. The problem may be related // to nested group elements, as the legend item texts are within 4 group // elements. if (/Trident\/7\.0/.test(win.navigator && win.navigator.userAgent) || isFirefox) { wrap(Legend.prototype, 'positionItem', function (proceed, item) { var legend = this, // If chart destroyed in sync, this is undefined (#2030) runPositionItem = function () { if (item._legendItemPos) { proceed.call(legend, item); } }; // Do it now, for export and to get checkbox placement runPositionItem(); // Do it after to work around the core issue if (!legend.bubbleLegend) { setTimeout(runPositionItem); } }); } H.Legend = Legend; return H.Legend; }); _registerModule(_modules, 'Core/Series/SeriesRegistry.js', [_modules['Core/Globals.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Series/Point.js'], _modules['Core/Utilities.js']], function (H, D, Point, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var defaultOptions = D.defaultOptions; var error = U.error, extendClass = U.extendClass, merge = U.merge; /* * * * Namespace * * */ var SeriesRegistry; (function (SeriesRegistry) { /* * * * Static Properties * * */ /** * @internal * @todo Move `Globals.seriesTypes` code to her. */ SeriesRegistry.seriesTypes = H.seriesTypes; /* * * * Static Functions * * */ /* eslint-disable valid-jsdoc */ /** * Internal function to initialize an individual series. * @private */ function getSeries(chart, options) { if (options === void 0) { options = {}; } var optionsChart = chart.options.chart, type = (options.type || optionsChart.type || optionsChart.defaultSeriesType || ''), SeriesClass = SeriesRegistry.seriesTypes[type]; // No such series type if (!SeriesRegistry) { error(17, true, chart, { missingModuleFor: type }); } var series = new SeriesClass(); if (typeof series.init === 'function') { series.init(chart, options); } return series; } SeriesRegistry.getSeries = getSeries; /** * Registers class pattern of a series. * * @private */ function registerSeriesType(seriesType, seriesClass) { var defaultPlotOptions = defaultOptions.plotOptions || {}, seriesOptions = seriesClass.defaultOptions; if (!seriesClass.prototype.pointClass) { seriesClass.prototype.pointClass = Point; } seriesClass.prototype.type = seriesType; if (seriesOptions) { defaultPlotOptions[seriesType] = seriesOptions; } SeriesRegistry.seriesTypes[seriesType] = seriesClass; } SeriesRegistry.registerSeriesType = registerSeriesType; /** * Old factory to create new series prototypes. * * @deprecated * @function Highcharts.seriesType * * @param {string} type * The series type name. * * @param {string} parent * The parent series type name. Use `line` to inherit from the basic * {@link Series} object. * * @param {Highcharts.SeriesOptionsType|Highcharts.Dictionary<*>} options * The additional default options that are merged with the parent's options. * * @param {Highcharts.Dictionary<*>} [props] * The properties (functions and primitives) to set on the new prototype. * * @param {Highcharts.Dictionary<*>} [pointProps] * Members for a series-specific extension of the {@link Point} prototype if * needed. * * @return {Highcharts.Series} * The newly created prototype as extended from {@link Series} or its * derivatives. */ function seriesType(type, parent, options, seriesProto, pointProto) { var defaultPlotOptions = defaultOptions.plotOptions || {}; parent = parent || ''; // Merge the options defaultPlotOptions[type] = merge(defaultPlotOptions[parent], options); // Create the class registerSeriesType(type, extendClass(SeriesRegistry.seriesTypes[parent] || function () { }, seriesProto)); SeriesRegistry.seriesTypes[type].prototype.type = type; // Create the point class if needed if (pointProto) { SeriesRegistry.seriesTypes[type].prototype.pointClass = extendClass(Point, pointProto); } return SeriesRegistry.seriesTypes[type]; } SeriesRegistry.seriesType = seriesType; /* eslint-enable valid-jsdoc */ })(SeriesRegistry || (SeriesRegistry = {})); /* * * * Compatibility * * */ H.seriesType = SeriesRegistry.seriesType; /* * * * Export * * */ return SeriesRegistry; }); _registerModule(_modules, 'Core/Chart/Chart.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Axis/Axis.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Foundation.js'], _modules['Core/Globals.js'], _modules['Core/Legend.js'], _modules['Core/MSPointer.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Color/Palette.js'], _modules['Core/Pointer.js'], _modules['Core/Renderer/RendererRegistry.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Time.js'], _modules['Core/Utilities.js'], _modules['Core/Renderer/HTML/AST.js']], function (A, Axis, FormatUtilities, Foundation, H, Legend, MSPointer, D, palette, Pointer, RendererRegistry, SeriesRegistry, Time, U, AST) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var animate = A.animate, animObject = A.animObject, setAnimation = A.setAnimation; var numberFormat = FormatUtilities.numberFormat; var registerEventOptions = Foundation.registerEventOptions; var charts = H.charts, doc = H.doc, marginNames = H.marginNames, win = H.win; var defaultOptions = D.defaultOptions, defaultTime = D.defaultTime; var seriesTypes = SeriesRegistry.seriesTypes; var addEvent = U.addEvent, attr = U.attr, cleanRecursively = U.cleanRecursively, createElement = U.createElement, css = U.css, defined = U.defined, discardElement = U.discardElement, erase = U.erase, error = U.error, extend = U.extend, find = U.find, fireEvent = U.fireEvent, getStyle = U.getStyle, isArray = U.isArray, isFunction = U.isFunction, isNumber = U.isNumber, isObject = U.isObject, isString = U.isString, merge = U.merge, objectEach = U.objectEach, pick = U.pick, pInt = U.pInt, relativeLength = U.relativeLength, removeEvent = U.removeEvent, splat = U.splat, syncTimeout = U.syncTimeout, uniqueKey = U.uniqueKey; /* * * * Class * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The Chart class. The recommended constructor is {@link Highcharts#chart}. * * @example * let chart = Highcharts.chart('container', { * title: { * text: 'My chart' * }, * series: [{ * data: [1, 3, 2, 4] * }] * }) * * @class * @name Highcharts.Chart * * @param {string|Highcharts.HTMLDOMElement} [renderTo] * The DOM element to render to, or its id. * * @param {Highcharts.Options} options * The chart options structure. * * @param {Highcharts.ChartCallbackFunction} [callback] * Function to run when the chart has loaded and and all external images * are loaded. Defining a * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) * handler is equivalent. */ var Chart = /** @class */ (function () { function Chart(a, b, c) { this.axes = void 0; this.axisOffset = void 0; this.bounds = void 0; this.chartHeight = void 0; this.chartWidth = void 0; this.clipBox = void 0; this.colorCounter = void 0; this.container = void 0; this.eventOptions = void 0; this.index = void 0; this.isResizing = void 0; this.labelCollectors = void 0; this.legend = void 0; this.margin = void 0; this.numberFormatter = void 0; this.options = void 0; this.plotBox = void 0; this.plotHeight = void 0; this.plotLeft = void 0; this.plotTop = void 0; this.plotWidth = void 0; this.pointCount = void 0; this.pointer = void 0; this.renderer = void 0; this.renderTo = void 0; this.series = void 0; this.sharedClips = {}; this.spacing = void 0; this.spacingBox = void 0; this.symbolCounter = void 0; this.time = void 0; this.titleOffset = void 0; this.userOptions = void 0; this.xAxis = void 0; this.yAxis = void 0; this.getArgs(a, b, c); } /** * Factory function for basic charts. * * @example * // Render a chart in to div#container * let chart = Highcharts.chart('container', { * title: { * text: 'My chart' * }, * series: [{ * data: [1, 3, 2, 4] * }] * }); * * @function Highcharts.chart * * @param {string|Highcharts.HTMLDOMElement} [renderTo] * The DOM element to render to, or its id. * * @param {Highcharts.Options} options * The chart options structure. * * @param {Highcharts.ChartCallbackFunction} [callback] * Function to run when the chart has loaded and and all external images are * loaded. Defining a * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load) * handler is equivalent. * * @return {Highcharts.Chart} * Returns the Chart object. */ Chart.chart = function (a, b, c) { return new Chart(a, b, c); }; /* * * * Functions * * */ /** * Handle the arguments passed to the constructor. * * @private * @function Highcharts.Chart#getArgs * * @param {...Array<*>} arguments * All arguments for the constructor. * * @fires Highcharts.Chart#event:init * @fires Highcharts.Chart#event:afterInit */ Chart.prototype.getArgs = function (a, b, c) { // Remove the optional first argument, renderTo, and // set it on this. if (isString(a) || a.nodeName) { this.renderTo = a; this.init(b, c); } else { this.init(a, b); } }; /** * Overridable function that initializes the chart. The constructor's * arguments are passed on directly. * * @function Highcharts.Chart#init * * @param {Highcharts.Options} userOptions * Custom options. * * @param {Function} [callback] * Function to run when the chart has loaded and and all external * images are loaded. * * @return {void} * * @fires Highcharts.Chart#event:init * @fires Highcharts.Chart#event:afterInit */ Chart.prototype.init = function (userOptions, callback) { // Handle regular options var userPlotOptions = userOptions.plotOptions || {}; // Fire the event with a default function fireEvent(this, 'init', { args: arguments }, function () { var options = merge(defaultOptions, userOptions); // do the merge var optionsChart = options.chart; // Override (by copy of user options) or clear tooltip options // in chart.options.plotOptions (#6218) objectEach(options.plotOptions, function (typeOptions, type) { if (isObject(typeOptions)) { // #8766 typeOptions.tooltip = (userPlotOptions[type] && // override by copy: merge(userPlotOptions[type].tooltip)) || void 0; // or clear } }); // User options have higher priority than default options // (#6218). In case of exporting: path is changed options.tooltip.userOptions = (userOptions.chart && userOptions.chart.forExport && userOptions.tooltip.userOptions) || userOptions.tooltip; /** * The original options given to the constructor or a chart factory * like {@link Highcharts.chart} and {@link Highcharts.stockChart}. * * @name Highcharts.Chart#userOptions * @type {Highcharts.Options} */ this.userOptions = userOptions; this.margin = []; this.spacing = []; // Pixel data bounds for touch zoom this.bounds = { h: {}, v: {} }; // An array of functions that returns labels that should be // considered for anti-collision this.labelCollectors = []; this.callback = callback; this.isResizing = 0; /** * The options structure for the chart after merging * {@link #defaultOptions} and {@link #userOptions}. It contains * members for the sub elements like series, legend, tooltip etc. * * @name Highcharts.Chart#options * @type {Highcharts.Options} */ this.options = options; /** * All the axes in the chart. * * @see Highcharts.Chart.xAxis * @see Highcharts.Chart.yAxis * * @name Highcharts.Chart#axes * @type {Array<Highcharts.Axis>} */ this.axes = []; /** * All the current series in the chart. * * @name Highcharts.Chart#series * @type {Array<Highcharts.Series>} */ this.series = []; /** * The `Time` object associated with the chart. Since v6.0.5, * time settings can be applied individually for each chart. If * no individual settings apply, the `Time` object is shared by * all instances. * * @name Highcharts.Chart#time * @type {Highcharts.Time} */ this.time = userOptions.time && Object.keys(userOptions.time).length ? new Time(userOptions.time) : H.time; /** * Callback function to override the default function that formats * all the numbers in the chart. Returns a string with the formatted * number. * * @name Highcharts.Chart#numberFormatter * @type {Highcharts.NumberFormatterCallbackFunction} */ this.numberFormatter = optionsChart.numberFormatter || numberFormat; /** * Whether the chart is in styled mode, meaning all presentatinoal * attributes are avoided. * * @name Highcharts.Chart#styledMode * @type {boolean} */ this.styledMode = optionsChart.styledMode; this.hasCartesianSeries = optionsChart.showAxes; var chart = this; /** * Index position of the chart in the {@link Highcharts#charts} * property. * * @name Highcharts.Chart#index * @type {number} * @readonly */ chart.index = charts.length; // Add the chart to the global lookup charts.push(chart); H.chartCount++; // Chart event handlers registerEventOptions(this, optionsChart); /** * A collection of the X axes in the chart. * * @name Highcharts.Chart#xAxis * @type {Array<Highcharts.Axis>} */ chart.xAxis = []; /** * A collection of the Y axes in the chart. * * @name Highcharts.Chart#yAxis * @type {Array<Highcharts.Axis>} * * @todo * Make events official: Fire the event `afterInit`. */ chart.yAxis = []; chart.pointCount = chart.colorCounter = chart.symbolCounter = 0; // Fire after init but before first render, before axes and series // have been initialized. fireEvent(chart, 'afterInit'); chart.firstRender(); }); }; /** * Internal function to unitialize an individual series. * * @private * @function Highcharts.Chart#initSeries */ Chart.prototype.initSeries = function (options) { var chart = this, optionsChart = chart.options.chart, type = (options.type || optionsChart.type || optionsChart.defaultSeriesType), SeriesClass = seriesTypes[type]; // No such series type if (!SeriesClass) { error(17, true, chart, { missingModuleFor: type }); } var series = new SeriesClass(); if (typeof series.init === 'function') { series.init(chart, options); } return series; }; /** * Internal function to set data for all series with enabled sorting. * * @private * @function Highcharts.Chart#setSeriesData */ Chart.prototype.setSeriesData = function () { this.getSeriesOrderByLinks().forEach(function (series) { // We need to set data for series with sorting after series init if (!series.points && !series.data && series.enabledDataSorting) { series.setData(series.options.data, false); } }); }; /** * Sort and return chart series in order depending on the number of linked * series. * * @private * @function Highcharts.Series#getSeriesOrderByLinks * @return {Array<Highcharts.Series>} */ Chart.prototype.getSeriesOrderByLinks = function () { return this.series.concat().sort(function (a, b) { if (a.linkedSeries.length || b.linkedSeries.length) { return b.linkedSeries.length - a.linkedSeries.length; } return 0; }); }; /** * Order all series above a given index. When series are added and ordered * by configuration, only the last series is handled (#248, #1123, #2456, * #6112). This function is called on series initialization and destroy. * * @private * @function Highcharts.Series#orderSeries * @param {number} [fromIndex] * If this is given, only the series above this index are handled. */ Chart.prototype.orderSeries = function (fromIndex) { var series = this.series; for (var i = (fromIndex || 0), iEnd = series.length; i < iEnd; ++i) { if (series[i]) { /** * Contains the series' index in the `Chart.series` array. * * @name Highcharts.Series#index * @type {number} * @readonly */ series[i].index = i; series[i].name = series[i].getName(); } } }; /** * Check whether a given point is within the plot area. * * @function Highcharts.Chart#isInsidePlot * * @param {number} plotX * Pixel x relative to the plot area. * * @param {number} plotY * Pixel y relative to the plot area. * * @param {Highcharts.ChartIsInsideOptionsObject} [options] * Options object. * * @return {boolean} * Returns true if the given point is inside the plot area. */ Chart.prototype.isInsidePlot = function (plotX, plotY, options) { var _a; if (options === void 0) { options = {}; } var _b = this, inverted = _b.inverted, plotBox = _b.plotBox, plotLeft = _b.plotLeft, plotTop = _b.plotTop, scrollablePlotBox = _b.scrollablePlotBox; var scrollLeft = 0, scrollTop = 0; if (options.visiblePlotOnly && this.scrollingContainer) { (_a = this.scrollingContainer, scrollLeft = _a.scrollLeft, scrollTop = _a.scrollTop); } var series = options.series, box = (options.visiblePlotOnly && scrollablePlotBox) || plotBox, x = options.inverted ? plotY : plotX, y = options.inverted ? plotX : plotY, e = { x: x, y: y, isInsidePlot: true }; if (!options.ignoreX) { var xAxis = (series && (inverted ? series.yAxis : series.xAxis)) || { pos: plotLeft, len: Infinity }; var chartX = options.paneCoordinates ? xAxis.pos + x : plotLeft + x; if (!(chartX >= Math.max(scrollLeft + plotLeft, xAxis.pos) && chartX <= Math.min(scrollLeft + plotLeft + box.width, xAxis.pos + xAxis.len))) { e.isInsidePlot = false; } } if (!options.ignoreY && e.isInsidePlot) { var yAxis = (series && (inverted ? series.xAxis : series.yAxis)) || { pos: plotTop, len: Infinity }; var chartY = options.paneCoordinates ? yAxis.pos + y : plotTop + y; if (!(chartY >= Math.max(scrollTop + plotTop, yAxis.pos) && chartY <= Math.min(scrollTop + plotTop + box.height, yAxis.pos + yAxis.len))) { e.isInsidePlot = false; } } fireEvent(this, 'afterIsInsidePlot', e); return e.isInsidePlot; }; /** * Redraw the chart after changes have been done to the data, axis extremes * chart size or chart elements. All methods for updating axes, series or * points have a parameter for redrawing the chart. This is `true` by * default. But in many cases you want to do more than one operation on the * chart before redrawing, for example add a number of points. In those * cases it is a waste of resources to redraw the chart for each new point * added. So you add the points and call `chart.redraw()` after. * * @function Highcharts.Chart#redraw * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] * If or how to apply animation to the redraw. * * @fires Highcharts.Chart#event:afterSetExtremes * @fires Highcharts.Chart#event:beforeRedraw * @fires Highcharts.Chart#event:predraw * @fires Highcharts.Chart#event:redraw * @fires Highcharts.Chart#event:render * @fires Highcharts.Chart#event:updatedData */ Chart.prototype.redraw = function (animation) { fireEvent(this, 'beforeRedraw'); var chart = this, axes = chart.hasCartesianSeries ? chart.axes : chart.colorAxis || [], series = chart.series, pointer = chart.pointer, legend = chart.legend, legendUserOptions = chart.userOptions.legend, renderer = chart.renderer, isHiddenChart = renderer.isHidden(), afterRedraw = []; var hasDirtyStacks, hasStackedSeries, i, isDirtyBox = chart.isDirtyBox, redrawLegend = chart.isDirtyLegend, serie; // Handle responsive rules, not only on resize (#6130) if (chart.setResponsive) { chart.setResponsive(false); } // Set the global animation. When chart.hasRendered is not true, the // redraw call comes from a responsive rule and animation should not // occur. setAnimation(chart.hasRendered ? animation : false, chart); if (isHiddenChart) { chart.temporaryDisplay(); } // Adjust title layout (reflow multiline text) chart.layOutTitles(); // link stacked series i = series.length; while (i--) { serie = series[i]; if (serie.options.stacking || serie.options.centerInCategory) { hasStackedSeries = true; if (serie.isDirty) { hasDirtyStacks = true; break; } } } if (hasDirtyStacks) { // mark others as dirty i = series.length; while (i--) { serie = series[i]; if (serie.options.stacking) { serie.isDirty = true; } } } // Handle updated data in the series series.forEach(function (serie) { if (serie.isDirty) { if (serie.options.legendType === 'point') { if (typeof serie.updateTotals === 'function') { serie.updateTotals(); } redrawLegend = true; } else if (legendUserOptions && (legendUserOptions.labelFormatter || legendUserOptions.labelFormat)) { redrawLegend = true; // #2165 } } if (serie.isDirtyData) { fireEvent(serie, 'updatedData'); } }); // handle added or removed series if (redrawLegend && legend && legend.options.enabled) { // draw legend graphics legend.render(); chart.isDirtyLegend = false; } // reset stacks if (hasStackedSeries) { chart.getStacks(); } // set axes scales axes.forEach(function (axis) { axis.updateNames(); axis.setScale(); }); chart.getMargins(); // #3098 // If one axis is dirty, all axes must be redrawn (#792, #2169) axes.forEach(function (axis) { if (axis.isDirty) { isDirtyBox = true; } }); // redraw axes axes.forEach(function (axis) { // Fire 'afterSetExtremes' only if extremes are set var key = axis.min + ',' + axis.max; if (axis.extKey !== key) { // #821, #4452 axis.extKey = key; // prevent a recursive call to chart.redraw() (#1119) afterRedraw.push(function () { fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751 delete axis.eventArgs; }); } if (isDirtyBox || hasStackedSeries) { axis.redraw(); } }); // the plot areas size has changed if (isDirtyBox) { chart.drawChartBox(); } // Fire an event before redrawing series, used by the boost module to // clear previous series renderings. fireEvent(chart, 'predraw'); // redraw affected series series.forEach(function (serie) { if ((isDirtyBox || serie.isDirty) && serie.visible) { serie.redraw(); } // Set it here, otherwise we will have unlimited 'updatedData' calls // for a hidden series after setData(). Fixes #6012 serie.isDirtyData = false; }); // move tooltip or reset if (pointer) { pointer.reset(true); } // redraw if canvas renderer.draw(); // Fire the events fireEvent(chart, 'redraw'); fireEvent(chart, 'render'); if (isHiddenChart) { chart.temporaryDisplay(true); } // Fire callbacks that are put on hold until after the redraw afterRedraw.forEach(function (callback) { callback.call(); }); }; /** * Get an axis, series or point object by `id` as given in the configuration * options. Returns `undefined` if no item is found. * * @sample highcharts/plotoptions/series-id/ * Get series by id * * @function Highcharts.Chart#get * * @param {string} id * The id as given in the configuration options. * * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined} * The retrieved item. */ Chart.prototype.get = function (id) { var series = this.series; /** * @private * @param {Highcharts.Axis|Highcharts.Series} item * @return {boolean} */ function itemById(item) { return (item.id === id || (item.options && item.options.id === id)); } var ret = // Search axes find(this.axes, itemById) || // Search series find(this.series, itemById); // Search points for (var i = 0; !ret && i < series.length; i++) { ret = find(series[i].points || [], itemById); } return ret; }; /** * Create the Axis instances based on the config options. * * @private * @function Highcharts.Chart#getAxes * @fires Highcharts.Chart#event:afterGetAxes * @fires Highcharts.Chart#event:getAxes */ Chart.prototype.getAxes = function () { var chart = this, options = this.options, xAxisOptions = options.xAxis = splat(options.xAxis || {}), yAxisOptions = options.yAxis = splat(options.yAxis || {}); fireEvent(this, 'getAxes'); // make sure the options are arrays and add some members xAxisOptions.forEach(function (axis, i) { axis.index = i; axis.isX = true; }); yAxisOptions.forEach(function (axis, i) { axis.index = i; }); // concatenate all axis options into one array var optionsArray = xAxisOptions.concat(yAxisOptions); optionsArray.forEach(function (axisOptions) { new Axis(chart, axisOptions); // eslint-disable-line no-new }); fireEvent(this, 'afterGetAxes'); }; /** * Returns an array of all currently selected points in the chart. Points * can be selected by clicking or programmatically by the * {@link Highcharts.Point#select} * function. * * @sample highcharts/plotoptions/series-allowpointselect-line/ * Get selected points * * @function Highcharts.Chart#getSelectedPoints * * @return {Array<Highcharts.Point>} * The currently selected points. */ Chart.prototype.getSelectedPoints = function () { var points = []; this.series.forEach(function (serie) { // For one-to-one points inspect series.data in order to retrieve // points outside the visible range (#6445). For grouped data, // inspect the generated series.points. points = points.concat(serie.getPointsCollection().filter(function (point) { return pick(point.selectedStaging, point.selected); })); }); return points; }; /** * Returns an array of all currently selected series in the chart. Series * can be selected either programmatically by the * {@link Highcharts.Series#select} * function or by checking the checkbox next to the legend item if * [series.showCheckBox](https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox) * is true. * * @sample highcharts/members/chart-getselectedseries/ * Get selected series * * @function Highcharts.Chart#getSelectedSeries * * @return {Array<Highcharts.Series>} * The currently selected series. */ Chart.prototype.getSelectedSeries = function () { return this.series.filter(function (serie) { return serie.selected; }); }; /** * Set a new title or subtitle for the chart. * * @sample highcharts/members/chart-settitle/ * Set title text and styles * * @function Highcharts.Chart#setTitle * * @param {Highcharts.TitleOptions} [titleOptions] * New title options. The title text itself is set by the * `titleOptions.text` property. * * @param {Highcharts.SubtitleOptions} [subtitleOptions] * New subtitle options. The subtitle text itself is set by the * `subtitleOptions.text` property. * * @param {boolean} [redraw] * Whether to redraw the chart or wait for a later call to * `chart.redraw()`. */ Chart.prototype.setTitle = function (titleOptions, subtitleOptions, redraw) { this.applyDescription('title', titleOptions); this.applyDescription('subtitle', subtitleOptions); // The initial call also adds the caption. On update, chart.update will // relay to Chart.setCaption. this.applyDescription('caption', void 0); this.layOutTitles(redraw); }; /** * Apply a title, subtitle or caption for the chart * * @private * @function Highcharts.Chart#applyDescription * @param name {string} * Either title, subtitle or caption * @param {Highcharts.TitleOptions|Highcharts.SubtitleOptions|Highcharts.CaptionOptions|undefined} explicitOptions * The options to set, will be merged with default options. */ Chart.prototype.applyDescription = function (name, explicitOptions) { var chart = this; // Default style var style = name === 'title' ? { color: palette.neutralColor80, fontSize: this.options.isStock ? '16px' : '18px' // #2944 } : { color: palette.neutralColor60 }; // Merge default options with explicit options var options = this.options[name] = merge( // Default styles (!this.styledMode && { style: style }), this.options[name], explicitOptions); var elem = this[name]; if (elem && explicitOptions) { this[name] = elem = elem.destroy(); // remove old } if (options && !elem) { elem = this.renderer.text(options.text, 0, 0, options.useHTML) .attr({ align: options.align, 'class': 'highcharts-' + name, zIndex: options.zIndex || 4 }) .add(); // Update methods, shortcut to Chart.setTitle, Chart.setSubtitle and // Chart.setCaption elem.update = function (updateOptions) { var fn = { title: 'setTitle', subtitle: 'setSubtitle', caption: 'setCaption' }[name]; chart[fn](updateOptions); }; // Presentational if (!this.styledMode) { elem.css(options.style); } /** * The chart title. The title has an `update` method that allows * modifying the options directly or indirectly via * `chart.update`. * * @sample highcharts/members/title-update/ * Updating titles * * @name Highcharts.Chart#title * @type {Highcharts.TitleObject} */ /** * The chart subtitle. The subtitle has an `update` method that * allows modifying the options directly or indirectly via * `chart.update`. * * @name Highcharts.Chart#subtitle * @type {Highcharts.SubtitleObject} */ this[name] = elem; } }; /** * Internal function to lay out the chart title, subtitle and caption, and * cache the full offset height for use in `getMargins`. The result is * stored in `this.titleOffset`. * * @private * @function Highcharts.Chart#layOutTitles * * @param {boolean} [redraw=true] * @fires Highcharts.Chart#event:afterLayOutTitles */ Chart.prototype.layOutTitles = function (redraw) { var titleOffset = [0, 0, 0], renderer = this.renderer, spacingBox = this.spacingBox; // Lay out the title and the subtitle respectively ['title', 'subtitle', 'caption'].forEach(function (key) { var title = this[key], titleOptions = this.options[key], verticalAlign = titleOptions.verticalAlign || 'top', offset = key === 'title' ? verticalAlign === 'top' ? -3 : 0 : // Floating subtitle (#6574) verticalAlign === 'top' ? titleOffset[0] + 2 : 0; var titleSize, height; if (title) { if (!this.styledMode) { titleSize = titleOptions.style && titleOptions.style.fontSize; } titleSize = renderer.fontMetrics(titleSize, title).b; title .css({ width: (titleOptions.width || spacingBox.width + (titleOptions.widthAdjust || 0)) + 'px' }); // Skip the cache for HTML (#3481, #11666) height = Math.round(title.getBBox(titleOptions.useHTML).height); title.align(extend({ y: verticalAlign === 'bottom' ? titleSize : offset + titleSize, height: height }, titleOptions), false, 'spacingBox'); if (!titleOptions.floating) { if (verticalAlign === 'top') { titleOffset[0] = Math.ceil(titleOffset[0] + height); } else if (verticalAlign === 'bottom') { titleOffset[2] = Math.ceil(titleOffset[2] + height); } } } }, this); // Handle title.margin and caption.margin if (titleOffset[0] && (this.options.title.verticalAlign || 'top') === 'top') { titleOffset[0] += this.options.title.margin; } if (titleOffset[2] && this.options.caption.verticalAlign === 'bottom') { titleOffset[2] += this.options.caption.margin; } var requiresDirtyBox = (!this.titleOffset || this.titleOffset.join(',') !== titleOffset.join(',')); // Used in getMargins this.titleOffset = titleOffset; fireEvent(this, 'afterLayOutTitles'); if (!this.isDirtyBox && requiresDirtyBox) { this.isDirtyBox = this.isDirtyLegend = requiresDirtyBox; // Redraw if necessary (#2719, #2744) if (this.hasRendered && pick(redraw, true) && this.isDirtyBox) { this.redraw(); } } }; /** * Internal function to get the chart width and height according to options * and container size. Sets {@link Chart.chartWidth} and * {@link Chart.chartHeight}. * * @private * @function Highcharts.Chart#getChartSize */ Chart.prototype.getChartSize = function () { var chart = this, optionsChart = chart.options.chart, widthOption = optionsChart.width, heightOption = optionsChart.height, renderTo = chart.renderTo; // Get inner width and height if (!defined(widthOption)) { chart.containerWidth = getStyle(renderTo, 'width'); } if (!defined(heightOption)) { chart.containerHeight = getStyle(renderTo, 'height'); } /** * The current pixel width of the chart. * * @name Highcharts.Chart#chartWidth * @type {number} */ chart.chartWidth = Math.max(// #1393 0, widthOption || chart.containerWidth || 600 // #1460 ); /** * The current pixel height of the chart. * * @name Highcharts.Chart#chartHeight * @type {number} */ chart.chartHeight = Math.max(0, relativeLength(heightOption, chart.chartWidth) || (chart.containerHeight > 1 ? chart.containerHeight : 400)); }; /** * If the renderTo element has no offsetWidth, most likely one or more of * its parents are hidden. Loop up the DOM tree to temporarily display the * parents, then save the original display properties, and when the true * size is retrieved, reset them. Used on first render and on redraws. * * @private * @function Highcharts.Chart#temporaryDisplay * * @param {boolean} [revert] * Revert to the saved original styles. */ Chart.prototype.temporaryDisplay = function (revert) { var node = this.renderTo, tempStyle; if (!revert) { while (node && node.style) { // When rendering to a detached node, it needs to be temporarily // attached in order to read styling and bounding boxes (#5783, // #7024). if (!doc.body.contains(node) && !node.parentNode) { node.hcOrigDetached = true; doc.body.appendChild(node); } if (getStyle(node, 'display', false) === 'none' || node.hcOricDetached) { node.hcOrigStyle = { display: node.style.display, height: node.style.height, overflow: node.style.overflow }; tempStyle = { display: 'block', overflow: 'hidden' }; if (node !== this.renderTo) { tempStyle.height = 0; } css(node, tempStyle); // If it still doesn't have an offset width after setting // display to block, it probably has an !important priority // #2631, 6803 if (!node.offsetWidth) { node.style.setProperty('display', 'block', 'important'); } } node = node.parentNode; if (node === doc.body) { break; } } } else { while (node && node.style) { if (node.hcOrigStyle) { css(node, node.hcOrigStyle); delete node.hcOrigStyle; } if (node.hcOrigDetached) { doc.body.removeChild(node); node.hcOrigDetached = false; } node = node.parentNode; } } }; /** * Set the {@link Chart.container|chart container's} class name, in * addition to `highcharts-container`. * * @function Highcharts.Chart#setClassName * * @param {string} [className] * The additional class name. */ Chart.prototype.setClassName = function (className) { this.container.className = 'highcharts-container ' + (className || ''); }; /** * Get the containing element, determine the size and create the inner * container div to hold the chart. * * @private * @function Highcharts.Chart#afterGetContainer * @fires Highcharts.Chart#event:afterGetContainer */ Chart.prototype.getContainer = function () { var chart = this, options = chart.options, optionsChart = options.chart, indexAttrName = 'data-highcharts-chart', containerId = uniqueKey(); var containerStyle, renderTo = chart.renderTo; if (!renderTo) { chart.renderTo = renderTo = optionsChart.renderTo; } if (isString(renderTo)) { chart.renderTo = renderTo = doc.getElementById(renderTo); } // Display an error if the renderTo is wrong if (!renderTo) { error(13, true, chart); } // If the container already holds a chart, destroy it. The check for // hasRendered is there because web pages that are saved to disk from // the browser, will preserve the data-highcharts-chart attribute and // the SVG contents, but not an interactive chart. So in this case, // charts[oldChartIndex] will point to the wrong chart if any (#2609). var oldChartIndex = pInt(attr(renderTo, indexAttrName)); if (isNumber(oldChartIndex) && charts[oldChartIndex] && charts[oldChartIndex].hasRendered) { charts[oldChartIndex].destroy(); } // Make a reference to the chart from the div attr(renderTo, indexAttrName, chart.index); // remove previous chart renderTo.innerHTML = ''; // If the container doesn't have an offsetWidth, it has or is a child of // a node that has display:none. We need to temporarily move it out to a // visible state to determine the size, else the legend and tooltips // won't render properly. The skipClone option is used in sparklines as // a micro optimization, saving about 1-2 ms each chart. if (!optionsChart.skipClone && !renderTo.offsetWidth) { chart.temporaryDisplay(); } // get the width and height chart.getChartSize(); var chartWidth = chart.chartWidth; var chartHeight = chart.chartHeight; // Allow table cells and flex-boxes to shrink without the chart blocking // them out (#6427) css(renderTo, { overflow: 'hidden' }); // Create the inner container if (!chart.styledMode) { containerStyle = extend({ position: 'relative', // needed for context menu (avoidscrollbars) and content // overflow in IE overflow: 'hidden', width: chartWidth + 'px', height: chartHeight + 'px', textAlign: 'left', lineHeight: 'normal', zIndex: 0, '-webkit-tap-highlight-color': 'rgba(0,0,0,0)', userSelect: 'none', 'touch-action': 'manipulation', outline: 'none' }, optionsChart.style || {}); } /** * The containing HTML element of the chart. The container is * dynamically inserted into the element given as the `renderTo` * parameter in the {@link Highcharts#chart} constructor. * * @name Highcharts.Chart#container * @type {Highcharts.HTMLDOMElement} */ var container = createElement('div', { id: containerId }, containerStyle, renderTo); chart.container = container; // cache the cursor (#1650) chart._cursor = container.style.cursor; // Initialize the renderer var Renderer = RendererRegistry.getRendererType(optionsChart.renderer); /** * The renderer instance of the chart. Each chart instance has only one * associated renderer. * * @name Highcharts.Chart#renderer * @type {Highcharts.SVGRenderer} */ chart.renderer = new Renderer(container, chartWidth, chartHeight, void 0, optionsChart.forExport, options.exporting && options.exporting.allowHTML, chart.styledMode); // Set the initial animation from the options setAnimation(void 0, chart); chart.setClassName(optionsChart.className); if (!chart.styledMode) { chart.renderer.setStyle(optionsChart.style); } else { // Initialize definitions for (var key in options.defs) { // eslint-disable-line guard-for-in this.renderer.definition(options.defs[key]); } } // Add a reference to the charts index chart.renderer.chartIndex = chart.index; fireEvent(this, 'afterGetContainer'); }; /** * Calculate margins by rendering axis labels in a preliminary position. * Title, subtitle and legend have already been rendered at this stage, but * will be moved into their final positions. * * @private * @function Highcharts.Chart#getMargins * @fires Highcharts.Chart#event:getMargins */ Chart.prototype.getMargins = function (skipAxes) { var _a = this, spacing = _a.spacing, margin = _a.margin, titleOffset = _a.titleOffset; this.resetMargins(); // Adjust for title and subtitle if (titleOffset[0] && !defined(margin[0])) { this.plotTop = Math.max(this.plotTop, titleOffset[0] + spacing[0]); } if (titleOffset[2] && !defined(margin[2])) { this.marginBottom = Math.max(this.marginBottom, titleOffset[2] + spacing[2]); } // Adjust for legend if (this.legend && this.legend.display) { this.legend.adjustMargins(margin, spacing); } fireEvent(this, 'getMargins'); if (!skipAxes) { this.getAxisMargins(); } }; /** * @private * @function Highcharts.Chart#getAxisMargins */ Chart.prototype.getAxisMargins = function () { var chart = this, // [top, right, bottom, left] axisOffset = chart.axisOffset = [0, 0, 0, 0], colorAxis = chart.colorAxis, margin = chart.margin, getOffset = function (axes) { axes.forEach(function (axis) { if (axis.visible) { axis.getOffset(); } }); }; // pre-render axes to get labels offset width if (chart.hasCartesianSeries) { getOffset(chart.axes); } else if (colorAxis && colorAxis.length) { getOffset(colorAxis); } // Add the axis offsets marginNames.forEach(function (m, side) { if (!defined(margin[side])) { chart[m] += axisOffset[side]; } }); chart.setChartSize(); }; /** * Reflows the chart to its container. By default, the chart reflows * automatically to its container following a `window.resize` event, as per * the [chart.reflow](https://api.highcharts.com/highcharts/chart.reflow) * option. However, there are no reliable events for div resize, so if the * container is resized without a window resize event, this must be called * explicitly. * * @sample highcharts/members/chart-reflow/ * Resize div and reflow * @sample highcharts/chart/events-container/ * Pop up and reflow * * @function Highcharts.Chart#reflow * * @param {global.Event} [e] * Event arguments. Used primarily when the function is called * internally as a response to window resize. */ Chart.prototype.reflow = function (e) { var chart = this, optionsChart = chart.options.chart, renderTo = chart.renderTo, hasUserSize = (defined(optionsChart.width) && defined(optionsChart.height)), width = optionsChart.width || getStyle(renderTo, 'width'), height = optionsChart.height || getStyle(renderTo, 'height'), target = e ? e.target : win; delete chart.pointer.chartPosition; // Width and height checks for display:none. Target is doc in IE8 and // Opera, win in Firefox, Chrome and IE9. if (!hasUserSize && !chart.isPrinting && width && height && (target === win || target === doc)) { if (width !== chart.containerWidth || height !== chart.containerHeight) { U.clearTimeout(chart.reflowTimeout); // When called from window.resize, e is set, else it's called // directly (#2224) chart.reflowTimeout = syncTimeout(function () { // Set size, it may have been destroyed in the meantime // (#1257) if (chart.container) { chart.setSize(void 0, void 0, false); } }, e ? 100 : 0); } chart.containerWidth = width; chart.containerHeight = height; } }; /** * Toggle the event handlers necessary for auto resizing, depending on the * `chart.reflow` option. * * @private * @function Highcharts.Chart#setReflow */ Chart.prototype.setReflow = function (reflow) { var chart = this; if (reflow !== false && !this.unbindReflow) { this.unbindReflow = addEvent(win, 'resize', function (e) { // a removed event listener still runs in Edge and IE if the // listener was removed while the event runs, so check if the // chart is not destroyed (#11609) if (chart.options) { chart.reflow(e); } }); addEvent(this, 'destroy', this.unbindReflow); } else if (reflow === false && this.unbindReflow) { // Unbind and unset this.unbindReflow = this.unbindReflow(); } // The following will add listeners to re-fit the chart before and after // printing (#2284). However it only works in WebKit. Should have worked // in Firefox, but not supported in IE. /* if (win.matchMedia) { win.matchMedia('print').addListener(function reflow() { chart.reflow(); }); } //*/ }; /** * Resize the chart to a given width and height. In order to set the width * only, the height argument may be skipped. To set the height only, pass * `undefined` for the width. * * @sample highcharts/members/chart-setsize-button/ * Test resizing from buttons * @sample highcharts/members/chart-setsize-jquery-resizable/ * Add a jQuery UI resizable * @sample stock/members/chart-setsize/ * Highcharts Stock with UI resizable * * @function Highcharts.Chart#setSize * * @param {number|null} [width] * The new pixel width of the chart. Since v4.2.6, the argument can * be `undefined` in order to preserve the current value (when * setting height only), or `null` to adapt to the width of the * containing element. * * @param {number|null} [height] * The new pixel height of the chart. Since v4.2.6, the argument can * be `undefined` in order to preserve the current value, or `null` * in order to adapt to the height of the containing element. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] * Whether and how to apply animation. * * @return {void} * * @fires Highcharts.Chart#event:endResize * @fires Highcharts.Chart#event:resize */ Chart.prototype.setSize = function (width, height, animation) { var chart = this, renderer = chart.renderer; // Handle the isResizing counter chart.isResizing += 1; // set the animation for the current process setAnimation(animation, chart); var globalAnimation = renderer.globalAnimation; chart.oldChartHeight = chart.chartHeight; chart.oldChartWidth = chart.chartWidth; if (typeof width !== 'undefined') { chart.options.chart.width = width; } if (typeof height !== 'undefined') { chart.options.chart.height = height; } chart.getChartSize(); // Resize the container with the global animation applied if enabled // (#2503) if (!chart.styledMode) { (globalAnimation ? animate : css)(chart.container, { width: chart.chartWidth + 'px', height: chart.chartHeight + 'px' }, globalAnimation); } chart.setChartSize(true); renderer.setSize(chart.chartWidth, chart.chartHeight, globalAnimation); // handle axes chart.axes.forEach(function (axis) { axis.isDirty = true; axis.setScale(); }); chart.isDirtyLegend = true; // force legend redraw chart.isDirtyBox = true; // force redraw of plot and chart border chart.layOutTitles(); // #2857 chart.getMargins(); chart.redraw(globalAnimation); chart.oldChartHeight = null; fireEvent(chart, 'resize'); // Fire endResize and set isResizing back. If animation is disabled, // fire without delay syncTimeout(function () { if (chart) { fireEvent(chart, 'endResize', null, function () { chart.isResizing -= 1; }); } }, animObject(globalAnimation).duration); }; /** * Set the public chart properties. This is done before and after the * pre-render to determine margin sizes. * * @private * @function Highcharts.Chart#setChartSize * @fires Highcharts.Chart#event:afterSetChartSize */ Chart.prototype.setChartSize = function (skipAxes) { var chart = this, inverted = chart.inverted, renderer = chart.renderer, chartWidth = chart.chartWidth, chartHeight = chart.chartHeight, optionsChart = chart.options.chart, spacing = chart.spacing, clipOffset = chart.clipOffset; var plotLeft, plotTop, plotWidth, plotHeight; /** * The current left position of the plot area in pixels. * * @name Highcharts.Chart#plotLeft * @type {number} */ chart.plotLeft = plotLeft = Math.round(chart.plotLeft); /** * The current top position of the plot area in pixels. * * @name Highcharts.Chart#plotTop * @type {number} */ chart.plotTop = plotTop = Math.round(chart.plotTop); /** * The current width of the plot area in pixels. * * @name Highcharts.Chart#plotWidth * @type {number} */ chart.plotWidth = plotWidth = Math.max(0, Math.round(chartWidth - plotLeft - chart.marginRight)); /** * The current height of the plot area in pixels. * * @name Highcharts.Chart#plotHeight * @type {number} */ chart.plotHeight = plotHeight = Math.max(0, Math.round(chartHeight - plotTop - chart.marginBottom)); chart.plotSizeX = inverted ? plotHeight : plotWidth; chart.plotSizeY = inverted ? plotWidth : plotHeight; chart.plotBorderWidth = optionsChart.plotBorderWidth || 0; // Set boxes used for alignment chart.spacingBox = renderer.spacingBox = { x: spacing[3], y: spacing[0], width: chartWidth - spacing[3] - spacing[1], height: chartHeight - spacing[0] - spacing[2] }; chart.plotBox = renderer.plotBox = { x: plotLeft, y: plotTop, width: plotWidth, height: plotHeight }; var plotBorderWidth = 2 * Math.floor(chart.plotBorderWidth / 2), clipX = Math.ceil(Math.max(plotBorderWidth, clipOffset[3]) / 2), clipY = Math.ceil(Math.max(plotBorderWidth, clipOffset[0]) / 2); chart.clipBox = { x: clipX, y: clipY, width: Math.floor(chart.plotSizeX - Math.max(plotBorderWidth, clipOffset[1]) / 2 - clipX), height: Math.max(0, Math.floor(chart.plotSizeY - Math.max(plotBorderWidth, clipOffset[2]) / 2 - clipY)) }; if (!skipAxes) { chart.axes.forEach(function (axis) { axis.setAxisSize(); axis.setAxisTranslation(); }); renderer.alignElements(); } fireEvent(chart, 'afterSetChartSize', { skipAxes: skipAxes }); }; /** * Initial margins before auto size margins are applied. * * @private * @function Highcharts.Chart#resetMargins */ Chart.prototype.resetMargins = function () { fireEvent(this, 'resetMargins'); var chart = this, chartOptions = chart.options.chart; // Create margin and spacing array ['margin', 'spacing'].forEach(function splashArrays(target) { var value = chartOptions[target], values = isObject(value) ? value : [value, value, value, value]; [ 'Top', 'Right', 'Bottom', 'Left' ].forEach(function (sideName, side) { chart[target][side] = pick(chartOptions[target + sideName], values[side]); }); }); // Set margin names like chart.plotTop, chart.plotLeft, // chart.marginRight, chart.marginBottom. marginNames.forEach(function (m, side) { chart[m] = pick(chart.margin[side], chart.spacing[side]); }); chart.axisOffset = [0, 0, 0, 0]; // top, right, bottom, left chart.clipOffset = [0, 0, 0, 0]; }; /** * Internal function to draw or redraw the borders and backgrounds for chart * and plot area. * * @private * @function Highcharts.Chart#drawChartBox * @fires Highcharts.Chart#event:afterDrawChartBox */ Chart.prototype.drawChartBox = function () { var chart = this, optionsChart = chart.options.chart, renderer = chart.renderer, chartWidth = chart.chartWidth, chartHeight = chart.chartHeight, styledMode = chart.styledMode, plotBGImage = chart.plotBGImage, chartBackgroundColor = optionsChart.backgroundColor, plotBackgroundColor = optionsChart.plotBackgroundColor, plotBackgroundImage = optionsChart.plotBackgroundImage, plotLeft = chart.plotLeft, plotTop = chart.plotTop, plotWidth = chart.plotWidth, plotHeight = chart.plotHeight, plotBox = chart.plotBox, clipRect = chart.clipRect, clipBox = chart.clipBox; var chartBackground = chart.chartBackground, plotBackground = chart.plotBackground, plotBorder = chart.plotBorder, chartBorderWidth, mgn, bgAttr, verb = 'animate'; // Chart area if (!chartBackground) { chart.chartBackground = chartBackground = renderer.rect() .addClass('highcharts-background') .add(); verb = 'attr'; } if (!styledMode) { // Presentational chartBorderWidth = optionsChart.borderWidth || 0; mgn = chartBorderWidth + (optionsChart.shadow ? 8 : 0); bgAttr = { fill: chartBackgroundColor || 'none' }; if (chartBorderWidth || chartBackground['stroke-width']) { // #980 bgAttr.stroke = optionsChart.borderColor; bgAttr['stroke-width'] = chartBorderWidth; } chartBackground .attr(bgAttr) .shadow(optionsChart.shadow); } else { chartBorderWidth = mgn = chartBackground.strokeWidth(); } chartBackground[verb]({ x: mgn / 2, y: mgn / 2, width: chartWidth - mgn - chartBorderWidth % 2, height: chartHeight - mgn - chartBorderWidth % 2, r: optionsChart.borderRadius }); // Plot background verb = 'animate'; if (!plotBackground) { verb = 'attr'; chart.plotBackground = plotBackground = renderer.rect() .addClass('highcharts-plot-background') .add(); } plotBackground[verb](plotBox); if (!styledMode) { // Presentational attributes for the background plotBackground .attr({ fill: plotBackgroundColor || 'none' }) .shadow(optionsChart.plotShadow); // Create the background image if (plotBackgroundImage) { if (!plotBGImage) { chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight).add(); } else { if (plotBackgroundImage !== plotBGImage.attr('href')) { plotBGImage.attr('href', plotBackgroundImage); } plotBGImage.animate(plotBox); } } } // Plot clip if (!clipRect) { chart.clipRect = renderer.clipRect(clipBox); } else { clipRect.animate({ width: clipBox.width, height: clipBox.height }); } // Plot area border verb = 'animate'; if (!plotBorder) { verb = 'attr'; chart.plotBorder = plotBorder = renderer.rect() .addClass('highcharts-plot-border') .attr({ zIndex: 1 // Above the grid }) .add(); } if (!styledMode) { // Presentational plotBorder.attr({ stroke: optionsChart.plotBorderColor, 'stroke-width': optionsChart.plotBorderWidth || 0, fill: 'none' }); } plotBorder[verb](plotBorder.crisp({ x: plotLeft, y: plotTop, width: plotWidth, height: plotHeight }, -plotBorder.strokeWidth())); // #3282 plotBorder should be negative; // reset chart.isDirtyBox = false; fireEvent(this, 'afterDrawChartBox'); }; /** * Detect whether a certain chart property is needed based on inspecting its * options and series. This mainly applies to the chart.inverted property, * and in extensions to the chart.angular and chart.polar properties. * * @private * @function Highcharts.Chart#propFromSeries * @return {void} */ Chart.prototype.propFromSeries = function () { var chart = this, optionsChart = chart.options.chart, seriesOptions = chart.options.series; var i, klass, value; /** * The flag is set to `true` if a series of the chart is inverted. * * @name Highcharts.Chart#inverted * @type {boolean|undefined} */ ['inverted', 'angular', 'polar'].forEach(function (key) { // The default series type's class klass = seriesTypes[(optionsChart.type || optionsChart.defaultSeriesType)]; // Get the value from available chart-wide properties value = // It is set in the options: optionsChart[key] || // The default series class: (klass && klass.prototype[key]); // requires it // 4. Check if any the chart's series require it i = seriesOptions && seriesOptions.length; while (!value && i--) { klass = seriesTypes[seriesOptions[i].type]; if (klass && klass.prototype[key]) { value = true; } } // Set the chart property chart[key] = value; }); }; /** * Internal function to link two or more series together, based on the * `linkedTo` option. This is done from `Chart.render`, and after * `Chart.addSeries` and `Series.remove`. * * @private * @function Highcharts.Chart#linkSeries * @fires Highcharts.Chart#event:afterLinkSeries */ Chart.prototype.linkSeries = function () { var chart = this, chartSeries = chart.series; // Reset links chartSeries.forEach(function (series) { series.linkedSeries.length = 0; }); // Apply new links chartSeries.forEach(function (series) { var linkedTo = series.options.linkedTo; if (isString(linkedTo)) { if (linkedTo === ':previous') { linkedTo = chart.series[series.index - 1]; } else { linkedTo = chart.get(linkedTo); } // #3341 avoid mutual linking if (linkedTo && linkedTo.linkedParent !== series) { linkedTo.linkedSeries.push(series); series.linkedParent = linkedTo; if (linkedTo.enabledDataSorting) { series.setDataSortingOptions(); } series.visible = pick(series.options.visible, linkedTo.options.visible, series.visible); // #3879 } } }); fireEvent(this, 'afterLinkSeries'); }; /** * Render series for the chart. * * @private * @function Highcharts.Chart#renderSeries */ Chart.prototype.renderSeries = function () { this.series.forEach(function (serie) { serie.translate(); serie.render(); }); }; /** * Render labels for the chart. * * @private * @function Highcharts.Chart#renderLabels */ Chart.prototype.renderLabels = function () { var chart = this, labels = chart.options.labels; if (labels.items) { labels.items.forEach(function (label) { var style = extend(labels.style, label.style), x = pInt(style.left) + chart.plotLeft, y = pInt(style.top) + chart.plotTop + 12; // delete to prevent rewriting in IE delete style.left; delete style.top; chart.renderer.text(label.html, x, y) .attr({ zIndex: 2 }) .css(style) .add(); }); } }; /** * Render all graphics for the chart. Runs internally on initialization. * * @private * @function Highcharts.Chart#render */ Chart.prototype.render = function () { var chart = this, axes = chart.axes, colorAxis = chart.colorAxis, renderer = chart.renderer, options = chart.options, renderAxes = function (axes) { axes.forEach(function (axis) { if (axis.visible) { axis.render(); } }); }; var correction = 0; // correction for X axis labels // Title chart.setTitle(); /** * The overview of the chart's series. * * @name Highcharts.Chart#legend * @type {Highcharts.Legend} */ chart.legend = new Legend(chart, options.legend); // Get stacks if (chart.getStacks) { chart.getStacks(); } // Get chart margins chart.getMargins(true); chart.setChartSize(); // Record preliminary dimensions for later comparison var tempWidth = chart.plotWidth; axes.some(function (axis) { if (axis.horiz && axis.visible && axis.options.labels.enabled && axis.series.length) { // 21 is the most common correction for X axis labels correction = 21; return true; } }); // use Math.max to prevent negative plotHeight chart.plotHeight = Math.max(chart.plotHeight - correction, 0); var tempHeight = chart.plotHeight; // Get margins by pre-rendering axes axes.forEach(function (axis) { axis.setScale(); }); chart.getAxisMargins(); // If the plot area size has changed significantly, calculate tick // positions again var redoHorizontal = tempWidth / chart.plotWidth > 1.1; // Height is more sensitive, use lower threshold var redoVertical = tempHeight / chart.plotHeight > 1.05; if (redoHorizontal || redoVertical) { axes.forEach(function (axis) { if ((axis.horiz && redoHorizontal) || (!axis.horiz && redoVertical)) { // update to reflect the new margins axis.setTickInterval(true); } }); chart.getMargins(); // second pass to check for new labels } // Draw the borders and backgrounds chart.drawChartBox(); // Axes if (chart.hasCartesianSeries) { renderAxes(axes); } else if (colorAxis && colorAxis.length) { renderAxes(colorAxis); } // The series if (!chart.seriesGroup) { chart.seriesGroup = renderer.g('series-group') .attr({ zIndex: 3 }) .add(); } chart.renderSeries(); // Labels chart.renderLabels(); // Credits chart.addCredits(); // Handle responsiveness if (chart.setResponsive) { chart.setResponsive(); } // Set flag chart.hasRendered = true; }; /** * Set a new credits label for the chart. * * @sample highcharts/credits/credits-update/ * Add and update credits * * @function Highcharts.Chart#addCredits * * @param {Highcharts.CreditsOptions} [credits] * A configuration object for the new credits. */ Chart.prototype.addCredits = function (credits) { var chart = this, creds = merge(true, this.options.credits, credits); if (creds.enabled && !this.credits) { /** * The chart's credits label. The label has an `update` method that * allows setting new options as per the * [credits options set](https://api.highcharts.com/highcharts/credits). * * @name Highcharts.Chart#credits * @type {Highcharts.SVGElement} */ this.credits = this.renderer.text(creds.text + (this.mapCredits || ''), 0, 0) .addClass('highcharts-credits') .on('click', function () { if (creds.href) { win.location.href = creds.href; } }) .attr({ align: creds.position.align, zIndex: 8 }); if (!chart.styledMode) { this.credits.css(creds.style); } this.credits .add() .align(creds.position); // Dynamically update this.credits.update = function (options) { chart.credits = chart.credits.destroy(); chart.addCredits(options); }; } }; /** * Remove the chart and purge memory. This method is called internally * before adding a second chart into the same container, as well as on * window unload to prevent leaks. * * @sample highcharts/members/chart-destroy/ * Destroy the chart from a button * @sample stock/members/chart-destroy/ * Destroy with Highcharts Stock * * @function Highcharts.Chart#destroy * * @fires Highcharts.Chart#event:destroy */ Chart.prototype.destroy = function () { var chart = this, axes = chart.axes, series = chart.series, container = chart.container, parentNode = container && container.parentNode; var i; // fire the chart.destoy event fireEvent(chart, 'destroy'); // Delete the chart from charts lookup array if (chart.renderer.forExport) { erase(charts, chart); // #6569 } else { charts[chart.index] = void 0; } H.chartCount--; chart.renderTo.removeAttribute('data-highcharts-chart'); // remove events removeEvent(chart); // ==== Destroy collections: // Destroy axes i = axes.length; while (i--) { axes[i] = axes[i].destroy(); } // Destroy scroller & scroller series before destroying base series if (this.scroller && this.scroller.destroy) { this.scroller.destroy(); } // Destroy each series i = series.length; while (i--) { series[i] = series[i].destroy(); } // ==== Destroy chart properties: [ 'title', 'subtitle', 'chartBackground', 'plotBackground', 'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits', 'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip', 'renderer' ].forEach(function (name) { var prop = chart[name]; if (prop && prop.destroy) { chart[name] = prop.destroy(); } }); // Remove container and all SVG, check container as it can break in IE // when destroyed before finished loading if (container) { container.innerHTML = ''; removeEvent(container); if (parentNode) { discardElement(container); } } // clean it all up objectEach(chart, function (val, key) { delete chart[key]; }); }; /** * Prepare for first rendering after all data are loaded. * * @private * @function Highcharts.Chart#firstRender * @fires Highcharts.Chart#event:beforeRender */ Chart.prototype.firstRender = function () { var chart = this, options = chart.options; // Hook for oldIE to check whether the chart is ready to render if (chart.isReadyToRender && !chart.isReadyToRender()) { return; } // Create the container chart.getContainer(); chart.resetMargins(); chart.setChartSize(); // Set the common chart properties (mainly invert) from the given series chart.propFromSeries(); // get axes chart.getAxes(); // Initialize the series (isArray(options.series) ? options.series : []).forEach( // #9680 function (serieOptions) { chart.initSeries(serieOptions); }); chart.linkSeries(); chart.setSeriesData(); // Run an event after axes and series are initialized, but before // render. At this stage, the series data is indexed and cached in the // xData and yData arrays, so we can access those before rendering. Used // in Highcharts Stock. fireEvent(chart, 'beforeRender'); // depends on inverted and on margins being set if (Pointer) { if (MSPointer.isRequired()) { chart.pointer = new MSPointer(chart, options); } else { /** * The Pointer that keeps track of mouse and touch interaction. * * @memberof Highcharts.Chart * @name pointer * @type {Highcharts.Pointer} * @instance */ chart.pointer = new Pointer(chart, options); } } chart.render(); chart.pointer.getChartPosition(); // #14973 // Fire the load event if there are no external images if (!chart.renderer.imgCount && !chart.hasLoaded) { chart.onload(); } // If the chart was rendered outside the top container, put it back in // (#3679) chart.temporaryDisplay(true); }; /** * Internal function that runs on chart load, async if any images are loaded * in the chart. Runs the callbacks and triggers the `load` and `render` * events. * * @private * @function Highcharts.Chart#onload * @fires Highcharts.Chart#event:load * @fires Highcharts.Chart#event:render */ Chart.prototype.onload = function () { // Run callbacks, first the ones registered by modules, then user's one this.callbacks.concat([this.callback]).forEach(function (fn) { // Chart destroyed in its own callback (#3600) if (fn && typeof this.index !== 'undefined') { fn.apply(this, [this]); } }, this); fireEvent(this, 'load'); fireEvent(this, 'render'); // Set up auto resize, check for not destroyed (#6068) if (defined(this.index)) { this.setReflow(this.options.chart.reflow); } // Don't run again this.hasLoaded = true; }; /** * Add a series to the chart after render time. Note that this method should * never be used when adding data synchronously at chart render time, as it * adds expense to the calculations and rendering. When adding data at the * same time as the chart is initialized, add the series as a configuration * option instead. With multiple axes, the `offset` is dynamically adjusted. * * @sample highcharts/members/chart-addseries/ * Add a series from a button * @sample stock/members/chart-addseries/ * Add a series in Highcharts Stock * * @function Highcharts.Chart#addSeries * * @param {Highcharts.SeriesOptionsType} options * The config options for the series. * * @param {boolean} [redraw=true] * Whether to redraw the chart after adding. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] * Whether to apply animation, and optionally animation * configuration. * * @return {Highcharts.Series} * The newly created series object. * * @fires Highcharts.Chart#event:addSeries * @fires Highcharts.Chart#event:afterAddSeries */ Chart.prototype.addSeries = function (options, redraw, animation) { var chart = this; var series; if (options) { // <- not necessary redraw = pick(redraw, true); // defaults to true fireEvent(chart, 'addSeries', { options: options }, function () { series = chart.initSeries(options); chart.isDirtyLegend = true; chart.linkSeries(); if (series.enabledDataSorting) { // We need to call `setData` after `linkSeries` series.setData(options.data, false); } fireEvent(chart, 'afterAddSeries', { series: series }); if (redraw) { chart.redraw(animation); } }); } return series; }; /** * Add an axis to the chart after render time. Note that this method should * never be used when adding data synchronously at chart render time, as it * adds expense to the calculations and rendering. When adding data at the * same time as the chart is initialized, add the axis as a configuration * option instead. * * @sample highcharts/members/chart-addaxis/ * Add and remove axes * * @function Highcharts.Chart#addAxis * * @param {Highcharts.AxisOptions} options * The axis options. * * @param {boolean} [isX=false] * Whether it is an X axis or a value axis. * * @param {boolean} [redraw=true] * Whether to redraw the chart after adding. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] * Whether and how to apply animation in the redraw. * * @return {Highcharts.Axis} * The newly generated Axis object. */ Chart.prototype.addAxis = function (options, isX, redraw, animation) { return this.createAxis(isX ? 'xAxis' : 'yAxis', { axis: options, redraw: redraw, animation: animation }); }; /** * Add a color axis to the chart after render time. Note that this method * should never be used when adding data synchronously at chart render time, * as it adds expense to the calculations and rendering. When adding data at * the same time as the chart is initialized, add the axis as a * configuration option instead. * * @sample highcharts/members/chart-addaxis/ * Add and remove axes * * @function Highcharts.Chart#addColorAxis * * @param {Highcharts.ColorAxisOptions} options * The axis options. * * @param {boolean} [redraw=true] * Whether to redraw the chart after adding. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] * Whether and how to apply animation in the redraw. * * @return {Highcharts.ColorAxis} * The newly generated Axis object. */ Chart.prototype.addColorAxis = function (options, redraw, animation) { return this.createAxis('colorAxis', { axis: options, redraw: redraw, animation: animation }); }; /** * Factory for creating different axis types. * * @private * @function Highcharts.Chart#createAxis * * @param {string} type * An axis type. * * @param {...Array<*>} arguments * All arguments for the constructor. * * @return {Highcharts.Axis | Highcharts.ColorAxis} * The newly generated Axis object. */ Chart.prototype.createAxis = function (type, options) { var isColorAxis = type === 'colorAxis', axisOptions = options.axis, redraw = options.redraw, animation = options.animation, userOptions = merge(axisOptions, { index: this[type].length, isX: type === 'xAxis' }); var axis; if (isColorAxis) { axis = new H.ColorAxis(this, userOptions); } else { axis = new Axis(this, userOptions); } if (isColorAxis) { this.isDirtyLegend = true; // Clear before 'bindAxes' (#11924) this.axes.forEach(function (axis) { axis.series = []; }); this.series.forEach(function (series) { series.bindAxes(); series.isDirtyData = true; }); } if (pick(redraw, true)) { this.redraw(animation); } return axis; }; /** * Dim the chart and show a loading text or symbol. Options for the loading * screen are defined in {@link * https://api.highcharts.com/highcharts/loading|the loading options}. * * @sample highcharts/members/chart-hideloading/ * Show and hide loading from a button * @sample highcharts/members/chart-showloading/ * Apply different text labels * @sample stock/members/chart-show-hide-loading/ * Toggle loading in Highcharts Stock * * @function Highcharts.Chart#showLoading * * @param {string} [str] * An optional text to show in the loading label instead of the * default one. The default text is set in * [lang.loading](https://api.highcharts.com/highcharts/lang.loading). */ Chart.prototype.showLoading = function (str) { var chart = this, options = chart.options, loadingOptions = options.loading, setLoadingSize = function () { if (loadingDiv) { css(loadingDiv, { left: chart.plotLeft + 'px', top: chart.plotTop + 'px', width: chart.plotWidth + 'px', height: chart.plotHeight + 'px' }); } }; var loadingDiv = chart.loadingDiv, loadingSpan = chart.loadingSpan; // create the layer at the first call if (!loadingDiv) { chart.loadingDiv = loadingDiv = createElement('div', { className: 'highcharts-loading highcharts-loading-hidden' }, null, chart.container); } if (!loadingSpan) { chart.loadingSpan = loadingSpan = createElement('span', { className: 'highcharts-loading-inner' }, null, loadingDiv); addEvent(chart, 'redraw', setLoadingSize); // #1080 } loadingDiv.className = 'highcharts-loading'; // Update text AST.setElementHTML(loadingSpan, pick(str, options.lang.loading, '')); if (!chart.styledMode) { // Update visuals css(loadingDiv, extend(loadingOptions.style, { zIndex: 10 })); css(loadingSpan, loadingOptions.labelStyle); // Show it if (!chart.loadingShown) { css(loadingDiv, { opacity: 0, display: '' }); animate(loadingDiv, { opacity: loadingOptions.style.opacity || 0.5 }, { duration: loadingOptions.showDuration || 0 }); } } chart.loadingShown = true; setLoadingSize(); }; /** * Hide the loading layer. * * @see Highcharts.Chart#showLoading * * @sample highcharts/members/chart-hideloading/ * Show and hide loading from a button * @sample stock/members/chart-show-hide-loading/ * Toggle loading in Highcharts Stock * * @function Highcharts.Chart#hideLoading */ Chart.prototype.hideLoading = function () { var options = this.options, loadingDiv = this.loadingDiv; if (loadingDiv) { loadingDiv.className = 'highcharts-loading highcharts-loading-hidden'; if (!this.styledMode) { animate(loadingDiv, { opacity: 0 }, { duration: options.loading.hideDuration || 100, complete: function () { css(loadingDiv, { display: 'none' }); } }); } } this.loadingShown = false; }; /** * A generic function to update any element of the chart. Elements can be * enabled and disabled, moved, re-styled, re-formatted etc. * * A special case is configuration objects that take arrays, for example * [xAxis](https://api.highcharts.com/highcharts/xAxis), * [yAxis](https://api.highcharts.com/highcharts/yAxis) or * [series](https://api.highcharts.com/highcharts/series). For these * collections, an `id` option is used to map the new option set to an * existing object. If an existing object of the same id is not found, the * corresponding item is updated. So for example, running `chart.update` * with a series item without an id, will cause the existing chart's series * with the same index in the series array to be updated. When the * `oneToOne` parameter is true, `chart.update` will also take care of * adding and removing items from the collection. Read more under the * parameter description below. * * Note that when changing series data, `chart.update` may mutate the passed * data options. * * See also the * [responsive option set](https://api.highcharts.com/highcharts/responsive). * Switching between `responsive.rules` basically runs `chart.update` under * the hood. * * @sample highcharts/members/chart-update/ * Update chart geometry * * @function Highcharts.Chart#update * * @param {Highcharts.Options} options * A configuration object for the new chart options. * * @param {boolean} [redraw=true] * Whether to redraw the chart. * * @param {boolean} [oneToOne=false] * When `true`, the `series`, `xAxis`, `yAxis` and `annotations` * collections will be updated one to one, and items will be either * added or removed to match the new updated options. For example, * if the chart has two series and we call `chart.update` with a * configuration containing three series, one will be added. If we * call `chart.update` with one series, one will be removed. Setting * an empty `series` array will remove all series, but leaving out * the`series` property will leave all series untouched. If the * series have id's, the new series options will be matched by id, * and the remaining ones removed. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true] * Whether to apply animation, and optionally animation * configuration. * * @fires Highcharts.Chart#event:update * @fires Highcharts.Chart#event:afterUpdate */ Chart.prototype.update = function (options, redraw, oneToOne, animation) { var chart = this, adders = { credits: 'addCredits', title: 'setTitle', subtitle: 'setSubtitle', caption: 'setCaption' }, isResponsiveOptions = options.isResponsiveOptions, itemsForRemoval = []; var updateAllAxes, updateAllSeries, runSetSize; fireEvent(chart, 'update', { options: options }); // If there are responsive rules in action, undo the responsive rules // before we apply the updated options and replay the responsive rules // on top from the chart.redraw function (#9617). if (!isResponsiveOptions) { chart.setResponsive(false, true); } options = cleanRecursively(options, chart.options); chart.userOptions = merge(chart.userOptions, options); // If the top-level chart option is present, some special updates are // required var optionsChart = options.chart; if (optionsChart) { merge(true, chart.options.chart, optionsChart); // Setter function if ('className' in optionsChart) { chart.setClassName(optionsChart.className); } if ('reflow' in optionsChart) { chart.setReflow(optionsChart.reflow); } if ('inverted' in optionsChart || 'polar' in optionsChart || 'type' in optionsChart) { // Parse options.chart.inverted and options.chart.polar together // with the available series. chart.propFromSeries(); updateAllAxes = true; } if ('alignTicks' in optionsChart) { // #6452 updateAllAxes = true; } if ('events' in optionsChart) { // Chart event handlers registerEventOptions(this, optionsChart); } objectEach(optionsChart, function (val, key) { if (chart.propsRequireUpdateSeries.indexOf('chart.' + key) !== -1) { updateAllSeries = true; } // Only dirty box if (chart.propsRequireDirtyBox.indexOf(key) !== -1) { chart.isDirtyBox = true; } // Chart setSize if (chart.propsRequireReflow.indexOf(key) !== -1) { if (isResponsiveOptions) { chart.isDirtyBox = true; } else { runSetSize = true; } } }); if (!chart.styledMode && 'style' in optionsChart) { chart.renderer.setStyle(optionsChart.style); } } // Moved up, because tooltip needs updated plotOptions (#6218) if (!chart.styledMode && options.colors) { this.options.colors = options.colors; } if (options.time) { // Maintaining legacy global time. If the chart is instanciated // first with global time, then updated with time options, we need // to create a new Time instance to avoid mutating the global time // (#10536). if (this.time === defaultTime) { this.time = new Time(options.time); } // If we're updating, the time class is different from other chart // classes (chart.legend, chart.tooltip etc) in that it doesn't know // about the chart. The other chart[something].update functions also // set the chart.options[something]. For the time class however we // need to update the chart options separately. #14230. merge(true, chart.options.time, options.time); } // Some option stuctures correspond one-to-one to chart objects that // have update methods, for example // options.credits => chart.credits // options.legend => chart.legend // options.title => chart.title // options.tooltip => chart.tooltip // options.subtitle => chart.subtitle // options.mapNavigation => chart.mapNavigation // options.navigator => chart.navigator // options.scrollbar => chart.scrollbar objectEach(options, function (val, key) { if (chart[key] && typeof chart[key].update === 'function') { chart[key].update(val, false); // If a one-to-one object does not exist, look for an adder function } else if (typeof chart[adders[key]] === 'function') { chart[adders[key]](val); // Else, just merge the options. For nodes like loading, noData, // plotOptions } else if (key !== 'colors' && chart.collectionsWithUpdate.indexOf(key) === -1) { merge(true, chart.options[key], options[key]); } if (key !== 'chart' && chart.propsRequireUpdateSeries.indexOf(key) !== -1) { updateAllSeries = true; } }); // Setters for collections. For axes and series, each item is referred // by an id. If the id is not found, it defaults to the corresponding // item in the collection, so setting one series without an id, will // update the first series in the chart. Setting two series without // an id will update the first and the second respectively (#6019) // chart.update and responsive. this.collectionsWithUpdate.forEach(function (coll) { var indexMap; if (options[coll]) { // In stock charts, the navigator series are also part of the // chart.series array, but those series should not be handled // here (#8196) and neither should the navigator axis (#9671). indexMap = []; chart[coll].forEach(function (s, i) { if (!s.options.isInternal) { indexMap.push(pick(s.options.index, i)); } }); splat(options[coll]).forEach(function (newOptions, i) { var hasId = defined(newOptions.id); var item; // Match by id if (hasId) { item = chart.get(newOptions.id); } // No match by id found, match by index instead if (!item && chart[coll]) { item = chart[coll][indexMap ? indexMap[i] : i]; // Check if we grabbed an item with an exising but // different id (#13541) if (item && hasId && defined(item.options.id)) { item = void 0; } } if (item && item.coll === coll) { item.update(newOptions, false); if (oneToOne) { item.touched = true; } } // If oneToOne and no matching item is found, add one if (!item && oneToOne && chart.collectionsWithInit[coll]) { chart.collectionsWithInit[coll][0].apply(chart, // [newOptions, ...extraArguments, redraw=false] [ newOptions ].concat( // Not all initializers require extra args chart.collectionsWithInit[coll][1] || []).concat([ false ])).touched = true; } }); // Add items for removal if (oneToOne) { chart[coll].forEach(function (item) { if (!item.touched && !item.options.isInternal) { itemsForRemoval.push(item); } else { delete item.touched; } }); } } }); itemsForRemoval.forEach(function (item) { if (item.chart) { // #9097, avoid removing twice item.remove(false); } }); if (updateAllAxes) { chart.axes.forEach(function (axis) { axis.update({}, false); }); } // Certain options require the whole series structure to be thrown away // and rebuilt if (updateAllSeries) { chart.getSeriesOrderByLinks().forEach(function (series) { // Avoid removed navigator series if (series.chart) { series.update({}, false); } }, this); } // Update size. Redraw is forced. var newWidth = optionsChart && optionsChart.width; var newHeight = optionsChart && (isString(optionsChart.height) ? relativeLength(optionsChart.height, newWidth || chart.chartWidth) : optionsChart.height); if ( // In this case, run chart.setSize with newWidth and newHeight which // are undefined, only for reflowing chart elements because margin // or spacing has been set (#8190) runSetSize || // In this case, the size is actually set (isNumber(newWidth) && newWidth !== chart.chartWidth) || (isNumber(newHeight) && newHeight !== chart.chartHeight)) { chart.setSize(newWidth, newHeight, animation); } else if (pick(redraw, true)) { chart.redraw(animation); } fireEvent(chart, 'afterUpdate', { options: options, redraw: redraw, animation: animation }); }; /** * Shortcut to set the subtitle options. This can also be done from {@link * Chart#update} or {@link Chart#setTitle}. * * @function Highcharts.Chart#setSubtitle * * @param {Highcharts.SubtitleOptions} options * New subtitle options. The subtitle text itself is set by the * `options.text` property. */ Chart.prototype.setSubtitle = function (options, redraw) { this.applyDescription('subtitle', options); this.layOutTitles(redraw); }; /** * Set the caption options. This can also be done from {@link * Chart#update}. * * @function Highcharts.Chart#setCaption * * @param {Highcharts.CaptionOptions} options * New caption options. The caption text itself is set by the * `options.text` property. */ Chart.prototype.setCaption = function (options, redraw) { this.applyDescription('caption', options); this.layOutTitles(redraw); }; /** * Display the zoom button, so users can reset zoom to the default view * settings. * * @function Highcharts.Chart#showResetZoom * * @fires Highcharts.Chart#event:afterShowResetZoom * @fires Highcharts.Chart#event:beforeShowResetZoom */ Chart.prototype.showResetZoom = function () { var chart = this, lang = defaultOptions.lang, btnOptions = chart.options.chart.resetZoomButton, theme = btnOptions.theme, states = theme.states, alignTo = (btnOptions.relativeTo === 'chart' || btnOptions.relativeTo === 'spacingBox' ? null : 'scrollablePlotBox'); /** * @private */ function zoomOut() { chart.zoomOut(); } fireEvent(this, 'beforeShowResetZoom', null, function () { chart.resetZoomButton = chart.renderer .button(lang.resetZoom, null, null, zoomOut, theme, states && states.hover) .attr({ align: btnOptions.position.align, title: lang.resetZoomTitle }) .addClass('highcharts-reset-zoom') .add() .align(btnOptions.position, false, alignTo); }); fireEvent(this, 'afterShowResetZoom'); }; /** * Zoom the chart out after a user has zoomed in. See also * [Axis.setExtremes](/class-reference/Highcharts.Axis#setExtremes). * * @function Highcharts.Chart#zoomOut * * @fires Highcharts.Chart#event:selection */ Chart.prototype.zoomOut = function () { fireEvent(this, 'selection', { resetSelection: true }, this.zoom); }; /** * Zoom into a given portion of the chart given by axis coordinates. * * @private * @function Highcharts.Chart#zoom * @param {Highcharts.SelectEventObject} event */ Chart.prototype.zoom = function (event) { var chart = this, pointer = chart.pointer, mouseDownPos = (chart.inverted ? pointer.mouseDownX : pointer.mouseDownY); var displayButton = false, hasZoomed; // If zoom is called with no arguments, reset the axes if (!event || event.resetSelection) { chart.axes.forEach(function (axis) { hasZoomed = axis.zoom(); }); pointer.initiated = false; // #6804 } else { // else, zoom in on all axes event.xAxis.concat(event.yAxis).forEach(function (axisData) { var axis = axisData.axis, axisStartPos = chart.inverted ? axis.left : axis.top, axisEndPos = chart.inverted ? axisStartPos + axis.width : axisStartPos + axis.height, isXAxis = axis.isXAxis; var isWithinPane = false; // Check if zoomed area is within the pane (#1289). // In case of multiple panes only one pane should be zoomed. if ((!isXAxis && mouseDownPos >= axisStartPos && mouseDownPos <= axisEndPos) || isXAxis || !defined(mouseDownPos)) { isWithinPane = true; } // don't zoom more than minRange if (pointer[isXAxis ? 'zoomX' : 'zoomY'] && isWithinPane) { hasZoomed = axis.zoom(axisData.min, axisData.max); if (axis.displayBtn) { displayButton = true; } } }); } // Show or hide the Reset zoom button var resetZoomButton = chart.resetZoomButton; if (displayButton && !resetZoomButton) { chart.showResetZoom(); } else if (!displayButton && isObject(resetZoomButton)) { chart.resetZoomButton = resetZoomButton.destroy(); } // Redraw if (hasZoomed) { chart.redraw(pick(chart.options.chart.animation, event && event.animation, chart.pointCount < 100)); } }; /** * Pan the chart by dragging the mouse across the pane. This function is * called on mouse move, and the distance to pan is computed from chartX * compared to the first chartX position in the dragging operation. * * @private * @function Highcharts.Chart#pan * @param {Highcharts.PointerEventObject} e * @param {string} panning */ Chart.prototype.pan = function (e, panning) { var chart = this, hoverPoints = chart.hoverPoints, panningOptions = (typeof panning === 'object' ? panning : { enabled: panning, type: 'x' }), chartOptions = chart.options.chart, hasMapNavigation = chart.options.mapNavigation && chart.options.mapNavigation.enabled; if (chartOptions && chartOptions.panning) { chartOptions.panning = panningOptions; } var type = panningOptions.type; var doRedraw; fireEvent(this, 'pan', { originalEvent: e }, function () { // remove active points for shared tooltip if (hoverPoints) { hoverPoints.forEach(function (point) { point.setState(); }); } var axes = chart.xAxis; if (type === 'xy') { axes = axes.concat(chart.yAxis); } else if (type === 'y') { axes = chart.yAxis; } var nextMousePos = {}; axes.forEach(function (axis) { if (!axis.options.panningEnabled || axis.options.isInternal) { return; } var horiz = axis.horiz, mousePos = e[horiz ? 'chartX' : 'chartY'], mouseDown = horiz ? 'mouseDownX' : 'mouseDownY', startPos = chart[mouseDown], halfPointRange = axis.minPointOffset || 0, pointRangeDirection = (axis.reversed && !chart.inverted) || (!axis.reversed && chart.inverted) ? -1 : 1, extremes = axis.getExtremes(), panMin = axis.toValue(startPos - mousePos, true) + halfPointRange * pointRangeDirection, panMax = axis.toValue(startPos + axis.len - mousePos, true) - ((halfPointRange * pointRangeDirection) || (axis.isXAxis && axis.pointRangePadding) || 0), flipped = panMax < panMin, hasVerticalPanning = axis.hasVerticalPanning(); var newMin = flipped ? panMax : panMin, newMax = flipped ? panMin : panMax, panningState = axis.panningState, spill; // General calculations of panning state. // This is related to using vertical panning. (#11315). if (hasVerticalPanning && !axis.isXAxis && (!panningState || panningState.isDirty)) { axis.series.forEach(function (series) { var processedData = series.getProcessedData(true), dataExtremes = series.getExtremes(processedData.yData, true); if (!panningState) { panningState = { startMin: Number.MAX_VALUE, startMax: -Number.MAX_VALUE }; } if (isNumber(dataExtremes.dataMin) && isNumber(dataExtremes.dataMax)) { panningState.startMin = Math.min(pick(series.options.threshold, Infinity), dataExtremes.dataMin, panningState.startMin); panningState.startMax = Math.max(pick(series.options.threshold, -Infinity), dataExtremes.dataMax, panningState.startMax); } }); } var paddedMin = Math.min(pick(panningState && panningState.startMin, extremes.dataMin), halfPointRange ? extremes.min : axis.toValue(axis.toPixels(extremes.min) - axis.minPixelPadding)); var paddedMax = Math.max(pick(panningState && panningState.startMax, extremes.dataMax), halfPointRange ? extremes.max : axis.toValue(axis.toPixels(extremes.max) + axis.minPixelPadding)); axis.panningState = panningState; // It is not necessary to calculate extremes on ordinal axis, // because they are already calculated, so we don't want to // override them. if (!axis.isOrdinal) { // If the new range spills over, either to the min or max, // adjust the new range. spill = paddedMin - newMin; if (spill > 0) { newMax += spill; newMin = paddedMin; } spill = newMax - paddedMax; if (spill > 0) { newMax = paddedMax; newMin -= spill; } // Set new extremes if they are actually new if (axis.series.length && newMin !== extremes.min && newMax !== extremes.max && newMin >= paddedMin && newMax <= paddedMax) { axis.setExtremes(newMin, newMax, false, false, { trigger: 'pan' }); if (!chart.resetZoomButton && !hasMapNavigation && // Show reset zoom button only when both newMin and // newMax values are between padded axis range. newMin !== paddedMin && newMax !== paddedMax && type.match('y')) { chart.showResetZoom(); axis.displayBtn = false; } doRedraw = true; } // set new reference for next run: nextMousePos[mouseDown] = mousePos; } }); objectEach(nextMousePos, function (pos, down) { chart[down] = pos; }); if (doRedraw) { chart.redraw(false); } css(chart.container, { cursor: 'move' }); }); }; return Chart; }()); extend(Chart.prototype, { // Hook for adding callbacks in modules callbacks: [], /** * These collections (arrays) implement `Chart.addSomethig` method used in * chart.update() to create new object in the collection. Equivalent for * deleting is resolved by simple `Somethig.remove()`. * * Note: We need to define these references after initializers are bound to * chart's prototype. */ collectionsWithInit: { // collectionName: [ initializingMethod, [extraArguments] ] xAxis: [Chart.prototype.addAxis, [true]], yAxis: [Chart.prototype.addAxis, [false]], series: [Chart.prototype.addSeries] }, /** * These collections (arrays) implement update() methods with support for * one-to-one option. */ collectionsWithUpdate: [ 'xAxis', 'yAxis', 'zAxis', 'series' ], /** * These properties cause isDirtyBox to be set to true when updating. Can be * extended from plugins. */ propsRequireDirtyBox: [ 'backgroundColor', 'borderColor', 'borderWidth', 'borderRadius', 'plotBackgroundColor', 'plotBackgroundImage', 'plotBorderColor', 'plotBorderWidth', 'plotShadow', 'shadow' ], /** * These properties require a full reflow of chart elements, best * implemented through running `Chart.setSize` internally (#8190). * @type {Array} */ propsRequireReflow: [ 'margin', 'marginTop', 'marginRight', 'marginBottom', 'marginLeft', 'spacing', 'spacingTop', 'spacingRight', 'spacingBottom', 'spacingLeft' ], /** * These properties cause all series to be updated when updating. Can be * extended from plugins. */ propsRequireUpdateSeries: [ 'chart.inverted', 'chart.polar', 'chart.ignoreHiddenSeries', 'chart.type', 'colors', 'plotOptions', 'time', 'tooltip' ] }); /* * * * Export * * */ /* * * * API Declarations * * */ /** * Callback for chart constructors. * * @callback Highcharts.ChartCallbackFunction * * @param {Highcharts.Chart} chart * Created chart. */ /** * Format a number and return a string based on input settings. * * @callback Highcharts.NumberFormatterCallbackFunction * * @param {number} number * The input number to format. * * @param {number} decimals * The amount of decimals. A value of -1 preserves the amount in the * input number. * * @param {string} [decimalPoint] * The decimal point, defaults to the one given in the lang options, or * a dot. * * @param {string} [thousandsSep] * The thousands separator, defaults to the one given in the lang * options, or a space character. * * @return {string} The formatted number. */ /** * The chart title. The title has an `update` method that allows modifying the * options directly or indirectly via `chart.update`. * * @interface Highcharts.TitleObject * @extends Highcharts.SVGElement */ /** * Modify options for the title. * * @function Highcharts.TitleObject#update * * @param {Highcharts.TitleOptions} titleOptions * Options to modify. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the title is altered. If doing more * operations on the chart, it is a good idea to set redraw to false and * call {@link Chart#redraw} after. */ /** * The chart subtitle. The subtitle has an `update` method that * allows modifying the options directly or indirectly via * `chart.update`. * * @interface Highcharts.SubtitleObject * @extends Highcharts.SVGElement */ /** * Modify options for the subtitle. * * @function Highcharts.SubtitleObject#update * * @param {Highcharts.SubtitleOptions} subtitleOptions * Options to modify. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the subtitle is altered. If doing * more operations on the chart, it is a good idea to set redraw to false * and call {@link Chart#redraw} after. */ /** * The chart caption. The caption has an `update` method that * allows modifying the options directly or indirectly via * `chart.update`. * * @interface Highcharts.CaptionObject * @extends Highcharts.SVGElement */ /** * Modify options for the caption. * * @function Highcharts.CaptionObject#update * * @param {Highcharts.CaptionOptions} captionOptions * Options to modify. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the caption is altered. If doing * more operations on the chart, it is a good idea to set redraw to false * and call {@link Chart#redraw} after. */ /** * @interface Highcharts.ChartIsInsideOptionsObject */ /** * @name Highcharts.ChartIsInsideOptionsObject#ignoreX * @type {boolean|undefined} */ /** * @name Highcharts.ChartIsInsideOptionsObject#ignoreY * @type {boolean|undefined} */ /** * @name Highcharts.ChartIsInsideOptionsObject#inverted * @type {boolean|undefined} */ /** * @name Highcharts.ChartIsInsideOptionsObject#paneCoordinates * @type {boolean|undefined} */ /** * @name Highcharts.ChartIsInsideOptionsObject#series * @type {Highcharts.Series|undefined} */ /** * @name Highcharts.ChartIsInsideOptionsObject#visiblePlotOnly * @type {boolean|undefined} */ ''; // include doclets above in transpilat return Chart; }); _registerModule(_modules, 'Mixins/LegendSymbol.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var merge = U.merge, pick = U.pick; /* eslint-disable valid-jsdoc */ /** * Legend symbol mixin. * * @private * @mixin Highcharts.LegendSymbolMixin */ var LegendSymbolMixin = H.LegendSymbolMixin = { /** * Get the series' symbol in the legend * * @private * @function Highcharts.LegendSymbolMixin.drawRectangle * * @param {Highcharts.Legend} legend * The legend object * * @param {Highcharts.Point|Highcharts.Series} item * The series (this) or point */ drawRectangle: function (legend, item) { var options = legend.options, symbolHeight = legend.symbolHeight, square = options.squareSymbol, symbolWidth = square ? symbolHeight : legend.symbolWidth; item.legendSymbol = this.chart.renderer.rect(square ? (legend.symbolWidth - symbolHeight) / 2 : 0, legend.baseline - symbolHeight + 1, // #3988 symbolWidth, symbolHeight, pick(legend.options.symbolRadius, symbolHeight / 2)) .addClass('highcharts-point') .attr({ zIndex: 3 }).add(item.legendGroup); }, /** * Get the series' symbol in the legend. This method should be overridable * to create custom symbols through * Highcharts.seriesTypes[type].prototype.drawLegendSymbols. * * @private * @function Highcharts.LegendSymbolMixin.drawLineMarker * * @param {Highcharts.Legend} legend * The legend object. */ drawLineMarker: function (legend) { var options = this.options, markerOptions = options.marker, radius, legendSymbol, symbolWidth = legend.symbolWidth, symbolHeight = legend.symbolHeight, generalRadius = symbolHeight / 2, renderer = this.chart.renderer, legendItemGroup = this.legendGroup, verticalCenter = legend.baseline - Math.round(legend.fontMetrics.b * 0.3), attr = {}; // Draw the line if (!this.chart.styledMode) { attr = { 'stroke-width': options.lineWidth || 0 }; if (options.dashStyle) { attr.dashstyle = options.dashStyle; } } this.legendLine = renderer .path([ ['M', 0, verticalCenter], ['L', symbolWidth, verticalCenter] ]) .addClass('highcharts-graph') .attr(attr) .add(legendItemGroup); // Draw the marker if (markerOptions && markerOptions.enabled !== false && symbolWidth) { // Do not allow the marker to be larger than the symbolHeight radius = Math.min(pick(markerOptions.radius, generalRadius), generalRadius); // Restrict symbol markers size if (this.symbol.indexOf('url') === 0) { markerOptions = merge(markerOptions, { width: symbolHeight, height: symbolHeight }); radius = 0; } this.legendSymbol = legendSymbol = renderer.symbol(this.symbol, (symbolWidth / 2) - radius, verticalCenter - radius, 2 * radius, 2 * radius, markerOptions) .addClass('highcharts-point') .add(legendItemGroup); legendSymbol.isMarker = true; } } }; return LegendSymbolMixin; }); _registerModule(_modules, 'Core/Series/Series.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Foundation.js'], _modules['Core/Globals.js'], _modules['Mixins/LegendSymbol.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Color/Palette.js'], _modules['Core/Series/Point.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (A, F, H, LegendSymbolMixin, D, palette, Point, SeriesRegistry, SVGElement, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var animObject = A.animObject, setAnimation = A.setAnimation; var registerEventOptions = F.registerEventOptions; var hasTouch = H.hasTouch, svg = H.svg, win = H.win; var defaultOptions = D.defaultOptions; var seriesTypes = SeriesRegistry.seriesTypes; var addEvent = U.addEvent, arrayMax = U.arrayMax, arrayMin = U.arrayMin, clamp = U.clamp, cleanRecursively = U.cleanRecursively, correctFloat = U.correctFloat, defined = U.defined, erase = U.erase, error = U.error, extend = U.extend, find = U.find, fireEvent = U.fireEvent, getNestedProperty = U.getNestedProperty, isArray = U.isArray, isNumber = U.isNumber, isString = U.isString, merge = U.merge, objectEach = U.objectEach, pick = U.pick, removeEvent = U.removeEvent, splat = U.splat, syncTimeout = U.syncTimeout; /* * * * Class * * */ /** * This is the base series prototype that all other series types inherit from. * A new series is initialized either through the * [series](https://api.highcharts.com/highcharts/series) * option structure, or after the chart is initialized, through * {@link Highcharts.Chart#addSeries}. * * The object can be accessed in a number of ways. All series and point event * handlers give a reference to the `series` object. The chart object has a * {@link Highcharts.Chart#series|series} property that is a collection of all * the chart's series. The point objects and axis objects also have the same * reference. * * Another way to reference the series programmatically is by `id`. Add an id * in the series configuration options, and get the series object by * {@link Highcharts.Chart#get}. * * Configuration options for the series are given in three levels. Options for * all series in a chart are given in the * [plotOptions.series](https://api.highcharts.com/highcharts/plotOptions.series) * object. Then options for all series of a specific type * are given in the plotOptions of that type, for example `plotOptions.line`. * Next, options for one single series are given in the series array, or as * arguments to `chart.addSeries`. * * The data in the series is stored in various arrays. * * - First, `series.options.data` contains all the original config options for * each point whether added by options or methods like `series.addPoint`. * * - Next, `series.data` contains those values converted to points, but in case * the series data length exceeds the `cropThreshold`, or if the data is * grouped, `series.data` doesn't contain all the points. It only contains the * points that have been created on demand. * * - Then there's `series.points` that contains all currently visible point * objects. In case of cropping, the cropped-away points are not part of this * array. The `series.points` array starts at `series.cropStart` compared to * `series.data` and `series.options.data`. If however the series data is * grouped, these can't be correlated one to one. * * - `series.xData` and `series.processedXData` contain clean x values, * equivalent to `series.data` and `series.points`. * * - `series.yData` and `series.processedYData` contain clean y values, * equivalent to `series.data` and `series.points`. * * @class * @name Highcharts.Series * * @param {Highcharts.Chart} chart * The chart instance. * * @param {Highcharts.SeriesOptionsType|object} options * The series options. */ var Series = /** @class */ (function () { function Series() { /* * * * Static Functions * * */ this._i = void 0; this.chart = void 0; this.data = void 0; this.eventOptions = void 0; this.eventsToUnbind = void 0; this.index = void 0; this.linkedSeries = void 0; this.options = void 0; this.points = void 0; this.processedXData = void 0; this.processedYData = void 0; this.tooltipOptions = void 0; this.userOptions = void 0; this.xAxis = void 0; this.yAxis = void 0; this.zones = void 0; /** eslint-enable valid-jsdoc */ } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ Series.prototype.init = function (chart, userOptions) { fireEvent(this, 'init', { options: userOptions }); var series = this, chartSeries = chart.series; // The 'eventsToUnbind' property moved from prototype into the // Series init to avoid reference to the same array between // the different series and charts. #12959, #13937 this.eventsToUnbind = []; /** * Read only. The chart that the series belongs to. * * @name Highcharts.Series#chart * @type {Highcharts.Chart} */ series.chart = chart; /** * Read only. The series' type, like "line", "area", "column" etc. * The type in the series options anc can be altered using * {@link Series#update}. * * @name Highcharts.Series#type * @type {string} */ /** * Read only. The series' current options. To update, use * {@link Series#update}. * * @name Highcharts.Series#options * @type {Highcharts.SeriesOptionsType} */ series.options = series.setOptions(userOptions); var options = series.options; series.linkedSeries = []; // bind the axes series.bindAxes(); extend(series, { /** * The series name as given in the options. Defaults to * "Series {n}". * * @name Highcharts.Series#name * @type {string} */ name: options.name, state: '', /** * Read only. The series' visibility state as set by {@link * Series#show}, {@link Series#hide}, or in the initial * configuration. * * @name Highcharts.Series#visible * @type {boolean} */ visible: options.visible !== false, /** * Read only. The series' selected state as set by {@link * Highcharts.Series#select}. * * @name Highcharts.Series#selected * @type {boolean} */ selected: options.selected === true // false by default }); registerEventOptions(this, options); var events = options.events; if ((events && events.click) || (options.point && options.point.events && options.point.events.click) || options.allowPointSelect) { chart.runTrackerClick = true; } series.getColor(); series.getSymbol(); // Initialize the parallel data arrays series.parallelArrays.forEach(function (key) { if (!series[key + 'Data']) { series[key + 'Data'] = []; } }); // Mark cartesian if (series.isCartesian) { chart.hasCartesianSeries = true; } // Get the index and register the series in the chart. The index is // one more than the current latest series index (#5960). var lastSeries; if (chartSeries.length) { lastSeries = chartSeries[chartSeries.length - 1]; } series._i = pick(lastSeries && lastSeries._i, -1) + 1; series.opacity = series.options.opacity; // Insert the series and re-order all series above the insertion // point. chart.orderSeries(this.insert(chartSeries)); // Set options for series with sorting and set data later. if (options.dataSorting && options.dataSorting.enabled) { series.setDataSortingOptions(); } else if (!series.points && !series.data) { series.setData(options.data, false); } fireEvent(this, 'afterInit'); }; /** * Check whether the series item is itself or inherits from a certain * series type. * * @function Highcharts.Series#is * @param {string} type The type of series to check for, can be either * featured or custom series types. For example `column`, `pie`, * `ohlc` etc. * * @return {boolean} * True if this item is or inherits from the given type. */ Series.prototype.is = function (type) { return seriesTypes[type] && this instanceof seriesTypes[type]; }; /** * Insert the series in a collection with other series, either the chart * series or yAxis series, in the correct order according to the index * option. Used internally when adding series. * * @private * @function Highcharts.Series#insert * @param {Array<Highcharts.Series>} collection * A collection of series, like `chart.series` or `xAxis.series`. * @return {number} * The index of the series in the collection. */ Series.prototype.insert = function (collection) { var indexOption = this.options.index, i; // Insert by index option if (isNumber(indexOption)) { i = collection.length; while (i--) { // Loop down until the interted element has higher index if (indexOption >= pick(collection[i].options.index, collection[i]._i)) { collection.splice(i + 1, 0, this); break; } } if (i === -1) { collection.unshift(this); } i = i + 1; // Or just push it to the end } else { collection.push(this); } return pick(i, collection.length - 1); }; /** * Set the xAxis and yAxis properties of cartesian series, and register * the series in the `axis.series` array. * * @private * @function Highcharts.Series#bindAxes */ Series.prototype.bindAxes = function () { var series = this, seriesOptions = series.options, chart = series.chart, axisOptions; fireEvent(this, 'bindAxes', null, function () { // repeat for xAxis and yAxis (series.axisTypes || []).forEach(function (AXIS) { var index = 0; // loop through the chart's axis objects chart[AXIS].forEach(function (axis) { axisOptions = axis.options; // apply if the series xAxis or yAxis option mathches // the number of the axis, or if undefined, use the // first axis if ((seriesOptions[AXIS] === index && !axisOptions.isInternal) || (typeof seriesOptions[AXIS] !== 'undefined' && seriesOptions[AXIS] === axisOptions.id) || (typeof seriesOptions[AXIS] === 'undefined' && axisOptions.index === 0)) { // register this series in the axis.series lookup series.insert(axis.series); // set this series.xAxis or series.yAxis reference /** * Read only. The unique xAxis object associated * with the series. * * @name Highcharts.Series#xAxis * @type {Highcharts.Axis} */ /** * Read only. The unique yAxis object associated * with the series. * * @name Highcharts.Series#yAxis * @type {Highcharts.Axis} */ series[AXIS] = axis; // mark dirty for redraw axis.isDirty = true; } if (!axisOptions.isInternal) { index++; } }); // The series needs an X and an Y axis if (!series[AXIS] && series.optionalAxis !== AXIS) { error(18, true, chart); } }); }); fireEvent(this, 'afterBindAxes'); }; /** * For simple series types like line and column, the data values are * held in arrays like xData and yData for quick lookup to find extremes * and more. For multidimensional series like bubble and map, this can * be extended with arrays like zData and valueData by adding to the * `series.parallelArrays` array. * * @private * @function Highcharts.Series#updateParallelArrays */ Series.prototype.updateParallelArrays = function (point, i) { var series = point.series, args = arguments, fn = isNumber(i) ? // Insert the value in the given position function (key) { var val = key === 'y' && series.toYData ? series.toYData(point) : point[key]; series[key + 'Data'][i] = val; } : // Apply the method specified in i with the following // arguments as arguments function (key) { Array.prototype[i].apply(series[key + 'Data'], Array.prototype.slice.call(args, 2)); }; series.parallelArrays.forEach(fn); }; /** * Define hasData functions for series. These return true if there * are data points on this series within the plot area. * * @private * @function Highcharts.Series#hasData * @return {boolean} */ Series.prototype.hasData = function () { return ((this.visible && typeof this.dataMax !== 'undefined' && typeof this.dataMin !== 'undefined') || ( // #3703 this.visible && this.yData && this.yData.length > 0) // #9758 ); }; /** * Return an auto incremented x value based on the pointStart and * pointInterval options. This is only used if an x value is not given * for the point that calls autoIncrement. * * @private * @function Highcharts.Series#autoIncrement * @return {number} */ Series.prototype.autoIncrement = function () { var options = this.options, xIncrement = this.xIncrement, date, pointInterval, pointIntervalUnit = options.pointIntervalUnit, time = this.chart.time; xIncrement = pick(xIncrement, options.pointStart, 0); this.pointInterval = pointInterval = pick(this.pointInterval, options.pointInterval, 1); // Added code for pointInterval strings if (pointIntervalUnit) { date = new time.Date(xIncrement); if (pointIntervalUnit === 'day') { time.set('Date', date, time.get('Date', date) + pointInterval); } else if (pointIntervalUnit === 'month') { time.set('Month', date, time.get('Month', date) + pointInterval); } else if (pointIntervalUnit === 'year') { time.set('FullYear', date, time.get('FullYear', date) + pointInterval); } pointInterval = date.getTime() - xIncrement; } this.xIncrement = xIncrement + pointInterval; return xIncrement; }; /** * Internal function to set properties for series if data sorting is * enabled. * * @private * @function Highcharts.Series#setDataSortingOptions */ Series.prototype.setDataSortingOptions = function () { var options = this.options; extend(this, { requireSorting: false, sorted: false, enabledDataSorting: true, allowDG: false }); // To allow unsorted data for column series. if (!defined(options.pointRange)) { options.pointRange = 1; } }; /** * Set the series options by merging from the options tree. Called * internally on initializing and updating series. This function will * not redraw the series. For API usage, use {@link Series#update}. * @private * @function Highcharts.Series#setOptions * * @param {Highcharts.SeriesOptionsType} itemOptions * The series options. * * @return {Highcharts.SeriesOptionsType} * * @fires Highcharts.Series#event:afterSetOptions */ Series.prototype.setOptions = function (itemOptions) { var chart = this.chart, chartOptions = chart.options, plotOptions = chartOptions.plotOptions, userOptions = chart.userOptions || {}, seriesUserOptions = merge(itemOptions), options, zones, zone, styledMode = chart.styledMode, e = { plotOptions: plotOptions, userOptions: seriesUserOptions }; fireEvent(this, 'setOptions', e); // These may be modified by the event var typeOptions = e.plotOptions[this.type], userPlotOptions = (userOptions.plotOptions || {}); // use copy to prevent undetected changes (#9762) /** * Contains series options by the user without defaults. * @name Highcharts.Series#userOptions * @type {Highcharts.SeriesOptionsType} */ this.userOptions = e.userOptions; options = merge(typeOptions, plotOptions.series, // #3881, chart instance plotOptions[type] should trump // plotOptions.series userOptions.plotOptions && userOptions.plotOptions[this.type], seriesUserOptions); // The tooltip options are merged between global and series specific // options. Importance order asscendingly: // globals: (1)tooltip, (2)plotOptions.series, // (3)plotOptions[this.type] // init userOptions with possible later updates: 4-6 like 1-3 and // (7)this series options this.tooltipOptions = merge(defaultOptions.tooltip, // 1 defaultOptions.plotOptions.series && defaultOptions.plotOptions.series.tooltip, // 2 defaultOptions.plotOptions[this.type].tooltip, // 3 chartOptions.tooltip.userOptions, // 4 plotOptions.series && plotOptions.series.tooltip, // 5 plotOptions[this.type].tooltip, // 6 seriesUserOptions.tooltip // 7 ); // When shared tooltip, stickyTracking is true by default, // unless user says otherwise. this.stickyTracking = pick(seriesUserOptions.stickyTracking, userPlotOptions[this.type] && userPlotOptions[this.type].stickyTracking, userPlotOptions.series && userPlotOptions.series.stickyTracking, (this.tooltipOptions.shared && !this.noSharedTooltip ? true : options.stickyTracking)); // Delete marker object if not allowed (#1125) if (typeOptions.marker === null) { delete options.marker; } // Handle color zones this.zoneAxis = options.zoneAxis; zones = this.zones = (options.zones || []).slice(); if ((options.negativeColor || options.negativeFillColor) && !options.zones) { zone = { value: options[this.zoneAxis + 'Threshold'] || options.threshold || 0, className: 'highcharts-negative' }; if (!styledMode) { zone.color = options.negativeColor; zone.fillColor = options.negativeFillColor; } zones.push(zone); } if (zones.length) { // Push one extra zone for the rest if (defined(zones[zones.length - 1].value)) { zones.push(styledMode ? {} : { color: this.color, fillColor: this.fillColor }); } } fireEvent(this, 'afterSetOptions', { options: options }); return options; }; /** * Return series name in "Series {Number}" format or the one defined by * a user. This method can be simply overridden as series name format * can vary (e.g. technical indicators). * * @function Highcharts.Series#getName * * @return {string} * The series name. */ Series.prototype.getName = function () { // #4119 return pick(this.options.name, 'Series ' + (this.index + 1)); }; /** * @private * @function Highcharts.Series#getCyclic */ Series.prototype.getCyclic = function (prop, value, defaults) { var i, chart = this.chart, userOptions = this.userOptions, indexName = prop + 'Index', counterName = prop + 'Counter', len = defaults ? defaults.length : pick(chart.options.chart[prop + 'Count'], chart[prop + 'Count']), setting; if (!value) { // Pick up either the colorIndex option, or the _colorIndex // after Series.update() setting = pick(userOptions[indexName], userOptions['_' + indexName]); if (defined(setting)) { // after Series.update() i = setting; } else { // #6138 if (!chart.series.length) { chart[counterName] = 0; } userOptions['_' + indexName] = i = chart[counterName] % len; chart[counterName] += 1; } if (defaults) { value = defaults[i]; } } // Set the colorIndex if (typeof i !== 'undefined') { this[indexName] = i; } this[prop] = value; }; /** * Get the series' color based on either the options or pulled from * global options. * * @private * @function Highcharts.Series#getColor */ Series.prototype.getColor = function () { if (this.chart.styledMode) { this.getCyclic('color'); } else if (this.options.colorByPoint) { this.color = palette.neutralColor20; } else { this.getCyclic('color', this.options.color || defaultOptions.plotOptions[this.type].color, this.chart.options.colors); } }; /** * Get all points' instances created for this series. * * @private * @function Highcharts.Series#getPointsCollection * @return {Array<Highcharts.Point>} */ Series.prototype.getPointsCollection = function () { return (this.hasGroupedData ? this.points : this.data) || []; }; /** * Get the series' symbol based on either the options or pulled from * global options. * * @private * @function Highcharts.Series#getSymbol * @return {void} */ Series.prototype.getSymbol = function () { var seriesMarkerOption = this.options.marker; this.getCyclic('symbol', seriesMarkerOption.symbol, this.chart.options.symbols); }; /** * Finds the index of an existing point that matches the given point * options. * * @private * @function Highcharts.Series#findPointIndex * @param {Highcharts.PointOptionsObject} optionsObject * The options of the point. * @param {number} fromIndex * The index to start searching from, used for optimizing * series with required sorting. * @returns {number|undefined} * Returns the index of a matching point, or undefined if no * match is found. */ Series.prototype.findPointIndex = function (optionsObject, fromIndex) { var id = optionsObject.id, x = optionsObject.x, oldData = this.points, matchingPoint, matchedById, pointIndex, matchKey, dataSorting = this.options.dataSorting; if (id) { matchingPoint = this.chart.get(id); } else if (this.linkedParent || this.enabledDataSorting) { matchKey = (dataSorting && dataSorting.matchByName) ? 'name' : 'index'; matchingPoint = find(oldData, function (oldPoint) { return !oldPoint.touched && oldPoint[matchKey] === optionsObject[matchKey]; }); // Add unmatched point as a new point if (!matchingPoint) { return void 0; } } if (matchingPoint) { pointIndex = matchingPoint && matchingPoint.index; if (typeof pointIndex !== 'undefined') { matchedById = true; } } // Search for the same X in the existing data set if (typeof pointIndex === 'undefined' && isNumber(x)) { pointIndex = this.xData.indexOf(x, fromIndex); } // Reduce pointIndex if data is cropped if (pointIndex !== -1 && typeof pointIndex !== 'undefined' && this.cropped) { pointIndex = (pointIndex >= this.cropStart) ? pointIndex - this.cropStart : pointIndex; } if (!matchedById && oldData[pointIndex] && oldData[pointIndex].touched) { pointIndex = void 0; } return pointIndex; }; /** * Internal function called from setData. If the point count is the same * as is was, or if there are overlapping X values, just run * Point.update which is cheaper, allows animation, and keeps references * to points. This also allows adding or removing points if the X-es * don't match. * * @private * @function Highcharts.Series#updateData */ Series.prototype.updateData = function (data, animation) { var options = this.options, dataSorting = options.dataSorting, oldData = this.points, pointsToAdd = [], hasUpdatedByKey, i, point, lastIndex, requireSorting = this.requireSorting, equalLength = data.length === oldData.length, succeeded = true; this.xIncrement = null; // Iterate the new data data.forEach(function (pointOptions, i) { var id, x, pointIndex, optionsObject = (defined(pointOptions) && this.pointClass.prototype.optionsToObject.call({ series: this }, pointOptions)) || {}; // Get the x of the new data point x = optionsObject.x; id = optionsObject.id; if (id || isNumber(x)) { pointIndex = this.findPointIndex(optionsObject, lastIndex); // Matching X not found // or used already due to ununique x values (#8995), // add point (but later) if (pointIndex === -1 || typeof pointIndex === 'undefined') { pointsToAdd.push(pointOptions); // Matching X found, update } else if (oldData[pointIndex] && pointOptions !== options.data[pointIndex]) { oldData[pointIndex].update(pointOptions, false, null, false); // Mark it touched, below we will remove all points that // are not touched. oldData[pointIndex].touched = true; // Speed optimize by only searching after last known // index. Performs ~20% bettor on large data sets. if (requireSorting) { lastIndex = pointIndex + 1; } // Point exists, no changes, don't remove it } else if (oldData[pointIndex]) { oldData[pointIndex].touched = true; } // If the length is equal and some of the nodes had a // match in the same position, we don't want to remove // non-matches. if (!equalLength || i !== pointIndex || (dataSorting && dataSorting.enabled) || this.hasDerivedData) { hasUpdatedByKey = true; } } else { // Gather all points that are not matched pointsToAdd.push(pointOptions); } }, this); // Remove points that don't exist in the updated data set if (hasUpdatedByKey) { i = oldData.length; while (i--) { point = oldData[i]; if (point && !point.touched && point.remove) { point.remove(false, animation); } } // If we did not find keys (ids or x-values), and the length is the // same, update one-to-one } else if (equalLength && (!dataSorting || !dataSorting.enabled)) { data.forEach(function (point, i) { // .update doesn't exist on a linked, hidden series (#3709) // (#10187) if (point !== oldData[i].y && oldData[i].update) { oldData[i].update(point, false, null, false); } }); // Don't add new points since those configs are used above pointsToAdd.length = 0; // Did not succeed in updating data } else { succeeded = false; } oldData.forEach(function (point) { if (point) { point.touched = false; } }); if (!succeeded) { return false; } // Add new points pointsToAdd.forEach(function (point) { this.addPoint(point, false, null, null, false); }, this); if (this.xIncrement === null && this.xData && this.xData.length) { this.xIncrement = arrayMax(this.xData); this.autoIncrement(); } return true; }; /** * Apply a new set of data to the series and optionally redraw it. The * new data array is passed by reference (except in case of * `updatePoints`), and may later be mutated when updating the chart * data. * * Note the difference in behaviour when setting the same amount of * points, or a different amount of points, as handled by the * `updatePoints` parameter. * * @sample highcharts/members/series-setdata/ * Set new data from a button * @sample highcharts/members/series-setdata-pie/ * Set data in a pie * @sample stock/members/series-setdata/ * Set new data in Highcharts Stock * @sample maps/members/series-setdata/ * Set new data in Highmaps * * @function Highcharts.Series#setData * * @param {Array<Highcharts.PointOptionsType>} data * Takes an array of data in the same format as described under * `series.{type}.data` for the given series type, for example a * line series would take data in the form described under * [series.line.data](https://api.highcharts.com/highcharts/series.line.data). * * @param {boolean} [redraw=true] * Whether to redraw the chart after the series is altered. If * doing more operations on the chart, it is a good idea to set * redraw to false and call {@link Chart#redraw} after. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] * When the updated data is the same length as the existing data, * points will be updated by default, and animation visualizes * how the points are changed. Set false to disable animation, or * a configuration object to set duration or easing. * * @param {boolean} [updatePoints=true] * When this is true, points will be updated instead of replaced * whenever possible. This occurs a) when the updated data is the * same length as the existing data, b) when points are matched * by their id's, or c) when points can be matched by X values. * This allows updating with animation and performs better. In * this case, the original array is not passed by reference. Set * `false` to prevent. */ Series.prototype.setData = function (data, redraw, animation, updatePoints) { var series = this, oldData = series.points, oldDataLength = (oldData && oldData.length) || 0, dataLength, options = series.options, chart = series.chart, dataSorting = options.dataSorting, firstPoint = null, xAxis = series.xAxis, i, turboThreshold = options.turboThreshold, pt, xData = this.xData, yData = this.yData, pointArrayMap = series.pointArrayMap, valueCount = pointArrayMap && pointArrayMap.length, keys = options.keys, indexOfX = 0, indexOfY = 1, updatedData; data = data || []; dataLength = data.length; redraw = pick(redraw, true); if (dataSorting && dataSorting.enabled) { data = this.sortData(data); } // First try to run Point.update which is cheaper, allows animation, // and keeps references to points. if (updatePoints !== false && dataLength && oldDataLength && !series.cropped && !series.hasGroupedData && series.visible && // Soft updating has no benefit in boost, and causes JS error // (#8355) !series.isSeriesBoosting) { updatedData = this.updateData(data, animation); } if (!updatedData) { // Reset properties series.xIncrement = null; series.colorCounter = 0; // for series with colorByPoint (#1547) // Update parallel arrays this.parallelArrays.forEach(function (key) { series[key + 'Data'].length = 0; }); // In turbo mode, only one- or twodimensional arrays of numbers // are allowed. The first value is tested, and we assume that // all the rest are defined the same way. Although the 'for' // loops are similar, they are repeated inside each if-else // conditional for max performance. if (turboThreshold && dataLength > turboThreshold) { firstPoint = series.getFirstValidPoint(data); if (isNumber(firstPoint)) { // assume all points are numbers for (i = 0; i < dataLength; i++) { xData[i] = this.autoIncrement(); yData[i] = data[i]; } // Assume all points are arrays when first point is } else if (isArray(firstPoint)) { if (valueCount) { // [x, low, high] or [x, o, h, l, c] for (i = 0; i < dataLength; i++) { pt = data[i]; xData[i] = pt[0]; yData[i] = pt.slice(1, valueCount + 1); } } else { // [x, y] if (keys) { indexOfX = keys.indexOf('x'); indexOfY = keys.indexOf('y'); indexOfX = indexOfX >= 0 ? indexOfX : 0; indexOfY = indexOfY >= 0 ? indexOfY : 1; } for (i = 0; i < dataLength; i++) { pt = data[i]; xData[i] = pt[indexOfX]; yData[i] = pt[indexOfY]; } } } else { // Highcharts expects configs to be numbers or arrays in // turbo mode error(12, false, chart); } } else { for (i = 0; i < dataLength; i++) { // stray commas in oldIE: if (typeof data[i] !== 'undefined') { pt = { series: series }; series.pointClass.prototype.applyOptions.apply(pt, [data[i]]); series.updateParallelArrays(pt, i); } } } // Forgetting to cast strings to numbers is a common caveat when // handling CSV or JSON if (yData && isString(yData[0])) { error(14, true, chart); } series.data = []; series.options.data = series.userOptions.data = data; // destroy old points i = oldDataLength; while (i--) { if (oldData[i] && oldData[i].destroy) { oldData[i].destroy(); } } // reset minRange (#878) if (xAxis) { xAxis.minRange = xAxis.userMinRange; } // redraw series.isDirty = chart.isDirtyBox = true; series.isDirtyData = !!oldData; animation = false; } // Typically for pie series, points need to be processed and // generated prior to rendering the legend if (options.legendType === 'point') { this.processData(); this.generatePoints(); } if (redraw) { chart.redraw(animation); } }; /** * Internal function to sort series data * * @private * @function Highcharts.Series#sortData * * @param {Array<Highcharts.PointOptionsType>} data * Force data grouping. * * @return {Array<Highcharts.PointOptionsObject>} */ Series.prototype.sortData = function (data) { var series = this, options = series.options, dataSorting = options.dataSorting, sortKey = dataSorting.sortKey || 'y', sortedData, getPointOptionsObject = function (series, pointOptions) { return (defined(pointOptions) && series.pointClass.prototype.optionsToObject.call({ series: series }, pointOptions)) || {}; }; data.forEach(function (pointOptions, i) { data[i] = getPointOptionsObject(series, pointOptions); data[i].index = i; }, this); // Sorting sortedData = data.concat().sort(function (a, b) { var aValue = getNestedProperty(sortKey, a); var bValue = getNestedProperty(sortKey, b); return bValue < aValue ? -1 : bValue > aValue ? 1 : 0; }); // Set x value depending on the position in the array sortedData.forEach(function (point, i) { point.x = i; }, this); // Set the same x for linked series points if they don't have their // own sorting if (series.linkedSeries) { series.linkedSeries.forEach(function (linkedSeries) { var options = linkedSeries.options, seriesData = options.data; if ((!options.dataSorting || !options.dataSorting.enabled) && seriesData) { seriesData.forEach(function (pointOptions, i) { seriesData[i] = getPointOptionsObject(linkedSeries, pointOptions); if (data[i]) { seriesData[i].x = data[i].x; seriesData[i].index = i; } }); linkedSeries.setData(seriesData, false); } }); } return data; }; /** * Internal function to process the data by cropping away unused data * points if the series is longer than the crop threshold. This saves * computing time for large series. * * @private * @function Highcharts.Series#getProcessedData * @param {boolean} [forceExtremesFromAll] * Force getting extremes of a total series data range. * @return {Highcharts.SeriesProcessedDataObject} */ Series.prototype.getProcessedData = function (forceExtremesFromAll) { var series = this, // copied during slice operation: processedXData = series.xData, processedYData = series.yData, dataLength = processedXData.length, croppedData, cropStart = 0, cropped, distance, closestPointRange, xAxis = series.xAxis, i, // loop variable options = series.options, cropThreshold = options.cropThreshold, getExtremesFromAll = forceExtremesFromAll || series.getExtremesFromAll || options.getExtremesFromAll, // #4599 isCartesian = series.isCartesian, xExtremes, val2lin = xAxis && xAxis.val2lin, isLog = !!(xAxis && xAxis.logarithmic), throwOnUnsorted = series.requireSorting, min, max; if (xAxis) { // corrected for log axis (#3053) xExtremes = xAxis.getExtremes(); min = xExtremes.min; max = xExtremes.max; } // optionally filter out points outside the plot area if (isCartesian && series.sorted && !getExtremesFromAll && (!cropThreshold || dataLength > cropThreshold || series.forceCrop)) { // it's outside current extremes if (processedXData[dataLength - 1] < min || processedXData[0] > max) { processedXData = []; processedYData = []; // only crop if it's actually spilling out } else if (series.yData && (processedXData[0] < min || processedXData[dataLength - 1] > max)) { croppedData = this.cropData(series.xData, series.yData, min, max); processedXData = croppedData.xData; processedYData = croppedData.yData; cropStart = croppedData.start; cropped = true; } } // Find the closest distance between processed points i = processedXData.length || 1; while (--i) { distance = (isLog ? (val2lin(processedXData[i]) - val2lin(processedXData[i - 1])) : (processedXData[i] - processedXData[i - 1])); if (distance > 0 && (typeof closestPointRange === 'undefined' || distance < closestPointRange)) { closestPointRange = distance; // Unsorted data is not supported by the line tooltip, as well // as data grouping and navigation in Stock charts (#725) and // width calculation of columns (#1900) } else if (distance < 0 && throwOnUnsorted) { error(15, false, series.chart); throwOnUnsorted = false; // Only once } } return { xData: processedXData, yData: processedYData, cropped: cropped, cropStart: cropStart, closestPointRange: closestPointRange }; }; /** * Internal function to apply processed data. * In Highcharts Stock, this function is extended to provide data grouping. * * @private * @function Highcharts.Series#processData * @param {boolean} [force] * Force data grouping. * @return {boolean|undefined} */ Series.prototype.processData = function (force) { var series = this, xAxis = series.xAxis, processedData; // If the series data or axes haven't changed, don't go through // this. Return false to pass the message on to override methods // like in data grouping. if (series.isCartesian && !series.isDirty && !xAxis.isDirty && !series.yAxis.isDirty && !force) { return false; } processedData = series.getProcessedData(); // Record the properties series.cropped = processedData.cropped; // undefined or true series.cropStart = processedData.cropStart; series.processedXData = processedData.xData; series.processedYData = processedData.yData; series.closestPointRange = series.basePointRange = processedData.closestPointRange; }; /** * Iterate over xData and crop values between min and max. Returns * object containing crop start/end cropped xData with corresponding * part of yData, dataMin and dataMax within the cropped range. * * @private * @function Highcharts.Series#cropData * @param {Array<number>} xData * @param {Array<number>} yData * @param {number} min * @param {number} max * @param {number} [cropShoulder] * @return {Highcharts.SeriesCropDataObject} */ Series.prototype.cropData = function (xData, yData, min, max, cropShoulder) { var dataLength = xData.length, cropStart = 0, cropEnd = dataLength, i, j; // line-type series need one point outside cropShoulder = pick(cropShoulder, this.cropShoulder); // iterate up to find slice start for (i = 0; i < dataLength; i++) { if (xData[i] >= min) { cropStart = Math.max(0, i - cropShoulder); break; } } // proceed to find slice end for (j = i; j < dataLength; j++) { if (xData[j] > max) { cropEnd = j + cropShoulder; break; } } return { xData: xData.slice(cropStart, cropEnd), yData: yData.slice(cropStart, cropEnd), start: cropStart, end: cropEnd }; }; /** * Generate the data point after the data has been processed by cropping * away unused points and optionally grouped in Highcharts Stock. * * @private * @function Highcharts.Series#generatePoints */ Series.prototype.generatePoints = function () { var series = this, options = series.options, dataOptions = options.data, data = series.data, dataLength, processedXData = series.processedXData, processedYData = series.processedYData, PointClass = series.pointClass, processedDataLength = processedXData.length, cropStart = series.cropStart || 0, cursor, hasGroupedData = series.hasGroupedData, keys = options.keys, point, points = [], i, groupCropStartIndex = (options.dataGrouping && options.dataGrouping.groupAll ? cropStart : 0); if (!data && !hasGroupedData) { var arr = []; arr.length = dataOptions.length; data = series.data = arr; } if (keys && hasGroupedData) { // grouped data has already applied keys (#6590) series.options.keys = false; } for (i = 0; i < processedDataLength; i++) { cursor = cropStart + i; if (!hasGroupedData) { point = data[cursor]; // #970: if (!point && typeof dataOptions[cursor] !== 'undefined') { data[cursor] = point = (new PointClass()).init(series, dataOptions[cursor], processedXData[i]); } } else { // splat the y data in case of ohlc data array point = (new PointClass()).init(series, [processedXData[i]].concat(splat(processedYData[i]))); /** * Highcharts Stock only. If a point object is created by data * grouping, it doesn't reflect actual points in the raw * data. In this case, the `dataGroup` property holds * information that points back to the raw data. * * - `dataGroup.start` is the index of the first raw data * point in the group. * * - `dataGroup.length` is the amount of points in the * group. * * @product highstock * * @name Highcharts.Point#dataGroup * @type {Highcharts.DataGroupingInfoObject|undefined} */ point.dataGroup = series.groupMap[groupCropStartIndex + i]; if (point.dataGroup.options) { point.options = point.dataGroup.options; extend(point, point.dataGroup.options); // Collision of props and options (#9770) delete point.dataLabels; } } if (point) { // #6279 /** * Contains the point's index in the `Series.points` array. * * @name Highcharts.Point#index * @type {number} * @readonly */ // For faster access in Point.update point.index = hasGroupedData ? (groupCropStartIndex + i) : cursor; points[i] = point; } } // restore keys options (#6590) series.options.keys = keys; // Hide cropped-away points - this only runs when the number of // points is above cropThreshold, or when swithching view from // non-grouped data to grouped data (#637) if (data && (processedDataLength !== (dataLength = data.length) || hasGroupedData)) { for (i = 0; i < dataLength; i++) { // when has grouped data, clear all points if (i === cropStart && !hasGroupedData) { i += processedDataLength; } if (data[i]) { data[i].destroyElements(); data[i].plotX = void 0; // #1003 } } } /** * Read only. An array containing those values converted to points. * In case the series data length exceeds the `cropThreshold`, or if * the data is grouped, `series.data` doesn't contain all the * points. Also, in case a series is hidden, the `data` array may be * empty. To access raw values, `series.options.data` will always be * up to date. `Series.data` only contains the points that have been * created on demand. To modify the data, use * {@link Highcharts.Series#setData} or * {@link Highcharts.Point#update}. * * @see Series.points * * @name Highcharts.Series#data * @type {Array<Highcharts.Point>} */ series.data = data; /** * An array containing all currently visible point objects. In case * of cropping, the cropped-away points are not part of this array. * The `series.points` array starts at `series.cropStart` compared * to `series.data` and `series.options.data`. If however the series * data is grouped, these can't be correlated one to one. To modify * the data, use {@link Highcharts.Series#setData} or * {@link Highcharts.Point#update}. * * @name Highcharts.Series#points * @type {Array<Highcharts.Point>} */ series.points = points; fireEvent(this, 'afterGeneratePoints'); }; /** * Get current X extremes for the visible data. * * @private * @function Highcharts.Series#getXExtremes * * @param {Array<number>} xData * The data to inspect. Defaults to the current data within the visible * range. * * @return {Highcharts.RangeObject} */ Series.prototype.getXExtremes = function (xData) { return { min: arrayMin(xData), max: arrayMax(xData) }; }; /** * Calculate Y extremes for the visible data. The result is returned * as an object with `dataMin` and `dataMax` properties. * * @private * @function Highcharts.Series#getExtremes * * @param {Array<number>} [yData] * The data to inspect. Defaults to the current data within the visible * range. * @param {boolean} [forceExtremesFromAll] * Force getting extremes of a total series data range. * * @return {Highcharts.DataExtremesObject} */ Series.prototype.getExtremes = function (yData, forceExtremesFromAll) { var xAxis = this.xAxis, yAxis = this.yAxis, xData = this.processedXData || this.xData, yDataLength, activeYData = [], activeCounter = 0, // #2117, need to compensate for log X axis xExtremes, xMin = 0, xMax = 0, validValue, withinRange, // Handle X outside the viewed area. This does not work with // non-sorted data like scatter (#7639). shoulder = this.requireSorting ? this.cropShoulder : 0, positiveValuesOnly = yAxis ? yAxis.positiveValuesOnly : false, x, y, i, j; yData = yData || this.stackedYData || this.processedYData || []; yDataLength = yData.length; if (xAxis) { xExtremes = xAxis.getExtremes(); xMin = xExtremes.min; xMax = xExtremes.max; } for (i = 0; i < yDataLength; i++) { x = xData[i]; y = yData[i]; // For points within the visible range, including the first // point outside the visible range (#7061), consider y extremes. validValue = ((isNumber(y) || isArray(y)) && ((y.length || y > 0) || !positiveValuesOnly)); withinRange = (forceExtremesFromAll || this.getExtremesFromAll || this.options.getExtremesFromAll || this.cropped || !xAxis || // for colorAxis support ((xData[i + shoulder] || x) >= xMin && (xData[i - shoulder] || x) <= xMax)); if (validValue && withinRange) { j = y.length; if (j) { // array, like ohlc or range data while (j--) { if (isNumber(y[j])) { // #7380, #11513 activeYData[activeCounter++] = y[j]; } } } else { activeYData[activeCounter++] = y; } } } var dataExtremes = { dataMin: arrayMin(activeYData), dataMax: arrayMax(activeYData) }; fireEvent(this, 'afterGetExtremes', { dataExtremes: dataExtremes }); return dataExtremes; }; /** * Set the current data extremes as `dataMin` and `dataMax` on the * Series item. Use this only when the series properties should be * updated. * * @private * @function Highcharts.Series#applyExtremes */ Series.prototype.applyExtremes = function () { var dataExtremes = this.getExtremes(); /** * Contains the minimum value of the series' data point. Some series * types like `networkgraph` do not support this property as they * lack a `y`-value. * @name Highcharts.Series#dataMin * @type {number|undefined} * @readonly */ this.dataMin = dataExtremes.dataMin; /** * Contains the maximum value of the series' data point. Some series * types like `networkgraph` do not support this property as they * lack a `y`-value. * @name Highcharts.Series#dataMax * @type {number|undefined} * @readonly */ this.dataMax = dataExtremes.dataMax; return dataExtremes; }; /** * Find and return the first non null point in the data * * @private * @function Highcharts.Series.getFirstValidPoint * * @param {Array<Highcharts.PointOptionsType>} data * Array of options for points * * @return {Highcharts.PointOptionsType} */ Series.prototype.getFirstValidPoint = function (data) { var firstPoint = null, dataLength = data.length, i = 0; while (firstPoint === null && i < dataLength) { firstPoint = data[i]; i++; } return firstPoint; }; /** * Translate data points from raw data values to chart specific * positioning data needed later in the `drawPoints` and `drawGraph` * functions. This function can be overridden in plugins and custom * series type implementations. * * @function Highcharts.Series#translate * * @fires Highcharts.Series#events:translate */ Series.prototype.translate = function () { if (!this.processedXData) { // hidden series this.processData(); } this.generatePoints(); var series = this, options = series.options, stacking = options.stacking, xAxis = series.xAxis, categories = xAxis.categories, enabledDataSorting = series.enabledDataSorting, yAxis = series.yAxis, points = series.points, dataLength = points.length, hasModifyValue = !!series.modifyValue, i, pointPlacement = series.pointPlacementToXValue(), // #7860 dynamicallyPlaced = Boolean(pointPlacement), threshold = options.threshold, stackThreshold = options.startFromThreshold ? threshold : 0, plotX, lastPlotX, stackIndicator, zoneAxis = this.zoneAxis || 'y', closestPointRangePx = Number.MAX_VALUE; /** * Plotted coordinates need to be within a limited range. Drawing * too far outside the viewport causes various rendering issues * (#3201, #3923, #7555). * @private */ function limitedRange(val) { return clamp(val, -1e5, 1e5); } // Translate each point for (i = 0; i < dataLength; i++) { var point = points[i], xValue = point.x, yValue = point.y, yBottom = point.low, stack = stacking && yAxis.stacking && yAxis.stacking.stacks[(series.negStacks && yValue < (stackThreshold ? 0 : threshold) ? '-' : '') + series.stackKey], pointStack = void 0, stackValues = void 0; if (yAxis.positiveValuesOnly && !yAxis.validatePositiveValue(yValue) || xAxis.positiveValuesOnly && !xAxis.validatePositiveValue(xValue)) { point.isNull = true; } // Get the plotX translation point.plotX = plotX = correctFloat(// #5236 limitedRange(xAxis.translate(// #3923 xValue, 0, 0, 0, 1, pointPlacement, this.type === 'flags')) // #3923 ); // Calculate the bottom y value for stacked series if (stacking && series.visible && stack && stack[xValue]) { stackIndicator = series.getStackIndicator(stackIndicator, xValue, series.index); if (!point.isNull) { pointStack = stack[xValue]; stackValues = pointStack.points[stackIndicator.key]; } } if (isArray(stackValues)) { yBottom = stackValues[0]; yValue = stackValues[1]; if (yBottom === stackThreshold && stackIndicator.key === stack[xValue].base) { yBottom = pick((isNumber(threshold) && threshold), yAxis.min); } // #1200, #1232 if (yAxis.positiveValuesOnly && yBottom <= 0) { yBottom = null; } point.total = point.stackTotal = pointStack.total; point.percentage = pointStack.total && (point.y / pointStack.total * 100); point.stackY = yValue; // Place the stack label // in case of variwide series (where widths of points are // different in most cases), stack labels are positioned // wrongly, so the call of the setOffset is omited here and // labels are correctly positioned later, at the end of the // variwide's translate function (#10962) if (!series.irregularWidths) { pointStack.setOffset(series.pointXOffset || 0, series.barW || 0); } } // Set translated yBottom or remove it point.yBottom = defined(yBottom) ? limitedRange(yAxis.translate(yBottom, 0, 1, 0, 1)) : null; // general hook, used for Highcharts Stock compare mode if (hasModifyValue) { yValue = series.modifyValue(yValue, point); } // Set the the plotY value, reset it for redraws // #3201 point.plotY = void 0; if (isNumber(yValue)) { var translated = yAxis.translate(yValue, false, true, false, true); if (typeof translated !== 'undefined') { point.plotY = limitedRange(translated); } } point.isInside = this.isPointInside(point); // Set client related positions for mouse tracking point.clientX = dynamicallyPlaced ? correctFloat(xAxis.translate(xValue, 0, 0, 0, 1, pointPlacement)) : plotX; // #1514, #5383, #5518 // Negative points. For bubble charts, this means negative z // values (#9728) point.negative = point[zoneAxis] < (options[zoneAxis + 'Threshold'] || threshold || 0); // some API data point.category = (categories && typeof categories[point.x] !== 'undefined' ? categories[point.x] : point.x); // Determine auto enabling of markers (#3635, #5099) if (!point.isNull && point.visible !== false) { if (typeof lastPlotX !== 'undefined') { closestPointRangePx = Math.min(closestPointRangePx, Math.abs(plotX - lastPlotX)); } lastPlotX = plotX; } // Find point zone point.zone = (this.zones.length && point.getZone()); // Animate new points with data sorting if (!point.graphic && series.group && enabledDataSorting) { point.isNew = true; } } series.closestPointRangePx = closestPointRangePx; fireEvent(this, 'afterTranslate'); }; /** * Return the series points with null points filtered out. * * @function Highcharts.Series#getValidPoints * * @param {Array<Highcharts.Point>} [points] * The points to inspect, defaults to {@link Series.points}. * * @param {boolean} [insideOnly=false] * Whether to inspect only the points that are inside the visible view. * * @param {boolean} [allowNull=false] * Whether to allow null points to pass as valid points. * * @return {Array<Highcharts.Point>} * The valid points. */ Series.prototype.getValidPoints = function (points, insideOnly, allowNull) { var chart = this.chart; // #3916, #5029, #5085 return (points || this.points || []).filter(function (point) { if (insideOnly && !chart.isInsidePlot(point.plotX, point.plotY, { inverted: chart.inverted })) { return false; } return point.visible !== false && (allowNull || !point.isNull); }); }; /** * Get the clipping for the series. Could be called for a series to * initiate animating the clip or to set the final clip (only width * and x). * * @private * @function Highcharts.Series#getClip * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] * Initialize the animation. * * @param {boolean} [finalBox] * Final size for the clip - end state for the animation. * * @return {Highcharts.Dictionary<number>} */ Series.prototype.getClipBox = function (animation, finalBox) { var series = this, options = series.options, chart = series.chart, inverted = chart.inverted, xAxis = series.xAxis, yAxis = xAxis && series.yAxis, clipBox, scrollablePlotAreaOptions = chart.options.chart.scrollablePlotArea || {}; if (animation && options.clip === false && yAxis) { // support for not clipped series animation (#10450) clipBox = inverted ? { y: -chart.chartWidth + yAxis.len + yAxis.pos, height: chart.chartWidth, width: chart.chartHeight, x: -chart.chartHeight + xAxis.len + xAxis.pos } : { y: -yAxis.pos, height: chart.chartHeight, width: chart.chartWidth, x: -xAxis.pos }; // x and width will be changed later when setting for animation // initial state in Series.setClip } else { clipBox = series.clipBox || chart.clipBox; if (finalBox) { clipBox.width = chart.plotSizeX; clipBox.x = (chart.scrollablePixelsX || 0) * (scrollablePlotAreaOptions.scrollPositionX || 0); } } return !finalBox ? clipBox : { width: clipBox.width, x: clipBox.x }; }; /** * Get the shared clip key, creating it if it doesn't exist. * * @private * @function Highcharts.Series#getSharedClipKey */ Series.prototype.getSharedClipKey = function (animation) { if (this.sharedClipKey) { return this.sharedClipKey; } var sharedClipKey = [ animation && animation.duration, animation && animation.easing, animation && animation.defer, this.getClipBox(animation).height, this.options.xAxis, this.options.yAxis ].join(','); if (this.options.clip !== false || animation) { this.sharedClipKey = sharedClipKey; } return sharedClipKey; }; /** * Set the clipping for the series. For animated series it is called * twice, first to initiate animating the clip then the second time * without the animation to set the final clip. * * @private * @function Highcharts.Series#setClip */ Series.prototype.setClip = function (animation) { var chart = this.chart, options = this.options, renderer = chart.renderer, inverted = chart.inverted, seriesClipBox = this.clipBox, clipBox = this.getClipBox(animation), sharedClipKey = this.getSharedClipKey(animation), // #4526 clipRect = chart.sharedClips[sharedClipKey], markerClipRect = chart.sharedClips[sharedClipKey + 'm']; if (animation) { clipBox.width = 0; if (inverted) { clipBox.x = chart.plotHeight + (options.clip !== false ? 0 : chart.plotTop); } } // If a clipping rectangle with the same properties is currently // present in the chart, use that. if (!clipRect) { // When animation is set, prepare the initial positions if (animation) { chart.sharedClips[sharedClipKey + 'm'] = markerClipRect = renderer.clipRect( // include the width of the first marker inverted ? (chart.plotSizeX || 0) + 99 : -99, inverted ? -chart.plotLeft : -chart.plotTop, 99, inverted ? chart.chartWidth : chart.chartHeight); } chart.sharedClips[sharedClipKey] = clipRect = renderer.clipRect(clipBox); // Create hashmap for series indexes clipRect.count = { length: 0 }; // When the series is rendered again before starting animating, in // compliance to a responsive rule } else if (!chart.hasLoaded) { clipRect.attr(clipBox); } if (animation) { if (!clipRect.count[this.index]) { clipRect.count[this.index] = true; clipRect.count.length += 1; } } if (options.clip !== false || animation) { this.group.clip(animation || seriesClipBox ? clipRect : chart.clipRect); this.markerGroup.clip(markerClipRect); } // Remove the shared clipping rectangle when all series are shown if (!animation) { if (clipRect.count[this.index]) { delete clipRect.count[this.index]; clipRect.count.length -= 1; } if (clipRect.count.length === 0) { if (!seriesClipBox) { chart.sharedClips[sharedClipKey] = clipRect.destroy(); } if (markerClipRect) { chart.sharedClips[sharedClipKey + 'm'] = markerClipRect.destroy(); } } } }; /** * Animate in the series. Called internally twice. First with the `init` * parameter set to true, which sets up the initial state of the * animation. Then when ready, it is called with the `init` parameter * undefined, in order to perform the actual animation. After the * second run, the function is removed. * * @function Highcharts.Series#animate * * @param {boolean} [init] * Initialize the animation. */ Series.prototype.animate = function (init) { var series = this, chart = series.chart, animation = animObject(series.options.animation), sharedClipKey = this.sharedClipKey; // Initialize the animation. Set up the clipping rectangle. if (init) { series.setClip(animation); // Run the animation } else if (sharedClipKey) { var clipRect = chart.sharedClips[sharedClipKey]; var markerClipRect = chart.sharedClips[sharedClipKey + 'm']; var finalBox = series.getClipBox(animation, true); if (clipRect) { clipRect.animate(finalBox, animation); } if (markerClipRect) { markerClipRect.animate({ width: finalBox.width + 99, x: finalBox.x - (chart.inverted ? 0 : 99) }, animation); } } }; /** * This runs after animation to land on the final plot clipping. * * @private * @function Highcharts.Series#afterAnimate * * @fires Highcharts.Series#event:afterAnimate */ Series.prototype.afterAnimate = function () { this.setClip(); fireEvent(this, 'afterAnimate'); this.finishedAnimating = true; }; /** * Draw the markers for line-like series types, and columns or other * graphical representation for {@link Point} objects for other series * types. The resulting element is typically stored as * {@link Point.graphic}, and is created on the first call and updated * and moved on subsequent calls. * * @function Highcharts.Series#drawPoints */ Series.prototype.drawPoints = function () { var series = this, points = series.points, chart = series.chart, i, point, graphic, verb, options = series.options, seriesMarkerOptions = options.marker, pointMarkerOptions, hasPointMarker, markerGroup = (series[series.specialGroup] || series.markerGroup), xAxis = series.xAxis, markerAttribs, globallyEnabled = pick(seriesMarkerOptions.enabled, !xAxis || xAxis.isRadial ? true : null, // Use larger or equal as radius is null in bubbles (#6321) series.closestPointRangePx >= (seriesMarkerOptions.enabledThreshold * seriesMarkerOptions.radius)); if (seriesMarkerOptions.enabled !== false || series._hasPointMarkers) { for (i = 0; i < points.length; i++) { point = points[i]; graphic = point.graphic; verb = graphic ? 'animate' : 'attr'; pointMarkerOptions = point.marker || {}; hasPointMarker = !!point.marker; var shouldDrawMarker = ((globallyEnabled && typeof pointMarkerOptions.enabled === 'undefined') || pointMarkerOptions.enabled) && !point.isNull && point.visible !== false; // only draw the point if y is defined if (shouldDrawMarker) { // Shortcuts var symbol = pick(pointMarkerOptions.symbol, series.symbol, 'rect'); markerAttribs = series.markerAttribs(point, (point.selected && 'select')); // Set starting position for point sliding animation. if (series.enabledDataSorting) { point.startXPos = xAxis.reversed ? -(markerAttribs.width || 0) : xAxis.width; } var isInside = point.isInside !== false; if (graphic) { // update // Since the marker group isn't clipped, each // individual marker must be toggled graphic[isInside ? 'show' : 'hide'](isInside) .animate(markerAttribs); } else if (isInside && ((markerAttribs.width || 0) > 0 || point.hasImage)) { /** * The graphic representation of the point. * Typically this is a simple shape, like a `rect` * for column charts or `path` for line markers, but * for some complex series types like boxplot or 3D * charts, the graphic may be a `g` element * containing other shapes. The graphic is generated * the first time {@link Series#drawPoints} runs, * and updated and moved on subsequent runs. * * @name Point#graphic * @type {SVGElement} */ point.graphic = graphic = chart.renderer .symbol(symbol, markerAttribs.x, markerAttribs.y, markerAttribs.width, markerAttribs.height, hasPointMarker ? pointMarkerOptions : seriesMarkerOptions) .add(markerGroup); // Sliding animation for new points if (series.enabledDataSorting && chart.hasRendered) { graphic.attr({ x: point.startXPos }); verb = 'animate'; } } if (graphic && verb === 'animate') { // update // Since the marker group isn't clipped, each // individual marker must be toggled graphic[isInside ? 'show' : 'hide'](isInside) .animate(markerAttribs); } // Presentational attributes if (graphic && !chart.styledMode) { graphic[verb](series.pointAttribs(point, (point.selected && 'select'))); } if (graphic) { graphic.addClass(point.getClassName(), true); } } else if (graphic) { point.graphic = graphic.destroy(); // #1269 } } } }; /** * Get non-presentational attributes for a point. Used internally for * both styled mode and classic. Can be overridden for different series * types. * * @see Series#pointAttribs * * @function Highcharts.Series#markerAttribs * * @param {Highcharts.Point} point * The Point to inspect. * * @param {string} [state] * The state, can be either `hover`, `select` or undefined. * * @return {Highcharts.SVGAttributes} * A hash containing those attributes that are not settable from CSS. */ Series.prototype.markerAttribs = function (point, state) { var seriesOptions = this.options, seriesMarkerOptions = seriesOptions.marker, seriesStateOptions, pointMarkerOptions = point.marker || {}, symbol = (pointMarkerOptions.symbol || seriesMarkerOptions.symbol), pointStateOptions, radius = pick(pointMarkerOptions.radius, seriesMarkerOptions.radius), attribs; // Handle hover and select states if (state) { seriesStateOptions = seriesMarkerOptions.states[state]; pointStateOptions = pointMarkerOptions.states && pointMarkerOptions.states[state]; radius = pick(pointStateOptions && pointStateOptions.radius, seriesStateOptions && seriesStateOptions.radius, radius + (seriesStateOptions && seriesStateOptions.radiusPlus || 0)); } point.hasImage = symbol && symbol.indexOf('url') === 0; if (point.hasImage) { radius = 0; // and subsequently width and height is not set } attribs = { // Math.floor for #1843: x: seriesOptions.crisp ? Math.floor(point.plotX - radius) : point.plotX - radius, y: point.plotY - radius }; if (radius) { attribs.width = attribs.height = 2 * radius; } return attribs; }; /** * Internal function to get presentational attributes for each point. * Unlike {@link Series#markerAttribs}, this function should return * those attributes that can also be set in CSS. In styled mode, * `pointAttribs` won't be called. * * @private * @function Highcharts.Series#pointAttribs * * @param {Highcharts.Point} [point] * The point instance to inspect. * * @param {string} [state] * The point state, can be either `hover`, `select` or 'normal'. If * undefined, normal state is assumed. * * @return {Highcharts.SVGAttributes} * The presentational attributes to be set on the point. */ Series.prototype.pointAttribs = function (point, state) { var seriesMarkerOptions = this.options.marker, seriesStateOptions, pointOptions = point && point.options, pointMarkerOptions = ((pointOptions && pointOptions.marker) || {}), pointStateOptions, color = this.color, pointColorOption = pointOptions && pointOptions.color, pointColor = point && point.color, strokeWidth = pick(pointMarkerOptions.lineWidth, seriesMarkerOptions.lineWidth), zoneColor = point && point.zone && point.zone.color, fill, stroke, opacity = 1; color = (pointColorOption || zoneColor || pointColor || color); fill = (pointMarkerOptions.fillColor || seriesMarkerOptions.fillColor || color); stroke = (pointMarkerOptions.lineColor || seriesMarkerOptions.lineColor || color); // Handle hover and select states state = state || 'normal'; if (state) { seriesStateOptions = seriesMarkerOptions.states[state]; pointStateOptions = (pointMarkerOptions.states && pointMarkerOptions.states[state]) || {}; strokeWidth = pick(pointStateOptions.lineWidth, seriesStateOptions.lineWidth, strokeWidth + pick(pointStateOptions.lineWidthPlus, seriesStateOptions.lineWidthPlus, 0)); fill = (pointStateOptions.fillColor || seriesStateOptions.fillColor || fill); stroke = (pointStateOptions.lineColor || seriesStateOptions.lineColor || stroke); opacity = pick(pointStateOptions.opacity, seriesStateOptions.opacity, opacity); } return { 'stroke': stroke, 'stroke-width': strokeWidth, 'fill': fill, 'opacity': opacity }; }; /** * Clear DOM objects and free up memory. * * @private * @function Highcharts.Series#destroy * * @fires Highcharts.Series#event:destroy */ Series.prototype.destroy = function (keepEventsForUpdate) { var series = this, chart = series.chart, issue134 = /AppleWebKit\/533/.test(win.navigator.userAgent), destroy, i, data = series.data || [], point, axis; // add event hook fireEvent(series, 'destroy'); // remove events this.removeEvents(keepEventsForUpdate); // erase from axes (series.axisTypes || []).forEach(function (AXIS) { axis = series[AXIS]; if (axis && axis.series) { erase(axis.series, series); axis.isDirty = axis.forceRedraw = true; } }); // remove legend items if (series.legendItem) { series.chart.legend.destroyItem(series); } // destroy all points with their elements i = data.length; while (i--) { point = data[i]; if (point && point.destroy) { point.destroy(); } } if (series.clips) { series.clips.forEach(function (clip) { return clip.destroy(); }); } // Clear the animation timeout if we are destroying the series // during initial animation U.clearTimeout(series.animationTimeout); // Destroy all SVGElements associated to the series objectEach(series, function (val, prop) { // Survive provides a hook for not destroying if (val instanceof SVGElement && !val.survive) { // issue 134 workaround destroy = issue134 && prop === 'group' ? 'hide' : 'destroy'; val[destroy](); } }); // remove from hoverSeries if (chart.hoverSeries === series) { chart.hoverSeries = void 0; } erase(chart.series, series); chart.orderSeries(); // clear all members objectEach(series, function (val, prop) { if (!keepEventsForUpdate || prop !== 'hcEvents') { delete series[prop]; } }); }; /** * Clip the graphs into zones for colors and styling. * * @private * @function Highcharts.Series#applyZones */ Series.prototype.applyZones = function () { var series = this, chart = this.chart, renderer = chart.renderer, zones = this.zones, translatedFrom, translatedTo, clips = (this.clips || []), clipAttr, graph = this.graph, area = this.area, chartSizeMax = Math.max(chart.chartWidth, chart.chartHeight), axis = this[(this.zoneAxis || 'y') + 'Axis'], extremes, reversed, inverted = chart.inverted, horiz, pxRange, pxPosMin, pxPosMax, ignoreZones = false, zoneArea, zoneGraph; if (zones.length && (graph || area) && axis && typeof axis.min !== 'undefined') { reversed = axis.reversed; horiz = axis.horiz; // The use of the Color Threshold assumes there are no gaps // so it is safe to hide the original graph and area // unless it is not waterfall series, then use showLine property // to set lines between columns to be visible (#7862) if (graph && !this.showLine) { graph.hide(); } if (area) { area.hide(); } // Create the clips extremes = axis.getExtremes(); zones.forEach(function (threshold, i) { translatedFrom = reversed ? (horiz ? chart.plotWidth : 0) : (horiz ? 0 : (axis.toPixels(extremes.min) || 0)); translatedFrom = clamp(pick(translatedTo, translatedFrom), 0, chartSizeMax); translatedTo = clamp(Math.round(axis.toPixels(pick(threshold.value, extremes.max), true) || 0), 0, chartSizeMax); if (ignoreZones) { translatedFrom = translatedTo = axis.toPixels(extremes.max); } pxRange = Math.abs(translatedFrom - translatedTo); pxPosMin = Math.min(translatedFrom, translatedTo); pxPosMax = Math.max(translatedFrom, translatedTo); if (axis.isXAxis) { clipAttr = { x: inverted ? pxPosMax : pxPosMin, y: 0, width: pxRange, height: chartSizeMax }; if (!horiz) { clipAttr.x = chart.plotHeight - clipAttr.x; } } else { clipAttr = { x: 0, y: inverted ? pxPosMax : pxPosMin, width: chartSizeMax, height: pxRange }; if (horiz) { clipAttr.y = chart.plotWidth - clipAttr.y; } } // VML SUPPPORT if (inverted && renderer.isVML) { if (axis.isXAxis) { clipAttr = { x: 0, y: reversed ? pxPosMin : pxPosMax, height: clipAttr.width, width: chart.chartWidth }; } else { clipAttr = { x: (clipAttr.y - chart.plotLeft - chart.spacingBox.x), y: 0, width: clipAttr.height, height: chart.chartHeight }; } } // END OF VML SUPPORT if (clips[i]) { clips[i].animate(clipAttr); } else { clips[i] = renderer.clipRect(clipAttr); } // when no data, graph zone is not applied and after setData // clip was ignored. As a result, it should be applied each // time. zoneArea = series['zone-area-' + i]; zoneGraph = series['zone-graph-' + i]; if (graph && zoneGraph) { zoneGraph.clip(clips[i]); } if (area && zoneArea) { zoneArea.clip(clips[i]); } // if this zone extends out of the axis, ignore the others ignoreZones = threshold.value > extremes.max; // Clear translatedTo for indicators if (series.resetZones && translatedTo === 0) { translatedTo = void 0; } }); this.clips = clips; } else if (series.visible) { // If zones were removed, restore graph and area if (graph) { graph.show(true); } if (area) { area.show(true); } } }; /** * Initialize and perform group inversion on series.group and * series.markerGroup. * * @private * @function Highcharts.Series#invertGroups */ Series.prototype.invertGroups = function (inverted) { var series = this, chart = series.chart; /** * @private */ function setInvert() { ['group', 'markerGroup'].forEach(function (groupName) { if (series[groupName]) { // VML/HTML needs explicit attributes for flipping if (chart.renderer.isVML) { series[groupName].attr({ width: series.yAxis.len, height: series.xAxis.len }); } series[groupName].width = series.yAxis.len; series[groupName].height = series.xAxis.len; // If inverted polar, don't invert series group series[groupName].invert(series.isRadialSeries ? false : inverted); } }); } // Pie, go away (#1736) if (!series.xAxis) { return; } // A fixed size is needed for inversion to work series.eventsToUnbind.push(addEvent(chart, 'resize', setInvert)); // Do it now setInvert(); // On subsequent render and redraw, just do setInvert without // setting up events again series.invertGroups = setInvert; }; /** * General abstraction for creating plot groups like series.group, * series.dataLabelsGroup and series.markerGroup. On subsequent calls, * the group will only be adjusted to the updated plot size. * * @private * @function Highcharts.Series#plotGroup */ Series.prototype.plotGroup = function (prop, name, visibility, zIndex, parent) { var group = this[prop]; var isNew = !group, attrs = { visibility: visibility, zIndex: zIndex || 0.1 // IE8 and pointer logic use this }; // Avoid setting undefined opacity, or in styled mode if (typeof this.opacity !== 'undefined' && !this.chart.styledMode && this.state !== 'inactive' // #13719 ) { attrs.opacity = this.opacity; } // Generate it on first call if (isNew) { this[prop] = group = this.chart.renderer .g() .add(parent); } // Add the class names, and replace existing ones as response to // Series.update (#6660) group.addClass(('highcharts-' + name + ' highcharts-series-' + this.index + ' highcharts-' + this.type + '-series ' + (defined(this.colorIndex) ? 'highcharts-color-' + this.colorIndex + ' ' : '') + (this.options.className || '') + (group.hasClass('highcharts-tracker') ? ' highcharts-tracker' : '')), true); // Place it on first and subsequent (redraw) calls group.attr(attrs)[isNew ? 'attr' : 'animate'](this.getPlotBox()); return group; }; /** * Get the translation and scale for the plot area of this series. * * @function Highcharts.Series#getPlotBox * * @return {Highcharts.SeriesPlotBoxObject} */ Series.prototype.getPlotBox = function () { var chart = this.chart, xAxis = this.xAxis, yAxis = this.yAxis; // Swap axes for inverted (#2339) if (chart.inverted) { xAxis = yAxis; yAxis = this.xAxis; } return { translateX: xAxis ? xAxis.left : chart.plotLeft, translateY: yAxis ? yAxis.top : chart.plotTop, scaleX: 1, scaleY: 1 }; }; /** * Removes the event handlers attached previously with addEvents. * @private * @function Highcharts.Series#removeEvents */ Series.prototype.removeEvents = function (keepEventsForUpdate) { var series = this; if (!keepEventsForUpdate) { // remove all events removeEvent(series); } if (series.eventsToUnbind.length) { // remove only internal events for proper update // #12355 - solves problem with multiple destroy events series.eventsToUnbind.forEach(function (unbind) { unbind(); }); series.eventsToUnbind.length = 0; } }; /** * Render the graph and markers. Called internally when first rendering * and later when redrawing the chart. This function can be extended in * plugins, but normally shouldn't be called directly. * * @function Highcharts.Series#render * * @fires Highcharts.Series#event:afterRender */ Series.prototype.render = function () { var series = this, chart = series.chart, group, options = series.options, animOptions = animObject(options.animation), // Animation doesn't work in IE8 quirks when the group div is // hidden, and looks bad in other oldIE animDuration = (!series.finishedAnimating && chart.renderer.isSVG && animOptions.duration), visibility = series.visible ? 'inherit' : 'hidden', // #2597 zIndex = options.zIndex, hasRendered = series.hasRendered, chartSeriesGroup = chart.seriesGroup, inverted = chart.inverted; fireEvent(this, 'render'); // the group group = series.plotGroup('group', 'series', visibility, zIndex, chartSeriesGroup); series.markerGroup = series.plotGroup('markerGroup', 'markers', visibility, zIndex, chartSeriesGroup); // initiate the animation if (animDuration && series.animate) { series.animate(true); } // SVGRenderer needs to know this before drawing elements (#1089, // #1795) group.inverted = pick(series.invertible, series.isCartesian) ? inverted : false; // Draw the graph if any if (series.drawGraph) { series.drawGraph(); series.applyZones(); } // Draw the points if (series.visible) { series.drawPoints(); } /* series.points.forEach(function (point) { if (point.redraw) { point.redraw(); } }); */ // Draw the data labels if (series.drawDataLabels) { series.drawDataLabels(); } // In pie charts, slices are added to the DOM, but actual rendering // is postponed until labels reserved their space if (series.redrawPoints) { series.redrawPoints(); } // draw the mouse tracking area if (series.drawTracker && series.options.enableMouseTracking !== false) { series.drawTracker(); } // Handle inverted series and tracker groups series.invertGroups(inverted); // Initial clipping, must be defined after inverting groups for VML. // Applies to columns etc. (#3839). if (options.clip !== false && !series.sharedClipKey && !hasRendered) { group.clip(chart.clipRect); } // Run the animation if (animDuration && series.animate) { series.animate(); } // Call the afterAnimate function on animation complete (but don't // overwrite the animation.complete option which should be available // to the user). if (!hasRendered) { // Additional time if defer is defined before afterAnimate // will be triggered if (animDuration && animOptions.defer) { animDuration += animOptions.defer; } series.animationTimeout = syncTimeout(function () { series.afterAnimate(); }, animDuration || 0); } // Means data is in accordance with what you see series.isDirty = false; // (See #322) series.isDirty = series.isDirtyData = false; // means // data is in accordance with what you see series.hasRendered = true; fireEvent(series, 'afterRender'); }; /** * Redraw the series. This function is called internally from * `chart.redraw` and normally shouldn't be called directly. * @private * @function Highcharts.Series#redraw */ Series.prototype.redraw = function () { var series = this, chart = series.chart, // cache it here as it is set to false in render, but used after wasDirty = series.isDirty || series.isDirtyData, group = series.group, xAxis = series.xAxis, yAxis = series.yAxis; // reposition on resize if (group) { if (chart.inverted) { group.attr({ width: chart.plotWidth, height: chart.plotHeight }); } group.animate({ translateX: pick(xAxis && xAxis.left, chart.plotLeft), translateY: pick(yAxis && yAxis.top, chart.plotTop) }); } series.translate(); series.render(); if (wasDirty) { // #3868, #3945 delete this.kdTree; } }; /** * @private * @function Highcharts.Series#searchPoint */ Series.prototype.searchPoint = function (e, compareX) { var series = this, xAxis = series.xAxis, yAxis = series.yAxis, inverted = series.chart.inverted; return this.searchKDTree({ clientX: inverted ? xAxis.len - e.chartY + xAxis.pos : e.chartX - xAxis.pos, plotY: inverted ? yAxis.len - e.chartX + yAxis.pos : e.chartY - yAxis.pos }, compareX, e); }; /** * Build the k-d-tree that is used by mouse and touch interaction to get * the closest point. Line-like series typically have a one-dimensional * tree where points are searched along the X axis, while scatter-like * series typically search in two dimensions, X and Y. * * @private * @function Highcharts.Series#buildKDTree */ Series.prototype.buildKDTree = function (e) { // Prevent multiple k-d-trees from being built simultaneously // (#6235) this.buildingKdTree = true; var series = this, dimensions = series.options.findNearestPointBy .indexOf('y') > -1 ? 2 : 1; /** * Internal function * @private */ function _kdtree(points, depth, dimensions) { var axis, median, length = points && points.length; if (length) { // alternate between the axis axis = series.kdAxisArray[depth % dimensions]; // sort point array points.sort(function (a, b) { return a[axis] - b[axis]; }); median = Math.floor(length / 2); // build and return nod return { point: points[median], left: _kdtree(points.slice(0, median), depth + 1, dimensions), right: _kdtree(points.slice(median + 1), depth + 1, dimensions) }; } } /** * Start the recursive build process with a clone of the points * array and null points filtered out. (#3873) * @private */ function startRecursive() { series.kdTree = _kdtree(series.getValidPoints(null, // For line-type series restrict to plot area, but // column-type series not (#3916, #4511) !series.directTouch), dimensions, dimensions); series.buildingKdTree = false; } delete series.kdTree; // For testing tooltips, don't build async. Also if touchstart, we // may be dealing with click events on mobile, so don't delay // (#6817). syncTimeout(startRecursive, series.options.kdNow || (e && e.type === 'touchstart') ? 0 : 1); }; /** * @private * @function Highcharts.Series#searchKDTree */ Series.prototype.searchKDTree = function (point, compareX, e) { var series = this, kdX = this.kdAxisArray[0], kdY = this.kdAxisArray[1], kdComparer = compareX ? 'distX' : 'dist', kdDimensions = series.options.findNearestPointBy .indexOf('y') > -1 ? 2 : 1; /** * Set the one and two dimensional distance on the point object. * @private */ function setDistance(p1, p2) { var x = (defined(p1[kdX]) && defined(p2[kdX])) ? Math.pow(p1[kdX] - p2[kdX], 2) : null, y = (defined(p1[kdY]) && defined(p2[kdY])) ? Math.pow(p1[kdY] - p2[kdY], 2) : null, r = (x || 0) + (y || 0); p2.dist = defined(r) ? Math.sqrt(r) : Number.MAX_VALUE; p2.distX = defined(x) ? Math.sqrt(x) : Number.MAX_VALUE; } /** * @private */ function _search(search, tree, depth, dimensions) { var point = tree.point, axis = series.kdAxisArray[depth % dimensions], tdist, sideA, sideB, ret = point, nPoint1, nPoint2; setDistance(search, point); // Pick side based on distance to splitting point tdist = search[axis] - point[axis]; sideA = tdist < 0 ? 'left' : 'right'; sideB = tdist < 0 ? 'right' : 'left'; // End of tree if (tree[sideA]) { nPoint1 = _search(search, tree[sideA], depth + 1, dimensions); ret = (nPoint1[kdComparer] < ret[kdComparer] ? nPoint1 : point); } if (tree[sideB]) { // compare distance to current best to splitting point to // decide wether to check side B or not if (Math.sqrt(tdist * tdist) < ret[kdComparer]) { nPoint2 = _search(search, tree[sideB], depth + 1, dimensions); ret = (nPoint2[kdComparer] < ret[kdComparer] ? nPoint2 : ret); } } return ret; } if (!this.kdTree && !this.buildingKdTree) { this.buildKDTree(e); } if (this.kdTree) { return _search(point, this.kdTree, kdDimensions, kdDimensions); } }; /** * @private * @function Highcharts.Series#pointPlacementToXValue */ Series.prototype.pointPlacementToXValue = function () { var _a = this, _b = _a.options, pointPlacement = _b.pointPlacement, pointRange = _b.pointRange, axis = _a.xAxis; var factor = pointPlacement; // Point placement is relative to each series pointRange (#5889) if (factor === 'between') { factor = axis.reversed ? -0.5 : 0.5; // #11955 } return isNumber(factor) ? factor * (pointRange || axis.pointRange) : 0; }; /** * @private * @function Highcharts.Series#isPointInside */ Series.prototype.isPointInside = function (point) { var isInside = typeof point.plotY !== 'undefined' && typeof point.plotX !== 'undefined' && point.plotY >= 0 && point.plotY <= this.yAxis.len && // #3519 point.plotX >= 0 && point.plotX <= this.xAxis.len; return isInside; }; /** * Draw the tracker object that sits above all data labels and markers to * track mouse events on the graph or points. For the line type charts * the tracker uses the same graphPath, but with a greater stroke width * for better control. * @private */ Series.prototype.drawTracker = function () { var series = this, options = series.options, trackByArea = options.trackByArea, trackerPath = [].concat(trackByArea ? series.areaPath : series.graphPath), // trackerPathLength = trackerPath.length, chart = series.chart, pointer = chart.pointer, renderer = chart.renderer, snap = chart.options.tooltip.snap, tracker = series.tracker, i, onMouseOver = function (e) { if (chart.hoverSeries !== series) { series.onMouseOver(); } }, /* * Empirical lowest possible opacities for TRACKER_FILL for an * element to stay invisible but clickable * IE6: 0.002 * IE7: 0.002 * IE8: 0.002 * IE9: 0.00000000001 (unlimited) * IE10: 0.0001 (exporting only) * FF: 0.00000000001 (unlimited) * Chrome: 0.000001 * Safari: 0.000001 * Opera: 0.00000000001 (unlimited) */ TRACKER_FILL = 'rgba(192,192,192,' + (svg ? 0.0001 : 0.002) + ')'; // Draw the tracker if (tracker) { tracker.attr({ d: trackerPath }); } else if (series.graph) { // create series.tracker = renderer.path(trackerPath) .attr({ visibility: series.visible ? 'visible' : 'hidden', zIndex: 2 }) .addClass(trackByArea ? 'highcharts-tracker-area' : 'highcharts-tracker-line') .add(series.group); if (!chart.styledMode) { series.tracker.attr({ 'stroke-linecap': 'round', 'stroke-linejoin': 'round', stroke: TRACKER_FILL, fill: trackByArea ? TRACKER_FILL : 'none', 'stroke-width': series.graph.strokeWidth() + (trackByArea ? 0 : 2 * snap) }); } // The tracker is added to the series group, which is clipped, but // is covered by the marker group. So the marker group also needs to // capture events. [ series.tracker, series.markerGroup, series.dataLabelsGroup ].forEach(function (tracker) { if (tracker) { tracker.addClass('highcharts-tracker') .on('mouseover', onMouseOver) .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); }); if (options.cursor && !chart.styledMode) { tracker.css({ cursor: options.cursor }); } if (hasTouch) { tracker.on('touchstart', onMouseOver); } } }); } fireEvent(this, 'afterDrawTracker'); }; /** * Add a point to the series after render time. The point can be added at * the end, or by giving it an X value, to the start or in the middle of the * series. * * @sample highcharts/members/series-addpoint-append/ * Append point * @sample highcharts/members/series-addpoint-append-and-shift/ * Append and shift * @sample highcharts/members/series-addpoint-x-and-y/ * Both X and Y values given * @sample highcharts/members/series-addpoint-pie/ * Append pie slice * @sample stock/members/series-addpoint/ * Append 100 points in Highcharts Stock * @sample stock/members/series-addpoint-shift/ * Append and shift in Highcharts Stock * @sample maps/members/series-addpoint/ * Add a point in Highmaps * * @function Highcharts.Series#addPoint * * @param {Highcharts.PointOptionsType} options * The point options. If options is a single number, a point with * that y value is appended to the series. If it is an array, it will * be interpreted as x and y values respectively. If it is an * object, advanced options as outlined under `series.data` are * applied. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the point is added. When adding * more than one point, it is highly recommended that the redraw * option be set to false, and instead {@link Chart#redraw} is * explicitly called after the adding of points is finished. * Otherwise, the chart will redraw after adding each point. * * @param {boolean} [shift=false] * If true, a point is shifted off the start of the series as one is * appended to the end. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] * Whether to apply animation, and optionally animation * configuration. * * @param {boolean} [withEvent=true] * Used internally, whether to fire the series `addPoint` event. * * @fires Highcharts.Series#event:addPoint */ Series.prototype.addPoint = function (options, redraw, shift, animation, withEvent) { var series = this, seriesOptions = series.options, data = series.data, chart = series.chart, xAxis = series.xAxis, names = xAxis && xAxis.hasNames && xAxis.names, dataOptions = seriesOptions.data, point, xData = series.xData, isInTheMiddle, i, x; // Optional redraw, defaults to true redraw = pick(redraw, true); // Get options and push the point to xData, yData and series.options. In // series.generatePoints the Point instance will be created on demand // and pushed to the series.data array. point = { series: series }; series.pointClass.prototype.applyOptions.apply(point, [options]); x = point.x; // Get the insertion point i = xData.length; if (series.requireSorting && x < xData[i - 1]) { isInTheMiddle = true; while (i && xData[i - 1] > x) { i--; } } // Insert undefined item series.updateParallelArrays(point, 'splice', i, 0, 0); // Update it series.updateParallelArrays(point, i); if (names && point.name) { names[x] = point.name; } dataOptions.splice(i, 0, options); if (isInTheMiddle) { series.data.splice(i, 0, null); series.processData(); } // Generate points to be added to the legend (#1329) if (seriesOptions.legendType === 'point') { series.generatePoints(); } // Shift the first point off the parallel arrays if (shift) { if (data[0] && data[0].remove) { data[0].remove(false); } else { data.shift(); series.updateParallelArrays(point, 'shift'); dataOptions.shift(); } } // Fire event if (withEvent !== false) { fireEvent(series, 'addPoint', { point: point }); } // redraw series.isDirty = true; series.isDirtyData = true; if (redraw) { chart.redraw(animation); // Animation is set anyway on redraw, #5665 } }; /** * Remove a point from the series. Unlike the * {@link Highcharts.Point#remove} method, this can also be done on a point * that is not instanciated because it is outside the view or subject to * Highcharts Stock data grouping. * * @sample highcharts/members/series-removepoint/ * Remove cropped point * * @function Highcharts.Series#removePoint * * @param {number} i * The index of the point in the {@link Highcharts.Series.data|data} * array. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the point is added. When * removing more than one point, it is highly recommended that the * `redraw` option be set to `false`, and instead {@link * Highcharts.Chart#redraw} is explicitly called after the adding of * points is finished. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] * Whether and optionally how the series should be animated. * * @fires Highcharts.Point#event:remove */ Series.prototype.removePoint = function (i, redraw, animation) { var series = this, data = series.data, point = data[i], points = series.points, chart = series.chart, remove = function () { if (points && points.length === data.length) { // #4935 points.splice(i, 1); } data.splice(i, 1); series.options.data.splice(i, 1); series.updateParallelArrays(point || { series: series }, 'splice', i, 1); if (point) { point.destroy(); } // redraw series.isDirty = true; series.isDirtyData = true; if (redraw) { chart.redraw(); } }; setAnimation(animation, chart); redraw = pick(redraw, true); // Fire the event with a default handler of removing the point if (point) { point.firePointEvent('remove', null, remove); } else { remove(); } }; /** * Remove a series and optionally redraw the chart. * * @sample highcharts/members/series-remove/ * Remove first series from a button * * @function Highcharts.Series#remove * * @param {boolean} [redraw=true] * Whether to redraw the chart or wait for an explicit call to * {@link Highcharts.Chart#redraw}. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation] * Whether to apply animation, and optionally animation * configuration. * * @param {boolean} [withEvent=true] * Used internally, whether to fire the series `remove` event. * * @fires Highcharts.Series#event:remove */ Series.prototype.remove = function (redraw, animation, withEvent, keepEvents) { var series = this, chart = series.chart; /** * @private */ function remove() { // Destroy elements series.destroy(keepEvents); // Redraw chart.isDirtyLegend = chart.isDirtyBox = true; chart.linkSeries(); if (pick(redraw, true)) { chart.redraw(animation); } } // Fire the event with a default handler of removing the point if (withEvent !== false) { fireEvent(series, 'remove', null, remove); } else { remove(); } }; /** * Update the series with a new set of options. For a clean and precise * handling of new options, all methods and elements from the series are * removed, and it is initialized from scratch. Therefore, this method is * more performance expensive than some other utility methods like {@link * Series#setData} or {@link Series#setVisible}. * * Note that `Series.update` may mutate the passed `data` options. * * @sample highcharts/members/series-update/ * Updating series options * @sample maps/members/series-update/ * Update series options in Highmaps * * @function Highcharts.Series#update * * @param {Highcharts.SeriesOptionsType} options * New options that will be merged with the series' existing options. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the series is altered. If doing * more operations on the chart, it is a good idea to set redraw to * false and call {@link Chart#redraw} after. * * @fires Highcharts.Series#event:update * @fires Highcharts.Series#event:afterUpdate */ Series.prototype.update = function (options, redraw) { options = cleanRecursively(options, this.userOptions); fireEvent(this, 'update', { options: options }); var series = this, chart = series.chart, // must use user options when changing type because series.options // is merged in with type specific plotOptions oldOptions = series.userOptions, seriesOptions, initialType = series.initialType || series.type, plotOptions = chart.options.plotOptions, newType = (options.type || oldOptions.type || chart.options.chart.type), keepPoints = !( // Indicators, histograms etc recalculate the data. It should be // possible to omit this. this.hasDerivedData || // New type requires new point classes (newType && newType !== this.type) || // New options affecting how the data points are built typeof options.pointStart !== 'undefined' || typeof options.pointInterval !== 'undefined' || // Changes to data grouping requires new points in new group series.hasOptionChanged('dataGrouping') || series.hasOptionChanged('pointStart') || series.hasOptionChanged('pointInterval') || series.hasOptionChanged('pointIntervalUnit') || series.hasOptionChanged('keys')), initialSeriesProto = seriesTypes[initialType].prototype, n, groups = [ 'group', 'markerGroup', 'dataLabelsGroup', 'transformGroup' ], preserve = [ 'eventOptions', 'navigatorSeries', 'baseSeries' ], // Animation must be enabled when calling update before the initial // animation has first run. This happens when calling update // directly after chart initialization, or when applying responsive // rules (#6912). animation = series.finishedAnimating && { animation: false }, kinds = {}; newType = newType || initialType; if (keepPoints) { preserve.push('data', 'isDirtyData', 'points', 'processedXData', 'processedYData', 'xIncrement', 'cropped', '_hasPointMarkers', '_hasPointLabels', 'clips', // #15420 // Networkgraph (#14397) 'nodes', 'layout', // Map specific, consider moving it to series-specific preserve- // properties (#10617) 'mapMap', 'mapData', 'minY', 'maxY', 'minX', 'maxX'); if (options.visible !== false) { preserve.push('area', 'graph'); } series.parallelArrays.forEach(function (key) { preserve.push(key + 'Data'); }); if (options.data) { // setData uses dataSorting options so we need to update them // earlier if (options.dataSorting) { extend(series.options.dataSorting, options.dataSorting); } this.setData(options.data, false); } } // Do the merge, with some forced options options = merge(oldOptions, animation, { // When oldOptions.index is null it should't be cleared. // Otherwise navigator series will have wrong indexes (#10193). index: typeof oldOptions.index === 'undefined' ? series.index : oldOptions.index, pointStart: pick( // when updating from blank (#7933) plotOptions && plotOptions.series && plotOptions.series.pointStart, oldOptions.pointStart, // when updating after addPoint series.xData[0]) }, (!keepPoints && { data: series.options.data }), options); // Merge does not merge arrays, but replaces them. Since points were // updated, `series.options.data` has correct merged options, use it: if (keepPoints && options.data) { options.data = series.options.data; } // Make sure preserved properties are not destroyed (#3094) preserve = groups.concat(preserve); preserve.forEach(function (prop) { preserve[prop] = series[prop]; delete series[prop]; }); var casting = false; if (seriesTypes[newType]) { casting = newType !== series.type; // Destroy the series and delete all properties, it will be // reinserted within the `init` call below series.remove(false, false, false, true); if (casting) { // Modern browsers including IE11 // @todo slow, consider alternatives mentioned: // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object/setPrototypeOf if (Object.setPrototypeOf) { Object.setPrototypeOf(series, seriesTypes[newType].prototype); // Legacy (IE < 11) } else { var ownEvents = Object.hasOwnProperty.call(series, 'hcEvents') && series.hcEvents; for (n in initialSeriesProto) { // eslint-disable-line guard-for-in series[n] = void 0; } // Reinsert all methods and properties from the new type // prototype (#2270, #3719). extend(series, seriesTypes[newType].prototype); // The events are tied to the prototype chain, don't copy if // they're not the series' own if (ownEvents) { series.hcEvents = ownEvents; } else { delete series.hcEvents; } } } } else { error(17, true, chart, { missingModuleFor: newType }); } // Re-register groups (#3094) and other preserved properties preserve.forEach(function (prop) { series[prop] = preserve[prop]; }); series.init(chart, options); // Remove particular elements of the points. Check `series.options` // because we need to consider the options being set on plotOptions as // well. if (keepPoints && this.points) { seriesOptions = series.options; // What kind of elements to destroy if (seriesOptions.visible === false) { kinds.graphic = 1; kinds.dataLabel = 1; } else if (!series._hasPointLabels) { var marker = seriesOptions.marker, dataLabels = seriesOptions.dataLabels; if (marker && (marker.enabled === false || 'symbol' in marker // #10870 )) { kinds.graphic = 1; } if (dataLabels && dataLabels.enabled === false) { kinds.dataLabel = 1; } } this.points.forEach(function (point) { if (point && point.series) { point.resolveColor(); // Destroy elements in order to recreate based on updated // series options. if (Object.keys(kinds).length) { point.destroyElements(kinds); } if (seriesOptions.showInLegend === false && point.legendItem) { chart.legend.destroyItem(point); } } }, this); } series.initialType = initialType; chart.linkSeries(); // Links are lost in series.remove (#3028) // #15383: Fire updatedData if the type has changed to keep linked // series such as indicators updated if (casting && series.linkedSeries.length) { series.isDirtyData = true; } fireEvent(this, 'afterUpdate'); if (pick(redraw, true)) { chart.redraw(keepPoints ? void 0 : false); } }; /** * Used from within series.update * @private */ Series.prototype.setName = function (name) { this.name = this.options.name = this.userOptions.name = name; this.chart.isDirtyLegend = true; }; /** * Check if the option has changed. * @private */ Series.prototype.hasOptionChanged = function (optionName) { var chart = this.chart, option = this.options[optionName], plotOptions = chart.options.plotOptions, oldOption = this.userOptions[optionName]; if (oldOption) { return option !== oldOption; } return option !== pick(plotOptions && plotOptions[this.type] && plotOptions[this.type][optionName], plotOptions && plotOptions.series && plotOptions.series[optionName], option); }; /** * Runs on mouse over the series graphical items. * * @function Highcharts.Series#onMouseOver * @fires Highcharts.Series#event:mouseOver */ Series.prototype.onMouseOver = function () { var series = this, chart = series.chart, hoverSeries = chart.hoverSeries, pointer = chart.pointer; pointer.setHoverChartIndex(); // set normal state to previous series if (hoverSeries && hoverSeries !== series) { hoverSeries.onMouseOut(); } // trigger the event, but to save processing time, // only if defined if (series.options.events.mouseOver) { fireEvent(series, 'mouseOver'); } // hover this series.setState('hover'); /** * Contains the original hovered series. * * @name Highcharts.Chart#hoverSeries * @type {Highcharts.Series|null} */ chart.hoverSeries = series; }; /** * Runs on mouse out of the series graphical items. * * @function Highcharts.Series#onMouseOut * * @fires Highcharts.Series#event:mouseOut */ Series.prototype.onMouseOut = function () { // trigger the event only if listeners exist var series = this, options = series.options, chart = series.chart, tooltip = chart.tooltip, hoverPoint = chart.hoverPoint; // #182, set to null before the mouseOut event fires chart.hoverSeries = null; // trigger mouse out on the point, which must be in this series if (hoverPoint) { hoverPoint.onMouseOut(); } // fire the mouse out event if (series && options.events.mouseOut) { fireEvent(series, 'mouseOut'); } // hide the tooltip if (tooltip && !series.stickyTracking && (!tooltip.shared || series.noSharedTooltip)) { tooltip.hide(); } // Reset all inactive states chart.series.forEach(function (s) { s.setState('', true); }); }; /** * Set the state of the series. Called internally on mouse interaction * operations, but it can also be called directly to visually * highlight a series. * * @function Highcharts.Series#setState * * @param {Highcharts.SeriesStateValue|""} [state] * The new state, can be either `'hover'`, `'inactive'`, `'select'`, * or `''` (an empty string), `'normal'` or `undefined` to set to * normal state. * @param {boolean} [inherit] * Determines if state should be inherited by points too. */ Series.prototype.setState = function (state, inherit) { var series = this, options = series.options, graph = series.graph, inactiveOtherPoints = options.inactiveOtherPoints, stateOptions = options.states, lineWidth = options.lineWidth, opacity = options.opacity, // By default a quick animation to hover/inactive, // slower to un-hover stateAnimation = pick((stateOptions[state || 'normal'] && stateOptions[state || 'normal'].animation), series.chart.options.chart.animation), attribs, i = 0; state = state || ''; if (series.state !== state) { // Toggle class names [ series.group, series.markerGroup, series.dataLabelsGroup ].forEach(function (group) { if (group) { // Old state if (series.state) { group.removeClass('highcharts-series-' + series.state); } // New state if (state) { group.addClass('highcharts-series-' + state); } } }); series.state = state; if (!series.chart.styledMode) { if (stateOptions[state] && stateOptions[state].enabled === false) { return; } if (state) { lineWidth = (stateOptions[state].lineWidth || lineWidth + (stateOptions[state].lineWidthPlus || 0)); // #4035 opacity = pick(stateOptions[state].opacity, opacity); } if (graph && !graph.dashstyle) { attribs = { 'stroke-width': lineWidth }; // Animate the graph stroke-width. graph.animate(attribs, stateAnimation); while (series['zone-graph-' + i]) { series['zone-graph-' + i].animate(attribs, stateAnimation); i = i + 1; } } // For some types (pie, networkgraph, sankey) opacity is // resolved on a point level if (!inactiveOtherPoints) { [ series.group, series.markerGroup, series.dataLabelsGroup, series.labelBySeries ].forEach(function (group) { if (group) { group.animate({ opacity: opacity }, stateAnimation); } }); } } } // Don't loop over points on a series that doesn't apply inactive state // to siblings markers (e.g. line, column) if (inherit && inactiveOtherPoints && series.points) { series.setAllPointsToState(state || void 0); } }; /** * Set the state for all points in the series. * * @function Highcharts.Series#setAllPointsToState * * @private * * @param {string} [state] * Can be either `hover` or undefined to set to normal state. */ Series.prototype.setAllPointsToState = function (state) { this.points.forEach(function (point) { if (point.setState) { point.setState(state); } }); }; /** * Show or hide the series. * * @function Highcharts.Series#setVisible * * @param {boolean} [visible] * True to show the series, false to hide. If undefined, the visibility is * toggled. * * @param {boolean} [redraw=true] * Whether to redraw the chart after the series is altered. If doing more * operations on the chart, it is a good idea to set redraw to false and * call {@link Chart#redraw|chart.redraw()} after. * * @fires Highcharts.Series#event:hide * @fires Highcharts.Series#event:show */ Series.prototype.setVisible = function (vis, redraw) { var series = this, chart = series.chart, legendItem = series.legendItem, showOrHide, ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, oldVisibility = series.visible; // if called without an argument, toggle visibility series.visible = vis = series.options.visible = series.userOptions.visible = typeof vis === 'undefined' ? !oldVisibility : vis; // #5618 showOrHide = vis ? 'show' : 'hide'; // show or hide elements [ 'group', 'dataLabelsGroup', 'markerGroup', 'tracker', 'tt' ].forEach(function (key) { if (series[key]) { series[key][showOrHide](); } }); // hide tooltip (#1361) if (chart.hoverSeries === series || (chart.hoverPoint && chart.hoverPoint.series) === series) { series.onMouseOut(); } if (legendItem) { chart.legend.colorizeItem(series, vis); } // rescale or adapt to resized chart series.isDirty = true; // in a stack, all other series are affected if (series.options.stacking) { chart.series.forEach(function (otherSeries) { if (otherSeries.options.stacking && otherSeries.visible) { otherSeries.isDirty = true; } }); } // show or hide linked series series.linkedSeries.forEach(function (otherSeries) { otherSeries.setVisible(vis, false); }); if (ignoreHiddenSeries) { chart.isDirtyBox = true; } fireEvent(series, showOrHide); if (redraw !== false) { chart.redraw(); } }; /** * Show the series if hidden. * * @sample highcharts/members/series-hide/ * Toggle visibility from a button * * @function Highcharts.Series#show * @fires Highcharts.Series#event:show */ Series.prototype.show = function () { this.setVisible(true); }; /** * Hide the series if visible. If the * [chart.ignoreHiddenSeries](https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries) * option is true, the chart is redrawn without this series. * * @sample highcharts/members/series-hide/ * Toggle visibility from a button * * @function Highcharts.Series#hide * @fires Highcharts.Series#event:hide */ Series.prototype.hide = function () { this.setVisible(false); }; /** * Select or unselect the series. This means its * {@link Highcharts.Series.selected|selected} * property is set, the checkbox in the legend is toggled and when selected, * the series is returned by the {@link Highcharts.Chart#getSelectedSeries} * function. * * @sample highcharts/members/series-select/ * Select a series from a button * * @function Highcharts.Series#select * * @param {boolean} [selected] * True to select the series, false to unselect. If undefined, the selection * state is toggled. * * @fires Highcharts.Series#event:select * @fires Highcharts.Series#event:unselect */ Series.prototype.select = function (selected) { var series = this; series.selected = selected = this.options.selected = (typeof selected === 'undefined' ? !series.selected : selected); if (series.checkbox) { series.checkbox.checked = selected; } fireEvent(series, selected ? 'select' : 'unselect'); }; /** * Checks if a tooltip should be shown for a given point. * * @private * @param {number} plotX * @param {number} plotY * @param {Highcharts.ChartIsInsideOptionsObject} [options] * @return {boolean} */ Series.prototype.shouldShowTooltip = function (plotX, plotY, options) { if (options === void 0) { options = {}; } options.series = this; options.visiblePlotOnly = true; return this.chart.isInsidePlot(plotX, plotY, options); }; /** * General options for all series types. * * @optionparent plotOptions.series */ Series.defaultOptions = { // base series options /** * The SVG value used for the `stroke-linecap` and `stroke-linejoin` * of a line graph. Round means that lines are rounded in the ends and * bends. * * @type {Highcharts.SeriesLinecapValue} * @default round * @since 3.0.7 * @apioption plotOptions.line.linecap */ /** * Pixel width of the graph line. * * @see In styled mode, the line stroke-width can be set with the * `.highcharts-graph` class name. * * @sample {highcharts} highcharts/plotoptions/series-linewidth-general/ * On all series * @sample {highcharts} highcharts/plotoptions/series-linewidth-specific/ * On one single series * * @product highcharts highstock * * @private */ lineWidth: 2, /** * For some series, there is a limit that shuts down initial animation * by default when the total number of points in the chart is too high. * For example, for a column chart and its derivatives, animation does * not run if there is more than 250 points totally. To disable this * cap, set `animationLimit` to `Infinity`. * * @type {number} * @apioption plotOptions.series.animationLimit */ /** * Allow this series' points to be selected by clicking on the graphic * (columns, point markers, pie slices, map areas etc). * * The selected points can be handled by point select and unselect * events, or collectively by the [getSelectedPoints * ](/class-reference/Highcharts.Chart#getSelectedPoints) function. * * And alternative way of selecting points is through dragging. * * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-line/ * Line * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-column/ * Column * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-pie/ * Pie * @sample {highcharts} highcharts/chart/events-selection-points/ * Select a range of points through a drag selection * @sample {highmaps} maps/plotoptions/series-allowpointselect/ * Map area * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/ * Map bubble * * @since 1.2.0 * * @private */ allowPointSelect: false, /** * When true, each point or column edge is rounded to its nearest pixel * in order to render sharp on screen. In some cases, when there are a * lot of densely packed columns, this leads to visible difference * in column widths or distance between columns. In these cases, * setting `crisp` to `false` may look better, even though each column * is rendered blurry. * * @sample {highcharts} highcharts/plotoptions/column-crisp-false/ * Crisp is false * * @since 5.0.10 * @product highcharts highstock gantt * * @private */ crisp: true, /** * If true, a checkbox is displayed next to the legend item to allow * selecting the series. The state of the checkbox is determined by * the `selected` option. * * @productdesc {highmaps} * Note that if a `colorAxis` is defined, the color axis is represented * in the legend, not the series. * * @sample {highcharts} highcharts/plotoptions/series-showcheckbox-true/ * Show select box * * @since 1.2.0 * * @private */ showCheckbox: false, /** * Enable or disable the initial animation when a series is displayed. * The animation can also be set as a configuration object. Please * note that this option only applies to the initial animation of the * series itself. For other animations, see [chart.animation]( * #chart.animation) and the animation parameter under the API methods. * The following properties are supported: * * - `defer`: The animation delay time in milliseconds. * * - `duration`: The duration of the animation in milliseconds. * * - `easing`: Can be a string reference to an easing function set on * the `Math` object or a function. See the _Custom easing function_ * demo below. * * Due to poor performance, animation is disabled in old IE browsers * for several chart types. * * @sample {highcharts} highcharts/plotoptions/series-animation-disabled/ * Animation disabled * @sample {highcharts} highcharts/plotoptions/series-animation-slower/ * Slower animation * @sample {highcharts} highcharts/plotoptions/series-animation-easing/ * Custom easing function * @sample {highstock} stock/plotoptions/animation-slower/ * Slower animation * @sample {highstock} stock/plotoptions/animation-easing/ * Custom easing function * @sample {highmaps} maps/plotoptions/series-animation-true/ * Animation enabled on map series * @sample {highmaps} maps/plotoptions/mapbubble-animation-false/ * Disabled on mapbubble series * * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} * @default {highcharts} true * @default {highstock} true * @default {highmaps} false * * @private */ animation: { /** @internal */ duration: 1000 }, /** * @default 0 * @type {number} * @since 8.2.0 * @apioption plotOptions.series.animation.defer */ /** * An additional class name to apply to the series' graphical elements. * This option does not replace default class names of the graphical * element. * * @type {string} * @since 5.0.0 * @apioption plotOptions.series.className */ /** * Disable this option to allow series rendering in the whole plotting * area. * * **Note:** Clipping should be always enabled when * [chart.zoomType](#chart.zoomType) is set * * @sample {highcharts} highcharts/plotoptions/series-clip/ * Disabled clipping * * @default true * @type {boolean} * @since 3.0.0 * @apioption plotOptions.series.clip */ /** * The main color of the series. In line type series it applies to the * line and the point markers unless otherwise specified. In bar type * series it applies to the bars unless a color is specified per point. * The default value is pulled from the `options.colors` array. * * In styled mode, the color can be defined by the * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series * color can be set with the `.highcharts-series`, * `.highcharts-color-{n}`, `.highcharts-{type}-series` or * `.highcharts-series-{n}` class, or individual classes given by the * `className` option. * * @productdesc {highmaps} * In maps, the series color is rarely used, as most choropleth maps use * the color to denote the value of each point. The series color can * however be used in a map with multiple series holding categorized * data. * * @sample {highcharts} highcharts/plotoptions/series-color-general/ * General plot option * @sample {highcharts} highcharts/plotoptions/series-color-specific/ * One specific series * @sample {highcharts} highcharts/plotoptions/series-color-area/ * Area color * @sample {highcharts} highcharts/series/infographic/ * Pattern fill * @sample {highmaps} maps/demo/category-map/ * Category map by multiple series * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @apioption plotOptions.series.color */ /** * Styled mode only. A specific color index to use for the series, so * its graphic representations are given the class name * `highcharts-color-{n}`. * * @type {number} * @since 5.0.0 * @apioption plotOptions.series.colorIndex */ /** * Whether to connect a graph line across null points, or render a gap * between the two points on either side of the null. * * @sample {highcharts} highcharts/plotoptions/series-connectnulls-false/ * False by default * @sample {highcharts} highcharts/plotoptions/series-connectnulls-true/ * True * * @type {boolean} * @default false * @product highcharts highstock * @apioption plotOptions.series.connectNulls */ /** * You can set the cursor to "pointer" if you have click events attached * to the series, to signal to the user that the points and lines can * be clicked. * * In styled mode, the series cursor can be set with the same classes * as listed under [series.color](#plotOptions.series.color). * * @sample {highcharts} highcharts/plotoptions/series-cursor-line/ * On line graph * @sample {highcharts} highcharts/plotoptions/series-cursor-column/ * On columns * @sample {highcharts} highcharts/plotoptions/series-cursor-scatter/ * On scatter markers * @sample {highstock} stock/plotoptions/cursor/ * Pointer on a line graph * @sample {highmaps} maps/plotoptions/series-allowpointselect/ * Map area * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/ * Map bubble * * @type {string|Highcharts.CursorValue} * @apioption plotOptions.series.cursor */ /** * A reserved subspace to store options and values for customized * functionality. Here you can add additional data for your own event * callbacks and formatter callbacks. * * @sample {highcharts} highcharts/point/custom/ * Point and series with custom data * * @type {Highcharts.Dictionary<*>} * @apioption plotOptions.series.custom */ /** * Name of the dash style to use for the graph, or for some series types * the outline of each shape. * * In styled mode, the * [stroke dash-array](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-dashstyle/) * can be set with the same classes as listed under * [series.color](#plotOptions.series.color). * * @sample {highcharts} highcharts/plotoptions/series-dashstyle-all/ * Possible values demonstrated * @sample {highcharts} highcharts/plotoptions/series-dashstyle/ * Chart suitable for printing in black and white * @sample {highstock} highcharts/plotoptions/series-dashstyle-all/ * Possible values demonstrated * @sample {highmaps} highcharts/plotoptions/series-dashstyle-all/ * Possible values demonstrated * @sample {highmaps} maps/plotoptions/series-dashstyle/ * Dotted borders on a map * * @type {Highcharts.DashStyleValue} * @default Solid * @since 2.1 * @apioption plotOptions.series.dashStyle */ /** * A description of the series to add to the screen reader information * about the series. * * @type {string} * @since 5.0.0 * @requires modules/accessibility * @apioption plotOptions.series.description */ /** * Options for the series data sorting. * * @type {Highcharts.DataSortingOptionsObject} * @since 8.0.0 * @product highcharts highstock * @apioption plotOptions.series.dataSorting */ /** * Enable or disable data sorting for the series. Use [xAxis.reversed]( * #xAxis.reversed) to change the sorting order. * * @sample {highcharts} highcharts/datasorting/animation/ * Data sorting in scatter-3d * @sample {highcharts} highcharts/datasorting/labels-animation/ * Axis labels animation * @sample {highcharts} highcharts/datasorting/dependent-sorting/ * Dependent series sorting * @sample {highcharts} highcharts/datasorting/independent-sorting/ * Independent series sorting * * @type {boolean} * @since 8.0.0 * @apioption plotOptions.series.dataSorting.enabled */ /** * Whether to allow matching points by name in an update. If this option * is disabled, points will be matched by order. * * @sample {highcharts} highcharts/datasorting/match-by-name/ * Enabled match by name * * @type {boolean} * @since 8.0.0 * @apioption plotOptions.series.dataSorting.matchByName */ /** * Determines what data value should be used to sort by. * * @sample {highcharts} highcharts/datasorting/sort-key/ * Sort key as `z` value * * @type {string} * @since 8.0.0 * @default y * @apioption plotOptions.series.dataSorting.sortKey */ /** * Enable or disable the mouse tracking for a specific series. This * includes point tooltips and click events on graphs and points. For * large datasets it improves performance. * * @sample {highcharts} highcharts/plotoptions/series-enablemousetracking-false/ * No mouse tracking * @sample {highmaps} maps/plotoptions/series-enablemousetracking-false/ * No mouse tracking * * @type {boolean} * @default true * @apioption plotOptions.series.enableMouseTracking */ /** * Whether to use the Y extremes of the total chart width or only the * zoomed area when zooming in on parts of the X axis. By default, the * Y axis adjusts to the min and max of the visible data. Cartesian * series only. * * @type {boolean} * @default false * @since 4.1.6 * @product highcharts highstock gantt * @apioption plotOptions.series.getExtremesFromAll */ /** * An array specifying which option maps to which key in the data point * array. This makes it convenient to work with unstructured data arrays * from different sources. * * @see [series.data](#series.line.data) * * @sample {highcharts|highstock} highcharts/series/data-keys/ * An extended data array with keys * @sample {highcharts|highstock} highcharts/series/data-nested-keys/ * Nested keys used to access object properties * * @type {Array<string>} * @since 4.1.6 * @apioption plotOptions.series.keys */ /** * The line cap used for line ends and line joins on the graph. * * @type {Highcharts.SeriesLinecapValue} * @default round * @product highcharts highstock * @apioption plotOptions.series.linecap */ /** * The [id](#series.id) of another series to link to. Additionally, * the value can be ":previous" to link to the previous series. When * two series are linked, only the first one appears in the legend. * Toggling the visibility of this also toggles the linked series. * * If master series uses data sorting and linked series does not have * its own sorting definition, the linked series will be sorted in the * same order as the master one. * * @sample {highcharts|highstock} highcharts/demo/arearange-line/ * Linked series * * @type {string} * @since 3.0 * @product highcharts highstock gantt * @apioption plotOptions.series.linkedTo */ /** * Options for the corresponding navigator series if `showInNavigator` * is `true` for this series. Available options are the same as any * series, documented at [plotOptions](#plotOptions.series) and * [series](#series). * * These options are merged with options in [navigator.series]( * #navigator.series), and will take precedence if the same option is * defined both places. * * @see [navigator.series](#navigator.series) * * @type {Highcharts.PlotSeriesOptions} * @since 5.0.0 * @product highstock * @apioption plotOptions.series.navigatorOptions */ /** * The color for the parts of the graph or points that are below the * [threshold](#plotOptions.series.threshold). Note that `zones` takes * precedence over the negative color. Using `negativeColor` is * equivalent to applying a zone with value of 0. * * @see In styled mode, a negative color is applied by setting this option * to `true` combined with the `.highcharts-negative` class name. * * @sample {highcharts} highcharts/plotoptions/series-negative-color/ * Spline, area and column * @sample {highcharts} highcharts/plotoptions/arearange-negativecolor/ * Arearange * @sample {highcharts} highcharts/css/series-negative-color/ * Styled mode * @sample {highstock} highcharts/plotoptions/series-negative-color/ * Spline, area and column * @sample {highstock} highcharts/plotoptions/arearange-negativecolor/ * Arearange * @sample {highmaps} highcharts/plotoptions/series-negative-color/ * Spline, area and column * @sample {highmaps} highcharts/plotoptions/arearange-negativecolor/ * Arearange * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 3.0 * @apioption plotOptions.series.negativeColor */ /** * Same as * [accessibility.series.descriptionFormatter](#accessibility.series.descriptionFormatter), * but for an individual series. Overrides the chart wide configuration. * * @type {Function} * @since 5.0.12 * @apioption plotOptions.series.pointDescriptionFormatter */ /** * If no x values are given for the points in a series, `pointInterval` * defines the interval of the x values. For example, if a series * contains one value every decade starting from year 0, set * `pointInterval` to `10`. In true `datetime` axes, the `pointInterval` * is set in milliseconds. * * It can be also be combined with `pointIntervalUnit` to draw irregular * time intervals. * * Please note that this options applies to the _series data_, not the * interval of the axis ticks, which is independent. * * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/ * Datetime X axis * @sample {highstock} stock/plotoptions/pointinterval-pointstart/ * Using pointStart and pointInterval * * @type {number} * @default 1 * @product highcharts highstock gantt * @apioption plotOptions.series.pointInterval */ /** * On datetime series, this allows for setting the * [pointInterval](#plotOptions.series.pointInterval) to irregular time * units, `day`, `month` and `year`. A day is usually the same as 24 * hours, but `pointIntervalUnit` also takes the DST crossover into * consideration when dealing with local time. Combine this option with * `pointInterval` to draw weeks, quarters, 6 months, 10 years etc. * * Please note that this options applies to the _series data_, not the * interval of the axis ticks, which is independent. * * @sample {highcharts} highcharts/plotoptions/series-pointintervalunit/ * One point a month * @sample {highstock} highcharts/plotoptions/series-pointintervalunit/ * One point a month * * @type {string} * @since 4.1.0 * @product highcharts highstock gantt * @validvalue ["day", "month", "year"] * @apioption plotOptions.series.pointIntervalUnit */ /** * Possible values: `"on"`, `"between"`, `number`. * * In a column chart, when pointPlacement is `"on"`, the point will not * create any padding of the X axis. In a polar column chart this means * that the first column points directly north. If the pointPlacement is * `"between"`, the columns will be laid out between ticks. This is * useful for example for visualising an amount between two points in * time or in a certain sector of a polar chart. * * Since Highcharts 3.0.2, the point placement can also be numeric, * where 0 is on the axis value, -0.5 is between this value and the * previous, and 0.5 is between this value and the next. Unlike the * textual options, numeric point placement options won't affect axis * padding. * * Note that pointPlacement needs a [pointRange]( * #plotOptions.series.pointRange) to work. For column series this is * computed, but for line-type series it needs to be set. * * For the `xrange` series type and gantt charts, if the Y axis is a * category axis, the `pointPlacement` applies to the Y axis rather than * the (typically datetime) X axis. * * Defaults to `undefined` in cartesian charts, `"between"` in polar * charts. * * @see [xAxis.tickmarkPlacement](#xAxis.tickmarkPlacement) * * @sample {highcharts|highstock} highcharts/plotoptions/series-pointplacement-between/ * Between in a column chart * @sample {highcharts|highstock} highcharts/plotoptions/series-pointplacement-numeric/ * Numeric placement for custom layout * @sample {highcharts|highstock} maps/plotoptions/heatmap-pointplacement/ * Placement in heatmap * * @type {string|number} * @since 2.3.0 * @product highcharts highstock gantt * @apioption plotOptions.series.pointPlacement */ /** * If no x values are given for the points in a series, pointStart * defines on what value to start. For example, if a series contains one * yearly value starting from 1945, set pointStart to 1945. * * @sample {highcharts} highcharts/plotoptions/series-pointstart-linear/ * Linear * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/ * Datetime * @sample {highstock} stock/plotoptions/pointinterval-pointstart/ * Using pointStart and pointInterval * * @type {number} * @default 0 * @product highcharts highstock gantt * @apioption plotOptions.series.pointStart */ /** * Whether to select the series initially. If `showCheckbox` is true, * the checkbox next to the series name in the legend will be checked * for a selected series. * * @sample {highcharts} highcharts/plotoptions/series-selected/ * One out of two series selected * * @type {boolean} * @default false * @since 1.2.0 * @apioption plotOptions.series.selected */ /** * Whether to apply a drop shadow to the graph line. Since 2.3 the * shadow can be an object configuration containing `color`, `offsetX`, * `offsetY`, `opacity` and `width`. * * @sample {highcharts} highcharts/plotoptions/series-shadow/ * Shadow enabled * * @type {boolean|Highcharts.ShadowOptionsObject} * @default false * @apioption plotOptions.series.shadow */ /** * Whether to display this particular series or series type in the * legend. Standalone series are shown in legend by default, and linked * series are not. Since v7.2.0 it is possible to show series that use * colorAxis by setting this option to `true`. * * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ * One series in the legend, one hidden * * @type {boolean} * @apioption plotOptions.series.showInLegend */ /** * Whether or not to show the series in the navigator. Takes precedence * over [navigator.baseSeries](#navigator.baseSeries) if defined. * * @type {boolean} * @since 5.0.0 * @product highstock * @apioption plotOptions.series.showInNavigator */ /** * If set to `true`, the accessibility module will skip past the points * in this series for keyboard navigation. * * @type {boolean} * @since 5.0.12 * @apioption plotOptions.series.skipKeyboardNavigation */ /** * Whether to stack the values of each series on top of each other. * Possible values are `undefined` to disable, `"normal"` to stack by * value or `"percent"`. * * When stacking is enabled, data must be sorted * in ascending X order. * * Some stacking options are related to specific series types. In the * streamgraph series type, the stacking option is set to `"stream"`. * The second one is `"overlap"`, which only applies to waterfall * series. * * @see [yAxis.reversedStacks](#yAxis.reversedStacks) * * @sample {highcharts} highcharts/plotoptions/series-stacking-line/ * Line * @sample {highcharts} highcharts/plotoptions/series-stacking-column/ * Column * @sample {highcharts} highcharts/plotoptions/series-stacking-bar/ * Bar * @sample {highcharts} highcharts/plotoptions/series-stacking-area/ * Area * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-line/ * Line * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-column/ * Column * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-bar/ * Bar * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-area/ * Area * @sample {highcharts} highcharts/plotoptions/series-waterfall-with-normal-stacking * Waterfall with normal stacking * @sample {highcharts} highcharts/plotoptions/series-waterfall-with-overlap-stacking * Waterfall with overlap stacking * @sample {highstock} stock/plotoptions/stacking/ * Area * * @type {string} * @product highcharts highstock * @validvalue ["normal", "overlap", "percent", "stream"] * @apioption plotOptions.series.stacking */ /** * Whether to apply steps to the line. Possible values are `left`, * `center` and `right`. * * @sample {highcharts} highcharts/plotoptions/line-step/ * Different step line options * @sample {highcharts} highcharts/plotoptions/area-step/ * Stepped, stacked area * @sample {highstock} stock/plotoptions/line-step/ * Step line * * @type {string} * @since 1.2.5 * @product highcharts highstock * @validvalue ["left", "center", "right"] * @apioption plotOptions.series.step */ /** * The threshold, also called zero level or base level. For line type * series this is only used in conjunction with * [negativeColor](#plotOptions.series.negativeColor). * * @see [softThreshold](#plotOptions.series.softThreshold). * * @type {number|null} * @default 0 * @since 3.0 * @product highcharts highstock * @apioption plotOptions.series.threshold */ /** * Set the initial visibility of the series. * * @sample {highcharts} highcharts/plotoptions/series-visible/ * Two series, one hidden and one visible * @sample {highstock} stock/plotoptions/series-visibility/ * Hidden series * * @type {boolean} * @default true * @apioption plotOptions.series.visible */ /** * Defines the Axis on which the zones are applied. * * @see [zones](#plotOptions.series.zones) * * @sample {highcharts} highcharts/series/color-zones-zoneaxis-x/ * Zones on the X-Axis * @sample {highstock} highcharts/series/color-zones-zoneaxis-x/ * Zones on the X-Axis * * @type {string} * @default y * @since 4.1.0 * @product highcharts highstock * @apioption plotOptions.series.zoneAxis */ /** * General event handlers for the series items. These event hooks can * also be attached to the series at run time using the * `Highcharts.addEvent` function. * * @declare Highcharts.SeriesEventsOptionsObject * * @private */ events: {}, /** * Fires after the series has finished its initial animation, or in case * animation is disabled, immediately as the series is displayed. * * @sample {highcharts} highcharts/plotoptions/series-events-afteranimate/ * Show label after animate * @sample {highstock} highcharts/plotoptions/series-events-afteranimate/ * Show label after animate * * @type {Highcharts.SeriesAfterAnimateCallbackFunction} * @since 4.0 * @product highcharts highstock gantt * @context Highcharts.Series * @apioption plotOptions.series.events.afterAnimate */ /** * Fires when the checkbox next to the series' name in the legend is * clicked. One parameter, `event`, is passed to the function. The state * of the checkbox is found by `event.checked`. The checked item is * found by `event.item`. Return `false` to prevent the default action * which is to toggle the select state of the series. * * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/ * Alert checkbox status * * @type {Highcharts.SeriesCheckboxClickCallbackFunction} * @since 1.2.0 * @context Highcharts.Series * @apioption plotOptions.series.events.checkboxClick */ /** * Fires when the series is clicked. One parameter, `event`, is passed * to the function, containing common event information. Additionally, * `event.point` holds a pointer to the nearest point on the graph. * * @sample {highcharts} highcharts/plotoptions/series-events-click/ * Alert click info * @sample {highstock} stock/plotoptions/series-events-click/ * Alert click info * @sample {highmaps} maps/plotoptions/series-events-click/ * Display click info in subtitle * * @type {Highcharts.SeriesClickCallbackFunction} * @context Highcharts.Series * @apioption plotOptions.series.events.click */ /** * Fires when the series is hidden after chart generation time, either * by clicking the legend item or by calling `.hide()`. * * @sample {highcharts} highcharts/plotoptions/series-events-hide/ * Alert when the series is hidden by clicking the legend item * * @type {Highcharts.SeriesHideCallbackFunction} * @since 1.2.0 * @context Highcharts.Series * @apioption plotOptions.series.events.hide */ /** * Fires when the legend item belonging to the series is clicked. One * parameter, `event`, is passed to the function. The default action * is to toggle the visibility of the series. This can be prevented * by returning `false` or calling `event.preventDefault()`. * * @sample {highcharts} highcharts/plotoptions/series-events-legenditemclick/ * Confirm hiding and showing * * @type {Highcharts.SeriesLegendItemClickCallbackFunction} * @context Highcharts.Series * @apioption plotOptions.series.events.legendItemClick */ /** * Fires when the mouse leaves the graph. One parameter, `event`, is * passed to the function, containing common event information. If the * [stickyTracking](#plotOptions.series) option is true, `mouseOut` * doesn't happen before the mouse enters another graph or leaves the * plot area. * * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-sticky/ * With sticky tracking by default * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-no-sticky/ * Without sticky tracking * * @type {Highcharts.SeriesMouseOutCallbackFunction} * @context Highcharts.Series * @apioption plotOptions.series.events.mouseOut */ /** * Fires when the mouse enters the graph. One parameter, `event`, is * passed to the function, containing common event information. * * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-sticky/ * With sticky tracking by default * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-no-sticky/ * Without sticky tracking * * @type {Highcharts.SeriesMouseOverCallbackFunction} * @context Highcharts.Series * @apioption plotOptions.series.events.mouseOver */ /** * Fires when the series is shown after chart generation time, either * by clicking the legend item or by calling `.show()`. * * @sample {highcharts} highcharts/plotoptions/series-events-show/ * Alert when the series is shown by clicking the legend item. * * @type {Highcharts.SeriesShowCallbackFunction} * @since 1.2.0 * @context Highcharts.Series * @apioption plotOptions.series.events.show */ /** * Options for the point markers of line-like series. Properties like * `fillColor`, `lineColor` and `lineWidth` define the visual appearance * of the markers. Other series types, like column series, don't have * markers, but have visual options on the series level instead. * * In styled mode, the markers can be styled with the * `.highcharts-point`, `.highcharts-point-hover` and * `.highcharts-point-select` class names. * * @declare Highcharts.PointMarkerOptionsObject * * @private */ marker: { /** * Enable or disable the point marker. If `undefined`, the markers * are hidden when the data is dense, and shown for more widespread * data points. * * @sample {highcharts} highcharts/plotoptions/series-marker-enabled/ * Disabled markers * @sample {highcharts} highcharts/plotoptions/series-marker-enabled-false/ * Disabled in normal state but enabled on hover * @sample {highstock} stock/plotoptions/series-marker/ * Enabled markers * * @type {boolean} * @default {highcharts} undefined * @default {highstock} false * @apioption plotOptions.series.marker.enabled */ /** * The threshold for how dense the point markers should be before * they are hidden, given that `enabled` is not defined. The number * indicates the horizontal distance between the two closest points * in the series, as multiples of the `marker.radius`. In other * words, the default value of 2 means points are hidden if * overlapping horizontally. * * @sample highcharts/plotoptions/series-marker-enabledthreshold * A higher threshold * * @since 6.0.5 */ enabledThreshold: 2, /** * The fill color of the point marker. When `undefined`, the series' * or point's color is used. * * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/ * White fill * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @apioption plotOptions.series.marker.fillColor */ /** * Image markers only. Set the image width explicitly. When using * this option, a `width` must also be set. * * @sample {highcharts} highcharts/plotoptions/series-marker-width-height/ * Fixed width and height * @sample {highstock} highcharts/plotoptions/series-marker-width-height/ * Fixed width and height * * @type {number} * @since 4.0.4 * @apioption plotOptions.series.marker.height */ /** * The color of the point marker's outline. When `undefined`, the * series' or point's color is used. * * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/ * Inherit from series color (undefined) * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ lineColor: palette.backgroundColor, /** * The width of the point marker's outline. * * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/ * 2px blue marker */ lineWidth: 0, /** * The radius of the point marker. * * @sample {highcharts} highcharts/plotoptions/series-marker-radius/ * Bigger markers * * @default {highstock} 2 * @default {highcharts} 4 * */ radius: 4, /** * A predefined shape or symbol for the marker. When undefined, the * symbol is pulled from options.symbols. Other possible values are * `'circle'`, `'square'`,`'diamond'`, `'triangle'` and * `'triangle-down'`. * * Additionally, the URL to a graphic can be given on this form: * `'url(graphic.png)'`. Note that for the image to be applied to * exported charts, its URL needs to be accessible by the export * server. * * Custom callbacks for symbol path generation can also be added to * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then * used by its method name, as shown in the demo. * * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/ * Predefined, graphic and custom markers * @sample {highstock} highcharts/plotoptions/series-marker-symbol/ * Predefined, graphic and custom markers * * @type {string} * @apioption plotOptions.series.marker.symbol */ /** * Image markers only. Set the image width explicitly. When using * this option, a `height` must also be set. * * @sample {highcharts} highcharts/plotoptions/series-marker-width-height/ * Fixed width and height * @sample {highstock} highcharts/plotoptions/series-marker-width-height/ * Fixed width and height * * @type {number} * @since 4.0.4 * @apioption plotOptions.series.marker.width */ /** * States for a single point marker. * * @declare Highcharts.PointStatesOptionsObject */ states: { /** * The normal state of a single point marker. Currently only * used for setting animation when returning to normal state * from hover. * * @declare Highcharts.PointStatesNormalOptionsObject */ normal: { /** * Animation when returning to normal state after hovering. * * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} */ animation: true }, /** * The hover state for a single point marker. * * @declare Highcharts.PointStatesHoverOptionsObject */ hover: { /** * Animation when hovering over the marker. * * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} */ animation: { /** @internal */ duration: 50 }, /** * Enable or disable the point marker. * * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-enabled/ * Disabled hover state */ enabled: true, /** * The fill color of the marker in hover state. When * `undefined`, the series' or point's fillColor for normal * state is used. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @apioption plotOptions.series.marker.states.hover.fillColor */ /** * The color of the point marker's outline. When * `undefined`, the series' or point's lineColor for normal * state is used. * * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-linecolor/ * White fill color, black line color * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @apioption plotOptions.series.marker.states.hover.lineColor */ /** * The width of the point marker's outline. When * `undefined`, the series' or point's lineWidth for normal * state is used. * * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-linewidth/ * 3px line width * * @type {number} * @apioption plotOptions.series.marker.states.hover.lineWidth */ /** * The radius of the point marker. In hover state, it * defaults to the normal state's radius + 2 as per the * [radiusPlus](#plotOptions.series.marker.states.hover.radiusPlus) * option. * * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-radius/ * 10px radius * * @type {number} * @apioption plotOptions.series.marker.states.hover.radius */ /** * The number of pixels to increase the radius of the * hovered point. * * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ * 5 pixels greater radius on hover * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ * 5 pixels greater radius on hover * * @since 4.0.3 */ radiusPlus: 2, /** * The additional line width for a hovered point. * * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ * 2 pixels wider on hover * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ * 2 pixels wider on hover * * @since 4.0.3 */ lineWidthPlus: 1 }, /** * The appearance of the point marker when selected. In order to * allow a point to be selected, set the * `series.allowPointSelect` option to true. * * @declare Highcharts.PointStatesSelectOptionsObject */ select: { /** * Enable or disable visible feedback for selection. * * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-enabled/ * Disabled select state * * @type {boolean} * @default true * @apioption plotOptions.series.marker.states.select.enabled */ /** * The radius of the point marker. In hover state, it * defaults to the normal state's radius + 2. * * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-radius/ * 10px radius for selected points * * @type {number} * @apioption plotOptions.series.marker.states.select.radius */ /** * The fill color of the point marker. * * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-fillcolor/ * Solid red discs for selected points * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ fillColor: palette.neutralColor20, /** * The color of the point marker's outline. When * `undefined`, the series' or point's color is used. * * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linecolor/ * Red line color for selected points * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ lineColor: palette.neutralColor100, /** * The width of the point marker's outline. * * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linewidth/ * 3px line width for selected points */ lineWidth: 2 } } }, /** * Properties for each single point. * * @declare Highcharts.PlotSeriesPointOptions * * @private */ point: { /** * Fires when a point is clicked. One parameter, `event`, is passed * to the function, containing common event information. * * If the `series.allowPointSelect` option is true, the default * action for the point's click event is to toggle the point's * select state. Returning `false` cancels this action. * * @sample {highcharts} highcharts/plotoptions/series-point-events-click/ * Click marker to alert values * @sample {highcharts} highcharts/plotoptions/series-point-events-click-column/ * Click column * @sample {highcharts} highcharts/plotoptions/series-point-events-click-url/ * Go to URL * @sample {highmaps} maps/plotoptions/series-point-events-click/ * Click marker to display values * @sample {highmaps} maps/plotoptions/series-point-events-click-url/ * Go to URL * * @type {Highcharts.PointClickCallbackFunction} * @context Highcharts.Point * @apioption plotOptions.series.point.events.click */ /** * Fires when the mouse leaves the area close to the point. One * parameter, `event`, is passed to the function, containing common * event information. * * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ * Show values in the chart's corner on mouse over * * @type {Highcharts.PointMouseOutCallbackFunction} * @context Highcharts.Point * @apioption plotOptions.series.point.events.mouseOut */ /** * Fires when the mouse enters the area close to the point. One * parameter, `event`, is passed to the function, containing common * event information. * * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/ * Show values in the chart's corner on mouse over * * @type {Highcharts.PointMouseOverCallbackFunction} * @context Highcharts.Point * @apioption plotOptions.series.point.events.mouseOver */ /** * Fires when the point is removed using the `.remove()` method. One * parameter, `event`, is passed to the function. Returning `false` * cancels the operation. * * @sample {highcharts} highcharts/plotoptions/series-point-events-remove/ * Remove point and confirm * * @type {Highcharts.PointRemoveCallbackFunction} * @since 1.2.0 * @context Highcharts.Point * @apioption plotOptions.series.point.events.remove */ /** * Fires when the point is selected either programmatically or * following a click on the point. One parameter, `event`, is passed * to the function. Returning `false` cancels the operation. * * @sample {highcharts} highcharts/plotoptions/series-point-events-select/ * Report the last selected point * @sample {highmaps} maps/plotoptions/series-allowpointselect/ * Report select and unselect * * @type {Highcharts.PointSelectCallbackFunction} * @since 1.2.0 * @context Highcharts.Point * @apioption plotOptions.series.point.events.select */ /** * Fires when the point is unselected either programmatically or * following a click on the point. One parameter, `event`, is passed * to the function. * Returning `false` cancels the operation. * * @sample {highcharts} highcharts/plotoptions/series-point-events-unselect/ * Report the last unselected point * @sample {highmaps} maps/plotoptions/series-allowpointselect/ * Report select and unselect * * @type {Highcharts.PointUnselectCallbackFunction} * @since 1.2.0 * @context Highcharts.Point * @apioption plotOptions.series.point.events.unselect */ /** * Fires when the point is updated programmatically through the * `.update()` method. One parameter, `event`, is passed to the * function. The new point options can be accessed through * `event.options`. Returning `false` cancels the operation. * * @sample {highcharts} highcharts/plotoptions/series-point-events-update/ * Confirm point updating * * @type {Highcharts.PointUpdateCallbackFunction} * @since 1.2.0 * @context Highcharts.Point * @apioption plotOptions.series.point.events.update */ /** * Events for each single point. * * @declare Highcharts.PointEventsOptionsObject */ events: {} }, /** * Options for the series data labels, appearing next to each data * point. * * Since v6.2.0, multiple data labels can be applied to each single * point by defining them as an array of configs. * * In styled mode, the data labels can be styled with the * `.highcharts-data-label-box` and `.highcharts-data-label` class names * ([see example](https://www.highcharts.com/samples/highcharts/css/series-datalabels)). * * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled * Data labels enabled * @sample {highcharts} highcharts/plotoptions/series-datalabels-multiple * Multiple data labels on a bar series * @sample {highcharts} highcharts/css/series-datalabels * Style mode example * * @type {*|Array<*>} * @product highcharts highstock highmaps gantt * * @private */ dataLabels: { /** * Enable or disable the initial animation when a series is * displayed for the `dataLabels`. The animation can also be set as * a configuration object. Please note that this option only * applies to the initial animation. * For other animations, see [chart.animation](#chart.animation) * and the animation parameter under the API methods. * The following properties are supported: * * - `defer`: The animation delay time in milliseconds. * * @sample {highcharts} highcharts/plotoptions/animation-defer/ * Animation defer settings * * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} * @since 8.2.0 * @apioption plotOptions.series.dataLabels.animation */ animation: {}, /** * The animation delay time in milliseconds. * Set to `0` renders dataLabel immediately. * As `undefined` inherits defer time from the [series.animation.defer](#plotOptions.series.animation.defer). * * @type {number} * @since 8.2.0 * @apioption plotOptions.series.dataLabels.animation.defer */ /** * The alignment of the data label compared to the point. If * `right`, the right side of the label should be touching the * point. For points with an extent, like columns, the alignments * also dictates how to align it inside the box, as given with the * [inside](#plotOptions.column.dataLabels.inside) * option. Can be one of `left`, `center` or `right`. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-align-left/ * Left aligned * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/ * Data labels inside the bar * * @type {Highcharts.AlignValue|null} */ align: 'center', /** * Whether to allow data labels to overlap. To make the labels less * sensitive for overlapping, the * [dataLabels.padding](#plotOptions.series.dataLabels.padding) * can be set to 0. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-allowoverlap-false/ * Don't allow overlap * * @type {boolean} * @default false * @since 4.1.0 * @apioption plotOptions.series.dataLabels.allowOverlap */ /** * The background color or gradient for the data label. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ * Data labels box options * @sample {highmaps} maps/plotoptions/series-datalabels-box/ * Data labels box options * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 2.2.1 * @apioption plotOptions.series.dataLabels.backgroundColor */ /** * The border color for the data label. Defaults to `undefined`. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ * Data labels box options * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 2.2.1 * @apioption plotOptions.series.dataLabels.borderColor */ /** * The border radius in pixels for the data label. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ * Data labels box options * @sample {highmaps} maps/plotoptions/series-datalabels-box/ * Data labels box options * * @type {number} * @default 0 * @since 2.2.1 * @apioption plotOptions.series.dataLabels.borderRadius */ /** * The border width in pixels for the data label. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ * Data labels box options * * @type {number} * @default 0 * @since 2.2.1 * @apioption plotOptions.series.dataLabels.borderWidth */ /** * A class name for the data label. Particularly in styled mode, * this can be used to give each series' or point's data label * unique styling. In addition to this option, a default color class * name is added so that we can give the labels a contrast text * shadow. * * @sample {highcharts} highcharts/css/data-label-contrast/ * Contrast text shadow * @sample {highcharts} highcharts/css/series-datalabels/ * Styling by CSS * * @type {string} * @since 5.0.0 * @apioption plotOptions.series.dataLabels.className */ /** * The text color for the data labels. Defaults to `undefined`. For * certain series types, like column or map, the data labels can be * drawn inside the points. In this case the data label will be * drawn with maximum contrast by default. Additionally, it will be * given a `text-outline` style with the opposite color, to further * increase the contrast. This can be overridden by setting the * `text-outline` style to `none` in the `dataLabels.style` option. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-color/ * Red data labels * @sample {highmaps} maps/demo/color-axis/ * White data labels * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @apioption plotOptions.series.dataLabels.color */ /** * Whether to hide data labels that are outside the plot area. By * default, the data label is moved inside the plot area according * to the * [overflow](#plotOptions.series.dataLabels.overflow) * option. * * @type {boolean} * @default true * @since 2.3.3 * @apioption plotOptions.series.dataLabels.crop */ /** * Whether to defer displaying the data labels until the initial * series animation has finished. Setting to `false` renders the * data label immediately. If set to `true` inherits the defer * time set in [plotOptions.series.animation](#plotOptions.series.animation). * If set to a number, a defer time is specified in milliseconds. * * @sample highcharts/plotoptions/animation-defer * Set defer time * * @since 4.0.0 * @type {boolean|number} * @product highcharts highstock gantt */ defer: true, /** * Enable or disable the data labels. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled/ * Data labels enabled * @sample {highmaps} maps/demo/color-axis/ * Data labels enabled * * @type {boolean} * @default false * @apioption plotOptions.series.dataLabels.enabled */ /** * A declarative filter to control of which data labels to display. * The declarative filter is designed for use when callback * functions are not available, like when the chart options require * a pure JSON structure or for use with graphical editors. For * programmatic control, use the `formatter` instead, and return * `undefined` to disable a single data label. * * @example * filter: { * property: 'percentage', * operator: '>', * value: 4 * } * * @sample {highcharts} highcharts/demo/pie-monochrome * Data labels filtered by percentage * * @declare Highcharts.DataLabelsFilterOptionsObject * @since 6.0.3 * @apioption plotOptions.series.dataLabels.filter */ /** * The operator to compare by. Can be one of `>`, `<`, `>=`, `<=`, * `==`, and `===`. * * @type {string} * @validvalue [">", "<", ">=", "<=", "==", "==="] * @apioption plotOptions.series.dataLabels.filter.operator */ /** * The point property to filter by. Point options are passed * directly to properties, additionally there are `y` value, * `percentage` and others listed under {@link Highcharts.Point} * members. * * @type {string} * @apioption plotOptions.series.dataLabels.filter.property */ /** * The value to compare against. * * @type {number} * @apioption plotOptions.series.dataLabels.filter.value */ /** * A * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) * for the data label. Available variables are the same as for * `formatter`. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ * Add a unit * @sample {highmaps} maps/plotoptions/series-datalabels-format/ * Formatted value in the data label * * @type {string} * @default y * @default point.value * @since 3.0 * @apioption plotOptions.series.dataLabels.format */ // eslint-disable-next-line valid-jsdoc /** * Callback JavaScript function to format the data label. Note that * if a `format` is defined, the format takes precedence and the * formatter is ignored. * * @sample {highmaps} maps/plotoptions/series-datalabels-format/ * Formatted value * * @type {Highcharts.DataLabelsFormatterCallbackFunction} */ formatter: function () { var numberFormatter = this.series.chart.numberFormatter; return typeof this.y !== 'number' ? '' : numberFormatter(this.y, -1); }, /** * For points with an extent, like columns or map areas, whether to * align the data label inside the box or to the actual value point. * Defaults to `false` in most cases, `true` in stacked columns. * * @type {boolean} * @since 3.0 * @apioption plotOptions.series.dataLabels.inside */ /** * Format for points with the value of null. Works analogously to * [format](#plotOptions.series.dataLabels.format). `nullFormat` can * be applied only to series which support displaying null points. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ * Format data label and tooltip for null point. * * @type {boolean|string} * @since 7.1.0 * @apioption plotOptions.series.dataLabels.nullFormat */ /** * Callback JavaScript function that defines formatting for points * with the value of null. Works analogously to * [formatter](#plotOptions.series.dataLabels.formatter). * `nullPointFormatter` can be applied only to series which support * displaying null points. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ * Format data label and tooltip for null point. * * @type {Highcharts.DataLabelsFormatterCallbackFunction} * @since 7.1.0 * @apioption plotOptions.series.dataLabels.nullFormatter */ /** * How to handle data labels that flow outside the plot area. The * default is `"justify"`, which aligns them inside the plot area. * For columns and bars, this means it will be moved inside the bar. * To display data labels outside the plot area, set `crop` to * `false` and `overflow` to `"allow"`. * * @type {Highcharts.DataLabelsOverflowValue} * @default justify * @since 3.0.6 * @apioption plotOptions.series.dataLabels.overflow */ /** * When either the `borderWidth` or the `backgroundColor` is set, * this is the padding within the box. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ * Data labels box options * @sample {highmaps} maps/plotoptions/series-datalabels-box/ * Data labels box options * * @since 2.2.1 */ padding: 5, /** * Aligns data labels relative to points. If `center` alignment is * not possible, it defaults to `right`. * * @type {Highcharts.AlignValue} * @default center * @apioption plotOptions.series.dataLabels.position */ /** * Text rotation in degrees. Note that due to a more complex * structure, backgrounds, borders and padding will be lost on a * rotated data label. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ * Vertical labels * * @type {number} * @default 0 * @apioption plotOptions.series.dataLabels.rotation */ /** * The shadow of the box. Works best with `borderWidth` or * `backgroundColor`. Since 2.3 the shadow can be an object * configuration containing `color`, `offsetX`, `offsetY`, `opacity` * and `width`. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/ * Data labels box options * * @type {boolean|Highcharts.ShadowOptionsObject} * @default false * @since 2.2.1 * @apioption plotOptions.series.dataLabels.shadow */ /** * The name of a symbol to use for the border around the label. * Symbols are predefined functions on the Renderer object. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-shape/ * A callout for annotations * * @type {string} * @default square * @since 4.1.2 * @apioption plotOptions.series.dataLabels.shape */ /** * Styles for the label. The default `color` setting is * `"contrast"`, which is a pseudo color that Highcharts picks up * and applies the maximum contrast to the underlying point item, * for example the bar in a bar chart. * * The `textOutline` is a pseudo property that applies an outline of * the given width with the given color, which by default is the * maximum contrast to the text. So a bright text color will result * in a black text outline for maximum readability on a mixed * background. In some cases, especially with grayscale text, the * text outline doesn't work well, in which cases it can be disabled * by setting it to `"none"`. When `useHTML` is true, the * `textOutline` will not be picked up. In this, case, the same * effect can be acheived through the `text-shadow` CSS property. * * For some series types, where each point has an extent, like for * example tree maps, the data label may overflow the point. There * are two strategies for handling overflow. By default, the text * will wrap to multiple lines. The other strategy is to set * `style.textOverflow` to `ellipsis`, which will keep the text on * one line plus it will break inside long words. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-style/ * Bold labels * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow/ * Long labels truncated with an ellipsis in a pie * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow-wrap/ * Long labels are wrapped in a pie * @sample {highmaps} maps/demo/color-axis/ * Bold labels * * @type {Highcharts.CSSObject} * @since 4.1.0 * @apioption plotOptions.series.dataLabels.style */ style: { /** @internal */ fontSize: '11px', /** @internal */ fontWeight: 'bold', /** @internal */ color: 'contrast', /** @internal */ textOutline: '1px contrast' }, /** * Options for a label text which should follow marker's shape. * Border and background are disabled for a label that follows a * path. * * **Note:** Only SVG-based renderer supports this option. Setting * `useHTML` to true will disable this option. * * @declare Highcharts.DataLabelsTextPathOptionsObject * @since 7.1.0 * @apioption plotOptions.series.dataLabels.textPath */ /** * Presentation attributes for the text path. * * @type {Highcharts.SVGAttributes} * @since 7.1.0 * @apioption plotOptions.series.dataLabels.textPath.attributes */ /** * Enable or disable `textPath` option for link's or marker's data * labels. * * @type {boolean} * @since 7.1.0 * @apioption plotOptions.series.dataLabels.textPath.enabled */ /** * Whether to * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html) * to render the labels. * * @type {boolean} * @default false * @apioption plotOptions.series.dataLabels.useHTML */ /** * The vertical alignment of a data label. Can be one of `top`, * `middle` or `bottom`. The default value depends on the data, for * instance in a column chart, the label is above positive values * and below negative values. * * @type {Highcharts.VerticalAlignValue|null} * @since 2.3.3 */ verticalAlign: 'bottom', /** * The x position offset of the label relative to the point in * pixels. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ * Vertical and positioned * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/ * Data labels inside the bar */ x: 0, /** * The Z index of the data labels. The default Z index puts it above * the series. Use a Z index of 2 to display it behind the series. * * @type {number} * @default 6 * @since 2.3.5 * @apioption plotOptions.series.dataLabels.z */ /** * The y position offset of the label relative to the point in * pixels. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/ * Vertical and positioned */ y: 0 }, /** * When the series contains less points than the crop threshold, all * points are drawn, even if the points fall outside the visible plot * area at the current zoom. The advantage of drawing all points * (including markers and columns), is that animation is performed on * updates. On the other hand, when the series contains more points than * the crop threshold, the series data is cropped to only contain points * that fall within the plot area. The advantage of cropping away * invisible points is to increase performance on large series. * * @since 2.2 * @product highcharts highstock * * @private */ cropThreshold: 300, /** * Opacity of a series parts: line, fill (e.g. area) and dataLabels. * * @see [states.inactive.opacity](#plotOptions.series.states.inactive.opacity) * * @since 7.1.0 * * @private */ opacity: 1, /** * The width of each point on the x axis. For example in a column chart * with one value each day, the pointRange would be 1 day (= 24 * 3600 * * 1000 milliseconds). This is normally computed automatically, but * this option can be used to override the automatic value. * * @product highstock * * @private */ pointRange: 0, /** * When this is true, the series will not cause the Y axis to cross * the zero plane (or [threshold](#plotOptions.series.threshold) option) * unless the data actually crosses the plane. * * For example, if `softThreshold` is `false`, a series of 0, 1, 2, * 3 will make the Y axis show negative values according to the * `minPadding` option. If `softThreshold` is `true`, the Y axis starts * at 0. * * @since 4.1.9 * @product highcharts highstock * * @private */ softThreshold: true, /** * @declare Highcharts.SeriesStatesOptionsObject * * @private */ states: { /** * The normal state of a series, or for point items in column, pie * and similar series. Currently only used for setting animation * when returning to normal state from hover. * * @declare Highcharts.SeriesStatesNormalOptionsObject */ normal: { /** * Animation when returning to normal state after hovering. * * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} */ animation: true }, /** * Options for the hovered series. These settings override the * normal state options when a series is moused over or touched. * * @declare Highcharts.SeriesStatesHoverOptionsObject */ hover: { /** * Enable separate styles for the hovered series to visualize * that the user hovers either the series itself or the legend. * * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled/ * Line * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-column/ * Column * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-pie/ * Pie * * @type {boolean} * @default true * @since 1.2 * @apioption plotOptions.series.states.hover.enabled */ /** * Animation setting for hovering the graph in line-type series. * * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} * @since 5.0.8 * @product highcharts highstock */ animation: { /** * The duration of the hover animation in milliseconds. By * default the hover state animates quickly in, and slowly * back to normal. * * @internal */ duration: 50 }, /** * Pixel width of the graph line. By default this property is * undefined, and the `lineWidthPlus` property dictates how much * to increase the linewidth from normal state. * * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidth/ * 5px line on hover * * @type {number} * @product highcharts highstock * @apioption plotOptions.series.states.hover.lineWidth */ /** * The additional line width for the graph of a hovered series. * * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/ * 5 pixels wider * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/ * 5 pixels wider * * @since 4.0.3 * @product highcharts highstock */ lineWidthPlus: 1, /** * In Highcharts 1.0, the appearance of all markers belonging * to the hovered series. For settings on the hover state of the * individual point, see * [marker.states.hover](#plotOptions.series.marker.states.hover). * * @deprecated * * @extends plotOptions.series.marker * @excluding states * @product highcharts highstock */ marker: { // lineWidth: base + 1, // radius: base + 1 }, /** * Options for the halo appearing around the hovered point in * line-type series as well as outside the hovered slice in pie * charts. By default the halo is filled by the current point or * series color with an opacity of 0.25\. The halo can be * disabled by setting the `halo` option to `null`. * * In styled mode, the halo is styled with the * `.highcharts-halo` class, with colors inherited from * `.highcharts-color-{n}`. * * @sample {highcharts} highcharts/plotoptions/halo/ * Halo options * @sample {highstock} highcharts/plotoptions/halo/ * Halo options * * @declare Highcharts.SeriesStatesHoverHaloOptionsObject * @type {null|*} * @since 4.0 * @product highcharts highstock */ halo: { /** * A collection of SVG attributes to override the appearance * of the halo, for example `fill`, `stroke` and * `stroke-width`. * * @type {Highcharts.SVGAttributes} * @since 4.0 * @product highcharts highstock * @apioption plotOptions.series.states.hover.halo.attributes */ /** * The pixel size of the halo. For point markers this is the * radius of the halo. For pie slices it is the width of the * halo outside the slice. For bubbles it defaults to 5 and * is the width of the halo outside the bubble. * * @since 4.0 * @product highcharts highstock */ size: 10, /** * Opacity for the halo unless a specific fill is overridden * using the `attributes` setting. Note that Highcharts is * only able to apply opacity to colors of hex or rgb(a) * formats. * * @since 4.0 * @product highcharts highstock */ opacity: 0.25 } }, /** * Specific options for point in selected states, after being * selected by * [allowPointSelect](#plotOptions.series.allowPointSelect) * or programmatically. * * @sample maps/plotoptions/series-allowpointselect/ * Allow point select demo * * @declare Highcharts.SeriesStatesSelectOptionsObject * @extends plotOptions.series.states.hover * @excluding brightness */ select: { animation: { /** @internal */ duration: 0 } }, /** * The opposite state of a hover for series. * * @sample highcharts/plotoptions/series-states-inactive-disabled * Disabled inactive state * * @declare Highcharts.SeriesStatesInactiveOptionsObject */ inactive: { /** * Enable or disable the inactive state for a series * * @sample highcharts/plotoptions/series-states-inactive-disabled * Disabled inactive state * * @type {boolean} * @default true * @apioption plotOptions.series.states.inactive.enabled */ /** * The animation for entering the inactive state. * * @type {boolean|Partial<Highcharts.AnimationOptionsObject>} */ animation: { /** @internal */ duration: 50 }, /** * Opacity of series elements (dataLabels, line, area). * * @type {number} */ opacity: 0.2 } }, /** * Sticky tracking of mouse events. When true, the `mouseOut` event on a * series isn't triggered until the mouse moves over another series, or * out of the plot area. When false, the `mouseOut` event on a series is * triggered when the mouse leaves the area around the series' graph or * markers. This also implies the tooltip when not shared. When * `stickyTracking` is false and `tooltip.shared` is false, the tooltip * will be hidden when moving the mouse between series. Defaults to true * for line and area type series, but to false for columns, pies etc. * * **Note:** The boost module will force this option because of * technical limitations. * * @sample {highcharts} highcharts/plotoptions/series-stickytracking-true/ * True by default * @sample {highcharts} highcharts/plotoptions/series-stickytracking-false/ * False * * @default {highcharts} true * @default {highstock} true * @default {highmaps} false * @since 2.0 * * @private */ stickyTracking: true, /** * A configuration object for the tooltip rendering of each single * series. Properties are inherited from [tooltip](#tooltip), but only * the following properties can be defined on a series level. * * @declare Highcharts.SeriesTooltipOptionsObject * @since 2.3 * @extends tooltip * @excluding animation, backgroundColor, borderColor, borderRadius, * borderWidth, className, crosshairs, enabled, formatter, * headerShape, hideDelay, outside, padding, positioner, * shadow, shape, shared, snap, split, stickOnContact, * style, useHTML * @apioption plotOptions.series.tooltip */ /** * When a series contains a data array that is longer than this, only * one dimensional arrays of numbers, or two dimensional arrays with * x and y values are allowed. Also, only the first point is tested, * and the rest are assumed to be the same format. This saves expensive * data checking and indexing in long series. Set it to `0` disable. * * Note: * In boost mode turbo threshold is forced. Only array of numbers or * two dimensional arrays are allowed. * * @since 2.2 * @product highcharts highstock gantt * * @private */ turboThreshold: 1000, /** * An array defining zones within a series. Zones can be applied to the * X axis, Y axis or Z axis for bubbles, according to the `zoneAxis` * option. The zone definitions have to be in ascending order regarding * to the value. * * In styled mode, the color zones are styled with the * `.highcharts-zone-{n}` class, or custom classed from the `className` * option * ([view live demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/color-zones/)). * * @see [zoneAxis](#plotOptions.series.zoneAxis) * * @sample {highcharts} highcharts/series/color-zones-simple/ * Color zones * @sample {highstock} highcharts/series/color-zones-simple/ * Color zones * * @declare Highcharts.SeriesZonesOptionsObject * @type {Array<*>} * @since 4.1.0 * @product highcharts highstock * @apioption plotOptions.series.zones */ /** * Styled mode only. A custom class name for the zone. * * @sample highcharts/css/color-zones/ * Zones styled by class name * * @type {string} * @since 5.0.0 * @apioption plotOptions.series.zones.className */ /** * Defines the color of the series. * * @see [series color](#plotOptions.series.color) * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 4.1.0 * @product highcharts highstock * @apioption plotOptions.series.zones.color */ /** * A name for the dash style to use for the graph. * * @see [plotOptions.series.dashStyle](#plotOptions.series.dashStyle) * * @sample {highcharts|highstock} highcharts/series/color-zones-dashstyle-dot/ * Dashed line indicates prognosis * * @type {Highcharts.DashStyleValue} * @since 4.1.0 * @product highcharts highstock * @apioption plotOptions.series.zones.dashStyle */ /** * Defines the fill color for the series (in area type series) * * @see [fillColor](#plotOptions.area.fillColor) * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 4.1.0 * @product highcharts highstock * @apioption plotOptions.series.zones.fillColor */ /** * The value up to where the zone extends, if undefined the zones * stretches to the last value in the series. * * @type {number} * @since 4.1.0 * @product highcharts highstock * @apioption plotOptions.series.zones.value */ /** * When using dual or multiple color axes, this number defines which * colorAxis the particular series is connected to. It refers to * either the * {@link #colorAxis.id|axis id} * or the index of the axis in the colorAxis array, with 0 being the * first. Set this option to false to prevent a series from connecting * to the default color axis. * * Since v7.2.0 the option can also be an axis id or an axis index * instead of a boolean flag. * * @sample highcharts/coloraxis/coloraxis-with-pie/ * Color axis with pie series * @sample highcharts/coloraxis/multiple-coloraxis/ * Multiple color axis * * @type {number|string|boolean} * @default 0 * @product highcharts highstock highmaps * @apioption plotOptions.series.colorAxis */ /** * Determines what data value should be used to calculate point color * if `colorAxis` is used. Requires to set `min` and `max` if some * custom point property is used or if approximation for data grouping * is set to `'sum'`. * * @sample highcharts/coloraxis/custom-color-key/ * Custom color key * @sample highcharts/coloraxis/changed-default-color-key/ * Changed default color key * * @type {string} * @default y * @since 7.2.0 * @product highcharts highstock highmaps * @apioption plotOptions.series.colorKey */ /** * Determines whether the series should look for the nearest point * in both dimensions or just the x-dimension when hovering the series. * Defaults to `'xy'` for scatter series and `'x'` for most other * series. If the data has duplicate x-values, it is recommended to * set this to `'xy'` to allow hovering over all points. * * Applies only to series types using nearest neighbor search (not * direct hover) for tooltip. * * @sample {highcharts} highcharts/series/findnearestpointby/ * Different hover behaviors * @sample {highstock} highcharts/series/findnearestpointby/ * Different hover behaviors * @sample {highmaps} highcharts/series/findnearestpointby/ * Different hover behaviors * * @since 5.0.10 * @validvalue ["x", "xy"] * * @private */ findNearestPointBy: 'x' }; return Series; }()); extend(Series.prototype, { axisTypes: ['xAxis', 'yAxis'], coll: 'series', colorCounter: 0, cropShoulder: 1, directTouch: false, drawLegendSymbol: LegendSymbolMixin.drawLineMarker, isCartesian: true, kdAxisArray: ['clientX', 'plotY'], // each point's x and y values are stored in this.xData and this.yData: parallelArrays: ['x', 'y'], pointClass: Point, requireSorting: true, // requires the data to be sorted: sorted: true }); /* * * * Registry * * */ SeriesRegistry.series = Series; /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * This is a placeholder type of the possible series options for * [Highcharts](../highcharts/series), [Highcharts Stock](../highstock/series), * [Highmaps](../highmaps/series), and [Gantt](../gantt/series). * * In TypeScript is this dynamically generated to reference all possible types * of series options. * * @ignore-declaration * @typedef {Highcharts.SeriesOptions|Highcharts.Dictionary<*>} Highcharts.SeriesOptionsType */ /** * Options for `dataSorting`. * * @interface Highcharts.DataSortingOptionsObject * @since 8.0.0 */ /** * Enable or disable data sorting for the series. * @name Highcharts.DataSortingOptionsObject#enabled * @type {boolean|undefined} */ /** * Whether to allow matching points by name in an update. * @name Highcharts.DataSortingOptionsObject#matchByName * @type {boolean|undefined} */ /** * Determines what data value should be used to sort by. * @name Highcharts.DataSortingOptionsObject#sortKey * @type {string|undefined} */ /** * Function callback when a series has been animated. * * @callback Highcharts.SeriesAfterAnimateCallbackFunction * * @param {Highcharts.Series} this * The series where the event occured. * * @param {Highcharts.SeriesAfterAnimateEventObject} event * Event arguments. */ /** * Event information regarding completed animation of a series. * * @interface Highcharts.SeriesAfterAnimateEventObject */ /** * Animated series. * @name Highcharts.SeriesAfterAnimateEventObject#target * @type {Highcharts.Series} */ /** * Event type. * @name Highcharts.SeriesAfterAnimateEventObject#type * @type {"afterAnimate"} */ /** * Function callback when the checkbox next to the series' name in the legend is * clicked. * * @callback Highcharts.SeriesCheckboxClickCallbackFunction * * @param {Highcharts.Series} this * The series where the event occured. * * @param {Highcharts.SeriesCheckboxClickEventObject} event * Event arguments. */ /** * Event information regarding check of a series box. * * @interface Highcharts.SeriesCheckboxClickEventObject */ /** * Whether the box has been checked. * @name Highcharts.SeriesCheckboxClickEventObject#checked * @type {boolean} */ /** * Related series. * @name Highcharts.SeriesCheckboxClickEventObject#item * @type {Highcharts.Series} */ /** * Related series. * @name Highcharts.SeriesCheckboxClickEventObject#target * @type {Highcharts.Series} */ /** * Event type. * @name Highcharts.SeriesCheckboxClickEventObject#type * @type {"checkboxClick"} */ /** * Function callback when a series is clicked. Return false to cancel toogle * actions. * * @callback Highcharts.SeriesClickCallbackFunction * * @param {Highcharts.Series} this * The series where the event occured. * * @param {Highcharts.SeriesClickEventObject} event * Event arguments. */ /** * Common information for a click event on a series. * * @interface Highcharts.SeriesClickEventObject * @extends global.Event */ /** * Nearest point on the graph. * @name Highcharts.SeriesClickEventObject#point * @type {Highcharts.Point} */ /** * Gets fired when the series is hidden after chart generation time, either by * clicking the legend item or by calling `.hide()`. * * @callback Highcharts.SeriesHideCallbackFunction * * @param {Highcharts.Series} this * The series where the event occured. * * @param {global.Event} event * The event that occured. */ /** * The SVG value used for the `stroke-linecap` and `stroke-linejoin` of a line * graph. * * @typedef {"butt"|"round"|"square"|string} Highcharts.SeriesLinecapValue */ /** * Gets fired when the legend item belonging to the series is clicked. The * default action is to toggle the visibility of the series. This can be * prevented by returning `false` or calling `event.preventDefault()`. * * @callback Highcharts.SeriesLegendItemClickCallbackFunction * * @param {Highcharts.Series} this * The series where the event occured. * * @param {Highcharts.SeriesLegendItemClickEventObject} event * The event that occured. */ /** * Information about the event. * * @interface Highcharts.SeriesLegendItemClickEventObject */ /** * Related browser event. * @name Highcharts.SeriesLegendItemClickEventObject#browserEvent * @type {global.PointerEvent} */ /** * Prevent the default action of toggle the visibility of the series. * @name Highcharts.SeriesLegendItemClickEventObject#preventDefault * @type {Function} */ /** * Related series. * @name Highcharts.SeriesCheckboxClickEventObject#target * @type {Highcharts.Series} */ /** * Event type. * @name Highcharts.SeriesCheckboxClickEventObject#type * @type {"checkboxClick"} */ /** * Gets fired when the mouse leaves the graph. * * @callback Highcharts.SeriesMouseOutCallbackFunction * * @param {Highcharts.Series} this * Series where the event occured. * * @param {global.PointerEvent} event * Event that occured. */ /** * Gets fired when the mouse enters the graph. * * @callback Highcharts.SeriesMouseOverCallbackFunction * * @param {Highcharts.Series} this * Series where the event occured. * * @param {global.PointerEvent} event * Event that occured. */ /** * Translation and scale for the plot area of a series. * * @interface Highcharts.SeriesPlotBoxObject */ /** * @name Highcharts.SeriesPlotBoxObject#scaleX * @type {number} */ /** * @name Highcharts.SeriesPlotBoxObject#scaleY * @type {number} */ /** * @name Highcharts.SeriesPlotBoxObject#translateX * @type {number} */ /** * @name Highcharts.SeriesPlotBoxObject#translateY * @type {number} */ /** * Gets fired when the series is shown after chart generation time, either by * clicking the legend item or by calling `.show()`. * * @callback Highcharts.SeriesShowCallbackFunction * * @param {Highcharts.Series} this * Series where the event occured. * * @param {global.Event} event * Event that occured. */ /** * Possible key values for the series state options. * * @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.SeriesStateValue */ ''; // detach doclets above /* * * * API Options * * */ /** * Series options for specific data and the data itself. In TypeScript you * have to cast the series options to specific series types, to get all * possible options for a series. * * @example * // TypeScript example * Highcharts.chart('container', { * series: [{ * color: '#06C', * data: [[0, 1], [2, 3]] * } as Highcharts.SeriesLineOptions ] * }); * * @type {Array<*>} * @apioption series */ /** * An id for the series. This can be used after render time to get a pointer * to the series object through `chart.get()`. * * @sample {highcharts} highcharts/plotoptions/series-id/ * Get series by id * * @type {string} * @since 1.2.0 * @apioption series.id */ /** * The index of the series in the chart, affecting the internal index in the * `chart.series` array, the visible Z index as well as the order in the * legend. * * @type {number} * @since 2.3.0 * @apioption series.index */ /** * The sequential index of the series in the legend. * * @see [legend.reversed](#legend.reversed), * [yAxis.reversedStacks](#yAxis.reversedStacks) * * @sample {highcharts|highstock} highcharts/series/legendindex/ * Legend in opposite order * * @type {number} * @apioption series.legendIndex */ /** * The name of the series as shown in the legend, tooltip etc. * * @sample {highcharts} highcharts/series/name/ * Series name * @sample {highmaps} maps/demo/category-map/ * Series name * * @type {string} * @apioption series.name */ /** * This option allows grouping series in a stacked chart. The stack option * can be a string or anything else, as long as the grouped series' stack * options match each other after conversion into a string. * * @sample {highcharts} highcharts/series/stack/ * Stacked and grouped columns * * @type {number|string} * @since 2.1 * @product highcharts highstock * @apioption series.stack */ /** * The type of series, for example `line` or `column`. By default, the * series type is inherited from [chart.type](#chart.type), so unless the * chart is a combination of series types, there is no need to set it on the * series level. * * @sample {highcharts} highcharts/series/type/ * Line and column in the same chart * @sample highcharts/series/type-dynamic/ * Dynamic types with button selector * @sample {highmaps} maps/demo/mapline-mappoint/ * Multiple types in the same map * * @type {string} * @apioption series.type */ /** * When using dual or multiple x axes, this number defines which xAxis the * particular series is connected to. It refers to either the * {@link #xAxis.id|axis id} * or the index of the axis in the xAxis array, with 0 being the first. * * @type {number|string} * @default 0 * @product highcharts highstock * @apioption series.xAxis */ /** * When using dual or multiple y axes, this number defines which yAxis the * particular series is connected to. It refers to either the * {@link #yAxis.id|axis id} * or the index of the axis in the yAxis array, with 0 being the first. * * @sample {highcharts} highcharts/series/yaxis/ * Apply the column series to the secondary Y axis * * @type {number|string} * @default 0 * @product highcharts highstock * @apioption series.yAxis */ /** * Define the visual z index of the series. * * @sample {highcharts} highcharts/plotoptions/series-zindex-default/ * With no z index, the series defined last are on top * @sample {highcharts} highcharts/plotoptions/series-zindex/ * With a z index, the series with the highest z index is on top * @sample {highstock} highcharts/plotoptions/series-zindex-default/ * With no z index, the series defined last are on top * @sample {highstock} highcharts/plotoptions/series-zindex/ * With a z index, the series with the highest z index is on top * * @type {number} * @product highcharts highstock * @apioption series.zIndex */ ''; // include precedent doclets in transpilat return Series; }); _registerModule(_modules, 'Extensions/ScrollablePlotArea.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Series/Series.js'], _modules['Core/Renderer/RendererRegistry.js'], _modules['Core/Utilities.js']], function (A, Axis, Chart, Series, RendererRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * Highcharts feature to make the Y axis stay fixed when scrolling the chart * horizontally on mobile devices. Supports left and right side axes. */ /* WIP on vertical scrollable plot area (#9378). To do: - Bottom axis positioning - Test with Gantt - Look for size optimizing the code - API and demos */ var stop = A.stop; var addEvent = U.addEvent, createElement = U.createElement, merge = U.merge, pick = U.pick; /* eslint-disable no-invalid-this, valid-jsdoc */ addEvent(Chart, 'afterSetChartSize', function (e) { var scrollablePlotArea = this.options.chart.scrollablePlotArea, scrollableMinWidth = scrollablePlotArea && scrollablePlotArea.minWidth, scrollableMinHeight = scrollablePlotArea && scrollablePlotArea.minHeight, scrollablePixelsX, scrollablePixelsY, corrections; if (!this.renderer.forExport) { // The amount of pixels to scroll, the difference between chart // width and scrollable width if (scrollableMinWidth) { this.scrollablePixelsX = scrollablePixelsX = Math.max(0, scrollableMinWidth - this.chartWidth); if (scrollablePixelsX) { this.scrollablePlotBox = this.renderer.scrollablePlotBox = merge(this.plotBox); this.plotBox.width = this.plotWidth += scrollablePixelsX; if (this.inverted) { this.clipBox.height += scrollablePixelsX; } else { this.clipBox.width += scrollablePixelsX; } corrections = { // Corrections for right side 1: { name: 'right', value: scrollablePixelsX } }; } // Currently we can only do either X or Y } else if (scrollableMinHeight) { this.scrollablePixelsY = scrollablePixelsY = Math.max(0, scrollableMinHeight - this.chartHeight); if (scrollablePixelsY) { this.scrollablePlotBox = this.renderer.scrollablePlotBox = merge(this.plotBox); this.plotBox.height = this.plotHeight += scrollablePixelsY; if (this.inverted) { this.clipBox.width += scrollablePixelsY; } else { this.clipBox.height += scrollablePixelsY; } corrections = { 2: { name: 'bottom', value: scrollablePixelsY } }; } } if (corrections && !e.skipAxes) { this.axes.forEach(function (axis) { // For right and bottom axes, only fix the plot line length if (corrections[axis.side]) { // Get the plot lines right in getPlotLinePath, // temporarily set it to the adjusted plot width. axis.getPlotLinePath = function () { var marginName = corrections[axis.side].name, correctionValue = corrections[axis.side].value, // axis.right or axis.bottom margin = this[marginName], path; // Temporarily adjust this[marginName] = margin - correctionValue; path = Axis.prototype.getPlotLinePath.apply(this, arguments); // Reset this[marginName] = margin; return path; }; } else { // Apply the corrected plotWidth axis.setAxisSize(); axis.setAxisTranslation(); } }); } } }); addEvent(Chart, 'render', function () { if (this.scrollablePixelsX || this.scrollablePixelsY) { if (this.setUpScrolling) { this.setUpScrolling(); } this.applyFixed(); } else if (this.fixedDiv) { // Has been in scrollable mode this.applyFixed(); } }); /** * @private * @function Highcharts.Chart#setUpScrolling * @return {void} */ Chart.prototype.setUpScrolling = function () { var _this = this; var css = { WebkitOverflowScrolling: 'touch', overflowX: 'hidden', overflowY: 'hidden' }; if (this.scrollablePixelsX) { css.overflowX = 'auto'; } if (this.scrollablePixelsY) { css.overflowY = 'auto'; } // Insert a container with position relative // that scrolling and fixed container renders to (#10555) this.scrollingParent = createElement('div', { className: 'highcharts-scrolling-parent' }, { position: 'relative' }, this.renderTo); // Add the necessary divs to provide scrolling this.scrollingContainer = createElement('div', { 'className': 'highcharts-scrolling' }, css, this.scrollingParent); // On scroll, reset the chart position because it applies to the scrolled // container addEvent(this.scrollingContainer, 'scroll', function () { if (_this.pointer) { delete _this.pointer.chartPosition; } }); this.innerContainer = createElement('div', { 'className': 'highcharts-inner-container' }, null, this.scrollingContainer); // Now move the container inside this.innerContainer.appendChild(this.container); // Don't run again this.setUpScrolling = null; }; /** * These elements are moved over to the fixed renderer and stay fixed when the * user scrolls the chart * @private */ Chart.prototype.moveFixedElements = function () { var container = this.container, fixedRenderer = this.fixedRenderer, fixedSelectors = [ '.highcharts-contextbutton', '.highcharts-credits', '.highcharts-legend', '.highcharts-legend-checkbox', '.highcharts-navigator-series', '.highcharts-navigator-xaxis', '.highcharts-navigator-yaxis', '.highcharts-navigator', '.highcharts-reset-zoom', '.highcharts-drillup-button', '.highcharts-scrollbar', '.highcharts-subtitle', '.highcharts-title' ], axisClass; if (this.scrollablePixelsX && !this.inverted) { axisClass = '.highcharts-yaxis'; } else if (this.scrollablePixelsX && this.inverted) { axisClass = '.highcharts-xaxis'; } else if (this.scrollablePixelsY && !this.inverted) { axisClass = '.highcharts-xaxis'; } else if (this.scrollablePixelsY && this.inverted) { axisClass = '.highcharts-yaxis'; } if (axisClass) { fixedSelectors.push(axisClass + ":not(.highcharts-radial-axis)", axisClass + "-labels:not(.highcharts-radial-axis-labels)"); } fixedSelectors.forEach(function (className) { [].forEach.call(container.querySelectorAll(className), function (elem) { (elem.namespaceURI === fixedRenderer.SVG_NS ? fixedRenderer.box : fixedRenderer.box.parentNode).appendChild(elem); elem.style.pointerEvents = 'auto'; }); }); }; /** * @private * @function Highcharts.Chart#applyFixed * @return {void} */ Chart.prototype.applyFixed = function () { var firstTime = !this.fixedDiv, chartOptions = this.options.chart, scrollableOptions = chartOptions.scrollablePlotArea, Renderer = RendererRegistry.getRendererType(); var fixedRenderer, scrollableWidth, scrollableHeight; // First render if (firstTime) { this.fixedDiv = createElement('div', { className: 'highcharts-fixed' }, { position: 'absolute', overflow: 'hidden', pointerEvents: 'none', zIndex: (chartOptions.style && chartOptions.style.zIndex || 0) + 2, top: 0 }, null, true); if (this.scrollingContainer) { this.scrollingContainer.parentNode.insertBefore(this.fixedDiv, this.scrollingContainer); } this.renderTo.style.overflow = 'visible'; this.fixedRenderer = fixedRenderer = new Renderer(this.fixedDiv, this.chartWidth, this.chartHeight, this.options.chart.style); // Mask this.scrollableMask = fixedRenderer .path() .attr({ fill: this.options.chart.backgroundColor || '#fff', 'fill-opacity': pick(scrollableOptions.opacity, 0.85), zIndex: -1 }) .addClass('highcharts-scrollable-mask') .add(); addEvent(this, 'afterShowResetZoom', this.moveFixedElements); addEvent(this, 'afterDrilldown', this.moveFixedElements); addEvent(this, 'afterLayOutTitles', this.moveFixedElements); } else { // Set the size of the fixed renderer to the visible width this.fixedRenderer.setSize(this.chartWidth, this.chartHeight); } if (this.scrollableDirty || firstTime) { this.scrollableDirty = false; this.moveFixedElements(); } // Increase the size of the scrollable renderer and background scrollableWidth = this.chartWidth + (this.scrollablePixelsX || 0); scrollableHeight = this.chartHeight + (this.scrollablePixelsY || 0); stop(this.container); this.container.style.width = scrollableWidth + 'px'; this.container.style.height = scrollableHeight + 'px'; this.renderer.boxWrapper.attr({ width: scrollableWidth, height: scrollableHeight, viewBox: [0, 0, scrollableWidth, scrollableHeight].join(' ') }); this.chartBackground.attr({ width: scrollableWidth, height: scrollableHeight }); this.scrollingContainer.style.height = this.chartHeight + 'px'; // Set scroll position if (firstTime) { if (scrollableOptions.scrollPositionX) { this.scrollingContainer.scrollLeft = this.scrollablePixelsX * scrollableOptions.scrollPositionX; } if (scrollableOptions.scrollPositionY) { this.scrollingContainer.scrollTop = this.scrollablePixelsY * scrollableOptions.scrollPositionY; } } // Mask behind the left and right side var axisOffset = this.axisOffset, maskTop = this.plotTop - axisOffset[0] - 1, maskLeft = this.plotLeft - axisOffset[3] - 1, maskBottom = this.plotTop + this.plotHeight + axisOffset[2] + 1, maskRight = this.plotLeft + this.plotWidth + axisOffset[1] + 1, maskPlotRight = this.plotLeft + this.plotWidth - (this.scrollablePixelsX || 0), maskPlotBottom = this.plotTop + this.plotHeight - (this.scrollablePixelsY || 0), d; if (this.scrollablePixelsX) { d = [ // Left side ['M', 0, maskTop], ['L', this.plotLeft - 1, maskTop], ['L', this.plotLeft - 1, maskBottom], ['L', 0, maskBottom], ['Z'], // Right side ['M', maskPlotRight, maskTop], ['L', this.chartWidth, maskTop], ['L', this.chartWidth, maskBottom], ['L', maskPlotRight, maskBottom], ['Z'] ]; } else if (this.scrollablePixelsY) { d = [ // Top side ['M', maskLeft, 0], ['L', maskLeft, this.plotTop - 1], ['L', maskRight, this.plotTop - 1], ['L', maskRight, 0], ['Z'], // Bottom side ['M', maskLeft, maskPlotBottom], ['L', maskLeft, this.chartHeight], ['L', maskRight, this.chartHeight], ['L', maskRight, maskPlotBottom], ['Z'] ]; } else { d = [['M', 0, 0]]; } if (this.redrawTrigger !== 'adjustHeight') { this.scrollableMask.attr({ d: d }); } }; addEvent(Axis, 'afterInit', function () { this.chart.scrollableDirty = true; }); addEvent(Series, 'show', function () { this.chart.scrollableDirty = true; }); /* * * * API Declarations * * */ /** * Options for a scrollable plot area. This feature provides a minimum size for * the plot area of the chart. If the size gets smaller than this, typically * on mobile devices, a native browser scrollbar is presented. This scrollbar * provides smooth scrolling for the contents of the plot area, whereas the * title, legend and unaffected axes are fixed. * * Since v7.1.2, a scrollable plot area can be defined for either horizontal or * vertical scrolling, depending on whether the `minWidth` or `minHeight` * option is set. * * @sample highcharts/chart/scrollable-plotarea * Scrollable plot area * @sample highcharts/chart/scrollable-plotarea-vertical * Vertically scrollable plot area * @sample {gantt} highcharts/chart/scrollable-plotarea-vertical * Gantt chart with vertically scrollable plot area * * @since 6.1.0 * @product highcharts gantt * @apioption chart.scrollablePlotArea */ /** * The minimum height for the plot area. If it gets smaller than this, the plot * area will become scrollable. * * @type {number} * @apioption chart.scrollablePlotArea.minHeight */ /** * The minimum width for the plot area. If it gets smaller than this, the plot * area will become scrollable. * * @type {number} * @apioption chart.scrollablePlotArea.minWidth */ /** * The initial scrolling position of the scrollable plot area. Ranges from 0 to * 1, where 0 aligns the plot area to the left and 1 aligns it to the right. * Typically we would use 1 if the chart has right aligned Y axes. * * @type {number} * @apioption chart.scrollablePlotArea.scrollPositionX */ /** * The initial scrolling position of the scrollable plot area. Ranges from 0 to * 1, where 0 aligns the plot area to the top and 1 aligns it to the bottom. * * @type {number} * @apioption chart.scrollablePlotArea.scrollPositionY */ /** * The opacity of mask applied on one of the sides of the plot * area. * * @sample {highcharts} highcharts/chart/scrollable-plotarea-opacity * Disabled opacity for the mask * * @type {number} * @default 0.85 * @since 7.1.1 * @apioption chart.scrollablePlotArea.opacity */ (''); // keep doclets above in transpiled file }); _registerModule(_modules, 'Core/Axis/StackingAxis.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Utilities.js']], function (A, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var getDeferredAnimation = A.getDeferredAnimation; var addEvent = U.addEvent, destroyObjectProperties = U.destroyObjectProperties, fireEvent = U.fireEvent, isNumber = U.isNumber, objectEach = U.objectEach, pick = U.pick; /* eslint-disable valid-jsdoc */ /** * Adds stacking support to axes. * @private * @class */ var StackingAxisAdditions = /** @class */ (function () { /* * * * Constructors * * */ function StackingAxisAdditions(axis) { this.oldStacks = {}; this.stacks = {}; this.stacksTouched = 0; this.axis = axis; } /* * * * Functions * * */ /** * Build the stacks from top down * @private */ StackingAxisAdditions.prototype.buildStacks = function () { var stacking = this; var axis = stacking.axis; var axisSeries = axis.series; var reversedStacks = axis.options.reversedStacks; var len = axisSeries.length; var actualSeries, i; if (!axis.isXAxis) { stacking.usePercentage = false; i = len; while (i--) { actualSeries = axisSeries[reversedStacks ? i : len - i - 1]; actualSeries.setStackedPoints(); actualSeries.setGroupedPoints(); } // Loop up again to compute percent and stream stack for (i = 0; i < len; i++) { axisSeries[i].modifyStacks(); } fireEvent(axis, 'afterBuildStacks'); } }; /** * @private */ StackingAxisAdditions.prototype.cleanStacks = function () { var stacking = this; var axis = stacking.axis; var stacks; if (!axis.isXAxis) { if (stacking.oldStacks) { stacks = stacking.stacks = stacking.oldStacks; } // reset stacks objectEach(stacks, function (type) { objectEach(type, function (stack) { stack.cumulative = stack.total; }); }); } }; /** * Set all the stacks to initial states and destroy unused ones. * @private */ StackingAxisAdditions.prototype.resetStacks = function () { var _this = this; var _a = this, axis = _a.axis, stacks = _a.stacks; if (!axis.isXAxis) { objectEach(stacks, function (type) { objectEach(type, function (stack, x) { // Clean up memory after point deletion (#1044, #4320) if (isNumber(stack.touched) && stack.touched < _this.stacksTouched) { stack.destroy(); delete type[x]; // Reset stacks } else { stack.total = null; stack.cumulative = null; } }); }); } }; /** * @private */ StackingAxisAdditions.prototype.renderStackTotals = function () { var stacking = this; var axis = stacking.axis; var chart = axis.chart; var renderer = chart.renderer; var stacks = stacking.stacks; var stackLabelsAnim = axis.options.stackLabels && axis.options.stackLabels.animation; var animationConfig = getDeferredAnimation(chart, stackLabelsAnim || false); var stackTotalGroup = stacking.stackTotalGroup = (stacking.stackTotalGroup || renderer .g('stack-labels') .attr({ visibility: 'visible', zIndex: 6, opacity: 0 }) .add()); // plotLeft/Top will change when y axis gets wider so we need to // translate the stackTotalGroup at every render call. See bug #506 // and #516 stackTotalGroup.translate(chart.plotLeft, chart.plotTop); // Render each stack total objectEach(stacks, function (type) { objectEach(type, function (stack) { stack.render(stackTotalGroup); }); }); stackTotalGroup.animate({ opacity: 1 }, animationConfig); }; return StackingAxisAdditions; }()); /** * Axis with stacking support. * @private * @class */ var StackingAxis = /** @class */ (function () { function StackingAxis() { } /* * * * Static Functions * * */ /** * Extends axis with stacking support. * @private */ StackingAxis.compose = function (AxisClass) { var axisProto = AxisClass.prototype; addEvent(AxisClass, 'init', StackingAxis.onInit); addEvent(AxisClass, 'destroy', StackingAxis.onDestroy); }; /** * @private */ StackingAxis.onDestroy = function () { var stacking = this.stacking; if (!stacking) { return; } var stacks = stacking.stacks; // Destroy each stack total objectEach(stacks, function (stack, stackKey) { destroyObjectProperties(stack); stacks[stackKey] = null; }); if (stacking && stacking.stackTotalGroup) { stacking.stackTotalGroup.destroy(); } }; /** * @private */ StackingAxis.onInit = function () { var axis = this; if (!axis.stacking) { axis.stacking = new StackingAxisAdditions(axis); } }; return StackingAxis; }()); return StackingAxis; }); _registerModule(_modules, 'Extensions/Stacking.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Series/Series.js'], _modules['Core/Axis/StackingAxis.js'], _modules['Core/Utilities.js']], function (Axis, Chart, F, H, Series, StackingAxis, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var format = F.format; var correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, isArray = U.isArray, isNumber = U.isNumber, objectEach = U.objectEach, pick = U.pick; /* * * * Class * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The class for stacks. Each stack, on a specific X value and either negative * or positive, has its own stack item. * * @private * @class * @name Highcharts.StackItem * @param {Highcharts.Axis} axis * @param {Highcharts.YAxisStackLabelsOptions} options * @param {boolean} isNegative * @param {number} x * @param {Highcharts.OptionsStackingValue} [stackOption] */ var StackItem = /** @class */ (function () { function StackItem(axis, options, isNegative, x, stackOption) { var inverted = axis.chart.inverted; this.axis = axis; // Tells if the stack is negative this.isNegative = isNegative; // Save the options to be able to style the label this.options = options = options || {}; // Save the x value to be able to position the label later this.x = x; // Initialize total value this.total = null; // This will keep each points' extremes stored by series.index and point // index this.points = {}; this.hasValidPoints = false; // Save the stack option on the series configuration object, // and whether to treat it as percent this.stack = stackOption; this.leftCliff = 0; this.rightCliff = 0; // The align options and text align varies on whether the stack is // negative and if the chart is inverted or not. // First test the user supplied value, then use the dynamic. this.alignOptions = { align: options.align || (inverted ? (isNegative ? 'left' : 'right') : 'center'), verticalAlign: options.verticalAlign || (inverted ? 'middle' : (isNegative ? 'bottom' : 'top')), y: options.y, x: options.x }; this.textAlign = options.textAlign || (inverted ? (isNegative ? 'right' : 'left') : 'center'); } /** * @private * @function Highcharts.StackItem#destroy */ StackItem.prototype.destroy = function () { destroyObjectProperties(this, this.axis); }; /** * Renders the stack total label and adds it to the stack label group. * * @private * @function Highcharts.StackItem#render * @param {Highcharts.SVGElement} group */ StackItem.prototype.render = function (group) { var chart = this.axis.chart, options = this.options, formatOption = options.format, attr = {}, str = formatOption ? // format the text in the label format(formatOption, this, chart) : options.formatter.call(this); // Change the text to reflect the new total and set visibility to hidden // in case the serie is hidden if (this.label) { this.label.attr({ text: str, visibility: 'hidden' }); } else { // Create new label this.label = chart.renderer .label(str, null, null, options.shape, null, null, options.useHTML, false, 'stack-labels'); attr = { r: options.borderRadius || 0, text: str, rotation: options.rotation, padding: pick(options.padding, 5), visibility: 'hidden' // hidden until setOffset is called }; if (!chart.styledMode) { attr.fill = options.backgroundColor; attr.stroke = options.borderColor; attr['stroke-width'] = options.borderWidth; this.label.css(options.style); } this.label.attr(attr); if (!this.label.added) { this.label.add(group); // add to the labels-group } } // Rank it higher than data labels (#8742) this.label.labelrank = chart.plotSizeY; }; /** * Sets the offset that the stack has from the x value and repositions the * label. * * @private * @function Highcarts.StackItem#setOffset * @param {number} xOffset * @param {number} xWidth * @param {number} [boxBottom] * @param {number} [boxTop] * @param {number} [defaultX] */ StackItem.prototype.setOffset = function (xOffset, xWidth, boxBottom, boxTop, defaultX) { var stackItem = this, axis = stackItem.axis, chart = axis.chart, // stack value translated mapped to chart coordinates y = axis.translate(axis.stacking.usePercentage ? 100 : (boxTop ? boxTop : stackItem.total), 0, 0, 0, 1), yZero = axis.translate(boxBottom ? boxBottom : 0), // stack origin // stack height: h = defined(y) && Math.abs(y - yZero), // x position: x = pick(defaultX, chart.xAxis[0].translate(stackItem.x)) + xOffset, stackBox = defined(y) && stackItem.getStackBox(chart, stackItem, x, y, xWidth, h, axis), label = stackItem.label, isNegative = stackItem.isNegative, isJustify = pick(stackItem.options.overflow, 'justify') === 'justify', textAlign = stackItem.textAlign, visible; if (label && stackBox) { var bBox = label.getBBox(), padding = label.padding, boxOffsetX = void 0, boxOffsetY = void 0; if (textAlign === 'left') { boxOffsetX = chart.inverted ? -padding : padding; } else if (textAlign === 'right') { boxOffsetX = bBox.width; } else { if (chart.inverted && textAlign === 'center') { boxOffsetX = bBox.width / 2; } else { boxOffsetX = chart.inverted ? (isNegative ? bBox.width + padding : -padding) : bBox.width / 2; } } boxOffsetY = chart.inverted ? bBox.height / 2 : (isNegative ? -padding : bBox.height); // Reset alignOptions property after justify #12337 stackItem.alignOptions.x = pick(stackItem.options.x, 0); stackItem.alignOptions.y = pick(stackItem.options.y, 0); // Set the stackBox position stackBox.x -= boxOffsetX; stackBox.y -= boxOffsetY; // Align the label to the box label.align(stackItem.alignOptions, null, stackBox); // Check if label is inside the plotArea #12294 if (chart.isInsidePlot(label.alignAttr.x + boxOffsetX - stackItem.alignOptions.x, label.alignAttr.y + boxOffsetY - stackItem.alignOptions.y)) { label.show(); } else { // Move label away to avoid the overlapping issues label.alignAttr.y = -9999; isJustify = false; } if (isJustify) { // Justify stackLabel into the stackBox Series.prototype.justifyDataLabel.call(this.axis, label, stackItem.alignOptions, label.alignAttr, bBox, stackBox); } label.attr({ x: label.alignAttr.x, y: label.alignAttr.y }); if (pick(!isJustify && stackItem.options.crop, true)) { visible = isNumber(label.x) && isNumber(label.y) && chart.isInsidePlot(label.x - padding + label.width, label.y) && chart.isInsidePlot(label.x + padding, label.y); if (!visible) { label.hide(); } } } }; /** * @private * @function Highcharts.StackItem#getStackBox * * @param {Highcharts.Chart} chart * * @param {Highcharts.StackItem} stackItem * * @param {number} x * * @param {number} y * * @param {number} xWidth * * @param {number} h * * @param {Highcharts.Axis} axis * * @return {Highcharts.BBoxObject} */ StackItem.prototype.getStackBox = function (chart, stackItem, x, y, xWidth, h, axis) { var reversed = stackItem.axis.reversed, inverted = chart.inverted, axisPos = axis.height + axis.pos - (inverted ? chart.plotLeft : chart.plotTop), neg = (stackItem.isNegative && !reversed) || (!stackItem.isNegative && reversed); // #4056 return { x: inverted ? (neg ? y - axis.right : y - h + axis.pos - chart.plotLeft) : x + chart.xAxis[0].transB - chart.plotLeft, y: inverted ? axis.height - x - xWidth : (neg ? (axisPos - y - h) : axisPos - y), width: inverted ? h : xWidth, height: inverted ? xWidth : h }; }; return StackItem; }()); /** * Generate stacks for each series and calculate stacks total values * * @private * @function Highcharts.Chart#getStacks */ Chart.prototype.getStacks = function () { var chart = this, inverted = chart.inverted; // reset stacks for each yAxis chart.yAxis.forEach(function (axis) { if (axis.stacking && axis.stacking.stacks && axis.hasVisibleSeries) { axis.stacking.oldStacks = axis.stacking.stacks; } }); chart.series.forEach(function (series) { var xAxisOptions = series.xAxis && series.xAxis.options || {}; if (series.options.stacking && (series.visible === true || chart.options.chart.ignoreHiddenSeries === false)) { series.stackKey = [ series.type, pick(series.options.stack, ''), inverted ? xAxisOptions.top : xAxisOptions.left, inverted ? xAxisOptions.height : xAxisOptions.width ].join(','); } }); }; // Stacking methods defined on the Axis prototype StackingAxis.compose(Axis); // Stacking methods defined for Series prototype /** * Set grouped points in a stack-like object. When `centerInCategory` is true, * and `stacking` is not enabled, we need a pseudo (horizontal) stack in order * to handle grouping of points within the same category. * * @private * @function Highcharts.Series#setStackedPoints * @return {void} */ Series.prototype.setGroupedPoints = function () { var stacking = this.yAxis.stacking; if (this.options.centerInCategory && (this.is('column') || this.is('columnrange')) && // With stacking enabled, we already have stacks that we can compute // from !this.options.stacking && // With only one series, we don't need to consider centerInCategory this.chart.series.length > 1) { Series.prototype.setStackedPoints.call(this, 'group'); // After updating, if we now have proper stacks, we must delete the group // pseudo stacks (#14986) } else if (stacking) { objectEach(stacking.stacks, function (type, key) { if (key.slice(-5) === 'group') { objectEach(type, function (stack) { return stack.destroy(); }); delete stacking.stacks[key]; } }); } }; /** * Adds series' points value to corresponding stack * * @private * @function Highcharts.Series#setStackedPoints */ Series.prototype.setStackedPoints = function (stackingParam) { var stacking = stackingParam || this.options.stacking; if (!stacking || (this.visible !== true && this.chart.options.chart.ignoreHiddenSeries !== false)) { return; } var series = this, xData = series.processedXData, yData = series.processedYData, stackedYData = [], yDataLength = yData.length, seriesOptions = series.options, threshold = seriesOptions.threshold, stackThreshold = pick(seriesOptions.startFromThreshold && threshold, 0), stackOption = seriesOptions.stack, stackKey = stackingParam ? series.type + "," + stacking : series.stackKey, negKey = '-' + stackKey, negStacks = series.negStacks, yAxis = series.yAxis, stacks = yAxis.stacking.stacks, oldStacks = yAxis.stacking.oldStacks, stackIndicator, isNegative, stack, other, key, pointKey, i, x, y; yAxis.stacking.stacksTouched += 1; // loop over the non-null y values and read them into a local array for (i = 0; i < yDataLength; i++) { x = xData[i]; y = yData[i]; stackIndicator = series.getStackIndicator(stackIndicator, x, series.index); pointKey = stackIndicator.key; // Read stacked values into a stack based on the x value, // the sign of y and the stack key. Stacking is also handled for null // values (#739) isNegative = negStacks && y < (stackThreshold ? 0 : threshold); key = isNegative ? negKey : stackKey; // Create empty object for this stack if it doesn't exist yet if (!stacks[key]) { stacks[key] = {}; } // Initialize StackItem for this x if (!stacks[key][x]) { if (oldStacks[key] && oldStacks[key][x]) { stacks[key][x] = oldStacks[key][x]; stacks[key][x].total = null; } else { stacks[key][x] = new StackItem(yAxis, yAxis.options.stackLabels, isNegative, x, stackOption); } } // If the StackItem doesn't exist, create it first stack = stacks[key][x]; if (y !== null) { stack.points[pointKey] = stack.points[series.index] = [pick(stack.cumulative, stackThreshold)]; // Record the base of the stack if (!defined(stack.cumulative)) { stack.base = pointKey; } stack.touched = yAxis.stacking.stacksTouched; // In area charts, if there are multiple points on the same X value, // let the area fill the full span of those points if (stackIndicator.index > 0 && series.singleStacks === false) { stack.points[pointKey][0] = stack.points[series.index + ',' + x + ',0'][0]; } // When updating to null, reset the point stack (#7493) } else { stack.points[pointKey] = stack.points[series.index] = null; } // Add value to the stack total if (stacking === 'percent') { // Percent stacked column, totals are the same for the positive and // negative stacks other = isNegative ? stackKey : negKey; if (negStacks && stacks[other] && stacks[other][x]) { other = stacks[other][x]; stack.total = other.total = Math.max(other.total, stack.total) + Math.abs(y) || 0; // Percent stacked areas } else { stack.total = correctFloat(stack.total + (Math.abs(y) || 0)); } } else if (stacking === 'group') { if (isArray(y)) { y = y[0]; } // In this stack, the total is the number of valid points if (y !== null) { stack.total = (stack.total || 0) + 1; } } else { stack.total = correctFloat(stack.total + (y || 0)); } if (stacking === 'group') { // This point's index within the stack, pushed to stack.points[1] stack.cumulative = (stack.total || 1) - 1; } else { stack.cumulative = pick(stack.cumulative, stackThreshold) + (y || 0); } if (y !== null) { stack.points[pointKey].push(stack.cumulative); stackedYData[i] = stack.cumulative; stack.hasValidPoints = true; } } if (stacking === 'percent') { yAxis.stacking.usePercentage = true; } if (stacking !== 'group') { this.stackedYData = stackedYData; // To be used in getExtremes } // Reset old stacks yAxis.stacking.oldStacks = {}; }; /** * Iterate over all stacks and compute the absolute values to percent * * @private * @function Highcharts.Series#modifyStacks */ Series.prototype.modifyStacks = function () { var series = this, yAxis = series.yAxis, stackKey = series.stackKey, stacks = yAxis.stacking.stacks, processedXData = series.processedXData, stackIndicator, stacking = series.options.stacking; if (series[stacking + 'Stacker']) { // Modifier function exists [stackKey, '-' + stackKey].forEach(function (key) { var i = processedXData.length, x, stack, pointExtremes; while (i--) { x = processedXData[i]; stackIndicator = series.getStackIndicator(stackIndicator, x, series.index, key); stack = stacks[key] && stacks[key][x]; pointExtremes = stack && stack.points[stackIndicator.key]; if (pointExtremes) { series[stacking + 'Stacker'](pointExtremes, stack, i); } } }); } }; /** * Modifier function for percent stacks. Blows up the stack to 100%. * * @private * @function Highcharts.Series#percentStacker */ Series.prototype.percentStacker = function (pointExtremes, stack, i) { var totalFactor = stack.total ? 100 / stack.total : 0; // Y bottom value pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor); // Y value pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor); this.stackedYData[i] = pointExtremes[1]; }; /** * Get stack indicator, according to it's x-value, to determine points with the * same x-value * * @private * @function Highcharts.Series#getStackIndicator * @param {Highcharts.StackItemIndicatorObject|undefined} stackIndicator * @param {number} x * @param {number} index * @param {string} [key] * @return {Highcharts.StackItemIndicatorObject} */ Series.prototype.getStackIndicator = function (stackIndicator, x, index, key) { // Update stack indicator, when: // first point in a stack || x changed || stack type (negative vs positive) // changed: if (!defined(stackIndicator) || stackIndicator.x !== x || (key && stackIndicator.key !== key)) { stackIndicator = { x: x, index: 0, key: key }; } else { (stackIndicator).index++; } stackIndicator.key = [index, x, stackIndicator.index].join(','); return stackIndicator; }; H.StackItem = StackItem; // @todo -> master /* * * * Default Export * * */ /** * Stack of data points * * @product highcharts * * @interface Highcharts.StackItemObject */ /** * Alignment settings * @name Highcharts.StackItemObject#alignOptions * @type {Highcharts.AlignObject} */ /** * Related axis * @name Highcharts.StackItemObject#axis * @type {Highcharts.Axis} */ /** * Cumulative value of the stacked data points * @name Highcharts.StackItemObject#cumulative * @type {number} */ /** * True if on the negative side * @name Highcharts.StackItemObject#isNegative * @type {boolean} */ /** * Related SVG element * @name Highcharts.StackItemObject#label * @type {Highcharts.SVGElement} */ /** * Related stack options * @name Highcharts.StackItemObject#options * @type {Highcharts.YAxisStackLabelsOptions} */ /** * Total value of the stacked data points * @name Highcharts.StackItemObject#total * @type {number} */ /** * Shared x value of the stack * @name Highcharts.StackItemObject#x * @type {number} */ ''; // keeps doclets above in JS file return H.StackItem; }); _registerModule(_modules, 'Series/Line/LineSeries.js', [_modules['Core/Color/Palette.js'], _modules['Core/Series/Series.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (palette, Series, SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var defined = U.defined, merge = U.merge; /* * * * Class * * */ /** * The line series is the base type and is therefor the series base prototype. * * @private */ var LineSeries = /** @class */ (function (_super) { __extends(LineSeries, _super); function LineSeries() { /* * * * Static Functions * * */ var _this = _super !== null && _super.apply(this, arguments) || this; /* * * * Properties * * */ _this.data = void 0; _this.options = void 0; _this.points = void 0; return _this; } /* * * * Functions * * */ /** * Draw the graph. Called internally when rendering line-like series * types. The first time it generates the `series.graph` item and * optionally other series-wide items like `series.area` for area * charts. On subsequent calls these items are updated with new * positions and attributes. * * @function Highcharts.Series#drawGraph */ LineSeries.prototype.drawGraph = function () { var series = this, options = this.options, graphPath = (this.gappedPath || this.getGraphPath).call(this), styledMode = this.chart.styledMode, props = [[ 'graph', 'highcharts-graph' ]]; // Presentational properties if (!styledMode) { props[0].push((options.lineColor || this.color || palette.neutralColor20 // when colorByPoint = true ), options.dashStyle); } props = series.getZonesGraphs(props); // Draw the graph props.forEach(function (prop, i) { var graphKey = prop[0], graph = series[graphKey], verb = graph ? 'animate' : 'attr', attribs; if (graph) { graph.endX = series.preventGraphAnimation ? null : graphPath.xMap; graph.animate({ d: graphPath }); } else if (graphPath.length) { // #1487 /** * SVG element of area-based charts. Can be used for styling * purposes. If zones are configured, this element will be * hidden and replaced by multiple zone areas, accessible * via `series['zone-area-x']` (where x is a number, * starting with 0). * * @name Highcharts.Series#area * @type {Highcharts.SVGElement|undefined} */ /** * SVG element of line-based charts. Can be used for styling * purposes. If zones are configured, this element will be * hidden and replaced by multiple zone lines, accessible * via `series['zone-graph-x']` (where x is a number, * starting with 0). * * @name Highcharts.Series#graph * @type {Highcharts.SVGElement|undefined} */ series[graphKey] = graph = series.chart.renderer .path(graphPath) .addClass(prop[1]) .attr({ zIndex: 1 }) // #1069 .add(series.group); } if (graph && !styledMode) { attribs = { 'stroke': prop[2], 'stroke-width': options.lineWidth, // Polygon series use filled graph 'fill': (series.fillGraph && series.color) || 'none' }; if (prop[3]) { attribs.dashstyle = prop[3]; } else if (options.linecap !== 'square') { attribs['stroke-linecap'] = attribs['stroke-linejoin'] = 'round'; } graph[verb](attribs) // Add shadow to normal series (0) or to first // zone (1) #3932 .shadow((i < 2) && options.shadow); } // Helpers for animation if (graph) { graph.startX = graphPath.xMap; graph.isArea = graphPath.isArea; // For arearange animation } }); }; // eslint-disable-next-line valid-jsdoc /** * Get the graph path. * * @private */ LineSeries.prototype.getGraphPath = function (points, nullsAsZeroes, connectCliffs) { var series = this, options = series.options, step = options.step, reversed, graphPath = [], xMap = [], gap; points = points || series.points; // Bottom of a stack is reversed reversed = points.reversed; if (reversed) { points.reverse(); } // Reverse the steps (#5004) step = { right: 1, center: 2 }[step] || (step && 3); if (step && reversed) { step = 4 - step; } // Remove invalid points, especially in spline (#5015) points = this.getValidPoints(points, false, !(options.connectNulls && !nullsAsZeroes && !connectCliffs)); // Build the line points.forEach(function (point, i) { var plotX = point.plotX, plotY = point.plotY, lastPoint = points[i - 1], // the path to this point from the previous pathToPoint; if ((point.leftCliff || (lastPoint && lastPoint.rightCliff)) && !connectCliffs) { gap = true; // ... and continue } // Line series, nullsAsZeroes is not handled if (point.isNull && !defined(nullsAsZeroes) && i > 0) { gap = !options.connectNulls; // Area series, nullsAsZeroes is set } else if (point.isNull && !nullsAsZeroes) { gap = true; } else { if (i === 0 || gap) { pathToPoint = [[ 'M', point.plotX, point.plotY ]]; // Generate the spline as defined in the SplineSeries object } else if (series.getPointSpline) { pathToPoint = [series.getPointSpline(points, point, i)]; } else if (step) { if (step === 1) { // right pathToPoint = [[ 'L', lastPoint.plotX, plotY ]]; } else if (step === 2) { // center pathToPoint = [[ 'L', (lastPoint.plotX + plotX) / 2, lastPoint.plotY ], [ 'L', (lastPoint.plotX + plotX) / 2, plotY ]]; } else { pathToPoint = [[ 'L', plotX, lastPoint.plotY ]]; } pathToPoint.push([ 'L', plotX, plotY ]); } else { // normal line to next point pathToPoint = [[ 'L', plotX, plotY ]]; } // Prepare for animation. When step is enabled, there are // two path nodes for each x value. xMap.push(point.x); if (step) { xMap.push(point.x); if (step === 2) { // step = center (#8073) xMap.push(point.x); } } graphPath.push.apply(graphPath, pathToPoint); gap = false; } }); graphPath.xMap = xMap; series.graphPath = graphPath; return graphPath; }; // eslint-disable-next-line valid-jsdoc /** * Get zones properties for building graphs. Extendable by series with * multiple lines within one series. * * @private */ LineSeries.prototype.getZonesGraphs = function (props) { // Add the zone properties if any this.zones.forEach(function (zone, i) { var propset = [ 'zone-graph-' + i, 'highcharts-graph highcharts-zone-graph-' + i + ' ' + (zone.className || '') ]; if (!this.chart.styledMode) { propset.push((zone.color || this.color), (zone.dashStyle || this.options.dashStyle)); } props.push(propset); }, this); return props; }; /** * General options for all series types. * * @optionparent plotOptions.series */ LineSeries.defaultOptions = merge(Series.defaultOptions, { // nothing here yet }); return LineSeries; }(Series)); SeriesRegistry.registerSeriesType('line', LineSeries); /* * * * Default Export * * */ /* * * * API Options * * */ /** * A line series displays information as a series of data points connected by * straight line segments. * * @sample {highcharts} highcharts/demo/line-basic/ * Line chart * @sample {highstock} stock/demo/basic-line/ * Line chart * * @extends plotOptions.series * @product highcharts highstock * @apioption plotOptions.line */ /** * The SVG value used for the `stroke-linecap` and `stroke-linejoin` * of a line graph. Round means that lines are rounded in the ends and * bends. * * @type {Highcharts.SeriesLinecapValue} * @default round * @since 3.0.7 * @apioption plotOptions.line.linecap */ /** * A `line` series. If the [type](#series.line.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.line * @excluding dataParser,dataURL * @product highcharts highstock * @apioption series.line */ /** * An array of data points for the series. For the `line` series type, * points can be given in the following ways: * * 1. An array of numerical values. In this case, the numerical values will be * interpreted as `y` options. The `x` values will be automatically * calculated, either starting at 0 and incremented by 1, or from * `pointStart` and `pointInterval` given in the series options. If the axis * has categories, these will be used. Example: * ```js * data: [0, 5, 3, 5] * ``` * * 2. An array of arrays with 2 values. In this case, the values correspond to * `x,y`. If the first value is a string, it is applied as the name of the * point, and the `x` value is inferred. * ```js * data: [ * [0, 1], * [1, 2], * [2, 8] * ] * ``` * * 3. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.line.turboThreshold), * this option is not available. * ```js * data: [{ * x: 1, * y: 9, * name: "Point2", * color: "#00FF00" * }, { * x: 1, * y: 6, * name: "Point1", * color: "#FF00FF" * }] * ``` * * **Note:** In TypeScript you have to extend `PointOptionsObject` with an * additional declaration to allow custom data types: * ```ts * declare module `highcharts` { * interface PointOptionsObject { * custom: Record<string, (boolean|number|string)>; * } * } * ``` * * @sample {highcharts} highcharts/chart/reflow-true/ * Numerical values * @sample {highcharts} highcharts/series/data-array-of-arrays/ * Arrays of numeric x and y * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ * Arrays of datetime x and y * @sample {highcharts} highcharts/series/data-array-of-name-value/ * Arrays of point.name and y * @sample {highcharts} highcharts/series/data-array-of-objects/ * Config objects * * @declare Highcharts.PointOptionsObject * @type {Array<number|Array<(number|string),(number|null)>|null|*>} * @apioption series.line.data */ /** * An additional, individual class name for the data point's graphic * representation. * * @type {string} * @since 5.0.0 * @product highcharts gantt * @apioption series.line.data.className */ /** * Individual color for the point. By default the color is pulled from * the global `colors` array. * * In styled mode, the `color` option doesn't take effect. Instead, use * `colorIndex`. * * @sample {highcharts} highcharts/point/color/ * Mark the highest point * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highcharts highstock gantt * @apioption series.line.data.color */ /** * A specific color index to use for the point, so its graphic representations * are given the class name `highcharts-color-{n}`. In styled mode this will * change the color of the graphic. In non-styled mode, the color by is set by * the `fill` attribute, so the change in class name won't have a visual effect * by default. * * @type {number} * @since 5.0.0 * @product highcharts gantt * @apioption series.line.data.colorIndex */ /** * A reserved subspace to store options and values for customized functionality. * Here you can add additional data for your own event callbacks and formatter * callbacks. * * @sample {highcharts} highcharts/point/custom/ * Point and series with custom data * * @type {Highcharts.Dictionary<*>} * @apioption series.line.data.custom */ /** * Individual data label for each point. The options are the same as * the ones for [plotOptions.series.dataLabels]( * #plotOptions.series.dataLabels). * * @sample highcharts/point/datalabels/ * Show a label for the last value * * @declare Highcharts.DataLabelsOptions * @extends plotOptions.line.dataLabels * @product highcharts highstock gantt * @apioption series.line.data.dataLabels */ /** * A description of the point to add to the screen reader information * about the point. * * @type {string} * @since 5.0.0 * @requires modules/accessibility * @apioption series.line.data.description */ /** * An id for the point. This can be used after render time to get a * pointer to the point object through `chart.get()`. * * @sample {highcharts} highcharts/point/id/ * Remove an id'd point * * @type {string} * @since 1.2.0 * @product highcharts highstock gantt * @apioption series.line.data.id */ /** * The rank for this point's data label in case of collision. If two * data labels are about to overlap, only the one with the highest `labelrank` * will be drawn. * * @type {number} * @apioption series.line.data.labelrank */ /** * The name of the point as shown in the legend, tooltip, dataLabels, etc. * * @see [xAxis.uniqueNames](#xAxis.uniqueNames) * * @sample {highcharts} highcharts/series/data-array-of-objects/ * Point names * * @type {string} * @apioption series.line.data.name */ /** * Whether the data point is selected initially. * * @type {boolean} * @default false * @product highcharts highstock gantt * @apioption series.line.data.selected */ /** * The x value of the point. For datetime axes, the X value is the timestamp * in milliseconds since 1970. * * @type {number} * @product highcharts highstock * @apioption series.line.data.x */ /** * The y value of the point. * * @type {number|null} * @product highcharts highstock * @apioption series.line.data.y */ /** * The individual point events. * * @extends plotOptions.series.point.events * @product highcharts highstock gantt * @apioption series.line.data.events */ /** * Options for the point markers of line-like series. * * @declare Highcharts.PointMarkerOptionsObject * @extends plotOptions.series.marker * @product highcharts highstock * @apioption series.line.data.marker */ ''; // include precedent doclets in transpilat return LineSeries; }); _registerModule(_modules, 'Series/Area/AreaSeries.js', [_modules['Core/Color/Color.js'], _modules['Mixins/LegendSymbol.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (Color, LegendSymbolMixin, SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var color = Color.parse; var LineSeries = SeriesRegistry.seriesTypes.line; var extend = U.extend, merge = U.merge, objectEach = U.objectEach, pick = U.pick; /* * * * Class * * */ /** * Area series type. * * @private * @class * @name AreaSeries * * @augments LineSeries */ var AreaSeries = /** @class */ (function (_super) { __extends(AreaSeries, _super); function AreaSeries() { /* * * * Static Properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; _this.data = void 0; _this.options = void 0; _this.points = void 0; return _this; /* eslint-enable valid-jsdoc */ } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Draw the graph and the underlying area. This method calls the Series * base function and adds the area. The areaPath is calculated in the * getSegmentPath method called from Series.prototype.drawGraph. * @private */ AreaSeries.prototype.drawGraph = function () { // Define or reset areaPath this.areaPath = []; // Call the base method _super.prototype.drawGraph.apply(this); // Define local variables var series = this, areaPath = this.areaPath, options = this.options, zones = this.zones, props = [[ 'area', 'highcharts-area', this.color, options.fillColor ]]; // area name, main color, fill color zones.forEach(function (zone, i) { props.push([ 'zone-area-' + i, 'highcharts-area highcharts-zone-area-' + i + ' ' + zone.className, zone.color || series.color, zone.fillColor || options.fillColor ]); }); props.forEach(function (prop) { var areaKey = prop[0], area = series[areaKey], verb = area ? 'animate' : 'attr', attribs = {}; // Create or update the area if (area) { // update area.endX = series.preventGraphAnimation ? null : areaPath.xMap; area.animate({ d: areaPath }); } else { // create attribs.zIndex = 0; // #1069 area = series[areaKey] = series.chart.renderer .path(areaPath) .addClass(prop[1]) .add(series.group); area.isArea = true; } if (!series.chart.styledMode) { attribs.fill = pick(prop[3], color(prop[2]) .setOpacity(pick(options.fillOpacity, 0.75)) .get()); } area[verb](attribs); area.startX = areaPath.xMap; area.shiftUnit = options.step ? 2 : 1; }); }; /** * @private */ AreaSeries.prototype.getGraphPath = function (points) { var getGraphPath = LineSeries.prototype.getGraphPath, graphPath, options = this.options, stacking = options.stacking, yAxis = this.yAxis, topPath, bottomPath, bottomPoints = [], graphPoints = [], seriesIndex = this.index, i, areaPath, plotX, stacks = yAxis.stacking.stacks[this.stackKey], threshold = options.threshold, translatedThreshold = Math.round(// #10909 yAxis.getThreshold(options.threshold)), isNull, yBottom, connectNulls = pick(// #10574 options.connectNulls, stacking === 'percent'), // To display null points in underlying stacked series, this // series graph must be broken, and the area also fall down to // fill the gap left by the null point. #2069 addDummyPoints = function (i, otherI, side) { var point = points[i], stackedValues = stacking && stacks[point.x].points[seriesIndex], nullVal = point[side + 'Null'] || 0, cliffVal = point[side + 'Cliff'] || 0, top, bottom, isNull = true; if (cliffVal || nullVal) { top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal; bottom = stackedValues[0] + cliffVal; isNull = !!nullVal; } else if (!stacking && points[otherI] && points[otherI].isNull) { top = bottom = threshold; } // Add to the top and bottom line of the area if (typeof top !== 'undefined') { graphPoints.push({ plotX: plotX, plotY: top === null ? translatedThreshold : yAxis.getThreshold(top), isNull: isNull, isCliff: true }); bottomPoints.push({ plotX: plotX, plotY: bottom === null ? translatedThreshold : yAxis.getThreshold(bottom), doCurve: false // #1041, gaps in areaspline areas }); } }; // Find what points to use points = points || this.points; // Fill in missing points if (stacking) { points = this.getStackPoints(points); } for (i = 0; i < points.length; i++) { // Reset after series.update of stacking property (#12033) if (!stacking) { points[i].leftCliff = points[i].rightCliff = points[i].leftNull = points[i].rightNull = void 0; } isNull = points[i].isNull; plotX = pick(points[i].rectPlotX, points[i].plotX); yBottom = stacking ? pick(points[i].yBottom, translatedThreshold) : translatedThreshold; if (!isNull || connectNulls) { if (!connectNulls) { addDummyPoints(i, i - 1, 'left'); } // Skip null point when stacking is false and connectNulls // true if (!(isNull && !stacking && connectNulls)) { graphPoints.push(points[i]); bottomPoints.push({ x: i, plotX: plotX, plotY: yBottom }); } if (!connectNulls) { addDummyPoints(i, i + 1, 'right'); } } } topPath = getGraphPath.call(this, graphPoints, true, true); bottomPoints.reversed = true; bottomPath = getGraphPath.call(this, bottomPoints, true, true); var firstBottomPoint = bottomPath[0]; if (firstBottomPoint && firstBottomPoint[0] === 'M') { bottomPath[0] = ['L', firstBottomPoint[1], firstBottomPoint[2]]; } areaPath = topPath.concat(bottomPath); if (areaPath.length) { areaPath.push(['Z']); } // TODO: don't set leftCliff and rightCliff when connectNulls? graphPath = getGraphPath .call(this, graphPoints, false, connectNulls); areaPath.xMap = topPath.xMap; this.areaPath = areaPath; return graphPath; }; /** * Return an array of stacked points, where null and missing points are * replaced by dummy points in order for gaps to be drawn correctly in * stacks. * @private */ AreaSeries.prototype.getStackPoints = function (points) { var series = this, segment = [], keys = [], xAxis = this.xAxis, yAxis = this.yAxis, stack = yAxis.stacking.stacks[this.stackKey], pointMap = {}, yAxisSeries = yAxis.series, seriesLength = yAxisSeries.length, upOrDown = yAxis.options.reversedStacks ? 1 : -1, seriesIndex = yAxisSeries.indexOf(series); points = points || this.points; if (this.options.stacking) { for (var i = 0; i < points.length; i++) { // Reset after point update (#7326) points[i].leftNull = points[i].rightNull = void 0; // Create a map where we can quickly look up the points by // their X values. pointMap[points[i].x] = points[i]; } // Sort the keys (#1651) objectEach(stack, function (stackX, x) { // nulled after switching between // grouping and not (#1651, #2336) if (stackX.total !== null) { keys.push(x); } }); keys.sort(function (a, b) { return a - b; }); var visibleSeries_1 = yAxisSeries.map(function (s) { return s.visible; }); keys.forEach(function (x, idx) { var y = 0, stackPoint, stackedValues; if (pointMap[x] && !pointMap[x].isNull) { segment.push(pointMap[x]); // Find left and right cliff. -1 goes left, 1 goes // right. [-1, 1].forEach(function (direction) { var nullName = direction === 1 ? 'rightNull' : 'leftNull', cliffName = direction === 1 ? 'rightCliff' : 'leftCliff', cliff = 0, otherStack = stack[keys[idx + direction]]; // If there is a stack next to this one, // to the left or to the right... if (otherStack) { var i = seriesIndex; // Can go either up or down, // depending on reversedStacks while (i >= 0 && i < seriesLength) { var si = yAxisSeries[i].index; stackPoint = otherStack.points[si]; if (!stackPoint) { // If the next point in this series // is missing, mark the point // with point.leftNull or // point.rightNull = true. if (si === series.index) { pointMap[x][nullName] = true; // If there are missing points in // the next stack in any of the // series below this one, we need // to substract the missing values // and add a hiatus to the left or // right. } else if (visibleSeries_1[i]) { stackedValues = stack[x].points[si]; if (stackedValues) { cliff -= stackedValues[1] - stackedValues[0]; } } } // When reversedStacks is true, loop up, // else loop down i += upOrDown; } } pointMap[x][cliffName] = cliff; }); // There is no point for this X value in this series, so we // insert a dummy point in order for the areas to be drawn // correctly. } else { // Loop down the stack to find the series below this // one that has a value (#1991) var i = seriesIndex; while (i >= 0 && i < seriesLength) { var si = yAxisSeries[i].index; stackPoint = stack[x].points[si]; if (stackPoint) { y = stackPoint[1]; break; } // When reversedStacks is true, loop up, else loop // down i += upOrDown; } y = pick(y, 0); y = yAxis.translate(// #6272 y, 0, 1, 0, 1); segment.push({ isNull: true, plotX: xAxis.translate(// #6272 x, 0, 0, 0, 1), x: x, plotY: y, yBottom: y }); } }); } return segment; }; /** * The area series type. * * @sample {highcharts} highcharts/demo/area-basic/ * Area chart * @sample {highstock} stock/demo/area/ * Area chart * * @extends plotOptions.line * @excluding useOhlcData * @product highcharts highstock * @optionparent plotOptions.area */ AreaSeries.defaultOptions = merge(LineSeries.defaultOptions, { /** * @see [fillColor](#plotOptions.area.fillColor) * @see [fillOpacity](#plotOptions.area.fillOpacity) * * @apioption plotOptions.area.color */ /** * Fill color or gradient for the area. When `null`, the series' `color` * is used with the series' `fillOpacity`. * * In styled mode, the fill color can be set with the `.highcharts-area` * class name. * * @see [color](#plotOptions.area.color) * @see [fillOpacity](#plotOptions.area.fillOpacity) * * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/ * Null by default * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/ * Gradient * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highcharts highstock * @apioption plotOptions.area.fillColor */ /** * Fill opacity for the area. When you set an explicit `fillColor`, * the `fillOpacity` is not applied. Instead, you should define the * opacity in the `fillColor` with an rgba color definition. The * `fillOpacity` setting, also the default setting, overrides the alpha * component of the `color` setting. * * In styled mode, the fill opacity can be set with the * `.highcharts-area` class name. * * @see [color](#plotOptions.area.color) * @see [fillColor](#plotOptions.area.fillColor) * * @sample {highcharts} highcharts/plotoptions/area-fillopacity/ * Automatic fill color and fill opacity of 0.1 * * @type {number} * @default {highcharts} 0.75 * @default {highstock} 0.75 * @product highcharts highstock * @apioption plotOptions.area.fillOpacity */ /** * A separate color for the graph line. By default the line takes the * `color` of the series, but the lineColor setting allows setting a * separate color for the line without altering the `fillColor`. * * In styled mode, the line stroke can be set with the * `.highcharts-graph` class name. * * @sample {highcharts} highcharts/plotoptions/area-linecolor/ * Dark gray line * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highcharts highstock * @apioption plotOptions.area.lineColor */ /** * A separate color for the negative part of the area. * * In styled mode, a negative color is set with the * `.highcharts-negative` class name. * * @see [negativeColor](#plotOptions.area.negativeColor) * * @sample {highcharts} highcharts/css/series-negative-color/ * Negative color in styled mode * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 3.0 * @product highcharts * @apioption plotOptions.area.negativeFillColor */ /** * Whether the whole area or just the line should respond to mouseover * tooltips and other mouse or touch events. * * @sample {highcharts|highstock} highcharts/plotoptions/area-trackbyarea/ * Display the tooltip when the area is hovered * * @type {boolean} * @default false * @since 1.1.6 * @product highcharts highstock * @apioption plotOptions.area.trackByArea */ /** * The Y axis value to serve as the base for the area, for * distinguishing between values above and below a threshold. The area * between the graph and the threshold is filled. * * * If a number is given, the Y axis will scale to the threshold. * * If `null`, the scaling behaves like a line series with fill between * the graph and the Y axis minimum. * * If `Infinity` or `-Infinity`, the area between the graph and the * corresponding Y axis extreme is filled (since v6.1.0). * * @sample {highcharts} highcharts/plotoptions/area-threshold/ * A threshold of 100 * @sample {highcharts} highcharts/plotoptions/area-threshold-infinity/ * A threshold of Infinity * * @type {number|null} * @since 2.0 * @product highcharts highstock */ threshold: 0 }); return AreaSeries; }(LineSeries)); extend(AreaSeries.prototype, { singleStacks: false, drawLegendSymbol: LegendSymbolMixin.drawRectangle }); SeriesRegistry.registerSeriesType('area', AreaSeries); /* * * * Default Export * * */ /* * * * API Options * * */ /** * A `area` series. If the [type](#series.area.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.area * @excluding dataParser, dataURL, useOhlcData * @product highcharts highstock * @apioption series.area */ /** * @see [fillColor](#series.area.fillColor) * @see [fillOpacity](#series.area.fillOpacity) * * @apioption series.area.color */ /** * An array of data points for the series. For the `area` series type, * points can be given in the following ways: * * 1. An array of numerical values. In this case, the numerical values will be * interpreted as `y` options. The `x` values will be automatically * calculated, either starting at 0 and incremented by 1, or from * `pointStart` * and `pointInterval` given in the series options. If the * axis has categories, these will be used. Example: * ```js * data: [0, 5, 3, 5] * ``` * * 2. An array of arrays with 2 values. In this case, the values correspond to * `x,y`. If the first value is a string, it is applied as the name of the * point, and the `x` value is inferred. * ```js * data: [ * [0, 9], * [1, 7], * [2, 6] * ] * ``` * * 3. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.area.turboThreshold), this option is not * available. * ```js * data: [{ * x: 1, * y: 9, * name: "Point2", * color: "#00FF00" * }, { * x: 1, * y: 6, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @sample {highcharts} highcharts/chart/reflow-true/ * Numerical values * @sample {highcharts} highcharts/series/data-array-of-arrays/ * Arrays of numeric x and y * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ * Arrays of datetime x and y * @sample {highcharts} highcharts/series/data-array-of-name-value/ * Arrays of point.name and y * @sample {highcharts} highcharts/series/data-array-of-objects/ * Config objects * * @type {Array<number|Array<(number|string),(number|null)>|null|*>} * @extends series.line.data * @product highcharts highstock * @apioption series.area.data */ /** * @see [color](#series.area.color) * @see [fillOpacity](#series.area.fillOpacity) * * @apioption series.area.fillColor */ /** * @see [color](#series.area.color) * @see [fillColor](#series.area.fillColor) * * @default {highcharts} 0.75 * @default {highstock} 0.75 * @apioption series.area.fillOpacity */ ''; // adds doclets above to transpilat return AreaSeries; }); _registerModule(_modules, 'Series/Spline/SplineSeries.js', [_modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var LineSeries = SeriesRegistry.seriesTypes.line; var merge = U.merge, pick = U.pick; /** * Spline series type. * * @private */ var SplineSeries = /** @class */ (function (_super) { __extends(SplineSeries, _super); function SplineSeries() { /* * * * Static Properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; /* * * * Properties * * */ _this.data = void 0; _this.options = void 0; _this.points = void 0; return _this; /* eslint-enable valid-jsdoc */ } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Get the spline segment from a given point's previous neighbour to the * given point. * * @private * @function Highcharts.seriesTypes.spline#getPointSpline * * @param {Array<Highcharts.Point>} * * @param {Highcharts.Point} point * * @param {number} i * * @return {Highcharts.SVGPathArray} */ SplineSeries.prototype.getPointSpline = function (points, point, i) { var // 1 means control points midway between points, 2 means 1/3 // from the point, 3 is 1/4 etc smoothing = 1.5, denom = smoothing + 1, plotX = point.plotX || 0, plotY = point.plotY || 0, lastPoint = points[i - 1], nextPoint = points[i + 1], leftContX, leftContY, rightContX, rightContY, ret; /** * @private */ function doCurve(otherPoint) { return otherPoint && !otherPoint.isNull && otherPoint.doCurve !== false && // #6387, area splines next to null: !point.isCliff; } // Find control points if (doCurve(lastPoint) && doCurve(nextPoint)) { var lastX = lastPoint.plotX || 0, lastY = lastPoint.plotY || 0, nextX = nextPoint.plotX || 0, nextY = nextPoint.plotY || 0, correction = 0; leftContX = (smoothing * plotX + lastX) / denom; leftContY = (smoothing * plotY + lastY) / denom; rightContX = (smoothing * plotX + nextX) / denom; rightContY = (smoothing * plotY + nextY) / denom; // Have the two control points make a straight line through main // point if (rightContX !== leftContX) { // #5016, division by zero correction = (((rightContY - leftContY) * (rightContX - plotX)) / (rightContX - leftContX) + plotY - rightContY); } leftContY += correction; rightContY += correction; // to prevent false extremes, check that control points are // between neighbouring points' y values if (leftContY > lastY && leftContY > plotY) { leftContY = Math.max(lastY, plotY); // mirror of left control point rightContY = 2 * plotY - leftContY; } else if (leftContY < lastY && leftContY < plotY) { leftContY = Math.min(lastY, plotY); rightContY = 2 * plotY - leftContY; } if (rightContY > nextY && rightContY > plotY) { rightContY = Math.max(nextY, plotY); leftContY = 2 * plotY - rightContY; } else if (rightContY < nextY && rightContY < plotY) { rightContY = Math.min(nextY, plotY); leftContY = 2 * plotY - rightContY; } // record for drawing in next point point.rightContX = rightContX; point.rightContY = rightContY; } // Visualize control points for debugging /* if (leftContX) { this.chart.renderer.circle( leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 2 ) .attr({ stroke: 'red', 'stroke-width': 2, fill: 'none', zIndex: 9 }) .add(); this.chart.renderer.path(['M', leftContX + this.chart.plotLeft, leftContY + this.chart.plotTop, 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) .attr({ stroke: 'red', 'stroke-width': 2, zIndex: 9 }) .add(); } if (rightContX) { this.chart.renderer.circle( rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 2 ) .attr({ stroke: 'green', 'stroke-width': 2, fill: 'none', zIndex: 9 }) .add(); this.chart.renderer.path(['M', rightContX + this.chart.plotLeft, rightContY + this.chart.plotTop, 'L', plotX + this.chart.plotLeft, plotY + this.chart.plotTop]) .attr({ stroke: 'green', 'stroke-width': 2, zIndex: 9 }) .add(); } // */ ret = [ 'C', pick(lastPoint.rightContX, lastPoint.plotX, 0), pick(lastPoint.rightContY, lastPoint.plotY, 0), pick(leftContX, plotX, 0), pick(leftContY, plotY, 0), plotX, plotY ]; // reset for updating series later lastPoint.rightContX = lastPoint.rightContY = void 0; return ret; }; /** * A spline series is a special type of line series, where the segments * between the data points are smoothed. * * @sample {highcharts} highcharts/demo/spline-irregular-time/ * Spline chart * @sample {highstock} stock/demo/spline/ * Spline chart * * @extends plotOptions.series * @excluding step, boostThreshold, boostBlending * @product highcharts highstock * @optionparent plotOptions.spline */ SplineSeries.defaultOptions = merge(LineSeries.defaultOptions); return SplineSeries; }(LineSeries)); SeriesRegistry.registerSeriesType('spline', SplineSeries); /* * * * Default Export * * */ /* * * * API Options * * */ /** * A `spline` series. If the [type](#series.spline.type) option is * not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.spline * @excluding dataParser, dataURL, step, boostThreshold, boostBlending * @product highcharts highstock * @apioption series.spline */ /** * An array of data points for the series. For the `spline` series type, * points can be given in the following ways: * * 1. An array of numerical values. In this case, the numerical values will be * interpreted as `y` options. The `x` values will be automatically * calculated, either starting at 0 and incremented by 1, or from * `pointStart` and `pointInterval` given in the series options. If the axis * has categories, these will be used. Example: * ```js * data: [0, 5, 3, 5] * ``` * * 2. An array of arrays with 2 values. In this case, the values correspond to * `x,y`. If the first value is a string, it is applied as the name of the * point, and the `x` value is inferred. * ```js * data: [ * [0, 9], * [1, 2], * [2, 8] * ] * ``` * * 3. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.spline.turboThreshold), * this option is not available. * ```js * data: [{ * x: 1, * y: 9, * name: "Point2", * color: "#00FF00" * }, { * x: 1, * y: 0, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @sample {highcharts} highcharts/chart/reflow-true/ * Numerical values * @sample {highcharts} highcharts/series/data-array-of-arrays/ * Arrays of numeric x and y * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ * Arrays of datetime x and y * @sample {highcharts} highcharts/series/data-array-of-name-value/ * Arrays of point.name and y * @sample {highcharts} highcharts/series/data-array-of-objects/ * Config objects * * @type {Array<number|Array<(number|string),(number|null)>|null|*>} * @extends series.line.data * @product highcharts highstock * @apioption series.spline.data */ ''; // adds doclets above intro transpilat return SplineSeries; }); _registerModule(_modules, 'Series/AreaSpline/AreaSplineSeries.js', [_modules['Series/Area/AreaSeries.js'], _modules['Series/Spline/SplineSeries.js'], _modules['Mixins/LegendSymbol.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (AreaSeries, SplineSeries, LegendSymbolMixin, SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var areaProto = AreaSeries.prototype; var extend = U.extend, merge = U.merge; /* * * * Class * * */ /** * AreaSpline series type. * * @private * @class * @name Highcharts.seriesTypes.areaspline * * @augments Highcharts.Series */ var AreaSplineSeries = /** @class */ (function (_super) { __extends(AreaSplineSeries, _super); function AreaSplineSeries() { /* * * * Static properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; /* * * * Properties * * */ _this.data = void 0; _this.points = void 0; _this.options = void 0; return _this; } /** * The area spline series is an area series where the graph between the * points is smoothed into a spline. * * @sample {highcharts} highcharts/demo/areaspline/ * Area spline chart * @sample {highstock} stock/demo/areaspline/ * Area spline chart * * @extends plotOptions.area * @excluding step, boostThreshold, boostBlending * @product highcharts highstock * @apioption plotOptions.areaspline */ /** * @see [fillColor](#plotOptions.areaspline.fillColor) * @see [fillOpacity](#plotOptions.areaspline.fillOpacity) * * @apioption plotOptions.areaspline.color */ /** * @see [color](#plotOptions.areaspline.color) * @see [fillOpacity](#plotOptions.areaspline.fillOpacity) * * @apioption plotOptions.areaspline.fillColor */ /** * @see [color](#plotOptions.areaspline.color) * @see [fillColor](#plotOptions.areaspline.fillColor) * * @default {highcharts} 0.75 * @default {highstock} 0.75 * @apioption plotOptions.areaspline.fillOpacity */ AreaSplineSeries.defaultOptions = merge(SplineSeries.defaultOptions, AreaSeries.defaultOptions); return AreaSplineSeries; }(SplineSeries)); extend(AreaSplineSeries.prototype, { getGraphPath: areaProto.getGraphPath, getStackPoints: areaProto.getStackPoints, drawGraph: areaProto.drawGraph, drawLegendSymbol: LegendSymbolMixin.drawRectangle }); SeriesRegistry.registerSeriesType('areaspline', AreaSplineSeries); /* * * * Default export * * */ /** * A `areaspline` series. If the [type](#series.areaspline.type) option * is not specified, it is inherited from [chart.type](#chart.type). * * * @extends series,plotOptions.areaspline * @excluding dataParser, dataURL, step, boostThreshold, boostBlending * @product highcharts highstock * @apioption series.areaspline */ /** * @see [fillColor](#series.areaspline.fillColor) * @see [fillOpacity](#series.areaspline.fillOpacity) * * @apioption series.areaspline.color */ /** * An array of data points for the series. For the `areaspline` series * type, points can be given in the following ways: * * 1. An array of numerical values. In this case, the numerical values will be * interpreted as `y` options. The `x` values will be automatically * calculated, either starting at 0 and incremented by 1, or from * `pointStart` and `pointInterval` given in the series options. If the axis * has categories, these will be used. Example: * ```js * data: [0, 5, 3, 5] * ``` * * 2. An array of arrays with 2 values. In this case, the values correspond to * `x,y`. If the first value is a string, it is applied as the name of the * point, and the `x` value is inferred. * ```js * data: [ * [0, 10], * [1, 9], * [2, 3] * ] * ``` * * 3. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.areaspline.turboThreshold), this option is not * available. * ```js * data: [{ * x: 1, * y: 4, * name: "Point2", * color: "#00FF00" * }, { * x: 1, * y: 4, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @sample {highcharts} highcharts/chart/reflow-true/ * Numerical values * @sample {highcharts} highcharts/series/data-array-of-arrays/ * Arrays of numeric x and y * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ * Arrays of datetime x and y * @sample {highcharts} highcharts/series/data-array-of-name-value/ * Arrays of point.name and y * @sample {highcharts} highcharts/series/data-array-of-objects/ * Config objects * * @type {Array<number|Array<(number|string),(number|null)>|null|*>} * @extends series.line.data * @product highcharts highstock * @apioption series.areaspline.data */ /** * @see [color](#series.areaspline.color) * @see [fillOpacity](#series.areaspline.fillOpacity) * * @apioption series.areaspline.fillColor */ /** * @see [color](#series.areaspline.color) * @see [fillColor](#series.areaspline.fillColor) * * @default {highcharts} 0.75 * @default {highstock} 0.75 * @apioption series.areaspline.fillOpacity */ ''; // adds doclets above into transpilat return AreaSplineSeries; }); _registerModule(_modules, 'Series/Column/ColumnSeries.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Mixins/LegendSymbol.js'], _modules['Core/Color/Palette.js'], _modules['Core/Series/Series.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (A, Color, H, LegendSymbolMixin, palette, Series, SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var animObject = A.animObject; var color = Color.parse; var hasTouch = H.hasTouch, noop = H.noop; var clamp = U.clamp, css = U.css, defined = U.defined, extend = U.extend, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, merge = U.merge, pick = U.pick, objectEach = U.objectEach; /** * The column series type. * * @private * @class * @name Highcharts.seriesTypes.column * * @augments Highcharts.Series */ var ColumnSeries = /** @class */ (function (_super) { __extends(ColumnSeries, _super); function ColumnSeries() { /* * * * Static Properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; /* * * * Properties * * */ _this.borderWidth = void 0; _this.data = void 0; _this.group = void 0; _this.options = void 0; _this.points = void 0; return _this; /* eslint-enable valid-jsdoc */ } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Animate the column heights one by one from zero. * * @private * @function Highcharts.seriesTypes.column#animate * * @param {boolean} init * Whether to initialize the animation or run it */ ColumnSeries.prototype.animate = function (init) { var series = this, yAxis = this.yAxis, options = series.options, inverted = this.chart.inverted, attr = {}, translateProp = inverted ? 'translateX' : 'translateY', translateStart, translatedThreshold; if (init) { attr.scaleY = 0.001; translatedThreshold = clamp(yAxis.toPixels(options.threshold), yAxis.pos, yAxis.pos + yAxis.len); if (inverted) { attr.translateX = translatedThreshold - yAxis.len; } else { attr.translateY = translatedThreshold; } // apply finnal clipping (used in Highcharts Stock) (#7083) // animation is done by scaleY, so cliping is for panes if (series.clipBox) { series.setClip(); } series.group.attr(attr); } else { // run the animation translateStart = Number(series.group.attr(translateProp)); series.group.animate({ scaleY: 1 }, extend(animObject(series.options.animation), { // Do the scale synchronously to ensure smooth // updating (#5030, #7228) step: function (val, fx) { if (series.group) { attr[translateProp] = translateStart + fx.pos * (yAxis.pos - translateStart); series.group.attr(attr); } } })); } }; /** * Initialize the series. Extends the basic Series.init method by * marking other series of the same type as dirty. * * @private * @function Highcharts.seriesTypes.column#init */ ColumnSeries.prototype.init = function (chart, options) { _super.prototype.init.apply(this, arguments); var series = this; chart = series.chart; // if the series is added dynamically, force redraw of other // series affected by a new column if (chart.hasRendered) { chart.series.forEach(function (otherSeries) { if (otherSeries.type === series.type) { otherSeries.isDirty = true; } }); } }; /** * Return the width and x offset of the columns adjusted for grouping, * groupPadding, pointPadding, pointWidth etc. * * @private * @function Highcharts.seriesTypes.column#getColumnMetrics * @return {Highcharts.ColumnMetricsObject} */ ColumnSeries.prototype.getColumnMetrics = function () { var series = this, options = series.options, xAxis = series.xAxis, yAxis = series.yAxis, reversedStacks = xAxis.options.reversedStacks, // Keep backward compatibility: reversed xAxis had reversed // stacks reverseStacks = (xAxis.reversed && !reversedStacks) || (!xAxis.reversed && reversedStacks), stackKey, stackGroups = {}, columnCount = 0; // Get the total number of column type series. This is called on // every series. Consider moving this logic to a chart.orderStacks() // function and call it on init, addSeries and removeSeries if (options.grouping === false) { columnCount = 1; } else { series.chart.series.forEach(function (otherSeries) { var otherYAxis = otherSeries.yAxis, otherOptions = otherSeries.options, columnIndex; if (otherSeries.type === series.type && (otherSeries.visible || !series.chart.options.chart.ignoreHiddenSeries) && yAxis.len === otherYAxis.len && yAxis.pos === otherYAxis.pos) { // #642, #2086 if (otherOptions.stacking && otherOptions.stacking !== 'group') { stackKey = otherSeries.stackKey; if (typeof stackGroups[stackKey] === 'undefined') { stackGroups[stackKey] = columnCount++; } columnIndex = stackGroups[stackKey]; } else if (otherOptions.grouping !== false) { // #1162 columnIndex = columnCount++; } otherSeries.columnIndex = columnIndex; } }); } var categoryWidth = Math.min(Math.abs(xAxis.transA) * ((xAxis.ordinal && xAxis.ordinal.slope) || options.pointRange || xAxis.closestPointRange || xAxis.tickInterval || 1), // #2610 xAxis.len // #1535 ), groupPadding = categoryWidth * options.groupPadding, groupWidth = categoryWidth - 2 * groupPadding, pointOffsetWidth = groupWidth / (columnCount || 1), pointWidth = Math.min(options.maxPointWidth || xAxis.len, pick(options.pointWidth, pointOffsetWidth * (1 - 2 * options.pointPadding))), pointPadding = (pointOffsetWidth - pointWidth) / 2, // #1251, #3737 colIndex = (series.columnIndex || 0) + (reverseStacks ? 1 : 0), pointXOffset = pointPadding + (groupPadding + colIndex * pointOffsetWidth - (categoryWidth / 2)) * (reverseStacks ? -1 : 1); // Save it for reading in linked series (Error bars particularly) series.columnMetrics = { width: pointWidth, offset: pointXOffset, paddedWidth: pointOffsetWidth, columnCount: columnCount }; return series.columnMetrics; }; /** * Make the columns crisp. The edges are rounded to the nearest full * pixel. * * @private * @function Highcharts.seriesTypes.column#crispCol */ ColumnSeries.prototype.crispCol = function (x, y, w, h) { var chart = this.chart, borderWidth = this.borderWidth, xCrisp = -(borderWidth % 2 ? 0.5 : 0), yCrisp = borderWidth % 2 ? 0.5 : 1, right, bottom, fromTop; if (chart.inverted && chart.renderer.isVML) { yCrisp += 1; } // Horizontal. We need to first compute the exact right edge, then // round it and compute the width from there. if (this.options.crisp) { right = Math.round(x + w) + xCrisp; x = Math.round(x) + xCrisp; w = right - x; } // Vertical bottom = Math.round(y + h) + yCrisp; fromTop = Math.abs(y) <= 0.5 && bottom > 0.5; // #4504, #4656 y = Math.round(y) + yCrisp; h = bottom - y; // Top edges are exceptions if (fromTop && h) { // #5146 y -= 1; h += 1; } return { x: x, y: y, width: w, height: h }; }; /** * Adjust for missing columns, according to the `centerInCategory` * option. Missing columns are either single points or stacks where the * point or points are either missing or null. * * @private * @function Highcharts.seriesTypes.column#adjustForMissingColumns * @param {number} x * The x coordinate of the column, left side * * @param {number} pointWidth * The pointWidth, already computed upstream * * @param {Highcharts.ColumnPoint} point * The point instance * * @param {Highcharts.ColumnMetricsObject} metrics * The series-wide column metrics * * @return {number} * The adjusted x position, or the original if not adjusted */ ColumnSeries.prototype.adjustForMissingColumns = function (x, pointWidth, point, metrics) { var _this = this; var stacking = this.options.stacking; if (!point.isNull && metrics.columnCount > 1) { var indexInCategory_1 = 0; var totalInCategory_1 = 0; // Loop over all the stacks on the Y axis. When stacking is // enabled, these are real point stacks. When stacking is not // enabled, but `centerInCategory` is true, there is one stack // handling the grouping of points in each category. This is // done in the `setGroupedPoints` function. objectEach(this.yAxis.stacking && this.yAxis.stacking.stacks, function (stack) { if (typeof point.x === 'number') { var stackItem = stack[point.x.toString()]; if (stackItem) { var pointValues = stackItem.points[_this.index], total = stackItem.total; // If true `stacking` is enabled, count the // total number of non-null stacks in the // category, and note which index this point is // within those stacks. if (stacking) { if (pointValues) { indexInCategory_1 = totalInCategory_1; } if (stackItem.hasValidPoints) { totalInCategory_1++; } // If `stacking` is not enabled, look for the // index and total of the `group` stack. } else if (isArray(pointValues)) { indexInCategory_1 = pointValues[1]; totalInCategory_1 = total || 0; } } } }); // Compute the adjusted x position var boxWidth = (totalInCategory_1 - 1) * metrics.paddedWidth + pointWidth; x = (point.plotX || 0) + boxWidth / 2 - pointWidth - indexInCategory_1 * metrics.paddedWidth; } return x; }; /** * Translate each point to the plot area coordinate system and find * shape positions * * @private * @function Highcharts.seriesTypes.column#translate */ ColumnSeries.prototype.translate = function () { var series = this, chart = series.chart, options = series.options, dense = series.dense = series.closestPointRange * series.xAxis.transA < 2, borderWidth = series.borderWidth = pick(options.borderWidth, dense ? 0 : 1 // #3635 ), xAxis = series.xAxis, yAxis = series.yAxis, threshold = options.threshold, translatedThreshold = series.translatedThreshold = yAxis.getThreshold(threshold), minPointLength = pick(options.minPointLength, 5), metrics = series.getColumnMetrics(), seriesPointWidth = metrics.width, // postprocessed for border width seriesBarW = series.barW = Math.max(seriesPointWidth, 1 + 2 * borderWidth), seriesXOffset = series.pointXOffset = metrics.offset, dataMin = series.dataMin, dataMax = series.dataMax; if (chart.inverted) { translatedThreshold -= 0.5; // #3355 } // When the pointPadding is 0, we want the columns to be packed // tightly, so we allow individual columns to have individual sizes. // When pointPadding is greater, we strive for equal-width columns // (#2694). if (options.pointPadding) { seriesBarW = Math.ceil(seriesBarW); } Series.prototype.translate.apply(series); // Record the new values series.points.forEach(function (point) { var yBottom = pick(point.yBottom, translatedThreshold), safeDistance = 999 + Math.abs(yBottom), pointWidth = seriesPointWidth, plotX = point.plotX || 0, // Don't draw too far outside plot area (#1303, #2241, // #4264) plotY = clamp(point.plotY, -safeDistance, yAxis.len + safeDistance), barX = plotX + seriesXOffset, barW = seriesBarW, barY = Math.min(plotY, yBottom), up, barH = Math.max(plotY, yBottom) - barY; // Handle options.minPointLength if (minPointLength && Math.abs(barH) < minPointLength) { barH = minPointLength; up = (!yAxis.reversed && !point.negative) || (yAxis.reversed && point.negative); // Reverse zeros if there's no positive value in the series // in visible range (#7046) if (isNumber(threshold) && isNumber(dataMax) && point.y === threshold && dataMax <= threshold && // and if there's room for it (#7311) (yAxis.min || 0) < threshold && // if all points are the same value (i.e zero) not draw // as negative points (#10646), but only if there's room // for it (#14876) (dataMin !== dataMax || (yAxis.max || 0) <= threshold)) { up = !up; } // If stacked... barY = (Math.abs(barY - translatedThreshold) > minPointLength ? // ...keep position yBottom - minPointLength : // #1485, #4051 translatedThreshold - (up ? minPointLength : 0)); } // Handle point.options.pointWidth // @todo Handle grouping/stacking too. Calculate offset properly if (defined(point.options.pointWidth)) { pointWidth = barW = Math.ceil(point.options.pointWidth); barX -= Math.round((pointWidth - seriesPointWidth) / 2); } // Adjust for null or missing points if (options.centerInCategory) { barX = series.adjustForMissingColumns(barX, pointWidth, point, metrics); } // Cache for access in polar point.barX = barX; point.pointWidth = pointWidth; // Fix the tooltip on center of grouped columns (#1216, #424, // #3648) point.tooltipPos = chart.inverted ? [ clamp(yAxis.len + yAxis.pos - chart.plotLeft - plotY, yAxis.pos - chart.plotLeft, yAxis.len + yAxis.pos - chart.plotLeft), xAxis.len + xAxis.pos - chart.plotTop - barX - barW / 2, barH ] : [ xAxis.left - chart.plotLeft + barX + barW / 2, clamp(plotY + yAxis.pos - chart.plotTop, yAxis.pos - chart.plotTop, yAxis.len + yAxis.pos - chart.plotTop), barH ]; // Register shape type and arguments to be used in drawPoints // Allow shapeType defined on pointClass level point.shapeType = series.pointClass.prototype.shapeType || 'rect'; point.shapeArgs = series.crispCol.apply(series, point.isNull ? // #3169, drilldown from null must have a position to work // from #6585, dataLabel should be placed on xAxis, not // floating in the middle of the chart [barX, translatedThreshold, barW, 0] : [barX, barY, barW, barH]); }); }; /** * Columns have no graph * * @private * @function Highcharts.seriesTypes.column#drawGraph */ ColumnSeries.prototype.drawGraph = function () { this.group[this.dense ? 'addClass' : 'removeClass']('highcharts-dense-data'); }; /** * Get presentational attributes * * @private * @function Highcharts.seriesTypes.column#pointAttribs */ ColumnSeries.prototype.pointAttribs = function (point, state) { var options = this.options, stateOptions, ret, p2o = this.pointAttrToOptions || {}, strokeOption = p2o.stroke || 'borderColor', strokeWidthOption = p2o['stroke-width'] || 'borderWidth', fill = (point && point.color) || this.color, // set to fill when borderColor null: stroke = ((point && point[strokeOption]) || options[strokeOption] || fill), strokeWidth = (point && point[strokeWidthOption]) || options[strokeWidthOption] || this[strokeWidthOption] || 0, dashstyle = (point && point.options.dashStyle) || options.dashStyle, opacity = pick(point && point.opacity, options.opacity, 1), zone, brightness; // Handle zone colors if (point && this.zones.length) { zone = point.getZone(); // When zones are present, don't use point.color (#4267). // Changed order (#6527), added support for colorAxis (#10670) fill = (point.options.color || (zone && (zone.color || point.nonZonedColor)) || this.color); if (zone) { stroke = zone.borderColor || stroke; dashstyle = zone.dashStyle || dashstyle; strokeWidth = zone.borderWidth || strokeWidth; } } // Select or hover states if (state && point) { stateOptions = merge(options.states[state], // #6401 point.options.states && point.options.states[state] || {}); brightness = stateOptions.brightness; fill = stateOptions.color || (typeof brightness !== 'undefined' && color(fill) .brighten(stateOptions.brightness) .get()) || fill; stroke = stateOptions[strokeOption] || stroke; strokeWidth = stateOptions[strokeWidthOption] || strokeWidth; dashstyle = stateOptions.dashStyle || dashstyle; opacity = pick(stateOptions.opacity, opacity); } ret = { fill: fill, stroke: stroke, 'stroke-width': strokeWidth, opacity: opacity }; if (dashstyle) { ret.dashstyle = dashstyle; } return ret; }; /** * Draw the columns. For bars, the series.group is rotated, so the same * coordinates apply for columns and bars. This method is inherited by * scatter series. * * @private * @function Highcharts.seriesTypes.column#drawPoints */ ColumnSeries.prototype.drawPoints = function () { var series = this, chart = this.chart, options = series.options, renderer = chart.renderer, animationLimit = options.animationLimit || 250, shapeArgs; // draw the columns series.points.forEach(function (point) { var plotY = point.plotY, graphic = point.graphic, hasGraphic = !!graphic, verb = graphic && chart.pointCount < animationLimit ? 'animate' : 'attr'; if (isNumber(plotY) && point.y !== null) { shapeArgs = point.shapeArgs; // When updating a series between 2d and 3d or cartesian and // polar, the shape type changes. if (graphic && point.hasNewShapeType()) { graphic = graphic.destroy(); } // Set starting position for point sliding animation. if (series.enabledDataSorting) { point.startXPos = series.xAxis.reversed ? -(shapeArgs ? (shapeArgs.width || 0) : 0) : series.xAxis.width; } if (!graphic) { point.graphic = graphic = renderer[point.shapeType](shapeArgs) .add(point.group || series.group); if (graphic && series.enabledDataSorting && chart.hasRendered && chart.pointCount < animationLimit) { graphic.attr({ x: point.startXPos }); hasGraphic = true; verb = 'animate'; } } if (graphic && hasGraphic) { // update graphic[verb](merge(shapeArgs)); } // Border radius is not stylable (#6900) if (options.borderRadius) { graphic[verb]({ r: options.borderRadius }); } // Presentational if (!chart.styledMode) { graphic[verb](series.pointAttribs(point, (point.selected && 'select'))) .shadow(point.allowShadow !== false && options.shadow, null, options.stacking && !options.borderRadius); } if (graphic) { graphic.addClass(point.getClassName(), true); graphic.attr({ visibility: point.visible ? 'inherit' : 'hidden' }); } } else if (graphic) { point.graphic = graphic.destroy(); // #1269 } }); }; /** * Draw the tracker for a point. * @private */ ColumnSeries.prototype.drawTracker = function () { var series = this, chart = series.chart, pointer = chart.pointer, onMouseOver = function (e) { var point = pointer.getPointFromEvent(e); // undefined on graph in scatterchart if (typeof point !== 'undefined') { pointer.isDirectTouch = true; point.onMouseOver(e); } }, dataLabels; // Add reference to the point series.points.forEach(function (point) { dataLabels = (isArray(point.dataLabels) ? point.dataLabels : (point.dataLabel ? [point.dataLabel] : [])); if (point.graphic) { point.graphic.element.point = point; } dataLabels.forEach(function (dataLabel) { if (dataLabel.div) { dataLabel.div.point = point; } else { dataLabel.element.point = point; } }); }); // Add the event listeners, we need to do this only once if (!series._hasTracking) { series.trackerGroups.forEach(function (key) { if (series[key]) { // we don't always have dataLabelsGroup series[key] .addClass('highcharts-tracker') .on('mouseover', onMouseOver) .on('mouseout', function (e) { pointer.onTrackerMouseOut(e); }); if (hasTouch) { series[key].on('touchstart', onMouseOver); } if (!chart.styledMode && series.options.cursor) { series[key] .css(css) .css({ cursor: series.options.cursor }); } } }); series._hasTracking = true; } fireEvent(this, 'afterDrawTracker'); }; /** * Remove this series from the chart * * @private * @function Highcharts.seriesTypes.column#remove */ ColumnSeries.prototype.remove = function () { var series = this, chart = series.chart; // column and bar series affects other series of the same type // as they are either stacked or grouped if (chart.hasRendered) { chart.series.forEach(function (otherSeries) { if (otherSeries.type === series.type) { otherSeries.isDirty = true; } }); } Series.prototype.remove.apply(series, arguments); }; /** * Column series display one column per value along an X axis. * * @sample {highcharts} highcharts/demo/column-basic/ * Column chart * @sample {highstock} stock/demo/column/ * Column chart * * @extends plotOptions.line * @excluding connectEnds, connectNulls, gapSize, gapUnit, linecap, * lineWidth, marker, step, useOhlcData * @product highcharts highstock * @optionparent plotOptions.column */ ColumnSeries.defaultOptions = merge(Series.defaultOptions, { /** * The corner radius of the border surrounding each column or bar. * * @sample {highcharts} highcharts/plotoptions/column-borderradius/ * Rounded columns * * @product highcharts highstock gantt * * @private */ borderRadius: 0, /** * When using automatic point colors pulled from the global * [colors](colors) or series-specific * [plotOptions.column.colors](series.colors) collections, this option * determines whether the chart should receive one color per series or * one color per point. * * In styled mode, the `colors` or `series.colors` arrays are not * supported, and instead this option gives the points individual color * class names on the form `highcharts-color-{n}`. * * @see [series colors](#plotOptions.column.colors) * * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-false/ * False by default * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-true/ * True * * @type {boolean} * @default false * @since 2.0 * @product highcharts highstock gantt * @apioption plotOptions.column.colorByPoint */ /** * A series specific or series type specific color set to apply instead * of the global [colors](#colors) when [colorByPoint]( * #plotOptions.column.colorByPoint) is true. * * @type {Array<Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject>} * @since 3.0 * @product highcharts highstock gantt * @apioption plotOptions.column.colors */ /** * When `true`, the columns will center in the category, ignoring null * or missing points. When `false`, space will be reserved for null or * missing points. * * @sample {highcharts} highcharts/series-column/centerincategory/ * Center in category * * @since 8.0.1 * @product highcharts highstock gantt * * @private */ centerInCategory: false, /** * Padding between each value groups, in x axis units. * * @sample {highcharts} highcharts/plotoptions/column-grouppadding-default/ * 0.2 by default * @sample {highcharts} highcharts/plotoptions/column-grouppadding-none/ * No group padding - all columns are evenly spaced * * @product highcharts highstock gantt * * @private */ groupPadding: 0.2, /** * Whether to group non-stacked columns or to let them render * independent of each other. Non-grouped columns will be laid out * individually and overlap each other. * * @sample {highcharts} highcharts/plotoptions/column-grouping-false/ * Grouping disabled * @sample {highstock} highcharts/plotoptions/column-grouping-false/ * Grouping disabled * * @type {boolean} * @default true * @since 2.3.0 * @product highcharts highstock gantt * @apioption plotOptions.column.grouping */ /** * @ignore-option * @private */ marker: null, /** * The maximum allowed pixel width for a column, translated to the * height of a bar in a bar chart. This prevents the columns from * becoming too wide when there is a small number of points in the * chart. * * @see [pointWidth](#plotOptions.column.pointWidth) * * @sample {highcharts} highcharts/plotoptions/column-maxpointwidth-20/ * Limited to 50 * @sample {highstock} highcharts/plotoptions/column-maxpointwidth-20/ * Limited to 50 * * @type {number} * @since 4.1.8 * @product highcharts highstock gantt * @apioption plotOptions.column.maxPointWidth */ /** * Padding between each column or bar, in x axis units. * * @sample {highcharts} highcharts/plotoptions/column-pointpadding-default/ * 0.1 by default * @sample {highcharts} highcharts/plotoptions/column-pointpadding-025/ * 0.25 * @sample {highcharts} highcharts/plotoptions/column-pointpadding-none/ * 0 for tightly packed columns * * @product highcharts highstock gantt * * @private */ pointPadding: 0.1, /** * A pixel value specifying a fixed width for each column or bar point. * When set to `undefined`, the width is calculated from the * `pointPadding` and `groupPadding`. The width effects the dimension * that is not based on the point value. For column series it is the * hoizontal length and for bar series it is the vertical length. * * @see [maxPointWidth](#plotOptions.column.maxPointWidth) * * @sample {highcharts} highcharts/plotoptions/column-pointwidth-20/ * 20px wide columns regardless of chart width or the amount of * data points * * @type {number} * @since 1.2.5 * @product highcharts highstock gantt * @apioption plotOptions.column.pointWidth */ /** * A pixel value specifying a fixed width for the column or bar. * Overrides pointWidth on the series. * * @see [series.pointWidth](#plotOptions.column.pointWidth) * * @type {number} * @default undefined * @since 7.0.0 * @product highcharts highstock gantt * @apioption series.column.data.pointWidth */ /** * The minimal height for a column or width for a bar. By default, * 0 values are not shown. To visualize a 0 (or close to zero) point, * set the minimal point length to a pixel value like 3\. In stacked * column charts, minPointLength might not be respected for tightly * packed values. * * @sample {highcharts} highcharts/plotoptions/column-minpointlength/ * Zero base value * @sample {highcharts} highcharts/plotoptions/column-minpointlength-pos-and-neg/ * Positive and negative close to zero values * * @product highcharts highstock gantt * * @private */ minPointLength: 0, /** * When the series contains less points than the crop threshold, all * points are drawn, event if the points fall outside the visible plot * area at the current zoom. The advantage of drawing all points * (including markers and columns), is that animation is performed on * updates. On the other hand, when the series contains more points than * the crop threshold, the series data is cropped to only contain points * that fall within the plot area. The advantage of cropping away * invisible points is to increase performance on large series. * * @product highcharts highstock gantt * * @private */ cropThreshold: 50, /** * The X axis range that each point is valid for. This determines the * width of the column. On a categorized axis, the range will be 1 * by default (one category unit). On linear and datetime axes, the * range will be computed as the distance between the two closest data * points. * * The default `null` means it is computed automatically, but this * option can be used to override the automatic value. * * This option is set by default to 1 if data sorting is enabled. * * @sample {highcharts} highcharts/plotoptions/column-pointrange/ * Set the point range to one day on a data set with one week * between the points * * @type {number|null} * @since 2.3 * @product highcharts highstock gantt * * @private */ pointRange: null, states: { /** * Options for the hovered point. These settings override the normal * state options when a point is moused over or touched. * * @extends plotOptions.series.states.hover * @excluding halo, lineWidth, lineWidthPlus, marker * @product highcharts highstock gantt */ hover: { /** @ignore-option */ halo: false, /** * A specific border color for the hovered point. Defaults to * inherit the normal state border color. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highcharts gantt * @apioption plotOptions.column.states.hover.borderColor */ /** * A specific color for the hovered point. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highcharts gantt * @apioption plotOptions.column.states.hover.color */ /** * How much to brighten the point on interaction. Requires the * main color to be defined in hex or rgb(a) format. * * In styled mode, the hover brightening is by default replaced * with a fill-opacity set in the `.highcharts-point:hover` * rule. * * @sample {highcharts} highcharts/plotoptions/column-states-hover-brightness/ * Brighten by 0.5 * * @product highcharts highstock gantt */ brightness: 0.1 }, /** * Options for the selected point. These settings override the * normal state options when a point is selected. * * @extends plotOptions.series.states.select * @excluding halo, lineWidth, lineWidthPlus, marker * @product highcharts highstock gantt */ select: { /** * A specific color for the selected point. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default #cccccc * @product highcharts highstock gantt */ color: palette.neutralColor20, /** * A specific border color for the selected point. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default #000000 * @product highcharts highstock gantt */ borderColor: palette.neutralColor100 } }, dataLabels: { align: void 0, verticalAlign: void 0, /** * The y position offset of the label relative to the point in * pixels. * * @type {number} */ y: void 0 }, // false doesn't work well: https://jsfiddle.net/highcharts/hz8fopan/14/ /** * @ignore-option * @private */ startFromThreshold: true, stickyTracking: false, tooltip: { distance: 6 }, /** * The Y axis value to serve as the base for the columns, for * distinguishing between values above and below a threshold. If `null`, * the columns extend from the padding Y axis minimum. * * @type {number|null} * @since 2.0 * @product highcharts * * @private */ threshold: 0, /** * The width of the border surrounding each column or bar. Defaults to * `1` when there is room for a border, but to `0` when the columns are * so dense that a border would cover the next column. * * In styled mode, the stroke width can be set with the * `.highcharts-point` rule. * * @sample {highcharts} highcharts/plotoptions/column-borderwidth/ * 2px black border * * @type {number} * @default undefined * @product highcharts highstock gantt * @apioption plotOptions.column.borderWidth */ /** * The color of the border surrounding each column or bar. * * In styled mode, the border stroke can be set with the * `.highcharts-point` rule. * * @sample {highcharts} highcharts/plotoptions/column-bordercolor/ * Dark gray border * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default #ffffff * @product highcharts highstock gantt * * @private */ borderColor: palette.backgroundColor }); return ColumnSeries; }(Series)); extend(ColumnSeries.prototype, { cropShoulder: 0, // When tooltip is not shared, this series (and derivatives) requires // direct touch/hover. KD-tree does not apply. directTouch: true, /** * Use a solid rectangle like the area series types * * @private * @function Highcharts.seriesTypes.column#drawLegendSymbol * * @param {Highcharts.Legend} legend * The legend object * * @param {Highcharts.Series|Highcharts.Point} item * The series (this) or point */ drawLegendSymbol: LegendSymbolMixin.drawRectangle, getSymbol: noop, // use separate negative stacks, unlike area stacks where a negative // point is substracted from previous (#1910) negStacks: true, trackerGroups: ['group', 'dataLabelsGroup'] }); SeriesRegistry.registerSeriesType('column', ColumnSeries); /* * * * Export * * */ /* * * * API Declarations * * */ /** * Adjusted width and x offset of the columns for grouping. * * @private * @interface Highcharts.ColumnMetricsObject */ /** * Width of the columns. * @name Highcharts.ColumnMetricsObject#width * @type {number} */ /** * Offset of the columns. * @name Highcharts.ColumnMetricsObject#offset * @type {number} */ ''; // detach doclets above /* * * * API Options * * */ /** * A `column` series. If the [type](#series.column.type) option is * not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.column * @excluding connectNulls, dataParser, dataURL, gapSize, gapUnit, linecap, * lineWidth, marker, connectEnds, step * @product highcharts highstock * @apioption series.column */ /** * An array of data points for the series. For the `column` series type, * points can be given in the following ways: * * 1. An array of numerical values. In this case, the numerical values will be * interpreted as `y` options. The `x` values will be automatically * calculated, either starting at 0 and incremented by 1, or from * `pointStart` and `pointInterval` given in the series options. If the axis * has categories, these will be used. Example: * ```js * data: [0, 5, 3, 5] * ``` * * 2. An array of arrays with 2 values. In this case, the values correspond to * `x,y`. If the first value is a string, it is applied as the name of the * point, and the `x` value is inferred. * ```js * data: [ * [0, 6], * [1, 2], * [2, 6] * ] * ``` * * 3. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.column.turboThreshold), this option is not * available. * ```js * data: [{ * x: 1, * y: 9, * name: "Point2", * color: "#00FF00" * }, { * x: 1, * y: 6, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @sample {highcharts} highcharts/chart/reflow-true/ * Numerical values * @sample {highcharts} highcharts/series/data-array-of-arrays/ * Arrays of numeric x and y * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ * Arrays of datetime x and y * @sample {highcharts} highcharts/series/data-array-of-name-value/ * Arrays of point.name and y * @sample {highcharts} highcharts/series/data-array-of-objects/ * Config objects * * @type {Array<number|Array<(number|string),(number|null)>|null|*>} * @extends series.line.data * @excluding marker * @product highcharts highstock * @apioption series.column.data */ /** * The color of the border surrounding the column or bar. * * In styled mode, the border stroke can be set with the `.highcharts-point` * rule. * * @sample {highcharts} highcharts/plotoptions/column-bordercolor/ * Dark gray border * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highcharts highstock * @apioption series.column.data.borderColor */ /** * The width of the border surrounding the column or bar. * * In styled mode, the stroke width can be set with the `.highcharts-point` * rule. * * @sample {highcharts} highcharts/plotoptions/column-borderwidth/ * 2px black border * * @type {number} * @product highcharts highstock * @apioption series.column.data.borderWidth */ /** * A name for the dash style to use for the column or bar. Overrides * dashStyle on the series. * * In styled mode, the stroke dash-array can be set with the same classes as * listed under [data.color](#series.column.data.color). * * @see [series.pointWidth](#plotOptions.column.dashStyle) * * @type {Highcharts.DashStyleValue} * @apioption series.column.data.dashStyle */ /** * A pixel value specifying a fixed width for the column or bar. Overrides * pointWidth on the series. The width effects the dimension that is not based * on the point value. * * @see [series.pointWidth](#plotOptions.column.pointWidth) * * @type {number} * @apioption series.column.data.pointWidth */ /** * @excluding halo, lineWidth, lineWidthPlus, marker * @product highcharts highstock * @apioption series.column.states.hover */ /** * @excluding halo, lineWidth, lineWidthPlus, marker * @product highcharts highstock * @apioption series.column.states.select */ ''; // includes above doclets in transpilat return ColumnSeries; }); _registerModule(_modules, 'Series/Bar/BarSeries.js', [_modules['Series/Column/ColumnSeries.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (ColumnSeries, SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var extend = U.extend, merge = U.merge; /* * * * Class * * */ /** * Bar series type. * * @private * @class * @name Highcharts.seriesTypes.bar * * @augments Highcharts.Series */ var BarSeries = /** @class */ (function (_super) { __extends(BarSeries, _super); function BarSeries() { /* * * * Static Properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; /* * * * Properties * * */ _this.data = void 0; _this.options = void 0; _this.points = void 0; return _this; } /** * A bar series is a special type of column series where the columns are * horizontal. * * @sample highcharts/demo/bar-basic/ * Bar chart * * @extends plotOptions.column * @product highcharts * @optionparent plotOptions.bar */ BarSeries.defaultOptions = merge(ColumnSeries.defaultOptions, { // nothing here yet }); return BarSeries; }(ColumnSeries)); extend(BarSeries.prototype, { inverted: true }); SeriesRegistry.registerSeriesType('bar', BarSeries); /* * * * Default Export * * */ /* * * * API Options * * */ /** * A `bar` series. If the [type](#series.bar.type) option is not specified, * it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.bar * @excluding connectNulls, dashStyle, dataParser, dataURL, gapSize, gapUnit, * linecap, lineWidth, marker, connectEnds, step * @product highcharts * @apioption series.bar */ /** * An array of data points for the series. For the `bar` series type, * points can be given in the following ways: * * 1. An array of numerical values. In this case, the numerical values will be * interpreted as `y` options. The `x` values will be automatically * calculated, either starting at 0 and incremented by 1, or from * `pointStart` and `pointInterval` given in the series options. If the axis * has categories, these will be used. Example: * ```js * data: [0, 5, 3, 5] * ``` * * 2. An array of arrays with 2 values. In this case, the values correspond to * `x,y`. If the first value is a string, it is applied as the name of the * point, and the `x` value is inferred. * ```js * data: [ * [0, 5], * [1, 10], * [2, 3] * ] * ``` * * 3. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.bar.turboThreshold), this option is not * available. * ```js * data: [{ * x: 1, * y: 1, * name: "Point2", * color: "#00FF00" * }, { * x: 1, * y: 10, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @sample {highcharts} highcharts/chart/reflow-true/ * Numerical values * @sample {highcharts} highcharts/series/data-array-of-arrays/ * Arrays of numeric x and y * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ * Arrays of datetime x and y * @sample {highcharts} highcharts/series/data-array-of-name-value/ * Arrays of point.name and y * @sample {highcharts} highcharts/series/data-array-of-objects/ * Config objects * * @type {Array<number|Array<(number|string),(number|null)>|null|*>} * @extends series.column.data * @product highcharts * @apioption series.bar.data */ /** * @excluding halo,lineWidth,lineWidthPlus,marker * @product highcharts highstock * @apioption series.bar.states.hover */ /** * @excluding halo,lineWidth,lineWidthPlus,marker * @product highcharts highstock * @apioption series.bar.states.select */ ''; // gets doclets above into transpilat return BarSeries; }); _registerModule(_modules, 'Series/Scatter/ScatterSeries.js', [_modules['Series/Column/ColumnSeries.js'], _modules['Series/Line/LineSeries.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (ColumnSeries, LineSeries, SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var addEvent = U.addEvent, extend = U.extend, merge = U.merge; /* * * * Class * * */ /** * Scatter series type. * * @private */ var ScatterSeries = /** @class */ (function (_super) { __extends(ScatterSeries, _super); function ScatterSeries() { var _this = _super !== null && _super.apply(this, arguments) || this; /* * * * Properties * * */ _this.data = void 0; _this.options = void 0; _this.points = void 0; return _this; /* eslint-enable valid-jsdoc */ } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Optionally add the jitter effect. * @private */ ScatterSeries.prototype.applyJitter = function () { var series = this, jitter = this.options.jitter, len = this.points.length; /** * Return a repeatable, pseudo-random number based on an integer * seed. * @private */ function unrandom(seed) { var rand = Math.sin(seed) * 10000; return rand - Math.floor(rand); } if (jitter) { this.points.forEach(function (point, i) { ['x', 'y'].forEach(function (dim, j) { var axis, plotProp = 'plot' + dim.toUpperCase(), min, max, translatedJitter; if (jitter[dim] && !point.isNull) { axis = series[dim + 'Axis']; translatedJitter = jitter[dim] * axis.transA; if (axis && !axis.isLog) { // Identify the outer bounds of the jitter range min = Math.max(0, point[plotProp] - translatedJitter); max = Math.min(axis.len, point[plotProp] + translatedJitter); // Find a random position within this range point[plotProp] = min + (max - min) * unrandom(i + j * len); // Update clientX for the tooltip k-d-tree if (dim === 'x') { point.clientX = point.plotX; } } } }); }); } }; /** * @private * @function Highcharts.seriesTypes.scatter#drawGraph */ ScatterSeries.prototype.drawGraph = function () { if (this.options.lineWidth) { _super.prototype.drawGraph.call(this); } else if (this.graph) { this.graph = this.graph.destroy(); } }; /** * A scatter plot uses cartesian coordinates to display values for two * variables for a set of data. * * @sample {highcharts} highcharts/demo/scatter/ * Scatter plot * * @extends plotOptions.line * @excluding cropThreshold, pointPlacement, shadow, useOhlcData * @product highcharts highstock * @optionparent plotOptions.scatter */ ScatterSeries.defaultOptions = merge(LineSeries.defaultOptions, { /** * The width of the line connecting the data points. * * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-none/ * 0 by default * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-1/ * 1px * * @product highcharts highstock */ lineWidth: 0, findNearestPointBy: 'xy', /** * Apply a jitter effect for the rendered markers. When plotting * discrete values, a little random noise may help telling the points * apart. The jitter setting applies a random displacement of up to `n` * axis units in either direction. So for example on a horizontal X * axis, setting the `jitter.x` to 0.24 will render the point in a * random position between 0.24 units to the left and 0.24 units to the * right of the true axis position. On a category axis, setting it to * 0.5 will fill up the bin and make the data appear continuous. * * When rendered on top of a box plot or a column series, a jitter value * of 0.24 will correspond to the underlying series' default * [groupPadding]( * https://api.highcharts.com/highcharts/plotOptions.column.groupPadding) * and [pointPadding]( * https://api.highcharts.com/highcharts/plotOptions.column.pointPadding) * settings. * * @sample {highcharts} highcharts/series-scatter/jitter * Jitter on a scatter plot * * @sample {highcharts} highcharts/series-scatter/jitter-boxplot * Jittered scatter plot on top of a box plot * * @product highcharts highstock * @since 7.0.2 */ jitter: { /** * The maximal X offset for the random jitter effect. */ x: 0, /** * The maximal Y offset for the random jitter effect. */ y: 0 }, marker: { enabled: true // Overrides auto-enabling in line series (#3647) }, /** * Sticky tracking of mouse events. When true, the `mouseOut` event * on a series isn't triggered until the mouse moves over another * series, or out of the plot area. When false, the `mouseOut` event on * a series is triggered when the mouse leaves the area around the * series' graph or markers. This also implies the tooltip. When * `stickyTracking` is false and `tooltip.shared` is false, the tooltip * will be hidden when moving the mouse between series. * * @type {boolean} * @default false * @product highcharts highstock * @apioption plotOptions.scatter.stickyTracking */ /** * A configuration object for the tooltip rendering of each single * series. Properties are inherited from [tooltip](#tooltip). * Overridable properties are `headerFormat`, `pointFormat`, * `yDecimals`, `xDateFormat`, `yPrefix` and `ySuffix`. Unlike other * series, in a scatter plot the series.name by default shows in the * headerFormat and point.x and point.y in the pointFormat. * * @product highcharts highstock */ tooltip: { headerFormat: '<span style="color:{point.color}">\u25CF</span> ' + '<span style="font-size: 10px"> {series.name}</span><br/>', pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>' } }); return ScatterSeries; }(LineSeries)); extend(ScatterSeries.prototype, { drawTracker: ColumnSeries.prototype.drawTracker, sorted: false, requireSorting: false, noSharedTooltip: true, trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup'], takeOrdinalPosition: false // #2342 }); /* * * * Events * * */ /* eslint-disable no-invalid-this */ addEvent(ScatterSeries, 'afterTranslate', function () { this.applyJitter(); }); SeriesRegistry.registerSeriesType('scatter', ScatterSeries); /* * * * Default Export * * */ /* * * * API Options * * */ /** * A `scatter` series. If the [type](#series.scatter.type) option is * not specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.scatter * @excluding cropThreshold, dataParser, dataURL, useOhlcData * @product highcharts highstock * @apioption series.scatter */ /** * An array of data points for the series. For the `scatter` series * type, points can be given in the following ways: * * 1. An array of numerical values. In this case, the numerical values will be * interpreted as `y` options. The `x` values will be automatically * calculated, either starting at 0 and incremented by 1, or from * `pointStart` and `pointInterval` given in the series options. If the axis * has categories, these will be used. Example: * ```js * data: [0, 5, 3, 5] * ``` * * 2. An array of arrays with 2 values. In this case, the values correspond to * `x,y`. If the first value is a string, it is applied as the name of the * point, and the `x` value is inferred. * ```js * data: [ * [0, 0], * [1, 8], * [2, 9] * ] * ``` * * 3. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.scatter.turboThreshold), this option is not * available. * ```js * data: [{ * x: 1, * y: 2, * name: "Point2", * color: "#00FF00" * }, { * x: 1, * y: 4, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @sample {highcharts} highcharts/chart/reflow-true/ * Numerical values * @sample {highcharts} highcharts/series/data-array-of-arrays/ * Arrays of numeric x and y * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ * Arrays of datetime x and y * @sample {highcharts} highcharts/series/data-array-of-name-value/ * Arrays of point.name and y * @sample {highcharts} highcharts/series/data-array-of-objects/ * Config objects * * @type {Array<number|Array<(number|string),(number|null)>|null|*>} * @extends series.line.data * @product highcharts highstock * @apioption series.scatter.data */ ''; // adds doclets above to transpilat return ScatterSeries; }); _registerModule(_modules, 'Mixins/CenteredSeries.js', [_modules['Core/Globals.js'], _modules['Core/Series/Series.js'], _modules['Core/Utilities.js']], function (H, Series, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /** * @private * @interface Highcharts.RadianAngles */ /** * @name Highcharts.RadianAngles#end * @type {number} */ /** * @name Highcharts.RadianAngles#start * @type {number} */ var isNumber = U.isNumber, pick = U.pick, relativeLength = U.relativeLength; var deg2rad = H.deg2rad; /* eslint-disable valid-jsdoc */ /** * @private * @mixin Highcharts.CenteredSeriesMixin */ var centeredSeriesMixin = H.CenteredSeriesMixin = { /** * Get the center of the pie based on the size and center options relative * to the plot area. Borrowed by the polar and gauge series types. * * @private * @function Highcharts.CenteredSeriesMixin.getCenter * * @return {Array<number>} */ getCenter: function () { var options = this.options, chart = this.chart, slicingRoom = 2 * (options.slicedOffset || 0), handleSlicingRoom, plotWidth = chart.plotWidth - 2 * slicingRoom, plotHeight = chart.plotHeight - 2 * slicingRoom, centerOption = options.center, smallestSize = Math.min(plotWidth, plotHeight), size = options.size, innerSize = options.innerSize || 0, positions, i, value; if (typeof size === 'string') { size = parseFloat(size); } if (typeof innerSize === 'string') { innerSize = parseFloat(innerSize); } positions = [ pick(centerOption[0], '50%'), pick(centerOption[1], '50%'), // Prevent from negative values pick(size && size < 0 ? void 0 : options.size, '100%'), pick(innerSize && innerSize < 0 ? void 0 : options.innerSize || 0, '0%') ]; // No need for inner size in angular (gauges) series but still required // for pie series if (chart.angular && !(this instanceof Series)) { positions[3] = 0; } for (i = 0; i < 4; ++i) { value = positions[i]; handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value)); // i == 0: centerX, relative to width // i == 1: centerY, relative to height // i == 2: size, relative to smallestSize // i == 3: innerSize, relative to size positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) + (handleSlicingRoom ? slicingRoom : 0); } // innerSize cannot be larger than size (#3632) if (positions[3] > positions[2]) { positions[3] = positions[2]; } return positions; }, /** * getStartAndEndRadians - Calculates start and end angles in radians. * Used in series types such as pie and sunburst. * * @private * @function Highcharts.CenteredSeriesMixin.getStartAndEndRadians * * @param {number} [start] * Start angle in degrees. * * @param {number} [end] * Start angle in degrees. * * @return {Highcharts.RadianAngles} * Returns an object containing start and end angles as radians. */ getStartAndEndRadians: function (start, end) { var startAngle = isNumber(start) ? start : 0, // must be a number endAngle = ((isNumber(end) && // must be a number end > startAngle && // must be larger than the start angle // difference must be less than 360 degrees (end - startAngle) < 360) ? end : startAngle + 360), correction = -90; return { start: deg2rad * (startAngle + correction), end: deg2rad * (endAngle + correction) }; } }; return centeredSeriesMixin; }); _registerModule(_modules, 'Series/Pie/PiePoint.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Series/Point.js'], _modules['Core/Utilities.js']], function (A, Point, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var setAnimation = A.setAnimation; var addEvent = U.addEvent, defined = U.defined, extend = U.extend, isNumber = U.isNumber, pick = U.pick, relativeLength = U.relativeLength; /* * * * Class * * */ var PiePoint = /** @class */ (function (_super) { __extends(PiePoint, _super); function PiePoint() { /* * * * Properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; _this.labelDistance = void 0; _this.options = void 0; _this.series = void 0; return _this; } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Extendable method for getting the path of the connector between the * data label and the pie slice. * @private */ PiePoint.prototype.getConnectorPath = function () { var labelPosition = this.labelPosition, options = this.series.options.dataLabels, connectorShape = options.connectorShape, predefinedShapes = this.connectorShapes; // find out whether to use the predefined shape if (predefinedShapes[connectorShape]) { connectorShape = predefinedShapes[connectorShape]; } return connectorShape.call(this, { // pass simplified label position object for user's convenience x: labelPosition.final.x, y: labelPosition.final.y, alignment: labelPosition.alignment }, labelPosition.connectorPosition, options); }; /** * @private */ PiePoint.prototype.getTranslate = function () { return this.sliced ? this.slicedTranslation : { translateX: 0, translateY: 0 }; }; /** * @private */ PiePoint.prototype.haloPath = function (size) { var shapeArgs = this.shapeArgs; return this.sliced || !this.visible ? [] : this.series.chart.renderer.symbols.arc(shapeArgs.x, shapeArgs.y, shapeArgs.r + size, shapeArgs.r + size, { // Substract 1px to ensure the background is not bleeding // through between the halo and the slice (#7495). innerR: shapeArgs.r - 1, start: shapeArgs.start, end: shapeArgs.end }); }; /** * Initialize the pie slice. * @private */ PiePoint.prototype.init = function () { Point.prototype.init.apply(this, arguments); var point = this, toggleSlice; point.name = pick(point.name, 'Slice'); // add event listener for select toggleSlice = function (e) { point.slice(e.type === 'select'); }; addEvent(point, 'select', toggleSlice); addEvent(point, 'unselect', toggleSlice); return point; }; /** * Negative points are not valid (#1530, #3623, #5322) * @private */ PiePoint.prototype.isValid = function () { return isNumber(this.y) && this.y >= 0; }; /** * Toggle the visibility of the pie slice. * @private * * @param {boolean} vis * Whether to show the slice or not. If undefined, the visibility is * toggled. */ PiePoint.prototype.setVisible = function (vis, redraw) { var point = this, series = point.series, chart = series.chart, ignoreHiddenPoint = series.options.ignoreHiddenPoint; redraw = pick(redraw, ignoreHiddenPoint); if (vis !== point.visible) { // If called without an argument, toggle visibility point.visible = point.options.visible = vis = typeof vis === 'undefined' ? !point.visible : vis; // update userOptions.data series.options.data[series.data.indexOf(point)] = point.options; // Show and hide associated elements. This is performed // regardless of redraw or not, because chart.redraw only // handles full series. ['graphic', 'dataLabel', 'connector', 'shadowGroup'].forEach(function (key) { if (point[key]) { point[key][vis ? 'show' : 'hide'](vis); } }); if (point.legendItem) { chart.legend.colorizeItem(point, vis); } // #4170, hide halo after hiding point if (!vis && point.state === 'hover') { point.setState(''); } // Handle ignore hidden slices if (ignoreHiddenPoint) { series.isDirty = true; } if (redraw) { chart.redraw(); } } }; /** * Set or toggle whether the slice is cut out from the pie. * @private * * @param {boolean} sliced * When undefined, the slice state is toggled. * * @param {boolean} redraw * Whether to redraw the chart. True by default. * * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} * Animation options. */ PiePoint.prototype.slice = function (sliced, redraw, animation) { var point = this, series = point.series, chart = series.chart; setAnimation(animation, chart); // redraw is true by default redraw = pick(redraw, true); /** * Pie series only. Whether to display a slice offset from the * center. * @name Highcharts.Point#sliced * @type {boolean|undefined} */ // if called without an argument, toggle point.sliced = point.options.sliced = sliced = defined(sliced) ? sliced : !point.sliced; // update userOptions.data series.options.data[series.data.indexOf(point)] = point.options; if (point.graphic) { point.graphic.animate(this.getTranslate()); } if (point.shadowGroup) { point.shadowGroup.animate(this.getTranslate()); } }; return PiePoint; }(Point)); extend(PiePoint.prototype, { connectorShapes: { // only one available before v7.0.0 fixedOffset: function (labelPosition, connectorPosition, options) { var breakAt = connectorPosition.breakAt, touchingSliceAt = connectorPosition.touchingSliceAt, lineSegment = options.softConnector ? [ 'C', // 1st control point (of the curve) labelPosition.x + // 5 gives the connector a little horizontal bend (labelPosition.alignment === 'left' ? -5 : 5), labelPosition.y, 2 * breakAt.x - touchingSliceAt.x, 2 * breakAt.y - touchingSliceAt.y, breakAt.x, breakAt.y // ] : [ 'L', breakAt.x, breakAt.y ]; // assemble the path return ([ ['M', labelPosition.x, labelPosition.y], lineSegment, ['L', touchingSliceAt.x, touchingSliceAt.y] ]); }, straight: function (labelPosition, connectorPosition) { var touchingSliceAt = connectorPosition.touchingSliceAt; // direct line to the slice return [ ['M', labelPosition.x, labelPosition.y], ['L', touchingSliceAt.x, touchingSliceAt.y] ]; }, crookedLine: function (labelPosition, connectorPosition, options) { var touchingSliceAt = connectorPosition.touchingSliceAt, series = this.series, pieCenterX = series.center[0], plotWidth = series.chart.plotWidth, plotLeft = series.chart.plotLeft, alignment = labelPosition.alignment, radius = this.shapeArgs.r, crookDistance = relativeLength(// % to fraction options.crookDistance, 1), crookX = alignment === 'left' ? pieCenterX + radius + (plotWidth + plotLeft - pieCenterX - radius) * (1 - crookDistance) : plotLeft + (pieCenterX - radius) * crookDistance, segmentWithCrook = [ 'L', crookX, labelPosition.y ], useCrook = true; // crookedLine formula doesn't make sense if the path overlaps // the label - use straight line instead in that case if (alignment === 'left' ? (crookX > labelPosition.x || crookX < touchingSliceAt.x) : (crookX < labelPosition.x || crookX > touchingSliceAt.x)) { useCrook = false; } // assemble the path var path = [ ['M', labelPosition.x, labelPosition.y] ]; if (useCrook) { path.push(segmentWithCrook); } path.push(['L', touchingSliceAt.x, touchingSliceAt.y]); return path; } } }); /* * * * Default Export * * */ return PiePoint; }); _registerModule(_modules, 'Series/Pie/PieSeries.js', [_modules['Mixins/CenteredSeries.js'], _modules['Series/Column/ColumnSeries.js'], _modules['Core/Globals.js'], _modules['Mixins/LegendSymbol.js'], _modules['Core/Color/Palette.js'], _modules['Series/Pie/PiePoint.js'], _modules['Core/Series/Series.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Renderer/SVG/Symbols.js'], _modules['Core/Utilities.js']], function (CenteredSeriesMixin, ColumnSeries, H, LegendSymbolMixin, palette, PiePoint, Series, SeriesRegistry, Symbols, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var getStartAndEndRadians = CenteredSeriesMixin.getStartAndEndRadians; var noop = H.noop; var clamp = U.clamp, extend = U.extend, fireEvent = U.fireEvent, merge = U.merge, pick = U.pick, relativeLength = U.relativeLength; /* * * * Class * * */ /** * Pie series type. * * @private * @class * @name Highcharts.seriesTypes.pie * * @augments Highcharts.Series */ var PieSeries = /** @class */ (function (_super) { __extends(PieSeries, _super); function PieSeries() { /* * * * Static Properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; /* * * * Properties * * */ _this.center = void 0; _this.data = void 0; _this.maxLabelDistance = void 0; _this.options = void 0; _this.points = void 0; return _this; /* eslint-enable valid-jsdoc */ } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Animates the pies in. * @private */ PieSeries.prototype.animate = function (init) { var series = this, points = series.points, startAngleRad = series.startAngleRad; if (!init) { points.forEach(function (point) { var graphic = point.graphic, args = point.shapeArgs; if (graphic && args) { // start values graphic.attr({ // animate from inner radius (#779) r: pick(point.startR, (series.center && series.center[3] / 2)), start: startAngleRad, end: startAngleRad }); // animate graphic.animate({ r: args.r, start: args.start, end: args.end }, series.options.animation); } }); } }; /** * Called internally to draw auxiliary graph in pie-like series in * situtation when the default graph is not sufficient enough to present * the data well. Auxiliary graph is saved in the same object as * regular graph. * @private */ PieSeries.prototype.drawEmpty = function () { var centerX, centerY, start = this.startAngleRad, end = this.endAngleRad, options = this.options; // Draw auxiliary graph if there're no visible points. if (this.total === 0 && this.center) { centerX = this.center[0]; centerY = this.center[1]; if (!this.graph) { this.graph = this.chart.renderer .arc(centerX, centerY, this.center[1] / 2, 0, start, end) .addClass('highcharts-empty-series') .add(this.group); } this.graph.attr({ d: Symbols.arc(centerX, centerY, this.center[2] / 2, 0, { start: start, end: end, innerR: this.center[3] / 2 }) }); if (!this.chart.styledMode) { this.graph.attr({ 'stroke-width': options.borderWidth, fill: options.fillColor || 'none', stroke: options.color || palette.neutralColor20 }); } } else if (this.graph) { // Destroy the graph object. this.graph = this.graph.destroy(); } }; /** * Slices in pie chart are initialized in DOM, but it's shapes and * animations are normally run in `drawPoints()`. * @private */ PieSeries.prototype.drawPoints = function () { var renderer = this.chart.renderer; this.points.forEach(function (point) { // When updating a series between 2d and 3d or cartesian and // polar, the shape type changes. if (point.graphic && point.hasNewShapeType()) { point.graphic = point.graphic.destroy(); } if (!point.graphic) { point.graphic = renderer[point.shapeType](point.shapeArgs) .add(point.series.group); point.delayedRendering = true; } }); }; /** * Extend the generatePoints method by adding total and percentage * properties to each point * @private */ PieSeries.prototype.generatePoints = function () { _super.prototype.generatePoints.call(this); this.updateTotals(); }; /** * Utility for getting the x value from a given y, used for * anticollision logic in data labels. Added point for using specific * points' label distance. * @private */ PieSeries.prototype.getX = function (y, left, point) { var center = this.center, // Variable pie has individual radius radius = this.radii ? this.radii[point.index] || 0 : center[2] / 2, angle, x; angle = Math.asin(clamp((y - center[1]) / (radius + point.labelDistance), -1, 1)); x = center[0] + (left ? -1 : 1) * (Math.cos(angle) * (radius + point.labelDistance)) + (point.labelDistance > 0 ? (left ? -1 : 1) * this.options.dataLabels.padding : 0); return x; }; /** * Define hasData function for non-cartesian series. Returns true if the * series has points at all. * @private */ PieSeries.prototype.hasData = function () { return !!this.processedXData.length; // != 0 }; /** * Draw the data points * @private */ PieSeries.prototype.redrawPoints = function () { var series = this, chart = series.chart, renderer = chart.renderer, groupTranslation, graphic, pointAttr, shapeArgs, shadow = series.options.shadow; this.drawEmpty(); if (shadow && !series.shadowGroup && !chart.styledMode) { series.shadowGroup = renderer .g('shadow') .attr({ zIndex: -1 }) .add(series.group); } // draw the slices series.points.forEach(function (point) { var animateTo = {}; graphic = point.graphic; if (!point.isNull && graphic) { var shadowGroup = void 0; shapeArgs = point.shapeArgs; // If the point is sliced, use special translation, else use // plot area translation groupTranslation = point.getTranslate(); if (!chart.styledMode) { // Put the shadow behind all points shadowGroup = point.shadowGroup; if (shadow && !shadowGroup) { shadowGroup = point.shadowGroup = renderer .g('shadow') .add(series.shadowGroup); } if (shadowGroup) { shadowGroup.attr(groupTranslation); } pointAttr = series.pointAttribs(point, (point.selected && 'select')); } // Draw the slice if (!point.delayedRendering) { graphic .setRadialReference(series.center); if (!chart.styledMode) { merge(true, animateTo, pointAttr); } merge(true, animateTo, shapeArgs, groupTranslation); graphic.animate(animateTo); } else { graphic .setRadialReference(series.center) .attr(shapeArgs) .attr(groupTranslation); if (!chart.styledMode) { graphic .attr(pointAttr) .attr({ 'stroke-linejoin': 'round' }) .shadow(shadow, shadowGroup); } point.delayedRendering = false; } graphic.attr({ visibility: point.visible ? 'inherit' : 'hidden' }); graphic.addClass(point.getClassName(), true); } else if (graphic) { point.graphic = graphic.destroy(); } }); }; /** * Utility for sorting data labels. * @private */ PieSeries.prototype.sortByAngle = function (points, sign) { points.sort(function (a, b) { return ((typeof a.angle !== 'undefined') && (b.angle - a.angle) * sign); }); }; /** * Do translation for pie slices * @private */ PieSeries.prototype.translate = function (positions) { this.generatePoints(); var series = this, cumulative = 0, precision = 1000, // issue #172 options = series.options, slicedOffset = options.slicedOffset, connectorOffset = slicedOffset + (options.borderWidth || 0), finalConnectorOffset, start, end, angle, radians = getStartAndEndRadians(options.startAngle, options.endAngle), startAngleRad = series.startAngleRad = radians.start, endAngleRad = series.endAngleRad = radians.end, circ = endAngleRad - startAngleRad, // 2 * Math.PI, points = series.points, // the x component of the radius vector for a given point radiusX, radiusY, labelDistance = options.dataLabels.distance, ignoreHiddenPoint = options.ignoreHiddenPoint, i, len = points.length, point; // Get positions - either an integer or a percentage string must be // given. If positions are passed as a parameter, we're in a // recursive loop for adjusting space for data labels. if (!positions) { series.center = positions = series.getCenter(); } // Calculate the geometry for each point for (i = 0; i < len; i++) { point = points[i]; // set start and end angle start = startAngleRad + (cumulative * circ); if (point.isValid() && (!ignoreHiddenPoint || point.visible)) { cumulative += point.percentage / 100; } end = startAngleRad + (cumulative * circ); // set the shape var shapeArgs = { x: positions[0], y: positions[1], r: positions[2] / 2, innerR: positions[3] / 2, start: Math.round(start * precision) / precision, end: Math.round(end * precision) / precision }; point.shapeType = 'arc'; point.shapeArgs = shapeArgs; // Used for distance calculation for specific point. point.labelDistance = pick((point.options.dataLabels && point.options.dataLabels.distance), labelDistance); // Compute point.labelDistance if it's defined as percentage // of slice radius (#8854) point.labelDistance = relativeLength(point.labelDistance, shapeArgs.r); // Saved for later dataLabels distance calculation. series.maxLabelDistance = Math.max(series.maxLabelDistance || 0, point.labelDistance); // The angle must stay within -90 and 270 (#2645) angle = (end + start) / 2; if (angle > 1.5 * Math.PI) { angle -= 2 * Math.PI; } else if (angle < -Math.PI / 2) { angle += 2 * Math.PI; } // Center for the sliced out slice point.slicedTranslation = { translateX: Math.round(Math.cos(angle) * slicedOffset), translateY: Math.round(Math.sin(angle) * slicedOffset) }; // set the anchor point for tooltips radiusX = Math.cos(angle) * positions[2] / 2; radiusY = Math.sin(angle) * positions[2] / 2; point.tooltipPos = [ positions[0] + radiusX * 0.7, positions[1] + radiusY * 0.7 ]; point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ? 1 : 0; point.angle = angle; // Set the anchor point for data labels. Use point.labelDistance // instead of labelDistance // #1174 // finalConnectorOffset - not override connectorOffset value. finalConnectorOffset = Math.min(connectorOffset, point.labelDistance / 5); // #1678 point.labelPosition = { natural: { // initial position of the data label - it's utilized for // finding the final position for the label x: positions[0] + radiusX + Math.cos(angle) * point.labelDistance, y: positions[1] + radiusY + Math.sin(angle) * point.labelDistance }, 'final': { // used for generating connector path - // initialized later in drawDataLabels function // x: undefined, // y: undefined }, // left - pie on the left side of the data label // right - pie on the right side of the data label // center - data label overlaps the pie alignment: point.labelDistance < 0 ? 'center' : point.half ? 'right' : 'left', connectorPosition: { breakAt: { x: positions[0] + radiusX + Math.cos(angle) * finalConnectorOffset, y: positions[1] + radiusY + Math.sin(angle) * finalConnectorOffset }, touchingSliceAt: { x: positions[0] + radiusX, y: positions[1] + radiusY } } }; } fireEvent(series, 'afterTranslate'); }; /** * Recompute total chart sum and update percentages of points. * @private */ PieSeries.prototype.updateTotals = function () { var i, total = 0, points = this.points, len = points.length, point, ignoreHiddenPoint = this.options.ignoreHiddenPoint; // Get the total sum for (i = 0; i < len; i++) { point = points[i]; if (point.isValid() && (!ignoreHiddenPoint || point.visible)) { total += point.y; } } this.total = total; // Set each point's properties for (i = 0; i < len; i++) { point = points[i]; point.percentage = (total > 0 && (point.visible || !ignoreHiddenPoint)) ? point.y / total * 100 : 0; point.total = total; } }; /** * A pie chart is a circular graphic which is divided into slices to * illustrate numerical proportion. * * @sample highcharts/demo/pie-basic/ * Pie chart * * @extends plotOptions.line * @excluding animationLimit, boostThreshold, connectEnds, connectNulls, * cropThreshold, dashStyle, dataSorting, dragDrop, * findNearestPointBy, getExtremesFromAll, label, lineWidth, * marker, negativeColor, pointInterval, pointIntervalUnit, * pointPlacement, pointStart, softThreshold, stacking, step, * threshold, turboThreshold, zoneAxis, zones, dataSorting, * boostBlending * @product highcharts * @optionparent plotOptions.pie */ PieSeries.defaultOptions = merge(Series.defaultOptions, { /** * @excluding legendItemClick * @apioption plotOptions.pie.events */ /** * Fires when the checkbox next to the point name in the legend is * clicked. One parameter, event, is passed to the function. The state * of the checkbox is found by event.checked. The checked item is found * by event.item. Return false to prevent the default action which is to * toggle the select state of the series. * * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/ * Alert checkbox status * * @type {Function} * @since 1.2.0 * @product highcharts * @context Highcharts.Point * @apioption plotOptions.pie.events.checkboxClick */ /** * Fires when the legend item belonging to the pie point (slice) is * clicked. The `this` keyword refers to the point itself. One * parameter, `event`, is passed to the function, containing common * event information. The default action is to toggle the visibility of * the point. This can be prevented by calling `event.preventDefault()`. * * @sample {highcharts} highcharts/plotoptions/pie-point-events-legenditemclick/ * Confirm toggle visibility * * @type {Highcharts.PointLegendItemClickCallbackFunction} * @since 1.2.0 * @product highcharts * @apioption plotOptions.pie.point.events.legendItemClick */ /** * The center of the pie chart relative to the plot area. Can be * percentages or pixel values. The default behaviour (as of 3.0) is to * center the pie so that all slices and data labels are within the plot * area. As a consequence, the pie may actually jump around in a chart * with dynamic values, as the data labels move. In that case, the * center should be explicitly set, for example to `["50%", "50%"]`. * * @sample {highcharts} highcharts/plotoptions/pie-center/ * Centered at 100, 100 * * @type {Array<(number|string|null),(number|string|null)>} * @default [null, null] * @product highcharts * * @private */ center: [null, null], /** * The color of the pie series. A pie series is represented as an empty * circle if the total sum of its values is 0. Use this property to * define the color of its border. * * In styled mode, the color can be defined by the * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series * color can be set with the `.highcharts-series`, * `.highcharts-color-{n}`, `.highcharts-{type}-series` or * `.highcharts-series-{n}` class, or individual classes given by the * `className` option. * * @sample {highcharts} highcharts/plotoptions/pie-emptyseries/ * Empty pie series * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default ${palette.neutralColor20} * @apioption plotOptions.pie.color */ /** * @product highcharts * * @private */ clip: false, /** * @ignore-option * * @private */ colorByPoint: true, /** * A series specific or series type specific color set to use instead * of the global [colors](#colors). * * @sample {highcharts} highcharts/demo/pie-monochrome/ * Set default colors for all pies * * @type {Array<Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject>} * @since 3.0 * @product highcharts * @apioption plotOptions.pie.colors */ /** * @declare Highcharts.SeriesPieDataLabelsOptionsObject * @extends plotOptions.series.dataLabels * @excluding align, allowOverlap, inside, staggerLines, step * @private */ dataLabels: { /** * Alignment method for data labels. Possible values are: * * - `toPlotEdges`: Each label touches the nearest vertical edge of * the plot area. * * - `connectors`: Connectors have the same x position and the * widest label of each half (left & right) touches the nearest * vertical edge of the plot area. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-connectors/ * alignTo: connectors * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-plotedges/ * alignTo: plotEdges * * @type {string} * @since 7.0.0 * @product highcharts * @apioption plotOptions.pie.dataLabels.alignTo */ allowOverlap: true, /** * The color of the line connecting the data label to the pie slice. * The default color is the same as the point's color. * * In styled mode, the connector stroke is given in the * `.highcharts-data-label-connector` class. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/ * Blue connectors * @sample {highcharts} highcharts/css/pie-point/ * Styled connectors * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 2.1 * @product highcharts * @apioption plotOptions.pie.dataLabels.connectorColor */ /** * The distance from the data label to the connector. Note that * data labels also have a default `padding`, so in order for the * connector to touch the text, the `padding` must also be 0. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/ * No padding * * @since 2.1 * @product highcharts */ connectorPadding: 5, /** * Specifies the method that is used to generate the connector path. * Highcharts provides 3 built-in connector shapes: `'fixedOffset'` * (default), `'straight'` and `'crookedLine'`. Using * `'crookedLine'` has the most sense (in most of the cases) when * `'alignTo'` is set. * * Users can provide their own method by passing a function instead * of a String. 3 arguments are passed to the callback: * * - Object that holds the information about the coordinates of the * label (`x` & `y` properties) and how the label is located in * relation to the pie (`alignment` property). `alignment` can by * one of the following: * `'left'` (pie on the left side of the data label), * `'right'` (pie on the right side of the data label) or * `'center'` (data label overlaps the pie). * * - Object that holds the information about the position of the * connector. Its `touchingSliceAt` porperty tells the position * of the place where the connector touches the slice. * * - Data label options * * The function has to return an SVG path definition in array form * (see the example). * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-string/ * connectorShape is a String * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorshape-function/ * connectorShape is a function * * @type {string|Function} * @since 7.0.0 * @product highcharts */ connectorShape: 'fixedOffset', /** * The width of the line connecting the data label to the pie slice. * * In styled mode, the connector stroke width is given in the * `.highcharts-data-label-connector` class. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/ * Disable the connector * @sample {highcharts} highcharts/css/pie-point/ * Styled connectors * * @type {number} * @default 1 * @since 2.1 * @product highcharts * @apioption plotOptions.pie.dataLabels.connectorWidth */ /** * Works only if `connectorShape` is `'crookedLine'`. It defines how * far from the vertical plot edge the coonnector path should be * crooked. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-crookdistance/ * crookDistance set to 90% * * @since 7.0.0 * @product highcharts */ crookDistance: '70%', /** * The distance of the data label from the pie's edge. Negative * numbers put the data label on top of the pie slices. Can also be * defined as a percentage of pie's radius. Connectors are only * shown for data labels outside the pie. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/ * Data labels on top of the pie * * @type {number|string} * @since 2.1 * @product highcharts */ distance: 30, enabled: true, /** * A * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting) * for the data label. Available variables are the same as for * `formatter`. * * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/ * Add a unit * * @type {string} * @default undefined * @since 3.0 * @apioption plotOptions.pie.dataLabels.format */ // eslint-disable-next-line valid-jsdoc /** * Callback JavaScript function to format the data label. Note that * if a `format` is defined, the format takes precedence and the * formatter is ignored. * * @type {Highcharts.DataLabelsFormatterCallbackFunction} * @default function () { return this.point.isNull ? void 0 : this.point.name; } */ formatter: function () { return this.point.isNull ? void 0 : this.point.name; }, /** * Whether to render the connector as a soft arc or a line with * sharp break. Works only if `connectorShape` equals to * `fixedOffset`. * * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-true/ * Soft * @sample {highcharts} highcharts/plotoptions/pie-datalabels-softconnector-false/ * Non soft * * @since 2.1.7 * @product highcharts */ softConnector: true, /** * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow * Long labels truncated with an ellipsis * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow-wrap * Long labels are wrapped * * @type {Highcharts.CSSObject} * @apioption plotOptions.pie.dataLabels.style */ x: 0 }, /** * If the total sum of the pie's values is 0, the series is represented * as an empty circle . The `fillColor` option defines the color of that * circle. Use [pie.borderWidth](#plotOptions.pie.borderWidth) to set * the border thickness. * * @sample {highcharts} highcharts/plotoptions/pie-emptyseries/ * Empty pie series * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @private */ fillColor: void 0, /** * The end angle of the pie in degrees where 0 is top and 90 is right. * Defaults to `startAngle` plus 360. * * @sample {highcharts} highcharts/demo/pie-semi-circle/ * Semi-circle donut * * @type {number} * @since 1.3.6 * @product highcharts * @apioption plotOptions.pie.endAngle */ /** * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries), * this option tells whether the series shall be redrawn as if the * hidden point were `null`. * * The default value changed from `false` to `true` with Highcharts * 3.0. * * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/ * True, the hiddden point is ignored * * @since 2.3.0 * @product highcharts * * @private */ ignoreHiddenPoint: true, /** * @ignore-option * * @private */ inactiveOtherPoints: true, /** * The size of the inner diameter for the pie. A size greater than 0 * renders a donut chart. Can be a percentage or pixel value. * Percentages are relative to the pie size. Pixel values are given as * integers. * * * Note: in Highcharts < 4.1.2, the percentage was relative to the plot * area, not the pie size. * * @sample {highcharts} highcharts/plotoptions/pie-innersize-80px/ * 80px inner size * @sample {highcharts} highcharts/plotoptions/pie-innersize-50percent/ * 50% of the plot area * @sample {highcharts} highcharts/demo/3d-pie-donut/ * 3D donut * * @type {number|string} * @default 0 * @since 2.0 * @product highcharts * @apioption plotOptions.pie.innerSize */ /** * @ignore-option * * @private */ legendType: 'point', /** * @ignore-option * * @private */ marker: null, /** * The minimum size for a pie in response to auto margins. The pie will * try to shrink to make room for data labels in side the plot area, * but only to this size. * * @type {number|string} * @default 80 * @since 3.0 * @product highcharts * @apioption plotOptions.pie.minSize */ /** * The diameter of the pie relative to the plot area. Can be a * percentage or pixel value. Pixel values are given as integers. The * default behaviour (as of 3.0) is to scale to the plot area and give * room for data labels within the plot area. * [slicedOffset](#plotOptions.pie.slicedOffset) is also included in the * default size calculation. As a consequence, the size of the pie may * vary when points are updated and data labels more around. In that * case it is best to set a fixed value, for example `"75%"`. * * @sample {highcharts} highcharts/plotoptions/pie-size/ * Smaller pie * * @type {number|string|null} * @product highcharts * * @private */ size: null, /** * Whether to display this particular series or series type in the * legend. Since 2.1, pies are not shown in the legend by default. * * @sample {highcharts} highcharts/plotoptions/series-showinlegend/ * One series in the legend, one hidden * * @product highcharts * * @private */ showInLegend: false, /** * If a point is sliced, moved out from the center, how many pixels * should it be moved?. * * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/ * 20px offset * * @product highcharts * * @private */ slicedOffset: 10, /** * The start angle of the pie slices in degrees where 0 is top and 90 * right. * * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/ * Start from right * * @type {number} * @default 0 * @since 2.3.4 * @product highcharts * @apioption plotOptions.pie.startAngle */ /** * Sticky tracking of mouse events. When true, the `mouseOut` event * on a series isn't triggered until the mouse moves over another * series, or out of the plot area. When false, the `mouseOut` event on * a series is triggered when the mouse leaves the area around the * series' graph or markers. This also implies the tooltip. When * `stickyTracking` is false and `tooltip.shared` is false, the tooltip * will be hidden when moving the mouse between series. * * @product highcharts * * @private */ stickyTracking: false, tooltip: { followPointer: true }, /** * The color of the border surrounding each slice. When `null`, the * border takes the same color as the slice fill. This can be used * together with a `borderWidth` to fill drawing gaps created by * antialiazing artefacts in borderless pies. * * In styled mode, the border stroke is given in the `.highcharts-point` * class. * * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/ * Black border * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default #ffffff * @product highcharts * * @private */ borderColor: palette.backgroundColor, /** * The width of the border surrounding each slice. * * When setting the border width to 0, there may be small gaps between * the slices due to SVG antialiasing artefacts. To work around this, * keep the border width at 0.5 or 1, but set the `borderColor` to * `null` instead. * * In styled mode, the border stroke width is given in the * `.highcharts-point` class. * * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/ * 3px border * * @product highcharts * * @private */ borderWidth: 1, /** * @ignore-options * @private */ lineWidth: void 0, states: { /** * @extends plotOptions.series.states.hover * @excluding marker, lineWidth, lineWidthPlus * @product highcharts */ hover: { /** * How much to brighten the point on interaction. Requires the * main color to be defined in hex or rgb(a) format. * * In styled mode, the hover brightness is by default replaced * by a fill-opacity given in the `.highcharts-point-hover` * class. * * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/ * Brightened by 0.5 * * @product highcharts */ brightness: 0.1 } } }); return PieSeries; }(Series)); extend(PieSeries.prototype, { axisTypes: [], directTouch: true, drawGraph: void 0, drawLegendSymbol: LegendSymbolMixin.drawRectangle, drawTracker: ColumnSeries.prototype.drawTracker, getCenter: CenteredSeriesMixin.getCenter, getSymbol: noop, isCartesian: false, noSharedTooltip: true, pointAttribs: ColumnSeries.prototype.pointAttribs, pointClass: PiePoint, requireSorting: false, searchPoint: noop, trackerGroups: ['group', 'dataLabelsGroup'] }); SeriesRegistry.registerSeriesType('pie', PieSeries); /* * * * Default Export * * */ /* * * * API Options * * */ /** * A `pie` series. If the [type](#series.pie.type) option is not specified, * it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.pie * @excluding cropThreshold, dataParser, dataURL, stack, xAxis, yAxis, * dataSorting, step, boostThreshold, boostBlending * @product highcharts * @apioption series.pie */ /** * An array of data points for the series. For the `pie` series type, * points can be given in the following ways: * * 1. An array of numerical values. In this case, the numerical values will be * interpreted as `y` options. Example: * ```js * data: [0, 5, 3, 5] * ``` * * 2. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.pie.turboThreshold), * this option is not available. * ```js * data: [{ * y: 1, * name: "Point2", * color: "#00FF00" * }, { * y: 7, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @sample {highcharts} highcharts/chart/reflow-true/ * Numerical values * @sample {highcharts} highcharts/series/data-array-of-arrays/ * Arrays of numeric x and y * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/ * Arrays of datetime x and y * @sample {highcharts} highcharts/series/data-array-of-name-value/ * Arrays of point.name and y * @sample {highcharts} highcharts/series/data-array-of-objects/ * Config objects * * @type {Array<number|Array<string,(number|null)>|null|*>} * @extends series.line.data * @excluding marker, x * @product highcharts * @apioption series.pie.data */ /** * @type {Highcharts.SeriesPieDataLabelsOptionsObject} * @product highcharts * @apioption series.pie.data.dataLabels */ /** * The sequential index of the data point in the legend. * * @type {number} * @product highcharts * @apioption series.pie.data.legendIndex */ /** * Whether to display a slice offset from the center. * * @sample {highcharts} highcharts/point/sliced/ * One sliced point * * @type {boolean} * @product highcharts * @apioption series.pie.data.sliced */ /** * @extends plotOptions.pie.dataLabels * @excluding align, allowOverlap, inside, staggerLines, step * @product highcharts * @apioption series.pie.dataLabels */ /** * @excluding legendItemClick * @product highcharts * @apioption series.pie.events */ ''; // placeholder for transpiled doclets above return PieSeries; }); _registerModule(_modules, 'Core/Series/DataLabels.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Color/Palette.js'], _modules['Core/Series/Series.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (A, F, H, palette, Series, SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var getDeferredAnimation = A.getDeferredAnimation; var format = F.format; var noop = H.noop; var seriesTypes = SeriesRegistry.seriesTypes; var arrayMax = U.arrayMax, clamp = U.clamp, defined = U.defined, extend = U.extend, fireEvent = U.fireEvent, isArray = U.isArray, merge = U.merge, objectEach = U.objectEach, pick = U.pick, relativeLength = U.relativeLength, splat = U.splat, stableSort = U.stableSort; /** * Callback JavaScript function to format the data label as a string. Note that * if a `format` is defined, the format takes precedence and the formatter is * ignored. * * @callback Highcharts.DataLabelsFormatterCallbackFunction * * @param {Highcharts.PointLabelObject} this * Data label context to format * * @param {Highcharts.DataLabelsOptions} options * [API options](/highcharts/plotOptions.series.dataLabels) of the data label * * @return {number|string|null|undefined} * Formatted data label text */ /** * Values for handling data labels that flow outside the plot area. * * @typedef {"allow"|"justify"} Highcharts.DataLabelsOverflowValue */ ''; // detach doclets above /* eslint-disable valid-jsdoc */ /** * General distribution algorithm for distributing labels of differing size * along a confined length in two dimensions. The algorithm takes an array of * objects containing a size, a target and a rank. It will place the labels as * close as possible to their targets, skipping the lowest ranked labels if * necessary. * * @private * @function Highcharts.distribute * @param {Highcharts.DataLabelsBoxArray} boxes * @param {number} len * @param {number} [maxDistance] * @return {void} */ H.distribute = function (boxes, len, maxDistance) { var i, overlapping = true, origBoxes = boxes, // Original array will be altered with added .pos restBoxes = [], // The outranked overshoot box, target, total = 0, reducedLen = origBoxes.reducedLen || len; /** * @private */ function sortByTarget(a, b) { return a.target - b.target; } // If the total size exceeds the len, remove those boxes with the lowest // rank i = boxes.length; while (i--) { total += boxes[i].size; } // Sort by rank, then slice away overshoot if (total > reducedLen) { stableSort(boxes, function (a, b) { return (b.rank || 0) - (a.rank || 0); }); i = 0; total = 0; while (total <= reducedLen) { total += boxes[i].size; i++; } restBoxes = boxes.splice(i - 1, boxes.length); } // Order by target stableSort(boxes, sortByTarget); // So far we have been mutating the original array. Now // create a copy with target arrays boxes = boxes.map(function (box) { return { size: box.size, targets: [box.target], align: pick(box.align, 0.5) }; }); while (overlapping) { // Initial positions: target centered in box i = boxes.length; while (i--) { box = boxes[i]; // Composite box, average of targets target = (Math.min.apply(0, box.targets) + Math.max.apply(0, box.targets)) / 2; box.pos = clamp(target - box.size * box.align, 0, len - box.size); } // Detect overlap and join boxes i = boxes.length; overlapping = false; while (i--) { // Overlap if (i > 0 && boxes[i - 1].pos + boxes[i - 1].size > boxes[i].pos) { // Add this size to the previous box boxes[i - 1].size += boxes[i].size; boxes[i - 1].targets = boxes[i - 1] .targets .concat(boxes[i].targets); boxes[i - 1].align = 0.5; // Overlapping right, push left if (boxes[i - 1].pos + boxes[i - 1].size > len) { boxes[i - 1].pos = len - boxes[i - 1].size; } boxes.splice(i, 1); // Remove this item overlapping = true; } } } // Add the rest (hidden boxes) origBoxes.push.apply(origBoxes, restBoxes); // Now the composite boxes are placed, we need to put the original boxes // within them i = 0; boxes.some(function (box) { var posInCompositeBox = 0; if (box.targets.some(function () { origBoxes[i].pos = box.pos + posInCompositeBox; // If the distance between the position and the target exceeds // maxDistance, abort the loop and decrease the length in increments // of 10% to recursively reduce the number of visible boxes by // rank. Once all boxes are within the maxDistance, we're good. if (typeof maxDistance !== 'undefined' && Math.abs(origBoxes[i].pos - origBoxes[i].target) > maxDistance) { // Reset the positions that are already set origBoxes.slice(0, i + 1).forEach(function (box) { delete box.pos; }); // Try with a smaller length origBoxes.reducedLen = (origBoxes.reducedLen || len) - (len * 0.1); // Recurse if (origBoxes.reducedLen > len * 0.1) { H.distribute(origBoxes, len, maxDistance); } // Exceeded maxDistance => abort return true; } posInCompositeBox += origBoxes[i].size; i++; })) { // Exceeded maxDistance => abort return true; } }); // Add the rest (hidden) boxes and sort by target stableSort(origBoxes, sortByTarget); }; /** * Draw the data labels * * @private * @function Highcharts.Series#drawDataLabels * @return {void} * @fires Highcharts.Series#event:afterDrawDataLabels */ Series.prototype.drawDataLabels = function () { var series = this, chart = series.chart, seriesOptions = series.options, seriesDlOptions = seriesOptions.dataLabels, points = series.points, pointOptions, hasRendered = series.hasRendered || 0, dataLabelsGroup, dataLabelAnim = seriesDlOptions.animation, animationConfig = seriesDlOptions.defer ? getDeferredAnimation(chart, dataLabelAnim, series) : { defer: 0, duration: 0 }, renderer = chart.renderer; /** * Handle the dataLabels.filter option. * @private */ function applyFilter(point, options) { var filter = options.filter, op, prop, val; if (filter) { op = filter.operator; prop = point[filter.property]; val = filter.value; if ((op === '>' && prop > val) || (op === '<' && prop < val) || (op === '>=' && prop >= val) || (op === '<=' && prop <= val) || (op === '==' && prop == val) || // eslint-disable-line eqeqeq (op === '===' && prop === val)) { return true; } return false; } return true; } /** * Merge two objects that can be arrays. If one of them is an array, the * other is merged into each element. If both are arrays, each element is * merged by index. If neither are arrays, we use normal merge. * @private */ function mergeArrays(one, two) { var res = [], i; if (isArray(one) && !isArray(two)) { res = one.map(function (el) { return merge(el, two); }); } else if (isArray(two) && !isArray(one)) { res = two.map(function (el) { return merge(one, el); }); } else if (!isArray(one) && !isArray(two)) { res = merge(one, two); } else { i = Math.max(one.length, two.length); while (i--) { res[i] = merge(one[i], two[i]); } } return res; } // Merge in plotOptions.dataLabels for series seriesDlOptions = mergeArrays(mergeArrays(chart.options.plotOptions && chart.options.plotOptions.series && chart.options.plotOptions.series.dataLabels, chart.options.plotOptions && chart.options.plotOptions[series.type] && chart.options.plotOptions[series.type].dataLabels), seriesDlOptions); fireEvent(this, 'drawDataLabels'); if (isArray(seriesDlOptions) || seriesDlOptions.enabled || series._hasPointLabels) { // Create a separate group for the data labels to avoid rotation dataLabelsGroup = series.plotGroup('dataLabelsGroup', 'data-labels', !hasRendered ? 'hidden' : 'inherit', // #5133, #10220 seriesDlOptions.zIndex || 6); dataLabelsGroup.attr({ opacity: +hasRendered }); // #3300 if (!hasRendered) { var group = series.dataLabelsGroup; if (group) { if (series.visible) { // #2597, #3023, #3024 dataLabelsGroup.show(true); } group[seriesOptions.animation ? 'animate' : 'attr']({ opacity: 1 }, animationConfig); } } // Make the labels for each point points.forEach(function (point) { // Merge in series options for the point. // @note dataLabelAttribs (like pointAttribs) would eradicate // the need for dlOptions, and simplify the section below. pointOptions = splat(mergeArrays(seriesDlOptions, point.dlOptions || // dlOptions is used in treemaps (point.options && point.options.dataLabels))); // Handle each individual data label for this point pointOptions.forEach(function (labelOptions, i) { // Options for one datalabel var labelEnabled = (labelOptions.enabled && // #2282, #4641, #7112, #10049 (!point.isNull || point.dataLabelOnNull) && applyFilter(point, labelOptions)), labelConfig, formatString, labelText, style, rotation, attr, dataLabel = point.dataLabels ? point.dataLabels[i] : point.dataLabel, connector = point.connectors ? point.connectors[i] : point.connector, labelDistance = pick(labelOptions.distance, point.labelDistance), isNew = !dataLabel; if (labelEnabled) { // Create individual options structure that can be extended // without affecting others labelConfig = point.getLabelConfig(); formatString = pick(labelOptions[point.formatPrefix + 'Format'], labelOptions.format); labelText = defined(formatString) ? format(formatString, labelConfig, chart) : (labelOptions[point.formatPrefix + 'Formatter'] || labelOptions.formatter).call(labelConfig, labelOptions); style = labelOptions.style; rotation = labelOptions.rotation; if (!chart.styledMode) { // Determine the color style.color = pick(labelOptions.color, style.color, series.color, palette.neutralColor100); // Get automated contrast color if (style.color === 'contrast') { point.contrastColor = renderer.getContrast((point.color || series.color)); style.color = (!defined(labelDistance) && labelOptions.inside) || labelDistance < 0 || !!seriesOptions.stacking ? point.contrastColor : palette.neutralColor100; } else { delete point.contrastColor; } if (seriesOptions.cursor) { style.cursor = seriesOptions.cursor; } } attr = { r: labelOptions.borderRadius || 0, rotation: rotation, padding: labelOptions.padding, zIndex: 1 }; if (!chart.styledMode) { attr.fill = labelOptions.backgroundColor; attr.stroke = labelOptions.borderColor; attr['stroke-width'] = labelOptions.borderWidth; } // Remove unused attributes (#947) objectEach(attr, function (val, name) { if (typeof val === 'undefined') { delete attr[name]; } }); } // If the point is outside the plot area, destroy it. #678, #820 if (dataLabel && (!labelEnabled || !defined(labelText))) { point.dataLabel = point.dataLabel && point.dataLabel.destroy(); if (point.dataLabels) { // Remove point.dataLabels if this was the last one if (point.dataLabels.length === 1) { delete point.dataLabels; } else { delete point.dataLabels[i]; } } if (!i) { delete point.dataLabel; } if (connector) { point.connector = point.connector.destroy(); if (point.connectors) { // Remove point.connectors if this was the last one if (point.connectors.length === 1) { delete point.connectors; } else { delete point.connectors[i]; } } } // Individual labels are disabled if the are explicitly disabled // in the point options, or if they fall outside the plot area. } else if (labelEnabled && defined(labelText)) { if (!dataLabel) { // Create new label element point.dataLabels = point.dataLabels || []; dataLabel = point.dataLabels[i] = rotation ? // Labels don't rotate, use text element renderer.text(labelText, 0, -9999, labelOptions.useHTML) .addClass('highcharts-data-label') : // We can use label renderer.label(labelText, 0, -9999, labelOptions.shape, null, null, labelOptions.useHTML, null, 'data-label'); // Store for backwards compatibility if (!i) { point.dataLabel = dataLabel; } dataLabel.addClass(' highcharts-data-label-color-' + point.colorIndex + ' ' + (labelOptions.className || '') + ( // #3398 labelOptions.useHTML ? ' highcharts-tracker' : '')); } else { // Use old element and just update text attr.text = labelText; } // Store data label options for later access dataLabel.options = labelOptions; dataLabel.attr(attr); if (!chart.styledMode) { // Styles must be applied before add in order to read // text bounding box dataLabel.css(style).shadow(labelOptions.shadow); } if (!dataLabel.added) { dataLabel.add(dataLabelsGroup); } if (labelOptions.textPath && !labelOptions.useHTML) { dataLabel.setTextPath((point.getDataLabelPath && point.getDataLabelPath(dataLabel)) || point.graphic, labelOptions.textPath); if (point.dataLabelPath && !labelOptions.textPath.enabled) { // clean the DOM point.dataLabelPath = point.dataLabelPath.destroy(); } } // Now the data label is created and placed at 0,0, so we // need to align it series.alignDataLabel(point, dataLabel, labelOptions, null, isNew); } }); }); } fireEvent(this, 'afterDrawDataLabels'); }; /** * Align each individual data label. * * @private * @function Highcharts.Series#alignDataLabel * @param {Highcharts.Point} point * @param {Highcharts.SVGElement} dataLabel * @param {Highcharts.DataLabelsOptions} options * @param {Highcharts.BBoxObject} alignTo * @param {boolean} [isNew] * @return {void} */ Series.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { var series = this, chart = this.chart, inverted = this.isCartesian && chart.inverted, enabledDataSorting = this.enabledDataSorting, plotX = pick(point.dlBox && point.dlBox.centerX, point.plotX, -9999), plotY = pick(point.plotY, -9999), bBox = dataLabel.getBBox(), baseline, rotation = options.rotation, normRotation, negRotation, align = options.align, rotCorr, // rotation correction isInsidePlot = chart.isInsidePlot(plotX, Math.round(plotY), { inverted: inverted, paneCoordinates: true, series: series }), // Math.round for rounding errors (#2683), alignTo to allow column // labels (#2700) alignAttr, // the final position; justify = pick(options.overflow, (enabledDataSorting ? 'none' : 'justify')) === 'justify', visible = this.visible && point.visible !== false && (point.series.forceDL || (enabledDataSorting && !justify) || isInsidePlot || ( // If the data label is inside the align box, it is enough // that parts of the align box is inside the plot area // (#12370). When stacking, it is always inside regardless // of the option (#15148). pick(options.inside, !!this.options.stacking) && alignTo && chart.isInsidePlot(plotX, inverted ? alignTo.x + 1 : alignTo.y + alignTo.height - 1, { inverted: inverted, paneCoordinates: true, series: series }))), setStartPos = function (alignOptions) { if (enabledDataSorting && series.xAxis && !justify) { series.setDataLabelStartPos(point, dataLabel, isNew, isInsidePlot, alignOptions); } }; if (visible) { baseline = chart.renderer.fontMetrics(chart.styledMode ? void 0 : options.style.fontSize, dataLabel).b; // The alignment box is a singular point alignTo = extend({ x: inverted ? this.yAxis.len - plotY : plotX, y: Math.round(inverted ? this.xAxis.len - plotX : plotY), width: 0, height: 0 }, alignTo); // Add the text size for alignment calculation extend(options, { width: bBox.width, height: bBox.height }); // Allow a hook for changing alignment in the last moment, then do the // alignment if (rotation) { justify = false; // Not supported for rotated text rotCorr = chart.renderer.rotCorr(baseline, rotation); // #3723 alignAttr = { x: (alignTo.x + (options.x || 0) + alignTo.width / 2 + rotCorr.x), y: (alignTo.y + (options.y || 0) + { top: 0, middle: 0.5, bottom: 1 }[options.verticalAlign] * alignTo.height) }; setStartPos(alignAttr); // data sorting dataLabel[isNew ? 'attr' : 'animate'](alignAttr) .attr({ align: align }); // Compensate for the rotated label sticking out on the sides normRotation = (rotation + 720) % 360; negRotation = normRotation > 180 && normRotation < 360; if (align === 'left') { alignAttr.y -= negRotation ? bBox.height : 0; } else if (align === 'center') { alignAttr.x -= bBox.width / 2; alignAttr.y -= bBox.height / 2; } else if (align === 'right') { alignAttr.x -= bBox.width; alignAttr.y -= negRotation ? 0 : bBox.height; } dataLabel.placed = true; dataLabel.alignAttr = alignAttr; } else { setStartPos(alignTo); // data sorting dataLabel.align(options, void 0, alignTo); alignAttr = dataLabel.alignAttr; } // Handle justify or crop if (justify && alignTo.height >= 0) { // #8830 this.justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew); // Now check that the data label is within the plot area } else if (pick(options.crop, true)) { visible = chart.isInsidePlot(alignAttr.x, alignAttr.y, { paneCoordinates: true, series: series }) && chart.isInsidePlot(alignAttr.x + bBox.width, alignAttr.y + bBox.height, { paneCoordinates: true, series: series }); } // When we're using a shape, make it possible with a connector or an // arrow pointing to thie point if (options.shape && !rotation) { dataLabel[isNew ? 'attr' : 'animate']({ anchorX: inverted ? chart.plotWidth - point.plotY : point.plotX, anchorY: inverted ? chart.plotHeight - point.plotX : point.plotY }); } } // To use alignAttr property in hideOverlappingLabels if (isNew && enabledDataSorting) { dataLabel.placed = false; } // Show or hide based on the final aligned position if (!visible && (!enabledDataSorting || justify)) { dataLabel.hide(true); dataLabel.placed = false; // don't animate back in } }; /** * Set starting position for data label sorting animation. * * @private * @function Highcharts.Series#setDataLabelStartPos * @param {Highcharts.SVGElement} dataLabel * @param {Highcharts.ColumnPoint} point * @param {boolean | undefined} [isNew] * @param {boolean} [isInside] * @param {Highcharts.AlignObject} [alignOptions] * * @return {void} */ Series.prototype.setDataLabelStartPos = function (point, dataLabel, isNew, isInside, alignOptions) { var chart = this.chart, inverted = chart.inverted, xAxis = this.xAxis, reversed = xAxis.reversed, labelCenter = inverted ? dataLabel.height / 2 : dataLabel.width / 2, pointWidth = point.pointWidth, halfWidth = pointWidth ? pointWidth / 2 : 0, startXPos, startYPos; startXPos = inverted ? alignOptions.x : (reversed ? -labelCenter - halfWidth : xAxis.width - labelCenter + halfWidth); startYPos = inverted ? (reversed ? this.yAxis.height - labelCenter + halfWidth : -labelCenter - halfWidth) : alignOptions.y; dataLabel.startXPos = startXPos; dataLabel.startYPos = startYPos; // We need to handle visibility in case of sorting point outside plot area if (!isInside) { dataLabel .attr({ opacity: 1 }) .animate({ opacity: 0 }, void 0, dataLabel.hide); } else if (dataLabel.visibility === 'hidden') { dataLabel.show(); dataLabel .attr({ opacity: 0 }) .animate({ opacity: 1 }); } // Save start position on first render, but do not change position if (!chart.hasRendered) { return; } // Set start position if (isNew) { dataLabel.attr({ x: dataLabel.startXPos, y: dataLabel.startYPos }); } dataLabel.placed = true; }; /** * If data labels fall partly outside the plot area, align them back in, in a * way that doesn't hide the point. * * @private * @function Highcharts.Series#justifyDataLabel * @param {Highcharts.SVGElement} dataLabel * @param {Highcharts.DataLabelsOptions} options * @param {Highcharts.SVGAttributes} alignAttr * @param {Highcharts.BBoxObject} bBox * @param {Highcharts.BBoxObject} [alignTo] * @param {boolean} [isNew] * @return {boolean|undefined} */ Series.prototype.justifyDataLabel = function (dataLabel, options, alignAttr, bBox, alignTo, isNew) { var chart = this.chart, align = options.align, verticalAlign = options.verticalAlign, off, justified, padding = dataLabel.box ? 0 : (dataLabel.padding || 0); var _a = options.x, x = _a === void 0 ? 0 : _a, _b = options.y, y = _b === void 0 ? 0 : _b; // Off left off = (alignAttr.x || 0) + padding; if (off < 0) { if (align === 'right' && x >= 0) { options.align = 'left'; options.inside = true; } else { x -= off; } justified = true; } // Off right off = (alignAttr.x || 0) + bBox.width - padding; if (off > chart.plotWidth) { if (align === 'left' && x <= 0) { options.align = 'right'; options.inside = true; } else { x += chart.plotWidth - off; } justified = true; } // Off top off = alignAttr.y + padding; if (off < 0) { if (verticalAlign === 'bottom' && y >= 0) { options.verticalAlign = 'top'; options.inside = true; } else { y -= off; } justified = true; } // Off bottom off = (alignAttr.y || 0) + bBox.height - padding; if (off > chart.plotHeight) { if (verticalAlign === 'top' && y <= 0) { options.verticalAlign = 'bottom'; options.inside = true; } else { y += chart.plotHeight - off; } justified = true; } if (justified) { options.x = x; options.y = y; dataLabel.placed = !isNew; dataLabel.align(options, void 0, alignTo); } return justified; }; if (seriesTypes.pie) { seriesTypes.pie.prototype.dataLabelPositioners = { // Based on the value computed in Highcharts' distribute algorithm. radialDistributionY: function (point) { return point.top + point.distributeBox.pos; }, // get the x - use the natural x position for labels near the // top and bottom, to prevent the top and botton slice // connectors from touching each other on either side // Based on the value computed in Highcharts' distribute algorithm. radialDistributionX: function (series, point, y, naturalY) { return series.getX(y < point.top + 2 || y > point.bottom - 2 ? naturalY : y, point.half, point); }, // dataLabels.distance determines the x position of the label justify: function (point, radius, seriesCenter) { return seriesCenter[0] + (point.half ? -1 : 1) * (radius + point.labelDistance); }, // Left edges of the left-half labels touch the left edge of the plot // area. Right edges of the right-half labels touch the right edge of // the plot area. alignToPlotEdges: function (dataLabel, half, plotWidth, plotLeft) { var dataLabelWidth = dataLabel.getBBox().width; return half ? dataLabelWidth + plotLeft : plotWidth - dataLabelWidth - plotLeft; }, // Connectors of each side end in the same x position. Labels are // aligned to them. Left edge of the widest left-half label touches the // left edge of the plot area. Right edge of the widest right-half label // touches the right edge of the plot area. alignToConnectors: function (points, half, plotWidth, plotLeft) { var maxDataLabelWidth = 0, dataLabelWidth; // find widest data label points.forEach(function (point) { dataLabelWidth = point.dataLabel.getBBox().width; if (dataLabelWidth > maxDataLabelWidth) { maxDataLabelWidth = dataLabelWidth; } }); return half ? maxDataLabelWidth + plotLeft : plotWidth - maxDataLabelWidth - plotLeft; } }; /** * Override the base drawDataLabels method by pie specific functionality * * @private * @function Highcharts.seriesTypes.pie#drawDataLabels * @return {void} */ seriesTypes.pie.prototype.drawDataLabels = function () { var series = this, data = series.data, point, chart = series.chart, options = series.options.dataLabels || {}, connectorPadding = options.connectorPadding, connectorWidth, plotWidth = chart.plotWidth, plotHeight = chart.plotHeight, plotLeft = chart.plotLeft, maxWidth = Math.round(chart.chartWidth / 3), connector, seriesCenter = series.center, radius = seriesCenter[2] / 2, centerY = seriesCenter[1], dataLabel, dataLabelWidth, // labelPos, labelPosition, labelHeight, // divide the points into right and left halves for anti collision halves = [ [], [] // left ], x, y, visibility, j, overflow = [0, 0, 0, 0], // top, right, bottom, left dataLabelPositioners = series.dataLabelPositioners, pointDataLabelsOptions; // get out if not enabled if (!series.visible || (!options.enabled && !series._hasPointLabels)) { return; } // Reset all labels that have been shortened data.forEach(function (point) { if (point.dataLabel && point.visible && point.dataLabel.shortened) { point.dataLabel .attr({ width: 'auto' }).css({ width: 'auto', textOverflow: 'clip' }); point.dataLabel.shortened = false; } }); // run parent method Series.prototype.drawDataLabels.apply(series); data.forEach(function (point) { if (point.dataLabel) { if (point.visible) { // #407, #2510 // Arrange points for detection collision halves[point.half].push(point); // Reset positions (#4905) point.dataLabel._pos = null; // Avoid long labels squeezing the pie size too far down if (!defined(options.style.width) && !defined(point.options.dataLabels && point.options.dataLabels.style && point.options.dataLabels.style.width)) { if (point.dataLabel.getBBox().width > maxWidth) { point.dataLabel.css({ // Use a fraction of the maxWidth to avoid // wrapping close to the end of the string. width: Math.round(maxWidth * 0.7) + 'px' }); point.dataLabel.shortened = true; } } } else { point.dataLabel = point.dataLabel.destroy(); // Workaround to make pies destroy multiple datalabels // correctly. This logic needs rewriting to support multiple // datalabels fully. if (point.dataLabels && point.dataLabels.length === 1) { delete point.dataLabels; } } } }); /* Loop over the points in each half, starting from the top and bottom * of the pie to detect overlapping labels. */ halves.forEach(function (points, i) { var top, bottom, length = points.length, positions = [], naturalY, sideOverflow, size, distributionLength; if (!length) { return; } // Sort by angle series.sortByAngle(points, i - 0.5); // Only do anti-collision when we have dataLabels outside the pie // and have connectors. (#856) if (series.maxLabelDistance > 0) { top = Math.max(0, centerY - radius - series.maxLabelDistance); bottom = Math.min(centerY + radius + series.maxLabelDistance, chart.plotHeight); points.forEach(function (point) { // check if specific points' label is outside the pie if (point.labelDistance > 0 && point.dataLabel) { // point.top depends on point.labelDistance value // Used for calculation of y value in getX method point.top = Math.max(0, centerY - radius - point.labelDistance); point.bottom = Math.min(centerY + radius + point.labelDistance, chart.plotHeight); size = point.dataLabel.getBBox().height || 21; // point.positionsIndex is needed for getting index of // parameter related to specific point inside positions // array - not every point is in positions array. point.distributeBox = { target: point.labelPosition.natural.y - point.top + size / 2, size: size, rank: point.y }; positions.push(point.distributeBox); } }); distributionLength = bottom + size - top; H.distribute(positions, distributionLength, distributionLength / 5); } // Now the used slots are sorted, fill them up sequentially for (j = 0; j < length; j++) { point = points[j]; // labelPos = point.labelPos; labelPosition = point.labelPosition; dataLabel = point.dataLabel; visibility = point.visible === false ? 'hidden' : 'inherit'; naturalY = labelPosition.natural.y; y = naturalY; if (positions && defined(point.distributeBox)) { if (typeof point.distributeBox.pos === 'undefined') { visibility = 'hidden'; } else { labelHeight = point.distributeBox.size; // Find label's y position y = dataLabelPositioners .radialDistributionY(point); } } // It is needed to delete point.positionIndex for // dynamically added points etc. delete point.positionIndex; // @todo unused // Find label's x position // justify is undocumented in the API - preserve support for it if (options.justify) { x = dataLabelPositioners.justify(point, radius, seriesCenter); } else { switch (options.alignTo) { case 'connectors': x = dataLabelPositioners.alignToConnectors(points, i, plotWidth, plotLeft); break; case 'plotEdges': x = dataLabelPositioners.alignToPlotEdges(dataLabel, i, plotWidth, plotLeft); break; default: x = dataLabelPositioners.radialDistributionX(series, point, y, naturalY); } } // Record the placement and visibility dataLabel._attr = { visibility: visibility, align: labelPosition.alignment }; pointDataLabelsOptions = point.options.dataLabels || {}; dataLabel._pos = { x: (x + pick(pointDataLabelsOptions.x, options.x) + // (#12985) ({ left: connectorPadding, right: -connectorPadding }[labelPosition.alignment] || 0)), // 10 is for the baseline (label vs text) y: (y + pick(pointDataLabelsOptions.y, options.y) - // (#12985) 10) }; // labelPos.x = x; // labelPos.y = y; labelPosition.final.x = x; labelPosition.final.y = y; // Detect overflowing data labels if (pick(options.crop, true)) { dataLabelWidth = dataLabel.getBBox().width; sideOverflow = null; // Overflow left if (x - dataLabelWidth < connectorPadding && i === 1 // left half ) { sideOverflow = Math.round(dataLabelWidth - x + connectorPadding); overflow[3] = Math.max(sideOverflow, overflow[3]); // Overflow right } else if (x + dataLabelWidth > plotWidth - connectorPadding && i === 0 // right half ) { sideOverflow = Math.round(x + dataLabelWidth - plotWidth + connectorPadding); overflow[1] = Math.max(sideOverflow, overflow[1]); } // Overflow top if (y - labelHeight / 2 < 0) { overflow[0] = Math.max(Math.round(-y + labelHeight / 2), overflow[0]); // Overflow left } else if (y + labelHeight / 2 > plotHeight) { overflow[2] = Math.max(Math.round(y + labelHeight / 2 - plotHeight), overflow[2]); } dataLabel.sideOverflow = sideOverflow; } } // for each point }); // for each half // Do not apply the final placement and draw the connectors until we // have verified that labels are not spilling over. if (arrayMax(overflow) === 0 || this.verifyDataLabelOverflow(overflow)) { // Place the labels in the final position this.placeDataLabels(); this.points.forEach(function (point) { // #8864: every connector can have individual options pointDataLabelsOptions = merge(options, point.options.dataLabels); connectorWidth = pick(pointDataLabelsOptions.connectorWidth, 1); // Draw the connector if (connectorWidth) { var isNew = void 0; connector = point.connector; dataLabel = point.dataLabel; if (dataLabel && dataLabel._pos && point.visible && point.labelDistance > 0) { visibility = dataLabel._attr.visibility; isNew = !connector; if (isNew) { point.connector = connector = chart.renderer .path() .addClass('highcharts-data-label-connector ' + ' highcharts-color-' + point.colorIndex + (point.className ? ' ' + point.className : '')) .add(series.dataLabelsGroup); if (!chart.styledMode) { connector.attr({ 'stroke-width': connectorWidth, 'stroke': (pointDataLabelsOptions.connectorColor || point.color || palette.neutralColor60) }); } } connector[isNew ? 'attr' : 'animate']({ d: point.getConnectorPath() }); connector.attr('visibility', visibility); } else if (connector) { point.connector = connector.destroy(); } } }); } }; /** * Extendable method for getting the path of the connector between the data * label and the pie slice. * * @private * @function Highcharts.seriesTypes.pie#connectorPath * * @param {*} labelPos * * @return {Highcharts.SVGPathArray} */ // TODO: depracated - remove it /* seriesTypes.pie.prototype.connectorPath = function (labelPos) { let x = labelPos.x, y = labelPos.y; return pick(this.options.dataLabels.softConnector, true) ? [ 'M', // end of the string at the label x + (labelPos[6] === 'left' ? 5 : -5), y, 'C', x, y, // first break, next to the label 2 * labelPos[2] - labelPos[4], 2 * labelPos[3] - labelPos[5], labelPos[2], labelPos[3], // second break 'L', labelPos[4], labelPos[5] // base ] : [ 'M', // end of the string at the label x + (labelPos[6] === 'left' ? 5 : -5), y, 'L', labelPos[2], labelPos[3], // second break 'L', labelPos[4], labelPos[5] // base ]; }; */ /** * Perform the final placement of the data labels after we have verified * that they fall within the plot area. * * @private * @function Highcharts.seriesTypes.pie#placeDataLabels * @return {void} */ seriesTypes.pie.prototype.placeDataLabels = function () { this.points.forEach(function (point) { var dataLabel = point.dataLabel, _pos; if (dataLabel && point.visible) { _pos = dataLabel._pos; if (_pos) { // Shorten data labels with ellipsis if they still overflow // after the pie has reached minSize (#223). if (dataLabel.sideOverflow) { dataLabel._attr.width = Math.max(dataLabel.getBBox().width - dataLabel.sideOverflow, 0); dataLabel.css({ width: dataLabel._attr.width + 'px', textOverflow: ((this.options.dataLabels.style || {}) .textOverflow || 'ellipsis') }); dataLabel.shortened = true; } dataLabel.attr(dataLabel._attr); dataLabel[dataLabel.moved ? 'animate' : 'attr'](_pos); dataLabel.moved = true; } else if (dataLabel) { dataLabel.attr({ y: -9999 }); } } // Clear for update delete point.distributeBox; }, this); }; seriesTypes.pie.prototype.alignDataLabel = noop; /** * Verify whether the data labels are allowed to draw, or we should run more * translation and data label positioning to keep them inside the plot area. * Returns true when data labels are ready to draw. * * @private * @function Highcharts.seriesTypes.pie#verifyDataLabelOverflow * @param {Array<number>} overflow * @return {boolean} */ seriesTypes.pie.prototype.verifyDataLabelOverflow = function (overflow) { var center = this.center, options = this.options, centerOption = options.center, minSize = options.minSize || 80, newSize = minSize, // If a size is set, return true and don't try to shrink the pie // to fit the labels. ret = options.size !== null; if (!ret) { // Handle horizontal size and center if (centerOption[0] !== null) { // Fixed center newSize = Math.max(center[2] - Math.max(overflow[1], overflow[3]), minSize); } else { // Auto center newSize = Math.max( // horizontal overflow center[2] - overflow[1] - overflow[3], minSize); // horizontal center center[0] += (overflow[3] - overflow[1]) / 2; } // Handle vertical size and center if (centerOption[1] !== null) { // Fixed center newSize = clamp(newSize, minSize, center[2] - Math.max(overflow[0], overflow[2])); } else { // Auto center newSize = clamp(newSize, minSize, // vertical overflow center[2] - overflow[0] - overflow[2]); // vertical center center[1] += (overflow[0] - overflow[2]) / 2; } // If the size must be decreased, we need to run translate and // drawDataLabels again if (newSize < center[2]) { center[2] = newSize; center[3] = Math.min(// #3632 relativeLength(options.innerSize || 0, newSize), newSize); this.translate(center); if (this.drawDataLabels) { this.drawDataLabels(); } // Else, return true to indicate that the pie and its labels is // within the plot area } else { ret = true; } } return ret; }; } if (seriesTypes.column) { /** * Override the basic data label alignment by adjusting for the position of * the column. * * @private * @function Highcharts.seriesTypes.column#alignDataLabel * @param {Highcharts.Point} point * @param {Highcharts.SVGElement} dataLabel * @param {Highcharts.DataLabelsOptions} options * @param {Highcharts.BBoxObject} alignTo * @param {boolean} [isNew] * @return {void} */ seriesTypes.column.prototype.alignDataLabel = function (point, dataLabel, options, alignTo, isNew) { var inverted = this.chart.inverted, series = point.series, // data label box for alignment dlBox = point.dlBox || point.shapeArgs, below = pick(point.below, // range series point.plotY > pick(this.translatedThreshold, series.yAxis.len)), // draw it inside the box? inside = pick(options.inside, !!this.options.stacking), overshoot; // Align to the column itself, or the top of it if (dlBox) { // Area range uses this method but not alignTo alignTo = merge(dlBox); if (alignTo.y < 0) { alignTo.height += alignTo.y; alignTo.y = 0; } // If parts of the box overshoots outside the plot area, modify the // box to center the label inside overshoot = alignTo.y + alignTo.height - series.yAxis.len; if (overshoot > 0 && overshoot < alignTo.height) { alignTo.height -= overshoot; } if (inverted) { alignTo = { x: series.yAxis.len - alignTo.y - alignTo.height, y: series.xAxis.len - alignTo.x - alignTo.width, width: alignTo.height, height: alignTo.width }; } // Compute the alignment box if (!inside) { if (inverted) { alignTo.x += below ? 0 : alignTo.width; alignTo.width = 0; } else { alignTo.y += below ? alignTo.height : 0; alignTo.height = 0; } } } // When alignment is undefined (typically columns and bars), display the // individual point below or above the point depending on the threshold options.align = pick(options.align, !inverted || inside ? 'center' : below ? 'right' : 'left'); options.verticalAlign = pick(options.verticalAlign, inverted || inside ? 'middle' : below ? 'top' : 'bottom'); // Call the parent method Series.prototype.alignDataLabel.call(this, point, dataLabel, options, alignTo, isNew); // If label was justified and we have contrast, set it: if (options.inside && point.contrastColor) { dataLabel.css({ color: point.contrastColor }); } }; } }); _registerModule(_modules, 'Extensions/OverlappingDataLabels.js', [_modules['Core/Chart/Chart.js'], _modules['Core/Utilities.js']], function (Chart, U) { /* * * * Highcharts module to hide overlapping data labels. * This module is included in Highcharts. * * (c) 2009-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var addEvent = U.addEvent, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, objectEach = U.objectEach, pick = U.pick; /** * Internal type * @private */ /* eslint-disable no-invalid-this */ // Collect potensial overlapping data labels. Stack labels probably don't need // to be considered because they are usually accompanied by data labels that lie // inside the columns. addEvent(Chart, 'render', function collectAndHide() { var chart = this, labels = []; // Consider external label collectors (this.labelCollectors || []).forEach(function (collector) { labels = labels.concat(collector()); }); (this.yAxis || []).forEach(function (yAxis) { if (yAxis.stacking && yAxis.options.stackLabels && !yAxis.options.stackLabels.allowOverlap) { objectEach(yAxis.stacking.stacks, function (stack) { objectEach(stack, function (stackItem) { if (stackItem.label && stackItem.label.visibility !== 'hidden' // #15607 ) { labels.push(stackItem.label); } }); }); } }); (this.series || []).forEach(function (series) { var dlOptions = series.options.dataLabels; if (series.visible && !(dlOptions.enabled === false && !series._hasPointLabels)) { // #3866 var push = function (points) { return points.forEach(function (point) { if (point.visible) { var dataLabels = (isArray(point.dataLabels) ? point.dataLabels : (point.dataLabel ? [point.dataLabel] : [])); dataLabels.forEach(function (label) { var options = label.options; label.labelrank = pick(options.labelrank, point.labelrank, point.shapeArgs && point.shapeArgs.height); // #4118 if (!options.allowOverlap) { labels.push(label); } else { // #13449 label.oldOpacity = label.opacity; label.newOpacity = 1; hideOrShow(label, chart); } }); } }); }; push(series.nodes || []); push(series.points); } }); this.hideOverlappingLabels(labels); }); /** * Hide overlapping labels. Labels are moved and faded in and out on zoom to * provide a smooth visual imression. * * @private * @function Highcharts.Chart#hideOverlappingLabels * @param {Array<Highcharts.SVGElement>} labels * Rendered data labels * @requires modules/overlapping-datalabels */ Chart.prototype.hideOverlappingLabels = function (labels) { var chart = this, len = labels.length, ren = chart.renderer, label, i, j, label1, label2, box1, box2, isLabelAffected = false, isIntersectRect = function (box1, box2) { return !(box2.x >= box1.x + box1.width || box2.x + box2.width <= box1.x || box2.y >= box1.y + box1.height || box2.y + box2.height <= box1.y); }, // Get the box with its position inside the chart, as opposed to getBBox // that only reports the position relative to the parent. getAbsoluteBox = function (label) { var pos, parent, bBox, // Substract the padding if no background or border (#4333) padding = label.box ? 0 : (label.padding || 0), lineHeightCorrection = 0, xOffset = 0, boxWidth, alignValue; if (label && (!label.alignAttr || label.placed)) { pos = label.alignAttr || { x: label.attr('x'), y: label.attr('y') }; parent = label.parentGroup; // Get width and height if pure text nodes (stack labels) if (!label.width) { bBox = label.getBBox(); label.width = bBox.width; label.height = bBox.height; // Labels positions are computed from top left corner, so // we need to substract the text height from text nodes too. lineHeightCorrection = ren .fontMetrics(null, label.element).h; } boxWidth = label.width - 2 * padding; alignValue = { left: '0', center: '0.5', right: '1' }[label.alignValue]; if (alignValue) { xOffset = +alignValue * boxWidth; } else if (isNumber(label.x) && Math.round(label.x) !== label.translateX) { xOffset = label.x - label.translateX; } return { x: pos.x + (parent.translateX || 0) + padding - (xOffset || 0), y: pos.y + (parent.translateY || 0) + padding - lineHeightCorrection, width: label.width - 2 * padding, height: label.height - 2 * padding }; } }; for (i = 0; i < len; i++) { label = labels[i]; if (label) { // Mark with initial opacity label.oldOpacity = label.opacity; label.newOpacity = 1; label.absoluteBox = getAbsoluteBox(label); } } // Prevent a situation in a gradually rising slope, that each label will // hide the previous one because the previous one always has lower rank. labels.sort(function (a, b) { return (b.labelrank || 0) - (a.labelrank || 0); }); // Detect overlapping labels for (i = 0; i < len; i++) { label1 = labels[i]; box1 = label1 && label1.absoluteBox; for (j = i + 1; j < len; ++j) { label2 = labels[j]; box2 = label2 && label2.absoluteBox; if (box1 && box2 && label1 !== label2 && // #6465, polar chart with connectEnds label1.newOpacity !== 0 && label2.newOpacity !== 0) { if (isIntersectRect(box1, box2)) { (label1.labelrank < label2.labelrank ? label1 : label2) .newOpacity = 0; } } } } // Hide or show labels.forEach(function (label) { if (hideOrShow(label, chart)) { isLabelAffected = true; } }); if (isLabelAffected) { fireEvent(chart, 'afterHideAllOverlappingLabels'); } }; /** * Hide or show labels based on opacity. * * @private * @function hideOrShow * @param {Highcharts.SVGElement} label * The label. * @param {Highcharts.Chart} chart * The chart that contains the label. * @return {boolean} */ function hideOrShow(label, chart) { var complete, newOpacity, isLabelAffected = false; if (label) { newOpacity = label.newOpacity; if (label.oldOpacity !== newOpacity) { // Make sure the label is completely hidden to avoid catching // clicks (#4362) if (label.alignAttr && label.placed) { // data labels label[newOpacity ? 'removeClass' : 'addClass']('highcharts-data-label-hidden'); complete = function () { if (!chart.styledMode) { label.css({ pointerEvents: newOpacity ? 'auto' : 'none' }); } }; isLabelAffected = true; // Animate or set the opacity label.alignAttr.opacity = newOpacity; label[label.isOld ? 'animate' : 'attr'](label.alignAttr, null, complete); fireEvent(chart, 'afterHideOverlappingLabel'); } else { // other labels, tick labels label.attr({ opacity: newOpacity }); } } label.isOld = true; } return isLabelAffected; } }); _registerModule(_modules, 'Core/Responsive.js', [_modules['Core/Utilities.js']], function (U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var extend = U.extend, find = U.find, isArray = U.isArray, isObject = U.isObject, merge = U.merge, objectEach = U.objectEach, pick = U.pick, splat = U.splat, uniqueKey = U.uniqueKey; /* * * * Class * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ var ResponsiveChart = /** @class */ (function () { function ResponsiveChart() { } /* * * * Functions * * */ /** * Get the current values for a given set of options. Used before we update * the chart with a new responsiveness rule. * * @todo Restore axis options (by id?). The matching of items in collections * bears resemblance to the oneToOne matching in Chart.update. Probably we * can refactor out that matching and reuse it in both functions. * * @private * @function Highcharts.Chart#currentOptions */ ResponsiveChart.prototype.currentOptions = function (options) { var chart = this, ret = {}; /** * Recurse over a set of options and its current values, * and store the current values in the ret object. */ function getCurrent(options, curr, ret, depth) { var i; objectEach(options, function (val, key) { if (!depth && chart.collectionsWithUpdate.indexOf(key) > -1 && curr[key]) { val = splat(val); ret[key] = []; // Iterate over collections like series, xAxis or yAxis and // map the items by index. for (i = 0; i < Math.max(val.length, curr[key].length); i++) { // Item exists in current data (#6347) if (curr[key][i]) { // If the item is missing from the new data, we need // to save the whole config structure. Like when // responsively updating from a dual axis layout to // a single axis and back (#13544). if (val[i] === void 0) { ret[key][i] = curr[key][i]; // Otherwise, proceed } else { ret[key][i] = {}; getCurrent(val[i], curr[key][i], ret[key][i], depth + 1); } } } } else if (isObject(val)) { ret[key] = isArray(val) ? [] : {}; getCurrent(val, curr[key] || {}, ret[key], depth + 1); } else if (typeof curr[key] === 'undefined') { // #10286 ret[key] = null; } else { ret[key] = curr[key]; } }); } getCurrent(options, this.options, ret, 0); return ret; }; /** * Handle a single responsiveness rule. * * @private * @function Highcharts.Chart#matchResponsiveRule * @param {Highcharts.ResponsiveRulesOptions} rule * @param {Array<string>} matches */ ResponsiveChart.prototype.matchResponsiveRule = function (rule, matches) { var condition = rule.condition, fn = condition.callback || function () { return (this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) && this.chartHeight <= pick(condition.maxHeight, Number.MAX_VALUE) && this.chartWidth >= pick(condition.minWidth, 0) && this.chartHeight >= pick(condition.minHeight, 0)); }; if (fn.call(this)) { matches.push(rule._id); } }; /** * Update the chart based on the current chart/document size and options for * responsiveness. * * @private * @function Highcharts.Chart#setResponsive * @param {boolean} [redraw=true] * @param {boolean} [reset=false] * Reset by un-applying all rules. Chart.update resets all rules before * applying updated options. */ ResponsiveChart.prototype.setResponsive = function (redraw, reset) { var options = this.options.responsive, currentResponsive = this.currentResponsive; var ruleIds = [], undoOptions; if (!reset && options && options.rules) { options.rules.forEach(function (rule) { if (typeof rule._id === 'undefined') { rule._id = uniqueKey(); } this.matchResponsiveRule(rule, ruleIds /* , redraw */); }, this); } // Merge matching rules var mergedOptions = merge.apply(void 0, ruleIds .map(function (ruleId) { return find((options || {}).rules || [], function (rule) { return (rule._id === ruleId); }); }) .map(function (rule) { return (rule && rule.chartOptions); })); mergedOptions.isResponsiveOptions = true; // Stringified key for the rules that currently apply. ruleIds = (ruleIds.toString() || void 0); var currentRuleIds = currentResponsive && currentResponsive.ruleIds; // Changes in what rules apply if (ruleIds !== currentRuleIds) { // Undo previous rules. Before we apply a new set of rules, we need // to roll back completely to base options (#6291). if (currentResponsive) { this.update(currentResponsive.undoOptions, redraw, true); } if (ruleIds) { // Get undo-options for matching rules undoOptions = this.currentOptions(mergedOptions); undoOptions.isResponsiveOptions = true; this.currentResponsive = { ruleIds: ruleIds, mergedOptions: mergedOptions, undoOptions: undoOptions }; this.update(mergedOptions, redraw, true); } else { this.currentResponsive = void 0; } } }; return ResponsiveChart; }()); /* * * * Composition * * */ var ResponsiveComposition = /** @class */ (function () { function ResponsiveComposition() { } /* * * * Static Functions * * */ ResponsiveComposition.compose = function (ChartClass) { extend(ChartClass.prototype, ResponsiveChart.prototype); return ChartClass; }; return ResponsiveComposition; }()); /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * A callback function to gain complete control on when the responsive rule * applies. * * @callback Highcharts.ResponsiveCallbackFunction * * @param {Highcharts.Chart} this * Chart context. * * @return {boolean} * Return `true` if it applies. */ (''); // detached doclets above /* * * * API Options * * */ /** * Allows setting a set of rules to apply for different screen or chart * sizes. Each rule specifies additional chart options. * * @sample {highstock} stock/demo/responsive/ * Stock chart * @sample highcharts/responsive/axis/ * Axis * @sample highcharts/responsive/legend/ * Legend * @sample highcharts/responsive/classname/ * Class name * * @since 5.0.0 * @apioption responsive */ /** * A set of rules for responsive settings. The rules are executed from * the top down. * * @sample {highcharts} highcharts/responsive/axis/ * Axis changes * @sample {highstock} highcharts/responsive/axis/ * Axis changes * @sample {highmaps} highcharts/responsive/axis/ * Axis changes * * @type {Array<*>} * @since 5.0.0 * @apioption responsive.rules */ /** * A full set of chart options to apply as overrides to the general * chart options. The chart options are applied when the given rule * is active. * * A special case is configuration objects that take arrays, for example * [xAxis](#xAxis), [yAxis](#yAxis) or [series](#series). For these * collections, an `id` option is used to map the new option set to * an existing object. If an existing object of the same id is not found, * the item of the same indexupdated. So for example, setting `chartOptions` * with two series items without an `id`, will cause the existing chart's * two series to be updated with respective options. * * @sample {highstock} stock/demo/responsive/ * Stock chart * @sample highcharts/responsive/axis/ * Axis * @sample highcharts/responsive/legend/ * Legend * @sample highcharts/responsive/classname/ * Class name * * @type {Highcharts.Options} * @since 5.0.0 * @apioption responsive.rules.chartOptions */ /** * Under which conditions the rule applies. * * @since 5.0.0 * @apioption responsive.rules.condition */ /** * A callback function to gain complete control on when the responsive * rule applies. Return `true` if it applies. This opens for checking * against other metrics than the chart size, for example the document * size or other elements. * * @type {Highcharts.ResponsiveCallbackFunction} * @since 5.0.0 * @context Highcharts.Chart * @apioption responsive.rules.condition.callback */ /** * The responsive rule applies if the chart height is less than this. * * @type {number} * @since 5.0.0 * @apioption responsive.rules.condition.maxHeight */ /** * The responsive rule applies if the chart width is less than this. * * @sample highcharts/responsive/axis/ * Max width is 500 * * @type {number} * @since 5.0.0 * @apioption responsive.rules.condition.maxWidth */ /** * The responsive rule applies if the chart height is greater than this. * * @type {number} * @default 0 * @since 5.0.0 * @apioption responsive.rules.condition.minHeight */ /** * The responsive rule applies if the chart width is greater than this. * * @type {number} * @default 0 * @since 5.0.0 * @apioption responsive.rules.condition.minWidth */ (''); // keeps doclets above in JS file return ResponsiveComposition; }); _registerModule(_modules, 'masters/highcharts.src.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Animation/Fx.js'], _modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Renderer/HTML/AST.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Renderer/SVG/SVGRenderer.js'], _modules['Core/Renderer/HTML/HTMLElement.js'], _modules['Core/Renderer/HTML/HTMLRenderer.js'], _modules['Core/Axis/Axis.js'], _modules['Core/Axis/PlotLineOrBand.js'], _modules['Core/Axis/Tick.js'], _modules['Core/Pointer.js'], _modules['Core/MSPointer.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Series/Series.js'], _modules['Core/Responsive.js'], _modules['Core/Color/Color.js'], _modules['Core/Time.js']], function (Highcharts, Utilities, DefaultOptions, Fx, Animation, AST, FormatUtilities, SVGElement, SVGRenderer, HTMLElement, HTMLRenderer, Axis, PlotLineOrBand, Tick, Pointer, MSPointer, Chart, Series, Responsive, Color, Time) { var G = Highcharts; // Animation G.animate = Animation.animate; G.animObject = Animation.animObject; G.getDeferredAnimation = Animation.getDeferredAnimation; G.setAnimation = Animation.setAnimation; G.stop = Animation.stop; G.timers = Fx.timers; // Classes G.AST = AST; G.Axis = Axis; G.Chart = Chart; G.chart = Chart.chart; G.Fx = Fx; G.PlotLineOrBand = PlotLineOrBand; G.Pointer = (MSPointer.isRequired() ? MSPointer : Pointer); G.Series = Series; G.SVGElement = SVGElement; G.SVGRenderer = SVGRenderer; G.Tick = Tick; G.Time = Time; // Color G.Color = Color; G.color = Color.parse; // Compositions HTMLRenderer.compose(SVGRenderer); HTMLElement.compose(SVGElement); // DefaultOptions G.defaultOptions = DefaultOptions.defaultOptions; G.getOptions = DefaultOptions.getOptions; G.time = DefaultOptions.defaultTime; G.setOptions = DefaultOptions.setOptions; // Format Utilities G.dateFormat = FormatUtilities.dateFormat; G.format = FormatUtilities.format; G.numberFormat = FormatUtilities.numberFormat; // Utilities G.addEvent = Utilities.addEvent; G.arrayMax = Utilities.arrayMax; G.arrayMin = Utilities.arrayMin; G.attr = Utilities.attr; G.clearTimeout = Utilities.clearTimeout; G.correctFloat = Utilities.correctFloat; G.createElement = Utilities.createElement; G.css = Utilities.css; G.defined = Utilities.defined; G.destroyObjectProperties = Utilities.destroyObjectProperties; G.discardElement = Utilities.discardElement; G.erase = Utilities.erase; G.error = Utilities.error; G.extend = Utilities.extend; G.extendClass = Utilities.extendClass; G.find = Utilities.find; G.fireEvent = Utilities.fireEvent; G.getMagnitude = Utilities.getMagnitude; G.getStyle = Utilities.getStyle; G.inArray = Utilities.inArray; G.isArray = Utilities.isArray; G.isClass = Utilities.isClass; G.isDOMElement = Utilities.isDOMElement; G.isFunction = Utilities.isFunction; G.isNumber = Utilities.isNumber; G.isObject = Utilities.isObject; G.isString = Utilities.isString; G.keys = Utilities.keys; G.merge = Utilities.merge; G.normalizeTickInterval = Utilities.normalizeTickInterval; G.objectEach = Utilities.objectEach; G.offset = Utilities.offset; G.pad = Utilities.pad; G.pick = Utilities.pick; G.pInt = Utilities.pInt; G.relativeLength = Utilities.relativeLength; G.removeEvent = Utilities.removeEvent; G.splat = Utilities.splat; G.stableSort = Utilities.stableSort; G.syncTimeout = Utilities.syncTimeout; G.timeUnits = Utilities.timeUnits; G.uniqueKey = Utilities.uniqueKey; G.useSerialIds = Utilities.useSerialIds; G.wrap = Utilities.wrap; // Compositions Responsive.compose(Chart); // Default Export return G; }); _registerModule(_modules, 'Core/Axis/NavigatorAxis.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var isTouchDevice = H.isTouchDevice; var addEvent = U.addEvent, correctFloat = U.correctFloat, defined = U.defined, isNumber = U.isNumber, pick = U.pick; /* eslint-disable valid-jsdoc */ /** * @private * @class */ var NavigatorAxisAdditions = /** @class */ (function () { /* * * * Constructors * * */ function NavigatorAxisAdditions(axis) { this.axis = axis; } /* * * * Functions * * */ /** * @private */ NavigatorAxisAdditions.prototype.destroy = function () { this.axis = void 0; }; /** * Add logic to normalize the zoomed range in order to preserve the pressed * state of range selector buttons * * @private * @function Highcharts.Axis#toFixedRange * @param {number} [pxMin] * @param {number} [pxMax] * @param {number} [fixedMin] * @param {number} [fixedMax] * @return {*} */ NavigatorAxisAdditions.prototype.toFixedRange = function (pxMin, pxMax, fixedMin, fixedMax) { var navigator = this; var axis = navigator.axis; var chart = axis.chart; var fixedRange = chart && chart.fixedRange, halfPointRange = (axis.pointRange || 0) / 2, newMin = pick(fixedMin, axis.translate(pxMin, true, !axis.horiz)), newMax = pick(fixedMax, axis.translate(pxMax, true, !axis.horiz)), changeRatio = fixedRange && (newMax - newMin) / fixedRange; // Add/remove half point range to/from the extremes (#1172) if (!defined(fixedMin)) { newMin = correctFloat(newMin + halfPointRange); } if (!defined(fixedMax)) { newMax = correctFloat(newMax - halfPointRange); } // If the difference between the fixed range and the actual requested // range is too great, the user is dragging across an ordinal gap, and // we need to release the range selector button. if (changeRatio > 0.7 && changeRatio < 1.3) { if (fixedMax) { newMin = newMax - fixedRange; } else { newMax = newMin + fixedRange; } } if (!isNumber(newMin) || !isNumber(newMax)) { // #1195, #7411 newMin = newMax = void 0; } return { min: newMin, max: newMax }; }; return NavigatorAxisAdditions; }()); /** * @private * @class */ var NavigatorAxis = /** @class */ (function () { function NavigatorAxis() { } /* * * * Static Functions * * */ /** * @private */ NavigatorAxis.compose = function (AxisClass) { AxisClass.keepProps.push('navigatorAxis'); /* eslint-disable no-invalid-this */ addEvent(AxisClass, 'init', function () { var axis = this; if (!axis.navigatorAxis) { axis.navigatorAxis = new NavigatorAxisAdditions(axis); } }); // For Stock charts, override selection zooming with some special // features because X axis zooming is already allowed by the Navigator // and Range selector. addEvent(AxisClass, 'zoom', function (e) { var axis = this; var chart = axis.chart; var chartOptions = chart.options; var navigator = chartOptions.navigator; var navigatorAxis = axis.navigatorAxis; var pinchType = chartOptions.chart.pinchType; var rangeSelector = chartOptions.rangeSelector; var zoomType = chartOptions.chart.zoomType; var previousZoom; if (axis.isXAxis && ((navigator && navigator.enabled) || (rangeSelector && rangeSelector.enabled))) { // For y only zooming, ignore the X axis completely if (zoomType === 'y') { e.zoomed = false; // For xy zooming, record the state of the zoom before zoom // selection, then when the reset button is pressed, revert to // this state. This should apply only if the chart is // initialized with a range (#6612), otherwise zoom all the way // out. } else if (((!isTouchDevice && zoomType === 'xy') || (isTouchDevice && pinchType === 'xy')) && axis.options.range) { previousZoom = navigatorAxis.previousZoom; if (defined(e.newMin)) { navigatorAxis.previousZoom = [axis.min, axis.max]; } else if (previousZoom) { e.newMin = previousZoom[0]; e.newMax = previousZoom[1]; navigatorAxis.previousZoom = void 0; } } } if (typeof e.zoomed !== 'undefined') { e.preventDefault(); } }); /* eslint-enable no-invalid-this */ }; /* * * * Static Properties * * */ /** * @private */ NavigatorAxis.AdditionsClass = NavigatorAxisAdditions; return NavigatorAxis; }()); return NavigatorAxis; }); _registerModule(_modules, 'Core/Axis/ScrollbarAxis.js', [_modules['Core/Utilities.js']], function (U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var addEvent = U.addEvent, defined = U.defined, pick = U.pick; /* * * * Composition * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * Creates scrollbars if enabled. * @private */ var ScrollbarAxis = /** @class */ (function () { function ScrollbarAxis() { } /** * Attaches to axis events to create scrollbars if enabled. * * @private * * @param AxisClass * Axis class to extend. * * @param ScrollbarClass * Scrollbar class to use. */ ScrollbarAxis.compose = function (AxisClass, ScrollbarClass) { var getExtremes = function (axis) { var axisMin = pick(axis.options && axis.options.min, axis.min); var axisMax = pick(axis.options && axis.options.max, axis.max); return { axisMin: axisMin, axisMax: axisMax, scrollMin: defined(axis.dataMin) ? Math.min(axisMin, axis.min, axis.dataMin, pick(axis.threshold, Infinity)) : axisMin, scrollMax: defined(axis.dataMax) ? Math.max(axisMax, axis.max, axis.dataMax, pick(axis.threshold, -Infinity)) : axisMax }; }; // Wrap axis initialization and create scrollbar if enabled: addEvent(AxisClass, 'afterInit', function () { var axis = this; if (axis.options && axis.options.scrollbar && axis.options.scrollbar.enabled) { // Predefined options: axis.options.scrollbar.vertical = !axis.horiz; axis.options.startOnTick = axis.options.endOnTick = false; axis.scrollbar = new ScrollbarClass(axis.chart.renderer, axis.options.scrollbar, axis.chart); addEvent(axis.scrollbar, 'changed', function (e) { var _a = getExtremes(axis), axisMin = _a.axisMin, axisMax = _a.axisMax, unitedMin = _a.scrollMin, unitedMax = _a.scrollMax, range = unitedMax - unitedMin, to, from; // #12834, scroll when show/hide series, wrong extremes if (!defined(axisMin) || !defined(axisMax)) { return; } if ((axis.horiz && !axis.reversed) || (!axis.horiz && axis.reversed)) { to = unitedMin + range * this.to; from = unitedMin + range * this.from; } else { // y-values in browser are reversed, but this also // applies for reversed horizontal axis: to = unitedMin + range * (1 - this.from); from = unitedMin + range * (1 - this.to); } if (this.shouldUpdateExtremes(e.DOMType)) { axis.setExtremes(from, to, true, e.DOMType !== 'mousemove' && e.DOMType !== 'touchmove', e); } else { // When live redraw is disabled, don't change extremes // Only change the position of the scollbar thumb this.setRange(this.from, this.to); } }); } }); // Wrap rendering axis, and update scrollbar if one is created: addEvent(AxisClass, 'afterRender', function () { var axis = this, _a = getExtremes(axis), scrollMin = _a.scrollMin, scrollMax = _a.scrollMax, scrollbar = axis.scrollbar, offset = axis.axisTitleMargin + (axis.titleOffset || 0), scrollbarsOffsets = axis.chart.scrollbarsOffsets, axisMargin = axis.options.margin || 0, offsetsIndex, from, to; if (scrollbar) { if (axis.horiz) { // Reserve space for labels/title if (!axis.opposite) { scrollbarsOffsets[1] += offset; } scrollbar.position(axis.left, axis.top + axis.height + 2 + scrollbarsOffsets[1] - (axis.opposite ? axisMargin : 0), axis.width, axis.height); // Next scrollbar should reserve space for margin (if set) if (!axis.opposite) { scrollbarsOffsets[1] += axisMargin; } offsetsIndex = 1; } else { // Reserve space for labels/title if (axis.opposite) { scrollbarsOffsets[0] += offset; } scrollbar.position(axis.left + axis.width + 2 + scrollbarsOffsets[0] - (axis.opposite ? 0 : axisMargin), axis.top, axis.width, axis.height); // Next scrollbar should reserve space for margin (if set) if (axis.opposite) { scrollbarsOffsets[0] += axisMargin; } offsetsIndex = 0; } scrollbarsOffsets[offsetsIndex] += scrollbar.size + scrollbar.options.margin; if (isNaN(scrollMin) || isNaN(scrollMax) || !defined(axis.min) || !defined(axis.max) || axis.min === axis.max // #10733 ) { // default action: when extremes are the same or there is // not extremes on the axis, but scrollbar exists, make it // full size scrollbar.setRange(0, 1); } else { from = (axis.min - scrollMin) / (scrollMax - scrollMin); to = (axis.max - scrollMin) / (scrollMax - scrollMin); if ((axis.horiz && !axis.reversed) || (!axis.horiz && axis.reversed)) { scrollbar.setRange(from, to); } else { // inverse vertical axis scrollbar.setRange(1 - to, 1 - from); } } } }); // Make space for a scrollbar: addEvent(AxisClass, 'afterGetOffset', function () { var axis = this, index = axis.horiz ? 2 : 1, scrollbar = axis.scrollbar; if (scrollbar) { axis.chart.scrollbarsOffsets = [0, 0]; // reset scrollbars offsets axis.chart.axisOffset[index] += scrollbar.size + scrollbar.options.margin; } }); return AxisClass; }; return ScrollbarAxis; }()); return ScrollbarAxis; }); _registerModule(_modules, 'Core/ScrollbarDefaults.js', [_modules['Core/Globals.js'], _modules['Core/Color/Palette.js']], function (H, Palette) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var isTouchDevice = H.isTouchDevice; /* * * * Constant * * */ /** * * The scrollbar is a means of panning over the X axis of a stock chart. * Scrollbars can also be applied to other types of axes. * * Another approach to scrollable charts is the [chart.scrollablePlotArea]( * https://api.highcharts.com/highcharts/chart.scrollablePlotArea) option that * is especially suitable for simpler cartesian charts on mobile. * * In styled mode, all the presentational options for the * scrollbar are replaced by the classes `.highcharts-scrollbar-thumb`, * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`, * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`. * * @sample stock/yaxis/inverted-bar-scrollbar/ * A scrollbar on a simple bar chart * * @product highstock gantt * @optionparent scrollbar * * @private */ var ScrollbarDefaults = { /** * The height of the scrollbar. The height also applies to the width * of the scroll arrows so that they are always squares. Defaults to * 20 for touch devices and 14 for mouse devices. * * @sample stock/scrollbar/height/ * A 30px scrollbar * * @type {number} * @default 20/14 */ height: isTouchDevice ? 20 : 14, /** * The border rounding radius of the bar. * * @sample stock/scrollbar/style/ * Scrollbar styling */ barBorderRadius: 0, /** * The corner radius of the scrollbar buttons. * * @sample stock/scrollbar/style/ * Scrollbar styling */ buttonBorderRadius: 0, /** * Enable or disable the scrollbar. * * @sample stock/scrollbar/enabled/ * Disable the scrollbar, only use navigator * * @type {boolean} * @default true * @apioption scrollbar.enabled */ /** * Whether to redraw the main chart as the scrollbar or the navigator * zoomed window is moved. Defaults to `true` for modern browsers and * `false` for legacy IE browsers as well as mobile devices. * * @sample stock/scrollbar/liveredraw * Setting live redraw to false * * @type {boolean} * @since 1.3 */ liveRedraw: void 0, /** * The margin between the scrollbar and its axis when the scrollbar is * applied directly to an axis. */ margin: 10, /** * The minimum width of the scrollbar. * * @since 1.2.5 */ minWidth: 6, /** * Whether to show or hide the scrollbar when the scrolled content is * zoomed out to it full extent. * * @type {boolean} * @default true * @apioption scrollbar.showFull */ step: 0.2, /** * The z index of the scrollbar group. */ zIndex: 3, /** * The background color of the scrollbar itself. * * @sample stock/scrollbar/style/ * Scrollbar styling * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ barBackgroundColor: Palette.neutralColor20, /** * The width of the bar's border. * * @sample stock/scrollbar/style/ * Scrollbar styling */ barBorderWidth: 1, /** * The color of the scrollbar's border. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ barBorderColor: Palette.neutralColor20, /** * The color of the small arrow inside the scrollbar buttons. * * @sample stock/scrollbar/style/ * Scrollbar styling * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ buttonArrowColor: Palette.neutralColor80, /** * The color of scrollbar buttons. * * @sample stock/scrollbar/style/ * Scrollbar styling * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ buttonBackgroundColor: Palette.neutralColor10, /** * The color of the border of the scrollbar buttons. * * @sample stock/scrollbar/style/ * Scrollbar styling * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ buttonBorderColor: Palette.neutralColor20, /** * The border width of the scrollbar buttons. * * @sample stock/scrollbar/style/ * Scrollbar styling */ buttonBorderWidth: 1, /** * The color of the small rifles in the middle of the scrollbar. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ rifleColor: Palette.neutralColor80, /** * The color of the track background. * * @sample stock/scrollbar/style/ * Scrollbar styling * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ trackBackgroundColor: Palette.neutralColor5, /** * The color of the border of the scrollbar track. * * @sample stock/scrollbar/style/ * Scrollbar styling * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ trackBorderColor: Palette.neutralColor5, /** * The corner radius of the border of the scrollbar track. * * @sample stock/scrollbar/style/ * Scrollbar styling * * @type {number} * @default 0 * @apioption scrollbar.trackBorderRadius */ /** * The width of the border of the scrollbar track. * * @sample stock/scrollbar/style/ * Scrollbar styling */ trackBorderWidth: 1 }; /* * * * Default Export * * */ return ScrollbarDefaults; }); _registerModule(_modules, 'Core/Scrollbar.js', [_modules['Core/DefaultOptions.js'], _modules['Core/Globals.js'], _modules['Core/Axis/ScrollbarAxis.js'], _modules['Core/ScrollbarDefaults.js'], _modules['Core/Utilities.js']], function (D, H, ScrollbarAxis, ScrollbarDefaults, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var defaultOptions = D.defaultOptions; var addEvent = U.addEvent, correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, fireEvent = U.fireEvent, merge = U.merge, pick = U.pick, removeEvent = U.removeEvent; /* * * * Constants * * */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * A reusable scrollbar, internally used in Highcharts Stock's * navigator and optionally on individual axes. * * @private * @class * @name Highcharts.Scrollbar * @param {Highcharts.SVGRenderer} renderer * @param {Highcharts.ScrollbarOptions} options * @param {Highcharts.Chart} chart */ var Scrollbar = /** @class */ (function () { /* * * * Constructors * * */ function Scrollbar(renderer, options, chart) { /* * * * Properties * * */ this._events = []; this.chart = void 0; this.chartX = 0; this.chartY = 0; this.from = 0; this.group = void 0; this.options = void 0; this.renderer = void 0; this.scrollbar = void 0; this.scrollbarButtons = []; this.scrollbarGroup = void 0; this.scrollbarLeft = 0; this.scrollbarRifles = void 0; this.scrollbarStrokeWidth = 1; this.scrollbarTop = 0; this.size = 0; this.to = 0; this.track = void 0; this.trackBorderWidth = 1; this.userOptions = void 0; this.x = 0; this.y = 0; this.init(renderer, options, chart); } /* * * * Static Functions * * */ Scrollbar.compose = function (AxisClass) { ScrollbarAxis.compose(AxisClass, Scrollbar); }; /** * When we have vertical scrollbar, rifles and arrow in buttons should be * rotated. The same method is used in Navigator's handles, to rotate them. * * @function Highcharts.swapXY * * @param {Highcharts.SVGPathArray} path * Path to be rotated. * * @param {boolean} [vertical] * If vertical scrollbar, swap x-y values. * * @return {Highcharts.SVGPathArray} * Rotated path. * * @requires modules/stock */ Scrollbar.swapXY = function (path, vertical) { if (vertical) { path.forEach(function (seg) { var len = seg.length; var temp; for (var i = 0; i < len; i += 2) { temp = seg[i + 1]; if (typeof temp === 'number') { seg[i + 1] = seg[i + 2]; seg[i + 2] = temp; } } }); } return path; }; /* * * * Functions * * */ /** * Set up the mouse and touch events for the Scrollbar * * @private * @function Highcharts.Scrollbar#addEvents */ Scrollbar.prototype.addEvents = function () { var buttonsOrder = this.options.inverted ? [1, 0] : [0, 1], buttons = this.scrollbarButtons, bar = this.scrollbarGroup.element, track = this.track.element, mouseDownHandler = this.mouseDownHandler.bind(this), mouseMoveHandler = this.mouseMoveHandler.bind(this), mouseUpHandler = this.mouseUpHandler.bind(this); // Mouse events var _events = [ [buttons[buttonsOrder[0]].element, 'click', this.buttonToMinClick.bind(this)], [buttons[buttonsOrder[1]].element, 'click', this.buttonToMaxClick.bind(this)], [track, 'click', this.trackClick.bind(this)], [bar, 'mousedown', mouseDownHandler], [bar.ownerDocument, 'mousemove', mouseMoveHandler], [bar.ownerDocument, 'mouseup', mouseUpHandler] ]; // Touch events if (H.hasTouch) { _events.push([bar, 'touchstart', mouseDownHandler], [bar.ownerDocument, 'touchmove', mouseMoveHandler], [bar.ownerDocument, 'touchend', mouseUpHandler]); } // Add them all _events.forEach(function (args) { addEvent.apply(null, args); }); this._events = _events; }; Scrollbar.prototype.buttonToMaxClick = function (e) { var scroller = this; var range = (scroller.to - scroller.from) * pick(scroller.options.step, 0.2); scroller.updatePosition(scroller.from + range, scroller.to + range); fireEvent(scroller, 'changed', { from: scroller.from, to: scroller.to, trigger: 'scrollbar', DOMEvent: e }); }; Scrollbar.prototype.buttonToMinClick = function (e) { var scroller = this; var range = correctFloat(scroller.to - scroller.from) * pick(scroller.options.step, 0.2); scroller.updatePosition(correctFloat(scroller.from - range), correctFloat(scroller.to - range)); fireEvent(scroller, 'changed', { from: scroller.from, to: scroller.to, trigger: 'scrollbar', DOMEvent: e }); }; /** * Get normalized (0-1) cursor position over the scrollbar * * @private * @function Highcharts.Scrollbar#cursorToScrollbarPosition * * @param {*} normalizedEvent * normalized event, with chartX and chartY values * * @return {Highcharts.Dictionary<number>} * Local position {chartX, chartY} */ Scrollbar.prototype.cursorToScrollbarPosition = function (normalizedEvent) { var scroller = this, options = scroller.options, minWidthDifference = options.minWidth > scroller.calculatedWidth ? options.minWidth : 0; // minWidth distorts translation return { chartX: (normalizedEvent.chartX - scroller.x - scroller.xOffset) / (scroller.barWidth - minWidthDifference), chartY: (normalizedEvent.chartY - scroller.y - scroller.yOffset) / (scroller.barWidth - minWidthDifference) }; }; /** * Destroys allocated elements. * * @private * @function Highcharts.Scrollbar#destroy * @return {void} */ Scrollbar.prototype.destroy = function () { var scroller = this, navigator = scroller.chart.scroller; // Disconnect events added in addEvents scroller.removeEvents(); // Destroy properties [ 'track', 'scrollbarRifles', 'scrollbar', 'scrollbarGroup', 'group' ].forEach(function (prop) { if (scroller[prop] && scroller[prop].destroy) { scroller[prop] = scroller[prop].destroy(); } }); // #6421, chart may have more scrollbars if (navigator && scroller === navigator.scrollbar) { navigator.scrollbar = null; // Destroy elements in collection destroyObjectProperties(navigator.scrollbarButtons); } }; /** * Draw the scrollbar buttons with arrows * * @private * @function Highcharts.Scrollbar#drawScrollbarButton * @param {number} index * 0 is left, 1 is right * @return {void} */ Scrollbar.prototype.drawScrollbarButton = function (index) { var scroller = this, renderer = scroller.renderer, scrollbarButtons = scroller.scrollbarButtons, options = scroller.options, size = scroller.size, group = renderer.g().add(scroller.group); var tempElem; scrollbarButtons.push(group); // Create a rectangle for the scrollbar button tempElem = renderer.rect() .addClass('highcharts-scrollbar-button') .add(group); // Presentational attributes if (!scroller.chart.styledMode) { tempElem.attr({ stroke: options.buttonBorderColor, 'stroke-width': options.buttonBorderWidth, fill: options.buttonBackgroundColor }); } // Place the rectangle based on the rendered stroke width tempElem.attr(tempElem.crisp({ x: -0.5, y: -0.5, width: size + 1, height: size + 1, r: options.buttonBorderRadius }, tempElem.strokeWidth())); // Button arrow tempElem = renderer .path(Scrollbar.swapXY([[ 'M', size / 2 + (index ? -1 : 1), size / 2 - 3 ], [ 'L', size / 2 + (index ? -1 : 1), size / 2 + 3 ], [ 'L', size / 2 + (index ? 2 : -2), size / 2 ]], options.vertical)) .addClass('highcharts-scrollbar-arrow') .add(scrollbarButtons[index]); if (!scroller.chart.styledMode) { tempElem.attr({ fill: options.buttonArrowColor }); } }; /** * @private * @function Highcharts.Scrollbar#init * @param {Highcharts.SVGRenderer} renderer * @param {Highcharts.ScrollbarOptions} options * @param {Highcharts.Chart} chart */ Scrollbar.prototype.init = function (renderer, options, chart) { var scroller = this; scroller.scrollbarButtons = []; scroller.renderer = renderer; scroller.userOptions = options; scroller.options = merge(ScrollbarDefaults, defaultOptions.scrollbar, options); scroller.chart = chart; // backward compatibility scroller.size = pick(scroller.options.size, scroller.options.height); // Init if (options.enabled) { scroller.render(); scroller.addEvents(); } }; Scrollbar.prototype.mouseDownHandler = function (e) { var scroller = this, normalizedEvent = scroller.chart.pointer.normalize(e), mousePosition = scroller.cursorToScrollbarPosition(normalizedEvent); scroller.chartX = mousePosition.chartX; scroller.chartY = mousePosition.chartY; scroller.initPositions = [scroller.from, scroller.to]; scroller.grabbedCenter = true; }; /** * Event handler for the mouse move event. * @private */ Scrollbar.prototype.mouseMoveHandler = function (e) { var scroller = this, normalizedEvent = scroller.chart.pointer.normalize(e), options = scroller.options, direction = options.vertical ? 'chartY' : 'chartX', initPositions = scroller.initPositions || []; var scrollPosition, chartPosition, change; // In iOS, a mousemove event with e.pageX === 0 is fired when // holding the finger down in the center of the scrollbar. This // should be ignored. if (scroller.grabbedCenter && // #4696, scrollbar failed on Android (!e.touches || e.touches[0][direction] !== 0)) { chartPosition = scroller.cursorToScrollbarPosition(normalizedEvent)[direction]; scrollPosition = scroller[direction]; change = chartPosition - scrollPosition; scroller.hasDragged = true; scroller.updatePosition(initPositions[0] + change, initPositions[1] + change); if (scroller.hasDragged) { fireEvent(scroller, 'changed', { from: scroller.from, to: scroller.to, trigger: 'scrollbar', DOMType: e.type, DOMEvent: e }); } } }; /** * Event handler for the mouse up event. * @private */ Scrollbar.prototype.mouseUpHandler = function (e) { var scroller = this; if (scroller.hasDragged) { fireEvent(scroller, 'changed', { from: scroller.from, to: scroller.to, trigger: 'scrollbar', DOMType: e.type, DOMEvent: e }); } scroller.grabbedCenter = scroller.hasDragged = scroller.chartX = scroller.chartY = null; }; /** * Position the scrollbar, method called from a parent with defined * dimensions. * * @private * @function Highcharts.Scrollbar#position * @param {number} x * x-position on the chart * @param {number} y * y-position on the chart * @param {number} width * width of the scrollbar * @param {number} height * height of the scorllbar * @return {void} */ Scrollbar.prototype.position = function (x, y, width, height) { var scroller = this, options = scroller.options, vertical = options.vertical, method = scroller.rendered ? 'animate' : 'attr'; var xOffset = height, yOffset = 0; scroller.x = x; scroller.y = y + this.trackBorderWidth; scroller.width = width; // width with buttons scroller.height = height; scroller.xOffset = xOffset; scroller.yOffset = yOffset; // If Scrollbar is a vertical type, swap options: if (vertical) { scroller.width = scroller.yOffset = width = yOffset = scroller.size; scroller.xOffset = xOffset = 0; scroller.barWidth = height - width * 2; // width without buttons scroller.x = x = x + scroller.options.margin; } else { scroller.height = scroller.xOffset = height = xOffset = scroller.size; scroller.barWidth = width - height * 2; // width without buttons scroller.y = scroller.y + scroller.options.margin; } // Set general position for a group: scroller.group[method]({ translateX: x, translateY: scroller.y }); // Resize background/track: scroller.track[method]({ width: width, height: height }); // Move right/bottom button ot it's place: scroller.scrollbarButtons[1][method]({ translateX: vertical ? 0 : width - xOffset, translateY: vertical ? height - yOffset : 0 }); }; /** * Removes the event handlers attached previously with addEvents. * * @private * @function Highcharts.Scrollbar#removeEvents * @return {void} */ Scrollbar.prototype.removeEvents = function () { this._events.forEach(function (args) { removeEvent.apply(null, args); }); this._events.length = 0; }; /** * Render scrollbar with all required items. * * @private * @function Highcharts.Scrollbar#render */ Scrollbar.prototype.render = function () { var scroller = this, renderer = scroller.renderer, options = scroller.options, size = scroller.size, styledMode = scroller.chart.styledMode, group = renderer.g('scrollbar').attr({ zIndex: options.zIndex, translateY: -99999 }).add(); // Draw the scrollbar group scroller.group = group; // Draw the scrollbar track: scroller.track = renderer.rect() .addClass('highcharts-scrollbar-track') .attr({ x: 0, r: options.trackBorderRadius || 0, height: size, width: size }).add(group); if (!styledMode) { scroller.track.attr({ fill: options.trackBackgroundColor, stroke: options.trackBorderColor, 'stroke-width': options.trackBorderWidth }); } scroller.trackBorderWidth = scroller.track.strokeWidth(); scroller.track.attr({ y: -this.trackBorderWidth % 2 / 2 }); // Draw the scrollbar itself scroller.scrollbarGroup = renderer.g().add(group); scroller.scrollbar = renderer.rect() .addClass('highcharts-scrollbar-thumb') .attr({ height: size, width: size, r: options.barBorderRadius || 0 }).add(scroller.scrollbarGroup); scroller.scrollbarRifles = renderer .path(Scrollbar.swapXY([ ['M', -3, size / 4], ['L', -3, 2 * size / 3], ['M', 0, size / 4], ['L', 0, 2 * size / 3], ['M', 3, size / 4], ['L', 3, 2 * size / 3] ], options.vertical)) .addClass('highcharts-scrollbar-rifles') .add(scroller.scrollbarGroup); if (!styledMode) { scroller.scrollbar.attr({ fill: options.barBackgroundColor, stroke: options.barBorderColor, 'stroke-width': options.barBorderWidth }); scroller.scrollbarRifles.attr({ stroke: options.rifleColor, 'stroke-width': 1 }); } scroller.scrollbarStrokeWidth = scroller.scrollbar.strokeWidth(); scroller.scrollbarGroup.translate(-scroller.scrollbarStrokeWidth % 2 / 2, -scroller.scrollbarStrokeWidth % 2 / 2); // Draw the buttons: scroller.drawScrollbarButton(0); scroller.drawScrollbarButton(1); }; /** * Set scrollbar size, with a given scale. * * @private * @function Highcharts.Scrollbar#setRange * @param {number} from * scale (0-1) where bar should start * @param {number} to * scale (0-1) where bar should end * @return {void} */ Scrollbar.prototype.setRange = function (from, to) { var scroller = this, options = scroller.options, vertical = options.vertical, minWidth = options.minWidth, fullWidth = scroller.barWidth, method = (this.rendered && !this.hasDragged && !(this.chart.navigator && this.chart.navigator.hasDragged)) ? 'animate' : 'attr'; if (!defined(fullWidth)) { return; } var toPX = fullWidth * Math.min(to, 1); var fromPX, newSize; from = Math.max(from, 0); fromPX = Math.ceil(fullWidth * from); scroller.calculatedWidth = newSize = correctFloat(toPX - fromPX); // We need to recalculate position, if minWidth is used if (newSize < minWidth) { fromPX = (fullWidth - minWidth + newSize) * from; newSize = minWidth; } var newPos = Math.floor(fromPX + scroller.xOffset + scroller.yOffset); var newRiflesPos = newSize / 2 - 0.5; // -0.5 -> rifle line width / 2 // Store current position: scroller.from = from; scroller.to = to; if (!vertical) { scroller.scrollbarGroup[method]({ translateX: newPos }); scroller.scrollbar[method]({ width: newSize }); scroller.scrollbarRifles[method]({ translateX: newRiflesPos }); scroller.scrollbarLeft = newPos; scroller.scrollbarTop = 0; } else { scroller.scrollbarGroup[method]({ translateY: newPos }); scroller.scrollbar[method]({ height: newSize }); scroller.scrollbarRifles[method]({ translateY: newRiflesPos }); scroller.scrollbarTop = newPos; scroller.scrollbarLeft = 0; } if (newSize <= 12) { scroller.scrollbarRifles.hide(); } else { scroller.scrollbarRifles.show(true); } // Show or hide the scrollbar based on the showFull setting if (options.showFull === false) { if (from <= 0 && to >= 1) { scroller.group.hide(); } else { scroller.group.show(); } } scroller.rendered = true; }; /** * Checks if the extremes should be updated in response to a scrollbar * change event. * * @private * @function Highcharts.Scrollbar#shouldUpdateExtremes * @param {string} [eventType] * @return {boolean} */ Scrollbar.prototype.shouldUpdateExtremes = function (eventType) { return (pick(this.options.liveRedraw, H.svg && !H.isTouchDevice && !this.chart.isBoosting) || // Mouseup always should change extremes eventType === 'mouseup' || eventType === 'touchend' || // Internal events !defined(eventType)); }; Scrollbar.prototype.trackClick = function (e) { var scroller = this; var normalizedEvent = scroller.chart.pointer.normalize(e), range = scroller.to - scroller.from, top = scroller.y + scroller.scrollbarTop, left = scroller.x + scroller.scrollbarLeft; if ((scroller.options.vertical && normalizedEvent.chartY > top) || (!scroller.options.vertical && normalizedEvent.chartX > left)) { // On the top or on the left side of the track: scroller.updatePosition(scroller.from + range, scroller.to + range); } else { // On the bottom or the right side of the track: scroller.updatePosition(scroller.from - range, scroller.to - range); } fireEvent(scroller, 'changed', { from: scroller.from, to: scroller.to, trigger: 'scrollbar', DOMEvent: e }); }; /** * Update the scrollbar with new options * * @private * @function Highcharts.Scrollbar#update * @param {Highcharts.ScrollbarOptions} options */ Scrollbar.prototype.update = function (options) { this.destroy(); this.init(this.chart.renderer, merge(true, this.options, options), this.chart); }; /** * Update position option in the Scrollbar, with normalized 0-1 scale * * @private * @function Highcharts.Scrollbar#updatePosition * @param {number} from * @param {number} to * @return {void} */ Scrollbar.prototype.updatePosition = function (from, to) { if (to > 1) { from = correctFloat(1 - correctFloat(to - from)); to = 1; } if (from < 0) { to = correctFloat(to - from); from = 0; } this.from = from; this.to = to; }; /* * * * Static Properties * * */ Scrollbar.defaultOptions = ScrollbarDefaults; return Scrollbar; }()); /* * * * Registry * * */ defaultOptions.scrollbar = merge(true, Scrollbar.defaultOptions, defaultOptions.scrollbar); /* * * * Default Export * * */ return Scrollbar; }); _registerModule(_modules, 'Core/Navigator.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Color/Color.js'], _modules['Core/Globals.js'], _modules['Core/Axis/NavigatorAxis.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Color/Palette.js'], _modules['Core/Renderer/RendererRegistry.js'], _modules['Core/Scrollbar.js'], _modules['Core/Series/Series.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (Axis, Chart, Color, H, NavigatorAxis, D, Palette, RendererRegistry, Scrollbar, Series, SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var color = Color.parse; var hasTouch = H.hasTouch, isTouchDevice = H.isTouchDevice; var defaultOptions = D.defaultOptions; var seriesTypes = SeriesRegistry.seriesTypes; var addEvent = U.addEvent, clamp = U.clamp, correctFloat = U.correctFloat, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, erase = U.erase, extend = U.extend, find = U.find, isArray = U.isArray, isNumber = U.isNumber, merge = U.merge, pick = U.pick, removeEvent = U.removeEvent, splat = U.splat; var defaultSeriesType, // Finding the min or max of a set of variables where we don't know if they // are defined, is a pattern that is repeated several places in Highcharts. // Consider making this a global utility method. numExt = function (extreme) { var args = []; for (var _i = 1; _i < arguments.length; _i++) { args[_i - 1] = arguments[_i]; } var numbers = [].filter.call(args, isNumber); if (numbers.length) { return Math[extreme].apply(0, numbers); } }; defaultSeriesType = typeof seriesTypes.areaspline === 'undefined' ? 'line' : 'areaspline'; extend(defaultOptions, { /** * Maximum range which can be set using the navigator's handles. * Opposite of [xAxis.minRange](#xAxis.minRange). * * @sample {highstock} stock/navigator/maxrange/ * Defined max and min range * * @type {number} * @since 6.0.0 * @product highstock gantt * @apioption xAxis.maxRange */ /** * The navigator is a small series below the main series, displaying * a view of the entire data set. It provides tools to zoom in and * out on parts of the data as well as panning across the dataset. * * @product highstock gantt * @optionparent navigator */ navigator: { /** * Whether the navigator and scrollbar should adapt to updated data * in the base X axis. When loading data async, as in the demo below, * this should be `false`. Otherwise new data will trigger navigator * redraw, which will cause unwanted looping. In the demo below, the * data in the navigator is set only once. On navigating, only the main * chart content is updated. * * @sample {highstock} stock/demo/lazy-loading/ * Set to false with async data loading * * @type {boolean} * @default true * @apioption navigator.adaptToUpdatedData */ /** * An integer identifying the index to use for the base series, or a * string representing the id of the series. * * **Note**: As of Highcharts 5.0, this is now a deprecated option. * Prefer [series.showInNavigator](#plotOptions.series.showInNavigator). * * @see [series.showInNavigator](#plotOptions.series.showInNavigator) * * @deprecated * @type {number|string} * @default 0 * @apioption navigator.baseSeries */ /** * Enable or disable the navigator. * * @sample {highstock} stock/navigator/enabled/ * Disable the navigator * * @type {boolean} * @default true * @apioption navigator.enabled */ /** * When the chart is inverted, whether to draw the navigator on the * opposite side. * * @type {boolean} * @default false * @since 5.0.8 * @apioption navigator.opposite */ /** * The height of the navigator. * * @sample {highstock} stock/navigator/height/ * A higher navigator */ height: 40, /** * The distance from the nearest element, the X axis or X axis labels. * * @sample {highstock} stock/navigator/margin/ * A margin of 2 draws the navigator closer to the X axis labels */ margin: 25, /** * Whether the mask should be inside the range marking the zoomed * range, or outside. In Highcharts Stock 1.x it was always `false`. * * @sample {highstock} stock/navigator/maskinside-false/ * False, mask outside * * @since 2.0 */ maskInside: true, /** * Options for the handles for dragging the zoomed area. * * @sample {highstock} stock/navigator/handles/ * Colored handles */ handles: { /** * Width for handles. * * @sample {highstock} stock/navigator/styled-handles/ * Styled handles * * @since 6.0.0 */ width: 7, /** * Height for handles. * * @sample {highstock} stock/navigator/styled-handles/ * Styled handles * * @since 6.0.0 */ height: 15, /** * Array to define shapes of handles. 0-index for left, 1-index for * right. * * Additionally, the URL to a graphic can be given on this form: * `url(graphic.png)`. Note that for the image to be applied to * exported charts, its URL needs to be accessible by the export * server. * * Custom callbacks for symbol path generation can also be added to * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then * used by its method name, as shown in the demo. * * @sample {highstock} stock/navigator/styled-handles/ * Styled handles * * @type {Array<string>} * @default ["navigator-handle", "navigator-handle"] * @since 6.0.0 */ symbols: ['navigator-handle', 'navigator-handle'], /** * Allows to enable/disable handles. * * @since 6.0.0 */ enabled: true, /** * The width for the handle border and the stripes inside. * * @sample {highstock} stock/navigator/styled-handles/ * Styled handles * * @since 6.0.0 * @apioption navigator.handles.lineWidth */ lineWidth: 1, /** * The fill for the handle. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ backgroundColor: Palette.neutralColor5, /** * The stroke for the handle border and the stripes inside. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} */ borderColor: Palette.neutralColor40 }, /** * The color of the mask covering the areas of the navigator series * that are currently not visible in the main series. The default * color is bluish with an opacity of 0.3 to see the series below. * * @see In styled mode, the mask is styled with the * `.highcharts-navigator-mask` and * `.highcharts-navigator-mask-inside` classes. * * @sample {highstock} stock/navigator/maskfill/ * Blue, semi transparent mask * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default rgba(102,133,194,0.3) */ maskFill: color(Palette.highlightColor60).setOpacity(0.3).get(), /** * The color of the line marking the currently zoomed area in the * navigator. * * @sample {highstock} stock/navigator/outline/ * 2px blue outline * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default #cccccc */ outlineColor: Palette.neutralColor20, /** * The width of the line marking the currently zoomed area in the * navigator. * * @see In styled mode, the outline stroke width is set with the * `.highcharts-navigator-outline` class. * * @sample {highstock} stock/navigator/outline/ * 2px blue outline * * @type {number} */ outlineWidth: 1, /** * Options for the navigator series. Available options are the same * as any series, documented at [plotOptions](#plotOptions.series) * and [series](#series). * * Unless data is explicitly defined on navigator.series, the data * is borrowed from the first series in the chart. * * Default series options for the navigator series are: * ```js * series: { * type: 'areaspline', * fillOpacity: 0.05, * dataGrouping: { * smoothed: true * }, * lineWidth: 1, * marker: { * enabled: false * } * } * ``` * * @see In styled mode, the navigator series is styled with the * `.highcharts-navigator-series` class. * * @sample {highstock} stock/navigator/series-data/ * Using a separate data set for the navigator * @sample {highstock} stock/navigator/series/ * A green navigator series * * @type {*|Array<*>|Highcharts.SeriesOptionsType|Array<Highcharts.SeriesOptionsType>} */ series: { /** * The type of the navigator series. * * Heads up: * In column-type navigator, zooming is limited to at least one * point with its `pointRange`. * * @sample {highstock} stock/navigator/column/ * Column type navigator * * @type {string} * @default {highstock} `areaspline` if defined, otherwise `line` * @default {gantt} gantt */ type: defaultSeriesType, /** * The fill opacity of the navigator series. */ fillOpacity: 0.05, /** * The pixel line width of the navigator series. */ lineWidth: 1, /** * @ignore-option */ compare: null, /** * Unless data is explicitly defined, the data is borrowed from the * first series in the chart. * * @type {Array<number|Array<number|string|null>|object|null>} * @product highstock * @apioption navigator.series.data */ /** * Data grouping options for the navigator series. * * @extends plotOptions.series.dataGrouping */ dataGrouping: { approximation: 'average', enabled: true, groupPixelWidth: 2, // Replace smoothed property by anchors, #12455. firstAnchor: 'firstPoint', anchor: 'middle', lastAnchor: 'lastPoint', // Day and week differs from plotOptions.series.dataGrouping units: [ ['millisecond', [1, 2, 5, 10, 20, 25, 50, 100, 200, 500]], ['second', [1, 2, 5, 10, 15, 30]], ['minute', [1, 2, 5, 10, 15, 30]], ['hour', [1, 2, 3, 4, 6, 8, 12]], ['day', [1, 2, 3, 4]], ['week', [1, 2, 3]], ['month', [1, 3, 6]], ['year', null] ] }, /** * Data label options for the navigator series. Data labels are * disabled by default on the navigator series. * * @extends plotOptions.series.dataLabels */ dataLabels: { enabled: false, zIndex: 2 // #1839 }, id: 'highcharts-navigator-series', className: 'highcharts-navigator-series', /** * Sets the fill color of the navigator series. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @apioption navigator.series.color */ /** * Line color for the navigator series. Allows setting the color * while disallowing the default candlestick setting. * * @type {Highcharts.ColorString|null} */ lineColor: null, marker: { enabled: false }, /** * Since Highcharts Stock v8, default value is the same as default * `pointRange` defined for a specific type (e.g. `null` for * column type). * * In Highcharts Stock version < 8, defaults to 0. * * @extends plotOptions.series.pointRange * @type {number|null} * @apioption navigator.series.pointRange */ /** * The threshold option. Setting it to 0 will make the default * navigator area series draw its area from the 0 value and up. * * @type {number|null} */ threshold: null }, /** * Options for the navigator X axis. Default series options for the * navigator xAxis are: * ```js * xAxis: { * tickWidth: 0, * lineWidth: 0, * gridLineWidth: 1, * tickPixelInterval: 200, * labels: { * align: 'left', * style: { * color: '#888' * }, * x: 3, * y: -4 * } * } * ``` * * @extends xAxis * @excluding linkedTo, maxZoom, minRange, opposite, range, scrollbar, * showEmpty, maxRange */ xAxis: { /** * Additional range on the right side of the xAxis. Works similar to * xAxis.maxPadding, but value is set in milliseconds. * Can be set for both, main xAxis and navigator's xAxis. * * @since 6.0.0 */ overscroll: 0, className: 'highcharts-navigator-xaxis', tickLength: 0, lineWidth: 0, gridLineColor: Palette.neutralColor10, gridLineWidth: 1, tickPixelInterval: 200, labels: { align: 'left', /** * @type {Highcharts.CSSObject} */ style: { /** @ignore */ color: Palette.neutralColor40 }, x: 3, y: -4 }, crosshair: false }, /** * Options for the navigator Y axis. Default series options for the * navigator yAxis are: * ```js * yAxis: { * gridLineWidth: 0, * startOnTick: false, * endOnTick: false, * minPadding: 0.1, * maxPadding: 0.1, * labels: { * enabled: false * }, * title: { * text: null * }, * tickWidth: 0 * } * ``` * * @extends yAxis * @excluding height, linkedTo, maxZoom, minRange, ordinal, range, * showEmpty, scrollbar, top, units, maxRange, minLength, * maxLength, resize */ yAxis: { className: 'highcharts-navigator-yaxis', gridLineWidth: 0, startOnTick: false, endOnTick: false, minPadding: 0.1, maxPadding: 0.1, labels: { enabled: false }, crosshair: false, title: { text: null }, tickLength: 0, tickWidth: 0 } } }); /* eslint-disable no-invalid-this, valid-jsdoc */ /** * Draw one of the handles on the side of the zoomed range in the navigator * * @private * @function Highcharts.Renderer#symbols.navigator-handle * @param {number} x * @param {number} y * @param {number} w * @param {number} h * @param {Highcharts.NavigatorHandlesOptions} options * @return {Highcharts.SVGPathArray} * Path to be used in a handle */ RendererRegistry.getRendererType().prototype.symbols['navigator-handle'] = function (_x, _y, _w, _h, options) { var halfWidth = (options && options.width || 0) / 2, markerPosition = Math.round(halfWidth / 3) + 0.5, height = options && options.height || 0; return [ ['M', -halfWidth - 1, 0.5], ['L', halfWidth, 0.5], ['L', halfWidth, height + 0.5], ['L', -halfWidth - 1, height + 0.5], ['L', -halfWidth - 1, 0.5], ['M', -markerPosition, 4], ['L', -markerPosition, height - 3], ['M', markerPosition - 1, 4], ['L', markerPosition - 1, height - 3] ]; }; /** * The Navigator class * * @private * @class * @name Highcharts.Navigator * * @param {Highcharts.Chart} chart * Chart object */ var Navigator = /** @class */ (function () { function Navigator(chart) { this.baseSeries = void 0; this.chart = void 0; this.handles = void 0; this.height = void 0; this.left = void 0; this.navigatorEnabled = void 0; this.navigatorGroup = void 0; this.navigatorOptions = void 0; this.navigatorSeries = void 0; this.navigatorSize = void 0; this.opposite = void 0; this.outline = void 0; this.outlineHeight = void 0; this.range = void 0; this.rendered = void 0; this.shades = void 0; this.size = void 0; this.top = void 0; this.xAxis = void 0; this.yAxis = void 0; this.zoomedMax = void 0; this.zoomedMin = void 0; this.init(chart); } /** * Draw one of the handles on the side of the zoomed range in the navigator * * @private * @function Highcharts.Navigator#drawHandle * * @param {number} x * The x center for the handle * * @param {number} index * 0 for left and 1 for right * * @param {boolean|undefined} inverted * flag for chart.inverted * * @param {string} verb * use 'animate' or 'attr' */ Navigator.prototype.drawHandle = function (x, index, inverted, verb) { var navigator = this, height = navigator.navigatorOptions.handles.height; // Place it navigator.handles[index][verb](inverted ? { translateX: Math.round(navigator.left + navigator.height / 2), translateY: Math.round(navigator.top + parseInt(x, 10) + 0.5 - height) } : { translateX: Math.round(navigator.left + parseInt(x, 10)), translateY: Math.round(navigator.top + navigator.height / 2 - height / 2 - 1) }); }; /** * Render outline around the zoomed range * * @private * @function Highcharts.Navigator#drawOutline * * @param {number} zoomedMin * in pixels position where zoomed range starts * * @param {number} zoomedMax * in pixels position where zoomed range ends * * @param {boolean|undefined} inverted * flag if chart is inverted * * @param {string} verb * use 'animate' or 'attr' */ Navigator.prototype.drawOutline = function (zoomedMin, zoomedMax, inverted, verb) { var navigator = this, maskInside = navigator.navigatorOptions.maskInside, outlineWidth = navigator.outline.strokeWidth(), halfOutline = outlineWidth / 2, outlineCorrection = (outlineWidth % 2) / 2, // #5800 outlineHeight = navigator.outlineHeight, scrollbarHeight = navigator.scrollbarHeight || 0, navigatorSize = navigator.size, left = navigator.left - scrollbarHeight, navigatorTop = navigator.top, verticalMin, path; if (inverted) { left -= halfOutline; verticalMin = navigatorTop + zoomedMax + outlineCorrection; zoomedMax = navigatorTop + zoomedMin + outlineCorrection; path = [ ['M', left + outlineHeight, navigatorTop - scrollbarHeight - outlineCorrection], ['L', left + outlineHeight, verticalMin], ['L', left, verticalMin], ['L', left, zoomedMax], ['L', left + outlineHeight, zoomedMax], ['L', left + outlineHeight, navigatorTop + navigatorSize + scrollbarHeight] ]; if (maskInside) { path.push(['M', left + outlineHeight, verticalMin - halfOutline], // upper left of zoomed range ['L', left + outlineHeight, zoomedMax + halfOutline] // upper right of z.r. ); } } else { zoomedMin += left + scrollbarHeight - outlineCorrection; zoomedMax += left + scrollbarHeight - outlineCorrection; navigatorTop += halfOutline; path = [ ['M', left, navigatorTop], ['L', zoomedMin, navigatorTop], ['L', zoomedMin, navigatorTop + outlineHeight], ['L', zoomedMax, navigatorTop + outlineHeight], ['L', zoomedMax, navigatorTop], ['L', left + navigatorSize + scrollbarHeight * 2, navigatorTop] // right ]; if (maskInside) { path.push(['M', zoomedMin - halfOutline, navigatorTop], // upper left of zoomed range ['L', zoomedMax + halfOutline, navigatorTop] // upper right of z.r. ); } } navigator.outline[verb]({ d: path }); }; /** * Render outline around the zoomed range * * @private * @function Highcharts.Navigator#drawMasks * * @param {number} zoomedMin * in pixels position where zoomed range starts * * @param {number} zoomedMax * in pixels position where zoomed range ends * * @param {boolean|undefined} inverted * flag if chart is inverted * * @param {string} verb * use 'animate' or 'attr' */ Navigator.prototype.drawMasks = function (zoomedMin, zoomedMax, inverted, verb) { var navigator = this, left = navigator.left, top = navigator.top, navigatorHeight = navigator.height, height, width, x, y; // Determine rectangle position & size // According to (non)inverted position: if (inverted) { x = [left, left, left]; y = [top, top + zoomedMin, top + zoomedMax]; width = [navigatorHeight, navigatorHeight, navigatorHeight]; height = [ zoomedMin, zoomedMax - zoomedMin, navigator.size - zoomedMax ]; } else { x = [left, left + zoomedMin, left + zoomedMax]; y = [top, top, top]; width = [ zoomedMin, zoomedMax - zoomedMin, navigator.size - zoomedMax ]; height = [navigatorHeight, navigatorHeight, navigatorHeight]; } navigator.shades.forEach(function (shade, i) { shade[verb]({ x: x[i], y: y[i], width: width[i], height: height[i] }); }); }; /** * Generate DOM elements for a navigator: * * - main navigator group * * - all shades * * - outline * * - handles * * @private * @function Highcharts.Navigator#renderElements */ Navigator.prototype.renderElements = function () { var navigator = this, navigatorOptions = navigator.navigatorOptions, maskInside = navigatorOptions.maskInside, chart = navigator.chart, inverted = chart.inverted, renderer = chart.renderer, navigatorGroup, mouseCursor = { cursor: inverted ? 'ns-resize' : 'ew-resize' }; // Create the main navigator group navigator.navigatorGroup = navigatorGroup = renderer.g('navigator') .attr({ zIndex: 8, visibility: 'hidden' }) .add(); // Create masks, each mask will get events and fill: [ !maskInside, maskInside, !maskInside ].forEach(function (hasMask, index) { navigator.shades[index] = renderer.rect() .addClass('highcharts-navigator-mask' + (index === 1 ? '-inside' : '-outside')) .add(navigatorGroup); if (!chart.styledMode) { navigator.shades[index] .attr({ fill: hasMask ? navigatorOptions.maskFill : 'rgba(0,0,0,0)' }) .css((index === 1) && mouseCursor); } }); // Create the outline: navigator.outline = renderer.path() .addClass('highcharts-navigator-outline') .add(navigatorGroup); if (!chart.styledMode) { navigator.outline.attr({ 'stroke-width': navigatorOptions.outlineWidth, stroke: navigatorOptions.outlineColor }); } // Create the handlers: if (navigatorOptions.handles.enabled) { [0, 1].forEach(function (index) { navigatorOptions.handles.inverted = chart.inverted; navigator.handles[index] = renderer.symbol(navigatorOptions.handles.symbols[index], -navigatorOptions.handles.width / 2 - 1, 0, navigatorOptions.handles.width, navigatorOptions.handles.height, navigatorOptions.handles); // zIndex = 6 for right handle, 7 for left. // Can't be 10, because of the tooltip in inverted chart #2908 navigator.handles[index].attr({ zIndex: 7 - index }) .addClass('highcharts-navigator-handle ' + 'highcharts-navigator-handle-' + ['left', 'right'][index]).add(navigatorGroup); if (!chart.styledMode) { var handlesOptions = navigatorOptions.handles; navigator.handles[index] .attr({ fill: handlesOptions.backgroundColor, stroke: handlesOptions.borderColor, 'stroke-width': handlesOptions.lineWidth }) .css(mouseCursor); } }); } }; /** * Update navigator * * @private * @function Highcharts.Navigator#update * * @param {Highcharts.NavigatorOptions} options * Options to merge in when updating navigator */ Navigator.prototype.update = function (options) { // Remove references to old navigator series in base series (this.series || []).forEach(function (series) { if (series.baseSeries) { delete series.baseSeries.navigatorSeries; } }); // Destroy and rebuild navigator this.destroy(); var chartOptions = this.chart.options; merge(true, chartOptions.navigator, this.options, options); this.init(this.chart); }; /** * Render the navigator * * @private * @function Highcharts.Navigator#render * @param {number} min * X axis value minimum * @param {number} max * X axis value maximum * @param {number} [pxMin] * Pixel value minimum * @param {number} [pxMax] * Pixel value maximum * @return {void} */ Navigator.prototype.render = function (min, max, pxMin, pxMax) { var navigator = this, chart = navigator.chart, navigatorWidth, scrollbarLeft, scrollbarTop, scrollbarHeight = navigator.scrollbarHeight, navigatorSize, xAxis = navigator.xAxis, pointRange = xAxis.pointRange || 0, scrollbarXAxis = xAxis.navigatorAxis.fake ? chart.xAxis[0] : xAxis, navigatorEnabled = navigator.navigatorEnabled, zoomedMin, zoomedMax, rendered = navigator.rendered, inverted = chart.inverted, verb, newMin, newMax, currentRange, minRange = chart.xAxis[0].minRange, maxRange = chart.xAxis[0].options.maxRange; // Don't redraw while moving the handles (#4703). if (this.hasDragged && !defined(pxMin)) { return; } min = correctFloat(min - pointRange / 2); max = correctFloat(max + pointRange / 2); // Don't render the navigator until we have data (#486, #4202, #5172). if (!isNumber(min) || !isNumber(max)) { // However, if navigator was already rendered, we may need to resize // it. For example hidden series, but visible navigator (#6022). if (rendered) { pxMin = 0; pxMax = pick(xAxis.width, scrollbarXAxis.width); } else { return; } } navigator.left = pick(xAxis.left, // in case of scrollbar only, without navigator chart.plotLeft + scrollbarHeight + (inverted ? chart.plotWidth : 0)); navigator.size = zoomedMax = navigatorSize = pick(xAxis.len, (inverted ? chart.plotHeight : chart.plotWidth) - 2 * scrollbarHeight); if (inverted) { navigatorWidth = scrollbarHeight; } else { navigatorWidth = navigatorSize + 2 * scrollbarHeight; } // Get the pixel position of the handles pxMin = pick(pxMin, xAxis.toPixels(min, true)); pxMax = pick(pxMax, xAxis.toPixels(max, true)); // Verify (#1851, #2238) if (!isNumber(pxMin) || Math.abs(pxMin) === Infinity) { pxMin = 0; pxMax = navigatorWidth; } // Are we below the minRange? (#2618, #6191) newMin = xAxis.toValue(pxMin, true); newMax = xAxis.toValue(pxMax, true); currentRange = Math.abs(correctFloat(newMax - newMin)); if (currentRange < minRange) { if (this.grabbedLeft) { pxMin = xAxis.toPixels(newMax - minRange - pointRange, true); } else if (this.grabbedRight) { pxMax = xAxis.toPixels(newMin + minRange + pointRange, true); } } else if (defined(maxRange) && correctFloat(currentRange - pointRange) > maxRange) { if (this.grabbedLeft) { pxMin = xAxis.toPixels(newMax - maxRange - pointRange, true); } else if (this.grabbedRight) { pxMax = xAxis.toPixels(newMin + maxRange + pointRange, true); } } // Handles are allowed to cross, but never exceed the plot area navigator.zoomedMax = clamp(Math.max(pxMin, pxMax), 0, zoomedMax); navigator.zoomedMin = clamp(navigator.fixedWidth ? navigator.zoomedMax - navigator.fixedWidth : Math.min(pxMin, pxMax), 0, zoomedMax); navigator.range = navigator.zoomedMax - navigator.zoomedMin; zoomedMax = Math.round(navigator.zoomedMax); zoomedMin = Math.round(navigator.zoomedMin); if (navigatorEnabled) { navigator.navigatorGroup.attr({ visibility: 'visible' }); // Place elements verb = rendered && !navigator.hasDragged ? 'animate' : 'attr'; navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb); navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb); if (navigator.navigatorOptions.handles.enabled) { navigator.drawHandle(zoomedMin, 0, inverted, verb); navigator.drawHandle(zoomedMax, 1, inverted, verb); } } if (navigator.scrollbar) { if (inverted) { scrollbarTop = navigator.top - scrollbarHeight; scrollbarLeft = navigator.left - scrollbarHeight + (navigatorEnabled || !scrollbarXAxis.opposite ? 0 : // Multiple axes has offsets: (scrollbarXAxis.titleOffset || 0) + // Self margin from the axis.title scrollbarXAxis.axisTitleMargin); scrollbarHeight = navigatorSize + 2 * scrollbarHeight; } else { scrollbarTop = navigator.top + (navigatorEnabled ? navigator.height : -scrollbarHeight); scrollbarLeft = navigator.left - scrollbarHeight; } // Reposition scrollbar navigator.scrollbar.position(scrollbarLeft, scrollbarTop, navigatorWidth, scrollbarHeight); // Keep scale 0-1 navigator.scrollbar.setRange( // Use real value, not rounded because range can be very small // (#1716) navigator.zoomedMin / (navigatorSize || 1), navigator.zoomedMax / (navigatorSize || 1)); } navigator.rendered = true; }; /** * Set up the mouse and touch events for the navigator * * @private * @function Highcharts.Navigator#addMouseEvents */ Navigator.prototype.addMouseEvents = function () { var navigator = this, chart = navigator.chart, container = chart.container, eventsToUnbind = [], mouseMoveHandler, mouseUpHandler; /** * Create mouse events' handlers. * Make them as separate functions to enable wrapping them: */ navigator.mouseMoveHandler = mouseMoveHandler = function (e) { navigator.onMouseMove(e); }; navigator.mouseUpHandler = mouseUpHandler = function (e) { navigator.onMouseUp(e); }; // Add shades and handles mousedown events eventsToUnbind = navigator.getPartsEvents('mousedown'); // Add mouse move and mouseup events. These are bind to doc/container, // because Navigator.grabbedSomething flags are stored in mousedown // events eventsToUnbind.push(addEvent(chart.renderTo, 'mousemove', mouseMoveHandler), addEvent(container.ownerDocument, 'mouseup', mouseUpHandler)); // Touch events if (hasTouch) { eventsToUnbind.push(addEvent(chart.renderTo, 'touchmove', mouseMoveHandler), addEvent(container.ownerDocument, 'touchend', mouseUpHandler)); eventsToUnbind.concat(navigator.getPartsEvents('touchstart')); } navigator.eventsToUnbind = eventsToUnbind; // Data events if (navigator.series && navigator.series[0]) { eventsToUnbind.push(addEvent(navigator.series[0].xAxis, 'foundExtremes', function () { chart.navigator.modifyNavigatorAxisExtremes(); })); } }; /** * Generate events for handles and masks * * @private * @function Highcharts.Navigator#getPartsEvents * * @param {string} eventName * Event name handler, 'mousedown' or 'touchstart' * * @return {Array<Function>} * An array of functions to remove navigator functions from the * events again. */ Navigator.prototype.getPartsEvents = function (eventName) { var navigator = this, events = []; ['shades', 'handles'].forEach(function (name) { navigator[name].forEach(function (navigatorItem, index) { events.push(addEvent(navigatorItem.element, eventName, function (e) { navigator[name + 'Mousedown'](e, index); })); }); }); return events; }; /** * Mousedown on a shaded mask, either: * * - will be stored for future drag&drop * * - will directly shift to a new range * * @private * @function Highcharts.Navigator#shadesMousedown * * @param {Highcharts.PointerEventObject} e * Mouse event * * @param {number} index * Index of a mask in Navigator.shades array */ Navigator.prototype.shadesMousedown = function (e, index) { e = this.chart.pointer.normalize(e); var navigator = this, chart = navigator.chart, xAxis = navigator.xAxis, zoomedMin = navigator.zoomedMin, navigatorPosition = navigator.left, navigatorSize = navigator.size, range = navigator.range, chartX = e.chartX, fixedMax, fixedMin, ext, left; // For inverted chart, swap some options: if (chart.inverted) { chartX = e.chartY; navigatorPosition = navigator.top; } if (index === 1) { // Store information for drag&drop navigator.grabbedCenter = chartX; navigator.fixedWidth = range; navigator.dragOffset = chartX - zoomedMin; } else { // Shift the range by clicking on shaded areas left = chartX - navigatorPosition - range / 2; if (index === 0) { left = Math.max(0, left); } else if (index === 2 && left + range >= navigatorSize) { left = navigatorSize - range; if (navigator.reversedExtremes) { // #7713 left -= range; fixedMin = navigator.getUnionExtremes().dataMin; } else { // #2293, #3543 fixedMax = navigator.getUnionExtremes().dataMax; } } if (left !== zoomedMin) { // it has actually moved navigator.fixedWidth = range; // #1370 ext = xAxis.navigatorAxis.toFixedRange(left, left + range, fixedMin, fixedMax); if (defined(ext.min)) { // #7411 chart.xAxis[0].setExtremes(Math.min(ext.min, ext.max), Math.max(ext.min, ext.max), true, null, // auto animation { trigger: 'navigator' }); } } } }; /** * Mousedown on a handle mask. * Will store necessary information for drag&drop. * * @private * @function Highcharts.Navigator#handlesMousedown * @param {Highcharts.PointerEventObject} e * Mouse event * @param {number} index * Index of a handle in Navigator.handles array * @return {void} */ Navigator.prototype.handlesMousedown = function (e, index) { e = this.chart.pointer.normalize(e); var navigator = this, chart = navigator.chart, baseXAxis = chart.xAxis[0], // For reversed axes, min and max are changed, // so the other extreme should be stored reverse = navigator.reversedExtremes; if (index === 0) { // Grab the left handle navigator.grabbedLeft = true; navigator.otherHandlePos = navigator.zoomedMax; navigator.fixedExtreme = reverse ? baseXAxis.min : baseXAxis.max; } else { // Grab the right handle navigator.grabbedRight = true; navigator.otherHandlePos = navigator.zoomedMin; navigator.fixedExtreme = reverse ? baseXAxis.max : baseXAxis.min; } chart.fixedRange = null; }; /** * Mouse move event based on x/y mouse position. * * @private * @function Highcharts.Navigator#onMouseMove * * @param {Highcharts.PointerEventObject} e * Mouse event */ Navigator.prototype.onMouseMove = function (e) { var navigator = this, chart = navigator.chart, left = navigator.left, navigatorSize = navigator.navigatorSize, range = navigator.range, dragOffset = navigator.dragOffset, inverted = chart.inverted, chartX; // In iOS, a mousemove event with e.pageX === 0 is fired when holding // the finger down in the center of the scrollbar. This should be // ignored. if (!e.touches || e.touches[0].pageX !== 0) { // #4696 e = chart.pointer.normalize(e); chartX = e.chartX; // Swap some options for inverted chart if (inverted) { left = navigator.top; chartX = e.chartY; } // Drag left handle or top handle if (navigator.grabbedLeft) { navigator.hasDragged = true; navigator.render(0, 0, chartX - left, navigator.otherHandlePos); // Drag right handle or bottom handle } else if (navigator.grabbedRight) { navigator.hasDragged = true; navigator.render(0, 0, navigator.otherHandlePos, chartX - left); // Drag scrollbar or open area in navigator } else if (navigator.grabbedCenter) { navigator.hasDragged = true; if (chartX < dragOffset) { // outside left chartX = dragOffset; // outside right } else if (chartX > navigatorSize + dragOffset - range) { chartX = navigatorSize + dragOffset - range; } navigator.render(0, 0, chartX - dragOffset, chartX - dragOffset + range); } if (navigator.hasDragged && navigator.scrollbar && pick(navigator.scrollbar.options.liveRedraw, // By default, don't run live redraw on VML, on touch // devices or if the chart is in boost. H.svg && !isTouchDevice && !this.chart.isBoosting)) { e.DOMType = e.type; // DOMType is for IE8 setTimeout(function () { navigator.onMouseUp(e); }, 0); } } }; /** * Mouse up event based on x/y mouse position. * * @private * @function Highcharts.Navigator#onMouseUp * @param {Highcharts.PointerEventObject} e * Mouse event * @return {void} */ Navigator.prototype.onMouseUp = function (e) { var navigator = this, chart = navigator.chart, xAxis = navigator.xAxis, scrollbar = navigator.scrollbar, DOMEvent = e.DOMEvent || e, inverted = chart.inverted, verb = navigator.rendered && !navigator.hasDragged ? 'animate' : 'attr', zoomedMax, zoomedMin, unionExtremes, fixedMin, fixedMax, ext; if ( // MouseUp is called for both, navigator and scrollbar (that order), // which causes calling afterSetExtremes twice. Prevent first call // by checking if scrollbar is going to set new extremes (#6334) (navigator.hasDragged && (!scrollbar || !scrollbar.hasDragged)) || e.trigger === 'scrollbar') { unionExtremes = navigator.getUnionExtremes(); // When dragging one handle, make sure the other one doesn't change if (navigator.zoomedMin === navigator.otherHandlePos) { fixedMin = navigator.fixedExtreme; } else if (navigator.zoomedMax === navigator.otherHandlePos) { fixedMax = navigator.fixedExtreme; } // Snap to right edge (#4076) if (navigator.zoomedMax === navigator.size) { fixedMax = navigator.reversedExtremes ? unionExtremes.dataMin : unionExtremes.dataMax; } // Snap to left edge (#7576) if (navigator.zoomedMin === 0) { fixedMin = navigator.reversedExtremes ? unionExtremes.dataMax : unionExtremes.dataMin; } ext = xAxis.navigatorAxis.toFixedRange(navigator.zoomedMin, navigator.zoomedMax, fixedMin, fixedMax); if (defined(ext.min)) { chart.xAxis[0].setExtremes(Math.min(ext.min, ext.max), Math.max(ext.min, ext.max), true, // Run animation when clicking buttons, scrollbar track etc, // but not when dragging handles or scrollbar navigator.hasDragged ? false : null, { trigger: 'navigator', triggerOp: 'navigator-drag', DOMEvent: DOMEvent // #1838 }); } } if (e.DOMType !== 'mousemove' && e.DOMType !== 'touchmove') { navigator.grabbedLeft = navigator.grabbedRight = navigator.grabbedCenter = navigator.fixedWidth = navigator.fixedExtreme = navigator.otherHandlePos = navigator.hasDragged = navigator.dragOffset = null; } // Update position of navigator shades, outline and handles (#12573) if (navigator.navigatorEnabled && isNumber(navigator.zoomedMin) && isNumber(navigator.zoomedMax)) { zoomedMin = Math.round(navigator.zoomedMin); zoomedMax = Math.round(navigator.zoomedMax); if (navigator.shades) { navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb); } if (navigator.outline) { navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb); } if (navigator.navigatorOptions.handles.enabled && Object.keys(navigator.handles).length === navigator.handles.length) { navigator.drawHandle(zoomedMin, 0, inverted, verb); navigator.drawHandle(zoomedMax, 1, inverted, verb); } } }; /** * Removes the event handlers attached previously with addEvents. * * @private * @function Highcharts.Navigator#removeEvents * @return {void} */ Navigator.prototype.removeEvents = function () { if (this.eventsToUnbind) { this.eventsToUnbind.forEach(function (unbind) { unbind(); }); this.eventsToUnbind = void 0; } this.removeBaseSeriesEvents(); }; /** * Remove data events. * * @private * @function Highcharts.Navigator#removeBaseSeriesEvents * @return {void} */ Navigator.prototype.removeBaseSeriesEvents = function () { var baseSeries = this.baseSeries || []; if (this.navigatorEnabled && baseSeries[0]) { if (this.navigatorOptions.adaptToUpdatedData !== false) { baseSeries.forEach(function (series) { removeEvent(series, 'updatedData', this.updatedDataHandler); }, this); } // We only listen for extremes-events on the first baseSeries if (baseSeries[0].xAxis) { removeEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes); } } }; /** * Initialize the Navigator object * * @private * @function Highcharts.Navigator#init * * @param {Highcharts.Chart} chart */ Navigator.prototype.init = function (chart) { var chartOptions = chart.options, navigatorOptions = chartOptions.navigator, navigatorEnabled = navigatorOptions.enabled, scrollbarOptions = chartOptions.scrollbar, scrollbarEnabled = scrollbarOptions.enabled, height = navigatorEnabled ? navigatorOptions.height : 0, scrollbarHeight = scrollbarEnabled ? scrollbarOptions.height : 0; this.handles = []; this.shades = []; this.chart = chart; this.setBaseSeries(); this.height = height; this.scrollbarHeight = scrollbarHeight; this.scrollbarEnabled = scrollbarEnabled; this.navigatorEnabled = navigatorEnabled; this.navigatorOptions = navigatorOptions; this.scrollbarOptions = scrollbarOptions; this.outlineHeight = height + scrollbarHeight; this.opposite = pick(navigatorOptions.opposite, Boolean(!navigatorEnabled && chart.inverted)); // #6262 var navigator = this, baseSeries = navigator.baseSeries, xAxisIndex = chart.xAxis.length, yAxisIndex = chart.yAxis.length, baseXaxis = baseSeries && baseSeries[0] && baseSeries[0].xAxis || chart.xAxis[0] || { options: {} }; chart.isDirtyBox = true; if (navigator.navigatorEnabled) { // an x axis is required for scrollbar also navigator.xAxis = new Axis(chart, merge({ // inherit base xAxis' break and ordinal options breaks: baseXaxis.options.breaks, ordinal: baseXaxis.options.ordinal }, navigatorOptions.xAxis, { id: 'navigator-x-axis', yAxis: 'navigator-y-axis', isX: true, type: 'datetime', index: xAxisIndex, isInternal: true, offset: 0, keepOrdinalPadding: true, startOnTick: false, endOnTick: false, minPadding: 0, maxPadding: 0, zoomEnabled: false }, chart.inverted ? { offsets: [scrollbarHeight, 0, -scrollbarHeight, 0], width: height } : { offsets: [0, -scrollbarHeight, 0, scrollbarHeight], height: height })); navigator.yAxis = new Axis(chart, merge(navigatorOptions.yAxis, { id: 'navigator-y-axis', alignTicks: false, offset: 0, index: yAxisIndex, isInternal: true, reversed: pick((navigatorOptions.yAxis && navigatorOptions.yAxis.reversed), (chart.yAxis[0] && chart.yAxis[0].reversed), false), zoomEnabled: false }, chart.inverted ? { width: height } : { height: height })); // If we have a base series, initialize the navigator series if (baseSeries || navigatorOptions.series.data) { navigator.updateNavigatorSeries(false); // If not, set up an event to listen for added series } else if (chart.series.length === 0) { navigator.unbindRedraw = addEvent(chart, 'beforeRedraw', function () { // We've got one, now add it as base if (chart.series.length > 0 && !navigator.series) { navigator.setBaseSeries(); navigator.unbindRedraw(); // reset } }); } navigator.reversedExtremes = (chart.inverted && !navigator.xAxis.reversed) || (!chart.inverted && navigator.xAxis.reversed); // Render items, so we can bind events to them: navigator.renderElements(); // Add mouse events navigator.addMouseEvents(); // in case of scrollbar only, fake an x axis to get translation } else { navigator.xAxis = { chart: chart, navigatorAxis: { fake: true }, translate: function (value, reverse) { var axis = chart.xAxis[0], ext = axis.getExtremes(), scrollTrackWidth = axis.len - 2 * scrollbarHeight, min = numExt('min', axis.options.min, ext.dataMin), valueRange = numExt('max', axis.options.max, ext.dataMax) - min; return reverse ? // from pixel to value (value * valueRange / scrollTrackWidth) + min : // from value to pixel scrollTrackWidth * (value - min) / valueRange; }, toPixels: function (value) { return this.translate(value); }, toValue: function (value) { return this.translate(value, true); } }; navigator.xAxis.navigatorAxis.axis = navigator.xAxis; navigator.xAxis.navigatorAxis.toFixedRange = (NavigatorAxis.AdditionsClass.prototype.toFixedRange.bind(navigator.xAxis.navigatorAxis)); } // Initialize the scrollbar if (chart.options.scrollbar.enabled) { chart.scrollbar = navigator.scrollbar = new Scrollbar(chart.renderer, merge(chart.options.scrollbar, { margin: navigator.navigatorEnabled ? 0 : 10, vertical: chart.inverted }), chart); addEvent(navigator.scrollbar, 'changed', function (e) { var range = navigator.size, to = range * this.to, from = range * this.from; navigator.hasDragged = navigator.scrollbar.hasDragged; navigator.render(0, 0, from, to); if (this.shouldUpdateExtremes(e.DOMType)) { setTimeout(function () { navigator.onMouseUp(e); }); } }); } // Add data events navigator.addBaseSeriesEvents(); // Add redraw events navigator.addChartEvents(); }; /** * Get the union data extremes of the chart - the outer data extremes of the * base X axis and the navigator axis. * * @private * @function Highcharts.Navigator#getUnionExtremes * @param {boolean} [returnFalseOnNoBaseSeries] * as the param says. * @return {Highcharts.Dictionary<(number|undefined)>|undefined} */ Navigator.prototype.getUnionExtremes = function (returnFalseOnNoBaseSeries) { var baseAxis = this.chart.xAxis[0], navAxis = this.xAxis, navAxisOptions = navAxis.options, baseAxisOptions = baseAxis.options, ret; if (!returnFalseOnNoBaseSeries || baseAxis.dataMin !== null) { ret = { dataMin: pick(// #4053 navAxisOptions && navAxisOptions.min, numExt('min', baseAxisOptions.min, baseAxis.dataMin, navAxis.dataMin, navAxis.min)), dataMax: pick(navAxisOptions && navAxisOptions.max, numExt('max', baseAxisOptions.max, baseAxis.dataMax, navAxis.dataMax, navAxis.max)) }; } return ret; }; /** * Set the base series and update the navigator series from this. With a bit * of modification we should be able to make this an API method to be called * from the outside * * @private * @function Highcharts.Navigator#setBaseSeries * @param {Highcharts.SeriesOptionsType} [baseSeriesOptions] * Additional series options for a navigator * @param {boolean} [redraw] * Whether to redraw after update. * @return {void} */ Navigator.prototype.setBaseSeries = function (baseSeriesOptions, redraw) { var chart = this.chart, baseSeries = this.baseSeries = []; baseSeriesOptions = (baseSeriesOptions || chart.options && chart.options.navigator.baseSeries || (chart.series.length ? // Find the first non-navigator series (#8430) find(chart.series, function (s) { return !s.options.isInternal; }).index : 0)); // Iterate through series and add the ones that should be shown in // navigator. (chart.series || []).forEach(function (series, i) { if ( // Don't include existing nav series !series.options.isInternal && (series.options.showInNavigator || (i === baseSeriesOptions || series.options.id === baseSeriesOptions) && series.options.showInNavigator !== false)) { baseSeries.push(series); } }); // When run after render, this.xAxis already exists if (this.xAxis && !this.xAxis.navigatorAxis.fake) { this.updateNavigatorSeries(true, redraw); } }; /** * Update series in the navigator from baseSeries, adding new if does not * exist. * * @private * @function Highcharts.Navigator.updateNavigatorSeries * @param {boolean} addEvents * @param {boolean} [redraw] * @return {void} */ Navigator.prototype.updateNavigatorSeries = function (addEvents, redraw) { var navigator = this, chart = navigator.chart, baseSeries = navigator.baseSeries, baseOptions, mergedNavSeriesOptions, chartNavigatorSeriesOptions = navigator.navigatorOptions.series, baseNavigatorOptions, navSeriesMixin = { enableMouseTracking: false, index: null, linkedTo: null, group: 'nav', padXAxis: false, xAxis: 'navigator-x-axis', yAxis: 'navigator-y-axis', showInLegend: false, stacking: void 0, isInternal: true, states: { inactive: { opacity: 1 } } }, // Remove navigator series that are no longer in the baseSeries navigatorSeries = navigator.series = (navigator.series || []).filter(function (navSeries) { var base = navSeries.baseSeries; if (baseSeries.indexOf(base) < 0) { // Not in array // If there is still a base series connected to this // series, remove event handler and reference. if (base) { removeEvent(base, 'updatedData', navigator.updatedDataHandler); delete base.navigatorSeries; } // Kill the nav series. It may already have been // destroyed (#8715). if (navSeries.chart) { navSeries.destroy(); } return false; } return true; }); // Go through each base series and merge the options to create new // series if (baseSeries && baseSeries.length) { baseSeries.forEach(function eachBaseSeries(base) { var linkedNavSeries = base.navigatorSeries, userNavOptions = extend( // Grab color and visibility from base as default { color: base.color, visible: base.visible }, !isArray(chartNavigatorSeriesOptions) ? chartNavigatorSeriesOptions : defaultOptions.navigator.series); // Don't update if the series exists in nav and we have disabled // adaptToUpdatedData. if (linkedNavSeries && navigator.navigatorOptions.adaptToUpdatedData === false) { return; } navSeriesMixin.name = 'Navigator ' + baseSeries.length; baseOptions = base.options || {}; baseNavigatorOptions = baseOptions.navigatorOptions || {}; // The dataLabels options are not merged correctly // if the settings are an array, #13847. userNavOptions.dataLabels = splat(userNavOptions.dataLabels); mergedNavSeriesOptions = merge(baseOptions, navSeriesMixin, userNavOptions, baseNavigatorOptions); // Once nav series type is resolved, pick correct pointRange mergedNavSeriesOptions.pointRange = pick( // Stricte set pointRange in options userNavOptions.pointRange, baseNavigatorOptions.pointRange, // Fallback to default values, e.g. `null` for column defaultOptions.plotOptions[mergedNavSeriesOptions.type || 'line'].pointRange); // Merge data separately. Do a slice to avoid mutating the // navigator options from base series (#4923). var navigatorSeriesData = baseNavigatorOptions.data || userNavOptions.data; navigator.hasNavigatorData = navigator.hasNavigatorData || !!navigatorSeriesData; mergedNavSeriesOptions.data = navigatorSeriesData || baseOptions.data && baseOptions.data.slice(0); // Update or add the series if (linkedNavSeries && linkedNavSeries.options) { linkedNavSeries.update(mergedNavSeriesOptions, redraw); } else { base.navigatorSeries = chart.initSeries(mergedNavSeriesOptions); base.navigatorSeries.baseSeries = base; // Store ref navigatorSeries.push(base.navigatorSeries); } }); } // If user has defined data (and no base series) or explicitly defined // navigator.series as an array, we create these series on top of any // base series. if (chartNavigatorSeriesOptions.data && !(baseSeries && baseSeries.length) || isArray(chartNavigatorSeriesOptions)) { navigator.hasNavigatorData = false; // Allow navigator.series to be an array chartNavigatorSeriesOptions = splat(chartNavigatorSeriesOptions); chartNavigatorSeriesOptions.forEach(function (userSeriesOptions, i) { navSeriesMixin.name = 'Navigator ' + (navigatorSeries.length + 1); mergedNavSeriesOptions = merge(defaultOptions.navigator.series, { // Since we don't have a base series to pull color from, // try to fake it by using color from series with same // index. Otherwise pull from the colors array. We need // an explicit color as otherwise updates will increment // color counter and we'll get a new color for each // update of the nav series. color: chart.series[i] && !chart.series[i].options.isInternal && chart.series[i].color || chart.options.colors[i] || chart.options.colors[0] }, navSeriesMixin, userSeriesOptions); mergedNavSeriesOptions.data = userSeriesOptions.data; if (mergedNavSeriesOptions.data) { navigator.hasNavigatorData = true; navigatorSeries.push(chart.initSeries(mergedNavSeriesOptions)); } }); } if (addEvents) { this.addBaseSeriesEvents(); } }; /** * Add data events. * For example when main series is updated we need to recalculate extremes * * @private * @function Highcharts.Navigator#addBaseSeriesEvent * @return {void} */ Navigator.prototype.addBaseSeriesEvents = function () { var navigator = this, baseSeries = navigator.baseSeries || []; // Bind modified extremes event to first base's xAxis only. // In event of > 1 base-xAxes, the navigator will ignore those. // Adding this multiple times to the same axis is no problem, as // duplicates should be discarded by the browser. if (baseSeries[0] && baseSeries[0].xAxis) { baseSeries[0].eventsToUnbind.push(addEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes)); } baseSeries.forEach(function (base) { // Link base series show/hide to navigator series visibility base.eventsToUnbind.push(addEvent(base, 'show', function () { if (this.navigatorSeries) { this.navigatorSeries.setVisible(true, false); } })); base.eventsToUnbind.push(addEvent(base, 'hide', function () { if (this.navigatorSeries) { this.navigatorSeries.setVisible(false, false); } })); // Respond to updated data in the base series, unless explicitily // not adapting to data changes. if (this.navigatorOptions.adaptToUpdatedData !== false) { if (base.xAxis) { base.eventsToUnbind.push(addEvent(base, 'updatedData', this.updatedDataHandler)); } } // Handle series removal base.eventsToUnbind.push(addEvent(base, 'remove', function () { if (this.navigatorSeries) { erase(navigator.series, this.navigatorSeries); if (defined(this.navigatorSeries.options)) { this.navigatorSeries.remove(false); } delete this.navigatorSeries; } })); }, this); }; /** * Get minimum from all base series connected to the navigator * @private * @param {number} currentSeriesMin * Minium from the current series * @return {number} Minimum from all series */ Navigator.prototype.getBaseSeriesMin = function (currentSeriesMin) { return this.baseSeries.reduce(function (min, series) { // (#10193) return Math.min(min, series.xData ? series.xData[0] : min); }, currentSeriesMin); }; /** * Set the navigator x axis extremes to reflect the total. The navigator * extremes should always be the extremes of the union of all series in the * chart as well as the navigator series. * * @private * @function Highcharts.Navigator#modifyNavigatorAxisExtremes */ Navigator.prototype.modifyNavigatorAxisExtremes = function () { var xAxis = this.xAxis, unionExtremes; if (typeof xAxis.getExtremes !== 'undefined') { unionExtremes = this.getUnionExtremes(true); if (unionExtremes && (unionExtremes.dataMin !== xAxis.min || unionExtremes.dataMax !== xAxis.max)) { xAxis.min = unionExtremes.dataMin; xAxis.max = unionExtremes.dataMax; } } }; /** * Hook to modify the base axis extremes with information from the Navigator * * @private * @function Highcharts.Navigator#modifyBaseAxisExtremes */ Navigator.prototype.modifyBaseAxisExtremes = function () { var baseXAxis = this, navigator = baseXAxis.chart.navigator, baseExtremes = baseXAxis.getExtremes(), baseMin = baseExtremes.min, baseMax = baseExtremes.max, baseDataMin = baseExtremes.dataMin, baseDataMax = baseExtremes.dataMax, range = baseMax - baseMin, stickToMin = navigator.stickToMin, stickToMax = navigator.stickToMax, overscroll = pick(baseXAxis.options.overscroll, 0), newMax, newMin, navigatorSeries = navigator.series && navigator.series[0], hasSetExtremes = !!baseXAxis.setExtremes, // When the extremes have been set by range selector button, don't // stick to min or max. The range selector buttons will handle the // extremes. (#5489) unmutable = baseXAxis.eventArgs && baseXAxis.eventArgs.trigger === 'rangeSelectorButton'; if (!unmutable) { // If the zoomed range is already at the min, move it to the right // as new data comes in if (stickToMin) { newMin = baseDataMin; newMax = newMin + range; } // If the zoomed range is already at the max, move it to the right // as new data comes in if (stickToMax) { newMax = baseDataMax + overscroll; // If stickToMin is true, the new min value is set above if (!stickToMin) { newMin = Math.max(baseDataMin, // don't go below data extremes (#13184) newMax - range, navigator.getBaseSeriesMin(navigatorSeries && navigatorSeries.xData ? navigatorSeries.xData[0] : -Number.MAX_VALUE)); } } // Update the extremes if (hasSetExtremes && (stickToMin || stickToMax)) { if (isNumber(newMin)) { baseXAxis.min = baseXAxis.userMin = newMin; baseXAxis.max = baseXAxis.userMax = newMax; } } } // Reset navigator.stickToMin = navigator.stickToMax = null; }; /** * Handler for updated data on the base series. When data is modified, the * navigator series must reflect it. This is called from the Chart.redraw * function before axis and series extremes are computed. * * @private * @function Highcharts.Navigator#updateDataHandler */ Navigator.prototype.updatedDataHandler = function () { var navigator = this.chart.navigator, baseSeries = this, navigatorSeries = this.navigatorSeries; // If the scrollbar is scrolled all the way to the right, keep right as // new data comes in. navigator.stickToMax = navigator.reversedExtremes ? Math.round(navigator.zoomedMin) === 0 : Math.round(navigator.zoomedMax) >= Math.round(navigator.size); navigator.stickToMin = navigator.shouldStickToMin(baseSeries, navigator); // Set the navigator series data to the new data of the base series if (navigatorSeries && !navigator.hasNavigatorData) { navigatorSeries.options.pointStart = baseSeries.xData[0]; navigatorSeries.setData(baseSeries.options.data, false, null, false); // #5414 } }; /** * Detect if the zoomed area should stick to the minimum, #14742. * * @private * @function Highcharts.Navigator#shouldStickToMin */ Navigator.prototype.shouldStickToMin = function (baseSeries, navigator) { var xDataMin = navigator.getBaseSeriesMin(baseSeries.xData[0]), xAxis = baseSeries.xAxis, max = xAxis.max, min = xAxis.min, range = xAxis.options.range; var stickToMin = true; if (isNumber(max) && isNumber(min)) { // If range declared, stick to the minimum only if the range // is smaller than the data set range. if (range && max - xDataMin > 0) { stickToMin = max - xDataMin < range && (!this.chart.fixedRange); } else { // If the current axis minimum falls outside the new // updated dataset, we must adjust. stickToMin = min <= xDataMin; } } return stickToMin; }; /** * Add chart events, like redrawing navigator, when chart requires that. * * @private * @function Highcharts.Navigator#addChartEvents * @return {void} */ Navigator.prototype.addChartEvents = function () { if (!this.eventsToUnbind) { this.eventsToUnbind = []; } this.eventsToUnbind.push( // Move the scrollbar after redraw, like after data updata even if // axes don't redraw addEvent(this.chart, 'redraw', function () { var navigator = this.navigator, xAxis = navigator && (navigator.baseSeries && navigator.baseSeries[0] && navigator.baseSeries[0].xAxis || this.xAxis[0]); // #5709, #13114 if (xAxis) { navigator.render(xAxis.min, xAxis.max); } }), // Make room for the navigator, can be placed around the chart: addEvent(this.chart, 'getMargins', function () { var chart = this, navigator = chart.navigator, marginName = navigator.opposite ? 'plotTop' : 'marginBottom'; if (chart.inverted) { marginName = navigator.opposite ? 'marginRight' : 'plotLeft'; } chart[marginName] = (chart[marginName] || 0) + (navigator.navigatorEnabled || !chart.inverted ? navigator.outlineHeight : 0) + navigator.navigatorOptions.margin; })); }; /** * Destroys allocated elements. * * @private * @function Highcharts.Navigator#destroy */ Navigator.prototype.destroy = function () { // Disconnect events added in addEvents this.removeEvents(); if (this.xAxis) { erase(this.chart.xAxis, this.xAxis); erase(this.chart.axes, this.xAxis); } if (this.yAxis) { erase(this.chart.yAxis, this.yAxis); erase(this.chart.axes, this.yAxis); } // Destroy series (this.series || []).forEach(function (s) { if (s.destroy) { s.destroy(); } }); // Destroy properties [ 'series', 'xAxis', 'yAxis', 'shades', 'outline', 'scrollbarTrack', 'scrollbarRifles', 'scrollbarGroup', 'scrollbar', 'navigatorGroup', 'rendered' ].forEach(function (prop) { if (this[prop] && this[prop].destroy) { this[prop].destroy(); } this[prop] = null; }, this); // Destroy elements in collection [this.handles].forEach(function (coll) { destroyObjectProperties(coll); }, this); }; return Navigator; }()); // End of prototype if (!H.Navigator) { H.Navigator = Navigator; NavigatorAxis.compose(Axis); // For Stock charts. For x only zooming, do not to create the zoom button // because X axis zooming is already allowed by the Navigator and Range // selector. (#9285) addEvent(Chart, 'beforeShowResetZoom', function () { var chartOptions = this.options, navigator = chartOptions.navigator, rangeSelector = chartOptions.rangeSelector; if (((navigator && navigator.enabled) || (rangeSelector && rangeSelector.enabled)) && ((!isTouchDevice && chartOptions.chart.zoomType === 'x') || (isTouchDevice && chartOptions.chart.pinchType === 'x'))) { return false; } }); // Initialize navigator for stock charts addEvent(Chart, 'beforeRender', function () { var options = this.options; if (options.navigator.enabled || options.scrollbar.enabled) { this.scroller = this.navigator = new Navigator(this); } }); // For stock charts, extend the Chart.setChartSize method so that we can set // the final top position of the navigator once the height of the chart, // including the legend, is determined. #367. We can't use Chart.getMargins, // because labels offsets are not calculated yet. addEvent(Chart, 'afterSetChartSize', function () { var legend = this.legend, navigator = this.navigator, scrollbarHeight, legendOptions, xAxis, yAxis; if (navigator) { legendOptions = legend && legend.options; xAxis = navigator.xAxis; yAxis = navigator.yAxis; scrollbarHeight = navigator.scrollbarHeight; // Compute the top position if (this.inverted) { navigator.left = navigator.opposite ? this.chartWidth - scrollbarHeight - navigator.height : this.spacing[3] + scrollbarHeight; navigator.top = this.plotTop + scrollbarHeight; } else { navigator.left = pick(xAxis.left, this.plotLeft + scrollbarHeight); navigator.top = navigator.navigatorOptions.top || this.chartHeight - navigator.height - scrollbarHeight - this.spacing[2] - (this.rangeSelector && this.extraBottomMargin ? this.rangeSelector.getHeight() : 0) - ((legendOptions && legendOptions.verticalAlign === 'bottom' && legendOptions.layout !== 'proximate' && // #13392 legendOptions.enabled && !legendOptions.floating) ? legend.legendHeight + pick(legendOptions.margin, 10) : 0) - (this.titleOffset ? this.titleOffset[2] : 0); } if (xAxis && yAxis) { // false if navigator is disabled (#904) if (this.inverted) { xAxis.options.left = yAxis.options.left = navigator.left; } else { xAxis.options.top = yAxis.options.top = navigator.top; } xAxis.setAxisSize(); yAxis.setAxisSize(); } } }); // Merge options, if no scrolling exists yet addEvent(Chart, 'update', function (e) { var navigatorOptions = (e.options.navigator || {}), scrollbarOptions = (e.options.scrollbar || {}); if (!this.navigator && !this.scroller && (navigatorOptions.enabled || scrollbarOptions.enabled)) { merge(true, this.options.navigator, navigatorOptions); merge(true, this.options.scrollbar, scrollbarOptions); delete e.options.navigator; delete e.options.scrollbar; } }); // Initialize navigator, if no scrolling exists yet addEvent(Chart, 'afterUpdate', function (event) { if (!this.navigator && !this.scroller && (this.options.navigator.enabled || this.options.scrollbar.enabled)) { this.scroller = this.navigator = new Navigator(this); if (pick(event.redraw, true)) { this.redraw(event.animation); // #7067 } } }); // Handle adding new series addEvent(Chart, 'afterAddSeries', function () { if (this.navigator) { // Recompute which series should be shown in navigator, and add them this.navigator.setBaseSeries(null, false); } }); // Handle updating series addEvent(Series, 'afterUpdate', function () { if (this.chart.navigator && !this.options.isInternal) { this.chart.navigator.setBaseSeries(null, false); } }); Chart.prototype.callbacks.push(function (chart) { var extremes, navigator = chart.navigator; // Initialize the navigator if (navigator && chart.xAxis[0]) { extremes = chart.xAxis[0].getExtremes(); navigator.render(extremes.min, extremes.max); } }); } H.Navigator = Navigator; return H.Navigator; }); _registerModule(_modules, 'Core/Axis/OrdinalAxis.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/Series/Series.js'], _modules['Core/Utilities.js']], function (Axis, Chart, H, Series, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var addEvent = U.addEvent, css = U.css, defined = U.defined, error = U.error, pick = U.pick, timeUnits = U.timeUnits; // Has a dependency on Navigator due to the use of Axis.toFixedRange /** * Extends the axis with ordinal support. * @private */ var OrdinalAxis; (function (OrdinalAxis) { /* * * * Classes * * */ /** * @private */ var Composition = /** @class */ (function () { /* * * * Constructors * * */ /** * @private */ function Composition(axis) { this.index = {}; this.axis = axis; } /* * * * Functions * * */ /** * Calculate the ordinal positions before tick positions are calculated. * * @private */ Composition.prototype.beforeSetTickPositions = function () { var axis = this.axis, ordinal = axis.ordinal, len, ordinalPositions = [], uniqueOrdinalPositions, useOrdinal = false, dist, extremes = axis.getExtremes(), min = extremes.min, max = extremes.max, minIndex, maxIndex, slope, hasBreaks = axis.isXAxis && !!axis.options.breaks, isOrdinal = axis.options.ordinal, overscrollPointsRange = Number.MAX_VALUE, ignoreHiddenSeries = axis.chart.options.chart.ignoreHiddenSeries, i, hasBoostedSeries; // Apply the ordinal logic if (isOrdinal || hasBreaks) { // #4167 YAxis is never ordinal ? axis.series.forEach(function (series, i) { uniqueOrdinalPositions = []; if ((!ignoreHiddenSeries || series.visible !== false) && (series.takeOrdinalPosition !== false || hasBreaks)) { // concatenate the processed X data into the existing // positions, or the empty array ordinalPositions = ordinalPositions.concat(series.processedXData); len = ordinalPositions.length; // remove duplicates (#1588) ordinalPositions.sort(function (a, b) { // without a custom function it is sorted as strings return a - b; }); overscrollPointsRange = Math.min(overscrollPointsRange, pick( // Check for a single-point series: series.closestPointRange, overscrollPointsRange)); if (len) { i = 0; while (i < len - 1) { if (ordinalPositions[i] !== ordinalPositions[i + 1]) { uniqueOrdinalPositions.push(ordinalPositions[i + 1]); } i++; } // Check first item: if (uniqueOrdinalPositions[0] !== ordinalPositions[0]) { uniqueOrdinalPositions.unshift(ordinalPositions[0]); } ordinalPositions = uniqueOrdinalPositions; } } if (series.isSeriesBoosting) { hasBoostedSeries = true; } }); if (hasBoostedSeries) { ordinalPositions.length = 0; } // cache the length len = ordinalPositions.length; // Check if we really need the overhead of mapping axis data // against the ordinal positions. If the series consist of // evenly spaced data any way, we don't need any ordinal logic. if (len > 2) { // two points have equal distance by default dist = ordinalPositions[1] - ordinalPositions[0]; i = len - 1; while (i-- && !useOrdinal) { if (ordinalPositions[i + 1] - ordinalPositions[i] !== dist) { useOrdinal = true; } } // When zooming in on a week, prevent axis padding for // weekends even though the data within the week is evenly // spaced. if (!axis.options.keepOrdinalPadding && (ordinalPositions[0] - min > dist || max - ordinalPositions[ordinalPositions.length - 1] > dist)) { useOrdinal = true; } } else if (axis.options.overscroll) { if (len === 2) { // Exactly two points, distance for overscroll is fixed: overscrollPointsRange = ordinalPositions[1] - ordinalPositions[0]; } else if (len === 1) { // We have just one point, closest distance is unknown. // Assume then it is last point and overscrolled range: overscrollPointsRange = axis.options.overscroll; ordinalPositions = [ ordinalPositions[0], ordinalPositions[0] + overscrollPointsRange ]; } else { // In case of zooming in on overscrolled range, stick to // the old range: overscrollPointsRange = ordinal.overscrollPointsRange; } } // Record the slope and offset to compute the linear values from // the array index. Since the ordinal positions may exceed the // current range, get the start and end positions within it // (#719, #665b) if (useOrdinal || axis.forceOrdinal) { if (axis.options.overscroll) { ordinal.overscrollPointsRange = overscrollPointsRange; ordinalPositions = ordinalPositions.concat(ordinal.getOverscrollPositions()); } // Register ordinal.positions = ordinalPositions; // This relies on the ordinalPositions being set. Use // Math.max and Math.min to prevent padding on either sides // of the data. minIndex = axis.ordinal2lin(// #5979 Math.max(min, ordinalPositions[0]), true); maxIndex = Math.max(axis.ordinal2lin(Math.min(max, ordinalPositions[ordinalPositions.length - 1]), true), 1); // #3339 // Set the slope and offset of the values compared to the // indices in the ordinal positions ordinal.slope = slope = (max - min) / (maxIndex - minIndex); ordinal.offset = min - (minIndex * slope); } else { ordinal.overscrollPointsRange = pick(axis.closestPointRange, ordinal.overscrollPointsRange); ordinal.positions = axis.ordinal.slope = ordinal.offset = void 0; } } axis.isOrdinal = isOrdinal && useOrdinal; // #3818, #4196, #4926 ordinal.groupIntervalFactor = null; // reset for next run }; /** * Get the ordinal positions for the entire data set. This is necessary * in chart panning because we need to find out what points or data * groups are available outside the visible range. When a panning * operation starts, if an index for the given grouping does not exists, * it is created and cached. This index is deleted on updated data, so * it will be regenerated the next time a panning operation starts. * * @private */ Composition.prototype.getExtendedPositions = function () { var ordinal = this, axis = ordinal.axis, axisProto = axis.constructor.prototype, chart = axis.chart, grouping = axis.series[0].currentDataGrouping, ordinalIndex = ordinal.index, key = grouping ? grouping.count + grouping.unitName : 'raw', overscroll = axis.options.overscroll, extremes = axis.getExtremes(), fakeAxis, fakeSeries; // If this is the first time, or the ordinal index is deleted by // updatedData, // create it. if (!ordinalIndex) { ordinalIndex = ordinal.index = {}; } if (!ordinalIndex[key]) { // Create a fake axis object where the extended ordinal // positions are emulated fakeAxis = { series: [], chart: chart, forceOrdinal: false, getExtremes: function () { return { min: extremes.dataMin, max: extremes.dataMax + overscroll }; }, getGroupPixelWidth: axisProto.getGroupPixelWidth, getTimeTicks: axisProto.getTimeTicks, options: { ordinal: true }, ordinal: { getGroupIntervalFactor: this.getGroupIntervalFactor }, ordinal2lin: axisProto.ordinal2lin, val2lin: axisProto.val2lin // #2590 }; fakeAxis.ordinal.axis = fakeAxis; // Add the fake series to hold the full data, then apply // processData to it axis.series.forEach(function (series) { fakeSeries = { xAxis: fakeAxis, xData: series.xData.slice(), chart: chart, destroyGroupedData: H.noop, getProcessedData: Series.prototype.getProcessedData }; fakeSeries.xData = fakeSeries.xData.concat(ordinal.getOverscrollPositions()); fakeSeries.options = { dataGrouping: grouping ? { enabled: true, forced: true, // doesn't matter which, use the fastest approximation: 'open', units: [[ grouping.unitName, [grouping.count] ]] } : { enabled: false } }; fakeAxis.series.push(fakeSeries); series.processData.apply(fakeSeries); // Force to use the ordinal when points are evenly spaced // (e.g. weeks), #3825. if (fakeSeries.closestPointRange !== fakeSeries.basePointRange && fakeSeries.currentDataGrouping) { fakeAxis.forceOrdinal = true; } }); // Run beforeSetTickPositions to compute the ordinalPositions axis.ordinal.beforeSetTickPositions.apply({ axis: fakeAxis }); // Cache it ordinalIndex[key] = fakeAxis.ordinal.positions; } return ordinalIndex[key]; }; /** * Find the factor to estimate how wide the plot area would have been if * ordinal gaps were included. This value is used to compute an imagined * plot width in order to establish the data grouping interval. * * A real world case is the intraday-candlestick example. Without this * logic, it would show the correct data grouping when viewing a range * within each day, but once moving the range to include the gap between * two days, the interval would include the cut-away night hours and the * data grouping would be wrong. So the below method tries to compensate * by identifying the most common point interval, in this case days. * * An opposite case is presented in issue #718. We have a long array of * daily data, then one point is appended one hour after the last point. * We expect the data grouping not to change. * * In the future, if we find cases where this estimation doesn't work * optimally, we might need to add a second pass to the data grouping * logic, where we do another run with a greater interval if the number * of data groups is more than a certain fraction of the desired group * count. * * @private */ Composition.prototype.getGroupIntervalFactor = function (xMin, xMax, series) { var ordinal = this, axis = ordinal.axis, i, processedXData = series.processedXData, len = processedXData.length, distances = [], median, groupIntervalFactor = ordinal.groupIntervalFactor; // Only do this computation for the first series, let the other // inherit it (#2416) if (!groupIntervalFactor) { // Register all the distances in an array for (i = 0; i < len - 1; i++) { distances[i] = processedXData[i + 1] - processedXData[i]; } // Sort them and find the median distances.sort(function (a, b) { return a - b; }); median = distances[Math.floor(len / 2)]; // Compensate for series that don't extend through the entire // axis extent. #1675. xMin = Math.max(xMin, processedXData[0]); xMax = Math.min(xMax, processedXData[len - 1]); ordinal.groupIntervalFactor = groupIntervalFactor = (len * median) / (xMax - xMin); } // Return the factor needed for data grouping return groupIntervalFactor; }; /** * Get ticks for an ordinal axis within a range where points don't * exist. It is required when overscroll is enabled. We can't base on * points, because we may not have any, so we use approximated * pointRange and generate these ticks between Axis.dataMax, * Axis.dataMax + Axis.overscroll evenly spaced. Used in panning and * navigator scrolling. * * @private */ Composition.prototype.getOverscrollPositions = function () { var ordinal = this, axis = ordinal.axis, extraRange = axis.options.overscroll, distance = ordinal.overscrollPointsRange, positions = [], max = axis.dataMax; if (defined(distance)) { // Max + pointRange because we need to scroll to the last positions.push(max); while (max <= axis.dataMax + extraRange) { max += distance; positions.push(max); } } return positions; }; /** * Make the tick intervals closer because the ordinal gaps make the * ticks spread out or cluster. * * @private */ Composition.prototype.postProcessTickInterval = function (tickInterval) { // Problem: https://jsfiddle.net/highcharts/FQm4E/1/ // This is a case where this algorithm doesn't work optimally. In // this case, the tick labels are spread out per week, but all the // gaps reside within weeks. So we have a situation where the labels // are courser than the ordinal gaps, and thus the tick interval // should not be altered. var ordinal = this, axis = ordinal.axis, ordinalSlope = ordinal.slope, ret; if (ordinalSlope) { if (!axis.options.breaks) { ret = tickInterval / (ordinalSlope / axis.closestPointRange); } else { ret = axis.closestPointRange || tickInterval; // #7275 } } else { ret = tickInterval; } return ret; }; return Composition; }()); OrdinalAxis.Composition = Composition; /* * * * Functions * * */ /** * Extends the axis with ordinal support. * * @private * * @param AxisClass * Axis class to extend. * * @param ChartClass * Chart class to use. * * @param SeriesClass * Series class to use. */ function compose(AxisClass, ChartClass, SeriesClass) { AxisClass.keepProps.push('ordinal'); var axisProto = AxisClass.prototype; /** * In an ordinal axis, there might be areas with dense consentrations of * points, then large gaps between some. Creating equally distributed * ticks over this entire range may lead to a huge number of ticks that * will later be removed. So instead, break the positions up in * segments, find the tick positions for each segment then concatenize * them. This method is used from both data grouping logic and X axis * tick position logic. * * @private */ AxisClass.prototype.getTimeTicks = function (normalizedInterval, min, max, startOfWeek, positions, closestDistance, findHigherRanks) { if (positions === void 0) { positions = []; } if (closestDistance === void 0) { closestDistance = 0; } var start = 0, end, segmentPositions, higherRanks = {}, hasCrossedHigherRank, info, posLength, outsideMax, groupPositions = [], lastGroupPosition = -Number.MAX_VALUE, tickPixelIntervalOption = this.options.tickPixelInterval, time = this.chart.time, // Record all the start positions of a segment, to use when // deciding what's a gap in the data. segmentStarts = []; // The positions are not always defined, for example for ordinal // positions when data has regular interval (#1557, #2090) if ((!this.options.ordinal && !this.options.breaks) || !positions || positions.length < 3 || typeof min === 'undefined') { return time.getTimeTicks.apply(time, arguments); } // Analyze the positions array to split it into segments on gaps // larger than 5 times the closest distance. The closest distance is // already found at this point, so we reuse that instead of // computing it again. posLength = positions.length; for (end = 0; end < posLength; end++) { outsideMax = end && positions[end - 1] > max; if (positions[end] < min) { // Set the last position before min start = end; } if (end === posLength - 1 || positions[end + 1] - positions[end] > closestDistance * 5 || outsideMax) { // For each segment, calculate the tick positions from the // getTimeTicks utility function. The interval will be the // same regardless of how long the segment is. if (positions[end] > lastGroupPosition) { // #1475 segmentPositions = time.getTimeTicks(normalizedInterval, positions[start], positions[end], startOfWeek); // Prevent duplicate groups, for example for multiple // segments within one larger time frame (#1475) while (segmentPositions.length && segmentPositions[0] <= lastGroupPosition) { segmentPositions.shift(); } if (segmentPositions.length) { lastGroupPosition = segmentPositions[segmentPositions.length - 1]; } segmentStarts.push(groupPositions.length); groupPositions = groupPositions.concat(segmentPositions); } // Set start of next segment start = end + 1; } if (outsideMax) { break; } } // Get the grouping info from the last of the segments. The info is // the same for all segments. if (segmentPositions) { info = segmentPositions.info; // Optionally identify ticks with higher rank, for example // when the ticks have crossed midnight. if (findHigherRanks && info.unitRange <= timeUnits.hour) { end = groupPositions.length - 1; // Compare points two by two for (start = 1; start < end; start++) { if (time.dateFormat('%d', groupPositions[start]) !== time.dateFormat('%d', groupPositions[start - 1])) { higherRanks[groupPositions[start]] = 'day'; hasCrossedHigherRank = true; } } // If the complete array has crossed midnight, we want // to mark the first positions also as higher rank if (hasCrossedHigherRank) { higherRanks[groupPositions[0]] = 'day'; } info.higherRanks = higherRanks; } // Save the info info.segmentStarts = segmentStarts; groupPositions.info = info; } else { error(12, false, this.chart); } // Don't show ticks within a gap in the ordinal axis, where the // space between two points is greater than a portion of the tick // pixel interval if (findHigherRanks && defined(tickPixelIntervalOption)) { var length_1 = groupPositions.length, i = length_1, itemToRemove = void 0, translated = void 0, translatedArr = [], lastTranslated = void 0, medianDistance = void 0, distance = void 0, distances = []; // Find median pixel distance in order to keep a reasonably even // distance between ticks (#748) while (i--) { translated = this.translate(groupPositions[i]); if (lastTranslated) { distances[i] = lastTranslated - translated; } translatedArr[i] = lastTranslated = translated; } distances.sort(); medianDistance = distances[Math.floor(distances.length / 2)]; if (medianDistance < tickPixelIntervalOption * 0.6) { medianDistance = null; } // Now loop over again and remove ticks where needed i = groupPositions[length_1 - 1] > max ? length_1 - 1 : length_1; // #817 lastTranslated = void 0; while (i--) { translated = translatedArr[i]; distance = Math.abs(lastTranslated - translated); // #4175 - when axis is reversed, the distance, is negative // but tickPixelIntervalOption positive, so we need to // compare the same values // Remove ticks that are closer than 0.6 times the pixel // interval from the one to the right, but not if it is // close to the median distance (#748). if (lastTranslated && distance < tickPixelIntervalOption * 0.8 && (medianDistance === null || distance < medianDistance * 0.8)) { // Is this a higher ranked position with a normal // position to the right? if (higherRanks[groupPositions[i]] && !higherRanks[groupPositions[i + 1]]) { // Yes: remove the lower ranked neighbour to the // right itemToRemove = i + 1; lastTranslated = translated; // #709 } else { // No: remove this one itemToRemove = i; } groupPositions.splice(itemToRemove, 1); } else { lastTranslated = translated; } } } return groupPositions; }; /** * Translate from linear (internal) to axis value. * * @private * @function Highcharts.Axis#lin2val * * @param {number} val * The linear abstracted value. * * @param {boolean} [fromIndex] * Translate from an index in the ordinal positions rather than a * value. * * @return {number} */ axisProto.lin2val = function (val, fromIndex) { var axis = this, ordinal = axis.ordinal, ordinalPositions = ordinal.positions, ret; // the visible range contains only equally spaced values if (!ordinalPositions) { ret = val; } else { var ordinalSlope = ordinal.slope, ordinalOffset = ordinal.offset, i = ordinalPositions.length - 1, linearEquivalentLeft = void 0, linearEquivalentRight = void 0, distance = void 0; // Handle the case where we translate from the index directly, // used only when panning an ordinal axis if (fromIndex) { if (val < 0) { // out of range, in effect panning to the left val = ordinalPositions[0]; } else if (val > i) { // out of range, panning to the right val = ordinalPositions[i]; } else { // split it up i = Math.floor(val); distance = val - i; // the decimal } // Loop down along the ordinal positions. When the linear // equivalent of i matches an ordinal position, interpolate // between the left and right values. } else { while (i--) { linearEquivalentLeft = (ordinalSlope * i) + ordinalOffset; if (val >= linearEquivalentLeft) { linearEquivalentRight = (ordinalSlope * (i + 1)) + ordinalOffset; // something between 0 and 1 distance = (val - linearEquivalentLeft) / (linearEquivalentRight - linearEquivalentLeft); break; } } } // If the index is within the range of the ordinal positions, // return the associated or interpolated value. If not, just // return the value. return (typeof distance !== 'undefined' && typeof ordinalPositions[i] !== 'undefined' ? ordinalPositions[i] + (distance ? distance * (ordinalPositions[i + 1] - ordinalPositions[i]) : 0) : val); } return ret; }; /** * Translate from a linear axis value to the corresponding ordinal axis * position. If there are no gaps in the ordinal axis this will be the * same. The translated value is the value that the point would have if * the axis were linear, using the same min and max. * * @private * @function Highcharts.Axis#val2lin * * @param {number} val * The axis value. * * @param {boolean} [toIndex] * Whether to return the index in the ordinalPositions or the new value. * * @return {number} */ axisProto.val2lin = function (val, toIndex) { var axis = this, ordinal = axis.ordinal, ordinalPositions = ordinal.positions, ret; if (!ordinalPositions) { ret = val; } else { var ordinalLength = ordinalPositions.length, i = void 0, distance = void 0, ordinalIndex = void 0; // first look for an exact match in the ordinalpositions array i = ordinalLength; while (i--) { if (ordinalPositions[i] === val) { ordinalIndex = i; break; } } // if that failed, find the intermediate position between the // two nearest values i = ordinalLength - 1; while (i--) { if (val > ordinalPositions[i] || i === 0) { // interpolate // something between 0 and 1 distance = (val - ordinalPositions[i]) / (ordinalPositions[i + 1] - ordinalPositions[i]); ordinalIndex = i + distance; break; } } ret = toIndex ? ordinalIndex : ordinal.slope * (ordinalIndex || 0) + ordinal.offset; } return ret; }; // Record this to prevent overwriting by broken-axis module (#5979) axisProto.ordinal2lin = axisProto.val2lin; /* eslint-disable no-invalid-this */ addEvent(AxisClass, 'afterInit', function () { var axis = this; if (!axis.ordinal) { axis.ordinal = new OrdinalAxis.Composition(axis); } }); addEvent(AxisClass, 'foundExtremes', function () { var axis = this; if (axis.isXAxis && defined(axis.options.overscroll) && axis.max === axis.dataMax && ( // Panning is an execption. We don't want to apply // overscroll when panning over the dataMax !axis.chart.mouseIsDown || axis.isInternal) && ( // Scrollbar buttons are the other execption: !axis.eventArgs || axis.eventArgs && axis.eventArgs.trigger !== 'navigator')) { axis.max += axis.options.overscroll; // Live data and buttons require translation for the min: if (!axis.isInternal && defined(axis.userMin)) { axis.min += axis.options.overscroll; } } }); // For ordinal axis, that loads data async, redraw axis after data is // loaded. If we don't do that, axis will have the same extremes as // previously, but ordinal positions won't be calculated. See #10290 addEvent(AxisClass, 'afterSetScale', function () { var axis = this; if (axis.horiz && !axis.isDirty) { axis.isDirty = axis.isOrdinal && axis.chart.navigator && !axis.chart.navigator.adaptToUpdatedData; } }); addEvent(AxisClass, 'initialAxisTranslation', function () { var axis = this; if (axis.ordinal) { axis.ordinal.beforeSetTickPositions(); axis.tickInterval = axis.ordinal.postProcessTickInterval(axis.tickInterval); } }); // Extending the Chart.pan method for ordinal axes addEvent(ChartClass, 'pan', function (e) { var chart = this, xAxis = chart.xAxis[0], overscroll = xAxis.options.overscroll, chartX = e.originalEvent.chartX, panning = chart.options.chart.panning, runBase = false; if (panning && panning.type !== 'y' && xAxis.options.ordinal && xAxis.series.length) { var mouseDownX = chart.mouseDownX, extremes = xAxis.getExtremes(), dataMax = extremes.dataMax, min = extremes.min, max = extremes.max, trimmedRange = void 0, hoverPoints = chart.hoverPoints, closestPointRange = (xAxis.closestPointRange || (xAxis.ordinal && xAxis.ordinal.overscrollPointsRange)), pointPixelWidth = (xAxis.translationSlope * (xAxis.ordinal.slope || closestPointRange)), // how many ordinal units did we move? movedUnits = (mouseDownX - chartX) / pointPixelWidth, // get index of all the chart's points extendedAxis = { ordinal: { positions: xAxis.ordinal.getExtendedPositions() } }, ordinalPositions = void 0, searchAxisLeft = void 0, lin2val = xAxis.lin2val, val2lin = xAxis.val2lin, searchAxisRight = void 0; // we have an ordinal axis, but the data is equally spaced if (!extendedAxis.ordinal.positions) { runBase = true; } else if (Math.abs(movedUnits) > 1) { // Remove active points for shared tooltip if (hoverPoints) { hoverPoints.forEach(function (point) { point.setState(); }); } if (movedUnits < 0) { searchAxisLeft = extendedAxis; searchAxisRight = xAxis.ordinal.positions ? xAxis : extendedAxis; } else { searchAxisLeft = xAxis.ordinal.positions ? xAxis : extendedAxis; searchAxisRight = extendedAxis; } // In grouped data series, the last ordinal position // represents the grouped data, which is to the left of the // real data max. If we don't compensate for this, we will // be allowed to pan grouped data series passed the right of // the plot area. ordinalPositions = searchAxisRight.ordinal.positions; if (dataMax > ordinalPositions[ordinalPositions.length - 1]) { ordinalPositions.push(dataMax); } // Get the new min and max values by getting the ordinal // index for the current extreme, then add the moved units // and translate back to values. This happens on the // extended ordinal positions if the new position is out of // range, else it happens on the current x axis which is // smaller and faster. chart.fixedRange = max - min; trimmedRange = xAxis.navigatorAxis.toFixedRange(null, null, lin2val.apply(searchAxisLeft, [ val2lin.apply(searchAxisLeft, [min, true]) + movedUnits, true // translate from index ]), lin2val.apply(searchAxisRight, [ val2lin.apply(searchAxisRight, [max, true]) + movedUnits, true // translate from index ])); // Apply it if it is within the available data range if (trimmedRange.min >= Math.min(extremes.dataMin, min) && trimmedRange.max <= Math.max(dataMax, max) + overscroll) { xAxis.setExtremes(trimmedRange.min, trimmedRange.max, true, false, { trigger: 'pan' }); } chart.mouseDownX = chartX; // set new reference for next run css(chart.container, { cursor: 'move' }); } } else { runBase = true; } // revert to the linear chart.pan version if (runBase || (panning && /y/.test(panning.type))) { if (overscroll) { xAxis.max = xAxis.dataMax + overscroll; } } else { e.preventDefault(); } }); addEvent(SeriesClass, 'updatedData', function () { var xAxis = this.xAxis; // Destroy the extended ordinal index on updated data if (xAxis && xAxis.options.ordinal) { delete xAxis.ordinal.index; } }); /* eslint-enable no-invalid-this */ } OrdinalAxis.compose = compose; })(OrdinalAxis || (OrdinalAxis = {})); OrdinalAxis.compose(Axis, Chart, Series); // @todo move to StockChart, remove from master return OrdinalAxis; }); _registerModule(_modules, 'Core/Axis/BrokenAxis.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Series/Series.js'], _modules['Extensions/Stacking.js'], _modules['Core/Utilities.js']], function (Axis, Series, StackItem, U) { /* * * * (c) 2009-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var addEvent = U.addEvent, find = U.find, fireEvent = U.fireEvent, isArray = U.isArray, isNumber = U.isNumber, pick = U.pick; /** * Axis with support of broken data rows. * @private * @class */ var BrokenAxis; (function (BrokenAxis) { /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Adds support for broken axes. * @private */ function compose(AxisClass, SeriesClass) { if (AxisClass.keepProps.indexOf('brokenAxis') === -1) { AxisClass.keepProps.push('brokenAxis'); var seriesProto = Series.prototype; seriesProto.drawBreaks = seriesDrawBreaks; seriesProto.gappedPath = seriesGappedPath; addEvent(AxisClass, 'init', onInit); addEvent(AxisClass, 'afterInit', onAfterInit); addEvent(AxisClass, 'afterSetTickPositions', onAfterSetTickPositions); addEvent(AxisClass, 'afterSetOptions', onAfterSetOptions); addEvent(SeriesClass, 'afterGeneratePoints', onSeriesAfterGeneratePoints); addEvent(SeriesClass, 'afterRender', onSeriesAfterRender); } return AxisClass; } BrokenAxis.compose = compose; /** * @private */ function onAfterInit() { if (typeof this.brokenAxis !== 'undefined') { this.brokenAxis.setBreaks(this.options.breaks, false); } } /** * Force Axis to be not-ordinal when breaks are defined. * @private */ function onAfterSetOptions() { var axis = this; if (axis.brokenAxis && axis.brokenAxis.hasBreaks) { axis.options.ordinal = false; } } /** * @private */ function onAfterSetTickPositions() { var axis = this, brokenAxis = axis.brokenAxis; if (brokenAxis && brokenAxis.hasBreaks) { var tickPositions = axis.tickPositions, info = axis.tickPositions.info, newPositions = []; for (var i = 0; i < tickPositions.length; i++) { if (!brokenAxis.isInAnyBreak(tickPositions[i])) { newPositions.push(tickPositions[i]); } } axis.tickPositions = newPositions; axis.tickPositions.info = info; } } /** * @private */ function onInit() { var axis = this; if (!axis.brokenAxis) { axis.brokenAxis = new Additions(axis); } } /** * @private */ function onSeriesAfterGeneratePoints() { var _a = this, isDirty = _a.isDirty, connectNulls = _a.options.connectNulls, points = _a.points, xAxis = _a.xAxis, yAxis = _a.yAxis; // Set, or reset visibility of the points. Axis.setBreaks marks // the series as isDirty if (isDirty) { var i = points.length; while (i--) { var point = points[i]; // Respect nulls inside the break (#4275) var nullGap = point.y === null && connectNulls === false; var isPointInBreak = (!nullGap && ((xAxis && xAxis.brokenAxis && xAxis.brokenAxis.isInAnyBreak(point.x, true)) || (yAxis && yAxis.brokenAxis && yAxis.brokenAxis.isInAnyBreak(point.y, true)))); // Set point.visible if in any break. // If not in break, reset visible to original value. point.visible = isPointInBreak ? false : point.options.visible !== false; } } } /** * @private */ function onSeriesAfterRender() { this.drawBreaks(this.xAxis, ['x']); this.drawBreaks(this.yAxis, pick(this.pointArrayMap, ['y'])); } /** * @private */ function seriesDrawBreaks(axis, keys) { var series = this, points = series.points; var breaks, threshold, eventName, y; if (axis && // #5950 axis.brokenAxis && axis.brokenAxis.hasBreaks) { var brokenAxis_1 = axis.brokenAxis; keys.forEach(function (key) { breaks = brokenAxis_1 && brokenAxis_1.breakArray || []; threshold = axis.isXAxis ? axis.min : pick(series.options.threshold, axis.min); points.forEach(function (point) { y = pick(point['stack' + key.toUpperCase()], point[key]); breaks.forEach(function (brk) { if (isNumber(threshold) && isNumber(y)) { eventName = false; if ((threshold < brk.from && y > brk.to) || (threshold > brk.from && y < brk.from)) { eventName = 'pointBreak'; } else if ((threshold < brk.from && y > brk.from && y < brk.to) || (threshold > brk.from && y > brk.to && y < brk.from)) { eventName = 'pointInBreak'; } if (eventName) { fireEvent(axis, eventName, { point: point, brk: brk }); } } }); }); }); } } /** * Extend getGraphPath by identifying gaps in the data so that we * can draw a gap in the line or area. This was moved from ordinal * axis module to broken axis module as of #5045. * * @private * @function Highcharts.Series#gappedPath * * @return {Highcharts.SVGPathArray} * Gapped path */ function seriesGappedPath() { var currentDataGrouping = this.currentDataGrouping, groupingSize = currentDataGrouping && currentDataGrouping.gapSize, points = this.points.slice(), yAxis = this.yAxis; var gapSize = this.options.gapSize, i = points.length - 1, stack; /** * Defines when to display a gap in the graph, together with the * [gapUnit](plotOptions.series.gapUnit) option. * * In case when `dataGrouping` is enabled, points can be grouped * into a larger time span. This can make the grouped points to * have a greater distance than the absolute value of `gapSize` * property, which will result in disappearing graph completely. * To prevent this situation the mentioned distance between * grouped points is used instead of previously defined * `gapSize`. * * In practice, this option is most often used to visualize gaps * in time series. In a stock chart, intraday data is available * for daytime hours, while gaps will appear in nights and * weekends. * * @see [gapUnit](plotOptions.series.gapUnit) * @see [xAxis.breaks](#xAxis.breaks) * * @sample {highstock} stock/plotoptions/series-gapsize/ * Setting the gap size to 2 introduces gaps for weekends in * daily datasets. * * @type {number} * @default 0 * @product highstock * @requires modules/broken-axis * @apioption plotOptions.series.gapSize */ /** * Together with [gapSize](plotOptions.series.gapSize), this * option defines where to draw gaps in the graph. * * When the `gapUnit` is `"relative"` (default), a gap size of 5 * means that if the distance between two points is greater than * 5 times that of the two closest points, the graph will be * broken. * * When the `gapUnit` is `"value"`, the gap is based on absolute * axis values, which on a datetime axis is milliseconds. This * also applies to the navigator series that inherits gap * options from the base series. * * @see [gapSize](plotOptions.series.gapSize) * * @type {string} * @default relative * @since 5.0.13 * @product highstock * @validvalue ["relative", "value"] * @requires modules/broken-axis * @apioption plotOptions.series.gapUnit */ if (gapSize && i > 0) { // #5008 // Gap unit is relative if (this.options.gapUnit !== 'value') { gapSize *= this.basePointRange; } // Setting a new gapSize in case dataGrouping is enabled // (#7686) if (groupingSize && groupingSize > gapSize && // Except when DG is forced (e.g. from other series) // and has lower granularity than actual points (#11351) groupingSize >= this.basePointRange) { gapSize = groupingSize; } // extension for ordinal breaks var current = void 0, next = void 0; while (i--) { // Reassign next if it is not visible if (!(next && next.visible !== false)) { next = points[i + 1]; } current = points[i]; // Skip iteration if one of the points is not visible if (next.visible === false || current.visible === false) { continue; } if (next.x - current.x > gapSize) { var xRange = (current.x + next.x) / 2; points.splice(// insert after this one i + 1, 0, { isNull: true, x: xRange }); // For stacked chart generate empty stack items, // #6546 if (yAxis.stacking && this.options.stacking) { stack = yAxis.stacking.stacks[this.stackKey][xRange] = new StackItem(yAxis, yAxis.options .stackLabels, false, xRange, this.stack); stack.total = 0; } } // Assign current to next for the upcoming iteration next = current; } } // Call base method return this.getGraphPath(points); } /* * * * Class * * */ /** * Provides support for broken axes. * @private * @class */ var Additions = /** @class */ (function () { /* * * * Constructors * * */ function Additions(axis) { this.hasBreaks = false; this.axis = axis; } /* * * * Static Functions * * */ /** * @private */ Additions.isInBreak = function (brk, val) { var repeat = brk.repeat || Infinity, from = brk.from, length = brk.to - brk.from, test = (val >= from ? (val - from) % repeat : repeat - ((from - val) % repeat)); var ret; if (!brk.inclusive) { ret = test < length && test !== 0; } else { ret = test <= length; } return ret; }; /** * @private */ Additions.lin2Val = function (val) { var axis = this; var brokenAxis = axis.brokenAxis; var breakArray = brokenAxis && brokenAxis.breakArray; if (!breakArray || !isNumber(val)) { return val; } var nval = val, brk, i; for (i = 0; i < breakArray.length; i++) { brk = breakArray[i]; if (brk.from >= nval) { break; } else if (brk.to < nval) { nval += brk.len; } else if (Additions.isInBreak(brk, nval)) { nval += brk.len; } } return nval; }; /** * @private */ Additions.val2Lin = function (val) { var axis = this; var brokenAxis = axis.brokenAxis; var breakArray = brokenAxis && brokenAxis.breakArray; if (!breakArray || !isNumber(val)) { return val; } var nval = val, brk, i; for (i = 0; i < breakArray.length; i++) { brk = breakArray[i]; if (brk.to <= val) { nval -= brk.len; } else if (brk.from >= val) { break; } else if (Additions.isInBreak(brk, val)) { nval -= (val - brk.from); break; } } return nval; }; /* * * * Functions * * */ /** * Returns the first break found where the x is larger then break.from * and smaller then break.to. * * @param {number} x * The number which should be within a break. * * @param {Array<Highcharts.XAxisBreaksOptions>} breaks * The array of breaks to search within. * * @return {Highcharts.XAxisBreaksOptions|undefined} * Returns the first break found that matches, returns false if no break * is found. */ Additions.prototype.findBreakAt = function (x, breaks) { return find(breaks, function (b) { return b.from < x && x < b.to; }); }; /** * @private */ Additions.prototype.isInAnyBreak = function (val, testKeep) { var brokenAxis = this, axis = brokenAxis.axis, breaks = axis.options.breaks || []; var i = breaks.length, inbrk, keep, ret; if (i && isNumber(val)) { while (i--) { if (Additions.isInBreak(breaks[i], val)) { inbrk = true; if (!keep) { keep = pick(breaks[i].showPoints, !axis.isXAxis); } } } if (inbrk && testKeep) { ret = inbrk && !keep; } else { ret = inbrk; } } return ret; }; /** * Dynamically set or unset breaks in an axis. This function in lighter * than usin Axis.update, and it also preserves animation. * * @private * @function Highcharts.Axis#setBreaks * * @param {Array<Highcharts.XAxisBreaksOptions>} [breaks] * The breaks to add. When `undefined` it removes existing breaks. * * @param {boolean} [redraw=true] * Whether to redraw the chart immediately. */ Additions.prototype.setBreaks = function (breaks, redraw) { var brokenAxis = this; var axis = brokenAxis.axis; var hasBreaks = (isArray(breaks) && !!breaks.length); axis.isDirty = brokenAxis.hasBreaks !== hasBreaks; brokenAxis.hasBreaks = hasBreaks; axis.options.breaks = axis.userOptions.breaks = breaks; axis.forceRedraw = true; // Force recalculation in setScale // Recalculate series related to the axis. axis.series.forEach(function (series) { series.isDirty = true; }); if (!hasBreaks && axis.val2lin === Additions.val2Lin) { // Revert to prototype functions delete axis.val2lin; delete axis.lin2val; } if (hasBreaks) { axis.userOptions.ordinal = false; axis.lin2val = Additions.lin2Val; axis.val2lin = Additions.val2Lin; axis.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) { // If trying to set extremes inside a break, extend min to // after, and max to before the break ( #3857 ) if (brokenAxis.hasBreaks) { var breaks_1 = (this.options.breaks || []); var axisBreak = void 0; while ((axisBreak = brokenAxis.findBreakAt(newMin, breaks_1))) { newMin = axisBreak.to; } while ((axisBreak = brokenAxis.findBreakAt(newMax, breaks_1))) { newMax = axisBreak.from; } // If both min and max is within the same break. if (newMax < newMin) { newMax = newMin; } } Axis.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments); }; axis.setAxisTranslation = function () { Axis.prototype.setAxisTranslation.call(this); brokenAxis.unitLength = void 0; if (brokenAxis.hasBreaks) { var breaks_2 = axis.options.breaks || [], // Temporary one: breakArrayT_1 = [], breakArray_1 = [], pointRangePadding = pick(axis.pointRangePadding, 0); var length_1 = 0, inBrk_1, repeat_1, min_1 = axis.userMin || axis.min, max_1 = axis.userMax || axis.max, start_1, i_1; // Min & max check (#4247) breaks_2.forEach(function (brk) { repeat_1 = brk.repeat || Infinity; if (isNumber(min_1) && isNumber(max_1)) { if (Additions.isInBreak(brk, min_1)) { min_1 += (brk.to % repeat_1) - (min_1 % repeat_1); } if (Additions.isInBreak(brk, max_1)) { max_1 -= (max_1 % repeat_1) - (brk.from % repeat_1); } } }); // Construct an array holding all breaks in the axis breaks_2.forEach(function (brk) { start_1 = brk.from; repeat_1 = brk.repeat || Infinity; if (isNumber(min_1) && isNumber(max_1)) { while (start_1 - repeat_1 > min_1) { start_1 -= repeat_1; } while (start_1 < min_1) { start_1 += repeat_1; } for (i_1 = start_1; i_1 < max_1; i_1 += repeat_1) { breakArrayT_1.push({ value: i_1, move: 'in' }); breakArrayT_1.push({ value: i_1 + brk.to - brk.from, move: 'out', size: brk.breakSize }); } } }); breakArrayT_1.sort(function (a, b) { return ((a.value === b.value) ? ((a.move === 'in' ? 0 : 1) - (b.move === 'in' ? 0 : 1)) : a.value - b.value); }); // Simplify the breaks inBrk_1 = 0; start_1 = min_1; breakArrayT_1.forEach(function (brk) { inBrk_1 += (brk.move === 'in' ? 1 : -1); if (inBrk_1 === 1 && brk.move === 'in') { start_1 = brk.value; } if (inBrk_1 === 0 && isNumber(start_1)) { breakArray_1.push({ from: start_1, to: brk.value, len: brk.value - start_1 - (brk.size || 0) }); length_1 += brk.value - start_1 - (brk.size || 0); } }); brokenAxis.breakArray = breakArray_1; // Used with staticScale, and below the actual axis // length, when breaks are substracted. if (isNumber(min_1) && isNumber(max_1) && isNumber(axis.min)) { brokenAxis.unitLength = max_1 - min_1 - length_1 + pointRangePadding; fireEvent(axis, 'afterBreaks'); if (axis.staticScale) { axis.transA = axis.staticScale; } else if (brokenAxis.unitLength) { axis.transA *= (max_1 - axis.min + pointRangePadding) / brokenAxis.unitLength; } if (pointRangePadding) { axis.minPixelPadding = axis.transA * (axis.minPointOffset || 0); } axis.min = min_1; axis.max = max_1; } } }; } if (pick(redraw, true)) { axis.chart.redraw(); } }; return Additions; }()); BrokenAxis.Additions = Additions; })(BrokenAxis || (BrokenAxis = {})); /* * * * Default Export * * */ return BrokenAxis; }); _registerModule(_modules, 'masters/modules/broken-axis.src.js', [_modules['Core/Globals.js'], _modules['Core/Axis/BrokenAxis.js']], function (Highcharts, BrokenAxis) { var G = Highcharts; // Compositions BrokenAxis.compose(G.Axis, G.Series); }); _registerModule(_modules, 'Extensions/DataGrouping.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Axis/DateTimeAxis.js'], _modules['Core/FormatUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Series/Point.js'], _modules['Core/Series/Series.js'], _modules['Core/Tooltip.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Utilities.js']], function (Axis, DateTimeAxis, F, H, Point, Series, Tooltip, D, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var format = F.format; var seriesProto = Series.prototype; var addEvent = U.addEvent, arrayMax = U.arrayMax, arrayMin = U.arrayMin, correctFloat = U.correctFloat, defined = U.defined, error = U.error, extend = U.extend, isNumber = U.isNumber, merge = U.merge, pick = U.pick; /** * @typedef {"average"|"averages"|"open"|"high"|"low"|"close"|"sum"} Highcharts.DataGroupingApproximationValue */ /** * The position of the point inside the group. * * @typedef {"start"|"middle"|"end"} Highcharts.DataGroupingAnchor */ /** * The position of the first or last point in the series inside the group. * * @typedef {"start"|"middle"|"end"|"firstPoint"|"lastPoint"} Highcharts.DataGroupingAnchorExtremes */ /** * @interface Highcharts.DataGroupingInfoObject */ /** * @name Highcharts.DataGroupingInfoObject#length * @type {number} */ /** * @name Highcharts.DataGroupingInfoObject#options * @type {Highcharts.SeriesOptionsType|undefined} */ /** * @name Highcharts.DataGroupingInfoObject#start * @type {number} */ ''; // detach doclets above /* ************************************************************************** * * Start data grouping module * * ************************************************************************** */ /* eslint-disable no-invalid-this, valid-jsdoc */ /** * Define the available approximation types. The data grouping * approximations takes an array or numbers as the first parameter. In case * of ohlc, four arrays are sent in as four parameters. Each array consists * only of numbers. In case null values belong to the group, the property * .hasNulls will be set to true on the array. * * @product highstock * * @private * @name Highcharts.approximations * @type {Highcharts.Dictionary<Function>} */ var approximations = H.approximations = { sum: function (arr) { var len = arr.length, ret; // 1. it consists of nulls exclusive if (!len && arr.hasNulls) { ret = null; // 2. it has a length and real values } else if (len) { ret = 0; while (len--) { ret += arr[len]; } } // 3. it has zero length, so just return undefined // => doNothing() return ret; }, average: function (arr) { var len = arr.length, ret = approximations.sum(arr); // If we have a number, return it divided by the length. If not, // return null or undefined based on what the sum method finds. if (isNumber(ret) && len) { ret = correctFloat(ret / len); } return ret; }, // The same as average, but for series with multiple values, like area // ranges. averages: function () { var ret = []; [].forEach.call(arguments, function (arr) { ret.push(approximations.average(arr)); }); // Return undefined when first elem. is undefined and let // sum method handle null (#7377) return typeof ret[0] === 'undefined' ? void 0 : ret; }, open: function (arr) { return arr.length ? arr[0] : (arr.hasNulls ? null : void 0); }, high: function (arr) { return arr.length ? arrayMax(arr) : (arr.hasNulls ? null : void 0); }, low: function (arr) { return arr.length ? arrayMin(arr) : (arr.hasNulls ? null : void 0); }, close: function (arr) { return arr.length ? arr[arr.length - 1] : (arr.hasNulls ? null : void 0); }, // ohlc and range are special cases where a multidimensional array is // input and an array is output ohlc: function (open, high, low, close) { open = approximations.open(open); high = approximations.high(high); low = approximations.low(low); close = approximations.close(close); if (isNumber(open) || isNumber(high) || isNumber(low) || isNumber(close)) { return [open, high, low, close]; } // else, return is undefined }, range: function (low, high) { low = approximations.low(low); high = approximations.high(high); if (isNumber(low) || isNumber(high)) { return [low, high]; } if (low === null && high === null) { return null; } // else, return is undefined } }; var groupData = function (xData, yData, groupPositions, approximation) { var series = this, data = series.data, dataOptions = series.options && series.options.data, groupedXData = [], groupedYData = [], groupMap = [], dataLength = xData.length, pointX, pointY, groupedY, // when grouping the fake extended axis for panning, // we don't need to consider y handleYData = !!yData, values = [], approximationFn, pointArrayMap = series.pointArrayMap, pointArrayMapLength = pointArrayMap && pointArrayMap.length, extendedPointArrayMap = ['x'].concat(pointArrayMap || ['y']), groupAll = this.options.dataGrouping && this.options.dataGrouping.groupAll, pos = 0, start = 0, valuesLen, i, j; /** * @private */ function getApproximation(approx) { if (typeof approx === 'function') { return approx; } if (approximations[approx]) { return approximations[approx]; } return approximations[(series.getDGApproximation && series.getDGApproximation()) || 'average']; } approximationFn = getApproximation(approximation); // Calculate values array size from pointArrayMap length if (pointArrayMapLength) { pointArrayMap.forEach(function () { values.push([]); }); } else { values.push([]); } valuesLen = pointArrayMapLength || 1; // Start with the first point within the X axis range (#2696) for (i = 0; i <= dataLength; i++) { if (xData[i] >= groupPositions[0]) { break; } } for (i; i <= dataLength; i++) { // when a new group is entered, summarize and initialize // the previous group while ((typeof groupPositions[pos + 1] !== 'undefined' && xData[i] >= groupPositions[pos + 1]) || i === dataLength) { // get the last group // get group x and y pointX = groupPositions[pos]; series.dataGroupInfo = { start: groupAll ? start : (series.cropStart + start), length: values[0].length }; groupedY = approximationFn.apply(series, values); // By default, let options of the first grouped point be passed over // to the grouped point. This allows preserving properties like // `name` and `color` or custom properties. Implementers can // override this from the approximation function, where they can // write custom options to `this.dataGroupInfo.options`. if (series.pointClass && !defined(series.dataGroupInfo.options)) { // Convert numbers and arrays into objects series.dataGroupInfo.options = merge(series.pointClass.prototype .optionsToObject.call({ series: series }, series.options.data[series.cropStart + start])); // Make sure the raw data (x, y, open, high etc) is not copied // over and overwriting approximated data. extendedPointArrayMap.forEach(function (key) { delete series.dataGroupInfo.options[key]; }); } // push the grouped data if (typeof groupedY !== 'undefined') { groupedXData.push(pointX); groupedYData.push(groupedY); groupMap.push(series.dataGroupInfo); } // reset the aggregate arrays start = i; for (j = 0; j < valuesLen; j++) { values[j].length = 0; // faster than values[j] = [] values[j].hasNulls = false; } // Advance on the group positions pos += 1; // don't loop beyond the last group if (i === dataLength) { break; } } // break out if (i === dataLength) { break; } // for each raw data point, push it to an array that contains all values // for this specific group if (pointArrayMap) { var index = (series.options.dataGrouping && series.options.dataGrouping.groupAll ? i : series.cropStart + i), point = (data && data[index]) || series.pointClass.prototype.applyOptions.apply({ series: series }, [dataOptions[index]]), val = void 0; for (j = 0; j < pointArrayMapLength; j++) { val = point[pointArrayMap[j]]; if (isNumber(val)) { values[j].push(val); } else if (val === null) { values[j].hasNulls = true; } } } else { pointY = handleYData ? yData[i] : null; if (isNumber(pointY)) { values[0].push(pointY); } else if (pointY === null) { values[0].hasNulls = true; } } } return { groupedXData: groupedXData, groupedYData: groupedYData, groupMap: groupMap }; }; var anchorPoints = function (series, groupedXData, xMax) { var options = series.options, dataGroupingOptions = options.dataGrouping, totalRange = series.currentDataGrouping && series.currentDataGrouping.gapSize; var i; // DataGrouping x-coordinates. if (dataGroupingOptions && series.xData && totalRange && series.groupMap) { var groupedDataLength = groupedXData.length - 1, anchor = dataGroupingOptions.anchor, firstAnchor = pick(dataGroupingOptions.firstAnchor, anchor), lastAnchor = pick(dataGroupingOptions.lastAnchor, anchor); // Anchor points that are not extremes. if (anchor && anchor !== 'start') { var shiftInterval = totalRange * { middle: 0.5, end: 1 }[anchor]; i = groupedXData.length - 1; while (i-- && i > 0) { groupedXData[i] += shiftInterval; } } // Change the first point position, but only when it is // the first point in the data set not in the current zoom. if (firstAnchor && firstAnchor !== 'start' && series.xData[0] >= groupedXData[0]) { var groupStart = series.groupMap[0].start, groupLength = series.groupMap[0].length; var firstGroupstEnd = void 0; if (isNumber(groupStart) && isNumber(groupLength)) { firstGroupstEnd = groupStart + (groupLength - 1); } groupedXData[0] = { middle: groupedXData[0] + 0.5 * totalRange, end: groupedXData[0] + totalRange, firstPoint: series.xData[0], lastPoint: firstGroupstEnd && series.xData[firstGroupstEnd] }[firstAnchor]; } // Change the last point position but only when it is // the last point in the data set not in the current zoom. if (lastAnchor && lastAnchor !== 'start' && totalRange && groupedXData[groupedDataLength] >= xMax - totalRange) { var lastGroupStart = series.groupMap[series.groupMap.length - 1].start; groupedXData[groupedDataLength] = { middle: groupedXData[groupedDataLength] + 0.5 * totalRange, end: groupedXData[groupedDataLength] + totalRange, firstPoint: lastGroupStart && series.xData[lastGroupStart], lastPoint: series.xData[series.xData.length - 1] }[lastAnchor]; } } }; var adjustExtremes = function (xAxis, groupedXData) { // Make sure the X axis extends to show the first group (#2533) // But only for visible series (#5493, #6393) if (defined(groupedXData[0]) && isNumber(xAxis.min) && isNumber(xAxis.dataMin) && groupedXData[0] < xAxis.min) { if ((!defined(xAxis.options.min) && xAxis.min <= xAxis.dataMin) || xAxis.min === xAxis.dataMin) { xAxis.min = Math.min(groupedXData[0], xAxis.min); } xAxis.dataMin = Math.min(groupedXData[0], xAxis.dataMin); } // When the last anchor set, change the extremes that // the last point is visible (#12455). if (defined(groupedXData[groupedXData.length - 1]) && isNumber(xAxis.max) && isNumber(xAxis.dataMax) && groupedXData[groupedXData.length - 1] > xAxis.max) { if ((!defined(xAxis.options.max) && isNumber(xAxis.dataMax) && xAxis.max >= xAxis.dataMax) || xAxis.max === xAxis.dataMax) { xAxis.max = Math.max(groupedXData[groupedXData.length - 1], xAxis.max); } xAxis.dataMax = Math.max(groupedXData[groupedXData.length - 1], xAxis.dataMax); } }; var dataGrouping = { approximations: approximations, groupData: groupData }; // ----------------------------------------------------------------------------- // The following code applies to implementation of data grouping on a Series var baseProcessData = seriesProto.processData, baseGeneratePoints = seriesProto.generatePoints, /** @ignore */ commonOptions = { // enabled: null, // (true for stock charts, false for basic), // forced: undefined, groupPixelWidth: 2, // the first one is the point or start value, the second is the start // value if we're dealing with range, the third one is the end value if // dealing with a range dateTimeLabelFormats: { millisecond: [ '%A, %b %e, %H:%M:%S.%L', '%A, %b %e, %H:%M:%S.%L', '-%H:%M:%S.%L' ], second: [ '%A, %b %e, %H:%M:%S', '%A, %b %e, %H:%M:%S', '-%H:%M:%S' ], minute: [ '%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M' ], hour: [ '%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M' ], day: [ '%A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y' ], week: [ 'Week from %A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y' ], month: [ '%B %Y', '%B', '-%B %Y' ], year: [ '%Y', '%Y', '-%Y' ] } // smoothed = false, // enable this for navigator series only }, specificOptions = { line: {}, spline: {}, area: {}, areaspline: {}, arearange: {}, column: { groupPixelWidth: 10 }, columnrange: { groupPixelWidth: 10 }, candlestick: { groupPixelWidth: 10 }, ohlc: { groupPixelWidth: 5 } }, // units are defined in a separate array to allow complete overriding in // case of a user option defaultDataGroupingUnits = H.defaultDataGroupingUnits = [ [ 'millisecond', [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples ], [ 'second', [1, 2, 5, 10, 15, 30] ], [ 'minute', [1, 2, 5, 10, 15, 30] ], [ 'hour', [1, 2, 3, 4, 6, 8, 12] ], [ 'day', [1] ], [ 'week', [1] ], [ 'month', [1, 3, 6] ], [ 'year', null ] ]; // Set default approximations to the prototypes if present. Properties are // inherited down. Can be overridden for individual series types. seriesProto.getDGApproximation = function () { if (this.is('arearange')) { return 'range'; } if (this.is('ohlc')) { return 'ohlc'; } if (this.is('column')) { return 'sum'; } return 'average'; }; /** * Takes parallel arrays of x and y data and groups the data into intervals * defined by groupPositions, a collection of starting x values for each group. * * @private * @function Highcharts.Series#groupData * * @param {Array<number>} xData * * @param {Array<number>|Array<Array<number>>} yData * * @param {boolean} groupPositions * * @param {string|Function} approximation * * @return {void} */ seriesProto.groupData = groupData; // Extend the basic processData method, that crops the data to the current zoom // range, with data grouping logic. seriesProto.processData = function () { var series = this, chart = series.chart, options = series.options, dataGroupingOptions = options.dataGrouping, groupingEnabled = series.allowDG !== false && dataGroupingOptions && pick(dataGroupingOptions.enabled, chart.options.isStock), visible = (series.visible || !chart.options.chart.ignoreHiddenSeries), hasGroupedData, skip, lastDataGrouping = this.currentDataGrouping, currentDataGrouping, croppedData, revertRequireSorting = false; // Run base method series.forceCrop = groupingEnabled; // #334 series.groupPixelWidth = null; // #2110 series.hasProcessed = true; // #2692 // Data needs to be sorted for dataGrouping if (groupingEnabled && !series.requireSorting) { series.requireSorting = revertRequireSorting = true; } // Skip if processData returns false or if grouping is disabled (in that // order) skip = (baseProcessData.apply(series, arguments) === false || !groupingEnabled); // Revert original requireSorting value if changed if (revertRequireSorting) { series.requireSorting = false; } if (!skip) { series.destroyGroupedData(); var i = void 0, processedXData = dataGroupingOptions.groupAll ? series.xData : series.processedXData, processedYData = dataGroupingOptions.groupAll ? series.yData : series.processedYData, plotSizeX = chart.plotSizeX, xAxis = series.xAxis, ordinal = xAxis.options.ordinal, groupPixelWidth = series.groupPixelWidth = xAxis.getGroupPixelWidth && xAxis.getGroupPixelWidth(); // Execute grouping if the amount of points is greater than the limit // defined in groupPixelWidth if (groupPixelWidth && processedXData && processedXData.length) { hasGroupedData = true; // Force recreation of point instances in series.translate, #5699 series.isDirty = true; series.points = null; // #6709 var extremes = xAxis.getExtremes(), xMin = extremes.min, xMax = extremes.max, groupIntervalFactor = (ordinal && xAxis.ordinal && xAxis.ordinal.getGroupIntervalFactor(xMin, xMax, series)) || 1, interval = (groupPixelWidth * (xMax - xMin) / plotSizeX) * groupIntervalFactor, groupPositions = xAxis.getTimeTicks(DateTimeAxis.AdditionsClass.prototype.normalizeTimeTickInterval(interval, dataGroupingOptions.units || defaultDataGroupingUnits), // Processed data may extend beyond axis (#4907) Math.min(xMin, processedXData[0]), Math.max(xMax, processedXData[processedXData.length - 1]), xAxis.options.startOfWeek, processedXData, series.closestPointRange), groupedData = seriesProto.groupData.apply(series, [ processedXData, processedYData, groupPositions, dataGroupingOptions.approximation ]), groupedXData = groupedData.groupedXData, groupedYData = groupedData.groupedYData, gapSize = 0; // The smoothed option is deprecated, instead, // there is a fallback to the new anchoring mechanism. #12455. if (dataGroupingOptions && dataGroupingOptions.smoothed && groupedXData.length) { dataGroupingOptions.firstAnchor = 'firstPoint'; dataGroupingOptions.anchor = 'middle'; dataGroupingOptions.lastAnchor = 'lastPoint'; error(32, false, chart, { 'dataGrouping.smoothed': 'use dataGrouping.anchor' }); } anchorPoints(series, groupedXData, xMax); // Record what data grouping values were used for (i = 1; i < groupPositions.length; i++) { // The grouped gapSize needs to be the largest distance between // the group to capture varying group sizes like months or DST // crossing (#10000). Also check that the gap is not at the // start of a segment. if (!groupPositions.info.segmentStarts || groupPositions.info.segmentStarts.indexOf(i) === -1) { gapSize = Math.max(groupPositions[i] - groupPositions[i - 1], gapSize); } } currentDataGrouping = groupPositions.info; currentDataGrouping.gapSize = gapSize; series.closestPointRange = groupPositions.info.totalRange; series.groupMap = groupedData.groupMap; if (visible) { adjustExtremes(xAxis, groupedXData); } // We calculated all group positions but we should render // only the ones within the visible range if (dataGroupingOptions.groupAll) { croppedData = series.cropData(groupedXData, groupedYData, xAxis.min, xAxis.max, 1 // Ordinal xAxis will remove left-most points otherwise ); groupedXData = croppedData.xData; groupedYData = croppedData.yData; series.cropStart = croppedData.start; // #15005 } // Set series props series.processedXData = groupedXData; series.processedYData = groupedYData; } else { series.groupMap = null; } series.hasGroupedData = hasGroupedData; series.currentDataGrouping = currentDataGrouping; series.preventGraphAnimation = (lastDataGrouping && lastDataGrouping.totalRange) !== (currentDataGrouping && currentDataGrouping.totalRange); } }; // Destroy the grouped data points. #622, #740 seriesProto.destroyGroupedData = function () { // Clear previous groups if (this.groupedData) { this.groupedData.forEach(function (point, i) { if (point) { this.groupedData[i] = point.destroy ? point.destroy() : null; } }, this); // Clears all: // - `this.groupedData` // - `this.points` // - `preserve` object in series.update() this.groupedData.length = 0; } }; // Override the generatePoints method by adding a reference to grouped data seriesProto.generatePoints = function () { baseGeneratePoints.apply(this); // Record grouped data in order to let it be destroyed the next time // processData runs this.destroyGroupedData(); // #622 this.groupedData = this.hasGroupedData ? this.points : null; }; // Override point prototype to throw a warning when trying to update grouped // points. addEvent(Point, 'update', function () { if (this.dataGroup) { error(24, false, this.series.chart); return false; } }); // Extend the original method, make the tooltip's header reflect the grouped // range. addEvent(Tooltip, 'headerFormatter', function (e) { var tooltip = this, chart = this.chart, time = chart.time, labelConfig = e.labelConfig, series = labelConfig.series, options = series.options, tooltipOptions = series.tooltipOptions, dataGroupingOptions = options.dataGrouping, xDateFormat = tooltipOptions.xDateFormat, xDateFormatEnd, xAxis = series.xAxis, currentDataGrouping, dateTimeLabelFormats, labelFormats, formattedKey, formatString = tooltipOptions[(e.isFooter ? 'footer' : 'header') + 'Format']; // apply only to grouped series if (xAxis && xAxis.options.type === 'datetime' && dataGroupingOptions && isNumber(labelConfig.key)) { // set variables currentDataGrouping = series.currentDataGrouping; dateTimeLabelFormats = dataGroupingOptions.dateTimeLabelFormats || // Fallback to commonOptions (#9693) commonOptions.dateTimeLabelFormats; // if we have grouped data, use the grouping information to get the // right format if (currentDataGrouping) { labelFormats = dateTimeLabelFormats[currentDataGrouping.unitName]; if (currentDataGrouping.count === 1) { xDateFormat = labelFormats[0]; } else { xDateFormat = labelFormats[1]; xDateFormatEnd = labelFormats[2]; } // if not grouped, and we don't have set the xDateFormat option, get the // best fit, so if the least distance between points is one minute, show // it, but if the least distance is one day, skip hours and minutes etc. } else if (!xDateFormat && dateTimeLabelFormats) { xDateFormat = tooltip.getXDateFormat(labelConfig, tooltipOptions, xAxis); } // now format the key formattedKey = time.dateFormat(xDateFormat, labelConfig.key); if (xDateFormatEnd) { formattedKey += time.dateFormat(xDateFormatEnd, labelConfig.key + currentDataGrouping.totalRange - 1); } // Replace default header style with class name if (series.chart.styledMode) { formatString = this.styledModeFormat(formatString); } // return the replaced format e.text = format(formatString, { point: extend(labelConfig.point, { key: formattedKey }), series: series }, chart); e.preventDefault(); } }); // Destroy grouped data on series destroy addEvent(Series, 'destroy', seriesProto.destroyGroupedData); // Handle default options for data grouping. This must be set at runtime because // some series types are defined after this. addEvent(Series, 'afterSetOptions', function (e) { var options = e.options, type = this.type, plotOptions = this.chart.options.plotOptions, defaultOptions = D.defaultOptions.plotOptions[type].dataGrouping, // External series, for example technical indicators should also // inherit commonOptions which are not available outside this module baseOptions = this.useCommonDataGrouping && commonOptions; if (specificOptions[type] || baseOptions) { // #1284 if (!defaultOptions) { defaultOptions = merge(commonOptions, specificOptions[type]); } var rangeSelector = this.chart.rangeSelector; options.dataGrouping = merge(baseOptions, defaultOptions, plotOptions.series && plotOptions.series.dataGrouping, // #1228 // Set by the StockChart constructor: plotOptions[type].dataGrouping, this.userOptions.dataGrouping, !options.isInternal && rangeSelector && isNumber(rangeSelector.selected) && rangeSelector.buttonOptions[rangeSelector.selected].dataGrouping); } }); // When resetting the scale reset the hasProccessed flag to avoid taking // previous data grouping of neighbour series into accound when determining // group pixel width (#2692). addEvent(Axis, 'afterSetScale', function () { this.series.forEach(function (series) { series.hasProcessed = false; }); }); // Get the data grouping pixel width based on the greatest defined individual // width of the axis' series, and if whether one of the axes need grouping. Axis.prototype.getGroupPixelWidth = function () { var series = this.series, len = series.length, i, groupPixelWidth = 0, doGrouping = false, dataLength, dgOptions; // If multiple series are compared on the same x axis, give them the same // group pixel width (#334) i = len; while (i--) { dgOptions = series[i].options.dataGrouping; if (dgOptions) { groupPixelWidth = Math.max(groupPixelWidth, // Fallback to commonOptions (#9693) pick(dgOptions.groupPixelWidth, commonOptions.groupPixelWidth)); } } // If one of the series needs grouping, apply it to all (#1634) i = len; while (i--) { dgOptions = series[i].options.dataGrouping; if (dgOptions && series[i].hasProcessed) { // #2692 dataLength = (series[i].processedXData || series[i].data).length; // Execute grouping if the amount of points is greater than the // limit defined in groupPixelWidth if (series[i].groupPixelWidth || dataLength > (this.chart.plotSizeX / groupPixelWidth) || (dataLength && dgOptions.forced)) { doGrouping = true; } } } return doGrouping ? groupPixelWidth : 0; }; /** * Highcharts Stock only. Force data grouping on all the axis' series. * * @product highstock * * @function Highcharts.Axis#setDataGrouping * * @param {boolean|Highcharts.DataGroupingOptionsObject} [dataGrouping] * A `dataGrouping` configuration. Use `false` to disable data grouping * dynamically. * * @param {boolean} [redraw=true] * Whether to redraw the chart or wait for a later call to * {@link Chart#redraw}. */ Axis.prototype.setDataGrouping = function (dataGrouping, redraw) { var axis = this; var i; redraw = pick(redraw, true); if (!dataGrouping) { dataGrouping = { forced: false, units: null }; } // Axis is instantiated, update all series if (this instanceof Axis) { i = this.series.length; while (i--) { this.series[i].update({ dataGrouping: dataGrouping }, false); } // Axis not yet instanciated, alter series options } else { this.chart.options.series.forEach(function (seriesOptions) { seriesOptions.dataGrouping = dataGrouping; }, false); } // Clear ordinal slope, so we won't accidentaly use the old one (#7827) if (axis.ordinal) { axis.ordinal.slope = void 0; } if (redraw) { this.chart.redraw(); } }; H.dataGrouping = dataGrouping; /* eslint-enable no-invalid-this, valid-jsdoc */ /** * Data grouping is the concept of sampling the data values into larger * blocks in order to ease readability and increase performance of the * JavaScript charts. Highcharts Stock by default applies data grouping when * the points become closer than a certain pixel value, determined by * the `groupPixelWidth` option. * * If data grouping is applied, the grouping information of grouped * points can be read from the [Point.dataGroup]( * /class-reference/Highcharts.Point#dataGroup). If point options other than * the data itself are set, for example `name` or `color` or custom properties, * the grouping logic doesn't know how to group it. In this case the options of * the first point instance are copied over to the group point. This can be * altered through a custom `approximation` callback function. * * @declare Highcharts.DataGroupingOptionsObject * @product highstock * @requires product:highstock * @requires module:modules/datagrouping * @apioption plotOptions.series.dataGrouping */ /** * Specifies how the points should be located on the X axis inside the group. * Points that are extremes can be set separately. Available options: * * - `start` places the point at the beginning of the group * (e.g. range 00:00:00 - 23:59:59 -> 00:00:00) * * - `middle` places the point in the middle of the group * (e.g. range 00:00:00 - 23:59:59 -> 12:00:00) * * - `end` places the point at the end of the group * (e.g. range 00:00:00 - 23:59:59 -> 23:59:59) * * @sample {highstock} stock/plotoptions/series-datagrouping-anchor * Changing the point x-coordinate inside the group. * * @see [dataGrouping.firstAnchor](#plotOptions.series.dataGrouping.firstAnchor) * @see [dataGrouping.lastAnchor](#plotOptions.series.dataGrouping.lastAnchor) * * @type {Highcharts.DataGroupingAnchor} * @since 9.1.0 * @default start * @apioption plotOptions.series.dataGrouping.anchor */ /** * The method of approximation inside a group. When for example 30 days * are grouped into one month, this determines what value should represent * the group. Possible values are "average", "averages", "open", "high", * "low", "close" and "sum". For OHLC and candlestick series the approximation * is "ohlc" by default, which finds the open, high, low and close values * within all the grouped data. For ranges, the approximation is "range", * which finds the low and high values. For multi-dimensional data, * like ranges and OHLC, "averages" will compute the average for each * dimension. * * Custom aggregate methods can be added by assigning a callback function * as the approximation. This function takes a numeric array as the * argument and should return a single numeric value or `null`. Note * that the numeric array will never contain null values, only true * numbers. Instead, if null values are present in the raw data, the * numeric array will have an `.hasNulls` property set to `true`. For * single-value data sets the data is available in the first argument * of the callback function. For OHLC data sets, all the open values * are in the first argument, all high values in the second etc. * * Since v4.2.7, grouping meta data is available in the approximation * callback from `this.dataGroupInfo`. It can be used to extract information * from the raw data. * * Defaults to `average` for line-type series, `sum` for columns, `range` * for range series and `ohlc` for OHLC and candlestick. * * @sample {highstock} stock/plotoptions/series-datagrouping-approximation * Approximation callback with custom data * @sample {highstock} stock/plotoptions/series-datagrouping-simple-approximation * Simple approximation demo * * @type {Highcharts.DataGroupingApproximationValue|Function} * @apioption plotOptions.series.dataGrouping.approximation */ /** * Datetime formats for the header of the tooltip in a stock chart. * The format can vary within a chart depending on the currently selected * time range and the current data grouping. * * The default formats are: * ```js * { * millisecond: [ * '%A, %b %e, %H:%M:%S.%L', '%A, %b %e, %H:%M:%S.%L', '-%H:%M:%S.%L' * ], * second: ['%A, %b %e, %H:%M:%S', '%A, %b %e, %H:%M:%S', '-%H:%M:%S'], * minute: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'], * hour: ['%A, %b %e, %H:%M', '%A, %b %e, %H:%M', '-%H:%M'], * day: ['%A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'], * week: ['Week from %A, %b %e, %Y', '%A, %b %e', '-%A, %b %e, %Y'], * month: ['%B %Y', '%B', '-%B %Y'], * year: ['%Y', '%Y', '-%Y'] * } * ``` * * For each of these array definitions, the first item is the format * used when the active time span is one unit. For instance, if the * current data applies to one week, the first item of the week array * is used. The second and third items are used when the active time * span is more than two units. For instance, if the current data applies * to two weeks, the second and third item of the week array are used, * and applied to the start and end date of the time span. * * @type {object} * @apioption plotOptions.series.dataGrouping.dateTimeLabelFormats */ /** * Enable or disable data grouping. * * @type {boolean} * @default true * @apioption plotOptions.series.dataGrouping.enabled */ /** * Specifies how the first grouped point is positioned on the xAxis. * If firstAnchor and/or lastAnchor are defined, then those options take * precedence over anchor for the first and/or last grouped points. * Available options: * * -`start` places the point at the beginning of the group * (e.g. range 00:00:00 - 23:59:59 -> 00:00:00) * * -`middle` places the point in the middle of the group * (e.g. range 00:00:00 - 23:59:59 -> 12:00:00) * * -`end` places the point at the end of the group * (e.g. range 00:00:00 - 23:59:59 -> 23:59:59) * * -`firstPoint` the first point in the group * (e.g. points at 00:13, 00:35, 00:59 -> 00:13) * * -`lastPoint` the last point in the group * (e.g. points at 00:13, 00:35, 00:59 -> 00:59) * * @sample {highstock} stock/plotoptions/series-datagrouping-first-anchor * Applying first and last anchor. * * @see [dataGrouping.anchor](#plotOptions.series.dataGrouping.anchor) * * @type {Highcharts.DataGroupingAnchorExtremes} * @since 9.1.0 * @default start * @apioption plotOptions.series.dataGrouping.firstAnchor */ /** * When data grouping is forced, it runs no matter how small the intervals * are. This can be handy for example when the sum should be calculated * for values appearing at random times within each hour. * * @type {boolean} * @default false * @apioption plotOptions.series.dataGrouping.forced */ /** * The approximate pixel width of each group. If for example a series * with 30 points is displayed over a 600 pixel wide plot area, no grouping * is performed. If however the series contains so many points that * the spacing is less than the groupPixelWidth, Highcharts will try * to group it into appropriate groups so that each is more or less * two pixels wide. If multiple series with different group pixel widths * are drawn on the same x axis, all series will take the greatest width. * For example, line series have 2px default group width, while column * series have 10px. If combined, both the line and the column will * have 10px by default. * * @type {number} * @default 2 * @apioption plotOptions.series.dataGrouping.groupPixelWidth */ /** * By default only points within the visible range are grouped. Enabling this * option will force data grouping to calculate all grouped points for a given * dataset. That option prevents for example a column series from calculating * a grouped point partially. The effect is similar to * [Series.getExtremesFromAll](#plotOptions.series.getExtremesFromAll) but does * not affect yAxis extremes. * * @sample {highstock} stock/plotoptions/series-datagrouping-groupall/ * Two series with the same data but different groupAll setting * * @type {boolean} * @default false * @since 6.1.0 * @apioption plotOptions.series.dataGrouping.groupAll */ /** * Specifies how the last grouped point is positioned on the xAxis. * If firstAnchor and/or lastAnchor are defined, then those options take * precedence over anchor for the first and/or last grouped points. * Available options: * * -`start` places the point at the beginning of the group * (e.g. range 00:00:00 - 23:59:59 -> 00:00:00) * * -`middle` places the point in the middle of the group * (e.g. range 00:00:00 - 23:59:59 -> 12:00:00) * * -`end` places the point at the end of the group * (e.g. range 00:00:00 - 23:59:59 -> 23:59:59) * * -`firstPoint` the first point in the group * (e.g. points at 00:13, 00:35, 00:59 -> 00:13) * * -`lastPoint` the last point in the group * (e.g. points at 00:13, 00:35, 00:59 -> 00:59) * * @sample {highstock} stock/plotoptions/series-datagrouping-first-anchor * Applying first and last anchor. * * @sample {highstock} stock/plotoptions/series-datagrouping-last-anchor * Applying the last anchor in the chart with live data. * * @see [dataGrouping.anchor](#plotOptions.series.dataGrouping.anchor) * * @type {Highcharts.DataGroupingAnchorExtremes} * @since 9.1.0 * @default start * @apioption plotOptions.series.dataGrouping.lastAnchor */ /** * Normally, a group is indexed by the start of that group, so for example * when 30 daily values are grouped into one month, that month's x value * will be the 1st of the month. This apparently shifts the data to * the left. When the smoothed option is true, this is compensated for. * The data is shifted to the middle of the group, and min and max * values are preserved. Internally, this is used in the Navigator series. * * @type {boolean} * @default false * @deprecated * @apioption plotOptions.series.dataGrouping.smoothed */ /** * An array determining what time intervals the data is allowed to be * grouped to. Each array item is an array where the first value is * the time unit and the second value another array of allowed multiples. * * Defaults to: * ```js * units: [[ * 'millisecond', // unit name * [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples * ], [ * 'second', * [1, 2, 5, 10, 15, 30] * ], [ * 'minute', * [1, 2, 5, 10, 15, 30] * ], [ * 'hour', * [1, 2, 3, 4, 6, 8, 12] * ], [ * 'day', * [1] * ], [ * 'week', * [1] * ], [ * 'month', * [1, 3, 6] * ], [ * 'year', * null * ]] * ``` * * @type {Array<Array<string,(Array<number>|null)>>} * @apioption plotOptions.series.dataGrouping.units */ /** * The approximate pixel width of each group. If for example a series * with 30 points is displayed over a 600 pixel wide plot area, no grouping * is performed. If however the series contains so many points that * the spacing is less than the groupPixelWidth, Highcharts will try * to group it into appropriate groups so that each is more or less * two pixels wide. Defaults to `10`. * * @sample {highstock} stock/plotoptions/series-datagrouping-grouppixelwidth/ * Two series with the same data density but different groupPixelWidth * * @type {number} * @default 10 * @apioption plotOptions.column.dataGrouping.groupPixelWidth */ ''; // required by JSDoc parsing return dataGrouping; }); _registerModule(_modules, 'Series/OHLC/OHLCPoint.js', [_modules['Core/Series/SeriesRegistry.js']], function (SeriesRegistry) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var ColumnSeries = SeriesRegistry.seriesTypes.column; /* * * * Class * * */ var OHLCPoint = /** @class */ (function (_super) { __extends(OHLCPoint, _super); function OHLCPoint() { /* * * * Properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; _this.close = void 0; _this.high = void 0; _this.low = void 0; _this.open = void 0; _this.options = void 0; _this.plotClose = void 0; _this.plotOpen = void 0; _this.series = void 0; return _this; /* eslint-enable valid-jsdoc */ } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Extend the parent method by adding up or down to the class name. * @private * @function Highcharts.seriesTypes.ohlc#getClassName * @return {string} */ OHLCPoint.prototype.getClassName = function () { return _super.prototype.getClassName.call(this) + (this.open < this.close ? ' highcharts-point-up' : ' highcharts-point-down'); }; /** * Save upColor as point color (#14826). * @private * @function Highcharts.seriesTypes.ohlc#resolveUpColor */ OHLCPoint.prototype.resolveUpColor = function () { if (this.open < this.close && !this.options.color && this.series.options.upColor) { this.color = this.series.options.upColor; } }; /** * Extend the parent method by saving upColor. * @private * @function Highcharts.seriesTypes.ohlc#resolveColor */ OHLCPoint.prototype.resolveColor = function () { _super.prototype.resolveColor.call(this); this.resolveUpColor(); }; /** * Extend the parent method by saving upColor. * @private * @function Highcharts.seriesTypes.ohlc#getZone * * @return {Highcharts.SeriesZonesOptionsObject} * The zone item. */ OHLCPoint.prototype.getZone = function () { var zone = _super.prototype.getZone.call(this); this.resolveUpColor(); return zone; }; return OHLCPoint; }(ColumnSeries.prototype.pointClass)); /* * * * Default Export * * */ return OHLCPoint; }); _registerModule(_modules, 'Series/OHLC/OHLCSeries.js', [_modules['Series/OHLC/OHLCPoint.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (OHLCPoint, SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var ColumnSeries = SeriesRegistry.seriesTypes.column; var extend = U.extend, merge = U.merge; /* * * * Class * * */ /** * The ohlc series type. * * @private * @class * @name Highcharts.seriesTypes.ohlc * * @augments Highcharts.Series */ var OHLCSeries = /** @class */ (function (_super) { __extends(OHLCSeries, _super); function OHLCSeries() { /* * * * Static Properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; /* * * * Properties * * */ _this.data = void 0; _this.options = void 0; _this.points = void 0; _this.yData = void 0; return _this; /* eslint-enable valid-jsdoc */ } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Draw the data points * @private */ OHLCSeries.prototype.drawPoints = function () { var series = this, points = series.points, chart = series.chart, /** * Extend vertical stem to open and close values. */ extendStem = function (path, halfStrokeWidth, openOrClose) { var start = path[0]; var end = path[1]; // We don't need to worry about crisp - openOrClose value // is already crisped and halfStrokeWidth should remove it. if (typeof start[2] === 'number') { start[2] = Math.max(openOrClose + halfStrokeWidth, start[2]); } if (typeof end[2] === 'number') { end[2] = Math.min(openOrClose - halfStrokeWidth, end[2]); } }; points.forEach(function (point) { var plotOpen, plotClose, crispCorr, halfWidth, path, graphic = point.graphic, crispX, isNew = !graphic, strokeWidth; if (typeof point.plotY !== 'undefined') { // Create and/or update the graphic if (!graphic) { point.graphic = graphic = chart.renderer.path() .add(series.group); } if (!chart.styledMode) { graphic.attr(series.pointAttribs(point, (point.selected && 'select'))); // #3897 } // crisp vector coordinates strokeWidth = graphic.strokeWidth(); crispCorr = (strokeWidth % 2) / 2; // #2596: crispX = Math.round(point.plotX) - crispCorr; halfWidth = Math.round(point.shapeArgs.width / 2); // the vertical stem path = [ ['M', crispX, Math.round(point.yBottom)], ['L', crispX, Math.round(point.plotHigh)] ]; // open if (point.open !== null) { plotOpen = Math.round(point.plotOpen) + crispCorr; path.push(['M', crispX, plotOpen], ['L', crispX - halfWidth, plotOpen]); extendStem(path, strokeWidth / 2, plotOpen); } // close if (point.close !== null) { plotClose = Math.round(point.plotClose) + crispCorr; path.push(['M', crispX, plotClose], ['L', crispX + halfWidth, plotClose]); extendStem(path, strokeWidth / 2, plotClose); } graphic[isNew ? 'attr' : 'animate']({ d: path }) .addClass(point.getClassName(), true); } }); }; /** * @private * @function Highcarts.seriesTypes.ohlc#init * @return {void} */ OHLCSeries.prototype.init = function () { _super.prototype.init.apply(this, arguments); this.options.stacking = void 0; // #8817 }; /** * Postprocess mapping between options and SVG attributes * @private */ OHLCSeries.prototype.pointAttribs = function (point, state) { var attribs = _super.prototype.pointAttribs.call(this, point, state), options = this.options; delete attribs.fill; if (!point.options.color && options.upColor && point.open < point.close) { attribs.stroke = options.upColor; } return attribs; }; OHLCSeries.prototype.toYData = function (point) { // return a plain array for speedy calculation return [point.open, point.high, point.low, point.close]; }; /** * Translate data points from raw values x and y to plotX and plotY * * @private * @function Highcharts.seriesTypes.ohlc#translate * @return {void} */ OHLCSeries.prototype.translate = function () { var series = this, yAxis = series.yAxis, hasModifyValue = !!series.modifyValue, translated = [ 'plotOpen', 'plotHigh', 'plotLow', 'plotClose', 'yBottom' ]; // translate OHLC for _super.prototype.translate.apply(series); // Do the translation series.points.forEach(function (point) { [point.open, point.high, point.low, point.close, point.low] .forEach(function (value, i) { if (value !== null) { if (hasModifyValue) { value = series.modifyValue(value); } point[translated[i]] = yAxis.toPixels(value, true); } }); // Align the tooltip to the high value to avoid covering the // point point.tooltipPos[1] = point.plotHigh + yAxis.pos - series.chart.plotTop; }); }; /** * An OHLC chart is a style of financial chart used to describe price * movements over time. It displays open, high, low and close values per * data point. * * @sample stock/demo/ohlc/ * OHLC chart * * @extends plotOptions.column * @excluding borderColor, borderRadius, borderWidth, crisp, stacking, * stack * @product highstock * @optionparent plotOptions.ohlc */ OHLCSeries.defaultOptions = merge(ColumnSeries.defaultOptions, { /** * The approximate pixel width of each group. If for example a series * with 30 points is displayed over a 600 pixel wide plot area, no * grouping is performed. If however the series contains so many points * that the spacing is less than the groupPixelWidth, Highcharts will * try to group it into appropriate groups so that each is more or less * two pixels wide. Defaults to `5`. * * @type {number} * @default 5 * @product highstock * @apioption plotOptions.ohlc.dataGrouping.groupPixelWidth */ /** * The pixel width of the line/border. Defaults to `1`. * * @sample {highstock} stock/plotoptions/ohlc-linewidth/ * A greater line width * * @type {number} * @default 1 * @product highstock * * @private */ lineWidth: 1, tooltip: { pointFormat: '<span style="color:{point.color}">\u25CF</span> ' + '<b> {series.name}</b><br/>' + 'Open: {point.open}<br/>' + 'High: {point.high}<br/>' + 'Low: {point.low}<br/>' + 'Close: {point.close}<br/>' }, threshold: null, states: { /** * @extends plotOptions.column.states.hover * @product highstock */ hover: { /** * The pixel width of the line representing the OHLC point. * * @type {number} * @default 3 * @product highstock */ lineWidth: 3 } }, /** * Determines which one of `open`, `high`, `low`, `close` values should * be represented as `point.y`, which is later used to set dataLabel * position and [compare](#plotOptions.series.compare). * * @sample {highstock} stock/plotoptions/ohlc-pointvalkey/ * Possible values * * @type {string} * @default close * @validvalue ["open", "high", "low", "close"] * @product highstock * @apioption plotOptions.ohlc.pointValKey */ /** * @default close * @apioption plotOptions.ohlc.colorKey */ /** * Line color for up points. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highstock * @apioption plotOptions.ohlc.upColor */ stickyTracking: true }); return OHLCSeries; }(ColumnSeries)); extend(OHLCSeries.prototype, { animate: null, directTouch: false, pointArrayMap: ['open', 'high', 'low', 'close'], pointAttrToOptions: { stroke: 'color', 'stroke-width': 'lineWidth' }, pointValKey: 'close' }); OHLCSeries.prototype.pointClass = OHLCPoint; SeriesRegistry.registerSeriesType('ohlc', OHLCSeries); /* * * * Default Export * * */ /* * * * API Options * * */ /** * A `ohlc` series. If the [type](#series.ohlc.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.ohlc * @excluding dataParser, dataURL * @product highstock * @apioption series.ohlc */ /** * An array of data points for the series. For the `ohlc` series type, * points can be given in the following ways: * * 1. An array of arrays with 5 or 4 values. In this case, the values correspond * to `x,open,high,low,close`. If the first value is a string, it is applied * as the name of the point, and the `x` value is inferred. The `x` value can * also be omitted, in which case the inner arrays should be of length 4\. * Then the `x` value is automatically calculated, either starting at 0 and * incremented by 1, or from `pointStart` and `pointInterval` given in the * series options. * ```js * data: [ * [0, 6, 5, 6, 7], * [1, 9, 4, 8, 2], * [2, 6, 3, 4, 10] * ] * ``` * * 2. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.ohlc.turboThreshold), this option is not * available. * ```js * data: [{ * x: 1, * open: 3, * high: 4, * low: 5, * close: 2, * name: "Point2", * color: "#00FF00" * }, { * x: 1, * open: 4, * high: 3, * low: 6, * close: 7, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @type {Array<Array<(number|string),number,number,number>|Array<(number|string),number,number,number,number>|*>} * @extends series.arearange.data * @excluding y, marker * @product highstock * @apioption series.ohlc.data */ /** * The closing value of each data point. * * @type {number} * @product highstock * @apioption series.ohlc.data.close */ /** * The opening value of each data point. * * @type {number} * @product highstock * @apioption series.ohlc.data.open */ ''; // adds doclets above to transpilat return OHLCSeries; }); _registerModule(_modules, 'Series/Candlestick/CandlestickSeries.js', [_modules['Core/DefaultOptions.js'], _modules['Core/Color/Palette.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (D, palette, SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var defaultOptions = D.defaultOptions; var _a = SeriesRegistry.seriesTypes, ColumnSeries = _a.column, OHLCSeries = _a.ohlc; var merge = U.merge; /* * * * Code * * */ /** * The candlestick series type. * * @private * @class * @name Highcharts.seriesTypes.candlestick * * @augments Highcharts.seriesTypes.ohlc */ var CandlestickSeries = /** @class */ (function (_super) { __extends(CandlestickSeries, _super); function CandlestickSeries() { /* * * * Static properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; /* * * * Properties * * */ _this.data = void 0; _this.options = void 0; _this.points = void 0; return _this; } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Postprocess mapping between options and SVG attributes * * @private * @function Highcharts.seriesTypes.candlestick#pointAttribs */ CandlestickSeries.prototype.pointAttribs = function (point, state) { var attribs = ColumnSeries.prototype.pointAttribs.call(this, point, state), options = this.options, isUp = point.open < point.close, stroke = options.lineColor || this.color, color = point.color || this.color, // (#14826) stateOptions; attribs['stroke-width'] = options.lineWidth; attribs.fill = point.options.color || (isUp ? (options.upColor || color) : color); attribs.stroke = point.options.lineColor || (isUp ? (options.upLineColor || stroke) : stroke); // Select or hover states if (state) { stateOptions = options.states[state]; attribs.fill = stateOptions.color || attribs.fill; attribs.stroke = stateOptions.lineColor || attribs.stroke; attribs['stroke-width'] = stateOptions.lineWidth || attribs['stroke-width']; } return attribs; }; /** * Draw the data points. * * @private * @function Highcharts.seriesTypes.candlestick#drawPoints * @return {void} */ CandlestickSeries.prototype.drawPoints = function () { var series = this, points = series.points, chart = series.chart, reversedYAxis = series.yAxis.reversed; points.forEach(function (point) { var graphic = point.graphic, plotOpen, plotClose, topBox, bottomBox, hasTopWhisker, hasBottomWhisker, crispCorr, crispX, path, halfWidth, isNew = !graphic; if (typeof point.plotY !== 'undefined') { if (!graphic) { point.graphic = graphic = chart.renderer.path() .add(series.group); } if (!series.chart.styledMode) { graphic .attr(series.pointAttribs(point, (point.selected && 'select'))) // #3897 .shadow(series.options.shadow); } // Crisp vector coordinates crispCorr = (graphic.strokeWidth() % 2) / 2; // #2596: crispX = Math.round(point.plotX) - crispCorr; plotOpen = point.plotOpen; plotClose = point.plotClose; topBox = Math.min(plotOpen, plotClose); bottomBox = Math.max(plotOpen, plotClose); halfWidth = Math.round(point.shapeArgs.width / 2); hasTopWhisker = reversedYAxis ? bottomBox !== point.yBottom : Math.round(topBox) !== Math.round(point.plotHigh); hasBottomWhisker = reversedYAxis ? Math.round(topBox) !== Math.round(point.plotHigh) : bottomBox !== point.yBottom; topBox = Math.round(topBox) + crispCorr; bottomBox = Math.round(bottomBox) + crispCorr; // Create the path. Due to a bug in Chrome 49, the path is // first instanciated with no values, then the values // pushed. For unknown reasons, instanciating the path array // with all the values would lead to a crash when updating // frequently (#5193). path = []; path.push(['M', crispX - halfWidth, bottomBox], ['L', crispX - halfWidth, topBox], ['L', crispX + halfWidth, topBox], ['L', crispX + halfWidth, bottomBox], ['Z'], // Ensure a nice rectangle #2602 ['M', crispX, topBox], [ 'L', // #460, #2094 crispX, hasTopWhisker ? Math.round(reversedYAxis ? point.yBottom : point.plotHigh) : topBox ], ['M', crispX, bottomBox], [ 'L', // #460, #2094 crispX, hasBottomWhisker ? Math.round(reversedYAxis ? point.plotHigh : point.yBottom) : bottomBox ]); graphic[isNew ? 'attr' : 'animate']({ d: path }) .addClass(point.getClassName(), true); } }); /* eslint-enable valid-jsdoc */ }; /** * A candlestick chart is a style of financial chart used to describe price * movements over time. * * @sample stock/demo/candlestick/ * Candlestick chart * * @extends plotOptions.ohlc * @excluding borderColor,borderRadius,borderWidth * @product highstock * @optionparent plotOptions.candlestick */ CandlestickSeries.defaultOptions = merge(OHLCSeries.defaultOptions, defaultOptions.plotOptions, { /** * The specific line color for up candle sticks. The default is to * inherit the general `lineColor` setting. * * @sample {highstock} stock/plotoptions/candlestick-linecolor/ * Candlestick line colors * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 1.3.6 * @product highstock * @apioption plotOptions.candlestick.upLineColor */ /** * @type {Highcharts.DataGroupingApproximationValue|Function} * @default ohlc * @product highstock * @apioption plotOptions.candlestick.dataGrouping.approximation */ states: { /** * @extends plotOptions.column.states.hover * @product highstock */ hover: { /** * The pixel width of the line/border around the candlestick. * * @product highstock */ lineWidth: 2 } }, /** * @extends plotOptions.ohlc.tooltip */ tooltip: defaultOptions.plotOptions.ohlc.tooltip, /** * @type {number|null} * @product highstock */ threshold: null, /** * The color of the line/border of the candlestick. * * In styled mode, the line stroke can be set with the * `.highcharts-candlestick-series .highcahrts-point` rule. * * @see [upLineColor](#plotOptions.candlestick.upLineColor) * * @sample {highstock} stock/plotoptions/candlestick-linecolor/ * Candlestick line colors * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default #000000 * @product highstock */ lineColor: palette.neutralColor100, /** * The pixel width of the candlestick line/border. Defaults to `1`. * * * In styled mode, the line stroke width can be set with the * `.highcharts-candlestick-series .highcahrts-point` rule. * * @product highstock */ lineWidth: 1, /** * The fill color of the candlestick when values are rising. * * In styled mode, the up color can be set with the * `.highcharts-candlestick-series .highcharts-point-up` rule. * * @sample {highstock} stock/plotoptions/candlestick-color/ * Custom colors * @sample {highstock} highcharts/css/candlestick/ * Colors in styled mode * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default #ffffff * @product highstock */ upColor: palette.backgroundColor, /** * @product highstock */ stickyTracking: true }); return CandlestickSeries; }(OHLCSeries)); SeriesRegistry.registerSeriesType('candlestick', CandlestickSeries); /* * * * Default Export * * */ /* * * * API Options * * */ /** * A `candlestick` series. If the [type](#series.candlestick.type) * option is not specified, it is inherited from [chart.type]( * #chart.type). * * @type {*} * @extends series,plotOptions.candlestick * @excluding dataParser, dataURL, marker * @product highstock * @apioption series.candlestick */ /** * An array of data points for the series. For the `candlestick` series * type, points can be given in the following ways: * * 1. An array of arrays with 5 or 4 values. In this case, the values correspond * to `x,open,high,low,close`. If the first value is a string, it is applied * as the name of the point, and the `x` value is inferred. The `x` value can * also be omitted, in which case the inner arrays should be of length 4. * Then the `x` value is automatically calculated, either starting at 0 and * incremented by 1, or from `pointStart` and `pointInterval` given in the * series options. * ```js * data: [ * [0, 7, 2, 0, 4], * [1, 1, 4, 2, 8], * [2, 3, 3, 9, 3] * ] * ``` * * 2. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.candlestick.turboThreshold), this option is not * available. * ```js * data: [{ * x: 1, * open: 9, * high: 2, * low: 4, * close: 6, * name: "Point2", * color: "#00FF00" * }, { * x: 1, * open: 1, * high: 4, * low: 7, * close: 7, * name: "Point1", * color: "#FF00FF" * }] * ``` * * @type {Array<Array<(number|string),number,number,number>|Array<(number|string),number,number,number,number>|*>} * @extends series.ohlc.data * @excluding y * @product highstock * @apioption series.candlestick.data */ ''; // adds doclets above to transpilat return CandlestickSeries; }); _registerModule(_modules, 'Series/Flags/FlagsPoint.js', [_modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (SeriesRegistry, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var ColumnSeries = SeriesRegistry.seriesTypes.column; var isNumber = U.isNumber; /* * * * Class * * */ var FlagsPoint = /** @class */ (function (_super) { __extends(FlagsPoint, _super); function FlagsPoint() { /* * * * Properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; _this.options = void 0; _this.series = void 0; return _this; } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * @private */ FlagsPoint.prototype.isValid = function () { // #9233 - Prevent from treating flags as null points (even if // they have no y values defined). return isNumber(this.y) || typeof this.y === 'undefined'; }; /** * @private */ FlagsPoint.prototype.hasNewShapeType = function () { var shape = this.options.shape || this.series.options.shape; return this.graphic && shape && shape !== this.graphic.symbolKey; }; return FlagsPoint; }(ColumnSeries.prototype.pointClass)); /* * * * Default Export * * */ return FlagsPoint; }); _registerModule(_modules, 'Mixins/OnSeries.js', [_modules['Series/Column/ColumnSeries.js'], _modules['Core/Series/Series.js'], _modules['Core/Utilities.js']], function (ColumnSeries, Series, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var columnProto = ColumnSeries.prototype; var seriesProto = Series.prototype; var defined = U.defined, stableSort = U.stableSort; /** * @private * @mixin onSeriesMixin */ var onSeriesMixin = { /* eslint-disable valid-jsdoc */ /** * Override getPlotBox. If the onSeries option is valid, return the plot box * of the onSeries, otherwise proceed as usual. * * @private * @function onSeriesMixin.getPlotBox * @return {Highcharts.SeriesPlotBoxObject} */ getPlotBox: function () { return seriesProto.getPlotBox.call((this.options.onSeries && this.chart.get(this.options.onSeries)) || this); }, /** * Extend the translate method by placing the point on the related series * * @private * @function onSeriesMixin.translate * @return {void} */ translate: function () { columnProto.translate.apply(this); var series = this, options = series.options, chart = series.chart, points = series.points, cursor = points.length - 1, point, lastPoint, optionsOnSeries = options.onSeries, onSeries = (optionsOnSeries && chart.get(optionsOnSeries)), onKey = options.onKey || 'y', step = onSeries && onSeries.options.step, onData = (onSeries && onSeries.points), i = onData && onData.length, inverted = chart.inverted, xAxis = series.xAxis, yAxis = series.yAxis, xOffset = 0, leftPoint, lastX, rightPoint, currentDataGrouping, distanceRatio; // relate to a master series if (onSeries && onSeries.visible && i) { xOffset = (onSeries.pointXOffset || 0) + (onSeries.barW || 0) / 2; currentDataGrouping = onSeries.currentDataGrouping; lastX = (onData[i - 1].x + (currentDataGrouping ? currentDataGrouping.totalRange : 0)); // #2374 // sort the data points stableSort(points, function (a, b) { return (a.x - b.x); }); onKey = 'plot' + onKey[0].toUpperCase() + onKey.substr(1); while (i-- && points[cursor]) { leftPoint = onData[i]; point = points[cursor]; point.y = leftPoint.y; if (leftPoint.x <= point.x && typeof leftPoint[onKey] !== 'undefined') { if (point.x <= lastX) { // #803 point.plotY = leftPoint[onKey]; // interpolate between points, #666 if (leftPoint.x < point.x && !step) { rightPoint = onData[i + 1]; if (rightPoint && typeof rightPoint[onKey] !== 'undefined') { // the distance ratio, between 0 and 1 distanceRatio = (point.x - leftPoint.x) / (rightPoint.x - leftPoint.x); point.plotY += distanceRatio * // the plotY distance (rightPoint[onKey] - leftPoint[onKey]); point.y += distanceRatio * (rightPoint.y - leftPoint.y); } } } cursor--; i++; // check again for points in the same x position if (cursor < 0) { break; } } } } // Add plotY position and handle stacking points.forEach(function (point, i) { var stackIndex; point.plotX += xOffset; // #2049 // Undefined plotY means the point is either on axis, outside series // range or hidden series. If the series is outside the range of the // x axis it should fall through with an undefined plotY, but then // we must remove the shapeArgs (#847). For inverted charts, we need // to calculate position anyway, because series.invertGroups is not // defined if (typeof point.plotY === 'undefined' || inverted) { if (point.plotX >= 0 && point.plotX <= xAxis.len) { // We're inside xAxis range if (inverted) { point.plotY = xAxis.translate(point.x, 0, 1, 0, 1); point.plotX = defined(point.y) ? yAxis.translate(point.y, 0, 0, 0, 1) : 0; } else { point.plotY = (xAxis.opposite ? 0 : series.yAxis.len) + xAxis.offset; // For the windbarb demo } } else { point.shapeArgs = {}; // 847 } } // if multiple flags appear at the same x, order them into a stack lastPoint = points[i - 1]; if (lastPoint && lastPoint.plotX === point.plotX) { if (typeof lastPoint.stackIndex === 'undefined') { lastPoint.stackIndex = 0; } stackIndex = lastPoint.stackIndex + 1; } point.stackIndex = stackIndex; // #3639 }); this.onSeries = onSeries; } /* eslint-enable valid-jsdoc */ }; return onSeriesMixin; }); _registerModule(_modules, 'Series/Flags/FlagsSymbols.js', [_modules['Core/Renderer/RendererRegistry.js'], _modules['Core/Renderer/SVG/SVGRenderer.js']], function (RendererRegistry, SVGRenderer) { /* * * * Imports * * */ var symbols = SVGRenderer.prototype.symbols; /* * * * Symbols * * */ // create the flag icon with anchor symbols.flag = function (x, y, w, h, options) { var anchorX = (options && options.anchorX) || x, anchorY = (options && options.anchorY) || y; // To do: unwanted any cast because symbols.circle has wrong type, it // actually returns an SVGPathArray var path = symbols.circle(anchorX - 1, anchorY - 1, 2, 2); path.push(['M', anchorX, anchorY], ['L', x, y + h], ['L', x, y], ['L', x + w, y], ['L', x + w, y + h], ['L', x, y + h], ['Z']); return path; }; /** * Create the circlepin and squarepin icons with anchor. * @private * @param {string} shape - circle or square * @return {void} */ function createPinSymbol(shape) { symbols[(shape + 'pin')] = function (x, y, w, h, options) { var anchorX = options && options.anchorX, anchorY = options && options.anchorY; var path; // For single-letter flags, make sure circular flags are not taller // than their width if (shape === 'circle' && h > w) { x -= Math.round((h - w) / 2); w = h; } path = (symbols[shape])(x, y, w, h); if (anchorX && anchorY) { /** * If the label is below the anchor, draw the connecting line from * the top edge of the label, otherwise start drawing from the * bottom edge */ var labelX = anchorX; if (shape === 'circle') { labelX = x + w / 2; } else { var startSeg = path[0]; var endSeg = path[1]; if (startSeg[0] === 'M' && endSeg[0] === 'L') { labelX = (startSeg[1] + endSeg[1]) / 2; } } var labelY = (y > anchorY) ? y : y + h; path.push([ 'M', labelX, labelY ], [ 'L', anchorX, anchorY ]); path = path.concat(symbols.circle(anchorX - 1, anchorY - 1, 2, 2)); } return path; }; } createPinSymbol('circle'); createPinSymbol('square'); /** * The symbol callbacks are generated on the SVGRenderer object in all browsers. * Even VML browsers need this in order to generate shapes in export. Now share * them with the VMLRenderer. */ var Renderer = RendererRegistry.getRendererType(); if (Renderer !== SVGRenderer) { Renderer.prototype.symbols.circlepin = symbols.circlepin; Renderer.prototype.symbols.flag = symbols.flag; Renderer.prototype.symbols.squarepin = symbols.squarepin; } /* * * * Default Export * * */ return symbols; }); _registerModule(_modules, 'Series/Flags/FlagsSeries.js', [_modules['Series/Flags/FlagsPoint.js'], _modules['Core/Globals.js'], _modules['Mixins/OnSeries.js'], _modules['Core/Color/Palette.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (FlagsPoint, H, OnSeriesMixin, palette, SeriesRegistry, SVGElement, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var noop = H.noop; var Series = SeriesRegistry.series, ColumnSeries = SeriesRegistry.seriesTypes.column; var addEvent = U.addEvent, defined = U.defined, extend = U.extend, merge = U.merge, objectEach = U.objectEach, wrap = U.wrap; /** * The Flags series. * * @private * @class * @name Highcharts.seriesTypes.flags * * @augments Highcharts.Series */ var FlagsSeries = /** @class */ (function (_super) { __extends(FlagsSeries, _super); function FlagsSeries() { /* * * * Static Properties * * */ var _this = _super !== null && _super.apply(this, arguments) || this; /* * * * Properties * * */ _this.data = void 0; _this.options = void 0; _this.points = void 0; return _this; /* eslint-enable valid-jsdoc */ } /* * * * Functions * * */ /* eslint-disable valid-jsdoc */ /** * Disable animation, but keep clipping (#8546). * @private */ FlagsSeries.prototype.animate = function (init) { if (init) { this.setClip(); } }; /** * Draw the markers. * @private */ FlagsSeries.prototype.drawPoints = function () { var series = this, points = series.points, chart = series.chart, renderer = chart.renderer, plotX, plotY, inverted = chart.inverted, options = series.options, optionsY = options.y, shape, i, point, graphic, stackIndex, anchorY, attribs, outsideRight, yAxis = series.yAxis, boxesMap = {}, boxes = [], centered; i = points.length; while (i--) { point = points[i]; outsideRight = (inverted ? point.plotY : point.plotX) > series.xAxis.len; plotX = point.plotX; stackIndex = point.stackIndex; shape = point.options.shape || options.shape; plotY = point.plotY; if (typeof plotY !== 'undefined') { plotY = point.plotY + optionsY - (typeof stackIndex !== 'undefined' && (stackIndex * options.stackDistance)); } // skip connectors for higher level stacked points point.anchorX = stackIndex ? void 0 : point.plotX; anchorY = stackIndex ? void 0 : point.plotY; centered = shape !== 'flag'; graphic = point.graphic; // Only draw the point if y is defined and the flag is within // the visible area if (typeof plotY !== 'undefined' && plotX >= 0 && !outsideRight) { // #15384 if (graphic && point.hasNewShapeType()) { graphic = graphic.destroy(); } // Create the flag if (!graphic) { graphic = point.graphic = renderer.label('', null, null, shape, null, null, options.useHTML) .addClass('highcharts-point') .add(series.markerGroup); // Add reference to the point for tracker (#6303) if (point.graphic.div) { point.graphic.div.point = point; } graphic.isNew = true; } graphic.attr({ align: centered ? 'center' : 'left', width: options.width, height: options.height, 'text-align': options.textAlign }); if (!chart.styledMode) { graphic .attr(series.pointAttribs(point)) .css(merge(options.style, point.style)) .shadow(options.shadow); } if (plotX > 0) { // #3119 plotX -= graphic.strokeWidth() % 2; // #4285 } // Plant the flag attribs = { y: plotY, anchorY: anchorY }; if (options.allowOverlapX) { attribs.x = plotX; attribs.anchorX = point.anchorX; } graphic.attr({ text: point.options.title || options.title || 'A' })[graphic.isNew ? 'attr' : 'animate'](attribs); // Rig for the distribute function if (!options.allowOverlapX) { if (!boxesMap[point.plotX]) { boxesMap[point.plotX] = { align: centered ? 0.5 : 0, size: graphic.width, target: plotX, anchorX: plotX }; } else { boxesMap[point.plotX].size = Math.max(boxesMap[point.plotX].size, graphic.width); } } // Set the tooltip anchor position point.tooltipPos = [ plotX, plotY + yAxis.pos - chart.plotTop ]; // #6327 } else if (graphic) { point.graphic = graphic.destroy(); } } // Handle X-dimension overlapping if (!options.allowOverlapX) { objectEach(boxesMap, function (box) { box.plotX = box.anchorX; boxes.push(box); }); H.distribute(boxes, inverted ? yAxis.len : this.xAxis.len, 100); points.forEach(function (point) { var box = point.graphic && boxesMap[point.plotX]; if (box) { point.graphic[point.graphic.isNew ? 'attr' : 'animate']({ x: box.pos + box.align * box.size, anchorX: point.anchorX }); // Hide flag when its box position is not specified // (#8573, #9299) if (!defined(box.pos)) { point.graphic.attr({ x: -9999, anchorX: -9999 }); point.graphic.isNew = true; } else { point.graphic.isNew = false; } } }); } // Can be a mix of SVG and HTML and we need events for both (#6303) if (options.useHTML) { wrap(series.markerGroup, 'on', function (proceed) { return SVGElement.prototype.on.apply( // for HTML // eslint-disable-next-line no-invalid-this proceed.apply(this, [].slice.call(arguments, 1)), // and for SVG [].slice.call(arguments, 1)); }); } }; /** * Extend the column trackers with listeners to expand and contract * stacks. * @private */ FlagsSeries.prototype.drawTracker = function () { var series = this, points = series.points; _super.prototype.drawTracker.call(this); /* * * Bring each stacked flag up on mouse over, this allows readability * of vertically stacked elements as well as tight points on the x * axis. #1924. */ points.forEach(function (point) { var graphic = point.graphic; if (graphic) { if (point.unbindMouseOver) { point.unbindMouseOver(); } point.unbindMouseOver = addEvent(graphic.element, 'mouseover', function () { // Raise this point if (point.stackIndex > 0 && !point.raised) { point._y = graphic.y; graphic.attr({ y: point._y - 8 }); point.raised = true; } // Revert other raised points points.forEach(function (otherPoint) { if (otherPoint !== point && otherPoint.raised && otherPoint.graphic) { otherPoint.graphic.attr({ y: otherPoint._y }); otherPoint.raised = false; } }); }); } }); }; /** * Get presentational attributes * @private */ FlagsSeries.prototype.pointAttribs = function (point, state) { var options = this.options, color = (point && point.color) || this.color, lineColor = options.lineColor, lineWidth = (point && point.lineWidth), fill = (point && point.fillColor) || options.fillColor; if (state) { fill = options.states[state].fillColor; lineColor = options.states[state].lineColor; lineWidth = options.states[state].lineWidth; } return { fill: fill || color, stroke: lineColor || color, 'stroke-width': lineWidth || options.lineWidth || 0 }; }; /** * @private */ FlagsSeries.prototype.setClip = function () { Series.prototype.setClip.apply(this, arguments); if (this.options.clip !== false && this.sharedClipKey && this.markerGroup) { this.markerGroup.clip(this.chart.sharedClips[this.sharedClipKey]); } }; /** * Flags are used to mark events in stock charts. They can be added on the * timeline, or attached to a specific series. * * @sample stock/demo/flags-general/ * Flags on a line series * * @extends plotOptions.column * @excluding animation, borderColor, borderRadius, borderWidth, * colorByPoint, dataGrouping, pointPadding, pointWidth, * turboThreshold * @product highstock * @optionparent plotOptions.flags */ FlagsSeries.defaultOptions = merge(ColumnSeries.defaultOptions, { /** * In case the flag is placed on a series, on what point key to place * it. Line and columns have one key, `y`. In range or OHLC-type series, * however, the flag can optionally be placed on the `open`, `high`, * `low` or `close` key. * * @sample {highstock} stock/plotoptions/flags-onkey/ * Range series, flag on high * * @type {string} * @default y * @since 4.2.2 * @product highstock * @validvalue ["y", "open", "high", "low", "close"] * @apioption plotOptions.flags.onKey */ /** * The id of the series that the flags should be drawn on. If no id * is given, the flags are drawn on the x axis. * * @sample {highstock} stock/plotoptions/flags/ * Flags on series and on x axis * * @type {string} * @product highstock * @apioption plotOptions.flags.onSeries */ pointRange: 0, /** * Whether the flags are allowed to overlap sideways. If `false`, the * flags are moved sideways using an algorithm that seeks to place every * flag as close as possible to its original position. * * @sample {highstock} stock/plotoptions/flags-allowoverlapx * Allow sideways overlap * * @since 6.0.4 */ allowOverlapX: false, /** * The shape of the marker. Can be one of "flag", "circlepin", * "squarepin", or an image of the format `url(/path-to-image.jpg)`. * Individual shapes can also be set for each point. * * @sample {highstock} stock/plotoptions/flags/ * Different shapes * * @type {Highcharts.FlagsShapeValue} * @product highstock */ shape: 'flag', /** * When multiple flags in the same series fall on the same value, this * number determines the vertical offset between them. * * @sample {highstock} stock/plotoptions/flags-stackdistance/ * A greater stack distance * * @product highstock */ stackDistance: 12, /** * Text alignment for the text inside the flag. * * @since 5.0.0 * @product highstock * @validvalue ["left", "center", "right"] */ textAlign: 'center', /** * Specific tooltip options for flag series. Flag series tooltips are * different from most other types in that a flag doesn't have a data * value, so the tooltip rather displays the `text` option for each * point. * * @extends plotOptions.series.tooltip * @excluding changeDecimals, valueDecimals, valuePrefix, valueSuffix * @product highstock */ tooltip: { pointFormat: '{point.text}' }, threshold: null, /** * The text to display on each flag. This can be defined on series * level, or individually for each point. Defaults to `"A"`. * * @type {string} * @default A * @product highstock * @apioption plotOptions.flags.title */ /** * The y position of the top left corner of the flag relative to either * the series (if onSeries is defined), or the x axis. Defaults to * `-30`. * * @product highstock */ y: -30, /** * Whether to use HTML to render the flag texts. Using HTML allows for * advanced formatting, images and reliable bi-directional text * rendering. Note that exported images won't respect the HTML, and that * HTML won't respect Z-index settings. * * @type {boolean} * @default false * @since 1.3 * @product highstock * @apioption plotOptions.flags.useHTML */ /** * Fixed width of the flag's shape. By default, width is autocalculated * according to the flag's title. * * @sample {highstock} stock/demo/flags-shapes/ * Flags with fixed width * * @type {number} * @product highstock * @apioption plotOptions.flags.width */ /** * Fixed height of the flag's shape. By default, height is * autocalculated according to the flag's title. * * @type {number} * @product highstock * @apioption plotOptions.flags.height */ /** * The fill color for the flags. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highstock */ fillColor: palette.backgroundColor, /** * The color of the line/border of the flag. * * In styled mode, the stroke is set in the * `.highcharts-flag-series.highcharts-point` rule. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default #000000 * @product highstock * @apioption plotOptions.flags.lineColor */ /** * The pixel width of the flag's line/border. * * @product highstock */ lineWidth: 1, states: { /** * @extends plotOptions.column.states.hover * @product highstock */ hover: { /** * The color of the line/border of the flag. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highstock */ lineColor: palette.neutralColor100, /** * The fill or background color of the flag. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highstock */ fillColor: palette.highlightColor20 } }, /** * The text styles of the flag. * * In styled mode, the styles are set in the * `.highcharts-flag-series .highcharts-point` rule. * * @type {Highcharts.CSSObject} * @default {"fontSize": "11px", "fontWeight": "bold"} * @product highstock */ style: { /** @ignore-option */ fontSize: '11px', /** @ignore-option */ fontWeight: 'bold' } }); return FlagsSeries; }(ColumnSeries)); extend(FlagsSeries.prototype, { allowDG: false, /** * @private * @function Highcharts.seriesTypes.flags#buildKDTree */ buildKDTree: noop, forceCrop: true, getPlotBox: OnSeriesMixin.getPlotBox, /** * Inherit the initialization from base Series. * * @private * @borrows Highcharts.Series#init as Highcharts.seriesTypes.flags#init */ init: Series.prototype.init, /** * Don't invert the flag marker group (#4960). * * @private * @function Highcharts.seriesTypes.flags#invertGroups */ invertGroups: noop, // Flags series group should not be invertible (#14063). invertible: false, noSharedTooltip: true, pointClass: FlagsPoint, sorted: false, takeOrdinalPosition: false, trackerGroups: ['markerGroup'], translate: OnSeriesMixin.translate }); SeriesRegistry.registerSeriesType('flags', FlagsSeries); /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * @typedef {"circlepin"|"flag"|"squarepin"} Highcharts.FlagsShapeValue */ ''; // detach doclets above /* * * * API Option * * */ /** * A `flags` series. If the [type](#series.flags.type) option is not * specified, it is inherited from [chart.type](#chart.type). * * @extends series,plotOptions.flags * @excluding animation, borderColor, borderRadius, borderWidth, colorByPoint, * connectNulls, dashStyle, dataGrouping, dataParser, dataURL, * gapSize, gapUnit, linecap, lineWidth, marker, pointPadding, * pointWidth, step, turboThreshold, useOhlcData * @product highstock * @apioption series.flags */ /** * An array of data points for the series. For the `flags` series type, * points can be given in the following ways: * * 1. An array of objects with named values. The following snippet shows only a * few settings, see the complete options set below. If the total number of * data points exceeds the series' * [turboThreshold](#series.flags.turboThreshold), this option is not * available. * ```js * data: [{ * x: 1, * title: "A", * text: "First event" * }, { * x: 1, * title: "B", * text: "Second event" * }] * ``` * * @type {Array<*>} * @extends series.line.data * @excluding dataLabels, marker, name, y * @product highstock * @apioption series.flags.data */ /** * The fill color of an individual flag. By default it inherits from * the series color. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @product highstock * @apioption series.flags.data.fillColor */ /** * The longer text to be shown in the flag's tooltip. * * @type {string} * @product highstock * @apioption series.flags.data.text */ /** * The short text to be shown on the flag. * * @type {string} * @product highstock * @apioption series.flags.data.title */ ''; // adds doclets above to transpiled file return FlagsSeries; }); _registerModule(_modules, 'Extensions/RangeSelector.js', [_modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/Globals.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Color/Palette.js'], _modules['Core/Renderer/SVG/SVGElement.js'], _modules['Core/Utilities.js']], function (Axis, Chart, H, D, palette, SVGElement, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var defaultOptions = D.defaultOptions; var addEvent = U.addEvent, createElement = U.createElement, css = U.css, defined = U.defined, destroyObjectProperties = U.destroyObjectProperties, discardElement = U.discardElement, extend = U.extend, find = U.find, fireEvent = U.fireEvent, isNumber = U.isNumber, merge = U.merge, objectEach = U.objectEach, pad = U.pad, pick = U.pick, pInt = U.pInt, splat = U.splat; /** * Define the time span for the button * * @typedef {"all"|"day"|"hour"|"millisecond"|"minute"|"month"|"second"|"week"|"year"|"ytd"} Highcharts.RangeSelectorButtonTypeValue */ /** * Callback function to react on button clicks. * * @callback Highcharts.RangeSelectorClickCallbackFunction * * @param {global.Event} e * Event arguments. * * @param {boolean|undefined} * Return false to cancel the default button event. */ /** * Callback function to parse values entered in the input boxes and return a * valid JavaScript time as milliseconds since 1970. * * @callback Highcharts.RangeSelectorParseCallbackFunction * * @param {string} value * Input value to parse. * * @return {number} * Parsed JavaScript time value. */ /* ************************************************************************** * * Start Range Selector code * * ************************************************************************** */ extend(defaultOptions, { /** * The range selector is a tool for selecting ranges to display within * the chart. It provides buttons to select preconfigured ranges in * the chart, like 1 day, 1 week, 1 month etc. It also provides input * boxes where min and max dates can be manually input. * * @product highstock gantt * @optionparent rangeSelector */ rangeSelector: { /** * Whether to enable all buttons from the start. By default buttons are * only enabled if the corresponding time range exists on the X axis, * but enabling all buttons allows for dynamically loading different * time ranges. * * @sample {highstock} stock/rangeselector/allbuttonsenabled-true/ * All buttons enabled * * @since 2.0.3 */ allButtonsEnabled: false, /** * An array of configuration objects for the buttons. * * Defaults to: * ```js * buttons: [{ * type: 'month', * count: 1, * text: '1m', * title: 'View 1 month' * }, { * type: 'month', * count: 3, * text: '3m', * title: 'View 3 months' * }, { * type: 'month', * count: 6, * text: '6m', * title: 'View 6 months' * }, { * type: 'ytd', * text: 'YTD', * title: 'View year to date' * }, { * type: 'year', * count: 1, * text: '1y', * title: 'View 1 year' * }, { * type: 'all', * text: 'All', * title: 'View all' * }] * ``` * * @sample {highstock} stock/rangeselector/datagrouping/ * Data grouping by buttons * * @type {Array<*>} */ buttons: void 0, /** * How many units of the defined type the button should span. If `type` * is "month" and `count` is 3, the button spans three months. * * @type {number} * @default 1 * @apioption rangeSelector.buttons.count */ /** * Fires when clicking on the rangeSelector button. One parameter, * event, is passed to the function, containing common event * information. * * ```js * click: function(e) { * console.log(this); * } * ``` * * Return false to stop default button's click action. * * @sample {highstock} stock/rangeselector/button-click/ * Click event on the button * * @type {Highcharts.RangeSelectorClickCallbackFunction} * @apioption rangeSelector.buttons.events.click */ /** * Additional range (in milliseconds) added to the end of the calculated * time span. * * @sample {highstock} stock/rangeselector/min-max-offsets/ * Button offsets * * @type {number} * @default 0 * @since 6.0.0 * @apioption rangeSelector.buttons.offsetMax */ /** * Additional range (in milliseconds) added to the start of the * calculated time span. * * @sample {highstock} stock/rangeselector/min-max-offsets/ * Button offsets * * @type {number} * @default 0 * @since 6.0.0 * @apioption rangeSelector.buttons.offsetMin */ /** * When buttons apply dataGrouping on a series, by default zooming * in/out will deselect buttons and unset dataGrouping. Enable this * option to keep buttons selected when extremes change. * * @sample {highstock} stock/rangeselector/preserve-datagrouping/ * Different preserveDataGrouping settings * * @type {boolean} * @default false * @since 6.1.2 * @apioption rangeSelector.buttons.preserveDataGrouping */ /** * A custom data grouping object for each button. * * @see [series.dataGrouping](#plotOptions.series.dataGrouping) * * @sample {highstock} stock/rangeselector/datagrouping/ * Data grouping by range selector buttons * * @type {*} * @extends plotOptions.series.dataGrouping * @apioption rangeSelector.buttons.dataGrouping */ /** * The text for the button itself. * * @type {string} * @apioption rangeSelector.buttons.text */ /** * Explanation for the button, shown as a tooltip on hover, and used by * assistive technology. * * @type {string} * @apioption rangeSelector.buttons.title */ /** * Defined the time span for the button. Can be one of `millisecond`, * `second`, `minute`, `hour`, `day`, `week`, `month`, `year`, `ytd`, * and `all`. * * @type {Highcharts.RangeSelectorButtonTypeValue} * @apioption rangeSelector.buttons.type */ /** * The space in pixels between the buttons in the range selector. */ buttonSpacing: 5, /** * Whether to collapse the range selector buttons into a dropdown when * there is not enough room to show everything in a single row, instead * of dividing the range selector into multiple rows. * Can be one of the following: * - `always`: Always collapse * - `responsive`: Only collapse when there is not enough room * - `never`: Never collapse * * @sample {highstock} stock/rangeselector/dropdown/ * Dropdown option * * @validvalue ["always", "responsive", "never"] * @since 9.0.0 */ dropdown: 'responsive', /** * Enable or disable the range selector. Default to `true` for stock * charts, using the `stockChart` factory. * * @sample {highstock} stock/rangeselector/enabled/ * Disable the range selector * * @type {boolean|undefined} * @default {highstock} true */ enabled: void 0, /** * The vertical alignment of the rangeselector box. Allowed properties * are `top`, `middle`, `bottom`. * * @sample {highstock} stock/rangeselector/vertical-align-middle/ * Middle * @sample {highstock} stock/rangeselector/vertical-align-bottom/ * Bottom * * @type {Highcharts.VerticalAlignValue} * @since 6.0.0 */ verticalAlign: 'top', /** * A collection of attributes for the buttons. The object takes SVG * attributes like `fill`, `stroke`, `stroke-width`, as well as `style`, * a collection of CSS properties for the text. * * The object can also be extended with states, so you can set * presentational options for `hover`, `select` or `disabled` button * states. * * CSS styles for the text label. * * In styled mode, the buttons are styled by the * `.highcharts-range-selector-buttons .highcharts-button` rule with its * different states. * * @sample {highstock} stock/rangeselector/styling/ * Styling the buttons and inputs * * @type {Highcharts.SVGAttributes} */ buttonTheme: { /** @ignore */ width: 28, /** @ignore */ height: 18, /** @ignore */ padding: 2, /** @ignore */ zIndex: 7 // #484, #852 }, /** * When the rangeselector is floating, the plot area does not reserve * space for it. This opens for positioning anywhere on the chart. * * @sample {highstock} stock/rangeselector/floating/ * Placing the range selector between the plot area and the * navigator * * @since 6.0.0 */ floating: false, /** * The x offset of the range selector relative to its horizontal * alignment within `chart.spacingLeft` and `chart.spacingRight`. * * @since 6.0.0 */ x: 0, /** * The y offset of the range selector relative to its horizontal * alignment within `chart.spacingLeft` and `chart.spacingRight`. * * @since 6.0.0 */ y: 0, /** * Deprecated. The height of the range selector. Currently it is * calculated dynamically. * * @deprecated * @type {number|undefined} * @since 2.1.9 */ height: void 0, /** * The border color of the date input boxes. * * @sample {highstock} stock/rangeselector/styling/ * Styling the buttons and inputs * * @type {Highcharts.ColorString} * @since 1.3.7 */ inputBoxBorderColor: 'none', /** * The pixel height of the date input boxes. * * @sample {highstock} stock/rangeselector/styling/ * Styling the buttons and inputs * * @since 1.3.7 */ inputBoxHeight: 17, /** * The pixel width of the date input boxes. When `undefined`, the width * is fitted to the rendered content. * * @sample {highstock} stock/rangeselector/styling/ * Styling the buttons and inputs * * @type {number|undefined} * @since 1.3.7 */ inputBoxWidth: void 0, /** * The date format in the input boxes when not selected for editing. * Defaults to `%b %e, %Y`. * * This is used to determine which type of input to show, * `datetime-local`, `date` or `time` and falling back to `text` when * the browser does not support the input type or the format contains * milliseconds. * * @sample {highstock} stock/rangeselector/input-type/ * Input types * @sample {highstock} stock/rangeselector/input-format/ * Milliseconds in the range selector * */ inputDateFormat: '%b %e, %Y', /** * A custom callback function to parse values entered in the input boxes * and return a valid JavaScript time as milliseconds since 1970. * The first argument passed is a value to parse, * second is a boolean indicating use of the UTC time. * * This will only get called for inputs of type `text`. Since v8.2.3, * the input type is dynamically determined based on the granularity * of the `inputDateFormat` and the browser support. * * @sample {highstock} stock/rangeselector/input-format/ * Milliseconds in the range selector * * @type {Highcharts.RangeSelectorParseCallbackFunction} * @since 1.3.3 */ inputDateParser: void 0, /** * The date format in the input boxes when they are selected for * editing. This must be a format that is recognized by JavaScript * Date.parse. * * This will only be used for inputs of type `text`. Since v8.2.3, * the input type is dynamically determined based on the granularity * of the `inputDateFormat` and the browser support. * * @sample {highstock} stock/rangeselector/input-format/ * Milliseconds in the range selector * */ inputEditDateFormat: '%Y-%m-%d', /** * Enable or disable the date input boxes. */ inputEnabled: true, /** * Positioning for the input boxes. Allowed properties are `align`, * `x` and `y`. * * @since 1.2.4 */ inputPosition: { /** * The alignment of the input box. Allowed properties are `left`, * `center`, `right`. * * @sample {highstock} stock/rangeselector/input-button-position/ * Alignment * * @type {Highcharts.AlignValue} * @since 6.0.0 */ align: 'right', /** * X offset of the input row. */ x: 0, /** * Y offset of the input row. */ y: 0 }, /** * The space in pixels between the labels and the date input boxes in * the range selector. * * @since 9.0.0 */ inputSpacing: 5, /** * The index of the button to appear pre-selected. * * @type {number} */ selected: void 0, /** * Positioning for the button row. * * @since 1.2.4 */ buttonPosition: { /** * The alignment of the input box. Allowed properties are `left`, * `center`, `right`. * * @sample {highstock} stock/rangeselector/input-button-position/ * Alignment * * @type {Highcharts.AlignValue} * @since 6.0.0 */ align: 'left', /** * X offset of the button row. */ x: 0, /** * Y offset of the button row. */ y: 0 }, /** * CSS for the HTML inputs in the range selector. * * In styled mode, the inputs are styled by the * `.highcharts-range-input text` rule in SVG mode, and * `input.highcharts-range-selector` when active. * * @sample {highstock} stock/rangeselector/styling/ * Styling the buttons and inputs * * @type {Highcharts.CSSObject} * @apioption rangeSelector.inputStyle */ inputStyle: { /** @ignore */ color: palette.highlightColor80, /** @ignore */ cursor: 'pointer' }, /** * CSS styles for the labels - the Zoom, From and To texts. * * In styled mode, the labels are styled by the * `.highcharts-range-label` class. * * @sample {highstock} stock/rangeselector/styling/ * Styling the buttons and inputs * * @type {Highcharts.CSSObject} */ labelStyle: { /** @ignore */ color: palette.neutralColor60 } } }); extend(defaultOptions.lang, /** * Language object. The language object is global and it can't be set * on each chart initialization. Instead, use `Highcharts.setOptions` to * set it before any chart is initialized. * * ```js * Highcharts.setOptions({ * lang: { * months: [ * 'Janvier', 'Février', 'Mars', 'Avril', * 'Mai', 'Juin', 'Juillet', 'Août', * 'Septembre', 'Octobre', 'Novembre', 'Décembre' * ], * weekdays: [ * 'Dimanche', 'Lundi', 'Mardi', 'Mercredi', * 'Jeudi', 'Vendredi', 'Samedi' * ] * } * }); * ``` * * @optionparent lang */ { /** * The text for the label for the range selector buttons. * * @product highstock gantt */ rangeSelectorZoom: 'Zoom', /** * The text for the label for the "from" input box in the range * selector. Since v9.0, this string is empty as the label is not * rendered by default. * * @product highstock gantt */ rangeSelectorFrom: '', /** * The text for the label for the "to" input box in the range selector. * * @product highstock gantt */ rangeSelectorTo: '→' }); /* eslint-disable no-invalid-this, valid-jsdoc */ /** * The range selector. * * @private * @class * @name Highcharts.RangeSelector * @param {Highcharts.Chart} chart */ var RangeSelector = /** @class */ (function () { function RangeSelector(chart) { /* * * * Properties * * */ this.buttons = void 0; this.buttonOptions = RangeSelector.prototype.defaultButtons; this.initialButtonGroupWidth = 0; this.options = void 0; this.chart = chart; // Run RangeSelector this.init(chart); } /** * The method to run when one of the buttons in the range selectors is * clicked * * @private * @function Highcharts.RangeSelector#clickButton * @param {number} i * The index of the button * @param {boolean} [redraw] * @return {void} */ RangeSelector.prototype.clickButton = function (i, redraw) { var rangeSelector = this, chart = rangeSelector.chart, rangeOptions = rangeSelector.buttonOptions[i], baseAxis = chart.xAxis[0], unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis || {}, dataMin = unionExtremes.dataMin, dataMax = unionExtremes.dataMax, newMin, newMax = baseAxis && Math.round(Math.min(baseAxis.max, pick(dataMax, baseAxis.max))), // #1568 type = rangeOptions.type, baseXAxisOptions, range = rangeOptions._range, rangeMin, minSetting, rangeSetting, ctx, ytdExtremes, dataGrouping = rangeOptions.dataGrouping; // chart has no data, base series is removed if (dataMin === null || dataMax === null) { return; } // Set the fixed range before range is altered chart.fixedRange = range; rangeSelector.setSelected(i); // Apply dataGrouping associated to button if (dataGrouping) { this.forcedDataGrouping = true; Axis.prototype.setDataGrouping.call(baseAxis || { chart: this.chart }, dataGrouping, false); this.frozenStates = rangeOptions.preserveDataGrouping; } // Apply range if (type === 'month' || type === 'year') { if (!baseAxis) { // This is set to the user options and picked up later when the // axis is instantiated so that we know the min and max. range = rangeOptions; } else { ctx = { range: rangeOptions, max: newMax, chart: chart, dataMin: dataMin, dataMax: dataMax }; newMin = baseAxis.minFromRange.call(ctx); if (isNumber(ctx.newMax)) { newMax = ctx.newMax; } } // Fixed times like minutes, hours, days } else if (range) { newMin = Math.max(newMax - range, dataMin); newMax = Math.min(newMin + range, dataMax); } else if (type === 'ytd') { // On user clicks on the buttons, or a delayed action running from // the beforeRender event (below), the baseAxis is defined. if (baseAxis) { // When "ytd" is the pre-selected button for the initial view, // its calculation is delayed and rerun in the beforeRender // event (below). When the series are initialized, but before // the chart is rendered, we have access to the xData array // (#942). if (typeof dataMax === 'undefined') { dataMin = Number.MAX_VALUE; dataMax = Number.MIN_VALUE; chart.series.forEach(function (series) { // reassign it to the last item var xData = series.xData; dataMin = Math.min(xData[0], dataMin); dataMax = Math.max(xData[xData.length - 1], dataMax); }); redraw = false; } ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, chart.time.useUTC); newMin = rangeMin = ytdExtremes.min; newMax = ytdExtremes.max; // "ytd" is pre-selected. We don't yet have access to processed // point and extremes data (things like pointStart and pointInterval // are missing), so we delay the process (#942) } else { rangeSelector.deferredYTDClick = i; return; } } else if (type === 'all' && baseAxis) { // If the navigator exist and the axis range is declared reset that // range and from now on only use the range set by a user, #14742. if (chart.navigator && chart.navigator.baseSeries[0]) { chart.navigator.baseSeries[0].xAxis.options.range = void 0; } newMin = dataMin; newMax = dataMax; } if (defined(newMin)) { newMin += rangeOptions._offsetMin; } if (defined(newMax)) { newMax += rangeOptions._offsetMax; } if (this.dropdown) { this.dropdown.selectedIndex = i + 1; } // Update the chart if (!baseAxis) { // Axis not yet instanciated. Temporarily set min and range // options and remove them on chart load (#4317). baseXAxisOptions = splat(chart.options.xAxis)[0]; rangeSetting = baseXAxisOptions.range; baseXAxisOptions.range = range; minSetting = baseXAxisOptions.min; baseXAxisOptions.min = rangeMin; addEvent(chart, 'load', function resetMinAndRange() { baseXAxisOptions.range = rangeSetting; baseXAxisOptions.min = minSetting; }); } else { // Existing axis object. Set extremes after render time. baseAxis.setExtremes(newMin, newMax, pick(redraw, true), void 0, // auto animation { trigger: 'rangeSelectorButton', rangeSelectorButton: rangeOptions }); } fireEvent(this, 'afterBtnClick'); }; /** * Set the selected option. This method only sets the internal flag, it * doesn't update the buttons or the actual zoomed range. * * @private * @function Highcharts.RangeSelector#setSelected * @param {number} [selected] * @return {void} */ RangeSelector.prototype.setSelected = function (selected) { this.selected = this.options.selected = selected; }; /** * Initialize the range selector * * @private * @function Highcharts.RangeSelector#init * @param {Highcharts.Chart} chart * @return {void} */ RangeSelector.prototype.init = function (chart) { var rangeSelector = this, options = chart.options.rangeSelector, buttonOptions = options.buttons || rangeSelector.defaultButtons.slice(), selectedOption = options.selected, blurInputs = function () { var minInput = rangeSelector.minInput, maxInput = rangeSelector.maxInput; // #3274 in some case blur is not defined if (minInput && minInput.blur) { fireEvent(minInput, 'blur'); } if (maxInput && maxInput.blur) { fireEvent(maxInput, 'blur'); } }; rangeSelector.chart = chart; rangeSelector.options = options; rangeSelector.buttons = []; rangeSelector.buttonOptions = buttonOptions; this.eventsToUnbind = []; this.eventsToUnbind.push(addEvent(chart.container, 'mousedown', blurInputs)); this.eventsToUnbind.push(addEvent(chart, 'resize', blurInputs)); // Extend the buttonOptions with actual range buttonOptions.forEach(rangeSelector.computeButtonRange); // zoomed range based on a pre-selected button index if (typeof selectedOption !== 'undefined' && buttonOptions[selectedOption]) { this.clickButton(selectedOption, false); } this.eventsToUnbind.push(addEvent(chart, 'load', function () { // If a data grouping is applied to the current button, release it // when extremes change if (chart.xAxis && chart.xAxis[0]) { addEvent(chart.xAxis[0], 'setExtremes', function (e) { if (this.max - this.min !== chart.fixedRange && e.trigger !== 'rangeSelectorButton' && e.trigger !== 'updatedData' && rangeSelector.forcedDataGrouping && !rangeSelector.frozenStates) { this.setDataGrouping(false, false); } }); } })); }; /** * Dynamically update the range selector buttons after a new range has been * set * * @private * @function Highcharts.RangeSelector#updateButtonStates * @return {void} */ RangeSelector.prototype.updateButtonStates = function () { var rangeSelector = this, chart = this.chart, dropdown = this.dropdown, baseAxis = chart.xAxis[0], actualRange = Math.round(baseAxis.max - baseAxis.min), hasNoData = !baseAxis.hasVisibleSeries, day = 24 * 36e5, // A single day in milliseconds unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || baseAxis, dataMin = unionExtremes.dataMin, dataMax = unionExtremes.dataMax, ytdExtremes = rangeSelector.getYTDExtremes(dataMax, dataMin, chart.time.useUTC), ytdMin = ytdExtremes.min, ytdMax = ytdExtremes.max, selected = rangeSelector.selected, selectedExists = isNumber(selected), allButtonsEnabled = rangeSelector.options.allButtonsEnabled, buttons = rangeSelector.buttons; rangeSelector.buttonOptions.forEach(function (rangeOptions, i) { var range = rangeOptions._range, type = rangeOptions.type, count = rangeOptions.count || 1, button = buttons[i], state = 0, disable, select, offsetRange = rangeOptions._offsetMax - rangeOptions._offsetMin, isSelected = i === selected, // Disable buttons where the range exceeds what is allowed in // the current view isTooGreatRange = range > dataMax - dataMin, // Disable buttons where the range is smaller than the minimum // range isTooSmallRange = range < baseAxis.minRange, // Do not select the YTD button if not explicitly told so isYTDButNotSelected = false, // Disable the All button if we're already showing all isAllButAlreadyShowingAll = false, isSameRange = range === actualRange; // Months and years have a variable range so we check the extremes if ((type === 'month' || type === 'year') && (actualRange + 36e5 >= { month: 28, year: 365 }[type] * day * count - offsetRange) && (actualRange - 36e5 <= { month: 31, year: 366 }[type] * day * count + offsetRange)) { isSameRange = true; } else if (type === 'ytd') { isSameRange = (ytdMax - ytdMin + offsetRange) === actualRange; isYTDButNotSelected = !isSelected; } else if (type === 'all') { isSameRange = (baseAxis.max - baseAxis.min >= dataMax - dataMin); isAllButAlreadyShowingAll = (!isSelected && selectedExists && isSameRange); } // The new zoom area happens to match the range for a button - mark // it selected. This happens when scrolling across an ordinal gap. // It can be seen in the intraday demos when selecting 1h and scroll // across the night gap. disable = (!allButtonsEnabled && (isTooGreatRange || isTooSmallRange || isAllButAlreadyShowingAll || hasNoData)); select = ((isSelected && isSameRange) || (isSameRange && !selectedExists && !isYTDButNotSelected) || (isSelected && rangeSelector.frozenStates)); if (disable) { state = 3; } else if (select) { selectedExists = true; // Only one button can be selected state = 2; } // If state has changed, update the button if (button.state !== state) { button.setState(state); if (dropdown) { dropdown.options[i + 1].disabled = disable; if (state === 2) { dropdown.selectedIndex = i + 1; } } // Reset (#9209) if (state === 0 && selected === i) { rangeSelector.setSelected(); } } }); }; /** * Compute and cache the range for an individual button * * @private * @function Highcharts.RangeSelector#computeButtonRange * @param {Highcharts.RangeSelectorButtonsOptions} rangeOptions * @return {void} */ RangeSelector.prototype.computeButtonRange = function (rangeOptions) { var type = rangeOptions.type, count = rangeOptions.count || 1, // these time intervals have a fixed number of milliseconds, as // opposed to month, ytd and year fixedTimes = { millisecond: 1, second: 1000, minute: 60 * 1000, hour: 3600 * 1000, day: 24 * 3600 * 1000, week: 7 * 24 * 3600 * 1000 }; // Store the range on the button object if (fixedTimes[type]) { rangeOptions._range = fixedTimes[type] * count; } else if (type === 'month' || type === 'year') { rangeOptions._range = { month: 30, year: 365 }[type] * 24 * 36e5 * count; } rangeOptions._offsetMin = pick(rangeOptions.offsetMin, 0); rangeOptions._offsetMax = pick(rangeOptions.offsetMax, 0); rangeOptions._range += rangeOptions._offsetMax - rangeOptions._offsetMin; }; /** * Get the unix timestamp of a HTML input for the dates * * @private * @function Highcharts.RangeSelector#getInputValue * @param {string} name * @return {number} */ RangeSelector.prototype.getInputValue = function (name) { var input = name === 'min' ? this.minInput : this.maxInput; var options = this.chart.options.rangeSelector; var time = this.chart.time; if (input) { return ((input.type === 'text' && options.inputDateParser) || this.defaultInputDateParser)(input.value, time.useUTC, time); } return 0; }; /** * Set the internal and displayed value of a HTML input for the dates * * @private * @function Highcharts.RangeSelector#setInputValue * @param {string} name * @param {number} [inputTime] * @return {void} */ RangeSelector.prototype.setInputValue = function (name, inputTime) { var options = this.options, time = this.chart.time, input = name === 'min' ? this.minInput : this.maxInput, dateBox = name === 'min' ? this.minDateBox : this.maxDateBox; if (input) { var hcTimeAttr = input.getAttribute('data-hc-time'); var updatedTime = defined(hcTimeAttr) ? Number(hcTimeAttr) : void 0; if (defined(inputTime)) { var previousTime = updatedTime; if (defined(previousTime)) { input.setAttribute('data-hc-time-previous', previousTime); } input.setAttribute('data-hc-time', inputTime); updatedTime = inputTime; } input.value = time.dateFormat(this.inputTypeFormats[input.type] || options.inputEditDateFormat, updatedTime); if (dateBox) { dateBox.attr({ text: time.dateFormat(options.inputDateFormat, updatedTime) }); } } }; /** * Set the min and max value of a HTML input for the dates * * @private * @function Highcharts.RangeSelector#setInputExtremes * @param {string} name * @param {number} min * @param {number} max * @return {void} */ RangeSelector.prototype.setInputExtremes = function (name, min, max) { var input = name === 'min' ? this.minInput : this.maxInput; if (input) { var format = this.inputTypeFormats[input.type]; var time = this.chart.time; if (format) { var newMin = time.dateFormat(format, min); if (input.min !== newMin) { input.min = newMin; } var newMax = time.dateFormat(format, max); if (input.max !== newMax) { input.max = newMax; } } } }; /** * @private * @function Highcharts.RangeSelector#showInput * @param {string} name * @return {void} */ RangeSelector.prototype.showInput = function (name) { var dateBox = name === 'min' ? this.minDateBox : this.maxDateBox; var input = name === 'min' ? this.minInput : this.maxInput; if (input && dateBox && this.inputGroup) { var isTextInput = input.type === 'text'; var _a = this.inputGroup, translateX = _a.translateX, translateY = _a.translateY; var inputBoxWidth = this.options.inputBoxWidth; css(input, { width: isTextInput ? ((dateBox.width + (inputBoxWidth ? -2 : 20)) + 'px') : 'auto', height: isTextInput ? ((dateBox.height - 2) + 'px') : 'auto', border: '2px solid silver' }); if (isTextInput && inputBoxWidth) { css(input, { left: (translateX + dateBox.x) + 'px', top: translateY + 'px' }); // Inputs of types date, time or datetime-local should be centered // on top of the dateBox } else { css(input, { left: Math.min(Math.round(dateBox.x + translateX - (input.offsetWidth - dateBox.width) / 2), this.chart.chartWidth - input.offsetWidth) + 'px', top: (translateY - (input.offsetHeight - dateBox.height) / 2) + 'px' }); } } }; /** * @private * @function Highcharts.RangeSelector#hideInput * @param {string} name * @return {void} */ RangeSelector.prototype.hideInput = function (name) { var input = name === 'min' ? this.minInput : this.maxInput; if (input) { css(input, { top: '-9999em', border: 0, width: '1px', height: '1px' }); } }; /** * @private * @function Highcharts.RangeSelector#defaultInputDateParser */ RangeSelector.prototype.defaultInputDateParser = function (inputDate, useUTC, time) { var hasTimezone = function (str) { return str.length > 6 && (str.lastIndexOf('-') === str.length - 6 || str.lastIndexOf('+') === str.length - 6); }; var input = inputDate.split('/').join('-').split(' ').join('T'); if (input.indexOf('T') === -1) { input += 'T00:00'; } if (useUTC) { input += 'Z'; } else if (H.isSafari && !hasTimezone(input)) { var offset = new Date(input).getTimezoneOffset() / 60; input += offset <= 0 ? "+" + pad(-offset) + ":00" : "-" + pad(offset) + ":00"; } var date = Date.parse(input); // If the value isn't parsed directly to a value by the // browser's Date.parse method, like YYYY-MM-DD in IE8, try // parsing it a different way if (!isNumber(date)) { var parts = inputDate.split('-'); date = Date.UTC(pInt(parts[0]), pInt(parts[1]) - 1, pInt(parts[2])); } if (time && useUTC && isNumber(date)) { date += time.getTimezoneOffset(date); } return date; }; /** * Draw either the 'from' or the 'to' HTML input box of the range selector * * @private * @function Highcharts.RangeSelector#drawInput * @param {string} name * @return {RangeSelectorInputElements} */ RangeSelector.prototype.drawInput = function (name) { var _a = this, chart = _a.chart, div = _a.div, inputGroup = _a.inputGroup; var rangeSelector = this, chartStyle = chart.renderer.style || {}, renderer = chart.renderer, options = chart.options.rangeSelector, lang = defaultOptions.lang, isMin = name === 'min'; /** * @private */ function updateExtremes() { var value = rangeSelector.getInputValue(name), chartAxis = chart.xAxis[0], dataAxis = chart.scroller && chart.scroller.xAxis ? chart.scroller.xAxis : chartAxis, dataMin = dataAxis.dataMin, dataMax = dataAxis.dataMax; var maxInput = rangeSelector.maxInput, minInput = rangeSelector.minInput; if (value !== Number(input.getAttribute('data-hc-time-previous')) && isNumber(value)) { input.setAttribute('data-hc-time-previous', value); // Validate the extremes. If it goes beyound the data min or // max, use the actual data extreme (#2438). if (isMin && maxInput && isNumber(dataMin)) { if (value > Number(maxInput.getAttribute('data-hc-time'))) { value = void 0; } else if (value < dataMin) { value = dataMin; } } else if (minInput && isNumber(dataMax)) { if (value < Number(minInput.getAttribute('data-hc-time'))) { value = void 0; } else if (value > dataMax) { value = dataMax; } } // Set the extremes if (typeof value !== 'undefined') { // @todo typof undefined chartAxis.setExtremes(isMin ? value : chartAxis.min, isMin ? chartAxis.max : value, void 0, void 0, { trigger: 'rangeSelectorInput' }); } } } // Create the text label var text = lang[isMin ? 'rangeSelectorFrom' : 'rangeSelectorTo']; var label = renderer .label(text, 0) .addClass('highcharts-range-label') .attr({ padding: text ? 2 : 0, height: text ? options.inputBoxHeight : 0 }) .add(inputGroup); // Create an SVG label that shows updated date ranges and and records // click events that bring in the HTML input. var dateBox = renderer .label('', 0) .addClass('highcharts-range-input') .attr({ padding: 2, width: options.inputBoxWidth, height: options.inputBoxHeight, 'text-align': 'center' }) .on('click', function () { // If it is already focused, the onfocus event doesn't fire // (#3713) rangeSelector.showInput(name); rangeSelector[name + 'Input'].focus(); }); if (!chart.styledMode) { dateBox.attr({ stroke: options.inputBoxBorderColor, 'stroke-width': 1 }); } dateBox.add(inputGroup); // Create the HTML input element. This is rendered as 1x1 pixel then set // to the right size when focused. var input = createElement('input', { name: name, className: 'highcharts-range-selector' }, void 0, div); // #14788: Setting input.type to an unsupported type throws in IE, so // we need to use setAttribute instead input.setAttribute('type', preferredInputType(options.inputDateFormat || '%b %e, %Y')); if (!chart.styledMode) { // Styles label.css(merge(chartStyle, options.labelStyle)); dateBox.css(merge({ color: palette.neutralColor80 }, chartStyle, options.inputStyle)); css(input, extend({ position: 'absolute', border: 0, boxShadow: '0 0 15px rgba(0,0,0,0.3)', width: '1px', height: '1px', padding: 0, textAlign: 'center', fontSize: chartStyle.fontSize, fontFamily: chartStyle.fontFamily, top: '-9999em' // #4798 }, options.inputStyle)); } // Blow up the input box input.onfocus = function () { rangeSelector.showInput(name); }; // Hide away the input box input.onblur = function () { // update extermes only when inputs are active if (input === H.doc.activeElement) { // Only when focused // Update also when no `change` event is triggered, like when // clicking inside the SVG (#4710) updateExtremes(); } // #10404 - move hide and blur outside focus rangeSelector.hideInput(name); rangeSelector.setInputValue(name); input.blur(); // #4606 }; var keyDown = false; // handle changes in the input boxes input.onchange = function () { // Update extremes and blur input when clicking date input calendar if (!keyDown) { updateExtremes(); rangeSelector.hideInput(name); input.blur(); } }; input.onkeypress = function (event) { // IE does not fire onchange on enter if (event.keyCode === 13) { updateExtremes(); } }; input.onkeydown = function (event) { keyDown = true; // Arrow keys if (event.keyCode === 38 || event.keyCode === 40) { updateExtremes(); } }; input.onkeyup = function () { keyDown = false; }; return { dateBox: dateBox, input: input, label: label }; }; /** * Get the position of the range selector buttons and inputs. This can be * overridden from outside for custom positioning. * * @private * @function Highcharts.RangeSelector#getPosition * * @return {Highcharts.Dictionary<number>} */ RangeSelector.prototype.getPosition = function () { var chart = this.chart, options = chart.options.rangeSelector, top = options.verticalAlign === 'top' ? chart.plotTop - chart.axisOffset[0] : 0; // set offset only for varticalAlign top return { buttonTop: top + options.buttonPosition.y, inputTop: top + options.inputPosition.y - 10 }; }; /** * Get the extremes of YTD. Will choose dataMax if its value is lower than * the current timestamp. Will choose dataMin if its value is higher than * the timestamp for the start of current year. * * @private * @function Highcharts.RangeSelector#getYTDExtremes * * @param {number} dataMax * * @param {number} dataMin * * @return {*} * Returns min and max for the YTD */ RangeSelector.prototype.getYTDExtremes = function (dataMax, dataMin, useUTC) { var time = this.chart.time, min, now = new time.Date(dataMax), year = time.get('FullYear', now), startOfYear = useUTC ? time.Date.UTC(year, 0, 1) : // eslint-disable-line new-cap +new time.Date(year, 0, 1); min = Math.max(dataMin, startOfYear); var ts = now.getTime(); return { max: Math.min(dataMax || ts, ts), min: min }; }; /** * Render the range selector including the buttons and the inputs. The first * time render is called, the elements are created and positioned. On * subsequent calls, they are moved and updated. * * @private * @function Highcharts.RangeSelector#render * @param {number} [min] * X axis minimum * @param {number} [max] * X axis maximum * @return {void} */ RangeSelector.prototype.render = function (min, max) { var chart = this.chart, renderer = chart.renderer, container = chart.container, chartOptions = chart.options, options = chartOptions.rangeSelector, // Place inputs above the container inputsZIndex = pick(chartOptions.chart.style && chartOptions.chart.style.zIndex, 0) + 1, inputEnabled = options.inputEnabled, rendered = this.rendered; if (options.enabled === false) { return; } // create the elements if (!rendered) { this.group = renderer.g('range-selector-group') .attr({ zIndex: 7 }) .add(); this.div = createElement('div', void 0, { position: 'relative', height: 0, zIndex: inputsZIndex }); if (this.buttonOptions.length) { this.renderButtons(); } // First create a wrapper outside the container in order to make // the inputs work and make export correct if (container.parentNode) { container.parentNode.insertBefore(this.div, container); } if (inputEnabled) { // Create the group to keep the inputs this.inputGroup = renderer.g('input-group').add(this.group); var minElems = this.drawInput('min'); this.minDateBox = minElems.dateBox; this.minLabel = minElems.label; this.minInput = minElems.input; var maxElems = this.drawInput('max'); this.maxDateBox = maxElems.dateBox; this.maxLabel = maxElems.label; this.maxInput = maxElems.input; } } if (inputEnabled) { // Set or reset the input values this.setInputValue('min', min); this.setInputValue('max', max); var unionExtremes = (chart.scroller && chart.scroller.getUnionExtremes()) || chart.xAxis[0] || {}; if (defined(unionExtremes.dataMin) && defined(unionExtremes.dataMax)) { var minRange = chart.xAxis[0].minRange || 0; this.setInputExtremes('min', unionExtremes.dataMin, Math.min(unionExtremes.dataMax, this.getInputValue('max')) - minRange); this.setInputExtremes('max', Math.max(unionExtremes.dataMin, this.getInputValue('min')) + minRange, unionExtremes.dataMax); } // Reflow if (this.inputGroup) { var x_1 = 0; [ this.minLabel, this.minDateBox, this.maxLabel, this.maxDateBox ].forEach(function (label) { if (label) { var width = label.getBBox().width; if (width) { label.attr({ x: x_1 }); x_1 += width + options.inputSpacing; } } }); } } this.alignElements(); this.rendered = true; }; /** * Render the range buttons. This only runs the first time, later the * positioning is laid out in alignElements. * * @private * @function Highcharts.RangeSelector#renderButtons * @return {void} */ RangeSelector.prototype.renderButtons = function () { var _this = this; var _a = this, buttons = _a.buttons, chart = _a.chart, options = _a.options; var lang = defaultOptions.lang; var renderer = chart.renderer; var buttonTheme = merge(options.buttonTheme); var states = buttonTheme && buttonTheme.states; // Prevent the button from resetting the width when the button state // changes since we need more control over the width when collapsing // the buttons var width = buttonTheme.width || 28; delete buttonTheme.width; delete buttonTheme.states; this.buttonGroup = renderer.g('range-selector-buttons').add(this.group); var dropdown = this.dropdown = createElement('select', void 0, { position: 'absolute', width: '1px', height: '1px', padding: 0, border: 0, top: '-9999em', cursor: 'pointer', opacity: 0.0001 }, this.div); // Prevent page zoom on iPhone addEvent(dropdown, 'touchstart', function () { dropdown.style.fontSize = '16px'; }); // Forward events from select to button [ [H.isMS ? 'mouseover' : 'mouseenter'], [H.isMS ? 'mouseout' : 'mouseleave'], ['change', 'click'] ].forEach(function (_a) { var from = _a[0], to = _a[1]; addEvent(dropdown, from, function () { var button = buttons[_this.currentButtonIndex()]; if (button) { fireEvent(button.element, to || from); } }); }); this.zoomText = renderer .label((lang && lang.rangeSelectorZoom) || '', 0) .attr({ padding: options.buttonTheme.padding, height: options.buttonTheme.height, paddingLeft: 0, paddingRight: 0 }) .add(this.buttonGroup); if (!this.chart.styledMode) { this.zoomText.css(options.labelStyle); buttonTheme['stroke-width'] = pick(buttonTheme['stroke-width'], 0); } createElement('option', { textContent: this.zoomText.textStr, disabled: true }, void 0, dropdown); this.buttonOptions.forEach(function (rangeOptions, i) { createElement('option', { textContent: rangeOptions.title || rangeOptions.text }, void 0, dropdown); buttons[i] = renderer .button(rangeOptions.text, 0, 0, function (e) { // extract events from button object and call var buttonEvents = (rangeOptions.events && rangeOptions.events.click), callDefaultEvent; if (buttonEvents) { callDefaultEvent = buttonEvents.call(rangeOptions, e); } if (callDefaultEvent !== false) { _this.clickButton(i); } _this.isActive = true; }, buttonTheme, states && states.hover, states && states.select, states && states.disabled) .attr({ 'text-align': 'center', width: width }) .add(_this.buttonGroup); if (rangeOptions.title) { buttons[i].attr('title', rangeOptions.title); } }); }; /** * Align the elements horizontally and vertically. * * @private * @function Highcharts.RangeSelector#alignElements * @return {void} */ RangeSelector.prototype.alignElements = function () { var _this = this; var _a = this, buttonGroup = _a.buttonGroup, buttons = _a.buttons, chart = _a.chart, group = _a.group, inputGroup = _a.inputGroup, options = _a.options, zoomText = _a.zoomText; var chartOptions = chart.options; var navButtonOptions = (chartOptions.exporting && chartOptions.exporting.enabled !== false && chartOptions.navigation && chartOptions.navigation.buttonOptions); var buttonPosition = options.buttonPosition, inputPosition = options.inputPosition, verticalAlign = options.verticalAlign; // Get the X offset required to avoid overlapping with the exporting // button. This is is used both by the buttonGroup and the inputGroup. var getXOffsetForExportButton = function (group, position) { if (navButtonOptions && _this.titleCollision(chart) && verticalAlign === 'top' && position.align === 'right' && ((position.y - group.getBBox().height - 12) < ((navButtonOptions.y || 0) + (navButtonOptions.height || 0) + chart.spacing[0]))) { return -40; } return 0; }; var plotLeft = chart.plotLeft; if (group && buttonPosition && inputPosition) { var translateX = buttonPosition.x - chart.spacing[3]; if (buttonGroup) { this.positionButtons(); if (!this.initialButtonGroupWidth) { var width_1 = 0; if (zoomText) { width_1 += zoomText.getBBox().width + 5; } buttons.forEach(function (button, i) { width_1 += button.width; if (i !== buttons.length - 1) { width_1 += options.buttonSpacing; } }); this.initialButtonGroupWidth = width_1; } plotLeft -= chart.spacing[3]; this.updateButtonStates(); // Detect collision between button group and exporting var xOffsetForExportButton_1 = getXOffsetForExportButton(buttonGroup, buttonPosition); this.alignButtonGroup(xOffsetForExportButton_1); // Skip animation group.placed = buttonGroup.placed = chart.hasLoaded; } var xOffsetForExportButton = 0; if (inputGroup) { // Detect collision between the input group and exporting button xOffsetForExportButton = getXOffsetForExportButton(inputGroup, inputPosition); if (inputPosition.align === 'left') { translateX = plotLeft; } else if (inputPosition.align === 'right') { translateX = -Math.max(chart.axisOffset[1], -xOffsetForExportButton); } // Update the alignment to the updated spacing box inputGroup.align({ y: inputPosition.y, width: inputGroup.getBBox().width, align: inputPosition.align, // fix wrong getBBox() value on right align x: inputPosition.x + translateX - 2 }, true, chart.spacingBox); // Skip animation inputGroup.placed = chart.hasLoaded; } this.handleCollision(xOffsetForExportButton); // Vertical align group.align({ verticalAlign: verticalAlign }, true, chart.spacingBox); var alignTranslateY = group.alignAttr.translateY; // Set position var groupHeight = group.getBBox().height + 20; // # 20 padding var translateY = 0; // Calculate bottom position if (verticalAlign === 'bottom') { var legendOptions = chart.legend && chart.legend.options; var legendHeight = (legendOptions && legendOptions.verticalAlign === 'bottom' && legendOptions.enabled && !legendOptions.floating ? (chart.legend.legendHeight + pick(legendOptions.margin, 10)) : 0); groupHeight = groupHeight + legendHeight - 20; translateY = (alignTranslateY - groupHeight - (options.floating ? 0 : options.y) - (chart.titleOffset ? chart.titleOffset[2] : 0) - 10 // 10 spacing ); } if (verticalAlign === 'top') { if (options.floating) { translateY = 0; } if (chart.titleOffset && chart.titleOffset[0]) { translateY = chart.titleOffset[0]; } translateY += ((chart.margin[0] - chart.spacing[0]) || 0); } else if (verticalAlign === 'middle') { if (inputPosition.y === buttonPosition.y) { translateY = alignTranslateY; } else if (inputPosition.y || buttonPosition.y) { if (inputPosition.y < 0 || buttonPosition.y < 0) { translateY -= Math.min(inputPosition.y, buttonPosition.y); } else { translateY = alignTranslateY - groupHeight; } } } group.translate(options.x, options.y + Math.floor(translateY)); // Translate HTML inputs var _b = this, minInput = _b.minInput, maxInput = _b.maxInput, dropdown = _b.dropdown; if (options.inputEnabled && minInput && maxInput) { minInput.style.marginTop = group.translateY + 'px'; maxInput.style.marginTop = group.translateY + 'px'; } if (dropdown) { dropdown.style.marginTop = group.translateY + 'px'; } } }; /** * Align the button group horizontally and vertically. * * @private * @function Highcharts.RangeSelector#alignButtonGroup * @param {number} xOffsetForExportButton * @param {number} [width] * @return {void} */ RangeSelector.prototype.alignButtonGroup = function (xOffsetForExportButton, width) { var _a = this, chart = _a.chart, options = _a.options, buttonGroup = _a.buttonGroup, buttons = _a.buttons; var buttonPosition = options.buttonPosition; var plotLeft = chart.plotLeft - chart.spacing[3]; var translateX = buttonPosition.x - chart.spacing[3]; if (buttonPosition.align === 'right') { translateX += xOffsetForExportButton - plotLeft; // #13014 } else if (buttonPosition.align === 'center') { translateX -= plotLeft / 2; } if (buttonGroup) { // Align button group buttonGroup.align({ y: buttonPosition.y, width: pick(width, this.initialButtonGroupWidth), align: buttonPosition.align, x: translateX }, true, chart.spacingBox); } }; /** * @private * @function Highcharts.RangeSelector#positionButtons * @return {void} */ RangeSelector.prototype.positionButtons = function () { var _a = this, buttons = _a.buttons, chart = _a.chart, options = _a.options, zoomText = _a.zoomText; var verb = chart.hasLoaded ? 'animate' : 'attr'; var buttonPosition = options.buttonPosition; var plotLeft = chart.plotLeft; var buttonLeft = plotLeft; if (zoomText && zoomText.visibility !== 'hidden') { // #8769, allow dynamically updating margins zoomText[verb]({ x: pick(plotLeft + buttonPosition.x, plotLeft) }); // Button start position buttonLeft += buttonPosition.x + zoomText.getBBox().width + 5; } this.buttonOptions.forEach(function (rangeOptions, i) { if (buttons[i].visibility !== 'hidden') { buttons[i][verb]({ x: buttonLeft }); // increase button position for the next button buttonLeft += buttons[i].width + options.buttonSpacing; } else { buttons[i][verb]({ x: plotLeft }); } }); }; /** * Handle collision between the button group and the input group * * @private * @function Highcharts.RangeSelector#handleCollision * * @param {number} xOffsetForExportButton * The X offset of the group required to make room for the * exporting button * @return {void} */ RangeSelector.prototype.handleCollision = function (xOffsetForExportButton) { var _this = this; var _a = this, chart = _a.chart, buttonGroup = _a.buttonGroup, inputGroup = _a.inputGroup; var _b = this.options, buttonPosition = _b.buttonPosition, dropdown = _b.dropdown, inputPosition = _b.inputPosition; var maxButtonWidth = function () { var buttonWidth = 0; _this.buttons.forEach(function (button) { var bBox = button.getBBox(); if (bBox.width > buttonWidth) { buttonWidth = bBox.width; } }); return buttonWidth; }; var groupsOverlap = function (buttonGroupWidth) { if (inputGroup && buttonGroup) { var inputGroupX = (inputGroup.alignAttr.translateX + inputGroup.alignOptions.x - xOffsetForExportButton + // getBBox for detecing left margin inputGroup.getBBox().x + // 2px padding to not overlap input and label 2); var inputGroupWidth = inputGroup.alignOptions.width; var buttonGroupX = buttonGroup.alignAttr.translateX + buttonGroup.getBBox().x; return (buttonGroupX + buttonGroupWidth > inputGroupX) && (inputGroupX + inputGroupWidth > buttonGroupX) && (buttonPosition.y < (inputPosition.y + inputGroup.getBBox().height)); } return false; }; var moveInputsDown = function () { if (inputGroup && buttonGroup) { inputGroup.attr({ translateX: inputGroup.alignAttr.translateX + (chart.axisOffset[1] >= -xOffsetForExportButton ? 0 : -xOffsetForExportButton), translateY: inputGroup.alignAttr.translateY + buttonGroup.getBBox().height + 10 }); } }; if (buttonGroup) { if (dropdown === 'always') { this.collapseButtons(xOffsetForExportButton); if (groupsOverlap(maxButtonWidth())) { // Move the inputs down if there is still a collision // after collapsing the buttons moveInputsDown(); } return; } if (dropdown === 'never') { this.expandButtons(); } } // Detect collision if (inputGroup && buttonGroup) { if ((inputPosition.align === buttonPosition.align) || // 20 is minimal spacing between elements groupsOverlap(this.initialButtonGroupWidth + 20)) { if (dropdown === 'responsive') { this.collapseButtons(xOffsetForExportButton); if (groupsOverlap(maxButtonWidth())) { moveInputsDown(); } } else { moveInputsDown(); } } else if (dropdown === 'responsive') { this.expandButtons(); } } else if (buttonGroup && dropdown === 'responsive') { if (this.initialButtonGroupWidth > chart.plotWidth) { this.collapseButtons(xOffsetForExportButton); } else { this.expandButtons(); } } }; /** * Collapse the buttons and put the select element on top. * * @private * @function Highcharts.RangeSelector#collapseButtons * @param {number} xOffsetForExportButton * @return {void} */ RangeSelector.prototype.collapseButtons = function (xOffsetForExportButton) { var _a = this, buttons = _a.buttons, buttonOptions = _a.buttonOptions, chart = _a.chart, dropdown = _a.dropdown, options = _a.options, zoomText = _a.zoomText; var userButtonTheme = (chart.userOptions.rangeSelector && chart.userOptions.rangeSelector.buttonTheme) || {}; var getAttribs = function (text) { return ({ text: text ? text + " \u25BE" : '▾', width: 'auto', paddingLeft: pick(options.buttonTheme.paddingLeft, userButtonTheme.padding, 8), paddingRight: pick(options.buttonTheme.paddingRight, userButtonTheme.padding, 8) }); }; if (zoomText) { zoomText.hide(); } var hasActiveButton = false; buttonOptions.forEach(function (rangeOptions, i) { var button = buttons[i]; if (button.state !== 2) { button.hide(); } else { button.show(); button.attr(getAttribs(rangeOptions.text)); hasActiveButton = true; } }); if (!hasActiveButton) { if (dropdown) { dropdown.selectedIndex = 0; } buttons[0].show(); buttons[0].attr(getAttribs(this.zoomText && this.zoomText.textStr)); } var align = options.buttonPosition.align; this.positionButtons(); if (align === 'right' || align === 'center') { this.alignButtonGroup(xOffsetForExportButton, buttons[this.currentButtonIndex()].getBBox().width); } this.showDropdown(); }; /** * Show all the buttons and hide the select element. * * @private * @function Highcharts.RangeSelector#expandButtons * @return {void} */ RangeSelector.prototype.expandButtons = function () { var _a = this, buttons = _a.buttons, buttonOptions = _a.buttonOptions, options = _a.options, zoomText = _a.zoomText; this.hideDropdown(); if (zoomText) { zoomText.show(); } buttonOptions.forEach(function (rangeOptions, i) { var button = buttons[i]; button.show(); button.attr({ text: rangeOptions.text, width: options.buttonTheme.width || 28, paddingLeft: pick(options.buttonTheme.paddingLeft, 'unset'), paddingRight: pick(options.buttonTheme.paddingRight, 'unset') }); if (button.state < 2) { button.setState(0); } }); this.positionButtons(); }; /** * Get the index of the visible button when the buttons are collapsed. * * @private * @function Highcharts.RangeSelector#currentButtonIndex * @return {number} */ RangeSelector.prototype.currentButtonIndex = function () { var dropdown = this.dropdown; if (dropdown && dropdown.selectedIndex > 0) { return dropdown.selectedIndex - 1; } return 0; }; /** * Position the select element on top of the button. * * @private * @function Highcharts.RangeSelector#showDropdown * @return {void} */ RangeSelector.prototype.showDropdown = function () { var _a = this, buttonGroup = _a.buttonGroup, buttons = _a.buttons, chart = _a.chart, dropdown = _a.dropdown; if (buttonGroup && dropdown) { var translateX = buttonGroup.translateX, translateY = buttonGroup.translateY; var bBox = buttons[this.currentButtonIndex()].getBBox(); css(dropdown, { left: (chart.plotLeft + translateX) + 'px', top: (translateY + 0.5) + 'px', width: bBox.width + 'px', height: bBox.height + 'px' }); this.hasVisibleDropdown = true; } }; /** * @private * @function Highcharts.RangeSelector#hideDropdown * @return {void} */ RangeSelector.prototype.hideDropdown = function () { var dropdown = this.dropdown; if (dropdown) { css(dropdown, { top: '-9999em', width: '1px', height: '1px' }); this.hasVisibleDropdown = false; } }; /** * Extracts height of range selector * * @private * @function Highcharts.RangeSelector#getHeight * @return {number} * Returns rangeSelector height */ RangeSelector.prototype.getHeight = function () { var rangeSelector = this, options = rangeSelector.options, rangeSelectorGroup = rangeSelector.group, inputPosition = options.inputPosition, buttonPosition = options.buttonPosition, yPosition = options.y, buttonPositionY = buttonPosition.y, inputPositionY = inputPosition.y, rangeSelectorHeight = 0, minPosition; if (options.height) { return options.height; } // Align the elements before we read the height in case we're switching // between wrapped and non-wrapped layout this.alignElements(); rangeSelectorHeight = rangeSelectorGroup ? // 13px to keep back compatibility (rangeSelectorGroup.getBBox(true).height) + 13 + yPosition : 0; minPosition = Math.min(inputPositionY, buttonPositionY); if ((inputPositionY < 0 && buttonPositionY < 0) || (inputPositionY > 0 && buttonPositionY > 0)) { rangeSelectorHeight += Math.abs(minPosition); } return rangeSelectorHeight; }; /** * Detect collision with title or subtitle * * @private * @function Highcharts.RangeSelector#titleCollision * * @param {Highcharts.Chart} chart * * @return {boolean} * Returns collision status */ RangeSelector.prototype.titleCollision = function (chart) { return !(chart.options.title.text || chart.options.subtitle.text); }; /** * Update the range selector with new options * * @private * @function Highcharts.RangeSelector#update * @param {Highcharts.RangeSelectorOptions} options * @return {void} */ RangeSelector.prototype.update = function (options) { var chart = this.chart; merge(true, chart.options.rangeSelector, options); this.destroy(); this.init(chart); this.render(); }; /** * Destroys allocated elements. * * @private * @function Highcharts.RangeSelector#destroy */ RangeSelector.prototype.destroy = function () { var rSelector = this, minInput = rSelector.minInput, maxInput = rSelector.maxInput; if (rSelector.eventsToUnbind) { rSelector.eventsToUnbind.forEach(function (unbind) { return unbind(); }); rSelector.eventsToUnbind = void 0; } // Destroy elements in collections destroyObjectProperties(rSelector.buttons); // Clear input element events if (minInput) { minInput.onfocus = minInput.onblur = minInput.onchange = null; } if (maxInput) { maxInput.onfocus = maxInput.onblur = maxInput.onchange = null; } // Destroy HTML and SVG elements objectEach(rSelector, function (val, key) { if (val && key !== 'chart') { if (val instanceof SVGElement) { // SVGElement val.destroy(); } else if (val instanceof window.HTMLElement) { // HTML element discardElement(val); } } if (val !== RangeSelector.prototype[key]) { rSelector[key] = null; } }, this); }; return RangeSelector; }()); /** * The default buttons for pre-selecting time frames */ RangeSelector.prototype.defaultButtons = [{ type: 'month', count: 1, text: '1m', title: 'View 1 month' }, { type: 'month', count: 3, text: '3m', title: 'View 3 months' }, { type: 'month', count: 6, text: '6m', title: 'View 6 months' }, { type: 'ytd', text: 'YTD', title: 'View year to date' }, { type: 'year', count: 1, text: '1y', title: 'View 1 year' }, { type: 'all', text: 'All', title: 'View all' }]; /** * The date formats to use when setting min, max and value on date inputs */ RangeSelector.prototype.inputTypeFormats = { 'datetime-local': '%Y-%m-%dT%H:%M:%S', 'date': '%Y-%m-%d', 'time': '%H:%M:%S' }; /** * Get the preferred input type based on a date format string. * * @private * @function preferredInputType * @param {string} format * @return {string} */ function preferredInputType(format) { var ms = format.indexOf('%L') !== -1; if (ms) { return 'text'; } var date = ['a', 'A', 'd', 'e', 'w', 'b', 'B', 'm', 'o', 'y', 'Y'].some(function (char) { return format.indexOf('%' + char) !== -1; }); var time = ['H', 'k', 'I', 'l', 'M', 'S'].some(function (char) { return format.indexOf('%' + char) !== -1; }); if (date && time) { return 'datetime-local'; } if (date) { return 'date'; } if (time) { return 'time'; } return 'text'; } /** * Get the axis min value based on the range option and the current max. For * stock charts this is extended via the {@link RangeSelector} so that if the * selected range is a multiple of months or years, it is compensated for * various month lengths. * * @private * @function Highcharts.Axis#minFromRange * @return {number|undefined} * The new minimum value. */ Axis.prototype.minFromRange = function () { var rangeOptions = this.range, type = rangeOptions.type, min, max = this.max, dataMin, range, time = this.chart.time, // Get the true range from a start date getTrueRange = function (base, count) { var timeName = type === 'year' ? 'FullYear' : 'Month'; var date = new time.Date(base); var basePeriod = time.get(timeName, date); time.set(timeName, date, basePeriod + count); if (basePeriod === time.get(timeName, date)) { time.set('Date', date, 0); // #6537 } return date.getTime() - base; }; if (isNumber(rangeOptions)) { min = max - rangeOptions; range = rangeOptions; } else { min = max + getTrueRange(max, -rangeOptions.count); // Let the fixedRange reflect initial settings (#5930) if (this.chart) { this.chart.fixedRange = max - min; } } dataMin = pick(this.dataMin, Number.MIN_VALUE); if (!isNumber(min)) { min = dataMin; } if (min <= dataMin) { min = dataMin; if (typeof range === 'undefined') { // #4501 range = getTrueRange(min, rangeOptions.count); } this.newMax = Math.min(min + range, this.dataMax); } if (!isNumber(max)) { min = void 0; } return min; }; if (!H.RangeSelector) { var chartDestroyEvents_1 = []; var initRangeSelector_1 = function (chart) { var extremes, rangeSelector = chart.rangeSelector, legend, alignTo, verticalAlign; /** * @private */ function render() { if (rangeSelector) { extremes = chart.xAxis[0].getExtremes(); legend = chart.legend; verticalAlign = (rangeSelector && rangeSelector.options.verticalAlign); if (isNumber(extremes.min)) { rangeSelector.render(extremes.min, extremes.max); } // Re-align the legend so that it's below the rangeselector if (legend.display && verticalAlign === 'top' && verticalAlign === legend.options.verticalAlign) { // Create a new alignment box for the legend. alignTo = merge(chart.spacingBox); if (legend.options.layout === 'vertical') { alignTo.y = chart.plotTop; } else { alignTo.y += rangeSelector.getHeight(); } legend.group.placed = false; // Don't animate the alignment. legend.align(alignTo); } } } if (rangeSelector) { var events = find(chartDestroyEvents_1, function (e) { return e[0] === chart; }); if (!events) { chartDestroyEvents_1.push([chart, [ // redraw the scroller on setExtremes addEvent(chart.xAxis[0], 'afterSetExtremes', function (e) { if (rangeSelector) { rangeSelector.render(e.min, e.max); } }), // redraw the scroller chart resize addEvent(chart, 'redraw', render) ]]); } // do it now render(); } }; // Initialize rangeselector for stock charts addEvent(Chart, 'afterGetContainer', function () { if (this.options.rangeSelector && this.options.rangeSelector.enabled) { this.rangeSelector = new RangeSelector(this); } }); addEvent(Chart, 'beforeRender', function () { var chart = this, axes = chart.axes, rangeSelector = chart.rangeSelector, verticalAlign; if (rangeSelector) { if (isNumber(rangeSelector.deferredYTDClick)) { rangeSelector.clickButton(rangeSelector.deferredYTDClick); delete rangeSelector.deferredYTDClick; } axes.forEach(function (axis) { axis.updateNames(); axis.setScale(); }); chart.getAxisMargins(); rangeSelector.render(); verticalAlign = rangeSelector.options.verticalAlign; if (!rangeSelector.options.floating) { if (verticalAlign === 'bottom') { this.extraBottomMargin = true; } else if (verticalAlign !== 'middle') { this.extraTopMargin = true; } } } }); addEvent(Chart, 'update', function (e) { var chart = this, options = e.options, optionsRangeSelector = options.rangeSelector, rangeSelector = chart.rangeSelector, verticalAlign, extraBottomMarginWas = this.extraBottomMargin, extraTopMarginWas = this.extraTopMargin; if (optionsRangeSelector && optionsRangeSelector.enabled && !defined(rangeSelector) && this.options.rangeSelector) { this.options.rangeSelector.enabled = true; this.rangeSelector = rangeSelector = new RangeSelector(this); } this.extraBottomMargin = false; this.extraTopMargin = false; if (rangeSelector) { initRangeSelector_1(this); verticalAlign = (optionsRangeSelector && optionsRangeSelector.verticalAlign) || (rangeSelector.options && rangeSelector.options.verticalAlign); if (!rangeSelector.options.floating) { if (verticalAlign === 'bottom') { this.extraBottomMargin = true; } else if (verticalAlign !== 'middle') { this.extraTopMargin = true; } } if (this.extraBottomMargin !== extraBottomMarginWas || this.extraTopMargin !== extraTopMarginWas) { this.isDirtyBox = true; } } }); addEvent(Chart, 'render', function () { var chart = this, rangeSelector = chart.rangeSelector, verticalAlign; if (rangeSelector && !rangeSelector.options.floating) { rangeSelector.render(); verticalAlign = rangeSelector.options.verticalAlign; if (verticalAlign === 'bottom') { this.extraBottomMargin = true; } else if (verticalAlign !== 'middle') { this.extraTopMargin = true; } } }); addEvent(Chart, 'getMargins', function () { var rangeSelector = this.rangeSelector, rangeSelectorHeight; if (rangeSelector) { rangeSelectorHeight = rangeSelector.getHeight(); if (this.extraTopMargin) { this.plotTop += rangeSelectorHeight; } if (this.extraBottomMargin) { this.marginBottom += rangeSelectorHeight; } } }); Chart.prototype.callbacks.push(initRangeSelector_1); // Remove resize/afterSetExtremes at chart destroy addEvent(Chart, 'destroy', function destroyEvents() { for (var i = 0; i < chartDestroyEvents_1.length; i++) { var events = chartDestroyEvents_1[i]; if (events[0] === this) { events[1].forEach(function (unbind) { return unbind(); }); chartDestroyEvents_1.splice(i, 1); return; } } }); H.RangeSelector = RangeSelector; } return RangeSelector; }); _registerModule(_modules, 'Core/Chart/StockChart.js', [_modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Axis/Axis.js'], _modules['Core/Chart/Chart.js'], _modules['Core/FormatUtilities.js'], _modules['Core/DefaultOptions.js'], _modules['Core/Color/Palette.js'], _modules['Core/Series/Point.js'], _modules['Core/Series/Series.js'], _modules['Core/Renderer/SVG/SVGRenderer.js'], _modules['Core/Utilities.js']], function (A, Axis, Chart, F, D, Palette, Point, Series, SVGRenderer, U) { /* * * * (c) 2010-2021 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ var __extends = (this && this.__extends) || (function () { var extendStatics = function (d, b) { extendStatics = Object.setPrototypeOf || ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; return extendStatics(d, b); }; return function (d, b) { extendStatics(d, b); function __() { this.constructor = d; } d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); var animObject = A.animObject; var format = F.format; var getOptions = D.getOptions; var pointTooltipFormatter = Point.prototype.tooltipFormatter; var _a = Series.prototype, seriesInit = _a.init, seriesProcessData = _a.processData; var addEvent = U.addEvent, arrayMax = U.arrayMax, arrayMin = U.arrayMin, clamp = U.clamp, defined = U.defined, extend = U.extend, find = U.find, isNumber = U.isNumber, isString = U.isString, merge = U.merge, pick = U.pick, splat = U.splat; // Has a dependency on Navigator due to the use of // defaultOptions.navigator // Has a dependency on Scrollbar due to the use of // defaultOptions.scrollbar // Has a dependency on RangeSelector due to the use of // defaultOptions.rangeSelector /* * * * Class * * */ /** * Stock-optimized chart. Use {@link Highcharts.Chart|Chart} for common charts. * * @requires modules/stock * * @class * @name Highcharts.StockChart * @extends Highcharts.Chart */ var StockChart = /** @class */ (function (_super) { __extends(StockChart, _super); function StockChart() { return _super !== null && _super.apply(this, arguments) || this; } /** * Initializes the chart. The constructor's arguments are passed on * directly. * * @function Highcharts.StockChart#init * * @param {Highcharts.Options} userOptions * Custom options. * * @param {Function} [callback] * Function to run when the chart has loaded and and all external * images are loaded. * * @return {void} * * @fires Highcharts.StockChart#event:init * @fires Highcharts.StockChart#event:afterInit */ StockChart.prototype.init = function (userOptions, callback) { var defaultOptions = getOptions(), xAxisOptions = userOptions.xAxis, yAxisOptions = userOptions.yAxis, // Always disable startOnTick:true on the main axis when the // navigator is enabled (#1090) navigatorEnabled = pick(userOptions.navigator && userOptions.navigator.enabled, defaultOptions.navigator.enabled, true); // Avoid doing these twice userOptions.xAxis = userOptions.yAxis = void 0; var options = merge({ chart: { panning: { enabled: true, type: 'x' }, pinchType: 'x' }, navigator: { enabled: navigatorEnabled }, scrollbar: { // #4988 - check if setOptions was called enabled: pick(defaultOptions.scrollbar && defaultOptions.scrollbar.enabled, true) }, rangeSelector: { // #4988 - check if setOptions was called enabled: pick(defaultOptions.rangeSelector.enabled, true) }, title: { text: null }, tooltip: { split: pick(defaultOptions.tooltip.split, true), crosshairs: true }, legend: { enabled: false } }, userOptions, // user's options { isStock: true // internal flag }); userOptions.xAxis = xAxisOptions; userOptions.yAxis = yAxisOptions; // apply X axis options to both single and multi y axes options.xAxis = splat(userOptions.xAxis || {}).map(function (xAxisOptions, i) { return merge(getDefaultAxisOptions('xAxis', xAxisOptions), defaultOptions.xAxis, // #3802 defaultOptions.xAxis && defaultOptions.xAxis[i], // #7690 xAxisOptions, // user options getForcedAxisOptions('xAxis', userOptions)); }); // apply Y axis options to both single and multi y axes options.yAxis = splat(userOptions.yAxis || {}).map(function (yAxisOptions, i) { return merge(getDefaultAxisOptions('yAxis', yAxisOptions), defaultOptions.yAxis, // #3802 defaultOptions.yAxis && defaultOptions.yAxis[i], // #7690 yAxisOptions // user options ); }); _super.prototype.init.call(this, options, callback); }; /** * Factory for creating different axis types. * Extended to add stock defaults. * * @private * @function Highcharts.StockChart#createAxis * * @param {string} type * An axis type. * * @param {Chart.CreateAxisOptionsObject} options * The axis creation options. * * @return {Highcharts.Axis | Highcharts.ColorAxis} */ StockChart.prototype.createAxis = function (type, options) { options.axis = merge(getDefaultAxisOptions(type, options.axis), options.axis, getForcedAxisOptions(type, this.userOptions)); return _super.prototype.createAxis.call(this, type, options); }; return StockChart; }(Chart)); /* eslint-disable no-invalid-this, valid-jsdoc */ (function (StockChart) { /** * Factory function for creating new stock charts. Creates a new * {@link Highcharts.StockChart|StockChart} object with different default * options than the basic Chart. * * @example * let chart = Highcharts.stockChart('container', { * series: [{ * data: [1, 2, 3, 4, 5, 6, 7, 8, 9], * pointInterval: 24 * 60 * 60 * 1000 * }] * }); * * @function Highcharts.stockChart * * @param {string|Highcharts.HTMLDOMElement} [renderTo] * The DOM element to render to, or its id. * * @param {Highcharts.Options} options * The chart options structure as described in the * [options reference](https://api.highcharts.com/highstock). * * @param {Highcharts.ChartCallbackFunction} [callback] * A function to execute when the chart object is finished loading * and rendering. In most cases the chart is built in one thread, * but in Internet Explorer version 8 or less the chart is sometimes * initialized before the document is ready, and in these cases the * chart object will not be finished synchronously. As a * consequence, code that relies on the newly built Chart object * should always run in the callback. Defining a * [chart.events.load](https://api.highcharts.com/highstock/chart.events.load) * handler is equivalent. * * @return {Highcharts.StockChart} * The chart object. */ function stockChart(a, b, c) { return new StockChart(a, b, c); } StockChart.stockChart = stockChart; })(StockChart || (StockChart = {})); /** * Get stock-specific default axis options. * * @private * @function getDefaultAxisOptions * @param {string} type * @param {Highcharts.AxisOptions} options * @return {Highcharts.AxisOptions} */ function getDefaultAxisOptions(type, options) { if (type === 'xAxis') { return { minPadding: 0, maxPadding: 0, overscroll: 0, ordinal: true, title: { text: null }, labels: { overflow: 'justify' }, showLastLabel: true }; } if (type === 'yAxis') { return { labels: { y: -2 }, opposite: pick(options.opposite, true), /** * @default {highcharts} true * @default {highstock} false * @apioption yAxis.showLastLabel * * @private */ showLastLabel: !!( // #6104, show last label by default for category axes options.categories || options.type === 'category'), title: { text: null } }; } return {}; } /** * Get stock-specific forced axis options. * * @private * @function getForcedAxisOptions * @param {string} type * @param {Highcharts.Options} chartOptions * @return {Highcharts.AxisOptions} */ function getForcedAxisOptions(type, chartOptions) { if (type === 'xAxis') { var defaultOptions = getOptions(), // Always disable startOnTick:true on the main axis when the // navigator is enabled (#1090) navigatorEnabled = pick(chartOptions.navigator && chartOptions.navigator.enabled, defaultOptions.navigator.enabled, true); var axisOptions = { type: 'datetime', categories: void 0 }; if (navigatorEnabled) { axisOptions.startOnTick = false; axisOptions.endOnTick = false; } return axisOptions; } return {}; } /* * * * Compositions * * */ // Handle som Stock-specific series defaults, override the plotOptions before // series options are handled. addEvent(Series, 'setOptions', function (e) { var overrides; if (this.chart.options.isStock) { if (this.is('column') || this.is('columnrange')) { overrides = { borderWidth: 0, shadow: false }; } else if (!this.is('scatter') && !this.is('sma')) { overrides = { marker: { enabled: false, radius: 2 } }; } if (overrides) { e.plotOptions[this.type] = merge(e.plotOptions[this.type], overrides); } } }); // Override the automatic label alignment so that the first Y axis' labels // are drawn on top of the grid line, and subsequent axes are drawn outside addEvent(Axis, 'autoLabelAlign', function (e) { var chart = this.chart, options = this.options, panes = chart._labelPanes = chart._labelPanes || {}, key, labelOptions = this.options.labels; if (this.chart.options.isStock && this.coll === 'yAxis') { key = options.top + ',' + options.height; // do it only for the first Y axis of each pane if (!panes[key] && labelOptions.enabled) { if (labelOptions.x === 15) { // default labelOptions.x = 0; } if (typeof labelOptions.align === 'undefined') { labelOptions.align = 'right'; } panes[key] = this; e.align = 'right'; e.preventDefault(); } } }); // Clear axis from label panes (#6071) addEvent(Axis, 'destroy', function () { var chart = this.chart, key = this.options && (this.options.top + ',' + this.options.height); if (key && chart._labelPanes && chart._labelPanes[key] === this) { delete chart._labelPanes[key]; } }); // Override getPlotLinePath to allow for multipane charts addEvent(Axis, 'getPlotLinePath', function (e) { var axis = this, series = (this.isLinked && !this.series ? this.linkedParent.series : this.series), chart = axis.chart, renderer = chart.renderer, axisLeft = axis.left, axisTop = axis.top, x1, y1, x2, y2, result = [], axes = [], // #3416 need a default array axes2, uniqueAxes, translatedValue = e.translatedValue, value = e.value, force = e.force, transVal; /** * Return the other axis based on either the axis option or on related * series. * @private */ function getAxis(coll) { var otherColl = coll === 'xAxis' ? 'yAxis' : 'xAxis', opt = axis.options[otherColl]; // Other axis indexed by number if (isNumber(opt)) { return [chart[otherColl][opt]]; } // Other axis indexed by id (like navigator) if (isString(opt)) { return [chart.get(opt)]; } // Auto detect based on existing series return series.map(function (s) { return s[otherColl]; }); } if ( // For stock chart, by default render paths across the panes // except the case when `acrossPanes` is disabled by user (#6644) (chart.options.isStock && e.acrossPanes !== false) && // Ignore in case of colorAxis or zAxis. #3360, #3524, #6720 axis.coll === 'xAxis' || axis.coll === 'yAxis') { e.preventDefault(); // Get the related axes based on series axes = getAxis(axis.coll); // Get the related axes based options.*Axis setting #2810 axes2 = (axis.isXAxis ? chart.yAxis : chart.xAxis); axes2.forEach(function (A) { if (defined(A.options.id) ? A.options.id.indexOf('navigator') === -1 : true) { var a = (A.isXAxis ? 'yAxis' : 'xAxis'), rax = (defined(A.options[a]) ? chart[a][A.options[a]] : chart[a][0]); if (axis === rax) { axes.push(A); } } }); // Remove duplicates in the axes array. If there are no axes in the axes // array, we are adding an axis without data, so we need to populate // this with grid lines (#2796). uniqueAxes = axes.length ? [] : [axis.isXAxis ? chart.yAxis[0] : chart.xAxis[0]]; // #3742 axes.forEach(function (axis2) { if (uniqueAxes.indexOf(axis2) === -1 && // Do not draw on axis which overlap completely. #5424 !find(uniqueAxes, function (unique) { return unique.pos === axis2.pos && unique.len === axis2.len; })) { uniqueAxes.push(axis2); } }); transVal = pick(translatedValue, axis.translate(value, null, null, e.old)); if (isNumber(transVal)) { if (axis.horiz) { uniqueAxes.forEach(function (axis2) { var skip; y1 = axis2.pos; y2 = y1 + axis2.len; x1 = x2 = Math.round(transVal + axis.transB); // outside plot area if (force !== 'pass' && (x1 < axisLeft || x1 > axisLeft + axis.width)) { if (force) { x1 = x2 = clamp(x1, axisLeft, axisLeft + axis.width); } else { skip = true; } } if (!skip) { result.push(['M', x1, y1], ['L', x2, y2]); } }); } else { uniqueAxes.forEach(function (axis2) { var skip; x1 = axis2.pos; x2 = x1 + axis2.len; y1 = y2 = Math.round(axisTop + axis.height - transVal); // outside plot area if (force !== 'pass' && (y1 < axisTop || y1 > axisTop + axis.height)) { if (force) { y1 = y2 = clamp(y1, axisTop, axisTop + axis.height); } else { skip = true; } } if (!skip) { result.push(['M', x1, y1], ['L', x2, y2]); } }); } } e.path = result.length > 0 ? renderer.crispPolyLine(result, e.lineWidth || 1) : // #3557 getPlotLinePath in regular Highcharts also returns null null; } }); /** * Function to crisp a line with multiple segments * * @private * @function Highcharts.SVGRenderer#crispPolyLine * @param {Highcharts.SVGPathArray} points * @param {number} width * @return {Highcharts.SVGPathArray} */ SVGRenderer.prototype.crispPolyLine = function (points, width) { // points format: [['M', 0, 0], ['L', 100, 0]] // normalize to a crisp line for (var i = 0; i < points.length; i = i + 2) { var start = points[i], end = points[i + 1]; if (start[1] === end[1]) { // Substract due to #1129. Now bottom and left axis gridlines behave // the same. start[1] = end[1] = Math.round(start[1]) - (width % 2 / 2); } if (start[2] === end[2]) { start[2] = end[2] = Math.round(start[2]) + (width % 2 / 2); } } return points; }; // Wrapper to hide the label addEvent(Axis, 'afterHideCrosshair', function () { if (this.crossLabel) { this.crossLabel = this.crossLabel.hide(); } }); // Extend crosshairs to also draw the label addEvent(Axis, 'afterDrawCrosshair', function (event) { // Check if the label has to be drawn if (!this.crosshair || !this.crosshair.label || !this.crosshair.label.enabled || !this.cross || !isNumber(this.min) || !isNumber(this.max)) { return; } var chart = this.chart, log = this.logarithmic, options = this.crosshair.label, // the label's options horiz = this.horiz, // axis orientation opposite = this.opposite, // axis position left = this.left, // left position top = this.top, // top position crossLabel = this.crossLabel, // the svgElement posx, posy, crossBox, formatOption = options.format, formatFormat = '', limit, align, tickInside = this.options.tickPosition === 'inside', snap = this.crosshair.snap !== false, offset = 0, // Use last available event (#5287) e = event.e || (this.cross && this.cross.e), point = event.point, min = this.min, max = this.max; if (log) { min = log.lin2log(min); max = log.lin2log(max); } align = (horiz ? 'center' : opposite ? (this.labelAlign === 'right' ? 'right' : 'left') : (this.labelAlign === 'left' ? 'left' : 'center')); // If the label does not exist yet, create it. if (!crossLabel) { crossLabel = this.crossLabel = chart.renderer .label('', 0, void 0, options.shape || 'callout') .addClass('highcharts-crosshair-label highcharts-color-' + (point ? point.series.colorIndex : this.series[0] && this.series[0].colorIndex)) .attr({ align: options.align || align, padding: pick(options.padding, 8), r: pick(options.borderRadius, 3), zIndex: 2 }) .add(this.labelGroup); // Presentational if (!chart.styledMode) { crossLabel .attr({ fill: options.backgroundColor || point && point.series && point.series.color || // #14888 Palette.neutralColor60, stroke: options.borderColor || '', 'stroke-width': options.borderWidth || 0 }) .css(extend({ color: Palette.backgroundColor, fontWeight: 'normal', fontSize: '11px', textAlign: 'center' }, options.style || {})); } } if (horiz) { posx = snap ? (point.plotX || 0) + left : e.chartX; posy = top + (opposite ? 0 : this.height); } else { posx = opposite ? this.width + left : 0; posy = snap ? (point.plotY || 0) + top : e.chartY; } if (!formatOption && !options.formatter) { if (this.dateTime) { formatFormat = '%b %d, %Y'; } formatOption = '{value' + (formatFormat ? ':' + formatFormat : '') + '}'; } // Show the label var value = snap ? (this.isXAxis ? point.x : point.y) : this.toValue(horiz ? e.chartX : e.chartY); // Crosshair should be rendered within Axis range (#7219). Also, the point // of currentPriceIndicator should be inside the plot area, #14879. var isInside = point ? point.series.isPointInside(point) : (isNumber(value) && value > min && value < max); var text = ''; if (formatOption) { text = format(formatOption, { value: value }, chart); } else if (options.formatter && isNumber(value)) { text = options.formatter.call(this, value); } crossLabel.attr({ text: text, x: posx, y: posy, visibility: isInside ? 'visible' : 'hidden' }); crossBox = crossLabel.getBBox(); // now it is placed we can correct its position if (isNumber(crossLabel.y)) { if (horiz) { if ((tickInside && !opposite) || (!tickInside && opposite)) { posy = crossLabel.y - crossBox.height; } } else { posy = crossLabel.y - (crossBox.height / 2); } } // check the edges if (horiz) { limit = { left: left - crossBox.x, right: left + this.width - crossBox.x }; } else { limit = { left: this.labelAlign === 'left' ? left : 0, right: this.labelAlign === 'right' ? left + this.width : chart.chartWidth }; } // left edge if (crossLabel.translateX < limit.left) { offset = limit.left - crossLabel.translateX; } // right edge if (crossLabel.translateX + crossBox.width >= limit.right) { offset = -(crossLabel.translateX + crossBox.width - limit.right); } // show the crosslabel crossLabel.attr({ x: posx + offset, y: posy, // First set x and y, then anchorX and anchorY, when box is actually // calculated, #5702 anchorX: horiz ? posx : (this.opposite ? 0 : chart.chartWidth), anchorY: horiz ? (this.opposite ? chart.chartHeight : 0) : posy + crossBox.height / 2 }); }); /* ************************************************************************** * * Start value compare logic * * ************************************************************************** */ /** * Extend series.init by adding a method to modify the y value used for plotting * on the y axis. This method is called both from the axis when finding dataMin * and dataMax, and from the series.translate method. * * @ignore * @function Highcharts.Series#init */ Series.prototype.init = function () { // Call base method seriesInit.apply(this, arguments); // Set comparison mode this.initCompare(this.options.compare); }; /** * Highcharts Stock only. Set the * [compare](https://api.highcharts.com/highstock/plotOptions.series.compare) * mode of the series after render time. In most cases it is more useful running * {@link Axis#setCompare} on the X axis to update all its series. * * @function Highcharts.Series#setCompare * * @param {string} [compare] * Can be one of `null` (default), `"percent"` or `"value"`. */ Series.prototype.setCompare = function (compare) { this.initCompare(compare); // Survive to export, #5485 this.userOptions.compare = compare; }; /** * @ignore * @function Highcharts.Series#initCompare * * @param {string} [compare] * Can be one of `null` (default), `"percent"` or `"value"`. */ Series.prototype.initCompare = function (compare) { // Set or unset the modifyValue method this.modifyValue = (compare === 'value' || compare === 'percent') ? function (value, point) { var compareValue = this.compareValue; if (typeof value !== 'undefined' && typeof compareValue !== 'undefined') { // #2601, #5814 // Get the modified value if (compare === 'value') { value -= compareValue; // Compare percent } else { value = 100 * (value / compareValue) - (this.options.compareBase === 100 ? 0 : 100); } // record for tooltip etc. if (point) { point.change = value; } return value; } return 0; } : null; // Mark dirty if (this.chart.hasRendered) { this.isDirty = true; } }; /** * Extend series.processData by finding the first y value in the plot area, * used for comparing the following values * * @ignore * @function Highcharts.Series#processData */ Series.prototype.processData = function (force) { var series = this, i, keyIndex = -1, processedXData, processedYData, compareStart = series.options.compareStart === true ? 0 : 1, length, compareValue; // call base method seriesProcessData.apply(this, arguments); if (series.xAxis && series.processedYData) { // not pies // local variables processedXData = series.processedXData; processedYData = series.processedYData; length = processedYData.length; // For series with more than one value (range, OHLC etc), compare // against close or the pointValKey (#4922, #3112, #9854) if (series.pointArrayMap) { keyIndex = series.pointArrayMap.indexOf(series.options.pointValKey || series.pointValKey || 'y'); } // find the first value for comparison for (i = 0; i < length - compareStart; i++) { compareValue = processedYData[i] && keyIndex > -1 ? processedYData[i][keyIndex] : processedYData[i]; if (isNumber(compareValue) && processedXData[i + compareStart] >= series.xAxis.min && compareValue !== 0) { series.compareValue = compareValue; break; } } } return; }; // Modify series extremes addEvent(Series, 'afterGetExtremes', function (e) { var dataExtremes = e.dataExtremes; if (this.modifyValue && dataExtremes) { var extremes = [ this.modifyValue(dataExtremes.dataMin), this.modifyValue(dataExtremes.dataMax) ]; dataExtremes.dataMin = arrayMin(extremes); dataExtremes.dataMax = arrayMax(extremes); } }); /** * Highcharts Stock only. Set the compare mode on all series * belonging to an Y axis after render time. * * @see [series.plotOptions.compare](https://api.highcharts.com/highstock/series.plotOptions.compare) * * @sample stock/members/axis-setcompare/ * Set compoare * * @function Highcharts.Axis#setCompare * * @param {string} [compare] * The compare mode. Can be one of `null` (default), `"value"` or * `"percent"`. * * @param {boolean} [redraw=true] * Whether to redraw the chart or to wait for a later call to * {@link Chart#redraw}. */ Axis.prototype.setCompare = function (compare, redraw) { if (!this.isXAxis) { this.series.forEach(function (series) { series.setCompare(compare); }); if (pick(redraw, true)) { this.chart.redraw(); } } }; /** * Extend the tooltip formatter by adding support for the point.change variable * as well as the changeDecimals option. * * @ignore * @function Highcharts.Point#tooltipFormatter * * @param {string} pointFormat */ Point.prototype.tooltipFormatter = function (pointFormat) { var point = this; var numberFormatter = point.series.chart.numberFormatter; pointFormat = pointFormat.replace('{point.change}', (point.change > 0 ? '+' : '') + numberFormatter(point.change, pick(point.series.tooltipOptions.changeDecimals, 2))); return pointTooltipFormatter.apply(this, [pointFormat]); }; /* ************************************************************************** * * End value compare logic * * ************************************************************************** */ // Extend the Series prototype to create a separate series clip box. This is // related to using multiple panes, and a future pane logic should incorporate // this feature (#2754). addEvent(Series, 'render', function () { var chart = this.chart, clipHeight; // Only do this on not 3d (#2939, #5904) nor polar (#6057) charts, and only // if the series type handles clipping in the animate method (#2975). if (!(chart.is3d && chart.is3d()) && !chart.polar && this.xAxis && !this.xAxis.isRadial && // Gauge, #6192 this.options.clip !== false // #15128 ) { clipHeight = this.yAxis.len; // Include xAxis line width (#8031) but only if the Y axis ends on the // edge of the X axis (#11005). if (this.xAxis.axisLine) { var dist = chart.plotTop + chart.plotHeight - this.yAxis.pos - this.yAxis.len, lineHeightCorrection = Math.floor(this.xAxis.axisLine.strokeWidth() / 2); if (dist >= 0) { clipHeight -= Math.max(lineHeightCorrection - dist, 0); } } // First render, initial clip box. clipBox also needs to be updated if // the series is rendered again before starting animating, in // compliance with a responsive rule (#13858). if (!chart.hasLoaded || (!this.clipBox && this.isDirty && !this.isDirtyData)) { this.clipBox = this.clipBox || merge(chart.clipBox); this.clipBox.width = this.xAxis.len; this.clipBox.height = clipHeight; } if (chart.hasRendered) { var animation = animObject(this.options.animation); // #15435: this.sharedClipKey might not have been set yet, for // example when updating the series, so we need to use this // function instead var sharedClipKey = this.getSharedClipKey(animation); var clipRect = chart.sharedClips[sharedClipKey]; // On redrawing, resizing etc, update the clip rectangle. // // #15435: Update it even when we are creating/updating clipBox, // since there could be series updating and pane size changes // happening at the same time and we dont destroy shared clips in // stock. if (clipRect) { // animate in case resize is done during initial animation clipRect.animate({ width: this.xAxis.len, height: clipHeight }); var markerClipRect = chart.sharedClips[sharedClipKey + 'm']; // also change markers clip animation for consistency // (marker clip rects should exist only on chart init) if (markerClipRect) { markerClipRect.animate({ width: this.xAxis.len }); } } } } }); addEvent(Chart, 'update', function (e) { var options = e.options; // Use case: enabling scrollbar from a disabled state. // Scrollbar needs to be initialized from a controller, Navigator in this // case (#6615) if ('scrollbar' in options && this.navigator) { merge(true, this.options.scrollbar, options.scrollbar); this.navigator.update({}, false); delete options.scrollbar; } }); /* * * * Default Export * * */ /* * * * API Options * * */ /** * Compare the values of the series against the first non-null, non- * zero value in the visible range. The y axis will show percentage * or absolute change depending on whether `compare` is set to `"percent"` * or `"value"`. When this is applied to multiple series, it allows * comparing the development of the series against each other. Adds * a `change` field to every point object. * * @see [compareBase](#plotOptions.series.compareBase) * @see [Axis.setCompare()](/class-reference/Highcharts.Axis#setCompare) * * @sample {highstock} stock/plotoptions/series-compare-percent/ * Percent * @sample {highstock} stock/plotoptions/series-compare-value/ * Value * * @type {string} * @since 1.0.1 * @product highstock * @apioption plotOptions.series.compare */ /** * Defines if comparison should start from the first point within the visible * range or should start from the first point **before** the range. * * In other words, this flag determines if first point within the visible range * will have 0% (`compareStart=true`) or should have been already calculated * according to the previous point (`compareStart=false`). * * @sample {highstock} stock/plotoptions/series-comparestart/ * Calculate compare within visible range * * @type {boolean} * @default false * @since 6.0.0 * @product highstock * @apioption plotOptions.series.compareStart */ /** * When [compare](#plotOptions.series.compare) is `percent`, this option * dictates whether to use 0 or 100 as the base of comparison. * * @sample {highstock} stock/plotoptions/series-comparebase/ * Compare base is 100 * * @type {number} * @default 0 * @since 5.0.6 * @product highstock * @validvalue [0, 100] * @apioption plotOptions.series.compareBase */ ''; // keeps doclets above in transpiled file return StockChart; }); _registerModule(_modules, 'masters/modules/stock.src.js', [_modules['Core/Globals.js'], _modules['Core/Scrollbar.js'], _modules['Core/Chart/StockChart.js']], function (Highcharts, Scrollbar, StockChart) { var G = Highcharts; // Classes G.Scrollbar = Scrollbar; G.StockChart = G.stockChart = StockChart.stockChart; // Compositions Scrollbar.compose(G.Axis); }); _registerModule(_modules, 'masters/highstock.src.js', [_modules['masters/highcharts.src.js']], function (Highcharts) { Highcharts.product = 'Highstock'; return Highcharts; }); _modules['masters/highstock.src.js']._modules = _modules; return _modules['masters/highstock.src.js']; }));