text: const Base = require('../../base');
  
  class SimulateAnneal extends Base {
    getDefaultCfg() {
      return {
        // 最大移动距离
        maxMove: 5,
        // 线长的权重
        lineLength: 0.2,
        // 线碰撞权重
        intersect: 1,
        // 标签与标签碰撞权重
        labelToLabel: 30,
        // 标签与别的shape碰撞权重
        labelToShape: 30,
        // 标签偏移
        orient: 3,
        // 迭代次数
        iteration: 200,
        // 退火阈值
        threshold: 0.1
      };
    }
    // 简单的图形碰撞检测
    _intersect(x1, x2, x3, x4, y1, y2, y3, y4) {
      const denom = (y4 - y3) * (x2 - x1) - (x4 - x3) * (y2 - y1);
      const numera = (x4 - x3) * (y1 - y3) - (y4 - y3) * (x1 - x3);
      const numerb = (x2 - x1) * (y1 - y3) - (y2 - y1) * (x1 - x3);
      const mua = numera / denom;
      const mub = numerb / denom;
      if (!(mua < 0 || mua > 1 || mub < 0 || mub > 1)) {
        return true;
      }
      return false;
    }
    // 计算重排一个label后的能量值
    energy(index) {
      const self = this;
      const labels = self.get('labels');
      const shapes = self.get('shapes');
      const labelLen = labels.length;
      const label = labels[index];
      const shape = shapes[index];
      const bbox = label.getBBox();
      let energy = 0;
      let dx = labels[index].attr('x') - shapes[index].attr('x');
      let dy = labels[index].attr('y') - shapes[index].attr('y');
      const dist = Math.sqrt(dx * dx + dy * dy);
  
      if (dist > 0) {
        energy += dist * self.get('lineLength');
      }
  
      dx /= dist;
      dy /= dist;
      if (dx > 0 && dy > 0) {
        energy += 0;
      } else if (dx < 0 && dy > 0) {
        energy += self.get('orient');
      } else if (dx < 0 && dy < 0) {
        energy += self.get('orient') * 2;
      } else {
        energy += self.get('orient') * 3;
      }
      let overlap,
        overlapX,
        overlapY,
        temp,
        tempBBox,
        tempShape,
        tempShapeBBox;
      for (let i = 0; i < labelLen; i++) {
        temp = labels[i];
        tempBBox = temp.getBBox();
        tempShape = shapes[i];
        tempShapeBBox = tempShape.getBBox();
        // 标签与标签碰撞增加能量
        if (i !== index) {
          overlap = self._intersect(shape.attr('x'), label.attr('x'), tempShape.attr('x'), temp.attr('x'),
            shape.attr('y'), label.attr('y'), tempShape.attr('y'), temp.attr('y'));
          if (overlap) {
            energy += self.get('intersect');
          }
          overlapX = Math.max(0, Math.min(tempBBox.maxX, bbox.maxX) - Math.max(tempBBox.minX, bbox.minX));
          overlapY = Math.max(0, Math.min(tempBBox.maxY, bbox.maxY) - Math.max(tempBBox.minY, bbox.minY));
          energy += overlapX * overlapY * self.get('labelToLabel');
        }
        // 标签与shape碰撞能量
        overlapX = Math.max(0, Math.min(tempShapeBBox.maxX, bbox.maxX) - Math.max(tempShapeBBox.minX, bbox.minX));
        overlapY = Math.max(0, Math.min(tempShapeBBox.maxY, bbox.maxY) - Math.max(tempShapeBBox.minY, bbox.minY));
        energy += overlapX * overlapY * self.get('labelToShape');
      }
      return energy;
    }
    // 蒙特卡罗模拟位移
    move(current) {
      const self = this;
      const labels = this.get('labels');
      const random = Math.floor(Math.random() * labels.length);
      const label = labels[random];
  
      const origin = {
        x: labels[random].attr('x'),
        y: labels[random].attr('y')
      };
      const originEnergy = self.energy(random);
      let moveX = label.attr('x') + (Math.random() - 0.5) * self.get('maxMove');
      if (moveX < 0) {
        moveX = origin.x;
      }
      let moveY = label.attr('y') + (Math.random() - 0.5) * self.get('maxMove');
      if (moveY < 0) {
        moveY = origin.y;
      }
      label.attr({ x: moveX, y: moveY });
      const newEnergy = self.energy(random);
      const d = newEnergy - originEnergy;
      if (Math.random() > Math.exp(-d / current)) {
        label.attr({ x: origin.x, y: origin.y });
      }
    }
    cooling(current, init, iterate) {
      return (current - (init / iterate));
    }
    adjust(labels, shapes) {
      const self = this;
      const iteration = self.get('iteration');
      self.set('labels', labels);
      self.set('shapes', shapes);
      const initTime = 1;
      let currentTime = 1;
      for (let i = 0; i < iteration; i++) {
        for (let j = 0; j < labels.length; j++) {
          self.move(currentTime);
        }
        currentTime = self.cooling(currentTime, initTime, iteration);
        if (currentTime < self.get('threshold')) {
          break;
        }
      }
    }
  }
  
  module.exports = SimulateAnneal;
  