UNPKG

highcharts

Version:
1,209 lines (1,204 loc) 503 kB
/** * @license Highcharts JS v11.3.0 (2024-01-10) * * (c) 2009-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/highcharts-more', ['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, 'Extensions/Pane/PaneComposition.js', [_modules['Core/Globals.js'], _modules['Core/Utilities.js']], function (H, U) { /* * * * Imports * * */ const { composed } = H; const { addEvent, correctFloat, defined, pick, pushUnique } = U; /* * * * Functions * * */ /** @private */ function chartGetHoverPane(eventArgs) { const chart = this; let hoverPane; if (eventArgs) { chart.pane.forEach((pane) => { const x = eventArgs.chartX - chart.plotLeft, y = eventArgs.chartY - chart.plotTop; if (isInsidePane(x, y, pane.center)) { hoverPane = pane; } }); } return hoverPane; } /** @private */ function compose(ChartClass, PointerClass) { if (pushUnique(composed, compose)) { const chartProto = ChartClass.prototype; chartProto.collectionsWithUpdate.push('pane'); chartProto.getHoverPane = chartGetHoverPane; addEvent(ChartClass, 'afterIsInsidePlot', onChartAfterIsInsiderPlot); addEvent(PointerClass, 'afterGetHoverData', onPointerAfterGetHoverData); addEvent(PointerClass, 'beforeGetHoverData', onPointerBeforeGetHoverData); } } /** * Check whether element is inside or outside pane. * @private * @param {number} x * Element's x coordinate * @param {number} y * Element's y coordinate * @param {Array<number>} center * Pane's center (x, y) and diameter * @param {number} startAngle * Pane's normalized start angle in radians (<-PI, PI>) * @param {number} endAngle * Pane's normalized end angle in radians (<-PI, PI>) */ function isInsidePane(x, y, center, startAngle, endAngle) { let insideSlice = true; const cx = center[0], cy = center[1]; const distance = Math.sqrt(Math.pow(x - cx, 2) + Math.pow(y - cy, 2)); if (defined(startAngle) && defined(endAngle)) { // Round angle to N-decimals to avoid numeric errors const angle = Math.atan2(correctFloat(y - cy, 8), correctFloat(x - cx, 8)); // Ignore full circle panes: if (endAngle !== startAngle) { // If normalized start angle is bigger than normalized end, // it means angles have different signs. In such situation we // check the <-PI, startAngle> and <endAngle, PI> ranges. if (startAngle > endAngle) { insideSlice = (angle >= startAngle && angle <= Math.PI) || (angle <= endAngle && angle >= -Math.PI); } else { // In this case, we simple check if angle is within the // <startAngle, endAngle> range insideSlice = angle >= startAngle && angle <= correctFloat(endAngle, 8); } } } // Round up radius because x and y values are rounded return distance <= Math.ceil(center[2] / 2) && insideSlice; } /** * Check if (x, y) position is within pane for polar. * @private */ function onChartAfterIsInsiderPlot(e) { const chart = this; if (chart.polar) { if (e.options.inverted) { [e.x, e.y] = [e.y, e.x]; } e.isInsidePlot = chart.pane.some((pane) => isInsidePane(e.x, e.y, pane.center, pane.axis && pane.axis.normalizedStartAngleRad, pane.axis && pane.axis.normalizedEndAngleRad)); } } function onPointerAfterGetHoverData(eventArgs) { const chart = this.chart; if (eventArgs.hoverPoint && eventArgs.hoverPoint.plotX && eventArgs.hoverPoint.plotY && chart.hoverPane && !isInsidePane(eventArgs.hoverPoint.plotX, eventArgs.hoverPoint.plotY, chart.hoverPane.center)) { eventArgs.hoverPoint = void 0; } } /** @private */ function onPointerBeforeGetHoverData(eventArgs) { const chart = this.chart; if (chart.polar) { // Find pane we are currently hovering over. chart.hoverPane = chart.getHoverPane(eventArgs); // Edit filter method to handle polar eventArgs.filter = function (s) { return (s.visible && !(!eventArgs.shared && s.directTouch) && // #3821 pick(s.options.enableMouseTracking, true) && (!chart.hoverPane || s.xAxis.pane === chart.hoverPane)); }; } else { chart.hoverPane = void 0; } } /* * * * Default Export * * */ const PaneComposition = { compose }; return PaneComposition; }); _registerModule(_modules, 'Extensions/Pane/PaneDefaults.js', [], function () { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * API Options * * */ /** * An array of background items for the pane. * * @sample {highcharts} highcharts/demo/gauge-speedometer/ * Speedometer gauge with multiple backgrounds * * @type {Array<*>} * @optionparent pane.background */ const background = { /** * The class name for this background. * * @sample {highcharts} highcharts/css/pane/ * Panes styled by CSS * @sample {highstock} highcharts/css/pane/ * Panes styled by CSS * @sample {highmaps} highcharts/css/pane/ * Panes styled by CSS * * @type {string} * @default highcharts-pane * @since 5.0.0 * @apioption pane.background.className */ /** * The shape of the pane background. When `solid`, the background * is circular. When `arc`, the background extends only from the min * to the max of the value axis. * * @type {Highcharts.PaneBackgroundShapeValue} * @since 2.3.0 * @product highcharts */ shape: 'circle', /** * The pixel border width of the pane background. * * @since 2.3.0 * @product highcharts */ borderWidth: 1, /** * The pane background border color. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @since 2.3.0 * @product highcharts */ borderColor: "#cccccc" /* Palette.neutralColor20 */, /** * The background color or gradient for the pane. * * @type {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} * @default { linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, stops: [[0, #ffffff], [1, #e6e6e6]] } * @since 2.3.0 * @product highcharts */ backgroundColor: { /** @ignore-option */ linearGradient: { x1: 0, y1: 0, x2: 0, y2: 1 }, /** @ignore-option */ stops: [ [0, "#ffffff" /* Palette.backgroundColor */], [1, "#e6e6e6" /* Palette.neutralColor10 */] ] }, /** @ignore-option */ from: -Number.MAX_VALUE, /** * The inner radius of the pane background. Can be either numeric * (pixels) or a percentage string. * * @type {number|string} * @since 2.3.0 * @product highcharts */ innerRadius: 0, /** @ignore-option */ to: Number.MAX_VALUE, /** * The outer radius of the circular pane background. Can be either * numeric (pixels) or a percentage string. * * @type {number|string} * @since 2.3.0 * @product highcharts */ outerRadius: '105%' }; /** * The pane serves as a container for axes and backgrounds for circular * gauges and polar charts. * * @since 2.3.0 * @product highcharts * @requires highcharts-more * @optionparent pane */ const pane = { /** * The end angle of the polar X axis or gauge value axis, given in * degrees where 0 is north. Defaults to [startAngle](#pane.startAngle) * + 360. * * @sample {highcharts} highcharts/demo/gauge-vu-meter/ * VU-meter with custom start and end angle * * @type {number} * @since 2.3.0 * @product highcharts * @apioption pane.endAngle */ /** * The center of a polar chart or angular gauge, given as an array * of [x, y] positions. Positions can be given as integers that * transform to pixels, or as percentages of the plot area size. * * @sample {highcharts} highcharts/demo/gauge-vu-meter/ * Two gauges with different center * * @type {Array<string|number>} * @default ["50%", "50%"] * @since 2.3.0 * @product highcharts */ center: ['50%', '50%'], /** * The size of the pane, either as a number defining pixels, or a * percentage defining a percentage of the available plot area (the * smallest of the plot height or plot width). * * @sample {highcharts} highcharts/demo/gauge-vu-meter/ * Smaller size * * @type {number|string} * @product highcharts */ size: '85%', /** * The inner size of the pane, either as a number defining pixels, or a * percentage defining a percentage of the pane's size. * * @sample {highcharts} highcharts/series-polar/column-inverted-inner * The inner size set to 20% * * @type {number|string} * @product highcharts */ innerSize: '0%', /** * The start angle of the polar X axis or gauge axis, given in degrees * where 0 is north. Defaults to 0. * * @sample {highcharts} highcharts/demo/gauge-vu-meter/ * VU-meter with custom start and end angle * * @since 2.3.0 * @product highcharts */ startAngle: 0 }; /* * * * Default Export * * */ const PaneDefaults = { pane, background }; return PaneDefaults; }); _registerModule(_modules, 'Extensions/Pane/Pane.js', [_modules['Series/CenteredUtilities.js'], _modules['Extensions/Pane/PaneComposition.js'], _modules['Extensions/Pane/PaneDefaults.js'], _modules['Core/Utilities.js']], function (CU, PaneComposition, PaneDefaults, U) { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { extend, merge, splat } = U; /* * * * Class * * */ /** * The Pane object allows options that are common to a set of X and Y axes. * * In the future, this can be extended to basic Highcharts and Highcharts Stock. * * @private * @class * @name Highcharts.Pane * @param {Highcharts.PaneOptions} options * @param {Highcharts.Chart} chart */ class Pane { /* * * * Constructor * * */ constructor(options, chart) { this.coll = 'pane'; // Member of chart.pane this.init(options, chart); } /* * * * Functions * * */ /** * Initialize the Pane object * * @private * @function Highcharts.Pane#init * * @param {Highcharts.PaneOptions} options * * @param {Highcharts.Chart} chart */ init(options, chart) { this.chart = chart; this.background = []; chart.pane.push(this); this.setOptions(options); } /** * @private * @function Highcharts.Pane#setOptions * * @param {Highcharts.PaneOptions} options */ setOptions(options) { // Set options. Angular charts have a default background (#3318) this.options = options = merge(PaneDefaults.pane, this.chart.angular ? { background: {} } : void 0, options); } /** * Render the pane with its backgrounds. * * @private * @function Highcharts.Pane#render */ render() { const options = this.options, renderer = this.chart.renderer; if (!this.group) { this.group = renderer.g('pane-group') .attr({ zIndex: options.zIndex || 0 }) .add(); } this.updateCenter(); let backgroundOption = this.options.background; // Render the backgrounds if (backgroundOption) { backgroundOption = splat(backgroundOption); const len = Math.max(backgroundOption.length, this.background.length || 0); for (let i = 0; i < len; i++) { // #6641 - if axis exists, chart is circular and apply // background if (backgroundOption[i] && this.axis) { this.renderBackground(merge(PaneDefaults.background, backgroundOption[i]), i); } else if (this.background[i]) { this.background[i] = this.background[i].destroy(); this.background.splice(i, 1); } } } } /** * Render an individual pane background. * * @private * @function Highcharts.Pane#renderBackground * * @param {Highcharts.PaneBackgroundOptions} backgroundOptions * Background options * * @param {number} i * The index of the background in this.backgrounds */ renderBackground(backgroundOptions, i) { const attribs = { 'class': 'highcharts-pane ' + (backgroundOptions.className || '') }; let method = 'animate'; if (!this.chart.styledMode) { extend(attribs, { 'fill': backgroundOptions.backgroundColor, 'stroke': backgroundOptions.borderColor, 'stroke-width': backgroundOptions.borderWidth }); } if (!this.background[i]) { this.background[i] = this.chart.renderer .path() .add(this.group); method = 'attr'; } this.background[i][method]({ 'd': this.axis.getPlotBandPath(backgroundOptions.from, backgroundOptions.to, backgroundOptions) }).attr(attribs); } /** * Gets the center for the pane and its axis. * * @private * @function Highcharts.Pane#updateCenter * @param {Highcharts.Axis} [axis] */ updateCenter(axis) { this.center = (axis || this.axis || {}).center = CU.getCenter.call(this); } /** * Destroy the pane item * * @ignore * @private * @function Highcharts.Pane#destroy * / destroy: function () { erase(this.chart.pane, this); this.background.forEach(function (background) { background.destroy(); }); this.background.length = 0; this.group = this.group.destroy(); }, */ /** * Update the pane item with new options * * @private * @function Highcharts.Pane#update * @param {Highcharts.PaneOptions} options * New pane options * @param {boolean} [redraw] */ update(options, redraw) { merge(true, this.options, options); this.setOptions(this.options); this.render(); this.chart.axes.forEach(function (axis) { if (axis.pane === this) { axis.pane = null; axis.update({}, redraw); } }, this); } } /* * * * Static Properties * * */ Pane.compose = PaneComposition.compose; /* * * * Default Export * * */ /* * * * API Declarations * * */ /** * @typedef {"arc"|"circle"|"solid"} Highcharts.PaneBackgroundShapeValue */ ''; // keeps doclets above in JS file return Pane; }); _registerModule(_modules, 'Series/AreaRange/AreaRangePoint.js', [_modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (SeriesRegistry, U) { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { area: { prototype: { pointClass: AreaPoint, pointClass: { prototype: areaProto } } } } = SeriesRegistry.seriesTypes; const { defined, isNumber, merge } = U; /* * * * Class * * */ class AreaRangePoint extends AreaPoint { /** * Range series only. The high or maximum value for each data point. * @name Highcharts.Point#high * @type {number|undefined} */ /** * Range series only. The low or minimum value for each data point. * @name Highcharts.Point#low * @type {number|undefined} */ /* * * * Functions * * */ /** * @private */ setState() { const prevState = this.state, series = this.series, isPolar = series.chart.polar, seriesOptionsMarker = series.options.marker, seriesDefaultSymbol = series.symbol; if (!defined(this.plotHigh)) { // Boost doesn't calculate plotHigh this.plotHigh = series.yAxis.toPixels(this.high, true); } if (!defined(this.plotLow)) { // Boost doesn't calculate plotLow this.plotLow = this.plotY = series.yAxis.toPixels(this.low, true); } series.lowerStateMarkerGraphic = series.stateMarkerGraphic; series.stateMarkerGraphic = series.upperStateMarkerGraphic; // Change state also for the top marker this.graphic = this.graphics && this.graphics[1]; this.plotY = this.plotHigh; if (isPolar && isNumber(this.plotHighX)) { this.plotX = this.plotHighX; } // Top state: areaProto.setState.apply(this, arguments); this.state = prevState; // Now restore defaults this.plotY = this.plotLow; this.graphic = this.graphics && this.graphics[0]; if (isPolar && isNumber(this.plotLowX)) { this.plotX = this.plotLowX; } series.upperStateMarkerGraphic = series.stateMarkerGraphic; series.stateMarkerGraphic = series.lowerStateMarkerGraphic; // Lower marker is stored at stateMarkerGraphic // to avoid reference duplication (#7021) series.lowerStateMarkerGraphic = void 0; const originalSettings = series.modifyMarkerSettings(); // Bottom state areaProto.setState.apply(this, arguments); // Restore previous state series.restoreMarkerSettings(originalSettings); } haloPath() { const isPolar = this.series.chart.polar; let path = []; // Bottom halo this.plotY = this.plotLow; if (isPolar && isNumber(this.plotLowX)) { this.plotX = this.plotLowX; } if (this.isInside) { path = areaProto.haloPath.apply(this, arguments); } // Top halo this.plotY = this.plotHigh; if (isPolar && isNumber(this.plotHighX)) { this.plotX = this.plotHighX; } if (this.isTopInside) { path = path.concat(areaProto.haloPath.apply(this, arguments)); } return path; } isValid() { return isNumber(this.low) && isNumber(this.high); } } /* * * * Default Export * * */ return AreaRangePoint; }); _registerModule(_modules, 'Series/AreaRange/AreaRangeSeries.js', [_modules['Series/AreaRange/AreaRangePoint.js'], _modules['Core/Globals.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (AreaRangePoint, H, SeriesRegistry, U) { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { noop } = H; const { area: AreaSeries, area: { prototype: areaProto }, column: { prototype: columnProto } } = SeriesRegistry.seriesTypes; const { addEvent, defined, extend, isArray, isNumber, pick, merge } = U; /* * * * Constants * * */ /** * The area range series is a carteseian series with higher and lower values for * each point along an X axis, where the area between the values is shaded. * * @sample {highcharts} highcharts/demo/arearange/ * Area range chart * @sample {highstock} stock/demo/arearange/ * Area range chart * * @extends plotOptions.area * @product highcharts highstock * @excluding stack, stacking * @requires highcharts-more * @optionparent plotOptions.arearange * * @private */ const areaRangeSeriesOptions = { /** * @see [fillColor](#plotOptions.arearange.fillColor) * @see [fillOpacity](#plotOptions.arearange.fillOpacity) * * @apioption plotOptions.arearange.color */ /** * @default low * @apioption plotOptions.arearange.colorKey */ /** * @see [color](#plotOptions.arearange.color) * @see [fillOpacity](#plotOptions.arearange.fillOpacity) * * @apioption plotOptions.arearange.fillColor */ /** * @see [color](#plotOptions.arearange.color) * @see [fillColor](#plotOptions.arearange.fillColor) * * @default {highcharts} 0.75 * @default {highstock} 0.75 * @apioption plotOptions.arearange.fillOpacity */ /** * 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`. * * @type {boolean|Highcharts.ShadowOptionsObject} * @product highcharts * @apioption plotOptions.arearange.shadow */ /** * Pixel width of the arearange graph line. * * @since 2.3.0 * * @private */ lineWidth: 1, /** * @type {number|null} */ threshold: null, tooltip: { pointFormat: '<span style="color:{series.color}">\u25CF</span> ' + '{series.name}: <b>{point.low}</b> - <b>{point.high}</b><br/>' }, /** * Whether the whole area or just the line should respond to mouseover * tooltips and other mouse or touch events. * * @since 2.3.0 * * @private */ trackByArea: true, /** * Extended data labels for range series types. Range series data * labels use no `x` and `y` options. Instead, they have `xLow`, * `xHigh`, `yLow` and `yHigh` options to allow the higher and lower * data label sets individually. * * @declare Highcharts.SeriesAreaRangeDataLabelsOptionsObject * @exclude x, y * @since 2.3.0 * @product highcharts highstock * * @private */ dataLabels: { align: void 0, verticalAlign: void 0, /** * X offset of the lower data labels relative to the point value. * * @sample highcharts/plotoptions/arearange-datalabels/ * Data labels on range series * @sample highcharts/plotoptions/arearange-datalabels/ * Data labels on range series */ xLow: 0, /** * X offset of the higher data labels relative to the point value. * * @sample highcharts/plotoptions/arearange-datalabels/ * Data labels on range series */ xHigh: 0, /** * Y offset of the lower data labels relative to the point value. * * @sample highcharts/plotoptions/arearange-datalabels/ * Data labels on range series */ yLow: 0, /** * Y offset of the higher data labels relative to the point value. * * @sample highcharts/plotoptions/arearange-datalabels/ * Data labels on range series */ yHigh: 0 } }; /* * * * Class * * */ /** * The AreaRange series type. * * @private * @class * @name Highcharts.seriesTypes.arearange * * @augments Highcharts.Series */ class AreaRangeSeries extends AreaSeries { /* * * * Functions * * */ toYData(point) { return [point.low, point.high]; } /** * Translate a point's plotHigh from the internal angle and radius measures * to true plotHigh coordinates. This is an addition of the toXY method * found in Polar.js, because it runs too early for arearanges to be * considered (#3419). * @private */ highToXY(point) { // Find the polar plotX and plotY const chart = this.chart, xy = this.xAxis.postTranslate(point.rectPlotX || 0, this.yAxis.len - (point.plotHigh || 0)); point.plotHighX = xy.x - chart.plotLeft; point.plotHigh = xy.y - chart.plotTop; point.plotLowX = point.plotX; } /** * Extend the line series' getSegmentPath method by applying the segment * path to both lower and higher values of the range. * @private */ getGraphPath(points) { const highPoints = [], highAreaPoints = [], getGraphPath = areaProto.getGraphPath, options = this.options, polar = this.chart.polar, connectEnds = polar && options.connectEnds !== false, connectNulls = options.connectNulls; let i, point, pointShim, step = options.step; points = points || this.points; // Create the top line and the top part of the area fill. The area fill // compensates for null points by drawing down to the lower graph, // moving across the null gap and starting again at the lower graph. i = points.length; while (i--) { point = points[i]; // Support for polar const highAreaPoint = polar ? { plotX: point.rectPlotX, plotY: point.yBottom, doCurve: false // #5186, gaps in areasplinerange fill } : { plotX: point.plotX, plotY: point.plotY, doCurve: false // #5186, gaps in areasplinerange fill }; if (!point.isNull && !connectEnds && !connectNulls && (!points[i + 1] || points[i + 1].isNull)) { highAreaPoints.push(highAreaPoint); } pointShim = { polarPlotY: point.polarPlotY, rectPlotX: point.rectPlotX, yBottom: point.yBottom, // plotHighX is for polar charts plotX: pick(point.plotHighX, point.plotX), plotY: point.plotHigh, isNull: point.isNull }; highAreaPoints.push(pointShim); highPoints.push(pointShim); if (!point.isNull && !connectEnds && !connectNulls && (!points[i - 1] || points[i - 1].isNull)) { highAreaPoints.push(highAreaPoint); } } // Get the paths const lowerPath = getGraphPath.call(this, points); if (step) { if (step === true) { step = 'left'; } options.step = { left: 'right', center: 'center', right: 'left' }[step]; // swap for reading in getGraphPath } const higherPath = getGraphPath.call(this, highPoints); const higherAreaPath = getGraphPath.call(this, highAreaPoints); options.step = step; // Create a line on both top and bottom of the range const linePath = [].concat(lowerPath, higherPath); // For the area path, we need to change the 'move' statement into // 'lineTo' if (!this.chart.polar && higherAreaPath[0] && higherAreaPath[0][0] === 'M') { // This probably doesn't work for spline higherAreaPath[0] = [ 'L', higherAreaPath[0][1], higherAreaPath[0][2] ]; } this.graphPath = linePath; this.areaPath = lowerPath.concat(higherAreaPath); // Prepare for sideways animation linePath.isArea = true; linePath.xMap = lowerPath.xMap; this.areaPath.xMap = lowerPath.xMap; return linePath; } /** * Extend the basic drawDataLabels method by running it for both lower and * higher values. * @private */ drawDataLabels() { const data = this.points, length = data.length, originalDataLabels = [], dataLabelOptions = this.options.dataLabels, inverted = this.chart.inverted; let i, point, up, upperDataLabelOptions, lowerDataLabelOptions; if (dataLabelOptions) { // Split into upper and lower options. If data labels is an array, // the first element is the upper label, the second is the lower. // // TODO: We want to change this and allow multiple labels for both // upper and lower values in the future - introducing some options // for which point value to use as Y for the dataLabel, so that this // could be handled in Series.drawDataLabels. This would also // improve performance since we now have to loop over all the points // multiple times to work around the data label logic. if (isArray(dataLabelOptions)) { upperDataLabelOptions = dataLabelOptions[0] || { enabled: false }; lowerDataLabelOptions = dataLabelOptions[1] || { enabled: false }; } else { // Make copies upperDataLabelOptions = extend({}, dataLabelOptions); upperDataLabelOptions.x = dataLabelOptions.xHigh; upperDataLabelOptions.y = dataLabelOptions.yHigh; lowerDataLabelOptions = extend({}, dataLabelOptions); lowerDataLabelOptions.x = dataLabelOptions.xLow; lowerDataLabelOptions.y = dataLabelOptions.yLow; } // Draw upper labels if (upperDataLabelOptions.enabled || this.hasDataLabels?.()) { // Set preliminary values for plotY and dataLabel // and draw the upper labels i = length; while (i--) { point = data[i]; if (point) { const { plotHigh = 0, plotLow = 0 } = point; up = upperDataLabelOptions.inside ? plotHigh < plotLow : plotHigh > plotLow; point.y = point.high; point._plotY = point.plotY; point.plotY = plotHigh; // Store original data labels and set preliminary label // objects to be picked up in the uber method originalDataLabels[i] = point.dataLabel; point.dataLabel = point.dataLabelUpper; // Set the default offset point.below = up; if (inverted) { if (!upperDataLabelOptions.align) { upperDataLabelOptions.align = up ? 'right' : 'left'; } } else { if (!upperDataLabelOptions.verticalAlign) { upperDataLabelOptions.verticalAlign = up ? 'top' : 'bottom'; } } } } this.options.dataLabels = upperDataLabelOptions; if (areaProto.drawDataLabels) { // #1209: areaProto.drawDataLabels.apply(this, arguments); } // Reset state after the upper labels were created. Move // it to point.dataLabelUpper and reassign the originals. // We do this here to support not drawing a lower label. i = length; while (i--) { point = data[i]; if (point) { point.dataLabelUpper = point.dataLabel; point.dataLabel = originalDataLabels[i]; delete point.dataLabels; point.y = point.low; point.plotY = point._plotY; } } } // Draw lower labels if (lowerDataLabelOptions.enabled || this.hasDataLabels?.()) { i = length; while (i--) { point = data[i]; if (point) { const { plotHigh = 0, plotLow = 0 } = point; up = lowerDataLabelOptions.inside ? plotHigh < plotLow : plotHigh > plotLow; // Set the default offset point.below = !up; if (inverted) { if (!lowerDataLabelOptions.align) { lowerDataLabelOptions.align = up ? 'left' : 'right'; } } else { if (!lowerDataLabelOptions.verticalAlign) { lowerDataLabelOptions.verticalAlign = up ? 'bottom' : 'top'; } } } } this.options.dataLabels = lowerDataLabelOptions; if (areaProto.drawDataLabels) { areaProto.drawDataLabels.apply(this, arguments); } } // Merge upper and lower into point.dataLabels for later destroying if (upperDataLabelOptions.enabled) { i = length; while (i--) { point = data[i]; if (point) { point.dataLabels = [ point.dataLabelUpper, point.dataLabel ].filter(function (label) { return !!label; }); } } } // Reset options this.options.dataLabels = dataLabelOptions; } } alignDataLabel() { columnProto.alignDataLabel.apply(this, arguments); } modifyMarkerSettings() { const series = this, originalMarkerSettings = { marker: series.options.marker, symbol: series.symbol }; if (series.options.lowMarker) { const { options: { marker, lowMarker } } = series; series.options.marker = merge(marker, lowMarker); if (lowMarker.symbol) { series.symbol = lowMarker.symbol; } } return originalMarkerSettings; } restoreMarkerSettings(originalSettings) { const series = this; series.options.marker = originalSettings.marker; series.symbol = originalSettings.symbol; } drawPoints() { const series = this, pointLength = series.points.length; let i, point; const originalSettings = series.modifyMarkerSettings(); // Draw bottom points areaProto.drawPoints.apply(series, arguments); // Restore previous state series.restoreMarkerSettings(originalSettings); // Prepare drawing top points i = 0; while (i < pointLength) { point = series.points[i]; /** * Array for multiple SVG graphics representing the point in the * chart. Only used in cases where the point can not be represented * by a single graphic. * * @see Highcharts.Point#graphic * * @name Highcharts.Point#graphics * @type {Array<Highcharts.SVGElement>|undefined} */ point.graphics = point.graphics || []; // Save original props to be overridden by temporary props for top // points point.origProps = { plotY: point.plotY, plotX: point.plotX, isInside: point.isInside, negative: point.negative, zone: point.zone, y: point.y }; if (point.graphic || point.graphics[0]) { point.graphics[0] = point.graphic; } point.graphic = point.graphics[1]; point.plotY = point.plotHigh; if (defined(point.plotHighX)) { point.plotX = point.plotHighX; } point.y = pick(point.high, point.origProps.y); // #15523 point.negative = point.y < (series.options.threshold || 0); if (series.zones.length) { point.zone = point.getZone(); } if (!series.chart.polar) { point.isInside = point.isTopInside = (typeof point.plotY !== 'undefined' && point.plotY >= 0 && point.plotY <= series.yAxis.len && // #3519 point.plotX >= 0 && point.plotX <= series.xAxis.len); } i++; } // Draw top points areaProto.drawPoints.apply(series, arguments); // Reset top points preliminary modifications i = 0; while (i < pointLength) { point = series.points[i]; point.graphics = point.graphics || []; if (point.graphic || point.graphics[1]) { point.graphics[1] = point.graphic; } point.graphic = point.graphics[0]; if (point.origProps) { extend(point, point.origProps); delete point.origProps; } i++; } } hasMarkerChanged(options, oldOptions) { const series = this, lowMarker = options.lowMarker, oldMarker = oldOptions.lowMarker || {}; return (lowMarker && (lowMarker.enabled === false || oldMarker.symbol !== lowMarker.symbol || // #10870, #15946 oldMarker.height !== lowMarker.height || // #16274 oldMarker.width !== lowMarker.width // #16274 )) || super.hasMarkerChanged(options, oldOptions); } } /** * * Static Properties * */ AreaRangeSeries.defaultOptions = merge(AreaSeries.defaultOptions, areaRangeSeriesOptions); addEvent(AreaRangeSeries, 'afterTranslate', function () { // Set plotLow and plotHigh // Rules out lollipop, but lollipop should not inherit range series in the // first place if (this.pointArrayMap.join(',') === 'low,high') { this.points.forEach((point) => { const high = point.high, plotY = point.plotY; if (point.isNull) { point.plotY = void 0; } else { point.plotLow = plotY; // Calculate plotHigh value based on each yAxis scale (#15752) point.plotHigh = isNumber(high) ? this.yAxis.translate(this.dataModify ? this.dataModify.modifyValue(high) : high, false, true, void 0, true) : void 0; if (this.dataModify) { point.yBottom = point.plotHigh; } } }); } }, { order: 0 }); addEvent(AreaRangeSeries, 'afterTranslate', function ()