Whole bunch of improvements.
This commit is contained in:
parent
8f9d858ad1
commit
af82e4060b
139
demo.html
139
demo.html
|
@ -7,109 +7,118 @@
|
|||
|
||||
var graph = new Graph();
|
||||
|
||||
var n1 = graph.newNode({label: "1"});
|
||||
var n2 = graph.newNode({label: "2"});
|
||||
var n3 = graph.newNode({label: "3"});
|
||||
var n4 = graph.newNode({label: "4"});
|
||||
var n5 = graph.newNode({label: "5"});
|
||||
var n6 = graph.newNode({label: "6", fill: '#EEF'});
|
||||
var n7 = graph.newNode({label: "7"});
|
||||
var n8 = graph.newNode({label: "8"});
|
||||
var n9 = graph.newNode({label: "9"});
|
||||
var n10 = graph.newNode({label: "10"});
|
||||
var n11 = graph.newNode({label: "11", fill: '#EFE'});
|
||||
var n12 = graph.newNode({label: "12", fill: '#FEE'});
|
||||
var n13 = graph.newNode({label: "13"});
|
||||
var n14 = graph.newNode({label: "14"});
|
||||
var n15 = graph.newNode({label: "15"});
|
||||
|
||||
var e1 = graph.newEdge(n1, n2);
|
||||
var e2 = graph.newEdge(n2, n3);
|
||||
var e3 = graph.newEdge(n3, n4);
|
||||
var e4 = graph.newEdge(n4, n1);
|
||||
var e5 = graph.newEdge(n3, n5);
|
||||
var e6 = graph.newEdge(n4, n5);
|
||||
var e7 = graph.newEdge(n5, n6);
|
||||
var e8 = graph.newEdge(n5, n7);
|
||||
var e9 = graph.newEdge(n1, n8);
|
||||
var e10 = graph.newEdge(n2, n9);
|
||||
var e11 = graph.newEdge(n9, n10);
|
||||
var e12 = graph.newEdge(n9, n11);
|
||||
var e13 = graph.newEdge(n1, n12);
|
||||
var e14 = graph.newEdge(n12, n13);
|
||||
var e15 = graph.newEdge(n13, n14);
|
||||
var e16 = graph.newEdge(n14, n15);
|
||||
var e17 = graph.newEdge(n15, n5);
|
||||
var e18 = graph.newEdge(n12, n14);
|
||||
// generate a random graph
|
||||
|
||||
var n = [];
|
||||
for (var i=0; i<15; i += 1)
|
||||
{
|
||||
n[i] = graph.newNode({label: ""+i});
|
||||
}
|
||||
|
||||
for (i=0; i<20; i += 1)
|
||||
{
|
||||
var i1 = Math.floor(Math.random() * n.length);
|
||||
var i2 = i1;
|
||||
|
||||
while (i1 === i2) {
|
||||
i2 = Math.floor(Math.random() * n.length);
|
||||
}
|
||||
|
||||
var n1 = n[i1];
|
||||
var n2 = n[i2];
|
||||
|
||||
var e = graph.newEdge(n1, n2);
|
||||
|
||||
var colors = ['#00A0B0', '#6A4A3C', '#CC333F', '#EB6841', '#EDC951', '#7DBE3C'];
|
||||
e.data.stroke = colors[Math.floor(Math.random() * colors.length)];
|
||||
}
|
||||
|
||||
|
||||
// -----------
|
||||
|
||||
var width = 800;
|
||||
var height = 600;
|
||||
var zoom = 50.0;
|
||||
var zoom = 40.0;
|
||||
|
||||
// convert to/from screen coordinates
|
||||
toScreen = function(p) {
|
||||
return {
|
||||
x: p.x * zoom + width/2.0,
|
||||
y: p.y * zoom + height/2.0
|
||||
};
|
||||
return new Vector(p.x * zoom + width/2.0, p.y * zoom + height/2.0);
|
||||
};
|
||||
|
||||
fromScreen = function(s) {
|
||||
return {
|
||||
x: (s.x - width/2.0) / zoom,
|
||||
y: (s.y - height/2.0) / zoom
|
||||
};
|
||||
return new Vector((s.x - width/2.0) / zoom, (s.y - height/2.0) / zoom);
|
||||
};
|
||||
|
||||
var paper = Raphael(10, 10, width, height);
|
||||
|
||||
var layout = new Layout.ForceDirected(graph, 500.0, 300.0, 0.5);
|
||||
|
||||
var boxWidth = 60;
|
||||
var boxWidth = 30;
|
||||
var boxHeight = 20;
|
||||
|
||||
var renderer = new Renderer(10, layout,
|
||||
var renderer = new Renderer(1, layout,
|
||||
function clear()
|
||||
{
|
||||
paper.clear();
|
||||
var r = paper.rect(0,0,width-1,height-1);
|
||||
r.attr("fill", "#FFFFFF");
|
||||
r.attr("stroke", "none");
|
||||
r.attr({"fill": "#FFFFFF", "stroke": "none"});
|
||||
},
|
||||
function drawEdge(edge, p1, p2)
|
||||
{
|
||||
var x1 = Math.floor(toScreen(p1).x);
|
||||
var y1 = Math.floor(toScreen(p1).y);
|
||||
var x2 = Math.floor(toScreen(p2).x);
|
||||
var y2 = Math.floor(toScreen(p2).y);
|
||||
var c = paper.path(["M", x1, y1, "L", x2, y2]);
|
||||
c.attr("stroke-width", 1.0);
|
||||
var x1 = toScreen(p1).x;
|
||||
var y1 = toScreen(p1).y;
|
||||
var x2 = toScreen(p2).x;
|
||||
var y2 = toScreen(p2).y;
|
||||
|
||||
var point = intersect_line_box(toScreen(p1), toScreen(p2), {x: x2-boxWidth/2.0, y: y2-boxHeight/2.0}, boxWidth, boxHeight);
|
||||
var x = point.x;
|
||||
var y = point.y;
|
||||
var direction = new Vector(x2-x1, y2-y1);
|
||||
var normal = direction.normal().normalise();
|
||||
|
||||
var from = graph.getEdges(edge.source, edge.target);
|
||||
var to = graph.getEdges(edge.target, edge.source);
|
||||
|
||||
var total = from.length + to.length;
|
||||
var n = from.indexOf(edge);
|
||||
|
||||
var spacing = 6.0;
|
||||
var totalWidth = total * spacing;
|
||||
|
||||
// Figure out how far off centre the line should be drawn
|
||||
var offset = normal.multiply(-((total - 1) * spacing)/2.0 + (n * spacing));
|
||||
|
||||
var stroke = typeof(edge.data.stroke) !== 'undefined' ? edge.data.stroke : "#000000";
|
||||
|
||||
var s1 = toScreen(p1).add(offset);
|
||||
var s2 = toScreen(p2).add(offset);
|
||||
|
||||
var intersection = intersect_line_box(s1, s2, {x: x2-boxWidth/2.0, y: y2-boxHeight/2.0}, boxWidth, boxHeight);
|
||||
|
||||
var c2 = paper.path(["M", s1.x, s1.y, "L", intersection.x, intersection.y]);
|
||||
c2.attr({stroke: stroke, "stroke-width": 2});
|
||||
|
||||
var arrow = paper.path(["M", -7, 4, "L", 0, 0, "L", 7, 0, "L", 0, 0, "L", -7, -4, "L", -5, 0, "z"]);
|
||||
arrow.rotate(Math.atan2(y2 - y1, x2 - x1) * (180.0 / Math.PI), 0, 0);
|
||||
arrow.translate(intersection.x, intersection.y);
|
||||
arrow.attr({fill: stroke, stroke: "none"});
|
||||
|
||||
var arrow = paper.path(["M", -7, 3, "L", 0, 0, "L", 7, 0, "L", 0, 0, "L", -7, -3, "L", -6, 0, "z"]);
|
||||
arrow.rotate(Math.atan2(y2 - y1, x2 - x1) * (180.0 / Math.PI));
|
||||
arrow.translate(x, y);
|
||||
arrow.attr("fill", "black");
|
||||
arrow.attr("stroke", "none");
|
||||
},
|
||||
function drawNode(node, p)
|
||||
{
|
||||
|
||||
var fill = typeof(node.data.fill) !== 'undefined' ? node.data.fill : "#FFFFFF";
|
||||
|
||||
var s = toScreen(p);
|
||||
var rect = paper.rect(s.x - boxWidth/2.0, s.y - boxHeight/2.0, boxWidth, boxHeight, 3);
|
||||
rect.attr("fill", fill);
|
||||
rect.attr("stroke-width", 1.0);
|
||||
var rect = paper.rect(s.x - boxWidth/2.0, s.y - boxHeight/2.0, boxWidth, boxHeight, 4);
|
||||
rect.attr({fill: fill, "stroke-width": 2});
|
||||
|
||||
if (typeof(node.data.label) !== 'undefined')
|
||||
{
|
||||
var text = paper.text(s.x, s.y, node.data.label);
|
||||
text.attr({
|
||||
"font-family": "Helvetica Neue",
|
||||
"font-size": "12px",
|
||||
"font-weight": "bold",
|
||||
});
|
||||
|
||||
}
|
||||
}
|
||||
);
|
||||
|
@ -141,8 +150,8 @@ jQuery('svg').mousemove(function(e){
|
|||
|
||||
selected.point.p.x = p.x;
|
||||
selected.point.p.y = p.y;
|
||||
renderer.start();
|
||||
}
|
||||
renderer.start();
|
||||
});
|
||||
|
||||
jQuery(window).bind('mouseup',function(e){
|
||||
|
|
115
springy.js
115
springy.js
|
@ -4,30 +4,33 @@ var Graph = function()
|
|||
this.nodeSet = {};
|
||||
this.nodes = [];
|
||||
this.edges = [];
|
||||
this.adjacency = {};
|
||||
|
||||
this.nextNodeId = 0;
|
||||
this.nextEdgeId = 0;
|
||||
this.eventListeners = [];
|
||||
};
|
||||
|
||||
Node = function(data)
|
||||
Node = function(id, data)
|
||||
{
|
||||
this.id = (Node.nextId += 1);
|
||||
this.id = id;
|
||||
this.data = typeof(data) !== 'undefined' ? data : {};
|
||||
};
|
||||
Node.nextId = 0;
|
||||
|
||||
Edge = function(source, target, data)
|
||||
|
||||
Edge = function(id, source, target, data)
|
||||
{
|
||||
this.id = (Edge.nextId += 1);
|
||||
this.id = id;
|
||||
this.source = source;
|
||||
this.target = target;
|
||||
this.data = typeof(data) !== 'undefined' ? data : {};
|
||||
};
|
||||
Edge.nextId = 0;
|
||||
|
||||
Graph.prototype.addNode = function(node)
|
||||
{
|
||||
this.nodes.push(node);
|
||||
this.nodeSet[node.id] = node;
|
||||
|
||||
this.notify();
|
||||
return node;
|
||||
};
|
||||
|
@ -35,24 +38,48 @@ Graph.prototype.addNode = function(node)
|
|||
Graph.prototype.addEdge = function(edge)
|
||||
{
|
||||
this.edges.push(edge);
|
||||
|
||||
if (typeof(this.adjacency[edge.source.id]) === 'undefined')
|
||||
{
|
||||
this.adjacency[edge.source.id] = [];
|
||||
}
|
||||
if (typeof(this.adjacency[edge.source.id][edge.target.id]) === 'undefined')
|
||||
{
|
||||
this.adjacency[edge.source.id][edge.target.id] = [];
|
||||
}
|
||||
|
||||
this.adjacency[edge.source.id][edge.target.id].push(edge);
|
||||
|
||||
this.notify();
|
||||
return edge;
|
||||
};
|
||||
|
||||
Graph.prototype.newNode = function(data)
|
||||
{
|
||||
var node = new Node(data);
|
||||
var node = new Node(this.nextNodeId++, data);
|
||||
this.addNode(node);
|
||||
return node;
|
||||
};
|
||||
|
||||
Graph.prototype.newEdge = function(source, target, data)
|
||||
{
|
||||
var edge = new Edge(source, target, data);
|
||||
var edge = new Edge(this.nextEdgeId++, source, target, data);
|
||||
this.addEdge(edge);
|
||||
return edge;
|
||||
};
|
||||
|
||||
// find the edges from node1 to node2
|
||||
Graph.prototype.getEdges = function(node1, node2)
|
||||
{
|
||||
if (typeof(this.adjacency[node1.id]) !== 'undefined'
|
||||
&& typeof(this.adjacency[node1.id][node2.id]) !== 'undefined')
|
||||
{
|
||||
return this.adjacency[node1.id][node2.id];
|
||||
}
|
||||
|
||||
return [];
|
||||
};
|
||||
|
||||
|
||||
Graph.prototype.addGraphListener = function(obj)
|
||||
{
|
||||
|
@ -77,7 +104,6 @@ Layout.ForceDirected = function(graph, stiffness, repulsion, damping)
|
|||
|
||||
this.nodePoints = {}; // keep track of points associated with nodes
|
||||
this.edgeSprings = {}; // keep track of points associated with nodes
|
||||
this.extraSprings = []; // springs that aren't associated with any edges
|
||||
|
||||
this.intervalId = null;
|
||||
};
|
||||
|
@ -87,7 +113,7 @@ Layout.ForceDirected.prototype.point = function(node)
|
|||
if (typeof(this.nodePoints[node.id]) === 'undefined')
|
||||
{
|
||||
var mass = typeof(node.data.mass) !== 'undefined' ? node.data.mass : 1.0;
|
||||
this.nodePoints[node.id] = new Layout.ForceDirected.Point(Layout.ForceDirected.Vector.random(), mass);
|
||||
this.nodePoints[node.id] = new Layout.ForceDirected.Point(Vector.random(), mass);
|
||||
}
|
||||
|
||||
return this.nodePoints[node.id];
|
||||
|
@ -98,6 +124,31 @@ Layout.ForceDirected.prototype.spring = function(edge)
|
|||
if (typeof(this.edgeSprings[edge.id]) === 'undefined')
|
||||
{
|
||||
var length = typeof(edge.data.length) !== 'undefined' ? edge.data.length : 1.0;
|
||||
|
||||
var existingSpring = false;
|
||||
|
||||
var from = this.graph.getEdges(edge.source, edge.target);
|
||||
from.forEach(function(e){
|
||||
if (existingSpring === false && typeof(this.edgeSprings[e.id]) !== 'undefined') {
|
||||
existingSpring = this.edgeSprings[e.id];
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (existingSpring !== false) {
|
||||
return new Layout.ForceDirected.Spring(existingSpring.point1, existingSpring.point2, 0.0, 0.0);
|
||||
}
|
||||
|
||||
var to = this.graph.getEdges(edge.target, edge.source);
|
||||
from.forEach(function(e){
|
||||
if (existingSpring === false && typeof(this.edgeSprings[e.id]) !== 'undefined') {
|
||||
existingSpring = this.edgeSprings[e.id];
|
||||
}
|
||||
}, this);
|
||||
|
||||
if (existingSpring !== false) {
|
||||
return new Layout.ForceDirected.Spring(existingSpring.point2, existingSpring.point1, 0.0, 0.0);
|
||||
}
|
||||
|
||||
this.edgeSprings[edge.id] = new Layout.ForceDirected.Spring(
|
||||
this.point(edge.source), this.point(edge.target), length, this.stiffness
|
||||
);
|
||||
|
@ -131,10 +182,6 @@ Layout.ForceDirected.prototype.eachSpring = function(callback)
|
|||
this.graph.edges.forEach(function(e){
|
||||
callback.call(t, t.spring(e));
|
||||
});
|
||||
|
||||
this.extraSprings.forEach(function(s){
|
||||
callback.call(t, s);
|
||||
});
|
||||
};
|
||||
|
||||
|
||||
|
@ -183,7 +230,7 @@ Layout.ForceDirected.prototype.updateVelocity = function(timestep)
|
|||
{
|
||||
this.eachNode(function(node, point) {
|
||||
point.v = point.v.add(point.f.multiply(timestep)).multiply(this.damping);
|
||||
point.f = new Layout.ForceDirected.Vector(0,0);
|
||||
point.f = new Vector(0,0);
|
||||
});
|
||||
};
|
||||
|
||||
|
@ -219,8 +266,8 @@ Layout.ForceDirected.prototype.start = function(interval, render, done)
|
|||
t.applyCoulombsLaw();
|
||||
t.applyHookesLaw();
|
||||
t.attractToCentre();
|
||||
t.updateVelocity(0.05);
|
||||
t.updatePosition(0.05);
|
||||
t.updateVelocity(0.04);
|
||||
t.updatePosition(0.04);
|
||||
|
||||
if (typeof(render) !== 'undefined') { render(); }
|
||||
|
||||
|
@ -253,48 +300,48 @@ Layout.ForceDirected.prototype.nearest = function(pos)
|
|||
};
|
||||
|
||||
// Vector
|
||||
Layout.ForceDirected.Vector = function(x, y)
|
||||
Vector = function(x, y)
|
||||
{
|
||||
this.x = x;
|
||||
this.y = y;
|
||||
};
|
||||
|
||||
Layout.ForceDirected.Vector.random = function()
|
||||
Vector.random = function()
|
||||
{
|
||||
return new Layout.ForceDirected.Vector(2.0 * (Math.random() - 0.5), 2.0 * (Math.random() - 0.5));
|
||||
return new Vector(2.0 * (Math.random() - 0.5), 2.0 * (Math.random() - 0.5));
|
||||
};
|
||||
|
||||
Layout.ForceDirected.Vector.prototype.add = function(v2)
|
||||
Vector.prototype.add = function(v2)
|
||||
{
|
||||
return new Layout.ForceDirected.Vector(this.x + v2.x, this.y + v2.y);
|
||||
return new Vector(this.x + v2.x, this.y + v2.y);
|
||||
};
|
||||
|
||||
Layout.ForceDirected.Vector.prototype.subtract = function(v2)
|
||||
Vector.prototype.subtract = function(v2)
|
||||
{
|
||||
return new Layout.ForceDirected.Vector(this.x - v2.x, this.y - v2.y);
|
||||
return new Vector(this.x - v2.x, this.y - v2.y);
|
||||
};
|
||||
|
||||
Layout.ForceDirected.Vector.prototype.multiply = function(n)
|
||||
Vector.prototype.multiply = function(n)
|
||||
{
|
||||
return new Layout.ForceDirected.Vector(this.x * n, this.y * n);
|
||||
return new Vector(this.x * n, this.y * n);
|
||||
};
|
||||
|
||||
Layout.ForceDirected.Vector.prototype.divide = function(n)
|
||||
Vector.prototype.divide = function(n)
|
||||
{
|
||||
return new Layout.ForceDirected.Vector(this.x / n, this.y / n);
|
||||
return new Vector(this.x / n, this.y / n);
|
||||
};
|
||||
|
||||
Layout.ForceDirected.Vector.prototype.magnitude = function()
|
||||
Vector.prototype.magnitude = function()
|
||||
{
|
||||
return Math.sqrt(this.x*this.x + this.y*this.y);
|
||||
};
|
||||
|
||||
Layout.ForceDirected.Vector.prototype.normal = function()
|
||||
Vector.prototype.normal = function()
|
||||
{
|
||||
return new Layout.ForceDirected.Vector(-this.y, this.x);
|
||||
return new Vector(-this.y, this.x);
|
||||
};
|
||||
|
||||
Layout.ForceDirected.Vector.prototype.normalise = function()
|
||||
Vector.prototype.normalise = function()
|
||||
{
|
||||
return this.divide(this.magnitude());
|
||||
};
|
||||
|
@ -304,8 +351,8 @@ Layout.ForceDirected.Point = function(position, mass)
|
|||
{
|
||||
this.p = position; // position
|
||||
this.m = mass; // mass
|
||||
this.v = new Layout.ForceDirected.Vector(0, 0); // velocity
|
||||
this.f = new Layout.ForceDirected.Vector(0, 0); // force
|
||||
this.v = new Vector(0, 0); // velocity
|
||||
this.f = new Vector(0, 0); // force
|
||||
};
|
||||
|
||||
Layout.ForceDirected.Point.prototype.applyForce = function(force)
|
||||
|
|
Loading…
Reference in New Issue
Block a user