| /** | 
|  * 感谢 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 | 
| } |