force-directed.js 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. /**
  2. * 感谢 https://github.com/chaangliu/ForceDirectedLayout/blob/master/javascript/force-directed.js
  3. * A force directed graph layout implementation by liuchang on 2018/05/10.
  4. */
  5. const CANVAS_WIDTH = 1000
  6. const CANVAS_HEIGHT = 1000
  7. let k
  8. let mNodeList = []
  9. let mEdgeList = []
  10. let mDxMap = {}
  11. let mDyMap = {}
  12. let mNodeMap = {}
  13. export function ForceDirected(data = {}) {
  14. // generate nodes and edges
  15. // for (let i = 0; i < 20; i++) {
  16. // mNodeList.push(new Node(i))
  17. // }
  18. k = 0
  19. mNodeList = []
  20. mEdgeList = []
  21. mDxMap = {}
  22. mDyMap = {}
  23. mNodeMap = {}
  24. let nodeList = data.nodeList
  25. for (let i = 0; i < nodeList.length; i++) {
  26. let node = nodeList[i]
  27. mNodeList.push(node)
  28. }
  29. // for (let i = 0; i < 20; i++) {
  30. // let edgeCount = Math.random() * 8 + 1
  31. // for (let j = 0; j < edgeCount; j++) {
  32. // let targetId = Math.floor(Math.random() * 20)
  33. // let edge = new Edge(i, targetId)
  34. // mEdgeList.push(edge)
  35. // }
  36. // }
  37. // line 转 edge
  38. let lineList = data.lineList
  39. for (let i = 0; i < lineList.length; i++) {
  40. let line = lineList[i]
  41. let edge = new Edge(line.from, line.to)
  42. mEdgeList.push(edge)
  43. }
  44. if (mNodeList && mEdgeList) {
  45. k = Math.sqrt(CANVAS_WIDTH * CANVAS_HEIGHT / mNodeList.length)
  46. }
  47. for (let i = 0; i < mNodeList.length; i++) {
  48. let node = mNodeList[i]
  49. if (node) {
  50. mNodeMap[node.id] = node
  51. }
  52. }
  53. // 随机生成坐标. Generate coordinates randomly.
  54. let initialX, initialY, initialSize = 40.0
  55. for (let i in mNodeList) {
  56. initialX = CANVAS_WIDTH * 0.5
  57. initialY = CANVAS_HEIGHT * 0.5
  58. mNodeList[i].x = initialX + initialSize * (Math.random() - 0.5)
  59. mNodeList[i].y = initialY + initialSize * (Math.random() - 0.5)
  60. }
  61. // 迭代200次. Iterate 200 times.
  62. for (let i = 0; i < 200; i++) {
  63. calculateRepulsive()
  64. calculateTraction()
  65. updateCoordinates()
  66. }
  67. // console.log(JSON.stringify(new Result(mNodeList, mEdgeList)))
  68. // 坐标添加px
  69. for (let i = 0; i < mNodeList.length; i++) {
  70. let node = mNodeList[i]
  71. node.left = node.x + 'px'
  72. node.top = node.y + 'px'
  73. node.x = undefined
  74. node.y = undefined
  75. }
  76. data.nodeList = mNodeList
  77. // console.log(data)
  78. return data
  79. }
  80. function Node(id = null) {
  81. this.id = id
  82. this.x = 22
  83. this.y = null
  84. }
  85. function Edge(source = null, target = null) {
  86. this.source = source
  87. this.target = target
  88. }
  89. /**
  90. * 计算两个Node的斥力产生的单位位移。
  91. * Calculate the displacement generated by the repulsive force between two nodes.*
  92. */
  93. function calculateRepulsive() {
  94. let ejectFactor = 6
  95. let distX, distY, dist
  96. for (let i = 0; i < mNodeList.length; i++) {
  97. mDxMap[mNodeList[i].id] = 0.0
  98. mDyMap[mNodeList[i].id] = 0.0
  99. for (let j = 0; j < mNodeList.length; j++) {
  100. if (i !== j) {
  101. distX = mNodeList[i].x - mNodeList[j].x
  102. distY = mNodeList[i].y - mNodeList[j].y
  103. dist = Math.sqrt(distX * distX + distY * distY)
  104. }
  105. if (dist < 30) {
  106. ejectFactor = 5
  107. }
  108. if (dist > 0 && dist < 250) {
  109. let id = mNodeList[i].id
  110. mDxMap[id] = mDxMap[id] + distX / dist * k * k / dist * ejectFactor
  111. mDyMap[id] = mDyMap[id] + distY / dist * k * k / dist * ejectFactor
  112. }
  113. }
  114. }
  115. }
  116. /**
  117. * 计算Edge的引力对两端Node产生的引力。
  118. * Calculate the traction force generated by the edge acted on the two nodes of its two ends.
  119. */
  120. function calculateTraction() {
  121. let condenseFactor = 3
  122. let startNode, endNode
  123. for (let e = 0; e < mEdgeList.length; e++) {
  124. let eStartID = mEdgeList[e].source
  125. let eEndID = mEdgeList[e].target
  126. startNode = mNodeMap[eStartID]
  127. endNode = mNodeMap[eEndID]
  128. if (!startNode) {
  129. console.log('Cannot find start node id: ' + eStartID + ', please check it out.')
  130. return
  131. }
  132. if (!endNode) {
  133. console.log('Cannot find end node id: ' + eEndID + ', please check it out.')
  134. return
  135. }
  136. let distX, distY, dist
  137. distX = startNode.x - endNode.x
  138. distY = startNode.y - endNode.y
  139. dist = Math.sqrt(distX * distX + distY * distY)
  140. mDxMap[eStartID] = mDxMap[eStartID] - distX * dist / k * condenseFactor
  141. mDyMap[eStartID] = mDyMap[eStartID] - distY * dist / k * condenseFactor
  142. mDxMap[eEndID] = mDxMap[eEndID] + distX * dist / k * condenseFactor
  143. mDyMap[eEndID] = mDyMap[eEndID] + distY * dist / k * condenseFactor
  144. }
  145. }
  146. /**
  147. * 更新坐标。
  148. * update the coordinates.
  149. */
  150. function updateCoordinates() {
  151. let maxt = 4, maxty = 3 // Additional coefficients.
  152. for (let v = 0; v < mNodeList.length; v++) {
  153. let node = mNodeList[v]
  154. let dx = Math.floor(mDxMap[node.id])
  155. let dy = Math.floor(mDyMap[node.id])
  156. if (dx < -maxt) dx = -maxt
  157. if (dx > maxt) dx = maxt
  158. if (dy < -maxty) dy = -maxty
  159. if (dy > maxty) dy = maxty
  160. node.x = node.x + dx >= CANVAS_WIDTH || node.x + dx <= 0 ? node.x - dx : node.x + dx
  161. node.y = node.y + dy >= CANVAS_HEIGHT || node.y + dy <= 0 ? node.y - dy : node.y + dy
  162. }
  163. }
  164. function Result(nodes = null, links = null) {
  165. this.nodes = nodes
  166. this.links = links
  167. }