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
=== added file 'algorithms.js'
--- algorithms.js 1970-01-01 00:00:00 +0000
+++ algorithms.js 2010-10-24 05:37:41 +0000
@@ -0,0 +1,102 @@
1var redraw;
2
3window.onload = function() {
4 var width = $(document).width();
5 var height = $(document).height() - 100;
6
7 /* Showcase of the Bellman-Ford search algorithm finding shortest paths
8 from one point to every node */
9
10 /* */
11
12 /* We need to write a new node renderer function to display the computed
13 distance.
14 (the Raphael graph drawing implementation of Dracula can draw this shape,
15 please consult the RaphaelJS reference for details http://raphaeljs.com/) */
16 var render = function(r, n) {
17 /* the Raphael set is obligatory, containing all you want to display */
18 var set = r.set().push(
19 /* custom objects go here */
20 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(
21 r.text(n.point[0], n.point[1] + 10, (n.label || n.id) + "\n(" + (n.distance == undefined ? "Infinity" : n.distance) + ")"));
22 return set;
23 };
24
25 var g = new Graph();
26
27 /* modify the edge creation to attach random weights */
28 g.edgeFactory.build = function(source, target) {
29 var e = jQuery.extend(true, {}, this.template);
30 e.source = source;
31 e.target = target;
32 e.style.label = e.weight = Math.floor(Math.random() * 10) + 1;
33 return e;
34 }
35
36 /* creating nodes and passing the new renderer function to overwrite the default one */
37 g.addNode("New York", {render:render}); // TODO add currying support for nicer code
38 g.addNode("Berlin", {render:render});
39 g.addNode("Tel Aviv", {render:render});
40 g.addNode("Tokyo", {render:render});
41 g.addNode("Roma", {render:render});
42 g.addNode("Madrid", {render:render});
43
44 /* connections */
45 g.addEdge("Tokyo", "Tel Aviv"/*, {weight:9, directed: true, stroke : "#bfa"}*/); // also supports directed graphs, but currently doesn't look that nice
46 g.addEdge("Tokyo", "New York");
47 g.addEdge("Tokyo", "Berlin");
48 g.addEdge("Tel Aviv", "Berlin");
49 g.addEdge("Tel Aviv", "New York");
50 g.addEdge("Tel Aviv", "Roma");
51 g.addEdge("Roma", "New York");
52 g.addEdge("Berlin", "New York");
53 g.addEdge("Madrid", "New York");
54 g.addEdge("Madrid", "Roma");
55 g.addEdge("Madrid", "Tokyo");
56
57 /* random edge weights (our undirected graph is modelled as a bidirectional graph) */
58/* for(e in g.edges)
59 if(g.edges[e].backedge != undefined) {
60 g.edges[e].weight = Math.floor(Math.random()*10) + 1;
61 g.edges[e].backedge.weight = g.edges[e].weight;
62 }
63*/
64 /* layout the graph using the Spring layout implementation */
65 var layouter = new Graph.Layout.Spring(g);
66
67 /* draw the graph using the RaphaelJS draw implementation */
68
69 /* calculating the shortest paths via Bellman Ford */
70// bellman_ford(g, g.nodes["Berlin"]);
71
72 /* calculating the shortest paths via Dijkstra */
73 dijkstra(g, g.nodes["Berlin"]);
74
75 /* calculating the shortest paths via Floyd-Warshall */
76 floyd_warshall(g, g.nodes["Berlin"]);
77
78
79 /* colourising the shortest paths and setting labels */
80 for(e in g.edges) {
81 if(g.edges[e].target.predecessor === g.edges[e].source || g.edges[e].source.predecessor === g.edges[e].target) {
82 g.edges[e].style.stroke = "#bfa";
83 g.edges[e].style.fill = "#56f";
84 } else {
85 g.edges[e].style.stroke = "#aaa";
86 }
87 }
88
89 var renderer = new Graph.Renderer.Raphael('canvas', g, width, height);
90
91 redraw = function() {
92 layouter.layout();
93 renderer.draw();
94 };
95
96/* var pos=0;
97 step = function(dir) {
98 pos+=dir;
99 var renderer = new Graph.Renderer.Raphael('canvas', g.snapshots[pos], width, height);
100 renderer.draw();
101 };*/
102};
0\ No newline at end of file103\ No newline at end of file
1104
=== added file 'classes.html'
--- classes.html 1970-01-01 00:00:00 +0000
+++ classes.html 2010-10-24 05:37:41 +0000
@@ -0,0 +1,18 @@
1<html>
2<head>
3 <script type="text/javascript" src="js/raphael-min.js"></script>
4 <script type="text/javascript" src="js/dracula_graffle.js"></script>
5 <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
6 <script type="text/javascript" src="js/dracula_graph.js"></script>
7 <script type="text/javascript" src="js/dracula_algorithms.js"></script>
8 <script type="text/javascript" src="classes.js"></script>
9 <style type="text/css">
10 body {
11 overflow: hidden;
12 }
13 </style>
14</head>
15<body>
16<div id="canvas"></div>
17</body>
18</html>
019
=== added file 'classes.js'
--- classes.js 1970-01-01 00:00:00 +0000
+++ classes.js 2010-10-24 05:37:41 +0000
@@ -0,0 +1,23 @@
1//Show UCLA CS class dependencies (not complete)
2$(document).ready(function() {
3 var width = $(document).width();
4 var height = $(document).height();
5 var g = new Graph();
6 g.edgeFactory.template.style.directed = true;
7 g.addEdge("CS 31","CS 32");
8 g.addEdge("CS 32","CS 33");
9 g.addEdge("CS 33","CS 35L");
10 g.addNode("CS M51A");
11 g.addEdge("CS 32", "CS 111");
12 g.addEdge("CS 33", "CS 111");
13 g.addEdge("CS 35L", "CS 111");
14 g.addEdge("CS 32", "CS 118");
15 g.addEdge("CS 33", "CS 118");
16 g.addEdge("CS 35L", "CS 118");
17 g.addEdge("CS 111", "CS 118");
18 g.addEdge("CS 32", "CS 131");
19 g.addEdge("CS 33", "CS 131");
20 g.addEdge("CS 35L", "CS 131");
21 var layouter = new Graph.Layout.Ordered(g, topological_sort(g));
22 var renderer = new Graph.Renderer.Raphael('canvas', g, width, height);
23});
0\ No newline at end of file24\ No newline at end of file
125
=== modified file 'dracula_algorithms.html'
--- dracula_algorithms.html 2010-09-17 18:04:25 +0000
+++ dracula_algorithms.html 2010-10-24 05:37:41 +0000
@@ -1,122 +1,20 @@
1<html>1<html>
2<head>2 <head>
3<!-- jQuery -->3 <!-- jQuery -->
4 <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>4 <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
5<!-- The Raphael JavaScript library for vector graphics display -->5 <!-- The Raphael JavaScript library for vector graphics display -->
6 <script type="text/javascript" src="js/raphael-min.js"></script>6 <script type="text/javascript" src="js/raphael-min.js"></script>
7<!-- Dracula -->7 <!-- Dracula -->
8<!-- An extension of Raphael for connecting shapes -->8 <!-- An extension of Raphael for connecting shapes -->
9 <script type="text/javascript" src="js/dracula_graffle.js"></script>9 <script type="text/javascript" src="js/dracula_graffle.js"></script>
10<!-- Graphs -->10 <!-- Graphs -->
11 <script type="text/javascript" src="js/dracula_graph.js"></script>11 <script type="text/javascript" src="js/dracula_graph.js"></script>
12 <script type="text/javascript" src="js/dracula_algorithms.js"></script>12 <script type="text/javascript" src="js/dracula_algorithms.js"></script>
13 <script type="text/javascript">13 <script type="text/javascript" src="algorithms.js"></script>
14<!--14 </head>
1515 <body>
16var redraw;16 <div id="canvas"></div>
17var height = 300;17 Looks ugly? Hit <button id="redraw" onclick="redraw();">redraw</button>!<br>
18var width = 400;18 <!--<button onclick="step(-1)">Previous step</button> - <button onclick="step(1)">Next step</button>-->
1919 </body>
20window.onload = function() {
21
22 /* Showcase of the Bellman-Ford search algorithm finding shortest paths
23 from one point to every node */
24
25 /* */
26
27 /* We need to write a new node renderer function to display the computed
28 distance.
29 (the Raphael graph drawing implementation of Dracula can draw this shape,
30 please consult the RaphaelJS reference for details http://raphaeljs.com/) */
31 var render = function(r, n) {
32 /* the Raphael set is obligatory, containing all you want to display */
33 var set = r.set().push(
34 /* custom objects go here */
35 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(
36 r.text(n.point[0], n.point[1] + 10, (n.label || n.id) + "\n(" + (n.distance == undefined ? "Infinity" : n.distance) + ")"));
37 return set;
38 };
39
40 var g = new Graph();
41 /* creating nodes and passing the new renderer function to overwrite the default one */
42 g.addNode("New York", {render:render}); // TODO add currying support for nicer code
43 g.addNode("Berlin", {render:render});
44 g.addNode("Tel Aviv", {render:render});
45 g.addNode("Tokyo", {render:render});
46 g.addNode("Roma", {render:render});
47 g.addNode("Madrid", {render:render});
48
49 /* modify the addEdge function to attach random weights */
50 g.addEdge2 = g.addEdge;
51 g.addEdge = function(from, to, style) { !style && (style={}); style.weight = Math.floor(Math.random()*10) + 1; g.addEdge2(from, to, style); };
52
53 /* connections */
54 g.addEdge("Tokyo", "Tel Aviv"/*, {weight:9, directed: true, stroke : "#bfa"}*/); // also supports directed graphs, but currently doesn't look that nice
55 g.addEdge("Tokyo", "New York");
56 g.addEdge("Tokyo", "Berlin");
57 g.addEdge("Tel Aviv", "Berlin");
58 g.addEdge("Tel Aviv", "New York");
59 g.addEdge("Tel Aviv", "Roma");
60 g.addEdge("Roma", "New York");
61 g.addEdge("Berlin", "New York");
62 g.addEdge("Madrid", "New York");
63 g.addEdge("Madrid", "Roma");
64 g.addEdge("Madrid", "Tokyo");
65
66 /* random edge weights (our undirected graph is modelled as a bidirectional graph) */
67/* for(e in g.edges)
68 if(g.edges[e].backedge != undefined) {
69 g.edges[e].weight = Math.floor(Math.random()*10) + 1;
70 g.edges[e].backedge.weight = g.edges[e].weight;
71 }
72*/
73 /* layout the graph using the Spring layout implementation */
74 var layouter = new Graph.Layout.Spring(g);
75
76 /* draw the graph using the RaphaelJS draw implementation */
77
78 /* calculating the shortest paths via Bellman Ford */
79// bellman_ford(g, g.nodes["Berlin"]);
80
81 /* calculating the shortest paths via Dijkstra */
82 dijkstra(g, g.nodes["Berlin"]);
83
84 /* calculating the shortest paths via Floyd-Warshall */
85 floyd_warshall(g, g.nodes["Berlin"]);
86
87
88 /* colourising the shortest paths and setting labels */
89 for(e in g.edges) {
90 g.edges[e].style = { label : g.edges[e].weight };
91 if(g.edges[e].target.predecessor === g.edges[e].source || g.edges[e].source.predecessor === g.edges[e].target) {
92 g.edges[e].style.stroke = "#bfa";
93 g.edges[e].style.fill = "#56f";
94 } else {
95 g.edges[e].style.stroke = "#aaa";
96 }
97 }
98
99 var renderer = new Graph.Renderer.Raphael('canvas', g, width, height);
100
101 redraw = function() {
102 layouter.layout();
103 renderer.draw();
104 };
105
106/* var pos=0;
107 step = function(dir) {
108 pos+=dir;
109 var renderer = new Graph.Renderer.Raphael('canvas', g.snapshots[pos], width, height);
110 renderer.draw();
111 };*/
112};
113
114-->
115 </script>
116</head>
117<body>
118<div id="canvas"></div>
119Looks ugly? Hit <button id="redraw" onclick="redraw();">redraw</button>!<br>
120<!--<button onclick="step(-1)">Previous step</button> - <button onclick="step(1)">Next step</button>-->
121</body>
122</html>20</html>
12321
=== added file 'example.js'
--- example.js 1970-01-01 00:00:00 +0000
+++ example.js 2010-10-24 05:37:41 +0000
@@ -0,0 +1,109 @@
1
2var redraw, g, renderer;
3
4/* only do all this when document has finished loading (needed for RaphaelJS) */
5window.onload = function() {
6
7 var width = $(document).width() - 20;
8 var height = $(document).height() - 60;
9
10 g = new Graph();
11
12 /* add a simple node */
13 g.addNode("strawberry");
14 g.addNode("cherry");
15
16 /* add a node with a customized label */
17 g.addNode("1", { label : "Tomato" });
18
19 /* add a node with a customized shape
20 (the Raphael graph drawing implementation can draw this shape, please
21 consult the RaphaelJS reference for details http://raphaeljs.com/) */
22 var render = function(r, n) {
23 var label = r.text(0, 30, n.label).attr({opacity:0});
24 /* the Raphael set is obligatory, containing all you want to display */
25 var set = r.set().push(
26 r.rect(-30, -13, 62, 86).attr({"fill": "#fa8", "stroke-width": 2, r : "9px"}))
27 .push(label);
28 /* make the label show only on hover */
29 set.hover(function(){ label.animate({opacity:1,"fill-opacity":1}, 500); }, function(){ label.animate({opacity:0},300); });
30
31 tooltip = r.set()
32 .push(
33 r.rect(0, 0, 90, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})
34 ).push(
35 r.text(25, 15, "overlay").attr({"fill": "#000000"})
36 );
37 for(i in set.items) {
38 set.items[i].tooltip(tooltip);
39 };
40 // set.tooltip(r.set().push(r.rect(0, 0, 30, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})).hide());
41 return set;
42 };
43 g.addNode("id35", {
44 label : "meat\nand\ngreed" ,
45 /* filling the shape with a color makes it easier to be dragged */
46 /* arguments: r = Raphael object, n : node object */
47 render : render
48 });
49 // g.addNode("Wheat", {
50 /* filling the shape with a color makes it easier to be dragged */
51 /* arguments: r = Raphael object, n : node object */
52 // shapes : [ {
53 // type: "rect",
54 // x: 10,
55 // y: 10,
56 // width: 25,
57 // height: 25,
58 // stroke: "#f00"
59 // }, {
60 // type: "text",
61 // x: 30,
62 // y: 40,
63 // text: "Dump"
64 // }],
65 // overlay : "<b>Hello <a href=\"http://wikipedia.org/\">World!</a></b>"
66 // });
67
68 st = { directed: true, label : "Label"};
69 g.addEdge("kiwi", "penguin", st);
70
71 /* connect nodes with edges */
72 g.addEdge("strawberry", "cherry");
73 g.addEdge("cherry", "apple");
74 g.addEdge("cherry", "apple")
75 g.addEdge("1", "id35");
76 g.addEdge("penguin", "id35");
77 g.addEdge("penguin", "apple");
78 g.addEdge("kiwi", "id35");
79
80 /* a directed connection, using an arrow */
81 g.addEdge("1", "cherry", { directed : true } );
82
83 /* customize the colors of that edge */
84 g.addEdge("id35", "apple", { stroke : "#bfa" , fill : "#56f", label : "Meat-to-Apple" });
85
86 /* add an unknown node implicitly by adding an edge */
87 g.addEdge("strawberry", "apple");
88
89 g.removeNode("1");
90
91 /* layout the graph using the Spring layout implementation */
92 var layouter = new Graph.Layout.Spring(g);
93
94 /* draw the graph using the RaphaelJS draw implementation */
95 renderer = new Graph.Renderer.Raphael('canvas', g, width, height);
96
97 redraw = function() {
98 layouter.layout();
99 renderer.draw();
100 };
101 hide = function(id) {
102 g.nodes[id].hide();
103 };
104 show = function(id) {
105 g.nodes[id].show();
106 };
107 // console.log(g.nodes["kiwi"]);
108};
109
0110
=== modified file 'index.html'
--- index.html 2010-09-20 18:56:38 +0000
+++ index.html 2010-10-24 05:37:41 +0000
@@ -2,118 +2,9 @@
2<head>2<head>
3 <script type="text/javascript" src="js/raphael-min.js"></script>3 <script type="text/javascript" src="js/raphael-min.js"></script>
4 <script type="text/javascript" src="js/dracula_graffle.js"></script>4 <script type="text/javascript" src="js/dracula_graffle.js"></script>
5 <script type="text/javascript" src="js/jquery-1.4.2.min.js"></script>
5 <script type="text/javascript" src="js/dracula_graph.js"></script>6 <script type="text/javascript" src="js/dracula_graph.js"></script>
6 <script type="text/javascript">7 <script type="text/javascript" src="example.js"></script>
7<!--
8
9var redraw, g, renderer;
10var height = 300;
11var width = 400;
12
13/* only do all this when document has finished loading (needed for RaphaelJS) */
14window.onload = function() {
15
16 g = new Graph();
17
18 /* add a simple node */
19 g.addNode("strawberry");
20 g.addNode("cherry");
21
22 /* add a node with a customized label */
23 g.addNode("1", { label : "Tomato" });
24
25 /* add a node with a customized shape
26 (the Raphael graph drawing implementation can draw this shape, please
27 consult the RaphaelJS reference for details http://raphaeljs.com/) */
28 var render = function(r, n) {
29 var label = r.text(0, 30, n.label).attr({opacity:0});
30 /* the Raphael set is obligatory, containing all you want to display */
31 var set = r.set().push(
32 r.rect(-30, -13, 62, 86).attr({"fill": "#fa8", "stroke-width": 2, r : "9px"}))
33 .push(label);
34 /* make the label show only on hover */
35 set.hover(function(){ label.animate({opacity:1,"fill-opacity":1}, 500); }, function(){ label.animate({opacity:0},300); });
36
37 tooltip = r.set()
38 .push(
39 r.rect(0, 0, 90, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})
40 ).push(
41 r.text(25, 15, "overlay").attr({"fill": "#000000"})
42 );
43 for(i in set.items) {
44 set.items[i].tooltip(tooltip);
45 };
46// set.tooltip(r.set().push(r.rect(0, 0, 30, 30).attr({"fill": "#fec", "stroke-width": 1, r : "9px"})).hide());
47 return set;
48 };
49 g.addNode("id35", {
50 label : "meat\nand\ngreed" ,
51 /* filling the shape with a color makes it easier to be dragged */
52 /* arguments: r = Raphael object, n : node object */
53 render : render
54 });
55// g.addNode("Wheat", {
56 /* filling the shape with a color makes it easier to be dragged */
57 /* arguments: r = Raphael object, n : node object */
58// shapes : [ {
59// type: "rect",
60// x: 10,
61// y: 10,
62// width: 25,
63// height: 25,
64// stroke: "#f00"
65// }, {
66// type: "text",
67// x: 30,
68// y: 40,
69// text: "Dump"
70// }],
71// overlay : "<b>Hello <a href=\"http://wikipedia.org/\">World!</a></b>"
72// });
73
74 st = { directed: true, label : "Label"};
75 g.addEdge("kiwi", "penguin", st);
76
77 /* connect nodes with edges */
78 g.addEdge("strawberry", "cherry");
79 g.addEdge("cherry", "apple");
80 g.addEdge("1", "id35");
81 g.addEdge("penguin", "id35");
82 g.addEdge("penguin", "apple");
83 g.addEdge("kiwi", "id35");
84
85 /* a directed connection, using an arrow */
86 g.addEdge("1", "cherry", { directed : true } );
87
88 /* customize the colors of that edge */
89 g.addEdge("id35", "apple", { stroke : "#bfa" , fill : "#56f", label : "Meat-to-Apple" });
90
91 /* add an unknown node implicitly by adding an edge */
92 g.addEdge("strawberry", "apple");
93
94 g.removeNode("apple");
95
96 /* layout the graph using the Spring layout implementation */
97 var layouter = new Graph.Layout.Spring(g);
98
99 /* draw the graph using the RaphaelJS draw implementation */
100 renderer = new Graph.Renderer.Raphael('canvas', g, width, height);
101
102 redraw = function() {
103 layouter.layout();
104 renderer.draw();
105 };
106 hide = function(id) {
107 g.nodes[id].hide();
108 };
109 show = function(id) {
110 g.nodes[id].show();
111 };
112// console.log(g.nodes["kiwi"]);
113};
114
115-->
116 </script>
117</head>8</head>
118<body>9<body>
119<div id="canvas"></div>10<div id="canvas"></div>
12011
=== modified file 'js/dracula_algorithms.js'
--- js/dracula_algorithms.js 2010-09-22 20:09:16 +0000
+++ js/dracula_algorithms.js 2010-10-24 05:37:41 +0000
@@ -35,6 +35,14 @@
35 edge.target.distance = edge.source.distance + edge.weight;35 edge.target.distance = edge.source.distance + edge.weight;
36 edge.target.predecessor = edge.source;36 edge.target.predecessor = edge.source;
37 }37 }
38 //Added by Jake Stothard (Needs to be tested)
39 if(!edge.style.directed) {
40 if(edge.target.distance + edge.weight < edge.source.distance) {
41 g.snapShot("Relax edge between "+edge.target.id+" and "+edge.source.id+".");
42 edge.source.distance = edge.target.distance + edge.weight;
43 edge.source.predecessor = edge.target;
44 }
45 }
38 }46 }
39 47
40 g.snapShot("Ready.");48 g.snapShot("Ready.");
@@ -79,23 +87,25 @@
7987
80 /* for each neighbour of node */88 /* for each neighbour of node */
81 for(e in node.edges) {89 for(e in node.edges) {
82 if(node.edges[e].target.optimized)90 var other = (node == node.edges[e].target) ? node.edges[e].source : node.edges[e].target;
91
92 if(other.optimized)
83 continue;93 continue;
8494
85 /* look for an alternative route */95 /* look for an alternative route */
86 var alt = node.distance + node.edges[e].weight;96 var alt = node.distance + node.edges[e].weight;
87 97
88 /* update distance and route if a better one has been found */98 /* update distance and route if a better one has been found */
89 if (alt < node.edges[e].target.distance) {99 if (alt < other.distance) {
90 100
91 /* update distance of neighbour */101 /* update distance of neighbour */
92 node.edges[e].target.distance = alt;102 other.distance = alt;
93103
94 /* update priority queue */104 /* update priority queue */
95 q.heapify();105 q.heapify();
96106
97 /* update path */107 /* update path */
98 node.edges[e].target.predecessor = node;108 other.predecessor = node;
99 g.snapShot("Enhancing node.")109 g.snapShot("Enhancing node.")
100 }110 }
101 }111 }
@@ -456,3 +466,60 @@
456Function.prototype.curry = function(){466Function.prototype.curry = function(){
457 return curry(this);467 return curry(this);
458};468};
469
470/**
471 * Topological Sort
472 *
473 * Sort a directed graph based on incoming edges
474 *
475 * Coded by Jake Stothard
476 */
477function topological_sort(g) {
478 //Mark nodes as "deleted" instead of actually deleting them
479 //That way we don't have to copy g
480
481 for(i in g.nodes)
482 g.nodes[i].deleted = false;
483
484 var ret = topological_sort_helper(g);
485
486 //Cleanup: Remove the deleted property
487 for(i in g.nodes)
488 delete g.nodes[i].deleted
489
490 return ret;
491}
492function topological_sort_helper(g) {
493 //Find node with no incoming edges
494 var node;
495 for(i in g.nodes) {
496 if(g.nodes[i].deleted)
497 continue; //Bad style, meh
498
499 var incoming = false;
500 for(j in g.nodes[i].edges) {
501 if(g.nodes[i].edges[j].target == g.nodes[i]
502 && g.nodes[i].edges[j].source.deleted == false) {
503 incoming = true;
504 break;
505 }
506 }
507 if(!incoming) {
508 node = g.nodes[i];
509 break;
510 }
511 }
512
513 // Either unsortable or done. Either way, GTFO
514 if(node == undefined)
515 return [];
516
517 //"Delete" node from g
518 node.deleted = true;
519
520 var tail = topological_sort_helper(g);
521
522 tail.unshift(node);
523
524 return tail;
525}
459526
=== modified file 'js/dracula_graph.js'
--- js/dracula_graph.js 2010-09-22 20:09:16 +0000
+++ js/dracula_graph.js 2010-10-24 05:37:41 +0000
@@ -28,18 +28,44 @@
28 * Original usage example:28 * Original usage example:
29 * http://ajaxian.com/archives/new-javascriptcanvas-graph-library29 * http://ajaxian.com/archives/new-javascriptcanvas-graph-library
30 *30 *
31/*--------------------------------------------------------------------------*/31 /*--------------------------------------------------------------------------*/
3232
3333// Branched by Jake Stothard <stothardj@gmail.com>.
34
35/*
36 * Edge Factory
37 */
38var AbstractEdge = function() {
39}
40AbstractEdge.prototype = {
41 hide: function() {
42 this.connection.fg.hide();
43 this.connection.bg && this.bg.connection.hide();
44 }
45};
46var EdgeFactory = function() {
47 this.template = new AbstractEdge();
48 this.template.style = new Object();
49 this.template.style.directed = false;
50 this.template.weight = 1;
51};
52EdgeFactory.prototype = {
53 build: function(source, target) {
54 var e = jQuery.extend(true, {}, this.template);
55 e.source = source;
56 e.target = target;
57 return e;
58 }
59};
3460
35/*61/*
36 * Graph62 * Graph
37 */63 */
38var Graph = function() {64var Graph = function() {
39 this.nodes = [];65 this.nodes = new Object();
40 this.nodelist = []; // nodes by index number, only used once TODO use only one node container
41 this.edges = [];66 this.edges = [];
42 this.snapshots = []; // previous graph states TODO to be implemented67 this.snapshots = []; // previous graph states TODO to be implemented
68 this.edgeFactory = new EdgeFactory();
43};69};
44Graph.prototype = {70Graph.prototype = {
45 /* 71 /*
@@ -52,25 +78,20 @@
52 addNode: function(id, content) {78 addNode: function(id, content) {
53 /* testing if node is already existing in the graph */79 /* testing if node is already existing in the graph */
54 if(this.nodes[id] == undefined) {80 if(this.nodes[id] == undefined) {
55 this.nodes[id] = new Graph.Node(id, content || {"id" : id}); /* nodes indexed by node id */81 this.nodes[id] = new Graph.Node(id, content);
56 this.nodelist.push(this.nodes[id]); /* node list indexed by numbers */
57 }82 }
58 return this.nodes[id];83 return this.nodes[id];
59 },84 },
6085
61 // TODO rename style to content
62 addEdge: function(source, target, style) {86 addEdge: function(source, target, style) {
63 var s = this.addNode(source);87 var s = this.addNode(source);
64 var t = this.addNode(target);88 var t = this.addNode(target);
65 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 here89 var edge = this.edgeFactory.build(s, t);
90 jQuery.extend(edge.style,style);
66 s.edges.push(edge);91 s.edges.push(edge);
67 this.edges.push(edge);92 this.edges.push(edge);
68 /* add an edge back if graph undirected */93 // NOTE: Even directed edges are added to both nodes.
69 if(!style || !style.directed) {94 t.edges.push(edge);
70 var backedge = { source: t, target: s, style: style, weight : style&&style.weight||1, backedge : edge }; // TODO tidy up here
71 this.edges.push(backedge);
72 t.edges.push(backedge);
73 }
74 },95 },
75 96
76 /* TODO to be implemented97 /* TODO to be implemented
@@ -78,20 +99,21 @@
78 * @comment a comment describing the state99 * @comment a comment describing the state
79 */100 */
80 snapShot: function(comment) {101 snapShot: function(comment) {
102 /* FIXME
81 var graph = new Graph();103 var graph = new Graph();
82// graph.nodes = clone(this.nodes);104 graph.nodes = jQuery.extend(true, {}, this.nodes);
83// graph.nodelist = clone(this.nodelist);105 graph.edges = jQuery.extend(true, {}, this.edges);
84// graph.edges = clone(this.edges);
85 graph.snapShot = null;
86 this.snapshots.push({comment: comment, graph: graph});106 this.snapshots.push({comment: comment, graph: graph});
107 */
87 },108 },
88 /* FIXME doesn't work properly yet */
89 removeNode: function(id) {109 removeNode: function(id) {
90 var i = 0;110 delete this.nodes[id];
91 for(n in this.nodes)111 for(var i = 0; i < this.edges.length; i++) {
92 (n == id) && this.nodes.splice(i, 1) && this.nodelist.splice(i, 1) | i++;112 if (this.edges[i].source.id == id || this.edges[i].target.id == id) {
93 for(i in this.edges)113 this.edges.splice(i, 1);
94 (this.edges[i].source.id == id || this.edges[i].target == id) && this.edges.splice(i, 1);114 i--;
115 }
116 }
95 }117 }
96};118};
97119
@@ -99,6 +121,7 @@
99 * Node121 * Node
100 */122 */
101Graph.Node = function(id, node){123Graph.Node = function(id, node){
124 node = node || {};
102 node.id = id;125 node.id = id;
103 node.edges = [];126 node.edges = [];
104 node.hide = function() {127 node.hide = function() {
@@ -165,7 +188,7 @@
165 var clientX = e.clientX - (newX < 20 ? newX - 20 : newX > selfRef.width - 20 ? newX - selfRef.width + 20 : 0);188 var clientX = e.clientX - (newX < 20 ? newX - 20 : newX > selfRef.width - 20 ? newX - selfRef.width + 20 : 0);
166 var clientY = e.clientY - (newY < 20 ? newY - 20 : newY > selfRef.height - 20 ? newY - selfRef.height + 20 : 0);189 var clientY = e.clientY - (newY < 20 ? newY - 20 : newY > selfRef.height - 20 ? newY - selfRef.height + 20 : 0);
167 selfRef.isDrag.set.translate(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy));190 selfRef.isDrag.set.translate(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy));
168// console.log(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy));191 // console.log(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy));
169 for (var i in selfRef.graph.edges) {192 for (var i in selfRef.graph.edges) {
170 selfRef.graph.edges[i].connection && selfRef.graph.edges[i].connection.draw();193 selfRef.graph.edges[i].connection && selfRef.graph.edges[i].connection.draw();
171 }194 }
@@ -249,7 +272,7 @@
249272
250 var box = shape.getBBox();273 var box = shape.getBBox();
251 shape.translate(Math.round(point[0])-(box.x+box.width/2)+0.5,Math.round(point[1]-(box.y+box.height/2)))274 shape.translate(Math.round(point[0])-(box.x+box.width/2)+0.5,Math.round(point[1]-(box.y+box.height/2)))
252//console.log(box,point);275 //console.log(box,point);
253 node.hidden || shape.show();276 node.hidden || shape.show();
254 node.shape = shape;277 node.shape = shape;
255 },278 },
@@ -275,132 +298,184 @@
275};298};
276Graph.Layout = {};299Graph.Layout = {};
277Graph.Layout.Spring = function(graph) {300Graph.Layout.Spring = function(graph) {
278 this.graph = graph;301 this.graph = graph;
279 this.iterations = 500;302 this.iterations = 500;
280 this.maxRepulsiveForceDistance = 6;303 this.maxRepulsiveForceDistance = 6;
281 this.k = 2;304 this.k = 2;
282 this.c = 0.01;305 this.c = 0.01;
283 this.maxVertexMovement = 0.5;306 this.maxVertexMovement = 0.5;
284 this.layout();307 this.layout();
285 };308};
286Graph.Layout.Spring.prototype = {309Graph.Layout.Spring.prototype = {
287 layout: function() {310 layout: function() {
288 this.layoutPrepare();311 this.layoutPrepare();
289 for (var i = 0; i < this.iterations; i++) {312 for (var i = 0; i < this.iterations; i++) {
290 this.layoutIteration();313 this.layoutIteration();
291 }314 }
292 this.layoutCalcBounds();315 this.layoutCalcBounds();
293 },316 },
294 317
295 layoutPrepare: function() {318 layoutPrepare: function() {
296 for (i in this.graph.nodes) {319 for (i in this.graph.nodes) {
297 var node = this.graph.nodes[i];320 var node = this.graph.nodes[i];
298 node.layoutPosX = 0;321 node.layoutPosX = 0;
299 node.layoutPosY = 0;322 node.layoutPosY = 0;
300 node.layoutForceX = 0;323 node.layoutForceX = 0;
301 node.layoutForceY = 0;324 node.layoutForceY = 0;
302 }325 }
303 326
304 },327 },
305 328
306 layoutCalcBounds: function() {329 layoutCalcBounds: function() {
307 var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;330 var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
308331
309 for (i in this.graph.nodes) {332 for (i in this.graph.nodes) {
310 var x = this.graph.nodes[i].layoutPosX;333 var x = this.graph.nodes[i].layoutPosX;
311 var y = this.graph.nodes[i].layoutPosY;334 var y = this.graph.nodes[i].layoutPosY;
312 335
313 if(x > maxx) maxx = x;336 if(x > maxx) maxx = x;
314 if(x < minx) minx = x;337 if(x < minx) minx = x;
315 if(y > maxy) maxy = y;338 if(y > maxy) maxy = y;
316 if(y < miny) miny = y;339 if(y < miny) miny = y;
317 }340 }
318341
319 this.graph.layoutMinX = minx;342 this.graph.layoutMinX = minx;
320 this.graph.layoutMaxX = maxx;343 this.graph.layoutMaxX = maxx;
321 this.graph.layoutMinY = miny;344 this.graph.layoutMinY = miny;
322 this.graph.layoutMaxY = maxy;345 this.graph.layoutMaxY = maxy;
323 },346 },
324 347
325 layoutIteration: function() {348 layoutIteration: function() {
326 // Forces on nodes due to node-node repulsions349 // Forces on nodes due to node-node repulsions
327 for (var i = 0; i < this.graph.nodelist.length; i++) {350
328 var node1 = this.graph.nodelist[i];351 var prev = new Array();
329 for (var j = i + 1; j < this.graph.nodelist.length; j++) {352 for(var c in this.graph.nodes) {
330 var node2 = this.graph.nodelist[j];353 var node1 = this.graph.nodes[c];
331 this.layoutRepulsive(node1, node2);354 for (var d in prev) {
332 }355 var node2 = this.graph.nodes[prev[d]];
333 }356 this.layoutRepulsive(node1, node2);
334 // Forces on nodes due to edge attractions357
335 for (var i = 0; i < this.graph.edges.length; i++) {358 }
336 var edge = this.graph.edges[i];359 prev.push(c);
337 this.layoutAttractive(edge); 360 }
338 }361
339 362 // Forces on nodes due to edge attractions
340 // Move by the given force363 for (var i = 0; i < this.graph.edges.length; i++) {
341 for (i in this.graph.nodes) {364 var edge = this.graph.edges[i];
342 var node = this.graph.nodes[i];365 this.layoutAttractive(edge);
343 var xmove = this.c * node.layoutForceX;366 }
344 var ymove = this.c * node.layoutForceY;367
345368 // Move by the given force
346 var max = this.maxVertexMovement;369 for (i in this.graph.nodes) {
347 if(xmove > max) xmove = max;370 var node = this.graph.nodes[i];
348 if(xmove < -max) xmove = -max;371 var xmove = this.c * node.layoutForceX;
349 if(ymove > max) ymove = max;372 var ymove = this.c * node.layoutForceY;
350 if(ymove < -max) ymove = -max;373
351 374 var max = this.maxVertexMovement;
352 node.layoutPosX += xmove;375 if(xmove > max) xmove = max;
353 node.layoutPosY += ymove;376 if(xmove < -max) xmove = -max;
354 node.layoutForceX = 0;377 if(ymove > max) ymove = max;
355 node.layoutForceY = 0;378 if(ymove < -max) ymove = -max;
356 }379
357 },380 node.layoutPosX += xmove;
358381 node.layoutPosY += ymove;
359 layoutRepulsive: function(node1, node2) {382 node.layoutForceX = 0;
360 var dx = node2.layoutPosX - node1.layoutPosX;383 node.layoutForceY = 0;
361 var dy = node2.layoutPosY - node1.layoutPosY;384 }
362 var d2 = dx * dx + dy * dy;385 },
363 if(d2 < 0.01) {386
364 dx = 0.1 * Math.random() + 0.1;387 layoutRepulsive: function(node1, node2) {
365 dy = 0.1 * Math.random() + 0.1;388 var dx = node2.layoutPosX - node1.layoutPosX;
366 var d2 = dx * dx + dy * dy;389 var dy = node2.layoutPosY - node1.layoutPosY;
367 }390 var d2 = dx * dx + dy * dy;
368 var d = Math.sqrt(d2);391 if(d2 < 0.01) {
369 if(d < this.maxRepulsiveForceDistance) {392 dx = 0.1 * Math.random() + 0.1;
370 var repulsiveForce = this.k * this.k / d;393 dy = 0.1 * Math.random() + 0.1;
371 node2.layoutForceX += repulsiveForce * dx / d;394 var d2 = dx * dx + dy * dy;
372 node2.layoutForceY += repulsiveForce * dy / d;395 }
373 node1.layoutForceX -= repulsiveForce * dx / d;396 var d = Math.sqrt(d2);
374 node1.layoutForceY -= repulsiveForce * dy / d;397 if(d < this.maxRepulsiveForceDistance) {
375 }398 var repulsiveForce = this.k * this.k / d;
376 },399 node2.layoutForceX += repulsiveForce * dx / d;
377400 node2.layoutForceY += repulsiveForce * dy / d;
378 layoutAttractive: function(edge) {401 node1.layoutForceX -= repulsiveForce * dx / d;
379 var node1 = edge.source;402 node1.layoutForceY -= repulsiveForce * dy / d;
380 var node2 = edge.target;403 }
381 404 },
382 var dx = node2.layoutPosX - node1.layoutPosX;405
383 var dy = node2.layoutPosY - node1.layoutPosY;406 layoutAttractive: function(edge) {
384 var d2 = dx * dx + dy * dy;407 var node1 = edge.source;
385 if(d2 < 0.01) {408 var node2 = edge.target;
386 dx = 0.1 * Math.random() + 0.1;409
387 dy = 0.1 * Math.random() + 0.1;410 var dx = node2.layoutPosX - node1.layoutPosX;
388 var d2 = dx * dx + dy * dy;411 var dy = node2.layoutPosY - node1.layoutPosY;
389 }412 var d2 = dx * dx + dy * dy;
390 var d = Math.sqrt(d2);413 if(d2 < 0.01) {
391 if(d > this.maxRepulsiveForceDistance) {414 dx = 0.1 * Math.random() + 0.1;
392 d = this.maxRepulsiveForceDistance;415 dy = 0.1 * Math.random() + 0.1;
393 d2 = d * d;416 var d2 = dx * dx + dy * dy;
394 }417 }
395 var attractiveForce = (d2 - this.k * this.k) / this.k;418 var d = Math.sqrt(d2);
396 if(edge.attraction == undefined) edge.attraction = 1;419 if(d > this.maxRepulsiveForceDistance) {
397 attractiveForce *= Math.log(edge.attraction) * 0.5 + 1;420 d = this.maxRepulsiveForceDistance;
398 421 d2 = d * d;
399 node2.layoutForceX -= attractiveForce * dx / d;422 }
400 node2.layoutForceY -= attractiveForce * dy / d;423 var attractiveForce = (d2 - this.k * this.k) / this.k;
401 node1.layoutForceX += attractiveForce * dx / d;424 if(edge.attraction == undefined) edge.attraction = 1;
402 node1.layoutForceY += attractiveForce * dy / d;425 attractiveForce *= Math.log(edge.attraction) * 0.5 + 1;
403 }426
427 node2.layoutForceX -= attractiveForce * dx / d;
428 node2.layoutForceY -= attractiveForce * dy / d;
429 node1.layoutForceX += attractiveForce * dx / d;
430 node1.layoutForceY += attractiveForce * dy / d;
431 }
432};
433
434Graph.Layout.Ordered = function(graph, order) {
435 this.graph = graph;
436 this.order = order;
437 this.layout();
438};
439Graph.Layout.Ordered.prototype = {
440 layout: function() {
441 this.layoutPrepare();
442 this.layoutCalcBounds();
443 },
444
445 layoutPrepare: function(order) {
446 for (i in this.graph.nodes) {
447 var node = this.graph.nodes[i];
448 node.layoutPosX = 0;
449 node.layoutPosY = 0;
450 }
451 var counter = 0;
452 for (i in this.order) {
453 var node = this.order[i];
454 node.layoutPosX = counter;
455 node.layoutPosY = Math.random();
456 counter++;
457 }
458 },
459
460 layoutCalcBounds: function() {
461 var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
462
463 for (i in this.graph.nodes) {
464 var x = this.graph.nodes[i].layoutPosX;
465 var y = this.graph.nodes[i].layoutPosY;
466
467 if(x > maxx) maxx = x;
468 if(x < minx) minx = x;
469 if(y > maxy) maxy = y;
470 if(y < miny) miny = y;
471 }
472
473 this.graph.layoutMinX = minx;
474 this.graph.layoutMaxX = maxx;
475
476 this.graph.layoutMinY = miny;
477 this.graph.layoutMaxY = maxy;
478 },
404};479};
405480
406/*481/*
@@ -409,23 +484,6 @@
409484
410function log(a) {console.log&&console.log(a);}485function log(a) {console.log&&console.log(a);}
411486
412function clone(obj){
413 if(obj == null || typeof(obj) != 'object')
414 return obj;
415
416 var temp = obj.constructor(); // changed
417
418 for(var key in obj)
419 temp[key] = clone(obj[key]);
420 return temp;
421}
422
423// this will eventually mess up array usage with for-in-loops
424//Array.prototype.onEach = function(f, arg) {
425// var l = this.length;
426// for( var i = 0; i < l; i++) this[i][f]( arg );
427//}
428
429/*487/*
430 * Raphael Tooltip Plugin488 * Raphael Tooltip Plugin
431 * - attaches an element as a tooltip to another element489 * - attaches an element as a tooltip to another element
@@ -445,7 +503,7 @@
445 function(event){ 503 function(event){
446 this.mousemove(function(event){ 504 this.mousemove(function(event){
447 this.tp.translate(event.clientX - 505 this.tp.translate(event.clientX -
448 this.tp.o.x,event.clientY - this.tp.o.y);506 this.tp.o.x,event.clientY - this.tp.o.y);
449 this.tp.o = {x: event.clientX, y: event.clientY};507 this.tp.o = {x: event.clientX, y: event.clientY};
450 });508 });
451 this.tp.show().toFront();509 this.tp.show().toFront();
@@ -453,6 +511,6 @@
453 function(event){511 function(event){
454 this.tp.hide();512 this.tp.hide();
455 this.unmousemove();513 this.unmousemove();
456 });514 });
457 return this;515 return this;
458};516};

Subscribers

People subscribed via source and target branches