Created
December 6, 2012 22:27
-
-
Save rdmpage/4229068 to your computer and use it in GitHub Desktop.
NEXUS tree viewer
This file contains 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 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 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