~therp-nl/openerp-web/7.0-lp1013636-x2m_honour_required_attribute

« back to all changes in this revision

Viewing changes to addons/web_diagram/static/lib/js/dracula_graph.js

[MERGE] from trunk

Show diffs side-by-side

added added

removed removed

Lines of Context:
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(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 || 800;
144
 
    this.height = height || 800;
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].connection && selfRef.graph.edges[i].connection.draw();
185
 
            }
186
 
            //selfRef.r.safari();
187
 
            selfRef.isDrag.dx = clientX;
188
 
            selfRef.isDrag.dy = clientY;
189
 
        }
190
 
    };
191
 
    d.onmouseup = function () {
192
 
        selfRef.isDrag && selfRef.isDrag.set.animate({"fill-opacity": .6}, 500);
193
 
        selfRef.isDrag = false;
194
 
    };
195
 
    this.draw();
196
 
};
197
 
Graph.Renderer.Raphael.prototype = {
198
 
    translate: function(point) {
199
 
        return [
200
 
            (point[0] - this.graph.layoutMinX) * this.factorX + this.radius,
201
 
            (point[1] - this.graph.layoutMinY) * this.factorY + this.radius
202
 
        ];
203
 
    },
204
 
 
205
 
    rotate: function(point, length, angle) {
206
 
        var dx = length * Math.cos(angle);
207
 
        var dy = length * Math.sin(angle);
208
 
        return [point[0]+dx, point[1]+dy];
209
 
    },
210
 
 
211
 
    draw: function() {
212
 
        this.factorX = (this.width - 2 * this.radius) / (this.graph.layoutMaxX - this.graph.layoutMinX);
213
 
        this.factorY = (this.height - 2 * this.radius) / (this.graph.layoutMaxY - this.graph.layoutMinY);
214
 
        for (i in this.graph.nodes) {
215
 
            this.drawNode(this.graph.nodes[i]);
216
 
        }
217
 
        for (var i = 0; i < this.graph.edges.length; i++) {
218
 
            this.drawEdge(this.graph.edges[i]);
219
 
        }
220
 
    },
221
 
 
222
 
    drawNode: function(node) {
223
 
        var point = this.translate([node.layoutPosX, node.layoutPosY]);
224
 
        node.point = point;
225
 
 
226
 
        /* if node has already been drawn, move the nodes */
227
 
        if(node.shape) {
228
 
            var oBBox = node.shape.getBBox();
229
 
            var opoint = { x: oBBox.x + oBBox.width / 2, y: oBBox.y + oBBox.height / 2};
230
 
            node.shape.translate(Math.round(point[0] - opoint.x), Math.round(point[1] - opoint.y));
231
 
            this.r.safari();
232
 
            return node;
233
 
        }/* else, draw new nodes */
234
 
 
235
 
        var shape;
236
 
 
237
 
        /* if a node renderer function is provided by the user, then use it
238
 
           or the default render function instead */
239
 
        if(!node.render) {
240
 
            node.render = function(r, node) {
241
 
                /* the default node drawing */
242
 
                var color = Raphael.getColor();
243
 
                var ellipse = r.ellipse(0, 0, 30, 20).attr({fill: color, stroke: color, "stroke-width": 2});
244
 
                /* set DOM node ID */
245
 
                ellipse.node.id = node.label || node.id;
246
 
                shape = r.set().
247
 
                    push(ellipse).
248
 
                    push(r.text(0, 30, node.label || node.id));
249
 
                return shape;
250
 
            }
251
 
        }
252
 
        /* or check for an ajax representation of the nodes */
253
 
        if(node.shapes) {
254
 
            // TODO ajax representation evaluation
255
 
        }
256
 
 
257
 
        shape = node.render(this.r, node).hide();
258
 
 
259
 
        shape.attr({"fill-opacity": .6});
260
 
        /* re-reference to the node an element belongs to, needed for dragging all elements of a node */
261
 
        shape.items.forEach(function(item){ item.set = shape; item.node.style.cursor = "move"; });
262
 
        shape.mousedown(this.dragger);
263
 
 
264
 
        var box = shape.getBBox();
265
 
        shape.translate(Math.round(point[0]-(box.x+box.width/2)),Math.round(point[1]-(box.y+box.height/2)))
266
 
        //console.log(box,point);
267
 
        node.hidden || shape.show();
268
 
        node.shape = shape;
269
 
    },
270
 
    drawEdge: function(edge) {
271
 
        /* if this edge already exists the other way around and is undirected */
272
 
        if(edge.backedge)
273
 
            return;
274
 
        if(edge.source.hidden || edge.target.hidden) {
275
 
            edge.connection && edge.connection.fg.hide() | edge.connection.bg && edge.connection.bg.hide();
276
 
            return;
277
 
        }
278
 
        /* if edge already has been drawn, only refresh the edge */
279
 
        if(!edge.connection) {
280
 
            edge.style && edge.style.callback && edge.style.callback(edge); // TODO move this somewhere else
281
 
            edge.connection = this.r.connection(edge.source.shape, edge.target.shape, edge.style);
282
 
            return;
283
 
        }
284
 
        //FIXME showing doesn't work well
285
 
        edge.connection.fg.show();
286
 
        edge.connection.bg && edge.connection.bg.show();
287
 
        edge.connection.draw();
288
 
    }
289
 
};
290
 
