Skip to content

Commit d0e2c73

Browse files
authored
Merge pull request #15 from davidpiegza/fix-vector-calculation-in-fd-layout
Fix vector calculation in force-directed-layout
2 parents e8f941e + 7c5110e commit d0e2c73

File tree

5 files changed

+269
-97
lines changed

5 files changed

+269
-97
lines changed

build/graph.min.js

Lines changed: 9 additions & 9 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

index_example.html

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
<script type="text/javascript" src="utils/TrackballControls.js"></script>
2323
<script type="text/javascript" src="utils/Label.js"></script>
2424
<script type="text/javascript" src="utils/ObjectSelection.js"></script>
25+
<script type="text/javascript" src="utils/Vector3.js"></script>
2526
<script type="text/javascript" src="layouts/force-directed-layout.js"></script>
2627
<script type="text/javascript" src="drawings/simple_graph.js"></script>
2728
<script type="text/javascript" src="drawings/sphere_graph.js"></script>

layouts/force-directed-layout.js

Lines changed: 31 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
/**
2-
@author David Piegza
2+
@author David Piegza (@davidpiegza)
3+
@author Timofey Rechkalov (@TRechkalov)
34
45
Implements a force-directed layout, the algorithm is based on Fruchterman and Reingold and
56
the JUNG implementation.
67
7-
Needs the graph data structure Graph.js:
8+
Needs the graph data structure Graph.js and the Vector3 object:
89
https://github.com/davidpiegza/Graph-Visualization/blob/master/Graph.js
10+
https://github.com/davidpiegza/Graph-Visualization/blob/master/utils/Vector3.js
911
1012
Parameters:
1113
graph - data structure
@@ -87,140 +89,82 @@ Layout.ForceDirected = function(graph, options) {
8789
this.generate = function() {
8890
if(layout_iterations < this.max_iterations && temperature > 0.000001) {
8991
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;
9393

9494
// calculate repulsion
9595
for(i=0; i < nodes_length; i++) {
9696
var node_v = graph.nodes[i];
9797
node_v.layout = node_v.layout || {};
9898
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();
104100
}
105101

106102
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);
112104

113-
for(var j=i+1; j < nodes_length; j++) {
105+
for(j=i+1; j < nodes_length; j++) {
114106
var node_u = graph.nodes[j];
115107
if(i != j) {
116108
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-
}
122109

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);
125111

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()));
134114

135115
force = (repulsion_constant * repulsion_constant) / delta_length;
136116

137-
if(this.layout === "3d") {
138-
force_z = (repulsion_constant * repulsion_constant) / delta_length_z;
139-
}
140-
141117
node_v.layout.force += force;
142118
node_u.layout.force += force;
143119

144-
node_v.layout.offset_x += (delta_x / delta_length) * force;
145-
node_v.layout.offset_y += (delta_y / delta_length) * force;
146-
147120
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();
153122
}
154-
node_u.layout.offset_x -= (delta_x / delta_length) * force;
155-
node_u.layout.offset_y -= (delta_y / delta_length) * force;
156123

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);
161127
}
162128
}
163129
}
164130

165131
// calculate attraction
166132
for(i=0; i < edges_length; i++) {
167133
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()));
178136

179137
force = (delta_length * delta_length) / attraction_constant;
180-
if(this.layout === "3d") {
181-
force_z = (delta_length_z * delta_length_z) / attraction_constant;
182-
}
183138

184139
edge.source.layout.force -= force;
185140
edge.target.layout.force += force;
186141

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);
198145
}
199146

200147
// calculate positions
201148
for(i=0; i < nodes_length; i++) {
202149
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));
204150

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()));
208152

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)));
214154

215155
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;
218156

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;
221165
}
222166

223-
// execute callback function if positions has been updated
167+
// execute callback function if position has been updated
224168
if(updated && typeof callback_positionUpdated === 'function') {
225169
callback_positionUpdated(node);
226170
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "graph-visualization",
3-
"version": "0.4.0",
3+
"version": "0.4.1",
44
"devDependencies": {
55
"grunt": "~1.0.1",
66
"grunt-contrib-jshint": "~1.1.0",

0 commit comments

Comments
 (0)