/**
 * @license Highstock JS v11.4.8 (2024-08-29)
 *
 * Indicator series type for Highcharts Stock
 *
 * (c) 2010-2024 Paweł Dalek
 *
 * 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/indicators/volume-by-price', ['highcharts', 'highcharts/modules/stock'], 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') {
                Highcharts.win.dispatchEvent(new CustomEvent(
                    'HighchartsModuleLoaded',
                    { detail: { path: path, module: obj[path] } }
                ));
            }
        }
    }
    _registerModule(_modules, 'Stock/Indicators/VBP/VBPPoint.js', [_modules['Core/Series/SeriesRegistry.js']], function (SeriesRegistry) {
        /* *
         *
         *  License: www.highcharts.com/license
         *
         *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
         *
         * */
        /* *
         *
         *  Imports
         *
         * */
        const { sma: { prototype: { pointClass: SMAPoint } } } = SeriesRegistry.seriesTypes;
        /* *
         *
         *  Class
         *
         * */
        class VBPPoint extends SMAPoint {
            // Required for destroying negative part of volume
            destroy() {
                // @todo: this.negativeGraphic doesn't seem to be used anywhere
                if (this.negativeGraphic) {
                    this.negativeGraphic = this.negativeGraphic.destroy();
                }
                super.destroy.apply(this, arguments);
            }
        }
        /* *
         *
         *  Default Export
         *
         * */

        return VBPPoint;
    });
    _registerModule(_modules, 'Stock/Indicators/VBP/VBPIndicator.js', [_modules['Stock/Indicators/VBP/VBPPoint.js'], _modules['Core/Animation/AnimationUtilities.js'], _modules['Core/Globals.js'], _modules['Core/Series/SeriesRegistry.js'], _modules['Core/Utilities.js']], function (VBPPoint, A, H, SeriesRegistry, U) {
        /* *
         *
         *  (c) 2010-2024 Paweł Dalek
         *
         *  Volume By Price (VBP) indicator for Highcharts Stock
         *
         *  License: www.highcharts.com/license
         *
         *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
         *
         * */
        const { animObject } = A;
        const { noop } = H;
        const { column: { prototype: columnProto }, sma: SMAIndicator } = SeriesRegistry.seriesTypes;
        const { addEvent, arrayMax, arrayMin, correctFloat, defined, error, extend, isArray, merge } = U;
        /* *
         *
         *  Constants
         *
         * */
        const abs = Math.abs;
        /* *
         *
         *  Functions
         *
         * */
        // Utils
        /**
         * @private
         */
        function arrayExtremesOHLC(data) {
            const dataLength = data.length;
            let min = data[0][3], max = min, i = 1, currentPoint;
            for (; i < dataLength; i++) {
                currentPoint = data[i][3];
                if (currentPoint < min) {
                    min = currentPoint;
                }
                if (currentPoint > max) {
                    max = currentPoint;
                }
            }
            return {
                min: min,
                max: max
            };
        }
        /* *
         *
         *  Class
         *
         * */
        /**
         * The Volume By Price (VBP) series type.
         *
         * @private
         * @class
         * @name Highcharts.seriesTypes.vbp
         *
         * @augments Highcharts.Series
         */
        class VBPIndicator extends SMAIndicator {
            /* *
             *
             *  Functions
             *
             * */
            init(chart, options) {
                const indicator = this;
                // Series.update() sends data that is not necessary as everything is
                // calculated in getValues(), #17007
                delete options.data;
                super.init.apply(indicator, arguments);
                // Only after series are linked add some additional logic/properties.
                const unbinder = addEvent(this.chart.constructor, 'afterLinkSeries', function () {
                    // Protection for a case where the indicator is being updated,
                    // for a brief moment the indicator is deleted.
                    if (indicator.options) {
                        const params = indicator.options.params, baseSeries = indicator.linkedParent, volumeSeries = chart.get(params.volumeSeriesID);
                        indicator.addCustomEvents(baseSeries, volumeSeries);
                    }
                    unbinder();
                }, {
                    order: 1
                });
                return indicator;
            }
            // Adds events related with removing series
            addCustomEvents(baseSeries, volumeSeries) {
                const indicator = this, toEmptyIndicator = () => {
                    indicator.chart.redraw();
                    indicator.setData([]);
                    indicator.zoneStarts = [];
                    if (indicator.zoneLinesSVG) {
                        indicator.zoneLinesSVG = indicator.zoneLinesSVG.destroy();
                    }
                };
                // If base series is deleted, indicator series data is filled with
                // an empty array
                indicator.dataEventsToUnbind.push(addEvent(baseSeries, 'remove', function () {
                    toEmptyIndicator();
                }));
                // If volume series is deleted, indicator series data is filled with
                // an empty array
                if (volumeSeries) {
                    indicator.dataEventsToUnbind.push(addEvent(volumeSeries, 'remove', function () {
                        toEmptyIndicator();
                    }));
                }
                return indicator;
            }
            // Initial animation
            animate(init) {
                const series = this, inverted = series.chart.inverted, group = series.group, attr = {};
                if (!init && group) {
                    const position = inverted ? series.yAxis.top : series.xAxis.left;
                    if (inverted) {
                        group['forceAnimate:translateY'] = true;
                        attr.translateY = position;
                    }
                    else {
                        group['forceAnimate:translateX'] = true;
                        attr.translateX = position;
                    }
                    group.animate(attr, extend(animObject(series.options.animation), {
                        step: function (val, fx) {
                            series.group.attr({
                                scaleX: Math.max(0.001, fx.pos)
                            });
                        }
                    }));
                }
            }
            drawPoints() {
                const indicator = this;
                if (indicator.options.volumeDivision.enabled) {
                    indicator.posNegVolume(true, true);
                    columnProto.drawPoints.apply(indicator, arguments);
                    indicator.posNegVolume(false, false);
                }
                columnProto.drawPoints.apply(indicator, arguments);
            }
            // Function responsible for dividing volume into positive and negative
            posNegVolume(initVol, pos) {
                const indicator = this, signOrder = pos ?
                    ['positive', 'negative'] :
                    ['negative', 'positive'], volumeDivision = indicator.options.volumeDivision, pointLength = indicator.points.length;
                let posWidths = [], negWidths = [], i = 0, pointWidth, priceZone, wholeVol, point;
                if (initVol) {
                    indicator.posWidths = posWidths;
                    indicator.negWidths = negWidths;
                }
                else {
                    posWidths = indicator.posWidths;
                    negWidths = indicator.negWidths;
                }
                for (; i < pointLength; i++) {
                    point = indicator.points[i];
                    point[signOrder[0] + 'Graphic'] = point.graphic;
                    point.graphic = point[signOrder[1] + 'Graphic'];
                    if (initVol) {
                        pointWidth = point.shapeArgs.width;
                        priceZone = indicator.priceZones[i];
                        wholeVol = priceZone.wholeVolumeData;
                        if (wholeVol) {
                            posWidths.push(pointWidth / wholeVol * priceZone.positiveVolumeData);
                            negWidths.push(pointWidth / wholeVol * priceZone.negativeVolumeData);
                        }
                        else {
                            posWidths.push(0);
                            negWidths.push(0);
                        }
                    }
                    point.color = pos ?
                        volumeDivision.styles.positiveColor :
                        volumeDivision.styles.negativeColor;
                    point.shapeArgs.width = pos ?
                        indicator.posWidths[i] :
                        indicator.negWidths[i];
                    point.shapeArgs.x = pos ?
                        point.shapeArgs.x :
                        indicator.posWidths[i];
                }
            }
            translate() {
                const indicator = this, options = indicator.options, chart = indicator.chart, yAxis = indicator.yAxis, yAxisMin = yAxis.min, zoneLinesOptions = indicator.options.zoneLines, priceZones = (indicator.priceZones);
                let yBarOffset = 0, volumeDataArray, maxVolume, primalBarWidth, barHeight, barHeightP, oldBarHeight, barWidth, pointPadding, chartPlotTop, barX, barY;
                columnProto.translate.apply(indicator);
                const indicatorPoints = indicator.points;
                // Do translate operation when points exist
                if (indicatorPoints.length) {
                    pointPadding = options.pointPadding < 0.5 ?
                        options.pointPadding :
                        0.1;
                    volumeDataArray = indicator.volumeDataArray;
                    maxVolume = arrayMax(volumeDataArray);
                    primalBarWidth = chart.plotWidth / 2;
                    chartPlotTop = chart.plotTop;
                    barHeight = abs(yAxis.toPixels(yAxisMin) -
                        yAxis.toPixels(yAxisMin + indicator.rangeStep));
                    oldBarHeight = abs(yAxis.toPixels(yAxisMin) -
                        yAxis.toPixels(yAxisMin + indicator.rangeStep));
                    if (pointPadding) {
                        barHeightP = abs(barHeight * (1 - 2 * pointPadding));
                        yBarOffset = abs((barHeight - barHeightP) / 2);
                        barHeight = abs(barHeightP);
                    }
                    indicatorPoints.forEach(function (point, index) {
                        barX = point.barX = point.plotX = 0;
                        barY = point.plotY = (yAxis.toPixels(priceZones[index].start) -
                            chartPlotTop -
                            (yAxis.reversed ?
                                (barHeight - oldBarHeight) :
                                barHeight) -
                            yBarOffset);
                        barWidth = correctFloat(primalBarWidth *
                            priceZones[index].wholeVolumeData / maxVolume);
                        point.pointWidth = barWidth;
                        point.shapeArgs = indicator.crispCol.apply(// eslint-disable-line no-useless-call
                        indicator, [barX, barY, barWidth, barHeight]);
                        point.volumeNeg = priceZones[index].negativeVolumeData;
                        point.volumePos = priceZones[index].positiveVolumeData;
                        point.volumeAll = priceZones[index].wholeVolumeData;
                    });
                    if (zoneLinesOptions.enabled) {
                        indicator.drawZones(chart, yAxis, indicator.zoneStarts, zoneLinesOptions.styles);
                    }
                }
            }
            getExtremes() {
                const prevCompare = this.options.compare, prevCumulative = this.options.cumulative;
                let ret;
                // Temporarily disable cumulative and compare while getting the extremes
                if (this.options.compare) {
                    this.options.compare = void 0;
                    ret = super.getExtremes();
                    this.options.compare = prevCompare;
                }
                else if (this.options.cumulative) {
                    this.options.cumulative = false;
                    ret = super.getExtremes();
                    this.options.cumulative = prevCumulative;
                }
                else {
                    ret = super.getExtremes();
                }
                return ret;
            }
            getValues(series, params) {
                const indicator = this, xValues = series.processedXData, yValues = series.processedYData, chart = indicator.chart, ranges = params.ranges, VBP = [], xData = [], yData = [], volumeSeries = chart.get(params.volumeSeriesID);
                // Checks if base series exists
                if (!series.chart) {
                    error('Base series not found! In case it has been removed, add ' +
                        'a new one.', true, chart);
                    return;
                }
                // Checks if volume series exists and if it has data
                if (!volumeSeries ||
                    !volumeSeries.processedXData.length) {
                    const errorMessage = volumeSeries && !volumeSeries.processedXData.length ?
                        ' does not contain any data.' :
                        ' not found! Check `volumeSeriesID`.';
                    error('Series ' +
                        params.volumeSeriesID + errorMessage, true, chart);
                    return;
                }
                // Checks if series data fits the OHLC format
                const isOHLC = isArray(yValues[0]);
                if (isOHLC && yValues[0].length !== 4) {
                    error('Type of ' +
                        series.name +
                        ' series is different than line, OHLC or candlestick.', true, chart);
                    return;
                }
                // Price zones contains all the information about the zones (index,
                // start, end, volumes, etc.)
                const priceZones = indicator.priceZones = indicator.specifyZones(isOHLC, xValues, yValues, ranges, volumeSeries);
                priceZones.forEach(function (zone, index) {
                    VBP.push([zone.x, zone.end]);
                    xData.push(VBP[index][0]);
                    yData.push(VBP[index][1]);
                });
                return {
                    values: VBP,
                    xData: xData,
                    yData: yData
                };
            }
            // Specifying where each zone should start ans end
            specifyZones(isOHLC, xValues, yValues, ranges, volumeSeries) {
                const indicator = this, rangeExtremes = (isOHLC ? arrayExtremesOHLC(yValues) : false), zoneStarts = indicator.zoneStarts = [], priceZones = [];
                let lowRange = rangeExtremes ?
                    rangeExtremes.min :
                    arrayMin(yValues), highRange = rangeExtremes ?
                    rangeExtremes.max :
                    arrayMax(yValues), i = 0, j = 1;
                // If the compare mode is set on the main series, change the VBP
                // zones to fit new extremes, #16277.
                const mainSeries = indicator.linkedParent;
                if (!indicator.options.compareToMain &&
                    mainSeries.dataModify) {
                    lowRange = mainSeries.dataModify.modifyValue(lowRange);
                    highRange = mainSeries.dataModify.modifyValue(highRange);
                }
                if (!defined(lowRange) || !defined(highRange)) {
                    if (this.points.length) {
                        this.setData([]);
                        this.zoneStarts = [];
                        if (this.zoneLinesSVG) {
                            this.zoneLinesSVG = this.zoneLinesSVG.destroy();
                        }
                    }
                    return [];
                }
                const rangeStep = indicator.rangeStep =
                    correctFloat(highRange - lowRange) / ranges;
                zoneStarts.push(lowRange);
                for (; i < ranges - 1; i++) {
                    zoneStarts.push(correctFloat(zoneStarts[i] + rangeStep));
                }
                zoneStarts.push(highRange);
                const zoneStartsLength = zoneStarts.length;
                //    Creating zones
                for (; j < zoneStartsLength; j++) {
                    priceZones.push({
                        index: j - 1,
                        x: xValues[0],
                        start: zoneStarts[j - 1],
                        end: zoneStarts[j]
                    });
                }
                return indicator.volumePerZone(isOHLC, priceZones, volumeSeries, xValues, yValues);
            }
            // Calculating sum of volume values for a specific zone
            volumePerZone(isOHLC, priceZones, volumeSeries, xValues, yValues) {
                const indicator = this, volumeXData = volumeSeries.processedXData, volumeYData = volumeSeries.processedYData, lastZoneIndex = priceZones.length - 1, baseSeriesLength = yValues.length, volumeSeriesLength = volumeYData.length;
                let previousValue, startFlag, endFlag, value, i;
                // Checks if each point has a corresponding volume value
                if (abs(baseSeriesLength - volumeSeriesLength)) {
                    // If the first point don't have volume, add 0 value at the
                    // beginning of the volume array
                    if (xValues[0] !== volumeXData[0]) {
                        volumeYData.unshift(0);
                    }
                    // If the last point don't have volume, add 0 value at the end
                    // of the volume array
                    if (xValues[baseSeriesLength - 1] !==
                        volumeXData[volumeSeriesLength - 1]) {
                        volumeYData.push(0);
                    }
                }
                indicator.volumeDataArray = [];
                priceZones.forEach(function (zone) {
                    zone.wholeVolumeData = 0;
                    zone.positiveVolumeData = 0;
                    zone.negativeVolumeData = 0;
                    for (i = 0; i < baseSeriesLength; i++) {
                        startFlag = false;
                        endFlag = false;
                        value = isOHLC ? yValues[i][3] : yValues[i];
                        previousValue = i ?
                            (isOHLC ?
                                yValues[i - 1][3] :
                                yValues[i - 1]) :
                            value;
                        // If the compare mode is set on the main series,
                        // change the VBP zones to fit new extremes, #16277.
                        const mainSeries = indicator.linkedParent;
                        if (!indicator.options.compareToMain &&
                            mainSeries.dataModify) {
                            value = mainSeries.dataModify.modifyValue(value);
                            previousValue = mainSeries.dataModify
                                .modifyValue(previousValue);
                        }
                        // Checks if this is the point with the
                        // lowest close value and if so, adds it calculations
                        if (value <= zone.start && zone.index === 0) {
                            startFlag = true;
                        }
                        // Checks if this is the point with the highest
                        // close value and if so, adds it calculations
                        if (value >= zone.end && zone.index === lastZoneIndex) {
                            endFlag = true;
                        }
                        if ((value > zone.start || startFlag) &&
                            (value < zone.end || endFlag)) {
                            zone.wholeVolumeData += volumeYData[i];
                            if (previousValue > value) {
                                zone.negativeVolumeData += volumeYData[i];
                            }
                            else {
                                zone.positiveVolumeData += volumeYData[i];
                            }
                        }
                    }
                    indicator.volumeDataArray.push(zone.wholeVolumeData);
                });
                return priceZones;
            }
            // Function responsible for drawing additional lines indicating zones
            drawZones(chart, yAxis, zonesValues, zonesStyles) {
                const indicator = this, renderer = chart.renderer, leftLinePos = 0, rightLinePos = chart.plotWidth, verticalOffset = chart.plotTop;
                let zoneLinesSVG = indicator.zoneLinesSVG, zoneLinesPath = [], verticalLinePos;
                zonesValues.forEach(function (value) {
                    verticalLinePos = yAxis.toPixels(value) - verticalOffset;
                    zoneLinesPath = zoneLinesPath.concat(chart.renderer.crispLine([[
                            'M',
                            leftLinePos,
                            verticalLinePos
                        ], [
                            'L',
                            rightLinePos,
                            verticalLinePos
                        ]], zonesStyles.lineWidth));
                });
                // Create zone lines one path or update it while animating
                if (zoneLinesSVG) {
                    zoneLinesSVG.animate({
                        d: zoneLinesPath
                    });
                }
                else {
                    zoneLinesSVG = indicator.zoneLinesSVG =
                        renderer
                            .path(zoneLinesPath)
                            .attr({
                            'stroke-width': zonesStyles.lineWidth,
                            'stroke': zonesStyles.color,
                            'dashstyle': zonesStyles.dashStyle,
                            'zIndex': indicator.group.zIndex + 0.1
                        })
                            .add(indicator.group);
                }
            }
        }
        /* *
         *
         *  Static Properties
         *
         * */
        /**
         * Volume By Price indicator.
         *
         * This series requires `linkedTo` option to be set.
         *
         * @sample stock/indicators/volume-by-price
         *         Volume By Price indicator
         *
         * @extends      plotOptions.sma
         * @since        6.0.0
         * @product      highstock
         * @requires     stock/indicators/indicators
         * @requires     stock/indicators/volume-by-price
         * @optionparent plotOptions.vbp
         */
        VBPIndicator.defaultOptions = merge(SMAIndicator.defaultOptions, {
            /**
             * @excluding index, period
             */
            params: {
                // Index and period are unchangeable, do not inherit (#15362)
                index: void 0,
                period: void 0,
                /**
                 * The number of price zones.
                 */
                ranges: 12,
                /**
                 * The id of volume series which is mandatory. For example using
                 * OHLC data, volumeSeriesID='volume' means the indicator will be
                 * calculated using OHLC and volume values.
                 */
                volumeSeriesID: 'volume'
            },
            /**
             * The styles for lines which determine price zones.
             */
            zoneLines: {
                /**
                 * Enable/disable zone lines.
                 */
                enabled: true,
                /**
                 * Specify the style of zone lines.
                 *
                 * @type    {Highcharts.CSSObject}
                 * @default {"color": "#0A9AC9", "dashStyle": "LongDash", "lineWidth": 1}
                 */
                styles: {
                    /** @ignore-option */
                    color: '#0A9AC9',
                    /** @ignore-option */
                    dashStyle: 'LongDash',
                    /** @ignore-option */
                    lineWidth: 1
                }
            },
            /**
             * The styles for bars when volume is divided into positive/negative.
             */
            volumeDivision: {
                /**
                 * Option to control if volume is divided.
                 */
                enabled: true,
                styles: {
                    /**
                     * Color of positive volume bars.
                     *
                     * @type {Highcharts.ColorString}
                     */
                    positiveColor: 'rgba(144, 237, 125, 0.8)',
                    /**
                     * Color of negative volume bars.
                     *
                     * @type {Highcharts.ColorString}
                     */
                    negativeColor: 'rgba(244, 91, 91, 0.8)'
                }
            },
            // To enable series animation; must be animationLimit > pointCount
            animationLimit: 1000,
            enableMouseTracking: false,
            pointPadding: 0,
            zIndex: -1,
            crisp: true,
            dataGrouping: {
                enabled: false
            },
            dataLabels: {
                allowOverlap: true,
                enabled: true,
                format: 'P: {point.volumePos:.2f} | N: {point.volumeNeg:.2f}',
                padding: 0,
                style: {
                    /** @internal */
                    fontSize: '0.5em'
                },
                verticalAlign: 'top'
            }
        });
        extend(VBPIndicator.prototype, {
            nameBase: 'Volume by Price',
            nameComponents: ['ranges'],
            calculateOn: {
                chart: 'render',
                xAxis: 'afterSetExtremes'
            },
            pointClass: VBPPoint,
            markerAttribs: noop,
            drawGraph: noop,
            getColumnMetrics: columnProto.getColumnMetrics,
            crispCol: columnProto.crispCol
        });
        SeriesRegistry.registerSeriesType('vbp', VBPIndicator);
        /* *
         *
         *  Default Export
         *
         * */
        /* *
         *
         *  API Options
         *
         * */
        /**
         * A `Volume By Price (VBP)` series. If the [type](#series.vbp.type) option is
         * not specified, it is inherited from [chart.type](#chart.type).
         *
         * @extends   series,plotOptions.vbp
         * @since     6.0.0
         * @product   highstock
         * @excluding dataParser, dataURL, compare, compareBase, compareStart
         * @requires  stock/indicators/indicators
         * @requires  stock/indicators/volume-by-price
         * @apioption series.vbp
         */
        ''; // To include the above in the js output

        return VBPIndicator;
    });
    _registerModule(_modules, 'masters/indicators/volume-by-price.src.js', [_modules['Core/Globals.js']], function (Highcharts) {


        return Highcharts;
    });
}));
