tnt.tree
Version:
TnT tree display
1,860 lines (1,613 loc) • 190 kB
JavaScript
(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);throw new Error("Cannot find module '"+o+"'")}var f=n[o]={exports:{}};t[o][0].call(f.exports,function(e){var n=t[o][1][e];return s(n?n:e)},f,f.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o<r.length;o++)s(r[o]);return s})({1:[function(require,module,exports){
if (typeof tnt === "undefined") {
module.exports = tnt = {};
}
tnt.tree = require("./index.js");
tnt.tree.node = require("tnt.tree.node");
tnt.tree.parse_newick = require("tnt.newick").parse_newick;
tnt.tree.parse_nhx = require("tnt.newick").parse_nhx;
},{"./index.js":2,"tnt.newick":8,"tnt.tree.node":10}],2:[function(require,module,exports){
// if (typeof tnt === "undefined") {
// module.exports = tnt = {}
// }
module.exports = tree = require("./src/index.js");
var eventsystem = require("biojs-events");
eventsystem.mixin(tree);
//tnt.utils = require("tnt.utils");
//tnt.tooltip = require("tnt.tooltip");
//tnt.tree = require("./src/index.js");
},{"./src/index.js":17,"biojs-events":3}],3:[function(require,module,exports){
var events = require("backbone-events-standalone");
events.onAll = function(callback,context){
this.on("all", callback,context);
return this;
};
// Mixin utility
events.oldMixin = events.mixin;
events.mixin = function(proto) {
events.oldMixin(proto);
// add custom onAll
var exports = ['onAll'];
for(var i=0; i < exports.length;i++){
var name = exports[i];
proto[name] = this[name];
}
return proto;
};
module.exports = events;
},{"backbone-events-standalone":5}],4:[function(require,module,exports){
/**
* Standalone extraction of Backbone.Events, no external dependency required.
* Degrades nicely when Backone/underscore are already available in the current
* global context.
*
* Note that docs suggest to use underscore's `_.extend()` method to add Events
* support to some given object. A `mixin()` method has been added to the Events
* prototype to avoid using underscore for that sole purpose:
*
* var myEventEmitter = BackboneEvents.mixin({});
*
* Or for a function constructor:
*
* function MyConstructor(){}
* MyConstructor.prototype.foo = function(){}
* BackboneEvents.mixin(MyConstructor.prototype);
*
* (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc.
* (c) 2013 Nicolas Perriault
*/
/* global exports:true, define, module */
(function() {
var root = this,
nativeForEach = Array.prototype.forEach,
hasOwnProperty = Object.prototype.hasOwnProperty,
slice = Array.prototype.slice,
idCounter = 0;
// Returns a partial implementation matching the minimal API subset required
// by Backbone.Events
function miniscore() {
return {
keys: Object.keys || function (obj) {
if (typeof obj !== "object" && typeof obj !== "function" || obj === null) {
throw new TypeError("keys() called on a non-object");
}
var key, keys = [];
for (key in obj) {
if (obj.hasOwnProperty(key)) {
keys[keys.length] = key;
}
}
return keys;
},
uniqueId: function(prefix) {
var id = ++idCounter + '';
return prefix ? prefix + id : id;
},
has: function(obj, key) {
return hasOwnProperty.call(obj, key);
},
each: function(obj, iterator, context) {
if (obj == null) return;
if (nativeForEach && obj.forEach === nativeForEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, l = obj.length; i < l; i++) {
iterator.call(context, obj[i], i, obj);
}
} else {
for (var key in obj) {
if (this.has(obj, key)) {
iterator.call(context, obj[key], key, obj);
}
}
}
},
once: function(func) {
var ran = false, memo;
return function() {
if (ran) return memo;
ran = true;
memo = func.apply(this, arguments);
func = null;
return memo;
};
}
};
}
var _ = miniscore(), Events;
// Backbone.Events
// ---------------
// A module that can be mixed in to *any object* in order to provide it with
// custom events. You may bind with `on` or remove with `off` callback
// functions to an event; `trigger`-ing an event fires all callbacks in
// succession.
//
// var object = {};
// _.extend(object, Backbone.Events);
// object.on('expand', function(){ alert('expanded'); });
// object.trigger('expand');
//
Events = {
// Bind an event to a `callback` function. Passing `"all"` will bind
// the callback to all events fired.
on: function(name, callback, context) {
if (!eventsApi(this, 'on', name, [callback, context]) || !callback) return this;
this._events || (this._events = {});
var events = this._events[name] || (this._events[name] = []);
events.push({callback: callback, context: context, ctx: context || this});
return this;
},
// Bind an event to only be triggered a single time. After the first time
// the callback is invoked, it will be removed.
once: function(name, callback, context) {
if (!eventsApi(this, 'once', name, [callback, context]) || !callback) return this;
var self = this;
var once = _.once(function() {
self.off(name, once);
callback.apply(this, arguments);
});
once._callback = callback;
return this.on(name, once, context);
},
// Remove one or many callbacks. If `context` is null, removes all
// callbacks with that function. If `callback` is null, removes all
// callbacks for the event. If `name` is null, removes all bound
// callbacks for all events.
off: function(name, callback, context) {
var retain, ev, events, names, i, l, j, k;
if (!this._events || !eventsApi(this, 'off', name, [callback, context])) return this;
if (!name && !callback && !context) {
this._events = {};
return this;
}
names = name ? [name] : _.keys(this._events);
for (i = 0, l = names.length; i < l; i++) {
name = names[i];
if (events = this._events[name]) {
this._events[name] = retain = [];
if (callback || context) {
for (j = 0, k = events.length; j < k; j++) {
ev = events[j];
if ((callback && callback !== ev.callback && callback !== ev.callback._callback) ||
(context && context !== ev.context)) {
retain.push(ev);
}
}
}
if (!retain.length) delete this._events[name];
}
}
return this;
},
// Trigger one or many events, firing all bound callbacks. Callbacks are
// passed the same arguments as `trigger` is, apart from the event name
// (unless you're listening on `"all"`, which will cause your callback to
// receive the true name of the event as the first argument).
trigger: function(name) {
if (!this._events) return this;
var args = slice.call(arguments, 1);
if (!eventsApi(this, 'trigger', name, args)) return this;
var events = this._events[name];
var allEvents = this._events.all;
if (events) triggerEvents(events, args);
if (allEvents) triggerEvents(allEvents, arguments);
return this;
},
// Tell this object to stop listening to either specific events ... or
// to every object it's currently listening to.
stopListening: function(obj, name, callback) {
var listeners = this._listeners;
if (!listeners) return this;
var deleteListener = !name && !callback;
if (typeof name === 'object') callback = this;
if (obj) (listeners = {})[obj._listenerId] = obj;
for (var id in listeners) {
listeners[id].off(name, callback, this);
if (deleteListener) delete this._listeners[id];
}
return this;
}
};
// Regular expression used to split event strings.
var eventSplitter = /\s+/;
// Implement fancy features of the Events API such as multiple event
// names `"change blur"` and jQuery-style event maps `{change: action}`
// in terms of the existing API.
var eventsApi = function(obj, action, name, rest) {
if (!name) return true;
// Handle event maps.
if (typeof name === 'object') {
for (var key in name) {
obj[action].apply(obj, [key, name[key]].concat(rest));
}
return false;
}
// Handle space separated event names.
if (eventSplitter.test(name)) {
var names = name.split(eventSplitter);
for (var i = 0, l = names.length; i < l; i++) {
obj[action].apply(obj, [names[i]].concat(rest));
}
return false;
}
return true;
};
// A difficult-to-believe, but optimized internal dispatch function for
// triggering events. Tries to keep the usual cases speedy (most internal
// Backbone events have 3 arguments).
var triggerEvents = function(events, args) {
var ev, i = -1, l = events.length, a1 = args[0], a2 = args[1], a3 = args[2];
switch (args.length) {
case 0: while (++i < l) (ev = events[i]).callback.call(ev.ctx); return;
case 1: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1); return;
case 2: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2); return;
case 3: while (++i < l) (ev = events[i]).callback.call(ev.ctx, a1, a2, a3); return;
default: while (++i < l) (ev = events[i]).callback.apply(ev.ctx, args);
}
};
var listenMethods = {listenTo: 'on', listenToOnce: 'once'};
// Inversion-of-control versions of `on` and `once`. Tell *this* object to
// listen to an event in another object ... keeping track of what it's
// listening to.
_.each(listenMethods, function(implementation, method) {
Events[method] = function(obj, name, callback) {
var listeners = this._listeners || (this._listeners = {});
var id = obj._listenerId || (obj._listenerId = _.uniqueId('l'));
listeners[id] = obj;
if (typeof name === 'object') callback = this;
obj[implementation](name, callback, this);
return this;
};
});
// Aliases for backwards compatibility.
Events.bind = Events.on;
Events.unbind = Events.off;
// Mixin utility
Events.mixin = function(proto) {
var exports = ['on', 'once', 'off', 'trigger', 'stopListening', 'listenTo',
'listenToOnce', 'bind', 'unbind'];
_.each(exports, function(name) {
proto[name] = this[name];
}, this);
return proto;
};
// Export Events as BackboneEvents depending on current context
if (typeof exports !== 'undefined') {
if (typeof module !== 'undefined' && module.exports) {
exports = module.exports = Events;
}
exports.BackboneEvents = Events;
}else if (typeof define === "function" && typeof define.amd == "object") {
define(function() {
return Events;
});
} else {
root.BackboneEvents = Events;
}
})(this);
},{}],5:[function(require,module,exports){
module.exports = require('./backbone-events-standalone');
},{"./backbone-events-standalone":4}],6:[function(require,module,exports){
module.exports = require("./src/api.js");
},{"./src/api.js":7}],7:[function(require,module,exports){
var api = function (who) {
var _methods = function () {
var m = [];
m.add_batch = function (obj) {
m.unshift(obj);
};
m.update = function (method, value) {
for (var i=0; i<m.length; i++) {
for (var p in m[i]) {
if (p === method) {
m[i][p] = value;
return true;
}
}
}
return false;
};
m.add = function (method, value) {
if (m.update (method, value) ) {
} else {
var reg = {};
reg[method] = value;
m.add_batch (reg);
}
};
m.get = function (method) {
for (var i=0; i<m.length; i++) {
for (var p in m[i]) {
if (p === method) {
return m[i][p];
}
}
}
};
return m;
};
var methods = _methods();
var api = function () {};
api.check = function (method, check, msg) {
if (method instanceof Array) {
for (var i=0; i<method.length; i++) {
api.check(method[i], check, msg);
}
return;
}
if (typeof (method) === 'function') {
method.check(check, msg);
} else {
who[method].check(check, msg);
}
return api;
};
api.transform = function (method, cbak) {
if (method instanceof Array) {
for (var i=0; i<method.length; i++) {
api.transform (method[i], cbak);
}
return;
}
if (typeof (method) === 'function') {
method.transform (cbak);
} else {
who[method].transform(cbak);
}
return api;
};
var attach_method = function (method, opts) {
var checks = [];
var transforms = [];
var getter = opts.on_getter || function () {
return methods.get(method);
};
var setter = opts.on_setter || function (x) {
for (var i=0; i<transforms.length; i++) {
x = transforms[i](x);
}
for (var j=0; j<checks.length; j++) {
if (!checks[j].check(x)) {
var msg = checks[j].msg ||
("Value " + x + " doesn't seem to be valid for this method");
throw (msg);
}
}
methods.add(method, x);
};
var new_method = function (new_val) {
if (!arguments.length) {
return getter();
}
setter(new_val);
return who; // Return this?
};
new_method.check = function (cbak, msg) {
if (!arguments.length) {
return checks;
}
checks.push ({check : cbak,
msg : msg});
return this;
};
new_method.transform = function (cbak) {
if (!arguments.length) {
return transforms;
}
transforms.push(cbak);
return this;
};
who[method] = new_method;
};
var getset = function (param, opts) {
if (typeof (param) === 'object') {
methods.add_batch (param);
for (var p in param) {
attach_method (p, opts);
}
} else {
methods.add (param, opts.default_value);
attach_method (param, opts);
}
};
api.getset = function (param, def) {
getset(param, {default_value : def});
return api;
};
api.get = function (param, def) {
var on_setter = function () {
throw ("Method defined only as a getter (you are trying to use it as a setter");
};
getset(param, {default_value : def,
on_setter : on_setter}
);
return api;
};
api.set = function (param, def) {
var on_getter = function () {
throw ("Method defined only as a setter (you are trying to use it as a getter");
};
getset(param, {default_value : def,
on_getter : on_getter}
);
return api;
};
api.method = function (name, cbak) {
if (typeof (name) === 'object') {
for (var p in name) {
who[p] = name[p];
}
} else {
who[name] = cbak;
}
return api;
};
return api;
};
module.exports = exports = api;
},{}],8:[function(require,module,exports){
module.exports = require("./src/newick.js");
},{"./src/newick.js":9}],9:[function(require,module,exports){
/**
* Newick and nhx formats parser in JavaScript.
*
* Copyright (c) Jason Davies 2010 and Miguel Pignatelli
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Example tree (from http://en.wikipedia.org/wiki/Newick_format):
*
* +--0.1--A
* F-----0.2-----B +-------0.3----C
* +------------------0.5-----E
* +---------0.4------D
*
* Newick format:
* (A:0.1,B:0.2,(C:0.3,D:0.4)E:0.5)F;
*
* Converted to JSON:
* {
* name: "F",
* branchset: [
* {name: "A", length: 0.1},
* {name: "B", length: 0.2},
* {
* name: "E",
* length: 0.5,
* branchset: [
* {name: "C", length: 0.3},
* {name: "D", length: 0.4}
* ]
* }
* ]
* }
*
* Converted to JSON, but with no names or lengths:
* {
* branchset: [
* {}, {}, {
* branchset: [{}, {}]
* }
* ]
* }
*/
module.exports = {
parse_newick : function(s) {
var ancestors = [];
var tree = {};
var tokens = s.split(/\s*(;|\(|\)|,|:)\s*/);
var subtree;
for (var i=0; i<tokens.length; i++) {
var token = tokens[i];
switch (token) {
case '(': // new branchset
subtree = {};
tree.children = [subtree];
ancestors.push(tree);
tree = subtree;
break;
case ',': // another branch
subtree = {};
ancestors[ancestors.length-1].children.push(subtree);
tree = subtree;
break;
case ')': // optional name next
tree = ancestors.pop();
break;
case ':': // optional length next
break;
default:
var x = tokens[i-1];
if (x == ')' || x == '(' || x == ',') {
tree.name = token;
} else if (x == ':') {
tree.branch_length = parseFloat(token);
}
}
}
return tree;
},
parse_nhx : function (s) {
var ancestors = [];
var tree = {};
var subtree;
var tokens = s.split( /\s*(;|\(|\)|\[|\]|,|:|=)\s*/ );
for (var i=0; i<tokens.length; i++) {
var token = tokens[i];
switch (token) {
case '(': // new children
subtree = {};
tree.children = [subtree];
ancestors.push(tree);
tree = subtree;
break;
case ',': // another branch
subtree = {};
ancestors[ancestors.length-1].children.push(subtree);
tree = subtree;
break;
case ')': // optional name next
tree = ancestors.pop();
break;
case ':': // optional length next
break;
default:
var x = tokens[i-1];
if (x == ')' || x == '(' || x == ',') {
tree.name = token;
}
else if (x == ':') {
var test_type = typeof token;
if(!isNaN(token)){
tree.branch_length = parseFloat(token);
}
}
else if (x == '='){
var x2 = tokens[i-2];
switch(x2){
case 'D':
tree.duplication = token;
break;
case 'G':
tree.gene_id = token;
break;
case 'T':
tree.taxon_id = token;
break;
default :
tree[tokens[i-2]] = token;
}
}
else {
var test;
}
}
}
return tree;
}
};
},{}],10:[function(require,module,exports){
var node = require("./src/node.js");
module.exports = exports = node;
},{"./src/node.js":15}],11:[function(require,module,exports){
module.exports = require("./src/index.js");
},{"./src/index.js":12}],12:[function(require,module,exports){
// require('fs').readdirSync(__dirname + '/').forEach(function(file) {
// if (file.match(/.+\.js/g) !== null && file !== __filename) {
// var name = file.replace('.js', '');
// module.exports[name] = require('./' + file);
// }
// });
// Same as
var utils = require("./utils.js");
utils.reduce = require("./reduce.js");
module.exports = exports = utils;
},{"./reduce.js":13,"./utils.js":14}],13:[function(require,module,exports){
var reduce = function () {
var smooth = 5;
var value = 'val';
var redundant = function (a, b) {
if (a < b) {
return ((b-a) <= (b * 0.2));
}
return ((a-b) <= (a * 0.2));
};
var perform_reduce = function (arr) {return arr;};
var reduce = function (arr) {
if (!arr.length) {
return arr;
}
var smoothed = perform_smooth(arr);
var reduced = perform_reduce(smoothed);
return reduced;
};
var median = function (v, arr) {
arr.sort(function (a, b) {
return a[value] - b[value];
});
if (arr.length % 2) {
v[value] = arr[~~(arr.length / 2)][value];
} else {
var n = ~~(arr.length / 2) - 1;
v[value] = (arr[n][value] + arr[n+1][value]) / 2;
}
return v;
};
var clone = function (source) {
var target = {};
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
return target;
};
var perform_smooth = function (arr) {
if (smooth === 0) { // no smooth
return arr;
}
var smooth_arr = [];
for (var i=0; i<arr.length; i++) {
var low = (i < smooth) ? 0 : (i - smooth);
var high = (i > (arr.length - smooth)) ? arr.length : (i + smooth);
smooth_arr[i] = median(clone(arr[i]), arr.slice(low,high+1));
}
return smooth_arr;
};
reduce.reducer = function (cbak) {
if (!arguments.length) {
return perform_reduce;
}
perform_reduce = cbak;
return reduce;
};
reduce.redundant = function (cbak) {
if (!arguments.length) {
return redundant;
}
redundant = cbak;
return reduce;
};
reduce.value = function (val) {
if (!arguments.length) {
return value;
}
value = val;
return reduce;
};
reduce.smooth = function (val) {
if (!arguments.length) {
return smooth;
}
smooth = val;
return reduce;
};
return reduce;
};
var block = function () {
var red = reduce()
.value('start');
var value2 = 'end';
var join = function (obj1, obj2) {
return {
'object' : {
'start' : obj1.object[red.value()],
'end' : obj2[value2]
},
'value' : obj2[value2]
};
};
// var join = function (obj1, obj2) { return obj1 };
red.reducer( function (arr) {
var value = red.value();
var redundant = red.redundant();
var reduced_arr = [];
var curr = {
'object' : arr[0],
'value' : arr[0][value2]
};
for (var i=1; i<arr.length; i++) {
if (redundant (arr[i][value], curr.value)) {
curr = join(curr, arr[i]);
continue;
}
reduced_arr.push (curr.object);
curr.object = arr[i];
curr.value = arr[i].end;
}
reduced_arr.push(curr.object);
// reduced_arr.push(arr[arr.length-1]);
return reduced_arr;
});
reduce.join = function (cbak) {
if (!arguments.length) {
return join;
}
join = cbak;
return red;
};
reduce.value2 = function (field) {
if (!arguments.length) {
return value2;
}
value2 = field;
return red;
};
return red;
};
var line = function () {
var red = reduce();
red.reducer ( function (arr) {
var redundant = red.redundant();
var value = red.value();
var reduced_arr = [];
var curr = arr[0];
for (var i=1; i<arr.length-1; i++) {
if (redundant (arr[i][value], curr[value])) {
continue;
}
reduced_arr.push (curr);
curr = arr[i];
}
reduced_arr.push(curr);
reduced_arr.push(arr[arr.length-1]);
return reduced_arr;
});
return red;
};
module.exports = reduce;
module.exports.line = line;
module.exports.block = block;
},{}],14:[function(require,module,exports){
module.exports = {
iterator : function(init_val) {
var i = init_val || 0;
var iter = function () {
return i++;
};
return iter;
},
script_path : function (script_name) { // script_name is the filename
var script_scaped = script_name.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
var script_re = new RegExp(script_scaped + '$');
var script_re_sub = new RegExp('(.*)' + script_scaped + '$');
// TODO: This requires phantom.js or a similar headless webkit to work (document)
var scripts = document.getElementsByTagName('script');
var path = ""; // Default to current path
if(scripts !== undefined) {
for(var i in scripts) {
if(scripts[i].src && scripts[i].src.match(script_re)) {
return scripts[i].src.replace(script_re_sub, '$1');
}
}
}
return path;
},
defer_cancel : function (cbak, time) {
var tick;
var defer_cancel = function () {
var args = Array.prototype.slice.call(arguments);
var that = this;
clearTimeout(tick);
tick = setTimeout (function () {
cbak.apply (that, args);
}, time);
};
return defer_cancel;
}
};
},{}],15:[function(require,module,exports){
var apijs = require("tnt.api");
var iterator = require("tnt.utils").iterator;
var tnt_node = function (data) {
//tnt.tree.node = function (data) {
"use strict";
var node = function () {
};
var api = apijs (node);
// API
// node.nodes = function() {
// if (cluster === undefined) {
// cluster = d3.layout.cluster()
// // TODO: length and children should be exposed in the API
// // i.e. the user should be able to change this defaults via the API
// // children is the defaults for parse_newick, but maybe we should change that
// // or at least not assume this is always the case for the data provided
// .value(function(d) {return d.length})
// .children(function(d) {return d.children});
// }
// nodes = cluster.nodes(data);
// return nodes;
// };
var apply_to_data = function (data, cbak) {
cbak(data);
if (data.children !== undefined) {
for (var i=0; i<data.children.length; i++) {
apply_to_data(data.children[i], cbak);
}
}
};
var create_ids = function () {
var i = iterator(1);
// We can't use apply because apply creates new trees on every node
// We should use the direct data instead
apply_to_data (data, function (d) {
if (d._id === undefined) {
d._id = i();
// TODO: Not sure _inSubTree is strictly necessary
// d._inSubTree = {prev:true, curr:true};
}
});
};
var link_parents = function (data) {
if (data === undefined) {
return;
}
if (data.children === undefined) {
return;
}
for (var i=0; i<data.children.length; i++) {
// _parent?
data.children[i]._parent = data;
link_parents(data.children[i]);
}
};
var compute_root_dists = function (data) {
apply_to_data (data, function (d) {
var l;
if (d._parent === undefined) {
d._root_dist = 0;
} else {
var l = 0;
if (d.branch_length) {
l = d.branch_length
}
d._root_dist = l + d._parent._root_dist;
}
});
};
// TODO: data can't be rewritten used the api yet. We need finalizers
node.data = function(new_data) {
if (!arguments.length) {
return data
}
data = new_data;
create_ids();
link_parents(data);
compute_root_dists(data);
return node;
};
// We bind the data that has been passed
node.data(data);
api.method ('find_all', function (cbak, deep) {
var nodes = [];
node.apply (function (n) {
if (cbak(n)) {
nodes.push (n);
}
});
return nodes;
});
api.method ('find_node', function (cbak, deep) {
if (cbak(node)) {
return node;
}
if (data.children !== undefined) {
for (var j=0; j<data.children.length; j++) {
var found = tnt_node(data.children[j]).find_node(cbak, deep);
if (found) {
return found;
}
}
}
if (deep && (data._children !== undefined)) {
for (var i=0; i<data._children.length; i++) {
tnt_node(data._children[i]).find_node(cbak, deep)
var found = tnt_node(data._children[i]).find_node(cbak, deep);
if (found) {
return found;
}
}
}
});
api.method ('find_node_by_name', function(name, deep) {
return node.find_node (function (node) {
return node.node_name() === name
}, deep);
});
api.method ('toggle', function() {
if (data) {
if (data.children) { // Uncollapsed -> collapse
var hidden = 0;
node.apply (function (n) {
var hidden_here = n.n_hidden() || 0;
hidden += (n.n_hidden() || 0) + 1;
});
node.n_hidden (hidden-1);
data._children = data.children;
data.children = undefined;
} else { // Collapsed -> uncollapse
node.n_hidden(0);
data.children = data._children;
data._children = undefined;
}
}
return this;
});
api.method ('is_collapsed', function () {
return (data._children !== undefined && data.children === undefined);
});
var has_ancestor = function(n, ancestor) {
// It is better to work at the data level
n = n.data();
ancestor = ancestor.data();
if (n._parent === undefined) {
return false
}
n = n._parent
for (;;) {
if (n === undefined) {
return false;
}
if (n === ancestor) {
return true;
}
n = n._parent;
}
};
// This is the easiest way to calculate the LCA I can think of. But it is very inefficient too.
// It is working fine by now, but in case it needs to be more performant we can implement the LCA
// algorithm explained here:
// http://community.topcoder.com/tc?module=Static&d1=tutorials&d2=lowestCommonAncestor
api.method ('lca', function (nodes) {
if (nodes.length === 1) {
return nodes[0];
}
var lca_node = nodes[0];
for (var i = 1; i<nodes.length; i++) {
lca_node = _lca(lca_node, nodes[i]);
}
return lca_node;
// return tnt_node(lca_node);
});
var _lca = function(node1, node2) {
if (node1.data() === node2.data()) {
return node1;
}
if (has_ancestor(node1, node2)) {
return node2;
}
return _lca(node1, node2.parent());
};
api.method('n_hidden', function (val) {
if (!arguments.length) {
return node.property('_hidden');
}
node.property('_hidden', val);
return node
});
api.method ('get_all_nodes', function (deep) {
var nodes = [];
node.apply(function (n) {
nodes.push(n);
}, deep);
return nodes;
});
api.method ('get_all_leaves', function (deep) {
var leaves = [];
node.apply(function (n) {
if (n.is_leaf(deep)) {
leaves.push(n);
}
}, deep);
return leaves;
});
api.method ('upstream', function(cbak) {
cbak(node);
var parent = node.parent();
if (parent !== undefined) {
parent.upstream(cbak);
}
// tnt_node(parent).upstream(cbak);
// node.upstream(node._parent, cbak);
});
api.method ('subtree', function(nodes, keep_singletons) {
if (keep_singletons === undefined) {
keep_singletons = false;
}
var node_counts = {};
for (var i=0; i<nodes.length; i++) {
var n = nodes[i];
if (n !== undefined) {
n.upstream (function (this_node){
var id = this_node.id();
if (node_counts[id] === undefined) {
node_counts[id] = 0;
}
node_counts[id]++
});
}
}
var is_singleton = function (node_data) {
var n_children = 0;
if (node_data.children === undefined) {
return false;
}
for (var i=0; i<node_data.children.length; i++) {
var id = node_data.children[i]._id;
if (node_counts[id] > 0) {
n_children++;
}
}
return n_children === 1;
};
var subtree = {};
copy_data (data, subtree, 0, function (node_data) {
var node_id = node_data._id;
var counts = node_counts[node_id];
// Is in path
if (counts > 0) {
if (is_singleton(node_data) && !keep_singletons) {
return false;
}
return true;
}
// Is not in path
return false;
});
return tnt_node(subtree.children[0]);
});
var copy_data = function (orig_data, subtree, currBranchLength, condition) {
if (orig_data === undefined) {
return;
}
if (condition(orig_data)) {
var copy = copy_node(orig_data, currBranchLength);
if (subtree.children === undefined) {
subtree.children = [];
}
subtree.children.push(copy);
if (orig_data.children === undefined) {
return;
}
for (var i = 0; i < orig_data.children.length; i++) {
copy_data (orig_data.children[i], copy, 0, condition);
}
} else {
if (orig_data.children === undefined) {
return;
}
currBranchLength += orig_data.branch_length || 0;
for (var i = 0; i < orig_data.children.length; i++) {
copy_data(orig_data.children[i], subtree, currBranchLength, condition);
}
}
};
var copy_node = function (node_data, extraBranchLength) {
var copy = {};
// copy all the own properties excepts links to other nodes or depth
for (var param in node_data) {
if ((param === "children") ||
(param === "_children") ||
(param === "_parent") ||
(param === "depth")) {
continue;
}
if (node_data.hasOwnProperty(param)) {
copy[param] = node_data[param];
}
}
if ((copy.branch_length !== undefined) && (extraBranchLength !== undefined)) {
copy.branch_length += extraBranchLength;
}
return copy;
};
// TODO: This method visits all the nodes
// a more performant version should return true
// the first time cbak(node) is true
api.method ('present', function (cbak) {
// cbak should return true/false
var is_true = false;
node.apply (function (n) {
if (cbak(n) === true) {
is_true = true;
}
});
return is_true;
});
// cbak is called with two nodes
// and should return a negative number, 0 or a positive number
api.method ('sort', function (cbak) {
if (data.children === undefined) {
return;
}
var new_children = [];
for (var i=0; i<data.children.length; i++) {
new_children.push(tnt_node(data.children[i]));
}
new_children.sort(cbak);
data.children = [];
for (var i=0; i<new_children.length; i++) {
data.children.push(new_children[i].data());
}
for (var i=0; i<data.children.length; i++) {
tnt_node(data.children[i]).sort(cbak);
}
});
api.method ('flatten', function (preserve_internal) {
if (node.is_leaf()) {
return node;
}
var data = node.data();
var newroot = copy_node(data);
var nodes;
if (preserve_internal) {
nodes = node.get_all_nodes();
nodes.shift(); // the self node is also included
} else {
nodes = node.get_all_leaves();
}
newroot.children = [];
for (var i=0; i<nodes.length; i++) {
delete (nodes[i].children);
newroot.children.push(copy_node(nodes[i].data()));
}
return tnt_node(newroot);
});
// TODO: This method only 'apply's to non collapsed nodes (ie ._children is not visited)
// Would it be better to have an extra flag (true/false) to visit also collapsed nodes?
api.method ('apply', function(cbak, deep) {
if (deep === undefined) {
deep = false;
}
cbak(node);
if (data.children !== undefined) {
for (var i=0; i<data.children.length; i++) {
var n = tnt_node(data.children[i])
n.apply(cbak, deep);
}
}
if ((data._children !== undefined) && deep) {
for (var j=0; j<data._children.length; j++) {
var n = tnt_node(data._children[j]);
n.apply(cbak, deep);
}
}
});
// TODO: Not sure if it makes sense to set via a callback:
// root.property (function (node, val) {
// node.deeper.field = val
// }, 'new_value')
api.method ('property', function(prop, value) {
if (arguments.length === 1) {
if ((typeof prop) === 'function') {
return prop(data)
}
return data[prop]
}
if ((typeof prop) === 'function') {
prop(data, value);
}
data[prop] = value;
return node;
});
api.method ('is_leaf', function(deep) {
if (deep) {
return ((data.children === undefined) && (data._children === undefined));
}
return data.children === undefined;
});
// It looks like the cluster can't be used for anything useful here
// It is now included as an optional parameter to the tnt.tree() method call
// so I'm commenting the getter
// node.cluster = function() {
// return cluster;
// };
// node.depth = function (node) {
// return node.depth;
// };
// node.name = function (node) {
// return node.name;
// };
api.method ('id', function () {
return node.property('_id');
});
api.method ('node_name', function () {
return node.property('name');
});
api.method ('branch_length', function () {
return node.property('branch_length');
});
api.method ('root_dist', function () {
return node.property('_root_dist');
});
api.method ('children', function (deep) {
var children = [];
if (data.children) {
for (var i=0; i<data.children.length; i++) {
children.push(tnt_node(data.children[i]));
}
}
if ((data._children) && deep) {
for (var j=0; j<data._children.length; j++) {
children.push(tnt_node(data._children[j]));
}
}
if (children.length === 0) {
return undefined;
}
return children;
});
api.method ('parent', function () {
if (data._parent === undefined) {
return undefined;
}
return tnt_node(data._parent);
});
return node;
};
module.exports = exports = tnt_node;
},{"tnt.api":6,"tnt.utils":11}],16:[function(require,module,exports){
var apijs = require('tnt.api');
var tree = {};
tree.diagonal = function () {
var d = function (diagonalPath) {
var source = diagonalPath.source;
var target = diagonalPath.target;
var midpointX = (source.x + target.x) / 2;
var midpointY = (source.y + target.y) / 2;
var pathData = [source, {x: target.x, y: source.y}, target];
pathData = pathData.map(d.projection());
return d.path()(pathData, radial_calc.call(this,pathData))
};
var api = apijs (d)
.getset ('projection')
.getset ('path')
var coordinateToAngle = function (coord, radius) {
var wholeAngle = 2 * Math.PI,
quarterAngle = wholeAngle / 4
var coordQuad = coord[0] >= 0 ? (coord[1] >= 0 ? 1 : 2) : (coord[1] >= 0 ? 4 : 3),
coordBaseAngle = Math.abs(Math.asin(coord[1] / radius))
// Since this is just based on the angle of the right triangle formed
// by the coordinate and the origin, each quad will have different
// offsets
var coordAngle;
switch (coordQuad) {
case 1:
coordAngle = quarterAngle - coordBaseAngle
break
case 2:
coordAngle = quarterAngle + coordBaseAngle
break
case 3:
coordAngle = 2*quarterAngle + quarterAngle - coordBaseAngle
break
case 4:
coordAngle = 3*quarterAngle + coordBaseAngle
}
return coordAngle
};
var radial_calc = function (pathData) {
var src = pathData[0];
var mid = pathData[1];
var dst = pathData[2];
var radius = Math.sqrt(src[0]*src[0] + src[1]*src[1]);
var srcAngle = coordinateToAngle(src, radius);
var midAngle = coordinateToAngle(mid, radius);
var clockwise = Math.abs(midAngle - srcAngle) > Math.PI ? midAngle <= srcAngle : midAngle > srcAngle;
return {
radius : radius,
clockwise : clockwise
};
};
return d;
};
// vertical diagonal for rect branches
tree.diagonal.vertical = function () {
var path = function(pathData, obj) {
var src = pathData[0];
var mid = pathData[1];
var dst = pathData[2];
var radius = 200000; // Number long enough
return "M" + src + " A" + [radius,radius] + " 0 0,0 " + mid + "M" + mid + "L" + dst;
};
var projection = function(d) {
return [d.y, d.x];
}
return tree.diagonal()
.path(path)
.projection(projection);
};
tree.diagonal.radial = function () {
var path = function(pathData, obj) {
var src = pathData[0];
var mid = pathData[1];
var dst = pathData[2];
var radius = obj.radius;
var clockwise = obj.clockwise;
if (clockwise) {
return "M" + src + " A" + [radius,radius] + " 0 0,0 " + mid + "M" + mid + "L" + dst;
} else {
return "M" + mid + " A" + [radius,radius] + " 0 0,0 " + src + "M" + mid + "L" + dst;
}
};
var projection = function(d) {
var r = d.y, a = (d.x - 90) / 180 * Math.PI;
return [r * Math.cos(a), r * Math.sin(a)];
};
return tree.diagonal()
.path(path)
.projection(projection)
};
module.exports = exports = tree.diagonal;
},{"tnt.api":6}],17:[function(require,module,exports){
var tree = require ("./tree.js");
tree.label = require("./label.js");
tree.diagonal = require("./diagonal.js");
tree.layout = require("./layout.js");
tree.node_display = require("./node_display.js");
// tree.node = require("tnt.tree.node");
// tree.parse_newick = require("tnt.newick").parse_newick;
// tree.parse_nhx = require("tnt.newick").parse_nhx;
module.exports = exports = tree;
},{"./diagonal.js":16,"./label.js":18,"./layout.js":19,"./node_display.js":20,"./tree.js":21}],18:[function(require,module,exports){
var apijs = require("tnt.api");
var tree = {};
tree.label = function () {
"use strict";
var dispatch = d3.dispatch ("click", "dblclick", "mouseover", "mouseout")
// TODO: Not sure if we should be removing by default prev labels
// or it would be better to have a separate remove method called by the vis
// on update
// We also have the problem that we may be transitioning from
// text to img labels and we need to remove the label of a different type
var label = function (node, layout_type, node_size) {
if (typeof (node) !== 'function') {
throw(node);
}
label.display().call(this, node, layout_type)
.attr("class", "tnt_tree_label")
.attr("transform", function (d) {
var t = label.transform()(node, layout_type);
return "translate (" + (t.translate[0] + node_size) + " " + t.translate[1] + ")rotate(" + t.rotate + ")";
})
// TODO: this click event is probably never fired since there is an onclick event in the node g element?
.on("click", function () {
dispatch.click.call(this, node)
})
.on("dblclick", function () {
dispatch.dblclick.call(this, node)
})
.on("mouseover", function () {
dispatch.mouseover.call(this, node)
})
.on("mouseout", function () {
dispatch.mouseout.call(this, node)
})
};
var api = apijs (label)
.getset ('width', function () { throw "Need a width callback" })
.getset ('height', function () { throw "Need a height callback" })
.getset ('display', function () { throw "Need a display callback" })
.getset ('transform', function () { throw "Need a transform callback" })
//.getset ('on_click');
return d3.rebind (label, dispatch, "on");
};
// Text based labels
tree.label.text = function () {
var label = tree.label();
var api = apijs (label)
.getset ('fontsize', 10)
.getset ('fontweight', "normal")
.getset ('color', "#000")
.getset ('text', function (d) {
return d.data().name;
})
label.display (function (node, layout_type) {
var l = d3.select(this)
.append("text")
.attr("text-anchor", function (d) {
if (layout_type === "radial") {
return (d.x%360 < 180) ? "start" : "end";
}
return "start";
})
.text(function(){
return label.text()(node)
})
.style('font-size', function () {
return d3.functor(label.fontsize())(node) + "px";
})
.style('font-weight', function () {
return d3.functor(label.fontweight())(node);
})
.style('fill', d3.functor(label.color())(node));
return l;
});
label.transform (function (node, layout_type) {
var d = node.data();
var t = {
translate : [5, 5],
rotate : 0
};
if (layout_type === "radial") {
t.translate[1] = t.translate[1] - (d.x%360 < 180 ? 0 : label.fontsize())
t.rotate = (d.x%360 < 180 ? 0 : 180)
}
return t;
});
// label.transform (function (node) {
// var d = node.data();
// return "translate(10 5)rotate(" + (d.x%360 < 180 ? 0 : 180) + ")";
// });
label.width (function (node) {
var svg = d3.select("body")
.append("svg")
.attr("height", 0)
.style('visibility', 'hidden');
var text = svg
.append("text")
.style('font-size', d3.functor(label.fontsize())(node) + "px")
.text(label.text()(node));
var width = text.node().getBBox().width;
svg.remove();
return width;
});
label.height (function (node) {
return d3.functor(label.fontsize())(node);
});
return label;
};
// Image based labels
tree.label.img = function () {
var label = tree.label();
var api = apijs (label)
.getset ('src', function () {})
label.display (function (node, layout_type) {
if (label.src()(node)) {
var l = d3.select(this)
.append("image")
.attr("width", label.width()())
.attr("height", label.height()())
.attr("xlink:href", label.src()(node));
return l;
}
// fallback text in case the img is not found?
return d3.select(this)
.append("text")
.text("");
});
label.transform (function (node, layout_type) {
var d = node.data();
var t = {
translate : [10, (-label.height()() / 2)],
rotate : 0
};
if (layout_type === 'radial') {
t.translate[0] = t.translate[0] + (d.x%360 < 180 ? 0 : label.width()()),
t.translate[1] = t.translate[1] + (d.x%360 < 180 ? 0 : label.height()()),
t.rotate = (d.x%360 < 180 ? 0 : 180)
}
return t;
});
return label;
};
// Labels made of 2+ simple labels
tree.label.composite = function () {
var labels = [];
var label = function (node, layout_type, node_size) {
var curr_xoffset = 0;
for (var i=0; i<labels.length; i++) {
var display = labels[i];
(function (offset) {
display.transform (function (node, layout_type) {
var tsuper = display._super_.transform()(node, layout_type);
var t = {
translate : [offset + tsuper.translate[0], tsuper.translate[1]],
rotate : tsuper.rotate
};
return t;
})
})(curr_xoffset);
curr_xoffset += 10;
curr_xoffset += display.width()(node);
display.call(this, node, layout_type, node_size);
}
};
var api = apijs (label)
api.method ('add_label', function (display, node) {
display._super_ = {};
apijs (display._super_)
.get ('transform', display.transform());
labels.push(display);
return label;
});
api.method ('width', function () {
return function (node) {
var tot_width = 0;
for (var i=0; i<labels.length; i++) {
tot_width += parseInt(labels[i].width()(node));
tot_width += parseInt(labels[i]._super_.transform()(node).translate[0]);
}
return tot_width;
}
});
api.method ('height', function () {
return function (node) {
var max_height = 0;
for (var i=0; i<labels.length; i++) {
var curr_height = labels[i].height()(node);
if ( curr_height > max_height) {
max_height = curr_height;
}
}
return max_height;
}
});
return label;
};
module.exports = exports = tree.label;
},{"tnt.api":6}],19:[function(require,module,exports){
// Based on the code by Ken-ichi Ueda in http://bl.ocks.org/kueda/1036776#d3.phylogram.js
var apijs = require("tnt.api");
var diagonal = require("./diagonal.js");
var tree = {};
tree.layout = function () {
var l = function () {
};
var cluster = d3.layout.cluster()
.sort(null)
.value(function (d) {return d.length} )
.separation(function () {return 1});
var api = apijs (l)
.getset ('scale', true)
.getset ('max_leaf_label_width', 0)
.method ("cluster", cluster)
.method('yscale', function () {throw "yscale is not defined in the base object"})
.method('adjust_cluster_size', function () {throw "adjust_cluster_size is not defined in the base object" })
.method('width', function () {throw "width is not defined in the base object"})
.method('height', function () {throw "height is not defined in the base object"});
api.method('scale_branch_lengths', function (curr) {
if (l.scale() === false) {
return
}
var nodes = curr.nodes;
var tree = curr.tree;
var root_dists = nodes.map (function (d) {
return d._root_dist;
});
var yscale = l.yscale(root_dists);
tree.apply (function (node) {
node.property("y", yscale(node.root_dist()));
});
});
return l;
};
tree.layout.vertical = function () {
var layout = tree.layout();
// Elements like 'labels' depend on the layout type. This exposes a way of identifying the layout type
layout.type = "vertical";
var api = apijs (layout)
.getset ('width', 360)
.get ('translate_vis', [20,20])
.method ('diagonal', diagonal.vertical)
.method ('transform_node', function (d) {
return "translate(" + d.y + "," + d.x + ")";
});
api.method('height',