Skip to content

Commit

Permalink
Merge pull request #37 from stephenshank/docs
Browse files Browse the repository at this point in the history
Examples and unit tests
  • Loading branch information
stevenweaver authored May 3, 2017
2 parents b0887d6 + 5c96934 commit 678cd04
Show file tree
Hide file tree
Showing 24 changed files with 1,630 additions and 7 deletions.
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## phylotree.js
# phylotree.js

A JavaScript interactive viewer of [phylogenetic trees](https://en.wikipedia.org/wiki/Phylogenetic_tree), written as an extension of the [D3](http://d3js.org) [hierarchy layout](https://github.com/mbostock/d3/wiki/Hierarchy-Layout). It generates high quality SVG vector graphics, allows a great degree of customizability (CSS or JavaScript callbacks), and comes with a lot of *built-in* convenicence features.

Expand All @@ -25,8 +25,12 @@ Key features

1. A [gallery of examples](http://bl.ocks.org/spond) is a good place to learn different ways that phylotree.js can be used to display and annotate the trees.
2. A [full-featured web application](http://veg.github.io/phylotree.js/index.html) based on phylotree.js, implemented in [index.html](index.html).
3. phylotree.js is also used by the 2015 revision of the [datamonkey.org server](http://test.datamonkey.org) for molecular sequence analyis.
3. phylotree.js is also used by the 2015 revision of the [datamonkey.org server](http://test.datamonkey.org) for molecular sequence analysis.

## Dependencies

See [bower.json](bower.json) for dependencies.

## Tests

Run tests using `mocha`.
352 changes: 352 additions & 0 deletions examples/clone-compartment/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,352 @@
<!DOCTYPE html>
<html lang='en'>

<head>
<meta charset="utf-8">

<link href="//veg.github.io/phylotree.js/phylotree.css" rel="stylesheet">

<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<link 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="../../phylotree.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.8.3/underscore-min.js"></script>

<style>
.date-text {
padding: 0.25em;
text-align: center;
}

.size-bubble,
.size-bubble * {
fill: #CCC;
shape-rendering: crispEdges;
stroke: black;
stroke-width: 2px;
}

.size-label {
display: block;
text-align: center;
font-family: sans-serif;
font-size: 12pt;
}


@media print {
.no-print,
.no-print * {
display: none !important;
}
}
</style>
</head>

<body>
<div class="container">
<div class="row no-print">
<div class="col-md-4">

<div>
<form>
<label>Radial layout </label>
<input type="checkbox" id="layout" / checked>
<button type="button" class="btn btn-primary" data-toggle="modal" data-target="#newick_modal">Input Newick</button
</form>
</div>

</div>
</div>

<div class = "row">
<div class = "col-md-12">
<svg id="tree_display"></svg>
</div>
</div>

<div class = "row">
<div class = "col-md-2" id = "compartment-legend">
</div>
<div class = "col-md-2" id="size-legend">
</div>
<div class = "col-md-2" id="date-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=t rue placeholder="" style='width: 100%; height: 100%' rows=2 0 selectionStart=1 selectionEnd=1 000>(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 -->

<script>
// use these formats for parsing and displaying sampling dates
var date_in = d3.time.format("%Y%m%d"),
date_out = d3.time.format("%B %d, %Y");

// default scheme to color by date
var coloring_scheme = d3.scale.category10();

// different shapes for labeling compartments
var compartment_labels = d3.scale.ordinal().range(["circle", "square", "diamond", "triangle-down", "triangle-up"]);

// global tree object
var tree;


// bubble size scale
var size_scale = d3.scale.pow().exponent(0.4).range([1, 10]).clamp(false).domain([0, 1]);

// determines the size of a node "bubble"

function bubbleSize(node) {
if (node && node.copy_number) {
return size_scale(node.copy_number);
}
return 1;
}

function nodeStyler(container, node) {
if (d3.layout.phylotree.is_leafnode(node)) {

var existing_circle = container.selectAll("circle");

if (existing_circle.size() == 1) {
existing_circle.remove();
}

if (node.copy_number) {
existing_circle = container.selectAll("path.node_shape").data([node.compartment]);
existing_circle.enter().append("path").classed("node_shape", true);

var bubble_size = tree.node_bubble_size(node);

var label = existing_circle.attr("d", function(d) {
return d3.svg.symbol().type(compartment_labels(d)).size(bubble_size * bubble_size)();
}).selectAll("title").data([node.copy_number]);
label.enter().append("title");
label.text("" + node.copy_number + " copies");


existing_circle.style("stroke-width", "1px").style("stroke", "black");
if (node.text_angle) {
existing_circle.attr("transform", function(d) {
return "rotate(" + node.text_angle + ") translate(" + (node.text_align == "end" ? -1 : 1) * bubble_size / 2 + ",0)";
});
} else {
existing_circle.attr("transform", function(d) {
return "translate(" + bubble_size / 2 + ",0)";
});
}
}

}

if (node.date) {
var node_color = coloring_scheme(node.date);
container.selectAll("circle").style("fill", node_color);
container.selectAll("path").style("fill", node_color);
container.style("fill", node_color);
}

}

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
'transitions': false,
// turn off d3 animations.
'draw-size-bubbles': true,
// draw node size bubbles
'annular-limit': 0.9
// controls how much of the overall diameter can be taken by
// empty "annular" space
})
.node_span(bubbleSize)
.style_nodes(nodeStyler)
.node_circle_size(0) // do not draw clickable circles for internal nodes
.branch_name(function() {
return ""
}) // no leaf names
;


/* the next call creates the tree object, and tree nodes */
tree(d3.layout.newick_parser(newick));

/* parse tip names which are assumed to be like this:
T0104_20140512_env_CSF_319_130
splitting on '_' yields
-- Patient ID (T0104); ignored
-- YYYYMMDD sample date; parsed and used to assign *colors* to edges/nodes
-- gene; ignored
-- compartment; parsed and used to assign *shapes* to nodes
-- clone ID; ignored [any other numeric fields with indices 4 through length-2 will be ignored]
-- copy number; parsed and used to scale "bubbles" on leaves
tips which do not conform to this naming convention are labeled as "reference",
and annotated using a special character.
*/

var unique_compartments = {},
unique_dates = {};

var copy_number_range = [1e100, 0],
attributed_node = null;

_.each(tree.get_nodes(), function(value, key) {
var attributes = value.name.split('_');
if (attributes.length >= 5) {
attributed_node = value;

value.compartment = attributes[3];
value.copy_number = Math.max(1, parseFloat(attributes[attributes.length - 1]));


copy_number_range[0] = Math.min(copy_number_range[0], value.copy_number);
copy_number_range[1] = Math.max(copy_number_range[1], value.copy_number);

value.date = date_out(date_in.parse(attributes[1]));
unique_compartments[value.compartment] = 1;
unique_dates[attributes[1]] = 1;
} else {
value.is_reference = true;
}

});



size_scale.domain(copy_number_range);
coloring_scheme.domain(_.keys(unique_dates).sort().map(function(d) {
return date_out(date_in.parse(d))
}));
compartment_labels.domain(_.keys(unique_compartments).sort());


if ($("#layout").prop("checked")) {
tree.radial(true);
}
tree.placenodes().layout();

var rescale = tree.node_bubble_size(attributed_node) / size_scale(attributed_node.copy_number) * 0.5,
rescaled_size = _.compose(function(d) {
return d * rescale
}, size_scale);

// create a legend for sizes
// power of 10 ticks

var size_bubbles = _.map(d3.range(Math.floor(Math.log(copy_number_range[0]) / Math.log(10)), 1 + Math.ceil(Math.log(copy_number_range[1]) / Math.log(10))),
function(v) {
return Math.pow(10, v);
});

d3.select("#size-legend").selectAll(".row").remove();
var reference_sizes = d3.select("#size-legend").selectAll(".row").data(_.map(size_bubbles, function(d) {
return [d];
}));
reference_sizes.enter().append("div").classed("row", true).selectAll(".col-md-2").data(function(d) {
return [d];
}).enter().append("div").classed("col-md-2", true);

var max_size = rescaled_size(size_bubbles[size_bubbles.length - 1]);

reference_sizes.selectAll(".col-md-2").each(function(d) {
var circle_size = rescaled_size(d);
d3.select(this).append("svg").attr("width", 2 * max_size + 4).attr("height", 2 * circle_size + 4).append("circle").classed("size-bubble", true).attr("r", circle_size).attr("cx", max_size + 2).attr("cy", circle_size + 2);
d3.select(this).append("span").classed("size-label", true).style("width", "" + (2 * max_size + 4) + "px").text(d);
});


// create a legend for compartments

d3.select("#compartment-legend").selectAll(".row").remove();
var compartment_depictions = d3.select("#compartment-legend").selectAll(".row").data(compartment_labels.domain());
compartment_depictions.enter().append("div").classed("row", true).selectAll(".col-md-2").data(function(d) {
return [d];
}).enter().append("div").classed("col-md-2", true);
compartment_depictions.exit().remove();

compartment_depictions.selectAll(".col-md-2").each(function(d) {
d3.select(this).append("svg").attr("width", 2 * max_size + 4).attr("height", 2 * max_size + 4).append("path").classed("size-bubble", true).attr("d", function(d) {
return d3.svg.symbol().type(compartment_labels(d)).size(max_size * max_size)();
}).attr("transform", "translate( " + max_size + "," + max_size + ")");

d3.select(this).append("span").classed("size-label", true).style("width", "" + (2 * max_size + 4) + "px").text(d);
});

// create a legend for dates
d3.select("#date-legend").selectAll(".row").remove();
var date_colors = d3.select("#date-legend").selectAll(".row").data(coloring_scheme.domain());
date_colors.enter().append("div").classed("row", true);
date_colors.exit().remove();

date_colors.each(function(d) {
d3.select(this).style("background-color", coloring_scheme(d)).classed("date-text", true).text(d).style("color", d3.rgb(coloring_scheme(d)).hsl().l < 0.5 ? "white" : "black");
});


// 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);
});
}

$("#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');
}
});

loadTreeFromURL("tree.nwk");
</script>

</body>

</html>
1 change: 1 addition & 0 deletions examples/clone-compartment/tree.nwk
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(((T0104_20140512_env_PBMC_11_676:5.3E-4,(T0104_20140512_env_PBMC_7_34:5.3E-4,(T0104_20140512_env_CSF_41_67:5.5E-4,(((T0104_20140512_env_CSF_38_125:5.5E-4,T0104_20140512_env_CSF_21_66:5.5E-4):5.5E-4,(T0104_20140512_env_CSF_24_62:5.5E-4,(T0104_20140512_env_CSF_35_153:5.5E-4,T0104_20140512_env_PBMC_6_48:5.5E-4):0.00297):0.00294):0.00168,T0104_20140512_env_CSF_18_125:0.00593):8.7E-4):0.00431):0.00432):0.00579,(((((T0104_20140512_env_PBMC_12_129:5.4E-4,(T0104_20140512_env_PBMC_63_31:0.00766,(T0104_20140512_env_PBMC_17_49:0.00309,T0104_20140512_env_PBMC_35_31:5.4E-4):0.00297):0.00392):0.00326,(T0104_20140512_env_PBMC_46_118:0.00631,((T0104_20140512_env_PBMC_2_734:5.4E-4,((T0104_20140512_env_PBMC_28_73:0.00306,T0104_20140512_env_PBMC_43_166:0.00591):0.0057,T0104_20140512_env_PBMC_56_156:0.00604):5.4E-4):0.00583,(T0104_20140512_env_PBMC_65_71:5.4E-4,T0104_20140512_env_PBMC_71_109:0.0031):0.00604):5.4E-4):0.00307):0.00239,T0104_20140512_env_PBMC_20_156:0.00896):5.5E-4,T0104_20140512_env_CSF_75_117:5.5E-4):0.00288,(T0104_20140512_env_PBMC_4_406:0.00302,(((T0104_20140512_env_CSF_57_56:0.01035,T0104_20140512_env_PBMC_38_31:0.00342):0.02318,((T0104_20140512_env_PBMC_26_43:5.5E-4,(T0104_20140512_env_PBMC_82_59:5.5E-4,T0104_20140512_env_PBMC_260_36:0.00935):0.01074):0.00356,T0104_20140512_env_PBMC_21_108:5.4E-4):5.4E-4):0.01498,(((T0104_20140512_env_CSF_423_43:5.5E-4,(T0104_20140512_env_CSF_390_53:0.00891,T0104_20140512_env_CSF_104_154:0.00601):5.2E-4):0.00247,(T0104_20140512_env_CSF_4_72:0.00303,(T0104_20140512_env_CSF_85_41:5.5E-4,(T0104_20140512_env_CSF_3_2552:8.2E-4,T0104_20140512_env_CSF_49_59:5.5E-4):6.0E-4):5.2E-4):0.00101):0.00295,((T0104_20140512_env_PBMC_62_42:0.00603,(T0104_20140512_env_PBMC_0_3329:5.5E-4,((T0104_20140512_env_CSF_26_85:5.5E-4,(T0104_20140512_env_CSF_1_9127:5.5E-4,(T0104_20140512_env_CSF_293_139:0.00379,T0104_20140512_env_PBMC_99_49:0.0031):0.00476):5.5E-4):6.9E-4,T0104_20140512_env_CSF_27_107:0.0030):0.00126):5.7E-4):0.0050,(T0104_20140512_env_CSF_319_130:5.5E-4,(T0104_20140512_env_CSF_80_58:0.00299,(T0104_20140512_env_CSF_2_69:0.00309,((T0104_20140512_env_CSF_122_65:5.5E-4,(T0104_20140512_env_CSF_23_120:5.5E-4,(T0104_20140512_env_CSF_52_52:0.0030,T0104_20140512_env_PBMC_193_47:5.5E-4):5.4E-4):0.00297):0.00297,((T0104_20140512_env_CSF_20_73:5.5E-4,T0104_20140512_env_PBMC_49_35:5.5E-4):0.00434,(T0104_20140512_env_CSF_22_57:0.00302,((((T0104_20140512_env_CSF_13_90:0.00455,T0104_20140512_env_PBMC_78_155:0.00302):0.00452,(T0104_20140512_env_CSF_44_140:5.3E-4,T0104_20140512_env_CSF_187_155:5.3E-4):5.3E-4):5.6E-4,(T0104_20140512_env_CSF_8_181:5.5E-4,T0104_20140512_env_PBMC_164_54:5.5E-4):0.00302):5.3E-4,T0104_20140512_env_CSF_7_73:0.00297):0.00179):7.7E-4):5.5E-4):5.5E-4):5.4E-4):5.3E-4):8.2E-4):5.4E-4):5.4E-4):6.7E-4):0.00439):5.5E-4):0.001955,((T0104_20140512_env_PBMC_73_89:0.00557,T0104_20140512_env_PBMC_115_53:0.00601):5.0E-4,(((T0104_20140512_env_PBMC_110_48:0.00294,(T0104_20140512_env_PBMC_127_31:0.0031,(T0104_20140512_env_PBMC_16_369:5.2E-4,T0104_20140512_env_PBMC_3_139:0.00933):5.5E-4):0.0114):0.02769,((T0104_20140512_env_PBMC_8_112:0.00299,(T0104_20140512_env_PBMC_14_225:0.00623,(T0104_20140512_env_PBMC_72_138:5.4E-4,T0104_20140512_env_PBMC_96_75:0.00301):0.00425):5.3E-4):0.00429,T0104_20140512_env_PBMC_52_46:0.0059):5.3E-4):0.00566,(T0104_20140512_env_PBMC_117_57:5.5E-4,((T0104_20140512_env_PBMC_30_427:5.4E-4,T0104_20140512_env_PBMC_175_50:0.00916):0.00573,T0104_20140512_env_PBMC_154_54:0.00304):5.4E-4):0.0059):5.5E-4):8.95E-4);
2 changes: 2 additions & 0 deletions examples/color-branches/annotation.csv
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
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
Loading

0 comments on commit 678cd04

Please sign in to comment.