text: /**
   * @module echarts/chart/helper/Line
   */
  define(function (require) {
  
      var symbolUtil = require('../../util/symbol');
      var vector = require('zrender/core/vector');
      var LinePath = require('./LinePath');
      var graphic = require('../../util/graphic');
      var zrUtil = require('zrender/core/util');
      var numberUtil = require('../../util/number');
  
      /**
       * @inner
       */
      function createSymbol(name, data, idx) {
          var color = data.getItemVisual(idx, 'color');
          var symbolType = data.getItemVisual(idx, 'symbol');
          var symbolSize = data.getItemVisual(idx, 'symbolSize');
  
          if (symbolType === 'none') {
              return;
          }
  
          if (!zrUtil.isArray(symbolSize)) {
              symbolSize = [symbolSize, symbolSize];
          }
          var symbolPath = symbolUtil.createSymbol(
              symbolType, -symbolSize[0] / 2, -symbolSize[1] / 2,
              symbolSize[0], symbolSize[1], color
          );
          symbolPath.name = name;
  
          return symbolPath;
      }
  
      function createLine(points) {
          var line = new LinePath({
              name: 'line',
              style: {
                  strokeNoScale: true
              }
          });
          setLinePoints(line.shape, points);
          return line;
      }
  
      function setLinePoints(targetShape, points) {
          var p1 = points[0];
          var p2 = points[1];
          var cp1 = points[2];
          targetShape.x1 = p1[0];
          targetShape.y1 = p1[1];
          targetShape.x2 = p2[0];
          targetShape.y2 = p2[1];
          targetShape.percent = 1;
  
          if (cp1) {
              targetShape.cpx1 = cp1[0];
              targetShape.cpy1 = cp1[1];
          }
      }
  
      function isSymbolArrow(symbol) {
          return symbol.type === 'symbol' && symbol.shape.symbolType === 'arrow';
      }
  
      function updateSymbolBeforeLineUpdate () {
          var lineGroup = this;
          var line = lineGroup.childOfName('line');
          // If line not changed
          if (!this.__dirty && !line.__dirty) {
              return;
          }
          var symbolFrom = lineGroup.childOfName('fromSymbol');
          var symbolTo = lineGroup.childOfName('toSymbol');
          var label = lineGroup.childOfName('label');
          var fromPos = line.pointAt(0);
          var toPos = line.pointAt(line.shape.percent);
  
          var d = vector.sub([], toPos, fromPos);
          vector.normalize(d, d);
  
          if (symbolFrom) {
              symbolFrom.attr('position', fromPos);
              // Rotate the arrow
              // FIXME Hard coded ?
              if (isSymbolArrow(symbolTo)) {
                  symbolTo.attr('rotation', tangentRotation(fromPos, toPos));
              }
          }
          if (symbolTo) {
              symbolTo.attr('position', toPos);
              if (isSymbolArrow(symbolFrom)) {
                  symbolFrom.attr('rotation', tangentRotation(toPos, fromPos));
              }
          }
  
          label.attr('position', toPos);
  
          var textPosition;
          var textAlign;
          var textBaseline;
          // End
          if (label.__position === 'end') {
              textPosition = [d[0] * 5 + toPos[0], d[1] * 5 + toPos[1]];
              textAlign = d[0] > 0.8 ? 'left' : (d[0] < -0.8 ? 'right' : 'center');
              textBaseline = d[1] > 0.8 ? 'top' : (d[1] < -0.8 ? 'bottom' : 'middle');
          }
          // Start
          else {
              textPosition = [-d[0] * 5 + fromPos[0], -d[1] * 5 + fromPos[1]];
              textAlign = d[0] > 0.8 ? 'right' : (d[0] < -0.8 ? 'left' : 'center');
              textBaseline = d[1] > 0.8 ? 'bottom' : (d[1] < -0.8 ? 'top' : 'middle');
          }
          label.attr({
              style: {
                  // Use the user specified text align and baseline first
                  textBaseline: label.__textBaseline || textBaseline,
                  textAlign: label.__textAlign || textAlign
              },
              position: textPosition
          });
      }
  
      function tangentRotation(p1, p2) {
          return -Math.PI / 2 - Math.atan2(
              p2[1] - p1[1], p2[0] - p1[0]
          );
      }
  
      /**
       * @constructor
       * @extends {module:zrender/graphic/Group}
       * @alias {module:echarts/chart/helper/Line}
       */
      function Line(lineData, fromData, toData, idx) {
          graphic.Group.call(this);
  
          this._createLine(lineData, fromData, toData, idx);
      }
  
      var lineProto = Line.prototype;
  
      // Update symbol position and rotation
      lineProto.beforeUpdate = updateSymbolBeforeLineUpdate;
  
      lineProto._createLine = function (lineData, fromData, toData, idx) {
          var seriesModel = lineData.hostModel;
          var linePoints = lineData.getItemLayout(idx);
  
          var line = createLine(linePoints);
          line.shape.percent = 0;
          graphic.initProps(line, {
              shape: {
                  percent: 1
              }
          }, seriesModel);
  
          this.add(line);
  
          var label = new graphic.Text({
              name: 'label'
          });
          this.add(label);
  
          if (fromData) {
              var symbolFrom = createSymbol('fromSymbol', fromData, idx);
              // symbols must added after line to make sure
              // it will be updated after line#update.
              // Or symbol position and rotation update in line#beforeUpdate will be one frame slow
              this.add(symbolFrom);
  
              this._fromSymbolType = fromData.getItemVisual(idx, 'symbol');
          }
          if (toData) {
              var symbolTo = createSymbol('toSymbol', toData, idx);
              this.add(symbolTo);
  
              this._toSymbolType = toData.getItemVisual(idx, 'symbol');
          }
  
          this._updateCommonStl(lineData, fromData, toData, idx);
      };
  
      lineProto.updateData = function (lineData, fromData, toData, idx) {
          var seriesModel = lineData.hostModel;
  
          var line = this.childOfName('line');
          var linePoints = lineData.getItemLayout(idx);
          var target = {
              shape: {}
          };
          setLinePoints(target.shape, linePoints);
          graphic.updateProps(line, target, seriesModel);
  
          // Symbol changed
          if (fromData) {
              var fromSymbolType = fromData.getItemVisual(idx, 'symbol');
              if (this._fromSymbolType !== fromSymbolType) {
                  var symbolFrom = createSymbol('fromSymbol', fromData, idx);
                  this.remove(line.childOfName('fromSymbol'));
                  this.add(symbolFrom);
              }
              this._fromSymbolType = fromSymbolType;
          }
          if (toData) {
              var toSymbolType = toData.getItemVisual(idx, 'symbol');
              // Symbol changed
              if (toSymbolType !== this._toSymbolType) {
                  var symbolTo = createSymbol('toSymbol', toData, idx);
                  this.remove(line.childOfName('toSymbol'));
                  this.add(symbolTo);
              }
              this._toSymbolType = toSymbolType;
          }
  
          this._updateCommonStl(lineData, fromData, toData, idx);
      };
  
      lineProto._updateCommonStl = function (lineData, fromData, toData, idx) {
          var seriesModel = lineData.hostModel;
  
          var line = this.childOfName('line');
          var itemModel = lineData.getItemModel(idx);
  
          var labelModel = itemModel.getModel('label.normal');
          var textStyleModel = labelModel.getModel('textStyle');
          var labelHoverModel = itemModel.getModel('label.emphasis');
          var textStyleHoverModel = labelHoverModel.getModel('textStyle');
  
          var defaultText = numberUtil.round(seriesModel.getRawValue(idx));
          if (isNaN(defaultText)) {
              // Use name
              defaultText = lineData.getName(idx);
          }
          line.setStyle(zrUtil.extend(
              {
                  stroke: lineData.getItemVisual(idx, 'color')
              },
              itemModel.getModel('lineStyle.normal').getLineStyle()
          ));
  
          var label = this.childOfName('label');
          label.setStyle({
              text: labelModel.get('show')
                  ? seriesModel.getFormattedLabel(idx, 'normal') || defaultText
                  : '',
              textFont: textStyleModel.getFont(),
              fill: textStyleModel.getTextColor() || lineData.getItemVisual(idx, 'color')
          });
          label.hoverStyle = {
              text: labelHoverModel.get('show')
                  ? seriesModel.getFormattedLabel(idx, 'emphasis') || defaultText
                  : '',
              textFont: textStyleModel.getFont(),
              fill: textStyleHoverModel.getTextColor()
          };
          label.__textAlign = textStyleModel.get('align');
          label.__textBaseline = textStyleModel.get('baseline');
          label.__position = labelModel.get('position');
  
          graphic.setHoverStyle(
              this, itemModel.getModel('lineStyle.emphasis').getLineStyle()
          );
      };
  
      lineProto.updateLayout = function (lineData, fromData, toData, idx) {
          var points = lineData.getItemLayout(idx);
          var linePath = this.childOfName('line');
          setLinePoints(linePath.shape, points);
          linePath.dirty(true);
          fromData && fromData.getItemGraphicEl(idx).attr('position', points[0]);
          toData && toData.getItemGraphicEl(idx).attr('position', points[1]);
      };
  
      zrUtil.inherits(Line, graphic.Group);
  
      return Line;
  });