text: var _config = require("../../config");
  
  var __DEV__ = _config.__DEV__;
  
  var zrUtil = require("zrender/lib/core/util");
  
  var SymbolDraw = require("../helper/SymbolDraw");
  
  var SymbolClz = require("../helper/Symbol");
  
  var lineAnimationDiff = require("./lineAnimationDiff");
  
  var graphic = require("../../util/graphic");
  
  var modelUtil = require("../../util/model");
  
  var _poly = require("./poly");
  
  var Polyline = _poly.Polyline;
  var Polygon = _poly.Polygon;
  
  var ChartView = require("../../view/Chart");
  
  var _number = require("../../util/number");
  
  var round = _number.round;
  
  var _helper = require("./helper");
  
  var prepareDataCoordInfo = _helper.prepareDataCoordInfo;
  var getStackedOnPoint = _helper.getStackedOnPoint;
  
  /*
  * Licensed to the Apache Software Foundation (ASF) under one
  * or more contributor license agreements.  See the NOTICE file
  * distributed with this work for additional information
  * regarding copyright ownership.  The ASF licenses this file
  * to you under the Apache License, Version 2.0 (the
  * "License"); you may not use this file except in compliance
  * with the License.  You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing,
  * software distributed under the License is distributed on an
  * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  * KIND, either express or implied.  See the License for the
  * specific language governing permissions and limitations
  * under the License.
  */
  // FIXME step not support polar
  function isPointsSame(points1, points2) {
    if (points1.length !== points2.length) {
      return;
    }
  
    for (var i = 0; i < points1.length; i++) {
      var p1 = points1[i];
      var p2 = points2[i];
  
      if (p1[0] !== p2[0] || p1[1] !== p2[1]) {
        return;
      }
    }
  
    return true;
  }
  
  function getSmooth(smooth) {
    return typeof smooth === 'number' ? smooth : smooth ? 0.5 : 0;
  }
  
  function getAxisExtentWithGap(axis) {
    var extent = axis.getGlobalExtent();
  
    if (axis.onBand) {
      // Remove extra 1px to avoid line miter in clipped edge
      var halfBandWidth = axis.getBandWidth() / 2 - 1;
      var dir = extent[1] > extent[0] ? 1 : -1;
      extent[0] += dir * halfBandWidth;
      extent[1] -= dir * halfBandWidth;
    }
  
    return extent;
  }
  /**
   * @param {module:echarts/coord/cartesian/Cartesian2D|module:echarts/coord/polar/Polar} coordSys
   * @param {module:echarts/data/List} data
   * @param {Object} dataCoordInfo
   * @param {Array.<Array.<number>>} points
   */
  
  
  function getStackedOnPoints(coordSys, data, dataCoordInfo) {
    if (!dataCoordInfo.valueDim) {
      return [];
    }
  
    var points = [];
  
    for (var idx = 0, len = data.count(); idx < len; idx++) {
      points.push(getStackedOnPoint(dataCoordInfo, coordSys, data, idx));
    }
  
    return points;
  }
  
  function createGridClipShape(cartesian, hasAnimation, forSymbol, seriesModel) {
    var xExtent = getAxisExtentWithGap(cartesian.getAxis('x'));
    var yExtent = getAxisExtentWithGap(cartesian.getAxis('y'));
    var isHorizontal = cartesian.getBaseAxis().isHorizontal();
    var x = Math.min(xExtent[0], xExtent[1]);
    var y = Math.min(yExtent[0], yExtent[1]);
    var width = Math.max(xExtent[0], xExtent[1]) - x;
    var height = Math.max(yExtent[0], yExtent[1]) - y; // Avoid float number rounding error for symbol on the edge of axis extent.
    // See #7913 and `test/dataZoom-clip.html`.
  
    if (forSymbol) {
      x -= 0.5;
      width += 0.5;
      y -= 0.5;
      height += 0.5;
    } else {
      var lineWidth = seriesModel.get('lineStyle.width') || 2; // Expand clip shape to avoid clipping when line value exceeds axis
  
      var expandSize = seriesModel.get('clipOverflow') ? lineWidth / 2 : Math.max(width, height);
  
      if (isHorizontal) {
        y -= expandSize;
        height += expandSize * 2;
      } else {
        x -= expandSize;
        width += expandSize * 2;
      }
    }
  
    var clipPath = new graphic.Rect({
      shape: {
        x: x,
        y: y,
        width: width,
        height: height
      }
    });
  
    if (hasAnimation) {
      clipPath.shape[isHorizontal ? 'width' : 'height'] = 0;
      graphic.initProps(clipPath, {
        shape: {
          width: width,
          height: height
        }
      }, seriesModel);
    }
  
    return clipPath;
  }
  
  function createPolarClipShape(polar, hasAnimation, forSymbol, seriesModel) {
    var angleAxis = polar.getAngleAxis();
    var radiusAxis = polar.getRadiusAxis();
    var radiusExtent = radiusAxis.getExtent().slice();
    radiusExtent[0] > radiusExtent[1] && radiusExtent.reverse();
    var angleExtent = angleAxis.getExtent();
    var RADIAN = Math.PI / 180; // Avoid float number rounding error for symbol on the edge of axis extent.
  
    if (forSymbol) {
      radiusExtent[0] -= 0.5;
      radiusExtent[1] += 0.5;
    }
  
    var clipPath = new graphic.Sector({
      shape: {
        cx: round(polar.cx, 1),
        cy: round(polar.cy, 1),
        r0: round(radiusExtent[0], 1),
        r: round(radiusExtent[1], 1),
        startAngle: -angleExtent[0] * RADIAN,
        endAngle: -angleExtent[1] * RADIAN,
        clockwise: angleAxis.inverse
      }
    });
  
    if (hasAnimation) {
      clipPath.shape.endAngle = -angleExtent[0] * RADIAN;
      graphic.initProps(clipPath, {
        shape: {
          endAngle: -angleExtent[1] * RADIAN
        }
      }, seriesModel);
    }
  
    return clipPath;
  }
  
  function createClipShape(coordSys, hasAnimation, forSymbol, seriesModel) {
    return coordSys.type === 'polar' ? createPolarClipShape(coordSys, hasAnimation, forSymbol, seriesModel) : createGridClipShape(coordSys, hasAnimation, forSymbol, seriesModel);
  }
  
  function turnPointsIntoStep(points, coordSys, stepTurnAt) {
    var baseAxis = coordSys.getBaseAxis();
    var baseIndex = baseAxis.dim === 'x' || baseAxis.dim === 'radius' ? 0 : 1;
    var stepPoints = [];
  
    for (var i = 0; i < points.length - 1; i++) {
      var nextPt = points[i + 1];
      var pt = points[i];
      stepPoints.push(pt);
      var stepPt = [];
  
      switch (stepTurnAt) {
        case 'end':
          stepPt[baseIndex] = nextPt[baseIndex];
          stepPt[1 - baseIndex] = pt[1 - baseIndex]; // default is start
  
          stepPoints.push(stepPt);
          break;
  
        case 'middle':
          // default is start
          var middle = (pt[baseIndex] + nextPt[baseIndex]) / 2;
          var stepPt2 = [];
          stepPt[baseIndex] = stepPt2[baseIndex] = middle;
          stepPt[1 - baseIndex] = pt[1 - baseIndex];
          stepPt2[1 - baseIndex] = nextPt[1 - baseIndex];
          stepPoints.push(stepPt);
          stepPoints.push(stepPt2);
          break;
  
        default:
          stepPt[baseIndex] = pt[baseIndex];
          stepPt[1 - baseIndex] = nextPt[1 - baseIndex]; // default is start
  
          stepPoints.push(stepPt);
      }
    } // Last points
  
  
    points[i] && stepPoints.push(points[i]);
    return stepPoints;
  }
  
  function getVisualGradient(data, coordSys) {
    var visualMetaList = data.getVisual('visualMeta');
  
    if (!visualMetaList || !visualMetaList.length || !data.count()) {
      // When data.count() is 0, gradient range can not be calculated.
      return;
    }
  
    if (coordSys.type !== 'cartesian2d') {
      return;
    }
  
    var coordDim;
    var visualMeta;
  
    for (var i = visualMetaList.length - 1; i >= 0; i--) {
      var dimIndex = visualMetaList[i].dimension;
      var dimName = data.dimensions[dimIndex];
      var dimInfo = data.getDimensionInfo(dimName);
      coordDim = dimInfo && dimInfo.coordDim; // Can only be x or y
  
      if (coordDim === 'x' || coordDim === 'y') {
        visualMeta = visualMetaList[i];
        break;
      }
    }
  
    if (!visualMeta) {
      return;
    } // If the area to be rendered is bigger than area defined by LinearGradient,
    // the canvas spec prescribes that the color of the first stop and the last
    // stop should be used. But if two stops are added at offset 0, in effect
    // browsers use the color of the second stop to render area outside
    // LinearGradient. So we can only infinitesimally extend area defined in
    // LinearGradient to render `outerColors`.
  
  
    var axis = coordSys.getAxis(coordDim); // dataToCoor mapping may not be linear, but must be monotonic.
  
    var colorStops = zrUtil.map(visualMeta.stops, function (stop) {
      return {
        coord: axis.toGlobalCoord(axis.dataToCoord(stop.value)),
        color: stop.color
      };
    });
    var stopLen = colorStops.length;
    var outerColors = visualMeta.outerColors.slice();
  
    if (stopLen && colorStops[0].coord > colorStops[stopLen - 1].coord) {
      colorStops.reverse();
      outerColors.reverse();
    }
  
    var tinyExtent = 10; // Arbitrary value: 10px
  
    var minCoord = colorStops[0].coord - tinyExtent;
    var maxCoord = colorStops[stopLen - 1].coord + tinyExtent;
    var coordSpan = maxCoord - minCoord;
  
    if (coordSpan < 1e-3) {
      return 'transparent';
    }
  
    zrUtil.each(colorStops, function (stop) {
      stop.offset = (stop.coord - minCoord) / coordSpan;
    });
    colorStops.push({
      offset: stopLen ? colorStops[stopLen - 1].offset : 0.5,
      color: outerColors[1] || 'transparent'
    });
    colorStops.unshift({
      // notice colorStops.length have been changed.
      offset: stopLen ? colorStops[0].offset : 0.5,
      color: outerColors[0] || 'transparent'
    }); // zrUtil.each(colorStops, function (colorStop) {
    //     // Make sure each offset has rounded px to avoid not sharp edge
    //     colorStop.offset = (Math.round(colorStop.offset * (end - start) + start) - start) / (end - start);
    // });
  
    var gradient = new graphic.LinearGradient(0, 0, 0, 0, colorStops, true);
    gradient[coordDim] = minCoord;
    gradient[coordDim + '2'] = maxCoord;
    return gradient;
  }
  
  function getIsIgnoreFunc(seriesModel, data, coordSys) {
    var showAllSymbol = seriesModel.get('showAllSymbol');
    var isAuto = showAllSymbol === 'auto';
  
    if (showAllSymbol && !isAuto) {
      return;
    }
  
    var categoryAxis = coordSys.getAxesByScale('ordinal')[0];
  
    if (!categoryAxis) {
      return;
    } // Note that category label interval strategy might bring some weird effect
    // in some scenario: users may wonder why some of the symbols are not
    // displayed. So we show all symbols as possible as we can.
  
  
    if (isAuto // Simplify the logic, do not determine label overlap here.
    && canShowAllSymbolForCategory(categoryAxis, data)) {
      return;
    } // Otherwise follow the label interval strategy on category axis.
  
  
    var categoryDataDim = data.mapDimension(categoryAxis.dim);
    var labelMap = {};
    zrUtil.each(categoryAxis.getViewLabels(), function (labelItem) {
      labelMap[labelItem.tickValue] = 1;
    });
    return function (dataIndex) {
      return !labelMap.hasOwnProperty(data.get(categoryDataDim, dataIndex));
    };
  }
  
  function canShowAllSymbolForCategory(categoryAxis, data) {
    // In mose cases, line is monotonous on category axis, and the label size
    // is close with each other. So we check the symbol size and some of the
    // label size alone with the category axis to estimate whether all symbol
    // can be shown without overlap.
    var axisExtent = categoryAxis.getExtent();
    var availSize = Math.abs(axisExtent[1] - axisExtent[0]) / categoryAxis.scale.count();
    isNaN(availSize) && (availSize = 0); // 0/0 is NaN.
    // Sampling some points, max 5.
  
    var dataLen = data.count();
    var step = Math.max(1, Math.round(dataLen / 5));
  
    for (var dataIndex = 0; dataIndex < dataLen; dataIndex += step) {
      if (SymbolClz.getSymbolSize(data, dataIndex // Only for cartesian, where `isHorizontal` exists.
      )[categoryAxis.isHorizontal() ? 1 : 0] // Empirical number
      * 1.5 > availSize) {
        return false;
      }
    }
  
    return true;
  }
  
  var _default = ChartView.extend({
    type: 'line',
    init: function () {
      var lineGroup = new graphic.Group();
      var symbolDraw = new SymbolDraw();
      this.group.add(symbolDraw.group);
      this._symbolDraw = symbolDraw;
      this._lineGroup = lineGroup;
    },
    render: function (seriesModel, ecModel, api) {
      var coordSys = seriesModel.coordinateSystem;
      var group = this.group;
      var data = seriesModel.getData();
      var lineStyleModel = seriesModel.getModel('lineStyle');
      var areaStyleModel = seriesModel.getModel('areaStyle');
      var points = data.mapArray(data.getItemLayout);
      var isCoordSysPolar = coordSys.type === 'polar';
      var prevCoordSys = this._coordSys;
      var symbolDraw = this._symbolDraw;
      var polyline = this._polyline;
      var polygon = this._polygon;
      var lineGroup = this._lineGroup;
      var hasAnimation = seriesModel.get('animation');
      var isAreaChart = !areaStyleModel.isEmpty();
      var valueOrigin = areaStyleModel.get('origin');
      var dataCoordInfo = prepareDataCoordInfo(coordSys, data, valueOrigin);
      var stackedOnPoints = getStackedOnPoints(coordSys, data, dataCoordInfo);
      var showSymbol = seriesModel.get('showSymbol');
      var isIgnoreFunc = showSymbol && !isCoordSysPolar && getIsIgnoreFunc(seriesModel, data, coordSys); // Remove temporary symbols
  
      var oldData = this._data;
      oldData && oldData.eachItemGraphicEl(function (el, idx) {
        if (el.__temp) {
          group.remove(el);
          oldData.setItemGraphicEl(idx, null);
        }
      }); // Remove previous created symbols if showSymbol changed to false
  
      if (!showSymbol) {
        symbolDraw.remove();
      }
  
      group.add(lineGroup); // FIXME step not support polar
  
      var step = !isCoordSysPolar && seriesModel.get('step'); // Initialization animation or coordinate system changed
  
      if (!(polyline && prevCoordSys.type === coordSys.type && step === this._step)) {
        showSymbol && symbolDraw.updateData(data, {
          isIgnore: isIgnoreFunc,
          clipShape: createClipShape(coordSys, false, true, seriesModel)
        });
  
        if (step) {
          // TODO If stacked series is not step
          points = turnPointsIntoStep(points, coordSys, step);
          stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);
        }
  
        polyline = this._newPolyline(points, coordSys, hasAnimation);
  
        if (isAreaChart) {
          polygon = this._newPolygon(points, stackedOnPoints, coordSys, hasAnimation);
        }
  
        lineGroup.setClipPath(createClipShape(coordSys, true, false, seriesModel));
      } else {
        if (isAreaChart && !polygon) {
          // If areaStyle is added
          polygon = this._newPolygon(points, stackedOnPoints, coordSys, hasAnimation);
        } else if (polygon && !isAreaChart) {
          // If areaStyle is removed
          lineGroup.remove(polygon);
          polygon = this._polygon = null;
        } // Update clipPath
  
  
        lineGroup.setClipPath(createClipShape(coordSys, false, false, seriesModel)); // Always update, or it is wrong in the case turning on legend
        // because points are not changed
  
        showSymbol && symbolDraw.updateData(data, {
          isIgnore: isIgnoreFunc,
          clipShape: createClipShape(coordSys, false, true, seriesModel)
        }); // Stop symbol animation and sync with line points
        // FIXME performance?
  
        data.eachItemGraphicEl(function (el) {
          el.stopAnimation(true);
        }); // In the case data zoom triggerred refreshing frequently
        // Data may not change if line has a category axis. So it should animate nothing
  
        if (!isPointsSame(this._stackedOnPoints, stackedOnPoints) || !isPointsSame(this._points, points)) {
          if (hasAnimation) {
            this._updateAnimation(data, stackedOnPoints, coordSys, api, step, valueOrigin);
          } else {
            // Not do it in update with animation
            if (step) {
              // TODO If stacked series is not step
              points = turnPointsIntoStep(points, coordSys, step);
              stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);
            }
  
            polyline.setShape({
              points: points
            });
            polygon && polygon.setShape({
              points: points,
              stackedOnPoints: stackedOnPoints
            });
          }
        }
      }
  
      var visualColor = getVisualGradient(data, coordSys) || data.getVisual('color');
      polyline.useStyle(zrUtil.defaults( // Use color in lineStyle first
      lineStyleModel.getLineStyle(), {
        fill: 'none',
        stroke: visualColor,
        lineJoin: 'bevel'
      }));
      var smooth = seriesModel.get('smooth');
      smooth = getSmooth(seriesModel.get('smooth'));
      polyline.setShape({
        smooth: smooth,
        smoothMonotone: seriesModel.get('smoothMonotone'),
        connectNulls: seriesModel.get('connectNulls')
      });
  
      if (polygon) {
        var stackedOnSeries = data.getCalculationInfo('stackedOnSeries');
        var stackedOnSmooth = 0;
        polygon.useStyle(zrUtil.defaults(areaStyleModel.getAreaStyle(), {
          fill: visualColor,
          opacity: 0.7,
          lineJoin: 'bevel'
        }));
  
        if (stackedOnSeries) {
          stackedOnSmooth = getSmooth(stackedOnSeries.get('smooth'));
        }
  
        polygon.setShape({
          smooth: smooth,
          stackedOnSmooth: stackedOnSmooth,
          smoothMonotone: seriesModel.get('smoothMonotone'),
          connectNulls: seriesModel.get('connectNulls')
        });
      }
  
      this._data = data; // Save the coordinate system for transition animation when data changed
  
      this._coordSys = coordSys;
      this._stackedOnPoints = stackedOnPoints;
      this._points = points;
      this._step = step;
      this._valueOrigin = valueOrigin;
    },
    dispose: function () {},
    highlight: function (seriesModel, ecModel, api, payload) {
      var data = seriesModel.getData();
      var dataIndex = modelUtil.queryDataIndex(data, payload);
  
      if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) {
        var symbol = data.getItemGraphicEl(dataIndex);
  
        if (!symbol) {
          // Create a temporary symbol if it is not exists
          var pt = data.getItemLayout(dataIndex);
  
          if (!pt) {
            // Null data
            return;
          }
  
          symbol = new SymbolClz(data, dataIndex);
          symbol.position = pt;
          symbol.setZ(seriesModel.get('zlevel'), seriesModel.get('z'));
          symbol.ignore = isNaN(pt[0]) || isNaN(pt[1]);
          symbol.__temp = true;
          data.setItemGraphicEl(dataIndex, symbol); // Stop scale animation
  
          symbol.stopSymbolAnimation(true);
          this.group.add(symbol);
        }
  
        symbol.highlight();
      } else {
        // Highlight whole series
        ChartView.prototype.highlight.call(this, seriesModel, ecModel, api, payload);
      }
    },
    downplay: function (seriesModel, ecModel, api, payload) {
      var data = seriesModel.getData();
      var dataIndex = modelUtil.queryDataIndex(data, payload);
  
      if (dataIndex != null && dataIndex >= 0) {
        var symbol = data.getItemGraphicEl(dataIndex);
  
        if (symbol) {
          if (symbol.__temp) {
            data.setItemGraphicEl(dataIndex, null);
            this.group.remove(symbol);
          } else {
            symbol.downplay();
          }
        }
      } else {
        // FIXME
        // can not downplay completely.
        // Downplay whole series
        ChartView.prototype.downplay.call(this, seriesModel, ecModel, api, payload);
      }
    },
  
    /**
     * @param {module:zrender/container/Group} group
     * @param {Array.<Array.<number>>} points
     * @private
     */
    _newPolyline: function (points) {
      var polyline = this._polyline; // Remove previous created polyline
  
      if (polyline) {
        this._lineGroup.remove(polyline);
      }
  
      polyline = new Polyline({
        shape: {
          points: points
        },
        silent: true,
        z2: 10
      });
  
      this._lineGroup.add(polyline);
  
      this._polyline = polyline;
      return polyline;
    },
  
    /**
     * @param {module:zrender/container/Group} group
     * @param {Array.<Array.<number>>} stackedOnPoints
     * @param {Array.<Array.<number>>} points
     * @private
     */
    _newPolygon: function (points, stackedOnPoints) {
      var polygon = this._polygon; // Remove previous created polygon
  
      if (polygon) {
        this._lineGroup.remove(polygon);
      }
  
      polygon = new Polygon({
        shape: {
          points: points,
          stackedOnPoints: stackedOnPoints
        },
        silent: true
      });
  
      this._lineGroup.add(polygon);
  
      this._polygon = polygon;
      return polygon;
    },
  
    /**
     * @private
     */
    // FIXME Two value axis
    _updateAnimation: function (data, stackedOnPoints, coordSys, api, step, valueOrigin) {
      var polyline = this._polyline;
      var polygon = this._polygon;
      var seriesModel = data.hostModel;
      var diff = lineAnimationDiff(this._data, data, this._stackedOnPoints, stackedOnPoints, this._coordSys, coordSys, this._valueOrigin, valueOrigin);
      var current = diff.current;
      var stackedOnCurrent = diff.stackedOnCurrent;
      var next = diff.next;
      var stackedOnNext = diff.stackedOnNext;
  
      if (step) {
        // TODO If stacked series is not step
        current = turnPointsIntoStep(diff.current, coordSys, step);
        stackedOnCurrent = turnPointsIntoStep(diff.stackedOnCurrent, coordSys, step);
        next = turnPointsIntoStep(diff.next, coordSys, step);
        stackedOnNext = turnPointsIntoStep(diff.stackedOnNext, coordSys, step);
      } // `diff.current` is subset of `current` (which should be ensured by
      // turnPointsIntoStep), so points in `__points` can be updated when
      // points in `current` are update during animation.
  
  
      polyline.shape.__points = diff.current;
      polyline.shape.points = current;
      graphic.updateProps(polyline, {
        shape: {
          points: next
        }
      }, seriesModel);
  
      if (polygon) {
        polygon.setShape({
          points: current,
          stackedOnPoints: stackedOnCurrent
        });
        graphic.updateProps(polygon, {
          shape: {
            points: next,
            stackedOnPoints: stackedOnNext
          }
        }, seriesModel);
      }
  
      var updatedDataInfo = [];
      var diffStatus = diff.status;
  
      for (var i = 0; i < diffStatus.length; i++) {
        var cmd = diffStatus[i].cmd;
  
        if (cmd === '=') {
          var el = data.getItemGraphicEl(diffStatus[i].idx1);
  
          if (el) {
            updatedDataInfo.push({
              el: el,
              ptIdx: i // Index of points
  
            });
          }
        }
      }
  
      if (polyline.animators && polyline.animators.length) {
        polyline.animators[0].during(function () {
          for (var i = 0; i < updatedDataInfo.length; i++) {
            var el = updatedDataInfo[i].el;
            el.attr('position', polyline.shape.__points[updatedDataInfo[i].ptIdx]);
          }
        });
      }
    },
    remove: function (ecModel) {
      var group = this.group;
      var oldData = this._data;
  
      this._lineGroup.removeAll();
  
      this._symbolDraw.remove(true); // Remove temporary created elements when highlighting
  
  
      oldData && oldData.eachItemGraphicEl(function (el, idx) {
        if (el.__temp) {
          group.remove(el);
          oldData.setItemGraphicEl(idx, null);
        }
      });
      this._polyline = this._polygon = this._coordSys = this._points = this._stackedOnPoints = this._data = null;
    }
  });
  
  module.exports = _default;