phylotree
Version:
A JavaScript library for developing applications and interactive visualizations involving [phylogenetic trees](https://en.wikipedia.org/wiki/Phylogenetic_tree), written as an extension of the [D3](http://d3js.org) [hierarchy layout](https://github.com/d3/
206 lines (147 loc) • 5.18 kB
JavaScript
import * as d3 from "d3";
import * as _ from "underscore";
/**
* Reroot the tree on the given node.
*
* @param {Node} node Node to reroot on.
* @param {fraction} if specified, partition the branch not into 0.5 : 0.5, but according to
the specified fraction
* @returns {Phylotree} The current ``phylotree``.
* @example
* // Reroot tree on a specific node
* const tree = new Phylotree("((A:0.1,B:0.2):0.05,(C:0.3,D:0.1):0.08);");
* const nodeC = tree.getNodeByName("C");
* tree.reroot(nodeC);
* // Tree is now rooted on the branch leading to C
*
* @example
* // Reroot with custom branch length partitioning
* const nodeA = tree.getNodeByName("A");
* tree.reroot(nodeA, 0.3); // 30% of branch length goes to new root
*
* @example
* // Reroot on midpoint of tree (balance tree)
* const tips = tree.getTips();
* const midpoint = tree.computeMidpoint();
* tree.reroot(midpoint);
*/
export function reroot(node, fraction) {
/** TODO add the option to root in the middle of a branch */
if(!(node instanceof d3.hierarchy)) {
throw new Error('node needs to be an instance of a d3.hierarchy node!');
}
let nodes = this.nodes.copy();
fraction = fraction !== undefined ? fraction : 0.5;
if (node.parent) {
var new_json = d3.hierarchy({
name: "new_root"
});
new_json.children = [node.copy()];
new_json.data.__mapped_bl = undefined
nodes.each(n => {
n.data.__mapped_bl = this.branch_length_accessor(n);
});
this.setBranchLength(n => {
return n.data.__mapped_bl;
});
let remove_me = node,
current_node = node.parent,
stashed_bl = _.noop();
let apportioned_bl =
node.data.__mapped_bl === undefined ? undefined : node.data.__mapped_bl * fraction;
stashed_bl = current_node.data.__mapped_bl;
current_node.data.__mapped_bl =
node.data.__mapped_bl === undefined
? undefined
: node.data.__mapped_bl - apportioned_bl;
node.data.__mapped_bl = apportioned_bl;
var remove_idx;
if (current_node.parent) {
new_json.children.push(current_node);
while (current_node.parent) {
remove_idx = current_node.children.indexOf(remove_me);
if (current_node.parent.parent) {
current_node.children.splice(remove_idx, 1, current_node.parent);
} else {
current_node.children.splice(remove_idx, 1);
}
let t = current_node.parent.data.__mapped_bl;
if (t !== undefined) {
current_node.parent.data.__mapped_bl = stashed_bl;
stashed_bl = t;
}
remove_me = current_node;
current_node = current_node.parent;
}
remove_idx = current_node.children.indexOf(remove_me);
current_node.children.splice(remove_idx, 1);
} else {
remove_idx = current_node.children.indexOf(remove_me);
current_node.children.splice(remove_idx, 1);
stashed_bl = current_node.data.__mapped_bl;
remove_me = new_json;
}
// current_node is now old root, and remove_me is the root child we came up
// the tree through
if (current_node.children.length == 1) {
if (stashed_bl) {
current_node.children[0].data.__mapped_bl += stashed_bl;
}
remove_me.children = remove_me.children.concat(current_node.children);
} else {
let new_node = new d3.hierarchy({ name: "__reroot_top_clade", __mapped_bl: stashed_bl });
_.extendOwn (new_json.children[0], node);
new_node.data.__mapped_bl = stashed_bl;
new_node.children = current_node.children.map(function(n) {
n.parent = new_node;
return n;
});
new_node.parent = remove_me;
remove_me.children.push(new_node);
}
}
// need to traverse the nodes and update parents
this.update(new_json);
this.traverse_and_compute(n => {
_.each (n.children, (c) => {c.parent = n;})
}, "pre-order");
if(!_.isUndefined(this.display)) {
// get options
let options = this.display.options;
// get container
d3.select(this.display.container).select('svg').remove()
// retain selection
let selectionName = this.display.selection_attribute_name
delete this.display;
let rendered_tree = this.render(options);
rendered_tree.selectionLabel(selectionName);
rendered_tree.update();
d3.select(rendered_tree.container).node().appendChild(rendered_tree.show());
d3.select(this.display.container).dispatch('reroot');
// Emit rerooted event via new event system
if (rendered_tree.emit) {
rendered_tree.emit('rerooted', this.nodes);
}
}
return this;
}
export function rootpath(attr_name, store_name) {
attr_name = attr_name || "attribute";
store_name = store_name || "y_scaled";
if ("parent" in this) {
let my_value = parseFloat(this[attr_name]);
this[store_name] =
this.parent[store_name] + (isNaN(my_value) ? 0.1 : my_value);
} else {
this[store_name] = 0.0;
}
return this[store_name];
}
export function pathToRoot(node) {
let selection = [];
while (node) {
selection.push(node);
node = node.parent;
}
return selection;
}