1
+ <!DOCTYPE html>
2
+ < html lang ="en ">
3
+ < head >
4
+ < meta charset ="UTF-8 ">
5
+ < title > Title</ title >
6
+ </ head >
7
+ < body >
8
+ < script type ="text/javascript ">
9
+
10
+ function Vector ( x , y ) {
11
+ this . x = x ;
12
+ this . y = y ;
13
+ }
14
+
15
+ Vector . prototype . plus = function ( other ) {
16
+ return new Vector ( this . x + other . x , this . y + other . y ) ;
17
+ } ;
18
+ Vector . prototype . minus = function ( other ) {
19
+ return new Vector ( this . x - other . x , this . y - other . y ) ;
20
+ } ;
21
+ Vector . prototype . times = function ( factor ) {
22
+ return new Vector ( this . x * factor , this . y * factor ) ;
23
+ } ;
24
+ Object . defineProperty ( Vector . prototype , "length" , {
25
+ get : function ( ) {
26
+ return Math . sqrt ( this . x * this . x + this . y * this . y ) ;
27
+ }
28
+ } ) ;
29
+
30
+ var nodeSize = 8 ;
31
+
32
+ function drawGraph ( graph ) {
33
+ var canvas = document . querySelector ( "canvas" ) ;
34
+ if ( ! canvas ) {
35
+ canvas = document . body . appendChild ( document . createElement ( "canvas" ) ) ;
36
+ canvas . width = canvas . height = 500 ;
37
+ }
38
+ var cx = canvas . getContext ( "2d" ) ;
39
+
40
+ cx . clearRect ( 0 , 0 , canvas . width , canvas . height ) ;
41
+ var scale = new Scale ( graph , canvas . width , canvas . height ) ;
42
+
43
+ // Draw the edges.
44
+ cx . strokeStyle = "orange" ;
45
+ cx . lineWidth = 3 ;
46
+ graph . forEach ( function ( origin , i ) {
47
+ origin . edges . forEach ( function ( target ) {
48
+ if ( graph . indexOf ( target ) <= i ) return ;
49
+ cx . beginPath ( ) ;
50
+ cx . moveTo ( scale . x ( origin . pos . x ) , scale . y ( origin . pos . y ) ) ;
51
+ cx . lineTo ( scale . x ( target . pos . x ) , scale . y ( target . pos . y ) ) ;
52
+ cx . stroke ( ) ;
53
+ } ) ;
54
+ } ) ;
55
+
56
+ // Draw the nodes.
57
+ cx . fillStyle = "purple" ;
58
+ graph . forEach ( function ( node ) {
59
+ cx . beginPath ( ) ;
60
+ cx . arc ( scale . x ( node . pos . x ) , scale . y ( node . pos . y ) , nodeSize , 0 , 7 ) ;
61
+ cx . fill ( ) ;
62
+ } ) ;
63
+ }
64
+
65
+ // The function starts by drawing the edges, so that they appear
66
+ // behind the nodes. Since the nodes on _both_ side of an edge refer
67
+ // to each other, and we don't want to draw every edge twice, edges
68
+ // are only drawn then the target comes _after_ the current node in
69
+ // the `graph` array.
70
+
71
+ // When the edges have been drawn, the nodes are drawn on top of them
72
+ // as purple discs. Remember that the last argument to `arc` gives the
73
+ // rotation, and we have to pass something bigger than 2π to get a
74
+ // full circle.
75
+
76
+ // Finding a scale at which to draw the graph is done by finding the
77
+ // top left and bottom right corners of the area taken up by the
78
+ // nodes. The offset at which nodes are drawn is based on the top left
79
+ // corner, and the scale is based on the size of the canvas divided by
80
+ // the distance between those corners. The function reserves space
81
+ // along the sides of the canvas based on the `nodeSize` variable, so
82
+ // that the circles drawn around nodes’ center points don't get cut off.
83
+
84
+ function Scale ( graph , width , height ) {
85
+ var xs = graph . map ( function ( node ) { return node . pos . x } ) ;
86
+ var ys = graph . map ( function ( node ) { return node . pos . y } ) ;
87
+ var minX = Math . min . apply ( null , xs ) ;
88
+ var minY = Math . min . apply ( null , ys ) ;
89
+ var maxX = Math . max . apply ( null , xs ) ;
90
+ var maxY = Math . max . apply ( null , ys ) ;
91
+
92
+ this . offsetX = minX ; this . offsetY = minY ;
93
+ this . scaleX = ( width - 2 * nodeSize ) / ( maxX - minX ) ;
94
+ this . scaleY = ( height - 2 * nodeSize ) / ( maxY - minY ) ;
95
+ }
96
+
97
+ Scale . prototype . x = function ( x ) {
98
+ return this . scaleX * ( x - this . offsetX ) + nodeSize ;
99
+ } ;
100
+ Scale . prototype . y = function ( y ) {
101
+ return this . scaleY * ( y - this . offsetY ) + nodeSize ;
102
+ } ;
103
+
104
+ //http://eloquentjavascript.net/code/draw_graph.js
105
+
106
+
107
+ //
108
+ function GraphNode ( pos , edges ) {
109
+ this . pos = new Vector ( Math . random ( ) * 1000 ,
110
+ Math . random ( ) * 1000 ) ;
111
+ this . edges = [ ] ;
112
+ }
113
+ GraphNode . prototype . connect = function ( other ) {
114
+ this . edges . push ( other ) ;
115
+ other . edges . push ( this ) ;
116
+ } ;
117
+ GraphNode . prototype . hasEdge = function ( other ) {
118
+ for ( var i = 0 ; i < this . edges . length ; i ++ )
119
+ if ( this . edges [ i ] == other )
120
+ return true ;
121
+ } ;
122
+
123
+ function treeGraph ( depth , branches ) {
124
+ var graph = [ ] ;
125
+ function buildNode ( depth ) {
126
+ var node = new GraphNode ( ) ;
127
+ graph . push ( node ) ;
128
+ if ( depth > 1 )
129
+ for ( var i = 0 ; i < branches ; i ++ )
130
+ node . connect ( buildNode ( depth - 1 ) ) ;
131
+ return node ;
132
+ }
133
+ buildNode ( depth ) ;
134
+ return graph ;
135
+ }
136
+
137
+ var springLength = 40 ;
138
+ var springStrength = 0.1 ;
139
+
140
+ var repulsionStrength = 1500 ;
141
+
142
+ function forceDirected_simple ( graph ) {
143
+ graph . forEach ( function ( node ) {
144
+ graph . forEach ( function ( other ) {
145
+ if ( other == node ) return ;
146
+ var apart = other . pos . minus ( node . pos ) ;
147
+ var distance = Math . max ( 1 , apart . length ) ;
148
+ var forceSize = - repulsionStrength / ( distance * distance ) ;
149
+ if ( node . hasEdge ( other ) )
150
+ forceSize += ( distance - springLength ) * springStrength ;
151
+ var normalized = apart . times ( 1 / distance ) ;
152
+ node . pos = node . pos . plus ( normalized . times ( forceSize ) ) ;
153
+ } ) ;
154
+ } ) ;
155
+ }
156
+
157
+ function runLayout ( implementation , graph ) {
158
+ var totalSteps = 0 , time = 0 ;
159
+ function step ( ) {
160
+ var startTime = Date . now ( ) ;
161
+ for ( var i = 0 ; i < 100 ; i ++ )
162
+ implementation ( graph ) ;
163
+ totalSteps += 100 ;
164
+ time += Date . now ( ) - startTime ;
165
+ drawGraph ( graph ) ;
166
+
167
+ if ( totalSteps < 4000 )
168
+ requestAnimationFrame ( step ) ;
169
+ else
170
+ console . log ( time ) ;
171
+ }
172
+ step ( ) ;
173
+ }
174
+
175
+ function forceDirected_forloop ( graph ) {
176
+ for ( var i = 0 ; i < graph . length ; i ++ ) {
177
+ var node = graph [ i ] ;
178
+ for ( var j = 0 ; j < graph . length ; j ++ ) {
179
+ if ( i == j ) continue ;
180
+ var other = graph [ j ] ;
181
+ var apart = other . pos . minus ( node . pos ) ;
182
+ var distance = Math . max ( 1 , apart . length ) ;
183
+ var forceSize = - 1 * repulsionStrength / ( distance * distance ) ;
184
+ if ( node . hasEdge ( other ) )
185
+ forceSize += ( distance - springLength ) * springStrength ;
186
+ var normalized = apart . times ( 1 / distance ) ;
187
+ node . pos = node . pos . plus ( normalized . times ( forceSize ) ) ;
188
+ }
189
+ }
190
+ }
191
+
192
+ function forceDirected_norepeat ( graph ) {
193
+ for ( var i = 0 ; i < graph . length ; i ++ ) {
194
+ var node = graph [ i ] ;
195
+ for ( var j = i + 1 ; j < graph . length ; j ++ ) {
196
+ var other = graph [ j ] ;
197
+ var apart = other . pos . minus ( node . pos ) ;
198
+ var distance = Math . max ( 1 , apart . length ) ;
199
+ var forceSize = - 1 * repulsionStrength / ( distance * distance ) ;
200
+ if ( node . hasEdge ( other ) )
201
+ forceSize += ( distance - springLength ) * springStrength ;
202
+ var applied = apart . times ( forceSize / distance ) ;
203
+ node . pos = node . pos . plus ( applied ) ;
204
+ other . pos = other . pos . minus ( applied ) ;
205
+ }
206
+ }
207
+ }
208
+
209
+ function forceDirected_novector ( graph ) {
210
+ for ( var i = 0 ; i < graph . length ; i ++ ) {
211
+ var node = graph [ i ] ;
212
+ for ( var j = i + 1 ; j < graph . length ; j ++ ) {
213
+ var other = graph [ j ] ;
214
+ var apartX = other . pos . x - node . pos . x ;
215
+ var apartY = other . pos . y - node . pos . y ;
216
+ var distance = Math . max ( 1 , Math . sqrt ( apartX * apartX + apartY * apartY ) ) ;
217
+ var forceSize = - repulsionStrength / ( distance * distance ) ;
218
+ if ( node . hasEdge ( other ) )
219
+ forceSize += ( distance - springLength ) * springStrength ;
220
+
221
+ var forceX = apartX * forceSize / distance ;
222
+ var forceY = apartY * forceSize / distance ;
223
+ node . pos . x += forceX ; node . pos . y += forceY ;
224
+ other . pos . x -= forceX ; other . pos . y -= forceY ;
225
+ }
226
+ }
227
+ }
228
+
229
+ function forceDirected_localforce ( graph ) {
230
+ var forcesX = [ ] , forcesY = [ ] ;
231
+ for ( var i = 0 ; i < graph . length ; i ++ )
232
+ forcesX [ i ] = forcesY [ i ] = 0 ;
233
+
234
+ for ( var i = 0 ; i < graph . length ; i ++ ) {
235
+ var node = graph [ i ] ;
236
+ for ( var j = i + 1 ; j < graph . length ; j ++ ) {
237
+ var other = graph [ j ] ;
238
+ var apartX = other . pos . x - node . pos . x ;
239
+ var apartY = other . pos . y - node . pos . y ;
240
+ var distance = Math . max ( 1 , Math . sqrt ( apartX * apartX + apartY * apartY ) ) ;
241
+ var forceSize = - repulsionStrength / ( distance * distance ) ;
242
+ if ( node . hasEdge ( other ) )
243
+ forceSize += ( distance - springLength ) * springStrength ;
244
+
245
+ var forceX = apartX * forceSize / distance ;
246
+ var forceY = apartY * forceSize / distance ;
247
+ forcesX [ i ] += forceX ; forcesY [ i ] += forceY ;
248
+ forcesX [ j ] -= forceX ; forcesY [ j ] -= forceY ;
249
+ }
250
+ }
251
+
252
+ for ( var i = 0 ; i < graph . length ; i ++ ) {
253
+ graph [ i ] . pos . x += forcesX [ i ] ;
254
+ graph [ i ] . pos . y += forcesY [ i ] ;
255
+ }
256
+ }
257
+
258
+ var mangledGraph = treeGraph ( 4 , 4 ) ;
259
+ mangledGraph . forEach ( function ( node ) {
260
+ var letter = Math . floor ( Math . random ( ) * 26 ) ;
261
+ node [ String . fromCharCode ( "A" . charCodeAt ( 0 ) + letter ) ] = true ;
262
+ } ) ;
263
+
264
+
265
+ runLayout ( forceDirected_simple , treeGraph ( 4 , 4 ) ) ;
266
+
267
+
268
+
269
+ </ script >
270
+ </ body >
271
+ </ html >
0 commit comments