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.

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