Edit in JSFiddle

const TAU = Math.PI * 2

const mapToEllipse = ({ x, y }, rx, ry, cosphi, sinphi, centerx, centery) => {
  x *= rx
  y *= ry

  const xp = cosphi * x - sinphi * y
  const yp = sinphi * x + cosphi * y

  return {
    x: xp + centerx,
    y: yp + centery
  }
}

const approxUnitArc = (ang1, ang2) => {
  const a = 4 / 3 * Math.tan(ang2 / 4)

  const x1 = Math.cos(ang1)
  const y1 = Math.sin(ang1)
  const x2 = Math.cos(ang1 + ang2)
  const y2 = Math.sin(ang1 + ang2)

  return [
    {
      x: x1 - y1 * a,
      y: y1 + x1 * a
    },
    {
      x: x2 + y2 * a,
      y: y2 - x2 * a
    },
    {
      x: x2,
      y: y2
    }
  ]
}

const vectorAngle = (ux, uy, vx, vy) => {
  const sign = (ux * vy - uy * vx < 0) ? -1 : 1
  const umag = Math.sqrt(ux * ux + uy * uy)
  const vmag = Math.sqrt(ux * ux + uy * uy)
  const dot = ux * vx + uy * vy

  let div = dot / (umag * vmag)

  if (div > 1) {
    div = 1
  }

  if (div < -1) {
    div = -1
  }

  return sign * Math.acos(div)
}

const getArcCenter = (
  px,
  py,
  cx,
  cy,
  rx,
  ry,
  largeArcFlag,
  sweepFlag,
  sinphi,
  cosphi,
  pxp,
  pyp
) => {
  const rxsq = Math.pow(rx, 2)
  const rysq = Math.pow(ry, 2)
  const pxpsq = Math.pow(pxp, 2)
  const pypsq = Math.pow(pyp, 2)

  let radicant = (rxsq * rysq) - (rxsq * pypsq) - (rysq * pxpsq)

  if (radicant < 0) {
    radicant = 0
  }

  radicant /= (rxsq * pypsq) + (rysq * pxpsq)
  radicant = Math.sqrt(radicant) * (largeArcFlag === sweepFlag ? -1 : 1)

  const centerxp = radicant * rx / ry * pyp
  const centeryp = radicant * -ry / rx * pxp

  const centerx = cosphi * centerxp - sinphi * centeryp + (px + cx) / 2
  const centery = sinphi * centerxp + cosphi * centeryp + (py + cy) / 2

  const vx1 = (pxp - centerxp) / rx
  const vy1 = (pyp - centeryp) / ry
  const vx2 = (-pxp - centerxp) / rx
  const vy2 = (-pyp - centeryp) / ry

  let ang1 = vectorAngle(1, 0, vx1, vy1)
  let ang2 = vectorAngle(vx1, vy1, vx2, vy2)

  if (sweepFlag === 0 && ang2 > 0) {
    ang2 -= TAU
  }

  if (sweepFlag === 1 && ang2 < 0) {
    ang2 += TAU
  }

  return [ centerx, centery, ang1, ang2 ]
}

const arcToBezier = ({
  px,
  py,
  cx,
  cy,
  rx,
  ry,
  xAxisRotation = 0,
  largeArcFlag = 0,
  sweepFlag = 0
}) => {
  const curves = []

  if (rx === 0 || ry === 0) {
    return []
  }

  const sinphi = Math.sin(xAxisRotation * TAU / 360)
  const cosphi = Math.cos(xAxisRotation * TAU / 360)

  const pxp = cosphi * (px - cx) / 2 + sinphi * (py - cy) / 2
  const pyp = -sinphi * (px - cx) / 2 + cosphi * (py - cy) / 2

  if (pxp === 0 && pyp === 0) {
    return []
  }

  rx = Math.abs(rx)
  ry = Math.abs(ry)

  const lambda =
    Math.pow(pxp, 2) / Math.pow(rx, 2) +
    Math.pow(pyp, 2) / Math.pow(ry, 2)

  if (lambda > 1) {
    rx *= Math.sqrt(lambda)
    ry *= Math.sqrt(lambda)
  }

  let [ centerx, centery, ang1, ang2 ] = getArcCenter(
    px,
    py,
    cx,
    cy,
    rx,
    ry,
    largeArcFlag,
    sweepFlag,
    sinphi,
    cosphi,
    pxp,
    pyp
  )

  const segments = Math.max(Math.ceil(Math.abs(ang2) / (TAU / 4)), 1)

  ang2 /= segments

  for (let i = 0; i < segments; i++) {
    curves.push(approxUnitArc(ang1, ang2))
    ang1 += ang2
  }

  return curves.map(curve => {
    const { x: x1, y: y1 } = mapToEllipse(curve[ 0 ], rx, ry, cosphi, sinphi, centerx, centery)
    const { x: x2, y: y2 } = mapToEllipse(curve[ 1 ], rx, ry, cosphi, sinphi, centerx, centery)
    const { x, y } = mapToEllipse(curve[ 2 ], rx, ry, cosphi, sinphi, centerx, centery)

    return { x1, y1, x2, y2, x, y }
  })
}

