Compare commits

..

1 Commits

Author SHA1 Message Date
Dennis Hotson
7ee84bf6e0 Fixed a few issues reported by jshint 2013-03-12 10:43:45 +00:00
10 changed files with 690 additions and 978 deletions

View File

@ -21,9 +21,7 @@ Try to imagine it as a bunch of springs connected to each other.
Demo Demo
---- ----
[basic](http://dhotson.github.com/springy/demo.html) [demo](http://dhotson.github.com/springy/demo.html)
| [simplified API](http://dhotson.github.com/springy/demo-simple.html)
| [JSON API](http://dhotson.github.com/springy/demo-json.html)
Getting Started Getting Started
@ -45,9 +43,9 @@ 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. add nodes and edges to graph and springyui.js for the rendering example.
Springy 1.1+ supports simplified API for adding nodes and edges, see Springy 1.1+ supports simplified API for adding nodes and edges, see
[demo-simple.html](http://dhotson.github.com/springy/demo-simple.html): [demo2.html](http://techtonik.github.com/springy/demo2.html):
var graph = new Springy.Graph(); var graph = new Graph();
graph.addNodes('mark', 'higgs', 'other', 'etc'); graph.addNodes('mark', 'higgs', 'other', 'etc');
graph.addEdges( graph.addEdges(
['mark', 'higgs'], ['mark', 'higgs'],
@ -55,22 +53,6 @@ Springy 1.1+ supports simplified API for adding nodes and edges, see
['mark', 'other'] ['mark', 'other']
); );
Springy 1.2+ also accepts JSON, see
[demo-json.html](http://dhotson.github.com/springy/demo-json.html):
graphJSON = {
"nodes": ["mark", "higgs", "other", "etc"],
"edges": [
["mark", "higgs"],
["mark", "etc"],
["mark", "other"]
]
};
var graph = new Springy.Graph();
graph.loadJSON(graphJSON);
Advanced Drawing Advanced Drawing
---- ----
@ -80,7 +62,7 @@ things before you get started.
This is the basic graph API, you can create nodes and edges etc. This is the basic graph API, you can create nodes and edges etc.
// make a new graph // make a new graph
var graph = new Springy.Graph(); var graph = new Graph();
// make some nodes // make some nodes
var node1 = graph.newNode({label: '1'}); var node1 = graph.newNode({label: '1'});
@ -91,12 +73,12 @@ This is the basic graph API, you can create nodes and edges etc.
So now to draw this graph, lets make a layout object: So now to draw this graph, lets make a layout object:
var layout = new Springy.Layout.ForceDirected(graph, 400.0, 400.0, 0.5); var layout = new Layout.ForceDirected(graph, 400.0, 400.0, 0.5);
I've written a Renderer class, which will handle the rendering loop. I've written a Renderer class, which will handle the rendering loop.
You just need to provide some callbacks to do the actual drawing. You just need to provide some callbacks to do the actual drawing.
var renderer = new Springy.Renderer(layout, var renderer = new Renderer(layout,
function clear() { function clear() {
// code to clear screen // code to clear screen
}, },

View File

@ -1,17 +0,0 @@
{
"name": "Springy",
"main": "springy.js",
"version": "2.7.1",
"homepage": "https://github.com/dhotson/springy",
"authors": [
"Dennis Hotson <dennis@99designs.com>"
],
"description": "A force directed graph layout algorithm",
"keywords": [
"graph",
"layout",
"visualization",
"physics"
],
"license": "MIT"
}

View File

@ -1,38 +0,0 @@
<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 graphJSON = {
"nodes": [
"Amphitryon",
"Alcmene",
"Iphicles",
"Heracles"
],
"edges": [
["Amphitryon", "Alcmene"],
["Alcmene", "Amphitryon"],
["Amphitryon", "Iphicles"],
["Amphitryon", "Heracles"]
]
};
jQuery(function(){
var graph = new Springy.Graph();
graph.loadJSON(graphJSON);
var springy = jQuery('#springydemo').springy({
graph: graph
});
});
</script>
<canvas id="springydemo" width="640" height="480" />
</body>
</html>

View File

@ -112,7 +112,7 @@ Raphael.fn.connection = function (obj1, obj2, style) {
</script> </script>
<script> <script>
var graph = new Springy.Graph(); var graph = new Graph();
var dennis = graph.newNode({label: 'Dennis'}); var dennis = graph.newNode({label: 'Dennis'});
var michael = graph.newNode({label: 'Michael'}); var michael = graph.newNode({label: 'Michael'});
@ -166,16 +166,16 @@ function moveSet(set, x, y) {
} }
function doit() { function doit() {
var layout = new Springy.Layout.ForceDirected(graph, 640, 480.0, 0.5); var layout = new Layout.ForceDirected(graph, 640, 480.0, 0.5);
var r = Raphael("holder", 640, 480); var r = Raphael("holder", 640, 480);
// calculate bounding box of graph layout.. with ease-in // calculate bounding box of graph layout.. with ease-in
var currentBB = layout.getBoundingBox(); var currentBB = layout.getBoundingBox();
var targetBB = {bottomleft: new Springy.Vector(-2, -2), topright: new Springy.Vector(2, 2)}; var targetBB = {bottomleft: new Vector(-2, -2), topright: new Vector(2, 2)};
// auto adjusting bounding box // auto adjusting bounding box
Springy.requestAnimationFrame(function adjust() { Layout.requestAnimationFrame(function adjust() {
targetBB = layout.getBoundingBox(); targetBB = layout.getBoundingBox();
// current gets 20% closer to target every iteration // current gets 20% closer to target every iteration
currentBB = { currentBB = {
@ -185,7 +185,7 @@ function doit() {
.divide(10)) .divide(10))
}; };
Springy.requestAnimationFrame(adjust); Layout.requestAnimationFrame(adjust);
}); });
// convert to/from screen coordinates // convert to/from screen coordinates
@ -193,11 +193,11 @@ function doit() {
var size = currentBB.topright.subtract(currentBB.bottomleft); var size = currentBB.topright.subtract(currentBB.bottomleft);
var sx = p.subtract(currentBB.bottomleft).divide(size.x).x * r.width; var sx = p.subtract(currentBB.bottomleft).divide(size.x).x * r.width;
var sy = p.subtract(currentBB.bottomleft).divide(size.y).y * r.height; var sy = p.subtract(currentBB.bottomleft).divide(size.y).y * r.height;
return new Springy.Vector(sx, sy); return new Vector(sx, sy);
}; };
var renderer = new Springy.Renderer(layout, var renderer = new Renderer(layout,
function clear() { function clear() {
// code to clear screen // code to clear screen
}, },

View File

@ -1,33 +0,0 @@
<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 Springy.Graph();
graph.addNodes('Dennis', 'Michael', 'Jessica', 'Timothy', 'Barbara')
graph.addNodes('Amphitryon', 'Alcmene', 'Iphicles', 'Heracles');
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'}],
['Amphitryon', 'Alcmene', {color: '#7DBE3C'}],
['Alcmene', 'Amphitryon', {color: '#BE7D3C'}],
['Amphitryon', 'Iphicles'],
['Amphitryon', 'Heracles'],
['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

@ -4,12 +4,9 @@
<script src="springy.js"></script> <script src="springy.js"></script>
<script src="springyui.js"></script> <script src="springyui.js"></script>
<script> <script>
var graph = new Springy.Graph(); var graph = new Graph();
var dennis = graph.newNode({ var dennis = graph.newNode({label: 'Dennis'});
label: 'Dennis',
ondoubleclick: function() { console.log("Hello!"); }
});
var michael = graph.newNode({label: 'Michael'}); var michael = graph.newNode({label: 'Michael'});
var jessica = graph.newNode({label: 'Jessica'}); var jessica = graph.newNode({label: 'Jessica'});
var timothy = graph.newNode({label: 'Timothy'}); var timothy = graph.newNode({label: 'Timothy'});
@ -32,12 +29,12 @@ graph.newEdge(dennis, bianca, {color: '#CC333F'});
graph.newEdge(bianca, monty, {color: '#EB6841'}); graph.newEdge(bianca, monty, {color: '#EB6841'});
jQuery(function(){ jQuery(function(){
var springy = window.springy = jQuery('#springydemo').springy({ var springy = jQuery('#springydemo').springy({
graph: graph, graph: graph,
nodeSelected: function(node){ nodeSelected: function(node){
console.log('Node selected: ' + JSON.stringify(node.data)); console.log('Node selected: ' + JSON.stringify(node.data));
} }
}); });
}); });
</script> </script>

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,21 +0,0 @@
{
"name": "springy",
"version": "2.7.1",
"description": "A force directed graph layout algorithm in JavaScript.",
"main": "springy.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"repository": {
"type": "git",
"url": "git://github.com/dhotson/springy.git"
},
"keywords": [
"graph",
"layout",
"visualization"
],
"author": "Dennis Hotson <dennis.hotson@gmail.com>",
"license": "MIT",
"readmeFilename": "README.mkdn"
}

1249
springy.js

File diff suppressed because it is too large Load Diff

View File

@ -26,53 +26,49 @@ Copyright (c) 2010 Dennis Hotson
(function() { (function() {
jQuery.fn.springy = function(params) { jQuery.fn.springy = function(params) {
var graph = this.graph = params.graph || new Springy.Graph(); var graph = this.graph = params.graph || new Graph();
var nodeFont = "16px Verdana, sans-serif";
var edgeFont = "8px Verdana, sans-serif";
var stiffness = params.stiffness || 400.0; var stiffness = params.stiffness || 400.0;
var repulsion = params.repulsion || 400.0; var repulsion = params.repulsion || 400.0;
var damping = params.damping || 0.5; var damping = params.damping || 0.5;
var minEnergyThreshold = params.minEnergyThreshold || 0.00001;
var nodeSelected = params.nodeSelected || null; var nodeSelected = params.nodeSelected || null;
var nodeImages = {};
var edgeLabelsUpright = true;
var canvas = this[0]; var canvas = this[0];
var ctx = canvas.getContext("2d"); var ctx = canvas.getContext('2d');
var layout = this.layout = new Springy.Layout.ForceDirected(graph, stiffness, repulsion, damping, minEnergyThreshold); var layout = this.layout = new Layout.ForceDirected(graph, stiffness, repulsion, damping);
// calculate bounding box of graph layout.. with ease-in // calculate bounding box of graph layout.. with ease-in
var currentBB = layout.getBoundingBox(); var currentBB = layout.getBoundingBox();
var targetBB = {bottomleft: new Springy.Vector(-2, -2), topright: new Springy.Vector(2, 2)}; var targetBB = {bottomleft: new Vector(-2, -2), topright: new Vector(2, 2)};
// auto adjusting bounding box // auto adjusting bounding box
Springy.requestAnimationFrame(function adjust() { Layout.requestAnimationFrame(function adjust() {
targetBB = layout.getBoundingBox(); targetBB = layout.getBoundingBox();
// current gets 20% closer to target every iteration // current gets 20% closer to target every iteration
currentBB = { currentBB = {
bottomleft: currentBB.bottomleft.add( targetBB.bottomleft.subtract(currentBB.bottomleft) bottomleft: currentBB.bottomleft.add(targetBB.bottomleft.subtract(currentBB.bottomleft)
.divide(10)), .divide(10)),
topright: currentBB.topright.add( targetBB.topright.subtract(currentBB.topright) topright: currentBB.topright.add(targetBB.topright.subtract(currentBB.topright)
.divide(10)) .divide(10))
}; };
Springy.requestAnimationFrame(adjust); Layout.requestAnimationFrame(adjust);
}); });
// convert to/from screen coordinates // convert to/from screen coordinates
var toScreen = function(p) { toScreen = function(p) {
var size = currentBB.topright.subtract(currentBB.bottomleft); var size = currentBB.topright.subtract(currentBB.bottomleft);
var sx = p.subtract(currentBB.bottomleft).divide(size.x).x * canvas.width; var sx = p.subtract(currentBB.bottomleft).divide(size.x).x * canvas.width;
var sy = p.subtract(currentBB.bottomleft).divide(size.y).y * canvas.height; var sy = p.subtract(currentBB.bottomleft).divide(size.y).y * canvas.height;
return new Springy.Vector(sx, sy); return new Vector(sx, sy);
}; };
var fromScreen = function(s) { fromScreen = function(s) {
var size = currentBB.topright.subtract(currentBB.bottomleft); var size = currentBB.topright.subtract(currentBB.bottomleft);
var px = (s.x / canvas.width) * size.x + currentBB.bottomleft.x; var px = (s.x / canvas.width) * size.x + currentBB.bottomleft.x;
var py = (s.y / canvas.height) * size.y + currentBB.bottomleft.y; var py = (s.y / canvas.height) * size.y + currentBB.bottomleft.y;
return new Springy.Vector(px, py); return new Vector(px, py);
}; };
// half-assed drag and drop // half-assed drag and drop
@ -81,6 +77,8 @@ jQuery.fn.springy = function(params) {
var dragged = null; var dragged = null;
jQuery(canvas).mousedown(function(e) { jQuery(canvas).mousedown(function(e) {
jQuery('.actions').hide();
var pos = jQuery(this).offset(); var pos = jQuery(this).offset();
var p = fromScreen({x: e.pageX - pos.left, y: e.pageY - pos.top}); var p = fromScreen({x: e.pageX - pos.left, y: e.pageY - pos.top});
selected = nearest = dragged = layout.nearest(p); selected = nearest = dragged = layout.nearest(p);
@ -96,17 +94,6 @@ jQuery.fn.springy = function(params) {
renderer.start(); renderer.start();
}); });
// Basic double click handler
jQuery(canvas).dblclick(function(e) {
var pos = jQuery(this).offset();
var p = fromScreen({x: e.pageX - pos.left, y: e.pageY - pos.top});
selected = layout.nearest(p);
node = selected.node;
if (node && node.data && node.data.ondoubleclick) {
node.data.ondoubleclick();
}
});
jQuery(canvas).mousemove(function(e) { jQuery(canvas).mousemove(function(e) {
var pos = jQuery(this).offset(); var pos = jQuery(this).offset();
var p = fromScreen({x: e.pageX - pos.left, y: e.pageY - pos.top}); var p = fromScreen({x: e.pageX - pos.left, y: e.pageY - pos.top});
@ -120,69 +107,33 @@ jQuery.fn.springy = function(params) {
renderer.start(); renderer.start();
}); });
jQuery(window).bind('mouseup',function(e) { jQuery(window).bind('mouseup', function(e) {
dragged = null; dragged = null;
}); });
var getTextWidth = function(node) { Node.prototype.getWidth = function() {
var text = (node.data.label !== undefined) ? node.data.label : node.id; var text = (this.data.label !== undefined) ? this.data.label : this.id;
if (node._width && node._width[text]) if (this._width && this._width[text])
return node._width[text]; return this._width[text];
ctx.save(); ctx.save();
ctx.font = (node.data.font !== undefined) ? node.data.font : nodeFont; ctx.font = '16px Verdana, sans-serif';
var width = ctx.measureText(text).width; var width = ctx.measureText(text).width + 10;
ctx.restore(); ctx.restore();
node._width || (node._width = {}); this._width || (this._width = {});
node._width[text] = width; this._width[text] = width;
return width; return width;
}; };
var getTextHeight = function(node) { Node.prototype.getHeight = function() {
return 16; return 20;
// In a more modular world, this would actually read the font size, but I think leaving it a constant is sufficient for now.
// If you change the font size, I'd adjust this too.
}; };
var getImageWidth = function(node) { var renderer = new Renderer(layout,
var width = (node.data.image.width !== undefined) ? node.data.image.width : nodeImages[node.data.image.src].object.width;
return width;
}
var getImageHeight = function(node) {
var height = (node.data.image.height !== undefined) ? node.data.image.height : nodeImages[node.data.image.src].object.height;
return height;
}
Springy.Node.prototype.getHeight = function() {
var height;
if (this.data.image == undefined) {
height = getTextHeight(this);
} else {
if (this.data.image.src in nodeImages && nodeImages[this.data.image.src].loaded) {
height = getImageHeight(this);
} else {height = 10;}
}
return height;
}
Springy.Node.prototype.getWidth = function() {
var width;
if (this.data.image == undefined) {
width = getTextWidth(this);
} else {
if (this.data.image.src in nodeImages && nodeImages[this.data.image.src].loaded) {
width = getImageWidth(this);
} else {width = 10;}
}
return width;
}
var renderer = this.renderer = new Springy.Renderer(layout,
function clear() { function clear() {
ctx.clearRect(0,0,canvas.width,canvas.height); ctx.clearRect(0, 0, canvas.width, canvas.height);
}, },
function drawEdge(edge, p1, p2) { function drawEdge(edge, p1, p2) {
var x1 = toScreen(p1).x; var x1 = toScreen(p1).x;
@ -190,7 +141,7 @@ jQuery.fn.springy = function(params) {
var x2 = toScreen(p2).x; var x2 = toScreen(p2).x;
var y2 = toScreen(p2).y; var y2 = toScreen(p2).y;
var direction = new Springy.Vector(x2-x1, y2-y1); var direction = new Vector(x2 - x1, y2 - y1);
var normal = direction.normal().normalise(); var normal = direction.normal().normalise();
var from = graph.getEdges(edge.source, edge.target); var from = graph.getEdges(edge.source, edge.target);
@ -200,28 +151,24 @@ jQuery.fn.springy = function(params) {
// Figure out edge's position in relation to other edges between the same nodes // Figure out edge's position in relation to other edges between the same nodes
var n = 0; var n = 0;
for (var i=0; i<from.length; i++) { for (var i = 0; i < from.length; i++) {
if (from[i].id === edge.id) { if (from[i].id === edge.id) {
n = i; n = i;
} }
} }
//change default to 10.0 to allow text fit between edges var spacing = 6.0;
var spacing = 12.0;
// Figure out how far off center the line should be drawn // Figure out how far off center the line should be drawn
var offset = normal.multiply(-((total - 1) * spacing)/2.0 + (n * spacing)); var offset = normal.multiply(-((total - 1) * spacing) / 2.0 + (n * spacing));
var paddingX = 6;
var paddingY = 6;
var s1 = toScreen(p1).add(offset); var s1 = toScreen(p1).add(offset);
var s2 = toScreen(p2).add(offset); var s2 = toScreen(p2).add(offset);
var boxWidth = edge.target.getWidth() + paddingX; var boxWidth = edge.target.getWidth();
var boxHeight = edge.target.getHeight() + paddingY; var boxHeight = edge.target.getHeight();
var intersection = intersect_line_box(s1, s2, {x: x2-boxWidth/2.0, y: y2-boxHeight/2.0}, boxWidth, boxHeight); var intersection = intersect_line_box(s1, s2, {x: x2 - boxWidth / 2.0, y: y2 - boxHeight / 2.0}, boxWidth, boxHeight);
if (!intersection) { if (!intersection) {
intersection = s2; intersection = s2;
@ -234,7 +181,7 @@ jQuery.fn.springy = function(params) {
var weight = (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;
@ -272,22 +219,13 @@ jQuery.fn.springy = function(params) {
// label // label
if (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';
ctx.textBaseline = "top"; ctx.textBaseline = 'top';
ctx.font = (edge.data.font !== undefined) ? edge.data.font : edgeFont; ctx.font = '10px Helvetica, sans-serif';
ctx.fillStyle = stroke; ctx.fillStyle = '#5BA6EC';
var angle = Math.atan2(s2.y - s1.y, s2.x - s1.x); ctx.fillText(text, (x1 + x2) / 2, (y1 + y2) / 2);
var displacement = -8;
if (edgeLabelsUpright && (angle > Math.PI/2 || angle < -Math.PI/2)) {
displacement = 8;
angle += Math.PI;
}
var textPos = s1.add(s2).divide(2).add(normal.multiply(displacement));
ctx.translate(textPos.x, textPos.y);
ctx.rotate(angle);
ctx.fillText(text, 0,-2);
ctx.restore(); ctx.restore();
} }
@ -297,57 +235,30 @@ jQuery.fn.springy = function(params) {
ctx.save(); ctx.save();
// Pulled out the padding aspect sso that the size functions could be used in multiple places var boxWidth = node.getWidth();
// These should probably be settable by the user (and scoped higher) but this suffices for now var boxHeight = node.getHeight();
var paddingX = 6;
var paddingY = 6;
var contentWidth = node.getWidth();
var contentHeight = node.getHeight();
var boxWidth = contentWidth + paddingX;
var boxHeight = contentHeight + paddingY;
// clear background // clear background
ctx.clearRect(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight); ctx.clearRect(s.x - boxWidth / 2, s.y - 10, boxWidth, 20);
// fill background // fill background
if (selected !== null && selected.node !== null && selected.node.id === node.id) { if (selected !== null && nearest.node !== null && selected.node.id === node.id) {
ctx.fillStyle = "#FFFFE0"; ctx.fillStyle = '#FFFFE0';
} else if (nearest !== null && nearest.node !== null && nearest.node.id === node.id) { } else if (nearest !== null && nearest.node !== null && nearest.node.id === node.id) {
ctx.fillStyle = "#EEEEEE"; ctx.fillStyle = '#EEEEEE';
} else { } else {
ctx.fillStyle = "#FFFFFF"; ctx.fillStyle = '#FFFFFF';
} }
ctx.fillRect(s.x - boxWidth/2, s.y - boxHeight/2, boxWidth, boxHeight); ctx.fillRect(s.x - boxWidth / 2, s.y - 10, boxWidth, 20);
ctx.textAlign = 'left';
ctx.textBaseline = 'top';
ctx.font = '16px Verdana, sans-serif';
ctx.fillStyle = '#000000';
ctx.font = '16px Verdana, sans-serif';
var text = (node.data.label !== undefined) ? node.data.label : node.id;
ctx.fillText(text, s.x - boxWidth / 2 + 5, s.y - 8);
if (node.data.image == undefined) {
ctx.textAlign = "left";
ctx.textBaseline = "top";
ctx.font = (node.data.font !== undefined) ? node.data.font : nodeFont;
ctx.fillStyle = (node.data.color !== undefined) ? node.data.color : "#000000";
var text = (node.data.label !== undefined) ? node.data.label : node.id;
ctx.fillText(text, s.x - contentWidth/2, s.y - contentHeight/2);
} else {
// Currently we just ignore any labels if the image object is set. One might want to extend this logic to allow for both, or other composite nodes.
var src = node.data.image.src; // There should probably be a sanity check here too, but un-src-ed images aren't exaclty a disaster.
if (src in nodeImages) {
if (nodeImages[src].loaded) {
// Our image is loaded, so it's safe to draw
ctx.drawImage(nodeImages[src].object, s.x - contentWidth/2, s.y - contentHeight/2, contentWidth, contentHeight);
}
}else{
// First time seeing an image with this src address, so add it to our set of image objects
// Note: we index images by their src to avoid making too many duplicates
nodeImages[src] = {};
var img = new Image();
nodeImages[src].object = img;
img.addEventListener("load", function () {
// HTMLImageElement objects are very finicky about being used before they are loaded, so we set a flag when it is done
nodeImages[src].loaded = true;
});
img.src = src;
}
}
ctx.restore(); ctx.restore();
} }
); );
@ -356,21 +267,21 @@ jQuery.fn.springy = function(params) {
// helpers for figuring out where to draw arrows // helpers for figuring out where to draw arrows
function intersect_line_line(p1, p2, p3, p4) { function intersect_line_line(p1, p2, p3, p4) {
var denom = ((p4.y - p3.y)*(p2.x - p1.x) - (p4.x - p3.x)*(p2.y - p1.y)); var denom = ((p4.y - p3.y) * (p2.x - p1.x) - (p4.x - p3.x) * (p2.y - p1.y));
// lines are parallel // lines are parallel
if (denom === 0) { if (denom === 0) {
return false; return false;
} }
var ua = ((p4.x - p3.x)*(p1.y - p3.y) - (p4.y - p3.y)*(p1.x - p3.x)) / denom; var ua = ((p4.x - p3.x) * (p1.y - p3.y) - (p4.y - p3.y) * (p1.x - p3.x)) / denom;
var ub = ((p2.x - p1.x)*(p1.y - p3.y) - (p2.y - p1.y)*(p1.x - p3.x)) / denom; var ub = ((p2.x - p1.x) * (p1.y - p3.y) - (p2.y - p1.y) * (p1.x - p3.x)) / denom;
if (ua < 0 || ua > 1 || ub < 0 || ub > 1) { if (ua < 0 || ua > 1 || ub < 0 || ub > 1) {
return false; return false;
} }
return new Springy.Vector(p1.x + ua * (p2.x - p1.x), p1.y + ua * (p2.y - p1.y)); return new Vector(p1.x + ua * (p2.x - p1.x), p1.y + ua * (p2.y - p1.y));
} }
function intersect_line_box(p1, p2, p3, w, h) { function intersect_line_box(p1, p2, p3, w, h) {
@ -389,6 +300,6 @@ jQuery.fn.springy = function(params) {
} }
return this; return this;
} };
})(); })();