A test block to experiment with building hierarchies from flat files. Pretty messy at the moment.
The flatToHierarchy()
function transforms a flat and wide table into a hierarchy based on a sequence of field names. Adapted from this Stack Overflow answer.
xxxxxxxxxx
<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>
https://d3js.org/d3.v4.min.js
https://d3js.org/d3.v4.min.js