Created
December 6, 2012 22:27
-
-
Save rdmpage/4229068 to your computer and use it in GitHub Desktop.
NEXUS tree viewer
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html> | |
| <head> | |
| <meta charset="utf-8" /> | |
| <title>NEXUS Tree Viewer</title> | |
| <style type="text/css" title="text/css"> | |
| @import url("/style.css?20120730"); | |
| </style> | |
| <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.8.3/jquery.min.js"></script> | |
| <script type="text/javascript" src="http://blog.accursedware.com/jquery-svgpan/jquery-svgpan.js"></script> | |
| <script src="nexus.js"></script> | |
| <script src="treelib.js"></script> | |
| </head> | |
| <body> | |
| <p><b>Paste in a <a href="http://en.wikipedia.org/wiki/Nexus_file">NEXUS</a> tree file</b></p> | |
| <div> | |
| <textarea id="nexus" rows="10" cols=100"></textarea> | |
| <select id="style"> | |
| <option value="cladogram">Cladogram</option> | |
| <option value="rectanglecladogram">Rectangular cladogram</option> | |
| <option value="phylogram">Phylogram</option> | |
| <option value="circle">Circle tree</option> | |
| <option value="circlephylogram">Circle phylogram</option> | |
| </select> | |
| <button onclick="showtree('nexus')">Show</button> | |
| </div> | |
| <p><span id="message"></span></p> | |
| <div style="width:500px;height:500px;background-color:white;border:1px solid rgb(228,228,228);"> | |
| <svg id="svg" xmlns="http://www.w3.org/2000/svg" version="1.1" height="500" width="500"> | |
| <g id="viewport"> | |
| </g> | |
| </svg> | |
| </div> | |
| <script> | |
| // http://stackoverflow.com/questions/498970/how-do-i-trim-a-string-in-javascript | |
| if (!String.prototype.trim) | |
| { | |
| String.prototype.trim=function(){return this.replace(/^\s+|\s+$/g, '');}; | |
| } | |
| function showtree(element_id) | |
| { | |
| var t = new Tree(); | |
| var element = document.getElementById(element_id); | |
| var text = element.value; | |
| text = text.trim(); | |
| var nexus = parse_nexus(text); | |
| if (nexus.status != NexusError.ok) | |
| { | |
| document.getElementById('message').innerHTML='Error parsing NEXUS'; | |
| } | |
| else | |
| { | |
| if (nexus.treesblock.trees.length == 0) | |
| { | |
| document.getElementById('message').innerHTML='No trees'; | |
| } | |
| else | |
| { | |
| newick = nexus.treesblock.trees[0].newick; | |
| t.Parse(newick); | |
| if (t.error != 0) | |
| { | |
| document.getElementById('message').innerHTML='Error parsing tree'; | |
| } | |
| else | |
| { | |
| document.getElementById('message').innerHTML='Parsed OK (use mouse to zoom and pan)'; | |
| //t.WriteNewick(); | |
| t.ComputeWeights(t.root); | |
| var td = null; | |
| var selectmenu = document.getElementById('style'); | |
| var drawing_type = (selectmenu.options[selectmenu.selectedIndex].value); | |
| switch (drawing_type) | |
| { | |
| case 'rectanglecladogram': | |
| td = new RectangleTreeDrawer(); | |
| break; | |
| case 'phylogram': | |
| if (t.has_edge_lengths) | |
| { | |
| td = new PhylogramTreeDrawer(); | |
| } | |
| else | |
| { | |
| td = new RectangleTreeDrawer(); | |
| } | |
| break; | |
| case 'circle': | |
| td = new CircleTreeDrawer(); | |
| break; | |
| case 'circlephylogram': | |
| if (t.has_edge_lengths) | |
| { | |
| td = new CirclePhylogramDrawer(); | |
| } | |
| else | |
| { | |
| td = new CircleTreeDrawer(); | |
| } | |
| break; | |
| case 'cladogram': | |
| default: | |
| td = new TreeDrawer(); | |
| break; | |
| } | |
| // clear existing diagram, if any | |
| var svg = document.getElementById('svg'); | |
| while (svg.hasChildNodes()) | |
| { | |
| svg.removeChild(svg.lastChild); | |
| } | |
| var g = document.createElementNS('http://www.w3.org/2000/svg','g'); | |
| g.setAttribute('id','viewport'); | |
| svg.appendChild(g); | |
| td.Init(t, {svg_id: 'viewport', width:500, height:500, fontHeight:10, root_length:0.1} ); | |
| td.CalcCoordinates(); | |
| td.Draw(); | |
| // font size | |
| var cssStyle = document.createElementNS('http://www.w3.org/2000/svg','style'); | |
| cssStyle.setAttribute('type','text/css'); | |
| var font_size = Math.floor(td.settings.height/t.num_leaves); | |
| var style=document.createTextNode("text{font-size:" + font_size + "px;}"); | |
| cssStyle.appendChild(style); | |
| svg.appendChild(cssStyle); | |
| // label leaves... | |
| var n = new NodeIterator(t.root); | |
| var q = n.Begin(); | |
| while (q != null) | |
| { | |
| if (q.IsLeaf()) | |
| { | |
| var label = q.label; | |
| if (nexus.treesblock.translate) | |
| { | |
| if (nexus.treesblock.translate[label]) | |
| { | |
| label = nexus.treesblock.translate[label]; | |
| } | |
| } | |
| switch (drawing_type) | |
| { | |
| case 'circle': | |
| case 'circlephylogram': | |
| var align = 'left'; | |
| var angle = q.angle * 180.0/Math.PI; | |
| if ((q.angle > Math.PI/2.0) && (q.angle < 1.5 * Math.PI)) | |
| { | |
| align = 'right'; | |
| angle += 180.0; | |
| } | |
| drawRotatedText('viewport', q.xy, label, angle, align) | |
| break; | |
| case 'cladogram': | |
| case 'rectanglecladogram': | |
| case 'phylogram': | |
| default: | |
| drawText('viewport', q.xy, label); | |
| break; | |
| } | |
| } | |
| q = n.Next(); | |
| } | |
| // Scale to fit window | |
| var bbox = svg.getBBox(); | |
| var scale = Math.min(td.settings.width/bbox.width, td.settings.height/bbox.height); | |
| // move drawing to centre of viewport | |
| var viewport = document.getElementById('viewport'); | |
| viewport.setAttribute('transform', 'scale(' + scale + ')'); | |
| // centre | |
| bbox = svg.getBBox(); | |
| if (bbox.x < 0) | |
| { | |
| viewport.setAttribute('transform', 'translate(' + -bbox.x + ' ' + -bbox.y + ')'); | |
| } | |
| // pan | |
| $('svg').svgPan('viewport'); | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| </body> | |
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Very basic NEXUS parser | |
| * | |
| * Supports TREES block | |
| * | |
| */ | |
| //-------------------------------------------------------------------------------------------------- | |
| // http://stackoverflow.com/questions/1303646/check-whether-variable-is-number-or-string-in-javascript | |
| function isNumber (o) { | |
| return ! isNaN (o-0); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| //https://raw.github.com/kvz/phpjs/master/functions/strings/strstr.js | |
| function strstr (haystack, needle, bool) { | |
| // http://kevin.vanzonneveld.net | |
| // + original by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) | |
| // + bugfixed by: Onno Marsman | |
| // + improved by: Kevin van Zonneveld (http://kevin.vanzonneveld.net) | |
| // * example 1: strstr('Kevin van Zonneveld', 'van'); | |
| // * returns 1: 'van Zonneveld' | |
| // * example 2: strstr('Kevin van Zonneveld', 'van', true); | |
| // * returns 2: 'Kevin ' | |
| // * example 3: strstr('name@example.com', '@'); | |
| // * returns 3: '@example.com' | |
| // * example 4: strstr('name@example.com', '@', true); | |
| // * returns 4: 'name' | |
| var pos = 0; | |
| haystack += ''; | |
| pos = haystack.indexOf(needle); | |
| if (pos == -1) { | |
| return false; | |
| } else { | |
| if (bool) { | |
| return haystack.substr(0, pos); | |
| } else { | |
| return haystack.slice(pos); | |
| } | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| // https://github.com/kvz/phpjs/blob/master/functions/strings/strchr.js | |
| function strchr (haystack, needle, bool) { | |
| // http://kevin.vanzonneveld.net | |
| // + original by: Philip Peterson | |
| // - depends on: strstr | |
| // * example 1: strchr('Kevin van Zonneveld', 'van'); | |
| // * returns 1: 'van Zonneveld' | |
| // * example 2: strchr('Kevin van Zonneveld', 'van', true); | |
| // * returns 2: 'Kevin ' | |
| return this.strstr(haystack, needle, bool); | |
| } | |
| var NEXUSPunctuation = "()[]{}/\\,;:=*'\"`+-"; | |
| var NEXUSWhiteSpace = "\n\r\t "; | |
| //-------------------------------------------------------------------------------------------------- | |
| function TokenTypes(){} | |
| TokenTypes.None = 0; | |
| TokenTypes.String = 1; | |
| TokenTypes.Hash = 2; | |
| TokenTypes.Number = 3; | |
| TokenTypes.SemiColon = 4; | |
| TokenTypes.OpenPar = 5; | |
| TokenTypes.ClosePar = 6; | |
| TokenTypes.Equals = 7; | |
| TokenTypes.Space = 8; | |
| TokenTypes.Comma = 9; | |
| TokenTypes.Asterix = 10; | |
| TokenTypes.Colon = 11; | |
| TokenTypes.Other = 12; | |
| TokenTypes.Bad = 13; | |
| TokenTypes.Minus = 14; | |
| TokenTypes.DoubleQuote = 15; | |
| TokenTypes.Period = 16; | |
| TokenTypes.Backslash = 17; | |
| TokenTypes.QuotedString = 18; | |
| //-------------------------------------------------------------------------------------------------- | |
| function NumberTokens(){} | |
| NumberTokens.start = 0; | |
| NumberTokens.sign = 1; | |
| NumberTokens.digit = 2; | |
| NumberTokens.fraction = 3; | |
| NumberTokens.expsymbol = 4; | |
| NumberTokens.expsign = 5; | |
| NumberTokens.exponent = 6; | |
| NumberTokens.bad = 7; | |
| NumberTokens.done = 8; | |
| //-------------------------------------------------------------------------------------------------- | |
| function StringTokens(){} | |
| StringTokens.ok = 0; | |
| StringTokens.quote = 1; | |
| StringTokens.done = 2; | |
| //-------------------------------------------------------------------------------------------------- | |
| function NexusError(){} | |
| NexusError.ok = 0; | |
| NexusError.nobegin = 1; | |
| NexusError.noend = 2; | |
| NexusError.syntax = 3; | |
| NexusError.badcommand = 4; | |
| NexusError.noblockname = 5; | |
| NexusError.badblock = 6; | |
| NexusError.nosemicolon = 7; | |
| //-------------------------------------------------------------------------------------------------- | |
| function Scanner(str) | |
| { | |
| this.error = 0; | |
| this.comment = ''; | |
| this.pos = 0; | |
| this.str = str; | |
| this.token = 0; | |
| this.buffer = ''; | |
| this.returnspace = false; | |
| } | |
| //---------------------------------------------------------------------------------------------- | |
| Scanner.prototype.GetToken = function(returnspace) | |
| { | |
| this.returnspace = typeof returnspace !== 'undefined' ? returnspace : false; | |
| this.token = TokenTypes.None; | |
| while ((this.token == TokenTypes.None) && (this.pos < this.str.length)) | |
| { | |
| //console.log(this.str.charAt(this.pos)); | |
| //console.log(this.buffer); | |
| //console.log(this.token); | |
| if (strchr(NEXUSWhiteSpace, this.str.charAt(this.pos))) | |
| { | |
| if (this.returnspace && (this.str.charAt(this.pos) == ' ')) | |
| { | |
| this.token = TokenTypes.Space; | |
| } | |
| } | |
| else | |
| { | |
| if (strchr(NEXUSPunctuation, this.str.charAt(this.pos))) | |
| { | |
| this.buffer = this.str.charAt(this.pos); | |
| switch (this.str.charAt(this.pos)) | |
| { | |
| case '[': | |
| this.ParseComment(); | |
| break; | |
| case "'": | |
| if (this.ParseString()) | |
| { | |
| this.token = TokenTypes.QuotedString; | |
| } | |
| else | |
| { | |
| this.token = TokenTypes.Bad; | |
| } | |
| break; | |
| case '(': | |
| this.token = TokenTypes.OpenPar; | |
| break; | |
| case ')': | |
| this.token = TokenTypes.ClosePar; | |
| break; | |
| case '=': | |
| this.token = TokenTypes.Equals; | |
| break; | |
| case ';': | |
| this.token = TokenTypes.SemiColon; | |
| break; | |
| case ',': | |
| this.token = TokenTypes.Comma; | |
| break; | |
| case '*': | |
| this.token = TokenTypes.Asterix; | |
| break; | |
| case ':': | |
| this.token = TokenTypes.Colon; | |
| break; | |
| case '-': | |
| this.token = TokenTypes.Minus; | |
| break; | |
| case '"': | |
| this.token = TokenTypes.DoubleQuote; | |
| break; | |
| case '/': | |
| this.token = TokenTypes.BackSlash; | |
| break; | |
| default: | |
| this.token = TokenTypes.Other; | |
| break; | |
| } | |
| } | |
| else | |
| { | |
| if (this.str.charAt(this.pos) == '#') | |
| { | |
| this.token = TokenTypes.Hash; | |
| } | |
| else if (this.str.charAt(this.pos) == '.') | |
| { | |
| this.token = TokenTypes.Period; | |
| } | |
| else | |
| { | |
| if (isNumber(this.str.charAt(this.pos))) | |
| { | |
| if (this.ParseNumber()) | |
| { | |
| this.token = TokenTypes.Number; | |
| } | |
| else | |
| { | |
| this.token = TokenTypes.Bad; | |
| } | |
| } | |
| else | |
| { | |
| if (this.ParseToken()) | |
| { | |
| this.token = TokenTypes.String; | |
| } | |
| else | |
| { | |
| this.token = TokenTypes.Bad; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| this.pos++; | |
| } | |
| return this.token; | |
| } | |
| //---------------------------------------------------------------------------------------------- | |
| Scanner.prototype.ParseComment = function() | |
| { | |
| this.buffer = ''; | |
| while ((this.str.charAt(this.pos) != ']') && (this.pos < this.str.length)) | |
| { | |
| this.buffer += this.str.charAt(this.pos); | |
| this.pos++; | |
| } | |
| this.buffer += this.str.charAt(this.pos); | |
| console.log('[' + this.buffer + ']'); | |
| } | |
| //---------------------------------------------------------------------------------------------- | |
| Scanner.prototype.ParseNumber = function() | |
| { | |
| this.buffer = ''; | |
| var state = NumberTokens.start; | |
| while ( | |
| (this.pos < this.str.length) | |
| && (!strchr (NEXUSWhiteSpace, this.str.charAt(this.pos))) | |
| && (!strchr (NEXUSPunctuation, this.str.charAt(this.pos))) | |
| && (this.str.charAt(this.pos) != '-') | |
| && (state != NumberTokens.bad) | |
| && (state != NumberTokens.done) | |
| ) | |
| { | |
| if (isNumber(this.str.charAt(this.pos))) | |
| { | |
| switch (state) | |
| { | |
| case NumberTokens.start: | |
| case NumberTokens.sign: | |
| state = NumberTokens.digit; | |
| break; | |
| case NumberTokens.expsymbol: | |
| case NumberTokens.expsign: | |
| state = NumberTokens.exponent; | |
| break; | |
| default: | |
| break; | |
| } | |
| } | |
| else if ((this.str.charAt(this.pos) == '-') && (this.str.charAt(this.pos) == '+')) | |
| { | |
| switch (state) | |
| { | |
| case NumberTokens.start: | |
| state = NumberTokens.sign; | |
| break; | |
| case NumberTokens.digit: | |
| state = NumberTokens.done; | |
| break; | |
| case NumberTokens.expsymbol: | |
| state = NumberTokens.expsign; | |
| break; | |
| default: | |
| state = NumberTokens.bad; | |
| break; | |
| } | |
| } | |
| else if ((this.str.charAt(this.pos) == '.') && (state == NumberTokens.digit)) | |
| { | |
| state = NumberTokens.fraction; | |
| } | |
| else if (((this.str.charAt(this.pos) == 'E') || (this.str.charAt(this.pos) == 'e')) && ((state == NumberTokens.digit) || (state == NumberTokens.fraction))) | |
| { | |
| state = NumberTokens.expsymbol; | |
| } | |
| else | |
| { | |
| state = NumberTokens.bad; | |
| } | |
| if ((state != NumberTokens.bad) && (state != NumberTokens.done)) | |
| { | |
| this.buffer += this.str.charAt(this.pos); | |
| this.pos++; | |
| } | |
| } | |
| this.pos--; | |
| //console.log(this.buffer); | |
| return true; | |
| } | |
| //---------------------------------------------------------------------------------------------- | |
| Scanner.prototype.ParseString = function() | |
| { | |
| this.buffer = ''; | |
| this.pos++; | |
| var state = StringTokens.ok; | |
| while ((state != StringTokens.done) && (this.pos < this.str.length)) | |
| { | |
| //console.log(this.pos + ' ' + this.str.charAt(this.pos)); | |
| switch (state) | |
| { | |
| case StringTokens.ok: | |
| if (this.str.charAt(this.pos) == "'") | |
| { | |
| state = StringTokens.quote; | |
| } | |
| else | |
| { | |
| this.buffer += this.str.charAt(this.pos); | |
| } | |
| break; | |
| case StringTokens.quote: | |
| if (this.str.charAt(this.pos) == "'") | |
| { | |
| this.buffer += this.str.charAt(this.pos); | |
| state = StringTokens.ok; | |
| } | |
| else | |
| { | |
| state = StringTokens.done; | |
| this.pos--; | |
| } | |
| break; | |
| default: | |
| break; | |
| } | |
| this.pos++; | |
| } | |
| this.pos--; | |
| //console.log(this.buffer); | |
| return (state == StringTokens.done) ? true : false; | |
| } | |
| //---------------------------------------------------------------------------------------------- | |
| Scanner.prototype.ParseToken = function() | |
| { | |
| this.buffer = ''; | |
| while ( | |
| this.pos < this.str.length | |
| && (!strchr (NEXUSWhiteSpace, this.str.charAt(this.pos))) | |
| && (!strchr (NEXUSPunctuation, this.str.charAt(this.pos))) | |
| ) | |
| { | |
| this.buffer += this.str.charAt(this.pos); | |
| this.pos++; | |
| } | |
| this.pos--; | |
| //console.log(this.buffer); | |
| return true; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| NexusReader.prototype = new Scanner; | |
| //---------------------------------------------------------------------------------------------- | |
| function NexusReader() | |
| { | |
| Scanner.apply(this, arguments); | |
| this.nexusCommands = ['begin', 'dimensions', 'end', 'endblock', 'link', 'taxa', 'taxlabels', 'title', 'translate', 'tree']; | |
| this.nexusBlocks = ['taxa', 'trees']; | |
| }; | |
| //---------------------------------------------------------------------------------------------- | |
| NexusReader.prototype.GetBlock = function() | |
| { | |
| var blockname = ''; | |
| var command = this.GetCommand(); | |
| //console.log('GetBlock: ' + this.buffer + ' ' + command); | |
| if (command.toLowerCase() != 'begin') | |
| { | |
| this.error = NexusError.nobegin; | |
| } | |
| else | |
| { | |
| // get block name | |
| var t = this.GetToken(); | |
| //console.log('GetCommand: ' + this.buffer); | |
| if (t == TokenTypes.String) | |
| { | |
| blockname = this.buffer.toLowerCase(); | |
| t = this.GetToken(); | |
| if (t != TokenTypes.SemiColon) | |
| { | |
| this.error = NexusError.noblockname; | |
| } | |
| } | |
| else | |
| { | |
| this.error = NexusError.noblockname; | |
| } | |
| } | |
| return blockname.toLowerCase(); | |
| } | |
| //---------------------------------------------------------------------------------------------- | |
| NexusReader.prototype.GetCommand = function() | |
| { | |
| var command = ''; | |
| var t = this.GetToken(); | |
| //console.log('GetCommand: ' + this.buffer); | |
| if (t == TokenTypes.String) | |
| { | |
| if (this.nexusCommands.indexOf(this.buffer.toLowerCase()) != -1) | |
| { | |
| command = this.buffer.toLowerCase(); | |
| } | |
| else | |
| { | |
| this.error = NexusError.badcommand; | |
| } | |
| } | |
| else | |
| { | |
| this.error = NexusError.syntax; | |
| } | |
| return command.toLowerCase(); | |
| } | |
| //---------------------------------------------------------------------------------------------- | |
| NexusReader.prototype.IsNexusFile = function() | |
| { | |
| this.error = NexusError.ok; | |
| var nexus = false; | |
| var t = this.GetToken(); | |
| if (t == TokenTypes.Hash) | |
| { | |
| t = this.GetToken(); | |
| if (t == TokenTypes.String) | |
| { | |
| nexus = ( this.buffer.toLowerCase() == 'nexus') ? true : false; | |
| } | |
| } | |
| return nexus; | |
| } | |
| //---------------------------------------------------------------------------------------------- | |
| NexusReader.prototype.SkipCommand = function() | |
| { | |
| var t = null; | |
| do { | |
| t = this.GetToken(); | |
| } while ((this.error == NexusError.ok) && (t != TokenTypes.SemiColon)); | |
| return this.error; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function parse_nexus(str) | |
| { | |
| var nexus = {}; | |
| nexus.status = NexusError.ok; | |
| var nx = new NexusReader(str); | |
| if (nx.IsNexusFile()) | |
| { | |
| //console.log('Is a NEXUS file'); | |
| } | |
| var blockname = nx.GetBlock(); | |
| //console.log("BLOCK="+blockname); | |
| if (blockname == 'taxa') | |
| { | |
| var command = nx.GetCommand(); | |
| while ( | |
| (command != 'end') | |
| && (command != 'endblock') | |
| && (nx.error == NexusError.ok) | |
| ) | |
| { | |
| switch (command) | |
| { | |
| case 'taxlabels': | |
| nx.SkipCommand(); | |
| command = nx.GetCommand(); | |
| break; | |
| default: | |
| //echo "Command to skip: $command\n"; | |
| nx.SkipCommand(); | |
| command = nx.GetCommand(); | |
| break; | |
| } | |
| // If end command eat the semicolon | |
| if ((command == 'end') || (command == 'endblock')) | |
| { | |
| nx.GetToken(); | |
| } | |
| } | |
| blockname = nx.GetBlock(); | |
| } | |
| if (blockname == 'trees') | |
| { | |
| nexus.treesblock = {}; | |
| nexus.treesblock.trees = []; | |
| command = nx.GetCommand(); | |
| while ( | |
| ((command != 'end') && (command != 'endblock')) | |
| && (nx.error == NexusError.ok) | |
| ) | |
| { | |
| // console.log(command); | |
| switch (command) | |
| { | |
| case 'translate': | |
| // translation table is an associative array | |
| nexus.treesblock.translate = {}; | |
| var done = false; | |
| while (!done && (nx.error == NexusError.ok)) | |
| { | |
| var t = nx.GetToken(); | |
| if ([TokenTypes.Number, TokenTypes.String, TokenTypes.QuotedString].indexOf(t) != -1) | |
| { | |
| var otu = nx.buffer; | |
| t = nx.GetToken(); | |
| if ([TokenTypes.Number, TokenTypes.String, TokenTypes.QuotedString].indexOf(t) != -1) | |
| { | |
| // cast otu to string | |
| nexus.treesblock.translate[String(otu)] = nx.buffer; | |
| //console.log(otu + ' ' + nx.buffer); | |
| t = nx.GetToken(); | |
| switch (t) | |
| { | |
| case TokenTypes.Comma: | |
| break; | |
| case TokenTypes.SemiColon: | |
| done = true; | |
| break; | |
| default: | |
| nx.error = NexusError.syntax; | |
| break; | |
| } | |
| } | |
| else | |
| { | |
| nx.error = NexusError.syntax; | |
| } | |
| } | |
| else | |
| { | |
| nx.error = NexusError.syntax; | |
| } | |
| } | |
| command = nx.GetCommand(); | |
| break; | |
| case 'tree': | |
| if (command == 'tree') | |
| { | |
| var tree = {}; | |
| t = nx.GetToken(); | |
| if (t == TokenTypes.Asterix) | |
| { | |
| tree.default = true; | |
| t = nx.GetToken(); | |
| } | |
| if (t == TokenTypes.String) | |
| { | |
| tree.label = nx.buffer; | |
| } | |
| t = nx.GetToken(); | |
| if (t == TokenTypes.Equals) | |
| { | |
| tree.newick = ''; | |
| t = nx.GetToken(); | |
| while (t != TokenTypes.SemiColon) | |
| { | |
| if (t == TokenTypes.QuotedString) | |
| { | |
| var s = nx.buffer; | |
| s = s.replace("'", "''"); | |
| s = "'" + s + "'"; | |
| tree.newick += s; | |
| } | |
| else | |
| { | |
| tree.newick += nx.buffer; | |
| } | |
| t = nx.GetToken(); | |
| } | |
| tree.newick += ';'; | |
| nexus.treesblock.trees.push(tree); | |
| } | |
| } | |
| command = nx.GetCommand(); | |
| break; | |
| default: | |
| //echo "Command to skip: $command\n"; | |
| nx.SkipCommand(); | |
| command = nx.GetCommand(); | |
| break; | |
| } | |
| // If end command eat the semicolon | |
| if ((command == 'end') || (command == 'endblock')) | |
| { | |
| nx.GetToken(); | |
| } | |
| } | |
| } | |
| nexus.status = nx.error; | |
| return nexus; | |
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * | |
| * Javascript library to display phylogenetic trees | |
| * | |
| */ | |
| //-------------------------------------------------------------------------------------------------- | |
| // http://stackoverflow.com/questions/3019278/any-way-to-specify-the-base-of-math-log-in-javascript | |
| function log10(val) { | |
| return Math.log(val) / Math.LN10; | |
| } | |
| // http://stackoverflow.com/questions/387707/whats-the-best-way-to-define-a-class-in-javascript | |
| //-------------------------------------------------------------------------------------------------- | |
| // http://stackoverflow.com/questions/1303646/check-whether-variable-is-number-or-string-in-javascript | |
| function isNumber (o) { | |
| return ! isNaN (o-0); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function ctype_alnum (str) | |
| { | |
| return (str.match(/^[a-z0-9]+$/i) != null); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function linePath(p0, p1) | |
| { | |
| var path = 'M ' + p0['x'] + ' ' + p0['y'] + ' ' + p1['x'] + ' ' + p1['y']; | |
| return path; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function drawLine(svg_id, p0, p1) | |
| { | |
| var line = document.createElementNS('http://www.w3.org/2000/svg','path'); | |
| //newLine.setAttribute('id','node' + p.id); | |
| line.setAttribute('vector-effect','non-scaling-stroke'); | |
| line.setAttribute('style','stroke:black;stroke-width:1;'); | |
| line.setAttribute('d', linePath(p0, p1)); | |
| var svg = document.getElementById(svg_id); | |
| svg.appendChild(line); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function drawText(svg_id, p, string) | |
| { | |
| var text = document.createElementNS('http://www.w3.org/2000/svg','text'); | |
| //newLine.setAttribute('id','node' + p.id); | |
| text.setAttribute('style','alignment-baseline:middle'); | |
| text.setAttribute('x', p['x']); | |
| text.setAttribute('y', p['y']); | |
| var textNode=document.createTextNode(string) | |
| text.appendChild(textNode); | |
| var svg = document.getElementById(svg_id); | |
| svg.appendChild(text); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function drawRotatedText(svg_id, p, string, angle, align) | |
| { | |
| var text = document.createElementNS('http://www.w3.org/2000/svg','text'); | |
| //newLine.setAttribute('id','node' + p.id); | |
| text.setAttribute('style','alignment-baseline:middle'); | |
| text.setAttribute('x', p['x']); | |
| text.setAttribute('y', p['y']); | |
| switch (align) | |
| { | |
| case 'left': | |
| text.setAttribute('text-anchor', 'start'); | |
| break; | |
| case 'centre': | |
| case 'center': | |
| text.setAttribute('text-anchor', 'middle'); | |
| break; | |
| case 'right': | |
| text.setAttribute('text-anchor', 'end'); | |
| break; | |
| default: | |
| text.setAttribute('text-anchor', 'start'); | |
| break; | |
| } | |
| if (angle != 0) | |
| { | |
| text.setAttribute('transform', 'rotate(' + angle + ' ' + p['x'] + ' ' + p['y'] + ')'); | |
| } | |
| var textNode=document.createTextNode(string) | |
| text.appendChild(textNode); | |
| var svg = document.getElementById(svg_id); | |
| svg.appendChild(text); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function circeArcPath(p0, p1, radius, large_arc_flag) | |
| { | |
| var path = 'M ' | |
| + p0['x'] + ' ' + p0['y'] | |
| + ' A ' + radius + ' ' + radius | |
| + ' 0 '; | |
| if (large_arc_flag) | |
| { | |
| path += ' 1 '; | |
| } | |
| else | |
| { | |
| path += ' 0 '; | |
| } | |
| path += ' 1 ' | |
| + p1['x'] + ' ' + p1['y'] ; | |
| return path; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function drawCircleArc(svg_id, p0, p1, radius, large_arc_flag) | |
| { | |
| var arc = document.createElementNS('http://www.w3.org/2000/svg','path'); | |
| arc.setAttribute('vector-effect','non-scaling-stroke'); | |
| arc.setAttribute('style','stroke:black;stroke-width:1;'); | |
| arc.setAttribute('fill','none'); | |
| var path = circeArcPath(p0, p1, radius, large_arc_flag); | |
| arc.setAttribute('d', path) | |
| var svg = document.getElementById(svg_id); | |
| svg.appendChild(arc); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function drawPath(svg_id, pathString) | |
| { | |
| var path = document.createElementNS('http://www.w3.org/2000/svg','path'); | |
| //newLine.setAttribute('id','node' + p.id); | |
| path.setAttribute('vector-effect','non-scaling-stroke'); | |
| path.setAttribute('style','stroke:blue;stroke-width:1;'); | |
| path.setAttribute('d', pathString); | |
| var svg = document.getElementById(svg_id); | |
| svg.appendChild(path); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| // http://stackoverflow.com/questions/894860/set-a-default-parameter-value-for-a-javascript-function | |
| function Node(label) | |
| { | |
| this.ancestor = null; | |
| this.child = null; | |
| this.sibling = null; | |
| this.label = typeof label !== 'undefined' ? label : ''; | |
| this.id = 0; | |
| this.weight = 0; | |
| this.xy = []; | |
| this.edge_length = 0.0; | |
| this.path_length = 0.0; | |
| this.depth = 0; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| Node.prototype.IsLeaf = function() | |
| { | |
| return (!this.child); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| Node.prototype.GetRightMostSibling = function() | |
| { | |
| var p = this; | |
| while (p.sibling) | |
| { | |
| p = p.sibling; | |
| } | |
| return p; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function Tree() | |
| { | |
| this.root = null; | |
| this.num_leaves = 0; | |
| this.num_nodes = 0; | |
| this.label_to_node_map = []; | |
| this.nodes = []; | |
| this.rooted = true; | |
| this.has_edge_lengths = false; | |
| this.error = 0; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| Tree.prototype.NewNode = function(label) | |
| { | |
| var node = new Node(label); | |
| node.id = this.num_nodes++; | |
| this.nodes[node.id] = node; | |
| if (typeof label !== undefined) | |
| { | |
| this.label_to_node_map[label] = node.id; | |
| } | |
| return node; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| Tree.prototype.Parse = function(str) | |
| { | |
| str = str.replace('"', ""); | |
| str = str.replace(/\(/g, "|(|"); | |
| str = str.replace(/\)/g, "|)|"); | |
| str = str.replace(/,/g, "|,|"); | |
| str = str.replace(/:/g, "|:|"); | |
| str = str.replace(/;/g, "|;|"); | |
| str = str.replace(/\|\|/g, "|"); | |
| str = str.replace(/^\|/, ""); | |
| str = str.replace(/\|$/, ""); | |
| //console.log(str); | |
| var token = str.split("|"); | |
| var curnode = this.NewNode(); | |
| this.root = curnode; | |
| var state = 0; | |
| var stack = []; | |
| var i = 0; | |
| var q = null; | |
| this.error = 0; | |
| while ((state != 99) && (this.error == 0)) | |
| { | |
| switch (state) | |
| { | |
| case 0: | |
| if (ctype_alnum(token[i].charAt(0))) | |
| { | |
| this.num_leaves++; | |
| label = token[i]; | |
| // to do: KML | |
| curnode.label = label; | |
| this.label_to_node_map[label] = curnode; | |
| i++; | |
| state = 1; | |
| } | |
| else | |
| { | |
| if (token[i].charAt(0) == "'") | |
| { | |
| label = token[i]; | |
| label = label.replace(/^'/, ""); | |
| label = label.replace(/'$/, ""); | |
| this.num_leaves++; | |
| // to do: KML | |
| curnode.label = label; | |
| this.label_to_node_map[label] = curnode; | |
| i++; | |
| state = 1; | |
| } | |
| else | |
| { | |
| switch (token[i]) | |
| { | |
| case '(': | |
| state = 2; | |
| break; | |
| default: | |
| state = 99; | |
| this.error = 1; // syntax | |
| break; | |
| } | |
| } | |
| } | |
| break; | |
| case 1: // getinternode | |
| switch (token[i]) | |
| { | |
| case ':': | |
| case ',': | |
| case ')': | |
| state = 2; | |
| break; | |
| default: | |
| state = 99; | |
| this.error = 1; // syntax | |
| break; | |
| } | |
| break; | |
| case 2: // nextmove | |
| switch (token[i]) | |
| { | |
| case ':': | |
| i++; | |
| if (isNumber(token[i])) | |
| { | |
| curnode.edge_length = parseFloat(token[i]); | |
| this.has_edge_lengths = true; | |
| i++; | |
| } | |
| break; | |
| case ',': | |
| q = this.NewNode(); | |
| curnode.sibling = q; | |
| var c = stack.length; | |
| if (c == 0) | |
| { | |
| state = 99; | |
| this.error = 2; // missing ( | |
| } | |
| else | |
| { | |
| q.ancestor = stack[c - 1]; | |
| curnode = q; | |
| state = 0; | |
| i++; | |
| } | |
| break; | |
| case '(': | |
| stack.push(curnode); | |
| q = this.NewNode(); | |
| curnode.child = q; | |
| q.ancestor = curnode; | |
| curnode = q; | |
| state = 0; | |
| i++; | |
| break; | |
| case ')': | |
| if (stack.length == 0) | |
| { | |
| state = 99; | |
| this.error = 3; // unbalanced | |
| } | |
| else | |
| { | |
| curnode = stack.pop(); | |
| state = 3; | |
| i++; | |
| } | |
| break; | |
| case ';': | |
| if (stack.length == 0) | |
| { | |
| state = 99; | |
| } | |
| else | |
| { | |
| state = 99; | |
| this.error = 4; // stack not empty | |
| } | |
| break; | |
| default: | |
| state = 99; | |
| this.error = 1; // syntax | |
| break; | |
| } | |
| break; | |
| case 3: // finishchildren | |
| if (ctype_alnum(token[i].charAt(0))) | |
| { | |
| curnode.label = token[i]; | |
| this.label_to_node_map[token[i]] = curnode; | |
| i++; | |
| } | |
| else | |
| { | |
| switch (token[i]) | |
| { | |
| case ':': | |
| i++; | |
| if (isNumber(token[i])) | |
| { | |
| curnode.edge_length = parseFloat(token[i]); | |
| this.has_edge_lengths = true; | |
| i++; | |
| } | |
| break; | |
| case ')': | |
| if (stack.length == 0) | |
| { | |
| state = 99; | |
| this.error = 3; // unbalanced | |
| } | |
| else | |
| { | |
| curnode = stack.pop(); | |
| i++; | |
| } | |
| break; | |
| case ',': | |
| q = this.NewNode(); | |
| curnode.sibling = q; | |
| if (stack.length == 0) | |
| { | |
| state = 99; | |
| this.error = 2; // missing ( | |
| } | |
| else | |
| { | |
| q.ancestor = stack[stack.length - 1]; | |
| curnode = q; | |
| state = 0; | |
| i++; | |
| } | |
| break; | |
| case ';': | |
| state = 2; | |
| break; | |
| default: | |
| state = 99; | |
| this.error = 1; // syntax | |
| break; | |
| } | |
| } | |
| break; | |
| } | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| Tree.prototype.ComputeWeights = function(p) | |
| { | |
| if (p) | |
| { | |
| p.weight = 0; | |
| this.ComputeWeights(p.child); | |
| this.ComputeWeights(p.sibling); | |
| if (p.IsLeaf()) | |
| { | |
| p.weight = 1; | |
| } | |
| if (p.ancestor) | |
| { | |
| p.ancestor.weight += p.weight; | |
| } | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| Tree.prototype.ComputeDepths = function() | |
| { | |
| for (var i in this.nodes) | |
| { | |
| if (this.nodes[i].IsLeaf()) | |
| { | |
| p = this.nodes[i].ancestor; | |
| var count = 1; | |
| while (p) | |
| { | |
| p.depth = Math.max(p.depth, count); | |
| count++; | |
| p = p.ancestor; | |
| } | |
| } | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| Tree.prototype.WriteNewick = function() | |
| { | |
| var newick = ''; | |
| var stack = []; | |
| var curnode = this.root; | |
| while (curnode) | |
| { | |
| //console.log(curnode.label); | |
| if (curnode.child) | |
| { | |
| newick += '('; | |
| stack.push(curnode); | |
| curnode = curnode.child; | |
| } | |
| else | |
| { | |
| newick += curnode.label; | |
| var length = curnode.edge_length; | |
| if (length) | |
| { | |
| newick += ':' + length; | |
| } | |
| while (stack.length > 0 && curnode.sibling == null) | |
| { | |
| newick += ')'; | |
| curnode = stack.pop(); | |
| // internal node label and length | |
| if (typeof curnode.label !== undefined) | |
| { | |
| newick += curnode.label; | |
| } | |
| var length = curnode.edge_length; | |
| if (length) | |
| { | |
| newick += ':' + length; | |
| } | |
| } | |
| if (stack.length == 0) | |
| { | |
| curnode = null; | |
| } | |
| else | |
| { | |
| newick += ','; | |
| curnode = curnode.sibling; | |
| } | |
| } | |
| } | |
| newick += ';'; | |
| return newick; | |
| //console.log(newick); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function NodeIterator(root) | |
| { | |
| this.root = root; | |
| this.cur = null; | |
| this.stack = []; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| NodeIterator.prototype.Begin = function() | |
| { | |
| this.cur = this.root; | |
| while (this.cur.child) | |
| { | |
| this.stack.push(this.cur); | |
| this.cur = this.cur.child; | |
| } | |
| return this.cur; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| NodeIterator.prototype.Next = function() | |
| { | |
| if (this.stack.length == 0) | |
| { | |
| this.cur = null; | |
| } | |
| else | |
| { | |
| if (this.cur.sibling) | |
| { | |
| var p = this.cur.sibling; | |
| while (p.child) | |
| { | |
| this.stack.push(p); | |
| p = p.child; | |
| } | |
| this.cur = p; | |
| } | |
| else | |
| { | |
| this.cur = this.stack.pop(); | |
| } | |
| } | |
| return this.cur; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| PreorderIterator.prototype = new NodeIterator; | |
| function PreorderIterator() | |
| { | |
| NodeIterator.apply(this, arguments) | |
| }; | |
| //-------------------------------------------------------------------------------------------------- | |
| PreorderIterator.prototype.Begin = function() | |
| { | |
| this.cur = this.root; | |
| return this.cur; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| PreorderIterator.prototype.Next = function() | |
| { | |
| if (this.cur.child) | |
| { | |
| this.stack.push(this.cur); | |
| this.cur = this.cur.child; | |
| } | |
| else | |
| { | |
| while (this.stack.length > 0 && this.cur.sibling == null) | |
| { | |
| this.cur = this.stack.pop(); | |
| } | |
| if (this.stack.length == 0) | |
| { | |
| this.cur = null; | |
| } | |
| else | |
| { | |
| this.cur = this.cur.sibling; | |
| } | |
| } | |
| return this.cur; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| function TreeDrawer() | |
| { | |
| //this.t = tree; | |
| this.leaf_count = 0; | |
| this.leaf_gap = 0; | |
| this.node_gap = 0; | |
| this.last_y = 0; | |
| this.svg_id; | |
| this.draw_scale_bar = false; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| TreeDrawer.prototype.Init = function(tree, settings) | |
| { | |
| this.t = tree; | |
| // defaults | |
| this.settings = settings; | |
| this.left = 0; | |
| this.top = 0; | |
| /* | |
| if (this.settings.fontHeight) | |
| { | |
| this.top += this.settings.fontHeight/2.0; | |
| this.settings.height -= this.settings.fontHeight; | |
| } | |
| */ | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| TreeDrawer.prototype.CalcInternal = function(p) | |
| { | |
| var pt = []; | |
| pt['x'] = this.left + this.node_gap * (this.t.num_leaves - p.weight); | |
| pt['y'] = this.last_y - ((p.weight - 1) * this.leaf_gap)/2; | |
| p.xy = pt; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| TreeDrawer.prototype.CalcLeaf = function(p) | |
| { | |
| var pt = []; | |
| pt['y'] = this.top + (this.leaf_count * this.leaf_gap); | |
| this.last_y = pt['y']; | |
| this.leaf_count++; | |
| // slanted cladogram | |
| pt['x'] = this.left + this.settings.width; | |
| p.xy = pt; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| TreeDrawer.prototype.CalcNodeGap = function() | |
| { | |
| if (this.t.rooted) | |
| { | |
| this.node_gap = this.settings.width / this.t.num_leaves; | |
| this.left += this.node_gap; | |
| this.settings.width -= this.node_gap; | |
| } | |
| else | |
| { | |
| this.node_gap = this.settings.width / (this.t.num_leaves - 1); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| TreeDrawer.prototype.CalcCoordinates = function() | |
| { | |
| this.t.ComputeWeights(this.t.root); | |
| this.leaf_count = 0; | |
| this.leaf_gap = this.settings.height/(this.t.num_leaves - 1); | |
| this.CalcNodeGap(); | |
| var n = new NodeIterator(this.t.root); | |
| var q = n.Begin(); | |
| while (q != null) | |
| { | |
| if (q.IsLeaf()) | |
| { | |
| this.CalcLeaf(q); | |
| } | |
| else | |
| { | |
| this.CalcInternal(q); | |
| } | |
| q = n.Next(); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| TreeDrawer.prototype.DrawLeaf = function(p) | |
| { | |
| var p0 = p.xy | |
| var anc = p.ancestor; | |
| if (anc) | |
| { | |
| var p1 = anc.xy; | |
| drawLine(this.settings.svg_id, p0, p1); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| TreeDrawer.prototype.DrawInternal = function(p) | |
| { | |
| var p0 = p.xy | |
| var anc = p.ancestor; | |
| if (anc) | |
| { | |
| var p1 = anc.xy; | |
| drawLine(this.settings.svg_id, p0, p1); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| TreeDrawer.prototype.DrawRoot = function() | |
| { | |
| var p0 = this.t.root.xy | |
| var p1 = []; | |
| p1['x'] = p0['x']; | |
| p1['y'] = p0['y']; | |
| p1['x'] -= this.node_gap; | |
| drawLine(this.settings.svg_id, p0, p1); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| TreeDrawer.prototype.Draw = function() | |
| { | |
| var n = new NodeIterator(this.t.root); | |
| var q = n.Begin(); | |
| while (q != null) | |
| { | |
| if (q.IsLeaf()) | |
| { | |
| this.DrawLeaf(q); | |
| } | |
| else | |
| { | |
| this.DrawInternal(q); | |
| } | |
| q = n.Next(); | |
| } | |
| if (this.t.rooted) | |
| { | |
| this.DrawRoot(); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| RectangleTreeDrawer.prototype = new TreeDrawer(); | |
| function RectangleTreeDrawer() | |
| { | |
| TreeDrawer.apply(this, arguments); | |
| this.max_depth = 0; | |
| }; | |
| //-------------------------------------------------------------------------------------------------- | |
| RectangleTreeDrawer.prototype.CalcInternal = function(p) | |
| { | |
| var pt = []; | |
| pt['x'] = this.left + this.node_gap * (this.t.root.depth - p.depth); | |
| var pl = p.child.xy; | |
| var pr = p.child.GetRightMostSibling().xy; | |
| pt['y'] = pl['y'] + (pr['y'] - pl['y'])/2; | |
| p.xy['x'] = pt['x']; | |
| p.xy['y'] = pt['y']; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| RectangleTreeDrawer.prototype.CalcNodeGap = function() | |
| { | |
| this.t.ComputeDepths(); | |
| //console.log(this.t.root.depth); | |
| if (this.t.rooted) | |
| { | |
| this.node_gap = this.settings.width / (this.t.root.depth + 1); | |
| this.left += this.node_gap; | |
| this.settings.width -= this.node_gap; | |
| } | |
| else | |
| { | |
| this.node_gap = this.settings.width / this.t.root.depth; | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| RectangleTreeDrawer.prototype.DrawLeaf = function(p) | |
| { | |
| var p0 = p.xy | |
| var p1 = []; | |
| var anc = p.ancestor; | |
| if (anc) | |
| { | |
| p1['x'] = anc.xy['x']; | |
| p1['y'] = p0['y']; | |
| drawLine(this.settings.svg_id, p0, p1); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| RectangleTreeDrawer.prototype.DrawInternal = function(p) | |
| { | |
| var p0 = []; | |
| var p1 = []; | |
| p0['x'] = p.xy['x']; | |
| p0['y'] = p.xy['y']; | |
| var anc = p.ancestor; | |
| if (anc) | |
| { | |
| p1['x'] = anc.xy['x']; | |
| p1['y'] = p0['y']; | |
| drawLine(this.settings.svg_id, p0, p1); | |
| } | |
| // vertical line | |
| var pl = p.child.xy; | |
| var pr = p.child.GetRightMostSibling().xy; | |
| p0['x'] = p0['x']; | |
| p0['y'] = pl['y']; | |
| p1['x'] = p0['x']; | |
| p1['y'] = pr['y']; | |
| drawLine(this.settings.svg_id, p0, p1); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| PhylogramTreeDrawer.prototype = new RectangleTreeDrawer(); | |
| function PhylogramTreeDrawer() | |
| { | |
| RectangleTreeDrawer.apply(this, arguments); | |
| this.max_path_length = 0; | |
| this.draw_scale_bar = true; | |
| }; | |
| //-------------------------------------------------------------------------------------------------- | |
| PhylogramTreeDrawer.prototype.CalcInternal = function(p) | |
| { | |
| var pt = []; | |
| pt['x'] = this.left + (p.path_length / this.max_path_length) * this.settings.width; | |
| var pl = p.child.xy; | |
| var pr = p.child.GetRightMostSibling().xy; | |
| pt['y'] = pl['y'] + (pr['y'] - pl['y'])/2; | |
| p.xy['x'] = pt['x']; | |
| p.xy['y'] = pt['y']; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| PhylogramTreeDrawer.prototype.CalcLeaf = function(p) | |
| { | |
| var pt = []; | |
| pt['x'] = this.left + (p.path_length / this.max_path_length) * this.settings.width; | |
| pt['y'] = this.top + (this.leaf_count * this.leaf_gap); | |
| this.last_y = pt['y']; | |
| this.leaf_count++; | |
| p.xy['x'] = pt['x']; | |
| p.xy['y'] = pt['y']; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| PhylogramTreeDrawer.prototype.CalcCoordinates = function() | |
| { | |
| this.max_path_length = 0; | |
| //console.log(this.max_path_length); | |
| this.t.root.path_length = this.t.root.edge_length; | |
| // build path lengths | |
| var n = new PreorderIterator(this.t.root); | |
| var q = n.Begin(); | |
| while (q != null) | |
| { | |
| var d = q.edge_length; | |
| if (d < 0.00001) | |
| { | |
| d = 0.0; | |
| } | |
| if (q != this.t.root) | |
| { | |
| q.path_length = q.ancestor.path_length + d; | |
| } | |
| //console.log(q.label + ' ' + q.path_length + ' ' + q.edge_length); | |
| this.max_path_length = Math.max(this.max_path_length, q.path_length); | |
| q = n.Next(); | |
| } | |
| //console.log(this.max_path_length); | |
| this.leaf_count = 0; | |
| this.leaf_gap = this.settings.height/(this.t.num_leaves - 1); | |
| n = new NodeIterator(this.t.root); | |
| var q = n.Begin(); | |
| while (q != null) | |
| { | |
| if (q.IsLeaf()) | |
| { | |
| this.CalcLeaf(q); | |
| } | |
| else | |
| { | |
| this.CalcInternal(q); | |
| } | |
| q = n.Next(); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| PhylogramTreeDrawer.prototype.Draw = function() | |
| { | |
| // parent method | |
| RectangleTreeDrawer.prototype.Draw.call(this); | |
| // scale bar | |
| if (this.draw_scale_bar) | |
| { | |
| this.DrawScaleBar(); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| PhylogramTreeDrawer.prototype.DrawScaleBar = function() | |
| { | |
| var p0 = []; | |
| var p1 = []; | |
| var m = log10(this.max_path_length); | |
| var i = Math.floor(m); | |
| var bar = Math.pow(10,i); | |
| var scalebar = (bar/this.max_path_length) * this.settings.width; | |
| p0['x'] = this.left; | |
| p0['y'] = this.top + this.settings.height + this.leaf_gap; | |
| p1['x'] = p0['x'] + scalebar; | |
| p1['y'] = p0['y']; | |
| drawLine(this.settings.svg_id, p0, p1); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CircleTreeDrawer.prototype = new RectangleTreeDrawer(); | |
| function CircleTreeDrawer() | |
| { | |
| RectangleTreeDrawer.apply(this, arguments); | |
| this.leaf_angle = 0; | |
| this.leaf_radius = 0; | |
| this.max_path_length = 0; | |
| this.root_length = 0; | |
| }; | |
| //-------------------------------------------------------------------------------------------------- | |
| CircleTreeDrawer.prototype.CalcInternalRadius = function(p) | |
| { | |
| p.radius = this.node_gap * (this.t.root.depth - p.depth); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CircleTreeDrawer.prototype.CalcInternal = function(p) | |
| { | |
| var left_angle = p.child.angle; | |
| var right_angle = p.child.GetRightMostSibling().angle; | |
| p.angle = left_angle + (right_angle - left_angle)/2; | |
| this.CalcInternalRadius(p); | |
| var pt = []; | |
| pt['x'] = p.radius * Math.cos(p.angle); | |
| pt['y'] = p.radius * Math.sin(p.angle); | |
| p.xy['x'] = pt['x']; | |
| p.xy['y'] = pt['y']; | |
| var q = p.child; | |
| while (q) | |
| { | |
| pt = []; | |
| pt['x'] = p.radius * Math.cos(q.angle); | |
| pt['y'] = p.radius * Math.sin(q.angle); | |
| q.backarc = []; | |
| q.backarc['x'] = pt['x']; | |
| q.backarc['y'] = pt['y']; | |
| q = q.sibling; | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CircleTreeDrawer.prototype.CalcLeafRadius = function(p) | |
| { | |
| p.radius = this.leaf_radius; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CircleTreeDrawer.prototype.CalcLeaf = function(p) | |
| { | |
| p.angle = this.leaf_angle * this.leaf_count; | |
| this.leaf_count++; | |
| this.CalcLeafRadius(p); | |
| var pt = []; | |
| pt['x'] = p.radius * Math.cos(p.angle); | |
| pt['y'] = p.radius * Math.sin(p.angle); | |
| p.xy['x'] = pt['x']; | |
| p.xy['y'] = pt['y']; | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CircleTreeDrawer.prototype.DrawLeaf = function(p) | |
| { | |
| var p0 = p.xy | |
| var p1 = p.backarc; | |
| drawLine(this.settings.svg_id, p0, p1); | |
| /* | |
| var p0 = p.xy | |
| var p1 = p.backarc; | |
| var path = linePath(p0, p1); | |
| var anc = p.ancestor; | |
| if (anc) | |
| { | |
| var p2 = anc.xy; | |
| var large_arc_flag = false; | |
| if (p.angle < anc.angle) | |
| { | |
| large_arc_flag = (Math.abs(anc.angle - p.angle) > Math.PI) ? true : false; | |
| path += circeArcPath(p1, p2, p.radius, large_arc_flag); | |
| } | |
| else | |
| { | |
| large_arc_flag = (Math.abs(p.angle - anc.angle) > Math.PI) ? true : false; | |
| path += circeArcPath(p2, p1, p.radius, large_arc_flag); | |
| } | |
| } | |
| drawPath(path); | |
| */ | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CircleTreeDrawer.prototype.DrawInternal = function(p) | |
| { | |
| var p0 = []; | |
| var p1 = []; | |
| p0['x'] = p.xy['x']; | |
| p0['y'] = p.xy['y']; | |
| var anc = p.ancestor; | |
| if (anc) | |
| { | |
| p0 = p.xy; | |
| p1 = p.backarc; | |
| drawLine(this.settings.svg_id, p0, p1); | |
| } | |
| // draw arc | |
| p0 = p.child.backarc; | |
| p1 = p.child.GetRightMostSibling().backarc; | |
| var large_arc_flag = (Math.abs(p.child.GetRightMostSibling().angle - p.child.angle) > Math.PI) ? true : false; | |
| drawCircleArc(this.settings.svg_id, p0, p1, p.radius, large_arc_flag); | |
| /* | |
| var anc = p.ancestor; | |
| if (anc) | |
| { | |
| var p0 = p.xy | |
| var p1 = p.backarc; | |
| var path = '';//linePath(p0, p1); | |
| var p2 = anc.xy; | |
| var large_arc_flag = false; //(Math.abs(p.angle - anc.angle) > Math.PI) ? true : false; | |
| if (p.angle < anc.angle) | |
| { | |
| path += circeArcPath(p1, p2, p.radius, large_arc_flag); | |
| } | |
| else | |
| { | |
| path += circeArcPath(p2, p1, p.radius, large_arc_flag); | |
| } | |
| drawPath(path); | |
| } | |
| */ | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CircleTreeDrawer.prototype.DrawRoot = function() | |
| { | |
| var p0 = this.t.root.xy | |
| var p1 = []; | |
| p1['x'] = 0; | |
| p1['y'] = 0; | |
| drawLine(this.settings.svg_id, p0, p1); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CircleTreeDrawer.prototype.CalcCoordinates = function() | |
| { | |
| this.t.ComputeDepths(); | |
| this.max_path_length = 0; | |
| //console.log(this.max_path_length); | |
| this.t.root.path_length = this.t.root.edge_length; | |
| // build path lengths | |
| var n = new PreorderIterator(this.t.root); | |
| var q = n.Begin(); | |
| while (q != null) | |
| { | |
| var d = q.edge_length; | |
| if (d < 0.00001) | |
| { | |
| d = 0.0; | |
| } | |
| if (q != this.t.root) | |
| { | |
| q.path_length = q.ancestor.path_length + d; | |
| } | |
| //console.log(q.label + ' ' + q.path_length + ' ' + q.edge_length); | |
| this.max_path_length = Math.max(this.max_path_length, q.path_length); | |
| q = n.Next(); | |
| } | |
| this.leaf_count = 0; | |
| this.leaf_angle = 2 * Math.PI / this.t.num_leaves; | |
| this.leaf_radius = this.settings.width/2; | |
| this.node_gap = this.leaf_radius / this.t.root.depth; | |
| n = new NodeIterator(this.t.root); | |
| var q = n.Begin(); | |
| while (q != null) | |
| { | |
| if (q.IsLeaf()) | |
| { | |
| this.CalcLeaf(q); | |
| } | |
| else | |
| { | |
| this.CalcInternal(q); | |
| } | |
| q = n.Next(); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CircleTreeDrawer.prototype.Draw = function() | |
| { | |
| // parent method | |
| TreeDrawer.prototype.Draw.call(this); | |
| // move drawing to centre of viewport | |
| var viewport = document.getElementById(this.settings.svg_id); | |
| viewport.setAttribute('transform', 'translate(' + (this.settings.width + this.root_length)/2 + ' ' + this.settings.height/2 + ')'); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CirclePhylogramDrawer.prototype = new CircleTreeDrawer(); | |
| function CirclePhylogramDrawer() | |
| { | |
| CircleTreeDrawer.apply(this, arguments) | |
| this.max_path_length = 0; | |
| this.draw_scale_bar = true; | |
| }; | |
| //-------------------------------------------------------------------------------------------------- | |
| CirclePhylogramDrawer.prototype.CalcInternalRadius = function(p) | |
| { | |
| p.radius = this.root_length + (p.path_length / this.max_path_length) * (this.settings.width/2) | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CirclePhylogramDrawer.prototype.CalcLeafRadius = function(p) | |
| { | |
| p.radius = this.root_length + (p.path_length / this.max_path_length) * (this.settings.width/2) | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| CirclePhylogramDrawer.prototype.CalcCoordinates = function() | |
| { | |
| this.max_path_length = 0; | |
| //console.log(this.max_path_length); | |
| if (this.settings.root_length) | |
| { | |
| this.root_length = this.settings.root_length * (this.settings.width/2); | |
| this.settings.width -= 2 * this.root_length; | |
| } | |
| this.t.root.path_length = this.t.root.edge_length; | |
| // build path lengths | |
| var n = new PreorderIterator(this.t.root); | |
| var q = n.Begin(); | |
| while (q != null) | |
| { | |
| var d = q.edge_length; | |
| if (d < 0.00001) | |
| { | |
| d = 0.0; | |
| } | |
| if (q != this.t.root) | |
| { | |
| q.path_length = q.ancestor.path_length + d; | |
| } | |
| this.max_path_length = Math.max(this.max_path_length, q.path_length); | |
| q = n.Next(); | |
| } | |
| this.leaf_count = 0; | |
| this.leaf_angle = 2 * Math.PI / this.t.num_leaves; | |
| n = new NodeIterator(this.t.root); | |
| var q = n.Begin(); | |
| while (q != null) | |
| { | |
| if (q.IsLeaf()) | |
| { | |
| this.CalcLeaf(q); | |
| } | |
| else | |
| { | |
| this.CalcInternal(q); | |
| } | |
| q = n.Next(); | |
| } | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| // http://stackoverflow.com/questions/3231459/create-unique-id-with-javascript | |
| function uniqueid(){ | |
| // always start with a letter (for DOM friendlyness) | |
| var idstr=String.fromCharCode(Math.floor((Math.random()*25)+65)); | |
| do { | |
| // between numbers and characters (48 is 0 and 90 is Z (42-48 = 90) | |
| var ascicode=Math.floor((Math.random()*42)+48); | |
| if (ascicode<58 || ascicode>64){ | |
| // exclude all chars between : (58) and @ (64) | |
| idstr+=String.fromCharCode(ascicode); | |
| } | |
| } while (idstr.length<32); | |
| return (idstr); | |
| } | |
| //-------------------------------------------------------------------------------------------------- | |
| // Draw a tree using Newick tree description for tag "element" | |
| function draw_tree(element) | |
| { | |
| var resize = false; | |
| var height = 200; | |
| var width = 200; | |
| if (element.style.width) | |
| { | |
| width = parseInt(element.style.width); | |
| } | |
| if (element.style.height) | |
| { | |
| height = parseInt(element.style.height); | |
| } | |
| width = Math.min(width, height); | |
| element.style.width = width + 'px'; | |
| element.style.height = height + 'px'; | |
| element.style.overflow = 'hidden'; | |
| var t = new Tree(); | |
| t.Parse(element.innerHTML); | |
| element.innerHTML = ''; | |
| var svg_id = uniqueid(); | |
| var svg_g_id = uniqueid(); | |
| var svg = document.createElementNS('http://www.w3.org/2000/svg','svg'); | |
| svg.setAttribute('id',svg_id); | |
| svg.setAttribute('version','1.1'); | |
| svg.setAttribute('height',height); | |
| svg.setAttribute('width',width); | |
| element.appendChild(svg); | |
| var g = document.createElementNS('http://www.w3.org/2000/svg','g'); | |
| g.setAttribute('id',svg_g_id); | |
| svg.appendChild(g); | |
| var td = null; | |
| var drawing_type = 'cladogram'; | |
| if (element.hasAttribute('data-drawing-type')) | |
| { | |
| drawing_type = element.getAttribute('data-drawing-type'); | |
| } | |
| switch (drawing_type) | |
| { | |
| case 'rectanglecladogram': | |
| td = new RectangleTreeDrawer(); | |
| break; | |
| case 'phylogram': | |
| td = new PhylogramTreeDrawer(); | |
| break; | |
| case 'circle': | |
| td = new CircleTreeDrawer(); | |
| break; | |
| case 'circlephylogram': | |
| td = new CirclePhylogramDrawer(); | |
| break; | |
| case 'cladogram': | |
| default: | |
| td = new TreeDrawer(); | |
| break; | |
| } | |
| td.Init(t, {svg_id: svg_g_id, width:width, height:height, root_length:0.1} ); | |
| td.CalcCoordinates(); | |
| td.Draw(); | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment