Skip to content

Instantly share code, notes, and snippets.

@jdesilvio
Last active August 29, 2015 14:23
Show Gist options
  • Save jdesilvio/bd35cbc38ed087249d98 to your computer and use it in GitHub Desktop.
Save jdesilvio/bd35cbc38ed087249d98 to your computer and use it in GitHub Desktop.
D3.js Sankey diagram - Modified from Mike Bostock's example
<!DOCTYPE html>
<!-- Credit to http://bost.ocks.org/mike/sankey/ and http://bl.ocks.org/d3noob/5028304 -->
<html lang="en">
<head>
<meta charset="utf-8">
<title>Page Title</title>
<script src="http://d3js.org/d3.v3.min.js" charset="utf-8"></script>
<script src="sankey.js"></script>
<style type="text/css">
body {
text-align:center;
}
.node rect {
cursor: move;
fill-opacity: .5;
shape-rendering: crispEdges;
}
.node rect:hover {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0 0 1px #fff, 0 0 2px #fff, 0 0 3px #fff, 0 0 5px #fff;
font-family: "Helvetica";
font-weight: bold;
font-size: 14px;
}
.headline {
font-family: "Helvetica";
font-weight: bold;
color: black;
text-align: center;
}
.highlight {
color: black;
}
.highlight:hover {
text-shadow: 1px 1px 1px #808080;
}
</style>
</head>
<body>
<h1 class=headline>Title</h1>
<p id="chart">
<script>
//Units to display next to number
var units = "Units";
// SVG size
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = 1200 - margin.left - margin.right,
height = 960 - margin.top - margin.bottom;
var formatNumber = d3.format(",.2f"), // number of decimal places shown
format = function(d) { return "$" + formatNumber(d); }, //prepend something to the number
color = d3.scale.category20();
// Append the SVG to the <p> tag
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 + ")");
// Set the sankey diagram properties
var sankey = d3.sankey()
.nodeWidth(36)
.nodePadding(10)
.size([width, height - 50]);
var path = sankey.link();
// load data
d3.json("data.json", function(error, graph) {
// Map names to nodes
var nodeMap = {};
graph.nodes.forEach(function(x) { nodeMap[x.name] = x; });
graph.links = graph.links.map(function(x) {
return {
source: nodeMap[x.source],
target: nodeMap[x.target],
value: x.value
};
});
// Initialize Sankey
sankey
.nodes(graph.nodes)
.links(graph.links)
.layout(32);
// Add links
var link = svg.append("g").selectAll(".link")
.data(graph.links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("fill", "none")
.style("stroke", "#000")
.style("stroke-opacity", .2)
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.attr("id", function(d,i){
d.id = i;
return "link-"+i;
})
.sort(function(a, b) { return b.dy - a.dy; })
.on("mouseover", function(d,i) {
d3.select(this).transition()
.ease("cubic-out")
.duration("500")
.style("stroke-opacity", .5);
})
.on("mouseout", function(d,i) {
d3.select(this).transition()
.ease("cubic-out")
.duration("500")
.style("stroke-opacity", .2);
});
// Add link titles
link.append("title")
.text(function(d) { return d.source.name + " → " + d.target.name + "\n" + format(d.value); });
// Add nodes
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(d3.behavior.drag()
.origin(function(d) { return d; })
.on("dragstart", function() { this.parentNode.appendChild(this); })
.on("drag", dragmove))
.on("mouseover",highlight_node_links)
.on("mouseout",highlight_node_links)
.on("mousedown", highlight_node_links);
// Add rectangles for the nodes
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", function(d) { return d.color = color(d.name.replace(/ .*/, "")); })
.style("stroke", function(d) { return d3.rgb(d.color).darker(2); })
.append("title")
.text(function(d) { return d.name + "\n" + format(d.value); });
// Add node titles
node.append("text")
.attr("x", -6)
.attr("y", function(d) { return d.dy / 2; })
.attr("dy", ".35em")
.attr("text-anchor", "end")
.attr("transform", null)
.text(function(d) { return d.name; })
.filter(function(d) { return d.x < width / 2; })
.attr("x", 6 + sankey.nodeWidth())
.attr("text-anchor", "start");
// Function for moving nodes
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);
}
// Function for hightlighting entire path
function highlight_node_links(node,i){
var remainingNodes=[],
nextNodes=[];
var stroke_opacity = 0;
if( d3.select(this).attr("mouseover") == "0" ){
d3.select(this).attr("mouseover","1");
stroke_opacity = 0.2;
} else {
d3.select(this).attr("mouseover","0");
stroke_opacity = 0.5;
}
var traverse = [{
linkType : "sourceLinks",
nodeType : "target"
}, {
linkType : "targetLinks",
nodeType : "source"
}];
traverse.forEach(function(step){
node[step.linkType].forEach(function(link) {
remainingNodes.push(link[step.nodeType]);
highlight_link(link.id, stroke_opacity);
});
while (remainingNodes.length) {
nextNodes = [];
remainingNodes.forEach(function(node) {
node[step.linkType].forEach(function(link) {
nextNodes.push(link[step.nodeType]);
highlight_link(link.id, stroke_opacity);
});
});
remainingNodes = nextNodes;
}
});
}
function highlight_link(id,opacity){
d3.select("#link-"+id).style("stroke-opacity", opacity);
}
});
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment