Skip to content

Instantly share code, notes, and snippets.

@basilesimon
Last active March 3, 2019 18:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save basilesimon/66db4338c15099f6e8d62f236db2ef2d to your computer and use it in GitHub Desktop.
Save basilesimon/66db4338c15099f6e8d62f236db2ef2d to your computer and use it in GitHub Desktop.
Tree of Life/ Tractatus Logico Philosophicus
license: gpl-3.0
height: 960
border: no
scrolling: yes

A re-implementation of Jason Davies’ Phylogenetic Tree of Life, with faded gray lines to connect the leaf nodes of the tree to their corresponding labels inspired by a figure from Nature.

This implementation modifies the depth of interior nodes in a cluster layout to show branch lengths. Toggle the checkbox in the top-left corner to show or hide branch lengths, and mouseover a label to highlight its path to the root.

forked from mbostock's block: Tree of Life

<!DOCTYPE html>
<meta charset="utf-8">
<style>
body {
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin: 0;
}
.links {
fill: none;
stroke: #000;
}
.link-extensions {
fill: none;
stroke: #000;
stroke-opacity: .25;
}
.labels {
font: 10px sans-serif;
font-weight: bold;
}
</style>
<label id="show-length">
</label>
<!-- Copyright 2011 Jason Davies https://github.com/jasondavies/newick.js -->
<script>function parseNewick(a){for(var e=[],r={},s=a.split(/\s*(;|\(|\)|,|:)\s*/),t=0;t<s.length;t++){var n=s[t];switch(n){case"(":var c={};r.branchset=[c],e.push(r),r=c;break;case",":var c={};e[e.length-1].branchset.push(c),r=c;break;case")":r=e.pop();break;case":":break;default:var h=s[t-1];")"==h||"("==h||","==h?r.name=n:":"==h&&(r.length=parseFloat(n))}}return r}</script>
<!-- Copyright 2016 Mike Bostock https://d3js.org -->
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://unpkg.com/d3-jetpack@2.0.20/build/d3-jetpack.js"></script>
<script>
const outerRadius = 900/2,
innerRadius = outerRadius - 150;
const color = d3
.scaleOrdinal()
.domain(["1.", "2.", "3.", "4.", "5.", "6.", "7."])
.range(d3.schemeCategory10);
const cluster = d3
.cluster()
.size([360, innerRadius])
.separation(function(a, b) {
return 1;
});
const svg = d3
.select('body')
.append('svg')
.at({
width: outerRadius * 2,
height: outerRadius * 2,
});
const chart = svg.append('g').translate([outerRadius, outerRadius]);
d3.text(
'https://gist.githubusercontent.com/basilesimon/fa37a436836e3556b1cc36a5067e5e33/raw/a4dddacc9e4332219ac5b94568818e7272977cdd/tlp.txt',
function(error, life) {
if (error) throw error;
const root = d3
.hierarchy(parseNewick(life), d => d.branchset)
.sum(d => (d.branchset ? 0 : 1))
.sort(
(a, b) =>
a.value - b.value || d3.ascending(a.data.length, b.data.length)
);
cluster(root);
setRadius(root, (root.data.length = 0), innerRadius / maxLength(root));
setColor(root);
const linkExtension = chart
.append('g')
.attr('class', 'link-extensions')
.selectAll('path')
.data(root.links().filter(d => !d.target.children))
.enter()
.append('path')
.each(function(d) {
d.target.linkExtensionNode = this;
})
.attr('d', linkExtensionConstiable);
const link = chart
.append('g')
.attr('class', 'links')
.selectAll('path')
.data(root.links())
.enter()
.append('path')
.each(function(d) {
d.target.linkNode = this;
})
.attr('d', linkConstant)
.attr('stroke', d => d.target.color);
chart
.append('g')
.attr('class', 'labels')
.selectAll('text')
.data(root.leaves())
.enter()
.append('text')
.attr('dy', '.31em')
.attr(
'transform',
d =>
'rotate(' +
(d.x - 90) +
')translate(' +
(innerRadius + 4) +
',0)' +
(d.x < 180 ? '' : 'rotate(180)')
)
.attr('text-anchor', d => (d.x < 180 ? 'start' : 'end'))
.text(d => (d.data.name.length < 3 ? d.data.name : ''));
}
);
// Compute the maximum cumulative length of any node in the tree.
const maxLength = d =>
d.data.length + (d.children ? d3.max(d.children, maxLength) : 0);
// Set the radius of each node by recursively summing and scaling the distance from the root.
const setRadius = (d, y0, k) => {
d.radius = (y0 += d.data.length) * k;
if (d.children)
d.children.forEach(function(d) {
setRadius(d, y0, k);
});
};
// Set the color of each node by recursively inheriting.
const setColor = d => {
const name = d.data.name.substring(0,2);
d.color =
color.domain().indexOf(name) >= 0
? color(name)
: d.parent ? d.parent.color : null;
if (d.children) d.children.forEach(setColor);
};
const linkConstiable = d =>
linkStep(d.source.x, d.source.radius, d.target.x, d.target.radius);
const linkConstant = d =>
linkStep(d.source.x, d.source.y, d.target.x, d.target.y);
const linkExtensionConstiable = d =>
linkStep(d.target.x, d.target.radius, d.target.x, innerRadius);
const linkExtensionConstant = d =>
linkStep(d.target.x, d.target.y, d.target.x, innerRadius);
// Like d3.svg.diagonal.radial, but with square corners.
function linkStep(startAngle, startRadius, endAngle, endRadius) {
const c0 = Math.cos((startAngle = (startAngle - 90) / 180 * Math.PI)),
s0 = Math.sin(startAngle),
c1 = Math.cos((endAngle = (endAngle - 90) / 180 * Math.PI)),
s1 = Math.sin(endAngle);
return (
'M' +
startRadius * c0 +
',' +
startRadius * s0 +
(endAngle === startAngle
? ''
: 'A' +
startRadius +
',' +
startRadius +
' 0 0 ' +
(endAngle > startAngle ? 1 : 0) +
' ' +
startRadius * c1 +
',' +
startRadius * s1) +
'L' +
endRadius * c1 +
',' +
endRadius * s1
);
}
</script>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment