Merge pull request #28 from techtonik/master

Simplified API for adding nodes and edges.
This commit is contained in:
Dennis Hotson 2013-02-05 15:12:46 -08:00
commit 77dfdb9e1d
4 changed files with 112 additions and 26 deletions

View File

@ -24,7 +24,7 @@ Demo
[demo](http://dhotson.github.com/springy/demo.html) [demo](http://dhotson.github.com/springy/demo.html)
Basic Usage Getting Started
---- ----
springy.js by itself is quite plain and doesn't include any code to do rendering springy.js by itself is quite plain and doesn't include any code to do rendering
@ -36,10 +36,24 @@ However, I've written a little helper jQuery plugin called springyui.js
to help get you started. It's got a semi-decent default renderer and some to help get you started. It's got a semi-decent default renderer and some
half assed drag and drop. half assed drag and drop.
See demo.html and springyui.js for an example of usage. Basic Usage
----
See [demo.html](http://dhotson.github.com/springy/demo.html) for the way to
add nodes and edges to graph and springyui.js for the rendering example.
Advanced Usage Springy 1.1+ supports simplified API for adding nodes and edges, see
[demo2.html](http://techtonik.github.com/springy/demo2.html):
var graph = new Graph();
graph.addNodes('mark', 'higgs', 'other', 'etc');
graph.addEdges(
['mark', 'higgs'],
['mark', 'etc'],
['mark', 'other']
);
Advanced Drawing
---- ----
If you're keen to do your own custom drawing, you'll need to know a few If you're keen to do your own custom drawing, you'll need to know a few

32
demo2.html Normal file
View File

@ -0,0 +1,32 @@
<html>
<body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
<script src="springy.js"></script>
<script src="springyui.js"></script>
<script>
var graph = new Graph();
graph.addNodes('Dennis', 'Michael', 'Jessica', 'Timothy', 'Barbara', 'Franklin')
graph.addNodes('Monty', 'James');
graph.addEdges(
['Dennis', 'Michael', {color: '#00A0B0', label: 'Foo bar'}],
['Michael', 'Dennis', {color: '#6A4A3C'}],
['Michael', 'Jessica', {color: '#CC333F'}],
['Jessica', 'Barbara', {color: '#EB6841'}],
['Michael', 'Timothy', {color: '#EDC951'}],
['Franklin', 'Monty', {color: '#7DBE3C'}],
['Dennis', 'Monty', {color: '#000000'}],
['Monty', 'James'],
['Barbara', 'Timothy', {color: '#6A4A3C'}]
);
jQuery(function(){
var springy = jQuery('#springydemo').springy({
graph: graph
});
});
</script>
<canvas id="springydemo" width="640" height="480" />
</body>
</html>

View File

@ -1,5 +1,5 @@
/** /**
* Springy v1.0.1 * Springy v1.1.0
* *
* Copyright (c) 2010 Dennis Hotson * Copyright (c) 2010 Dennis Hotson
* *
@ -38,18 +38,28 @@ var Graph = function() {
var Node = function(id, data) { var Node = function(id, data) {
this.id = id; this.id = id;
this.data = typeof(data) !== 'undefined' ? data : {}; this.data = (data !== undefined) ? data : {};
// Data fields used by layout algorithm in this file:
// this.data.mass
// Data used by default renderer in springyui.js
// this.data.label
}; };
var Edge = function(id, source, target, data) { var Edge = function(id, source, target, data) {
this.id = id; this.id = id;
/** @type {Node} */
this.source = source; this.source = source;
this.target = target; this.target = target;
this.data = typeof(data) !== 'undefined' ? data : {}; this.data = (data !== undefined) ? data : {};
// Edge data field used by layout alorithm
// this.data.length
// this.data.type
}; };
Graph.prototype.addNode = function(node) { Graph.prototype.addNode = function(node) {
if (typeof(this.nodeSet[node.id]) === 'undefined') { if (!(node.id in this.nodeSet)) {
this.nodes.push(node); this.nodes.push(node);
} }
@ -59,6 +69,16 @@ Graph.prototype.addNode = function(node) {
return node; return node;
}; };
Graph.prototype.addNodes = function() {
// accepts variable number of arguments, where each argument
// is a string that becomes both node identifier and label
for (var i = 0; i < arguments.length; i++) {
var name = arguments[i];
var node = new Node(name, data = {label:name});
this.addNode(node);
}
};
Graph.prototype.addEdge = function(edge) { Graph.prototype.addEdge = function(edge) {
var exists = false; var exists = false;
this.edges.forEach(function(e) { this.edges.forEach(function(e) {
@ -69,10 +89,10 @@ Graph.prototype.addEdge = function(edge) {
this.edges.push(edge); this.edges.push(edge);
} }
if (typeof(this.adjacency[edge.source.id]) === 'undefined') { if (!(edge.source.id in this.adjacency)) {
this.adjacency[edge.source.id] = {}; this.adjacency[edge.source.id] = {};
} }
if (typeof(this.adjacency[edge.source.id][edge.target.id]) === 'undefined') { if (!(edge.target.id in this.adjacency[edge.source.id])) {
this.adjacency[edge.source.id][edge.target.id] = []; this.adjacency[edge.source.id][edge.target.id] = [];
} }
@ -89,6 +109,25 @@ Graph.prototype.addEdge = function(edge) {
return edge; return edge;
}; };
Graph.prototype.addEdges = function() {
// accepts variable number of arguments, where each argument
// is a triple [nodeid1, nodeid2, attributes]
for (var i = 0; i < arguments.length; i++) {
var e = arguments[i];
var node1 = this.nodeSet[e[0]];
if (node1 == undefined) {
throw new TypeError("invalid node name: " + e[0]);
}
var node2 = this.nodeSet[e[1]];
if (node2 == undefined) {
throw new TypeError("invalid node name: " + e[1]);
}
var attr = e[2];
this.newEdge(node1, node2, attr);
}
};
Graph.prototype.newNode = function(data) { Graph.prototype.newNode = function(data) {
var node = new Node(this.nextNodeId++, data); var node = new Node(this.nextNodeId++, data);
this.addNode(node); this.addNode(node);
@ -103,8 +142,8 @@ Graph.prototype.newEdge = function(source, target, data) {
// find the edges from node1 to node2 // find the edges from node1 to node2
Graph.prototype.getEdges = function(node1, node2) { Graph.prototype.getEdges = function(node1, node2) {
if (typeof(this.adjacency[node1.id]) !== 'undefined' if (node1.id in this.adjacency
&& typeof(this.adjacency[node1.id][node2.id]) !== 'undefined') { && node2.id in this.adjacency[node1.id]) {
return this.adjacency[node1.id][node2.id]; return this.adjacency[node1.id][node2.id];
} }
@ -113,7 +152,7 @@ Graph.prototype.getEdges = function(node1, node2) {
// remove a node and it's associated edges from the graph // remove a node and it's associated edges from the graph
Graph.prototype.removeNode = function(node) { Graph.prototype.removeNode = function(node) {
if (typeof(this.nodeSet[node.id]) !== 'undefined') { if (node.id in this.nodeSet) {
delete this.nodeSet[node.id]; delete this.nodeSet[node.id];
} }
@ -236,8 +275,8 @@ Layout.ForceDirected = function(graph, stiffness, repulsion, damping) {
}; };
Layout.ForceDirected.prototype.point = function(node) { Layout.ForceDirected.prototype.point = function(node) {
if (typeof(this.nodePoints[node.id]) === 'undefined') { if (!(node.id in this.nodePoints)) {
var mass = typeof(node.data.mass) !== 'undefined' ? node.data.mass : 1.0; var mass = (node.data.mass !== undefined) ? node.data.mass : 1.0;
this.nodePoints[node.id] = new Layout.ForceDirected.Point(Vector.random(), mass); this.nodePoints[node.id] = new Layout.ForceDirected.Point(Vector.random(), mass);
} }
@ -245,14 +284,14 @@ Layout.ForceDirected.prototype.point = function(node) {
}; };
Layout.ForceDirected.prototype.spring = function(edge) { Layout.ForceDirected.prototype.spring = function(edge) {
if (typeof(this.edgeSprings[edge.id]) === 'undefined') { if (!(edge.id in this.edgeSprings)) {
var length = typeof(edge.data.length) !== 'undefined' ? edge.data.length : 1.0; var length = (edge.data.length !== undefined) ? edge.data.length : 1.0;
var existingSpring = false; var existingSpring = false;
var from = this.graph.getEdges(edge.source, edge.target); var from = this.graph.getEdges(edge.source, edge.target);
from.forEach(function(e) { from.forEach(function(e) {
if (existingSpring === false && typeof(this.edgeSprings[e.id]) !== 'undefined') { if (existingSpring === false && e.id in this.edgeSprings) {
existingSpring = this.edgeSprings[e.id]; existingSpring = this.edgeSprings[e.id];
} }
}, this); }, this);
@ -263,7 +302,7 @@ Layout.ForceDirected.prototype.spring = function(edge) {
var to = this.graph.getEdges(edge.target, edge.source); var to = this.graph.getEdges(edge.target, edge.source);
from.forEach(function(e){ from.forEach(function(e){
if (existingSpring === false && typeof(this.edgeSprings[e.id]) !== 'undefined') { if (existingSpring === false && e.id in this.edgeSprings) {
existingSpring = this.edgeSprings[e.id]; existingSpring = this.edgeSprings[e.id];
} }
}, this); }, this);
@ -397,13 +436,14 @@ Layout.ForceDirected.prototype.start = function(render, done) {
t.updateVelocity(0.03); t.updateVelocity(0.03);
t.updatePosition(0.03); t.updatePosition(0.03);
if (typeof(render) !== 'undefined') if (render !== undefined) {
render(); render();
}
// stop simulation when energy of the system goes below a threshold // stop simulation when energy of the system goes below a threshold
if (t.totalEnergy() < 0.01) { if (t.totalEnergy() < 0.01) {
t._started = false; t._started = false;
if (typeof(done) !== 'undefined') { done(); } if (done !== undefined) { done(); }
} else { } else {
Layout.requestAnimationFrame(step); Layout.requestAnimationFrame(step);
} }

View File

@ -112,7 +112,7 @@ jQuery.fn.springy = function(params) {
}); });
Node.prototype.getWidth = function() { Node.prototype.getWidth = function() {
var text = typeof(this.data.label) !== 'undefined' ? this.data.label : this.id; var text = (this.data.label !== undefined) ? this.data.label : this.id;
if (this._width && this._width[text]) if (this._width && this._width[text])
return this._width[text]; return this._width[text];
@ -174,18 +174,18 @@ jQuery.fn.springy = function(params) {
intersection = s2; intersection = s2;
} }
var stroke = typeof(edge.data.color) !== 'undefined' ? edge.data.color : '#000000'; var stroke = (edge.data.color !== undefined) ? edge.data.color : '#000000';
var arrowWidth; var arrowWidth;
var arrowLength; var arrowLength;
var weight = typeof(edge.data.weight) !== 'undefined' ? edge.data.weight : 1.0; var weight = (edge.data.weight !== undefined) ? edge.data.weight : 1.0;
ctx.lineWidth = Math.max(weight * 2, 0.1); ctx.lineWidth = Math.max(weight * 2, 0.1);
arrowWidth = 1 + ctx.lineWidth; arrowWidth = 1 + ctx.lineWidth;
arrowLength = 8; arrowLength = 8;
var directional = typeof(edge.data.directional) !== 'undefined' ? edge.data.directional : true; var directional = (edge.data.directional !== undefined) ? edge.data.directional : true;
// line // line
var lineEnd; var lineEnd;
@ -218,7 +218,7 @@ jQuery.fn.springy = function(params) {
} }
// label // label
if (typeof(edge.data.label) !== 'undefined') { if (edge.data.label !== undefined) {
text = edge.data.label text = edge.data.label
ctx.save(); ctx.save();
ctx.textAlign = "center"; ctx.textAlign = "center";
@ -256,7 +256,7 @@ jQuery.fn.springy = function(params) {
ctx.font = "16px Verdana, sans-serif"; ctx.font = "16px Verdana, sans-serif";
ctx.fillStyle = "#000000"; ctx.fillStyle = "#000000";
ctx.font = "16px Verdana, sans-serif"; ctx.font = "16px Verdana, sans-serif";
var text = typeof(node.data.label) !== 'undefined' ? node.data.label : node.id; var text = (node.data.label !== undefined) ? node.data.label : node.id;
ctx.fillText(text, s.x - boxWidth/2 + 5, s.y - 8); ctx.fillText(text, s.x - boxWidth/2 + 5, s.y - 8);
ctx.restore(); ctx.restore();