Skip to content

Instantly share code, notes, and snippets.

@hailtothethief
Last active June 25, 2017 18:13
Show Gist options
  • Save hailtothethief/bdf8fbe0c6622c0d225350646570dfaf to your computer and use it in GitHub Desktop.
Save hailtothethief/bdf8fbe0c6622c0d225350646570dfaf to your computer and use it in GitHub Desktop.
Sankey from CSV III
license: gpl-3.0

Create a Sankey diagram from a CSV file. I got most of this code from d3noob's block, but I added the code to convert the CSV data into the format required by d3-sankey.

In this version, the links are colored as a linear gradient, transitioning from the color of the source to that of the target. In version 1, the links were not colored. In version 2, the links were colored according to this source.

My verdict: A single color is the easiest to read.

forked from HarryStevens's block: Sankey from CSV III

forked from esperandeo's block: Sankey from CSV III

La percentuale è da intendersi in quante canzoni è presente (in percentuale) quel termine

forked from salvob41's block: Sankey from CSV III

forked from salvob41's block: Sankey from CSV III

source target value
bello 1950 28.093645484949832
bello 1960 15.640599001663894
bello 1970 21.73913043478261
bello 1980 18.426103646833013
bello 1990 17.431192660550458
bello 2000 13.828689370485037
bello 2010 18.666666666666668
dolce 1950 18.06020066889632
dolce 1960 5.657237936772046
dolce 1970 6.340579710144928
dolce 1980 6.71785028790787
dolce 1990 5.402650356778797
grande 1950 10.702341137123746
grande 1960 8.818635607321132
grande 1970 12.862318840579709
grande 1980 17.85028790786948
grande 1990 13.455657492354739
grande 2000 10.8359133126935
grande 2010 15.80952380952381
lontano 1950 9.364548494983277
lontano 1960 4.492512479201332
lontano 1980 8.152173913043478
lontano 1990 10.556621880998081
lontano 2000 4.791029561671763
lontano 2010 3.8183694530443755
bianco 1950 8.695652173913043
bianco 1960 4.9916805324459235
bianco 1970 7.608695652173914
bianco 1980 7.293666026871401
bianco 1990 5.09683995922528
bianco 2000 4.437564499484004
bianco 2010 5.333333333333334
vivo 1950 6.688963210702341
vivo 1960 6.322795341098169
vivo 1980 5.072463768115942
vivo 1990 14.971209213051823
vivo 2000 7.135575942915392
vivo 2010 4.3343653250774
nuovo 1950 5.016722408026756
nuovo 1960 5.990016638935108
nuovo 1970 8.695652173913043
nuovo 1980 10.9404990403071
nuovo 1990 11.416921508664627
nuovo 2000 11.55830753353973
nuovo 2010 13.523809523809524
vero 1960 14.046822742474916
vero 1970 9.650582362728786
vero 1980 9.420289855072465
vero 1990 18.809980806142036
vero 2000 7.747196738022426
vero 2010 6.29514963880289
forte 1960 5.351170568561873
forte 1970 6.4891846921797
forte 1980 5.615942028985507
forte 1990 13.81957773512476
forte 2000 5.5045871559633035
forte 2010 4.12796697626419
strano 1970 21.070234113712374
strano 1980 7.154742096505824
strano 1990 11.77536231884058
strano 2000 10.17274472168906
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<style>
body {
margin: 0;
font-family: "Helvetica Neue", sans-serif;
}
.node rect {
cursor: move;
fill-opacity: .9;
shape-rendering: crispEdges;
}
.node text {
pointer-events: none;
text-shadow: 0px 0px 2px #fff;
font-size: .8em;
}
.link {
fill: none;
stroke: #000;
stroke-opacity: .5;
}
.link:hover {
stroke-opacity: 1;
}
</style>
</head>
<body>
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="sankey.js"></script>
<script>
var data = ["most_common_all_a", "most_common_all_s", "most_common_all_v"];
var select = d3.select('body')
.append('select')
.attr('class','select')
.on('change',onchange)
var options = select
.selectAll('option')
.data(data).enter()
.append('option')
.attr('value',function (d,i){
return data[i];
})
.text(function (d) { return d; });
// set the dimensions and margins of the graph
var margin = {top: 10, right: 10, bottom: 10, left: 10},
width = window.innerWidth - margin.left - margin.right,
height = window.innerHeight - margin.top - margin.bottom;
// format variables
var formatNumber = d3.format(",.0f"), // zero decimal places
format = function(d) { return formatNumber(d) + "%"; },
color = d3.scaleOrdinal(d3.schemeCategory20);
// append the svg object to the body of the page
var svg = d3.select("body").append("svg")
.attr('class', 'graph')
.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(10)
.nodePadding(10)
.size([width, height]);
var path = sankey.link();
// defs for the linear gradients
var defs = svg.append("defs");
// load the data
d3.queue()
.defer(d3.csv, "data.csv")
.await(ready);
function onchange() {
selectValue = d3.select('select').property('value')
name = selectValue+".csv";
//svg.selectAll("*").remove();
svg.select("defs").selectAll("*").remove();
svg.selectAll("g").selectAll("*").remove();
d3.queue()
.defer(d3.csv, name)
.await(ready);
};
function ready(error, csv){
// create an array to push all sources and targets, before making them unique
var arr = [];
csv.forEach(function(d){
arr.push(d.source);
arr.push(d.target);
});
// create nodes array
var nodes = arr.filter(onlyUnique).map(function(d,i){
return {
node: i,
name: d
}
});
// create links array
var links = csv.map(function(csv_row){
return {
source: getNode("source"),
target: getNode("target"),
value: +csv_row.value
}
function getNode(type){
return nodes.filter(function(node_object){ return node_object.name == csv_row[type]; })[0].node;
}
});
sankey
.nodes(nodes)
.links(links)
.layout(32);
// add in the links
var link = svg.append("g").selectAll(".link")
.data(links)
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.style("stroke", function(d, i){
// create the gradient
var gradient_id = "gradient-" + i;
var gradient = defs.append("linearGradient")
.attr("id", gradient_id);
gradient.append("stop")
.attr("offset", "5%")
.attr("stop-color", color(d.source.name.replace(/ .*/, "")));
gradient.append("stop")
.attr("offset", "95%")
.attr("stop-color", color(d.target.name.replace(/ .*/, "")));
// TODO: Figure out what the hell is happening with bob and jimmy
return i == 0 ? color(d.source.name.replace(/ .*/, "")) : "url(#" + gradient_id + ")";
// return "url(#" + gradient_id + ")";
})
.style("stroke-width", function(d) { return Math.max(1, d.dy); })
.sort(function(a, b) { return b.dy - a.dy; });
// add the link titles
link.append("title")
.text(function(d) {
return d.source.name + " → " +
d.target.name + "\n" + format(d.value); });
// add in the nodes
var node = svg.append("g").selectAll(".node")
.data(nodes)
.enter().append("g")
.attr("class", "node")
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.call(d3.drag()
.subject(function(d) {
return d;
})
.on("start", function() {
this.parentNode.appendChild(this);
})
.on("drag", dragmove));
// add the rectangles for the nodes
node.append("rect")
.attr("height", function(d) { return d.dy < 0 ? .1 : 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 in the title for the nodes
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");
// the function for moving the 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);
}
}
// unique values of an array
function onlyUnique(value, index, self) {
return self.indexOf(value) === index;
}
</script>
</body>
</html>
bello 1950 28.093645484949832
bello 1960 15.640599001663894
bello 1970 21.73913043478261
bello 1980 18.426103646833013
bello 1990 17.431192660550458
bello 2000 13.828689370485037
bello 2010 18.666666666666668
dolce 1950 18.06020066889632
dolce 1960 5.657237936772046
dolce 1970 6.340579710144928
dolce 1980 6.71785028790787
dolce 1990 5.402650356778797
grande 1950 10.702341137123746
grande 1960 8.818635607321132
grande 1970 12.862318840579709
grande 1980 17.85028790786948
grande 1990 13.455657492354739
grande 2000 10.8359133126935
grande 2010 15.80952380952381
lontano 1950 9.364548494983277
lontano 1960 4.492512479201332
lontano 1980 8.152173913043478
lontano 1990 10.556621880998081
lontano 2000 4.791029561671763
lontano 2010 3.8183694530443755
bianco 1950 8.695652173913043
bianco 1960 4.9916805324459235
bianco 1970 7.608695652173914
bianco 1980 7.293666026871401
bianco 1990 5.09683995922528
bianco 2000 4.437564499484004
bianco 2010 5.333333333333334
vivo 1950 6.688963210702341
vivo 1960 6.322795341098169
vivo 1980 5.072463768115942
vivo 1990 14.971209213051823
vivo 2000 7.135575942915392
vivo 2010 4.3343653250774
nuovo 1950 5.016722408026756
nuovo 1960 5.990016638935108
nuovo 1970 8.695652173913043
nuovo 1980 10.9404990403071
nuovo 1990 11.416921508664627
nuovo 2000 11.55830753353973
nuovo 2010 13.523809523809524
vero 1960 14.046822742474916
vero 1970 9.650582362728786
vero 1980 9.420289855072465
vero 1990 18.809980806142036
vero 2000 7.747196738022426
vero 2010 6.29514963880289
forte 1960 5.351170568561873
forte 1970 6.4891846921797
forte 1980 5.615942028985507
forte 1990 13.81957773512476
forte 2000 5.5045871559633035
forte 2010 4.12796697626419
strano 1970 21.070234113712374
strano 1980 7.154742096505824
strano 1990 11.77536231884058
strano 2000 10.17274472168906
source target value
cuore 1950 44.481605351170565
cuore 1960 17.470881863560734
cuore 1970 26.6304347826087
cuore 1980 31.285988483685223
cuore 1990 26.19775739041794
cuore 2000 22.084623323013417
cuore 2010 24.0
amore 1950 43.812709030100336
amore 1960 45.25790349417637
amore 1970 49.275362318840585
amore 1980 46.257197696737045
amore 1990 34.25076452599388
amore 2000 30.4437564499484
amore 2010 35.80952380952381
vita 1950 31.438127090301005
vita 1960 26.788685524126453
vita 1970 23.007246376811594
vita 1980 27.2552783109405
vita 1990 31.906218144750255
vita 2000 26.006191950464398
vita 2010 32.761904761904766
giorno 1950 19.39799331103679
giorno 1960 27.953410981697168
giorno 1970 28.07971014492754
giorno 1980 24.760076775431862
giorno 1990 23.751274209989806
giorno 2000 23.52941176470588
giorno 2010 29.714285714285715
sole 1950 18.394648829431436
sole 1960 19.800332778702163
sole 1970 20.28985507246377
sole 1980 19.577735124760075
sole 1990 17.940876656472987
sole 2000 16.09907120743034
sole 2010 18.285714285714285
mondo 1950 16.387959866220736
mondo 1960 22.129783693843592
mondo 1970 15.39855072463768
mondo 1980 22.072936660268713
mondo 1990 24.057084607543324
mondo 2000 19.298245614035086
mondo 2010 26.476190476190474
tempo 1950 16.05351170568562
tempo 1960 10.316139767054908
tempo 1970 19.565217391304348
tempo 1980 19.577735124760075
tempo 1990 24.97451580020387
tempo 2000 25.696594427244584
tempo 2010 30.857142857142854
notte 1950 15.050167224080269
notte 1960 13.976705490848584
notte 1970 19.384057971014492
notte 1980 23.224568138195778
notte 1990 21.202854230377167
notte 2000 19.09184726522188
notte 2010 20.57142857142857
occhio 1950 14.381270903010032
occhio 1960 22.129783693843592
occhio 1970 30.61594202898551
occhio 1980 24.760076775431862
occhio 1990 23.547400611620795
occhio 2000 23.52941176470588
occhio 2010 24.952380952380953
mano 1950 12.040133779264215
mano 1960 16.80532445923461
mano 1970 24.27536231884058
mano 1980 21.689059500959694
mano 1990 18.654434250764528
mano 2000 20.020639834881322
mano 2010 21.714285714285715
source target value
dire 1950 49.49832775919732
dire 1960 39.267886855241265
dire 1970 47.46376811594203
dire 1980 39.34740882917466
dire 1990 40.87665647298675
dire 2000 40.14447884416924
dire 2010 44.57142857142857
avere 1950 48.49498327759198
avere 1960 60.89850249584027
avere 1970 77.17391304347827
avere 1980 67.75431861804223
avere 1990 69.92864424057085
avere 2000 72.34262125902993
avere 2010 77.52380952380953
volere 1950 40.13377926421405
volere 1960 44.42595673876872
volere 1970 45.471014492753625
volere 1980 43.37811900191939
volere 1990 42.60958205912334
volere 2000 46.542827657378744
volere 2010 47.42857142857143
potere 1950 34.11371237458194
potere 1960 29.61730449251248
potere 1970 35.869565217391305
potere 1980 32.437619961612285
potere 1990 40.57084607543323
potere 2000 33.539731682146545
potere 2010 36.76190476190476
sapere 1950 32.441471571906355
sapere 1960 38.43594009983361
sapere 1970 45.65217391304348
sapere 1980 43.5700575815739
sapere 1990 43.6289500509684
sapere 2000 46.43962848297213
sapere 2010 45.714285714285715
andare 1950 31.438127090301005
andare 1960 32.4459234608985
andare 1970 42.572463768115945
andare 1980 47.98464491362764
andare 1990 42.40570846075433
andare 2000 39.42208462332301
andare 2010 45.52380952380952
sentire 1950 29.09698996655518
sentire 1960 21.630615640599
sentire 1970 22.644927536231883
sentire 1980 22.84069097888676
sentire 1990 30.68297655453619
sentire 2000 29.30856553147575
sentire 2010 33.904761904761905
vedere 1950 25.75250836120401
vedere 1960 29.61730449251248
vedere 1970 30.978260869565215
vedere 1980 28.79078694817658
vedere 1990 34.25076452599388
vedere 2000 31.166150670794636
vedere 2010 33.33333333333333
pensare 1960 32.441471571906355
pensare 1970 20.133111480865225
pensare 1980 21.3768115942029
pensare 1990 41.45873320537428
pensare 2000 22.52803261977574
pensare 2010 14.551083591331269
guardare 1960 25.08361204013378
guardare 1970 20.465890183028286
guardare 1980 18.115942028985508
guardare 1990 44.145873320537426
guardare 2000 23.037716615698265
guardare 2010 14.75748194014448
d3.sankey = function() {
var sankey = {},
nodeWidth = 24,
nodePadding = 8,
size = [1, 1],
nodes = [],
links = [];
sankey.nodeWidth = function(_) {
if (!arguments.length) return nodeWidth;
nodeWidth = +_;
return sankey;
};
sankey.nodePadding = function(_) {
if (!arguments.length) return nodePadding;
nodePadding = +_;
return sankey;
};
sankey.nodes = function(_) {
if (!arguments.length) return nodes;
nodes = _;
return sankey;
};
sankey.links = function(_) {
if (!arguments.length) return links;
links = _;
return sankey;
};
sankey.size = function(_) {
if (!arguments.length) return size;
size = _;
return sankey;
};
sankey.layout = function(iterations) {
computeNodeLinks();
computeNodeValues();
computeNodeBreadths();
computeNodeDepths(iterations);
computeLinkDepths();
return sankey;
};
sankey.relayout = function() {
computeLinkDepths();
return sankey;
};
sankey.link = function() {
var curvature = .5;
function link(d) {
var x0 = d.source.x + d.source.dx,
x1 = d.target.x,
xi = d3.interpolateNumber(x0, x1),
x2 = xi(curvature),
x3 = xi(1 - curvature),
y0 = d.source.y + d.sy + d.dy / 2,
y1 = d.target.y + d.ty + d.dy / 2;
return "M" + x0 + "," + y0
+ "C" + x2 + "," + y0
+ " " + x3 + "," + y1
+ " " + x1 + "," + y1;
}
link.curvature = function(_) {
if (!arguments.length) return curvature;
curvature = +_;
return link;
};
return link;
};
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks() {
nodes.forEach(function(node) {
node.sourceLinks = [];
node.targetLinks = [];
});
links.forEach(function(link) {
var source = link.source,
target = link.target;
if (typeof source === "number") source = link.source = nodes[link.source];
if (typeof target === "number") target = link.target = nodes[link.target];
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
function computeNodeValues() {
nodes.forEach(function(node) {
node.value = Math.max(
d3.sum(node.sourceLinks, value),
d3.sum(node.targetLinks, value)
);
});
}
// Iteratively assign the breadth (x-position) for each node.
// Nodes are assigned the maximum breadth of incoming neighbors plus one;
// nodes with no incoming links are assigned breadth zero, while
// nodes with no outgoing links are assigned the maximum breadth.
function computeNodeBreadths() {
var remainingNodes = nodes,
nextNodes,
x = 0;
while (remainingNodes.length) {
nextNodes = [];
remainingNodes.forEach(function(node) {
node.x = x;
node.dx = nodeWidth;
node.sourceLinks.forEach(function(link) {
if (nextNodes.indexOf(link.target) < 0) {
nextNodes.push(link.target);
}
});
});
remainingNodes = nextNodes;
++x;
}
//
moveSinksRight(x);
scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
}
function moveSourcesRight() {
nodes.forEach(function(node) {
if (!node.targetLinks.length) {
node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;
}
});
}
function moveSinksRight(x) {
nodes.forEach(function(node) {
if (!node.sourceLinks.length) {
node.x = x - 1;
}
});
}
function scaleNodeBreadths(kx) {
nodes.forEach(function(node) {
node.x *= kx;
});
}
function computeNodeDepths(iterations) {
var nodesByBreadth = d3.nest()
.key(function(d) { return d.x; })
.sortKeys(d3.ascending)
.entries(nodes)
.map(function(d) { return d.values; });
//
initializeNodeDepth();
resolveCollisions();
for (var alpha = 1; iterations > 0; --iterations) {
relaxRightToLeft(alpha *= .99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
function initializeNodeDepth() {
var ky = d3.min(nodesByBreadth, function(nodes) {
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
});
nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node, i) {
node.y = i;
node.dy = node.value * ky;
});
});
links.forEach(function(link) {
link.dy = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
nodesByBreadth.forEach(function(nodes, breadth) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedSource(link) {
return center(link.source) * link.value;
}
}
function relaxRightToLeft(alpha) {
nodesByBreadth.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);
node.y += (y - center(node)) * alpha;
}
});
});
function weightedTarget(link) {
return center(link.target) * link.value;
}
}
function resolveCollisions() {
nodesByBreadth.forEach(function(nodes) {
var node,
dy,
y0 = 0,
n = nodes.length,
i;
// Push any overlapping nodes down.
nodes.sort(ascendingDepth);
for (i = 0; i < n; ++i) {
node = nodes[i];
dy = y0 - node.y;
if (dy > 0) node.y += dy;
y0 = node.y + node.dy + nodePadding;
}
// If the bottommost node goes outside the bounds, push it back up.
dy = y0 - nodePadding - size[1];
if (dy > 0) {
y0 = node.y -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
dy = node.y + node.dy + nodePadding - y0;
if (dy > 0) node.y -= dy;
y0 = node.y;
}
}
});
}
function ascendingDepth(a, b) {
return a.y - b.y;
}
}
function computeLinkDepths() {
nodes.forEach(function(node) {
node.sourceLinks.sort(ascendingTargetDepth);
node.targetLinks.sort(ascendingSourceDepth);
});
nodes.forEach(function(node) {
var sy = 0, ty = 0;
node.sourceLinks.forEach(function(link) {
link.sy = sy;
sy += link.dy;
});
node.targetLinks.forEach(function(link) {
link.ty = ty;
ty += link.dy;
});
});
function ascendingSourceDepth(a, b) {
return a.source.y - b.source.y;
}
function ascendingTargetDepth(a, b) {
return a.target.y - b.target.y;
}
}
function center(node) {
return node.y + node.dy / 2;
}
function value(link) {
return link.value;
}
return sankey;
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment