layout.vala 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159
  1. /********************************************************************
  2. # Copyright 2014-2022 Daniel 'grindhold' Brendle
  3. #
  4. # This file is part of libgtkflow.
  5. #
  6. # libgtkflow is free software: you can redistribute it and/or
  7. # modify it under the terms of the GNU Lesser General Public License
  8. # as published by the Free Software Foundation, either
  9. # version 3 of the License, or (at your option) any later
  10. # version.
  11. #
  12. # libgtkflow is distributed in the hope that it will be
  13. # useful, but WITHOUT ANY WARRANTY; without even the implied
  14. # warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
  15. # PURPOSE. See the GNU Lesser General Public License for more details.
  16. #
  17. # You should have received a copy of the GNU Lesser General Public
  18. # License along with libgtkflow.
  19. # If not, see http://www.gnu.org/licenses/.
  20. *********************************************************************/
  21. namespace GtkFlow {
  22. public interface Layout {
  23. /**
  24. * Rearranges the Nodes on a nodeview according to the
  25. * algorithm that implements this method
  26. */
  27. internal abstract void arrange(List<unowned Node> nodes);
  28. }
  29. /**
  30. * Spring-Simulation mechanism autolayouter
  31. *
  32. * THIS FEATURE IS VERY EXPERIMENTAL. USE AT YOUR OWN RISK.
  33. * IT WILL VERY LIKELY SEGFAULT AND MAYBE RID YOUR ACCOUNT
  34. * OF ALL YOUR METICUOUSLY COLLECTED FLURBOS.
  35. */
  36. public class ForceLayout : GLib.Object, Layout {
  37. /**
  38. * Desired spacing between nodes in pixels
  39. */
  40. private const double SPACING = 30;
  41. /**
  42. * How often the forces will be calculated
  43. */
  44. private const uint RUNS = 50;
  45. private NodeView node_view = null;
  46. /**
  47. * Rearranges the Nodes on a nodeview according to simulated
  48. * behaviour of mechanical springs
  49. */
  50. internal void arrange(List<unowned Node> nodes) {
  51. this.detect_nodeview(nodes);
  52. var forces = new HashTable<Node, Gdk.Point?>(direct_hash, direct_equal);
  53. for (int i = 0; i < RUNS; i++) {
  54. foreach (Node from in nodes) {
  55. forces[from] = {0,0};
  56. foreach (Node to in nodes) {
  57. var force = this.calculate_force(from, to);
  58. forces[from].x += force.x;
  59. forces[from].y += force.y;
  60. }
  61. }
  62. foreach (Node from in nodes) {
  63. var alloc = this.node_view.get_node_allocation(from.gnode);
  64. alloc.x += forces[from].x;
  65. alloc.y += forces[from].y;
  66. this.node_view.set_node_position(from.gnode, alloc.x, alloc.y);
  67. }
  68. }
  69. int min_x = 0;
  70. int min_y = 0;
  71. foreach (Node node in nodes) {
  72. var alloc = this.node_view.get_node_allocation(node.gnode);
  73. min_x = int.min(alloc.x, min_x);
  74. min_y = int.min(alloc.y, min_y);
  75. }
  76. foreach (Node node in nodes) {
  77. var alloc = this.node_view.get_node_allocation(node.gnode);
  78. alloc.x += min_x.abs();
  79. alloc.y += min_y.abs();
  80. this.node_view.set_node_position(node.gnode, alloc.x, alloc.y);
  81. }
  82. }
  83. /**
  84. * Detects the nodeview that has to be written to
  85. * And throws an error if the given Node List contains more
  86. * than one or no nodeview
  87. */
  88. private void detect_nodeview (List<unowned Node> nodes) {
  89. foreach (Node node in nodes) {
  90. if (node.node_view == null) {
  91. warning("Node given to layouter is not attached to nodeview");
  92. continue;
  93. }
  94. if (this.node_view == null)
  95. this.node_view = node.node_view;
  96. else if (this.node_view != node.node_view)
  97. warning("Nodes given to this layouter do not belong to the same NodeView");
  98. }
  99. if (this.node_view == null)
  100. error ("No nodeview could be detected");
  101. }
  102. /**
  103. * Calculates the ideal distance between the two nodes
  104. * of the Connection and the angle of the direct line
  105. * that would connect them.
  106. */
  107. private Gdk.Point calculate_force(Node from, Node to) {
  108. if (from == to)
  109. return {0,0};
  110. var alloc_from = this.node_view.get_node_allocation(from.gnode);
  111. var alloc_to = this.node_view.get_node_allocation(to.gnode);
  112. // This is a relative viewpoint so we dont need acutal positions
  113. Gdk.Point from_middle = {alloc_from.width/2,alloc_from.height/2};
  114. Gdk.Point to_middle = {alloc_to.width/2,alloc_to.height/2};
  115. // Calculate the desired distance
  116. double x_dist = (double)from_middle.x + (double)to_middle.x;
  117. double y_dist = (double)from_middle.y + (double)to_middle.y;
  118. double desired_distance = SPACING + Math.sqrt(Math.pow(x_dist, 2.0) + Math.pow(y_dist, 2.0));
  119. // This is an absolute viewpoint so we need acutal positions
  120. from_middle = {alloc_from.x + alloc_from.width/2, alloc_from.y + alloc_from.height/2};
  121. to_middle = {alloc_to.x + alloc_to.width/2, alloc_to.y + alloc_to.height/2};
  122. // Calculate the actual distance
  123. double x_dist_real = (double)to_middle.x - (double)from_middle.x;
  124. double y_dist_real = (double)to_middle.y - (double)from_middle.y;
  125. double real_distance = SPACING + Math.sqrt(Math.pow(x_dist_real, 2.0) + Math.pow(y_dist_real, 2.0));
  126. // Calculate attractive force
  127. double force = 0d;
  128. if (from.gnode.is_neighbor(to.gnode) && real_distance > desired_distance) {
  129. force = Math.pow(real_distance, 2.0) / desired_distance;
  130. force *= 0.1 * double.max(real_distance/desired_distance -1, 0);
  131. } else {
  132. force = - Math.pow(desired_distance, 2.0) / real_distance;
  133. force *= 0.1 * double.max(desired_distance/real_distance -1, 0);
  134. }
  135. // Calculate x / y force components
  136. double f_x = 1.5*((force / 2.0) / real_distance) * x_dist_real;
  137. double f_y = 0.5*((force / 2.0) / real_distance) * y_dist_real;
  138. // Write out new allocation
  139. return {(int)Math.round(f_x), (int)Math.round(f_y)};
  140. }
  141. }
  142. }