Path: blob/master/sandbox/RFinance2014/libraries/widgets/nvd3/js/sankey.js
1433 views
d3.sankey = function() {1var sankey = {},2nodeWidth = 24,3nodePadding = 8,4size = [1, 1],5nodes = [],6links = [];78sankey.nodeWidth = function(_) {9if (!arguments.length) return nodeWidth;10nodeWidth = +_;11return sankey;12};1314sankey.nodePadding = function(_) {15if (!arguments.length) return nodePadding;16nodePadding = +_;17return sankey;18};1920sankey.nodes = function(_) {21if (!arguments.length) return nodes;22nodes = _;23return sankey;24};2526sankey.links = function(_) {27if (!arguments.length) return links;28links = _;29return sankey;30};3132sankey.size = function(_) {33if (!arguments.length) return size;34size = _;35return sankey;36};3738sankey.layout = function(iterations) {39computeNodeLinks();40computeNodeValues();41computeNodeBreadths();42computeNodeDepths(iterations);43computeLinkDepths();44return sankey;45};4647sankey.relayout = function() {48computeLinkDepths();49return sankey;50};5152sankey.link = function() {53var curvature = .5;5455function link(d) {56var x0 = d.source.x + d.source.dx,57x1 = d.target.x,58xi = d3.interpolateNumber(x0, x1),59x2 = xi(curvature),60x3 = xi(1 - curvature),61y0 = d.source.y + d.sy + d.dy / 2,62y1 = d.target.y + d.ty + d.dy / 2;63return "M" + x0 + "," + y064+ "C" + x2 + "," + y065+ " " + x3 + "," + y166+ " " + x1 + "," + y1;67}6869link.curvature = function(_) {70if (!arguments.length) return curvature;71curvature = +_;72return link;73};7475return link;76};7778// Populate the sourceLinks and targetLinks for each node.79// Also, if the source and target are not objects, assume they are indices.80function computeNodeLinks() {81nodes.forEach(function(node) {82node.sourceLinks = [];83node.targetLinks = [];84});85links.forEach(function(link) {86var source = link.source,87target = link.target;88if (typeof source === "number") source = link.source = nodes[link.source];89if (typeof target === "number") target = link.target = nodes[link.target];90source.sourceLinks.push(link);91target.targetLinks.push(link);92});93}9495// Compute the value (size) of each node by summing the associated links.96function computeNodeValues() {97nodes.forEach(function(node) {98node.value = Math.max(99d3.sum(node.sourceLinks, value),100d3.sum(node.targetLinks, value)101);102});103}104105// Iteratively assign the breadth (x-position) for each node.106// Nodes are assigned the maximum breadth of incoming neighbors plus one;107// nodes with no incoming links are assigned breadth zero, while108// nodes with no outgoing links are assigned the maximum breadth.109function computeNodeBreadths() {110var remainingNodes = nodes,111nextNodes,112x = 0;113114while (remainingNodes.length) {115nextNodes = [];116remainingNodes.forEach(function(node) {117node.x = x;118node.dx = nodeWidth;119node.sourceLinks.forEach(function(link) {120nextNodes.push(link.target);121});122});123remainingNodes = nextNodes;124++x;125}126127//128moveSinksRight(x);129scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));130}131132function moveSourcesRight() {133nodes.forEach(function(node) {134if (!node.targetLinks.length) {135node.x = d3.min(node.sourceLinks, function(d) { return d.target.x; }) - 1;136}137});138}139140function moveSinksRight(x) {141nodes.forEach(function(node) {142if (!node.sourceLinks.length) {143node.x = x - 1;144}145});146}147148function scaleNodeBreadths(kx) {149nodes.forEach(function(node) {150node.x *= kx;151});152}153154function computeNodeDepths(iterations) {155var nodesByBreadth = d3.nest()156.key(function(d) { return d.x; })157.sortKeys(d3.ascending)158.entries(nodes)159.map(function(d) { return d.values; });160161//162initializeNodeDepth();163resolveCollisions();164for (var alpha = 1; iterations > 0; --iterations) {165relaxRightToLeft(alpha *= .99);166resolveCollisions();167relaxLeftToRight(alpha);168resolveCollisions();169}170171function initializeNodeDepth() {172var ky = d3.min(nodesByBreadth, function(nodes) {173return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);174});175176nodesByBreadth.forEach(function(nodes) {177nodes.forEach(function(node, i) {178node.y = i;179node.dy = node.value * ky;180});181});182183links.forEach(function(link) {184link.dy = link.value * ky;185});186}187188function relaxLeftToRight(alpha) {189nodesByBreadth.forEach(function(nodes, breadth) {190nodes.forEach(function(node) {191if (node.targetLinks.length) {192var y = d3.sum(node.targetLinks, weightedSource) / d3.sum(node.targetLinks, value);193node.y += (y - center(node)) * alpha;194}195});196});197198function weightedSource(link) {199return center(link.source) * link.value;200}201}202203function relaxRightToLeft(alpha) {204nodesByBreadth.slice().reverse().forEach(function(nodes) {205nodes.forEach(function(node) {206if (node.sourceLinks.length) {207var y = d3.sum(node.sourceLinks, weightedTarget) / d3.sum(node.sourceLinks, value);208node.y += (y - center(node)) * alpha;209}210});211});212213function weightedTarget(link) {214return center(link.target) * link.value;215}216}217218function resolveCollisions() {219nodesByBreadth.forEach(function(nodes) {220var node,221dy,222y0 = 0,223n = nodes.length,224i;225226// Push any overlapping nodes down.227nodes.sort(ascendingDepth);228for (i = 0; i < n; ++i) {229node = nodes[i];230dy = y0 - node.y;231if (dy > 0) node.y += dy;232y0 = node.y + node.dy + nodePadding;233}234235// If the bottommost node goes outside the bounds, push it back up.236dy = y0 - nodePadding - size[1];237if (dy > 0) {238y0 = node.y -= dy;239240// Push any overlapping nodes back up.241for (i = n - 2; i >= 0; --i) {242node = nodes[i];243dy = node.y + node.dy + nodePadding - y0;244if (dy > 0) node.y -= dy;245y0 = node.y;246}247}248});249}250251function ascendingDepth(a, b) {252return a.y - b.y;253}254}255256function computeLinkDepths() {257nodes.forEach(function(node) {258node.sourceLinks.sort(ascendingTargetDepth);259node.targetLinks.sort(ascendingSourceDepth);260});261nodes.forEach(function(node) {262var sy = 0, ty = 0;263node.sourceLinks.forEach(function(link) {264link.sy = sy;265sy += link.dy;266});267node.targetLinks.forEach(function(link) {268link.ty = ty;269ty += link.dy;270});271});272273function ascendingSourceDepth(a, b) {274return a.source.y - b.source.y;275}276277function ascendingTargetDepth(a, b) {278return a.target.y - b.target.y;279}280}281282function center(node) {283return node.y + node.dy / 2;284}285286function value(link) {287return link.value;288}289290return sankey;291};292293294