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 }
  })
}

function CacBezierCurveMidPos(tempSourcePos, tempTargetPos) {
    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];
}
let canvas, stage;

// Aliases
let Stage = createjs.Stage,
    Shape = createjs.Shape,
    Text = createjs.Text,
    Graphics = createjs.Graphics,
    Container = createjs.Container;

let edgeContainer = new Container(),
    arrowContainer = new Container(),
    nodeContainer = new Container(),
    textContainer = new Container(),
    dragContainer = new Container();

// For scale limmit
const SCALE_MAX = 100, SCALE_MIN = 0.4;

// Fix canvas position offset
const offsetX = 0, offsetY = 0;

// Defalut node radius
// Node shape default is circle
// FIXME: Other shape‘s support
let nodeWidth = 30;

// Cache the shape that what your mouse click generate
let point = {};

// Cache list
// Have unique identifiers => id
let nodeList = {},// Cache node
    edgeList = {},// Cache edge
    arrowList = {},// Cache arrow
    textList = {},// Cache text
    edgeInfoList = {},// Cache edge info

    bezierList = {},// Cache bezierCurve to Deal with 2 bezierCurve
    circleList = {};// Cache init node shape info


// Now is just for Bezier,TODO is for all(include straight use midPos to Calculate)
// let midPos;

// Fix Events trigger conflict between nativeEvent and CreatejsEvent
let nodeFlag = false;
let simulation;// D3-force simulation

/**
 * CiCi is the core method for initialization
 * @param {Object} opts
 * opts defined some init options
 * For example:(after "..." means no necessary)
 * {container: document.getElementById('canvas'),
 *  elements: {
 *      nodes:[{ id: 'a'
 *                      ...width:10,color:'black'},
 *             { id: 'b'
 *                      ...width:30,color:'#d3d3d3'}],
 *      edges:{ source:'a',target:'b'
 *                      ...curveStyle:'bezier',targetShape:'triangle',sourceShape:'circle'}}
 */
function CiCi({ container, elements }) {

    if (!container) return;// Todo => Throw error
    canvas = container;

    // Enable touch interactions if supported on the current device:
    createjs.Touch.enable(stage);
    // Enabled mouse over / out events
    stage = new Stage(canvas);
    // Keep tracking the mouse even when it leaves the canvas
    stage.mouseMoveOutside = true;
    stage.enableMouseOver(10);
    // Auto update stage
    createjs.Ticker.framerate = 60;
    createjs.Ticker.addEventListener("tick", stage);

    let { nodes, edges } = elements;

    //Init canvas property
    canvas.height = window.innerHeight - offsetY;
    canvas.width = window.innerWidth - offsetX;
    canvas.style.background = '#d3d3d3';

    // Extrac nodes/edges information 
    let simpleCheck = function (data) {
        let id = data.id;
        // Simple check
        if (data === null) {
            console.error("Can't set node to null");
            return false;
        }
        if (id !== null) {
            // Check whether id is already exists
            if ((nodeList[id] != undefined) || edgeList[id] != undefined) {
                console.error("id已存在");
                return false;
            }
        } else {
            // Id is neccesary
            console.error("id是必须参数");
            return false;
        }
        return true;
    }
    let tempObj = {} , resNodes = [], resEdge = [];
    for (let i = 0, l = nodes.length; i < l; i++) {
        let data = nodes[i];
        let checkFlag = simpleCheck(data);
        if (checkFlag) {
            //nodeList[data.id] = data;
            tempObj[data.id] = data;
        }
    }
    for (let i = 0, l = edges.length; i < l; i++) {
        let data = edges[i];
        let checkFlag = simpleCheck(data);
        if (checkFlag) {
            if(tempObj[data.target]&&tempObj[data.source]){
                edgeInfoList[data.id] = data;
                resEdge.push(data);
                if(!nodeList[data.target]){
                    nodeList[data.target] = tempObj[data.target];
                    resNodes.push(tempObj[data.target]);
                }
                if(!nodeList[data.source]){
                    nodeList[data.source] = tempObj[data.source];
                    resNodes.push(tempObj[data.source]);
                }
            }
        }
    }

    //Init node
    initializeNodes(resNodes, resEdge);

    // Init edge
    for (let i = 0, l = resEdge.length; i < l; i++) {
        let data = resEdge[i];
        if (data.source && data.target) {
            // Get position info
            let source = nodeList[data.source.id];
            let target = nodeList[data.target.id];

            drawArrowAndEdge(data, source, target);
        }
    }

    // Init event
    initEvent(canvas);

    // Hierarchy order
    stage.addChild(edgeContainer);
    stage.addChild(arrowContainer);
    stage.addChild(nodeContainer);// Node above edge and arrow
    stage.addChild(textContainer);// Text above node
    stage.addChild(dragContainer);// Hightest level
}

