Devel-SizeMe
view release on metacpan or search on metacpan
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
curi.push(this.compute(fromi[j], toi[j], delta));
}
cur.push(curi);
} else {
cur.push(this.compute(fromi, toi, delta));
}
}
elem[setter](prop, cur);
},
'node': function(elem, props, delta, map, getter, setter) {
map = this[map];
if(props) {
var len = props.length;
for(var i=0; i<len; i++) {
var pi = props[i];
this[map[pi]](elem, pi, delta, getter, setter);
}
} else {
for(var pi in map) {
this[map[pi]](elem, pi, delta, getter, setter);
}
}
},
'edge': function(elem, props, delta, mapKey, getter, setter) {
var adjs = elem.adjacencies;
for(var id in adjs) this['node'](adjs[id], props, delta, mapKey, getter, setter);
},
'node-property': function(elem, props, delta) {
this['node'](elem, props, delta, 'map', 'getData', 'setData');
},
'edge-property': function(elem, props, delta) {
this['edge'](elem, props, delta, 'map', 'getData', 'setData');
},
'label-property': function(elem, props, delta) {
this['node'](elem, props, delta, 'label', 'getLabelData', 'setLabelData');
},
'node-style': function(elem, props, delta) {
this['node'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
},
'edge-style': function(elem, props, delta) {
this['edge'](elem, props, delta, 'canvas', 'getCanvasStyle', 'setCanvasStyle');
}
},
/*
sequence
Iteratively performs an action while refreshing the state of the visualization.
Parameters:
options - (object) An object containing some sequence options described below
condition - (function) A function returning a boolean instance in order to stop iterations.
step - (function) A function to execute on each step of the iteration.
onComplete - (function) A function to execute when the sequence finishes.
duration - (number) Duration (in milliseconds) of each step.
Example:
(start code js)
var rg = new $jit.RGraph(options);
var i = 0;
rg.fx.sequence({
condition: function() {
return i == 10;
},
step: function() {
alert(i++);
},
onComplete: function() {
alert('done!');
}
});
(end code)
*/
sequence: function(options) {
var that = this;
options = $.merge({
condition: $.lambda(false),
step: $.empty,
onComplete: $.empty,
duration: 200
}, options || {});
var interval = setInterval(function() {
if(options.condition()) {
options.step();
} else {
clearInterval(interval);
options.onComplete();
}
that.viz.refresh(true);
}, options.duration);
},
/*
prepare
Prepare graph position and other attribute values before performing an Animation.
This method is used internally by the Toolkit.
See also:
<Animation>, <Graph.Plot.animate>
*/
prepare: function(modes) {
var graph = this.viz.graph,
accessors = {
'node-property': {
'getter': 'getData',
'setter': 'setData'
},
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
/*
* Class: Layouts.ForceDirected
*
* Implements a Force Directed Layout.
*
* Implemented By:
*
* <ForceDirected>
*
* Credits:
*
* Marcus Cobden <http://marcuscobden.co.uk>
*
*/
Layouts.ForceDirected = new Class({
getOptions: function(random) {
var s = this.canvas.getSize();
var w = s.width, h = s.height;
//count nodes
var count = 0;
this.graph.eachNode(function(n) {
count++;
});
var k2 = w * h / count, k = Math.sqrt(k2);
var l = this.config.levelDistance;
return {
width: w,
height: h,
tstart: w * 0.1,
nodef: function(x) { return k2 / (x || 1); },
edgef: function(x) { return /* x * x / k; */ k * (x - l); }
};
},
compute: function(property, incremental) {
var prop = $.splat(property || ['current', 'start', 'end']);
var opt = this.getOptions();
NodeDim.compute(this.graph, prop, this.config);
this.graph.computeLevels(this.root, 0, "ignore");
this.graph.eachNode(function(n) {
$.each(prop, function(p) {
var pos = n.getPos(p);
if(pos.equals(Complex.KER)) {
pos.x = opt.width/5 * (Math.random() - 0.5);
pos.y = opt.height/5 * (Math.random() - 0.5);
}
//initialize disp vector
n.disp = {};
$.each(prop, function(p) {
n.disp[p] = $C(0, 0);
});
});
});
this.computePositions(prop, opt, incremental);
},
computePositions: function(property, opt, incremental) {
var times = this.config.iterations, i = 0, that = this;
if(incremental) {
(function iter() {
for(var total=incremental.iter, j=0; j<total; j++) {
opt.t = opt.tstart;
if(times) opt.t *= (1 - i++/(times -1));
that.computePositionStep(property, opt);
if(times && i >= times) {
incremental.onComplete();
return;
}
}
incremental.onStep(Math.round(i / (times -1) * 100));
setTimeout(iter, 1);
})();
} else {
for(; i < times; i++) {
opt.t = opt.tstart * (1 - i/(times -1));
this.computePositionStep(property, opt);
}
}
},
computePositionStep: function(property, opt) {
var graph = this.graph;
var min = Math.min, max = Math.max;
var dpos = $C(0, 0);
//calculate repulsive forces
graph.eachNode(function(v) {
//initialize disp
$.each(property, function(p) {
v.disp[p].x = 0; v.disp[p].y = 0;
});
graph.eachNode(function(u) {
if(u.id != v.id) {
$.each(property, function(p) {
var vp = v.getPos(p), up = u.getPos(p);
dpos.x = vp.x - up.x;
dpos.y = vp.y - up.y;
var norm = dpos.norm() || 1;
v.disp[p].$add(dpos
.$scale(opt.nodef(norm) / norm));
});
}
});
});
//calculate attractive forces
var T = !!graph.getNode(this.root).visited;
graph.eachNode(function(node) {
node.eachAdjacency(function(adj) {
var nodeTo = adj.nodeTo;
if(!!nodeTo.visited === T) {
$.each(property, function(p) {
var vp = node.getPos(p), up = nodeTo.getPos(p);
dpos.x = vp.x - up.x;
dpos.y = vp.y - up.y;
var norm = dpos.norm() || 1;
node.disp[p].$add(dpos.$scale(-opt.edgef(norm) / norm));
nodeTo.disp[p].$add(dpos.$scale(-1));
});
}
});
node.visited = !T;
});
//arrange positions to fit the canvas
var t = opt.t, w2 = opt.width / 2, h2 = opt.height / 2;
graph.eachNode(function(u) {
$.each(property, function(p) {
var disp = u.disp[p];
var norm = disp.norm() || 1;
var p = u.getPos(p);
p.$add($C(disp.x * min(Math.abs(disp.x), t) / norm,
disp.y * min(Math.abs(disp.y), t) / norm));
p.x = min(w2, max(-w2, p.x));
p.y = min(h2, max(-h2, p.y));
});
});
}
});
/*
* File: ForceDirected.js
*/
/*
Class: ForceDirected
A visualization that lays graphs using a Force-Directed layout algorithm.
Inspired by:
Force-Directed Drawing Algorithms (Stephen G. Kobourov) <http://www.cs.brown.edu/~rt/gdhandbook/chapters/force-directed.pdf>
Implements:
All <Loader> methods
Constructor Options:
Inherits options from
- <Options.Canvas>
- <Options.Controller>
- <Options.Node>
- <Options.Edge>
- <Options.Label>
- <Options.Events>
- <Options.Tips>
- <Options.NodeStyles>
- <Options.Navigation>
Additionally, there are two parameters
levelDistance - (number) Default's *50*. The natural length desired for the edges.
iterations - (number) Default's *50*. The number of iterations for the spring layout simulation. Depending on the browser's speed you could set this to a more 'interesting' number, like *200*.
Instance Properties:
canvas - Access a <Canvas> instance.
graph - Access a <Graph> instance.
op - Access a <ForceDirected.Op> instance.
fx - Access a <ForceDirected.Plot> instance.
labels - Access a <ForceDirected.Label> interface implementation.
*/
$jit.ForceDirected = new Class( {
Implements: [ Loader, Extras, Layouts.ForceDirected ],
initialize: function(controller) {
var $ForceDirected = $jit.ForceDirected;
var config = {
iterations: 50,
levelDistance: 50
};
this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
"Fx", "Tips", "NodeStyles", "Events", "Navigation", "Controller", "Label"), config, controller);
var canvasConfig = this.config;
if(canvasConfig.useCanvas) {
this.canvas = canvasConfig.useCanvas;
this.config.labelContainer = this.canvas.id + '-label';
} else {
if(canvasConfig.background) {
canvasConfig.background = $.merge({
type: 'Circles'
}, canvasConfig.background);
}
this.canvas = new Canvas(this, canvasConfig);
this.config.labelContainer = (typeof canvasConfig.injectInto == 'string'? canvasConfig.injectInto : canvasConfig.injectInto.id) + '-label';
}
this.graphOptions = {
'klass': Complex,
'Node': {
'selected': false,
'exist': true,
'drawn': true
}
};
this.graph = new Graph(this.graphOptions, this.config.Node,
this.config.Edge);
this.labels = new $ForceDirected.Label[canvasConfig.Label.type](this);
this.fx = new $ForceDirected.Plot(this, $ForceDirected);
this.op = new $ForceDirected.Op(this);
this.json = null;
this.busy = false;
// initialize extras
this.initializeExtras();
},
/*
Method: refresh
Computes positions and plots the tree.
*/
refresh: function() {
this.compute();
this.plot();
},
reposition: function() {
this.compute('end');
},
/*
Method: computeIncremental
Performs the Force Directed algorithm incrementally.
Description:
ForceDirected algorithms can perform many computations and lead to JavaScript taking too much time to complete.
This method splits the algorithm into smaller parts allowing the user to track the evolution of the algorithm and
avoiding browser messages such as "This script is taking too long to complete".
Parameters:
opt - (object) The object properties are described below
iter - (number) Default's *20*. Split the algorithm into pieces of _iter_ iterations. For example, if the _iterations_ configuration property
of your <ForceDirected> class is 100, then you could set _iter_ to 20 to split the main algorithm into 5 smaller pieces.
property - (string) Default's *end*. Whether to update starting, current or ending node positions. Possible values are 'end', 'start', 'current'.
You can also set an array of these properties. If you'd like to keep the current node positions but to perform these
computations for final animation positions then you can just choose 'end'.
onStep - (function) A callback function called when each "small part" of the algorithm completed. This function gets as first formal
parameter a percentage value.
onComplete - A callback function called when the algorithm completed.
Example:
In this example I calculate the end positions and then animate the graph to those positions
(start code js)
var fd = new $jit.ForceDirected(...);
fd.computeIncremental({
iter: 20,
property: 'end',
onStep: function(perc) {
Log.write("loading " + perc + "%");
},
onComplete: function() {
Log.write("done");
fd.animate();
}
});
(end code)
In this example I calculate all positions and (re)plot the graph
(start code js)
var fd = new ForceDirected(...);
fd.computeIncremental({
iter: 20,
property: ['end', 'start', 'current'],
onStep: function(perc) {
Log.write("loading " + perc + "%");
},
onComplete: function() {
Log.write("done");
fd.plot();
}
});
(end code)
*/
computeIncremental: function(opt) {
opt = $.merge( {
iter: 20,
property: 'end',
onStep: $.empty,
onComplete: $.empty
}, opt || {});
this.config.onBeforeCompute(this.graph.getNode(this.root));
this.compute(opt.property, opt);
},
( run in 0.620 second using v1.01-cache-2.11-cpan-71847e10f99 )