Merge lp:~stothardj/dracula/nonodelist into lp:dracula
- nonodelist
- Merge into trunk
Status: | Merged |
---|---|
Merged at revision: | 39 |
Proposed branch: | lp:~stothardj/dracula/nonodelist |
Merge into: | lp:dracula |
Diff against target: |
1142 lines (+567/-401) 8 files modified
algorithms.js (+102/-0) classes.html (+18/-0) classes.js (+23/-0) dracula_algorithms.html (+13/-115) example.js (+109/-0) index.html (+2/-111) js/dracula_algorithms.js (+71/-4) js/dracula_graph.js (+229/-171) |
To merge this branch: | bzr merge lp:~stothardj/dracula/nonodelist |
Related bugs: |
Reviewer | Review Type | Date Requested | Status |
---|---|---|---|
Johann Philipp Strathausen | Approve | ||
Review via email: mp+39216@code.launchpad.net |
Commit message
Description of the change
* Removed redundant nodelist data structure.
* Removed redundant "backedges" when undirected graphs are made. Instead add a reference to the same edge to both nodes. Modified algorithm functions to work with this.
* Made removeNode work.
* Added Topological sort algorithm.
* Added ordered layout to show of topological sort.
Johann Philipp Strathausen (strathausen) wrote : | # |
Jake Stothard (stothardj) wrote : | # |
Hey, no problem. Hope you don't mind that a lot of the patch is just from my
text editor tabbing differently. Anyway, I'll be around if you want to ask
me about any of the changes.
On Tue, Nov 2, 2010 at 5:11 AM, Filip <email address hidden> wrote:
> Hi there, thanks a lot for the merge request! I was travelling last week,
> so I didn't have a look at it yet. But I think I'll approve it soon.
> --
> https:/
> You are the owner of lp:~stothardj/dracula/nonodelist.
>
Johann Philipp Strathausen (strathausen) wrote : | # |
Yes, I have a first question after a first glance at the diff: Is the jQuery dependency really necessary? I like jQuery and use it for most of my projects anyway, but otherwise I wanted to keep the dependencies as minimal as possible. Like for example, there used to be a Prototype dependency just for using class structures, until I figured out that using plain JavaScript prototype and such would do the same with the same amount of code.
So, if you really think jQuery is a big help here and it's not possible without it, I will leave the jQuery code in Dracula.
Jake Stothard (stothardj) wrote : | # |
Short answer: No, you can pull it out.
I thought I'd be using jQuery for more in this, but it seems not to be
necessary actually. I was using jQuery.extend to make copies, but I saw you
had a clone function so if that works that can be used. The other thing was
using jQuery to get the window size in the demos, which again is replacable.
That's all I can see that I used.
On Wed, Nov 3, 2010 at 2:34 AM, Filip <email address hidden> wrote:
> Yes, I have a first question after a first glance at the diff: Is the
> jQuery dependency really necessary? I like jQuery and use it for most of my
> projects anyway, but otherwise I wanted to keep the dependencies as minimal
> as possible. Like for example, there used to be a Prototype dependency just
> for using class structures, until I figured out that using plain JavaScript
> prototype and such would do the same with the same amount of code.
>
> So, if you really think jQuery is a big help here and it's not possible
> without it, I will leave the jQuery code in Dracula.
> --
> https:/
> You are the owner of lp:~stothardj/dracula/nonodelist.
>
Johann Philipp Strathausen (strathausen) wrote : | # |
Okay, looks fine to me :-) thanks for the work! I will merge it now and remove the dependence of jQuery later...
Preview Diff
1 | === added file 'algorithms.js' |
2 | --- algorithms.js 1970-01-01 00:00:00 +0000 |
3 | +++ algorithms.js 2010-10-24 05:37:41 +0000 |
4 | @@ -0,0 +1,102 @@ |
5 | +var redraw; |
6 | + |
7 | +window.onload = function() { |
8 | + var width = $(document).width(); |
9 | + var height = $(document).height() - 100; |
10 | + |
11 | + /* Showcase of the Bellman-Ford search algorithm finding shortest paths |
12 | + from one point to every node */ |
13 | + |
14 | + /* */ |
15 | + |
16 | + /* We need to write a new node renderer function to display the computed |
17 | + distance. |
18 | + (the Raphael graph drawing implementation of Dracula can draw this shape, |
19 | + please consult the RaphaelJS reference for details http://raphaeljs.com/) */ |
20 | + var render = function(r, n) { |
21 | + /* the Raphael set is obligatory, containing all you want to display */ |
22 | + var set = r.set().push( |
23 | + /* custom objects go here */ |
24 | + r.rect(n.point[0]-30, n.point[1]-13, 60, 44).attr({"fill": "#feb", r : "12px", "stroke-width" : n.distance == 0 ? "3px" : "1px" })).push( |
25 | + r.text(n.point[0], n.point[1] + 10, (n.label || n.id) + "\n(" + (n.distance == undefined ? "Infinity" : n.distance) + ")")); |
26 | + return set; |
27 | + }; |
28 | + |
29 | + var g = new Graph(); |
30 | + |
31 | + /* modify the edge creation to attach random weights */ |
32 | + g.edgeFactory.build = function(source, target) { |
33 | + var e = jQuery.extend(true, {}, this.template); |
34 | + e.source = source; |
35 | + e.target = target; |
36 | + e.style.label = e.weight = Math.floor(Math.random() * 10) + 1; |
37 | + return e; |
38 | + } |
39 | + |
40 | + /* creating nodes and passing the new renderer function to overwrite the default one */ |
41 | + g.addNode("New York", {render:render}); // TODO add currying support for nicer code |
42 | + g.addNode("Berlin", {render:render}); |
43 | + g.addNode("Tel Aviv", {render:render}); |
44 | + g.addNode("Tokyo", {render:render}); |
45 | + g.addNode("Roma", {render:render}); |
46 | + g.addNode("Madrid", {render:render}); |
47 | + |
48 | + /* connections */ |
49 | + g.addEdge("Tokyo", "Tel Aviv"/*, {weight:9, directed: true, stroke : "#bfa"}*/); // also supports directed graphs, but currently doesn't look that nice |
50 | + g.addEdge("Tokyo", "New York"); |
51 | + g.addEdge("Tokyo", "Berlin"); |
52 | + g.addEdge("Tel Aviv", "Berlin"); |
53 | + g.addEdge("Tel Aviv", "New York"); |
54 | + g.addEdge("Tel Aviv", "Roma"); |
55 | + g.addEdge("Roma", "New York"); |
56 | + g.addEdge("Berlin", "New York"); |
57 | + g.addEdge("Madrid", "New York"); |
58 | + g.addEdge("Madrid", "Roma"); |
59 | + g.addEdge("Madrid", "Tokyo"); |
60 | + |
61 | + /* random edge weights (our undirected graph is modelled as a bidirectional graph) */ |
62 | +/* for(e in g.edges) |
63 | + if(g.edges[e].backedge != undefined) { |
64 | + g.edges[e].weight = Math.floor(Math.random()*10) + 1; |
65 | + g.edges[e].backedge.weight = g.edges[e].weight; |
66 | + } |
67 | +*/ |
68 | + /* layout the graph using the Spring layout implementation */ |
69 | + var layouter = new Graph.Layout.Spring(g); |
70 | + |
71 | + /* draw the graph using the RaphaelJS draw implementation */ |
72 | + |
73 | + /* calculating the shortest paths via Bellman Ford */ |
74 | +// bellman_ford(g, g.nodes["Berlin"]); |
75 | + |
76 | + /* calculating the shortest paths via Dijkstra */ |
77 | + dijkstra(g, g.nodes["Berlin"]); |
78 | + |
79 | + /* calculating the shortest paths via Floyd-Warshall */ |
80 | + floyd_warshall(g, g.nodes["Berlin"]); |
81 | + |
82 | + |
83 | + /* colourising the shortest paths and setting labels */ |
84 | + for(e in g.edges) { |
85 | + if(g.edges[e].target.predecessor === g.edges[e].source || g.edges[e].source.predecessor === g.edges[e].target) { |
86 | + g.edges[e].style.stroke = "#bfa"; |
87 | + g.edges[e].style.fill = "#56f"; |
88 | + } else { |
89 | + g.edges[e].style.stroke = "#aaa"; |
90 | + } |
91 | + } |
92 | + |
93 | + var renderer = new Graph.Renderer.Raphael('canvas', g, width, height); |
94 | + |
95 | + redraw = function() { |
96 | + layouter.layout(); |
97 | + renderer.draw(); |
98 | + }; |
99 | + |
100 | +/* var pos=0; |
101 | + step = function(dir) { |
102 | + pos+=dir; |
103 | + var renderer = new Graph.Renderer.Raphael('canvas', g.snapshots[pos], width, height); |
104 | + renderer.draw(); |
105 | + };*/ |
106 | +}; |
107 | \ No newline at end of file |
108 | |
109 | === added file 'classes.html' |
110 | --- classes.html 1970-01-01 00:00:00 +0000 |
111 | +++ classes.html 2010-10-24 05:37:41 +0000 |
112 | @@ -0,0 +1,18 @@ |
113 | +<html> |
114 | +<head> |
115 | + <script type="text/javascript" src="js/raphael-min.js"></script> |
116 | + <script type="text/javascript" src="js/dracula_graffle.js"></script> |
117 | + <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script> |
118 | + <script type="text/javascript" src="js/dracula_graph.js"></script> |
119 | + <script type="text/javascript" src="js/dracula_algorithms.js"></script> |
120 | + <script type="text/javascript" src="classes.js"></script> |
121 | + <style type="text/css"> |
122 | + body { |
123 | + overflow: hidden; |
124 | + } |
125 | + </style> |
126 | +</head> |
127 | +<body> |
128 | +<div id="canvas"></div> |
129 | +</body> |
130 | +</html> |
131 | |
132 | === added file 'classes.js' |
133 | --- classes.js 1970-01-01 00:00:00 +0000 |
134 | +++ classes.js 2010-10-24 05:37:41 +0000 |
135 | @@ -0,0 +1,23 @@ |
136 | +//Show UCLA CS class dependencies (not complete) |
137 | +$(document).ready(function() { |
138 | + var width = $(document).width(); |
139 | + var height = $(document).height(); |
140 | + var g = new Graph(); |
141 | + g.edgeFactory.template.style.directed = true; |
142 | + g.addEdge("CS 31","CS 32"); |
143 | + g.addEdge("CS 32","CS 33"); |
144 | + g.addEdge("CS 33","CS 35L"); |
145 | + g.addNode("CS M51A"); |
146 | + g.addEdge("CS 32", "CS 111"); |
147 | + g.addEdge("CS 33", "CS 111"); |
148 | + g.addEdge("CS 35L", "CS 111"); |
149 | + g.addEdge("CS 32", "CS 118"); |
150 | + g.addEdge("CS 33", "CS 118"); |
151 | + g.addEdge("CS 35L", "CS 118"); |
152 | + g.addEdge("CS 111", "CS 118"); |
153 | + g.addEdge("CS 32", "CS 131"); |
154 | + g.addEdge("CS 33", "CS 131"); |
155 | + g.addEdge("CS 35L", "CS 131"); |
156 | + var layouter = new Graph.Layout.Ordered(g, topological_sort(g)); |
157 | + var renderer = new Graph.Renderer.Raphael('canvas', g, width, height); |
158 | +}); |
159 | \ No newline at end of file |
160 | |
161 | === modified file 'dracula_algorithms.html' |
162 | --- dracula_algorithms.html 2010-09-17 18:04:25 +0000 |
163 | +++ dracula_algorithms.html 2010-10-24 05:37:41 +0000 |
164 | @@ -1,122 +1,20 @@ |
165 | <html> |
166 | -<head> |
167 | -<!-- jQuery --> |
168 | + <head> |
169 | + <!-- jQuery --> |
170 | <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script> |
171 | -<!-- The Raphael JavaScript library for vector graphics display --> |
172 | + <!-- The Raphael JavaScript library for vector graphics display --> |
173 | <script type="text/javascript" src="js/raphael-min.js"></script> |
174 | -<!-- Dracula --> |
175 | -<!-- An extension of Raphael for connecting shapes --> |
176 | + <!-- Dracula --> |
177 | + <!-- An extension of Raphael for connecting shapes --> |
178 | <script type="text/javascript" src="js/dracula_graffle.js"></script> |
179 | -<!-- Graphs --> |
180 | + <!-- Graphs --> |
181 | <script type="text/javascript" src="js/dracula_graph.js"></script> |
182 | <script type="text/javascript" src="js/dracula_algorithms.js"></script> |
183 | - <script type="text/javascript"> |
184 | -<!-- |
185 | - |
186 | -var redraw; |
187 | -var height = 300; |
188 | -var width = 400; |
189 | - |
190 | -window.onload = function() { |
191 | - |
192 | - /* Showcase of the Bellman-Ford search algorithm finding shortest paths |
193 | - from one point to every node */ |
194 | - |
195 | - /* */ |
196 | - |
197 | - /* We need to write a new node renderer function to display the computed |
198 | - distance. |
199 | - (the Raphael graph drawing implementation of Dracula can draw this shape, |
200 | - please consult the RaphaelJS reference for details http://raphaeljs.com/) */ |
201 | - var render = function(r, n) { |
202 | - /* the Raphael set is obligatory, containing all you want to display */ |
203 | - var set = r.set().push( |
204 | - /* custom objects go here */ |
205 | - r.rect(n.point[0]-30, n.point[1]-13, 60, 44).attr({"fill": "#feb", r : "12px", "stroke-width" : n.distance == 0 ? "3px" : "1px" })).push( |
206 | - r.text(n.point[0], n.point[1] + 10, (n.label || n.id) + "\n(" + (n.distance == undefined ? "Infinity" : n.distance) + ")")); |
207 | - return set; |
208 | - }; |
209 | - |
210 | - var g = new Graph(); |
211 | - /* creating nodes and passing the new renderer function to overwrite the default one */ |
212 | - g.addNode("New York", {render:render}); // TODO add currying support for nicer code |
213 | - g.addNode("Berlin", {render:render}); |
214 | - g.addNode("Tel Aviv", {render:render}); |
215 | - g.addNode("Tokyo", {render:render}); |
216 | - g.addNode("Roma", {render:render}); |
217 | - g.addNode("Madrid", {render:render}); |
218 | - |
219 | - /* modify the addEdge function to attach random weights */ |
220 | - g.addEdge2 = g.addEdge; |
221 | - g.addEdge = function(from, to, style) { !style && (style={}); style.weight = Math.floor(Math.random()*10) + 1; g.addEdge2(from, to, style); }; |
222 | - |
223 | - /* connections */ |
224 | - g.addEdge("Tokyo", "Tel Aviv"/*, {weight:9, directed: true, stroke : "#bfa"}*/); // also supports directed graphs, but currently doesn't look that nice |
225 | - g.addEdge("Tokyo", "New York"); |
226 | - g.addEdge("Tokyo", "Berlin"); |
227 | - g.addEdge("Tel Aviv", "Berlin"); |
228 | - g.addEdge("Tel Aviv", "New York"); |
229 | - g.addEdge("Tel Aviv", "Roma"); |
230 | - g.addEdge("Roma", "New York"); |
231 | - g.addEdge("Berlin", "New York"); |
232 | - g.addEdge("Madrid", "New York"); |
233 | - g.addEdge("Madrid", "Roma"); |
234 | - g.addEdge("Madrid", "Tokyo"); |
235 | - |
236 | - /* random edge weights (our undirected graph is modelled as a bidirectional graph) */ |
237 | -/* for(e in g.edges) |
238 | - if(g.edges[e].backedge != undefined) { |
239 | - g.edges[e].weight = Math.floor(Math.random()*10) + 1; |
240 | - g.edges[e].backedge.weight = g.edges[e].weight; |
241 | - } |
242 | -*/ |
243 | - /* layout the graph using the Spring layout implementation */ |
244 | - var layouter = new Graph.Layout.Spring(g); |
245 | - |
246 | - /* draw the graph using the RaphaelJS draw implementation */ |
247 | - |
248 | - /* calculating the shortest paths via Bellman Ford */ |
249 | -// bellman_ford(g, g.nodes["Berlin"]); |
250 | - |
251 | - /* calculating the shortest paths via Dijkstra */ |
252 | - dijkstra(g, g.nodes["Berlin"]); |
253 | - |
254 | - /* calculating the shortest paths via Floyd-Warshall */ |
255 | - floyd_warshall(g, g.nodes["Berlin"]); |
256 | - |
257 | - |
258 | - /* colourising the shortest paths and setting labels */ |
259 | - for(e in g.edges) { |
260 | - g.edges[e].style = { label : g.edges[e].weight }; |
261 | - if(g.edges[e].target.predecessor === g.edges[e].source || g.edges[e].source.predecessor === g.edges[e].target) { |
262 | - g.edges[e].style.stroke = "#bfa"; |
263 | - g.edges[e].style.fill = "#56f"; |
264 | - } else { |
265 | - g.edges[e].style.stroke = "#aaa"; |
266 | - } |
267 | - } |
268 | - |
269 | - var renderer = new Graph.Renderer.Raphael('canvas', g, width, height); |
270 | - |
271 | - redraw = function() { |
272 | - layouter.layout(); |
273 | - renderer.draw(); |
274 | - }; |
275 | - |
276 | -/* var pos=0; |
277 | - step = function(dir) { |
278 | - pos+=dir; |
279 | - var renderer = new Graph.Renderer.Raphael('canvas', g.snapshots[pos], width, height); |
280 | - renderer.draw(); |
281 | - };*/ |
282 | -}; |
283 | - |
284 | ---> |
285 | - </script> |
286 | -</head> |
287 | -<body> |
288 | -<div id="canvas"></div> |
289 | -Looks ugly? Hit <button id="redraw" onclick="redraw();">redraw</button>!<br> |
290 | -<!--<button onclick="step(-1)">Previous step</button> - <button onclick="step(1)">Next step</button>--> |
291 | -</body> |
292 | + <script type="text/javascript" src="algorithms.js"></script> |
293 | + </head> |
294 | + <body> |
295 | + <div id="canvas"></div> |
296 | + Looks ugly? Hit <button id="redraw" onclick="redraw();">redraw</button>!<br> |
297 | + <!--<button onclick="step(-1)">Previous step</button> - <button onclick="step(1)">Next step</button>--> |
298 | + </body> |
299 | </html> |
300 | |
301 | === added file 'example.js' |
302 | --- example.js 1970-01-01 00:00:00 +0000 |
303 | +++ example.js 2010-10-24 05:37:41 +0000 |
304 | @@ -0,0 +1,109 @@ |
305 | + |
306 | +var redraw, g, renderer; |
307 | + |
308 | +/* only do all this when document has finished loading (needed for RaphaelJS) */ |
309 | +window.onload = function() { |
310 | + |
311 | + var width = $(document).width() - 20; |
312 | + var height = $(document).height() - 60; |
313 | + |
314 | + g = new Graph(); |
315 | + |
316 | + /* add a simple node */ |
317 | + g.addNode("strawberry"); |
318 | + g.addNode("cherry"); |
319 | + |
320 | + /* add a node with a customized label */ |
321 | + g.addNode("1", { label : "Tomato" }); |
322 | + |
323 | + /* add a node with a customized shape |
324 | + (the Raphael graph drawing implementation can draw this shape, please |
325 | + consult the RaphaelJS reference for details http://raphaeljs.com/) */ |
326 | + var render = function(r, n) { |
327 | + var label = r.text(0, 30, n.label).attr({opacity:0}); |
328 | + /* the Raphael set is obligatory, containing all you want to display */ |
329 | + var set = r.set().push( |
330 | + r.rect(-30, -13, 62, 86).attr({"fill": "#fa8", "stroke-width": 2, r : "9px"})) |
331 | + .push(label); |
332 | + /* make the label show only on hover */ |
333 | + set.hover(function(){ label.animate({opacity:1,"fill-opacity":1}, 500); }, function(){ label.animate({opacity:0},300); }); |
334 | + |
335 | + tooltip = r.set() |
336 | + .push( |
337 | + r.rect(0, 0, 90, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"}) |
338 | + ).push( |
339 | + r.text(25, 15, "overlay").attr({"fill": "#000000"}) |
340 | + ); |
341 | + for(i in set.items) { |
342 | + set.items[i].tooltip(tooltip); |
343 | + }; |
344 | + // set.tooltip(r.set().push(r.rect(0, 0, 30, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})).hide()); |
345 | + return set; |
346 | + }; |
347 | + g.addNode("id35", { |
348 | + label : "meat\nand\ngreed" , |
349 | + /* filling the shape with a color makes it easier to be dragged */ |
350 | + /* arguments: r = Raphael object, n : node object */ |
351 | + render : render |
352 | + }); |
353 | + // g.addNode("Wheat", { |
354 | + /* filling the shape with a color makes it easier to be dragged */ |
355 | + /* arguments: r = Raphael object, n : node object */ |
356 | + // shapes : [ { |
357 | + // type: "rect", |
358 | + // x: 10, |
359 | + // y: 10, |
360 | + // width: 25, |
361 | + // height: 25, |
362 | + // stroke: "#f00" |
363 | + // }, { |
364 | + // type: "text", |
365 | + // x: 30, |
366 | + // y: 40, |
367 | + // text: "Dump" |
368 | + // }], |
369 | + // overlay : "<b>Hello <a href=\"http://wikipedia.org/\">World!</a></b>" |
370 | + // }); |
371 | + |
372 | + st = { directed: true, label : "Label"}; |
373 | + g.addEdge("kiwi", "penguin", st); |
374 | + |
375 | + /* connect nodes with edges */ |
376 | + g.addEdge("strawberry", "cherry"); |
377 | + g.addEdge("cherry", "apple"); |
378 | + g.addEdge("cherry", "apple") |
379 | + g.addEdge("1", "id35"); |
380 | + g.addEdge("penguin", "id35"); |
381 | + g.addEdge("penguin", "apple"); |
382 | + g.addEdge("kiwi", "id35"); |
383 | + |
384 | + /* a directed connection, using an arrow */ |
385 | + g.addEdge("1", "cherry", { directed : true } ); |
386 | + |
387 | + /* customize the colors of that edge */ |
388 | + g.addEdge("id35", "apple", { stroke : "#bfa" , fill : "#56f", label : "Meat-to-Apple" }); |
389 | + |
390 | + /* add an unknown node implicitly by adding an edge */ |
391 | + g.addEdge("strawberry", "apple"); |
392 | + |
393 | + g.removeNode("1"); |
394 | + |
395 | + /* layout the graph using the Spring layout implementation */ |
396 | + var layouter = new Graph.Layout.Spring(g); |
397 | + |
398 | + /* draw the graph using the RaphaelJS draw implementation */ |
399 | + renderer = new Graph.Renderer.Raphael('canvas', g, width, height); |
400 | + |
401 | + redraw = function() { |
402 | + layouter.layout(); |
403 | + renderer.draw(); |
404 | + }; |
405 | + hide = function(id) { |
406 | + g.nodes[id].hide(); |
407 | + }; |
408 | + show = function(id) { |
409 | + g.nodes[id].show(); |
410 | + }; |
411 | + // console.log(g.nodes["kiwi"]); |
412 | +}; |
413 | + |
414 | |
415 | === modified file 'index.html' |
416 | --- index.html 2010-09-20 18:56:38 +0000 |
417 | +++ index.html 2010-10-24 05:37:41 +0000 |
418 | @@ -2,118 +2,9 @@ |
419 | <head> |
420 | <script type="text/javascript" src="js/raphael-min.js"></script> |
421 | <script type="text/javascript" src="js/dracula_graffle.js"></script> |
422 | + <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script> |
423 | <script type="text/javascript" src="js/dracula_graph.js"></script> |
424 | - <script type="text/javascript"> |
425 | -<!-- |
426 | - |
427 | -var redraw, g, renderer; |
428 | -var height = 300; |
429 | -var width = 400; |
430 | - |
431 | -/* only do all this when document has finished loading (needed for RaphaelJS) */ |
432 | -window.onload = function() { |
433 | - |
434 | - g = new Graph(); |
435 | - |
436 | - /* add a simple node */ |
437 | - g.addNode("strawberry"); |
438 | - g.addNode("cherry"); |
439 | - |
440 | - /* add a node with a customized label */ |
441 | - g.addNode("1", { label : "Tomato" }); |
442 | - |
443 | - /* add a node with a customized shape |
444 | - (the Raphael graph drawing implementation can draw this shape, please |
445 | - consult the RaphaelJS reference for details http://raphaeljs.com/) */ |
446 | - var render = function(r, n) { |
447 | - var label = r.text(0, 30, n.label).attr({opacity:0}); |
448 | - /* the Raphael set is obligatory, containing all you want to display */ |
449 | - var set = r.set().push( |
450 | - r.rect(-30, -13, 62, 86).attr({"fill": "#fa8", "stroke-width": 2, r : "9px"})) |
451 | - .push(label); |
452 | - /* make the label show only on hover */ |
453 | - set.hover(function(){ label.animate({opacity:1,"fill-opacity":1}, 500); }, function(){ label.animate({opacity:0},300); }); |
454 | - |
455 | - tooltip = r.set() |
456 | - .push( |
457 | - r.rect(0, 0, 90, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"}) |
458 | - ).push( |
459 | - r.text(25, 15, "overlay").attr({"fill": "#000000"}) |
460 | - ); |
461 | - for(i in set.items) { |
462 | - set.items[i].tooltip(tooltip); |
463 | - }; |
464 | -// set.tooltip(r.set().push(r.rect(0, 0, 30, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})).hide()); |
465 | - return set; |
466 | - }; |
467 | - g.addNode("id35", { |
468 | - label : "meat\nand\ngreed" , |
469 | - /* filling the shape with a color makes it easier to be dragged */ |
470 | - /* arguments: r = Raphael object, n : node object */ |
471 | - render : render |
472 | - }); |
473 | -// g.addNode("Wheat", { |
474 | - /* filling the shape with a color makes it easier to be dragged */ |
475 | - /* arguments: r = Raphael object, n : node object */ |
476 | -// shapes : [ { |
477 | -// type: "rect", |
478 | -// x: 10, |
479 | -// y: 10, |
480 | -// width: 25, |
481 | -// height: 25, |
482 | -// stroke: "#f00" |
483 | -// }, { |
484 | -// type: "text", |
485 | -// x: 30, |
486 | -// y: 40, |
487 | -// text: "Dump" |
488 | -// }], |
489 | -// overlay : "<b>Hello <a href=\"http://wikipedia.org/\">World!</a></b>" |
490 | -// }); |
491 | - |
492 | - st = { directed: true, label : "Label"}; |
493 | - g.addEdge("kiwi", "penguin", st); |
494 | - |
495 | - /* connect nodes with edges */ |
496 | - g.addEdge("strawberry", "cherry"); |
497 | - g.addEdge("cherry", "apple"); |
498 | - g.addEdge("1", "id35"); |
499 | - g.addEdge("penguin", "id35"); |
500 | - g.addEdge("penguin", "apple"); |
501 | - g.addEdge("kiwi", "id35"); |
502 | - |
503 | - /* a directed connection, using an arrow */ |
504 | - g.addEdge("1", "cherry", { directed : true } ); |
505 | - |
506 | - /* customize the colors of that edge */ |
507 | - g.addEdge("id35", "apple", { stroke : "#bfa" , fill : "#56f", label : "Meat-to-Apple" }); |
508 | - |
509 | - /* add an unknown node implicitly by adding an edge */ |
510 | - g.addEdge("strawberry", "apple"); |
511 | - |
512 | - g.removeNode("apple"); |
513 | - |
514 | - /* layout the graph using the Spring layout implementation */ |
515 | - var layouter = new Graph.Layout.Spring(g); |
516 | - |
517 | - /* draw the graph using the RaphaelJS draw implementation */ |
518 | - renderer = new Graph.Renderer.Raphael('canvas', g, width, height); |
519 | - |
520 | - redraw = function() { |
521 | - layouter.layout(); |
522 | - renderer.draw(); |
523 | - }; |
524 | - hide = function(id) { |
525 | - g.nodes[id].hide(); |
526 | - }; |
527 | - show = function(id) { |
528 | - g.nodes[id].show(); |
529 | - }; |
530 | -// console.log(g.nodes["kiwi"]); |
531 | -}; |
532 | - |
533 | ---> |
534 | - </script> |
535 | + <script type="text/javascript" src="example.js"></script> |
536 | </head> |
537 | <body> |
538 | <div id="canvas"></div> |
539 | |
540 | === modified file 'js/dracula_algorithms.js' |
541 | --- js/dracula_algorithms.js 2010-09-22 20:09:16 +0000 |
542 | +++ js/dracula_algorithms.js 2010-10-24 05:37:41 +0000 |
543 | @@ -35,6 +35,14 @@ |
544 | edge.target.distance = edge.source.distance + edge.weight; |
545 | edge.target.predecessor = edge.source; |
546 | } |
547 | + //Added by Jake Stothard (Needs to be tested) |
548 | + if(!edge.style.directed) { |
549 | + if(edge.target.distance + edge.weight < edge.source.distance) { |
550 | + g.snapShot("Relax edge between "+edge.target.id+" and "+edge.source.id+"."); |
551 | + edge.source.distance = edge.target.distance + edge.weight; |
552 | + edge.source.predecessor = edge.target; |
553 | + } |
554 | + } |
555 | } |
556 | |
557 | g.snapShot("Ready."); |
558 | @@ -79,23 +87,25 @@ |
559 | |
560 | /* for each neighbour of node */ |
561 | for(e in node.edges) { |
562 | - if(node.edges[e].target.optimized) |
563 | + var other = (node == node.edges[e].target) ? node.edges[e].source : node.edges[e].target; |
564 | + |
565 | + if(other.optimized) |
566 | continue; |
567 | |
568 | /* look for an alternative route */ |
569 | var alt = node.distance + node.edges[e].weight; |
570 | |
571 | /* update distance and route if a better one has been found */ |
572 | - if (alt < node.edges[e].target.distance) { |
573 | + if (alt < other.distance) { |
574 | |
575 | /* update distance of neighbour */ |
576 | - node.edges[e].target.distance = alt; |
577 | + other.distance = alt; |
578 | |
579 | /* update priority queue */ |
580 | q.heapify(); |
581 | |
582 | /* update path */ |
583 | - node.edges[e].target.predecessor = node; |
584 | + other.predecessor = node; |
585 | g.snapShot("Enhancing node.") |
586 | } |
587 | } |
588 | @@ -456,3 +466,60 @@ |
589 | Function.prototype.curry = function(){ |
590 | return curry(this); |
591 | }; |
592 | + |
593 | +/** |
594 | + * Topological Sort |
595 | + * |
596 | + * Sort a directed graph based on incoming edges |
597 | + * |
598 | + * Coded by Jake Stothard |
599 | + */ |
600 | +function topological_sort(g) { |
601 | + //Mark nodes as "deleted" instead of actually deleting them |
602 | + //That way we don't have to copy g |
603 | + |
604 | + for(i in g.nodes) |
605 | + g.nodes[i].deleted = false; |
606 | + |
607 | + var ret = topological_sort_helper(g); |
608 | + |
609 | + //Cleanup: Remove the deleted property |
610 | + for(i in g.nodes) |
611 | + delete g.nodes[i].deleted |
612 | + |
613 | + return ret; |
614 | +} |
615 | +function topological_sort_helper(g) { |
616 | + //Find node with no incoming edges |
617 | + var node; |
618 | + for(i in g.nodes) { |
619 | + if(g.nodes[i].deleted) |
620 | + continue; //Bad style, meh |
621 | + |
622 | + var incoming = false; |
623 | + for(j in g.nodes[i].edges) { |
624 | + if(g.nodes[i].edges[j].target == g.nodes[i] |
625 | + && g.nodes[i].edges[j].source.deleted == false) { |
626 | + incoming = true; |
627 | + break; |
628 | + } |
629 | + } |
630 | + if(!incoming) { |
631 | + node = g.nodes[i]; |
632 | + break; |
633 | + } |
634 | + } |
635 | + |
636 | + // Either unsortable or done. Either way, GTFO |
637 | + if(node == undefined) |
638 | + return []; |
639 | + |
640 | + //"Delete" node from g |
641 | + node.deleted = true; |
642 | + |
643 | + var tail = topological_sort_helper(g); |
644 | + |
645 | + tail.unshift(node); |
646 | + |
647 | + return tail; |
648 | +} |
649 | |
650 | === modified file 'js/dracula_graph.js' |
651 | --- js/dracula_graph.js 2010-09-22 20:09:16 +0000 |
652 | +++ js/dracula_graph.js 2010-10-24 05:37:41 +0000 |
653 | @@ -28,18 +28,44 @@ |
654 | * Original usage example: |
655 | * http://ajaxian.com/archives/new-javascriptcanvas-graph-library |
656 | * |
657 | -/*--------------------------------------------------------------------------*/ |
658 | - |
659 | - |
660 | + /*--------------------------------------------------------------------------*/ |
661 | + |
662 | +// Branched by Jake Stothard <stothardj@gmail.com>. |
663 | + |
664 | +/* |
665 | + * Edge Factory |
666 | + */ |
667 | +var AbstractEdge = function() { |
668 | +} |
669 | +AbstractEdge.prototype = { |
670 | + hide: function() { |
671 | + this.connection.fg.hide(); |
672 | + this.connection.bg && this.bg.connection.hide(); |
673 | + } |
674 | +}; |
675 | +var EdgeFactory = function() { |
676 | + this.template = new AbstractEdge(); |
677 | + this.template.style = new Object(); |
678 | + this.template.style.directed = false; |
679 | + this.template.weight = 1; |
680 | +}; |
681 | +EdgeFactory.prototype = { |
682 | + build: function(source, target) { |
683 | + var e = jQuery.extend(true, {}, this.template); |
684 | + e.source = source; |
685 | + e.target = target; |
686 | + return e; |
687 | + } |
688 | +}; |
689 | |
690 | /* |
691 | * Graph |
692 | */ |
693 | var Graph = function() { |
694 | - this.nodes = []; |
695 | - this.nodelist = []; // nodes by index number, only used once TODO use only one node container |
696 | + this.nodes = new Object(); |
697 | this.edges = []; |
698 | this.snapshots = []; // previous graph states TODO to be implemented |
699 | + this.edgeFactory = new EdgeFactory(); |
700 | }; |
701 | Graph.prototype = { |
702 | /* |
703 | @@ -52,25 +78,20 @@ |
704 | addNode: function(id, content) { |
705 | /* testing if node is already existing in the graph */ |
706 | if(this.nodes[id] == undefined) { |
707 | - this.nodes[id] = new Graph.Node(id, content || {"id" : id}); /* nodes indexed by node id */ |
708 | - this.nodelist.push(this.nodes[id]); /* node list indexed by numbers */ |
709 | + this.nodes[id] = new Graph.Node(id, content); |
710 | } |
711 | return this.nodes[id]; |
712 | }, |
713 | |
714 | - // TODO rename style to content |
715 | addEdge: function(source, target, style) { |
716 | var s = this.addNode(source); |
717 | var t = this.addNode(target); |
718 | - var edge = { source: s, target: t, style: style, weight: style&&style.weight||1, hide: function() { this.connection.fg.hide(); this.connection.bg && this.bg.connection.hide(); } }; // TODO tidy up here |
719 | + var edge = this.edgeFactory.build(s, t); |
720 | + jQuery.extend(edge.style,style); |
721 | s.edges.push(edge); |
722 | this.edges.push(edge); |
723 | - /* add an edge back if graph undirected */ |
724 | - if(!style || !style.directed) { |
725 | - var backedge = { source: t, target: s, style: style, weight : style&&style.weight||1, backedge : edge }; // TODO tidy up here |
726 | - this.edges.push(backedge); |
727 | - t.edges.push(backedge); |
728 | - } |
729 | + // NOTE: Even directed edges are added to both nodes. |
730 | + t.edges.push(edge); |
731 | }, |
732 | |
733 | /* TODO to be implemented |
734 | @@ -78,20 +99,21 @@ |
735 | * @comment a comment describing the state |
736 | */ |
737 | snapShot: function(comment) { |
738 | + /* FIXME |
739 | var graph = new Graph(); |
740 | -// graph.nodes = clone(this.nodes); |
741 | -// graph.nodelist = clone(this.nodelist); |
742 | -// graph.edges = clone(this.edges); |
743 | - graph.snapShot = null; |
744 | + graph.nodes = jQuery.extend(true, {}, this.nodes); |
745 | + graph.edges = jQuery.extend(true, {}, this.edges); |
746 | this.snapshots.push({comment: comment, graph: graph}); |
747 | + */ |
748 | }, |
749 | - /* FIXME doesn't work properly yet */ |
750 | removeNode: function(id) { |
751 | - var i = 0; |
752 | - for(n in this.nodes) |
753 | - (n == id) && this.nodes.splice(i, 1) && this.nodelist.splice(i, 1) | i++; |
754 | - for(i in this.edges) |
755 | - (this.edges[i].source.id == id || this.edges[i].target == id) && this.edges.splice(i, 1); |
756 | + delete this.nodes[id]; |
757 | + for(var i = 0; i < this.edges.length; i++) { |
758 | + if (this.edges[i].source.id == id || this.edges[i].target.id == id) { |
759 | + this.edges.splice(i, 1); |
760 | + i--; |
761 | + } |
762 | + } |
763 | } |
764 | }; |
765 | |
766 | @@ -99,6 +121,7 @@ |
767 | * Node |
768 | */ |
769 | Graph.Node = function(id, node){ |
770 | + node = node || {}; |
771 | node.id = id; |
772 | node.edges = []; |
773 | node.hide = function() { |
774 | @@ -165,7 +188,7 @@ |
775 | var clientX = e.clientX - (newX < 20 ? newX - 20 : newX > selfRef.width - 20 ? newX - selfRef.width + 20 : 0); |
776 | var clientY = e.clientY - (newY < 20 ? newY - 20 : newY > selfRef.height - 20 ? newY - selfRef.height + 20 : 0); |
777 | selfRef.isDrag.set.translate(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy)); |
778 | -// console.log(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy)); |
779 | + // console.log(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy)); |
780 | for (var i in selfRef.graph.edges) { |
781 | selfRef.graph.edges[i].connection && selfRef.graph.edges[i].connection.draw(); |
782 | } |
783 | @@ -249,7 +272,7 @@ |
784 | |
785 | var box = shape.getBBox(); |
786 | shape.translate(Math.round(point[0])-(box.x+box.width/2)+0.5,Math.round(point[1]-(box.y+box.height/2))) |
787 | -//console.log(box,point); |
788 | + //console.log(box,point); |
789 | node.hidden || shape.show(); |
790 | node.shape = shape; |
791 | }, |
792 | @@ -275,132 +298,184 @@ |
793 | }; |
794 | Graph.Layout = {}; |
795 | Graph.Layout.Spring = function(graph) { |
796 | - this.graph = graph; |
797 | - this.iterations = 500; |
798 | - this.maxRepulsiveForceDistance = 6; |
799 | - this.k = 2; |
800 | - this.c = 0.01; |
801 | - this.maxVertexMovement = 0.5; |
802 | - this.layout(); |
803 | - }; |
804 | + this.graph = graph; |
805 | + this.iterations = 500; |
806 | + this.maxRepulsiveForceDistance = 6; |
807 | + this.k = 2; |
808 | + this.c = 0.01; |
809 | + this.maxVertexMovement = 0.5; |
810 | + this.layout(); |
811 | +}; |
812 | Graph.Layout.Spring.prototype = { |
813 | - layout: function() { |
814 | - this.layoutPrepare(); |
815 | - for (var i = 0; i < this.iterations; i++) { |
816 | - this.layoutIteration(); |
817 | - } |
818 | - this.layoutCalcBounds(); |
819 | - }, |
820 | - |
821 | - layoutPrepare: function() { |
822 | - for (i in this.graph.nodes) { |
823 | - var node = this.graph.nodes[i]; |
824 | - node.layoutPosX = 0; |
825 | - node.layoutPosY = 0; |
826 | - node.layoutForceX = 0; |
827 | - node.layoutForceY = 0; |
828 | - } |
829 | - |
830 | - }, |
831 | - |
832 | - layoutCalcBounds: function() { |
833 | - var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity; |
834 | - |
835 | - for (i in this.graph.nodes) { |
836 | - var x = this.graph.nodes[i].layoutPosX; |
837 | - var y = this.graph.nodes[i].layoutPosY; |
838 | - |
839 | - if(x > maxx) maxx = x; |
840 | - if(x < minx) minx = x; |
841 | - if(y > maxy) maxy = y; |
842 | - if(y < miny) miny = y; |
843 | - } |
844 | - |
845 | - this.graph.layoutMinX = minx; |
846 | - this.graph.layoutMaxX = maxx; |
847 | - this.graph.layoutMinY = miny; |
848 | - this.graph.layoutMaxY = maxy; |
849 | - }, |
850 | - |
851 | - layoutIteration: function() { |
852 | - // Forces on nodes due to node-node repulsions |
853 | - for (var i = 0; i < this.graph.nodelist.length; i++) { |
854 | - var node1 = this.graph.nodelist[i]; |
855 | - for (var j = i + 1; j < this.graph.nodelist.length; j++) { |
856 | - var node2 = this.graph.nodelist[j]; |
857 | - this.layoutRepulsive(node1, node2); |
858 | - } |
859 | - } |
860 | - // Forces on nodes due to edge attractions |
861 | - for (var i = 0; i < this.graph.edges.length; i++) { |
862 | - var edge = this.graph.edges[i]; |
863 | - this.layoutAttractive(edge); |
864 | - } |
865 | - |
866 | - // Move by the given force |
867 | - for (i in this.graph.nodes) { |
868 | - var node = this.graph.nodes[i]; |
869 | - var xmove = this.c * node.layoutForceX; |
870 | - var ymove = this.c * node.layoutForceY; |
871 | - |
872 | - var max = this.maxVertexMovement; |
873 | - if(xmove > max) xmove = max; |
874 | - if(xmove < -max) xmove = -max; |
875 | - if(ymove > max) ymove = max; |
876 | - if(ymove < -max) ymove = -max; |
877 | - |
878 | - node.layoutPosX += xmove; |
879 | - node.layoutPosY += ymove; |
880 | - node.layoutForceX = 0; |
881 | - node.layoutForceY = 0; |
882 | - } |
883 | - }, |
884 | - |
885 | - layoutRepulsive: function(node1, node2) { |
886 | - var dx = node2.layoutPosX - node1.layoutPosX; |
887 | - var dy = node2.layoutPosY - node1.layoutPosY; |
888 | - var d2 = dx * dx + dy * dy; |
889 | - if(d2 < 0.01) { |
890 | - dx = 0.1 * Math.random() + 0.1; |
891 | - dy = 0.1 * Math.random() + 0.1; |
892 | - var d2 = dx * dx + dy * dy; |
893 | - } |
894 | - var d = Math.sqrt(d2); |
895 | - if(d < this.maxRepulsiveForceDistance) { |
896 | - var repulsiveForce = this.k * this.k / d; |
897 | - node2.layoutForceX += repulsiveForce * dx / d; |
898 | - node2.layoutForceY += repulsiveForce * dy / d; |
899 | - node1.layoutForceX -= repulsiveForce * dx / d; |
900 | - node1.layoutForceY -= repulsiveForce * dy / d; |
901 | - } |
902 | - }, |
903 | - |
904 | - layoutAttractive: function(edge) { |
905 | - var node1 = edge.source; |
906 | - var node2 = edge.target; |
907 | - |
908 | - var dx = node2.layoutPosX - node1.layoutPosX; |
909 | - var dy = node2.layoutPosY - node1.layoutPosY; |
910 | - var d2 = dx * dx + dy * dy; |
911 | - if(d2 < 0.01) { |
912 | - dx = 0.1 * Math.random() + 0.1; |
913 | - dy = 0.1 * Math.random() + 0.1; |
914 | - var d2 = dx * dx + dy * dy; |
915 | - } |
916 | - var d = Math.sqrt(d2); |
917 | - if(d > this.maxRepulsiveForceDistance) { |
918 | - d = this.maxRepulsiveForceDistance; |
919 | - d2 = d * d; |
920 | - } |
921 | - var attractiveForce = (d2 - this.k * this.k) / this.k; |
922 | - if(edge.attraction == undefined) edge.attraction = 1; |
923 | - attractiveForce *= Math.log(edge.attraction) * 0.5 + 1; |
924 | - |
925 | - node2.layoutForceX -= attractiveForce * dx / d; |
926 | - node2.layoutForceY -= attractiveForce * dy / d; |
927 | - node1.layoutForceX += attractiveForce * dx / d; |
928 | - node1.layoutForceY += attractiveForce * dy / d; |
929 | - } |
930 | + layout: function() { |
931 | + this.layoutPrepare(); |
932 | + for (var i = 0; i < this.iterations; i++) { |
933 | + this.layoutIteration(); |
934 | + } |
935 | + this.layoutCalcBounds(); |
936 | + }, |
937 | + |
938 | + layoutPrepare: function() { |
939 | + for (i in this.graph.nodes) { |
940 | + var node = this.graph.nodes[i]; |
941 | + node.layoutPosX = 0; |
942 | + node.layoutPosY = 0; |
943 | + node.layoutForceX = 0; |
944 | + node.layoutForceY = 0; |
945 | + } |
946 | + |
947 | + }, |
948 | + |
949 | + layoutCalcBounds: function() { |
950 | + var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity; |
951 | + |
952 | + for (i in this.graph.nodes) { |
953 | + var x = this.graph.nodes[i].layoutPosX; |
954 | + var y = this.graph.nodes[i].layoutPosY; |
955 | + |
956 | + if(x > maxx) maxx = x; |
957 | + if(x < minx) minx = x; |
958 | + if(y > maxy) maxy = y; |
959 | + if(y < miny) miny = y; |
960 | + } |
961 | + |
962 | + this.graph.layoutMinX = minx; |
963 | + this.graph.layoutMaxX = maxx; |
964 | + this.graph.layoutMinY = miny; |
965 | + this.graph.layoutMaxY = maxy; |
966 | + }, |
967 | + |
968 | + layoutIteration: function() { |
969 | + // Forces on nodes due to node-node repulsions |
970 | + |
971 | + var prev = new Array(); |
972 | + for(var c in this.graph.nodes) { |
973 | + var node1 = this.graph.nodes[c]; |
974 | + for (var d in prev) { |
975 | + var node2 = this.graph.nodes[prev[d]]; |
976 | + this.layoutRepulsive(node1, node2); |
977 | + |
978 | + } |
979 | + prev.push(c); |
980 | + } |
981 | + |
982 | + // Forces on nodes due to edge attractions |
983 | + for (var i = 0; i < this.graph.edges.length; i++) { |
984 | + var edge = this.graph.edges[i]; |
985 | + this.layoutAttractive(edge); |
986 | + } |
987 | + |
988 | + // Move by the given force |
989 | + for (i in this.graph.nodes) { |
990 | + var node = this.graph.nodes[i]; |
991 | + var xmove = this.c * node.layoutForceX; |
992 | + var ymove = this.c * node.layoutForceY; |
993 | + |
994 | + var max = this.maxVertexMovement; |
995 | + if(xmove > max) xmove = max; |
996 | + if(xmove < -max) xmove = -max; |
997 | + if(ymove > max) ymove = max; |
998 | + if(ymove < -max) ymove = -max; |
999 | + |
1000 | + node.layoutPosX += xmove; |
1001 | + node.layoutPosY += ymove; |
1002 | + node.layoutForceX = 0; |
1003 | + node.layoutForceY = 0; |
1004 | + } |
1005 | + }, |
1006 | + |
1007 | + layoutRepulsive: function(node1, node2) { |
1008 | + var dx = node2.layoutPosX - node1.layoutPosX; |
1009 | + var dy = node2.layoutPosY - node1.layoutPosY; |
1010 | + var d2 = dx * dx + dy * dy; |
1011 | + if(d2 < 0.01) { |
1012 | + dx = 0.1 * Math.random() + 0.1; |
1013 | + dy = 0.1 * Math.random() + 0.1; |
1014 | + var d2 = dx * dx + dy * dy; |
1015 | + } |
1016 | + var d = Math.sqrt(d2); |
1017 | + if(d < this.maxRepulsiveForceDistance) { |
1018 | + var repulsiveForce = this.k * this.k / d; |
1019 | + node2.layoutForceX += repulsiveForce * dx / d; |
1020 | + node2.layoutForceY += repulsiveForce * dy / d; |
1021 | + node1.layoutForceX -= repulsiveForce * dx / d; |
1022 | + node1.layoutForceY -= repulsiveForce * dy / d; |
1023 | + } |
1024 | + }, |
1025 | + |
1026 | + layoutAttractive: function(edge) { |
1027 | + var node1 = edge.source; |
1028 | + var node2 = edge.target; |
1029 | + |
1030 | + var dx = node2.layoutPosX - node1.layoutPosX; |
1031 | + var dy = node2.layoutPosY - node1.layoutPosY; |
1032 | + var d2 = dx * dx + dy * dy; |
1033 | + if(d2 < 0.01) { |
1034 | + dx = 0.1 * Math.random() + 0.1; |
1035 | + dy = 0.1 * Math.random() + 0.1; |
1036 | + var d2 = dx * dx + dy * dy; |
1037 | + } |
1038 | + var d = Math.sqrt(d2); |
1039 | + if(d > this.maxRepulsiveForceDistance) { |
1040 | + d = this.maxRepulsiveForceDistance; |
1041 | + d2 = d * d; |
1042 | + } |
1043 | + var attractiveForce = (d2 - this.k * this.k) / this.k; |
1044 | + if(edge.attraction == undefined) edge.attraction = 1; |
1045 | + attractiveForce *= Math.log(edge.attraction) * 0.5 + 1; |
1046 | + |
1047 | + node2.layoutForceX -= attractiveForce * dx / d; |
1048 | + node2.layoutForceY -= attractiveForce * dy / d; |
1049 | + node1.layoutForceX += attractiveForce * dx / d; |
1050 | + node1.layoutForceY += attractiveForce * dy / d; |
1051 | + } |
1052 | +}; |
1053 | + |
1054 | +Graph.Layout.Ordered = function(graph, order) { |
1055 | + this.graph = graph; |
1056 | + this.order = order; |
1057 | + this.layout(); |
1058 | +}; |
1059 | +Graph.Layout.Ordered.prototype = { |
1060 | + layout: function() { |
1061 | + this.layoutPrepare(); |
1062 | + this.layoutCalcBounds(); |
1063 | + }, |
1064 | + |
1065 | + layoutPrepare: function(order) { |
1066 | + for (i in this.graph.nodes) { |
1067 | + var node = this.graph.nodes[i]; |
1068 | + node.layoutPosX = 0; |
1069 | + node.layoutPosY = 0; |
1070 | + } |
1071 | + var counter = 0; |
1072 | + for (i in this.order) { |
1073 | + var node = this.order[i]; |
1074 | + node.layoutPosX = counter; |
1075 | + node.layoutPosY = Math.random(); |
1076 | + counter++; |
1077 | + } |
1078 | + }, |
1079 | + |
1080 | + layoutCalcBounds: function() { |
1081 | + var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity; |
1082 | + |
1083 | + for (i in this.graph.nodes) { |
1084 | + var x = this.graph.nodes[i].layoutPosX; |
1085 | + var y = this.graph.nodes[i].layoutPosY; |
1086 | + |
1087 | + if(x > maxx) maxx = x; |
1088 | + if(x < minx) minx = x; |
1089 | + if(y > maxy) maxy = y; |
1090 | + if(y < miny) miny = y; |
1091 | + } |
1092 | + |
1093 | + this.graph.layoutMinX = minx; |
1094 | + this.graph.layoutMaxX = maxx; |
1095 | + |
1096 | + this.graph.layoutMinY = miny; |
1097 | + this.graph.layoutMaxY = maxy; |
1098 | + }, |
1099 | }; |
1100 | |
1101 | /* |
1102 | @@ -409,23 +484,6 @@ |
1103 | |
1104 | function log(a) {console.log&&console.log(a);} |
1105 | |
1106 | -function clone(obj){ |
1107 | - if(obj == null || typeof(obj) != 'object') |
1108 | - return obj; |
1109 | - |
1110 | - var temp = obj.constructor(); // changed |
1111 | - |
1112 | - for(var key in obj) |
1113 | - temp[key] = clone(obj[key]); |
1114 | - return temp; |
1115 | -} |
1116 | - |
1117 | -// this will eventually mess up array usage with for-in-loops |
1118 | -//Array.prototype.onEach = function(f, arg) { |
1119 | -// var l = this.length; |
1120 | -// for( var i = 0; i < l; i++) this[i][f]( arg ); |
1121 | -//} |
1122 | - |
1123 | /* |
1124 | * Raphael Tooltip Plugin |
1125 | * - attaches an element as a tooltip to another element |
1126 | @@ -445,7 +503,7 @@ |
1127 | function(event){ |
1128 | this.mousemove(function(event){ |
1129 | this.tp.translate(event.clientX - |
1130 | - this.tp.o.x,event.clientY - this.tp.o.y); |
1131 | + this.tp.o.x,event.clientY - this.tp.o.y); |
1132 | this.tp.o = {x: event.clientX, y: event.clientY}; |
1133 | }); |
1134 | this.tp.show().toFront(); |
1135 | @@ -453,6 +511,6 @@ |
1136 | function(event){ |
1137 | this.tp.hide(); |
1138 | this.unmousemove(); |
1139 | - }); |
1140 | + }); |
1141 | return this; |
1142 | }; |
Hi there, thanks a lot for the merge request! I was travelling last week, so I didn't have a look at it yet. But I think I'll approve it soon.