UNPKG

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
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; }