Skip to content

Instantly share code, notes, and snippets.

@timelyportfolio
Last active August 29, 2015 14:13
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 timelyportfolio/59acc3853b02e47e0dfc to your computer and use it in GitHub Desktop.
Save timelyportfolio/59acc3853b02e47e0dfc to your computer and use it in GitHub Desktop.
phylogram options for networkD3

Very early and crude implementation of a couple different phylogram options for networkD3.

Code to reproduce is in this repo code.R, but I'll also paste here for visibility.

devtools::install_github("timelyportfolio/networkD3@feature/phylogram")

library(networkD3)
library(htmltools)

html_print(tagList(
  tags$h1("phylogram")
  ,phyloNetwork("((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0")
  ,tags$h1("phylogram radial")
  ,phyloNetwork("((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0",type ="phylogram.radial")    
  ,tags$h1("phylonator")
  ,phyloNetwork("((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0",type ="phylonator")
  ,tags$h1("phylonator no scaling")
  ,phyloNetwork("((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0",type ="phylonator", skipBranchLengthScaling = T)  
))
devtools::install_github("timelyportfolio/networkD3@feature/phylogram")
library(networkD3)
library(htmltools)
html_print(tagList(
tags$h1("phylogram")
,phyloNetwork("((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0")
,tags$h1("phylogram radial")
,phyloNetwork("((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0",type ="phylogram.radial")
,tags$h1("phylonator")
,phyloNetwork("((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0",type ="phylonator")
,tags$h1("phylonator no scaling")
,phyloNetwork("((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0",type ="phylonator", skipBranchLengthScaling = T)
))
/*
d3.phylogram.js
Wrapper around a d3-based phylogram (tree where branch lengths are scaled)
Also includes a radial dendrogram visualization (branch lengths not scaled)
along with some helper methods for building angled-branch trees.
Copyright (c) 2013, Ken-ichi Ueda
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. Redistributions in binary
form must reproduce the above copyright notice, this list of conditions and
the following disclaimer in the documentation and/or other materials
provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
DOCUMENTATION
d3.phylogram.build(selector, nodes, options)
Creates a phylogram.
Arguments:
selector: selector of an element that will contain the SVG
nodes: JS object of nodes
Options:
width
Width of the vis, will attempt to set a default based on the width of
the container.
height
Height of the vis, will attempt to set a default based on the height
of the container.
vis
Pre-constructed d3 vis.
tree
Pre-constructed d3 tree layout.
children
Function for retrieving an array of children given a node. Default is
to assume each node has an attribute called "branchset"
diagonal
Function that creates the d attribute for an svg:path. Defaults to a
right-angle diagonal.
skipTicks
Skip the tick rule.
skipBranchLengthScaling
Make a dendrogram instead of a phylogram.
d3.phylogram.buildRadial(selector, nodes, options)
Creates a radial dendrogram.
Options: same as build, but without diagonal, skipTicks, and
skipBranchLengthScaling
d3.phylogram.rightAngleDiagonal()
Similar to d3.diagonal except it create an orthogonal crook instead of a
smooth Bezier curve.
d3.phylogram.radialRightAngleDiagonal()
d3.phylogram.rightAngleDiagonal for radial layouts.
*/
if (!d3) { throw "d3 wasn't included!"};
(function() {
d3.phylogram = {}
d3.phylogram.rightAngleDiagonal = function() {
var projection = function(d) { return [d.y, d.x]; }
var path = function(pathData) {
return "M" + pathData[0] + ' ' + pathData[1] + " " + pathData[2];
}
function diagonal(diagonalPath, i) {
var source = diagonalPath.source,
target = diagonalPath.target,
midpointX = (source.x + target.x) / 2,
midpointY = (source.y + target.y) / 2,
pathData = [source, {x: target.x, y: source.y}, target];
pathData = pathData.map(projection);
return path(pathData)
}
diagonal.projection = function(x) {
if (!arguments.length) return projection;
projection = x;
return diagonal;
};
diagonal.path = function(x) {
if (!arguments.length) return path;
path = x;
return diagonal;
};
return diagonal;
}
d3.phylogram.radialRightAngleDiagonal = function() {
return d3.phylogram.rightAngleDiagonal()
.path(function(pathData) {
var src = pathData[0],
mid = pathData[1],
dst = pathData[2],
radius = Math.sqrt(src[0]*src[0] + src[1]*src[1]),
srcAngle = d3.phylogram.coordinateToAngle(src, radius),
midAngle = d3.phylogram.coordinateToAngle(mid, radius),
clockwise = Math.abs(midAngle - srcAngle) > Math.PI ? midAngle <= srcAngle : midAngle > srcAngle,
rotation = 0,
largeArc = 0,
sweep = clockwise ? 0 : 1;
return 'M' + src + ' ' +
"A" + [radius,radius] + ' ' + rotation + ' ' + largeArc+','+sweep + ' ' + mid +
'L' + dst;
})
.projection(function(d) {
var r = d.y, a = (d.x - 90) / 180 * Math.PI;
return [r * Math.cos(a), r * Math.sin(a)];
})
}
// Convert XY and radius to angle of a circle centered at 0,0
d3.phylogram.coordinateToAngle = function(coord, radius) {
var wholeAngle = 2 * Math.PI,
quarterAngle = wholeAngle / 4
var coordQuad = coord[0] >= 0 ? (coord[1] >= 0 ? 1 : 2) : (coord[1] >= 0 ? 4 : 3),
coordBaseAngle = Math.abs(Math.asin(coord[1] / radius))
// Since this is just based on the angle of the right triangle formed
// by the coordinate and the origin, each quad will have different
// offsets
switch (coordQuad) {
case 1:
coordAngle = quarterAngle - coordBaseAngle
break
case 2:
coordAngle = quarterAngle + coordBaseAngle
break
case 3:
coordAngle = 2*quarterAngle + quarterAngle - coordBaseAngle
break
case 4:
coordAngle = 3*quarterAngle + coordBaseAngle
}
return coordAngle
}
d3.phylogram.styleTreeNodes = function(vis) {
vis.selectAll('g.leaf.node')
.append("svg:circle")
.attr("r", 4.5)
.attr('stroke', 'yellowGreen')
.attr('fill', 'greenYellow')
.attr('stroke-width', '2px');
vis.selectAll('g.root.node')
.append('svg:circle')
.attr("r", 4.5)
.attr('fill', 'steelblue')
.attr('stroke', '#369')
.attr('stroke-width', '2px');
}
function scaleBranchLengths(nodes, w) {
// Visit all nodes and adjust y pos width distance metric
var visitPreOrder = function(root, callback) {
callback(root)
if (root.children) {
for (var i = root.children.length - 1; i >= 0; i--){
visitPreOrder(root.children[i], callback)
};
}
}
visitPreOrder(nodes[0], function(node) {
node.rootDist = (node.parent ? node.parent.rootDist : 0) + (node.length || 0)
})
var rootDists = nodes.map(function(n) { return n.rootDist; });
var yscale = d3.scale.linear()
.domain([0, d3.max(rootDists)])
.range([0, w]);
visitPreOrder(nodes[0], function(node) {
node.y = yscale(node.rootDist)
})
return yscale
}
d3.phylogram.build = function(selector, nodes, options) {
options = options || {}
var w = options.width || parseInt(d3.select(selector).style('width')) ||parseInt(d3.select(selector).attr('width')),
h = options.height || parseInt(d3.select(selector).style('height')) || parseInt(d3.select(selector).attr('height')),
w = parseInt(w),
h = parseInt(h);
var tree = options.tree || d3.layout.cluster()
.size([h, w])
.sort(function(node) { return node.children ? node.children.length : -1; })
.children(options.children || function(node) {
return node.branchset
});
var diagonal = options.diagonal || d3.phylogram.rightAngleDiagonal();
var vis = options.vis || d3.select(selector).append("svg:svg")
.attr("width", w + 300)
.attr("height", h + 30)
.append("svg:g")
.attr("transform", "translate(20, 20)");
var nodes = tree(nodes);
if (options.skipBranchLengthScaling) {
var yscale = d3.scale.linear()
.domain([0, w])
.range([0, w]);
} else {
var yscale = scaleBranchLengths(nodes, w)
}
if (!options.skipTicks) {
vis.selectAll('line')
.data(yscale.ticks(10))
.enter().append('svg:line')
.attr('y1', 0)
.attr('y2', h)
.attr('x1', yscale)
.attr('x2', yscale)
.attr("stroke", "#ddd");
vis.selectAll("text.rule")
.data(yscale.ticks(10))
.enter().append("svg:text")
.attr("class", "rule")
.attr("x", yscale)
.attr("y", 0)
.attr("dy", -3)
.attr("text-anchor", "middle")
.attr('font-size', '8px')
.attr('fill', '#ccc')
.text(function(d) { return Math.round(d*100) / 100; });
}
var link = vis.selectAll("path.link")
.data(tree.links(nodes))
.enter().append("svg:path")
.attr("class", "link")
.attr("d", diagonal)
.attr("fill", "none")
.attr("stroke", "#aaa")
.attr("stroke-width", "4px");
var node = vis.selectAll("g.node")
.data(nodes)
.enter().append("svg:g")
.attr("class", function(n) {
if (n.children) {
if (n.depth == 0) {
return "root node"
} else {
return "inner node"
}
} else {
return "leaf node"
}
})
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; })
d3.phylogram.styleTreeNodes(vis)
if (!options.skipLabels) {
vis.selectAll('g.inner.node')
.append("svg:text")
.attr("dx", -6)
.attr("dy", -6)
.attr("text-anchor", 'end')
.attr('font-size', '8px')
.attr('fill', '#ccc')
.text(function(d) { return d.length; });
vis.selectAll('g.leaf.node').append("svg:text")
.attr("dx", 8)
.attr("dy", 3)
.attr("text-anchor", "start")
.attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
.attr('font-size', '10px')
.attr('fill', 'black')
.text(function(d) { return d.name + ' ('+d.length+')'; });
}
return {tree: tree, vis: vis}
}
d3.phylogram.buildRadial = function(selector, nodes, options) {
options = options || {}
var w = options.width || parseInt(d3.select(selector).style('width')) || parseInt(d3.select(selector).attr('width')),
r = w / 2,
labelWidth = options.skipLabels ? 10 : options.labelWidth || 120;
var vis = d3.select(selector).append("svg:svg")
.attr("width", r * 2)
.attr("height", r * 2)
.append("svg:g")
.attr("transform", "translate(" + r + "," + r + ")");
var tree = d3.layout.tree()
.size([360, r - labelWidth])
.sort(function(node) { return node.children ? node.children.length : -1; })
.children(options.children || function(node) {
return node.branchset
})
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; });
var phylogram = d3.phylogram.build(selector, nodes, {
vis: vis,
tree: tree,
skipBranchLengthScaling: true,
skipTicks: true,
skipLabels: options.skipLabels,
diagonal: d3.phylogram.radialRightAngleDiagonal()
})
vis.selectAll('g.node')
.attr("transform", function(d) { return "rotate(" + (d.x - 90) + ")translate(" + d.y + ")"; })
if (!options.skipLabels) {
vis.selectAll('g.leaf.node text')
.attr("dx", function(d) { return d.x < 180 ? 8 : -8; })
.attr("dy", ".31em")
.attr("text-anchor", function(d) { return d.x < 180 ? "start" : "end"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; })
.attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
.attr('font-size', '10px')
.attr('fill', 'black')
.text(function(d) { return d.name; });
vis.selectAll('g.inner.node text')
.attr("dx", function(d) { return d.x < 180 ? -6 : 6; })
.attr("text-anchor", function(d) { return d.x < 180 ? "end" : "start"; })
.attr("transform", function(d) { return d.x < 180 ? null : "rotate(180)"; });
}
return {tree: tree, vis: vis}
}
}());
/*
d3.phylonator.js by Tim Thimmaiah (https://github.com/tim-thimmaiah)
Modified from d3.phylogram.js by Ken-Ichi (https://github.com/kueda)
Copyright (c) Tim Thimmaiah 2013.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
*/
if (!d3) { throw "d3 wasn't included!"};
(function() {
d3.phylonator = {}
d3.phylonator.rightAngleDiagonal = function() {
var projection = function(d) { return [d.y, d.x]; }
var path = function(pathData) {
return "M" + pathData[0] + ' ' + pathData[1] + " " + pathData[2];
}
function diagonal(diagonalPath, i) {
var source = diagonalPath.source,
target = diagonalPath.target,
midpointX = (source.x + target.x) / 2,
midpointY = (source.y + target.y) / 2,
pathData = [source, {x: target.x, y: source.y}, target];
pathData = pathData.map(projection);
return path(pathData)
}
diagonal.projection = function(x) {
if (!arguments.length) return projection;
projection = x;
return diagonal;
};
diagonal.path = function(x) {
if (!arguments.length) return path;
path = x;
return diagonal;
};
return diagonal;
}
d3.phylonator.radialRightAngleDiagonal = function() {
return d3.phylonator.rightAngleDiagonal()
.path(function(pathData) {
var src = pathData[0],
mid = pathData[1],
dst = pathData[2],
radius = Math.sqrt(src[0]*src[0] + src[1]*src[1]),
srcAngle = d3.phylonator.coordinateToAngle(src, radius),
midAngle = d3.phylonator.coordinateToAngle(mid, radius),
clockwise = Math.abs(midAngle - srcAngle) > Math.PI ? midAngle <= srcAngle : midAngle > srcAngle,
rotation = 0,
largeArc = 0,
sweep = clockwise ? 0 : 1;
return 'M' + src + ' ' +
"A" + [radius,radius] + ' ' + rotation + ' ' + largeArc+','+sweep + ' ' + mid +
'L' + dst;
})
.projection(function(d) {
var r = d.y, a = (d.x - 90) / 180 * Math.PI;
return [r * Math.cos(a), r * Math.sin(a)];
})
}
// Convert XY and radius to angle of a circle centered at 0,0
d3.phylonator.coordinateToAngle = function(coord, radius) {
var wholeAngle = 2 * Math.PI,
quarterAngle = wholeAngle / 4
var coordQuad = coord[0] >= 0 ? (coord[1] >= 0 ? 1 : 2) : (coord[1] >= 0 ? 4 : 3),
coordBaseAngle = Math.abs(Math.asin(coord[1] / radius))
// Since this is just based on the angle of the right triangle formed
// by the coordinate and the origin, each quad will have different
// offsets
switch (coordQuad) {
case 1:
coordAngle = quarterAngle - coordBaseAngle
break
case 2:
coordAngle = quarterAngle + coordBaseAngle
break
case 3:
coordAngle = 2*quarterAngle + quarterAngle - coordBaseAngle
break
case 4:
coordAngle = 3*quarterAngle + coordBaseAngle
}
return coordAngle
}
d3.phylonator.styleTreeNodes = function(vis, nodes) {
var nodeMouseOver = function() {
var circle = d3.select(this);
circle.attr("fill", "steelblue");
circle.attr("r", 3.5);
}
var nodeMouseOut = function() {
var circle = d3.select(this);
circle.attr("fill", "white");
circle.attr("r", 2.5);
}
function highlight(x, y, a) {
var pointy = y;
var pointx = x;
vis.selectAll("path.link")
.attr("y2", function(d) {
if(d.target.y == pointy && d.target.x == pointx) {
var path = d3.select(this);
if( typeof path[0][0].oldstroke === "undefined" || path[0][0].oldstroke === null){
path[0][0].oldstroke = path.attr("stroke");
path.attr("stroke", "rgba(255,0,0,"+a+")");
} else {
path.attr("stroke", path[0][0].oldstroke);
path[0][0].oldstroke = null;
}
pointy = d.source.y;
pointx = d.source.x;
a = a-0.1;
if (pointy!=0) {
highlight(pointx, pointy,a);
}
}
});
}
vis.selectAll('g.leaf.node')
.append("svg:circle")
.attr("r", 2.5)
.attr("class", function(d) {
return d.type
})
.attr('stroke', 'steelBlue')
.attr('fill', 'white')
.attr('stroke-width', '1.5px')
.on("mouseout", function(d) {
var circle = d3.select(this);
circle.attr("fill", "white");
circle.attr("r", 2.5);
var pointy = d.y;
var pointx = d.x;
var a = 1;
highlight(pointx, pointy, a);
})
.on("mouseover", function(d) {
var circle = d3.select(this);
circle.attr("fill", "steelblue");
circle.attr("r", 3.5);
var pointy = d.y;
var pointx = d.x;
var a = 1;
highlight(pointx, pointy, a);
})
.on("click", function(d) {
/*var circle = d3.select(this);
circle.attr("fill", "steelblue");
circle.attr("r", 3.5);
var pointy = d.y;
var pointx = d.x;
var a = 1;
highlight(pointx, pointy, a);
*/
});
vis.selectAll('g.root.node')
.append('svg:circle')
.attr("r", 4.5)
.attr('fill', 'steelblue')
.attr('stroke', '#369')
.attr('stroke-width', '1.5px');
}
function scaleBranchLengths(nodes, w) {
// Visit all nodes and adjust y pos width distance metric
var visitPreOrder = function(root, callback) {
callback(root)
if (root.children) {
for (var i = root.children.length - 1; i >= 0; i--){
visitPreOrder(root.children[i], callback)
};
}
}
visitPreOrder(nodes[0], function(node) {
node.rootDist = (node.parent ? node.parent.rootDist : 0) + (!(typeof node.branchset === "undefined" ) ? node.branchset.length : 0)
})
var rootDists = nodes.map(function(n) { return n.rootDist; });
var yscale = d3.scale.linear()
.domain([0, d3.max(rootDists)])
.range([0, w]);
visitPreOrder(nodes[0], function(node) {
node.y = yscale(node.rootDist)
})
return yscale
}
d3.phylonator.build = function(selector, nodes, options) {
options = options || {}
var w = options.width || d3.select(selector).style('width') || d3.select(selector).attr('width'),
h = options.height || d3.select(selector).style('height') || d3.select(selector).attr('height'),
w = parseInt(w),
h = parseInt(h),
x = d3.scale.linear().domain([0, w]).range([0, w]),
y = d3.scale.linear().domain([0, h]).range([0, h]);
var tree = options.tree || d3.layout.cluster()
.size([h, w])
/* .sort(function(node) { return node.children ? node.children.length : -1; }) */ //Sorting Nodes (Optional)
.children(options.children || function(node) {
return node.branchset
});
var diagonal = options.diagonal || d3.phylonator.rightAngleDiagonal();
var vis = options.vis || d3.select(selector).append("svg:svg")
.attr("width", w + 300)
.attr("height", h + 30)
.attr('pointer-events', 'all')
.append('svg:g')
.call(d3.behavior.zoom().scaleExtent([0.5,5]).on("zoom", redraw)) //Zooming
.append("svg:g")
.attr("transform", "translate(0, 0)")
.attr("id", "phylonator_svg") //Reference
var nodes = tree(nodes);
vis.append('svg:rect') //For Panning
.attr('width', w)
.attr('height', h)
.attr('fill', 'white');
function redraw() {
vis.attr("transform",
"translate(" + (d3.event.translate) + ")"
+ " scale(" + d3.event.scale + ")");
}
if (options.skipBranchLengthScaling) {
var yscale = d3.scale.linear()
.domain([0, w])
.range([0, w]);
} else {
var yscale = scaleBranchLengths(nodes, w)
}
if (!options.skipTicks) {
vis.selectAll('line')
.data(yscale.ticks(10))
.enter().append('svg:line')
.attr('y1', 0)
.attr('y2', h)
.attr('x1', yscale)
.attr('x2', yscale)
.attr("stroke", "#ddd");
vis.selectAll("text.rule")
.data(yscale.ticks(10))
.enter().append("svg:text")
.attr("class", "rule")
.attr("x", yscale)
.attr("y", 0)
.attr("dy", -3)
.attr("text-anchor", "middle")
.attr('font-size', '8px')
.attr('fill', '#ccc')
.text(function(d) { return Math.round(d*100) / 100; });
}
var link = vis.selectAll("path.link")
.data(tree.links(nodes))
.enter().append("svg:path")
.attr("class", "link")
.attr("d", diagonal)
.attr("fill", "none")
.attr("stroke", "#aaa")
.attr("stroke-width", "1.5px")
.attr("x1", function(d) { return d.source.x; })
.attr("y1", function(d) { return d.source.y; })
.attr("x2", function(d) { return d.target.x; })
.attr("y2", function(d) { return d.target.y; });
var node = vis.selectAll("g.node")
.data(nodes)
.enter().append("svg:g")
.attr("class", function(n) {
if (n.children) {
if (n.depth == 0) {
return "root node"
} else {
return "inner node"
}
} else {
return "leaf node"
}
})
.attr("transform", function(d) { return "translate(" + d.y + "," + d.x + ")"; });
var baseNode = vis.selectAll("g.leaf.node")
.on("mouseover", function(d) {
var pointy = d.y;
var pointx = d.x;
var a = 1;
// highlight(pointx, pointy, a);
})
.on("mouseout", function(d) {
var pointy = d.y;
var pointx = d.x;
var a = 1;
// highlight(pointx, pointy, a);
});
var linkedByIndex = {};
tree.links(nodes).forEach(function(d) {
linkedByIndex[d.source.index + "," + d.target.index] = 1;
});
function isConnected(a, b) {
return linkedByIndex[a.index + "," + b.index] || linkedByIndex[b.index + "," + a.index] || a.index == b.index;
}
function fade(opacity) {
return function(d) {
node.style("stroke-opacity", function(o) {
thisOpacity = isConnected(d, o) ? 1 : opacity;
this.setAttribute('fill-opacity', thisOpacity);
return thisOpacity;
});
link.style("stroke-opacity", function(o) {
return o.source === d || o.target === d ? 1 : opacity;
});
};
}
d3.phylonator.styleTreeNodes(vis, nodes)
if (!options.skipLabels) {
vis.selectAll('g.leaf.node').append("svg:text")
.attr("dx", 8)
.attr("dy", 3)
.attr("text-anchor", "start")
.attr('font-family', 'Helvetica Neue, Helvetica, sans-serif')
.attr('font-size', '10px')
.attr('fill', 'black')
.attr("pointer-events", "all")
.text(function(d) {
return d.name + ' ('+d.length+')';
});
}
return {tree: tree, vis: vis}
}
}());
(function() {
// If window.HTMLWidgets is already defined, then use it; otherwise create a
// new object. This allows preceding code to set options that affect the
// initialization process (though none currently exist).
window.HTMLWidgets = window.HTMLWidgets || {};
// See if we're running in a viewer pane. If not, we're in a web browser.
var viewerMode = window.HTMLWidgets.viewerMode =
/\bviewer_pane=1\b/.test(window.location);
// See if we're running in Shiny mode. If not, it's a static document.
// Note that static widgets can appear in both Shiny and static modes, but
// obviously, Shiny widgets can only appear in Shiny apps/documents.
var shinyMode = window.HTMLWidgets.shinyMode =
typeof(window.Shiny) !== "undefined" && !!window.Shiny.outputBindings;
// We can't count on jQuery being available, so we implement our own
// version if necessary.
function querySelectorAll(scope, selector) {
if (typeof(jQuery) !== "undefined" && scope instanceof jQuery) {
return scope.find(selector);
}
if (scope.querySelectorAll) {
return scope.querySelectorAll(selector);
}
}
function asArray(value) {
if (value === null)
return [];
if ($.isArray(value))
return value;
return [value];
}
// Implement jQuery's extend
function extend(target /*, ... */) {
if (arguments.length == 1) {
return target;
}
for (var i = 1; i < arguments.length; i++) {
var source = arguments[i];
for (var prop in source) {
if (source.hasOwnProperty(prop)) {
target[prop] = source[prop];
}
}
}
return target;
}
// Replaces the specified method with the return value of funcSource.
//
// Note that funcSource should not BE the new method, it should be a function
// that RETURNS the new method. funcSource receives a single argument that is
// the overridden method, it can be called from the new method. The overridden
// method can be called like a regular function, it has the target permanently
// bound to it so "this" will work correctly.
function overrideMethod(target, methodName, funcSource) {
var superFunc = target[methodName] || function() {};
var superFuncBound = function() {
return superFunc.apply(target, arguments);
};
target[methodName] = funcSource(superFuncBound);
}
// Implement a vague facsimilie of jQuery's data method
function elementData(el, name, value) {
if (arguments.length == 2) {
return el["htmlwidget_data_" + name];
} else if (arguments.length == 3) {
el["htmlwidget_data_" + name] = value;
return el;
} else {
throw new Error("Wrong number of arguments for elementData: " +
arguments.length);
}
}
// http://stackoverflow.com/questions/3446170/escape-string-for-use-in-javascript-regex
function escapeRegExp(str) {
return str.replace(/[\-\[\]\/\{\}\(\)\*\+\?\.\\\^\$\|]/g, "\\$&");
}
function hasClass(el, className) {
var re = new RegExp("\\b" + escapeRegExp(className) + "\\b");
return re.test(el.className);
}
// elements - array (or array-like object) of HTML elements
// className - class name to test for
// include - if true, only return elements with given className;
// if false, only return elements *without* given className
function filterByClass(elements, className, include) {
var results = [];
for (var i = 0; i < elements.length; i++) {
if (hasClass(elements[i], className) == include)
results.push(elements[i]);
}
return results;
}
function on(obj, eventName, func) {
if (obj.addEventListener) {
obj.addEventListener(eventName, func, false);
} else if (obj.attachEvent) {
obj.attachEvent(eventName, func);
}
}
function off(obj, eventName, func) {
if (obj.removeEventListener)
obj.removeEventListener(eventName, func, false);
else if (obj.detachEvent) {
obj.detachEvent(eventName, func);
}
}
// Translate array of values to top/right/bottom/left, as usual with
// the "padding" CSS property
// https://developer.mozilla.org/en-US/docs/Web/CSS/padding
function unpackPadding(value) {
if (typeof(value) === "number")
value = [value];
if (value.length === 1) {
return {top: value[0], right: value[0], bottom: value[0], left: value[0]};
}
if (value.length === 2) {
return {top: value[0], right: value[1], bottom: value[0], left: value[1]};
}
if (value.length === 3) {
return {top: value[0], right: value[1], bottom: value[2], left: value[1]};
}
if (value.length === 4) {
return {top: value[0], right: value[1], bottom: value[2], left: value[3]};
}
}
// Convert an unpacked padding object to a CSS value
function paddingToCss(paddingObj) {
return paddingObj.top + "px " + paddingObj.right + "px " + paddingObj.bottom + "px " + paddingObj.left + "px";
}
// Makes a number suitable for CSS
function px(x) {
if (typeof(x) === "number")
return x + "px";
else
return x;
}
// Retrieves runtime widget sizing information for an element.
// The return value is either null, or an object with fill, padding,
// defaultWidth, defaultHeight fields.
function sizingPolicy(el) {
var sizingEl = document.querySelector("script[data-for='" + el.id + "'][type='application/htmlwidget-sizing']");
if (!sizingEl)
return null;
var sp = JSON.parse(sizingEl.textContent || sizingEl.text || "{}");
if (viewerMode) {
return sp.viewer;
} else {
return sp.browser;
}
}
function initSizing(el) {
var sizing = sizingPolicy(el);
if (!sizing)
return;
var cel = document.getElementById("htmlwidget_container");
if (!cel)
return;
if (typeof(sizing.padding) !== "undefined") {
document.body.style.margin = "0";
document.body.style.padding = paddingToCss(unpackPadding(sizing.padding));
}
if (sizing.fill) {
document.body.style.overflow = "hidden";
document.body.style.width = "100%";
document.body.style.height = "100%";
document.documentElement.style.width = "100%";
document.documentElement.style.height = "100%";
if (cel) {
cel.style.position = "absolute";
var pad = unpackPadding(sizing.padding);
cel.style.top = pad.top + "px";
cel.style.right = pad.right + "px";
cel.style.bottom = pad.bottom + "px";
cel.style.left = pad.left + "px";
el.style.width = "100%";
el.style.height = "100%";
}
return {
getWidth: function() { return cel.offsetWidth; },
getHeight: function() { return cel.offsetHeight; }
};
} else {
el.style.width = px(sizing.width);
el.style.height = px(sizing.height);
return {
getWidth: function() { return el.offsetWidth; },
getHeight: function() { return el.offsetHeight; }
};
}
}
// Default implementations for methods
var defaults = {
find: function(scope) {
return querySelectorAll(scope, "." + this.name);
},
renderError: function(el, err) {
var $el = $(el);
this.clearError(el);
// Add all these error classes, as Shiny does
var errClass = "shiny-output-error";
if (err.type !== null) {
// use the classes of the error condition as CSS class names
errClass = errClass + " " + $.map(asArray(err.type), function(type) {
return errClass + "-" + type;
}).join(" ");
}
errClass = errClass + " htmlwidgets-error";
// Is el inline or block? If inline or inline-block, just display:none it
// and add an inline error.
var display = $el.css("display");
$el.data("restore-display-mode", display);
if (display === "inline" || display === "inline-block") {
$el.hide();
if (err.message !== "") {
var errorSpan = $("<span>").addClass(errClass);
errorSpan.text(err.message);
$el.after(errorSpan);
}
} else if (display === "block") {
// If block, add an error just after the el, set visibility:none on the
// el, and position the error to be on top of the el.
// Mark it with a unique ID and CSS class so we can remove it later.
$el.css("visibility", "hidden");
if (err.message !== "") {
var errorDiv = $("<div>").addClass(errClass).css("position", "absolute")
.css("top", el.offsetTop)
.css("left", el.offsetLeft)
// setting width can push out the page size, forcing otherwise
// unnecessary scrollbars to appear and making it impossible for
// the element to shrink; so use max-width instead
.css("maxWidth", el.offsetWidth)
.css("height", el.offsetHeight);
errorDiv.text(err.message);
$el.after(errorDiv);
// Really dumb way to keep the size/position of the error in sync with
// the parent element as the window is resized or whatever.
var intId = setInterval(function() {
if (!errorDiv[0].parentElement) {
clearInterval(intId);
return;
}
errorDiv
.css("top", el.offsetTop)
.css("left", el.offsetLeft)
.css("maxWidth", el.offsetWidth)
.css("height", el.offsetHeight);
}, 500);
}
}
},
clearError: function(el) {
var $el = $(el);
var display = $el.data("restore-display-mode");
$el.data("restore-display-mode", null);
if (display === "inline" || display === "inline-block") {
if (display)
$el.css("display", display);
$(el.nextSibling).filter(".htmlwidgets-error").remove();
} else if (display === "block"){
$el.css("visibility", "inherit");
$(el.nextSibling).filter(".htmlwidgets-error").remove();
}
},
sizing: {}
};
// Called by widget bindings to register a new type of widget. The definition
// object can contain the following properties:
// - name (required) - A string indicating the binding name, which will be
// used by default as the CSS classname to look for.
// - initialize (optional) - A function(el) that will be called once per
// widget element; if a value is returned, it will be passed as the third
// value to renderValue.
// - renderValue (required) - A function(el, data, initValue) that will be
// called with data. Static contexts will cause this to be called once per
// element; Shiny apps will cause this to be called multiple times per
// element, as the data changes.
window.HTMLWidgets.widget = function(definition) {
if (!definition.name) {
throw new Error("Widget must have a name");
}
if (!definition.type) {
throw new Error("Widget must have a type");
}
// Currently we only support output widgets
if (definition.type !== "output") {
throw new Error("Unrecognized widget type '" + definition.type + "'");
}
// TODO: Verify that .name is a valid CSS classname
if (!definition.renderValue) {
throw new Error("Widget must have a renderValue function");
}
// For static rendering (non-Shiny), use a simple widget registration
// scheme. We also use this scheme for Shiny apps/documents that also
// contain static widgets.
window.HTMLWidgets.widgets = window.HTMLWidgets.widgets || [];
// Merge defaults into the definition; don't mutate the original definition.
var staticBinding = extend({}, defaults, definition);
overrideMethod(staticBinding, "find", function(superfunc) {
return function(scope) {
var results = superfunc(scope);
// Filter out Shiny outputs, we only want the static kind
return filterByClass(results, "html-widget-output", false);
};
});
window.HTMLWidgets.widgets.push(staticBinding);
if (shinyMode) {
// Shiny is running. Register the definition as an output binding.
// Merge defaults into the definition; don't mutate the original definition.
// The base object is a Shiny output binding if we're running in Shiny mode,
// or an empty object if we're not.
var shinyBinding = extend(new Shiny.OutputBinding(), defaults, definition);
// Wrap renderValue to handle initialization, which unfortunately isn't
// supported natively by Shiny at the time of this writing.
// NB: shinyBinding.initialize may be undefined, as it's optional.
// Rename initialize to make sure it isn't called by a future version
// of Shiny that does support initialize directly.
shinyBinding._htmlwidgets_initialize = shinyBinding.initialize;
delete shinyBinding.initialize;
overrideMethod(shinyBinding, "find", function(superfunc) {
return function(scope) {
var results = superfunc(scope);
// Only return elements that are Shiny outputs, not static ones
var dynamicResults = results.filter(".html-widget-output");
// It's possible that whatever caused Shiny to think there might be
// new dynamic outputs, also caused there to be new static outputs.
// Since there might be lots of different htmlwidgets bindings, we
// schedule execution for later--no need to staticRender multiple
// times.
if (results.length !== dynamicResults.length)
scheduleStaticRender();
return dynamicResults;
};
});
overrideMethod(shinyBinding, "renderValue", function(superfunc) {
return function(el, data) {
// Resolve strings marked as javascript literals to objects
for (var i = 0; data.evals && i < data.evals.length; i++) {
window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]);
}
if (!this.renderOnNullValue) {
if (data.x === null) {
el.style.visibility = "hidden";
return;
} else {
el.style.visibility = "inherit";
}
}
if (!elementData(el, "initialized")) {
initSizing(el);
elementData(el, "initialized", true);
if (this._htmlwidgets_initialize) {
var result = this._htmlwidgets_initialize(el, el.offsetWidth,
el.offsetHeight);
elementData(el, "init_result", result);
}
}
Shiny.renderDependencies(data.deps);
superfunc(el, data.x, elementData(el, "init_result"));
};
});
overrideMethod(shinyBinding, "resize", function(superfunc) {
return function(el, width, height) {
// Shiny can call resize before initialize/renderValue have been
// called, which doesn't make sense for widgets.
if (elementData(el, "initialized")) {
superfunc(el, width, height, elementData(el, "init_result"));
}
};
});
Shiny.outputBindings.register(shinyBinding, shinyBinding.name);
}
};
var scheduleStaticRenderTimerId = null;
function scheduleStaticRender() {
if (!scheduleStaticRenderTimerId) {
scheduleStaticRenderTimerId = setTimeout(function() {
scheduleStaticRenderTimerId = null;
staticRender();
}, 1);
}
}
// Render static widgets after the document finishes loading
// Statically render all elements that are of this widget's class
function staticRender() {
var bindings = window.HTMLWidgets.widgets || [];
for (var i = 0; i < bindings.length; i++) {
var binding = bindings[i];
var matches = binding.find(document.documentElement);
for (var j = 0; j < matches.length; j++) {
var el = matches[j];
var sizeObj = initSizing(el, binding);
if (hasClass(el, "html-widget-static-bound"))
continue;
el.className = el.className + " html-widget-static-bound";
var initResult;
if (binding.initialize) {
initResult = binding.initialize(el,
sizeObj ? sizeObj.getWidth() : el.offsetWidth,
sizeObj ? sizeObj.getHeight() : el.offsetHeight
);
}
if (binding.resize) {
var lastSize = {};
on(window, "resize", function(e) {
var size = {
w: sizeObj ? sizeObj.getWidth() : el.offsetWidth,
h: sizeObj ? sizeObj.getHeight() : el.offsetHeight
};
if (size.w === 0 && size.h === 0)
return;
if (size.w === lastSize.w && size.h === lastSize.h)
return;
lastSize = size;
binding.resize(el, size.w, size.h, initResult);
});
}
var scriptData = document.querySelector("script[data-for='" + el.id + "'][type='application/json']");
if (scriptData) {
var data = JSON.parse(scriptData.textContent || scriptData.text);
// Resolve strings marked as javascript literals to objects
for (var i = 0; data.evals && i < data.evals.length; i++) {
window.HTMLWidgets.evaluateStringMember(data.x, data.evals[i]);
}
binding.renderValue(el, data.x, initResult);
}
}
}
}
// Wait until after the document has loaded to render the widgets.
if (document.addEventListener) {
document.addEventListener("DOMContentLoaded", function() {
document.removeEventListener("DOMContentLoaded", arguments.callee, false);
staticRender();
}, false);
} else if (document.attachEvent) {
document.attachEvent("onreadystatechange", function() {
if (document.readyState === "complete") {
document.detachEvent("onreadystatechange", arguments.callee);
staticRender();
}
});
}
window.HTMLWidgets.getAttachmentUrl = function(depname, key) {
// If no key, default to the first item
if (typeof(key) === "undefined")
key = 1;
var link = document.getElementById(depname + "-" + key + "-attachment");
if (!link) {
throw new Error("Attachment " + depname + "/" + key + " not found in document");
}
return link.getAttribute("href");
};
window.HTMLWidgets.dataframeToD3 = function(df) {
var names = [];
var length;
for (var name in df) {
if (df.hasOwnProperty(name))
names.push(name);
if (typeof(df[name]) !== "object" || typeof(df[name].length) === "undefined") {
throw new Error("All fields must be arrays");
} else if (typeof(length) !== "undefined" && length !== df[name].length) {
throw new Error("All fields must be arrays of the same length");
}
length = df[name].length;
}
var results = [];
var item;
for (var row = 0; row < length; row++) {
item = {};
for (var col = 0; col < names.length; col++) {
item[names[col]] = df[names[col]][row];
}
results.push(item);
}
return results;
};
window.HTMLWidgets.transposeArray2D = function(array) {
var newArray = array[0].map(function(col, i) {
return array.map(function(row) {
return row[i]
})
});
return newArray;
};
// Split value at splitChar, but allow splitChar to be escaped
// using escapeChar. Any other characters escaped by escapeChar
// will be included as usual (including escapeChar itself).
function splitWithEscape(value, splitChar, escapeChar) {
var results = [];
var escapeMode = false;
var currentResult = "";
for (var pos = 0; pos < value.length; pos++) {
if (!escapeMode) {
if (value[pos] === splitChar) {
results.push(currentResult);
currentResult = "";
} else if (value[pos] === escapeChar) {
escapeMode = true;
} else {
currentResult += value[pos];
}
} else {
currentResult += value[pos];
escapeMode = false;
}
}
if (currentResult !== "") {
results.push(currentResult);
}
return results;
}
// Function authored by Yihui/JJ Allaire
window.HTMLWidgets.evaluateStringMember = function(o, member) {
var parts = splitWithEscape(member, '.', '\\');
for (var i = 0, l = parts.length; i < l; i++) {
var part = parts[i];
// part may be a character or 'numeric' member name
if (o !== null && typeof o === "object" && part in o) {
if (i == (l - 1)) { // if we are at the end of the line then evalulate
if (typeof o[part] === "string")
o[part] = eval("(" + o[part] + ")");
} else { // otherwise continue to next embedded object
o = o[part];
}
}
}
};
})();
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<script src="htmlwidgets.js"></script>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script src="d3.phylonator.js"></script>
<script src="d3.phylogram.js"></script>
<script src="newick.js"></script>
<script src="phyloNetwork.js"></script>
</head>
<body>
<h1>phylogram</h1>
<div id="htmlwidget-8250" style="width:960px;height:500px;" class="phyloNetwork"></div>
<script type="application/json" data-for="htmlwidget-8250">{ "x": {
"newick": "((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0",
"type": "phylogram",
"options": {
"height": null,
"width": null
}
},"evals": [ ] }</script>
<h1>phylogram radial</h1>
<div id="htmlwidget-5231" style="width:960px;height:500px;" class="phyloNetwork"></div>
<script type="application/json" data-for="htmlwidget-5231">{ "x": {
"newick": "((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0",
"type": "phylogram.radial",
"options": {
"height": null,
"width": null
}
},"evals": [ ] }</script>
<h1>phylonator</h1>
<div id="htmlwidget-2860" style="width:960px;height:500px;" class="phyloNetwork"></div>
<script type="application/json" data-for="htmlwidget-2860">{ "x": {
"newick": "((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0",
"type": "phylonator",
"options": {
"height": null,
"width": null
}
},"evals": [ ] }</script>
<h1>phylonator no scaling</h1>
<div id="htmlwidget-7679" style="width:960px;height:500px;" class="phyloNetwork"></div>
<script type="application/json" data-for="htmlwidget-7679">{ "x": {
"newick": "((A7RLM8:0.6571,(B3SBJ1:0.60468,B3SB47:1.39873):0.32942):0.0564,(C3YZV6:0.63868,(B7PB18:1.48452,((Q9I9H8:0.00775,A5WVJ8:0.00101):0.44669,(Q6GNU6:0.41674,((E1BR73:0.00015,E1BS49:0.00017):0.26544,((D2HE46:0.04922,(O14727:0.00017,A7E2A2:0.00089):0.05297):0.02176,((O88879:0.0026,(A2RRK8:0.00015,Q5DU30:0.00088):0.00017):0.02035,(Q8VI66:0.00344,(D3ZA56:0.09057,Q9EPV5:0.00017):0.00098):0.02368):0.0708):0.15795):0.09069):0.11555):0.43916):0.18436):0.11567):0",
"type": "phylonator",
"options": {
"height": null,
"width": null,
"skipBranchLengthScaling": true
}
},"evals": [ ] }</script>
</body>
</html>
/**
* Newick format parser in JavaScript.
*
* Copyright (c) Jason Davies 2010.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*
* Example tree (from http://en.wikipedia.org/wiki/Newick_format):
*
* +--0.1--A
* F-----0.2-----B +-------0.3----C
* +------------------0.5-----E
* +---------0.4------D
*
* Newick format:
* (A:0.1,B:0.2,(C:0.3,D:0.4)E:0.5)F;
*
* Converted to JSON:
* {
* name: "F",
* branchset: [
* {name: "A", length: 0.1},
* {name: "B", length: 0.2},
* {
* name: "E",
* length: 0.5,
* branchset: [
* {name: "C", length: 0.3},
* {name: "D", length: 0.4}
* ]
* }
* ]
* }
*
* Converted to JSON, but with no names or lengths:
* {
* branchset: [
* {}, {}, {
* branchset: [{}, {}]
* }
* ]
* }
*/
(function(exports) {
exports.parse = function(s) {
var ancestors = [];
var tree = {};
var tokens = s.split(/\s*(;|\(|\)|,|:)\s*/);
for (var i=0; i<tokens.length; i++) {
var token = tokens[i];
switch (token) {
case '(': // new branchset
var subtree = {};
tree.branchset = [subtree];
ancestors.push(tree);
tree = subtree;
break;
case ',': // another branch
var subtree = {};
ancestors[ancestors.length-1].branchset.push(subtree);
tree = subtree;
break;
case ')': // optional name next
tree = ancestors.pop();
break;
case ':': // optional length next
break;
default:
var x = tokens[i-1];
if (x == ')' || x == '(' || x == ',') {
tree.name = token;
} else if (x == ':') {
tree.length = parseFloat(token);
}
}
}
return tree;
};
})(
// exports will be set in any commonjs platform; use it if it's available
typeof exports !== "undefined" ?
exports :
// otherwise construct a name space. outside the anonymous function,
// "this" will always be "window" in a browser, even in strict mode.
this.Newick = {}
);
HTMLWidgets.widget({
name: "phyloNetwork",
type: "output",
initialize: function(el, width, height) {
return { phylo:null } ;
},
resize: function(el, width, height, phylo) {
},
renderValue: function(el, x, phylo) {
// x is a list with two elements, options and newick
// prepare newick with newick.js
var newick = Newick.parse( x.newick )
var newickNodes = []
function buildNewickNodes(node, callback) {
newickNodes.push(node)
if (node.branchset) {
for (var i = 0; i < node.branchset.length; i++) {
buildNewickNodes(node.branchset[i])
}
}
}
buildNewickNodes(newick)
// build the phylogram
if( x.type == "phylonator"){
d3.phylonator.build('#' + el.id, newick, x.options);
} else if(x.type == "phylogram" ) {
d3.phylogram.build('#' + el.id, newick, x.options);
} else if(x.type == "phylogram.radial" ) {
d3.phylogram.buildRadial('#' + el.id, newick, x.options);
}
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment