Skip to content

Commit aa63975

Browse files
committed
Added 3d option and comments.
1 parent 3aa2dd7 commit aa63975

File tree

1 file changed

+108
-4
lines changed

1 file changed

+108
-4
lines changed

layouts/force-based-layout.js

Lines changed: 108 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,53 @@
1+
/**
2+
@author David Piegza
3+
4+
Implements a force-directed layout, the algorithm is based on Fruchterman and Reingold and
5+
the JUNG implementation.
6+
7+
Needs the following graph data structure:
8+
https://github.com/davidpiegza/Graph-Visualization/blob/master/Graph.js
9+
10+
Parameters:
11+
graph - data structure
12+
options = {
13+
layout: "2d" or "3d"
14+
attraction: <float>, attraction value for force-directed layout
15+
repulsion: <float>, repulsion value for force-directed layout
16+
iterations: <int>, maximum number of iterations
17+
width: <int>, width of the viewport
18+
height: <int>, height of the viewport
19+
20+
positionUpdated: <function>, called when the position of the node has been updated
21+
}
22+
23+
Example:
24+
25+
create:
26+
layout = new Layout.ForceDirected(graph, {width: 2000, height: 2000, iterations: 1000, layout: "3d"});
27+
28+
call init when graph is loaded (and for reset or when new nodes has been added to graph):
29+
layout.init();
30+
31+
call generate in a render method, returns true if it's still calculating and false if it's finished
32+
layout.generate();
33+
34+
35+
Feel free to contribute a new layout!
36+
37+
*/
138

239
var Layout = Layout || {};
340

441
Layout.ForceDirected = function(graph, options) {
542
var options = options || {};
43+
var layout = options.layout || "2d";
44+
var callback_positionUpdated = options.positionUpdated;
45+
646
var EPSILON = 0.000001;
747
var attraction_multiplier = options.attraction || 5;
848
var attraction_constant;
949
var repulsion_multiplier = options.repulsion || 0.75;
1050
var repulsion_constant;
11-
var max_dimension;
1251
var forceConstant;
1352
var layout_iterations = 0;
1453
var max_iterations = options.iterations || 10000;
@@ -17,7 +56,10 @@ Layout.ForceDirected = function(graph, options) {
1756
var width = options.width || 200;
1857
var height = options.height || 200;
1958

59+
this.finished = false;
60+
2061
this.init = function() {
62+
this.finished = false;
2163
temperature = width / 10.0;
2264
forceConstant = Math.sqrt(height * width / graph.nodes.length);
2365
attraction_constant = attraction_multiplier * forceConstant;
@@ -41,9 +83,10 @@ Layout.ForceDirected = function(graph, options) {
4183

4284
// info.innerHTML = "node_force: " + parseInt(node_force) + "<br>edge_force: " + edge_force + "<br>div: " + (node_force-edge_force);
4385

44-
temperature *= (1.0 - (layout_iterations / max_iterations));
86+
// temperature *= (1.0 - (layout_iterations / max_iterations));
4587
layout_iterations++;
4688
} else {
89+
this.finished = true;
4790
return false;
4891
}
4992
return true;
@@ -53,30 +96,52 @@ Layout.ForceDirected = function(graph, options) {
5396
node_v.layout = node_v.layout || {};
5497
node_v.layout.offset_x = 0;
5598
node_v.layout.offset_y = 0;
99+
if(layout === "3d") {
100+
node_v.layout.offset_z = 0;
101+
}
56102
node_v.layout.force = 0;
57103
node_v.layout.tmp_pos_x = node_v.layout.tmp_pos_x || node_v.position.x;
58104
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+
59109

60110

61111
graph.nodes.forEach(function(node_u) {
62112
if(node_v.id != node_u.id) {
63113
node_u.layout = node_u.layout || {};
64114
node_u.layout.tmp_pos_x = node_u.layout.tmp_pos_x || node_u.position.x;
65115
node_u.layout.tmp_pos_y = node_u.layout.tmp_pos_y || node_u.position.y;
116+
if(layout === "3d") {
117+
node_u.layout.tmp_pos_z = node_u.layout.tmp_pos_z || node_u.position.z;
118+
}
66119

67-
// var delta_x = node_v.position.x - node_u.position.x;
68-
// var delta_y = node_v.position.y - node_u.position.y;
69120
var delta_x = node_v.layout.tmp_pos_x - node_u.layout.tmp_pos_x;
70121
var delta_y = node_v.layout.tmp_pos_y - node_u.layout.tmp_pos_y;
122+
if(layout === "3d") {
123+
var delta_z = node_v.layout.tmp_pos_z - node_u.layout.tmp_pos_z;
124+
}
71125

72126
var delta_length = Math.max(EPSILON, Math.sqrt((delta_x * delta_x) + (delta_y * delta_y)));
127+
if(layout === "3d") {
128+
var delta_length_z = Math.max(EPSILON, Math.sqrt((delta_z * delta_z) + (delta_y * delta_y)));
129+
}
130+
73131
var force = (repulsion_constant * repulsion_constant) / delta_length;
132+
if(layout === "3d") {
133+
var force_z = (repulsion_constant * repulsion_constant) / delta_length_z;
134+
}
135+
74136

75137
node_v.layout.force += force;
76138
node_u.layout.force += force;
77139

78140
node_v.layout.offset_x += (delta_x / delta_length) * force;
79141
node_v.layout.offset_y += (delta_y / delta_length) * force;
142+
if(layout === "3d") {
143+
node_v.layout.offset_z += (delta_z / delta_length_z) * force_z;
144+
}
80145
}
81146
});
82147
};
@@ -86,36 +151,75 @@ Layout.ForceDirected = function(graph, options) {
86151
// var delta_y = edge.source.position.y - edge.target.position.y;
87152
var delta_x = edge.source.layout.tmp_pos_x - edge.target.layout.tmp_pos_x;
88153
var delta_y = edge.source.layout.tmp_pos_y - edge.target.layout.tmp_pos_y;
154+
if(layout === "3d") {
155+
var delta_z = edge.source.layout.tmp_pos_z - edge.target.layout.tmp_pos_z;
156+
}
89157

90158
var delta_length = Math.max(EPSILON, Math.sqrt((delta_x * delta_x) + (delta_y * delta_y)));
159+
if(layout === "3d") {
160+
var delta_length_z = Math.max(EPSILON, Math.sqrt((delta_z * delta_z) + (delta_y * delta_y)));
161+
}
91162
var force = (delta_length * delta_length) / attraction_constant;
163+
if(layout === "3d") {
164+
var force_z = (delta_length_z * delta_length_z) / attraction_constant;
165+
}
92166

93167
edge.source.layout.force -= force;
94168
edge.target.layout.force += force;
95169

96170
edge.source.layout.offset_x -= (delta_x / delta_length) * force;
97171
edge.source.layout.offset_y -= (delta_y / delta_length) * force;
172+
if(layout === "3d") {
173+
edge.source.layout.offset_z -= (delta_z / delta_length_z) * force_z;
174+
}
175+
98176
edge.target.layout.offset_x += (delta_x / delta_length) * force;
99177
edge.target.layout.offset_y += (delta_y / delta_length) * force;
178+
if(layout === "3d") {
179+
edge.target.layout.offset_z += (delta_z / delta_length_z) * force_z;
180+
}
100181
};
101182

102183
var calcPositions = function(graph, node) {
103184
var delta_length = Math.max(EPSILON, norm(node));
185+
if(layout === "3d") {
186+
var delta_length_z = Math.max(EPSILON, norm2(node));
187+
}
104188

105189
node.layout.tmp_pos_x += (node.layout.offset_x / delta_length) * Math.min(delta_length, temperature);
106190
node.layout.tmp_pos_y += (node.layout.offset_y / delta_length) * Math.min(delta_length, temperature);
191+
if(layout === "3d") {
192+
node.layout.tmp_pos_z += (node.layout.offset_z / delta_length_z) * Math.min(delta_length_z, temperature);
193+
}
107194

108195
var c = 100;
196+
var updated = false;
109197
if(node.position.x < (node.layout.tmp_pos_x - c) || node.position.x > (node.layout.tmp_pos_x + c)) {
110198
node.position.x -= (node.position.x-node.layout.tmp_pos_x)/10;
199+
updated = true;
111200
}
112201
if(node.position.y < (node.layout.tmp_pos_y - c) || node.position.y > (node.layout.tmp_pos_y + c)) {
113202
node.position.y -= (node.position.y-node.layout.tmp_pos_y)/10;
203+
updated = true;
204+
}
205+
if(layout === "3d") {
206+
if(node.position.z < (node.layout.tmp_pos_z - c) || node.position.z > (node.layout.tmp_pos_z + c)) {
207+
node.position.z -= (node.position.z-node.layout.tmp_pos_z)/10;
208+
updated = true;
209+
}
210+
}
211+
212+
if(updated && typeof callback_positionUpdated === 'function') {
213+
callback_positionUpdated(node);
114214
}
115215
};
116216

117217
var norm = function(node) {
118218
return Math.sqrt(node.layout.offset_x * node.layout.offset_x + node.layout.offset_y * node.layout.offset_y);
119219
};
120220

221+
var norm2 = function(node) {
222+
return Math.sqrt(node.layout.offset_z * node.layout.offset_z + node.layout.offset_y * node.layout.offset_y);
223+
};
224+
121225
};

0 commit comments

Comments
 (0)