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.

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