Skip to content

Commit

Permalink
Merge branch 'trs/improved-newick-parsing'
Browse files Browse the repository at this point in the history
  • Loading branch information
tsibley committed Nov 2, 2023
2 parents 1ae7ad7 + cabba98 commit b034aea
Show file tree
Hide file tree
Showing 3 changed files with 53 additions and 51 deletions.
88 changes: 39 additions & 49 deletions auspice_client_customisation/parseNewick.js
Original file line number Diff line number Diff line change
@@ -1,55 +1,45 @@
/**
* Newick format parser in JavaScript.
*
* Copyright (c) Jason Davies 2010.
*
* 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.
*/
import { parse as _parseNewick } from "newick-js";

/* NOTE: parseNewick function slightly modified to produce an object better suited for Nextstrain. */

export const parseNewick = (nwk) => {
const ancestors = [];
let tree = {};
const tokens = nwk.split(/\s*(;|\(|\)|,|:)\s*/);
for (let i=0; i<tokens.length; i++) {
const token = tokens[i];
const subtree = {};
switch (token) {
case '(': // new child nodes up next
tree.children = [subtree];
ancestors.push(tree);
tree = subtree;
break;
case ',': // next node: another child of the last ancestor
ancestors[ancestors.length-1].children.push(subtree);
tree = subtree;
break;
case ')': // optional name next
tree = ancestors.pop();
break;
case ':': // optional length next
break;
default:
const x = tokens[i-1];
if (x === ')' || x === '(' || x === ',') {
tree.name = token;
} else if (x === ':') {
tree.node_attrs = {div: parseFloat(token)};
}
}
const parseNewick = (nwk) => {
const {root, rootWeight, graph: [,edges]} = _parseNewick(nwk);
const edgesByParent = new Map();

for (const [parent, child, weight] of edges) {
if (!edgesByParent.has(parent))
edgesByParent.set(parent, new Set());
edgesByParent.get(parent).add({child, weight});
}
return tree;
};

const constructTree = (parent, weight) => {
const tree = {
// Particulars of this object are tied to getTreeStruct() below.
name: parent.label ?? "",
node_attrs: {
div: Number.isFinite(weight) ? weight : 0,
}
};

const childEdges = edgesByParent.get(parent);

if (childEdges?.size) {
tree.children = [];

for (const {child, weight} of childEdges) {
/* childEdges is reversed relative to the order given by the Newick input
* due to a side-effect of the parser's internals, so we unshift()
* instead of push() to restore the input order.
*/
tree.children.unshift(
constructTree(child, weight)
);
}
}

return tree;
};

return constructTree(root, rootWeight);
};

const getTreeStruct = (nwk) => {
const tree = parseNewick(nwk);
Expand Down
13 changes: 12 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
},
"dependencies": {
"auspice": "2.50.0",
"heroku-ssl-redirect": "0.0.4"
"heroku-ssl-redirect": "0.0.4",
"newick-js": "^1.2.1"
}
}

0 comments on commit b034aea

Please sign in to comment.