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.

372 lines
12 KiB

11 years ago
  1. /**
  2. * Created by Alex on 2/10/14.
  3. */
  4. var barnesHutMixin = {
  5. /**
  6. * This function calculates the forces the nodes apply on eachother based on a gravitational model.
  7. * The Barnes Hut method is used to speed up this N-body simulation.
  8. *
  9. * @private
  10. */
  11. _calculateNodeForces : function() {
  12. var node;
  13. var nodes = this.calculationNodes;
  14. var nodeIndices = this.calculationNodeIndices;
  15. var nodeCount = nodeIndices.length;
  16. this._formBarnesHutTree(nodes,nodeIndices);
  17. var barnesHutTree = this.barnesHutTree;
  18. // place the nodes one by one recursively
  19. for (var i = 0; i < nodeCount; i++) {
  20. node = nodes[nodeIndices[i]];
  21. // starting with root is irrelevant, it never passes the BarnesHut condition
  22. this._getForceContribution(barnesHutTree.root.children.NW,node);
  23. this._getForceContribution(barnesHutTree.root.children.NE,node);
  24. this._getForceContribution(barnesHutTree.root.children.SW,node);
  25. this._getForceContribution(barnesHutTree.root.children.SE,node);
  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. _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 < theta = 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.theta) {
  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.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.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. _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 (x < minX) { minX = x; }
  103. if (x > maxX) { maxX = x; }
  104. if (y < minY) { minY = y; }
  105. if (y > maxY) { maxY = y; }
  106. }
  107. // make the range a square
  108. var sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
  109. if (sizeDiff > 0) {minY -= 0.5 * sizeDiff; maxY += 0.5 * sizeDiff;} // xSize > ySize
  110. else {minX += 0.5 * sizeDiff; maxX -= 0.5 * sizeDiff;} // xSize < ySize
  111. var minimumTreeSize = 1e-5;
  112. var rootSize = Math.max(minimumTreeSize,Math.abs(maxX - minX));
  113. var halfRootSize = 0.5 * rootSize;
  114. var centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
  115. // construct the barnesHutTree
  116. var barnesHutTree = {root:{
  117. centerOfMass:{x:0,y:0}, // Center of Mass
  118. mass:0,
  119. range: {minX:centerX-halfRootSize,maxX:centerX+halfRootSize,
  120. minY:centerY-halfRootSize,maxY:centerY+halfRootSize},
  121. size: rootSize,
  122. calcSize: 1 / rootSize,
  123. children: {data:null},
  124. maxWidth: 0,
  125. level: 0,
  126. childrenCount: 4
  127. }};
  128. this._splitBranch(barnesHutTree.root);
  129. // place the nodes one by one recursively
  130. for (i = 0; i < nodeCount; i++) {
  131. node = nodes[nodeIndices[i]];
  132. this._placeInTree(barnesHutTree.root,node);
  133. }
  134. // make global
  135. this.barnesHutTree = barnesHutTree
  136. },
  137. _updateBranchMass : function(parentBranch, node) {
  138. var totalMass = parentBranch.mass + node.mass;
  139. var totalMassInv = 1/totalMass;
  140. parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.mass;
  141. parentBranch.centerOfMass.x *= totalMassInv;
  142. parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.mass;
  143. parentBranch.centerOfMass.y *= totalMassInv;
  144. parentBranch.mass = totalMass;
  145. var biggestSize = Math.max(Math.max(node.height,node.radius),node.width);
  146. parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
  147. },
  148. _placeInTree : function(parentBranch,node,skipMassUpdate) {
  149. if (skipMassUpdate != true || skipMassUpdate === undefined) {
  150. // update the mass of the branch.
  151. this._updateBranchMass(parentBranch,node);
  152. }
  153. if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
  154. if (parentBranch.children.NW.range.maxY > node.y) { // in NW
  155. this._placeInRegion(parentBranch,node,"NW");
  156. }
  157. else { // in SW
  158. this._placeInRegion(parentBranch,node,"SW");
  159. }
  160. }
  161. else { // in NE or SE
  162. if (parentBranch.children.NW.range.maxY > node.y) { // in NE
  163. this._placeInRegion(parentBranch,node,"NE");
  164. }
  165. else { // in SE
  166. this._placeInRegion(parentBranch,node,"SE");
  167. }
  168. }
  169. },
  170. _placeInRegion : function(parentBranch,node,region) {
  171. switch (parentBranch.children[region].childrenCount) {
  172. case 0: // place node here
  173. parentBranch.children[region].children.data = node;
  174. parentBranch.children[region].childrenCount = 1;
  175. this._updateBranchMass(parentBranch.children[region],node);
  176. break;
  177. case 1: // convert into children
  178. // if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
  179. // we move one node a pixel and we do not put it in the tree.
  180. if (parentBranch.children[region].children.data.x == node.x &&
  181. parentBranch.children[region].children.data.y == node.y) {
  182. node.x += Math.random();
  183. node.y += Math.random();
  184. }
  185. else {
  186. this._splitBranch(parentBranch.children[region]);
  187. this._placeInTree(parentBranch.children[region],node);
  188. }
  189. break;
  190. case 4: // place in branch
  191. this._placeInTree(parentBranch.children[region],node);
  192. break;
  193. }
  194. },
  195. /**
  196. * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
  197. * after the split is complete.
  198. *
  199. * @param parentBranch
  200. * @private
  201. */
  202. _splitBranch : function(parentBranch) {
  203. // if the branch is filled with a node, replace the node in the new subset.
  204. var containedNode = null;
  205. if (parentBranch.childrenCount == 1) {
  206. containedNode = parentBranch.children.data;
  207. parentBranch.mass = 0; parentBranch.centerOfMass.x = 0; parentBranch.centerOfMass.y = 0;
  208. }
  209. parentBranch.childrenCount = 4;
  210. parentBranch.children.data = null;
  211. this._insertRegion(parentBranch,"NW");
  212. this._insertRegion(parentBranch,"NE");
  213. this._insertRegion(parentBranch,"SW");
  214. this._insertRegion(parentBranch,"SE");
  215. if (containedNode != null) {
  216. this._placeInTree(parentBranch,containedNode);
  217. }
  218. },
  219. /**
  220. * This function subdivides the region into four new segments.
  221. * Specifically, this inserts a single new segment.
  222. * It fills the children section of the parentBranch
  223. *
  224. * @param parentBranch
  225. * @param region
  226. * @param parentRange
  227. * @private
  228. */
  229. _insertRegion : function(parentBranch, region) {
  230. var minX,maxX,minY,maxY;
  231. var childSize = 0.5 * parentBranch.size;
  232. switch (region) {
  233. case "NW":
  234. minX = parentBranch.range.minX;
  235. maxX = parentBranch.range.minX + childSize;
  236. minY = parentBranch.range.minY;
  237. maxY = parentBranch.range.minY + childSize;
  238. break;
  239. case "NE":
  240. minX = parentBranch.range.minX + childSize;
  241. maxX = parentBranch.range.maxX;
  242. minY = parentBranch.range.minY;
  243. maxY = parentBranch.range.minY + childSize;
  244. break;
  245. case "SW":
  246. minX = parentBranch.range.minX;
  247. maxX = parentBranch.range.minX + childSize;
  248. minY = parentBranch.range.minY + childSize;
  249. maxY = parentBranch.range.maxY;
  250. break;
  251. case "SE":
  252. minX = parentBranch.range.minX + childSize;
  253. maxX = parentBranch.range.maxX;
  254. minY = parentBranch.range.minY + childSize;
  255. maxY = parentBranch.range.maxY;
  256. break;
  257. }
  258. parentBranch.children[region] = {
  259. centerOfMass:{x:0,y:0},
  260. mass:0,
  261. range:{minX:minX,maxX:maxX,minY:minY,maxY:maxY},
  262. size: 0.5 * parentBranch.size,
  263. calcSize: 2 * parentBranch.calcSize,
  264. children: {data:null},
  265. maxWidth: 0,
  266. level: parentBranch.level+1,
  267. childrenCount: 0
  268. };
  269. },
  270. /**
  271. * This function is for debugging purposed, it draws the tree.
  272. *
  273. * @param ctx
  274. * @param color
  275. * @private
  276. */
  277. _drawTree : function(ctx,color) {
  278. if (this.barnesHutTree !== undefined) {
  279. ctx.lineWidth = 1;
  280. this._drawBranch(this.barnesHutTree.root,ctx,color);
  281. }
  282. },
  283. /**
  284. * This function is for debugging purposes. It draws the branches recursively.
  285. *
  286. * @param branch
  287. * @param ctx
  288. * @param color
  289. * @private
  290. */
  291. _drawBranch : function(branch,ctx,color) {
  292. if (color === undefined) {
  293. color = "#FF0000";
  294. }
  295. if (branch.childrenCount == 4) {
  296. this._drawBranch(branch.children.NW,ctx);
  297. this._drawBranch(branch.children.NE,ctx);
  298. this._drawBranch(branch.children.SE,ctx);
  299. this._drawBranch(branch.children.SW,ctx);
  300. }
  301. ctx.strokeStyle = color;
  302. ctx.beginPath();
  303. ctx.moveTo(branch.range.minX,branch.range.minY);
  304. ctx.lineTo(branch.range.maxX,branch.range.minY);
  305. ctx.stroke();
  306. ctx.beginPath();
  307. ctx.moveTo(branch.range.maxX,branch.range.minY);
  308. ctx.lineTo(branch.range.maxX,branch.range.maxY);
  309. ctx.stroke();
  310. ctx.beginPath();
  311. ctx.moveTo(branch.range.maxX,branch.range.maxY);
  312. ctx.lineTo(branch.range.minX,branch.range.maxY);
  313. ctx.stroke();
  314. ctx.beginPath();
  315. ctx.moveTo(branch.range.minX,branch.range.maxY);
  316. ctx.lineTo(branch.range.minX,branch.range.minY);
  317. ctx.stroke();
  318. /*
  319. if (branch.mass > 0) {
  320. ctx.circle(branch.centerOfMass.x, branch.centerOfMass.y, 3*branch.mass);
  321. ctx.stroke();
  322. }
  323. */
  324. }
  325. };