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.

157 lines
4.7 KiB

  1. /**
  2. * Created by Alex on 8/7/2015.
  3. */
  4. import FloydWarshall from "./FloydWarshall.js"
  5. class KamadaKawai {
  6. constructor(body, edgeLength, edgeStrength) {
  7. this.body = body;
  8. this.springLength = edgeLength;
  9. this.springConstant = edgeStrength;
  10. this.distanceSolver = new FloydWarshall();
  11. }
  12. setOptions(options) {
  13. if (options) {
  14. if (options.springLength) {
  15. this.springLength = options.springLength;
  16. }
  17. if (options.springConstant) {
  18. this.springConstant = options.springConstant;
  19. }
  20. }
  21. }
  22. solve(nodesArray, edgesArray) {
  23. console.time("FLOYD - getDistances");
  24. let D_matrix = this.distanceSolver.getDistances(this.body, nodesArray, edgesArray); // distance matrix
  25. console.timeEnd("FLOYD - getDistances");
  26. // get the L Matrix
  27. this._createL_matrix(D_matrix);
  28. // get the K Matrix
  29. this._createK_matrix(D_matrix);
  30. console.time("positioning")
  31. let threshold = 0.01;
  32. let counter = 0;
  33. let maxIterations = Math.min(10*this.body.nodeIndices.length);;
  34. let maxEnergy = 1e9; // just to pass the first check.
  35. let highE_nodeId = 0, dE_dx = 0, dE_dy = 0;
  36. while (maxEnergy > threshold && counter < maxIterations) {
  37. counter += 1;
  38. [highE_nodeId, maxEnergy, dE_dx, dE_dy] = this._getHighestEnergyNode();
  39. this._moveNode(highE_nodeId, dE_dx, dE_dy);
  40. }
  41. console.timeEnd("positioning")
  42. }
  43. _getHighestEnergyNode() {
  44. let nodesArray = this.body.nodeIndices;
  45. let maxEnergy = 0;
  46. let maxEnergyNode = nodesArray[0];
  47. let energies = {dE_dx: 0, dE_dy: 0};
  48. for (let nodeIdx = 0; nodeIdx < nodesArray.length; nodeIdx++) {
  49. let m = nodesArray[nodeIdx];
  50. let [delta_m,dE_dx,dE_dy] = this._getEnergy(m);
  51. if (maxEnergy < delta_m) {
  52. maxEnergy = delta_m;
  53. maxEnergyNode = m;
  54. energies.dE_dx = dE_dx;
  55. energies.dE_dy = dE_dy;
  56. }
  57. }
  58. return [maxEnergyNode, maxEnergy, energies.dE_dx, energies.dE_dy];
  59. }
  60. _getEnergy(m) {
  61. let nodesArray = this.body.nodeIndices;
  62. let nodes = this.body.nodes;
  63. let x_m = nodes[m].x;
  64. let y_m = nodes[m].y;
  65. let dE_dx = 0;
  66. let dE_dy = 0;
  67. for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) {
  68. let i = nodesArray[iIdx];
  69. if (i !== m) {
  70. let x_i = nodes[i].x;
  71. let y_i = nodes[i].y;
  72. let denominator = 1.0 / Math.sqrt(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2));
  73. dE_dx += this.K_matrix[m][i] * ((x_m - x_i) - this.L_matrix[m][i] * (x_m - x_i) * denominator);
  74. dE_dy += this.K_matrix[m][i] * ((y_m - y_i) - this.L_matrix[m][i] * (y_m - y_i) * denominator);
  75. }
  76. }
  77. let delta_m = Math.sqrt(Math.pow(dE_dx, 2) + Math.pow(dE_dy, 2));
  78. return [delta_m, dE_dx, dE_dy];
  79. }
  80. _moveNode(m, dE_dx, dE_dy) {
  81. let nodesArray = this.body.nodeIndices;
  82. let nodes = this.body.nodes;
  83. let d2E_dx2 = 0;
  84. let d2E_dxdy = 0;
  85. let d2E_dy2 = 0;
  86. let x_m = nodes[m].x;
  87. let y_m = nodes[m].y;
  88. for (let iIdx = 0; iIdx < nodesArray.length; iIdx++) {
  89. let i = nodesArray[iIdx];
  90. if (i !== m) {
  91. let x_i = nodes[i].x;
  92. let y_i = nodes[i].y;
  93. let denominator = 1.0 / Math.pow(Math.pow(x_m - x_i, 2) + Math.pow(y_m - y_i, 2), 1.5);
  94. d2E_dx2 += this.K_matrix[m][i] * (1 - this.L_matrix[m][i] * Math.pow(y_m - y_i, 2) * denominator);
  95. d2E_dxdy += this.K_matrix[m][i] * (this.L_matrix[m][i] * (x_m - x_i) * (y_m - y_i) * denominator);
  96. d2E_dy2 += this.K_matrix[m][i] * (1 - this.L_matrix[m][i] * Math.pow(x_m - x_i, 2) * denominator);
  97. }
  98. }
  99. // make the variable names easier to make the solving of the linear system easier to read
  100. let A = d2E_dx2, B = d2E_dxdy, C = dE_dx, D = d2E_dy2, E = dE_dy;
  101. // solve the linear system for dx and dy
  102. let dy = (C / A + E / B) / (B / A - D / B);
  103. let dx = -(B * dy + C) / A;
  104. // move the node
  105. nodes[m].x += dx;
  106. nodes[m].y += dy;
  107. }
  108. _createL_matrix(D_matrix) {
  109. let nodesArray = this.body.nodeIndices;
  110. let edgeLength = this.springLength;
  111. this.L_matrix = [];
  112. for (let i = 0; i < nodesArray.length; i++) {
  113. this.L_matrix[nodesArray[i]] = {};
  114. for (let j = 0; j < nodesArray.length; j++) {
  115. this.L_matrix[nodesArray[i]][nodesArray[j]] = edgeLength * D_matrix[nodesArray[i]][nodesArray[j]];
  116. }
  117. }
  118. }
  119. _createK_matrix(D_matrix) {
  120. let nodesArray = this.body.nodeIndices;
  121. let edgeStrength = this.springConstant;
  122. this.K_matrix = [];
  123. for (let i = 0; i < nodesArray.length; i++) {
  124. this.K_matrix[nodesArray[i]] = {};
  125. for (let j = 0; j < nodesArray.length; j++) {
  126. this.K_matrix[nodesArray[i]][nodesArray[j]] = edgeStrength * Math.pow(D_matrix[nodesArray[i]][nodesArray[j]], -2);
  127. }
  128. }
  129. }
  130. }
  131. export default KamadaKawai;