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.

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