//Aliases
let Container = PIXI.Container,
    Application = PIXI.Application,
    Sprite = PIXI.Sprite,
    Graphics = PIXI.Graphics;

let renderer = new Application(window.innerWidth, window.innerHeight, {
    antialias: true,// antialias: true,//这抗锯齿一开整个世界都变了  =>bug(just Chrome,FF is ok): renderer = new PIXI.WebGLRenderer + renderer.render(stage);
    forceFXAA: true,//For WebglRender AA
    backgroundColor: 0x1099bb
}),//Todo=> parameter
    stage = renderer.stage,
    edgeContainer = new Container(),
    arrowContainer = new Container(),
    nodeContainer = new Container(),//Node above edge
    dragContainer = new Container();//Hightest level

//Canvas(defalut is Webgl) Use for Render test
// renderer.renderer = new PIXI.CanvasRenderer(window.innerWidth, window.innerHeight, {
//     backgroundColor: 0x1099bb
// })

document.body.appendChild(renderer.view);

const SCALE_MAX = 24, SCALE_MIN = 0.1;//For scale limmit
let nodeWidth = 30;//defalut node radius
let point = {};//Todo 这里以后指针的形状也可以自定义
let movePosBegin = {};

let nodeList = {},//Save node
    edgeList = {},//Cache edge
    arrowList = {},//Save arrow
    edgeInfoList = {};//Save edge info

let circleList = {};
let bezierList = {};//Deal with 2 bezierCurve
let midPos;//Now is just for Bezier,TODO is for all(include straight use midPos to Calculate)

function PiCi(opts) {
    opts = Object.assign({}, opts);
    //Extrac nodes/edges information from opts
    let elements = opts.elements;
    let nodes = [];
    let edges = [];
    if (!elements) elements = [];
    if (elements.length > 0) {//init Node
        for (let i = 0, l = elements.length; i < l; i++) {
            let data = elements[i].data,
                id = data.id;

            //Simple check
            if (data == null) data = {};
            if (id == null) {//Id is neccesary
                //Check whether id is already exists
                if ((nodeList[id] != undefined) || edgeList[id] != undefined) {
                    console.error("id已存在");
                    break;
                } else {
                    console.error("id是必须参数");
                    break;
                }
            }

            if (!data.source && !data.target) {//Node
                nodes.push(data);
            }else{
                edges.push(data);
                //Save this edge's info
                edgeInfoList[data.id] = data;
            }
        }
    }

    initializeNodes(nodes,edges);
    
    //层级顺序
    stage.addChild(edgeContainer);
    stage.addChild(arrowContainer);
    stage.addChild(nodeContainer);
    stage.addChild(dragContainer);
}

function drawArrowAndEdge(data, source, target) {
    //Remove old edge (drawArrowShape会擦除旧arrow)
    if(edgeList[data.id])edgeContainer.removeChild(edgeList[data.id]);
    //Draw Arrow
    let newSourcePos, newTargetPos;
    if (data.targetShape) {
        switch (data.curveStyle) {
            case "bezier":
                //三阶贝塞尔曲线
                let bMidPos = CacBezierCurveMidPos(source, target, 100);
                if(bezierList[source+'+'+target]){
                    bezierList[source+'+'+target] = 1;//'source+target'
                }else{
                    bezierList[source+'+'+target]++;
                }
                let pos2 = { x: bMidPos.x2, y: bMidPos.y2 }
                //drawCircle(pos2.x, pos2.y, 5);
                newTargetPos = drawArrowShape(data.id, data.targetShape, pos2, target, source, target, true);
                break;
            case "quadraticCurve":
                //二阶贝塞尔曲线
                let cMidPos = CacQuadraticCurveMidPos(source, target, 100);
                //drawCircle(cMidPos.x, cMidPos.y, 5);
                newTargetPos = drawArrowShape(data.id, data.targetShape, cMidPos, target, source, target, true);
                break;
            default:
                newTargetPos = drawArrowShape(data.id, data.targetShape, source, target, source, target, true);
                break;
        }
    }
    if (data.sourceShape) {
        switch (data.curveStyle) {
            case "bezier":
                //三阶贝塞尔曲线
                let bMidPos = CacBezierCurveMidPos(source, target, 100);
                let pos1 = { x: bMidPos.x1, y: bMidPos.y1 }
                //drawCircle(pos1.x, pos1.y, 5);
                newSourcePos = drawArrowShape(data.id, data.sourceShape, source, pos1, source, target, false);
                break;
            case "quadraticCurve":
                //二阶贝塞尔曲线
                let cMidPos = CacQuadraticCurveMidPos(source, target, 100);
                //drawCircle(cMidPos.x, cMidPos.y, 5);
                newSourcePos = drawArrowShape(data.id, data.sourceShape, source, cMidPos, source, target, false);
                break;
            default:
                newSourcePos = drawArrowShape(data.id, data.sourceShape, source, target, source, target, false);
                break;
        }
    }

    let tempSourcePos = newSourcePos ? newSourcePos : source;
    let tempTargetPos = newTargetPos ? newTargetPos : target;

    //Draw edge
    let line = new Graphics();
    line.lineStyle(4, 0xFFFFFF, 1);

    line.moveTo(tempSourcePos.x, tempSourcePos.y);
    switch (data.curveStyle) {
        case "bezier":
            //三阶贝塞尔曲线
            let cPos = CacBezierCurveMidPos(tempSourcePos, tempTargetPos, 100);
            line.bezierCurveTo(cPos.x1, cPos.y1, cPos.x2, cPos.y2, cPos.x, cPos.y);
            break;
        case "quadraticCurve":
            //二阶贝塞尔曲线
            let bPos = CacQuadraticCurveMidPos(tempSourcePos, tempTargetPos, 100);
            line.quadraticCurveTo(bPos.x, bPos.y, tempTargetPos.x, tempTargetPos.y);
            break;
        default:
            line.lineTo(tempTargetPos.x, tempTargetPos.y);
            break;
    }

    edgeList[data.id] = line;//保存边引用

    edgeContainer.addChild(line);
}

//d3-force init
function initializeNodes(nodes,edges) {
    let width = window.innerWidth;
    let height = window.innerHeight;
    let simulation = d3.forceSimulation(nodes)
    .alpha(.02)
    .force("charge", d3.forceManyBody().strength(-34))
    .force("collide",  d3.forceCollide(120) ) 
    .force("center", d3.forceCenter(width / 2, height / 2));

    let ticked = function(){
        nodes.forEach(drawNode);
    }

    let drawNode = function(node){
        nodeList[node.id] = node;
        if(!circleList[node.id]){//Node只有初次渲染的时候需要绘制
            let circle = new Graphics();
            if (node.color) {
                circle.beginFill(node.color);
            } else {
                circle.beginFill(0x66CCFF);
            }
    
            let width = nodeWidth;
            if (node.width) width = node.width;
            circle.drawCircle(0, 0, width);
            circle.endFill();
            circle = setNode(circle, node.id);
    
            //Move
            circle.x = node.x;
            circle.y = node.y;
            circleList[node.id] = circle;
            nodeContainer.addChild(circle);
        }else{//只需要移动位置
            circleList[node.id].x = node.x;
            circleList[node.id].y = node.y;
        }
        //Edge的更新有两个条件
        //1.变动的节点通过边连接的另外一个节点是否已经完成了初始化被赋予了坐标
        //2.变动的节点是有边相连接的
        for (let i = 0, l = edges.length; i < l; i++) {
            let data = edges[i],
                id = data.id;
            if(nodeList[data.source]&&nodeList[data.target]){//条件1
                if (data.source===node.id || data.target===node.id) {//条件2
                    //Get position info
                    let source = nodeList[data.source];
                    let target = nodeList[data.target];
                    drawArrowAndEdge(data, source, target);
                }
            }
        }
    }
    simulation.on('tick', ticked);
}


//脑残算法
function myStupiedCacQuadraticCurveMidPos(tempSourcePos, tempTargetPos, height = 100) {
    let disX = Math.abs(tempTargetPos.x - tempSourcePos.x), disY = Math.abs(tempTargetPos.y - tempSourcePos.y);
    let angle = Math.atan(disY / disX);
    let halfLen = Math.sqrt(disX * disX + disY * disY) / 2;
    let angle1 = Math.atan(height / halfLen);
    let angleTotal = angle + angle1;
    if (angleTotal - Math.PI / 2 > 0) {//两角相加为一个钝角了
        let angleBeats = Math.PI - (angle + angle1);
        let edge = 100 / Math.sin(angle1);
        let xLen = edge * Math.cos(angleBeats);
        let yLen = edge * Math.sin(angleBeats);
        let minX = tempSourcePos.x - tempTargetPos.x >= 0 ? tempTargetPos.x : tempSourcePos.x;
        let minY = tempSourcePos.y - tempTargetPos.y >= 0 ? tempSourcePos.y : tempTargetPos.y;
        return {
            x: minX - xLen,
            y: minY - yLen
        }
    } else {//两角相加为一个锐角了
        let edgeLen = height / Math.sin(angle1);
        let xLen = edgeLen * Math.sin(angleTotal);
        let yLen = edgeLen * Math.cos(angleTotal);
        let maxX = tempSourcePos.x - tempTargetPos.x >= 0 ? tempSourcePos.x : tempTargetPos.x;
        let minY = tempSourcePos.y - tempTargetPos.y >= 0 ? tempSourcePos.y : tempTargetPos.y;
        return {
            x: maxX - xLen,
            y: minY + yLen
        }
    }
}

//二阶贝塞尔曲线---大神算法
function CacQuadraticCurveMidPos(tempSourcePos, tempTargetPos, h = 100) {
    let x2 = (tempSourcePos.x - tempTargetPos.x) * (tempSourcePos.x - tempTargetPos.x);
    let y2 = (tempSourcePos.y - tempTargetPos.y) * (tempSourcePos.y - tempTargetPos.y);
    let sqrt = Math.sqrt(x2 + y2);
    let resX = (tempSourcePos.y - tempTargetPos.y) * (2 - h) / sqrt;
    let resY = (tempSourcePos.x - tempTargetPos.x) * (2 - h) / sqrt;
    return {
        x: resX,
        y: resY
    };
}

//三阶贝塞尔曲线---引用库:arcTobezier
function CacBezierCurveMidPos(tempSourcePos, tempTargetPos, height = 100) {
    let dx = tempSourcePos.x - tempTargetPos.x,
        dy = tempSourcePos.y - tempTargetPos.y,
        dr = Math.sqrt(dx * dx + dy * dy);

    const curve = {
        type: 'arc',
        rx: dr,
        ry: dr,
        largeArcFlag: 0,
        sweepFlag: 1,
        xAxisRotation: 0,
    }

    const curves = arcToBezier({
        px: tempSourcePos.x,
        py: tempSourcePos.y,
        cx: tempTargetPos.x,
        cy: tempTargetPos.y,
        rx: curve.rx,
        ry: curve.ry,
        xAxisRotation: curve.xAxisRotation,
        largeArcFlag: curve.largeArcFlag,
        sweepFlag: curve.sweepFlag,
    });

    return curves[0];
}

function setNode(graph, id) {

    let onDragStart = function (event) {
        // store a reference to the data
        // the reason for this is because of multitouch
        // we want to track the movement of this particular touch
        event.stopPropagation();
        this.data = event.data;
        this.dragging = true;
    }

    let onDragEnd = function () {
        this.dragging = false;
        // set the interaction data to null
        this.data = null;
        //归位
        dragContainer.removeChild(this);
        nodeContainer.addChild(this);
    }

    let onDragMove = function () {
        if (this.dragging) {
            let newPosition = this.data.getLocalPosition(this.parent);
            this.x = newPosition.x;
            this.y = newPosition.y;
            updateEdge(id, newPosition);//闭包的缘故,id是能访问得到的
            nodeContainer.removeChild(this);
            dragContainer.addChild(this);
        }
    }

    let updateEdge = function (id, newPos) {
        for (let element in edgeInfoList) {//一个节点可能连接着多根线
            if (edgeInfoList[element].target === id) {
                drawNewEdge(edgeInfoList[element], true, newPos);
            } else if (edgeInfoList[element].source === id) {
                drawNewEdge(edgeInfoList[element], false, newPos);
            }
        };
    }

    let drawNewEdge = function (data, targetFlag, newPos) {
        let oldLine = edgeList[data.id];//在线的引用保存对象里找到线
        edgeContainer.removeChild(oldLine);//删除线重新画
        //不落帧
        if (targetFlag) {
            nodeList[data.target].x = newPos.x;
            nodeList[data.target].y = newPos.y;
        } else {
            nodeList[data.source].x = newPos.x;
            nodeList[data.source].y = newPos.y;
        }

        let source = nodeList[data.source],//起点(node坐标)
            target = nodeList[data.target];//终点(node坐标)

        //Redraw
        drawArrowAndEdge(data, source, target);

        // //Save position change
        if (targetFlag) {
            //保存修改了的target Node坐标
            nodeList[data.target].x = newPos.x;
            nodeList[data.target].y = newPos.y;
        } else {
            //保存修改了的source Node坐标
            nodeList[data.source].x = newPos.x;
            nodeList[data.source].y = newPos.y;
        }

    }

    graph.interactive = true;
    // this button mode will mean the hand cursor appears when you roll over the bunny with your mouse
    graph.buttonMode = true;
    graph
        .on('pointerdown', onDragStart)
        .on('pointerup', onDragEnd)
        .on('pointerupoutside', onDragEnd)
        .on('pointermove', onDragMove);

    return graph;
}

