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/
132 lines (114 loc) • 3.84 kB
JavaScript
const fs = require("fs");
const phylotree = require("../dist/phylotree.js");
const commander = require("commander");
const _ = require("underscore");
// Parse command line arguments
commander
.arguments("<newick>", "Input newick file")
.requiredOption(
"-p --patterns <patterns>",
"Comma-separated regex patterns for tagging",
)
.requiredOption(
"-t --tags <tags>",
"Comma-separated tags that correspond to each pattern",
)
.option("-r --reroot <node>", "Node to reroot on (optional)")
.option("-i --invert", "Invert the selection", false)
.option("-l --leaf-only", "Only label leaf nodes", false)
.option(
"-s --strategy <strategy>",
"Strategy for labeling internal nodes: none, all, some, parsimony",
"all",
)
.usage("./test/data/tree.nwk -p 'regex1,regex2' -t 'tag1,tag2'")
.description(
"Tag branches in a Newick tree based on regex patterns and labeling strategy",
)
.parse(process.argv);
// Input validation
if (commander.patterns.split(",").length !== commander.tags.split(",").length) {
throw new Error(
"The number of patterns must match the number of corresponding tags.",
);
}
// Parse patterns and tags
const patterns = commander.patterns.split(",");
const tags = commander.tags.split(",");
// Function to get annotations for Newick output
function annotator(node) {
return node.data.annotation ? `{${node.data.annotation}}` : "";
}
// Main tagging function
fs.readFile(commander.args[0], (err, newick_data) => {
if (err) throw new Error("Error reading Newick file: " + err.message);
// Parse tree
const tree = new phylotree.phylotree(newick_data.toString());
// Reroot if specified
if (commander.reroot) {
const reroot_node = tree.getNodeByName(commander.reroot);
if (reroot_node) {
tree.reroot(reroot_node);
}
}
// Initial leaf node tagging
function tagLeafNodes(node) {
if (tree.isLeafNode(node)) {
for (let i = 0; i < patterns.length; i++) {
const matches = new RegExp(patterns[i]).test(node.data.name);
if (commander.invert ? !matches : matches) {
node.data.annotation = tags[i];
break;
}
}
}
}
function applyStrategy() {
switch (commander.strategy) {
case "none":
// Only leaf nodes are tagged
break;
case "all":
// Tag internal node if all descendants have the same non-empty tag
tree.traverse_and_compute((node) => {
if (!tree.isLeafNode(node)) {
const descendantTags = _.uniq(
node.descendants().map((d) => d.data.annotation), // Get all annotations, including empty ones
).filter((tag) => tag !== ""); // Filter out empty annotations
if (descendantTags.length === 1) {
node.data.annotation = descendantTags[0];
}
}
});
break;
case "some":
// Tag internal node if any descendants have a non-empty tag
tree.traverse_and_compute((node) => {
if (!tree.isLeafNode(node)) {
const descendantTags = node
.descendants()
.map((d) => d.data.annotation) // Get all annotations
.filter((tag) => tag !== ""); // Filter out empty annotations
if (descendantTags.length > 0) {
node.data.annotation = descendantTags[0];
}
}
});
break;
case "parsimony":
// Use maxParsimony to tag internal nodes
tree.maxParsimony("annotation");
break;
}
}
// Apply tagging
if (!commander.leafOnly) {
tree.traverse_and_compute(tagLeafNodes);
applyStrategy();
} else {
tree.traverse_and_compute(tagLeafNodes);
}
// Output tagged tree
console.log(tree.getNewick(annotator));
});