Skip to content

Instantly share code, notes, and snippets.

@rdmpage
Created December 6, 2012 14:02
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/4224658 to your computer and use it in GitHub Desktop.
Save rdmpage/4224658 to your computer and use it in GitHub Desktop.
Javascript phylogeny viewer
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Javascript Phylogeny 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="treelib.js"></script>
</head>
<body>
<p><b>Paste in a <a href="http://en.wikipedia.org/wiki/Newick_format">Newick-format</a> tree description</b></p>
<div>
<textarea id="newick" 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('newick')">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 newick = element.value;
newick = newick.trim(newick);
t.Parse(newick);
if (t.error != 0)
{
document.getElementById('message').innerHTML='Error parsing tree';
}
else
{
document.getElementById('message').innerHTML='Parsed OK';
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 cssStyle = document.createElementNS('http://www.w3.org/2000/svg','style');
cssStyle.setAttribute('type','text/css');
var style=document.createTextNode("text{font-size:6px;}");
cssStyle.appendChild(style);
svg.appendChild(cssStyle);
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();
// label leaves...
var n = new NodeIterator(t.root);
var q = n.Begin();
while (q != null)
{
if (q.IsLeaf())
{
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, q.label, angle, align)
break;
case 'cladogram':
case 'rectanglecladogram':
case 'phylogram':
default:
drawText('viewport', q.xy, q.label);
break;
}
}
q = n.Next();
}
// pan
$('svg').svgPan('viewport');
}
}
</script>
</body>
</html>
/**
*
* 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