//脑残画法
function drawArrowShape(id, shape, sourcePos, targetPos, source, target, targetFlag) {

    switch (shape) {
        case 'circle':
            let c_nodeRadius = nodeWidth;
            if (!targetFlag && sourcePos.width) c_nodeRadius = sourcePos.width;
            if (targetFlag && targetPos.width) c_nodeRadius = targetPos.width;

            //边界判定 => 贴一起了就别显示啦
            if ((Math.abs(source.y - target.y) < c_nodeRadius * 1.5) &&
                (Math.abs(source.x - target.x) < c_nodeRadius * 1.5)) {
                c_nodeRadius = 0;
            }

            let srcPos = targetFlag ? targetPos : sourcePos;
            let tgtPos = targetFlag ? sourcePos : targetPos;

            let c_angle = Math.atan(Math.abs(srcPos.y - tgtPos.y) / Math.abs(srcPos.x - tgtPos.x))
            let circleWidth = c_nodeRadius / 2;
            //posX和posY就是circle的最终中心坐标
            let posX = (c_nodeRadius + circleWidth) * Math.cos(c_angle),
                posY = (c_nodeRadius + circleWidth) * Math.sin(c_angle);

            //分类讨论target和source的相对左右位置
            if (srcPos.x > tgtPos.x) {//source节点在右边
                posX = srcPos.x - posX;
            } else {
                posX = srcPos.x + posX;
            }
            if (srcPos.y > tgtPos.y) {//source节点在上边
                posY = srcPos.y - posY;
            } else {
                posY = srcPos.y + posY;
            }

            //Draw circle
            let circle = new Graphics();
            circle.beginFill(0x66CCFF);

            circle.drawCircle(0, 0, circleWidth);
            circle.endFill();

            circle.x = posX;
            circle.y = posY;

            //updateArrow 
            updateArrow(id, circle, targetFlag);

            return {
                x: posX,
                y: posY
            }

        case 'triangle':
            //这个三角形默认按顶角为50°,两个底角为65°来算,两边长先按一半nodeWidth来算吧
            //先画出来再想抽象的事
            let t_nodeRadius = nodeWidth;
            if (!targetFlag && sourcePos.width) t_nodeRadius = sourcePos.width;
            if (targetFlag && targetPos.width) t_nodeRadius = targetPos.width;

            //边界判定 => 贴一起了就别显示啦
            if ((Math.abs(source.y - target.y) < t_nodeRadius * 1.5) &&
                (Math.abs(source.x - target.x) < t_nodeRadius * 1.5)) {
                t_nodeRadius = 0;
            }

            let t_srcPos = targetFlag ? sourcePos : targetPos;
            let t_tgtPos = targetFlag ? targetPos : sourcePos;

            let topAngle = Math.PI / 180 * 50,//角度转弧度,注意Math的那些方法的单位是弧度
                sideEdge = t_nodeRadius,//瞅着合适,先凑合
                halfBottomEdge = Math.sin(topAngle / 2) * sideEdge,
                centerEdge = Math.cos(topAngle / 2) * sideEdge;
            //angle是一样的,先按node中心算,arrow中心算之后再说,先todo(直线版看出不这个问题,曲线就崩了)
            let angle = Math.atan(Math.abs(t_srcPos.y - t_tgtPos.y) / Math.abs(t_srcPos.x - t_tgtPos.x));
            let beginPosX = t_nodeRadius * Math.cos(angle),
                beginPosY = t_nodeRadius * Math.sin(angle),
                pos1X, pos1Y, pos2X, pos2Y,
                centerX = (t_nodeRadius + centerEdge) * Math.cos(angle),
                centerY = (t_nodeRadius + centerEdge) * Math.sin(angle);

            pos1X = pos2X = Math.sin(angle) * halfBottomEdge;
            pos1Y = pos2Y = Math.cos(angle) * halfBottomEdge;//简单的几何知识(手动抽搐😖)

            //还需要分类讨论target和source的左右位置的各种情况
            //1234代表target相对source所在象限
            if (t_srcPos.x > t_tgtPos.x) {//source节点在右
                if (t_srcPos.y > t_tgtPos.y) {//下 ----> 1
                    beginPosX = t_tgtPos.x + beginPosX;
                    beginPosY = t_tgtPos.y + beginPosY;

                    centerX = t_tgtPos.x + centerX;
                    centerY = t_tgtPos.y + centerY;

                    pos1X = centerX + pos1X;
                    pos1Y = centerY - pos1Y;//+ -

                    pos2X = centerX - pos2X;
                    pos2Y = centerY + pos2Y;//- +
                } else {//上 ----> 4
                    beginPosX = t_tgtPos.x + beginPosX;
                    beginPosY = t_tgtPos.y - beginPosY;

                    centerX = t_tgtPos.x + centerX;
                    centerY = t_tgtPos.y - centerY;

                    pos1X = centerX + pos1X;
                    pos1Y = centerY + pos1Y;//+ +

                    pos2X = centerX - pos2X;
                    pos2Y = centerY - pos2Y;//- -
                }

            } else {//source节点在左
                if (t_srcPos.y > t_tgtPos.y) {//下 ----> 2
                    beginPosX = t_tgtPos.x - beginPosX;
                    beginPosY = t_tgtPos.y + beginPosY;

                    centerX = t_tgtPos.x - centerX;
                    centerY = t_tgtPos.y + centerY;

                    pos1X = centerX - pos1X;
                    pos1Y = centerY - pos1Y;//- -

                    pos2X = centerX + pos2X;
                    pos2Y = centerY + pos2Y;//+ +
                } else {//上 ----> 3
                    beginPosX = t_tgtPos.x - beginPosX;
                    beginPosY = t_tgtPos.y - beginPosY;

                    centerX = t_tgtPos.x - centerX;
                    centerY = t_tgtPos.y - centerY;

                    pos1X = centerX - pos1X;
                    pos1Y = centerY + pos1Y;//- +

                    pos2X = centerX + pos2X;
                    pos2Y = centerY - pos2Y;//+ -
                }
            }

            //Draw triangle
            let triangle = new Graphics();

            triangle.beginFill(0x66CCFF);
            triangle.lineStyle(0, 0x66CCFF, 1);
            triangle.moveTo(beginPosX, beginPosY);
            triangle.lineTo(pos1X, pos1Y);
            triangle.lineTo(pos2X, pos2Y);
            triangle.endFill();

            updateArrow(id, triangle, targetFlag);

            return {
                x: centerX,
                y: centerY
            }
    }
}

function updateArrow(id, shape, targetFlag) {
    if (!arrowList[id]) arrowList[id] = {};
    if (!targetFlag) {//Source arrow
        if (arrowList[id].sourceArrow) arrowContainer.removeChild(arrowList[id].sourceArrow);
        //save newArrow
        arrowList[id].sourceArrow = shape;
    } else {//Target arrow
        if (arrowList[id].targetArrow) arrowContainer.removeChild(arrowList[id].targetArrow);
        //save newArrow
        arrowList[id].targetArrow = shape;
    }
    arrowContainer.addChild(shape);
}

// Scale/Zoom
renderer.view.addEventListener('wheel', function (e) {
    if (e.deltaY < 0) {
        zooming(true, e.pageX, e.pageY);
    } else {
        zooming(false, e.pageX, e.pageY);
    }
});

