Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
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
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,6 +187,10 @@ If *iterations* is specified, sets the number of relaxation iterations when [gen

If *circularLinkGap* is specified, sets the gap (in pixels) between circular links that travel next to each other. If *circularLinkGap*, it defaults to 2.

<a name="sankey_circularNodeSort" href="#sankey_circularNodeSort">#</a> <i>sankey</i>.<b>nodeSort</b>([<i>nodeSort</i>]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L151 "Source")
If *nodeSort* is specified, sets the node sort method and returns this Sankey generator. If *sort* is not specified, returns the current node sort method, which defaults to *undefined*, indicating that vertical order of nodes within each column will be determined automatically by the layout. If *sort* is null, the order is fixed by the input. Otherwise, the specified *sort* function determines the order; the function is passed two nodes, and must return a value less than 0 if the first node should be above the second, and a value greater than 0 if the second node should be above the first, or 0 if the order is not specified.
Sorting is only applied to nodes that both part or not part of a circular loop. When the result of a nodeSort is 0 then nodes are sorted by the top (y0) of the node

### Alignments

See [*sankey*.nodeAlign](#sankey_nodeAlign).
Expand Down
149 changes: 89 additions & 60 deletions dist/d3-sankey-circular.es.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,31 +43,6 @@ var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol

/// https://github.com/tomshanley/d3-sankeyCircular-circular

// sort links' breadth (ie top to bottom in a column), based on their source nodes' breadths
function ascendingSourceBreadth(a, b) {
return ascendingBreadth(a.source, b.source) || a.index - b.index;
}

// sort links' breadth (ie top to bottom in a column), based on their target nodes' breadths
function ascendingTargetBreadth(a, b) {
return ascendingBreadth(a.target, b.target) || a.index - b.index;
}

// sort nodes' breadth (ie top to bottom in a column)
// if both nodes have circular links, or both don't have circular links, then sort by the top (y0) of the node
// else push nodes that have top circular links to the top, and nodes that have bottom circular links to the bottom
function ascendingBreadth(a, b) {
if (a.partOfCycle === b.partOfCycle) {
return a.y0 - b.y0;
} else {
if (a.circularLinkType === 'top' || b.circularLinkType === 'bottom') {
return -1;
} else {
return 1;
}
}
}

// return the value of a node or link
function value(d) {
return d.value;
Expand Down Expand Up @@ -139,7 +114,8 @@ function sankeyCircular () {
iterations = 32,
circularLinkGap = 2,
paddingRatio,
sortNodes = null;
sortNodes = null,
nodeSort = null;

function sankeyCircular() {
var graph = {
Expand Down Expand Up @@ -186,7 +162,10 @@ function sankeyCircular () {
sortTargetLinks(graph, y1, id);
}

// 8.1 Adjust node and link positions back to fill height of chart area if compressed
// 8.1 Fix nodes overlapping after sortNodes
resolveNodesOverlap(graph, y0, py);

// 8.2 Adjust node and link positions back to fill height of chart area if compressed
fillHeight(graph, y0, y1);

// 9. Calculate visually appealling path for the circular paths, and create the "d" string
Expand All @@ -195,6 +174,31 @@ function sankeyCircular () {
return graph;
} // end of sankeyCircular function

// sort links' breadth (ie top to bottom in a column), based on their source nodes' breadths
function ascendingSourceBreadth(a, b) {
return ascendingBreadth(a.source, b.source) || a.index - b.index;
}

// sort links' breadth (ie top to bottom in a column), based on their target nodes' breadths
function ascendingTargetBreadth(a, b) {
return ascendingBreadth(a.target, b.target) || a.index - b.index;
}

// sort nodes' breadth (ie top to bottom in a column)
// if both nodes have circular links, or both don't have circular links, then sort by the top (y0) of the node
// else push nodes that have top circular links to the top, and nodes that have bottom circular links to the bottom
function ascendingBreadth(a, b) {
if (a.partOfCycle === b.partOfCycle) {
if (nodeSort) return nodeSort(a, b) || a.y0 - b.y0;
return a.y0 - b.y0;
} else {
if (a.circularLinkType === 'top' || b.circularLinkType === 'bottom') {
return -1;
} else {
return 1;
}
}
}

// Set the sankeyCircular parameters
// nodeID, nodeAlign, nodeWidth, nodePadding, nodes, links, size, extent, iterations, nodePaddingRatio, circularLinkGap
Expand Down Expand Up @@ -246,6 +250,10 @@ function sankeyCircular () {
return arguments.length ? (sortNodes = _, sankeyCircular) : sortNodes;
};

sankeyCircular.nodeSort = function (_) {
return arguments.length ? (nodeSort = _, sankeyCircular) : nodeSort;
};

sankeyCircular.update = function (graph) {
// 5. Calculate the nodes' depth based on the incoming and outgoing links
// Sets the nodes':
Expand Down Expand Up @@ -440,7 +448,7 @@ function sankeyCircular () {

// assign column numbers, and get max value
graph.nodes.forEach(function (node) {
node.column = Math.floor(align.call(null, node, x));
node.column = sortNodes !== null ? node[sortNodes] : Math.floor(align.call(null, node, x));
});
}

Expand Down Expand Up @@ -529,44 +537,28 @@ function sankeyCircular () {

// For each node in each column, check the node's vertical position in relation to its targets and sources vertical position
// and shift up/down to be closer to the vertical middle of those targets and sources
function relaxLeftAndRight(alpha, id) {
var columnsLength = columns.length;

function relaxLeftAndRight(alpha) {
columns.forEach(function (nodes) {
var n = nodes.length;
var depth = nodes[0].depth;

nodes.forEach(function (node) {
// check the node is not an orphan
var nodeHeight;
if (node.sourceLinks.length || node.targetLinks.length) {
if (node.partOfCycle && numberOfNonSelfLinkingCycles(node, id) > 0) ; else if (depth == 0 && n == 1) {
nodeHeight = node.y1 - node.y0;

node.y0 = y1 / 2 - nodeHeight / 2;
node.y1 = y1 / 2 + nodeHeight / 2;
} else if (depth == columnsLength - 1 && n == 1) {
nodeHeight = node.y1 - node.y0;

node.y0 = y1 / 2 - nodeHeight / 2;
node.y1 = y1 / 2 + nodeHeight / 2;
nodeHeight = node.y1 - node.y0;
node.y0 = y1 / 2 - nodeHeight / 2;
node.y1 = y1 / 2 + nodeHeight / 2;
var avg = 0;

var avgTargetY = mean(node.sourceLinks, linkTargetCenter);
var avgSourceY = mean(node.targetLinks, linkSourceCenter);
if (avgTargetY && avgSourceY) {
avg = (avgTargetY + avgSourceY) / 2;
} else {
var avg = 0;

var avgTargetY = mean(node.sourceLinks, linkTargetCenter);
var avgSourceY = mean(node.targetLinks, linkSourceCenter);

if (avgTargetY && avgSourceY) {
avg = (avgTargetY + avgSourceY) / 2;
} else {
avg = avgTargetY || avgSourceY;
}

var dy = (avg - nodeCenter(node)) * alpha;
// positive if it node needs to move down
node.y0 += dy;
node.y1 += dy;
avg = avgTargetY || avgSourceY;
}
var dy = (avg - nodeCenter(node)) * alpha;
// positive if it node needs to move down
node.y0 += dy;
node.y1 += dy;
}
});
});
Expand Down Expand Up @@ -1487,4 +1479,41 @@ function fillHeight(graph, y0, y1) {
}
}

export { sankeyCircular, center as sankeyCenter, left as sankeyLeft, right as sankeyRight, justify as sankeyJustify };

function resolveNodesOverlap(graph, y0, py) {
var columns = nest().key(function (d) {
return d.column;
}).sortKeys(ascending).entries(graph.nodes).map(function (d) {
return d.values;
});

columns.forEach(function (nodes) {
var node,
dy,
y = y0,
n = nodes.length,
i;
// Push any overlapping nodes down.
nodes.sort(ascendingBreadth);

for (i = 0; i < n; ++i) {
node = nodes[i];
dy = y - node.y0;

if (dy > 0) {
node.y0 += dy;
node.y1 += dy;
node.targetLinks.forEach(function (l) {
l.y1 = l.y1 + dy;
});
node.sourceLinks.forEach(function (l) {
l.y0 = l.y0 + dy;
});
}
y = node.y1 + py;
}
});
}

export { sankeyCircular, addCircularPathData, center as sankeyCenter, left as sankeyLeft, right as sankeyRight, justify as sankeyJustify };

Loading