UNPKG

tnt.tree

Version:
1,860 lines (1,613 loc) 190 kB
(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',