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.

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