vis.js is a dynamic, browser-based visualization library

106 lines
3.2 KiB

  1. class HierarchicalSpringSolver {
  2. constructor(body, physicsBody, options) {
  3. this.body = body;
  4. this.physicsBody = physicsBody;
  5. this.setOptions(options);
  6. }
  7. setOptions(options) {
  8. this.options = options;
  9. }
  10. /**
  11. * This function calculates the springforces on the nodes, accounting for the support nodes.
  12. *
  13. * @private
  14. */
  15. solve() {
  16. var edgeLength, edge;
  17. var dx, dy, fx, fy, springForce, distance;
  18. var edges = this.body.edges;
  19. var factor = 0.5;
  20. var edgeIndices = this.physicsBody.physicsEdgeIndices;
  21. var nodeIndices = this.physicsBody.physicsNodeIndices;
  22. var forces = this.physicsBody.forces;
  23. // initialize the spring force counters
  24. for (let i = 0; i < nodeIndices.length; i++) {
  25. let nodeId = nodeIndices[i];
  26. forces[nodeId].springFx = 0;
  27. forces[nodeId].springFy = 0;
  28. }
  29. // forces caused by the edges, modelled as springs
  30. for (let i = 0; i < edgeIndices.length; i++) {
  31. edge = edges[edgeIndices[i]];
  32. if (edge.connected === true) {
  33. edgeLength = edge.options.length === undefined ? this.options.springLength : edge.options.length;
  34. dx = (edge.from.x - edge.to.x);
  35. dy = (edge.from.y - edge.to.y);
  36. distance = Math.sqrt(dx * dx + dy * dy);
  37. distance = distance === 0 ? 0.01 : distance;
  38. // the 1/distance is so the fx and fy can be calculated without sine or cosine.
  39. springForce = this.options.springConstant * (edgeLength - distance) / distance;
  40. fx = dx * springForce;
  41. fy = dy * springForce;
  42. if (edge.to.level != edge.from.level) {
  43. if (forces[edge.toId] !== undefined) {
  44. forces[edge.toId].springFx -= fx;
  45. forces[edge.toId].springFy -= fy;
  46. }
  47. if (forces[edge.fromId] !== undefined) {
  48. forces[edge.fromId].springFx += fx;
  49. forces[edge.fromId].springFy += fy;
  50. }
  51. }
  52. else {
  53. if (forces[edge.toId] !== undefined) {
  54. forces[edge.toId].x -= factor * fx;
  55. forces[edge.toId].y -= factor * fy;
  56. }
  57. if (forces[edge.fromId] !== undefined) {
  58. forces[edge.fromId].x += factor * fx;
  59. forces[edge.fromId].y += factor * fy;
  60. }
  61. }
  62. }
  63. }
  64. // normalize spring forces
  65. var springForce = 1;
  66. var springFx, springFy;
  67. for (let i = 0; i < nodeIndices.length; i++) {
  68. let nodeId = nodeIndices[i];
  69. springFx = Math.min(springForce,Math.max(-springForce,forces[nodeId].springFx));
  70. springFy = Math.min(springForce,Math.max(-springForce,forces[nodeId].springFy));
  71. forces[nodeId].x += springFx;
  72. forces[nodeId].y += springFy;
  73. }
  74. // retain energy balance
  75. var totalFx = 0;
  76. var totalFy = 0;
  77. for (let i = 0; i < nodeIndices.length; i++) {
  78. let nodeId = nodeIndices[i];
  79. totalFx += forces[nodeId].x;
  80. totalFy += forces[nodeId].y;
  81. }
  82. var correctionFx = totalFx / nodeIndices.length;
  83. var correctionFy = totalFy / nodeIndices.length;
  84. for (let i = 0; i < nodeIndices.length; i++) {
  85. let nodeId = nodeIndices[i];
  86. forces[nodeId].x -= correctionFx;
  87. forces[nodeId].y -= correctionFy;
  88. }
  89. }
  90. }
  91. export default HierarchicalSpringSolver;