Skip to content

Instantly share code, notes, and snippets.

@spond
Last active March 11, 2016 21:42
Show Gist options
  • Save spond/dbc459acfaa6f134c67d to your computer and use it in GitHub Desktop.
Save spond/dbc459acfaa6f134c67d to your computer and use it in GitHub Desktop.
Color terminal branches based on user-specified clustering; style interior branches based on bootstrap support.

Annotated tree

An example that shows how to use styling call-backs to

  1. Color terminal branches based on an externally supplied annotation (cluster assignment)
  2. Adjust thickness of interior branches based on their bootstrap support
  3. Label interior nodes (for the cladogram layout) with bootstrap values
PSTVd TASVd TCDVd CSVd CLVd CCCVd ASSVd PBCVd CVd-OS CbVd-1 CbVd-2 CbVd-3 CCHMVd ELVd ASBVd HSVd CVdIV CVDIII CBLVd CEVd PLMVd
1 1 1 1 1 1 1 1 1 1 1 1 2 2 2 1 1 1 1 1 2
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset="utf-8">
<link type="text/css" href="//veg.github.io/phylotree.js/phylotree.css" rel="stylesheet">
<link type="text/css"rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link type="text/css" rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap-theme.min.css">
<script src="//code.jquery.com/jquery.js"></script>
<script src="//netdna.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js"></script>
<script src="//d3js.org/d3.v3.min.js"></script>
<script src="//veg.github.io/phylotree.js/phylotree.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>
<style>
.bootstrap {
font-size: 12px;
background-fill: #cccccc;
}
.cluster-text {
padding: 0.25em;
text-align: center;
}
@media print
{
.no-print, .no-print *
{
display: none !important;
}
}
</style>
</head>
<body>
<div class = "container">
<div class = "row no-print">
<div class = "col-md-6">
<div>
<form>
<label>Radial layout </label>
<input type="checkbox" id="layout"/>
<button type="button" class="btn btn-sm" data-toggle="modal" data-target="#newick_modal">Input Newick</button>
<button type="button" class="btn btn-sm" data-toggle="modal" data-target="#clustering_modal">Input clustering</button>
</form>
</div>
</div>
</div>
<div class = "row">
<div class = "col-md-12" id = "tree-container">
<svg id="tree_display"></svg>
</div>
</div>
<div class = "row">
<div class = "col-md-2" id="cluster-legend">
</div>
</div>
<div class="modal" id = 'newick_modal'>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Newick string to render</h4>
</div>
<div class="modal-body" id = 'newick_body'>
<textarea id = 'nwk_spec' autofocus = true placeholder = "" style = 'width: 100%; height: 100%' rows = 20 selectionStart = 1 selectionEnd = 1000>(a : 0.1, (b : 0.11, (c : 0.12, d : 0.13) : 0.14) : 0.15)</textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id = 'validate_newick'>Display this tree</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div class="modal" id = 'clustering_modal'>
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title">Clustering CSV</h4>
</div>
<div class="modal-body" id = 'clustering_body'>
<textarea id = 'csv_spec' autofocus = true placeholder = "" style = 'width: 100%; height: 100%' rows = 20 selectionStart = 1 selectionEnd = 1000>
Seq1,Seq2,Seq3
A,B,A
</textarea>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-primary" id = 'validate_csv'>Use this labeling</button>
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<script>
// the global tree variable
var tree;
// the dictionary mapping node names to clusters
var clustering = {};
// default scheme to color by date
var coloring_scheme = d3.scale.category10();
// this will be used to map bootstrap support values to edge thickness
var bootstrap_scale = d3.scale.linear().domain ([0,0.5,0.7,0.9,0.95,1]).range ([1,2,3,4,5,6]).interpolate (d3.interpolateRound);
function edgeStyler (dom_element, edge_object) {
if ("bootstrap" in edge_object.target) {
dom_element.style ("stroke-width", bootstrap_scale (edge_object.target.bootstrap) + "pt");
}
dom_element.style ("stroke", "cluster" in edge_object.target ? coloring_scheme (edge_object.target.cluster) : null);
}
function nodeStyler (dom_element, node_object) {
if ("bootstrap" in node_object && node_object.bootstrap) {
var label = dom_element.selectAll (".bootstrap");
if (label.empty()) {
dom_element.append ("text").classed ("bootstrap", true).text (node_object.bootstrap).attr ("dx", ".3em").attr("text-anchor", "start").attr ("alignment-baseline", "middle");
} else {
if (tree.radial()) { // do not show internal node labels in radial mode
label.remove();
}
}
}
}
function drawATree(newick) {
tree = d3.layout.phylotree()
.svg(d3.select("#tree_display"))
.options({
'selectable': false,
// make nodes and branches not selectable
'collapsible': false,
// turn off the menu on internal nodes
})
.style_edges(edgeStyler)
.style_nodes (nodeStyler)
.node_circle_size (0) // do not draw clickable circles for internal nodes
;
/* the next call creates the tree object, and tree nodes */
tree(d3_phylotree_newick_parser(newick));
// parse bootstrap support from internal node names
_.each (tree.get_nodes(), function (node) {
if (node.children) {
node.bootstrap = parseFloat (node.name);
}
});
tree.spacing_x (25).spacing_y (100);
if ($("#layout").prop("checked")) {
tree.radial (true);
}
tree.placenodes().layout();
// UI handlers
$("#layout").on("click", function(e) {
tree.radial($(this).prop("checked")).placenodes().update();
});
}
function loadTreeFromURL(url) {
d3.text(url, function(error, newick) {
drawATree(newick);
});
}
function applyAnnotation (clustering) {
coloring_scheme.domain ([]); // reset the coloring scheme
if (tree) {
// refresh cluster assignments for tips
/*_.each (tree.get_nodes(), function (node) {
if (node.name in clustering) {
node.cluster = clustering[node.name];
} else {
delete node.cluster;
}
});*/
tree.traverse_and_compute (function (node) {
if (node.name in clustering) {
node.cluster = clustering[node.name];
} else {
delete node.cluster;
var children_clusters = _.keys(_.countBy (node.children, function (d) {
return d.cluster;
}));
if (children_clusters.length == 1 && children_clusters[0]) {
node.cluster = children_clusters[0];
}
}
},
"post-order");
tree.update();
// update the legend
d3.select ("#cluster-legend").selectAll (".row").remove();
var cluster_colors = d3.select ("#cluster-legend").selectAll (".row").data (coloring_scheme.domain().sort().map (function (d) {return [d];}));
cluster_colors.enter().append ("div").classed ("row", true);
cluster_colors.exit().remove();
cluster_colors = cluster_colors.selectAll ("span").data (function (d) {return d});
cluster_colors.enter().append ("span").classed ("cluster-text", true);
cluster_colors.each (function (d) {
d3.select(this).style ("color", coloring_scheme (d), "important").classed ("cluster-text", true).text ("Cluster " + d);
});
}
}
function loadAnnotationFromURL (url) {
d3.csv (url, function (error, csv) {
applyAnnotation(csv[0]);
});
};
$("#validate_newick").on ("click", function (e) {
var res = d3_phylotree_newick_parser ( $('textarea[id$="nwk_spec"]').val(), true);
if (res["error"] || ! res["json"]) {
var warning_div = d3.select ("#newick_body").selectAll ("div .alert-danger").data ([res["error"]])
warning_div.enter ().append ("div");
warning_div.html (function (d) {return d;}).attr ("class", "alert-danger");
} else {
drawATree ($('textarea[id$="nwk_spec"]').val());
$('#newick_modal').modal('hide');
}
});
$("#validate_csv").on ("click", function (e) {
var res = d3.csv.parse ( $('textarea[id$="csv_spec"]').val());
if (!res) {
var warning_div = d3.select ("#clustering_body").selectAll ("div .alert-danger").data ("Failed to parse CSV")
warning_div.enter ().append ("div");
warning_div.html (function (d) {return d;}).attr ("class", "alert-danger");
} else {
applyAnnotation (res[0]);
$('#clustering_modal').modal('hide');
}
});
loadTreeFromURL("tree.nwk");
loadAnnotationFromURL ("annotation.csv");
</script>
</body>
</html>
(((((((TASVd:0.06828169,CSVd:0.06828169)0.8170:0.05635267,CEVd:0.12463435)0.8290:0.06964112,(PSTVd:0.02617095,TCDVd:0.02617095)1.0000:0.16810452)0.6700:0.08751399,CLVd:0.28178947)0.8700:0.13005822,(HSVd:0.26704011,(CCCVd:0.20577326,CVdIV:0.20577326)0.6520:0.06126685)0.8790:0.14480757)0.8810:0.18306776,(((CbVd-2:0.08236826,CbVd-3:0.08236826)0.9300:0.10543001,CbVd-1:0.18779827)0.9970:0.29222981,(PBCVd:0.38569588,(CBLVd:0.30671661,(ASSVd:0.25961362,(CVd-OS:0.14517605,CVDIII:0.14517605)0.9870:0.11443757)0.5950:0.04710299)0.5660:0.07897927)0.7300:0.09433220)0.6860:0.11488736)0.9840:0.46199326,((CCHMVd:0.37098428,PLMVd:0.37098428)0.8850:0.20471288,(ELVd:0.37028264,ASBVd:0.37028264)0.8730:0.20541453)0.9840:0.48121154);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment