view release on metacpan or search on metacpan
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
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
};
this.graph = new Graph(this.graphOptions, this.config.Node, this.config.Edge);
this.labels = new $ST.Label[canvasConfig.Label.type](this);
this.fx = new $ST.Plot(this, $ST);
this.op = new $ST.Op(this);
this.group = new $ST.Group(this);
this.geom = new $ST.Geom(this);
this.clickedNode= null;
// initialize extras
this.initializeExtras();
},
/*
Method: plot
Plots the <ST>. This is a shortcut to *fx.plot*.
*/
plot: function() { this.fx.plot(this.controller); },
/*
Method: switchPosition
Switches the tree orientation.
Parameters:
pos - (string) The new tree orientation. Possible values are "top", "left", "right" and "bottom".
method - (string) Set this to "animate" if you want to animate the tree when switching its position. You can also set this parameter to "replot" to just replot the subtree.
onComplete - (optional|object) This callback is called once the "switching" animation is complete.
Example:
(start code js)
st.switchPosition("right", "animate", {
onComplete: function() {
alert('completed!');
}
});
(end code)
*/
switchPosition: function(pos, method, onComplete) {
var Geom = this.geom, Plot = this.fx, that = this;
if(!Plot.busy) {
Plot.busy = true;
this.contract({
onComplete: function() {
Geom.switchOrientation(pos);
that.compute('end', false);
Plot.busy = false;
if(method == 'animate') {
that.onClick(that.clickedNode.id, onComplete);
} else if(method == 'replot') {
that.select(that.clickedNode.id, onComplete);
}
}
}, pos);
}
},
/*
Method: switchAlignment
Switches the tree alignment.
Parameters:
align - (string) The new tree alignment. Possible values are "left", "center" and "right".
method - (string) Set this to "animate" if you want to animate the tree after aligning its position. You can also set this parameter to "replot" to just replot the subtree.
onComplete - (optional|object) This callback is called once the "switching" animation is complete.
Example:
(start code js)
st.switchAlignment("right", "animate", {
onComplete: function() {
alert('completed!');
}
});
(end code)
*/
switchAlignment: function(align, method, onComplete) {
this.config.align = align;
if(method == 'animate') {
this.select(this.clickedNode.id, onComplete);
} else if(method == 'replot') {
this.onClick(this.clickedNode.id, onComplete);
}
},
/*
Method: addNodeInPath
Adds a node to the current path as selected node. The selected node will be visible (as in non-collapsed) at all times.
Parameters:
id - (string) A <Graph.Node> id.
Example:
(start code js)
st.addNodeInPath("nodeId");
(end code)
*/
addNodeInPath: function(id) {
nodesInPath.push(id);
this.select((this.clickedNode && this.clickedNode.id) || this.root);
},
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
},
move: function(node, onComplete) {
this.compute('end', false);
var move = onComplete.Move, offset = {
'x': move.offsetX,
'y': move.offsetY
};
if(move.enable) {
this.geom.translate(node.endPos.add(offset).$scale(-1), "end");
}
this.fx.animate($.merge(this.controller, { modes: ['linear'] }, onComplete));
},
expand: function (node, onComplete) {
var nodeArray = getNodesToShow.call(this, node);
this.group.expand(nodeArray, $.merge(this.controller, onComplete));
},
selectPath: function(node) {
var that = this;
this.graph.eachNode(function(n) { n.selected = false; });
function path(node) {
if(node == null || node.selected) return;
node.selected = true;
$.each(that.group.getSiblings([node])[node.id],
function(n) {
n.exist = true;
n.drawn = true;
});
var parents = node.getParents();
parents = (parents.length > 0)? parents[0] : null;
path(parents);
};
for(var i=0, ns = [node.id].concat(nodesInPath); i < ns.length; i++) {
path(this.graph.getNode(ns[i]));
}
},
/*
Method: setRoot
Switches the current root node. Changes the topology of the Tree.
Parameters:
id - (string) The id of the node to be set as root.
method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
onComplete - (optional|object) An action to perform after the animation (if any).
Example:
(start code js)
st.setRoot('nodeId', 'animate', {
onComplete: function() {
alert('complete!');
}
});
(end code)
*/
setRoot: function(id, method, onComplete) {
if(this.busy) return;
this.busy = true;
var that = this, canvas = this.canvas;
var rootNode = this.graph.getNode(this.root);
var clickedNode = this.graph.getNode(id);
function $setRoot() {
if(this.config.multitree && clickedNode.data.$orn) {
var orn = clickedNode.data.$orn;
var opp = {
'left': 'right',
'right': 'left',
'top': 'bottom',
'bottom': 'top'
}[orn];
rootNode.data.$orn = opp;
(function tag(rootNode) {
rootNode.eachSubnode(function(n) {
if(n.id != id) {
n.data.$orn = opp;
tag(n);
}
});
})(rootNode);
delete clickedNode.data.$orn;
}
this.root = id;
this.clickedNode = clickedNode;
this.graph.computeLevels(this.root, 0, "ignore");
this.geom.setRightLevelToShow(clickedNode, canvas, {
execHide: false,
onShow: function(node) {
if(!node.drawn) {
node.drawn = true;
node.setData('alpha', 1, 'end');
node.setData('alpha', 0);
node.pos.setc(clickedNode.pos.x, clickedNode.pos.y);
}
}
});
this.compute('end');
this.busy = true;
this.fx.animate({
modes: ['linear', 'node-property:alpha'],
onComplete: function() {
that.busy = false;
that.onClick(id, {
onComplete: function() {
onComplete && onComplete.onComplete();
}
});
}
});
}
// delete previous orientations (if any)
delete rootNode.data.$orns;
if(method == 'animate') {
$setRoot.call(this);
that.selectPath(clickedNode);
} else if(method == 'replot') {
$setRoot.call(this);
this.select(this.root);
}
},
/*
Method: addSubtree
Adds a subtree.
Parameters:
subtree - (object) A JSON Tree object. See also <Loader.loadJSON>.
method - (string) Set this to "animate" if you want to animate the tree after adding the subtree. You can also set this parameter to "replot" to just replot the subtree.
onComplete - (optional|object) An action to perform after the animation (if any).
Example:
(start code js)
st.addSubtree(json, 'animate', {
onComplete: function() {
alert('complete!');
}
});
(end code)
*/
addSubtree: function(subtree, method, onComplete) {
if(method == 'replot') {
this.op.sum(subtree, $.extend({ type: 'replot' }, onComplete || {}));
} else if (method == 'animate') {
this.op.sum(subtree, $.extend({ type: 'fade:seq' }, onComplete || {}));
}
},
/*
Method: removeSubtree
Removes a subtree.
Parameters:
id - (string) The _id_ of the subtree to be removed.
removeRoot - (boolean) Default's *false*. Remove the root of the subtree or only its subnodes.
method - (string) Set this to "animate" if you want to animate the tree after removing the subtree. You can also set this parameter to "replot" to just replot the subtree.
onComplete - (optional|object) An action to perform after the animation (if any).
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
that.graph.eachNode(function(n) {
var pos = n.pos.getc(true);
n.startPos.setc(pos.x, pos.y);
n.endPos.setc(pos.x, pos.y);
n.visited = false;
});
var offset = { x: complete.offsetX, y: complete.offsetY };
that.geom.translate(node.endPos.add(offset).$scale(-1), ["start", "current", "end"]);
group.show(getNodesToShow.call(that));
that.plot();
complete.onAfterCompute(that.clickedNode);
complete.onComplete();
}
});
},
/*
Method: onClick
Animates the <ST> to center the node specified by *id*.
Parameters:
id - (string) A node id.
options - (optional|object) A group of options and callbacks described below.
onComplete - (object) An object callback called when the animation finishes.
Move - (object) An object that has as properties _offsetX_ or _offsetY_ for adding some offset position to the centered node.
Example:
(start code js)
st.onClick('mynodeid', {
Move: {
enable: true,
offsetX: 30,
offsetY: 5
},
onComplete: function() {
alert('yay!');
}
});
(end code)
*/
onClick: function (id, options) {
var canvas = this.canvas, that = this, Geom = this.geom, config = this.config;
var innerController = {
Move: {
enable: true,
offsetX: config.offsetX || 0,
offsetY: config.offsetY || 0
},
setRightLevelToShowConfig: false,
onBeforeRequest: $.empty,
onBeforeContract: $.empty,
onBeforeMove: $.empty,
onBeforeExpand: $.empty
};
var complete = $.merge(this.controller, innerController, options);
if(!this.busy) {
this.busy = true;
var node = this.graph.getNode(id);
this.selectPath(node, this.clickedNode);
this.clickedNode = node;
complete.onBeforeCompute(node);
complete.onBeforeRequest(node);
this.requestNodes(node, {
onComplete: function() {
complete.onBeforeContract(node);
that.contract({
onComplete: function() {
Geom.setRightLevelToShow(node, canvas, complete.setRightLevelToShowConfig);
complete.onBeforeMove(node);
that.move(node, {
Move: complete.Move,
onComplete: function() {
complete.onBeforeExpand(node);
that.expand(node, {
onComplete: function() {
that.busy = false;
complete.onAfterCompute(id);
complete.onComplete();
}
}); // expand
}
}); // move
}
});// contract
}
});// request
}
}
});
})();
$jit.ST.$extend = true;
/*
Class: ST.Op
Custom extension of <Graph.Op>.
Extends:
All <Graph.Op> methods
See also:
<Graph.Op>
*/
$jit.ST.Op = new Class({
Implements: Graph.Op
});
/*
Performs operations on group of nodes.
*/
$jit.ST.Group = new Class({
initialize: function(viz) {
this.viz = viz;
this.canvas = viz.canvas;
this.config = viz.config;
this.animation = new Animation;
this.nodes = null;
},
/*
Calls the request method on the controller to request a subtree for each node.
*/
requestNodes: function(nodes, controller) {
var counter = 0, len = nodes.length, nodeSelected = {};
var complete = function() { controller.onComplete(); };
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
ctx.textBaseline = 'middle';
var aggValue = aggregates(node.name, valLeft, valRight, node, valAcum);
if(aggValue !== false) {
ctx.fillText(aggValue !== true? aggValue : valAcum, x, y - acumLeft - config.labelOffset - label.size/2, width);
}
if(showLabels(node.name, valLeft, valRight, node)) {
ctx.fillText(node.name, x, y + label.size/2 + config.labelOffset);
}
ctx.restore();
}
}
},
'contains': function(node, mpos) {
var pos = node.pos.getc(true),
width = node.getData('width'),
height = node.getData('height'),
algnPos = this.getAlignedPos(pos, width, height),
x = algnPos.x, y = algnPos.y,
dimArray = node.getData('dimArray'),
rx = mpos.x - x;
//bounding box check
if(mpos.x < x || mpos.x > x + width
|| mpos.y > y || mpos.y < y - height) {
return false;
}
//deep check
for(var i=0, l=dimArray.length, lAcum=y, rAcum=y; i<l; i++) {
var dimi = dimArray[i];
lAcum -= dimi[0];
rAcum -= dimi[1];
var intersec = lAcum + (rAcum - lAcum) * rx / width;
if(mpos.y >= intersec) {
var index = +(rx > width/2);
return {
'name': node.getData('stringArray')[i],
'color': node.getData('colorArray')[i],
'value': node.getData('valueArray')[i][index],
'index': index
};
}
}
return false;
}
}
});
/*
Class: AreaChart
A visualization that displays stacked area charts.
Constructor Options:
See <Options.AreaChart>.
*/
$jit.AreaChart = new Class({
st: null,
colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
selected: {},
busy: false,
initialize: function(opt) {
this.controller = this.config =
$.merge(Options("Canvas", "Margin", "Label", "AreaChart"), {
Label: { type: 'Native' }
}, opt);
//set functions for showLabels and showAggregates
var showLabels = this.config.showLabels,
typeLabels = $.type(showLabels),
showAggregates = this.config.showAggregates,
typeAggregates = $.type(showAggregates);
this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
this.initializeViz();
},
initializeViz: function() {
var config = this.config,
that = this,
nodeType = config.type.split(":")[0],
nodeLabels = {};
var delegate = new $jit.ST({
injectInto: config.injectInto,
width: config.width,
height: config.height,
orientation: "bottom",
levelDistance: 0,
siblingOffset: 0,
subtreeOffset: 0,
withLabels: config.Label.type != 'Native',
useCanvas: config.useCanvas,
Label: {
type: config.Label.type
},
Node: {
overridable: true,
type: 'areachart-' + nodeType,
align: 'left',
width: 1,
height: 1
},
Edge: {
type: 'none'
},
Tips: {
enable: config.Tips.enable,
type: 'Native',
force: true,
onShow: function(tip, node, contains) {
var elem = contains;
config.Tips.onShow(tip, elem, node);
}
},
Events: {
enable: true,
type: 'Native',
onClick: function(node, eventInfo, evt) {
if(!config.filterOnClick && !config.Events.enable) return;
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
ch.push({
'id': prefix + val.label,
'name': val.label,
'data': {
'value': valArray,
'$valueArray': valArray,
'$colorArray': color,
'$stringArray': name,
'$next': next.label,
'$prev': prev? prev.label:false,
'$config': config,
'$gradient': gradient
},
'children': []
});
}
var root = {
'id': prefix + '$root',
'name': '',
'data': {
'$type': 'none',
'$width': 1,
'$height': 1
},
'children': ch
};
delegate.loadJSON(root);
this.normalizeDims();
delegate.compute();
delegate.select(delegate.root);
if(animate) {
delegate.fx.animate({
modes: ['node-property:height:dimArray'],
duration:1500
});
}
},
/*
Method: updateJSON
Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
Parameters:
json - (object) JSON data to be updated. The JSON format corresponds to the one described in <AreaChart.loadJSON>.
onComplete - (object) A callback object to be called when the animation transition when updating the data end.
Example:
(start code js)
areaChart.updateJSON(json, {
onComplete: function() {
alert('update complete!');
}
});
(end code)
*/
updateJSON: function(json, onComplete) {
if(this.busy) return;
this.busy = true;
var delegate = this.delegate,
graph = delegate.graph,
labels = json.label && $.splat(json.label),
values = json.values,
animate = this.config.animate,
that = this,
hashValues = {};
//convert the whole thing into a hash
for (var i = 0, l = values.length; i < l; i++) {
hashValues[values[i].label] = values[i];
}
graph.eachNode(function(n) {
var v = hashValues[n.name],
stringArray = n.getData('stringArray'),
valArray = n.getData('valueArray'),
next = n.getData('next');
if (v) {
v.values = $.splat(v.values);
$.each(valArray, function(a, i) {
a[0] = v.values[i];
if(labels) stringArray[i] = labels[i];
});
n.setData('valueArray', valArray);
}
if(next) {
v = hashValues[next];
if(v) {
$.each(valArray, function(a, i) {
a[1] = v.values[i];
});
}
}
});
this.normalizeDims();
delegate.compute();
delegate.select(delegate.root);
if(animate) {
delegate.fx.animate({
modes: ['node-property:height:dimArray'],
duration:1500,
onComplete: function() {
that.busy = false;
onComplete && onComplete.onComplete();
}
});
}
},
/*
Method: filter
Filter selected stacks, collapsing all other stacks. You can filter multiple stacks at the same time.
Parameters:
filters - (array) An array of strings with the name of the stacks to be filtered.
callback - (object) An object with an *onComplete* callback method.
Example:
(start code js)
areaChart.filter(['label A', 'label C'], {
onComplete: function() {
console.log('done!');
}
});
(end code)
See also:
<AreaChart.restore>.
*/
filter: function(filters, callback) {
if(this.busy) return;
this.busy = true;
if(this.config.Tips.enable) this.delegate.tips.hide();
this.select(false, false, false);
var args = $.splat(filters);
var rt = this.delegate.graph.getNode(this.delegate.root);
var that = this;
this.normalizeDims();
rt.eachAdjacency(function(adj) {
var n = adj.nodeTo,
dimArray = n.getData('dimArray', 'end'),
stringArray = n.getData('stringArray');
n.setData('dimArray', $.map(dimArray, function(d, i) {
return ($.indexOf(args, stringArray[i]) > -1)? d:[0, 0];
}), 'end');
});
this.delegate.fx.animate({
modes: ['node-property:dimArray'],
duration:1500,
onComplete: function() {
that.busy = false;
callback && callback.onComplete();
}
});
},
/*
Method: restore
Sets all stacks that could have been filtered visible.
Example:
(start code js)
areaChart.restore();
(end code)
See also:
<AreaChart.filter>.
*/
restore: function(callback) {
if(this.busy) return;
this.busy = true;
if(this.config.Tips.enable) this.delegate.tips.hide();
this.select(false, false, false);
this.normalizeDims();
var that = this;
this.delegate.fx.animate({
modes: ['node-property:height:dimArray'],
duration:1500,
onComplete: function() {
that.busy = false;
callback && callback.onComplete();
}
});
},
//adds the little brown bar when hovering the node
select: function(id, name, index) {
if(!this.config.selectOnHover) return;
var s = this.selected;
if(s.id != id || s.name != name
|| s.index != index) {
s.id = id;
s.name = name;
s.index = index;
this.delegate.graph.eachNode(function(n) {
n.setData('border', false);
});
if(id) {
var n = this.delegate.graph.getNode(id);
n.setData('border', s);
var link = index === 0? 'prev':'next';
link = n.getData(link);
if(link) {
n = this.delegate.graph.getByName(link);
if(n) {
n.setData('border', {
name: name,
index: 1-index
});
}
}
}
this.delegate.plot();
}
},
/*
Method: getLegend
Returns an object containing as keys the legend names and as values hex strings with color values.
Example:
(start code js)
var legend = areaChart.getLegend();
(end code)
*/
getLegend: function() {
var legend = {};
var n;
this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
n = adj.nodeTo;
});
var colors = n.getData('colorArray'),
len = colors.length;
$.each(n.getData('stringArray'), function(s, i) {
legend[s] = colors[i % len];
});
return legend;
},
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
config = node.getData('config'),
rx = mpos.x - x,
horz = config.orientation == 'horizontal',
fixedDim = (horz? height : width) / len;
//bounding box check
if(horz) {
if(mpos.x < x || mpos.x > x + width
|| mpos.y > y + height || mpos.y < y) {
return false;
}
} else {
if(mpos.x < x || mpos.x > x + width
|| mpos.y > y || mpos.y < y - height) {
return false;
}
}
//deep check
for(var i=0, l=dimArray.length; i<l; i++) {
var dimi = dimArray[i];
if(horz) {
var limit = y + fixedDim * i;
if(mpos.x <= x+ dimi && mpos.y >= limit && mpos.y <= limit + fixedDim) {
return {
'name': node.getData('stringArray')[i],
'color': node.getData('colorArray')[i],
'value': node.getData('valueArray')[i],
'label': node.name
};
}
} else {
var limit = x + fixedDim * i;
if(mpos.x >= limit && mpos.x <= limit + fixedDim && mpos.y >= y - dimi) {
return {
'name': node.getData('stringArray')[i],
'color': node.getData('colorArray')[i],
'value': node.getData('valueArray')[i],
'label': node.name
};
}
}
}
return false;
}
}
});
/*
Class: BarChart
A visualization that displays stacked bar charts.
Constructor Options:
See <Options.BarChart>.
*/
$jit.BarChart = new Class({
st: null,
colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
selected: {},
busy: false,
initialize: function(opt) {
this.controller = this.config =
$.merge(Options("Canvas", "Margin", "Label", "BarChart"), {
Label: { type: 'Native' }
}, opt);
//set functions for showLabels and showAggregates
var showLabels = this.config.showLabels,
typeLabels = $.type(showLabels),
showAggregates = this.config.showAggregates,
typeAggregates = $.type(showAggregates);
this.config.showLabels = typeLabels == 'function'? showLabels : $.lambda(showLabels);
this.config.showAggregates = typeAggregates == 'function'? showAggregates : $.lambda(showAggregates);
this.initializeViz();
},
initializeViz: function() {
var config = this.config, that = this;
var nodeType = config.type.split(":")[0],
horz = config.orientation == 'horizontal',
nodeLabels = {};
var delegate = new $jit.ST({
injectInto: config.injectInto,
width: config.width,
height: config.height,
orientation: horz? 'left' : 'bottom',
levelDistance: 0,
siblingOffset: config.barsOffset,
subtreeOffset: 0,
withLabels: config.Label.type != 'Native',
useCanvas: config.useCanvas,
Label: {
type: config.Label.type
},
Node: {
overridable: true,
type: 'barchart-' + nodeType,
align: 'left',
width: 1,
height: 1
},
Edge: {
type: 'none'
},
Tips: {
enable: config.Tips.enable,
type: 'Native',
force: true,
onShow: function(tip, node, contains) {
var elem = contains;
config.Tips.onShow(tip, elem, node);
}
},
Events: {
enable: true,
type: 'Native',
onClick: function(node, eventInfo, evt) {
if(!config.Events.enable) return;
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
}
if(config.showLabels(node.name, acum, node)) {
labelStyle.display = '';
} else {
labelStyle.display = 'none';
}
var aggValue = config.showAggregates(node.name, acum, node);
if(aggValue !== false) {
aggregateStyle.display = '';
} else {
aggregateStyle.display = 'none';
}
if(config.orientation == 'horizontal') {
aggregateStyle.textAlign = 'right';
labelStyle.textAlign = 'left';
labelStyle.textIndex = aggregateStyle.textIndent = config.labelOffset + 'px';
aggregateStyle.top = labelStyle.top = (height-font)/2 + 'px';
domElement.style.height = wrapperStyle.height = height + 'px';
} else {
aggregateStyle.top = (-font - config.labelOffset) + 'px';
labelStyle.top = (config.labelOffset + height) + 'px';
domElement.style.top = parseInt(domElement.style.top, 10) - height + 'px';
domElement.style.height = wrapperStyle.height = height + 'px';
}
labels.aggregate.innerHTML = aggValue !== true? aggValue : acum;
}
}
});
var size = delegate.canvas.getSize(),
margin = config.Margin;
if(horz) {
delegate.config.offsetX = size.width/2 - margin.left
- (config.showLabels && (config.labelOffset + config.Label.size));
delegate.config.offsetY = (margin.bottom - margin.top)/2;
} else {
delegate.config.offsetY = -size.height/2 + margin.bottom
+ (config.showLabels && (config.labelOffset + config.Label.size));
delegate.config.offsetX = (margin.right - margin.left)/2;
}
this.delegate = delegate;
this.canvas = this.delegate.canvas;
},
/*
Method: loadJSON
Loads JSON data into the visualization.
Parameters:
json - The JSON data format. This format is described in <http://blog.thejit.org/2010/04/24/new-javascript-infovis-toolkit-visualizations/#json-data-format>.
Example:
(start code js)
var barChart = new $jit.BarChart(options);
barChart.loadJSON(json);
(end code)
*/
loadJSON: function(json) {
if(this.busy) return;
this.busy = true;
var prefix = $.time(),
ch = [],
delegate = this.delegate,
name = $.splat(json.label),
color = $.splat(json.color || this.colors),
config = this.config,
gradient = !!config.type.split(":")[1],
animate = config.animate,
horz = config.orientation == 'horizontal',
that = this;
for(var i=0, values=json.values, l=values.length; i<l; i++) {
var val = values[i]
var valArray = $.splat(values[i].values);
var acum = 0;
ch.push({
'id': prefix + val.label,
'name': val.label,
'data': {
'value': valArray,
'$valueArray': valArray,
'$colorArray': color,
'$stringArray': name,
'$gradient': gradient,
'$config': config
},
'children': []
});
}
var root = {
'id': prefix + '$root',
'name': '',
'data': {
'$type': 'none',
'$width': 1,
'$height': 1
},
'children': ch
};
delegate.loadJSON(root);
this.normalizeDims();
delegate.compute();
delegate.select(delegate.root);
if(animate) {
if(horz) {
delegate.fx.animate({
modes: ['node-property:width:dimArray'],
duration:1500,
onComplete: function() {
that.busy = false;
}
});
} else {
delegate.fx.animate({
modes: ['node-property:height:dimArray'],
duration:1500,
onComplete: function() {
that.busy = false;
}
});
}
} else {
this.busy = false;
}
},
/*
Method: updateJSON
Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
Parameters:
json - (object) JSON data to be updated. The JSON format corresponds to the one described in <BarChart.loadJSON>.
onComplete - (object) A callback object to be called when the animation transition when updating the data end.
Example:
(start code js)
barChart.updateJSON(json, {
onComplete: function() {
alert('update complete!');
}
});
(end code)
*/
updateJSON: function(json, onComplete) {
if(this.busy) return;
this.busy = true;
this.select(false, false, false);
var delegate = this.delegate;
var graph = delegate.graph;
var values = json.values;
var animate = this.config.animate;
var that = this;
var horz = this.config.orientation == 'horizontal';
$.each(values, function(v) {
var n = graph.getByName(v.label);
if(n) {
n.setData('valueArray', $.splat(v.values));
if(json.label) {
n.setData('stringArray', $.splat(json.label));
}
}
});
this.normalizeDims();
delegate.compute();
delegate.select(delegate.root);
if(animate) {
if(horz) {
delegate.fx.animate({
modes: ['node-property:width:dimArray'],
duration:1500,
onComplete: function() {
that.busy = false;
onComplete && onComplete.onComplete();
}
});
} else {
delegate.fx.animate({
modes: ['node-property:height:dimArray'],
duration:1500,
onComplete: function() {
that.busy = false;
onComplete && onComplete.onComplete();
}
});
}
}
},
//adds the little brown bar when hovering the node
select: function(id, name) {
if(!this.config.hoveredColor) return;
var s = this.selected;
if(s.id != id || s.name != name) {
s.id = id;
s.name = name;
s.color = this.config.hoveredColor;
this.delegate.graph.eachNode(function(n) {
if(id == n.id) {
n.setData('border', s);
} else {
n.setData('border', false);
}
});
this.delegate.plot();
}
},
/*
Method: getLegend
Returns an object containing as keys the legend names and as values hex strings with color values.
Example:
(start code js)
var legend = barChart.getLegend();
(end code)
*/
getLegend: function() {
var legend = {};
var n;
this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
n = adj.nodeTo;
});
var colors = n.getData('colorArray'),
len = colors.length;
$.each(n.getData('stringArray'), function(s, i) {
legend[s] = colors[i % len];
});
return legend;
},
/*
Method: getMaxValue
Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
Example:
(start code js)
var ans = barChart.getMaxValue();
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
labels - Access a <Sunburst.Label> interface implementation.
*/
$jit.Sunburst = new Class({
Implements: [ Loader, Extras, Layouts.Radial ],
initialize: function(controller) {
var $Sunburst = $jit.Sunburst;
var config = {
interpolation: 'linear',
levelDistance: 100,
Node: {
'type': 'multipie',
'height':0
},
Edge: {
'type': 'none'
},
Label: {
textAlign: 'start',
textBaseline: 'middle'
}
};
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': Polar,
'Node': {
'selected': false,
'exist': true,
'drawn': true
}
};
this.graph = new Graph(this.graphOptions, this.config.Node,
this.config.Edge);
this.labels = new $Sunburst.Label[canvasConfig.Label.type](this);
this.fx = new $Sunburst.Plot(this, $Sunburst);
this.op = new $Sunburst.Op(this);
this.json = null;
this.root = null;
this.rotated = null;
this.busy = false;
// initialize extras
this.initializeExtras();
},
/*
createLevelDistanceFunc
Returns the levelDistance function used for calculating a node distance
to its origin. This function returns a function that is computed
per level and not per node, such that all nodes with the same depth will have the
same distance to the origin. The resulting function gets the
parent node as parameter and returns a float.
*/
createLevelDistanceFunc: function() {
var ld = this.config.levelDistance;
return function(elem) {
return (elem._depth + 1) * ld;
};
},
/*
Method: refresh
Computes positions and plots the tree.
*/
refresh: function() {
this.compute();
this.plot();
},
/*
reposition
An alias for computing new positions to _endPos_
See also:
<Sunburst.compute>
*/
reposition: function() {
this.compute('end');
},
/*
Method: rotate
Rotates the graph so that the selected node is horizontal on the right.
Parameters:
node - (object) A <Graph.Node>.
method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
opt - (object) Configuration options merged with this visualization configuration options.
See also:
<Sunburst.rotateAngle>
*/
rotate: function(node, method, opt) {
var theta = node.getPos(opt.property || 'current').getp(true).theta;
this.rotated = node;
this.rotateAngle(-theta, method, opt);
},
/*
Method: rotateAngle
Rotates the graph of an angle theta.
Parameters:
node - (object) A <Graph.Node>.
method - (string) Whether to perform an animation or just replot the graph. Possible values are "replot" or "animate".
opt - (object) Configuration options merged with this visualization configuration options.
See also:
<Sunburst.rotate>
*/
rotateAngle: function(theta, method, opt) {
var that = this;
var options = $.merge(this.config, opt || {}, {
modes: [ 'polar' ]
});
var prop = opt.property || (method === "animate" ? 'end' : 'current');
if(method === 'animate') {
this.fx.animation.pause();
}
this.graph.eachNode(function(n) {
var p = n.getPos(prop);
p.theta += theta;
if (p.theta < 0) {
p.theta += Math.PI * 2;
}
});
if (method == 'animate') {
this.fx.animate(options);
} else if (method == 'replot') {
this.fx.plot();
this.busy = false;
}
},
/*
Method: plot
Plots the Sunburst. This is a shortcut to *fx.plot*.
*/
plot: function() {
this.fx.plot();
}
});
$jit.Sunburst.$extend = true;
(function(Sunburst) {
/*
Class: Sunburst.Op
Custom extension of <Graph.Op>.
Extends:
All <Graph.Op> methods
See also:
<Graph.Op>
*/
Sunburst.Op = new Class( {
Implements: Graph.Op
});
/*
Class: Sunburst.Plot
Custom extension of <Graph.Plot>.
Extends:
All <Graph.Plot> methods
See also:
<Graph.Plot>
*/
Sunburst.Plot = new Class( {
Implements: Graph.Plot
});
/*
Class: Sunburst.Label
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
ctx.fillStyle = ctx.strokeStyle = label.color;
var scale = resizeLabels? node.getData('normalizedDim') : 1,
fontSize = (label.size * scale) >> 0;
fontSize = fontSize < +resizeLabels? +resizeLabels : fontSize;
ctx.font = label.style + ' ' + fontSize + 'px ' + label.family;
ctx.textBaseline = 'middle';
ctx.textAlign = 'center';
polar.rho = acum + config.labelOffset + config.sliceOffset;
polar.theta = node.pos.theta;
var cart = polar.getc(true);
ctx.fillText(node.name, cart.x, cart.y);
ctx.restore();
}
}
},
'contains': function(node, pos) {
if (this.nodeTypes['none'].anglecontains.call(this, node, pos)) {
var rho = Math.sqrt(pos.x * pos.x + pos.y * pos.y);
var ld = this.config.levelDistance, d = node._depth;
var config = node.getData('config');
if(rho <=ld * d + config.sliceOffset) {
var dimArray = node.getData('dimArray');
for(var i=0,l=dimArray.length,acum=config.sliceOffset; i<l; i++) {
var dimi = dimArray[i];
if(rho >= acum && rho <= acum + dimi) {
return {
name: node.getData('stringArray')[i],
color: node.getData('colorArray')[i],
value: node.getData('valueArray')[i],
label: node.name
};
}
acum += dimi;
}
}
return false;
}
return false;
}
}
});
/*
Class: PieChart
A visualization that displays stacked bar charts.
Constructor Options:
See <Options.PieChart>.
*/
$jit.PieChart = new Class({
sb: null,
colors: ["#416D9C", "#70A35E", "#EBB056", "#C74243", "#83548B", "#909291", "#557EAA"],
selected: {},
busy: false,
initialize: function(opt) {
this.controller = this.config =
$.merge(Options("Canvas", "PieChart", "Label"), {
Label: { type: 'Native' }
}, opt);
this.initializeViz();
},
initializeViz: function() {
var config = this.config, that = this;
var nodeType = config.type.split(":")[0];
var delegate = new $jit.Sunburst({
injectInto: config.injectInto,
width: config.width,
height: config.height,
useCanvas: config.useCanvas,
withLabels: config.Label.type != 'Native',
Label: {
type: config.Label.type
},
Node: {
overridable: true,
type: 'piechart-' + nodeType,
width: 1,
height: 1
},
Edge: {
type: 'none'
},
Tips: {
enable: config.Tips.enable,
type: 'Native',
force: true,
onShow: function(tip, node, contains) {
var elem = contains;
config.Tips.onShow(tip, elem, node);
}
},
Events: {
enable: true,
type: 'Native',
onClick: function(node, eventInfo, evt) {
if(!config.Events.enable) return;
var elem = eventInfo.getContains();
config.Events.onClick(elem, eventInfo, evt);
},
onMouseMove: function(node, eventInfo, evt) {
if(!config.hoveredColor) return;
if(node) {
var elem = eventInfo.getContains();
that.select(node.id, elem.name, elem.index);
} else {
that.select(false, false, false);
}
}
},
onCreateLabel: function(domElement, node) {
var labelConf = config.Label;
if(config.showLabels) {
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
var val = values[i];
var valArray = $.splat(val.values);
ch.push({
'id': prefix + val.label,
'name': val.label,
'data': {
'value': valArray,
'$valueArray': valArray,
'$colorArray': mono? $.splat(color[i % colorLength]) : color,
'$stringArray': name,
'$gradient': gradient,
'$config': config,
'$angularWidth': $.reduce(valArray, function(x,y){return x+y;})
},
'children': []
});
}
var root = {
'id': prefix + '$root',
'name': '',
'data': {
'$type': 'none',
'$width': 1,
'$height': 1
},
'children': ch
};
delegate.loadJSON(root);
this.normalizeDims();
delegate.refresh();
if(animate) {
delegate.fx.animate({
modes: ['node-property:dimArray'],
duration:1500
});
}
},
/*
Method: updateJSON
Use this method when updating values for the current JSON data. If the items specified by the JSON data already exist in the graph then their values will be updated.
Parameters:
json - (object) JSON data to be updated. The JSON format corresponds to the one described in <PieChart.loadJSON>.
onComplete - (object) A callback object to be called when the animation transition when updating the data end.
Example:
(start code js)
pieChart.updateJSON(json, {
onComplete: function() {
alert('update complete!');
}
});
(end code)
*/
updateJSON: function(json, onComplete) {
if(this.busy) return;
this.busy = true;
var delegate = this.delegate;
var graph = delegate.graph;
var values = json.values;
var animate = this.config.animate;
var that = this;
$.each(values, function(v) {
var n = graph.getByName(v.label),
vals = $.splat(v.values);
if(n) {
n.setData('valueArray', vals);
n.setData('angularWidth', $.reduce(vals, function(x,y){return x+y;}));
if(json.label) {
n.setData('stringArray', $.splat(json.label));
}
}
});
this.normalizeDims();
if(animate) {
delegate.compute('end');
delegate.fx.animate({
modes: ['node-property:dimArray:span', 'linear'],
duration:1500,
onComplete: function() {
that.busy = false;
onComplete && onComplete.onComplete();
}
});
} else {
delegate.refresh();
}
},
//adds the little brown bar when hovering the node
select: function(id, name) {
if(!this.config.hoveredColor) return;
var s = this.selected;
if(s.id != id || s.name != name) {
s.id = id;
s.name = name;
s.color = this.config.hoveredColor;
this.delegate.graph.eachNode(function(n) {
if(id == n.id) {
n.setData('border', s);
} else {
n.setData('border', false);
}
});
this.delegate.plot();
}
},
/*
Method: getLegend
Returns an object containing as keys the legend names and as values hex strings with color values.
Example:
(start code js)
var legend = pieChart.getLegend();
(end code)
*/
getLegend: function() {
var legend = {};
var n;
this.delegate.graph.getNode(this.delegate.root).eachAdjacency(function(adj) {
n = adj.nodeTo;
});
var colors = n.getData('colorArray'),
len = colors.length;
$.each(n.getData('stringArray'), function(s, i) {
legend[s] = colors[i % len];
});
return legend;
},
/*
Method: getMaxValue
Returns the maximum accumulated value for the stacks. This method is used for normalizing the graph heights according to the canvas height.
Example:
(start code js)
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
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.config.Label);
this.labels = new $jit.Icicle.Label[this.config.Label.type](this);
this.fx = new $jit.Icicle.Plot(this, $jit.Icicle);
this.op = new $jit.Icicle.Op(this);
this.group = new $jit.Icicle.Group(this);
this.clickedNode = null;
this.initializeExtras();
},
/*
Method: refresh
Computes positions and plots the tree.
*/
refresh: function(){
var labelType = this.config.Label.type;
if(labelType != 'Native') {
var that = this;
this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
}
this.compute();
this.plot();
},
/*
Method: plot
Plots the Icicle visualization. This is a shortcut to *fx.plot*.
*/
plot: function(){
this.fx.plot(this.config);
},
/*
Method: enter
Sets the node as root.
Parameters:
node - (object) A <Graph.Node>.
*/
enter: function (node) {
if (this.busy)
return;
this.busy = true;
var that = this,
config = this.config;
var callback = {
onComplete: function() {
//compute positions of newly inserted nodes
if(config.request)
that.compute();
if(config.animate) {
that.graph.nodeList.setDataset(['current', 'end'], {
'alpha': [1, 0] //fade nodes
});
Graph.Util.eachSubgraph(node, function(n) {
n.setData('alpha', 1, 'end');
}, "ignore");
that.fx.animate({
duration: 500,
modes:['node-property:alpha'],
onComplete: function() {
that.clickedNode = node;
that.compute('end');
that.fx.animate({
modes:['linear', 'node-property:width:height'],
duration: 1000,
onComplete: function() {
that.busy = false;
that.clickedNode = node;
}
});
}
});
} else {
that.clickedNode = node;
that.busy = false;
that.refresh();
}
}
};
if(config.request) {
this.requestNodes(clickedNode, callback);
} else {
callback.onComplete();
}
},
/*
Method: out
Sets the parent node of the current selected node as root.
*/
out: function(){
if(this.busy)
return;
var that = this,
GUtil = Graph.Util,
config = this.config,
graph = this.graph,
parents = GUtil.getParents(graph.getNode(this.clickedNode && this.clickedNode.id || this.root)),
parent = parents[0],
clickedNode = parent,
previousClickedNode = this.clickedNode;
this.busy = true;
this.events.hoveredNode = false;
if(!parent) {
this.busy = false;
return;
}
//final plot callback
callback = {
onComplete: function() {
that.clickedNode = parent;
if(config.request) {
that.requestNodes(parent, {
onComplete: function() {
that.compute();
that.plot();
that.busy = false;
}
});
} else {
that.compute();
that.plot();
that.busy = false;
}
}
};
//animate node positions
if(config.animate) {
this.clickedNode = clickedNode;
this.compute('end');
//animate the visible subtree only
this.clickedNode = previousClickedNode;
this.fx.animate({
modes:['linear', 'node-property:width:height'],
duration: 1000,
onComplete: function() {
//animate the parent subtree
that.clickedNode = clickedNode;
//change nodes alpha
graph.nodeList.setDataset(['current', 'end'], {
'alpha': [0, 1]
});
GUtil.eachSubgraph(previousClickedNode, function(node) {
node.setData('alpha', 1);
}, "ignore");
that.fx.animate({
duration: 500,
modes:['node-property:alpha'],
onComplete: function() {
callback.onComplete();
}
});
}
});
} else {
callback.onComplete();
}
},
requestNodes: function(node, onComplete){
var handler = $.merge(this.controller, onComplete),
levelsToShow = this.config.constrained ? this.config.levelsToShow : Number.MAX_VALUE;
if (handler.request) {
var leaves = [], d = node._depth;
Graph.Util.eachLevel(node, 0, levelsToShow, function(n){
if (n.drawn && !Graph.Util.anySubnode(n)) {
leaves.push(n);
n._level = n._depth - d;
if (this.config.constrained)
n._level = levelsToShow - n._level;
}
});
this.group.requestNodes(leaves, handler);
} else {
handler.onComplete();
}
}
});
/*
Class: Icicle.Op
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
- <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();
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
orientation: "h",
titleHeight: 13,
offset: 2,
levelsToShow: 0,
constrained: false,
animate: false,
Node: {
type: 'rectangle',
overridable: true,
//we all know why this is not zero,
//right, Firefox?
width: 3,
height: 3,
color: '#444'
},
Label: {
textAlign: 'center',
textBaseline: 'top'
},
Edge: {
type: 'none'
},
duration: 700,
fps: 45
};
this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
"Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "Label"), config, controller);
this.layout.orientation = this.config.orientation;
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 TM.Label[canvasConfig.Label.type](this);
this.fx = new TM.Plot(this);
this.op = new TM.Op(this);
this.group = new TM.Group(this);
this.geom = new TM.Geom(this);
this.clickedNode = null;
this.busy = false;
// initialize extras
this.initializeExtras();
},
/*
Method: refresh
Computes positions and plots the tree.
*/
refresh: function(){
if(this.busy) return;
this.busy = true;
var that = this;
if(this.config.animate) {
this.compute('end');
this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
&& this.clickedNode.id || this.root));
this.fx.animate($.merge(this.config, {
modes: ['linear', 'node-property:width:height'],
onComplete: function() {
that.busy = false;
}
}));
} else {
var labelType = this.config.Label.type;
if(labelType != 'Native') {
var that = this;
this.graph.eachNode(function(n) { that.labels.hideLabel(n, false); });
}
this.busy = false;
this.compute();
this.config.levelsToShow > 0 && this.geom.setRightLevelToShow(this.graph.getNode(this.clickedNode
&& this.clickedNode.id || this.root));
this.plot();
}
},
/*
Method: plot
Plots the TreeMap. This is a shortcut to *fx.plot*.
*/
plot: function(){
this.fx.plot();
},
/*
Method: leaf
Returns whether the node is a leaf.
Parameters:
n - (object) A <Graph.Node>.
*/
leaf: function(n){
return n.getSubnodes([
1, 1
], "ignore").length == 0;
},
/*
Method: enter
Sets the node as root.
Parameters:
n - (object) A <Graph.Node>.
*/
enter: function(n){
if(this.busy) return;
this.busy = true;
var that = this,
config = this.config,
graph = this.graph,
clickedNode = n,
previousClickedNode = this.clickedNode;
var callback = {
onComplete: function() {
//ensure that nodes are shown for that level
if(config.levelsToShow > 0) {
that.geom.setRightLevelToShow(n);
}
//compute positions of newly inserted nodes
if(config.levelsToShow > 0 || config.request) that.compute();
if(config.animate) {
//fade nodes
graph.nodeList.setData('alpha', 0, 'end');
n.eachSubgraph(function(n) {
n.setData('alpha', 1, 'end');
}, "ignore");
that.fx.animate({
duration: 500,
modes:['node-property:alpha'],
onComplete: function() {
//compute end positions
that.clickedNode = clickedNode;
that.compute('end');
//animate positions
//TODO(nico) commenting this line didn't seem to throw errors...
that.clickedNode = previousClickedNode;
that.fx.animate({
modes:['linear', 'node-property:width:height'],
duration: 1000,
onComplete: function() {
that.busy = false;
//TODO(nico) check comment above
that.clickedNode = clickedNode;
}
});
}
});
} else {
that.busy = false;
that.clickedNode = n;
that.refresh();
}
}
};
if(config.request) {
this.requestNodes(clickedNode, callback);
} else {
callback.onComplete();
}
},
/*
Method: out
Sets the parent node of the current selected node as root.
*/
out: function(){
if(this.busy) return;
this.busy = true;
this.events.hoveredNode = false;
var that = this,
config = this.config,
graph = this.graph,
parents = graph.getNode(this.clickedNode
&& this.clickedNode.id || this.root).getParents(),
parent = parents[0],
clickedNode = parent,
previousClickedNode = this.clickedNode;
//if no parents return
if(!parent) {
this.busy = false;
return;
}
//final plot callback
callback = {
onComplete: function() {
that.clickedNode = parent;
if(config.request) {
that.requestNodes(parent, {
onComplete: function() {
that.compute();
that.plot();
that.busy = false;
}
});
} else {
that.compute();
that.plot();
that.busy = false;
}
}
};
//prune tree
if (config.levelsToShow > 0)
this.geom.setRightLevelToShow(parent);
//animate node positions
if(config.animate) {
this.clickedNode = clickedNode;
this.compute('end');
//animate the visible subtree only
this.clickedNode = previousClickedNode;
this.fx.animate({
modes:['linear', 'node-property:width:height'],
duration: 1000,
onComplete: function() {
//animate the parent subtree
that.clickedNode = clickedNode;
//change nodes alpha
graph.eachNode(function(n) {
n.setDataset(['current', 'end'], {
'alpha': [0, 1]
});
}, "ignore");
previousClickedNode.eachSubgraph(function(node) {
node.setData('alpha', 1);
}, "ignore");
that.fx.animate({
duration: 500,
modes:['node-property:alpha'],
onComplete: function() {
callback.onComplete();
}
});
}
});
} else {
callback.onComplete();
}
},
requestNodes: function(node, onComplete){
var handler = $.merge(this.controller, onComplete),
lev = this.config.levelsToShow;
if (handler.request) {
var leaves = [], d = node._depth;
node.eachLevel(0, lev, function(n){
var nodeLevel = lev - (n._depth - d);
if (n.drawn && !n.anySubnode() && nodeLevel > 0) {
leaves.push(n);
n._level = nodeLevel;
}
});
this.group.requestNodes(leaves, handler);
} else {
handler.onComplete();
}
},
reposition: function() {
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
Additionally, there are other parameters and some default values changed
interpolation - (string) Default's *linear*. Describes the way nodes are interpolated. Possible values are 'linear' and 'polar'.
levelDistance - (number) Default's *100*. The distance between levels of the tree.
Instance Properties:
canvas - Access a <Canvas> instance.
graph - Access a <Graph> instance.
op - Access a <RGraph.Op> instance.
fx - Access a <RGraph.Plot> instance.
labels - Access a <RGraph.Label> interface implementation.
*/
$jit.RGraph = new Class( {
Implements: [
Loader, Extras, Layouts.Radial
],
initialize: function(controller){
var $RGraph = $jit.RGraph;
var config = {
interpolation: 'linear',
levelDistance: 100
};
this.controller = this.config = $.merge(Options("Canvas", "Node", "Edge",
"Fx", "Controller", "Tips", "NodeStyles", "Events", "Navigation", "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': Polar,
'Node': {
'selected': false,
'exist': true,
'drawn': true
}
};
this.graph = new Graph(this.graphOptions, this.config.Node,
this.config.Edge);
this.labels = new $RGraph.Label[canvasConfig.Label.type](this);
this.fx = new $RGraph.Plot(this, $RGraph);
this.op = new $RGraph.Op(this);
this.json = null;
this.root = null;
this.busy = false;
this.parent = false;
// initialize extras
this.initializeExtras();
},
/*
createLevelDistanceFunc
Returns the levelDistance function used for calculating a node distance
to its origin. This function returns a function that is computed
per level and not per node, such that all nodes with the same depth will have the
same distance to the origin. The resulting function gets the
parent node as parameter and returns a float.
*/
createLevelDistanceFunc: function(){
var ld = this.config.levelDistance;
return function(elem){
return (elem._depth + 1) * ld;
};
},
/*
Method: refresh
Computes positions and plots the tree.
*/
refresh: function(){
this.compute();
this.plot();
},
reposition: function(){
this.compute('end');
},
/*
Method: plot
Plots the RGraph. This is a shortcut to *fx.plot*.
*/
plot: function(){
this.fx.plot();
},
/*
getNodeAndParentAngle
Returns the _parent_ of the given node, also calculating its angle span.
*/
getNodeAndParentAngle: function(id){
var theta = false;
var n = this.graph.getNode(id);
var ps = n.getParents();
var p = (ps.length > 0)? ps[0] : false;
if (p) {
var posParent = p.pos.getc(), posChild = n.pos.getc();
var newPos = posParent.add(posChild.scale(-1));
theta = Math.atan2(newPos.y, newPos.x);
if (theta < 0)
theta += 2 * Math.PI;
}
return {
parent: p,
theta: theta
};
},
/*
tagChildren
Enumerates the children in order to maintain child ordering (second constraint of the paper).
*/
tagChildren: function(par, id){
if (par.angleSpan) {
var adjs = [];
par.eachAdjacency(function(elem){
adjs.push(elem.nodeTo);
}, "ignore");
var len = adjs.length;
for ( var i = 0; i < len && id != adjs[i].id; i++)
;
for ( var j = (i + 1) % len, k = 0; id != adjs[j].id; j = (j + 1) % len) {
adjs[j].dist = k++;
}
}
},
/*
Method: onClick
Animates the <RGraph> to center the node specified by *id*.
Parameters:
id - A <Graph.Node> id.
opt - (optional|object) An object containing some extra properties described below
hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
Example:
(start code js)
rgraph.onClick('someid');
//or also...
rgraph.onClick('someid', {
hideLabels: false
});
(end code)
*/
onClick: function(id, opt){
if (this.root != id && !this.busy) {
this.busy = true;
this.root = id;
var that = this;
this.controller.onBeforeCompute(this.graph.getNode(id));
var obj = this.getNodeAndParentAngle(id);
// second constraint
this.tagChildren(obj.parent, id);
this.parent = obj.parent;
this.compute('end');
// first constraint
var thetaDiff = obj.theta - obj.parent.endPos.theta;
this.graph.eachNode(function(elem){
elem.endPos.set(elem.endPos.getp().add($P(thetaDiff, 0)));
});
var mode = this.config.interpolation;
opt = $.merge( {
onComplete: $.empty
}, opt || {});
this.fx.animate($.merge( {
hideLabels: true,
modes: [
mode
]
}, opt, {
onComplete: function(){
that.busy = false;
opt.onComplete();
}
}));
}
}
});
$jit.RGraph.$extend = true;
(function(RGraph){
/*
Class: RGraph.Op
Custom extension of <Graph.Op>.
Extends:
All <Graph.Op> methods
See also:
<Graph.Op>
*/
RGraph.Op = new Class( {
Implements: Graph.Op
});
/*
Class: RGraph.Plot
Custom extension of <Graph.Plot>.
Extends:
All <Graph.Plot> methods
See also:
<Graph.Plot>
*/
RGraph.Plot = new Class( {
Implements: Graph.Plot
});
/*
Object: RGraph.Label
Custom extension of <Graph.Label>.
Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
Extends:
All <Graph.Label> methods and subclasses.
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
Edge.type - Described in <Options.Edge>. It's default value has been changed to *hyperline*.
Instance Properties:
canvas - Access a <Canvas> instance.
graph - Access a <Graph> instance.
op - Access a <Hypertree.Op> instance.
fx - Access a <Hypertree.Plot> instance.
labels - Access a <Hypertree.Label> interface implementation.
*/
$jit.Hypertree = new Class( {
Implements: [ Loader, Extras, Layouts.Radial ],
initialize: function(controller) {
var $Hypertree = $jit.Hypertree;
var config = {
radius: "auto",
offset: 0,
Edge: {
type: 'hyperline'
},
duration: 1500,
fps: 35
};
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': Polar,
'Node': {
'selected': false,
'exist': true,
'drawn': true
}
};
this.graph = new Graph(this.graphOptions, this.config.Node,
this.config.Edge);
this.labels = new $Hypertree.Label[canvasConfig.Label.type](this);
this.fx = new $Hypertree.Plot(this, $Hypertree);
this.op = new $Hypertree.Op(this);
this.json = null;
this.root = null;
this.busy = false;
// initialize extras
this.initializeExtras();
},
/*
createLevelDistanceFunc
Returns the levelDistance function used for calculating a node distance
to its origin. This function returns a function that is computed
per level and not per node, such that all nodes with the same depth will have the
same distance to the origin. The resulting function gets the
parent node as parameter and returns a float.
*/
createLevelDistanceFunc: function() {
// get max viz. length.
var r = this.getRadius();
// get max depth.
var depth = 0, max = Math.max, config = this.config;
this.graph.eachNode(function(node) {
depth = max(node._depth, depth);
}, "ignore");
depth++;
// node distance generator
var genDistFunc = function(a) {
return function(node) {
node.scale = r;
var d = node._depth + 1;
var acum = 0, pow = Math.pow;
while (d) {
acum += pow(a, d--);
}
return acum - config.offset;
};
};
// estimate better edge length.
for ( var i = 0.51; i <= 1; i += 0.01) {
var valSeries = (1 - Math.pow(i, depth)) / (1 - i);
if (valSeries >= 2) { return genDistFunc(i - 0.01); }
}
return genDistFunc(0.75);
},
/*
Method: getRadius
Returns the current radius of the visualization. If *config.radius* is *auto* then it
calculates the radius by taking the smaller size of the <Canvas> widget.
See also:
<Canvas.getSize>
*/
getRadius: function() {
var rad = this.config.radius;
if (rad !== "auto") { return rad; }
var s = this.canvas.getSize();
return Math.min(s.width, s.height) / 2;
lib/Devel/SizeMe/Graph/static/jit.js view on Meta::CPAN
},
/*
Method: plot
Plots the <Hypertree>. This is a shortcut to *fx.plot*.
*/
plot: function() {
this.fx.plot();
},
/*
Method: onClick
Animates the <Hypertree> to center the node specified by *id*.
Parameters:
id - A <Graph.Node> id.
opt - (optional|object) An object containing some extra properties described below
hideLabels - (boolean) Default's *true*. Hide labels when performing the animation.
Example:
(start code js)
ht.onClick('someid');
//or also...
ht.onClick('someid', {
hideLabels: false
});
(end code)
*/
onClick: function(id, opt) {
var pos = this.graph.getNode(id).pos.getc(true);
this.move(pos, opt);
},
/*
Method: move
Translates the tree to the given position.
Parameters:
pos - (object) A *x, y* coordinate object where x, y in [0, 1), to move the tree to.
opt - This object has been defined in <Hypertree.onClick>
Example:
(start code js)
ht.move({ x: 0, y: 0.7 }, {
hideLabels: false
});
(end code)
*/
move: function(pos, opt) {
var versor = $C(pos.x, pos.y);
if (this.busy === false && versor.norm() < 1) {
this.busy = true;
var root = this.graph.getClosestNodeToPos(versor), that = this;
this.graph.computeLevels(root.id, 0);
this.controller.onBeforeCompute(root);
opt = $.merge( {
onComplete: $.empty
}, opt || {});
this.fx.animate($.merge( {
modes: [ 'moebius' ],
hideLabels: true
}, opt, {
onComplete: function() {
that.busy = false;
opt.onComplete();
}
}), versor);
}
}
});
$jit.Hypertree.$extend = true;
(function(Hypertree) {
/*
Class: Hypertree.Op
Custom extension of <Graph.Op>.
Extends:
All <Graph.Op> methods
See also:
<Graph.Op>
*/
Hypertree.Op = new Class( {
Implements: Graph.Op
});
/*
Class: Hypertree.Plot
Custom extension of <Graph.Plot>.
Extends:
All <Graph.Plot> methods
See also:
<Graph.Plot>
*/
Hypertree.Plot = new Class( {
Implements: Graph.Plot
});
/*
Object: Hypertree.Label
Custom extension of <Graph.Label>.
Contains custom <Graph.Label.SVG>, <Graph.Label.HTML> and <Graph.Label.Native> extensions.
Extends:
All <Graph.Label> methods and subclasses.