/**
 * InitializeNodes method use for layout and paint nodes
 * @param {Array} nodes
 * @param {Array} edges
 */
function initializeNodes(nodes, edges) {
    //D3-force Layout
    simulation = d3.forceSimulation(nodes)
        .force('link', d3.forceLink().id((d) => d.id))
        .force('center', d3.forceCenter(canvas.width / 2, canvas.height / 2))
        .force('charge', d3.forceManyBody())

    //links方法后source和target变成对象了,而不是原来的字符串了
    simulation.force('link').links(edges).distance(300).strength(0.5);

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

    let drawNode = function (node) {
        nodeList[node.id] = node;
        if (!circleList[node.id]) {//Node只有初次渲染的时候需要绘制
            //Draw node
            let graphics = new Shape();
            let circle = graphics.graphics;
            if (node.color) {
                circle.beginFill(node.color);
            } else {
                circle.beginFill("#66CCFF");
            }

            drawText(node, node.x, node.y);

            let width = nodeWidth;
            if (node.width) width = node.width;
            circle.drawCircle(0, 0, width);
            circle.endFill();

            graphics = setNode(graphics, node.id);

            //Move the graph to its designated position
            graphics.x = node.x;
            graphics.y = node.y;
            circleList[node.id] = graphics;
            nodeContainer.addChild(graphics);
        } else {//只需要移动位置
            circleList[node.id].x = node.x;
            circleList[node.id].y = node.y;
            updateText(node.id, { x: node.x, 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.id] && nodeList[data.target.id]) {//条件1
                if (data.source.id === node.id || data.target.id === node.id) {//条件2
                    //Get position info
                    let source = nodeList[data.source.id];
                    let target = nodeList[data.target.id];
                    drawArrowAndEdge(data, source, target);
                }
            }
        }
    }
    simulation.on('tick', ticked);

}

/**
 * DrawText method use for update text when node position change
 * @param {Object} nodeInfo 
 * text=>node text to show 
 * id=> use for set text id 
 * x/y => text position
 * textOpts => text style
 */
function drawText({ text, id, textOpts = {} }, x, y) {
    if (text === '' || text === undefined) return;
    if (textList[id]) textContainer.removeChild(textList[id]);
    let nodeText = new Text(text, textOpts.font || '36px Arial', textOpts.color || 'white');
    x += textOpts.disX || 0;
    y += textOpts.disY || 0;

    //test
    //console.log(nodeText);

    nodeText.textAlign = 'center';
    nodeText.textBaseline = 'middle';
    nodeText = Object.assign(nodeText, textOpts);
    nodeText.x = x;
    nodeText.y = y;
    // Cache text
    textList[id] = nodeText;
    textContainer.addChild(nodeText);
}

/**
 * UpdateText method use for update text when node position change
 * @param {String} id
 * @param {Object} newPos
 */
let updateText = function (id, newPos) {
    textList[id].x = newPos.x;
    textList[id].y = newPos.y;
}

/**
 * SetNode method use for init node's event when dragging node
 * @param {Shape} graph
 * @param {String} id
 * @return {Shape}
 */
function setNode(graph, id) {

    let onDragStart = function (event) {
        simulation.alphaTarget(0.3).restart();
        event.stopPropagation();// FIXME
        nodeFlag = true;
    }

    let onDragEnd = function (event) {
        simulation.alphaTarget(0);
        //Sticky or not
        nodeList[id].fx = null;
        nodeList[id].fy = null;
        let target = event.target;
        target.dragging = false;
        // Set the interaction data to null
        target.data = null;
        // Put back the original container
        dragContainer.removeChild(this);
        dragContainer.removeChild(textList[id]);
        nodeContainer.addChild(this);
        textContainer.addChild(textList[id]);
        nodeFlag = false;
    }

    let onDragMove = function (event) {
        let newPosition = stage.globalToLocal(event.stageX, event.stageY);
        //这是节点图形位置更新而已
        this.x = newPosition.x;
        this.y = newPosition.y;
        updateNode(id, newPosition);
        updateEdge(id, newPosition);// id => closure
        updateText(id, newPosition);
        textContainer.removeChild(textList[id]);
        nodeContainer.removeChild(this);
        dragContainer.addChild(this);
        dragContainer.addChild(textList[id]);
    }

    //图钉效果
    let onDbClick = function(){
        nodeList[id].fx = null;
        nodeList[id].fy = null;
    }

    let updateNode = function(id, newPos){
        nodeList[id].x = newPos.x;
        nodeList[id].y = newPos.y;
        nodeList[id].fx = newPos.x;
        nodeList[id].fy = newPos.y;
    }
    /**
     * UpdateEdge method use for update edge when node position change
     * @param {String} id
     * @param {Object} newPos
     */
    let updateEdge = function (id, newPos) {
        // A node may be connected to multiple lines
        for (let element in edgeInfoList) {
            if (edgeInfoList[element].target.id === id) {
                drawNewEdge(edgeInfoList[element], true, newPos);
            } else if (edgeInfoList[element].source.id === id) {
                drawNewEdge(edgeInfoList[element], false, newPos);
            }
        };
    }

    /**
     * @param {Object} data
     * @param {Bool} targetFlag
     * @param {Object} newPos {x,y}
     */
    let drawNewEdge = function (data, targetFlag, { x, y }) {

        // Find the line from cache list
        let oldLine = edgeList[data.id];

        // Remove old line
        edgeContainer.removeChild(oldLine);

        // Update for this frame
        if (targetFlag) {
            nodeList[data.target.id].x = x;
            nodeList[data.target.id].y = y;
        } else {
            nodeList[data.source.id].x = x;
            nodeList[data.source.id].y = y;
        }

        let source = nodeList[data.source.id],// Begin position (Node)
            target = nodeList[data.target.id];// End position (Node)

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

        // Save position changed
        if (targetFlag) {
            // Save target node
            nodeList[data.target.id].x = x;
            nodeList[data.target.id].y = y;
        } else {
            // Save source node
            nodeList[data.source.id].x = x;
            nodeList[data.source.id].y = y;
        }

    }

    graph.cursor = "pointer";
    graph.on('mousedown', onDragStart);
    graph.on('pressup', onDragEnd);
    graph.on('pressmove', onDragMove);
    //graph.on('dblclick', onDbClick);

    return graph;
}

/**
 * DrawArrowAndEdge method use for update the arrow and edge from node source and target position
 * @param {Object} data
 * @param {Object} source {x,y}
 * @param {Object} target {x,y}
 */
