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 each other 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 {Object} parentBranch
  54. * @param {vis.Node} 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 {number} distance
  91. * @param {number} dx
  92. * @param {number} dy
  93. * @param {vis.Node} node
  94. * @param {Object} 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 && node.shape.radius) {
  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 {Array<vis.Node>} nodes
  117. * @param {Array<number>} nodeIndices
  118. * @returns {{root: {centerOfMass: {x: number, y: number}, mass: number, range: {minX: number, maxX: number, minY: number, maxY: number}, size: number, calcSize: number, children: {data: null}, maxWidth: number, level: number, childrenCount: number}}} BarnesHutTree
  119. * @private
  120. */
  121. _formBarnesHutTree(nodes, nodeIndices) {
  122. let node;
  123. let nodeCount = nodeIndices.length;
  124. let minX = nodes[nodeIndices[0]].x;
  125. let minY = nodes[nodeIndices[0]].y;
  126. let maxX = nodes[nodeIndices[0]].x;
  127. let maxY = nodes[nodeIndices[0]].y;
  128. // get the range of the nodes
  129. for (let i = 1; i < nodeCount; i++) {
  130. let x = nodes[nodeIndices[i]].x;
  131. let y = nodes[nodeIndices[i]].y;
  132. if (nodes[nodeIndices[i]].options.mass > 0) {
  133. if (x < minX) {
  134. minX = x;
  135. }
  136. if (x > maxX) {
  137. maxX = x;
  138. }
  139. if (y < minY) {
  140. minY = y;
  141. }
  142. if (y > maxY) {
  143. maxY = y;
  144. }
  145. }
  146. }
  147. // make the range a square
  148. let sizeDiff = Math.abs(maxX - minX) - Math.abs(maxY - minY); // difference between X and Y
  149. if (sizeDiff > 0) {
  150. minY -= 0.5 * sizeDiff;
  151. maxY += 0.5 * sizeDiff;
  152. } // xSize > ySize
  153. else {
  154. minX += 0.5 * sizeDiff;
  155. maxX -= 0.5 * sizeDiff;
  156. } // xSize < ySize
  157. let minimumTreeSize = 1e-5;
  158. let rootSize = Math.max(minimumTreeSize, Math.abs(maxX - minX));
  159. let halfRootSize = 0.5 * rootSize;
  160. let centerX = 0.5 * (minX + maxX), centerY = 0.5 * (minY + maxY);
  161. // construct the barnesHutTree
  162. let barnesHutTree = {
  163. root: {
  164. centerOfMass: {x: 0, y: 0},
  165. mass: 0,
  166. range: {
  167. minX: centerX - halfRootSize, maxX: centerX + halfRootSize,
  168. minY: centerY - halfRootSize, maxY: centerY + halfRootSize
  169. },
  170. size: rootSize,
  171. calcSize: 1 / rootSize,
  172. children: {data: null},
  173. maxWidth: 0,
  174. level: 0,
  175. childrenCount: 4
  176. }
  177. };
  178. this._splitBranch(barnesHutTree.root);
  179. // place the nodes one by one recursively
  180. for (let i = 0; i < nodeCount; i++) {
  181. node = nodes[nodeIndices[i]];
  182. if (node.options.mass > 0) {
  183. this._placeInTree(barnesHutTree.root, node);
  184. }
  185. }
  186. // make global
  187. return barnesHutTree
  188. }
  189. /**
  190. * this updates the mass of a branch. this is increased by adding a node.
  191. *
  192. * @param {Object} parentBranch
  193. * @param {vis.Node} node
  194. * @private
  195. */
  196. _updateBranchMass(parentBranch, node) {
  197. let totalMass = parentBranch.mass + node.options.mass;
  198. let totalMassInv = 1 / totalMass;
  199. parentBranch.centerOfMass.x = parentBranch.centerOfMass.x * parentBranch.mass + node.x * node.options.mass;
  200. parentBranch.centerOfMass.x *= totalMassInv;
  201. parentBranch.centerOfMass.y = parentBranch.centerOfMass.y * parentBranch.mass + node.y * node.options.mass;
  202. parentBranch.centerOfMass.y *= totalMassInv;
  203. parentBranch.mass = totalMass;
  204. let biggestSize = Math.max(Math.max(node.height, node.radius), node.width);
  205. parentBranch.maxWidth = (parentBranch.maxWidth < biggestSize) ? biggestSize : parentBranch.maxWidth;
  206. }
  207. /**
  208. * determine in which branch the node will be placed.
  209. *
  210. * @param {Object} parentBranch
  211. * @param {vis.Node} node
  212. * @param {boolean} skipMassUpdate
  213. * @private
  214. */
  215. _placeInTree(parentBranch, node, skipMassUpdate) {
  216. if (skipMassUpdate != true || skipMassUpdate === undefined) {
  217. // update the mass of the branch.
  218. this._updateBranchMass(parentBranch, node);
  219. }
  220. if (parentBranch.children.NW.range.maxX > node.x) { // in NW or SW
  221. if (parentBranch.children.NW.range.maxY > node.y) { // in NW
  222. this._placeInRegion(parentBranch, node, "NW");
  223. }
  224. else { // in SW
  225. this._placeInRegion(parentBranch, node, "SW");
  226. }
  227. }
  228. else { // in NE or SE
  229. if (parentBranch.children.NW.range.maxY > node.y) { // in NE
  230. this._placeInRegion(parentBranch, node, "NE");
  231. }
  232. else { // in SE
  233. this._placeInRegion(parentBranch, node, "SE");
  234. }
  235. }
  236. }
  237. /**
  238. * actually place the node in a region (or branch)
  239. *
  240. * @param {Object} parentBranch
  241. * @param {vis.Node} node
  242. * @param {'NW'| 'NE' | 'SW' | 'SE'} region
  243. * @private
  244. */
  245. _placeInRegion(parentBranch, node, region) {
  246. switch (parentBranch.children[region].childrenCount) {
  247. case 0: // place node here
  248. parentBranch.children[region].children.data = node;
  249. parentBranch.children[region].childrenCount = 1;
  250. this._updateBranchMass(parentBranch.children[region], node);
  251. break;
  252. case 1: // convert into children
  253. // if there are two nodes exactly overlapping (on init, on opening of cluster etc.)
  254. // we move one node a little bit and we do not put it in the tree.
  255. if (parentBranch.children[region].children.data.x === node.x &&
  256. parentBranch.children[region].children.data.y === node.y) {
  257. node.x += this.seededRandom();
  258. node.y += this.seededRandom();
  259. }
  260. else {
  261. this._splitBranch(parentBranch.children[region]);
  262. this._placeInTree(parentBranch.children[region], node);
  263. }
  264. break;
  265. case 4: // place in branch
  266. this._placeInTree(parentBranch.children[region], node);
  267. break;
  268. }
  269. }
  270. /**
  271. * this function splits a branch into 4 sub branches. If the branch contained a node, we place it in the subbranch
  272. * after the split is complete.
  273. *
  274. * @param {Object} parentBranch
  275. * @private
  276. */
  277. _splitBranch(parentBranch) {
  278. // if the branch is shaded with a node, replace the node in the new subset.
  279. let containedNode = null;
  280. if (parentBranch.childrenCount === 1) {
  281. containedNode = parentBranch.children.data;
  282. parentBranch.mass = 0;
  283. parentBranch.centerOfMass.x = 0;
  284. parentBranch.centerOfMass.y = 0;
  285. }
  286. parentBranch.childrenCount = 4;
  287. parentBranch.children.data = null;
  288. this._insertRegion(parentBranch, "NW");
  289. this._insertRegion(parentBranch, "NE");
  290. this._insertRegion(parentBranch, "SW");
  291. this._insertRegion(parentBranch, "SE");
  292. if (containedNode != null) {
  293. this._placeInTree(parentBranch, containedNode);
  294. }
  295. }
  296. /**
  297. * This function subdivides the region into four new segments.
  298. * Specifically, this inserts a single new segment.
  299. * It fills the children section of the parentBranch
  300. *
  301. * @param {Object} parentBranch
  302. * @param {'NW'| 'NE' | 'SW' | 'SE'} region
  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 {CanvasRenderingContext2D} ctx
  351. * @param {string} 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 {Object} branch
  364. * @param {CanvasRenderingContext2D} ctx
  365. * @param {string} 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;