Update to latest springy code
This commit is contained in:
parent
a01a2cbc5e
commit
7e24c1c209
246
demo-raphael.html
Normal file
246
demo-raphael.html
Normal file
|
@ -0,0 +1,246 @@
|
|||
<html>
|
||||
<body>
|
||||
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.3.2/jquery.min.js"></script>
|
||||
<script src="http://yandex.st/raphael/2.0/raphael.min.js"></script>
|
||||
<script src="springy.js"></script>
|
||||
<script>
|
||||
/**
|
||||
* Originally grabbed from the official RaphaelJS Documentation
|
||||
* http://raphaeljs.com/graffle.html
|
||||
* Adopted (arrows) and commented by Philipp Strathausen http://blog.ameisenbar.de
|
||||
* Licenced under the MIT licence.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Usage:
|
||||
* connect two shapes
|
||||
* parameters:
|
||||
* source shape [or connection for redrawing],
|
||||
* target shape,
|
||||
* style with { fg : linecolor, bg : background color, directed: boolean }
|
||||
* returns:
|
||||
* connection { draw = function() }
|
||||
*/
|
||||
Raphael.fn.connection = function (obj1, obj2, style) {
|
||||
var selfRef = this;
|
||||
/* create and return new connection */
|
||||
var edge = {/*
|
||||
from : obj1,
|
||||
to : obj2,
|
||||
style : style,*/
|
||||
draw : function() {
|
||||
/* get bounding boxes of target and source */
|
||||
var bb1 = obj1.getBBox();
|
||||
var bb2 = obj2.getBBox();
|
||||
var off1 = 0;
|
||||
var off2 = 0;
|
||||
/* coordinates for potential connection coordinates from/to the objects */
|
||||
var p = [
|
||||
{x: bb1.x + bb1.width / 2, y: bb1.y - off1}, /* NORTH 1 */
|
||||
{x: bb1.x + bb1.width / 2, y: bb1.y + bb1.height + off1}, /* SOUTH 1 */
|
||||
{x: bb1.x - off1, y: bb1.y + bb1.height / 2}, /* WEST 1 */
|
||||
{x: bb1.x + bb1.width + off1, y: bb1.y + bb1.height / 2}, /* EAST 1 */
|
||||
{x: bb2.x + bb2.width / 2, y: bb2.y - off2}, /* NORTH 2 */
|
||||
{x: bb2.x + bb2.width / 2, y: bb2.y + bb2.height + off2}, /* SOUTH 2 */
|
||||
{x: bb2.x - off2, y: bb2.y + bb2.height / 2}, /* WEST 2 */
|
||||
{x: bb2.x + bb2.width + off2, y: bb2.y + bb2.height / 2} /* EAST 2 */
|
||||
];
|
||||
|
||||
/* distances between objects and according coordinates connection */
|
||||
var d = {}, dis = [];
|
||||
|
||||
/*
|
||||
* find out the best connection coordinates by trying all possible ways
|
||||
*/
|
||||
/* loop the first object's connection coordinates */
|
||||
for (var i = 0; i < 4; i++) {
|
||||
/* loop the seond object's connection coordinates */
|
||||
for (var j = 4; j < 8; j++) {
|
||||
var dx = Math.abs(p[i].x - p[j].x),
|
||||
dy = Math.abs(p[i].y - p[j].y);
|
||||
if ((i == j - 4) || (((i != 3 && j != 6) || p[i].x < p[j].x) && ((i != 2 && j != 7) || p[i].x > p[j].x) && ((i != 0 && j != 5) || p[i].y > p[j].y) && ((i != 1 && j != 4) || p[i].y < p[j].y))) {
|
||||
dis.push(dx + dy);
|
||||
d[dis[dis.length - 1].toFixed(3)] = [i, j];
|
||||
}
|
||||
}
|
||||
}
|
||||
var res = dis.length == 0 ? [0, 4] : d[Math.min.apply(Math, dis).toFixed(3)];
|
||||
/* bezier path */
|
||||
var x1 = p[res[0]].x,
|
||||
y1 = p[res[0]].y,
|
||||
x4 = p[res[1]].x,
|
||||
y4 = p[res[1]].y,
|
||||
dx = Math.max(Math.abs(x1 - x4) / 2, 10),
|
||||
dy = Math.max(Math.abs(y1 - y4) / 2, 10),
|
||||
x2 = [x1, x1, x1 - dx, x1 + dx][res[0]].toFixed(3),
|
||||
y2 = [y1 - dy, y1 + dy, y1, y1][res[0]].toFixed(3),
|
||||
x3 = [0, 0, 0, 0, x4, x4, x4 - dx, x4 + dx][res[1]].toFixed(3),
|
||||
y3 = [0, 0, 0, 0, y1 + dy, y1 - dy, y4, y4][res[1]].toFixed(3);
|
||||
/* assemble path and arrow */
|
||||
var path = ["M", x1.toFixed(3), y1.toFixed(3), "C", x2, y2, x3, y3, x4.toFixed(3), y4.toFixed(3)].join(",");
|
||||
/* arrow */
|
||||
if(style && style.directed) {
|
||||
/* magnitude, length of the last path vector */
|
||||
var mag = Math.sqrt((y4 - y3) * (y4 - y3) + (x4 - x3) * (x4 - x3));
|
||||
/* vector normalisation to specified length */
|
||||
var norm = function(x,l){return (-x*(l||5)/mag);};
|
||||
/* calculate array coordinates (two lines orthogonal to the path vector) */
|
||||
var arr = [
|
||||
{x:(norm(x4-x3)+norm(y4-y3)+x4).toFixed(3), y:(norm(y4-y3)+norm(x4-x3)+y4).toFixed(3)},
|
||||
{x:(norm(x4-x3)-norm(y4-y3)+x4).toFixed(3), y:(norm(y4-y3)-norm(x4-x3)+y4).toFixed(3)}
|
||||
];
|
||||
path = path + ",M"+arr[0].x+","+arr[0].y+",L"+x4+","+y4+",L"+arr[1].x+","+arr[1].y;
|
||||
}
|
||||
/* function to be used for moving existent path(s), e.g. animate() or attr() */
|
||||
var move = "attr";
|
||||
/* applying path(s) */
|
||||
edge.fg && edge.fg[move]({path:path})
|
||||
|| (edge.fg = selfRef.path(path).attr({stroke: style && style.stroke || "#000", fill: "none"}).toBack());
|
||||
edge.bg && edge.bg[move]({path:path})
|
||||
|| style && style.fill && (edge.bg = style.fill.split && selfRef.path(path).attr({stroke: style.fill.split("|")[0], fill: "none", "stroke-width": style.fill.split("|")[1] || 3}).toBack());
|
||||
/* setting label */
|
||||
style && style.label
|
||||
&& (edge.label && edge.label.attr({x:(x1+x4)/2, y:(y1+y4)/2})
|
||||
|| (edge.label = selfRef.text((x1+x4)/2, (y1+y4)/2, style.label).attr({fill: "#000", "font-size": style["font-size"] || "12px"})));
|
||||
style && style.label && style["label-style"] && edge.label && edge.label.attr(style["label-style"]);
|
||||
style && style.callback && style.callback(edge);
|
||||
}
|
||||
}
|
||||
edge.draw();
|
||||
return edge;
|
||||
};
|
||||
</script>
|
||||
|
||||
<script>
|
||||
var graph = new Springy.Graph();
|
||||
|
||||
var dennis = graph.newNode({label: 'Dennis'});
|
||||
var michael = graph.newNode({label: 'Michael'});
|
||||
var jessica = graph.newNode({label: 'Jessica'});
|
||||
var timothy = graph.newNode({label: 'Timothy'});
|
||||
var barbara = graph.newNode({label: 'Barbara'});
|
||||
var franklin = graph.newNode({label: 'Franklin'});
|
||||
var monty = graph.newNode({label: 'Monty'});
|
||||
var james = graph.newNode({label: 'James'});
|
||||
var bianca = graph.newNode({label: 'Bianca'});
|
||||
|
||||
graph.newEdge(dennis, michael, {color: '#00A0B0'});
|
||||
graph.newEdge(michael, dennis, {color: '#6A4A3C'});
|
||||
graph.newEdge(michael, jessica, {color: '#CC333F'});
|
||||
graph.newEdge(jessica, barbara, {color: '#EB6841'});
|
||||
graph.newEdge(michael, timothy, {color: '#EDC951'});
|
||||
graph.newEdge(franklin, monty, {color: '#7DBE3C'});
|
||||
graph.newEdge(dennis, monty, {color: '#000000'});
|
||||
graph.newEdge(monty, james, {color: '#00A0B0'});
|
||||
graph.newEdge(barbara, timothy, {color: '#6A4A3C'});
|
||||
graph.newEdge(dennis, bianca, {color: '#CC333F'});
|
||||
graph.newEdge(bianca, monty, {color: '#EB6841'});
|
||||
|
||||
Raphael.fn.label = function(str) {
|
||||
|
||||
var color = Raphael.getColor();
|
||||
|
||||
this.setStart();
|
||||
|
||||
var shape = this.rect(0, 0, 60, 30, 10);
|
||||
shape.attr({fill: color, stroke: color, "fill-opacity": 0, "stroke-width": 2, cursor: "move"}).setOffset();
|
||||
|
||||
var text = this.text(30, 15, str).attr({'font-size': 15}).setOffset();
|
||||
|
||||
return this.setFinish();
|
||||
|
||||
}
|
||||
|
||||
Raphael.el.setOffset = function() {
|
||||
this.offsetx = this.attr('x');
|
||||
this.offsety = this.attr('y');
|
||||
}
|
||||
|
||||
function moveSet(set, x, y) {
|
||||
set.forEach(function(item) {
|
||||
item.attr({
|
||||
x: x + item.offsetx,
|
||||
y: y + item.offsety
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
function doit() {
|
||||
var layout = new Springy.Layout.ForceDirected(graph, 640, 480.0, 0.5);
|
||||
|
||||
var r = Raphael("holder", 640, 480);
|
||||
|
||||
// calculate bounding box of graph layout.. with ease-in
|
||||
var currentBB = layout.getBoundingBox();
|
||||
var targetBB = {bottomleft: new Springy.Vector(-2, -2), topright: new Springy.Vector(2, 2)};
|
||||
|
||||
// auto adjusting bounding box
|
||||
Springy.requestAnimationFrame(function adjust() {
|
||||
targetBB = layout.getBoundingBox();
|
||||
// current gets 20% closer to target every iteration
|
||||
currentBB = {
|
||||
bottomleft: currentBB.bottomleft.add( targetBB.bottomleft.subtract(currentBB.bottomleft)
|
||||
.divide(10)),
|
||||
topright: currentBB.topright.add( targetBB.topright.subtract(currentBB.topright)
|
||||
.divide(10))
|
||||
};
|
||||
|
||||
Springy.requestAnimationFrame(adjust);
|
||||
});
|
||||
|
||||
// convert to/from screen coordinates
|
||||
toScreen = function(p) {
|
||||
var size = currentBB.topright.subtract(currentBB.bottomleft);
|
||||
var sx = p.subtract(currentBB.bottomleft).divide(size.x).x * r.width;
|
||||
var sy = p.subtract(currentBB.bottomleft).divide(size.y).y * r.height;
|
||||
return new Springy.Vector(sx, sy);
|
||||
};
|
||||
|
||||
|
||||
var renderer = new Springy.Renderer(layout,
|
||||
function clear() {
|
||||
// code to clear screen
|
||||
},
|
||||
function drawEdge(edge, p1, p2) {
|
||||
var connection;
|
||||
|
||||
if (!edge.connection) {
|
||||
|
||||
if (!edge.source.shape || !edge.target.shape)
|
||||
return;
|
||||
|
||||
connection = r.connection(edge.source.shape, edge.target.shape, {stroke: edge.data['color']});
|
||||
edge.connection = connection;
|
||||
|
||||
} else {
|
||||
edge.connection.draw();
|
||||
}
|
||||
|
||||
},
|
||||
function drawNode(node, p) {
|
||||
|
||||
var shape;
|
||||
|
||||
if (!node.shape) {
|
||||
node.shape = r.label(node.data['label']);
|
||||
}
|
||||
shape = node.shape;
|
||||
|
||||
s = toScreen(p);
|
||||
moveSet(shape, Math.floor(s.x), Math.floor(s.y));
|
||||
|
||||
});
|
||||
|
||||
renderer.start();
|
||||
|
||||
}
|
||||
|
||||
jQuery(function(){
|
||||
doit();
|
||||
});
|
||||
</script>
|
||||
|
||||
<div id="holder" width="640" height="480"></div>
|
||||
|
||||
</body>
|
||||
</html>
|
37
springy.js
37
springy.js
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Springy v2.0.1
|
||||
*
|
||||
* Copyright (c) 2010 Dennis Hotson
|
||||
* Copyright (c) 2010-2013 Dennis Hotson
|
||||
*
|
||||
* Permission is hereby granted, free of charge, to any person
|
||||
* obtaining a copy of this software and associated documentation
|
||||
|
@ -483,14 +483,19 @@
|
|||
}), root);
|
||||
|
||||
|
||||
// start simulation
|
||||
Layout.ForceDirected.prototype.start = function(render, done) {
|
||||
/**
|
||||
* Start simulation if it's not running already.
|
||||
* In case it's running then the call is ignored, and none of the callbacks passed is ever executed.
|
||||
*/
|
||||
Layout.ForceDirected.prototype.start = function(render, onRenderStop, onRenderStart) {
|
||||
var t = this;
|
||||
|
||||
if (this._started) return;
|
||||
this._started = true;
|
||||
this._stop = false;
|
||||
|
||||
if (onRenderStart !== undefined) { onRenderStart(); }
|
||||
|
||||
Springy.requestAnimationFrame(function step() {
|
||||
t.applyCoulombsLaw();
|
||||
t.applyHookesLaw();
|
||||
|
@ -505,7 +510,7 @@
|
|||
// stop simulation when energy of the system goes below a threshold
|
||||
if (t._stop || t.totalEnergy() < 0.01) {
|
||||
t._started = false;
|
||||
if (done !== undefined) { done(); }
|
||||
if (onRenderStop !== undefined) { onRenderStop(); }
|
||||
} else {
|
||||
Springy.requestAnimationFrame(step);
|
||||
}
|
||||
|
@ -625,12 +630,18 @@
|
|||
// return Math.abs(ac.x * n.x + ac.y * n.y);
|
||||
// };
|
||||
|
||||
// Renderer handles the layout rendering loop
|
||||
var Renderer = Springy.Renderer = function(layout, clear, drawEdge, drawNode) {
|
||||
/**
|
||||
* Renderer handles the layout rendering loop
|
||||
* @param onRenderStop optional callback function that gets executed whenever rendering stops.
|
||||
* @param onRenderStart optional callback function that gets executed whenever rendering starts.
|
||||
*/
|
||||
var Renderer = Springy.Renderer = function(layout, clear, drawEdge, drawNode, onRenderStop, onRenderStart) {
|
||||
this.layout = layout;
|
||||
this.clear = clear;
|
||||
this.drawEdge = drawEdge;
|
||||
this.drawNode = drawNode;
|
||||
this.onRenderStop = onRenderStop;
|
||||
this.onRenderStart = onRenderStart;
|
||||
|
||||
this.layout.graph.addGraphListener(this);
|
||||
}
|
||||
|
@ -639,7 +650,17 @@
|
|||
this.start();
|
||||
};
|
||||
|
||||
Renderer.prototype.start = function() {
|
||||
/**
|
||||
* Starts the simulation of the layout in use.
|
||||
*
|
||||
* Note that in case the algorithm is still or already running then the layout that's in use
|
||||
* might silently ignore the call, and your optional <code>done</code> callback is never executed.
|
||||
* At least the built-in ForceDirected layout behaves in this way.
|
||||
*
|
||||
* @param done An optional callback function that gets executed when the springy algorithm stops,
|
||||
* either because it ended or because stop() was called.
|
||||
*/
|
||||
Renderer.prototype.start = function(done) {
|
||||
var t = this;
|
||||
this.layout.start(function render() {
|
||||
t.clear();
|
||||
|
@ -651,7 +672,7 @@
|
|||
t.layout.eachNode(function(node, point) {
|
||||
t.drawNode(node, point.p);
|
||||
});
|
||||
});
|
||||
}, this.onRenderStart, this.onRenderStop);
|
||||
};
|
||||
|
||||
Renderer.prototype.stop = function() {
|
||||
|
|
26
springyui.js
26
springyui.js
|
@ -27,7 +27,8 @@ Copyright (c) 2010 Dennis Hotson
|
|||
|
||||
jQuery.fn.springy = function(params) {
|
||||
var graph = this.graph = params.graph || new Springy.Graph();
|
||||
|
||||
var nodeFont = "16px Verdana, sans-serif";
|
||||
var edgeFont = "8px Verdana, sans-serif";
|
||||
var stiffness = params.stiffness || 400.0;
|
||||
var repulsion = params.repulsion || 400.0;
|
||||
var damping = params.damping || 0.5;
|
||||
|
@ -57,14 +58,14 @@ jQuery.fn.springy = function(params) {
|
|||
});
|
||||
|
||||
// convert to/from screen coordinates
|
||||
toScreen = function(p) {
|
||||
var toScreen = function(p) {
|
||||
var size = currentBB.topright.subtract(currentBB.bottomleft);
|
||||
var sx = p.subtract(currentBB.bottomleft).divide(size.x).x * canvas.width;
|
||||
var sy = p.subtract(currentBB.bottomleft).divide(size.y).y * canvas.height;
|
||||
return new Springy.Vector(sx, sy);
|
||||
};
|
||||
|
||||
fromScreen = function(s) {
|
||||
var fromScreen = function(s) {
|
||||
var size = currentBB.topright.subtract(currentBB.bottomleft);
|
||||
var px = (s.x / canvas.width) * size.x + currentBB.bottomleft.x;
|
||||
var py = (s.y / canvas.height) * size.y + currentBB.bottomleft.y;
|
||||
|
@ -126,7 +127,7 @@ jQuery.fn.springy = function(params) {
|
|||
return this._width[text];
|
||||
|
||||
ctx.save();
|
||||
ctx.font = "16px Verdana, sans-serif";
|
||||
ctx.font = (this.data.font !== undefined) ? this.data.font : nodeFont;
|
||||
var width = ctx.measureText(text).width + 10;
|
||||
ctx.restore();
|
||||
|
||||
|
@ -166,7 +167,8 @@ jQuery.fn.springy = function(params) {
|
|||
}
|
||||
}
|
||||
|
||||
var spacing = 6.0;
|
||||
//change default to 10.0 to allow text fit between edges
|
||||
var spacing = 12.0;
|
||||
|
||||
// Figure out how far off center the line should be drawn
|
||||
var offset = normal.multiply(-((total - 1) * spacing)/2.0 + (n * spacing));
|
||||
|
@ -232,9 +234,12 @@ jQuery.fn.springy = function(params) {
|
|||
ctx.save();
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.font = "10px Helvetica, sans-serif";
|
||||
ctx.fillStyle = "#5BA6EC";
|
||||
ctx.fillText(text, (x1+x2)/2, (y1+y2)/2);
|
||||
ctx.font = (edge.data.font !== undefined) ? edge.data.font : edgeFont;
|
||||
ctx.fillStyle = stroke;
|
||||
var textPos = s1.add(s2).divide(2).add(normal.multiply(-8));
|
||||
ctx.translate(textPos.x, textPos.y);
|
||||
ctx.rotate(Math.atan2(s2.y - s1.y, s2.x - s1.x));
|
||||
ctx.fillText(text, 0,-2);
|
||||
ctx.restore();
|
||||
}
|
||||
|
||||
|
@ -251,7 +256,7 @@ jQuery.fn.springy = function(params) {
|
|||
ctx.clearRect(s.x - boxWidth/2, s.y - 10, boxWidth, 20);
|
||||
|
||||
// fill background
|
||||
if (selected !== null && nearest.node !== null && selected.node.id === node.id) {
|
||||
if (selected !== null && selected.node !== null && selected.node.id === node.id) {
|
||||
ctx.fillStyle = "#FFFFE0";
|
||||
} else if (nearest !== null && nearest.node !== null && nearest.node.id === node.id) {
|
||||
ctx.fillStyle = "#EEEEEE";
|
||||
|
@ -262,9 +267,8 @@ jQuery.fn.springy = function(params) {
|
|||
|
||||
ctx.textAlign = "left";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.font = "16px Verdana, sans-serif";
|
||||
ctx.font = (node.data.font !== undefined) ? node.data.font : nodeFont;
|
||||
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);
|
||||
|
||||
|
|
Loading…
Reference in New Issue
Block a user