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.
forked from fogonwater's block: Building a hierarchy from a flat table
forked from anonymous's block: Building a radial hierarchy from a flat table
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;}
.node circle {
fill: #999;
}
.node text {
font: 10px sans-serif;
}
.node--internal circle {
fill: #555;
stroke: #222;
}
.node--internal text {
text-shadow: 0 1px 0 #fff, 0 -1px 0 #fff, 1px 0 0 #fff, -1px 0 0 #fff;
}
.link {
fill: none;
stroke: #555;
stroke-opacity: 0.4;
stroke-width: 1.5px;
}
</style>
<body>
<svg width="1060" height="1060"></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,
max_radius = 20,
max_value = 4000,
g = svg.append("g").attr("transform", "translate(" + (width / 2 + 40) + "," + (height / 2 + 90) + ")"),
// Plantae, Fungi, Protozoa,Chromista
kingdom = 'Plantae'
// Declare a D3 layout
const tree = d3.tree()
.size([2 * Math.PI, 400])
.separation(function(a, b) { return (a.parent == b.parent ? 1 : 2) / a.depth; })
// Load our external data
d3.csv("data.csv", function(data) {
//var data = data.filter( (d) => d.kingdom == kingdom)
data.sort(function(x, y){return d3.descending(+x.described, +y.described)})
var fineData = []
data.forEach(function(d){
if (+d.adventive > 0) {
var item = Object.assign({}, d)
item['type'] = 'adventive'
item['itemCount'] = +d.adventive
fineData.push(item)
}
if ((+d.described - +d.adventive - +d.endemic_species) > 0) {
var item = Object.assign({}, d)
item['type'] = 'native'
item['itemCount'] = +d.described - +d.adventive - +d.endemic_species
fineData.push(item)
}
if (+d.endemic_species > 0) {
var item = Object.assign({}, d)
item['type'] = 'endemic'
item['itemCount'] = +d.endemic_species
fineData.push(item)
}
})
// Build our hierarchy from a series of levels
// change/reorder the level elements for a different tree
const levels = ["kingdom", "phylum_common", "taxon"],
root = flatToHierarchy(fineData, levels, 'type', 'itemCount')
// Draw on screen
var link = g.selectAll(".link")
.data(tree(root).links())
.enter().append("path")
.attr("class", "link")
.attr("d", d3.linkRadial()
.angle(function(d) { return d.x; })
.radius(function(d) { return d.y; }))
var node = g.selectAll(".node")
.data(tree(root).descendants())
.enter().append("g")
//.attr("class", function(d) { return "node" + (d.children ? " node--internal" : " node--leaf"); })
.style("fill", function(d) {
if (d.children) {return '#888'}
//console.log(d.data.type)
if (d.data.type == 'adventive') {return 'red'}
if (d.data.type == 'native') {return 'blue'}
if (d.data.type == 'endemic') {return 'green'}
})
.attr("transform", function(d) { return "translate(" + radialPoint(d.x, d.y) + ")"; })
node.append("circle")
.attr('r', (d) => radius(d.value))
/*
node.append("text")
.attr("dy", "0.31em")
.attr("x", function(d) { return d.x < Math.PI === !d.children ? 6 : -6; })
.attr("text-anchor", function(d) { return d.x < Math.PI === !d.children ? "start" : "end"; })
.attr("transform", function(d) { return "rotate(" + (d.x < Math.PI ? d.x - Math.PI / 2 : d.x + Math.PI / 2) * 180 / Math.PI + ")"; })
.text(function(d) { return d.children ? d.data.name : ''})*/
})
//
//
// 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 radius(val) {
return Math.sqrt(val / max_value) * max_radius
}
function radialPoint(x, y) {
return [(y = +y) * Math.cos(x -= Math.PI / 2), y * Math.sin(x)];
}
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],
'type':d.type /// TODO - generic way to add fields
});
}
})
})
// 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