UNPKG

highcharts

Version:
1,228 lines (1,220 loc) 552 kB
/** * @license Highstock JS v11.3.0 (2024-01-10) * * Highcharts Stock as a plugin for Highcharts * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license */ (function (factory) { if (typeof module === 'object' && module.exports) { factory['default'] = factory; module.exports = factory; } else if (typeof define === 'function' && define.amd) { define('highcharts/modules/stock', ['highcharts'], function (Highcharts) { factory(Highcharts); factory.Highcharts = Highcharts; return factory; }); } else { factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined); } }(function (Highcharts) { 'use strict'; var _modules = Highcharts ? Highcharts._modules : {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); if (typeof CustomEvent === 'function') { window.dispatchEvent(new CustomEvent( 'HighchartsModuleLoaded', { detail: { path: path, module: obj[path] } } )); } } } _registerModule(_modules, 'Core/Axis/BrokenAxis.js', [_modules['Core/Globals.js'], _modules['Core/Axis/Stacking/StackItem.js'], _modules['Core/Utilities.js']], function (H, StackItem, U) { /* * * * (c) 2009-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { composed } = H; const { addEvent, find, fireEvent, isArray, isNumber, pick, pushUnique } = U; /* * * * Composition * * */ /** * Axis with support of broken data rows. * @private */ var BrokenAxis; (function (BrokenAxis) { /* * * * Declarations * * */ /* * * * Functions * * */ /** * Adds support for broken axes. * @private */ function compose(AxisClass, SeriesClass) { if (pushUnique(composed, compose)) { AxisClass.keepProps.push('brokenAxis'); addEvent(AxisClass, 'init', onAxisInit); addEvent(AxisClass, 'afterInit', onAxisAfterInit); addEvent(AxisClass, 'afterSetTickPositions', onAxisAfterSetTickPositions); addEvent(AxisClass, 'afterSetOptions', onAxisAfterSetOptions); const seriesProto = SeriesClass.prototype; seriesProto.drawBreaks = seriesDrawBreaks; seriesProto.gappedPath = seriesGappedPath; addEvent(SeriesClass, 'afterGeneratePoints', onSeriesAfterGeneratePoints); addEvent(SeriesClass, 'afterRender', onSeriesAfterRender); } return AxisClass; } BrokenAxis.compose = compose; /** * @private */ function onAxisAfterInit() { if (typeof this.brokenAxis !== 'undefined') { this.brokenAxis.setBreaks(this.options.breaks, false); } } /** * Force Axis to be not-ordinal when breaks are defined. * @private */ function onAxisAfterSetOptions() { const axis = this; if (axis.brokenAxis?.hasBreaks) { axis.options.ordinal = false; } } /** * @private */ function onAxisAfterSetTickPositions() { const axis = this, brokenAxis = axis.brokenAxis; if (brokenAxis?.hasBreaks) { const tickPositions = axis.tickPositions, info = axis.tickPositions.info, newPositions = []; for (let i = 0; i < tickPositions.length; i++) { if (!brokenAxis.isInAnyBreak(tickPositions[i])) { newPositions.push(tickPositions[i]); } } axis.tickPositions = newPositions; axis.tickPositions.info = info; } } /** * @private */ function onAxisInit() { const axis = this; if (!axis.brokenAxis) { axis.brokenAxis = new Additions(axis); } } /** * @private */ function onSeriesAfterGeneratePoints() { const { isDirty, options: { connectNulls }, points, xAxis, yAxis } = this; // Set, or reset visibility of the points. Axis.setBreaks marks // the series as isDirty if (isDirty) { let i = points.length; while (i--) { const point = points[i]; // Respect nulls inside the break (#4275) const nullGap = point.y === null && connectNulls === false; const isPointInBreak = (!nullGap && (xAxis?.brokenAxis?.isInAnyBreak(point.x, true) || 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) { const series = this, points = series.points; let breaks, threshold, y; if (axis?.brokenAxis?.hasBreaks) { const brokenAxis = axis.brokenAxis; keys.forEach(function (key) { breaks = brokenAxis?.breakArray || []; threshold = axis.isXAxis ? axis.min : pick(series.options.threshold, axis.min); // Array of breaks that have been "zoomed-out" which means that // they were shown previously, but now after zoom, they are not // (#19885). const breaksOutOfRange = axis?.options?.breaks?.filter(function (brk) { let isOut = true; // Iterate to see if "brk" is in axis range for (let i = 0; i < breaks.length; i++) { const otherBreak = breaks[i]; if (otherBreak.from === brk.from && otherBreak.to === brk.to) { isOut = false; break; } } return isOut; }); points.forEach(function (point) { y = pick(point['stack' + key.toUpperCase()], point[key]); breaks.forEach(function (brk) { if (isNumber(threshold) && isNumber(y)) { let eventName = ''; 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, brk }); } } }); breaksOutOfRange?.forEach(function (brk) { fireEvent(axis, 'pointOutsideOfBreak', { point, 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() { const currentDataGrouping = this.currentDataGrouping, groupingSize = currentDataGrouping?.gapSize, points = this.points.slice(), yAxis = this.yAxis; let 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 let current, next; 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) { const 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 */ class Additions { /* * * * Static Functions * * */ /** * @private */ static isInBreak(brk, val) { const repeat = brk.repeat || Infinity, from = brk.from, length = brk.to - brk.from, test = (val >= from ? (val - from) % repeat : repeat - ((from - val) % repeat)); let ret; if (!brk.inclusive) { ret = test < length && test !== 0; } else { ret = test <= length; } return ret; } /** * @private */ static lin2Val(val) { const axis = this; const brokenAxis = axis.brokenAxis; const breakArray = brokenAxis && brokenAxis.breakArray; if (!breakArray || !isNumber(val)) { return val; } let 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 */ static val2Lin(val) { const axis = this; const brokenAxis = axis.brokenAxis; const breakArray = brokenAxis && brokenAxis.breakArray; if (!breakArray || !isNumber(val)) { return val; } let 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; } /* * * * Constructors * * */ constructor(axis) { this.hasBreaks = false; this.axis = axis; } /* * * * 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. */ findBreakAt(x, breaks) { return find(breaks, function (b) { return b.from < x && x < b.to; }); } /** * @private */ isInAnyBreak(val, testKeep) { const brokenAxis = this, axis = brokenAxis.axis, breaks = axis.options.breaks || []; let 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. */ setBreaks(breaks, redraw) { const brokenAxis = this; const axis = brokenAxis.axis; const hasBreaks = isArray(breaks) && !!breaks.length && !!Object.keys(breaks[0]).length; // Check for [{}], #16368. axis.isDirty = brokenAxis.hasBreaks !== hasBreaks; brokenAxis.hasBreaks = hasBreaks; if (breaks !== axis.options.breaks) { 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) { const breaks = (this.options.breaks || []); let axisBreak; while ((axisBreak = brokenAxis.findBreakAt(newMin, breaks))) { newMin = axisBreak.to; } while ((axisBreak = brokenAxis.findBreakAt(newMax, breaks))) { newMax = axisBreak.from; } // If both min and max is within the same break. if (newMax < newMin) { newMax = newMin; } } axis.constructor.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments); }; axis.setAxisTranslation = function () { axis.constructor.prototype.setAxisTranslation.call(this); brokenAxis.unitLength = void 0; if (brokenAxis.hasBreaks) { const breaks = axis.options.breaks || [], // Temporary one: breakArrayT = [], breakArray = [], pointRangePadding = pick(axis.pointRangePadding, 0); let length = 0, inBrk, repeat, min = axis.userMin || axis.min, max = axis.userMax || axis.max, start, i; // Min & max check (#4247) breaks.forEach(function (brk) { repeat = brk.repeat || Infinity; if (isNumber(min) && isNumber(max)) { if (Additions.isInBreak(brk, min)) { min += ((brk.to % repeat) - (min % repeat)); } if (Additions.isInBreak(brk, max)) { max -= ((max % repeat) - (brk.from % repeat)); } } }); // Construct an array holding all breaks in the axis breaks.forEach(function (brk) { start = brk.from; repeat = brk.repeat || Infinity; if (isNumber(min) && isNumber(max)) { while (start - repeat > min) { start -= repeat; } while (start < min) { start += repeat; } for (i = start; i < max; i += repeat) { breakArrayT.push({ value: i, move: 'in' }); breakArrayT.push({ value: i + brk.to - brk.from, move: 'out', size: brk.breakSize }); } } }); breakArrayT.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 = 0; start = min; breakArrayT.forEach(function (brk) { inBrk += (brk.move === 'in' ? 1 : -1); if (inBrk === 1 && brk.move === 'in') { start = brk.value; } if (inBrk === 0 && isNumber(start)) { breakArray.push({ from: start, to: brk.value, len: brk.value - start - (brk.size || 0) }); length += (brk.value - start - (brk.size || 0)); } }); brokenAxis.breakArray = breakArray; // Used with staticScale, and below the actual axis // length, when breaks are substracted. if (isNumber(min) && isNumber(max) && isNumber(axis.min)) { brokenAxis.unitLength = max - min - length + pointRangePadding; fireEvent(axis, 'afterBreaks'); if (axis.staticScale) { axis.transA = axis.staticScale; } else if (brokenAxis.unitLength) { axis.transA *= (max - axis.min + pointRangePadding) / brokenAxis.unitLength; } if (pointRangePadding) { axis.minPixelPadding = axis.transA * (axis.minPointOffset || 0); } axis.min = min; axis.max = max; } } }; } if (pick(redraw, true)) { axis.chart.redraw(); } } } 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) { const G = Highcharts; // Compositions BrokenAxis.compose(G.Axis, G.Series); }); _registerModule(_modules, 'Extensions/DataGrouping/ApproximationRegistry.js', [], function () { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Constants * * */ /** * 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 */ const ApproximationRegistry = { // approximations added programmatically }; /* * * * Default Export * * */ return ApproximationRegistry; }); _registerModule(_modules, 'Extensions/DataGrouping/ApproximationDefaults.js', [_modules['Extensions/DataGrouping/ApproximationRegistry.js'], _modules['Core/Utilities.js']], function (ApproximationRegistry, U) { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { arrayMax, arrayMin, correctFloat, extend, isNumber } = U; /* * * * Functions * * */ /** * @private */ function average(arr) { const len = arr.length; let ret = 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. * @private */ function averages() { const ret = []; [].forEach.call(arguments, function (arr) { ret.push(average(arr)); }); // Return undefined when first elem. is undefined and let // sum method handle null (#7377) return typeof ret[0] === 'undefined' ? void 0 : ret; } /** * @private */ function close(arr) { return arr.length ? arr[arr.length - 1] : (arr.hasNulls ? null : void 0); } /** * @private */ function high(arr) { return arr.length ? arrayMax(arr) : (arr.hasNulls ? null : void 0); } /** * HLC, OHLC and range are special cases where a multidimensional array is input * and an array is output. * @private */ function hlc(high, low, close) { high = ApproximationRegistry.high(high); low = ApproximationRegistry.low(low); close = ApproximationRegistry.close(close); if (isNumber(high) || isNumber(low) || isNumber(close)) { return [high, low, close]; } } /** * @private */ function low(arr) { return arr.length ? arrayMin(arr) : (arr.hasNulls ? null : void 0); } /** * @private */ function ohlc(open, high, low, close) { open = ApproximationRegistry.open(open); high = ApproximationRegistry.high(high); low = ApproximationRegistry.low(low); close = ApproximationRegistry.close(close); if (isNumber(open) || isNumber(high) || isNumber(low) || isNumber(close)) { return [open, high, low, close]; } } /** * @private */ function open(arr) { return arr.length ? arr[0] : (arr.hasNulls ? null : void 0); } /** * @private */ function range(low, high) { low = ApproximationRegistry.low(low); high = ApproximationRegistry.high(high); if (isNumber(low) || isNumber(high)) { return [low, high]; } if (low === null && high === null) { return null; } // else, return is undefined } /** * @private */ function sum(arr) { let 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; } /* * * * Default Export * * */ const ApproximationDefaults = { average, averages, close, high, hlc, low, ohlc, open, range, sum }; extend(ApproximationRegistry, ApproximationDefaults); return ApproximationDefaults; }); _registerModule(_modules, 'Extensions/DataGrouping/DataGroupingDefaults.js', [], function () { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Constants * * */ /** * Common options * @private */ const common = { // 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, %e %b, %H:%M:%S.%L', '%A, %e %b, %H:%M:%S.%L', '-%H:%M:%S.%L' ], second: [ '%A, %e %b, %H:%M:%S', '%A, %e %b, %H:%M:%S', '-%H:%M:%S' ], minute: [ '%A, %e %b, %H:%M', '%A, %e %b, %H:%M', '-%H:%M' ], hour: [ '%A, %e %b, %H:%M', '%A, %e %b, %H:%M', '-%H:%M' ], day: [ '%A, %e %b %Y', '%A, %e %b', '-%A, %e %b %Y' ], week: [ 'Week from %A, %e %b %Y', '%A, %e %b', '-%A, %e %b %Y' ], month: [ '%B %Y', '%B', '-%B %Y' ], year: [ '%Y', '%Y', '-%Y' ] } // smoothed = false, // enable this for navigator series only }; /** * Extends common options * @private */ const seriesSpecific = { line: {}, spline: {}, area: {}, areaspline: {}, arearange: {}, column: { groupPixelWidth: 10 }, columnrange: { groupPixelWidth: 10 }, candlestick: { groupPixelWidth: 10 }, ohlc: { groupPixelWidth: 5 }, hlc: { groupPixelWidth: 5 // Move to HeikinAshiSeries.ts aftre refactoring data grouping. }, heikinashi: { groupPixelWidth: 10 } }; /** * Units are defined in a separate array to allow complete overriding in * case of a user option. * @private */ const units = [ [ '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 ] ]; /* * * * Default Export * * */ const DataGroupingDefaults = { common, seriesSpecific, units }; return DataGroupingDefaults; }); _registerModule(_modules, 'Extensions/DataGrouping/DataGroupingAxisComposition.js', [_modules['Extensions/DataGrouping/DataGroupingDefaults.js'], _modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (DataGroupingDefaults, H, U) { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { composed } = H; const { addEvent, extend, merge, pick, pushUnique } = U; /* * * * Variables * * */ let AxisConstructor; /* * * * Functions * * */ /** * Check the groupPixelWidth and apply the grouping if needed. * Fired only after processing the data. * * @product highstock * * @function Highcharts.Axis#applyGrouping */ function applyGrouping(e) { const axis = this, series = axis.series; // Reset the groupPixelWidth for all series, #17141. series.forEach(function (series) { series.groupPixelWidth = void 0; // #2110 }); series.forEach(function (series) { series.groupPixelWidth = (axis.getGroupPixelWidth && axis.getGroupPixelWidth()); if (series.groupPixelWidth) { series.hasProcessed = true; // #2692 } // Fire independing on series.groupPixelWidth to always set a proper // dataGrouping state, (#16238) series.applyGrouping(!!e.hasExtremesChanged); }); } /** * @private */ function compose(AxisClass) { AxisConstructor = AxisClass; if (pushUnique(composed, compose)) { addEvent(AxisClass, 'afterSetScale', onAfterSetScale); // When all series are processed, calculate the group pixel width and // then if this value is different than zero apply groupings. addEvent(AxisClass, 'postProcessData', applyGrouping); extend(AxisClass.prototype, { applyGrouping, getGroupPixelWidth, setDataGrouping }); } } /** * 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. * @private */ function getGroupPixelWidth() { const series = this.series; let i = series.length, groupPixelWidth = 0, doGrouping = false, dataLength, dgOptions; // If one of the series needs grouping, apply it to all (#1634) while (i--) { dgOptions = series[i].options.dataGrouping; if (dgOptions) { // #2692 // If multiple series are compared on the same x axis, give them the // same group pixel width (#334) groupPixelWidth = Math.max(groupPixelWidth, // Fallback to commonOptions (#9693) pick(dgOptions.groupPixelWidth, DataGroupingDefaults.common.groupPixelWidth)); 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; } /** * 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). * @private */ function onAfterSetScale() { this.series.forEach(function (series) { series.hasProcessed = false; }); } /** * 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}. */ function setDataGrouping(dataGrouping, redraw) { const axis = this; let i; redraw = pick(redraw, true); if (!dataGrouping) { dataGrouping = { forced: false, units: null }; } // Axis is instantiated, update all series if (this instanceof AxisConstructor) { 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) { // Merging dataGrouping options with already defined options #16759 seriesOptions.dataGrouping = typeof dataGrouping === 'boolean' ? dataGrouping : merge(dataGrouping, seriesOptions.dataGrouping); }); } // 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(); } } /* * * * Default Export * * */ const DataGroupingAxisComposition = { compose }; return DataGroupingAxisComposition; }); _registerModule(_modules, 'Extensions/DataGrouping/DataGroupingSeriesComposition.js', [_modules['Extensions/DataGrouping/ApproximationRegistry.js'], _modules['Extensions/DataGrouping/DataGroupingDefaults.js'], _modules['Core/Axis/DateTimeAxis.js'], _modules['Core/Defaults.js'], _modules['Core/Globals.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (ApproximationRegistry, DataGroupingDefaults, DateTimeAxis, D, H, SeriesRegistry, U) { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { composed } = H; const { series: { prototype: seriesProto } } = SeriesRegistry; const { addEvent, defined, error, extend, isNumber, merge, pick, pushUnique } = U; /* * * * Constants * * */ const baseGeneratePoints = seriesProto.generatePoints; /* * * * Functions * * */ /** * @private */ function adjustExtremes(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); } } /** * @private */ function anchorPoints(series, groupedXData, xMax) { const options = series.options, dataGroupingOptions = options.dataGrouping, totalRange = (series.currentDataGrouping && series.currentDataGrouping.gapSize); if (!(dataGroupingOptions && series.xData && totalRange && series.groupMap)) { return; } const groupedDataLastIndex = groupedXData.length - 1, anchor = dataGroupingOptions.anchor, firstAnchor = dataGroupingOptions.firstAnchor, lastAnchor = dataGroupingOptions.lastAnchor; let anchorIndexIterator = groupedXData.length - 1, anchorFirstIndex = 0; // Change the first point position, but only when it is // the f