/**
 * @license  Highcharts JS v7.0.3 (2019-02-06)
 * Timeline series
 *
 * (c) 2010-2019 Highsoft AS
 * Author: Daniel Studencki
 *
 * License: www.highcharts.com/license
 */
'use strict';
(function (factory) {
    if (typeof module === 'object' && module.exports) {
        factory['default'] = factory;
        module.exports = factory;
    } else if (typeof define === 'function' && define.amd) {
        define(function () {
            return factory;
        });
    } else {
        factory(typeof Highcharts !== 'undefined' ? Highcharts : undefined);
    }
}(function (Highcharts) {
    (function (H) {
        /* *
         *
         *  Experimental Timeline Series.
         *  Note: This API is in alpha stage and will be changed before final release.
         *
         *  (c) 2010-2019 Highsoft AS
         *
         *  Author: Daniel Studencki
         *
         *  License: www.highcharts.com/license
         *
         * */



        var addEvent = H.addEvent,
            extend = H.extend,
            defined = H.defined,
            LegendSymbolMixin = H.LegendSymbolMixin,
            TrackerMixin = H.TrackerMixin,
            merge = H.merge,
            pick = H.pick,
            Point = H.Point,
            Series = H.Series,
            undocumentedSeriesType = H.seriesType;

        /* *
         * The timeline series type.
         *
         * @private
         * @class
         * @name Highcharts.seriesTypes.timeline
         *
         * @augments Highcharts.Series
         */
        undocumentedSeriesType('timeline', 'line',

            /* *
             * The timeline series presents given events along a drawn line.
             *
             * @sample highcharts/series-timeline/alternate-labels Timeline series
             *
             * @extends      plotOptions.line
             * @since        7.0.0
             * @product      highcharts
             * @excluding    animationLimit, boostThreshold, connectEnds, connectNulls,
             *               cropThreshold, dashStyle, findNearestPointBy,
             *               getExtremesFromAll, lineWidth, negativeColor,
             *               pointInterval, pointIntervalUnit, pointPlacement,
             *               pointStart, softThreshold, stacking, step, threshold,
             *               turboThreshold, zoneAxis, zones
             * @optionparent plotOptions.timeline
             */
            {
                colorByPoint: true,
                stickyTracking: false,
                ignoreHiddenPoint: true,
                legendType: 'point',
                lineWidth: 0,
                tooltip: {
                    headerFormat: '<span style="color:{point.color}">● </span>' +
                    '<span style="font-weight: bold;">{point.point.date}</span><br/>',
                    pointFormat: '{point.description}'
                },
                states: {
                    hover: {
                        lineWidthPlus: 5,
                        halo: {
                            size: 0
                        }
                    }
                },
                dataLabels: {
                    enabled: true,
                    allowOverlap: true,
                    /* *
                     * The width of the line connecting the data label to the point.
                     *
                     *
                     * In styled mode, the connector stroke width is given in the
                     * `.highcharts-data-label-connector` class.
                     *
                     * @type {Number}
                     * @default 1
                     * @sample {highcharts} highcharts/series-timeline/connector-styles
                     *         Custom connector width and color
                     */
                    connectorWidth: 1,
                    /* *
                     * The color of the line connecting the data label to the point.
                     *
                     * In styled mode, the connector stroke is given in the
                     * `.highcharts-data-label-connector` class.
                     *
                     * @type {String}
                     * @sample {highcharts} highcharts/series-timeline/connector-styles
                     *         Custom connector width and color
                     */
                    connectorColor: '#000000',
                    backgroundColor: '#ffffff',
                    /* *
                     * @type      {Highcharts.FormatterCallbackFunction<object>}
                     * @default function () {
                     *   var format;
                     *
                     *   if (!this.series.chart.styledMode) {
                     *       format = '<span style="color:' + this.point.color +
                     *           '">● </span><span style="font-weight: bold;" > ' +
                     *           (this.point.date || '') + '</span><br/>' +
                     *           (this.point.label || '');
                     *   } else {
                     *       format = '<span>● </span>' +
                     *           '<span>' + (this.point.date || '') +
                     *           '</span><br/>' + (this.point.label || '');
                     *   }
                     *   return format;
                     * }
                     * @apioption plotOptions.timeline.dataLabels.formatter
                     */
                    formatter: function () {
                        var format;

                        if (!this.series.chart.styledMode) {
                            format = '<span style="color:' + this.point.color +
                            '">● </span><span style="font-weight: bold;" > ' +
                            (this.point.date || '') + '</span><br/>' +
                            (this.point.label || '');
                        } else {
                            format = '<span>● </span>' +
                            '<span>' + (this.point.date || '') +
                            '</span><br/>' + (this.point.label || '');
                        }
                        return format;
                    },
                    borderWidth: 1,
                    borderColor: '#666666',
                    /* *
                     * A pixel value defining the distance between the data label
                     * and the point. Negative numbers puts the label on top
                     * of the point.
                     *
                     * @type {Number}
                     * @default 100
                     */
                    distance: 100,
                    /* *
                     * Whether to position data labels alternately. For example, if
                     * [distance](#plotOptions.timeline.dataLabels.distance) is set
                     * equal to `100`, then the first data label 's distance will be
                     * set equal to `100`, the second one equal to `-100`, and so on.
                     *
                     * @type {Boolean}
                     * @default true
                     * @sample {highcharts} highcharts/series-timeline/alternate-disabled
                     *         Alternate disabled
                     */
                    alternate: true,
                    verticalAlign: 'middle',
                    color: '#333333'
                },
                marker: {
                    enabledThreshold: 0,
                    symbol: 'square',
                    height: 15
                }
            },
            /* *
             * @lends Highcharts.Series#
             */
            {
                requireSorting: false,
                trackerGroups: ['markerGroup', 'dataLabelsGroup'],
                // Use a simple symbol from LegendSymbolMixin
                drawLegendSymbol: LegendSymbolMixin.drawRectangle,
                // Use a group of trackers from TrackerMixin
                drawTracker: TrackerMixin.drawTrackerPoint,
                init: function () {
                    var series = this;

                    Series.prototype.init.apply(series, arguments);

                    // Distribute data labels before rendering them. Distribution is
                    // based on the 'dataLabels.distance' and 'dataLabels.alternate'
                    // property.
                    addEvent(series, 'drawDataLabels', function () {

                        // Delete the oldTextWidth parameter, in order to force
                        // adjusting data label wrapper box width. It's needed only when
                        // useHTML is enabled. This prevents the data label text getting
                        // out of the box range.
                        if (series.options.dataLabels.useHTML) {
                            series.points.forEach(function (p) {
                                if (p.visible && p.dataLabel) {
                                    delete p.dataLabel.text.oldTextWidth;
                                }
                            });
                        }

                        // Distribute data labels basing on defined algorithm.
                        series.distributeDL();
                    });

                    addEvent(series, 'afterDrawDataLabels', function () {
                        var seriesOptions = series.options,
                            options = seriesOptions.dataLabels,
                            hasRendered = series.hasRendered || 0,
                            defer = pick(options.defer, !!seriesOptions.animation),
                            connectorsGroup = series.connectorsGroup,
                            dataLabel;

                        // Create (or redraw) the group for all connectors.
                        connectorsGroup = series.plotGroup(
                            'connectorsGroup',
                            'data-labels-connectors',
                            defer && !hasRendered ? 'hidden' : 'visible',
                            options.zIndex || 5
                        );

                        // Draw or align connector for each point.
                        series.points.forEach(function (point) {
                            dataLabel = point.dataLabel;

                            if (dataLabel) {
                                // Within this wrap method is necessary to save the
                                // current animation params, because the data label
                                // target position (after animation) is needed to align
                                // connectors.
                                dataLabel.animate = function (params) {
                                    if (this.targetPosition) {
                                        this.targetPosition = params;
                                    }
                                    return H.SVGElement.prototype.animate.apply(
                                        this,
                                        arguments
                                    );
                                };

                                // Initialize the targetPosition field within data label
                                // object. It's necessary because there is need to know
                                // expected position of specific data label, when
                                // aligning connectors. This field is overrided inside
                                // of SVGElement.animate() wrapped  method.
                                if (!dataLabel.targetPosition) {
                                    dataLabel.targetPosition = {};
                                }

                                return !point.connector ?
                                    point.drawConnector() :
                                    point.alignConnector();
                            }
                        });
                        // Animate connectors group. It's animated in the same way like
                        // dataLabels, and also depends on dataLabels.defer parameter.
                        if (defer) {
                            connectorsGroup.attr({
                                opacity: +hasRendered
                            });
                            if (!hasRendered) {
                                addEvent(series, 'afterAnimate', function () {
                                    if (series.visible) {
                                        connectorsGroup.show(true);
                                    }
                                    connectorsGroup[
                                        seriesOptions.animation ? 'animate' : 'attr'
                                    ]({
                                        opacity: 1
                                    }, {
                                        duration: 200
                                    });
                                });
                            }
                        }
                    });
                },
                alignDataLabel: function (point, dataLabel) {
                    var series = this,
                        isInverted = series.chart.inverted,
                        visiblePoints = series.visibilityMap.filter(function (point) {
                            return point;
                        }),
                        visiblePointsCount = series.visiblePointsCount,
                        pointIndex = visiblePoints.indexOf(point),
                        isFirstOrLast = !pointIndex ||
                        pointIndex === visiblePointsCount - 1,
                        dataLabelsOptions = series.options.dataLabels,
                        userDLOptions = point.userDLOptions || {},
                        // Define multiplier which is used to calculate data label
                        // width. If data labels are alternate, they have two times more
                        // space to adapt (excepting first and last ones, which has only
                        // one and half), than in case of placing all data labels side
                        // by side.
                        multiplier = dataLabelsOptions.alternate ?
                            (isFirstOrLast ? 1.5 : 2) :
                            1,
                        distance,
                        availableSpace = Math.floor(
                            series.xAxis.len / visiblePointsCount
                        ),
                        pad = dataLabel.padding,
                        targetDLWidth,
                        styles;

                    // Adjust data label width to the currently available space.
                    if (point.visible) {
                        distance = Math.abs(
                            userDLOptions.x || point.options.dataLabels.x
                        );
                        if (isInverted) {
                            targetDLWidth = (
                                (distance - pad) * 2 - (point.itemHeight / 2)
                            );
                            styles = {
                                width: targetDLWidth,
                                // Apply ellipsis when data label height is exceeded.
                                textOverflow: dataLabel.width / targetDLWidth *
                                dataLabel.height / 2 > availableSpace * multiplier ?
                                    'ellipsis' : 'none'
                            };
                        } else {
                            styles = {
                                width: userDLOptions.width ||
                                dataLabelsOptions.width ||
                                availableSpace * multiplier - (pad * 2)
                            };
                        }
                        dataLabel.css(styles);

                        if (!series.chart.styledMode) {
                            dataLabel.shadow({});
                        }
                    }
                    Series.prototype.alignDataLabel.apply(series, arguments);
                },
                processData: function () {
                    var series = this,
                        xMap = [],
                        base,
                        visiblePoints = 0,
                        i;

                    series.visibilityMap = series.getVisibilityMap();

                    // Calculate currently visible points.
                    series.visibilityMap.forEach(function (point) {
                        if (point) {
                            visiblePoints++;
                        }
                    });

                    series.visiblePointsCount = visiblePoints;
                    base = series.xAxis.options.max / visiblePoints;

                    // Generate xData map.
                    for (i = 1; i <= visiblePoints; i++) {
                        xMap.push(
                            (base * i) - (base / 2)
                        );
                    }

                    // Set all hidden points y values as negatives, in order to move
                    // them away from plot area. It is necessary to avoid hiding data
                    // labels, when dataLabels.allowOverlap is set to false.
                    series.visibilityMap.forEach(function (vis, i) {
                        if (!vis) {
                            xMap.splice(i, 0, series.yData[i] === null ? null : -99);
                        }
                    });

                    series.xData = xMap;
                    series.yData = xMap.map(function (data) {
                        return defined(data) ? 1 : null;
                    });

                    Series.prototype.processData.call(this, arguments);
                },
                generatePoints: function () {
                    var series = this;

                    Series.prototype.generatePoints.apply(series);
                    series.points.forEach(function (point, i) {
                        point.applyOptions({
                            x: series.xData[i]
                        });
                    });
                },
                getVisibilityMap: function () {
                    var series = this,
                        map = (series.data.length ?
                            series.data : series.userOptions.data
                        ).map(function (point) {
                            return (
                                point &&
                                point.visible !== false &&
                                !point.isNull
                            ) ? point : false;
                        });

                    return map;
                },
                distributeDL: function () {
                    var series = this,
                        dataLabelsOptions = series.options.dataLabels,
                        options,
                        pointDLOptions,
                        newOptions = {},
                        visibilityIndex = 1,
                        distance = dataLabelsOptions.distance;

                    series.points.forEach(function (point) {
                        if (point.visible && !point.isNull) {
                            options = point.options;
                            pointDLOptions = point.options.dataLabels;

                            if (!series.hasRendered) {
                                point.userDLOptions = merge({}, pointDLOptions);
                            }

                            newOptions[series.chart.inverted ? 'x' : 'y'] =
                            dataLabelsOptions.alternate && visibilityIndex % 2 ?
                                -distance : distance;

                            options.dataLabels = merge(newOptions, point.userDLOptions);
                            visibilityIndex++;
                        }
                    });
                },
                markerAttribs: function (point, state) {
                    var series = this,
                        seriesMarkerOptions = series.options.marker,
                        seriesStateOptions,
                        pointMarkerOptions = point.marker || {},
                        symbol = (
                            pointMarkerOptions.symbol || seriesMarkerOptions.symbol
                        ),
                        pointStateOptions,
                        width = pick(
                            pointMarkerOptions.width,
                            seriesMarkerOptions.width,
                            series.xAxis.len / series.visiblePointsCount
                        ),
                        height = pick(
                            pointMarkerOptions.height,
                            seriesMarkerOptions.height
                        ),
                        radius = 0,
                        attribs;

                    // Handle hover and select states
                    if (state) {
                        seriesStateOptions = seriesMarkerOptions.states[state] || {};
                        pointStateOptions = pointMarkerOptions.states &&
                        pointMarkerOptions.states[state] || {};

                        radius = pick(
                            pointStateOptions.radius,
                            seriesStateOptions.radius,
                            radius + (
                                seriesStateOptions.radiusPlus ||
                            0
                            )
                        );
                    }

                    point.hasImage = symbol && symbol.indexOf('url') === 0;

                    attribs = {
                        x: Math.floor(point.plotX) - (width / 2) - (radius / 2),
                        y: point.plotY - (height / 2) - (radius / 2),
                        width: width + radius,
                        height: height + radius
                    };

                    return attribs;

                },
                bindAxes: function () {
                    var series = this,
                        timelineXAxis = {
                            gridLineWidth: 0,
                            lineWidth: 0,
                            min: 0,
                            dataMin: 0,
                            minPadding: 0,
                            max: 100,
                            dataMax: 100,
                            maxPadding: 0,
                            title: null,
                            tickPositions: []
                        },
                        timelineYAxis = {
                            gridLineWidth: 0,
                            min: 0.5,
                            dataMin: 0.5,
                            minPadding: 0,
                            max: 1.5,
                            dataMax: 1.5,
                            maxPadding: 0,
                            title: null,
                            labels: {
                                enabled: false
                            }
                        };

                    Series.prototype.bindAxes.call(series);
                    extend(series.xAxis.options, timelineXAxis);
                    extend(series.yAxis.options, timelineYAxis);
                }
            },
            /* *
             * @lends Highcharts.Point#
             */
            {
                init: function () {
                    var point = Point.prototype.init.apply(this, arguments);

                    point.name = pick(point.name, point.date, 'Event');
                    point.y = 1;

                    return point;
                },
                // The setVisible method is taken from Pie series prototype, in order to
                // prevent importing whole Pie series.
                setVisible: function (vis, redraw) {
                    var point = this,
                        series = point.series,
                        chart = series.chart,
                        ignoreHiddenPoint = series.options.ignoreHiddenPoint;

                    redraw = pick(redraw, ignoreHiddenPoint);

                    if (vis !== point.visible) {

                        // If called without an argument, toggle visibility
                        point.visible = point.options.visible = vis =
                        vis === undefined ? !point.visible : vis;
                        // update userOptions.data
                        series.options.data[series.data.indexOf(point)] = point.options;

                        // Show and hide associated elements. This is performed
                        // regardless of redraw or not, because chart.redraw only
                        // handles full series.
                        ['graphic', 'dataLabel', 'connector'].forEach(
                            function (key) {
                                if (point[key]) {
                                    point[key][vis ? 'show' : 'hide'](true);
                                }
                            }
                        );

                        if (point.legendItem) {
                            chart.legend.colorizeItem(point, vis);
                        }

                        // #4170, hide halo after hiding point
                        if (!vis && point.state === 'hover') {
                            point.setState('');
                        }

                        // Handle ignore hidden slices
                        if (ignoreHiddenPoint) {
                            series.isDirty = true;
                        }

                        if (redraw) {
                            chart.redraw();
                        }
                    }
                },
                setState: function () {
                    var proceed = Series.prototype.pointClass.prototype.setState;

                    // Prevent triggering the setState method on null points.
                    if (!this.isNull) {
                        proceed.apply(this, arguments);
                    }
                },
                getConnectorPath: function () {
                    var point = this,
                        chart = point.series.chart,
                        xAxisLen = point.series.xAxis.len,
                        inverted = chart.inverted,
                        direction = inverted ? 'x2' : 'y2',
                        dl = point.dataLabel,
                        targetDLPos = dl.targetPosition,
                        coords = {
                            x1: point.plotX,
                            y1: point.plotY,
                            x2: point.plotX,
                            y2: targetDLPos.y || dl.y
                        },
                        negativeDistance = (
                            coords[direction] < point.series.yAxis.len / 2
                        ),
                        path;

                    // Recalculate coords when the chart is inverted.
                    if (inverted) {
                        coords = {
                            x1: point.plotY,
                            y1: xAxisLen - point.plotX,
                            x2: targetDLPos.x || dl.x,
                            y2: xAxisLen - point.plotX
                        };
                    }

                    // Subtract data label width or height from expected coordinate so
                    // that the connector would start from the appropriate edge.
                    if (negativeDistance) {
                        coords[direction] += dl[inverted ? 'width' : 'height'];
                    }

                    path = chart.renderer.crispLine([
                        'M',
                        coords.x1,
                        coords.y1,
                        'L',
                        coords.x2,
                        coords.y2
                    ], dl.options.connectorWidth || 1);

                    return path;
                },
                drawConnector: function () {
                    var point = this,
                        series = point.series,
                        dlOptions = point.dataLabel.options = merge(
                            {}, series.options.dataLabels,
                            point.options.dataLabels
                        );

                    point.connector = series.chart.renderer
                        .path(point.getConnectorPath())
                        .add(series.connectorsGroup);

                    if (!series.chart.styledMode) {
                        point.connector.attr({
                            stroke: dlOptions.connectorColor,
                            'stroke-width': dlOptions.connectorWidth,
                            opacity: point.dataLabel.opacity
                        });
                    }
                },
                alignConnector: function () {
                    var point = this,
                        connector = point.connector,
                        bBox = connector.getBBox(),
                        isVisible = bBox.y > 0;

                    connector[isVisible ? 'animate' : 'attr']({
                        d: point.getConnectorPath()
                    });
                }
            });

        // Hide/show connector related with a specific data label, after overlapping
        // detected.
        addEvent(H.Chart, 'afterHideOverlappingLabels', function () {
            var series = this.series,
                dataLabel,
                connector;

            series.forEach(function (series) {
                if (series.points) {
                    series.points.forEach(function (point) {
                        dataLabel = point.dataLabel;
                        connector = point.connector;

                        if (
                            dataLabel &&
                            dataLabel.targetPosition &&
                            connector
                        ) {
                            connector.attr({
                                opacity: dataLabel.targetPosition.opacity ||
                                    dataLabel.newOpacity
                            });
                        }
                    });
                }
            });
        });

        /* *
         * The `timeline` series. If the [type](#series.timeline.type) option is
         * not specified, it is inherited from [chart.type](#chart.type).
         *
         * @extends   series,plotOptions.timeline
         * @excluding animationLimit, boostThreshold, connectEnds, connectNulls,
         *            cropThreshold, dashStyle, dataParser, dataURL, findNearestPointBy,
         *            getExtremesFromAll, lineWidth, negativeColor,
         *            pointInterval, pointIntervalUnit, pointPlacement, pointStart,
         *            softThreshold, stacking, stack, step, threshold, turboThreshold,
         *            zoneAxis, zones
         * @product   highcharts
         * @apioption series.timeline
         */

        /* *
         * An array of data points for the series. For the `timeline` series type,
         * points can be given with three general parameters, `date`, `label`,
         * and `description`:
         *
         * Example:
         *
         * ```js
         * series: [{
         *    type: 'timeline',
         *    data: [{
         *        date: 'Jan 2018',
         *        label: 'Some event label',
         *        description: 'Description to show in tooltip'
         *    }]
         * }]
         * ```
         *
         * @sample {highcharts} highcharts/series-timeline/alternate-labels
         *         Alternate labels
         *
         * @type      {Array<number|*>}
         * @extends   series.line.data
         * @excluding marker, x, y
         * @product   highcharts
         * @apioption series.timeline.data
         */

        /* *
         * The date of event.
         *
         * @type      {string}
         * @product   highcharts
         * @apioption series.timeline.data.date
         */

        /* *
         * The label of event.
         *
         * @type      {string}
         * @product   highcharts
         * @apioption series.timeline.data.label
         */

        /* *
         * The description of event. This description will be shown in tooltip.
         *
         * @type      {string}
         * @product   highcharts
         * @apioption series.timeline.data.description
         */

    }(Highcharts));
    return (function () {


    }());
}));
