|
<!DOCTYPE html> |
|
<html> |
|
<meta charset="utf-8"> |
|
<title>Sankey Diagram</title> |
|
<style> |
|
body { |
|
font-family: sans-serif; |
|
} |
|
|
|
#chart { |
|
height: 500px; |
|
} |
|
|
|
.node rect { |
|
cursor: move; |
|
fill-opacity: .9; |
|
shape-rendering: crispEdges; |
|
} |
|
|
|
.node text { |
|
pointer-events: none; |
|
font-size: 12px; |
|
} |
|
|
|
.link { |
|
fill: none; |
|
stroke: #000; |
|
stroke-opacity: .16; |
|
} |
|
|
|
.link:hover { |
|
stroke-opacity: .5; |
|
} |
|
|
|
</style> |
|
<body> |
|
<div id="chart"></div> |
|
<script src="https://d3js.org/d3.v4.min.js"></script> |
|
<script src="https://unpkg.com/d3-sankey@0.4.1"></script> |
|
<script> |
|
|
|
var margin = { |
|
top: 1, |
|
right: 1, |
|
bottom: 6, |
|
left: 1 |
|
}, |
|
width = 960 - margin.left - margin.right, |
|
height = 500 - margin.top - margin.bottom; |
|
|
|
var fontScale = d3.scaleLinear() |
|
.range([8, 30]); |
|
|
|
var formatNumber = d3.format(",.0f"), |
|
format = function(d) { |
|
return formatNumber(d) + " TWh"; |
|
}, |
|
color = d3.scaleOrdinal(d3.schemeCategory20); |
|
|
|
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 weightText = svg.append("text") |
|
.text("Links weighted equally") |
|
.attr("x", width/2-170) |
|
.attr("y", height) |
|
.style("font-size", "24px"); |
|
|
|
var sankey = d3.sankey() |
|
.nodeWidth(15) |
|
.nodePadding(10) |
|
.size([width, height]); |
|
|
|
var path = sankey.link(); |
|
|
|
d3.json("energy.json", function(energy) { |
|
var empty_links = energy.links.map(function(d) { |
|
d.id = d.source + " -> " + d.target; |
|
return { |
|
source: d.source, |
|
target: d.target, |
|
id: d.id, |
|
value: 1 |
|
} |
|
}); |
|
|
|
sankey |
|
.nodes(energy.nodes) |
|
.links(empty_links) |
|
.layout(32); |
|
|
|
fontScale.domain(d3.extent(energy.nodes, function(d) { return d.value })); |
|
|
|
var link = svg.append("g").selectAll(".link") |
|
.data(empty_links, function(d) { return d.id; }) |
|
.enter().append("path") |
|
.attr("class", "link") |
|
.attr("d", path) |
|
.style("stroke-width", function(d) { |
|
return Math.max(1, d.dy) + "px"; |
|
}) |
|
.sort(function(a, b) { |
|
return b.dy - a.dy; |
|
}); |
|
|
|
link.append("title") |
|
.text(function(d) { |
|
return d.source.name + " → " + d.target.name + "\n" + format(d.value); |
|
}); |
|
|
|
var node = svg.append("g").selectAll(".node") |
|
.data(energy.nodes, function(d) { return d.name; }) |
|
.enter().append("g") |
|
.attr("class", "node") |
|
.attr("transform", function(d) { |
|
return "translate(" + d.x + "," + d.y + ")"; |
|
}); |
|
|
|
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(1.8); |
|
}) |
|
.append("title") |
|
.text(function(d) { |
|
return d.name + "\n" + format(d.value); |
|
}); |
|
|
|
node.append("text") |
|
.attr("x", -6) |
|
.attr("y", function(d) { |
|
return d.dy / 2; |
|
}) |
|
.attr("dy", ".35em") |
|
.attr("text-anchor", "end") |
|
.attr("transform", null) |
|
.style("fill", function(d) { |
|
return d3.rgb(d.color).darker(2.4); |
|
}) |
|
.text(function(d) { |
|
return d.name; |
|
}) |
|
.style("font-size", function(d) { |
|
return Math.floor(fontScale(d.value)) + "px"; |
|
}) |
|
.filter(function(d) { |
|
return d.x < width / 2; |
|
}) |
|
.attr("x", 6 + sankey.nodeWidth()) |
|
.attr("text-anchor", "start"); |
|
|
|
function update(nodeData, linkData) { |
|
sankey |
|
.nodes(nodeData) |
|
.links(linkData) |
|
.layout(32); |
|
|
|
sankey.relayout(); |
|
fontScale.domain(d3.extent(nodeData, function(d) { return d.value })); |
|
|
|
svg.selectAll(".link") |
|
.data(linkData, function(d) { return d.id; }) |
|
.sort(function(a, b) { |
|
return b.dy - a.dy; |
|
}) |
|
.transition() |
|
.duration(1300) |
|
.attr("d", path) |
|
.style("stroke-width", function(d) { |
|
return Math.max(1, d.dy) + "px"; |
|
}); |
|
|
|
svg.selectAll(".node") |
|
.data(nodeData, function(d) { return d.name; }) |
|
.transition() |
|
.duration(1300) |
|
.attr("transform", function(d) { |
|
return "translate(" + d.x + "," + d.y + ")"; |
|
}); |
|
|
|
svg.selectAll(".node rect") |
|
.transition() |
|
.duration(1300) |
|
.attr("height", function(d) { |
|
return d.dy; |
|
}); |
|
|
|
svg.selectAll(".node text") |
|
.transition() |
|
.duration(1300) |
|
.attr("y", function(d) { |
|
return d.dy / 2; |
|
}) |
|
.style("font-size", function(d) { |
|
return Math.floor(fontScale(d.value)) + "px"; |
|
}); |
|
}; |
|
|
|
var counter = 0; |
|
function toggleTransition() { |
|
counter++; |
|
var activeLinks = counter % 2 ? energy.links : empty_links; |
|
weightText.text(counter % 2 ? "Links weighted by value" : "Links weighted equally"); |
|
update(energy.nodes, activeLinks); |
|
setTimeout(toggleTransition, 2400); |
|
}; |
|
|
|
setTimeout(toggleTransition, 2400); |
|
}); |
|
|
|
</script> |