Revision: 2171
Initial Code
Initial URL
Initial Description
Initial Title
Initial Tags
Initial Language
at January 8, 2007 16:29 by jmah
Initial Code
/* Graph JavaScript framework, version 0.0.1 * (c) 2006 Aslak Hellesoy <[email protected]> * (c) 2006 Dave Hoover <[email protected]> * * Ported from Graph::Layouter::Spring in * http://search.cpan.org/~pasky/Graph-Layderer-0.02/ * The algorithm is based on a spring-style layouter of a Java-based social * network tracker PieSpy written by Paul Mutton E<lt>[email protected]<gt>. * * Graph is freely distributable under the terms of an MIT-style license. * For details, see the Graph web site: http://dev.buildpatternd.com/trac * /*--------------------------------------------------------------------------*/ var Graph = Class.create(); Graph.prototype = { initialize: function() { this.nodeSet = {}; this.nodes = []; this.edges = []; }, addNode: function(value) { if(typeof value == 'string') { // Create a new div var key = value; var element = document.createElement('div'); element.innerHTML = value; // THIS DOESN'T WORK!! NEED AN OTHER CONTAINER. document.appendChild(element); } else { // Assuming it's a DOM node *with* and id. var key = value.id; var element = value; } var node = this.nodeSet[key]; if(node == undefined) { node = new Graph.Node(element); this.nodeSet[key] = node; this.nodes.push(node); } return node; }, // Uniqueness must be ensured by caller addEdge: function(source, target) { var s = this.addNode(source); var t = this.addNode(target); var edge = {source: s, target: t}; this.edges.push(edge); } }; Graph.Node = Class.create(); Graph.Node.prototype = { initialize: function(value) { this.value = value; } }; Graph.Renderer = {}; Graph.Renderer.Basic = Class.create(); Graph.Renderer.Basic.prototype = { initialize: function(element, graph) { this.element = element; this.graph = graph; this.ctx = element.getContext("2d"); this.radius = 20; this.arrowAngle = Math.PI/10; this.factorX = (element.width - 2 * this.radius) / (graph.layoutMaxX - graph.layoutMinX); this.factorY = (element.height - 2 * this.radius) / (graph.layoutMaxY - graph.layoutMinY); }, translate: function(point) { return [ (point[0] - this.graph.layoutMinX) * this.factorX + this.radius, (point[1] - this.graph.layoutMinY) * this.factorY + this.radius ]; }, rotate: function(point, length, angle) { var dx = length * Math.cos(angle); var dy = length * Math.sin(angle); return [point[0]+dx, point[1]+dy]; }, draw: function() { for (var i = 0; i < this.graph.nodes.length; i++) { this.drawNode(this.graph.nodes[i]); } for (var i = 0; i < this.graph.edges.length; i++) { this.drawEdge(this.graph.edges[i]); } }, drawNode: function(node) { var point = this.translate([node.layoutPosX, node.layoutPosY]); node.value.style.position = 'absolute'; node.value.style.top = point[1] + 'px'; node.value.style.left = point[0] + 'px'; this.ctx.strokeStyle = 'black' this.ctx.beginPath(); this.ctx.arc(point[0], point[1], this.radius, 0, Math.PI*2, true); this.ctx.closePath(); this.ctx.stroke(); }, drawEdge: function(edge) { var source = this.translate([edge.source.layoutPosX, edge.source.layoutPosY]); var target = this.translate([edge.target.layoutPosX, edge.target.layoutPosY]); var tan = (target[1] - source[1]) / (target[0] - source[0]); var theta = Math.atan(tan); if(source[0] <= target[0]) {theta = Math.PI+theta} source = this.rotate(source, -this.radius, theta); target = this.rotate(target, this.radius, theta); // draw the edge this.ctx.strokeStyle = 'grey'; this.ctx.fillStyle = 'grey'; this.ctx.lineWidth = 1.0; this.ctx.beginPath(); this.ctx.moveTo(source[0], source[1]); this.ctx.lineTo(target[0], target[1]); this.ctx.stroke(); this.drawArrowHead(20, this.arrowAngle, theta, source[0], source[1], target[0], target[1]); }, drawArrowHead: function(length, alpha, theta, startx, starty, endx, endy) { var top = this.rotate([endx, endy], length, theta + alpha); var bottom = this.rotate([endx, endy], length, theta - alpha); this.ctx.beginPath(); this.ctx.moveTo(endx, endy); this.ctx.lineTo(top[0], top[1]); this.ctx.lineTo(bottom[0], bottom[1]); this.ctx.fill(); } }; Graph.Layout = {}; Graph.Layout.Spring = Class.create(); Graph.Layout.Spring.prototype = { initialize: function(graph) { this.graph = graph; this.iterations = 500; this.maxRepulsiveForceDistance = 6; this.k = 2; this.c = 0.01; this.maxVertexMovement = 0.5; }, layout: function() { this.layoutPrepare(); for (var i = 0; i < this.iterations; i++) { this.layoutIteration(); } this.layoutCalcBounds(); }, layoutPrepare: function() { for (var i = 0; i < this.graph.nodes.length; i++) { var node = this.graph.nodes[i]; node.layoutPosX = 0; node.layoutPosY = 0; node.layoutForceX = 0; node.layoutForceY = 0; } }, layoutCalcBounds: function() { var minx = Infinity, maxx = -Infinity, miny = Infinity, maxy = -Infinity; for (var i = 0; i < this.graph.nodes.length; i++) { var x = this.graph.nodes[i].layoutPosX; var y = this.graph.nodes[i].layoutPosY; if(x > maxx) maxx = x; if(x < minx) minx = x; if(y > maxy) maxy = y; if(y < miny) miny = y; } this.graph.layoutMinX = minx; this.graph.layoutMaxX = maxx; this.graph.layoutMinY = miny; this.graph.layoutMaxY = maxy; }, layoutIteration: function() { // Forces on nodes due to node-node repulsions for (var i = 0; i < this.graph.nodes.length; i++) { var node1 = this.graph.nodes[i]; for (var j = i + 1; j < this.graph.nodes.length; j++) { var node2 = this.graph.nodes[j]; this.layoutRepulsive(node1, node2); } } // Forces on nodes due to edge attractions for (var i = 0; i < this.graph.edges.length; i++) { var edge = this.graph.edges[i]; this.layoutAttractive(edge); } // Move by the given force for (var i = 0; i < this.graph.nodes.length; i++) { var node = this.graph.nodes[i]; var xmove = this.c * node.layoutForceX; var ymove = this.c * node.layoutForceY; var max = this.maxVertexMovement; if(xmove > max) xmove = max; if(xmove < -max) xmove = -max; if(ymove > max) ymove = max; if(ymove < -max) ymove = -max; node.layoutPosX += xmove; node.layoutPosY += ymove; node.layoutForceX = 0; node.layoutForceY = 0; } }, layoutRepulsive: function(node1, node2) { var dx = node2.layoutPosX - node1.layoutPosX; var dy = node2.layoutPosY - node1.layoutPosY; var d2 = dx * dx + dy * dy; if(d2 < 0.01) { dx = 0.1 * Math.random() + 0.1; dy = 0.1 * Math.random() + 0.1; var d2 = dx * dx + dy * dy; } var d = Math.sqrt(d2); if(d < this.maxRepulsiveForceDistance) { var repulsiveForce = this.k * this.k / d; node2.layoutForceX += repulsiveForce * dx / d; node2.layoutForceY += repulsiveForce * dy / d; node1.layoutForceX -= repulsiveForce * dx / d; node1.layoutForceY -= repulsiveForce * dy / d; } }, layoutAttractive: function(edge) { var node1 = edge.source; var node2 = edge.target; var dx = node2.layoutPosX - node1.layoutPosX; var dy = node2.layoutPosY - node1.layoutPosY; var d2 = dx * dx + dy * dy; if(d2 < 0.01) { dx = 0.1 * Math.random() + 0.1; dy = 0.1 * Math.random() + 0.1; var d2 = dx * dx + dy * dy; } var d = Math.sqrt(d2); if(d > this.maxRepulsiveForceDistance) { d = this.maxRepulsiveForceDistance; d2 = d * d; } var attractiveForce = (d2 - this.k * this.k) / this.k; if(edge.weight == undefined || edge.weight < 1) edge.weight = 1; attractiveForce *= Math.log(edge.weight) * 0.5 + 1; node2.layoutForceX -= attractiveForce * dx / d; node2.layoutForceY -= attractiveForce * dy / d; node1.layoutForceX += attractiveForce * dx / d; node1.layoutForceY += attractiveForce * dy / d; } };
Initial URL
http://www.ajaxian.com/archives/new-javascriptcanvas-graph-library
Initial Description
Mirror of library
Initial Title
Graph JavaScript framework, version 0.0.1
Initial Tags
layout
Initial Language
JavaScript