Skip to content

Instantly share code, notes, and snippets.

@jasonhodges
Last active June 2, 2017 20:56
Show Gist options
  • Save jasonhodges/34d109fafe3c81a06044f0d201e5e9b7 to your computer and use it in GitHub Desktop.
Save jasonhodges/34d109fafe3c81a06044f0d201e5e9b7 to your computer and use it in GitHub Desktop.
d3v4 sankey
license: mit
{
"sankey": {
"nodes": [{
"name": "11909",
"node": 0
}, {
"name": "11988",
"node": 1
}, {
"name": "12013",
"node": 2
}, {
"name": "11913",
"node": 3
}, {
"name": "11735",
"node": 4
}, {
"name": "11735",
"node": 5
}, {
"name": "11776",
"node": 6
}, {
"name": "12051",
"node": 7
}, {
"name": "12051",
"node": 8
}, {
"name": "12051",
"node": 9
}, {
"name": "12012",
"node": 10
}, {
"name": "12051",
"node": 11
}, {
"name": "11757",
"node": 12
}, {
"name": "12064",
"node": 13
}, {
"name": "11913",
"node": 14
}, {
"name": "12051",
"node": 15
}, {
"name": "12012",
"node": 16
}],
"links": [{
"source": 0,
"target": 1,
"value": 0.06884080477620182,
"rank": 1
}, {
"source": 0,
"target": 4,
"value": 0.023963833385098526,
"rank": 2
},{
"source": 0,
"target": 6,
"value": 0.020634756300974336,
"rank": 3
},{
"source": 0,
"target": 7,
"value": 0.020501317136579586,
"rank": 4
},{
"source": 0,
"target": 6,
"value": 0.013360021166212283,
"rank": 5
},{
"source": 1,
"target": 2,
"value": 0.06884080477620182,
"rank": 1
}, {
"source": 2,
"target": 3,
"value": 0.06884080477620182,
"rank": 1
}, {
"source": 4,
"target": 5,
"value": 0.023963833385098526,
"rank": 2
}, {
"source": 5,
"target": 3,
"value": 0.023963833385098526,
"rank": 2
}, {
"source": 6,
"target": 7,
"value": 0.020634756300974336,
"rank": 3
}, {
"source": 6,
"target": 9,
"value": 0.013360021166212283,
"rank": 5
},{
"source": 7,
"target": 8,
"value": 0.020634756300974336,
"rank": 3
}, {
"source": 7,
"target": 8,
"value": 0.020501317136579586,
"rank": 4
},{
"source": 8,
"target": 9,
"value": 0.020634756300974336,
"rank": 3
}, {
"source": 8,
"target": 9,
"value": 0.020501317136579586,
"rank": 4
},{
"source": 9,
"target": 10,
"value": 0.020634756300974336,
"rank": 3
},{
"source": 9,
"target": 10,
"value": 0.020501317136579586,
"rank": 4
}, {
"source": 9,
"target": 10,
"value": 0.013360021166212283,
"rank": 5
},{
"source": 10,
"target": 11,
"value": 0.020634756300974336,
"rank": 3
}, {
"source": 10,
"target": 15,
"value": 0.020501317136579586,
"rank": 4
}, {
"source": 10,
"target": 11,
"value": 0.013360021166212283,
"rank": 5
}, {
"source": 11,
"target": 12,
"value": 0.013360021166212283,
"rank": 5
}, {
"source": 12,
"target": 13,
"value": 0.013360021166212283,
"rank": 5
}, {
"source": 11,
"target": 12,
"value": 0.020634756300974336,
"rank": 3
}, {
"source": 12,
"target": 13,
"value": 0.020634756300974336,
"rank": 3
}, {
"source": 13,
"target": 3,
"value": 0.020634756300974336,
"rank": 3
},{
"source": 13,
"target": 14,
"value": 0.020501317136579586,
"rank": 4
}, {
"source": 13,
"target": 14,
"value": 0.013360021166212283,
"rank": 5
}, {
"source": 14,
"target": 3,
"value": 0.020501317136579586,
"rank": 4
}, {
"source": 14,
"target": 3,
"value": 0.013360021166212283,
"rank": 5
}, {
"source": 15,
"target": 16,
"value": 0.020501317136579586,
"rank": 4
}, {
"source": 16,
"target": 13,
"value": 0.020501317136579586,
"rank": 4
} ]
},
"params": [0, 0, 0, 0, 1]
}
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<script src="https://d3js.org/d3.v4.min.js"></script>
<script src="https://d3js.org/d3-drag.v1.min.js"></script>
<script src="https://unpkg.com/d3-sankey@0.4.1"></script>
<style>
body { margin:0;position:fixed;top:0;right:0;bottom:0;left:0; }
.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: blue;stroke-opacity: .2;}
.link_opacitize {fill: none;stroke: red;stroke-opacity: 1}
.link:hover {stroke-opacity: .5;}
</style>
</head>
<body>
<script>
// Feel free to change or delete any of the code you see in this editor!
var svg = d3.select("body").append("svg")
.attr("width", 960)
.attr("height", 500)
svg.append("text")
.text("Edit the code below to change me!")
.attr("y", 200)
.attr("x", 120)
.style("font-size", 36)
.style("font-family", "monospace")
var margin = {
top: 20,
right: 1,
bottom: 20,
left: 1
},
width = 1100 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom,
padding = 38,
nodeWidth = 25,
layout = 100,
lowopacity= 0.3,
highopacity = 0.7;
svg.append("rect")
.attr("x",0)
.attr("y",0)
.attr("width","100%")
.attr("height","100%")
.attr("fill","white");
svg=svg
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform",
"translate(" + margin.left + "," + margin.top + ")");
svg.selectAll("g").remove();
var sankey = d3.sankey()
.nodeWidth(nodeWidth)
.nodePadding(padding)
.size([width, height]);
var formatNumber = d3.format(",.0%"),
format = function(d) {
return formatNumber(d);
},
color = d3.scaleLinear(d3.schemeCategory20);
var path = sankey.link();
d3.json("data.json", function(data) {
let links = data.sankey.links;
let goodNodes = data.sankey.nodes.filter((node) => {
return !!node.name;
});
let nodes = goodNodes;
sankey
.nodes(nodes)
.links(links)
.layout(layout);
var link = svg.append("g").selectAll(".link")
.data(links, function(d) { return d.rank; })
.enter().append("path")
.attr("class", "link")
.attr("d", path)
.attr('id', function(d, i){
console.log(d, i);
d.id = i;
return "link-" + d.id;
})
.on("click", highlight_links)
.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(nodes, function(d) { return d.node; })
.enter().append("g")
.attr("class", "node")
.attr('id', function(d, i) {
d.id = i;
return "node-" + d.node;
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
}).on("click", function() {
if (d3.event.defaultPrevented) return; // click suppressed
console.log("clicked!");
alert("clicked")
}).call(d3.drag()
.filter(function() {
console.log(d3.event)
return d3.event.shiftKey
})
.subject(function(d) {
return d;
})
.on("start", 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) {
// return d.color = color(d.name.replace(/ .*/, ""));
// })
.style("fill", '#384650')
.style("fill-opacity", ".9")
.style("shape-rendering", "crispEdges")
.style("stroke", function(d) { return d3.rgb(d.color).darker(1.6); })
.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.node + '(' + 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 = Math.max(0, Math.min(width - d.dx, d3.event.x))) + "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
});
function highlight_node_links(node,i){
console.log('clicked!', node.id);
var remainingNodes=[],
nextNodes=[];
var stroke_opacity = 0;
if( d3.select(this).attr("data-clicked") == "1" ){
d3.select(this).attr("data-clicked","0");
stroke_opacity = 0.1;
}else{
d3.select(this).attr("data-clicked","1");
stroke_opacity = 0.5;
}
// these settings change the path selection
var traverse = [{
linkType : "sourceLinks",
nodeType : "target"
}
// ,{
// linkType : "targetLinks",
// nodeType : "source"
// }
];
// traverse.forEach(function(step){
// console.log('1st step: ', step);
// console.log('1st node: ', node, node.id);
// }
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) {
console.log('remainingNodes: ', remainingNodes);
console.log('1st node: ', node);
node[step.linkType].forEach(function(link) {
nextNodes.push(link[step.nodeType]);
console.log('nextNodes: ', nextNodes);
highlight_link(link.id, stroke_opacity);
});
});
remainingNodes = nextNodes;
}
});
}
function highlight_link(id, opacity){
console.log('highlight_link: ',id , opacity);
let linkStroke = d3.select("#link-"+id);
linkStroke.attr('class', function() {
console.log(this.getAttribute('class').indexOf('_opacitize'));
if (this.getAttribute('class').indexOf('_opacitize') >= 0) {
return this.getAttribute('class').replace(/_opacitize/g, '');
} else {
return String(this.getAttribute('class')).trim() + '_opacitize';
}
})
}
function highlight_links(link,i){
console.log('highlight paths clicked!', this, link, i);
var remainingNodes=[],
nextNodes=[];
highlight_link(link.id, 1);
if(link.target.sourceLinks.filter((obj) => obj.rank === link.rank)) {
highlight_links(link.target.sourceLinks.filter((obj) => obj.rank === link.rank)[0], 0);
return;
} else {
return;
}
}
function highlight_path(id,opacity){
d3.select("#link-"+id).style("stroke-opacity", opacity);
}
</script>
</body>
render() {
var margin = {
top: 20,
right: 1,
bottom: 20,
left: 1
},
width = 1100 - margin.left - margin.right,
height = 700 - margin.top - margin.bottom,
padding = 38,
nodeWidth = 25,
layout = 100,
lowopacity: 0.3,
highopacity = 0.7;
var svg = d3.select("#chart").append("svg");
svg.append("rect")
.attr("x",0)
.attr("y",0)
.attr("width","100%")
.attr("height","100%")
.attr("fill","white");
svg=svg
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.selectAll("g").remove();
var sankey = d3.sankey()
.nodeWidth(nodeWidth)
.nodePadding(padding)
.size([width, height]);
var formatNumber = d3.format(",.0%"),
format = function(d) {
return formatNumber(d);
},
color = d3.scaleLinear(d3.schemeCategory20);
var path = sankey.link();
d3.json("data.json", function(energy) {
let goodNodes = energy.sankey.nodes.filter((node) => {
return !!node.name;
});
console.log('goodnodes: ', goodNodes);
sankey
.nodes(goodNodes)
.links(energy.sankey.links)
.layout(layout);
var link = svg.append("g").selectAll(".link")
.data(energy.sankey.links)
.enter().append("path")
.attr("class", "link")
.attr('id', function(d, i){
console.log(d, i);
d.id = i;
return "link-" + d.id;
})
.attr("d", path)
.on("click", highlight_links)
.style("stroke-width", function(d) { return Math.max(0, d.dy); })
.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(goodNodes)
.enter().append("g")
.attr("class", "node")
.attr('id', function(d, i) {
d.id = i;
return "node-" + d.node;
})
.attr("transform", function(d) {
return "translate(" + d.x + "," + d.y + ")";
})
.on("click", highlight_node_links)
.call(d3.drag()
.subject(function(d) {
return d;
})
.on("start", function() {
this.parentNode.appendChild(this);
})
.on("drag", dragmove));
node.append("rect")
.attr("height", function(d) { return d.dy; })
.attr("width", sankey.nodeWidth())
.style("fill", '#384650')
.style("fill-opacity", ".9")
.style("shape-rendering", "crispEdges")
// .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.node + '(' + 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 = Math.max(0, Math.min(width - d.dx, d3.event.x))) + "," + (
d.y = Math.max(0, Math.min(height - d.dy, d3.event.y))) + ")");
sankey.relayout();
link.attr("d", path);
}
});
}
function highlight_node_links(node,i){
console.log('clicked!', node.id);
var remainingNodes=[],
nextNodes=[];
var stroke_opacity = 0;
if( d3.select(this).attr("data-clicked") == "1" ){
d3.select(this).attr("data-clicked","0");
stroke_opacity = 0.1;
}else{
d3.select(this).attr("data-clicked","1");
stroke_opacity = 0.5;
}
// these settings change the path selection
var traverse = [{
linkType : "sourceLinks",
nodeType : "target"
}
// ,{
// linkType : "targetLinks",
// nodeType : "source"
// }
];
// traverse.forEach(function(step){
// console.log('1st step: ', step);
// console.log('1st node: ', node, node.id);
// }
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) {
console.log('remainingNodes: ', remainingNodes);
console.log('1st node: ', node);
node[step.linkType].forEach(function(link) {
nextNodes.push(link[step.nodeType]);
console.log('nextNodes: ', nextNodes);
highlight_link(link.id, stroke_opacity);
});
});
remainingNodes = nextNodes;
}
});
}
function highlight_link(id, opacity){
console.log('highlight_link: ',id , opacity);
let linkStroke = d3.select("#link-"+id);
linkStroke.attr('class', function() {
console.log(this.getAttribute('class').indexOf('_opacitize'));
if (this.getAttribute('class').indexOf('_opacitize') >= 0) {
return this.getAttribute('class').replace(/_opacitize/g, '');
} else {
return String(this.getAttribute('class')).trim() + '_opacitize';
}
})
}
function highlight_links(link,i){
console.log('highlight paths clicked!', this, link, i);
var remainingNodes=[],
nextNodes=[];
highlight_link(link.id, 1);
if(link.target.sourceLinks.filter((obj) => obj.rank === link.rank)) {
highlight_links(link.target.sourceLinks.filter((obj) => obj.rank === link.rank)[0], 0);
return;
} else {
return;
}
}
function highlight_path(id,opacity){
d3.select("#link-"+id).style("stroke-opacity", opacity);
}
.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: blue;
stroke-opacity: .2;
}
.link_opacitize {
fill: none;
stroke: red;
stroke-opacity: 1
}
.link:hover {
stroke-opacity: .5;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment