Skip to content
Merged
Show file tree
Hide file tree
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
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

A fork of the d3-sankey library (https://github.com/d3/d3-sankey) to allow circular links (ie cyclic graphs, like in [this example](https://bl.ocks.org/tomshanley/6f3fcf68c0dbc401548733dd0c64e3c3)).

The library contains a portion of code from Colin Fergus' bl.ock https://gist.github.com/cfergus/3956043 to detect circular links.
The library contains a portion of code from Colin Fergus' bl.ock https://gist.github.com/cfergus/3956043 to detect circular links.

## Install

Expand All @@ -29,6 +29,15 @@ Computes the node and link positions for the given *arguments*, returning a *gra
* *graph*.nodes - the array of [nodes](#sankey_nodes)
* *graph*.links - the array of [links](#sankey_links)

<a href="#sankey_update" name="sankey_update">#</a> <i>sankey</i>.<b>update</b>(<i>graph</i>)

Recomputes the specified *graph*’s links’ positions, updating the following properties of each *link*:

* *link*.y0 - the link’s vertical starting position (at source node)
* *link*.y1 - the link’s vertical end position (at target node)

This method is intended to be called after computing the initial [Sankey layout](#_sankey), for example when the diagram is repositioned interactively.

<a name="sankey_nodes" href="#sankey_nodes">#</a> <i>sankey</i>.<b>nodes</b>([<i>nodes</i>]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L93 "Source")

If *nodes* is specified, sets the Sankey generator’s nodes accessor to the specified function or array and returns this Sankey generator. If *nodes* is not specified, returns the current nodes accessor, which defaults to:
Expand Down Expand Up @@ -181,19 +190,19 @@ If *circularLinkGap* is specified, sets the gap (in pixels) between circular lin

See [*sankey*.nodeAlign](#sankey_nodeAlign).

<a name="sankeyLeft" href="#sankeyLeft">#</a> d3.<b>sankeyLeft</b>(<i>node</i>, <i>n</i>)
<a name="sankeyLeft" href="#sankeyLeft">#</a> d3.<b>sankeyLeft</b>(<i>node</i>, <i>n</i>)

Returns *node*.depth.

<a name="sankeyRight" href="#sankeyRight">#</a> d3.<b>sankeyRight</b>(<i>node</i>, <i>n</i>)
<a name="sankeyRight" href="#sankeyRight">#</a> d3.<b>sankeyRight</b>(<i>node</i>, <i>n</i>)

Returns *n* - 1 - *node*.height.

<a name="sankeyCenter" href="#sankeyCenter">#</a> d3.<b>sankeyCenter</b>(<i>node</i>, <i>n</i>)
<a name="sankeyCenter" href="#sankeyCenter">#</a> d3.<b>sankeyCenter</b>(<i>node</i>, <i>n</i>)

Like [d3.sankeyLeft](#sankeyLeft), except that nodes without any incoming links are moved as right as possible.

<a name="sankeyJustify" href="#sankeyJustify">#</a> d3.<b>sankeyJustify</b>(<i>node</i>, <i>n</i>)
<a name="sankeyJustify" href="#sankeyJustify">#</a> d3.<b>sankeyJustify</b>(<i>node</i>, <i>n</i>)

Like [d3.sankeyLeft](#sankeyLeft), except that nodes without any outgoing links are moved to the far right.

Expand All @@ -212,7 +221,7 @@ svg.append("g")
.selectAll("path");
.data(sankey.links)
.enter()
.append("path")
.append("path")
.attr("d", function(d){
return d.path;
})
Expand Down
59 changes: 56 additions & 3 deletions dist/d3-sankey-circular.js
Original file line number Diff line number Diff line change
Expand Up @@ -247,6 +247,58 @@
return arguments.length ? (sortNodes = _, sankeyCircular) : sortNodes;
};

sankeyCircular.update = function (graph) {
// 5. Calculate the nodes' depth based on the incoming and outgoing links
// Sets the nodes':
// - depth: the depth in the graph
// - column: the depth (0, 1, 2, etc), as is relates to visual position from left to right
// - x0, x1: the x coordinates, as is relates to visual position from left to right
// computeNodeDepths(graph)

// Force position of circular link type based on position
graph.links.forEach(function (link) {
if (link.circular) {
link.circularLinkType = link.y0 + link.y1 < y1 ? 'top' : 'bottom';

link.source.circularLinkType = link.circularLinkType;
link.target.circularLinkType = link.circularLinkType;
}
});

// 3. Determine how the circular links will be drawn,
// either travelling back above the main chart ("top")
// or below the main chart ("bottom")
selectCircularLinkTypes(graph, id);

// 6. Calculate the nodes' and links' vertical position within their respective column
// Also readjusts sankeyCircular size if circular links are needed, and node x's
// computeNodeBreadths(graph, iterations, id)
computeLinkBreadths(graph);

sortSourceLinks(graph, y1, id, false); // Sort links but do not move nodes
sortTargetLinks(graph, y1, id);

// 7. Sort links per node, based on the links' source/target nodes' breadths
// 8. Adjust nodes that overlap links that span 2+ columns
// var linkSortingIterations = 4; //Possibly let user control this number, like the iterations over node placement
// for (var iteration = 0; iteration < linkSortingIterations; iteration++) {
//
// sortSourceLinks(graph, y1, id)
// sortTargetLinks(graph, y1, id)
// resolveNodeLinkOverlaps(graph, y0, y1, id)
// sortSourceLinks(graph, y1, id)
// sortTargetLinks(graph, y1, id)
//
// }

// 8.1 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
addCircularPathData(graph, circularLinkGap, y1, id);
return graph;
};

// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks(graph) {
Expand All @@ -269,6 +321,7 @@
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
return graph;
}

// Compute the value (size) and cycleness of each node by summing the associated links.
Expand Down Expand Up @@ -912,7 +965,7 @@

// bottom links
if (link.circularLinkType == 'bottom') {
link.circularPathData.verticalFullExtent = y1 + verticalMargin + link.circularPathData.verticalBuffer;
link.circularPathData.verticalFullExtent = Math.max(y1, link.source.y1, link.target.y1) + verticalMargin + link.circularPathData.verticalBuffer;
link.circularPathData.verticalLeftInnerExtent = link.circularPathData.verticalFullExtent - link.circularPathData.leftLargeArcRadius;
link.circularPathData.verticalRightInnerExtent = link.circularPathData.verticalFullExtent - link.circularPathData.rightLargeArcRadius;
} else {
Expand Down Expand Up @@ -1214,10 +1267,10 @@
}

// sort and set the links' y0 for each node
function sortSourceLinks(graph, y1, id) {
function sortSourceLinks(graph, y1, id, moveNodes) {
graph.nodes.forEach(function (node) {
// move any nodes up which are off the bottom
if (node.y + (node.y1 - node.y0) > y1) {
if (moveNodes && node.y + (node.y1 - node.y0) > y1) {
node.y = node.y - (node.y + (node.y1 - node.y0) - y1);
}

Expand Down
59 changes: 56 additions & 3 deletions dist/d3-sankey-circular.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -245,6 +245,58 @@ function sankeyCircular () {
return arguments.length ? (sortNodes = _, sankeyCircular) : sortNodes;
};

sankeyCircular.update = function (graph) {
// 5. Calculate the nodes' depth based on the incoming and outgoing links
// Sets the nodes':
// - depth: the depth in the graph
// - column: the depth (0, 1, 2, etc), as is relates to visual position from left to right
// - x0, x1: the x coordinates, as is relates to visual position from left to right
// computeNodeDepths(graph)

// Force position of circular link type based on position
graph.links.forEach(function (link) {
if (link.circular) {
link.circularLinkType = link.y0 + link.y1 < y1 ? 'top' : 'bottom';

link.source.circularLinkType = link.circularLinkType;
link.target.circularLinkType = link.circularLinkType;
}
});

// 3. Determine how the circular links will be drawn,
// either travelling back above the main chart ("top")
// or below the main chart ("bottom")
selectCircularLinkTypes(graph, id);

// 6. Calculate the nodes' and links' vertical position within their respective column
// Also readjusts sankeyCircular size if circular links are needed, and node x's
// computeNodeBreadths(graph, iterations, id)
computeLinkBreadths(graph);

sortSourceLinks(graph, y1, id, false); // Sort links but do not move nodes
sortTargetLinks(graph, y1, id);

// 7. Sort links per node, based on the links' source/target nodes' breadths
// 8. Adjust nodes that overlap links that span 2+ columns
// var linkSortingIterations = 4; //Possibly let user control this number, like the iterations over node placement
// for (var iteration = 0; iteration < linkSortingIterations; iteration++) {
//
// sortSourceLinks(graph, y1, id)
// sortTargetLinks(graph, y1, id)
// resolveNodeLinkOverlaps(graph, y0, y1, id)
// sortSourceLinks(graph, y1, id)
// sortTargetLinks(graph, y1, id)
//
// }

// 8.1 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
addCircularPathData(graph, circularLinkGap, y1, id);
return graph;
};

// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
function computeNodeLinks(graph) {
Expand All @@ -267,6 +319,7 @@ function sankeyCircular () {
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
return graph;
}

// Compute the value (size) and cycleness of each node by summing the associated links.
Expand Down Expand Up @@ -910,7 +963,7 @@ function addCircularPathData(graph, circularLinkGap, y1, id) {

// bottom links
if (link.circularLinkType == 'bottom') {
link.circularPathData.verticalFullExtent = y1 + verticalMargin + link.circularPathData.verticalBuffer;
link.circularPathData.verticalFullExtent = Math.max(y1, link.source.y1, link.target.y1) + verticalMargin + link.circularPathData.verticalBuffer;
link.circularPathData.verticalLeftInnerExtent = link.circularPathData.verticalFullExtent - link.circularPathData.leftLargeArcRadius;
link.circularPathData.verticalRightInnerExtent = link.circularPathData.verticalFullExtent - link.circularPathData.rightLargeArcRadius;
} else {
Expand Down Expand Up @@ -1212,10 +1265,10 @@ function adjustNodeHeight(node, dy, sankeyY0, sankeyY1) {
}

// sort and set the links' y0 for each node
function sortSourceLinks(graph, y1, id) {
function sortSourceLinks(graph, y1, id, moveNodes) {
graph.nodes.forEach(function (node) {
// move any nodes up which are off the bottom
if (node.y + (node.y1 - node.y0) > y1) {
if (moveNodes && node.y + (node.y1 - node.y0) > y1) {
node.y = node.y - (node.y + (node.y1 - node.y0) - y1);
}

Expand Down
Loading