gameplan 0.0.1 → 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,127 @@
1
+ /**
2
+ * Originally grabbed from the official RaphaelJS Documentation
3
+ * http://raphaeljs.com/graffle.html
4
+ * Adopted (arrows) and commented by Philipp Strathausen http://blog.ameisenbar.de
5
+ * Licenced under the MIT licence.
6
+ */
7
+
8
+ /**
9
+ * Usage:
10
+ * connect two shapes
11
+ * parameters:
12
+ * source shape [or connection for redrawing],
13
+ * target shape,
14
+ * style with { fg : linecolor, bg : background color, directed: boolean }
15
+ * returns:
16
+ * connection { draw = function() }
17
+ */
18
+ Raphael.fn.connection = function Connection(obj1, obj2, style) {
19
+ var selfRef = this;
20
+ /* create and return new connection */
21
+ var edge = {/*
22
+ from : obj1,
23
+ to : obj2,
24
+ style : style,*/
25
+ draw : function() {
26
+ /* get bounding boxes of target and source */
27
+ var bb1 = obj1.getBBox();
28
+ var bb2 = obj2.getBBox();
29
+ var off1 = 0;
30
+ var off2 = 0;
31
+ /* coordinates for potential connection coordinates from/to the objects */
32
+ var p = [
33
+ /* NORTH 1 */
34
+ { x: bb1.x + bb1.width / 2, y: bb1.y - off1 },
35
+ /* SOUTH 1 */
36
+ { x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + off1 },
37
+ /* WEST */
38
+ { x: bb1.x - off1, y: bb1.y + bb1.height / 2 },
39
+ /* EAST 1 */
40
+ { x: bb1.x + bb1.width + off1, y: bb1.y + bb1.height / 2 },
41
+ /* NORTH 2 */
42
+ { x: bb2.x + bb2.width / 2, y: bb2.y - off2 },
43
+ /* SOUTH 2 */
44
+ { x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + off2 },
45
+ /* WEST 2 */
46
+ { x: bb2.x - off2, y: bb2.y + bb2.height / 2 },
47
+ /* EAST 2 */
48
+ { x: bb2.x + bb2.width + off2, y: bb2.y + bb2.height / 2 }
49
+ ];
50
+
51
+ /* distances between objects and according coordinates connection */
52
+ var d = {}, dis = [];
53
+
54
+ /*
55
+ * find out the best connection coordinates by trying all possible ways
56
+ */
57
+ /* loop the first object's connection coordinates */
58
+ for (var i = 0; i < 4; i++) {
59
+ /* loop the seond object's connection coordinates */
60
+ for (var j = 4; j < 8; j++) {
61
+ var dx = Math.abs(p[i].x - p[j].x);
62
+ var dy = Math.abs(p[i].y - p[j].y);
63
+ if ((i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x)
64
+ && ((i != 2 && j != 7) || p[i].x > p[j].x)
65
+ && ((i != 0 && j != 5) || p[i].y > p[j].y)
66
+ && ((i != 1 && j != 4) || p[i].y < p[j].y)))
67
+ {
68
+ dis.push(dx + dy);
69
+ d[dis[dis.length - 1].toFixed(3)] = [i, j];
70
+ }
71
+ }
72
+ }
73
+ var res = dis.length == 0 ? [0, 4] : d[Math.min.apply(Math, dis).toFixed(3)];
74
+ /* bezier path */
75
+ var x1 = p[res[0]].x,
76
+ y1 = p[res[0]].y,
77
+ x4 = p[res[1]].x,
78
+ y4 = p[res[1]].y,
79
+ dx = Math.max(Math.abs(x1 - x4) / 2, 10),
80
+ dy = Math.max(Math.abs(y1 - y4) / 2, 10),
81
+ x2 = [ x1, x1, x1 - dx, x1 + dx ][res[0]].toFixed(3),
82
+ y2 = [ y1 - dy, y1 + dy, y1, y1 ][res[0]].toFixed(3),
83
+ x3 = [ 0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx ][res[1]].toFixed(3),
84
+ y3 = [ 0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4 ][res[1]].toFixed(3);
85
+ /* assemble path and arrow */
86
+ var path = [ "M" + x1.toFixed(3), y1.toFixed(3),
87
+ "C" + x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3) ].join(",");
88
+ /* arrow */
89
+ if(style && style.directed) {
90
+ // magnitude, length of the last path vector
91
+ var mag = Math.sqrt((y4 - y3) * (y4 - y3) + (x4 - x3) * (x4 - x3));
92
+ // vector normalisation to specified length
93
+ var norm = function(x,l){return (-x*(l||5)/mag);};
94
+ // calculate array coordinates (two lines orthogonal to the path vector)
95
+ var arr = [
96
+ { x:(norm(x4-x3)+norm(y4-y3)+x4).toFixed(3),
97
+ y:(norm(y4-y3)+norm(x4-x3)+y4).toFixed(3) },
98
+ { x:(norm(x4-x3)-norm(y4-y3)+x4).toFixed(3),
99
+ y:(norm(y4-y3)-norm(x4-x3)+y4).toFixed(3) }
100
+ ];
101
+ path = path + ",M"+arr[0].x+","+arr[0].y+",L"+x4+","+y4+",L"+arr[1].x+","+arr[1].y;
102
+ }
103
+ /* function to be used for moving existent path(s), e.g. animate() or attr() */
104
+ var move = "attr";
105
+ /* applying path(s) */
106
+ edge.fg && edge.fg[move]({path:path})
107
+ || (edge.fg = selfRef.path(path)
108
+ .attr({ stroke: style && style.stroke || "#000", fill: "none" })
109
+ .toBack());
110
+ edge.bg && edge.bg[move]({path:path})
111
+ || style && style.fill && (edge.bg = style.fill.split
112
+ && selfRef.path(path)
113
+ .attr({ stroke: style.fill.split("|")[0], fill: "none",
114
+ "stroke-width": style.fill.split("|")[1] || 3 }).toBack());
115
+ /* setting label */
116
+ style && style.label
117
+ && (edge.label && edge.label.attr({x:(x1+x4)/2, y:(y1+y4)/2})
118
+ || (edge.label = selfRef.text((x1+x4)/2, (y1+y4)/2, style.label)
119
+ .attr({fill: "#000", "font-size": style["font-size"] || "12px"})));
120
+ style && style.label && style["label-style"] && edge.label
121
+ && edge.label.attr(style["label-style"]);
122
+ style && style.callback && style.callback(edge);
123
+ }
124
+ }
125
+ edge.draw();
126
+ return edge;
127
+ };
@@ -0,0 +1,667 @@
1
+ /*
2
+ * Dracula Graph Layout and Drawing Framework 0.0.3alpha
3
+ * (c) 2010 Philipp Strathausen <strathausen@gmail.com>, http://strathausen.eu
4
+ * Contributions by Jake Stothard <stothardj@gmail.com>.
5
+ *
6
+ * based on the Graph JavaScript framework, version 0.0.1
7
+ * (c) 2006 Aslak Hellesoy <aslak.hellesoy@gmail.com>
8
+ * (c) 2006 Dave Hoover <dave.hoover@gmail.com>
9
+ *
10
+ * Ported from Graph::Layouter::Spring in
11
+ * http://search.cpan.org/~pasky/Graph-Layderer-0.02/
12
+ * The algorithm is based on a spring-style layouter of a Java-based social
13
+ * network tracker PieSpy written by Paul Mutton <paul@jibble.org>.
14
+ *
15
+ * This code is freely distributable under the MIT license. Commercial use is
16
+ * hereby granted without any cost or restriction.
17
+ *
18
+ * Links:
19
+ *
20
+ * Graph Dracula JavaScript Framework:
21
+ * http://graphdracula.net
22
+ *
23
+ /*--------------------------------------------------------------------------*/
24
+
25
+ /*
26
+ * Edge Factory
27
+ */
28
+ var AbstractEdge = function() {
29
+ }
30
+ AbstractEdge.prototype = {
31
+ hide: function() {
32
+ this.connection.fg.hide();
33
+ this.connection.bg && this.bg.connection.hide();
34
+ }
35
+ };
36
+ var EdgeFactory = function() {
37
+ this.template = new AbstractEdge();
38
+ this.template.style = new Object();
39
+ this.template.style.directed = false;
40
+ this.template.weight = 1;
41
+ };
42
+ EdgeFactory.prototype = {
43
+ build: function(source, target) {
44
+ var e = jQuery.extend(true, {}, this.template);
45
+ e.source = source;
46
+ e.target = target;
47
+ return e;
48
+ }
49
+ };
50
+
51
+ /*
52
+ * Graph
53
+ */
54
+ var Graph = function() {
55
+ this.nodes = {};
56
+ this.edges = [];
57
+ this.snapshots = []; // previous graph states TODO to be implemented
58
+ this.edgeFactory = new EdgeFactory();
59
+ };
60
+ Graph.prototype = {
61
+ /*
62
+ * add a node
63
+ * @id the node's ID (string or number)
64
+ * @content (optional, dictionary) can contain any information that is
65
+ * being interpreted by the layout algorithm or the graph
66
+ * representation
67
+ */
68
+ addNode: function(id, content) {
69
+ /* testing if node is already existing in the graph */
70
+ if(this.nodes[id] == undefined) {
71
+ this.nodes[id] = new Graph.Node(id, content);
72
+ }
73
+ return this.nodes[id];
74
+ },
75
+
76
+ addEdge: function(source, target, style) {
77
+ var s = this.addNode(source);
78
+ var t = this.addNode(target);
79
+ var edge = this.edgeFactory.build(s, t);
80
+ jQuery.extend(true, edge.style, style);
81
+ s.edges.push(edge);
82
+ this.edges.push(edge);
83
+ // NOTE: Even directed edges are added to both nodes.
84
+ t.edges.push(edge);
85
+ },
86
+
87
+ /* TODO to be implemented
88
+ * Preserve a copy of the graph state (nodes, positions, ...)
89
+ * @comment a comment describing the state
90
+ */
91
+ snapShot: function(comment) {
92
+ /* FIXME
93
+ var graph = new Graph();
94
+ graph.nodes = jQuery.extend(true, {}, this.nodes);
95
+ graph.edges = jQuery.extend(true, {}, this.edges);
96
+ this.snapshots.push({comment: comment, graph: graph});
97
+ */
98
+ },
99
+ removeNode: function(id) {
100
+ delete this.nodes[id];
101
+ for(var i = 0; i < this.edges.length; i++) {
102
+ if (this.edges[i].source.id == id || this.edges[i].target.id == id) {
103
+ this.edges.splice(i, 1);
104
+ i--;
105
+ }
106
+ }
107
+ }
108
+ };
109
+
110
+ /*
111
+ * Node
112
+ */
113
+ Graph.Node = function(id, node){
114
+ node = node || {};
115
+ node.id = id;
116
+ node.edges = [];
117
+ node.hide = function() {
118
+ this.hidden = true;
119
+ this.shape && this.shape.hide(); /* FIXME this is representation specific code and should be elsewhere */
120
+ for(i in this.edges)
121
+ (this.edges[i].source.id == id || this.edges[i].target == id) && this.edges[i].hide && this.edges[i].hide();
122
+ };
123
+ node.show = function() {
124
+ this.hidden = false;
125
+ this.shape && this.shape.show();
126
+ for(i in this.edges)
127
+ (this.edges[i].source.id == id || this.edges[i].target == id) && this.edges[i].show && this.edges[i].show();
128
+ };
129
+ return node;
130
+ };
131
+ Graph.Node.prototype = {
132
+ };
133
+
134
+ /*
135
+ * Renderer base class
136
+ */
137
+ Graph.Renderer = {};
138
+
139
+ /*
140
+ * Renderer implementation using RaphaelJS
141
+ */
142
+ Graph.Renderer.Raphael = function(element, graph, width, height) {
143
+ this.width = width || 400;
144
+ this.height = height || 400;
145
+ var selfRef = this;
146
+ this.r = Raphael(element, this.width, this.height);
147
+ this.radius = 40; /* max dimension of a node */
148
+ this.graph = graph;
149
+ this.mouse_in = false;
150
+
151
+ /* TODO default node rendering function */
152
+ if(!this.graph.render) {
153
+ this.graph.render = function() {
154
+ return;
155
+ }
156
+ }
157
+
158
+ /*
159
+ * Dragging
160
+ */
161
+ this.isDrag = false;
162
+ this.dragger = function (e) {
163
+ this.dx = e.clientX;
164
+ this.dy = e.clientY;
165
+ selfRef.isDrag = this;
166
+ this.set && this.set.animate({"fill-opacity": .1}, 200);
167
+ e.preventDefault && e.preventDefault();
168
+ };
169
+
170
+ var d = document.getElementById(element);
171
+ d.onmousemove = function (e) {
172
+ e = e || window.event;
173
+ if (selfRef.isDrag) {
174
+ var bBox = selfRef.isDrag.set.getBBox();
175
+ // TODO round the coordinates here (eg. for proper image representation)
176
+ var newX = e.clientX - selfRef.isDrag.dx + (bBox.x + bBox.width / 2);
177
+ var newY = e.clientY - selfRef.isDrag.dy + (bBox.y + bBox.height / 2);
178
+ /* prevent shapes from being dragged out of the canvas */
179
+ var clientX = e.clientX - (newX < 20 ? newX - 20 : newX > selfRef.width - 20 ? newX - selfRef.width + 20 : 0);
180
+ var clientY = e.clientY - (newY < 20 ? newY - 20 : newY > selfRef.height - 20 ? newY - selfRef.height + 20 : 0);
181
+ selfRef.isDrag.set.translate(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy));
182
+ // console.log(clientX - Math.round(selfRef.isDrag.dx), clientY - Math.round(selfRef.isDrag.dy));
183
+ for (var i in selfRef.graph.edges) {
184
+ selfRef.graph.edges[i] &&
185
+ selfRef.graph.edges[i].connection && selfRef.graph.edges[i].connection.draw();
186
+ }
187
+ //selfRef.r.safari();
188
+ selfRef.isDrag.dx = clientX;
189
+ selfRef.isDrag.dy = clientY;
190
+ }
191
+ };
192
+ d.onmouseup = function () {
193
+ selfRef.isDrag && selfRef.isDrag.set.animate({"fill-opacity": .6}, 500);
194
+ selfRef.isDrag = false;
195
+ };
196
+ this.draw();
197
+ };
198
+
199
+
200
+ /* Moved this default node renderer function out of the main prototype code
201
+ * so it can be override by default */
202
+ Graph.Renderer.defaultRenderFunc = function(r, node) {
203
+ /* the default node drawing */
204
+ var color = Raphael.getColor();
205
+ var ellipse = r.ellipse(0, 0, 30, 20).attr({fill: color, stroke: color, "stroke-width": 2});
206
+ /* set DOM node ID */
207
+ ellipse.node.id = node.label || node.id;
208
+ shape = r.set().
209
+ push(ellipse).
210
+ push(r.text(0, 30, node.label || node.id));
211
+ return shape;
212
+ }
213
+
214
+
215
+ Graph.Renderer.Raphael.prototype = {
216
+ translate: function(point) {
217
+ return [
218
+ (point[0] - this.graph.layoutMinX) * this.factorX + this.radius,
219
+ (point[1] - this.graph.layoutMinY) * this.factorY + this.radius
220
+ ];
221
+ },
222
+
223
+ rotate: function(point, length, angle) {
224
+ var dx = length * Math.cos(angle);
225
+ var dy = length * Math.sin(angle);
226
+ return [point[0]+dx, point[1]+dy];
227
+ },
228
+
229
+ draw: function() {
230
+ this.factorX = (this.width - 2 * this.radius) / (this.graph.layoutMaxX - this.graph.layoutMinX);
231
+ this.factorY = (this.height - 2 * this.radius) / (this.graph.layoutMaxY - this.graph.layoutMinY);
232
+ for (i in this.graph.nodes) {
233
+ this.drawNode(this.graph.nodes[i]);
234
+ }
235
+ for (var i = 0; i < this.graph.edges.length; i++) {
236
+ this.drawEdge(this.graph.edges[i]);
237
+ }
238
+ },
239
+
240
+ drawNode: function(node) {
241
+ var point = this.translate([node.layoutPosX, node.layoutPosY]);
242
+ node.point = point;
243
+
244
+ /* if node has already been drawn, move the nodes */
245
+ if(node.shape) {
246
+ var oBBox = node.shape.getBBox();
247
+ var opoint = { x: oBBox.x + oBBox.width / 2, y: oBBox.y + oBBox.height / 2};
248
+ node.shape.translate(Math.round(point[0] - opoint.x), Math.round(point[1] - opoint.y));
249
+ this.r.safari();
250
+ return node;
251
+ }/* else, draw new nodes */
252
+
253
+ var shape;
254
+
255
+ /* if a node renderer function is provided by the user, then use it
256
+ or the default render function instead */
257
+ if(!node.render) {
258
+ node.render = Graph.Renderer.defaultRenderFunc;
259
+ }
260
+ /* or check for an ajax representation of the nodes */
261
+ if(node.shapes) {
262
+ // TODO ajax representation evaluation
263
+ }
264
+
265
+ shape = node.render(this.r, node).hide();
266
+
267
+ shape.attr({"fill-opacity": .6});
268
+ /* re-reference to the node an element belongs to, needed for dragging all elements of a node */
269
+ shape.items.forEach(function(item){ item.set = shape; item.node.style.cursor = "move"; });
270
+ shape.mousedown(this.dragger);
271
+
272
+ var box = shape.getBBox();
273
+ shape.translate(Math.round(point[0]-(box.x+box.width/2)),Math.round(point[1]-(box.y+box.height/2)))
274
+ //console.log(box,point);
275
+ node.hidden || shape.show();
276
+ node.shape = shape;
277
+ },
278
+ drawEdge: function(edge) {
279
+ /* if this edge already exists the other way around and is undirected */
280
+ if(edge.backedge)
281
+ return;
282
+ if(edge.source.hidden || edge.target.hidden) {
283
+ edge.connection && edge.connection.fg.hide();
284
+ edge.connection.bg && edge.connection.bg.hide();
285
+ return;
286
+ }
287
+ /* if edge already has been drawn, only refresh the edge */
288
+ if(!edge.connection) {
289
+ edge.style && edge.style.callback && edge.style.callback(edge); // TODO move this somewhere else
290
+ edge.connection = this.r.connection(edge.source.shape, edge.target.shape, edge.style);
291
+ return;
292
+ }
293
+ //FIXME showing doesn't work well
294
+ edge.connection.fg.show();
295
+ edge.connection.bg && edge.connection.bg.show();
296
+ edge.connection.draw();
297
+ }
298
+ };
299
+ Graph.Layout = {};
300
+ Graph.Layout.Spring = function(graph) {
301
+ this.graph = graph;
302
+ this.iterations = 500;
303
+ this.maxRepulsiveForceDistance = 6;
304
+ this.k = 2;
305
+ this.c = 0.01;
306
+ this.maxVertexMovement = 0.5;
307
+ this.layout();
308
+ };
309
+ Graph.Layout.Spring.prototype = {
310
+ layout: function() {
311
+ this.layoutPrepare();
312
+ for (var i = 0; i < this.iterations; i++) {
313
+ this.layoutIteration();
314
+ }
315
+ this.layoutCalcBounds();
316
+ },
317
+
318
+ layoutPrepare: function() {
319
+ for (i in this.graph.nodes) {
320
+ var node = this.graph.nodes[i];
321
+ node.layoutPosX = 0;
322
+ node.layoutPosY = 0;
323
+ node.layoutForceX = 0;
324
+ node.layoutForceY = 0;
325
+ }
326
+
327
+ },
328
+
329
+ layoutCalcBounds: function() {
330
+ var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
331
+
332
+ for (i in this.graph.nodes) {
333
+ var x = this.graph.nodes[i].layoutPosX;
334
+ var y = this.graph.nodes[i].layoutPosY;
335
+
336
+ if(x > maxx) maxx = x;
337
+ if(x < minx) minx = x;
338
+ if(y > maxy) maxy = y;
339
+ if(y < miny) miny = y;
340
+ }
341
+
342
+ this.graph.layoutMinX = minx;
343
+ this.graph.layoutMaxX = maxx;
344
+ this.graph.layoutMinY = miny;
345
+ this.graph.layoutMaxY = maxy;
346
+ },
347
+
348
+ layoutIteration: function() {
349
+ // Forces on nodes due to node-node repulsions
350
+
351
+ var prev = new Array();
352
+ for(var c in this.graph.nodes) {
353
+ var node1 = this.graph.nodes[c];
354
+ for (var d in prev) {
355
+ var node2 = this.graph.nodes[prev[d]];
356
+ this.layoutRepulsive(node1, node2);
357
+
358
+ }
359
+ prev.push(c);
360
+ }
361
+
362
+ // Forces on nodes due to edge attractions
363
+ for (var i = 0; i < this.graph.edges.length; i++) {
364
+ var edge = this.graph.edges[i];
365
+ this.layoutAttractive(edge);
366
+ }
367
+
368
+ // Move by the given force
369
+ for (i in this.graph.nodes) {
370
+ var node = this.graph.nodes[i];
371
+ var xmove = this.c * node.layoutForceX;
372
+ var ymove = this.c * node.layoutForceY;
373
+
374
+ var max = this.maxVertexMovement;
375
+ if(xmove > max) xmove = max;
376
+ if(xmove < -max) xmove = -max;
377
+ if(ymove > max) ymove = max;
378
+ if(ymove < -max) ymove = -max;
379
+
380
+ node.layoutPosX += xmove;
381
+ node.layoutPosY += ymove;
382
+ node.layoutForceX = 0;
383
+ node.layoutForceY = 0;
384
+ }
385
+ },
386
+
387
+ layoutRepulsive: function(node1, node2) {
388
+ if (typeof node1 == 'undefined' || typeof node2 == 'undefined')
389
+ return;
390
+ var dx = node2.layoutPosX - node1.layoutPosX;
391
+ var dy = node2.layoutPosY - node1.layoutPosY;
392
+ var d2 = dx * dx + dy * dy;
393
+ if(d2 < 0.01) {
394
+ dx = 0.1 * Math.random() + 0.1;
395
+ dy = 0.1 * Math.random() + 0.1;
396
+ var d2 = dx * dx + dy * dy;
397
+ }
398
+ var d = Math.sqrt(d2);
399
+ if(d < this.maxRepulsiveForceDistance) {
400
+ var repulsiveForce = this.k * this.k / d;
401
+ node2.layoutForceX += repulsiveForce * dx / d;
402
+ node2.layoutForceY += repulsiveForce * dy / d;
403
+ node1.layoutForceX -= repulsiveForce * dx / d;
404
+ node1.layoutForceY -= repulsiveForce * dy / d;
405
+ }
406
+ },
407
+
408
+ layoutAttractive: function(edge) {
409
+ var node1 = edge.source;
410
+ var node2 = edge.target;
411
+
412
+ var dx = node2.layoutPosX - node1.layoutPosX;
413
+ var dy = node2.layoutPosY - node1.layoutPosY;
414
+ var d2 = dx * dx + dy * dy;
415
+ if(d2 < 0.01) {
416
+ dx = 0.1 * Math.random() + 0.1;
417
+ dy = 0.1 * Math.random() + 0.1;
418
+ var d2 = dx * dx + dy * dy;
419
+ }
420
+ var d = Math.sqrt(d2);
421
+ if(d > this.maxRepulsiveForceDistance) {
422
+ d = this.maxRepulsiveForceDistance;
423
+ d2 = d * d;
424
+ }
425
+ var attractiveForce = (d2 - this.k * this.k) / this.k;
426
+ if(edge.attraction == undefined) edge.attraction = 1;
427
+ attractiveForce *= Math.log(edge.attraction) * 0.5 + 1;
428
+
429
+ node2.layoutForceX -= attractiveForce * dx / d;
430
+ node2.layoutForceY -= attractiveForce * dy / d;
431
+ node1.layoutForceX += attractiveForce * dx / d;
432
+ node1.layoutForceY += attractiveForce * dy / d;
433
+ }
434
+ };
435
+
436
+ Graph.Layout.Ordered = function(graph, order) {
437
+ this.graph = graph;
438
+ this.order = order;
439
+ this.layout();
440
+ };
441
+ Graph.Layout.Ordered.prototype = {
442
+ layout: function() {
443
+ this.layoutPrepare();
444
+ this.layoutCalcBounds();
445
+ },
446
+
447
+ layoutPrepare: function(order) {
448
+ for (i in this.graph.nodes) {
449
+ var node = this.graph.nodes[i];
450
+ node.layoutPosX = 0;
451
+ node.layoutPosY = 0;
452
+ }
453
+ var counter = 0;
454
+ for (i in this.order) {
455
+ var node = this.order[i];
456
+ node.layoutPosX = counter;
457
+ node.layoutPosY = Math.random();
458
+ counter++;
459
+ }
460
+ },
461
+
462
+ layoutCalcBounds: function() {
463
+ var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
464
+
465
+ for (i in this.graph.nodes) {
466
+ var x = this.graph.nodes[i].layoutPosX;
467
+ var y = this.graph.nodes[i].layoutPosY;
468
+
469
+ if(x > maxx) maxx = x;
470
+ if(x < minx) minx = x;
471
+ if(y > maxy) maxy = y;
472
+ if(y < miny) miny = y;
473
+ }
474
+
475
+ this.graph.layoutMinX = minx;
476
+ this.graph.layoutMaxX = maxx;
477
+
478
+ this.graph.layoutMinY = miny;
479
+ this.graph.layoutMaxY = maxy;
480
+ }
481
+ };
482
+
483
+
484
+ Graph.Layout.OrderedTree = function(graph, order) {
485
+ this.graph = graph;
486
+ this.order = order;
487
+ this.layout();
488
+ };
489
+
490
+ /*
491
+ * OrderedTree is like Ordered but assumes there is one root
492
+ * This way we can give non random positions to nodes on the Y-axis
493
+ * it assumes the ordered nodes are of a perfect binary tree
494
+ */
495
+ Graph.Layout.OrderedTree.prototype = {
496
+ layout: function() {
497
+ this.layoutPrepare();
498
+ this.layoutCalcBounds();
499
+ },
500
+
501
+ layoutPrepare: function(order) {
502
+ for (i in this.graph.nodes) {
503
+ var node = this.graph.nodes[i];
504
+ node.layoutPosX = 0;
505
+ node.layoutPosY = 0;
506
+ }
507
+ //to reverse the order of rendering, we need to find out the
508
+ //absolute number of levels we have. simple log math applies.
509
+ var numNodes = this.order.length;
510
+ var totalLevels = Math.floor(Math.log(numNodes) / Math.log(2));
511
+
512
+ var counter = 1;
513
+ for (i in this.order) {
514
+ var node = this.order[i];
515
+ //rank aka x coordinate
516
+ var rank = Math.floor(Math.log(counter) / Math.log(2));
517
+ //file relative to top
518
+ var file = counter - Math.pow(rank, 2);
519
+
520
+ log('Node ' + node.id + ' #' + counter + ' is at rank ' + rank + ' file ' + file);
521
+ node.layoutPosX = totalLevels - rank;
522
+ node.layoutPosY = file;
523
+ counter++;
524
+ }
525
+ },
526
+
527
+ layoutCalcBounds: function() {
528
+ var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
529
+
530
+ for (i in this.graph.nodes) {
531
+ var x = this.graph.nodes[i].layoutPosX;
532
+ var y = this.graph.nodes[i].layoutPosY;
533
+
534
+ if(x > maxx) maxx = x;
535
+ if(x < minx) minx = x;
536
+ if(y > maxy) maxy = y;
537
+ if(y < miny) miny = y;
538
+ }
539
+
540
+ this.graph.layoutMinX = minx;
541
+ this.graph.layoutMaxX = maxx;
542
+
543
+ this.graph.layoutMinY = miny;
544
+ this.graph.layoutMaxY = maxy;
545
+ }
546
+ };
547
+
548
+
549
+ Graph.Layout.TournamentTree = function(graph, order) {
550
+ this.graph = graph;
551
+ this.order = order;
552
+ this.layout();
553
+ };
554
+
555
+ /*
556
+ * TournamentTree looks more like a binary tree
557
+ */
558
+ Graph.Layout.TournamentTree.prototype = {
559
+ layout: function() {
560
+ this.layoutPrepare();
561
+ this.layoutCalcBounds();
562
+ },
563
+
564
+ layoutPrepare: function(order) {
565
+ for (i in this.graph.nodes) {
566
+ var node = this.graph.nodes[i];
567
+ node.layoutPosX = 0;
568
+ node.layoutPosY = 0;
569
+ }
570
+ //to reverse the order of rendering, we need to find out the
571
+ //absolute number of levels we have. simple log math applies.
572
+ var numNodes = this.order.length;
573
+ var totalLevels = Math.floor(Math.log(numNodes) / Math.log(2));
574
+
575
+ var counter = 1;
576
+ for (i in this.order) {
577
+ var node = this.order[i];
578
+ var depth = Math.floor(Math.log(counter) / Math.log(2));
579
+ var xpos = counter - Math.pow(depth, 2);
580
+ var offset = Math.pow(2, totalLevels - depth);
581
+ var final_x = offset + (counter - Math.pow(2,depth)) * Math.pow(2,(totalLevels - depth)+1);
582
+
583
+ log('Node ' + node.id + ' #' + counter + ' is at depth ' + depth + ' offset ' + offset + ' final_x ' + final_x);
584
+ node.layoutPosX = final_x;
585
+ node.layoutPosY = depth;
586
+ counter++;
587
+ }
588
+ },
589
+
590
+ layoutCalcBounds: function() {
591
+ var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
592
+
593
+ for (i in this.graph.nodes) {
594
+ var x = this.graph.nodes[i].layoutPosX;
595
+ var y = this.graph.nodes[i].layoutPosY;
596
+
597
+ if(x > maxx) maxx = x;
598
+ if(x < minx) minx = x;
599
+ if(y > maxy) maxy = y;
600
+ if(y < miny) miny = y;
601
+ }
602
+
603
+ this.graph.layoutMinX = minx;
604
+ this.graph.layoutMaxX = maxx;
605
+
606
+ this.graph.layoutMinY = miny;
607
+ this.graph.layoutMaxY = maxy;
608
+ }
609
+ };
610
+
611
+
612
+
613
+
614
+ /*
615
+ * usefull JavaScript extensions,
616
+ */
617
+
618
+ function log(a) {console.log&&console.log(a);}
619
+
620
+ /*
621
+ * Raphael Tooltip Plugin
622
+ * - attaches an element as a tooltip to another element
623
+ *
624
+ * Usage example, adding a rectangle as a tooltip to a circle:
625
+ *
626
+ * paper.circle(100,100,10).tooltip(paper.rect(0,0,20,30));
627
+ *
628
+ * If you want to use more shapes, you'll have to put them into a set.
629
+ *
630
+ */
631
+ Raphael.el.tooltip = function (tp) {
632
+ this.tp = tp;
633
+ this.tp.o = {x: 0, y: 0};
634
+ this.tp.hide();
635
+ this.hover(
636
+ function(event){
637
+ this.mousemove(function(event){
638
+ this.tp.translate(event.clientX -
639
+ this.tp.o.x,event.clientY - this.tp.o.y);
640
+ this.tp.o = {x: event.clientX, y: event.clientY};
641
+ });
642
+ this.tp.show().toFront();
643
+ },
644
+ function(event){
645
+ this.tp.hide();
646
+ this.unmousemove();
647
+ });
648
+ return this;
649
+ };
650
+
651
+ /* For IE */
652
+ if (!Array.prototype.forEach)
653
+ {
654
+ Array.prototype.forEach = function(fun /*, thisp*/)
655
+ {
656
+ var len = this.length;
657
+ if (typeof fun != "function")
658
+ throw new TypeError();
659
+
660
+ var thisp = arguments[1];
661
+ for (var i = 0; i < len; i++)
662
+ {
663
+ if (i in this)
664
+ fun.call(thisp, this[i], i, this);
665
+ }
666
+ };
667
+ }