Graph.Layout = {};
291
 
Graph.Layout.Spring = function(graph) {
292
 
    this.graph = graph;
293
 
    this.iterations = 500;
294
 
    this.maxRepulsiveForceDistance = 6;
295
 
    this.k = 2;
296
 
    this.c = 0.01;
297
 
    this.maxVertexMovement = 0.5;
298
 
    this.layout();
299
 
};
300
 
Graph.Layout.Spring.prototype = {
301
 
    layout: function() {
302
 
        this.layoutPrepare();
303
 
        for (var i = 0; i < this.iterations; i++) {
304
 
            this.layoutIteration();
305
 
        }
306
 
        this.layoutCalcBounds();
307
 
    },
308
 
 
309
 
    layoutPrepare: function() {
310
 
        for (i in this.graph.nodes) {
311
 
            var node = this.graph.nodes[i];
312
 
            node.layoutPosX = 0;
313
 
            node.layoutPosY = 0;
314
 
            node.layoutForceX = 0;
315
 
            node.layoutForceY = 0;
316
 
        }
317
 
 
318
 
    },
319
 
 
320
 
    layoutCalcBounds: function() {
321
 
        var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
322
 
 
323
 
        for (i in this.graph.nodes) {
324
 
            var x = this.graph.nodes[i].layoutPosX;
325
 
            var y = this.graph.nodes[i].layoutPosY;
326
 
 
327
 
            if(x > maxx) maxx = x;
328
 
            if(x < minx) minx = x;
329
 
            if(y > maxy) maxy = y;
330
 
            if(y < miny) miny = y;
331
 
        }
332
 
 
333
 
        this.graph.layoutMinX = minx;
334
 
        this.graph.layoutMaxX = maxx;
335
 
        this.graph.layoutMinY = miny;
336
 
        this.graph.layoutMaxY = maxy;
337
 
    },
338
 
 
339
 
    layoutIteration: function() {
340
 
        // Forces on nodes due to node-node repulsions
341
 
 
342
 
        var prev = new Array();
343
 
        for(var c in this.graph.nodes) {
344
 
            var node1 = this.graph.nodes[c];
345
 
            for (var d in prev) {
346
 
                var node2 = this.graph.nodes[prev[d]];
347
 
                this.layoutRepulsive(node1, node2);
348
 
 
349
 
            }
350
 
            prev.push(c);
351
 
        }
352
 
 
353
 
        // Forces on nodes due to edge attractions
354
 
        for (var i = 0; i < this.graph.edges.length; i++) {
355
 
            var edge = this.graph.edges[i];
356
 
            this.layoutAttractive(edge);
357
 
        }
358
 
 
359
 
        // Move by the given force
360
 
        for (i in this.graph.nodes) {
361
 
            var node = this.graph.nodes[i];
362
 
            var xmove = this.c * node.layoutForceX;
363
 
            var ymove = this.c * node.layoutForceY;
364
 
 
365
 
            var max = this.maxVertexMovement;
366
 
            if(xmove > max) xmove = max;
367
 
            if(xmove < -max) xmove = -max;
368
 
            if(ymove > max) ymove = max;
369
 
            if(ymove < -max) ymove = -max;
370
 
 
371
 
            node.layoutPosX += xmove;
372
 
            node.layoutPosY += ymove;
373
 
            node.layoutForceX = 0;
374
 
            node.layoutForceY = 0;
375
 
        }
376
 
    },
377
 
 
378
 
    layoutRepulsive: function(node1, node2) {
379
 
        if (typeof node1 == 'undefined' || typeof node2 == 'undefined')
380
 
            return;
381
 
        var dx = node2.layoutPosX - node1.layoutPosX;
382
 
        var dy = node2.layoutPosY - node1.layoutPosY;
383
 
        var d2 = dx * dx + dy * dy;
384
 
        if(d2 < 0.01) {
385
 
            dx = 0.1 * Math.random() + 0.1;
386
 
            dy = 0.1 * Math.random() + 0.1;
387
 
            var d2 = dx * dx + dy * dy;
388
 
        }
389
 
        var d = Math.sqrt(d2);
390
 
        if(d < this.maxRepulsiveForceDistance) {
391
 
            var repulsiveForce = this.k * this.k / d;
392
 
            node2.layoutForceX += repulsiveForce * dx / d;
393
 
            node2.layoutForceY += repulsiveForce * dy / d;
394
 
            node1.layoutForceX -= repulsiveForce * dx / d;
395
 
            node1.layoutForceY -= repulsiveForce * dy / d;
396
 
        }
397
 
    },
398
 
 
399
 
    layoutAttractive: function(edge) {
400
 
        var node1 = edge.source;
401
 
        var node2 = edge.target;
402
 
 
403
 
        var dx = node2.layoutPosX - node1.layoutPosX;
404
 
        var dy = node2.layoutPosY - node1.layoutPosY;
405
 
        var d2 = dx * dx + dy * dy;
406
 
        if(d2 < 0.01) {
407
 
            dx = 0.1 * Math.random() + 0.1;
408
 
            dy = 0.1 * Math.random() + 0.1;
409
 
            var d2 = dx * dx + dy * dy;
410
 
        }
411
 
        var d = Math.sqrt(d2);
412
 
        if(d > this.maxRepulsiveForceDistance) {
413
 
            d = this.maxRepulsiveForceDistance;
414
 
            d2 = d * d;
415
 
        }
416
 
        var attractiveForce = (d2 - this.k * this.k) / this.k;
417
 
        if(edge.attraction == undefined) edge.attraction = 1;
418
 
        attractiveForce *= Math.log(edge.attraction) * 0.5 + 1;
419
 
 
420
 
        node2.layoutForceX -= attractiveForce * dx / d;
421
 
        node2.layoutForceY -= attractiveForce * dy / d;
422
 
        node1.layoutForceX += attractiveForce * dx / d;
423
 
        node1.layoutForceY += attractiveForce * dy / d;
424
 
    }
425
 
};
426
 
 
427
 
Graph.Layout.Ordered = function(graph, order) {
428
 
    this.graph = graph;
429
 
    this.order = order;
430
 
    this.layout();
431
 
};
432
 
Graph.Layout.Ordered.prototype = {
433
 
    layout: function() {
434
 
        this.layoutPrepare();
435
 
        this.layoutCalcBounds();
436
 
    },
437
 
 
438
 
    layoutPrepare: function(order) {
439
 
        for (i in this.graph.nodes) {
440
 
            var node = this.graph.nodes[i];
441
 
            node.layoutPosX = 0;
442
 
            node.layoutPosY = 0;
443
 
        }
444
 
            var counter = 0;
445
 
            for (i in this.order) {
446
 
                var node = this.order[i];
447
 
                node.layoutPosX = counter;
448
 
                node.layoutPosY = Math.random();
449
 
                counter++;
450
 
            }
451
 
    },
452
 
 
453
 
    layoutCalcBounds: function() {
454
 
        var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
455
 
 
456
 
        for (i in this.graph.nodes) {
457
 
            var x = this.graph.nodes[i].layoutPosX;
458
 
            var y = this.graph.nodes[i].layoutPosY;
459
 
 
460
 
            if(x > maxx) maxx = x;
461
 
            if(x < minx) minx = x;
462
 
            if(y > maxy) maxy = y;
463
 
            if(y < miny) miny = y;
464
 
        }
465
 
 
466
 
        this.graph.layoutMinX = minx;
467
 
        this.graph.layoutMaxX = maxx;
468
 
 
469
 
        this.graph.layoutMinY = miny;
470
 
        this.graph.layoutMaxY = maxy;
471
 
    }
472
 
};
473
 
 
474
 
/*
475
 
 * usefull JavaScript extensions,
476
 
 */
477
 
 
478
 
function log(a) {console.log&&console.log(a);}
479
 
 
480
 
/*
481
 
 * Raphael Tooltip Plugin
482
 
 * - attaches an element as a tooltip to another element
483
 
 *
484
 
 * Usage example, adding a rectangle as a tooltip to a circle:
485
 
 *
486
 
 *      paper.circle(100,100,10).tooltip(paper.rect(0,0,20,30));
487
 
 *
488
 
 * If you want to use more shapes, you'll have to put them into a set.
489
 
 *
490
 
 */
491
 
Raphael.el.tooltip = function (tp) {
492
 
    this.tp = tp;
493
 
    this.tp.o = {x: 0, y: 0};
494
 
    this.tp.hide();
495
 
    this.hover(
496
 
        function(event){
497
 
            this.mousemove(function(event){
498
 
                this.tp.translate(event.clientX -
499
 
                                  this.tp.o.x,event.clientY - this.tp.o.y);
500
 
                this.tp.o = {x: event.clientX, y: event.clientY};
501
 
            });
502
 
            this.tp.show().toFront();
503
 
        },
504
 
        function(event){
505
 
            this.tp.hide();
506
 
            this.unmousemove();
507
 
        });
508
 
    return this;
509
 
};
510
 
 
511
 
/* For IE */
512
 
if (!Array.prototype.forEach)
513
 
{
514
 
  Array.prototype.forEach = function(fun /*, thisp*/)
515
 
  {
516
 
    var len = this.length;
517
 
    if (typeof fun != "function")
518
 
      throw new TypeError();
519
 
 
520
 
    var thisp = arguments[1];
521
 
    for (var i = 0; i < len; i++)
522
 
    {
523
 
      if (i in this)
524
 
        fun.call(thisp, this[i], i, this);
525
 
    }
526
 
  };
527
 
}