Skip to content

Instantly share code, notes, and snippets.

@rdmpage
Created December 6, 2012 22:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save rdmpage/4229068 to your computer and use it in GitHub Desktop.
Save rdmpage/4229068 to your computer and use it in GitHub Desktop.
NEXUS tree viewer
<!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>
/**
* 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;
}
/**
*
* 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