This is a tree diagram that used data from a 'flat' format. It has been written with d3.js v4 and is based on the simple horizontal version here
This is designed to be used as part of documenting an update to the book D3 Tips and Tricks to version 4 of d3.js.
forked from d3noob's block: Tree diagram using flat data with v4
xxxxxxxxxx
<meta charset="utf-8">
<style> /* set the CSS */
svg {
background: lightblue;
}
.node circle {
fill: #fff;
stroke: blue;
}
.node text { font-family: sans-serif; }
.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: orange;
stroke-width: 2px;
}
</style>
<body>
<!-- load the d3.js library -->
<script src="//d3js.org/d3.v4.min.js"></script>
<script>
// set the dimensions and margins of the diagram
var margin = {top: 20, right: 40, bottom: 20, left: 20},
width = 300 - margin.left - margin.right,
height = 300 - margin.top - margin.bottom;
// append the svg object to the body of the page
// appends a 'group' element to 'svg'
// moves the 'group' element to the top left margin
var svg = d3.select("body").append("svg")
.attr("width", width + margin.right + margin.left)
.attr("height", height + margin.top + margin.bottom)
.call(d3.zoom().on("zoom", function () {
var transform = d3.event.transform
svg.attr("transform", "translate(" + transform.x + "," + transform.y + ") scale(" + transform.k + ")");
svg.selectAll(".node")
.attr("r", function() {
return 5 / transform.k; })
.style("stroke-width", function() {
return 1 / transform.k; });
svg.selectAll(".label")
.attr("font-size", function() {
return 10 / transform.k; })
.attr("dy", function() {
return String(.35 / transform.k) + "em"; })
.attr("y", function(d) {
return d.children || d._children ? (-13 / transform.k) : (13 / transform.k);});
}))
.append("g")
.attr("transform", "translate("
+ margin.left + "," + margin.top + ")");
var i = 0,
duration = 750,
root;
// declares a tree layout and assigns the size
var treemap = d3.tree()
.size([height, width]);
// the flat data
var mapData = {"count": 5, "features": [{"geometry": {"type": "Point", "coordinates": [-78.81574630737306, 35.93493056942606]}, "myw": {"feature_type": "outage", "title": "Outage: 16"}, "id": 16, "bbox": [-78.81574630737306, 35.93493056942606, -78.81574630737306, 35.93493056942606], "type": "Feature", "properties": {"id": 16, "protective_device": null, "damage_details": null, "tree_crew_needed": null, "address": "2938 US-70, Durham, NC 27703, USA", "bucket_truck_needed": null, "wire_down": null, "road_closure": null, "order_id": "mywda_order/71", "upstream_outage": null, "m_value": 500, "myw_geometry_world_name": "geo"}}, {"geometry": {"type": "Point", "coordinates": [-78.82291316986085, 35.938179428303215]}, "myw": {"feature_type": "outage", "title": "Outage: 19"}, "id": 19, "bbox": [-78.82291316986085, 35.938179428303215, -78.82291316986085, 35.938179428303215], "type": "Feature", "properties": {"id": 19, "protective_device": null, "damage_details": null, "tree_crew_needed": null, "address": "1812 Page Rd, Durham, NC 27703, USA", "bucket_truck_needed": null, "wire_down": null, "road_closure": null, "order_id": "mywda_order/71", "upstream_outage": "outage/16", "m_value": 650, "myw_geometry_world_name": "geo"}}, {"geometry": {"type": "Point", "coordinates": [-78.82190465927125, 35.937936202851205]}, "myw": {"feature_type": "outage", "title": "Outage: 18"}, "id": 18, "bbox": [-78.82190465927125, 35.937936202851205, -78.82190465927125, 35.937936202851205], "type": "Feature", "properties": {"id": 18, "protective_device": null, "damage_details": null, "tree_crew_needed": null, "address": "287 Discovery Way, Durham, NC 27703, USA", "bucket_truck_needed": null, "wire_down": null, "road_closure": null, "order_id": "mywda_order/71", "upstream_outage": "outage/16", "m_value": 600, "myw_geometry_world_name": "geo"}}, {"geometry": {"type": "Point", "coordinates": [-78.8203811645508, 35.937154401686215]}, "myw": {"feature_type": "outage", "title": "Outage: 17"}, "id": 17, "bbox": [-78.8203811645508, 35.937154401686215, -78.8203811645508, 35.937154401686215], "type": "Feature", "properties": {"id": 17, "protective_device": null, "damage_details": null, "tree_crew_needed": null, "address": "1150 Beta Rd, Durham, NC 27703, USA", "bucket_truck_needed": null, "wire_down": null, "road_closure": null, "order_id": "mywda_order/71", "upstream_outage": "outage/16", "m_value": 550, "myw_geometry_world_name": "geo"}}, {"geometry": {"type": "Point", "coordinates": [-78.82148623466493, 35.93804478573477]}, "myw": {"feature_type": "outage", "title": "Outage: 20"}, "id": 20, "bbox": [-78.82148623466493, 35.93804478573477, -78.82148623466493, 35.93804478573477], "type": "Feature", "properties": {"id": 20, "protective_device": null, "damage_details": null, "tree_crew_needed": null, "address": "256 Discovery Way, Durham, NC 27703, USA", "bucket_truck_needed": null, "wire_down": null, "road_closure": null, "order_id": "mywda_order/71", "upstream_outage": "outage/19", "m_value": 700, "myw_geometry_world_name": "geo"}}], "previous_offset": null, "limit": 500, "offset": 0, "next_offset": null, "unlimited_count": 5, "type": "MywFeatureCollection"}
var flatData = mapData.features;
console.log(flatData);
// convert the flat data into a hierarchy
var treeData = d3.stratify()
.id(function(d) { return "outage/" + d.properties.id; })
.parentId(function(d) { return d.properties.upstream_outage; })
(flatData);
console.log(treeData);
// assign the name to each node
treeData.each(function(d) {
d.name = d.data.properties.address.split(',')[0];
});
// assigns the data to a hierarchy using parent-child relationships
root = d3.hierarchy(treeData, function(d) {
return d.children;
});
root.x0 = 0;
root.y0 = width / 0;
// Collapse after the second level
root.children.forEach(collapse);
update(root);
// Collapse the node and all it's children
function collapse(d) {
if(d.children) {
d._children = d.children
d._children.forEach(collapse)
d.children = null
}
}
function update(source) {
// Assigns the x and y position for the nodes
var treeData = treemap(root);
// Compute the new tree layout.
var nodes = treeData.descendants(),
links = treeData.descendants().slice(1);
// Normalize for fixed-depth.
//nodes.forEach(function(d){ d.x = d.depth * 60});
// ****************** Nodes section ***************************
// Update the nodes...
var node = svg.selectAll('g.node')
.data(nodes, function(d) {return d.id || (d.id = ++i); });
// Enter any new modes at the parent's previous position.
var nodeEnter = node.enter().append('g')
.attr('class', 'node')
.attr("transform", function(d) {
return "translate(" + source.x0 + "," + source.y0 + ")";
})
.on('click', click);
// Add Circle for the nodes
nodeEnter.append('circle')
.attr('class', 'node')
.attr('r', 1e-6)
.style("fill", function(d) {
return d._children ? "blue" : "#fff";
});
// Add labels for the nodes
nodeEnter.append('text')
.attr("class", "label")
.attr("font-size", 10)
.attr("dy", ".35em")
.attr("x", function(d) {
return d.children || d._children ? 0 : 0;
})
.attr("y", function(d) {
return d.children || d._children ? -13 : 13;
})
.attr("text-anchor", function(d) {
return d.children || d._children ? "middle" : "middle";
})
.text(function(d) { return d.data.name; });
// UPDATE
var nodeUpdate = nodeEnter.merge(node);
// Transition to the proper position for the node
nodeUpdate.transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
});
// Update the node attributes and style
nodeUpdate.select('circle.node')
.attr('r', 5)
.style("fill", function(d) {
return d._children ? "blue" : "#fff";
})
.attr('cursor', 'pointer');
// Remove any exiting nodes
var nodeExit = node.exit().transition()
.duration(duration)
.attr("transform", function(d) {
return "translate(" + source.x + "," + source.y + ")";
})
.remove();
// On exit reduce the node circles size to 0
nodeExit.select('circle')
.attr('r', 1e-6);
// On exit reduce the opacity of text labels
nodeExit.select('text')
.style('fill-opacity', 1e-6);
// ****************** links section ***************************
// Update the links...
var link = svg.selectAll('path.link')
.data(links, function(d) { return d.id; });
// Enter any new links at the parent's previous position.
var linkEnter = link.enter().insert('path', "g")
.attr("class", "link")
.attr('d', function(d){
var o = {y: source.y0, x: source.x0}
return diagonal(o, o)
});
// UPDATE
var linkUpdate = linkEnter.merge(link);
// Transition back to the parent element position
linkUpdate.transition()
.duration(duration)
.attr('d', function(d){ return diagonal(d, d.parent) });
// Remove any exiting links
var linkExit = link.exit().transition()
.duration(duration)
.attr('d', function(d) {
var o = {x: source.x, y: source.y}
return diagonal(o, o)
})
.remove();
// Store the old positions for transition.
nodes.forEach(function(d){
d.x0 = d.x;
d.y0 = d.y;
});
// Creates a curved (diagonal) path from parent to the child nodes
function diagonal(s, d) {
path = `M ${s.x} ${s.y}
C ${(d.x + d.x) / 2 } ${s.y / 2},
${(d.x + d.x) / 2 } ${s.y / 2},
${d.x} ${d.y}`
return path
}
// Toggle children on click.
function click(d) {
if (d.children) {
d._children = d.children;
d.children = null;
} else {
d.children = d._children;
d._children = null;
}
update(d);
}
}
;
</script>
</body>
https://d3js.org/d3.v4.min.js