/** 
 | 
 * 感谢 https://github.com/chaangliu/ForceDirectedLayout/blob/master/javascript/force-directed.js 
 | 
 * A force directed graph layout implementation by liuchang on 2018/05/10. 
 | 
 */ 
 | 
const CANVAS_WIDTH = 1000 
 | 
const CANVAS_HEIGHT = 1000 
 | 
let k 
 | 
let mNodeList = [] 
 | 
let mEdgeList = [] 
 | 
let mDxMap = {} 
 | 
let mDyMap = {} 
 | 
let mNodeMap = {} 
 | 
  
 | 
export function ForceDirected(data = {}) { 
 | 
    // generate nodes and edges 
 | 
    // for (let i = 0; i < 20; i++) { 
 | 
    //     mNodeList.push(new Node(i)) 
 | 
    // } 
 | 
    k = 0 
 | 
    mNodeList = [] 
 | 
    mEdgeList = [] 
 | 
    mDxMap = {} 
 | 
    mDyMap = {} 
 | 
    mNodeMap = {} 
 | 
  
 | 
    let nodeList = data.nodeList 
 | 
    for (let i = 0; i < nodeList.length; i++) { 
 | 
        let node = nodeList[i] 
 | 
        mNodeList.push(node) 
 | 
    } 
 | 
  
 | 
    // for (let i = 0; i < 20; i++) { 
 | 
    //     let edgeCount = Math.random() * 8 + 1 
 | 
    //     for (let j = 0; j < edgeCount; j++) { 
 | 
    //         let targetId = Math.floor(Math.random() * 20) 
 | 
    //         let edge = new Edge(i, targetId) 
 | 
    //         mEdgeList.push(edge) 
 | 
    //     } 
 | 
    // } 
 | 
    // line 转 edge 
 | 
    let lineList = data.lineList 
 | 
    for (let i = 0; i < lineList.length; i++) { 
 | 
        let line = lineList[i] 
 | 
        let edge = new Edge(line.from, line.to) 
 | 
        mEdgeList.push(edge) 
 | 
    } 
 | 
  
 | 
    if (mNodeList && mEdgeList) { 
 | 
        k = Math.sqrt(CANVAS_WIDTH * CANVAS_HEIGHT / mNodeList.length) 
 | 
    } 
 | 
    for (let i = 0; i < mNodeList.length; i++) { 
 | 
        let node = mNodeList[i] 
 | 
        if (node) { 
 | 
            mNodeMap[node.id] = node 
 | 
        } 
 | 
    } 
 | 
  
 | 
    // 随机生成坐标. Generate coordinates randomly. 
 | 
    let initialX, initialY, initialSize = 40.0 
 | 
    for (let i in mNodeList) { 
 | 
        initialX = CANVAS_WIDTH * 0.5 
 | 
        initialY = CANVAS_HEIGHT * 0.5 
 | 
        mNodeList[i].x = initialX + initialSize * (Math.random() - 0.5) 
 | 
        mNodeList[i].y = initialY + initialSize * (Math.random() - 0.5) 
 | 
    } 
 | 
  
 | 
    // 迭代200次. Iterate 200 times. 
 | 
    for (let i = 0; i < 200; i++) { 
 | 
        calculateRepulsive() 
 | 
        calculateTraction() 
 | 
        updateCoordinates() 
 | 
    } 
 | 
    // console.log(JSON.stringify(new Result(mNodeList, mEdgeList))) 
 | 
    // 坐标添加px 
 | 
    for (let i = 0; i < mNodeList.length; i++) { 
 | 
        let node = mNodeList[i] 
 | 
        node.left = node.x + 'px' 
 | 
        node.top = node.y + 'px' 
 | 
        node.x = undefined 
 | 
        node.y = undefined 
 | 
    } 
 | 
  
 | 
    data.nodeList = mNodeList 
 | 
  
 | 
    // console.log(data) 
 | 
    return data 
 | 
} 
 | 
  
 | 
function Node(id = null) { 
 | 
    this.id = id 
 | 
    this.x = 22 
 | 
    this.y = null 
 | 
} 
 | 
  
 | 
function Edge(source = null, target = null) { 
 | 
    this.source = source 
 | 
    this.target = target 
 | 
} 
 | 
  
 | 
/** 
 | 
 * 计算两个Node的斥力产生的单位位移。 
 | 
 * Calculate the displacement generated by the repulsive force between two nodes.* 
 | 
 */ 
 | 
function calculateRepulsive() { 
 | 
    let ejectFactor = 6 
 | 
    let distX, distY, dist 
 | 
    for (let i = 0; i < mNodeList.length; i++) { 
 | 
        mDxMap[mNodeList[i].id] = 0.0 
 | 
        mDyMap[mNodeList[i].id] = 0.0 
 | 
        for (let j = 0; j < mNodeList.length; j++) { 
 | 
            if (i !== j) { 
 | 
                distX = mNodeList[i].x - mNodeList[j].x 
 | 
                distY = mNodeList[i].y - mNodeList[j].y 
 | 
                dist = Math.sqrt(distX * distX + distY * distY) 
 | 
            } 
 | 
            if (dist < 30) { 
 | 
                ejectFactor = 5 
 | 
            } 
 | 
            if (dist > 0 && dist < 250) { 
 | 
                let id = mNodeList[i].id 
 | 
                mDxMap[id] = mDxMap[id] + distX / dist * k * k / dist * ejectFactor 
 | 
                mDyMap[id] = mDyMap[id] + distY / dist * k * k / dist * ejectFactor 
 | 
            } 
 | 
        } 
 | 
    } 
 | 
} 
 | 
  
 | 
/** 
 | 
 * 计算Edge的引力对两端Node产生的引力。 
 | 
 * Calculate the traction force generated by the edge acted on the two nodes of its two ends. 
 | 
 */ 
 | 
function calculateTraction() { 
 | 
    let condenseFactor = 3 
 | 
    let startNode, endNode 
 | 
    for (let e = 0; e < mEdgeList.length; e++) { 
 | 
        let eStartID = mEdgeList[e].source 
 | 
        let eEndID = mEdgeList[e].target 
 | 
        startNode = mNodeMap[eStartID] 
 | 
        endNode = mNodeMap[eEndID] 
 | 
        if (!startNode) { 
 | 
            console.log('Cannot find start node id: ' + eStartID + ', please check it out.') 
 | 
            return 
 | 
        } 
 | 
        if (!endNode) { 
 | 
            console.log('Cannot find end node id: ' + eEndID + ', please check it out.') 
 | 
            return 
 | 
        } 
 | 
        let distX, distY, dist 
 | 
        distX = startNode.x - endNode.x 
 | 
        distY = startNode.y - endNode.y 
 | 
        dist = Math.sqrt(distX * distX + distY * distY) 
 | 
        mDxMap[eStartID] = mDxMap[eStartID] - distX * dist / k * condenseFactor 
 | 
        mDyMap[eStartID] = mDyMap[eStartID] - distY * dist / k * condenseFactor 
 | 
        mDxMap[eEndID] = mDxMap[eEndID] + distX * dist / k * condenseFactor 
 | 
        mDyMap[eEndID] = mDyMap[eEndID] + distY * dist / k * condenseFactor 
 | 
    } 
 | 
} 
 | 
  
 | 
/** 
 | 
 * 更新坐标。 
 | 
 * update the coordinates. 
 | 
 */ 
 | 
function updateCoordinates() { 
 | 
    let maxt = 4, maxty = 3 // Additional coefficients. 
 | 
    for (let v = 0; v < mNodeList.length; v++) { 
 | 
        let node = mNodeList[v] 
 | 
        let dx = Math.floor(mDxMap[node.id]) 
 | 
        let dy = Math.floor(mDyMap[node.id]) 
 | 
  
 | 
        if (dx < -maxt) dx = -maxt 
 | 
        if (dx > maxt) dx = maxt 
 | 
        if (dy < -maxty) dy = -maxty 
 | 
        if (dy > maxty) dy = maxty 
 | 
        node.x = node.x + dx >= CANVAS_WIDTH || node.x + dx <= 0 ? node.x - dx : node.x + dx 
 | 
        node.y = node.y + dy >= CANVAS_HEIGHT || node.y + dy <= 0 ? node.y - dy : node.y + dy 
 | 
    } 
 | 
} 
 | 
  
 | 
function Result(nodes = null, links = null) { 
 | 
    this.nodes = nodes 
 | 
    this.links = links 
 | 
} 
 |