|
1 | 1 | /**
|
2 |
| - @author David Piegza |
| 2 | + @author David Piegza (@davidpiegza) |
| 3 | + @author Timofey Rechkalov (@TRechkalov) |
3 | 4 |
|
4 | 5 | Implements a force-directed layout, the algorithm is based on Fruchterman and Reingold and
|
5 | 6 | the JUNG implementation.
|
6 | 7 |
|
7 |
| - Needs the graph data structure Graph.js: |
| 8 | + Needs the graph data structure Graph.js and the Vector3 object: |
8 | 9 | https://github.com/davidpiegza/Graph-Visualization/blob/master/Graph.js
|
| 10 | + https://github.com/davidpiegza/Graph-Visualization/blob/master/utils/Vector3.js |
9 | 11 |
|
10 | 12 | Parameters:
|
11 | 13 | graph - data structure
|
@@ -87,140 +89,82 @@ Layout.ForceDirected = function(graph, options) {
|
87 | 89 | this.generate = function() {
|
88 | 90 | if(layout_iterations < this.max_iterations && temperature > 0.000001) {
|
89 | 91 | var start = new Date().getTime();
|
90 |
| - var i; |
91 |
| - |
92 |
| - var delta_x, delta_y, delta_z, delta_length, delta_length_z, force, force_z; |
| 92 | + var i, j, delta, delta_length, force, change; |
93 | 93 |
|
94 | 94 | // calculate repulsion
|
95 | 95 | for(i=0; i < nodes_length; i++) {
|
96 | 96 | var node_v = graph.nodes[i];
|
97 | 97 | node_v.layout = node_v.layout || {};
|
98 | 98 | if(i === 0) {
|
99 |
| - node_v.layout.offset_x = 0; |
100 |
| - node_v.layout.offset_y = 0; |
101 |
| - if(this.layout === "3d") { |
102 |
| - node_v.layout.offset_z = 0; |
103 |
| - } |
| 99 | + node_v.layout.offset = new Vector3(); |
104 | 100 | }
|
105 | 101 |
|
106 | 102 | node_v.layout.force = 0;
|
107 |
| - node_v.layout.tmp_pos_x = node_v.layout.tmp_pos_x || node_v.position.x; |
108 |
| - node_v.layout.tmp_pos_y = node_v.layout.tmp_pos_y || node_v.position.y; |
109 |
| - if(this.layout === "3d") { |
110 |
| - node_v.layout.tmp_pos_z = node_v.layout.tmp_pos_z || node_v.position.z; |
111 |
| - } |
| 103 | + node_v.layout.tmp_pos = node_v.layout.tmp_pos || new Vector3().setVector(node_v.position); |
112 | 104 |
|
113 |
| - for(var j=i+1; j < nodes_length; j++) { |
| 105 | + for(j=i+1; j < nodes_length; j++) { |
114 | 106 | var node_u = graph.nodes[j];
|
115 | 107 | if(i != j) {
|
116 | 108 | node_u.layout = node_u.layout || {};
|
117 |
| - node_u.layout.tmp_pos_x = node_u.layout.tmp_pos_x || node_u.position.x; |
118 |
| - node_u.layout.tmp_pos_y = node_u.layout.tmp_pos_y || node_u.position.y; |
119 |
| - if(this.layout === "3d") { |
120 |
| - node_u.layout.tmp_pos_z = node_u.layout.tmp_pos_z || node_u.position.z; |
121 |
| - } |
122 | 109 |
|
123 |
| - delta_x = node_v.layout.tmp_pos_x - node_u.layout.tmp_pos_x; |
124 |
| - delta_y = node_v.layout.tmp_pos_y - node_u.layout.tmp_pos_y; |
| 110 | + node_u.layout.tmp_pos = node_u.layout.tmp_pos || new Vector3().setVector(node_u.position); |
125 | 111 |
|
126 |
| - if(this.layout === "3d") { |
127 |
| - delta_z = node_v.layout.tmp_pos_z - node_u.layout.tmp_pos_z; |
128 |
| - } |
129 |
| - |
130 |
| - delta_length = Math.max(EPSILON, Math.sqrt((delta_x * delta_x) + (delta_y * delta_y))); |
131 |
| - if(this.layout === "3d") { |
132 |
| - delta_length_z = Math.max(EPSILON, Math.sqrt((delta_z * delta_z) + (delta_y * delta_y))); |
133 |
| - } |
| 112 | + delta = node_v.layout.tmp_pos.clone().sub(node_u.layout.tmp_pos); |
| 113 | + delta_length = Math.max(EPSILON, Math.sqrt(delta.clone().multiply(delta).sum())); |
134 | 114 |
|
135 | 115 | force = (repulsion_constant * repulsion_constant) / delta_length;
|
136 | 116 |
|
137 |
| - if(this.layout === "3d") { |
138 |
| - force_z = (repulsion_constant * repulsion_constant) / delta_length_z; |
139 |
| - } |
140 |
| - |
141 | 117 | node_v.layout.force += force;
|
142 | 118 | node_u.layout.force += force;
|
143 | 119 |
|
144 |
| - node_v.layout.offset_x += (delta_x / delta_length) * force; |
145 |
| - node_v.layout.offset_y += (delta_y / delta_length) * force; |
146 |
| - |
147 | 120 | if(i === 0) {
|
148 |
| - node_u.layout.offset_x = 0; |
149 |
| - node_u.layout.offset_y = 0; |
150 |
| - if(this.layout === "3d") { |
151 |
| - node_u.layout.offset_z = 0; |
152 |
| - } |
| 121 | + node_u.layout.offset = new Vector3(); |
153 | 122 | }
|
154 |
| - node_u.layout.offset_x -= (delta_x / delta_length) * force; |
155 |
| - node_u.layout.offset_y -= (delta_y / delta_length) * force; |
156 | 123 |
|
157 |
| - if(this.layout === "3d") { |
158 |
| - node_v.layout.offset_z += (delta_z / delta_length_z) * force_z; |
159 |
| - node_u.layout.offset_z -= (delta_z / delta_length_z) * force_z; |
160 |
| - } |
| 124 | + change = delta.clone().multiply(new Vector3().setScalar(force/delta_length)); |
| 125 | + node_v.layout.offset.add(change); |
| 126 | + node_u.layout.offset.sub(change); |
161 | 127 | }
|
162 | 128 | }
|
163 | 129 | }
|
164 | 130 |
|
165 | 131 | // calculate attraction
|
166 | 132 | for(i=0; i < edges_length; i++) {
|
167 | 133 | var edge = graph.edges[i];
|
168 |
| - delta_x = edge.source.layout.tmp_pos_x - edge.target.layout.tmp_pos_x; |
169 |
| - delta_y = edge.source.layout.tmp_pos_y - edge.target.layout.tmp_pos_y; |
170 |
| - if(this.layout === "3d") { |
171 |
| - delta_z = edge.source.layout.tmp_pos_z - edge.target.layout.tmp_pos_z; |
172 |
| - } |
173 |
| - |
174 |
| - delta_length = Math.max(EPSILON, Math.sqrt((delta_x * delta_x) + (delta_y * delta_y))); |
175 |
| - if(this.layout === "3d") { |
176 |
| - delta_length_z = Math.max(EPSILON, Math.sqrt((delta_z * delta_z) + (delta_y * delta_y))); |
177 |
| - } |
| 134 | + delta = edge.source.layout.tmp_pos.clone().sub(edge.target.layout.tmp_pos); |
| 135 | + delta_length = Math.max(EPSILON, Math.sqrt(delta.clone().multiply(delta).sum())); |
178 | 136 |
|
179 | 137 | force = (delta_length * delta_length) / attraction_constant;
|
180 |
| - if(this.layout === "3d") { |
181 |
| - force_z = (delta_length_z * delta_length_z) / attraction_constant; |
182 |
| - } |
183 | 138 |
|
184 | 139 | edge.source.layout.force -= force;
|
185 | 140 | edge.target.layout.force += force;
|
186 | 141 |
|
187 |
| - edge.source.layout.offset_x -= (delta_x / delta_length) * force; |
188 |
| - edge.source.layout.offset_y -= (delta_y / delta_length) * force; |
189 |
| - if(this.layout === "3d") { |
190 |
| - edge.source.layout.offset_z -= (delta_z / delta_length_z) * force_z; |
191 |
| - } |
192 |
| - |
193 |
| - edge.target.layout.offset_x += (delta_x / delta_length) * force; |
194 |
| - edge.target.layout.offset_y += (delta_y / delta_length) * force; |
195 |
| - if(this.layout === "3d") { |
196 |
| - edge.target.layout.offset_z += (delta_z / delta_length_z) * force_z; |
197 |
| - } |
| 142 | + change = delta.clone().multiply(new Vector3().setScalar(force/delta_length)); |
| 143 | + edge.target.layout.offset.add(change); |
| 144 | + edge.source.layout.offset.sub(change); |
198 | 145 | }
|
199 | 146 |
|
200 | 147 | // calculate positions
|
201 | 148 | for(i=0; i < nodes_length; i++) {
|
202 | 149 | var node = graph.nodes[i];
|
203 |
| - delta_length = Math.max(EPSILON, Math.sqrt(node.layout.offset_x * node.layout.offset_x + node.layout.offset_y * node.layout.offset_y)); |
204 | 150 |
|
205 |
| - if(this.layout === "3d") { |
206 |
| - delta_length_z = Math.max(EPSILON, Math.sqrt(node.layout.offset_z * node.layout.offset_z + node.layout.offset_y * node.layout.offset_y)); |
207 |
| - } |
| 151 | + delta_length = Math.max(EPSILON, Math.sqrt(node.layout.offset.clone().multiply(node.layout.offset).sum())); |
208 | 152 |
|
209 |
| - node.layout.tmp_pos_x += (node.layout.offset_x / delta_length) * Math.min(delta_length, temperature); |
210 |
| - node.layout.tmp_pos_y += (node.layout.offset_y / delta_length) * Math.min(delta_length, temperature); |
211 |
| - if(this.layout === "3d") { |
212 |
| - node.layout.tmp_pos_z += (node.layout.offset_z / delta_length_z) * Math.min(delta_length_z, temperature); |
213 |
| - } |
| 153 | + node.layout.tmp_pos.add(node.layout.offset.clone().multiply(new Vector3().setScalar(Math.min(delta_length, temperature) / delta_length))); |
214 | 154 |
|
215 | 155 | var updated = true;
|
216 |
| - node.position.x -= (node.position.x-node.layout.tmp_pos_x)/10; |
217 |
| - node.position.y -= (node.position.y-node.layout.tmp_pos_y)/10; |
218 | 156 |
|
219 |
| - if(this.layout === "3d") { |
220 |
| - node.position.z -= (node.position.z-node.layout.tmp_pos_z)/10; |
| 157 | + var tmpPosition = new Vector3(node.position.x, node.position.y, node.position.z); |
| 158 | + tmpPosition.sub(node.layout.tmp_pos).divide(new Vector3().setScalar(10)); |
| 159 | + |
| 160 | + node.position.x -= tmpPosition.x; |
| 161 | + node.position.y -= tmpPosition.y; |
| 162 | + |
| 163 | + if(this.layout === '3d') { |
| 164 | + node.position.z -= tmpPosition.z; |
221 | 165 | }
|
222 | 166 |
|
223 |
| - // execute callback function if positions has been updated |
| 167 | + // execute callback function if position has been updated |
224 | 168 | if(updated && typeof callback_positionUpdated === 'function') {
|
225 | 169 | callback_positionUpdated(node);
|
226 | 170 | }
|
|
0 commit comments