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.

758 lines
24 KiB

9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
9 years ago
  1. var BarnesHutSolver = require('./components/physics/BarnesHutSolver').default;
  2. var Repulsion = require('./components/physics/RepulsionSolver').default;
  3. var HierarchicalRepulsion = require('./components/physics/HierarchicalRepulsionSolver').default;
  4. var SpringSolver = require('./components/physics/SpringSolver').default;
  5. var HierarchicalSpringSolver = require('./components/physics/HierarchicalSpringSolver').default;
  6. var CentralGravitySolver = require('./components/physics/CentralGravitySolver').default;
  7. var ForceAtlas2BasedRepulsionSolver = require('./components/physics/FA2BasedRepulsionSolver').default;
  8. var ForceAtlas2BasedCentralGravitySolver = require('./components/physics/FA2BasedCentralGravitySolver').default;
  9. var util = require('../../util');
  10. /**
  11. * @class PhysicsEngine
  12. */
  13. class PhysicsEngine {
  14. /**
  15. * @param {Object} body
  16. * @constructor PhysicsEngine
  17. */
  18. constructor(body) {
  19. this.body = body;
  20. this.physicsBody = {physicsNodeIndices:[], physicsEdgeIndices:[], forces: {}, velocities: {}};
  21. this.physicsEnabled = true;
  22. this.simulationInterval = 1000 / 60;
  23. this.requiresTimeout = true;
  24. this.previousStates = {};
  25. this.referenceState = {};
  26. this.freezeCache = {};
  27. this.renderTimer = undefined;
  28. // parameters for the adaptive timestep
  29. this.adaptiveTimestep = false;
  30. this.adaptiveTimestepEnabled = false;
  31. this.adaptiveCounter = 0;
  32. this.adaptiveInterval = 3;
  33. this.stabilized = false;
  34. this.startedStabilization = false;
  35. this.stabilizationIterations = 0;
  36. this.ready = false; // will be set to true if the stabilize
  37. // default options
  38. this.options = {};
  39. this.defaultOptions = {
  40. enabled: true,
  41. barnesHut: {
  42. theta: 0.5,
  43. gravitationalConstant: -2000,
  44. centralGravity: 0.3,
  45. springLength: 95,
  46. springConstant: 0.04,
  47. damping: 0.09,
  48. avoidOverlap: 0
  49. },
  50. forceAtlas2Based: {
  51. theta: 0.5,
  52. gravitationalConstant: -50,
  53. centralGravity: 0.01,
  54. springConstant: 0.08,
  55. springLength: 100,
  56. damping: 0.4,
  57. avoidOverlap: 0
  58. },
  59. repulsion: {
  60. centralGravity: 0.2,
  61. springLength: 200,
  62. springConstant: 0.05,
  63. nodeDistance: 100,
  64. damping: 0.09,
  65. avoidOverlap: 0
  66. },
  67. hierarchicalRepulsion: {
  68. centralGravity: 0.0,
  69. springLength: 100,
  70. springConstant: 0.01,
  71. nodeDistance: 120,
  72. damping: 0.09
  73. },
  74. maxVelocity: 50,
  75. minVelocity: 0.75, // px/s
  76. solver: 'barnesHut',
  77. stabilization: {
  78. enabled: true,
  79. iterations: 1000, // maximum number of iteration to stabilize
  80. updateInterval: 50,
  81. onlyDynamicEdges: false,
  82. fit: true
  83. },
  84. timestep: 0.5,
  85. adaptiveTimestep: true
  86. };
  87. util.extend(this.options, this.defaultOptions);
  88. this.timestep = 0.5;
  89. this.layoutFailed = false;
  90. this.bindEventListeners();
  91. }
  92. /**
  93. * Binds event listeners
  94. */
  95. bindEventListeners() {
  96. this.body.emitter.on('initPhysics', () => {this.initPhysics();});
  97. this.body.emitter.on('_layoutFailed', () => {this.layoutFailed = true;});
  98. this.body.emitter.on('resetPhysics', () => {this.stopSimulation(); this.ready = false;});
  99. this.body.emitter.on('disablePhysics', () => {this.physicsEnabled = false; this.stopSimulation();});
  100. this.body.emitter.on('restorePhysics', () => {
  101. this.setOptions(this.options);
  102. if (this.ready === true) {
  103. this.startSimulation();
  104. }
  105. });
  106. this.body.emitter.on('startSimulation', () => {
  107. if (this.ready === true) {
  108. this.startSimulation();
  109. }
  110. });
  111. this.body.emitter.on('stopSimulation', () => {this.stopSimulation();});
  112. this.body.emitter.on('destroy', () => {
  113. this.stopSimulation(false);
  114. this.body.emitter.off();
  115. });
  116. // this event will trigger a rebuilding of the cache everything. Used when nodes or edges have been added or removed.
  117. this.body.emitter.on("_dataChanged", () => {
  118. // update shortcut lists
  119. this.updatePhysicsData();
  120. });
  121. // debug: show forces
  122. // this.body.emitter.on("afterDrawing", (ctx) => {this._drawForces(ctx);});
  123. }
  124. /**
  125. * set the physics options
  126. * @param {Object} options
  127. */
  128. setOptions(options) {
  129. if (options !== undefined) {
  130. if (options === false) {
  131. this.options.enabled = false;
  132. this.physicsEnabled = false;
  133. this.stopSimulation();
  134. }
  135. else if (options === true) {
  136. this.options.enabled = true;
  137. this.physicsEnabled = true;
  138. this.startSimulation();
  139. }
  140. else {
  141. this.physicsEnabled = true;
  142. util.selectiveNotDeepExtend(['stabilization'], this.options, options);
  143. util.mergeOptions(this.options, options, 'stabilization');
  144. if (options.enabled === undefined) {
  145. this.options.enabled = true;
  146. }
  147. if (this.options.enabled === false) {
  148. this.physicsEnabled = false;
  149. this.stopSimulation();
  150. }
  151. // set the timestep
  152. this.timestep = this.options.timestep;
  153. }
  154. }
  155. this.init();
  156. }
  157. /**
  158. * configure the engine.
  159. */
  160. init() {
  161. var options;
  162. if (this.options.solver === 'forceAtlas2Based') {
  163. options = this.options.forceAtlas2Based;
  164. this.nodesSolver = new ForceAtlas2BasedRepulsionSolver(this.body, this.physicsBody, options);
  165. this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
  166. this.gravitySolver = new ForceAtlas2BasedCentralGravitySolver(this.body, this.physicsBody, options);
  167. }
  168. else if (this.options.solver === 'repulsion') {
  169. options = this.options.repulsion;
  170. this.nodesSolver = new Repulsion(this.body, this.physicsBody, options);
  171. this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
  172. this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
  173. }
  174. else if (this.options.solver === 'hierarchicalRepulsion') {
  175. options = this.options.hierarchicalRepulsion;
  176. this.nodesSolver = new HierarchicalRepulsion(this.body, this.physicsBody, options);
  177. this.edgesSolver = new HierarchicalSpringSolver(this.body, this.physicsBody, options);
  178. this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
  179. }
  180. else { // barnesHut
  181. options = this.options.barnesHut;
  182. this.nodesSolver = new BarnesHutSolver(this.body, this.physicsBody, options);
  183. this.edgesSolver = new SpringSolver(this.body, this.physicsBody, options);
  184. this.gravitySolver = new CentralGravitySolver(this.body, this.physicsBody, options);
  185. }
  186. this.modelOptions = options;
  187. }
  188. /**
  189. * initialize the engine
  190. */
  191. initPhysics() {
  192. if (this.physicsEnabled === true && this.options.enabled === true) {
  193. if (this.options.stabilization.enabled === true) {
  194. this.stabilize();
  195. }
  196. else {
  197. this.stabilized = false;
  198. this.ready = true;
  199. this.body.emitter.emit('fit', {}, this.layoutFailed); // if the layout failed, we use the approximation for the zoom
  200. this.startSimulation();
  201. }
  202. }
  203. else {
  204. this.ready = true;
  205. this.body.emitter.emit('fit');
  206. }
  207. }
  208. /**
  209. * Start the simulation
  210. */
  211. startSimulation() {
  212. if (this.physicsEnabled === true && this.options.enabled === true) {
  213. this.stabilized = false;
  214. // when visible, adaptivity is disabled.
  215. this.adaptiveTimestep = false;
  216. // this sets the width of all nodes initially which could be required for the avoidOverlap
  217. this.body.emitter.emit("_resizeNodes");
  218. if (this.viewFunction === undefined) {
  219. this.viewFunction = this.simulationStep.bind(this);
  220. this.body.emitter.on('initRedraw', this.viewFunction);
  221. this.body.emitter.emit('_startRendering');
  222. }
  223. }
  224. else {
  225. this.body.emitter.emit('_redraw');
  226. }
  227. }
  228. /**
  229. * Stop the simulation, force stabilization.
  230. * @param {boolean} [emit=true]
  231. */
  232. stopSimulation(emit = true) {
  233. this.stabilized = true;
  234. if (emit === true) {
  235. this._emitStabilized();
  236. }
  237. if (this.viewFunction !== undefined) {
  238. this.body.emitter.off('initRedraw', this.viewFunction);
  239. this.viewFunction = undefined;
  240. if (emit === true) {
  241. this.body.emitter.emit('_stopRendering');
  242. }
  243. }
  244. }
  245. /**
  246. * The viewFunction inserts this step into each render loop. It calls the physics tick and handles the cleanup at stabilized.
  247. *
  248. */
  249. simulationStep() {
  250. // check if the physics have settled
  251. var startTime = Date.now();
  252. this.physicsTick();
  253. var physicsTime = Date.now() - startTime;
  254. // run double speed if it is a little graph
  255. if ((physicsTime < 0.4 * this.simulationInterval || this.runDoubleSpeed === true) && this.stabilized === false) {
  256. this.physicsTick();
  257. // this makes sure there is no jitter. The decision is taken once to run it at double speed.
  258. this.runDoubleSpeed = true;
  259. }
  260. if (this.stabilized === true) {
  261. this.stopSimulation();
  262. }
  263. }
  264. /**
  265. * trigger the stabilized event.
  266. *
  267. * @param {Number} [amountOfIterations=this.stabilizationIterations]
  268. * @private
  269. */
  270. _emitStabilized(amountOfIterations = this.stabilizationIterations) {
  271. if (this.stabilizationIterations > 1 || this.startedStabilization === true) {
  272. setTimeout(() => {
  273. this.body.emitter.emit('stabilized', {iterations: amountOfIterations});
  274. this.startedStabilization = false;
  275. this.stabilizationIterations = 0;
  276. }, 0);
  277. }
  278. }
  279. /**
  280. * A single simulation step (or 'tick') in the physics simulation
  281. *
  282. * @private
  283. */
  284. physicsTick() {
  285. // this is here to ensure that there is no start event when the network is already stable.
  286. if (this.startedStabilization === false) {
  287. this.body.emitter.emit('startStabilizing');
  288. this.startedStabilization = true;
  289. }
  290. if (this.stabilized === false) {
  291. // adaptivity means the timestep adapts to the situation, only applicable for stabilization
  292. if (this.adaptiveTimestep === true && this.adaptiveTimestepEnabled === true) {
  293. // this is the factor for increasing the timestep on success.
  294. let factor = 1.2;
  295. // we assume the adaptive interval is
  296. if (this.adaptiveCounter % this.adaptiveInterval === 0) { // we leave the timestep stable for "interval" iterations.
  297. // first the big step and revert. Revert saves the reference state.
  298. this.timestep = 2 * this.timestep;
  299. this.calculateForces();
  300. this.moveNodes();
  301. this.revert();
  302. // now the normal step. Since this is the last step, it is the more stable one and we will take this.
  303. this.timestep = 0.5 * this.timestep;
  304. // since it's half the step, we do it twice.
  305. this.calculateForces();
  306. this.moveNodes();
  307. this.calculateForces();
  308. this.moveNodes();
  309. // we compare the two steps. if it is acceptable we double the step.
  310. if (this._evaluateStepQuality() === true) {
  311. this.timestep = factor * this.timestep;
  312. }
  313. else {
  314. // if not, we decrease the step to a minimum of the options timestep.
  315. // if the decreased timestep is smaller than the options step, we do not reset the counter
  316. // we assume that the options timestep is stable enough.
  317. if (this.timestep/factor < this.options.timestep) {
  318. this.timestep = this.options.timestep;
  319. }
  320. else {
  321. // if the timestep was larger than 2 times the option one we check the adaptivity again to ensure
  322. // that large instabilities do not form.
  323. this.adaptiveCounter = -1; // check again next iteration
  324. this.timestep = Math.max(this.options.timestep, this.timestep/factor);
  325. }
  326. }
  327. }
  328. else {
  329. // normal step, keeping timestep constant
  330. this.calculateForces();
  331. this.moveNodes();
  332. }
  333. // increment the counter
  334. this.adaptiveCounter += 1;
  335. }
  336. else {
  337. // case for the static timestep, we reset it to the one in options and take a normal step.
  338. this.timestep = this.options.timestep;
  339. this.calculateForces();
  340. this.moveNodes();
  341. }
  342. // determine if the network has stabilzied
  343. if (this.stabilized === true) {
  344. this.revert();
  345. }
  346. this.stabilizationIterations++;
  347. }
  348. }
  349. /**
  350. * 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.
  351. *
  352. * @private
  353. */
  354. updatePhysicsData() {
  355. this.physicsBody.forces = {};
  356. this.physicsBody.physicsNodeIndices = [];
  357. this.physicsBody.physicsEdgeIndices = [];
  358. let nodes = this.body.nodes;
  359. let edges = this.body.edges;
  360. // get node indices for physics
  361. for (let nodeId in nodes) {
  362. if (nodes.hasOwnProperty(nodeId)) {
  363. if (nodes[nodeId].options.physics === true) {
  364. this.physicsBody.physicsNodeIndices.push(nodes[nodeId].id);
  365. }
  366. }
  367. }
  368. // get edge indices for physics
  369. for (let edgeId in edges) {
  370. if (edges.hasOwnProperty(edgeId)) {
  371. if (edges[edgeId].options.physics === true) {
  372. this.physicsBody.physicsEdgeIndices.push(edges[edgeId].id);
  373. }
  374. }
  375. }
  376. // get the velocity and the forces vector
  377. for (let i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) {
  378. let nodeId = this.physicsBody.physicsNodeIndices[i];
  379. this.physicsBody.forces[nodeId] = {x:0,y:0};
  380. // forces can be reset because they are recalculated. Velocities have to persist.
  381. if (this.physicsBody.velocities[nodeId] === undefined) {
  382. this.physicsBody.velocities[nodeId] = {x:0,y:0};
  383. }
  384. }
  385. // clean deleted nodes from the velocity vector
  386. for (let nodeId in this.physicsBody.velocities) {
  387. if (nodes[nodeId] === undefined) {
  388. delete this.physicsBody.velocities[nodeId];
  389. }
  390. }
  391. }
  392. /**
  393. * Revert the simulation one step. This is done so after stabilization, every new start of the simulation will also say stabilized.
  394. */
  395. revert() {
  396. var nodeIds = Object.keys(this.previousStates);
  397. var nodes = this.body.nodes;
  398. var velocities = this.physicsBody.velocities;
  399. this.referenceState = {};
  400. for (let i = 0; i < nodeIds.length; i++) {
  401. let nodeId = nodeIds[i];
  402. if (nodes[nodeId] !== undefined) {
  403. if (nodes[nodeId].options.physics === true) {
  404. this.referenceState[nodeId] = {
  405. positions: {x:nodes[nodeId].x, y:nodes[nodeId].y}
  406. };
  407. velocities[nodeId].x = this.previousStates[nodeId].vx;
  408. velocities[nodeId].y = this.previousStates[nodeId].vy;
  409. nodes[nodeId].x = this.previousStates[nodeId].x;
  410. nodes[nodeId].y = this.previousStates[nodeId].y;
  411. }
  412. }
  413. else {
  414. delete this.previousStates[nodeId];
  415. }
  416. }
  417. }
  418. /**
  419. * This compares the reference state to the current state
  420. *
  421. * @returns {boolean}
  422. * @private
  423. */
  424. _evaluateStepQuality() {
  425. let dx, dy, dpos;
  426. let nodes = this.body.nodes;
  427. let reference = this.referenceState;
  428. let posThreshold = 0.3;
  429. for (let nodeId in this.referenceState) {
  430. if (this.referenceState.hasOwnProperty(nodeId) && nodes[nodeId] !== undefined) {
  431. dx = nodes[nodeId].x - reference[nodeId].positions.x;
  432. dy = nodes[nodeId].y - reference[nodeId].positions.y;
  433. dpos = Math.sqrt(Math.pow(dx,2) + Math.pow(dy,2))
  434. if (dpos > posThreshold) {
  435. return false;
  436. }
  437. }
  438. }
  439. return true;
  440. }
  441. /**
  442. * move the nodes one timestep and check if they are stabilized
  443. */
  444. moveNodes() {
  445. var nodeIndices = this.physicsBody.physicsNodeIndices;
  446. var maxVelocity = this.options.maxVelocity ? this.options.maxVelocity : 1e9;
  447. var maxNodeVelocity = 0;
  448. var averageNodeVelocity = 0;
  449. // the velocity threshold (energy in the system) for the adaptivity toggle
  450. var velocityAdaptiveThreshold = 5;
  451. for (let i = 0; i < nodeIndices.length; i++) {
  452. let nodeId = nodeIndices[i];
  453. let nodeVelocity = this._performStep(nodeId, maxVelocity);
  454. // stabilized is true if stabilized is true and velocity is smaller than vmin --> all nodes must be stabilized
  455. maxNodeVelocity = Math.max(maxNodeVelocity,nodeVelocity);
  456. averageNodeVelocity += nodeVelocity;
  457. }
  458. // evaluating the stabilized and adaptiveTimestepEnabled conditions
  459. this.adaptiveTimestepEnabled = (averageNodeVelocity/nodeIndices.length) < velocityAdaptiveThreshold;
  460. this.stabilized = maxNodeVelocity < this.options.minVelocity;
  461. }
  462. /**
  463. * Perform the actual step
  464. *
  465. * @param {vis.Node.id} nodeId
  466. * @param {number} maxVelocity
  467. * @returns {number}
  468. * @private
  469. */
  470. _performStep(nodeId, maxVelocity) {
  471. let node = this.body.nodes[nodeId];
  472. let timestep = this.timestep;
  473. let forces = this.physicsBody.forces;
  474. let velocities = this.physicsBody.velocities;
  475. // store the state so we can revert
  476. this.previousStates[nodeId] = {x:node.x, y:node.y, vx:velocities[nodeId].x, vy:velocities[nodeId].y};
  477. if (node.options.fixed.x === false) {
  478. let dx = this.modelOptions.damping * velocities[nodeId].x; // damping force
  479. let ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration
  480. velocities[nodeId].x += ax * timestep; // velocity
  481. velocities[nodeId].x = (Math.abs(velocities[nodeId].x) > maxVelocity) ? ((velocities[nodeId].x > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].x;
  482. node.x += velocities[nodeId].x * timestep; // position
  483. }
  484. else {
  485. forces[nodeId].x = 0;
  486. velocities[nodeId].x = 0;
  487. }
  488. if (node.options.fixed.y === false) {
  489. let dy = this.modelOptions.damping * velocities[nodeId].y; // damping force
  490. let ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration
  491. velocities[nodeId].y += ay * timestep; // velocity
  492. velocities[nodeId].y = (Math.abs(velocities[nodeId].y) > maxVelocity) ? ((velocities[nodeId].y > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].y;
  493. node.y += velocities[nodeId].y * timestep; // position
  494. }
  495. else {
  496. forces[nodeId].y = 0;
  497. velocities[nodeId].y = 0;
  498. }
  499. let totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x,2) + Math.pow(velocities[nodeId].y,2));
  500. return totalVelocity;
  501. }
  502. /**
  503. * calculate the forces for one physics iteration.
  504. */
  505. calculateForces() {
  506. this.gravitySolver.solve();
  507. this.nodesSolver.solve();
  508. this.edgesSolver.solve();
  509. }
  510. /**
  511. * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
  512. * because only the supportnodes for the smoothCurves have to settle.
  513. *
  514. * @private
  515. */
  516. _freezeNodes() {
  517. var nodes = this.body.nodes;
  518. for (var id in nodes) {
  519. if (nodes.hasOwnProperty(id)) {
  520. if (nodes[id].x && nodes[id].y) {
  521. this.freezeCache[id] = {x:nodes[id].options.fixed.x,y:nodes[id].options.fixed.y};
  522. nodes[id].options.fixed.x = true;
  523. nodes[id].options.fixed.y = true;
  524. }
  525. }
  526. }
  527. }
  528. /**
  529. * Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
  530. *
  531. * @private
  532. */
  533. _restoreFrozenNodes() {
  534. var nodes = this.body.nodes;
  535. for (var id in nodes) {
  536. if (nodes.hasOwnProperty(id)) {
  537. if (this.freezeCache[id] !== undefined) {
  538. nodes[id].options.fixed.x = this.freezeCache[id].x;
  539. nodes[id].options.fixed.y = this.freezeCache[id].y;
  540. }
  541. }
  542. }
  543. this.freezeCache = {};
  544. }
  545. /**
  546. * Find a stable position for all nodes
  547. *
  548. * @param {Number} [iterations=this.options.stabilization.iterations]
  549. */
  550. stabilize(iterations = this.options.stabilization.iterations) {
  551. if (typeof iterations !== 'number') {
  552. console.log('The stabilize method needs a numeric amount of iterations. Switching to default: ', this.options.stabilization.iterations);
  553. iterations = this.options.stabilization.iterations;
  554. }
  555. if (this.physicsBody.physicsNodeIndices.length === 0) {
  556. this.ready = true;
  557. return;
  558. }
  559. // enable adaptive timesteps
  560. this.adaptiveTimestep = true && this.options.adaptiveTimestep;
  561. // this sets the width of all nodes initially which could be required for the avoidOverlap
  562. this.body.emitter.emit("_resizeNodes");
  563. // stop the render loop
  564. this.stopSimulation();
  565. // set stabilze to false
  566. this.stabilized = false;
  567. // block redraw requests
  568. this.body.emitter.emit('_blockRedraw');
  569. this.targetIterations = iterations;
  570. // start the stabilization
  571. if (this.options.stabilization.onlyDynamicEdges === true) {
  572. this._freezeNodes();
  573. }
  574. this.stabilizationIterations = 0;
  575. setTimeout(() => this._stabilizationBatch(),0);
  576. }
  577. /**
  578. * One batch of stabilization
  579. * @private
  580. */
  581. _stabilizationBatch() {
  582. var self = this;
  583. var running = () => (self.stabilized === false && self.stabilizationIterations < self.targetIterations);
  584. var sendProgress = () => {
  585. self.body.emitter.emit('stabilizationProgress', {
  586. iterations: self.stabilizationIterations,
  587. total: self.targetIterations
  588. });
  589. };
  590. // this is here to ensure that there is at least one start event.
  591. if (this.startedStabilization === false) {
  592. this.body.emitter.emit('startStabilizing');
  593. this.startedStabilization = true;
  594. sendProgress();
  595. }
  596. var count = 0;
  597. while (running() && count < this.options.stabilization.updateInterval) {
  598. this.physicsTick();
  599. count++;
  600. }
  601. sendProgress();
  602. if (running()) {
  603. setTimeout(this._stabilizationBatch.bind(this),0);
  604. }
  605. else {
  606. this._finalizeStabilization();
  607. }
  608. }
  609. /**
  610. * Wrap up the stabilization, fit and emit the events.
  611. * @private
  612. */
  613. _finalizeStabilization() {
  614. this.body.emitter.emit('_allowRedraw');
  615. if (this.options.stabilization.fit === true) {
  616. this.body.emitter.emit('fit');
  617. }
  618. if (this.options.stabilization.onlyDynamicEdges === true) {
  619. this._restoreFrozenNodes();
  620. }
  621. this.body.emitter.emit('stabilizationIterationsDone');
  622. this.body.emitter.emit('_requestRedraw');
  623. if (this.stabilized === true) {
  624. this._emitStabilized();
  625. }
  626. else {
  627. this.startSimulation();
  628. }
  629. this.ready = true;
  630. }
  631. /**
  632. * TODO: Is this function used at all? If not, remove it!
  633. * @param {CanvasRenderingContext2D} ctx
  634. * @private
  635. */
  636. _drawForces(ctx) {
  637. for (var i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) {
  638. let node = this.body.nodes[this.physicsBody.physicsNodeIndices[i]];
  639. let force = this.physicsBody.forces[this.physicsBody.physicsNodeIndices[i]];
  640. let factor = 20;
  641. let colorFactor = 0.03;
  642. let forceSize = Math.sqrt(Math.pow(force.x,2) + Math.pow(force.x,2));
  643. let size = Math.min(Math.max(5,forceSize),15);
  644. let arrowSize = 3*size;
  645. let color = util.HSVToHex((180 - Math.min(1,Math.max(0,colorFactor*forceSize))*180) / 360,1,1);
  646. ctx.lineWidth = size;
  647. ctx.strokeStyle = color;
  648. ctx.beginPath();
  649. ctx.moveTo(node.x,node.y);
  650. ctx.lineTo(node.x+factor*force.x, node.y+factor*force.y);
  651. ctx.stroke();
  652. let angle = Math.atan2(force.y, force.x);
  653. ctx.fillStyle = color;
  654. ctx.arrowEndpoint(node.x + factor*force.x + Math.cos(angle)*arrowSize, node.y + factor*force.y+Math.sin(angle)*arrowSize, angle, arrowSize);
  655. ctx.fill();
  656. }
  657. }
  658. }
  659. export default PhysicsEngine;