-
-
Save clkao/6551972 to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<title>Cyclic Sankeys</title> | |
<style type="text/css"> | |
#chart { | |
height: 500px; | |
} | |
.node rect { | |
cursor: move; | |
fill-opacity: .9; | |
shape-rendering: crispEdges; | |
} | |
.node text { | |
pointer-events: none; | |
text-shadow: 0 1px 0 #fff; | |
} | |
.linkish { | |
fill: #111; | |
stroke: #000; | |
opacity: .3; | |
} | |
.link:hover { | |
opacity: .5; | |
} | |
body { | |
font-size: 12px; | |
} | |
</style> | |
<script src="http://d3js.org/d3.v3.min.js"></script> | |
<script src="sankey.js"></script> | |
</head> | |
<body> | |
<div id="loop"></div> | |
<script> | |
var _fillColors = d3.scale.category20(); | |
function createSankey(container, data) { | |
var nodes = data.nodes; | |
var links = data.links; | |
var width = 1200; | |
var height = 600; | |
var sankey = d3.sankey() | |
.size([width - 200, height - 200]) | |
.nodeWidth(15) | |
.nodePadding(10) | |
.nodes(nodes) | |
.links(links) | |
.layout(32); | |
var svg = d3.select(container).append("svg") | |
.attr("width", width) | |
.attr("height", height) | |
.append("g") | |
.attr('transform', 'translate(' + 50 + ',' + 100 + ')'); | |
var path = sankey.reversibleLink(); | |
var linkEnter = svg.append("g").selectAll(".link") | |
.data(links) | |
.enter().append("g") | |
.attr("class", "link") | |
.attr("opacity", .2) | |
.sort(function(a, b) { return b.dy - a.dy; }); | |
var path0 = linkEnter.append("path") | |
.attr("class", "link0") | |
.attr("fill", "none") | |
.attr("d", path(0)) | |
.attr("class", "linkish"); | |
var path1 = linkEnter.append("path") | |
.attr("class", "link1") | |
.attr("fill", "none") | |
.attr("d", path(1)) | |
.attr("class", "linkish"); | |
var path2 = linkEnter.append("path") | |
.attr("class", "link2") | |
.attr("fill", "none") | |
.attr("d", path(2)) | |
.attr("class", "linkish"); | |
linkEnter.append("title") | |
.text(function(d) { return d.source.name + " -> " + d.target.name; }); | |
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.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) { | |
return _fillColors(d.name); | |
}) | |
.style("stroke", function(d) { | |
return _fillColors(d.name); | |
}) | |
.on("mouseover", function(d){ | |
svg.selectAll(".link") | |
.filter(function(l){ | |
return l.source == d || l.target == d; | |
}) | |
.transition() | |
.style('opacity', .7); | |
}) | |
.on("mouseout", function(d){ | |
svg.selectAll(".link") | |
.filter(function(l){ | |
return l.source == d || l.target == d; | |
}) | |
.transition() | |
.style('opacity', .2); | |
}) | |
.append("title") | |
.text(function(d) { return d.name; }); | |
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 = 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(); | |
path1.attr('d', path(1)); | |
path0.attr('d', path(0)); | |
path2.attr('d', path(2)); | |
} | |
} | |
d3.json("ly-flow.json", function(data) { | |
createSankey(document.getElementById('loop'), data); | |
}); | |
</script> | |
</body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{"nodes":[{"name":"委員提案"},{"name":"退回程序"},{"name":"一讀"},{"name":"未處理"},{"name":"三讀"},{"name":"外交及國防"},{"name":"司法及法制"},{"name":"二讀(廣泛討論)"},{"name":"教育"},{"name":"社會福利及衛生環境"},{"name":"財政"},{"name":"交通"},{"name":"經濟"},{"name":"內政"},{"name":"司法及法制,經濟"},{"name":"教育,經濟"},{"name":"司法及法制,外交及國防"},{"name":"司法及法制,社會福利及衛生環境"},{"name":"司法及法制,教育"},{"name":"教育,社會福利及衛生環境"},{"name":"內政,司法及法制"},{"name":"司法及法制,交通"},{"name":"教育,財政"},{"name":"復議(另定期處理)"},{"name":"司法及法制,內政"},{"name":"內政,外交及國防"},{"name":"交通,內政"},{"name":"內政,經濟"},{"name":"經濟,財政"},{"name":"經濟,外交及國防"},{"name":"黨團協商"},{"name":"外交及國防,司法及法制"},{"name":"內政,社會福利及衛生環境"},{"name":"教育,內政"},{"name":"經濟,社會福利及衛生環境"},{"name":"交通,經濟,司法及法制"},{"name":"經濟,內政,財政"},{"name":"經濟,內政"},{"name":"經濟,司法及法制"},{"name":"政府提案"},{"name":"二讀(逐條討論)"},{"name":"公布"},{"name":"復議"},{"name":"覆議(全院委員會審查)"},{"name":"經濟,教育"},{"name":"覆議(交付審查)"},{"name":"覆議(不維持原決議)"}],"links":[{"source":0,"target":1,"value":78},{"source":0,"target":2,"value":2171},{"source":0,"target":3,"value":9},{"source":0,"target":4,"value":2},{"source":1,"target":2,"value":147},{"source":1,"target":5,"value":1},{"source":1,"target":3,"value":44},{"source":1,"target":6,"value":2},{"source":1,"target":7,"value":1},{"source":2,"target":3,"value":1350},{"source":2,"target":8,"value":63},{"source":2,"target":6,"value":194},{"source":2,"target":9,"value":264},{"source":2,"target":10,"value":93},{"source":2,"target":11,"value":81},{"source":2,"target":12,"value":72},{"source":2,"target":13,"value":121},{"source":2,"target":14,"value":15},{"source":2,"target":15,"value":4},{"source":2,"target":16,"value":15},{"source":2,"target":17,"value":25},{"source":2,"target":4,"value":4},{"source":2,"target":18,"value":29},{"source":2,"target":19,"value":6},{"source":2,"target":7,"value":13},{"source":2,"target":20,"value":14},{"source":2,"target":21,"value":2},{"source":2,"target":22,"value":2},{"source":2,"target":23,"value":20},{"source":2,"target":24,"value":11},{"source":2,"target":5,"value":15},{"source":2,"target":25,"value":5},{"source":2,"target":26,"value":1},{"source":2,"target":27,"value":1},{"source":2,"target":28,"value":2},{"source":2,"target":29,"value":3},{"source":2,"target":30,"value":1},{"source":2,"target":31,"value":1},{"source":2,"target":32,"value":1},{"source":2,"target":33,"value":2},{"source":2,"target":34,"value":2},{"source":2,"target":1,"value":1},{"source":2,"target":35,"value":1},{"source":2,"target":36,"value":2},{"source":2,"target":37,"value":1},{"source":2,"target":38,"value":1},{"source":39,"target":1,"value":115},{"source":39,"target":2,"value":113},{"source":39,"target":4,"value":1},{"source":39,"target":3,"value":1},{"source":8,"target":7,"value":7},{"source":8,"target":3,"value":44},{"source":8,"target":30,"value":4},{"source":8,"target":4,"value":8},{"source":7,"target":3,"value":100},{"source":7,"target":4,"value":78},{"source":7,"target":30,"value":60},{"source":6,"target":30,"value":6},{"source":6,"target":4,"value":43},{"source":6,"target":3,"value":112},{"source":6,"target":7,"value":35},{"source":30,"target":3,"value":12},{"source":30,"target":4,"value":57},{"source":30,"target":7,"value":20},{"source":9,"target":3,"value":127},{"source":9,"target":4,"value":87},{"source":9,"target":30,"value":12},{"source":9,"target":7,"value":35},{"source":9,"target":40,"value":3},{"source":4,"target":41,"value":391},{"source":4,"target":42,"value":21},{"source":4,"target":23,"value":2},{"source":4,"target":7,"value":2},{"source":4,"target":2,"value":2},{"source":4,"target":43,"value":1},{"source":10,"target":4,"value":31},{"source":10,"target":3,"value":31},{"source":10,"target":30,"value":1},{"source":10,"target":7,"value":23},{"source":10,"target":40,"value":7},{"source":11,"target":7,"value":40},{"source":11,"target":4,"value":30},{"source":11,"target":40,"value":1},{"source":11,"target":3,"value":10},{"source":12,"target":4,"value":19},{"source":12,"target":7,"value":17},{"source":12,"target":3,"value":35},{"source":12,"target":30,"value":1},{"source":13,"target":3,"value":103},{"source":13,"target":40,"value":3},{"source":13,"target":4,"value":11},{"source":13,"target":7,"value":5},{"source":5,"target":1,"value":1},{"source":5,"target":3,"value":9},{"source":5,"target":4,"value":4},{"source":5,"target":7,"value":3},{"source":14,"target":3,"value":14},{"source":14,"target":30,"value":1},{"source":15,"target":3,"value":5},{"source":15,"target":44,"value":1},{"source":42,"target":4,"value":21},{"source":42,"target":24,"value":2},{"source":16,"target":4,"value":12},{"source":16,"target":7,"value":2},{"source":16,"target":3,"value":1},{"source":17,"target":7,"value":10},{"source":17,"target":3,"value":14},{"source":17,"target":4,"value":1},{"source":18,"target":7,"value":11},{"source":18,"target":3,"value":18},{"source":19,"target":4,"value":6},{"source":23,"target":3,"value":12},{"source":23,"target":42,"value":2},{"source":23,"target":2,"value":5},{"source":23,"target":5,"value":1},{"source":23,"target":13,"value":1},{"source":23,"target":15,"value":1},{"source":20,"target":25,"value":9},{"source":20,"target":3,"value":5},{"source":25,"target":3,"value":14},{"source":21,"target":7,"value":2},{"source":22,"target":7,"value":2},{"source":40,"target":3,"value":14},{"source":24,"target":30,"value":3},{"source":24,"target":7,"value":9},{"source":24,"target":3,"value":1},{"source":26,"target":3,"value":1},{"source":27,"target":3,"value":1},{"source":28,"target":3,"value":2},{"source":29,"target":3,"value":3},{"source":31,"target":4,"value":1},{"source":32,"target":3,"value":1},{"source":33,"target":4,"value":2},{"source":34,"target":3,"value":1},{"source":34,"target":4,"value":1},{"source":35,"target":3,"value":1},{"source":36,"target":3,"value":2},{"source":37,"target":3,"value":1},{"source":44,"target":15,"value":1},{"source":38,"target":7,"value":1},{"source":43,"target":45,"value":1},{"source":43,"target":46,"value":1},{"source":45,"target":43,"value":1},{"source":46,"target":45,"value":1}]} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
d3.sankey = function() { | |
var sankey = {}, | |
nodeWidth = 24, | |
nodePadding = 8, | |
size = [1, 1], | |
nodes = [], | |
links = [], | |
components = []; | |
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(); | |
computeNodeStructure(); | |
computeNodeBreadths(); | |
computeNodeDepths(iterations); | |
computeLinkDepths(); | |
return sankey; | |
}; | |
sankey.relayout = function() { | |
computeLinkDepths(); | |
return sankey; | |
}; | |
// A more involved path generator that requires 3 elements to render -- | |
// It draws a starting element, intermediate and end element that are useful | |
// while drawing reverse links to get an appropriate fill. | |
// | |
// Each link is now an area and not a basic spline and no longer guarantees | |
// fixed width throughout. | |
// | |
// Sample usage: | |
// | |
// linkNodes = this._svg.append("g").selectAll(".link") | |
// .data(this.links) | |
// .enter().append("g") | |
// .attr("fill", "none") | |
// .attr("class", ".link") | |
// .sort(function(a, b) { return b.dy - a.dy; }); | |
// | |
// linkNodePieces = []; | |
// for (var i = 0; i < 3; i++) { | |
// linkNodePieces[i] = linkNodes.append("path") | |
// .attr("class", ".linkPiece") | |
// .attr("d", path(i)) | |
// .attr("fill", ...) | |
// } | |
sankey.reversibleLink = function() { | |
var curvature = .5; | |
// Used when source is behind target, the first and last paths are simple | |
// lines at the start and end node while the second path is the spline | |
function forwardLink(part, 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, | |
y1 = d.target.y + d.ty, | |
y2 = d.source.y + d.sy + d.dy, | |
y3 = d.target.y + d.ty + d.dy; | |
switch (part) { | |
case 0: | |
return "M" + x0 + "," + y0 + "L" + x0 + "," + (y0 + d.dy); | |
case 1: | |
return "M" + x0 + "," + y0 | |
+ "C" + x2 + "," + y0 + " " + x3 + "," + y1 + " " + x1 + "," + y1 | |
+ "L" + x1 + "," + y3 | |
+ "C" + x3 + "," + y3 + " " + x2 + "," + y2 + " " + x0 + "," + y2 | |
+ "Z"; | |
case 2: | |
return "M" + x1 + "," + y1 + "L" + x1 + "," + (y1 + d.dy); | |
} | |
} | |
// Used for self loops and when the source is actually in front of the | |
// target; the first element is a turning path from the source to the | |
// destination, the second element connects the two twists and the last | |
// twists into the target element. | |
// | |
// | |
// /--Target | |
// \----------------------\ | |
// Source--/ | |
// | |
function backwardLink(part, d) { | |
var curveExtension = 30; | |
var curveDepth = 15; | |
function getDir(d) { | |
return d.source.y + d.sy > d.target.y + d.ty ? -1 : 1; | |
} | |
function p(x, y) { | |
return x + "," + y + " "; | |
} | |
var dt = getDir(d) * curveDepth, | |
x0 = d.source.x + d.source.dx, | |
y0 = d.source.y + d.sy, | |
x1 = d.target.x, | |
y1 = d.target.y + d.ty; | |
switch (part) { | |
case 0: | |
return "M" + p(x0, y0) + | |
"C" + p(x0, y0) + | |
p(x0 + curveExtension, y0) + | |
p(x0 + curveExtension, y0 + dt) + | |
"L" + p(x0 + curveExtension, y0 + dt + d.dy) + | |
"C" + p(x0 + curveExtension, y0 + d.dy) + | |
p(x0, y0 + d.dy) + | |
p(x0, y0 + d.dy) + | |
"Z"; | |
case 1: | |
return "M" + p(x0 + curveExtension, y0 + dt) + | |
"C" + p(x0 + curveExtension, y0 + 3 * dt) + | |
p(x1 - curveExtension, y1 - 3 * dt) + | |
p(x1 - curveExtension, y1 - dt) + | |
"L" + p(x1 - curveExtension, y1 - dt + d.dy) + | |
"C" + p(x1 - curveExtension, y1 - 3 * dt + d.dy) + | |
p(x0 + curveExtension, y0 + 3 * dt + d.dy) + | |
p(x0 + curveExtension, y0 + dt + d.dy) + | |
"Z"; | |
case 2: | |
return "M" + p(x1 - curveExtension, y1 - dt) + | |
"C" + p(x1 - curveExtension, y1) + | |
p(x1, y1) + | |
p(x1, y1) + | |
"L" + p(x1, y1 + d.dy) + | |
"C" + p(x1, y1 + d.dy) + | |
p(x1 - curveExtension, y1 + d.dy) + | |
p(x1 - curveExtension, y1 + d.dy - dt) + | |
"Z"; | |
} | |
} | |
return function(part) { | |
return function(d) { | |
if (d.source.x < d.target.x) { | |
return forwardLink(part, d); | |
} else { | |
return backwardLink(part, d); | |
} | |
} | |
} | |
}; | |
// The standard link path using a constant width spline that needs a | |
// single path element. | |
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) | |
); | |
}); | |
} | |
// Take the list of nodes and create a DAG of supervertices, each consisting | |
// of a strongly connected component of the graph | |
// | |
// Based off: | |
// http://en.wikipedia.org/wiki/Tarjan's_strongly_connected_components_algorithm | |
function computeNodeStructure() { | |
var nodeStack = [], | |
index = 0; | |
nodes.forEach(function(node) { | |
if (!node.index) { | |
connect(node); | |
} | |
}); | |
function connect(node) { | |
node.index = index++; | |
node.lowIndex = node.index; | |
node.onStack = true; | |
nodeStack.push(node); | |
if (node.sourceLinks) { | |
node.sourceLinks.forEach(function(sourceLink){ | |
var target = sourceLink.target; | |
if (!target.hasOwnProperty('index')) { | |
connect(target); | |
node.lowIndex = Math.min(node.lowIndex, target.lowIndex); | |
} else if (target.onStack) { | |
node.lowIndex = Math.min(node.lowIndex, target.index); | |
} | |
}); | |
if (node.lowIndex === node.index) { | |
var component = [], currentNode; | |
do { | |
currentNode = nodeStack.pop() | |
currentNode.onStack = false; | |
component.push(currentNode); | |
} while (currentNode != node); | |
components.push({ | |
root: node, | |
scc: component | |
}); | |
} | |
} | |
} | |
components.forEach(function(component, i){ | |
component.index = i; | |
component.scc.forEach(function(node) { | |
node.component = i; | |
}); | |
}); | |
} | |
// Assign the breadth (x-position) for each strongly connected component, | |
// followed by assigning breadth within the component. | |
function computeNodeBreadths() { | |
layerComponents(); | |
components.forEach(function(component, i){ | |
bfs(component.root, function(node){ | |
var result = node.sourceLinks | |
.filter(function(sourceLink){ | |
return sourceLink.target.component == i; | |
}) | |
.map(function(sourceLink){ | |
return sourceLink.target; | |
}); | |
return result; | |
}); | |
}); | |
var max = 0; | |
var componentsByBreadth = d3.nest() | |
.key(function(d) { return d.x; }) | |
.sortKeys(d3.ascending) | |
.entries(components) | |
.map(function(d) { return d.values; }); | |
var max = -1, nextMax = -1; | |
componentsByBreadth.forEach(function(c){ | |
c.forEach(function(component){ | |
component.x = max + 1; | |
component.scc.forEach(function(node){ | |
node.x = component.x + node.x; | |
nextMax = Math.max(nextMax, node.x); | |
}); | |
}); | |
max = nextMax; | |
}); | |
nodes | |
.filter(function(node) { | |
var outLinks = node.sourceLinks.filter(function(link){ return link.source.name != link.target.name; }); | |
return (outLinks.length == 0); | |
}) | |
.forEach(function(node) { node.x = max; }) | |
scaleNodeBreadths((size[0] - nodeWidth) / Math.max(max, 1)); | |
function flatten(a) { | |
return [].concat.apply([], a); | |
} | |
function layerComponents() { | |
var remainingComponents = components, | |
nextComponents, | |
visitedIndex, | |
x = 0; | |
while (remainingComponents.length) { | |
nextComponents = []; | |
visitedIndex = {}; | |
remainingComponents.forEach(function(component) { | |
component.x = x; | |
component.scc.forEach(function(n) { | |
n.sourceLinks.forEach(function(l) { | |
if (!visitedIndex.hasOwnProperty(l.target.component) && | |
l.target.component != component.index) { | |
nextComponents.push(components[l.target.component]); | |
visitedIndex[l.target.component] = true; | |
} | |
}) | |
}); | |
}); | |
remainingComponents = nextComponents; | |
++x; | |
} | |
} | |
function bfs(node, extractTargets) { | |
var queue = [node], currentCount = 1, nextCount = 0; | |
var x = 0; | |
while(currentCount > 0) { | |
var currentNode = queue.shift(); | |
currentCount--; | |
if (!currentNode.hasOwnProperty('x')) { | |
currentNode.x = x; | |
currentNode.dx = nodeWidth; | |
var targets = extractTargets(currentNode); | |
queue = queue.concat(targets); | |
nextCount += targets.length; | |
} | |
if (currentCount == 0) { // level change | |
x++; | |
currentCount = nextCount; | |
nextCount = 0; | |
} | |
} | |
} | |
} | |
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