|
<!DOCTYPE html> |
|
<head> |
|
<meta charset="utf-8"> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<style> |
|
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; } |
|
</style> |
|
</head> |
|
<meta charset="utf-8"> |
|
<style> |
|
body {text-align:center} |
|
svg { font-family: Courier;} |
|
</style> |
|
<body> |
|
<svg width="960" height="620"> |
|
<text class="title" x=10 y=30>a little tree</text> |
|
</svg> |
|
|
|
|
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
|
|
<script> |
|
const svg = d3.select("svg"), |
|
width = +svg.attr("width"), |
|
height= +svg.attr("height"), |
|
margin= 20 |
|
|
|
// Set up a size scale |
|
const radius = d3.scaleLinear() |
|
.range( [1, 14]) |
|
.domain([1,1000]) |
|
.clamp(true) |
|
|
|
// Declare a D3 layout |
|
const tree = d3.tree() |
|
.size([height * 0.85, width * 0.8]) |
|
|
|
// Load our external data |
|
d3.csv("data.csv", function(data) { |
|
|
|
data.sort(function(x, y){ |
|
return d3.descending(+x.described, +y.described); |
|
}) |
|
|
|
// Build our hierarchy from a series of levels |
|
// change/reorder the level elements for a different tree |
|
const levels = ["kingdom", "phylum_latin", "notes"], |
|
hierarchy = flatToHierarchy(data, levels, 'taxon', 'described'), |
|
nodes = hierarchy.descendants(), |
|
leaves=hierarchy.leaves(), |
|
links = tree(hierarchy).links() |
|
|
|
// Draw on screen |
|
svg.selectAll('path') |
|
.data(links) |
|
.enter().append('path') |
|
.attr('d', d3.linkHorizontal() |
|
.x(function(d) { return d.y; }) |
|
.y(function(d) { return d.x; })) |
|
.style('fill', 'none') |
|
.style('stroke', '#aaa') |
|
.style('stroke-width',1) |
|
|
|
svg.selectAll('circle') |
|
.data(nodes) |
|
.enter().append('circle') |
|
.style('r', (d) => radius(d.value)) |
|
.style('fill', 'white') |
|
.style('stroke', '#444') |
|
.style('stroke-width',1) |
|
// TODO - make clearer |
|
.attr('transform', function (d) { |
|
return 'translate(' + d.y + ',' + d.x + ')' |
|
}) |
|
}) |
|
|
|
|
|
// Helper function for converting flat data to a hierarchy |
|
// with a name and count field on each node. |
|
// TODO - probably shouldn't name the value field "count" |
|
|
|
function flatToHierarchy(flatData, levels, nameField, countField) { |
|
// Adapted from https://stackoverflow.com/a/19317823 |
|
var nestedData = { name :"root", children : [] } |
|
|
|
// For each data row, loop through the expected levels traversing the output tree |
|
flatData.forEach(function(d){ |
|
// Keep this as a reference to the current level |
|
var depthCursor = nestedData.children; |
|
// Go down one level at a time |
|
levels.forEach(function( property, depth ){ |
|
|
|
// See if a branch has already been created |
|
var index; |
|
depthCursor.forEach(function(child,i){ |
|
if ( d[property] == child.name ) index = i; |
|
}); |
|
// Add a branch if it isn't there |
|
if ( isNaN(index) ) { |
|
depthCursor.push({ name : d[property], children : []}); |
|
index = depthCursor.length - 1; |
|
} |
|
// Reference the new child array as we go deeper into the tree |
|
depthCursor = depthCursor[index].children; |
|
// This is a leaf, so add last element to specified branch |
|
if ( depth === levels.length - 1 ) { |
|
depthCursor.push({ |
|
'name':d[nameField], |
|
'count':+d[countField] |
|
}); |
|
} |
|
}) |
|
}) |
|
|
|
// sum up the leaves / branches and return the hierarchy |
|
return d3.hierarchy(nestedData).sum(function(d){ return d.count; }) |
|
|
|
} |
|
|
|
</script> |
|
</body> |
|
</html> |