Skip to content
This repository has been archived by the owner on Nov 7, 2018. It is now read-only.

More functionality for sankey.js #120

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 118 additions & 28 deletions sankey/sankey.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,10 @@ d3.sankey = function() {
sankey.layout = function(iterations) {
computeNodeLinks();
computeNodeValues();
computeNodeBreadths();
computeNodeDepths(iterations);
// use posX /posY if specified in node definition.
// posX /posY needs to be specified for all nodes, but is only checked in first).
if ("posX" in nodes[1]) {setNodeBreadths();} else {computeNodeBreadths();};
computeNodeDepths(iterations);
computeLinkDepths();
return sankey;
};
Expand All @@ -53,19 +55,69 @@ d3.sankey = function() {
sankey.link = function() {
var curvature = .5;

// Determine the link x,y values.
// Links are made up of 2 small straight elements at beginning and end
// with a curved element in between.
// (x0,y0), (x5,y5) are the start/end points of the links
// (x1,y1), (x4,y4) change between straight and curved elements
// (x2,y2), (x3,y3) curve helper points
// Nodes with the attribute vertical have vertical incoming links,
// either from the top or bottom, depending on deltaY.
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),
if (d.target.vertical) {
var x0 = d.source.x + d.source.dx,
x1 = x0 + d.source.dx,
x5 = d.target.x + d.dy/2,
x4 = x5,
xi = d3.interpolateNumber(x1, x4),
x2 = Math.max(xi(curvature), x1+d.dy),
x3 = x5,
y0 = d.source.y + d.sy + d.dy / 2,
y1 = d.target.y + d.ty + d.dy / 2;
y1 = y0,
y5 = d.target.y + d.ty + d.dy / 2,
y4,
deltaY = y5-y0,
y2 = y0;
if (deltaY > 0) {
y5 = d.target.y;
y4 = y5 - d.target.dx;
}
else {
y5 = d.target.y + d.source.dx;
y4 = y5 + d.target.dx;
};
var yi = d3.interpolateNumber(y1, y4);
if (deltaY > 0) {
y3 = Math.min(yi(curvature), y4-2*d.dy);
}
else {
y3 = Math.max(yi(curvature), y4+2*d.dy);
};
}

else {
var x0 = d.source.x + d.source.dx,
x1 = x0 + d.source.dx,
x5 = d.target.x,
x4 = x5 - d.target.dx,
xi = d3.interpolateNumber(x1, x4),
x2 = Math.max(xi(curvature), x1+2*d.dy),
x3 = Math.min(xi(curvature), x4-2*d.dy),
y0 = d.source.y + d.sy + d.dy / 2,
y1 = y0,
y5 = d.target.y + d.ty + d.dy / 2,
y4 = y5,
y2 = y0,
y3 = y5;
}

return "M" + x0 + "," + y0
+ "C" + x2 + "," + y0
+ " " + x3 + "," + y1
+ " " + x1 + "," + y1;
}
+ "L" + x1 + "," + y1
+ "C" + x2 + "," + y2
+ " " + x3 + "," + y3
+ " " + x4 + "," + y4
+ "L" + x5 + "," + y5;
};

link.curvature = function(_) {
if (!arguments.length) return curvature;
Expand Down Expand Up @@ -103,10 +155,22 @@ d3.sankey = function() {
});
}

// Set breadth (x-position) for each node, if given in data.
function setNodeBreadths() {
var posXlist = []
nodes.forEach(function(node) {
node.x = node.posX;
node.dx = nodeWidth;
posXlist.push(node.posX);
});
scaleNodeBreadths((size[0] - nodeWidth) / Math.max.apply(null, posXlist));
};

// 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.
// nodes with no outgoing links are assigned the maximum breadth, except
// nodes with the vertical attribute, which are moved to the incoming neighbor plus 0.5
function computeNodeBreadths() {
var remainingNodes = nodes,
nextNodes,
Expand Down Expand Up @@ -143,7 +207,8 @@ d3.sankey = function() {
function moveSinksRight(x) {
nodes.forEach(function(node) {
if (!node.sourceLinks.length) {
node.x = x - 1;
if (node.vertical) { node.x = node.x -0.5;}
else { node.x = x - 1; }
}
});
}
Expand All @@ -153,32 +218,49 @@ d3.sankey = function() {
node.x *= kx;
});
}


// Iteratively assign the depth (y-position) for each node
// Nodes with the vertical attribute are moved to the bottom
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);

if (!("posY" in nodes[1])) {
resolveCollisions();
}

for (var alpha = 1; iterations > 0; --iterations) {
relaxRightToLeft(alpha *= .99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
moveVerticalDown();
};

function initializeNodeDepth() {
var ky = d3.min(nodesByBreadth, function(nodes) {
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
var posYlist = [ ],
maxPosY,
ky = d3.min(nodesByBreadth, function(nodes) {
return (size[1] - (nodes.length - 1) * nodePadding) / d3.sum(nodes, value);
});

nodes.forEach(function(node) {
posYlist.push(node.posY)
});


maxPosY = Math.max(Math.max.apply(null, posYlist), 1);
nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node, i) {
node.y = i;
if ("posY" in node) {
node.y = node.posY / maxPosY * size[1];
}
else {
node.y = i;
};
node.dy = node.value * ky;
});
});
Expand Down Expand Up @@ -254,6 +336,14 @@ d3.sankey = function() {
function ascendingDepth(a, b) {
return a.y - b.y;
}

function moveVerticalDown() {
nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node, i) {
if ( node.vertical ) { node.y = size[1] - node.dy; };
});
});
}
}

function computeLinkDepths() {
Expand Down