text: // FIXME emphasis label position is not same with normal label position
  define(function (require) {
  
      'use strict';
  
      var textContain = require('zrender/contain/text');
  
      function adjustSingleSide(list, cx, cy, r, dir, viewWidth, viewHeight) {
          list.sort(function (a, b) {
              return a.y - b.y;
          });
  
          // 压
          function shiftDown(start, end, delta, dir) {
              for (var j = start; j < end; j++) {
                  list[j].y += delta;
                  if (j > start
                      && j + 1 < end
                      && list[j + 1].y > list[j].y + list[j].height
                  ) {
                      shiftUp(j, delta / 2);
                      return;
                  }
              }
  
              shiftUp(end - 1, delta / 2);
          }
  
          // 弹
          function shiftUp(end, delta) {
              for (var j = end; j >= 0; j--) {
                  list[j].y -= delta;
                  if (j > 0
                      && list[j].y > list[j - 1].y + list[j - 1].height
                  ) {
                      break;
                  }
              }
          }
  
          function changeX(list, isDownList, cx, cy, r, dir) {
              var lastDeltaX = dir > 0
                  ? isDownList                // 右侧
                      ? Number.MAX_VALUE      // 下
                      : 0                     // 上
                  : isDownList                // 左侧
                      ? Number.MAX_VALUE      // 下
                      : 0;                    // 上
  
              for (var i = 0, l = list.length; i < l; i++) {
                  // Not change x for center label
                  if (list[i].position === 'center') {
                      continue;
                  }
                  var deltaY = Math.abs(list[i].y - cy);
                  var length = list[i].len;
                  var length2 = list[i].len2;
                  var deltaX = (deltaY < r + length)
                      ? Math.sqrt(
                          (r + length + length2) * (r + length + length2)
                          - deltaY * deltaY
                      )
                      : Math.abs(list[i].x - cx);
                  if (isDownList && deltaX >= lastDeltaX) {
                      // 右下，左下
                      deltaX = lastDeltaX - 10;
                  }
                  if (!isDownList && deltaX <= lastDeltaX) {
                      // 右上，左上
                      deltaX = lastDeltaX + 10;
                  }
  
                  list[i].x = cx + deltaX * dir;
                  lastDeltaX = deltaX;
              }
          }
  
          var lastY = 0;
          var delta;
          var len = list.length;
          var upList = [];
          var downList = [];
          for (var i = 0; i < len; i++) {
              delta = list[i].y - lastY;
              if (delta < 0) {
                  shiftDown(i, len, -delta, dir);
              }
              lastY = list[i].y + list[i].height;
          }
          if (viewHeight - lastY < 0) {
              shiftUp(len - 1, lastY - viewHeight);
          }
          for (var i = 0; i < len; i++) {
              if (list[i].y >= cy) {
                  downList.push(list[i]);
              } else {
                  upList.push(list[i]);
              }
          }
          changeX(upList, false, cx, cy, r, dir);
          changeX(downList, true, cx, cy, r, dir);
      }
  
      function avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight) {
          var leftList = [];
          var rightList = [];
          for (var i = 0; i < labelLayoutList.length; i++) {
              if (labelLayoutList[i].x < cx) {
                  leftList.push(labelLayoutList[i]);
              } else {
                  rightList.push(labelLayoutList[i]);
              }
          }
  
          adjustSingleSide(rightList, cx, cy, r, 1, viewWidth, viewHeight);
          adjustSingleSide(leftList, cx, cy, r, -1, viewWidth, viewHeight);
  
          for (var i = 0; i < labelLayoutList.length; i++) {
              var linePoints = labelLayoutList[i].linePoints;
              if (linePoints) {
                  var dist = linePoints[1][0] - linePoints[2][0];
                  if (labelLayoutList[i].x < cx) {
                      linePoints[2][0] = labelLayoutList[i].x + 3;
                  } else {
                      linePoints[2][0] = labelLayoutList[i].x - 3;
                  }
                  linePoints[1][1] = linePoints[2][1] = labelLayoutList[i].y;
                  linePoints[1][0] = linePoints[2][0] + dist;
              }
          }
      }
  
      return function (seriesModel, r, viewWidth, viewHeight) {
          var data = seriesModel.getData();
          var labelLayoutList = [];
          var cx;
          var cy;
          var hasLabelRotate = false;
  
          data.each(function (idx) {
              var layout = data.getItemLayout(idx);
  
              var itemModel = data.getItemModel(idx);
              var labelModel = itemModel.getModel('label.normal');
              // Use position in normal or emphasis
              var labelPosition = labelModel.get('position') || itemModel.get('label.emphasis.position');
  
              var labelLineModel = itemModel.getModel('labelLine.normal');
              var labelLineLen = labelLineModel.get('length');
              var labelLineLen2 = labelLineModel.get('length2');
  
              var midAngle = (layout.startAngle + layout.endAngle) / 2;
              var dx = Math.cos(midAngle);
              var dy = Math.sin(midAngle);
  
              var textX;
              var textY;
              var linePoints;
              var textAlign;
  
              cx = layout.cx;
              cy = layout.cy;
  
              var isLabelInside = labelPosition === 'inside' || labelPosition === 'inner';
              if (labelPosition === 'center') {
                  textX = layout.cx;
                  textY = layout.cy;
                  textAlign = 'center';
              } else {
                  var x1 = (isLabelInside ? (layout.r + layout.r0) / 2 * dx : layout.r * dx) + cx;
                  var y1 = (isLabelInside ? (layout.r + layout.r0) / 2 * dy : layout.r * dy) + cy;
  
                  textX = x1 + dx * 3;
                  textY = y1 + dy * 3;
  
                  if (!isLabelInside) {
                      // For roseType
                      var x2 = x1 + dx * (labelLineLen + r - layout.r);
                      var y2 = y1 + dy * (labelLineLen + r - layout.r);
                      var x3 = x2 + ((dx < 0 ? -1 : 1) * labelLineLen2);
                      var y3 = y2;
  
                      textX = x3 + (dx < 0 ? -5 : 5);
                      textY = y3;
                      linePoints = [[x1, y1], [x2, y2], [x3, y3]];
                  }
  
                  textAlign = isLabelInside ? 'center' : (dx > 0 ? 'left' : 'right');
              }
              var font = labelModel.getModel('textStyle').getFont();
  
              var labelRotate = labelModel.get('rotate')
                  ? (dx < 0 ? -midAngle + Math.PI : -midAngle) : 0;
              var text = seriesModel.getFormattedLabel(idx, 'normal')
                  || data.getName(idx);
              var textRect = textContain.getBoundingRect(
                  text, font, textAlign, 'top'
              );
              hasLabelRotate = !!labelRotate;
              layout.label = {
                  x: textX,
                  y: textY,
                  position: labelPosition,
                  height: textRect.height,
                  len: labelLineLen,
                  len2: labelLineLen2,
                  linePoints: linePoints,
                  textAlign: textAlign,
                  verticalAlign: 'middle',
                  font: font,
                  rotation: labelRotate
              };
  
              // Not layout the inside label
              if (!isLabelInside) {
                  labelLayoutList.push(layout.label);
              }
          });
          if (!hasLabelRotate && seriesModel.get('avoidLabelOverlap')) {
              avoidOverlap(labelLayoutList, cx, cy, r, viewWidth, viewHeight);
          }
      };
  });