function drawArrowAndEdge(data, source, target) {

    // Remove old edge (drawArrowShape will remove old arrow)
    if (edgeList[data.id]) edgeContainer.removeChild(edgeList[data.id]);

    // Draw Arrow
    let newSourcePos, newTargetPos;
    if (data.targetShape) {
        switch (data.curveStyle) {
            case "bezier":
                // CacBezierCurve
                let bMidPos = CacBezierCurveMidPos(source, target);
                // Todo => complete bezierList
                // if (bezierList[source + '+' + target]) {
                //     bezierList[source + '+' + target] = 1;//'source+target'
                // } else {
                //     bezierList[source + '+' + target]++;
                // }

                let pos2 = { x: bMidPos.x2, y: bMidPos.y2 }

                if (data.text) drawText(data, (bMidPos.x1 + bMidPos.x2) / 2, (bMidPos.y1 + bMidPos.y2) / 2);
                newTargetPos = drawArrowShape(data.id, data.targetShape, pos2, target, source, target, true);
                break;
            case "quadraticCurve":
                // QuadraticCurve
                let cMidPos = CacQuadraticCurveMidPos(source, target, 100);
                newTargetPos = drawArrowShape(data.id, data.targetShape, cMidPos, target, source, target, true);
                break;
            default:
                if (data.text) drawText(data, (source.x + target.x) / 2, (source.y + target.y) / 2);
                newTargetPos = drawArrowShape(data.id, data.targetShape, source, target, source, target, true);
                break;
        }
    }
    if (data.sourceShape) {
        switch (data.curveStyle) {
            case "bezier":
                // Cacular Third Bezier Curve's Mid pos
                let bMidPos = CacBezierCurveMidPos(source, target);
                let pos1 = { x: bMidPos.x1, y: bMidPos.y1 }

                if (data.text) drawText(data, (bMidPos.x1 + bMidPos.x2) / 2, (bMidPos.y1 + bMidPos.y2) / 2);
                newSourcePos = drawArrowShape(data.id, data.sourceShape, source, pos1, source, target, false);
                break;
            case "quadraticCurve":
                // Cacular Second Bezier Curve's Mid pos
                let cMidPos = CacQuadraticCurveMidPos(source, target, 100);
                newSourcePos = drawArrowShape(data.id, data.sourceShape, source, cMidPos, source, target, false);
                break;
            default:
                if (data.text) drawText(data, (source.x + target.x) / 2, (source.y + target.y) / 2);
                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 graphics = new Shape();
    let line = graphics.graphics;
    if (data.lineMode === "dash") {//Todo => param
        line.setStrokeStyle(4).setStrokeDash([20, 10], 0).beginStroke("#FFF");
    } else {
        line.setStrokeStyle(4).beginStroke("#FFF");
    }
    line.moveTo(tempSourcePos.x, tempSourcePos.y);
    switch (data.curveStyle) {
        case "bezier":
            // Cacular Third Bezier Curve's Mid pos
            let cPos = CacBezierCurveMidPos(tempSourcePos, tempTargetPos, 100);
            line.bezierCurveTo(cPos.x1, cPos.y1, cPos.x2, cPos.y2, cPos.x, cPos.y);
            break;
        case "quadraticCurve":
            // Cacular Second Bezier Curve's Mid pos
            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] = graphics;

    edgeContainer.addChild(graphics);
}

/**
 * DrawArrowShape method use for draw the arrow from node source and target position 
 * @param {String} id arrow's id
 * @param {String} shape arrow's shape
 * @param {Object} sourcePos {x,y} node source pos
 * @param {Object} targetPos {x,y} node target pos
 * @param {Object} source {x,y} arrow source pos
 * @param {Object} target {x,y} arrow target pos
 * @param {Bool} targetFlag  draw source or target arrow
 */
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;

            //Boundary determination => hide it if stick together
            if ((Math.abs(source.y - target.y) < c_nodeRadius * 1.5) &&
                (Math.abs(source.x - target.x) < c_nodeRadius * 1.5)) {
                c_nodeRadius = 0;
                if (textList[id]) textList[id].visible = false;
            }

            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 and posY is the circle's final position
            let posX = (c_nodeRadius + circleWidth) * Math.cos(c_angle),
                posY = (c_nodeRadius + circleWidth) * Math.sin(c_angle);

            // Discusses the relative position of target and source
            if (srcPos.x > tgtPos.x) {// Source node is right
                posX = srcPos.x - posX;
            } else {
                posX = srcPos.x + posX;
            }
            if (srcPos.y > tgtPos.y) {// Source node is Up
                posY = srcPos.y - posY;
            } else {
                posY = srcPos.y + posY;
            }

            //Draw circle
            let cGraphics = new Shape();
            let circle = cGraphics.graphics;
            circle.beginFill("#66CCFF");

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

            cGraphics.x = posX;
            cGraphics.y = posY;

            //updateArrow 
            updateArrow(id, cGraphics, 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;

            //Boundary determination
            if ((Math.abs(source.y - target.y) < t_nodeRadius * 1.5) &&
                (Math.abs(source.x - target.x) < t_nodeRadius * 1.5)) {
                t_nodeRadius = 0;
                if (textList[id]) textList[id].visible = false;
            }

            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 tGraphics = new Shape();
            let triangle = tGraphics.graphics;

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

            updateArrow(id, tGraphics, targetFlag);

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

/** 
 * @param {String} id arrow's id
 * @param {String} shape arrow's shape
 * @param {Bool} targetFlag  source or target arrow
 */
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);
}

/** 
 * InitEvent method use for init canvas's zoom and drag event
 * @param canvas canvas to init 
 */
function initEvent(canvas) {

    // Scale/Zoom
    canvas.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;
        let point = toLocalPos(x, y);
        // //Zooming    
        if (zoomFlag) {
            if (scale < SCALE_MAX) {
                scale += 0.1;
                //moving      
                stage.x = stage.x - (point.x * 0.1),
                    stage.y = stage.y - (point.y * 0.1);
            }
        } else {
            if (scale > SCALE_MIN) {
                scale -= 0.1;
                //moving
                stage.x = stage.x - (point.x * -0.1),
                    stage.y = stage.y - (point.y * -0.1);
            }
        }
        stage.scale = scale;
    }


    // Drag/Move
    let movePosBegin = {};
    let startMousePos = {};
    let hitArea = new Shape();
    let canvasDragging = false;
    hitArea.graphics.rect(0, 0, canvas.width, canvas.height);
    canvas.hitArea = hitArea;

    // Could use bind
    canvas.addEventListener('mousedown', stagePointerDown);
    canvas.addEventListener('mouseup', stagePointerUp);
    canvas.addEventListener('mouseout', stagePointerUp);
    canvas.addEventListener('mousemove', stagePointerMove);

    function stagePointerDown(event) {
        if (!nodeFlag) {
            canvasDragging = true;

            movePosBegin.x = stage.x;
            movePosBegin.y = stage.y;

            startMousePos.x = event.pageX;
            startMousePos.y = event.pageY;
            //Draw circle
            let r = 30 / stage.scale;
            drawCircle(startMousePos.x, startMousePos.y, r);
        }

    }

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

    function stagePointerMove(event) {
        if (canvasDragging && !nodeFlag) {
            //Move  circle
            let x = event.pageX;
            let y = event.pageY;

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

            let offsetX = x - startMousePos.x,//差值
                offsetY = y - startMousePos.y;

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

    }

}

function toLocalPos(x, y) {
    let localPos = stage.globalToLocal(x - offsetX, y - offsetY);
    return localPos;
}

function drawCircle(x, y, r = 30) {
    //Draw circle
    let cGraphics = new Shape();
    let circle = cGraphics.graphics;
    circle.beginFill(Graphics.getRGB(0, 0, 0, 0.2));//alpha

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

    let localPos = toLocalPos(x, y);
    cGraphics.x = localPos.x;
    cGraphics.y = localPos.y;

    point.circle = cGraphics;
    dragContainer.addChild(cGraphics);
}

function autoGenera(nodeNum,edgeNum){
    let resObj = {},nodes = [],edges = [];
    for(let i=0;i<nodeNum;i++){
        let data = {};
        //Ascii => A => 65
        data.id = String.fromCharCode(65+i);
        data.width = (Math.random()*30)+20;
        data.text = data.id;
        //data.textOpts = {lineWidth:30}
        if(i%2===0)data.color="#000";
        nodes.push(data);
    }
    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 data = {};
        data.id = String.fromCharCode(65+nodeNum+i);
        data.source = nodes[randomNode1].id;
        data.target = nodes[randomNode2].id;
        data.text = data.id;
        data.textOpts = {color:'#000',outline:2}
        if(i%2==0)data.curveStyle = 'bezier'
        if(i%3==0)data.lineMode = 'dash'
        if(i%2==0){
            data.targetShape='triangle';
            data.sourceShape='circle';
        }else{
            data.targetShape='circle';
            data.sourceShape='triangle';
        }
        edges.push(data);
    }
    resObj.nodes = nodes;
    resObj.edges = edges;
    return resObj;
}

CiCi({
    container: document.getElementById('testCanvas'),
    elements: autoGenera(6, 6)

})
<canvas id="testCanvas"></canvas>
<script src="https://code.createjs.com/1.0.0/easeljs.min.js"></script>

<script src="//d3js.org/d3.v4.min.js"></script>