Skip to content

Instantly share code, notes, and snippets.

@syntagmatic
Last active November 8, 2019 07:55
Show Gist options
  • Save syntagmatic/77c7f7e8802e8824eed473dd065c450b to your computer and use it in GitHub Desktop.
Save syntagmatic/77c7f7e8802e8824eed473dd065c450b to your computer and use it in GitHub Desktop.
Sankey Transitions
border: no

This example created for development purposes using the d3-sankey plugin for D3 4.0.

Based on xaranke's port of Mike Bostock's Sankey Diagram with drag behavior enabled and several fixes for the d3 version 4.x release.

It would be nice if d3-sankey's value accessor was configurable, to avoid creating the empty_links data structure in the code below.

{
"nodes": [{
"name": "Agricultural 'waste'"
}, {
"name": "Bio-conversion"
}, {
"name": "Liquid"
}, {
"name": "Losses"
}, {
"name": "Solid"
}, {
"name": "Gas"
}, {
"name": "Biofuel imports"
}, {
"name": "Biomass imports"
}, {
"name": "Coal imports"
}, {
"name": "Coal"
}, {
"name": "Coal reserves"
}, {
"name": "District heating"
}, {
"name": "Industry"
}, {
"name": "Heating and cooling - commercial"
}, {
"name": "Heating and cooling - homes"
}, {
"name": "Electricity grid"
}, {
"name": "Over generation / exports"
}, {
"name": "H2 conversion"
}, {
"name": "Road transport"
}, {
"name": "Agriculture"
}, {
"name": "Rail transport"
}, {
"name": "Lighting & appliances - commercial"
}, {
"name": "Lighting & appliances - homes"
}, {
"name": "Gas imports"
}, {
"name": "Ngas"
}, {
"name": "Gas reserves"
}, {
"name": "Thermal generation"
}, {
"name": "Geothermal"
}, {
"name": "H2"
}, {
"name": "Hydro"
}, {
"name": "International shipping"
}, {
"name": "Domestic aviation"
}, {
"name": "International aviation"
}, {
"name": "National navigation"
}, {
"name": "Marine algae"
}, {
"name": "Nuclear"
}, {
"name": "Oil imports"
}, {
"name": "Oil"
}, {
"name": "Oil reserves"
}, {
"name": "Other waste"
}, {
"name": "Pumped heat"
}, {
"name": "Solar PV"
}, {
"name": "Solar Thermal"
}, {
"name": "Solar"
}, {
"name": "Tidal"
}, {
"name": "UK land based bioenergy"
}, {
"name": "Wave"
}, {
"name": "Wind"
}],
"links": [{
"source": 0,
"target": 1,
"value": 124.729
}, {
"source": 1,
"target": 2,
"value": 0.597
}, {
"source": 1,
"target": 3,
"value": 26.862
}, {
"source": 1,
"target": 4,
"value": 280.322
}, {
"source": 1,
"target": 5,
"value": 81.144
}, {
"source": 6,
"target": 2,
"value": 35
}, {
"source": 7,
"target": 4,
"value": 35
}, {
"source": 8,
"target": 9,
"value": 11.606
}, {
"source": 10,
"target": 9,
"value": 63.965
}, {
"source": 9,
"target": 4,
"value": 75.571
}, {
"source": 11,
"target": 12,
"value": 10.639
}, {
"source": 11,
"target": 13,
"value": 22.505
}, {
"source": 11,
"target": 14,
"value": 46.184
}, {
"source": 15,
"target": 16,
"value": 104.453
}, {
"source": 15,
"target": 14,
"value": 113.726
}, {
"source": 15,
"target": 17,
"value": 27.14
}, {
"source": 15,
"target": 12,
"value": 342.165
}, {
"source": 15,
"target": 18,
"value": 37.797
}, {
"source": 15,
"target": 19,
"value": 4.412
}, {
"source": 15,
"target": 13,
"value": 40.858
}, {
"source": 15,
"target": 3,
"value": 56.691
}, {
"source": 15,
"target": 20,
"value": 7.863
}, {
"source": 15,
"target": 21,
"value": 90.008
}, {
"source": 15,
"target": 22,
"value": 93.494
}, {
"source": 23,
"target": 24,
"value": 40.719
}, {
"source": 25,
"target": 24,
"value": 82.233
}, {
"source": 5,
"target": 13,
"value": 0.129
}, {
"source": 5,
"target": 3,
"value": 1.401
}, {
"source": 5,
"target": 26,
"value": 151.891
}, {
"source": 5,
"target": 19,
"value": 2.096
}, {
"source": 5,
"target": 12,
"value": 48.58
}, {
"source": 27,
"target": 15,
"value": 7.013
}, {
"source": 17,
"target": 28,
"value": 20.897
}, {
"source": 17,
"target": 3,
"value": 6.242
}, {
"source": 28,
"target": 18,
"value": 20.897
}, {
"source": 29,
"target": 15,
"value": 6.995
}, {
"source": 2,
"target": 12,
"value": 121.066
}, {
"source": 2,
"target": 30,
"value": 128.69
}, {
"source": 2,
"target": 18,
"value": 135.835
}, {
"source": 2,
"target": 31,
"value": 14.458
}, {
"source": 2,
"target": 32,
"value": 206.267
}, {
"source": 2,
"target": 19,
"value": 3.64
}, {
"source": 2,
"target": 33,
"value": 33.218
}, {
"source": 2,
"target": 20,
"value": 4.413
}, {
"source": 34,
"target": 1,
"value": 4.375
}, {
"source": 24,
"target": 5,
"value": 122.952
}, {
"source": 35,
"target": 26,
"value": 839.978
}, {
"source": 36,
"target": 37,
"value": 504.287
}, {
"source": 38,
"target": 37,
"value": 107.703
}, {
"source": 37,
"target": 2,
"value": 611.99
}, {
"source": 39,
"target": 4,
"value": 56.587
}, {
"source": 39,
"target": 1,
"value": 77.81
}, {
"source": 40,
"target": 14,
"value": 193.026
}, {
"source": 40,
"target": 13,
"value": 70.672
}, {
"source": 41,
"target": 15,
"value": 59.901
}, {
"source": 42,
"target": 14,
"value": 19.263
}, {
"source": 43,
"target": 42,
"value": 19.263
}, {
"source": 43,
"target": 41,
"value": 59.901
}, {
"source": 4,
"target": 19,
"value": 0.882
}, {
"source": 4,
"target": 26,
"value": 400.12
}, {
"source": 4,
"target": 12,
"value": 46.477
}, {
"source": 26,
"target": 15,
"value": 525.531
}, {
"source": 26,
"target": 3,
"value": 787.129
}, {
"source": 26,
"target": 11,
"value": 79.329
}, {
"source": 44,
"target": 15,
"value": 9.452
}, {
"source": 45,
"target": 1,
"value": 182.01
}, {
"source": 46,
"target": 15,
"value": 19.013
}, {
"source": 47,
"target": 15,
"value": 289.366
}]
}
<!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/[email protected]"></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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment