Skip to content

Commit 87e3621

Browse files
committed
Optimized code.
1 parent 6e86040 commit 87e3621

File tree

1 file changed

+146
-147
lines changed

1 file changed

+146
-147
lines changed

layouts/force-directed-layout.js

Lines changed: 146 additions & 147 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
positionUpdated: <function>, called when the position of the node has been updated
2121
}
2222
23-
Example:
23+
Examples
2424
2525
create:
2626
layout = new Layout.ForceDirected(graph, {width: 2000, height: 2000, iterations: 1000, layout: "3d"});
@@ -40,184 +40,183 @@ var Layout = Layout || {};
4040

4141
Layout.ForceDirected = function(graph, options) {
4242
var options = options || {};
43-
var layout = options.layout || "2d";
43+
44+
this.layout = options.layout || "2d";
45+
this.attraction_multiplier = options.attraction || 5;
46+
this.repulsion_multiplier = options.repulsion || 0.75;
47+
this.max_iterations = options.iterations || 1000;
48+
this.graph = graph;
49+
this.width = options.width || 200;
50+
this.height = options.height || 200;
51+
this.finished = false;
52+
4453
var callback_positionUpdated = options.positionUpdated;
4554

4655
var EPSILON = 0.000001;
47-
var attraction_multiplier = options.attraction || 5;
4856
var attraction_constant;
49-
var repulsion_multiplier = options.repulsion || 0.75;
5057
var repulsion_constant;
5158
var forceConstant;
5259
var layout_iterations = 0;
53-
var max_iterations = options.iterations || 1000;
5460
var temperature = 0;
55-
var graph = graph;
56-
var width = options.width || 200;
57-
var height = options.height || 200;
58-
59-
this.finished = false;
61+
var nodes_length;
62+
var edges_length;
63+
var that = this;
6064

65+
// performance test
66+
var mean_time = 0;
67+
6168
this.init = function() {
6269
this.finished = false;
63-
temperature = width / 10.0;
64-
forceConstant = Math.sqrt(height * width / graph.nodes.length);
65-
attraction_constant = attraction_multiplier * forceConstant;
66-
repulsion_constant = repulsion_multiplier * forceConstant;
70+
temperature = this.width / 10.0;
71+
nodes_length = this.graph.nodes.length;
72+
edges_length = this.graph.edges.length;
73+
forceConstant = Math.sqrt(this.height * this.width / nodes_length);
74+
attraction_constant = this.attraction_multiplier * forceConstant;
75+
repulsion_constant = this.repulsion_multiplier * forceConstant;
6776
};
6877

6978
this.generate = function() {
7079
// TODO: stop if total force reached 0
71-
if(layout_iterations < max_iterations) {
72-
graph.nodes.forEach(function(node_v) {
73-
calcRepulsion(graph, node_v);
74-
});
75-
76-
graph.edges.forEach(function(edge) {
77-
calcAttraction(graph, edge);
78-
});
79-
80-
graph.nodes.forEach(function(node_v) {
81-
calcPositions(graph, node_v);
82-
});
83-
84-
// info.innerHTML = "node_force: " + parseInt(node_force) + "<br>edge_force: " + edge_force + "<br>div: " + (node_force-edge_force);
85-
86-
// temperature *= (1.0 - (layout_iterations / max_iterations));
87-
layout_iterations++;
88-
} else {
89-
this.finished = true;
90-
return false;
91-
}
92-
return true;
93-
};
94-
95-
var calcRepulsion = function(graph, node_v) {
96-
node_v.layout = node_v.layout || {};
97-
node_v.layout.offset_x = 0;
98-
node_v.layout.offset_y = 0;
99-
if(layout === "3d") {
100-
node_v.layout.offset_z = 0;
101-
}
102-
node_v.layout.force = 0;
103-
node_v.layout.tmp_pos_x = node_v.layout.tmp_pos_x || node_v.position.x;
104-
node_v.layout.tmp_pos_y = node_v.layout.tmp_pos_y || node_v.position.y;
105-
if(layout === "3d") {
106-
node_v.layout.tmp_pos_z = node_v.layout.tmp_pos_z || node_v.position.z;
107-
}
108-
109-
graph.nodes.forEach(function(node_u) {
110-
if(node_v.id != node_u.id) {
111-
node_u.layout = node_u.layout || {};
112-
node_u.layout.tmp_pos_x = node_u.layout.tmp_pos_x || node_u.position.x;
113-
node_u.layout.tmp_pos_y = node_u.layout.tmp_pos_y || node_u.position.y;
114-
if(layout === "3d") {
115-
node_u.layout.tmp_pos_z = node_u.layout.tmp_pos_z || node_u.position.z;
80+
if(layout_iterations < this.max_iterations) {
81+
var start = new Date().getTime();
82+
83+
// calculate repulsion
84+
for(var i=0; i < nodes_length; i++) {
85+
var node_v = graph.nodes[i];
86+
node_v.layout = node_v.layout || {};
87+
node_v.layout.offset_x = 0;
88+
node_v.layout.offset_y = 0;
89+
if(this.layout === "3d") {
90+
node_v.layout.offset_z = 0;
91+
}
92+
node_v.layout.force = 0;
93+
node_v.layout.tmp_pos_x = node_v.layout.tmp_pos_x || node_v.position.x;
94+
node_v.layout.tmp_pos_y = node_v.layout.tmp_pos_y || node_v.position.y;
95+
if(this.layout === "3d") {
96+
node_v.layout.tmp_pos_z = node_v.layout.tmp_pos_z || node_v.position.z;
11697
}
11798

118-
var delta_x = node_v.layout.tmp_pos_x - node_u.layout.tmp_pos_x;
119-
var delta_y = node_v.layout.tmp_pos_y - node_u.layout.tmp_pos_y;
120-
if(layout === "3d") {
121-
var delta_z = node_v.layout.tmp_pos_z - node_u.layout.tmp_pos_z;
99+
for(var j=0; j < nodes_length; j++) {
100+
var node_u = graph.nodes[j];
101+
if(node_v.id != node_u.id) {
102+
node_u.layout = node_u.layout || {};
103+
node_u.layout.tmp_pos_x = node_u.layout.tmp_pos_x || node_u.position.x;
104+
node_u.layout.tmp_pos_y = node_u.layout.tmp_pos_y || node_u.position.y;
105+
if(this.layout === "3d") {
106+
node_u.layout.tmp_pos_z = node_u.layout.tmp_pos_z || node_u.position.z;
107+
}
108+
109+
var delta_x = node_v.layout.tmp_pos_x - node_u.layout.tmp_pos_x;
110+
var delta_y = node_v.layout.tmp_pos_y - node_u.layout.tmp_pos_y;
111+
if(this.layout === "3d") {
112+
var delta_z = node_v.layout.tmp_pos_z - node_u.layout.tmp_pos_z;
113+
}
114+
115+
var delta_length = Math.max(EPSILON, Math.sqrt((delta_x * delta_x) + (delta_y * delta_y)));
116+
if(this.layout === "3d") {
117+
var delta_length_z = Math.max(EPSILON, Math.sqrt((delta_z * delta_z) + (delta_y * delta_y)));
118+
}
119+
120+
var force = (repulsion_constant * repulsion_constant) / delta_length;
121+
if(this.layout === "3d") {
122+
var force_z = (repulsion_constant * repulsion_constant) / delta_length_z;
123+
}
124+
125+
node_v.layout.force += force;
126+
node_u.layout.force += force;
127+
128+
node_v.layout.offset_x += (delta_x / delta_length) * force;
129+
node_v.layout.offset_y += (delta_y / delta_length) * force;
130+
if(this.layout === "3d") {
131+
node_v.layout.offset_z += (delta_z / delta_length_z) * force_z;
132+
}
133+
}
122134
}
135+
}
123136

137+
// calc attraction
138+
for(var i=0; i < edges_length; i++) {
139+
var edge = graph.edges[i];
140+
var delta_x = edge.source.layout.tmp_pos_x - edge.target.layout.tmp_pos_x;
141+
var delta_y = edge.source.layout.tmp_pos_y - edge.target.layout.tmp_pos_y;
142+
if(this.layout === "3d") {
143+
var delta_z = edge.source.layout.tmp_pos_z - edge.target.layout.tmp_pos_z;
144+
}
145+
124146
var delta_length = Math.max(EPSILON, Math.sqrt((delta_x * delta_x) + (delta_y * delta_y)));
125-
if(layout === "3d") {
147+
if(this.layout === "3d") {
126148
var delta_length_z = Math.max(EPSILON, Math.sqrt((delta_z * delta_z) + (delta_y * delta_y)));
127149
}
128-
129-
var force = (repulsion_constant * repulsion_constant) / delta_length;
130-
if(layout === "3d") {
131-
var force_z = (repulsion_constant * repulsion_constant) / delta_length_z;
150+
var force = (delta_length * delta_length) / attraction_constant;
151+
if(this.layout === "3d") {
152+
var force_z = (delta_length_z * delta_length_z) / attraction_constant;
132153
}
133154

155+
edge.source.layout.force -= force;
156+
edge.target.layout.force += force;
134157

135-
node_v.layout.force += force;
136-
node_u.layout.force += force;
137-
138-
node_v.layout.offset_x += (delta_x / delta_length) * force;
139-
node_v.layout.offset_y += (delta_y / delta_length) * force;
140-
if(layout === "3d") {
141-
node_v.layout.offset_z += (delta_z / delta_length_z) * force_z;
158+
edge.source.layout.offset_x -= (delta_x / delta_length) * force;
159+
edge.source.layout.offset_y -= (delta_y / delta_length) * force;
160+
if(this.layout === "3d") {
161+
edge.source.layout.offset_z -= (delta_z / delta_length_z) * force_z;
142162
}
143-
}
144-
});
145-
};
146163

147-
var calcAttraction = function(graph, edge) {
148-
// var delta_x = edge.source.position.x - edge.target.position.x;
149-
// var delta_y = edge.source.position.y - edge.target.position.y;
150-
var delta_x = edge.source.layout.tmp_pos_x - edge.target.layout.tmp_pos_x;
151-
var delta_y = edge.source.layout.tmp_pos_y - edge.target.layout.tmp_pos_y;
152-
if(layout === "3d") {
153-
var delta_z = edge.source.layout.tmp_pos_z - edge.target.layout.tmp_pos_z;
154-
}
155-
156-
var delta_length = Math.max(EPSILON, Math.sqrt((delta_x * delta_x) + (delta_y * delta_y)));
157-
if(layout === "3d") {
158-
var delta_length_z = Math.max(EPSILON, Math.sqrt((delta_z * delta_z) + (delta_y * delta_y)));
159-
}
160-
var force = (delta_length * delta_length) / attraction_constant;
161-
if(layout === "3d") {
162-
var force_z = (delta_length_z * delta_length_z) / attraction_constant;
163-
}
164+
edge.target.layout.offset_x += (delta_x / delta_length) * force;
165+
edge.target.layout.offset_y += (delta_y / delta_length) * force;
166+
if(this.layout === "3d") {
167+
edge.target.layout.offset_z += (delta_z / delta_length_z) * force_z;
168+
}
169+
}
170+
171+
// calc positions
172+
for(var i=0; i < nodes_length; i++) {
173+
var node = graph.nodes[i];
174+
var delta_length = Math.max(EPSILON, Math.sqrt(node.layout.offset_x * node.layout.offset_x + node.layout.offset_y * node.layout.offset_y));
175+
if(this.layout === "3d") {
176+
var delta_length_z = Math.max(EPSILON, Math.sqrt(node.layout.offset_z * node.layout.offset_z + node.layout.offset_y * node.layout.offset_y));
177+
}
178+
// alert(delta_length_z + " " + this.layout);
164179

165-
edge.source.layout.force -= force;
166-
edge.target.layout.force += force;
180+
node.layout.tmp_pos_x += (node.layout.offset_x / delta_length) * Math.min(delta_length, temperature);
181+
node.layout.tmp_pos_y += (node.layout.offset_y / delta_length) * Math.min(delta_length, temperature);
182+
if(this.layout === "3d") {
183+
node.layout.tmp_pos_z += (node.layout.offset_z / delta_length_z) * Math.min(delta_length_z, temperature);
184+
}
167185

168-
edge.source.layout.offset_x -= (delta_x / delta_length) * force;
169-
edge.source.layout.offset_y -= (delta_y / delta_length) * force;
170-
if(layout === "3d") {
171-
edge.source.layout.offset_z -= (delta_z / delta_length_z) * force_z;
172-
}
173-
174-
edge.target.layout.offset_x += (delta_x / delta_length) * force;
175-
edge.target.layout.offset_y += (delta_y / delta_length) * force;
176-
if(layout === "3d") {
177-
edge.target.layout.offset_z += (delta_z / delta_length_z) * force_z;
178-
}
179-
};
186+
var c = 200;
187+
var updated = false;
188+
if(node.position.x < (node.layout.tmp_pos_x - c) || node.position.x > (node.layout.tmp_pos_x + c)) {
189+
node.position.x -= (node.position.x-node.layout.tmp_pos_x)/10;
190+
updated = true;
191+
}
192+
if(node.position.y < (node.layout.tmp_pos_y - c) || node.position.y > (node.layout.tmp_pos_y + c)) {
193+
node.position.y -= (node.position.y-node.layout.tmp_pos_y)/10;
194+
updated = true;
195+
}
196+
if(this.layout === "3d") {
197+
if(node.position.z < (node.layout.tmp_pos_z - c) || node.position.z > (node.layout.tmp_pos_z + c)) {
198+
node.position.z -= (node.position.z-node.layout.tmp_pos_z)/10;
199+
updated = true;
200+
}
201+
}
180202

181-
var calcPositions = function(graph, node) {
182-
var delta_length = Math.max(EPSILON, norm(node));
183-
if(layout === "3d") {
184-
var delta_length_z = Math.max(EPSILON, norm2(node));
185-
}
186-
187-
node.layout.tmp_pos_x += (node.layout.offset_x / delta_length) * Math.min(delta_length, temperature);
188-
node.layout.tmp_pos_y += (node.layout.offset_y / delta_length) * Math.min(delta_length, temperature);
189-
if(layout === "3d") {
190-
node.layout.tmp_pos_z += (node.layout.offset_z / delta_length_z) * Math.min(delta_length_z, temperature);
191-
}
192-
193-
var c = 200;
194-
var updated = false;
195-
if(node.position.x < (node.layout.tmp_pos_x - c) || node.position.x > (node.layout.tmp_pos_x + c)) {
196-
node.position.x -= (node.position.x-node.layout.tmp_pos_x)/10;
197-
updated = true;
198-
}
199-
if(node.position.y < (node.layout.tmp_pos_y - c) || node.position.y > (node.layout.tmp_pos_y + c)) {
200-
node.position.y -= (node.position.y-node.layout.tmp_pos_y)/10;
201-
updated = true;
202-
}
203-
if(layout === "3d") {
204-
if(node.position.z < (node.layout.tmp_pos_z - c) || node.position.z > (node.layout.tmp_pos_z + c)) {
205-
node.position.z -= (node.position.z-node.layout.tmp_pos_z)/10;
206-
updated = true;
203+
if(updated && typeof callback_positionUpdated === 'function') {
204+
callback_positionUpdated(node);
205+
}
207206
}
208-
}
207+
var end = new Date().getTime();
208+
mean_time += end - start;
209+
// info.innerHTML = "node_force: " + parseInt(node_force) + "<br>edge_force: " + edge_force + "<br>div: " + (node_force-edge_force);
209210

210-
if(updated && typeof callback_positionUpdated === 'function') {
211-
callback_positionUpdated(node);
211+
// temperature *= (1.0 - (layout_iterations / this.max_iterations));
212+
layout_iterations++;
213+
} else {
214+
if(!this.finished) {
215+
console.log("Average time: " + (mean_time/layout_iterations) + " ms");
216+
}
217+
this.finished = true;
218+
return false;
212219
}
220+
return true;
213221
};
214-
215-
var norm = function(node) {
216-
return Math.sqrt(node.layout.offset_x * node.layout.offset_x + node.layout.offset_y * node.layout.offset_y);
217-
};
218-
219-
var norm2 = function(node) {
220-
return Math.sqrt(node.layout.offset_z * node.layout.offset_z + node.layout.offset_y * node.layout.offset_y);
221-
};
222-
223222
};

0 commit comments

Comments
 (0)