Merge lp:~stothardj/dracula/nonodelist into lp:dracula

Proposed by Jake Stothard
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
Reviewer Review Type Date Requested Status
Johann Philipp Strathausen Approve
Review via email: mp+39216@code.launchpad.net

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.

To post a comment you must log in.
Revision history for this message
Johann Philipp Strathausen (strathausen) 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.

Revision history for this message
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://code.launchpad.net/~stothardj/dracula/nonodelist/+merge/39216<https://code.launchpad.net/%7Estothardj/dracula/nonodelist/+merge/39216>
> You are the owner of lp:~stothardj/dracula/nonodelist.
>

Revision history for this message
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.

Revision history for this message
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://code.launchpad.net/~stothardj/dracula/nonodelist/+merge/39216<https://code.launchpad.net/%7Estothardj/dracula/nonodelist/+merge/39216>
> You are the owner of lp:~stothardj/dracula/nonodelist.
>

Revision history for this message
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...

review: Approve

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
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 };

Subscribers

People subscribed via source and target branches