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/
205 lines (165 loc) • 4.8 kB
JavaScript
import * as _ from "underscore";
// These methods are part of the Phylotree object
export function setPartitions(partitions) {
this.partitions = partitions;
}
export function getPartitions(attributes) {
return this.partitions;
}
/**
* Returns T/F whether every branch in the tree has a branch length
*
* @returns {Object} true if every branch in the tree has a branch length
*/
export default function hasBranchLengths() {
let bl = this.branch_length;
if (bl) {
return _.every(this.nodes.descendants(), function(node) {
return !node.parent || !_.isUndefined(bl(node));
});
}
return false;
}
/**
* Returns branch lengths
*
* @returns {Array} array of branch lengths
*/
export function getBranchLengths() {
let bl = this.branch_length;
return _.map(this.nodes.descendants(), node => { return bl(node)});
}
export function defBranchLengthAccessor(_node, new_length) {
let _node_data = _node.data;
if (
"attribute" in _node_data &&
_node_data["attribute"] &&
_node_data["attribute"].length
) {
if(new_length > 0) {
_node_data["attribute"] = String(new_length);
}
let bl = parseFloat(_node_data["attribute"]);
if (!isNaN(bl)) {
return Math.max(0, bl);
}
}
// Allow for empty branch length at root
if(_node_data.name == "root") {
return 0;
}
console.warn('Undefined branch length at ' + _node_data.name + '!');
return undefined;
}
/**
* Get or set branch length accessor.
*
* @param {Function} attr Empty if getting, or new branch length accessor if setting.
* @returns {Object} The branch length accessor if getting, or the current this if setting.
* @example
* // Set a custom branch length accessor
* const tree = new Phylotree(newick);
* tree.setBranchLength(function(node) {
* // Use a transformed branch length
* return Math.log(node.data.attribute + 1);
* });
*
* @example
* // Set all branch lengths to 1 (equal branch lengths)
* tree.setBranchLength(function(node) {
* return 1;
* });
*
* @example
* // Get the current branch length accessor
* const currentAccessor = tree.setBranchLength();
*/
export function setBranchLength(attr) {
if (!arguments.length) return this.branch_length_accessor;
this.branch_length_accessor = attr ? attr : defBranchLengthAccessor;
return this;
}
/**
* Normalizes branch lengths to range [0,1]
* @returns {Phylotree} The current phylotree instance
* @example
* // Normalize all branch lengths to 0-1 range
* const tree = new Phylotree(newick);
* tree.normalizeBranchLengths();
* // Now all branch lengths are between 0 and 1
*
* @example
* // Useful for comparing trees with different scales
* const tree1 = new Phylotree(newick1).normalizeBranchLengths();
* const tree2 = new Phylotree(newick2).normalizeBranchLengths();
* // Both trees now have comparable branch length scales
*/
export function normalize(attr) {
let bl = this.branch_length;
let branch_lengths = _.map(this.nodes.descendants(), function(node) {
if(bl(node)) {
return bl(node);
} else {
return null;
}
});
const max_bl = _.max(branch_lengths);
const min_bl = _.min(branch_lengths);
let scaler = function (x) {
return (x - min_bl)/(max_bl - min_bl);
}
_.each(this.nodes.descendants(), (node) => {
let len = bl(node);
if(len) {
bl(node, scaler(len));
}
});
return this;
}
/**
* Scales branch lengths using a custom transformation function
*
* @param {Function} scale_by Function that transforms each branch length
* @returns {Phylotree} The current phylotree instance
* @example
* // Scale all branch lengths by a constant factor
* const tree = new Phylotree(newick);
* tree.scaleBranchLengths(function(length) {
* return length * 2; // Double all branch lengths
* });
*
* @example
* // Apply logarithmic transformation
* tree.scaleBranchLengths(function(length) {
* return Math.log10(length + 1);
* });
*
* @example
* // Convert to time units (assuming substitutions per site)
* const mutationRate = 1e-8;
* tree.scaleBranchLengths(function(length) {
* return length / mutationRate; // Convert to years
* });
*/
export function scale(scale_by) {
let bl = this.branch_length;
_.each(this.nodes.descendants(), (node) => {
let len = bl(node);
if(len) {
bl(node, scale_by(len));
}
});
return this;
}
/**
* Get or set branch name accessor.
*
* @param {Function} attr (Optional) If setting, a function that accesses a branch name
* from a node.
* @returns The ``nodeLabel`` accessor if getting, or the current ``this`` if setting.
*/
export function branchName(attr) {
if (!arguments.length) return this.nodeLabel;
this.nodeLabel = attr;
return this;
}