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.

464 lines
14 KiB

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