Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
Download

📚 The CoCalc Library - books, templates and other resources

132939 views
License: OTHER
1
/* Graph JavaScript framework, version 0.0.1
2
* (c) 2006 Aslak Hellesoy <[email protected]>
3
* (c) 2006 Dave Hoover <[email protected]>
4
*
5
* Ported from Graph::Layouter::Spring in
6
* http://search.cpan.org/~pasky/Graph-Layderer-0.02/
7
* The algorithm is based on a spring-style layouter of a Java-based social
8
* network tracker PieSpy written by Paul Mutton E<lt>[email protected]<gt>.
9
*
10
* Adopted by Philipp Strathausen <[email protected]> to support Raphael JS
11
* for rendering, dragging and much more. See http://blog.ameisenbar.de
12
*
13
* Graph is freely distributable under the terms of an MIT-style license.
14
* For details, see the Graph web site: http://dev.buildpatternd.com/trac
15
*
16
* Links:
17
*
18
* Demo of the original applet:
19
* http://redsquirrel.com/dave/work/webdep/
20
*
21
* Mirrored original source code at snipplr:
22
* http://snipplr.com/view/1950/graph-javascript-framework-version-001/
23
*
24
* Original usage example:
25
* http://ajaxian.com/archives/new-javascriptcanvas-graph-library
26
*
27
/*--------------------------------------------------------------------------*/
28
29
/*
30
* Graph
31
*/
32
var Graph = function() {
33
this.nodes = [];
34
this.edges = [];
35
};
36
Graph.prototype = {
37
addNode: function(id, content) {
38
/* testing if node is already existing in the graph */
39
var new_node = this.nodes[id];
40
if(new_node == undefined) {
41
new_node = new Graph.Node(id, content||{"id":id});
42
this.nodes[id] = new_node;
43
this.nodes.push(new_node); // TODO get rid of the array
44
}
45
return new_node;
46
},
47
48
addEdge: function(source, target, style) {
49
var s = this.addNode(source);
50
var t = this.addNode(target);
51
var color;
52
var colorbg;
53
var directed;
54
if(style) { color = style.color; colorbg = style.colorbg; directed = style.directed }
55
var edge = { source: s, target: t, color: color, colorbg: colorbg, directed: directed };
56
this.edges.push(edge);
57
}
58
};
59
60
/*
61
* Node
62
*/
63
Graph.Node = function(id, value){
64
this.id = id;
65
this.content = value;
66
};
67
Graph.Node.prototype = {
68
};
69
Graph.Renderer = {};
70
Graph.Renderer.Raphael = function(element, graph, width, height) {
71
this.width = width||400;
72
this.height = height||400;
73
var selfRef = this;
74
this.r = Raphael(element, this.width, this.height);
75
this.radius = 40; /* max dimension of a node */
76
this.graph = graph;
77
this.mouse_in = false;
78
79
/*
80
* Dragging
81
*/
82
this.isDrag = false;
83
this.dragger = function (e) {
84
this.dx = e.clientX;
85
this.dy = e.clientY;
86
selfRef.isDrag = this;
87
this.animate({"fill-opacity": .2}, 500);
88
e.preventDefault && e.preventDefault();
89
};
90
91
document.onmousemove = function (e) {
92
e = e || window.event;
93
if (selfRef.isDrag) {
94
var newX = e.clientX - selfRef.isDrag.dx + (selfRef.isDrag.attrs.cx == null ? (selfRef.isDrag.attrs.x + selfRef.isDrag.attrs.width / 2) : selfRef.isDrag.attrs.cx);
95
var newY = e.clientY - selfRef.isDrag.dy + (selfRef.isDrag.attrs.cy == null ? (selfRef.isDrag.attrs.y + selfRef.isDrag.attrs.height / 2) : selfRef.isDrag.attrs.cy);
96
/* prevent shapes from being dragged out of the canvas */
97
var clientX = e.clientX - (newX < 20 ? newX - 20 : newX > selfRef.width - 20 ? newX - selfRef.width + 20 : 0);
98
var clientY = e.clientY - (newY < 20 ? newY - 20 : newY > selfRef.height - 20 ? newY - selfRef.height + 20 : 0);
99
selfRef.isDrag.translate(clientX - selfRef.isDrag.dx, clientY - selfRef.isDrag.dy);
100
selfRef.isDrag.label.translate(clientX - selfRef.isDrag.dx, clientY - selfRef.isDrag.dy);
101
for (var i in selfRef.graph.edges) {
102
selfRef.graph.edges[i].connection.draw();
103
}
104
//selfRef.r.safari();
105
selfRef.isDrag.dx = clientX;
106
selfRef.isDrag.dy = clientY;
107
}
108
};
109
document.onmouseup = function () {
110
selfRef.isDrag && selfRef.isDrag.animate({"fill-opacity": 0}, 500);
111
selfRef.isDrag = false;
112
};
113
};
114
115
/*
116
* Renderer using RaphaelJS
117
*/
118
Graph.Renderer.Raphael.prototype = {
119
translate: function(point) {
120
return [
121
(point[0] - this.graph.layoutMinX) * this.factorX + this.radius,
122
(point[1] - this.graph.layoutMinY) * this.factorY + this.radius
123
];
124
},
125
126
rotate: function(point, length, angle) {
127
var dx = length * Math.cos(angle);
128
var dy = length * Math.sin(angle);
129
return [point[0]+dx, point[1]+dy];
130
},
131
132
draw: function() {
133
this.factorX = (width - 2 * this.radius) / (this.graph.layoutMaxX - this.graph.layoutMinX);
134
this.factorY = (height - 2 * this.radius) / (this.graph.layoutMaxY - this.graph.layoutMinY);
135
for (var i = 0; i < this.graph.nodes.length; i++) {
136
this.drawNode(this.graph.nodes[i]);
137
}
138
for (var i = 0; i < this.graph.edges.length; i++) {
139
this.drawEdge(this.graph.edges[i]);
140
}
141
},
142
drawNode: function(node) {
143
var point = this.translate([node.layoutPosX, node.layoutPosY]);
144
node.point = point;
145
146
/* if node has already been drawn, move the nodes */
147
if(node.shape) {
148
// console.log(node.shape.attrs );
149
var opoint = [ node.shape.attrs.cx || node.shape.attrs.x + node.shape.attrs.width / 2 , node.shape.attrs.cy || node.shape.attrs.y + node.shape.attrs.height / 2 + 15 ];
150
node.shape.translate(point[0]-opoint[0], point[1]-opoint[1]);
151
node.shape.label.translate(point[0]-opoint[0], point[1]-opoint[1]);
152
this.r.safari();
153
return;
154
}
155
var shape;
156
if(node.content.getShape) {
157
shape = node.content.getShape(this.r, point[0], point[1]);
158
shape.attr({"fill-opacity": 0});
159
} else {
160
shape = this.r.ellipse(point[0], point[1], 30, 20);
161
var color = Raphael.getColor();
162
shape.attr({fill: color, stroke: color, "fill-opacity": 0, "stroke-width": 2})
163
}
164
shape.mousedown(this.dragger);
165
shape.node.style.cursor = "move";
166
shape.label = this.r.text(point[0], point[1] + 30, node.content.label || node.id); // Beware: operator || also considers values like -1, 0, ...
167
node.shape = shape;
168
},
169
drawEdge: function(edge) {
170
/* if edge already has been drawn, only refresh the edge */
171
edge.connection && edge.connection.draw();
172
if(!edge.connection)
173
edge.connection = this.r.connection(edge.source.shape, edge.target.shape, { fg: edge.color, bg: edge.colorbg, directed: edge.directed });
174
}
175
};
176
Graph.Layout = {};
177
Graph.Layout.Spring = function(graph) {
178
this.graph = graph;
179
this.iterations = 500;
180
this.maxRepulsiveForceDistance = 6;
181
this.k = 2;
182
this.c = 0.01;
183
this.maxVertexMovement = 0.5;
184
};
185
Graph.Layout.Spring.prototype = {
186
layout: function() {
187
this.layoutPrepare();
188
for (var i = 0; i < this.iterations; i++) {
189
this.layoutIteration();
190
}
191
this.layoutCalcBounds();
192
},
193
194
layoutPrepare: function() {
195
for (var i = 0; i < this.graph.nodes.length; i++) {
196
var node = this.graph.nodes[i];
197
node.layoutPosX = 0;
198
node.layoutPosY = 0;
199
node.layoutForceX = 0;
200
node.layoutForceY = 0;
201
}
202
203
},
204
205
layoutCalcBounds: function() {
206
var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity;
207
208
for (var i = 0; i < this.graph.nodes.length; i++) {
209
var x = this.graph.nodes[i].layoutPosX;
210
var y = this.graph.nodes[i].layoutPosY;
211
212
if(x > maxx) maxx = x;
213
if(x < minx) minx = x;
214
if(y > maxy) maxy = y;
215
if(y < miny) miny = y;
216
}
217
218
this.graph.layoutMinX = minx;
219
this.graph.layoutMaxX = maxx;
220
this.graph.layoutMinY = miny;
221
this.graph.layoutMaxY = maxy;
222
},
223
224
layoutIteration: function() {
225
// Forces on nodes due to node-node repulsions
226
for (var i = 0; i < this.graph.nodes.length; i++) {
227
var node1 = this.graph.nodes[i];
228
for (var j = i + 1; j < this.graph.nodes.length; j++) {
229
var node2 = this.graph.nodes[j];
230
this.layoutRepulsive(node1, node2);
231
}
232
}
233
// Forces on nodes due to edge attractions
234
for (var i = 0; i < this.graph.edges.length; i++) {
235
var edge = this.graph.edges[i];
236
this.layoutAttractive(edge);
237
}
238
239
// Move by the given force
240
for (var i = 0; i < this.graph.nodes.length; i++) {
241
var node = this.graph.nodes[i];
242
var xmove = this.c * node.layoutForceX;
243
var ymove = this.c * node.layoutForceY;
244
245
var max = this.maxVertexMovement;
246
if(xmove > max) xmove = max;
247
if(xmove < -max) xmove = -max;
248
if(ymove > max) ymove = max;
249
if(ymove < -max) ymove = -max;
250
251
node.layoutPosX += xmove;
252
node.layoutPosY += ymove;
253
node.layoutForceX = 0;
254
node.layoutForceY = 0;
255
}
256
},
257
258
layoutRepulsive: function(node1, node2) {
259
var dx = node2.layoutPosX - node1.layoutPosX;
260
var dy = node2.layoutPosY - node1.layoutPosY;
261
var d2 = dx * dx + dy * dy;
262
if(d2 < 0.01) {
263
dx = 0.1 * Math.random() + 0.1;
264
dy = 0.1 * Math.random() + 0.1;
265
var d2 = dx * dx + dy * dy;
266
}
267
var d = Math.sqrt(d2);
268
if(d < this.maxRepulsiveForceDistance) {
269
var repulsiveForce = this.k * this.k / d;
270
node2.layoutForceX += repulsiveForce * dx / d;
271
node2.layoutForceY += repulsiveForce * dy / d;
272
node1.layoutForceX -= repulsiveForce * dx / d;
273
node1.layoutForceY -= repulsiveForce * dy / d;
274
}
275
},
276
277
layoutAttractive: function(edge) {
278
var node1 = edge.source;
279
var node2 = edge.target;
280
281
var dx = node2.layoutPosX - node1.layoutPosX;
282
var dy = node2.layoutPosY - node1.layoutPosY;
283
var d2 = dx * dx + dy * dy;
284
if(d2 < 0.01) {
285
dx = 0.1 * Math.random() + 0.1;
286
dy = 0.1 * Math.random() + 0.1;
287
var d2 = dx * dx + dy * dy;
288
}
289
var d = Math.sqrt(d2);
290
if(d > this.maxRepulsiveForceDistance) {
291
d = this.maxRepulsiveForceDistance;
292
d2 = d * d;
293
}
294
var attractiveForce = (d2 - this.k * this.k) / this.k;
295
if(edge.weight == undefined || edge.weight < 1) edge.weight = 1;
296
attractiveForce *= Math.log(edge.weight) * 0.5 + 1;
297
298
node2.layoutForceX -= attractiveForce * dx / d;
299
node2.layoutForceY -= attractiveForce * dy / d;
300
node1.layoutForceX += attractiveForce * dx / d;
301
node1.layoutForceY += attractiveForce * dy / d;
302
}
303
};
304
305