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.

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