An adaptation of the Sankey diagram to show traceability of project outputs through to benefits. Double clicking on a node shows the links up and down the model.
We adapted the sankey.js file so that node heights weren't determined by the number and size of incoming/outgoing links, and aligned the ends of the links to the middle of the nodes.
Thanks to Simon Titheridge!
forked from tomshanley's block: Sankey - highlight links by doubleclicking nodes
xxxxxxxxxx
<meta charset="utf-8">
<title>Traceability</title>
<link href="sankey_style.css" rel="stylesheet">
<style media="screen" type="text/css">
.sankey-graph {
height: 1000px;
}
.sankey-graph .node rect {
shape-rendering: crispEdges;
pointer-events:all;
fill: none;
stroke: rgb(160, 160,160);
stroke-width: 2px;
}
.sankey-graph .node text {
pointer-events: none;
text-shadow: 0 1px 0 #fff;
}
.sankey-graph .bar rect {
stroke: none;
}
.sankey-graph .link {
fill: none;
stroke: #000;
}
</style>
<body>
<header>
</header>
<p id="chart" class="sankey-graph">
<script src="https://d3js.org/d3.v5.min.js"></script>
<script src="sankey-bm.js"></script>
<script>
console.clear()
var margin = {top: 1, right: 0, bottom: 6, left: 0},
width = 1895, //also change CSS file to change Width
height = 1030 - margin.top - margin.bottom,
node_Width = 200;
node_Stroke = 2;
var svg = d3.select("#chart").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var sankey = d3.sankey()
.nodeWidth(node_Width) //Node width
.nodePadding(8) //spacing between nodes
.size([width, height]);
var path = sankey.link();
d3.json("benefits.json").then(function(graph) {
var drag = d3.drag()
.on("drag", dragmove);
function dragmove(d) {
d3.select(this).attr("transform",
"translate(" + d.x + "," + (d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
sankey.nodes(graph.nodes)
.links(graph.links)
.layout(101); //iterations for layout algorithm
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("opacity", 0.2)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
var node = svg.append("g").selectAll(".node")
.data(graph.nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) { return "translate(" + d.x + "," + d.y + ")"; })
.call(drag)
.on("click", handleNodeClick);
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
node.append("text")
.attr("y", 11)
.text(function(d) {
return d.name;
})
//Recursive highlighting of nodes
function handleNodeClick(d) {
if (d3.event.defaultPrevented) return;
//Reset colour of nodes to black
d3.selectAll("rect")
.style("fill","none");
d3.selectAll(".link")
.style("stroke","black")
.style("opacity", 0.2)
//Highlight clicked node
d3.selectAll("rect")
.style("fill", function(d2,i) {
return d.name == d2.name ? "LightCoral" : "none"
})
iterateLinkedLinksRight(d); //Recurse source direction
iterateLinkedLinksLeft(d); //Recurse target direction
}
function iterateLinkedLinksRight(pStartNode) {
//Select links that have a given source name
d3.selectAll("path.link").filter(function(pLinkedLink,i) {
return pLinkedLink.source.name == pStartNode.name;
})
.style("stroke","LightCoral")
.style("opacity", 0.6)
.each(iterateLinkedNodesRight);
}
function iterateLinkedNodesRight(pStartLink) {
//Select nodes that have a given source name
d3.selectAll("rect").filter(function(pLinkedNode,i) {
return pLinkedNode.name == pStartLink.target.name;
})
.style("fill","LightCoral")
.each(iterateLinkedLinksRight);
}
function iterateLinkedLinksLeft(pStartNode) {
//Select links that have a given source name
d3.selectAll("path.link").filter(function(pLinkedLink,i) {
return pLinkedLink.target.name == pStartNode.name;
})
.style("stroke","LightCoral")
.style("opacity", 0.6)
.each(iterateLinkedNodesLeft);
}
function iterateLinkedNodesLeft(pStartLink) {
//Select nodes that have a given source name
d3.selectAll("rect").filter(function(pLinkedNode,i) {
return pLinkedNode.name == pStartLink.source.name;
})
.style("fill","LightCoral")
.each(iterateLinkedLinksLeft);
}
});
</script>
https://d3js.org/d3.v5.min.js