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.

397 lines
12 KiB

10 years ago
10 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. if (this.constants.physics.barnesHut.gravitationalConstant != 0) {
  13. var node;
  14. var nodes = this.calculationNodes;
  15. var nodeIndices = this.calculationNodeIndices;
  16. var nodeCount = nodeIndices.length;
  17. this._formBarnesHutTree(nodes,nodeIndices);
  18. var barnesHutTree = this.barnesHutTree;
  19. // place the nodes one by one recursively
  20. for (var i = 0; i < nodeCount; i++) {
  21. node = nodes[nodeIndices[i]];
  22. // starting with root is irrelevant, it never passes the BarnesHut condition
  23. this._getForceContribution(barnesHutTree.root.children.NW,node);
  24. this._getForceContribution(barnesHutTree.root.children.NE,node);
  25. this._getForceContribution(barnesHutTree.root.children.SW,node);
  26. this._getForceContribution(barnesHutTree.root.children.SE,node);
  27. }
  28. }
  29. },
  30. /**
  31. * This function traverses the barnesHutTree. It checks when it can approximate distant nodes with their center of mass.
  32. * If a region contains a single node, we check if it is not itself, then we apply the force.
  33. *
  34. * @param parentBranch
  35. * @param node
  36. * @private
  37. */
  38. _getForceContribution : function(parentBranch,node) {
  39. // we get no force contribution from an empty region
  40. if (parentBranch.childrenCount > 0) {
  41. var dx,dy,distance;
  42. // get the distance from the center of mass to the node.
  43. dx = parentBranch.centerOfMass.x - node.x;
  44. dy = parentBranch.centerOfMass.y - node.y;
  45. distance = Math.sqrt(dx * dx + dy * dy);
  46. // BarnesHut condition
  47. // original condition : s/d < theta = passed === d/s > 1/theta = passed
  48. // calcSize = 1/s --> d * 1/s > 1/theta = passed
  49. if (distance * parentBranch.calcSize > this.constants.physics.barnesHut.theta) {
  50. // duplicate code to reduce function calls to speed up program
  51. if (distance == 0) {
  52. distance = 0.1*Math.random();
  53. dx = distance;
  54. }
  55. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance);
  56. var fx = dx * gravityForce;
  57. var fy = dy * gravityForce;
  58. node.fx += fx;
  59. node.fy += fy;
  60. }
  61. else {
  62. // Did not pass the condition, go into children if available
  63. if (parentBranch.childrenCount == 4) {
  64. this._getForceContribution(parentBranch.children.NW,node);
  65. this._getForceContribution(parentBranch.children.NE,node);
  66. this._getForceContribution(parentBranch.children.SW,node);
  67. this._getForceContribution(parentBranch.children.SE,node);
  68. }
  69. else { // parentBranch must have only one node, if it was empty we wouldnt be here
  70. if (parentBranch.children.data.id != node.id) { // if it is not self
  71. // duplicate code to reduce function calls to speed up program
  72. if (distance == 0) {
  73. distance = 0.5*Math.random();
  74. dx = distance;
  75. }
  76. var gravityForce = this.constants.physics.barnesHut.gravitationalConstant * parentBranch.mass * node.mass / (distance * distance * distance);
  77. var fx = dx * gravityForce;
  78. var fy = dy * gravityForce;
  79. node.fx += fx;
  80. node.fy += fy;
  81. }
  82. }
  83. }
  84. }
  85. },
  86. /**
  87. * This function constructs the barnesHut tree recursively. It creates the root, splits it and starts placing the nodes.
  88. *
  89. * @param nodes
  90. * @param nodeIndices
  91. * @private
  92. */
  93. _formBarnesHutTree : function(nodes,nodeIndices) {
  94. var node;
  95. var nodeCount = nodeIndices.length;
  96. var minX = Number.MAX_VALUE,
  97. minY = Number.MAX_VALUE,
  98. maxX =-Number.MAX_VALUE,
  99. maxY =-Number.MAX_VALUE;
  100. // get the range of the nodes
  101. for (var i = 0; i < nodeCount; i++) {
  102. var x = nodes[nodeIndices[i]].x;
  103. var y = nodes[nodeIndices[i]].y;
  104. if (x < minX) { minX = x; }
  105. if (x > maxX) { maxX = x; }
  106. if (y < minY) { minY = y; }
  107. if (y > maxY) { maxY = y; }
  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 = {root:{
  119. centerOfMass:{x:0,y:0}, // Center of Mass
  120. mass:0,
  121. range: {minX:centerX-halfRootSize,maxX:centerX+halfRootSize,
  122. minY:centerY-halfRootSize,maxY:centerY+halfRootSize},
  123. size: rootSize,
  124. calcSize: 1 / rootSize,
  125. children: {data:null},
  126. maxWidth: 0,
  127. level: 0,
  128. childrenCount: 4
  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. _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. _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. _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. _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. _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. _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. _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. }
  350. };