See
- Altered Sankey plugin: https://github.com/briantjacobs/d3-plugins/tree/master/sankey
- Blog post http://briantjacobs.com/d3-sankey-schema
See
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> | |
<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; | |
text-shadow: 0 1px 0 #fff; | |
} | |
.link { | |
fill: none; | |
stroke: #000; | |
stroke-opacity: .2; | |
} | |
.link:hover { | |
stroke-opacity: .5; | |
} | |
</style> | |
</head> | |
<body> | |
<p id="chart"></p> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/d3/3.4.2/d3.min.js"></script> | |
<script src="https://rawgithub.com/briantjacobs/d3-plugins/master/sankey/sankey.js"></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 formatNumber = d3.format(",.0f"), | |
format = function(d) { return formatNumber(d) + " TWh"; }, | |
color = d3.scale.category20(); | |
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(15) | |
.nodePadding(10) | |
.size([width, height]) | |
.schema({ | |
id: "type_id", | |
source: "fuel_origin", | |
target: "fuel_dest", | |
value: "val" | |
}); | |
var path = sankey.link(); | |
//incomplete extract from | |
//http://bost.ocks.org/mike/sankey/energy.json | |
//http://www.decc.gov.uk/en/content/cms/tackling/2050/calculator_on/calculator_on.aspx" | |
var energy = {"nodes":[ | |
{"type_id": 12, "name":"Agricultural 'waste'"}, | |
{"type_id": 16, "name":"Bio-conversion"}, | |
{"type_id": 17, "name":"Liquid"}, | |
{"type_id": 10, "name":"Losses"}, | |
{"type_id": 19, "name":"Solid"}, | |
{"type_id": 13, "name":"Gas"}, | |
{"type_id": 14, "name":"Biofuel imports"} | |
], | |
"links": [ | |
{"fuel_origin":12,"fuel_dest":16,"val":124.729}, | |
{"fuel_origin":16,"fuel_dest":17,"val":0.597}, | |
{"fuel_origin":16,"fuel_dest":10,"val":26.862}, | |
{"fuel_origin":16,"fuel_dest":19,"val":280.322}, | |
{"fuel_origin":16,"fuel_dest":13,"val":81.144}, | |
{"fuel_origin":14,"fuel_dest":17,"val":35} | |
] | |
}; | |
sankey | |
.nodes(energy.nodes) | |
.links(energy.links) | |
.layout(32); | |
var link = svg.append("g").selectAll(".link") | |
.data(energy.links) | |
.enter().append("path") | |
.attr("class", "link") | |
.attr("d", path) | |
.style("stroke-width", function(d) { return Math.max(1, d.dy); }) | |
.sort(function(a, b) { return b.dy - a.dy; }); | |
link.append("title") | |
.text(function(d) { return d.fuel_origin.name + " → " + d.fuel_dest.name + "\n" + format(d.value); }); | |
var node = svg.append("g").selectAll(".node") | |
.data(energy.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)); | |
node.append("rect") | |
.attr("height", function(d) { return d.dy; }) | |
.attr("width", sankey.nodeWidth()) | |
.style("fill", function(d) {console.log(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); }); | |
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 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); | |
} | |
</script> | |
</body></html> |
I suppose one approach is, if your database id columns are integer
data points, you could add a varchar
prefix to the id (uniquely differentiating each of the source tables), and thus each identifier field in a node would be unique.
One issue that comes to mind, though, is when the
nodes
involve more than one category or table of data such that the id's from the database could overlap. What would be the best approach in this scenario?