/* *
 *
 *  (c) 2010-2019 Torstein Honsi
 *
 *  License: www.highcharts.com/license
 *
 *  !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
 *
 * */
'use strict';
import H from './Globals.js';
import U from './Utilities.js';
var defined = U.defined, erase = U.erase, isArray = U.isArray, isNumber = U.isNumber, splat = U.splat;
import './Color.js';
import './Axis.js';
import './Chart.js';
import './Series.js';
import './Options.js';
import './Scrollbar.js';
var addEvent = H.addEvent, Axis = H.Axis, Chart = H.Chart, color = H.color, defaultOptions = H.defaultOptions, destroyObjectProperties = H.destroyObjectProperties, extend = H.extend, hasTouch = H.hasTouch, isTouchDevice = H.isTouchDevice, merge = H.merge, pick = H.pick, removeEvent = H.removeEvent, Scrollbar = H.Scrollbar, Series = H.Series, seriesTypes = H.seriesTypes, 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 = 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 Highstock 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: '#f2f2f2',
            /**
             * The stroke for the handle border and the stripes inside.
             *
             * @type    {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject}
             */
            borderColor: '#999999'
        },
        /**
         * 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('#6685c2').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: '#cccccc',
        /**
         * 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:
         *
         * <pre>series: {
         *     type: 'areaspline',
         *     fillOpacity: 0.05,
         *     dataGrouping: {
         *         smoothed: true
         *     },
         *     lineWidth: 1,
         *     marker: {
         *         enabled: false
         *     }
         * }</pre>
         *
         * @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 {*|Highcharts.SeriesOptionsType}
         */
        series: {
            /**
             * The type of the navigator series. Defaults to `areaspline` if
             * defined, otherwise `line`.
             *
             * @type    {string}
             * @default areaspline
             */
            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,
                smoothed: true,
                // 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: {
                /** @internal */
                enabled: false,
                /** @internal */
                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
            },
            pointRange: 0,
            /**
             * 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:
         *
         * <pre>xAxis: {
         *     tickWidth: 0,
         *     lineWidth: 0,
         *     gridLineWidth: 1,
         *     tickPixelInterval: 200,
         *     labels: {
         *            align: 'left',
         *         style: {
         *             color: '#888'
         *         },
         *         x: 3,
         *         y: -4
         *     }
         * }</pre>
         *
         * @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: '#e6e6e6',
            gridLineWidth: 1,
            tickPixelInterval: 200,
            labels: {
                align: 'left',
                /**
                 * @type {Highcharts.CSSObject}
                 */
                style: {
                    /** @ignore */
                    color: '#999999'
                },
                x: 3,
                y: -4
            },
            crosshair: false
        },
        /**
         * Options for the navigator Y axis. Default series options
         * for the navigator yAxis are:
         *
         * <pre>yAxis: {
         *     gridLineWidth: 0,
         *     startOnTick: false,
         *     endOnTick: false,
         *     minPadding: 0.1,
         *     maxPadding: 0.1,
         *     labels: {
         *         enabled: false
         *     },
         *     title: {
         *         text: null
         *     },
         *     tickWidth: 0
         * }</pre>
         *
         * @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
 */
H.Renderer.prototype.symbols['navigator-handle'] = function (x, y, w, h, options) {
    var halfWidth = options.width / 2, markerPosition = Math.round(halfWidth / 3) + 0.5, height = options.height;
    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
    ];
};
/**
 * 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 {*}
 */
Axis.prototype.toFixedRange = function (pxMin, pxMax, fixedMin, fixedMax) {
    var fixedRange = this.chart && this.chart.fixedRange, newMin = pick(fixedMin, this.translate(pxMin, true, !this.horiz)), newMax = pick(fixedMax, this.translate(pxMax, true, !this.horiz)), changeRatio = fixedRange && (newMax - newMin) / fixedRange;
    // 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 = undefined;
    }
    return {
        min: newMin,
        max: newMax
    };
};
/**
 * The Navigator class
 *
 * @private
 * @class
 * @name Highcharts.Navigator
 *
 * @param {Highcharts.Chart} chart
 *        Chart object
 */
function Navigator(chart) {
    this.init(chart);
}
Navigator.prototype = {
    /**
     * 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'
     */
    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'
     */
    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, 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,
                // top edge
                navigatorTop - scrollbarHeight - outlineCorrection,
                'L',
                left + outlineHeight,
                verticalMin,
                'L',
                left,
                verticalMin,
                'L',
                left,
                zoomedMax,
                'L',
                left + outlineHeight,
                zoomedMax,
                'L',
                left + outlineHeight,
                // bottom edge
                navigatorTop + navigatorSize + scrollbarHeight
            ].concat(maskInside ? [
                'M',
                left + outlineHeight,
                verticalMin - halfOutline,
                '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
            ].concat(maskInside ? [
                'M',
                zoomedMin - halfOutline,
                navigatorTop,
                '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'
     */
    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
     */
    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
     */
    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}
     */
    render: function (min, max, pxMin, pxMax) {
        var navigator = this, chart = navigator.chart, navigatorWidth, scrollbarLeft, scrollbarTop, scrollbarHeight = navigator.scrollbarHeight, navigatorSize, xAxis = navigator.xAxis, scrollbarXAxis = xAxis.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;
        }
        // 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(H.correctFloat(newMax - newMin));
        if (currentRange < minRange) {
            if (this.grabbedLeft) {
                pxMin = xAxis.toPixels(newMax - minRange, true);
            }
            else if (this.grabbedRight) {
                pxMax = xAxis.toPixels(newMin + minRange, true);
            }
        }
        else if (defined(maxRange) && currentRange > maxRange) {
            if (this.grabbedLeft) {
                pxMin = xAxis.toPixels(newMax - maxRange, true);
            }
            else if (this.grabbedRight) {
                pxMax = xAxis.toPixels(newMin + maxRange, true);
            }
        }
        // Handles are allowed to cross, but never exceed the plot area
        navigator.zoomedMax = Math.min(Math.max(pxMin, pxMax, 0), zoomedMax);
        navigator.zoomedMin = Math.min(Math.max(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
     */
    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(container, 'mousemove', mouseMoveHandler), addEvent(container.ownerDocument, 'mouseup', mouseUpHandler));
        // Touch events
        if (hasTouch) {
            eventsToUnbind.push(addEvent(container, '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.
     */
    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
     */
    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.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}
     */
    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
     */
    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}
     */
    onMouseUp: function (e) {
        var navigator = this, chart = navigator.chart, xAxis = navigator.xAxis, scrollbar = navigator.scrollbar, unionExtremes, fixedMin, fixedMax, ext, DOMEvent = e.DOMEvent || e;
        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.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') {
            navigator.grabbedLeft = navigator.grabbedRight =
                navigator.grabbedCenter = navigator.fixedWidth =
                    navigator.fixedExtreme = navigator.otherHandlePos =
                        navigator.hasDragged = navigator.dragOffset = null;
        }
    },
    /**
     * Removes the event handlers attached previously with addEvents.
     *
     * @private
     * @function Highcharts.Navigator#removeEvents
     * @return {void}
     */
    removeEvents: function () {
        if (this.eventsToUnbind) {
            this.eventsToUnbind.forEach(function (unbind) {
                unbind();
            });
            this.eventsToUnbind = undefined;
        }
        this.removeBaseSeriesEvents();
    },
    /**
     * Remove data events.
     *
     * @private
     * @function Highcharts.Navigator#removeBaseSeriesEvents
     * @return {void}
     */
    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
     */
    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, !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,
                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 = {
                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);
                },
                toFixedRange: Axis.prototype.toFixedRange,
                fake: true
            };
        }
        // 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 (chart.options.scrollbar.liveRedraw ||
                    (e.DOMType !== 'mousemove' &&
                        e.DOMType !== 'touchmove')) {
                    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}
     */
    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}
     */
    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)
                H.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.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}
     */
    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: false,
            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 || {};
                mergedNavSeriesOptions = merge(baseOptions, navSeriesMixin, userNavOptions, baseNavigatorOptions);
                // 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}
     */
    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) {
            addEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes);
        }
        baseSeries.forEach(function (base) {
            // Link base series show/hide to navigator series visibility
            addEvent(base, 'show', function () {
                if (this.navigatorSeries) {
                    this.navigatorSeries.setVisible(true, false);
                }
            });
            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) {
                    addEvent(base, 'updatedData', this.updatedDataHandler);
                }
            }
            // Handle series removal
            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
     */
    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
     */
    modifyNavigatorAxisExtremes: function () {
        var xAxis = this.xAxis, unionExtremes;
        if (xAxis.getExtremes) {
            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
     */
    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(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
     */
    updatedDataHandler: function () {
        var navigator = this.chart.navigator, baseSeries = this, navigatorSeries = this.navigatorSeries, xDataMin = navigator.getBaseSeriesMin(baseSeries.xData[0]);
        // 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);
        // Detect whether the zoomed area should stick to the minimum or
        // maximum. If the current axis minimum falls outside the new updated
        // dataset, we must adjust.
        navigator.stickToMin = isNumber(baseSeries.xAxis.min) &&
            (baseSeries.xAxis.min <= xDataMin) &&
            (!this.chart.fixedRange || !navigator.stickToMax);
        // 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
        }
    },
    /**
     * Add chart events, like redrawing navigator, when chart requires that.
     *
     * @private
     * @function Highcharts.Navigator#addChartEvents
     * @return {void}
     */
    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 ||
                navigator.scrollbar && this.xAxis[0]); // #5709
            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
     */
    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);
    }
};
if (!H.Navigator) {
    H.Navigator = Navigator;
    // For Stock charts, override selection zooming with some special features
    // because X axis zooming is already allowed by the Navigator and Range
    // selector.
    addEvent(Axis, 'zoom', function (e) {
        var chart = this.chart, chartOptions = chart.options, zoomType = chartOptions.chart.zoomType, pinchType = chartOptions.chart.pinchType, previousZoom, navigator = chartOptions.navigator, rangeSelector = chartOptions.rangeSelector;
        if (this.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')) &&
                this.options.range) {
                previousZoom = this.previousZoom;
                if (defined(e.newMin)) {
                    this.previousZoom = [this.min, this.max];
                }
                else if (previousZoom) {
                    e.newMin = previousZoom[0];
                    e.newMax = previousZoom[1];
                    delete this.previousZoom;
                }
            }
        }
        if (e.zoomed !== undefined) {
            e.preventDefault();
        }
    });
    // 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 = 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.enabled &&
                            !legendOptions.floating) ?
                            legend.legendHeight +
                                pick(legendOptions.margin, 10) :
                            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);
        }
    });
}
