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.

216 lines
7.9 KiB

  1. import BarnesHutSolver from './components/physics/BarnesHutSolver';
  2. import Repulsion from './components/physics/RepulsionSolver';
  3. import HierarchicalRepulsion from './components/physics/HierarchicalRepulsionSolver';
  4. import SpringSolver from './components/physics/SpringSolver';
  5. import HierarchicalSpringSolver from './components/physics/HierarchicalSpringSolver';
  6. import CentralGravitySolver from './components/physics/CentralGravitySolver';
  7. import ForceAtlas2BasedRepulsionSolver from './components/physics/FA2BasedRepulsionSolver';
  8. import ForceAtlas2BasedCentralGravitySolver from './components/physics/FA2BasedCentralGravitySolver';
  9. class PhysicsWorker {
  10. constructor(postMessage) {
  11. this.body = {};
  12. this.physicsBody = {physicsNodeIndices:[], physicsEdgeIndices:[], forces: {}, velocities: {}};
  13. this.postMessage = postMessage;
  14. this.options = {};
  15. this.stabilized = false;
  16. this.previousStates = {};
  17. this.positions = {};
  18. this.timestep = 0.5;
  19. }
  20. handleMessage(event) {
  21. var msg = event.data;
  22. switch (msg.type) {
  23. case 'calculateForces':
  24. this.calculateForces();
  25. this.moveNodes();
  26. this.postMessage({
  27. type: 'positions',
  28. data: {
  29. positions: this.positions,
  30. stabilized: this.stabilized
  31. }
  32. });
  33. break;
  34. case 'update':
  35. let node = this.body.nodes[msg.data.id];
  36. node.x = msg.data.x;
  37. node.y = msg.data.y;
  38. break;
  39. case 'options':
  40. this.options = msg.data;
  41. this.timestep = this.options.timestep;
  42. this.init();
  43. break;
  44. case 'physicsObjects':
  45. this.body.nodes = msg.data.nodes;
  46. this.body.edges = msg.data.edges;
  47. this.updatePhysicsData();
  48. break;
  49. default:
  50. console.warn('unknown message from PhysicsEngine', msg);
  51. }
  52. }
  53. /**
  54. * configure the engine.
  55. */
  56. init() {
  57. var options;
  58. if (this.options.solver === 'forceAtlas2Based') {
  59. options = this.options.forceAtlas2Based;
  60. this.nodesSolver = new ForceAtlas2BasedRepulsionSolver(this.body, this.physicsBody, options);
  61. this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
  62. this.gravitySolver = new ForceAtlas2BasedCentralGravitySolver(this.body, this.physicsBody, options);
  63. }
  64. else if (this.options.solver === 'repulsion') {
  65. options = this.options.repulsion;
  66. this.nodesSolver = new Repulsion(this.body, this.physicsBody, options);
  67. this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
  68. this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
  69. }
  70. else if (this.options.solver === 'hierarchicalRepulsion') {
  71. options = this.options.hierarchicalRepulsion;
  72. this.nodesSolver = new HierarchicalRepulsion(this.body, this.physicsBody, options);
  73. this.edgesSolver = new HierarchicalSpringSolver(this.body, this.physicsBody, options);
  74. this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
  75. }
  76. else { // barnesHut
  77. options = this.options.barnesHut;
  78. this.nodesSolver = new BarnesHutSolver(this.body, this.physicsBody, options);
  79. this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
  80. this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
  81. }
  82. this.modelOptions = options;
  83. }
  84. /**
  85. * Nodes and edges can have the physics toggles on or off. A collection of indices is created here so we can skip the check all the time.
  86. *
  87. * @private
  88. */
  89. updatePhysicsData() {
  90. this.physicsBody.forces = {};
  91. this.physicsBody.physicsNodeIndices = [];
  92. this.physicsBody.physicsEdgeIndices = [];
  93. let nodes = this.body.nodes;
  94. let edges = this.body.edges;
  95. // get node indices for physics
  96. for (let nodeId in nodes) {
  97. if (nodes.hasOwnProperty(nodeId)) {
  98. this.physicsBody.physicsNodeIndices.push(nodeId);
  99. this.positions[nodeId] = {
  100. x: nodes[nodeId].x,
  101. y: nodes[nodeId].y
  102. }
  103. }
  104. }
  105. // get edge indices for physics
  106. for (let edgeId in edges) {
  107. if (edges.hasOwnProperty(edgeId)) {
  108. this.physicsBody.physicsEdgeIndices.push(edgeId);
  109. }
  110. }
  111. // get the velocity and the forces vector
  112. for (let i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) {
  113. let nodeId = this.physicsBody.physicsNodeIndices[i];
  114. this.physicsBody.forces[nodeId] = {x: 0, y: 0};
  115. // forces can be reset because they are recalculated. Velocities have to persist.
  116. if (this.physicsBody.velocities[nodeId] === undefined) {
  117. this.physicsBody.velocities[nodeId] = {x: 0, y: 0};
  118. }
  119. }
  120. // clean deleted nodes from the velocity vector
  121. for (let nodeId in this.physicsBody.velocities) {
  122. if (nodes[nodeId] === undefined) {
  123. delete this.physicsBody.velocities[nodeId];
  124. }
  125. }
  126. // console.log(this.physicsBody);
  127. }
  128. /**
  129. * move the nodes one timestap and check if they are stabilized
  130. * @returns {boolean}
  131. */
  132. moveNodes() {
  133. var nodeIndices = this.physicsBody.physicsNodeIndices;
  134. var maxVelocity = this.options.maxVelocity ? this.options.maxVelocity : 1e9;
  135. var maxNodeVelocity = 0;
  136. for (let i = 0; i < nodeIndices.length; i++) {
  137. let nodeId = nodeIndices[i];
  138. let nodeVelocity = this._performStep(nodeId, maxVelocity);
  139. // stabilized is true if stabilized is true and velocity is smaller than vmin --> all nodes must be stabilized
  140. maxNodeVelocity = Math.max(maxNodeVelocity,nodeVelocity);
  141. }
  142. // evaluating the stabilized and adaptiveTimestepEnabled conditions
  143. this.stabilized = maxNodeVelocity < this.options.minVelocity;
  144. }
  145. /**
  146. * Perform the actual step
  147. *
  148. * @param nodeId
  149. * @param maxVelocity
  150. * @returns {number}
  151. * @private
  152. */
  153. _performStep(nodeId,maxVelocity) {
  154. let node = this.body.nodes[nodeId];
  155. let timestep = this.timestep;
  156. let forces = this.physicsBody.forces;
  157. let velocities = this.physicsBody.velocities;
  158. // store the state so we can revert
  159. this.previousStates[nodeId] = {x:node.x, y:node.y, vx:velocities[nodeId].x, vy:velocities[nodeId].y};
  160. if (node.options.fixed.x === false) {
  161. let dx = this.modelOptions.damping * velocities[nodeId].x; // damping force
  162. let ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration
  163. velocities[nodeId].x += ax * timestep; // velocity
  164. velocities[nodeId].x = (Math.abs(velocities[nodeId].x) > maxVelocity) ? ((velocities[nodeId].x > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].x;
  165. node.x += velocities[nodeId].x * timestep; // position
  166. this.positions[nodeId].x = node.x;
  167. }
  168. else {
  169. forces[nodeId].x = 0;
  170. velocities[nodeId].x = 0;
  171. }
  172. if (node.options.fixed.y === false) {
  173. let dy = this.modelOptions.damping * velocities[nodeId].y; // damping force
  174. let ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration
  175. velocities[nodeId].y += ay * timestep; // velocity
  176. velocities[nodeId].y = (Math.abs(velocities[nodeId].y) > maxVelocity) ? ((velocities[nodeId].y > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].y;
  177. node.y += velocities[nodeId].y * timestep; // position
  178. this.positions[nodeId].y = node.y;
  179. }
  180. else {
  181. forces[nodeId].y = 0;
  182. velocities[nodeId].y = 0;
  183. }
  184. let totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x,2) + Math.pow(velocities[nodeId].y,2));
  185. return totalVelocity;
  186. }
  187. /**
  188. * calculate the forces for one physics iteration.
  189. */
  190. calculateForces() {
  191. this.gravitySolver.solve();
  192. this.nodesSolver.solve();
  193. this.edgesSolver.solve();
  194. }
  195. }
  196. export default PhysicsWorker;