UNPKG

phylojs

Version:

A simple typescript library for phylogenetic trees

2 lines 18.2 kB
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.phylojs=e():t.phylojs=e()}(self,(()=>(()=>{"use strict";var t={d:(e,r)=>{for(var n in r)t.o(r,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:r[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{Node:()=>r,Tree:()=>n,read:()=>C,readNeXML:()=>N,readNewick:()=>l,readNexus:()=>f,readPhyJSON:()=>k,readPhyloXML:()=>m,readTreesFromNeXML:()=>M,readTreesFromNewick:()=>a,readTreesFromNexus:()=>p,readTreesFromPhyJSON:()=>A,readTreesFromPhyloXML:()=>w,writeNewick:()=>i,writeNexus:()=>s});class r{constructor(t){this.id=t,this.parent=void 0,this.children=[],this.height=void 0,this.branchLength=void 0,this.label=void 0,this.annotation={},this.hybridID=void 0,this.rttDist=void 0}toString(){return`node#${this.id}`}addChild(t){this.children.push(t),t.parent=this}removeChild(t){const e=this.children.indexOf(t);this.children.splice(e,1),t.parent=void 0}isRoot(){return void 0===this.parent}isLeaf(){return 0===this.children.length}isSingleton(){return 1===this.children.length}isHybrid(){return void 0!==this.hybridID}_getAncestors(t){return t.isRoot()?[t]:t.parent?[t].concat(this._getAncestors(t.parent)):[t]}getAncestors(){return this._getAncestors(this)}isLeftOf(t){const e=this.getAncestors().reverse(),r=t.getAncestors().reverse();let n;for(n=1;n<Math.min(e.length,r.length);n++)if(e[n]!=r[n]){const t=e[n-1];return t.children.indexOf(e[n])<t.children.indexOf(r[n])}}copy(){const t=new r(this.id);t.height=this.height,t.branchLength=this.branchLength,t.label=this.label;for(const e in this.annotation)t.annotation[e]=this.annotation[e];t.id=this.id,t.hybridID=this.hybridID;for(let e=0;e<this.children.length;e++)t.addChild(this.children[e].copy());return t}applyPreOrder(t){const e=[],r=[this];for(;r.length>0;){const n=r.pop();if(!n)continue;const i=t(n);e.push(i);for(let t=n.children.length-1;t>=0;t--)r.push(n.children[t])}return e}applyPostOrder(t){const e=[],r=[this],n=new Set;for(;r.length>0;){const i=r[r.length-1];if(!i){r.pop();continue}let o=!0;for(let t=i.children.length-1;t>=0;t--)n.has(i.children[t])||(r.push(i.children[t]),o=!1);if(o){const i=r.pop();if(i){n.add(i);const r=t(i);e.push(r)}}}return e}}class n{constructor(t){this.root=t}applyPostOrder(t){this.root.applyPostOrder(t)}applyPreOrder(t){this.root.applyPreOrder(t)}computeNodeHeights(){this.root.applyPreOrder((t=>(void 0===t.parent?t.height=0:void 0!==t.branchLength&&void 0!==t.parent.height&&(t.height=t.parent.height+t.branchLength),t.height)))}isUltrametric(t=1e-6){this.computeNodeHeights();const e=this.leafList.map((t=>t.height));let r;return e.some((t=>null==t))?(this.ultrametric=!1,!1):(r=e,r.every(((e,r,n)=>Math.abs(e-n[0])<t))?(this.ultrametric=!0,!0):(this.ultrametric=!1,!1))}ladderise(){this.root.applyPreOrder((t=>{t.children.sort(((t,e)=>{const r=this.getClade(t).getTipLabels().length,n=this.getClade(e).getTipLabels().length;return r<n?-1:r>n?1:0}))}))}getBranchLengths(){return this.nodeList.map((t=>t.branchLength))}getRTTDist(){return this.root.applyPreOrder((t=>{if(void 0===t.parent?t.rttDist=0:void 0!==t.branchLength&&void 0!==t.parent.rttDist?t.rttDist=t.branchLength+t.parent.rttDist:t.rttDist=t.parent.rttDist,t.isLeaf())return t.rttDist})).filter((t=>void 0!==t))}reassignNodeIDs(){let t=0;for(let e=0;e<this.nodeList.length;e++)this.nodeList[e].id=t++}clearCaches(){this._nodeList=void 0,this.nodeIDMap=void 0,this.labelNodeMap=void 0,this._leafList=void 0,this.recombEdgeMap=void 0,this.ultrametric=void 0}get nodeList(){return void 0===this._nodeList&&void 0!==this.root&&(this._nodeList=this.root.applyPreOrder((t=>t))),this._nodeList?this._nodeList:[]}getNode(t){if(void 0===this.nodeIDMap&&void 0!==this.root){this.nodeIDMap={};for(let t=0;t<this.nodeList.length;t++){const e=this.nodeList[t];this.nodeIDMap[e.id]=e}}return null==this.nodeIDMap?null:this.nodeIDMap[t]}get leafList(){return void 0===this._leafList&&void 0!==this.root&&(this._leafList=this.nodeList.filter((t=>t.isLeaf()))),null==this._leafList?[]:this._leafList}getNodeByLabel(t){if(void 0===this.labelNodeMap&&void 0!==this.root){this.labelNodeMap={};for(let t=0;t<this.leafList.length;t++){const e=this.leafList[t];void 0!==e.label&&(this.labelNodeMap[e.label]=e)}}return null==this.labelNodeMap||void 0===this.labelNodeMap[t]?null:this.labelNodeMap[t]}getRecombEdgeMap(){if(void 0===this.recombEdgeMap){let t,e,r;r=void 0!==this.root?this.nodeList.filter((t=>t.isHybrid())):[];const n={},i={};for(e=0;e<r.length;e++)null!==r[e]&&(t=r[e],void 0!==t.hybridID&&(t.isLeaf()?t.hybridID in i?i[t.hybridID].push(t):i[t.hybridID]=[t]:n[t.hybridID]=t));let o;for(o in this.recombEdgeMap={},n){if(!(o in i))throw"Extended Newick error: hybrid nodes must come in groups of 2 or more.";this.recombEdgeMap[o]=[n[o]].concat(i[o])}for(o in i)o in this.recombEdgeMap||(this.recombEdgeMap[o]=i[o])}return this.recombEdgeMap}isRecombSrcNode(t){return void 0!==t.hybridID&&t.isHybrid()&&this.getRecombEdgeMap()[t.hybridID][0]==t}isRecombDestNode(t){return void 0!==t.hybridID&&t.isHybrid()&&this.getRecombEdgeMap()[t.hybridID][0]!=t}isNetwork(){return Object.keys(this.getRecombEdgeMap()).length>0}getClade(t){return new n(t)}getMRCA(t){const e=t.length;if(0===e)return null;if(1===e)return t[0].parent||t[0];const r=new Map;let n=t.slice();for(;n.length>0;){const t=[];for(const i of n){const n=(r.get(i)||0)+1;if(n===e)return i;r.set(i,n),i.parent&&t.push(i.parent)}n=t}return null}getTipLabels(t){let e;return e=void 0!==t?this.getClade(t).leafList.map((t=>{var e;return null!==(e=t.label)&&void 0!==e?e:t.id.toString()})):this.leafList.map((t=>{var e;return null!==(e=t.label)&&void 0!==e?e:t.id.toString()})),e}getTotalBranchLength(){let t=0;const e=this.nodeList;for(const r of e)void 0!==r.branchLength&&(t+=r.branchLength);return t}reroot(t,e=.5){var n,i,o;if(this.isNetwork())throw new Error("Cannot reroot a network.");const s=this.root;this.recombEdgeMap=void 0;const h=this.getRecombEdgeMap(),l=t,a=l.parent;if(!a)throw new Error("Cannot reroot at the current root");const c=null!==(n=l.branchLength)&&void 0!==n?n:0,d=c*Math.min(1,Math.max(0,e)),g=c-d;a.removeChild(l),l.parent=void 0;const u=new r(0),f=new Map;function p(t,e,r){var n,i;f.has(t)||f.set(t,[]),f.has(e)||f.set(e,[]),null===(n=f.get(t))||void 0===n||n.push({node:e,weight:r}),null===(i=f.get(e))||void 0===i||i.push({node:t,weight:r})}const b=[...this.nodeList];for(const t of b)for(const e of t.children)t===a&&e===l||t===l&&e===a||p(t,e,null!==(i=e.branchLength)&&void 0!==i?i:0);p(u,l,d),p(u,a,g);for(const t in h){const e=h[+t],r=e[0];for(let t=1;t<e.length;t++){const n=e[t];p(r,n,null!==(o=n.branchLength)&&void 0!==o?o:0)}}for(const t of b)t.parent=void 0,t.children=[];this.root=u;const m=new Set([u]),w=[u];for(;w.length;){const t=w.shift();if(!t)throw new Error("Queue is unexpectedly empty");for(const{node:e,weight:r}of f.get(t)||[])m.has(e)||(e.parent=t,t.children.push(e),e.branchLength=r,m.add(e),w.push(e))}if(1===s.children.length&&!s.isHybrid()){const[t]=s.children;if(!s.parent)throw new Error("Old root does not have a parent.");const e=s.parent;if(e.removeChild(s),s.removeChild(t),e.addChild(t),void 0===s.branchLength||void 0===t.branchLength)throw new Error("Branch length is undefined for either the old root or the only child.");t.branchLength+=s.branchLength}this.clearCaches(),this.computeNodeHeights(),this.reassignNodeIDs();const y=this.getRecombEdgeMap();for(const t in y){const[e,...r]=y[t];for(const t of r){if(void 0===t.branchLength||void 0===t.height||void 0===e.height)throw new Error("Branch length or height is undefined for the destination or source node.");t.branchLength+=t.height-e.height}}}getInternalNodeHeights(){return this.root.applyPreOrder((t=>{if(void 0===t.parent?t.height=0:void 0!==t.branchLength&&void 0!==t.parent.height?t.height=t.branchLength+t.parent.height:t.height=t.parent.height,!t.isLeaf())return t.height})).filter((t=>void 0!==t))}gammaStatistic(t=1e-6){let e;const r=this.leafList.length;let n=0;if(r<=2)return NaN;if(!this.isUltrametric(t))return console.warn("Gamma Statistic requires an untrametric tree!"),NaN;this.computeNodeHeights();const i=this.nodeList;if(i.map((t=>t.height)).slice(1).slice(1).some((t=>null==t)))return NaN;i.some((t=>t.isHybrid()))&&console.warn("This is a netowrk! Gamma statistic ignoring hybrid nodes."),e=i.slice(1).filter((t=>!t.isLeaf())).map((t=>t.height)).filter((t=>void 0!==t));const o=this.leafList.map((t=>t.height))[0];e.push(o),e.sort(((t,e)=>t-e));const s=e[0];e=e.map(((t,e,r)=>t-r[e-1])),e[0]=s;const h=e.map(((t,e)=>(e+2)*t)).reduce(((t,e)=>t+e),0);let l;for(let t=0;t<=e.length-2;t++){l=0;for(let r=0;r<=t;r++)l+=(r+2)*e[r];n+=l}return n/=r-2,(n-h/2)/(h*Math.sqrt(1/(12*(r-2))))}sackinIndex(){return this.nodeList.filter((t=>!t.isLeaf())).map((t=>this.getClade(t).leafList.length)).reduce(((t,e)=>t+e),0)}isBinary(){return!!this.nodeList.filter((t=>!t.isLeaf())).map((t=>t.children.length)).every((t=>t<=2))}collessIndex(t="standard"){if(!this.isBinary())return console.warn("Coless Imbalance not defined for non-binary trees"),NaN;const e=this.nodeList.filter((t=>!t.isLeaf())).map((e=>{let r=this.getClade(e.children[0]).leafList.length,n=this.getClade(e.children[1]).leafList.length;return Number.isInteger(r)||(r=0),Number.isInteger(n)||(n=0),"quadratic"===t?Math.pow(r-n,2):Math.abs(r-n)})).reduce(((t,e)=>t+e),0);if("corrected"===t){const t=this.leafList.length;return t<=2?0:2*e/((t-1)*(t-2))}return e}}function i(t,e=t=>""){let r="";return void 0!==t.root&&(r+=o(t.root,e)+";"),r}function o(t,e=t=>""){let r="";if(!t.isLeaf()){r+="(";for(let n=0;n<t.children.length;n++)n>0&&(r+=","),r+=o(t.children[n],e);r+=")"}return void 0!==t.label&&null==t.hybridID?r+=`"${t.label}"`:void 0!==t.label&&void 0!==t.hybridID?r+=`"${t.label}"#${t.hybridID}`:null==t.label&&void 0!==t.hybridID&&(r+=`#${t.hybridID}`),r+=e(t.annotation),void 0!==t.branchLength&&(0==t.branchLength?r+=":0.0":r+=`:${t.branchLength}`),r}function s(t,e=t=>""){let r="#NEXUS\n\nbegin trees;\n";return void 0!==t.root&&(r+=`\ttree tree_1 = [&R] ${o(t.root,e)};\n`),r+="end;",r}class h extends Error{}function l(t,e=d){const r=[],i=[];t.includes("\n")&&(t=t.slice(0,t.indexOf("\n")),console.warn("Multiple trees in Newick string. Only reading the first tree. Use readTreesFromNewick() to read all trees."));let o=0;for(;o<t.length;){for(;o<t.length&&(t.charAt(o)<"!"||t.charAt(o)>"~");)o++;if(o===t.length)break;const n=t.charAt(o);if(","===n)o++;else if("("===n)r.push(-1),o++;else if(")"===n){const n=i.length;let s,h;for(s=r.length-1;s>=0&&!(r[s]<0);--s);if(s<0)throw new Error("Unmatched closing parenthesis in Newick string");const l=r.length-1-s;for(o=c(t,o+1,i,n,e),s=r.length-1,h=l-1;h>=0;s--,h--)i[n].children[h]=i[r[s]],i[r[s]].parent=i[n];r.length=s,r.push(n)}else r.push(i.length),o=c(t,o,i,i.length,e)}return r.length>1&&console.warn("Multiple unconnected trees found in Newick string"),new n(i[i.length-1])}function a(t){const e=[],r=t.split(/;\s*\n/);for(let t of r)if(t=t.trim(),0!==t.length)try{e.push(l(t))}catch(t){if(!(t instanceof h))throw t;console.log("Skipping Newick tree: "+t.message)}return e}function c(t,e,n,i,o){const s=e;let h,l,a=0;const c=new r(i);let d;for(h=e;h<t.length&&","!=t.charAt(h)&&")"!=t.charAt(h);++h){const e=t.charAt(h);if("["==e){const e=h;0==a&&(a=h);do{++h}while(h<t.length&&"]"!=t.charAt(h));if(h==t.length)throw new Error("Unclosed annotation bracket in Newick string");c.annotation=o(t.slice(e+1,h))}else if(":"==e){for(0==a&&(a=h),l=++h;h<t.length;++h){const e=t.charAt(h);if((e<"0"||e>"9")&&"e"!=e&&"E"!=e&&"+"!=e&&"-"!=e&&"."!=e)break}c.branchLength=parseFloat(t.slice(l,h)),--h}else e<"!"&&e>"~"&&0==a&&(a=h)}if(0==a&&(a=h),a>s)if(d=t.slice(s,a).replace(/;$/g,"").replace(/^"|"$/g,"").replace(/^'|'$/g,""),d.includes("#")){const t=function(t){if(!t.includes("#"))throw"No hash(#), in hybrid label.";const e=t.split("#"),r=e[0].length>0?e[0]:void 0,n=Number(e[1].replace(/H|LGT|R/g,""));if(!Number.isInteger(n))throw"Hybrid ID is not an integer!";return{label:r,hybridID:n}}(d);c.label=t.label,c.hybridID=t.hybridID}else d.length>0?c.label=d:c.label=void 0;return n.push(c),h}function d(t){return t.startsWith("&&NHX:")?function(t){t.startsWith("&&NHX:")&&(t=t.slice(6));const e={};return t.split(/:/g).forEach((t=>{if(!t)return;const r=t.split("=");if(r.length<2)return;const n=r[0].trim(),i=r[1];i&&i.includes("{")&&i.includes("}")?e[n]=i.replace(/{|}/g,"").split(/,/g):e[n]=i})),e}(t):function(t){t.startsWith("&")&&(t=t.slice(1));const e={};return t.split(/[,:](?![^{]*\})/g).forEach((t=>{const r=t.split("=");if(r.length<2)return;const n=r[0].trim(),i=r[1];i.includes("{")&&i.includes("}")?e[n]=i.replace(/{|}/g,"").split(/,|:/g):e[n]=i})),e}(t)}function g(t,e){return t.root.applyPreOrder((t=>{t.label&&e[t.label]&&(t.label=e[t.label])})),t}function u(t,e){const r=[],n=t.split("\n");let i=!1,o="";const s=function(t){const e={},r=t.split("\n");let n="";for(let t=1;t<r.length;t++)if(n+=r[t].trim(),n=n.replace(/\[[^&][^\]]*\]/g,"").trim(),n.toLowerCase().startsWith("translate")){const t=n.slice(9,n.length-1).split(",");for(let r=0;r<t.length;r++){const n=t[r].trim().split(/\s+/),i=n[0];let o=n.slice(1).join(" ");o=o.replace(/^"(.*)"$/,"$1").replace(/^'(.*)'$/,"$1"),e[i]=o}break}return e}(t);for(let t=1;t<n.length;t++){if(o+=n[t].trim(),";"!==o[o.length-1])continue;if(o=o.replace(/\[[^&][^\]]*\]/g,"").trim(),!i){"begin trees;"===o.toLowerCase()&&(i=!0),o="";continue}if("end;"===o.toLowerCase())break;const a=/tree (\w|\.)+ *(\[&[^\]]*] *)* *= *(\[&[^\]]*] *)* */.exec(o.toLowerCase());if(null!==a){const t=a[0].length;try{let n=l(o.slice(t));if(n=g(n,s),r.push(n),e)return r}catch(t){if(!(t instanceof h))throw t;console.log("Skipping Nexus tree: "+t.message)}}o=""}return r}function f(t){const e=u(t,!0);if(0===e.length)throw new Error("No trees found in Nexus.");return e[0]}function p(t){return u(t,!1)}function b(t){return(new DOMParser).parseFromString(t,"application/xml").getElementsByTagName("phylogeny")}function m(t){const e=b(t);if(0===e.length)throw new Error("No phylogeny element found in phyloXML.");const r=e[0],i=new y(r);if(!i.root)throw new Error("Failed to parse the phyloXML data correctly.");return new n(i.root)}function w(t){const e=[],r=b(t);for(let t=0;t<r.length;t++){const i=r[t];try{const t=new y(i);e.push(new n(t.root))}catch(e){console.log(`Skipping phyloXML tree ${t}: Unrooted tree.`)}}return e}class y{constructor(t){this.thisNodeID=0,this.root=this.walkDom(void 0,t),0===this.root.branchLength&&(this.root.branchLength=void 0)}annotateNode(t,e,r){for(let n=0;n<r.length;n++){const i=r[n].tagName,o=r[n].textContent;t.annotation[e+"_"+i]=o}}walkDom(t,e){const n=new r(this.thisNodeID++);void 0!==t&&t.addChild(n);for(let t=0;t<e.children.length;t++){const r=e.children[t];switch(r.tagName){case"clade":this.walkDom(n,r);break;case"name":r.textContent&&(n.label=r.textContent);break;case"taxonomy":this.annotateNode(n,"taxonomy",r.children);break;case"sequence":this.annotateNode(n,"sequence",r.children);break;case"confidence":n.annotation[`confidence_${r.getAttribute("type")||""}`]=r.textContent;break;case"branch_length":n.branchLength=Number(r.textContent);break;case"property":{const t=r.getAttribute("ref");t&&(n.annotation[t]=r.textContent);break}}}const i=e.getAttribute("rooted");if(i&&"false"===i.toLowerCase())throw new h("Unrooted tree.");return e.hasAttribute("branch_length")&&(n.branchLength=Number(e.getAttribute("branch_length"))),n}}const L="http://www.nexml.org/2009";function v(t){return(new DOMParser).parseFromString(t,"application/xml").getElementsByTagName("tree")}function N(t){const e=v(t);if(0===e.length)throw new Error("No tree element found in NeXML.");const r=e[0],i=new D(r);if(!i.root)throw new Error("Failed to parse the NeXML data correctly.");return new n(i.root)}function M(t){const e=[],r=v(t);for(let t=0;t<r.length;t++){const i=r[t],o=new D(i);o.root?e.push(new n(o.root)):console.log("Skipping NeXML tree: Unrooted tree.")}return e}class D{constructor(t){this.thisNodeID=0,this.processTree(t)}processTree(t){const e=t.getElementsByTagNameNS(L,"node"),n=t.getElementsByTagNameNS(L,"edge");let i,o;const s={};for(let t=0;t<e.length;t++){const n=e[t],h=new r(this.thisNodeID++);h.label=n.getAttribute("label")||void 0,s[n.getAttribute("id")||""]=h,"true"===n.getAttribute("root")&&(this.root=h),i=n.getElementsByTagNameNS(L,"meta");for(let t=0;t<i.length;t++)o=i[t],o.hasAttribute("property")&&o.hasAttribute("content")&&(h.annotation[o.getAttribute("property")||""]=o.getAttribute("content"))}if(!this.root)throw new h("Unrooted tree.");for(let t=0;t<n.length;t++){const e=n[t],r=s[e.getAttribute("source")||""],h=s[e.getAttribute("target")||""];r.addChild(h),e.hasAttribute("length")&&(h.branchLength=parseFloat(e.getAttribute("length")||"0")),i=e.getElementsByTagNameNS(L,"meta");for(let t=0;t<i.length;t++)o=i[t],o.hasAttribute("property")&&o.hasAttribute("content")&&(h.annotation[o.getAttribute("property")||""]=o.getAttribute("content"))}}}function E(t){const e={};for(const r of t)r.id&&r.name&&(e[r.id]=r.name);return e}function I(t,e,n){const i=new r(n);return i.label=t.taxon&&e[t.taxon]||void 0,i.branchLength=t.branch_length,t.children&&t.children.forEach(((t,r)=>{const o=I(t,e,n+1+r);i.addChild(o)})),i}function x(t,e){const r=I(t,e,0);return new n(r)}function k(t){const e=JSON.parse(t),r=E(e.taxa);return x(e.trees[0].root,r)}function A(t){const e=JSON.parse(t),r=E(e.taxa),n=[];for(const t of e.trees){const e=x(t.root,r);n.push(e)}return n}function C(t,e="newick"){if(t.startsWith("http://")||t.startsWith("https://"))throw new Error("Fetching trees from the internet is not yet supported");switch(e){case"newick":return a(t);case"nexus":return p(t);case"phyloxml":return w(t);case"nexml":return M(t);case"phyjson":return A(t);default:throw new Error("Invalid schema")}}return e})())); //# sourceMappingURL=phylojs.min.js.map