function zooming(zoomFlag, x, y) {
    //Current scale    
    let scale = stage.scale.x;
    let point = toLocalPos(x, y);
    //Zooming    
    if (zoomFlag) {
        if (scale < SCALE_MAX) {
            scale += 0.1;
            //moving      
            stage.position.set(stage.x - (point.x * 0.1), stage.y - (point.y * 0.1))
        }
    } else {
        if (scale > SCALE_MIN) {
            scale -= 0.1;
            //moving
            stage.position.set(stage.x - (point.x * -0.1), stage.y - (point.y * -0.1))
        }
    }
    stage.scale.set(scale, scale);
}

function drawCircle(x, y, r = 30) {
    let circle = new Graphics();
    circle.beginFill(0x000000, 0.2);

    circle.drawCircle(0, 0, r);
    circle.endFill();
    let localPos = toLocalPos(x, y);
    circle.x = localPos.x;
    circle.y = localPos.y;
    point.circle = circle;
    dragContainer.addChild(circle);
}

// Drag/Move
let startMousePos = {};

stage.hitArea = new PIXI.Rectangle(0, 0, window.innerWidth, window.innerHeight);
stage.interactive = true;
stage.buttonMode = true;

stage.on('pointerdown', stagePointerDown)
    .on('pointerup', stagePointerUp)
    .on('pointerupoutside', stagePointerUp)
    .on('pointermove', stagePointerMove);

function stagePointerDown(event) {
    this.dragging = true;
    movePosBegin.x = stage.x;
    movePosBegin.y = stage.y;
    let newPosition = event.data.global;
    let x = newPosition.x;
    let y = newPosition.y;
    startMousePos.x = x; startMousePos.y = y;
    //Draw circle
    let r = 30 / stage.scale.x;
    drawCircle(x, y, r);
}

function stagePointerUp(event) {
    this.dragging = false;
    //Remove  circle
    if (point.circle) dragContainer.removeChild(point.circle);
}

function stagePointerMove(event) {
    if (this.dragging) {
        //Move  circle
        let newPosition = event.data.global;
        let x = newPosition.x;
        let y = newPosition.y;

        //Remove  circle first
        if (point.circle) dragContainer.removeChild(point.circle);
        //Redraw circle
        //Current scale    
        let scale = stage.scale.x;
        let r = 30 / scale;
        drawCircle(x, y, r);

        //需要注意这里的差值必须要拿global坐标来算而不是to stageLocalPos来算
        //因为stage.x/y按照的是global坐标来移动的
        let offsetX = x - startMousePos.x,//差值
            offsetY = y - startMousePos.y;

        stage.x = movePosBegin.x + offsetX;
        stage.y = movePosBegin.y + offsetY;//修正差值
    }
}

function toLocalPos(x, y) {
    let mouse = new PIXI.Point(x, y);
    let localPos = stage.toLocal(mouse);
    return localPos;
}

function autoGenera(nodeNum,edgeNum){//先node再edge
    let res = [];
    for(let i=0;i<nodeNum;i++){
        let temp = {};
        let data = temp.data = {};
        //Ascii => A => 65
        data.id = String.fromCharCode(65+i);
        data.width = (Math.random()*30)+20;
        if(i%2===0)data.color="0x000000";
        res.push(temp);
    }
    for(let i=0;i<edgeNum;i++){
        let randomNode1 = Math.floor(Math.random()*nodeNum);
        let randomNode2 = Math.floor(Math.random()*nodeNum);
        //toFix => Node can arrow itself
        if(randomNode1 === randomNode2){
            if(randomNode1===1){
                randomNode1++;
            }else{
                randomNode2++;
            }
        };
        let temp = {};
        let data = temp.data = {};
        data.id = String.fromCharCode(65+nodeNum+i);
        data.source = res[randomNode1].data.id;
        data.target = res[randomNode2].data.id;
        if(i%2==0)data.curveStyle = 'bezier'
        if(i%2==0){
            data.targetShape='triangle';
            data.sourceShape='circle';
        }else{
            data.targetShape='circle';
            data.sourceShape='triangle';
        }
        res.push(temp);
    }
    return res;
}

PiCi({
    container: document.getElementById('cy'),
    elements: autoGenera(10,5)
    
})
<script src="https://cdnjs.cloudflare.com/ajax/libs/pixi.js/4.5.1/pixi.min.js"></script>
<script src="//d3js.org/d3.v4.min.js"></script>