vis.js is a dynamic, browser-based visualization library
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

399 lines
12 KiB

  1. /**
  2. * This function calculates the forces the nodes apply on eachother based on a gravitational model.
  3. * The Barnes Hut method is used to speed up this N-body simulation.
  4. *
  5. * @private
  6. */
  7. exports._calculateNodeForces = function() {
  8. if (this.constants.physics.barnesHut.gravitationalConstant != 0) {
  9. var node;
  10. var nodes = this.calculationNodes;
  11. var nodeIndices = this.calculationNodeIndices;
  12. var nodeCount = nodeIndices.length;
  13. this._formBarnesHutTree(nodes,nodeIndices);
  14. var barnesHutTree = this.barnesHutTree;
  15. // place the nodes one by one recursively
  16. for (var i = 0; i < nodeCount; i++) {
  17. node = nodes[nodeIndices[i]];
  18. if (node.options.mass > 0) {
  19. // starting with root is irrelevant, it never passes the BarnesHut condition
  20. this._getForceContribution(barnesHutTree.root.children.NW,node);
  21. this._getForceContribution(barnesHutTree.root.children.NE,node);
  22. this._getForceContribution(barnesHutTree.root.children.SW,node);
  23. this._getForceContribution(barnesHutTree.root.children.SE,node);
  24. }
  25. }
  26. }
  27. };
  28. /**
  29. * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
  30. * If a region contains a single node, we check if it is not itself, then we apply the force.
  31. *
  32. * @param parentBranch
  33. * @param node
  34. * @private
  35. */
  36. exports._getForceContribution = function(parentBranch,node) {
  37. // we get no force contribution from an empty region
  38. if (parentBranch.childrenCount > 0) {
  39. var dx,dy,distance;
  40. // get the distance from the center of mass to the node.
  41. dx = parentBranch.centerOfMass.x - node.x;
  42. dy = parentBranch.centerOfMass.y - node.y;
  43. distance = Math.sqrt(dx * dx + dy * dy);
  44. // BarnesHut condition
  45. // original condition : s/d < thetaInverted = passed === d/s > 1/theta = passed
  46. // calcSize = 1/s --> d * 1/s > 1/theta = passed
  47. if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.thetaInverted) {
  48. // duplicate code to reduce function calls to speed up program
  49. if (distance == 0) {
  50. distance = 0.1*Math.random();
  51. dx = distance;
  52. }
  53. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  54. var fx = dx * gravityForce;
  55. var fy = dy * gravityForce;
  56. node.fx += fx;
  57. node.fy += fy;
  58. }
  59. else {
  60. // Did not pass the condition, go into children if available
  61. if (parentBranch.childrenCount == 4) {
  62. this._getForceContribution(parentBranch.children.NW,node);
  63. this._getForceContribution(parentBranch.children.NE,node);
  64. this._getForceContribution(parentBranch.children.SW,node);
  65. this._getForceContribution(parentBranch.children.SE,node);
  66. }
  67. else { // parentBranch must have only one node, if it was empty we wouldnt be here
  68. if (parentBranch.children.data.id != node.id) { // if it is not self
  69. // duplicate code to reduce function calls to speed up program
  70. if (distance == 0) {
  71. distance = 0.5*Math.random();
  72. dx = distance;
  73. }
  74. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.options.mass / (distance * distance * distance);
  75. var fx = dx * gravityForce;
  76. var fy = dy * gravityForce;
  77. node.fx += fx;
  78. node.fy += fy;
  79. }
  80. }
  81. }
  82. }
  83. };
  84. /**
  85. * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
  86. *
  87. * @param nodes
  88. * @param nodeIndices
  89. * @private
  90. */
  91. exports._formBarnesHutTree = function(nodes,nodeIndices) {
  92. var node;
  93. var nodeCount = nodeIndices.length;
  94. var minX = Number.MAX_VALUE,
  95. minY = Number.MAX_VALUE,
  96. maxX =-Number.MAX_VALUE,
  97. maxY =-Number.MAX_VALUE;
  98. // get the range of the nodes
  99. for (var i = 0; i < nodeCount; i++) {
  100. var x = nodes[nodeIndices[i]].x;
  101. var y = nodes[nodeIndices[i]].y;
  102. if (nodes[nodeIndices[i]].options.mass > 0) {
  103. if (x < minX) { minX = x; }
  104. if (x > maxX) { maxX = x; }
  105. if (y < minY) { minY = y; }
  106. if (y > maxY) { maxY = y; }
  107. }
  108. }
  109. // make the range a square
  110. var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
  111. if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
  112. else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
  113. var minimumTreeSize = 1e-5;
  114. var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
  115. var halfRootSize = 0.5 * rootSize;
  116. var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
  117. // construct the barnesHutTree
  118. var barnesHutTree = {
  119. root:{
  120. centerOfMass: {x:0, y:0},
  121. mass:0,
  122. range: {
  123. minX: centerX-halfRootSize,maxX:centerX+halfRootSize,
  124. minY: centerY-halfRootSize,maxY:centerY+halfRootSize
  125. },
  126. size: rootSize,
  127. calcSize: 1 / rootSize,
  128. children: { data:null},
  129. maxWidth: 0,
  130. level: 0,
  131. childrenCount: 4
  132. }
  133. };
  134. this._splitBranch(barnesHutTree.root);
  135. // place the nodes one by one recursively
  136. for (i = 0; i < nodeCount; i++) {
  137. node = nodes[nodeIndices[i]];
  138. if (node.options.mass > 0) {
  139. this._placeInTree(barnesHutTree.root,node);
  140. }
  141. }
  142. // make global
  143. this.barnesHutTree = barnesHutTree
  144. };
  145. /**
  146. * this updates the mass of a branch. this is increased by adding a node.
  147. *
  148. * @param parentBranch
  149. * @param node
  150. * @private
  151. */
  152. exports._updateBranchMass = function(parentBranch, node) {
  153. var totalMass = parentBranch.mass + node.options.mass;
  154. var totalMassInv = 1/totalMass;
  155. parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
  156. parentBranch.centerOfMass.x *= totalMassInv;
  157. parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
  158. parentBranch.centerOfMass.y *= totalMassInv;
  159. parentBranch.mass = totalMass;
  160. var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
  161. parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
  162. };
  163. /**
  164. * determine in which branch the node will be placed.
  165. *
  166. * @param parentBranch
  167. * @param node
  168. * @param skipMassUpdate
  169. * @private
  170. */
  171. exports._placeInTree = function(parentBranch,node,skipMassUpdate) {
  172. if (skipMassUpdate != true || skipMassUpdate === undefined) {
  173. // update the mass of the branch.
  174. this._updateBranchMass(parentBranch,node);
  175. }
  176. if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
  177. if (parentBranch.children.NW.range.maxY > node.y) { // in NW
  178. this._placeInRegion(parentBranch,node,"NW");
  179. }
  180. else { // in SW
  181. this._placeInRegion(parentBranch,node,"SW");
  182. }
  183. }
  184. else { // in NE or SE
  185. if (parentBranch.children.NW.range.maxY > node.y) { // in NE
  186. this._placeInRegion(parentBranch,node,"NE");
  187. }
  188. else { // in SE
  189. this._placeInRegion(parentBranch,node,"SE");
  190. }
  191. }
  192. };
  193. /**
  194. * actually place the node in a region (or branch)
  195. *
  196. * @param parentBranch
  197. * @param node
  198. * @param region
  199. * @private
  200. */
  201. exports._placeInRegion = function(parentBranch,node,region) {
  202. switch (parentBranch.children[region].childrenCount) {
  203. case 0: // place node here
  204. parentBranch.children[region].children.data = node;
  205. parentBranch.children[region].childrenCount = 1;
  206. this._updateBranchMass(parentBranch.children[region],node);
  207. break;
  208. case 1: // convert into children
  209. // if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
  210. // we move one node a pixel and we do not put it in the tree.
  211. if (parentBranch.children[region].children.data.x == node.x &&
  212. parentBranch.children[region].children.data.y == node.y) {
  213. node.x += Math.random();
  214. node.y += Math.random();
  215. }
  216. else {
  217. this._splitBranch(parentBranch.children[region]);
  218. this._placeInTree(parentBranch.children[region],node);
  219. }
  220. break;
  221. case 4: // place in branch
  222. this._placeInTree(parentBranch.children[region],node);
  223. break;
  224. }
  225. };
  226. /**
  227. * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
  228. * after the split is complete.
  229. *
  230. * @param parentBranch
  231. * @private
  232. */
  233. exports._splitBranch = function(parentBranch) {
  234. // if the branch is shaded with a node, replace the node in the new subset.
  235. var containedNode = null;
  236. if (parentBranch.childrenCount == 1) {
  237. containedNode = parentBranch.children.data;
  238. parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
  239. }
  240. parentBranch.childrenCount = 4;
  241. parentBranch.children.data = null;
  242. this._insertRegion(parentBranch,"NW");
  243. this._insertRegion(parentBranch,"NE");
  244. this._insertRegion(parentBranch,"SW");
  245. this._insertRegion(parentBranch,"SE");
  246. if (containedNode != null) {
  247. this._placeInTree(parentBranch,containedNode);
  248. }
  249. };
  250. /**
  251. * This function subdivides the region into four new segments.
  252. * Specifically, this inserts a single new segment.
  253. * It fills the children section of the parentBranch
  254. *
  255. * @param parentBranch
  256. * @param region
  257. * @param parentRange
  258. * @private
  259. */
  260. exports._insertRegion = function(parentBranch, region) {
  261. var minX,maxX,minY,maxY;
  262. var childSize = 0.5 * parentBranch.size;
  263. switch (region) {
  264. case "NW":
  265. minX = parentBranch.range.minX;
  266. maxX = parentBranch.range.minX + childSize;
  267. minY = parentBranch.range.minY;
  268. maxY = parentBranch.range.minY + childSize;
  269. break;
  270. case "NE":
  271. minX = parentBranch.range.minX + childSize;
  272. maxX = parentBranch.range.maxX;
  273. minY = parentBranch.range.minY;
  274. maxY = parentBranch.range.minY + childSize;
  275. break;
  276. case "SW":
  277. minX = parentBranch.range.minX;
  278. maxX = parentBranch.range.minX + childSize;
  279. minY = parentBranch.range.minY + childSize;
  280. maxY = parentBranch.range.maxY;
  281. break;
  282. case "SE":
  283. minX = parentBranch.range.minX + childSize;
  284. maxX = parentBranch.range.maxX;
  285. minY = parentBranch.range.minY + childSize;
  286. maxY = parentBranch.range.maxY;
  287. break;
  288. }
  289. parentBranch.children[region] = {
  290. centerOfMass:{x:0,y:0},
  291. mass:0,
  292. range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
  293. size: 0.5 * parentBranch.size,
  294. calcSize: 2 * parentBranch.calcSize,
  295. children: {data:null},
  296. maxWidth: 0,
  297. level: parentBranch.level+1,
  298. childrenCount: 0
  299. };
  300. };
  301. /**
  302. * This function is for debugging purposed, it draws the tree.
  303. *
  304. * @param ctx
  305. * @param color
  306. * @private
  307. */
  308. exports._drawTree = function(ctx,color) {
  309. if (this.barnesHutTree !== undefined) {
  310. ctx.lineWidth = 1;
  311. this._drawBranch(this.barnesHutTree.root,ctx,color);
  312. }
  313. };
  314. /**
  315. * This function is for debugging purposes. It draws the branches recursively.
  316. *
  317. * @param branch
  318. * @param ctx
  319. * @param color
  320. * @private
  321. */
  322. exports._drawBranch = function(branch,ctx,color) {
  323. if (color === undefined) {
  324. color = "#FF0000";
  325. }
  326. if (branch.childrenCount == 4) {
  327. this._drawBranch(branch.children.NW,ctx);
  328. this._drawBranch(branch.children.NE,ctx);
  329. this._drawBranch(branch.children.SE,ctx);
  330. this._drawBranch(branch.children.SW,ctx);
  331. }
  332. ctx.strokeStyle = color;
  333. ctx.beginPath();
  334. ctx.moveTo(branch.range.minX,branch.range.minY);
  335. ctx.lineTo(branch.range.maxX,branch.range.minY);
  336. ctx.stroke();
  337. ctx.beginPath();
  338. ctx.moveTo(branch.range.maxX,branch.range.minY);
  339. ctx.lineTo(branch.range.maxX,branch.range.maxY);
  340. ctx.stroke();
  341. ctx.beginPath();
  342. ctx.moveTo(branch.range.maxX,branch.range.maxY);
  343. ctx.lineTo(branch.range.minX,branch.range.maxY);
  344. ctx.stroke();
  345. ctx.beginPath();
  346. ctx.moveTo(branch.range.minX,branch.range.maxY);
  347. ctx.lineTo(branch.range.minX,branch.range.minY);
  348. ctx.stroke();
  349. /*
  350. if (branch.mass > 0) {
  351. ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
  352. ctx.stroke();
  353. }
  354. */
  355. };