|
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