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.

757 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. * The physics engine
  12. */
  13. class PhysicsEngine {
  14. /**
  15. * @param {Object} body
  16. */
  17. constructor(body) {
  18. this.body = body;
  19. this.physicsBody = {physicsNodeIndices:[], physicsEdgeIndices:[], forces: {}, velocities: {}};
  20. this.physicsEnabled = true;
  21. this.simulationInterval = 1000 / 60;
  22. this.requiresTimeout = true;
  23. this.previousStates = {};
  24. this.referenceState = {};
  25. this.freezeCache = {};
  26. this.renderTimer = undefined;
  27. // parameters for the adaptive timestep
  28. this.adaptiveTimestep = false;
  29. this.adaptiveTimestepEnabled = false;
  30. this.adaptiveCounter = 0;
  31. this.adaptiveInterval = 3;
  32. this.stabilized = false;
  33. this.startedStabilization = false;
  34. this.stabilizationIterations = 0;
  35. this.ready = false; // will be set to true if the stabilize
  36. // default options
  37. this.options = {};
  38. this.defaultOptions = {
  39. enabled: true,
  40. barnesHut: {
  41. theta: 0.5,
  42. gravitationalConstant: -2000,
  43. centralGravity: 0.3,
  44. springLength: 95,
  45. springConstant: 0.04,
  46. damping: 0.09,
  47. avoidOverlap: 0
  48. },
  49. forceAtlas2Based: {
  50. theta: 0.5,
  51. gravitationalConstant: -50,
  52. centralGravity: 0.01,
  53. springConstant: 0.08,
  54. springLength: 100,
  55. damping: 0.4,
  56. avoidOverlap: 0
  57. },
  58. repulsion: {
  59. centralGravity: 0.2,
  60. springLength: 200,
  61. springConstant: 0.05,
  62. nodeDistance: 100,
  63. damping: 0.09,
  64. avoidOverlap: 0
  65. },
  66. hierarchicalRepulsion: {
  67. centralGravity: 0.0,
  68. springLength: 100,
  69. springConstant: 0.01,
  70. nodeDistance: 120,
  71. damping: 0.09
  72. },
  73. maxVelocity: 50,
  74. minVelocity: 0.75, // px/s
  75. solver: 'barnesHut',
  76. stabilization: {
  77. enabled: true,
  78. iterations: 1000, // maximum number of iteration to stabilize
  79. updateInterval: 50,
  80. onlyDynamicEdges: false,
  81. fit: true
  82. },
  83. timestep: 0.5,
  84. adaptiveTimestep: true
  85. };
  86. util.extend(this.options, this.defaultOptions);
  87. this.timestep = 0.5;
  88. this.layoutFailed = false;
  89. this.bindEventListeners();
  90. }
  91. /**
  92. * Binds event listeners
  93. */
  94. bindEventListeners() {
  95. this.body.emitter.on('initPhysics', () => {this.initPhysics();});
  96. this.body.emitter.on('_layoutFailed', () => {this.layoutFailed = true;});
  97. this.body.emitter.on('resetPhysics', () => {this.stopSimulation(); this.ready = false;});
  98. this.body.emitter.on('disablePhysics', () => {this.physicsEnabled = false; this.stopSimulation();});
  99. this.body.emitter.on('restorePhysics', () => {
  100. this.setOptions(this.options);
  101. if (this.ready === true) {
  102. this.startSimulation();
  103. }
  104. });
  105. this.body.emitter.on('startSimulation', () => {
  106. if (this.ready === true) {
  107. this.startSimulation();
  108. }
  109. });
  110. this.body.emitter.on('stopSimulation', () => {this.stopSimulation();});
  111. this.body.emitter.on('destroy', () => {
  112. this.stopSimulation(false);
  113. this.body.emitter.off();
  114. });
  115. // this event will trigger a rebuilding of the cache everything. Used when nodes or edges have been added or removed.
  116. this.body.emitter.on("_dataChanged", () => {
  117. // 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. * A single simulation step (or 'tick') in the physics simulation
  280. *
  281. * @private
  282. */
  283. physicsTick() {
  284. // this is here to ensure that there is no start event when the network is already stable.
  285. if (this.startedStabilization === false) {
  286. this.body.emitter.emit('startStabilizing');
  287. this.startedStabilization = true;
  288. }
  289. if (this.stabilized === false) {
  290. // adaptivity means the timestep adapts to the situation, only applicable for stabilization
  291. if (this.adaptiveTimestep === true && this.adaptiveTimestepEnabled === true) {
  292. // this is the factor for increasing the timestep on success.
  293. let factor = 1.2;
  294. // we assume the adaptive interval is
  295. if (this.adaptiveCounter % this.adaptiveInterval === 0) { // we leave the timestep stable for "interval" iterations.
  296. // first the big step and revert. Revert saves the reference state.
  297. this.timestep = 2 * this.timestep;
  298. this.calculateForces();
  299. this.moveNodes();
  300. this.revert();
  301. // now the normal step. Since this is the last step, it is the more stable one and we will take this.
  302. this.timestep = 0.5 * this.timestep;
  303. // since it's half the step, we do it twice.
  304. this.calculateForces();
  305. this.moveNodes();
  306. this.calculateForces();
  307. this.moveNodes();
  308. // we compare the two steps. if it is acceptable we double the step.
  309. if (this._evaluateStepQuality() === true) {
  310. this.timestep = factor * this.timestep;
  311. }
  312. else {
  313. // if not, we decrease the step to a minimum of the options timestep.
  314. // if the decreased timestep is smaller than the options step, we do not reset the counter
  315. // we assume that the options timestep is stable enough.
  316. if (this.timestep/factor < this.options.timestep) {
  317. this.timestep = this.options.timestep;
  318. }
  319. else {
  320. // if the timestep was larger than 2 times the option one we check the adaptivity again to ensure
  321. // that large instabilities do not form.
  322. this.adaptiveCounter = -1; // check again next iteration
  323. this.timestep = Math.max(this.options.timestep, this.timestep/factor);
  324. }
  325. }
  326. }
  327. else {
  328. // normal step, keeping timestep constant
  329. this.calculateForces();
  330. this.moveNodes();
  331. }
  332. // increment the counter
  333. this.adaptiveCounter += 1;
  334. }
  335. else {
  336. // case for the static timestep, we reset it to the one in options and take a normal step.
  337. this.timestep = this.options.timestep;
  338. this.calculateForces();
  339. this.moveNodes();
  340. }
  341. // determine if the network has stabilzied
  342. if (this.stabilized === true) {
  343. this.revert();
  344. }
  345. this.stabilizationIterations++;
  346. }
  347. }
  348. /**
  349. * 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.
  350. *
  351. * @private
  352. */
  353. updatePhysicsData() {
  354. this.physicsBody.forces = {};
  355. this.physicsBody.physicsNodeIndices = [];
  356. this.physicsBody.physicsEdgeIndices = [];
  357. let nodes = this.body.nodes;
  358. let edges = this.body.edges;
  359. // get node indices for physics
  360. for (let nodeId in nodes) {
  361. if (nodes.hasOwnProperty(nodeId)) {
  362. if (nodes[nodeId].options.physics === true) {
  363. this.physicsBody.physicsNodeIndices.push(nodes[nodeId].id);
  364. }
  365. }
  366. }
  367. // get edge indices for physics
  368. for (let edgeId in edges) {
  369. if (edges.hasOwnProperty(edgeId)) {
  370. if (edges[edgeId].options.physics === true) {
  371. this.physicsBody.physicsEdgeIndices.push(edges[edgeId].id);
  372. }
  373. }
  374. }
  375. // get the velocity and the forces vector
  376. for (let i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) {
  377. let nodeId = this.physicsBody.physicsNodeIndices[i];
  378. this.physicsBody.forces[nodeId] = {x:0,y:0};
  379. // forces can be reset because they are recalculated. Velocities have to persist.
  380. if (this.physicsBody.velocities[nodeId] === undefined) {
  381. this.physicsBody.velocities[nodeId] = {x:0,y:0};
  382. }
  383. }
  384. // clean deleted nodes from the velocity vector
  385. for (let nodeId in this.physicsBody.velocities) {
  386. if (nodes[nodeId] === undefined) {
  387. delete this.physicsBody.velocities[nodeId];
  388. }
  389. }
  390. }
  391. /**
  392. * Revert the simulation one step. This is done so after stabilization, every new start of the simulation will also say stabilized.
  393. */
  394. revert() {
  395. var nodeIds = Object.keys(this.previousStates);
  396. var nodes = this.body.nodes;
  397. var velocities = this.physicsBody.velocities;
  398. this.referenceState = {};
  399. for (let i = 0; i < nodeIds.length; i++) {
  400. let nodeId = nodeIds[i];
  401. if (nodes[nodeId] !== undefined) {
  402. if (nodes[nodeId].options.physics === true) {
  403. this.referenceState[nodeId] = {
  404. positions: {x:nodes[nodeId].x, y:nodes[nodeId].y}
  405. };
  406. velocities[nodeId].x = this.previousStates[nodeId].vx;
  407. velocities[nodeId].y = this.previousStates[nodeId].vy;
  408. nodes[nodeId].x = this.previousStates[nodeId].x;
  409. nodes[nodeId].y = this.previousStates[nodeId].y;
  410. }
  411. }
  412. else {
  413. delete this.previousStates[nodeId];
  414. }
  415. }
  416. }
  417. /**
  418. * This compares the reference state to the current state
  419. *
  420. * @returns {boolean}
  421. * @private
  422. */
  423. _evaluateStepQuality() {
  424. let dx, dy, dpos;
  425. let nodes = this.body.nodes;
  426. let reference = this.referenceState;
  427. let posThreshold = 0.3;
  428. for (let nodeId in this.referenceState) {
  429. if (this.referenceState.hasOwnProperty(nodeId) && nodes[nodeId] !== undefined) {
  430. dx = nodes[nodeId].x - reference[nodeId].positions.x;
  431. dy = nodes[nodeId].y - reference[nodeId].positions.y;
  432. dpos = Math.sqrt(Math.pow(dx,2) + Math.pow(dy,2))
  433. if (dpos > posThreshold) {
  434. return false;
  435. }
  436. }
  437. }
  438. return true;
  439. }
  440. /**
  441. * move the nodes one timestep and check if they are stabilized
  442. */
  443. moveNodes() {
  444. var nodeIndices = this.physicsBody.physicsNodeIndices;
  445. var maxVelocity = this.options.maxVelocity ? this.options.maxVelocity : 1e9;
  446. var maxNodeVelocity = 0;
  447. var averageNodeVelocity = 0;
  448. // the velocity threshold (energy in the system) for the adaptivity toggle
  449. var velocityAdaptiveThreshold = 5;
  450. for (let i = 0; i < nodeIndices.length; i++) {
  451. let nodeId = nodeIndices[i];
  452. let nodeVelocity = this._performStep(nodeId, maxVelocity);
  453. // stabilized is true if stabilized is true and velocity is smaller than vmin --> all nodes must be stabilized
  454. maxNodeVelocity = Math.max(maxNodeVelocity,nodeVelocity);
  455. averageNodeVelocity += nodeVelocity;
  456. }
  457. // evaluating the stabilized and adaptiveTimestepEnabled conditions
  458. this.adaptiveTimestepEnabled = (averageNodeVelocity/nodeIndices.length) < velocityAdaptiveThreshold;
  459. this.stabilized = maxNodeVelocity < this.options.minVelocity;
  460. }
  461. /**
  462. * Perform the actual step
  463. *
  464. * @param {Node.id} nodeId
  465. * @param {number} maxVelocity
  466. * @returns {number}
  467. * @private
  468. */
  469. _performStep(nodeId, maxVelocity) {
  470. let node = this.body.nodes[nodeId];
  471. let timestep = this.timestep;
  472. let forces = this.physicsBody.forces;
  473. let velocities = this.physicsBody.velocities;
  474. // store the state so we can revert
  475. this.previousStates[nodeId] = {x:node.x, y:node.y, vx:velocities[nodeId].x, vy:velocities[nodeId].y};
  476. if (node.options.fixed.x === false) {
  477. let dx = this.modelOptions.damping * velocities[nodeId].x; // damping force
  478. let ax = (forces[nodeId].x - dx) / node.options.mass; // acceleration
  479. velocities[nodeId].x += ax * timestep; // velocity
  480. velocities[nodeId].x = (Math.abs(velocities[nodeId].x) > maxVelocity) ? ((velocities[nodeId].x > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].x;
  481. node.x += velocities[nodeId].x * timestep; // position
  482. }
  483. else {
  484. forces[nodeId].x = 0;
  485. velocities[nodeId].x = 0;
  486. }
  487. if (node.options.fixed.y === false) {
  488. let dy = this.modelOptions.damping * velocities[nodeId].y; // damping force
  489. let ay = (forces[nodeId].y - dy) / node.options.mass; // acceleration
  490. velocities[nodeId].y += ay * timestep; // velocity
  491. velocities[nodeId].y = (Math.abs(velocities[nodeId].y) > maxVelocity) ? ((velocities[nodeId].y > 0) ? maxVelocity : -maxVelocity) : velocities[nodeId].y;
  492. node.y += velocities[nodeId].y * timestep; // position
  493. }
  494. else {
  495. forces[nodeId].y = 0;
  496. velocities[nodeId].y = 0;
  497. }
  498. let totalVelocity = Math.sqrt(Math.pow(velocities[nodeId].x,2) + Math.pow(velocities[nodeId].y,2));
  499. return totalVelocity;
  500. }
  501. /**
  502. * calculate the forces for one physics iteration.
  503. */
  504. calculateForces() {
  505. this.gravitySolver.solve();
  506. this.nodesSolver.solve();
  507. this.edgesSolver.solve();
  508. }
  509. /**
  510. * When initializing and stabilizing, we can freeze nodes with a predefined position. This greatly speeds up stabilization
  511. * because only the supportnodes for the smoothCurves have to settle.
  512. *
  513. * @private
  514. */
  515. _freezeNodes() {
  516. var nodes = this.body.nodes;
  517. for (var id in nodes) {
  518. if (nodes.hasOwnProperty(id)) {
  519. if (nodes[id].x && nodes[id].y) {
  520. this.freezeCache[id] = {x:nodes[id].options.fixed.x,y:nodes[id].options.fixed.y};
  521. nodes[id].options.fixed.x = true;
  522. nodes[id].options.fixed.y = true;
  523. }
  524. }
  525. }
  526. }
  527. /**
  528. * Unfreezes the nodes that have been frozen by _freezeDefinedNodes.
  529. *
  530. * @private
  531. */
  532. _restoreFrozenNodes() {
  533. var nodes = this.body.nodes;
  534. for (var id in nodes) {
  535. if (nodes.hasOwnProperty(id)) {
  536. if (this.freezeCache[id] !== undefined) {
  537. nodes[id].options.fixed.x = this.freezeCache[id].x;
  538. nodes[id].options.fixed.y = this.freezeCache[id].y;
  539. }
  540. }
  541. }
  542. this.freezeCache = {};
  543. }
  544. /**
  545. * Find a stable position for all nodes
  546. *
  547. * @param {number} [iterations=this.options.stabilization.iterations]
  548. */
  549. stabilize(iterations = this.options.stabilization.iterations) {
  550. if (typeof iterations !== 'number') {
  551. console.log('The stabilize method needs a numeric amount of iterations. Switching to default: ', this.options.stabilization.iterations);
  552. iterations = this.options.stabilization.iterations;
  553. }
  554. if (this.physicsBody.physicsNodeIndices.length === 0) {
  555. this.ready = true;
  556. return;
  557. }
  558. // enable adaptive timesteps
  559. this.adaptiveTimestep = true && this.options.adaptiveTimestep;
  560. // this sets the width of all nodes initially which could be required for the avoidOverlap
  561. this.body.emitter.emit("_resizeNodes");
  562. // stop the render loop
  563. this.stopSimulation();
  564. // set stabilze to false
  565. this.stabilized = false;
  566. // block redraw requests
  567. this.body.emitter.emit('_blockRedraw');
  568. this.targetIterations = iterations;
  569. // start the stabilization
  570. if (this.options.stabilization.onlyDynamicEdges === true) {
  571. this._freezeNodes();
  572. }
  573. this.stabilizationIterations = 0;
  574. setTimeout(() => this._stabilizationBatch(),0);
  575. }
  576. /**
  577. * One batch of stabilization
  578. * @private
  579. */
  580. _stabilizationBatch() {
  581. var self = this;
  582. var running = () => (self.stabilized === false && self.stabilizationIterations < self.targetIterations);
  583. var sendProgress = () => {
  584. self.body.emitter.emit('stabilizationProgress', {
  585. iterations: self.stabilizationIterations,
  586. total: self.targetIterations
  587. });
  588. };
  589. // this is here to ensure that there is at least one start event.
  590. if (this.startedStabilization === false) {
  591. this.body.emitter.emit('startStabilizing');
  592. this.startedStabilization = true;
  593. sendProgress();
  594. }
  595. var count = 0;
  596. while (running() && count < this.options.stabilization.updateInterval) {
  597. this.physicsTick();
  598. count++;
  599. }
  600. sendProgress();
  601. if (running()) {
  602. setTimeout(this._stabilizationBatch.bind(this),0);
  603. }
  604. else {
  605. this._finalizeStabilization();
  606. }
  607. }
  608. /**
  609. * Wrap up the stabilization, fit and emit the events.
  610. * @private
  611. */
  612. _finalizeStabilization() {
  613. this.body.emitter.emit('_allowRedraw');
  614. if (this.options.stabilization.fit === true) {
  615. this.body.emitter.emit('fit');
  616. }
  617. if (this.options.stabilization.onlyDynamicEdges === true) {
  618. this._restoreFrozenNodes();
  619. }
  620. this.body.emitter.emit('stabilizationIterationsDone');
  621. this.body.emitter.emit('_requestRedraw');
  622. if (this.stabilized === true) {
  623. this._emitStabilized();
  624. }
  625. else {
  626. this.startSimulation();
  627. }
  628. this.ready = true;
  629. }
  630. /**
  631. * TODO: Is this function used at all? If not, remove it!
  632. * @param {CanvasRenderingContext2D} ctx
  633. * @private
  634. */
  635. _drawForces(ctx) {
  636. for (var i = 0; i < this.physicsBody.physicsNodeIndices.length; i++) {
  637. let node = this.body.nodes[this.physicsBody.physicsNodeIndices[i]];
  638. let force = this.physicsBody.forces[this.physicsBody.physicsNodeIndices[i]];
  639. let factor = 20;
  640. let colorFactor = 0.03;
  641. let forceSize = Math.sqrt(Math.pow(force.x,2) + Math.pow(force.x,2));
  642. let size = Math.min(Math.max(5,forceSize),15);
  643. let arrowSize = 3*size;
  644. let color = util.HSVToHex((180 - Math.min(1,Math.max(0,colorFactor*forceSize))*180) / 360,1,1);
  645. ctx.lineWidth = size;
  646. ctx.strokeStyle = color;
  647. ctx.beginPath();
  648. ctx.moveTo(node.x,node.y);
  649. ctx.lineTo(node.x+factor*force.x, node.y+factor*force.y);
  650. ctx.stroke();
  651. let angle = Math.atan2(force.y, force.x);
  652. ctx.fillStyle = color;
  653. ctx.arrowEndpoint(node.x + factor*force.x + Math.cos(angle)*arrowSize, node.y + factor*force.y+Math.sin(angle)*arrowSize, angle, arrowSize);
  654. ctx.fill();
  655. }
  656. }
  657. }
  658. export default PhysicsEngine;