diff --git a/.eslintrc b/.eslintrc
index affd3f6..551cc59 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,5 +1,12 @@
parserOptions:
sourceType: "module"
+env:
+ es6: true
+
extends:
"eslint:recommended"
+
+rules:
+ no-cond-assign: 0
+ no-constant-condition: 0
diff --git a/README.md b/README.md
index 5a91298..b6c0be9 100644
--- a/README.md
+++ b/README.md
@@ -1,20 +1,15 @@
# d3-sankey
-Sankey diagrams visualize the directed flow between nodes in an acyclic network. For example, this diagram shows a possible scenario of UK energy production and consumption in 2050:
-
-[
](https://bl.ocks.org/mbostock/ca9a0bb7ba204d12974bca90acc507c0)
-
-Source: Department of Energy & Climate Change, Tom Counsell.
+D3 4.0 implementation of the Sankey plugin to visualize the flow between nodes in a directed acyclic network.
## Installing
-If you use NPM, `npm install d3-sankey`. Otherwise, download the [latest release](https://github.com/d3/d3-sankey/releases/latest). You can also load directly from [unpkg.com](https://unpkg.com/d3-sankey/). AMD, CommonJS, and vanilla environments are supported. In vanilla, a `d3` global is exported:
+If you use NPM, `npm install d3-sankey`. Otherwise, download the [latest release](https://github.com/d3/d3-sankey/releases/latest).
+
+You can also load directly from unpkg.
-```html
-
-
-
-
+```javascript
+
```
-# d3.sankey() [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L52 "Source")
-
-Constructs a new Sankey generator with the default settings.
-
-# sankey(arguments…) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L62 "Source")
+## Demo
+Here is Mike Bostock's famous example [recreated with d3-sankey](http://bl.ocks.org/xaranke/9ada4c74a87b57ae7308).
-Computes the node and link positions for the given *arguments*, returning a *graph* representing the Sankey layout. The returned *graph* has the following properties:
+Clone or download the block, then run `npm install` and `npm run build` to create `d3.min.js`.
-* *graph*.nodes - the array of [nodes](#sankey_nodes)
-* *graph*.links - the array of [links](#sankey_links)
+## API Reference
-# sankey.update(graph) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L72 "Source")
+# sankey()
-Recomputes the specified *graph*’s links’ positions, updating the following properties of each *link*:
+Constructs a new sankey generator with the default settings.
-* *link*.y0 - the link’s vertical starting position (at source node)
-* *link*.y1 - the link’s vertical end position (at target node)
+# sankey.nodeWidth([width])
-This method is intended to be called after computing the initial [Sankey layout](#_sankey), for example when the diagram is repositioned interactively.
-
-# sankey.nodes([nodes]) [<>](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:
+If width is specified, sets the node width to the specified function or number and returns this sankey generator. If width is not specified, returns the current node width accessor, which defaults to:
```js
-function nodes(graph) {
- return graph.nodes;
+function nodeWidth() {
+ return 24;
}
```
-If *nodes* is specified as a function, the function is invoked when the Sankey layout is [generated](#_sankey), being passed any arguments passed to the Sankey generator. This function must return an array of nodes. If *nodes* is not a function, it must be a constant array of *nodes*.
-
-Each *node* must be an object. The following properties are assigned by the [Sankey generator](#_sankey):
-
-* *node*.sourceLinks - the array of outgoing [links](#sankey_links) which have this node as their source
-* *node*.targetLinks - the array of incoming [links](#sankey_links) which have this node as their target
-* *node*.value - the node’s value; the sum of *link*.value for the node’s incoming [links](#sankey_links)
-* *node*.index - the node’s zero-based index within the array of nodes
-* *node*.depth - the node’s zero-based graph depth, derived from the graph topology
-* *node*.height - the node’s zero-based graph height, derived from the graph topology
-* *node*.x0 - the node’s minimum horizontal position, derived from *node*.depth
-* *node*.x1 - the node’s maximum horizontal position (*node*.x0 + [*sankey*.nodeWidth](#sankey_nodeWidth))
-* *node*.y0 - the node’s minimum vertical position
-* *node*.y1 - the node’s maximum vertical position (*node*.y1 - *node*.y0 is proportional to *node*.value)
+# sankey.nodePadding([padding])
-See also [*sankey*.links](#sankey_links).
-
-# sankey.links([links]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L97 "Source")
-
-If *links* is specified, sets the Sankey generator’s links accessor to the specified function or array and returns this Sankey generator. If *links* is not specified, returns the current links accessor, which defaults to:
+If padding is specified, sets the node padding to the specified function or number and returns this sankey generator. If padding is not specified, returns the current node padding accessor, which defaults to:
```js
-function links(graph) {
- return graph.links;
+function nodePadding() {
+ return 8;
}
```
+Here padding refers to the vertical space between nodes that occupy the same horizontal space.
-If *links* is specified as a function, the function is invoked when the Sankey layout is [generated](#_sankey), being passed any arguments passed to the Sankey generator. This function must return an array of links. If *links* is not a function, it must be a constant array of *links*.
-
-Each *link* must be an object with the following properties:
-
-* *link*.source - the link’s source [node](#sankey_nodes)
-* *link*.target - the link’s target [node](#sankey_nodes)
-* *link*.value - the link’s numeric value
+# sankey.nodes([nodes])
-For convenience, a link’s source and target may be initialized using numeric or string identifiers rather than object references; ; see [*sankey*.nodeId](#sankey_nodeId). The following properties are assigned to each link by the [Sankey generator](#_sankey):
-
-* *link*.y0 - the link’s vertical starting position (at source node)
-* *link*.y1 - the link’s vertical end position (at target node)
-* *link*.width - the link’s width (proportional to *link*.value)
-* *link*.index - the zero-based index of *link* within the array of links
-
-# sankey.nodeId([id]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L77 "Source")
-
-If *id* is specified, sets the node id accessor to the specified function and returns this Sankey generator. If *id* is not specified, returns the current node id accessor, which defaults to the numeric *node*.index:
+If nodes is specified, sets the list of nodes to the specified function or array and returns this sankey generator. If nodes is not specified, returns the current accessor to the list of nodes, which defaults to:
```js
-function id(d) {
- return d.index;
+function nodes() {
+ return [];
}
```
-The default id accessor allows each link’s source and target to be specified as a zero-based index into the [nodes](#sankey_nodes) array. For example:
+# sankey.links([links])
-```js
-var nodes = [
- {"id": "Alice"},
- {"id": "Bob"},
- {"id": "Carol"}
-];
-
-var links = [
- {"source": 0, "target": 1}, // Alice → Bob
- {"source": 1, "target": 2} // Bob → Carol
-];
-```
-
-Now consider a different id accessor that returns a string:
+If links is specified, sets the list of links to the specified function or array and returns this sankey generator. If links is not specified, returns the current accessor to the list of links, which defaults to:
```js
-function id(d) {
- return d.id;
+function links() {
+ return [];
}
```
-With this accessor, you can use named sources and targets:
-
-```js
-var nodes = [
- {"id": "Alice"},
- {"id": "Bob"},
- {"id": "Carol"}
-];
-
-var links = [
- {"source": "Alice", "target": "Bob"},
- {"source": "Bob", "target": "Carol"}
-];
-```
-
-This is particularly useful when representing graphs in JSON, as JSON does not allow references. See [this example](https://bl.ocks.org/mbostock/f584aa36df54c451c94a9d0798caed35).
-
-# sankey.nodeAlign([align]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L81 "Source")
-
-If *align* is specified, sets the node [alignment method](#alignments) the specified function and returns this Sankey generator. If *align* is not specified, returns the current node alignment method, which defaults to [d3.sankeyJustify](#sankeyJustify). The specified function is evaluated for each input *node* in order, being passed the current *node* and the total depth *n* of the graph (one plus the maximum *node*.depth), and must return an integer between 0 and *n* - 1 that indicates the desired horizontal position of the node in the generated Sankey diagram.
+# sankey.layout([iterations])
-# sankey.nodeWidth([width]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L85 "Source")
+Returns the current accessor to the SVG layout object. Here iterations is the number of times the converging function computeNodeDepths is run.
-If *width* is specified, sets the node width to the specified number and returns this Sankey generator. If *width* is not specified, returns the current node width, which defaults to 24.
+# sankey.relayout()
-# sankey.nodePadding([padding]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L89 "Source")
-
-If *padding* is specified, sets the vertical separation between nodes at each column to the specified number and returns this Sankey generator. If *padding* is not specified, returns the current node padding, which defaults to 8.
-
-# sankey.extent([extent]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L105 "Source")
-
-If *extent* is specified, sets the extent of the Sankey layout to the specified bounds and returns the layout. The *extent* bounds are specified as an array \[\[x0, y0\], \[x1, y1\]\], where *x0* is the left side of the extent, *y0* is the top, *x1* is the right and *y1* is the bottom. If *extent* is not specified, returns the current extent which defaults to [[0, 0], [1, 1]].
-
-# sankey.size([size]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L101 "Source")
-
-An alias for [*sankey*.extent](#sankey_extent) where the minimum *x* and *y* of the extent are ⟨0,0⟩. Equivalent to:
-
-```js
-sankey.extent([[0, 0], size]);
-```
-
-# sankey.iterations([iterations]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L109 "Source")
-
-If *iterations* is specified, sets the number of relaxation iterations when [generating the layout](#_sankey) and returns this Sankey generator. If *iterations* is not specified, returns the current number of relaxation iterations, which defaults to 32.
-
-### Alignments
-
-See [*sankey*.nodeAlign](#sankey_nodeAlign).
-
-# d3.sankeyLeft(node, n) [<>](https://github.com/d3/d3-sankey/blob/master/src/align.js#L7 "Source")
-
-
-
-Returns *node*.depth.
-
-# d3.sankeyRight(node, n) [<>](https://github.com/d3/d3-sankey/blob/master/src/align.js#L11 "Source")
-
-
-
-Returns *n* - 1 - *node*.height.
-
-# d3.sankeyCenter(node, n) [<>](https://github.com/d3/d3-sankey/blob/master/src/align.js#L19 "Source")
-
-
-
-Like [d3.sankeyLeft](#sankeyLeft), except that nodes without any incoming links are moved as right as possible.
-
-# d3.sankeyJustify(node, n) [<>](https://github.com/d3/d3-sankey/blob/master/src/align.js#L15 "Source")
-
-
-
-Like [d3.sankeyLeft](#sankeyLeft), except that nodes without any outgoing links are moved to the far right.
-
-### Links
-
-# d3.sankeyLinkHorizontal() [<>](https://github.com/d3/d3-sankey/blob/master/src/sankeyLinkHorizontal.js "Source")
-
-Returns a [horizontal link shape](https://github.com/d3/d3-shape/blob/master/README.md#linkHorizontal) suitable for a Sankey diagram. The [source accessor](https://github.com/d3/d3-shape/blob/master/README.md#link_source) is defined as:
-
-```js
-function source(d) {
- return [d.source.x1, d.y0];
-}
-```
-
-The [target accessor](https://github.com/d3/d3-shape/blob/master/README.md#link_target) is defined as:
-
-```js
-function target(d) {
- return [d.target.x0, d.y1];
-}
-```
-
-For example, to render the links of a Sankey diagram in SVG, you might say:
-
-```js
-svg.append("g")
- .attr("fill", "none")
- .attr("stroke", "#000")
- .attr("stroke-opacity", 0.2)
- .selectAll("path")
- .data(graph.links)
- .enter().append("path")
- .attr("d", d3.sankeyLinkHorizontal())
- .attr("stroke-width", function(d) { return d.width; });
-```
+Similar to layout but only recalculates the depth of links. Primarily used when a node is moved vertically.
diff --git a/build/d3-sankey.js b/build/d3-sankey.js
index 4701c8f..2155cfa 100644
--- a/build/d3-sankey.js
+++ b/build/d3-sankey.js
@@ -1,168 +1,117 @@
-// https://github.com/d3/d3-sankey Version 0.7.1. Copyright 2019 Mike Bostock.
+// https://github.com/d3/d3-sankey Version 0.5.0. Copyright 2018 Mike Bostock.
(function (global, factory) {
- typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-collection'), require('d3-shape')) :
- typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-collection', 'd3-shape'], factory) :
+ typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('d3-array'), require('d3-collection'), require('d3-interpolate')) :
+ typeof define === 'function' && define.amd ? define(['exports', 'd3-array', 'd3-collection', 'd3-interpolate'], factory) :
(factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3));
-}(this, (function (exports,d3Array,d3Collection,d3Shape) { 'use strict';
-
-function targetDepth(d) {
- return d.target.depth;
-}
-
-function left(node) {
- return node.depth;
-}
-
-function right(node, n) {
- return n - 1 - node.height;
-}
-
-function justify(node, n) {
- return node.sourceLinks.length ? node.depth : n - 1;
-}
-
-function center(node) {
- return node.targetLinks.length ? node.depth
- : node.sourceLinks.length ? d3Array.min(node.sourceLinks, targetDepth) - 1
- : 0;
-}
-
-function constant(x) {
- return function() {
- return x;
- };
-}
-
-function ascendingSourceBreadth(a, b) {
- return ascendingBreadth(a.source, b.source) || a.index - b.index;
-}
-
-function ascendingTargetBreadth(a, b) {
- return ascendingBreadth(a.target, b.target) || a.index - b.index;
-}
-
-function ascendingBreadth(a, b) {
- return a.y0 - b.y0;
-}
-
-function value(d) {
- return d.value;
-}
-
-function nodeCenter(node) {
- return (node.y0 + node.y1) / 2;
-}
-
-function weightedSource(link) {
- return nodeCenter(link.source) * link.value;
-}
-
-function weightedTarget(link) {
- return nodeCenter(link.target) * link.value;
-}
-
-function defaultId(d) {
- return d.index;
-}
-
-function defaultNodes(graph) {
- return graph.nodes;
-}
-
-function defaultLinks(graph) {
- return graph.links;
-}
-
-function find(nodeById, id) {
- var node = nodeById.get(id);
- if (!node) throw new Error("missing: " + id);
- return node;
-}
+}(this, (function (exports,d3Array,d3Collection,d3Interpolate) { 'use strict';
var sankey = function() {
- var x0 = 0, y0 = 0, x1 = 1, y1 = 1, // extent
- dx = 24, // nodeWidth
- py = 8, // nodePadding
- id = defaultId,
- align = justify,
- nodes = defaultNodes,
- links = defaultLinks,
- iterations = 32,
+ var sankey = {},
+ nodeWidth = 24,
+ nodePadding = 8,
+ size = [1, 1],
+ nodes = [],
+ links = [],
maxPaddedSpace = 2 / 3; // Defined as a fraction of the total available space
- function sankey() {
- var graph = {nodes: nodes.apply(null, arguments), links: links.apply(null, arguments)};
- computeNodeLinks(graph);
- computeNodeValues(graph);
- computeNodeDepths(graph);
- computeNodeBreadths(graph, iterations);
- computeLinkBreadths(graph);
- return graph;
- }
-
- sankey.update = function(graph) {
- computeLinkBreadths(graph);
- return graph;
- };
-
- sankey.nodeId = function(_) {
- return arguments.length ? (id = typeof _ === "function" ? _ : constant(_), sankey) : id;
- };
-
- sankey.nodeAlign = function(_) {
- return arguments.length ? (align = typeof _ === "function" ? _ : constant(_), sankey) : align;
- };
-
sankey.nodeWidth = function(_) {
- return arguments.length ? (dx = +_, sankey) : dx;
+ if (!arguments.length) return nodeWidth;
+ nodeWidth = +_;
+ return sankey;
};
sankey.nodePadding = function(_) {
- return arguments.length ? (py = +_, sankey) : py;
+ if (!arguments.length) return nodePadding;
+ nodePadding = +_;
+ return sankey;
};
sankey.nodes = function(_) {
- return arguments.length ? (nodes = typeof _ === "function" ? _ : constant(_), sankey) : nodes;
+ if (!arguments.length) return nodes;
+ nodes = _;
+ return sankey;
};
sankey.links = function(_) {
- return arguments.length ? (links = typeof _ === "function" ? _ : constant(_), sankey) : links;
+ if (!arguments.length) return links;
+ links = _;
+ return sankey;
};
sankey.size = function(_) {
- return arguments.length ? (x0 = y0 = 0, x1 = +_[0], y1 = +_[1], sankey) : [x1 - x0, y1 - y0];
+ if (!arguments.length) return size;
+ size = _;
+ return sankey;
};
- sankey.extent = function(_) {
- return arguments.length ? (x0 = +_[0][0], x1 = +_[1][0], y0 = +_[0][1], y1 = +_[1][1], sankey) : [[x0, y0], [x1, y1]];
+ sankey.layout = function(iterations) {
+ computeNodeLinks();
+ computeNodeValues();
+ computeNodeBreadths();
+ computeNodeDepths(iterations);
+ computeLinkDepths();
+ return sankey;
};
- sankey.iterations = function(_) {
- return arguments.length ? (iterations = +_, sankey) : iterations;
+ sankey.relayout = function() {
+ computeLinkDepths();
+ return sankey;
+ };
+
+ sankey.link = function() {
+ var curvature = .5;
+
+ function link(d) {
+ var x0 = d.source.x + d.source.dx,
+ x1 = d.target.x,
+ xi = d3Interpolate.interpolateNumber(x0, x1),
+ x2 = xi(curvature),
+ x3 = xi(1 - curvature),
+ y0a = d.source.y + d.sy,
+ y0b = y0a + d.dy,
+ y1a = d.target.y + d.ty,
+ y1b = y1a + d.dy;
+ return "M" + x0 + "," + y0a
+ + "C" + x2 + "," + y0a
+ + " " + x3 + "," + y1a
+ + " " + x1 + "," + y1a
+ + "L" + x1 + "," + y1b
+ + "C" + x3 + "," + y1b
+ + " " + x2 + "," + y0b
+ + " " + x0 + "," + y0b
+ + "Z";
+ }
+
+ 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(graph) {
- graph.nodes.forEach(function(node, i) {
- node.index = i;
+ function computeNodeLinks() {
+ nodes.forEach(function(node) {
node.sourceLinks = [];
node.targetLinks = [];
});
- var nodeById = d3Collection.map(graph.nodes, id);
- graph.links.forEach(function(link, i) {
- link.index = i;
- var source = link.source, target = link.target;
- if (typeof source !== "object") source = link.source = find(nodeById, source);
- if (typeof target !== "object") target = link.target = find(nodeById, target);
+ links.forEach(function(link, i) {
+ 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];
+ link.originalIndex = i;
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
- function computeNodeValues(graph) {
- graph.nodes.forEach(function(node) {
+ function computeNodeValues() {
+ nodes.forEach(function(node) {
node.value = Math.max(
d3Array.sum(node.sourceLinks, value),
d3Array.sum(node.targetLinks, value)
@@ -170,174 +119,202 @@ var sankey = function() {
});
}
- // Iteratively assign the depth (x-position) for each node.
- // Nodes are assigned the maximum depth of incoming neighbors plus one;
- // nodes with no incoming links are assigned depth zero, while
- // nodes with no outgoing links are assigned the maximum depth.
- function computeNodeDepths(graph) {
- var nodes, next, x;
-
- for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
- nodes.forEach(function(node) {
- node.depth = x;
+ // 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.
+ function computeNodeBreadths() {
+ var remainingNodes = nodes,
+ nextNodes,
+ x = 0;
+
+ while (remainingNodes.length) {
+ nextNodes = [];
+ remainingNodes.forEach(function(node) {
+ node.x = x;
+ node.dx = nodeWidth;
node.sourceLinks.forEach(function(link) {
- if (next.indexOf(link.target) < 0) {
- next.push(link.target);
+ if (nextNodes.indexOf(link.target) < 0) {
+ nextNodes.push(link.target);
}
});
});
+ remainingNodes = nextNodes;
+ ++x;
}
- for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
- nodes.forEach(function(node) {
- node.height = x;
- node.targetLinks.forEach(function(link) {
- if (next.indexOf(link.source) < 0) {
- next.push(link.source);
- }
- });
- });
- }
+ //
+ moveSinksRight(x);
+ scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
+ }
- var kx = (x1 - x0 - dx) / (x - 1);
- graph.nodes.forEach(function(node) {
- node.x1 = (node.x0 = x0 + Math.max(0, Math.min(x - 1, Math.floor(align.call(null, node, x)))) * kx) + dx;
+ // function moveSourcesRight() {
+ // nodes.forEach(function(node) {
+ // if (!node.targetLinks.length) {
+ // node.x = 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 computeNodeBreadths(graph) {
- var columns = d3Collection.nest()
- .key(function(d) { return d.x0; })
+ function scaleNodeBreadths(kx) {
+ nodes.forEach(function(node) {
+ node.x *= kx;
+ });
+ }
+
+ function computeNodeDepths(iterations) {
+ var nodesByBreadth = d3Collection.nest()
+ .key(function(d) { return d.x; })
.sortKeys(d3Array.ascending)
- .entries(graph.nodes)
+ .entries(nodes)
.map(function(d) { return d.values; });
//
- initializeNodeBreadth();
+ initializeNodeDepth();
resolveCollisions();
- for (var alpha = 1, n = iterations; n > 0; --n) {
- relaxRightToLeft(alpha *= 0.99);
+ for (var alpha = 1; iterations > 0; --iterations) {
+ relaxRightToLeft(alpha *= .99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
- function initializeNodeBreadth() {
- var L = d3Array.max(columns, function(nodes) {
+ function initializeNodeDepth() {
+ var L = d3Array.max(nodesByBreadth, function(nodes) {
return nodes.length;
});
- var maxNodePadding = maxPaddedSpace * (y1 - y0) / (L - 1);
- if(py > maxNodePadding) py = maxNodePadding;
- var ky = d3Array.min(columns, function(nodes) {
- return (y1 - y0 - (nodes.length - 1) * py) / d3Array.sum(nodes, value);
+ var maxNodePadding = maxPaddedSpace * size[1] / (L - 1);
+ if(nodePadding > maxNodePadding) nodePadding = maxNodePadding;
+ var ky = d3Array.min(nodesByBreadth, function(nodes) {
+ return (size[1] - (nodes.length - 1) * nodePadding) / d3Array.sum(nodes, value);
});
- columns.forEach(function(nodes) {
+ nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node, i) {
- node.y1 = (node.y0 = i) + node.value * ky;
+ node.y = i;
+ node.dy = node.value * ky;
});
});
- graph.links.forEach(function(link) {
- link.width = link.value * ky;
+ links.forEach(function(link) {
+ link.dy = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
- columns.forEach(function(nodes) {
+ nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
- var dy = (d3Array.sum(node.targetLinks, weightedSource) / d3Array.sum(node.targetLinks, value) - nodeCenter(node)) * alpha;
- node.y0 += dy, node.y1 += dy;
+ var y = d3Array.sum(node.targetLinks, weightedSource) / d3Array.sum(node.targetLinks, value);
+ node.y += (y - center(node)) * alpha;
}
});
});
+
+ function weightedSource(link) {
+ return center(link.source) * link.value;
+ }
}
function relaxRightToLeft(alpha) {
- columns.slice().reverse().forEach(function(nodes) {
+ nodesByBreadth.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
- var dy = (d3Array.sum(node.sourceLinks, weightedTarget) / d3Array.sum(node.sourceLinks, value) - nodeCenter(node)) * alpha;
- node.y0 += dy, node.y1 += dy;
+ var y = d3Array.sum(node.sourceLinks, weightedTarget) / d3Array.sum(node.sourceLinks, value);
+ node.y += (y - center(node)) * alpha;
}
});
});
+
+ function weightedTarget(link) {
+ return center(link.target) * link.value;
+ }
}
function resolveCollisions() {
- columns.forEach(function(nodes) {
+ nodesByBreadth.forEach(function(nodes) {
var node,
dy,
- y = y0,
+ y0 = 0,
n = nodes.length,
i;
// Push any overlapping nodes down.
- nodes.sort(ascendingBreadth);
+ nodes.sort(ascendingDepth);
for (i = 0; i < n; ++i) {
node = nodes[i];
- dy = y - node.y0;
- if (dy > 0) node.y0 += dy, node.y1 += dy;
- y = node.y1 + py;
+ 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 = y - py - y1;
+ dy = y0 - nodePadding - size[1];
if (dy > 0) {
- y = (node.y0 -= dy), node.y1 -= dy;
+ y0 = node.y -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
- dy = node.y1 + py - y;
- if (dy > 0) node.y0 -= dy, node.y1 -= dy;
- y = node.y0;
+ 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 computeLinkBreadths(graph) {
- graph.nodes.forEach(function(node) {
- node.sourceLinks.sort(ascendingTargetBreadth);
- node.targetLinks.sort(ascendingSourceBreadth);
+ function computeLinkDepths() {
+ nodes.forEach(function(node) {
+ node.sourceLinks.sort(ascendingTargetDepth);
+ node.targetLinks.sort(ascendingSourceDepth);
});
- graph.nodes.forEach(function(node) {
- var y0 = node.y0, y1 = y0;
+ nodes.forEach(function(node) {
+ var sy = 0, ty = 0;
node.sourceLinks.forEach(function(link) {
- link.y0 = y0 + link.width / 2, y0 += link.width;
+ link.sy = sy;
+ sy += link.dy;
});
node.targetLinks.forEach(function(link) {
- link.y1 = y1 + link.width / 2, y1 += link.width;
+ link.ty = ty;
+ ty += link.dy;
});
});
- }
- return sankey;
-};
+ function ascendingSourceDepth(a, b) {
+ return (a.source.y - b.source.y) || (a.originalIndex - b.originalIndex);
+ }
-function horizontalSource(d) {
- return [d.source.x1, d.y0];
-}
+ function ascendingTargetDepth(a, b) {
+ return (a.target.y - b.target.y) || (a.originalIndex - b.originalIndex);
+ }
+ }
+
+ function center(node) {
+ return node.y + node.dy / 2;
+ }
-function horizontalTarget(d) {
- return [d.target.x0, d.y1];
-}
+ function value(link) {
+ return link.value;
+ }
-var sankeyLinkHorizontal = function() {
- return d3Shape.linkHorizontal()
- .source(horizontalSource)
- .target(horizontalTarget);
+ return sankey;
};
exports.sankey = sankey;
-exports.sankeyCenter = center;
-exports.sankeyLeft = left;
-exports.sankeyRight = right;
-exports.sankeyJustify = justify;
-exports.sankeyLinkHorizontal = sankeyLinkHorizontal;
Object.defineProperty(exports, '__esModule', { value: true });
diff --git a/build/d3-sankey.min.js b/build/d3-sankey.min.js
index 5d87e18..9f8aa0f 100644
--- a/build/d3-sankey.min.js
+++ b/build/d3-sankey.min.js
@@ -1,2 +1,2 @@
-// https://github.com/d3/d3-sankey Version 0.7.1. Copyright 2019 Mike Bostock.
-!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("d3-array"),require("d3-collection"),require("d3-shape")):"function"==typeof define&&define.amd?define(["exports","d3-array","d3-collection","d3-shape"],t):t(n.d3=n.d3||{},n.d3,n.d3,n.d3)}(this,function(n,g,k,t){"use strict";function e(n){return n.target.depth}function p(n,t){return n.sourceLinks.length?n.depth:t-1}function v(n){return function(){return n}}function L(n,t){return E(n.source,t.source)||n.index-t.index}function x(n,t){return E(n.target,t.target)||n.index-t.index}function E(n,t){return n.y0-t.y0}function m(n){return n.value}function w(n){return(n.y0+n.y1)/2}function M(n){return w(n.source)*n.value}function b(n){return w(n.target)*n.value}function j(n){return n.index}function q(n){return n.nodes}function z(n){return n.links}function O(n,t){var e=n.get(t);if(!e)throw new Error("missing: "+t);return e}function r(n){return[n.source.x1,n.y0]}function o(n){return[n.target.x0,n.y1]}n.sankey=function(){var u=0,c=0,i=1,f=1,s=24,a=8,t=j,h=p,e=q,r=z,d=32,l=2/3;function o(){var n={nodes:e.apply(null,arguments),links:r.apply(null,arguments)};return function(n){n.nodes.forEach(function(n,t){n.index=t,n.sourceLinks=[],n.targetLinks=[]});var o=k.map(n.nodes,t);n.links.forEach(function(n,t){n.index=t;var e=n.source,r=n.target;"object"!=typeof e&&(e=n.source=O(o,e)),"object"!=typeof r&&(r=n.target=O(o,r)),e.sourceLinks.push(n),r.targetLinks.push(n)})}(n),n.nodes.forEach(function(n){n.value=Math.max(g.sum(n.sourceLinks,m),g.sum(n.targetLinks,m))}),function(n){var t,e,r;for(t=n.nodes,e=[],r=0;t.length;++r,t=e,e=[])t.forEach(function(n){n.depth=r,n.sourceLinks.forEach(function(n){e.indexOf(n.target)<0&&e.push(n.target)})});for(t=n.nodes,e=[],r=0;t.length;++r,t=e,e=[])t.forEach(function(n){n.height=r,n.targetLinks.forEach(function(n){e.indexOf(n.source)<0&&e.push(n.source)})});var o=(i-u-s)/(r-1);n.nodes.forEach(function(n){n.x1=(n.x0=u+Math.max(0,Math.min(r-1,Math.floor(h.call(null,n,r))))*o)+s})}(n),function(r){var o=k.nest().key(function(n){return n.x0}).sortKeys(g.ascending).entries(r.nodes).map(function(n){return n.values});(function(){var n=g.max(o,function(n){return n.length}),t=l*(f-c)/(n-1);t0&&(t.y+=r),u=t.y+t.dy+g;if((r=u-g-h[1])>0)for(u=t.y-=r,e=i-2;e>=0;--e)t=n[e],r=t.y+t.dy+g-u,r>0&&(t.y-=r),u=t.y})}function o(n,t){return n.y-t.y}var u=r.nest().key(function(n){return n.x}).sortKeys(t.ascending).entries(v).map(function(n){return n.values});!function(){var n=t.max(u,function(n){return n.length}),r=x*h[1]/(n-1);g>r&&(g=r);var e=t.min(u,function(n){return(h[1]-(n.length-1)*g)/t.sum(n,y)});u.forEach(function(n){n.forEach(function(n,t){n.y=t,n.dy=n.value*e})}),k.forEach(function(n){n.dy=n.value*e})}(),e();for(var i=1;n>0;--n)!function(n){function r(n){return s(n.target)*n.value}u.slice().reverse().forEach(function(e){e.forEach(function(e){if(e.sourceLinks.length){var o=t.sum(e.sourceLinks,r)/t.sum(e.sourceLinks,y);e.y+=(o-s(e))*n}})})}(i*=.99),e(),function(n){function r(n){return s(n.source)*n.value}u.forEach(function(e){e.forEach(function(e){if(e.targetLinks.length){var o=t.sum(e.targetLinks,r)/t.sum(e.targetLinks,y);e.y+=(o-s(e))*n}})})}(i),e()}function a(){function n(n,t){return n.source.y-t.source.y||n.originalIndex-t.originalIndex}function t(n,t){return n.target.y-t.target.y||n.originalIndex-t.originalIndex}v.forEach(function(r){r.sourceLinks.sort(t),r.targetLinks.sort(n)}),v.forEach(function(n){var t=0,r=0;n.sourceLinks.forEach(function(n){n.sy=t,t+=n.dy}),n.targetLinks.forEach(function(n){n.ty=r,r+=n.dy})})}function s(n){return n.y+n.dy/2}function y(n){return n.value}var d={},l=24,g=8,h=[1,1],v=[],k=[],x=2/3;return d.nodeWidth=function(n){return arguments.length?(l=+n,d):l},d.nodePadding=function(n){return arguments.length?(g=+n,d):g},d.nodes=function(n){return arguments.length?(v=n,d):v},d.links=function(n){return arguments.length?(k=n,d):k},d.size=function(n){return arguments.length?(h=n,d):h},d.layout=function(t){return n(),o(),u(),f(t),a(),d},d.relayout=function(){return a(),d},d.link=function(){function n(n){var r=n.source.x+n.source.dx,o=n.target.x,u=e.interpolateNumber(r,o),i=u(t),c=u(1-t),f=n.source.y+n.sy,a=f+n.dy,s=n.target.y+n.ty,y=s+n.dy;return"M"+r+","+f+"C"+i+","+f+" "+c+","+s+" "+o+","+s+"L"+o+","+y+"C"+c+","+y+" "+i+","+a+" "+r+","+a+"Z"}var t=.5;return n.curvature=function(r){return arguments.length?(t=+r,n):t},n},d};n.sankey=o,Object.defineProperty(n,"__esModule",{value:!0})});
\ No newline at end of file
diff --git a/img/align-center.png b/img/align-center.png
deleted file mode 100644
index a8a39f0..0000000
Binary files a/img/align-center.png and /dev/null differ
diff --git a/img/align-left.png b/img/align-left.png
deleted file mode 100644
index 8edac29..0000000
Binary files a/img/align-left.png and /dev/null differ
diff --git a/img/align-right.png b/img/align-right.png
deleted file mode 100644
index 70c33d5..0000000
Binary files a/img/align-right.png and /dev/null differ
diff --git a/img/energy.png b/img/energy.png
deleted file mode 100644
index ec8f24f..0000000
Binary files a/img/energy.png and /dev/null differ
diff --git a/index.js b/index.js
index 0658a63..9aef7af 100644
--- a/index.js
+++ b/index.js
@@ -1,3 +1 @@
export {default as sankey} from "./src/sankey";
-export {center as sankeyCenter, left as sankeyLeft, right as sankeyRight, justify as sankeyJustify} from "./src/align";
-export {default as sankeyLinkHorizontal} from "./src/sankeyLinkHorizontal";
diff --git a/package.json b/package.json
index d6f2aae..c2a9b09 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
- "name": "d3-sankey",
- "version": "0.7.1",
+ "name": "@plotly/d3-sankey",
+ "version": "0.5.0",
"description": "Visualize flow between nodes in a directed acyclic network.",
"keywords": [
"d3",
@@ -9,33 +9,33 @@
],
"author": {
"name": "Mike Bostock",
- "url": "https://bost.ocks.org/mike/"
+ "url": "http://bost.ocks.org/mike"
},
"license": "BSD-3-Clause",
"main": "build/d3-sankey.js",
- "module": "index",
"jsnext:main": "index",
+ "module": "index",
"homepage": "https://github.com/d3/d3-sankey",
"repository": {
"type": "git",
"url": "https://github.com/d3/d3-sankey.git"
},
"scripts": {
- "pretest": "rm -rf build && mkdir build && rollup --banner \"$(preamble)\" -g d3-array:d3,d3-collection:d3,d3-shape:d3 -f umd -n d3 -o build/d3-sankey.js -- index.js",
+ "pretest": "rm -rf build && mkdir build && rollup --banner \"$(preamble)\" -g d3-array:d3,d3-collection:d3,d3-interpolate:d3 -f umd -n d3 -o build/d3-sankey.js -- index.js",
"test": "tape 'test/**/*-test.js' && eslint index.js src",
- "prepublish": "npm run test && uglifyjs -b beautify=false,preamble=\"'$(preamble)'\" build/d3-sankey.js -c -m -o build/d3-sankey.min.js",
+ "prepublish": "npm run test && uglifyjs --preamble \"$(preamble)\" build/d3-sankey.js -c -m -o build/d3-sankey.min.js",
"postpublish": "git push && git push --tags && zip -j build/d3-sankey.zip -- LICENSE README.md build/d3-sankey.js build/d3-sankey.min.js"
},
"dependencies": {
"d3-array": "1",
"d3-collection": "1",
- "d3-shape": "^1.2.0"
+ "d3-interpolate": "1"
},
"devDependencies": {
- "eslint": "4",
- "package-preamble": "0.1.0",
- "rollup": "0.43",
+ "eslint": "3",
+ "package-preamble": "0.0.2",
+ "rollup": "0.41",
"tape": "4",
- "uglify-js": "3"
+ "uglify-js": "2"
}
}
diff --git a/src/align.js b/src/align.js
deleted file mode 100644
index a4bdedf..0000000
--- a/src/align.js
+++ /dev/null
@@ -1,23 +0,0 @@
-import {min} from "d3-array";
-
-function targetDepth(d) {
- return d.target.depth;
-}
-
-export function left(node) {
- return node.depth;
-}
-
-export function right(node, n) {
- return n - 1 - node.height;
-}
-
-export function justify(node, n) {
- return node.sourceLinks.length ? node.depth : n - 1;
-}
-
-export function center(node) {
- return node.targetLinks.length ? node.depth
- : node.sourceLinks.length ? min(node.sourceLinks, targetDepth) - 1
- : 0;
-}
diff --git a/src/constant.js b/src/constant.js
deleted file mode 100644
index 384e25b..0000000
--- a/src/constant.js
+++ /dev/null
@@ -1,5 +0,0 @@
-export default function constant(x) {
- return function() {
- return x;
- };
-}
diff --git a/src/sankey.js b/src/sankey.js
index a4ff6b7..cc7ad20 100644
--- a/src/sankey.js
+++ b/src/sankey.js
@@ -1,138 +1,114 @@
-import {ascending, min, max, sum} from "d3-array";
-import {map, nest} from "d3-collection";
-import {justify} from "./align";
-import constant from "./constant";
-
-function ascendingSourceBreadth(a, b) {
- return ascendingBreadth(a.source, b.source) || a.index - b.index;
-}
-
-function ascendingTargetBreadth(a, b) {
- return ascendingBreadth(a.target, b.target) || a.index - b.index;
-}
-
-function ascendingBreadth(a, b) {
- return a.y0 - b.y0;
-}
-
-function value(d) {
- return d.value;
-}
-
-function nodeCenter(node) {
- return (node.y0 + node.y1) / 2;
-}
-
-function weightedSource(link) {
- return nodeCenter(link.source) * link.value;
-}
-
-function weightedTarget(link) {
- return nodeCenter(link.target) * link.value;
-}
-
-function defaultId(d) {
- return d.index;
-}
-
-function defaultNodes(graph) {
- return graph.nodes;
-}
-
-function defaultLinks(graph) {
- return graph.links;
-}
-
-function find(nodeById, id) {
- var node = nodeById.get(id);
- if (!node) throw new Error("missing: " + id);
- return node;
-}
+import {ascending, min, sum, max} from "d3-array";
+import {nest} from "d3-collection";
+import {interpolateNumber} from "d3-interpolate";
export default function() {
- var x0 = 0, y0 = 0, x1 = 1, y1 = 1, // extent
- dx = 24, // nodeWidth
- py = 8, // nodePadding
- id = defaultId,
- align = justify,
- nodes = defaultNodes,
- links = defaultLinks,
- iterations = 32,
+ var sankey = {},
+ nodeWidth = 24,
+ nodePadding = 8,
+ size = [1, 1],
+ nodes = [],
+ links = [],
maxPaddedSpace = 2 / 3; // Defined as a fraction of the total available space
- function sankey() {
- var graph = {nodes: nodes.apply(null, arguments), links: links.apply(null, arguments)};
- computeNodeLinks(graph);
- computeNodeValues(graph);
- computeNodeDepths(graph);
- computeNodeBreadths(graph, iterations);
- computeLinkBreadths(graph);
- return graph;
- }
-
- sankey.update = function(graph) {
- computeLinkBreadths(graph);
- return graph;
- };
-
- sankey.nodeId = function(_) {
- return arguments.length ? (id = typeof _ === "function" ? _ : constant(_), sankey) : id;
- };
-
- sankey.nodeAlign = function(_) {
- return arguments.length ? (align = typeof _ === "function" ? _ : constant(_), sankey) : align;
- };
-
sankey.nodeWidth = function(_) {
- return arguments.length ? (dx = +_, sankey) : dx;
+ if (!arguments.length) return nodeWidth;
+ nodeWidth = +_;
+ return sankey;
};
sankey.nodePadding = function(_) {
- return arguments.length ? (py = +_, sankey) : py;
+ if (!arguments.length) return nodePadding;
+ nodePadding = +_;
+ return sankey;
};
sankey.nodes = function(_) {
- return arguments.length ? (nodes = typeof _ === "function" ? _ : constant(_), sankey) : nodes;
+ if (!arguments.length) return nodes;
+ nodes = _;
+ return sankey;
};
sankey.links = function(_) {
- return arguments.length ? (links = typeof _ === "function" ? _ : constant(_), sankey) : links;
+ if (!arguments.length) return links;
+ links = _;
+ return sankey;
};
sankey.size = function(_) {
- return arguments.length ? (x0 = y0 = 0, x1 = +_[0], y1 = +_[1], sankey) : [x1 - x0, y1 - y0];
+ if (!arguments.length) return size;
+ size = _;
+ return sankey;
};
- sankey.extent = function(_) {
- return arguments.length ? (x0 = +_[0][0], x1 = +_[1][0], y0 = +_[0][1], y1 = +_[1][1], sankey) : [[x0, y0], [x1, y1]];
+ sankey.layout = function(iterations) {
+ computeNodeLinks();
+ computeNodeValues();
+ computeNodeBreadths();
+ computeNodeDepths(iterations);
+ computeLinkDepths();
+ return sankey;
};
- sankey.iterations = function(_) {
- return arguments.length ? (iterations = +_, sankey) : iterations;
+ sankey.relayout = function() {
+ computeLinkDepths();
+ return sankey;
+ };
+
+ sankey.link = function() {
+ var curvature = .5;
+
+ function link(d) {
+ var x0 = d.source.x + d.source.dx,
+ x1 = d.target.x,
+ xi = interpolateNumber(x0, x1),
+ x2 = xi(curvature),
+ x3 = xi(1 - curvature),
+ y0a = d.source.y + d.sy,
+ y0b = y0a + d.dy,
+ y1a = d.target.y + d.ty,
+ y1b = y1a + d.dy;
+ return "M" + x0 + "," + y0a
+ + "C" + x2 + "," + y0a
+ + " " + x3 + "," + y1a
+ + " " + x1 + "," + y1a
+ + "L" + x1 + "," + y1b
+ + "C" + x3 + "," + y1b
+ + " " + x2 + "," + y0b
+ + " " + x0 + "," + y0b
+ + "Z";
+ }
+
+ 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(graph) {
- graph.nodes.forEach(function(node, i) {
- node.index = i;
+ function computeNodeLinks() {
+ nodes.forEach(function(node) {
node.sourceLinks = [];
node.targetLinks = [];
});
- var nodeById = map(graph.nodes, id);
- graph.links.forEach(function(link, i) {
- link.index = i;
- var source = link.source, target = link.target;
- if (typeof source !== "object") source = link.source = find(nodeById, source);
- if (typeof target !== "object") target = link.target = find(nodeById, target);
+ links.forEach(function(link, i) {
+ 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];
+ link.originalIndex = i;
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
- function computeNodeValues(graph) {
- graph.nodes.forEach(function(node) {
+ function computeNodeValues() {
+ nodes.forEach(function(node) {
node.value = Math.max(
sum(node.sourceLinks, value),
sum(node.targetLinks, value)
@@ -140,149 +116,196 @@ export default function() {
});
}
- // Iteratively assign the depth (x-position) for each node.
- // Nodes are assigned the maximum depth of incoming neighbors plus one;
- // nodes with no incoming links are assigned depth zero, while
- // nodes with no outgoing links are assigned the maximum depth.
- function computeNodeDepths(graph) {
- var nodes, next, x;
-
- for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
- nodes.forEach(function(node) {
- node.depth = x;
+ // 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.
+ function computeNodeBreadths() {
+ var remainingNodes = nodes,
+ nextNodes,
+ x = 0;
+
+ while (remainingNodes.length) {
+ nextNodes = [];
+ remainingNodes.forEach(function(node) {
+ node.x = x;
+ node.dx = nodeWidth;
node.sourceLinks.forEach(function(link) {
- if (next.indexOf(link.target) < 0) {
- next.push(link.target);
+ if (nextNodes.indexOf(link.target) < 0) {
+ nextNodes.push(link.target);
}
});
});
+ remainingNodes = nextNodes;
+ ++x;
}
- for (nodes = graph.nodes, next = [], x = 0; nodes.length; ++x, nodes = next, next = []) {
- nodes.forEach(function(node) {
- node.height = x;
- node.targetLinks.forEach(function(link) {
- if (next.indexOf(link.source) < 0) {
- next.push(link.source);
- }
- });
- });
- }
+ //
+ moveSinksRight(x);
+ scaleNodeBreadths((size[0] - nodeWidth) / (x - 1));
+ }
+
+ // function moveSourcesRight() {
+ // nodes.forEach(function(node) {
+ // if (!node.targetLinks.length) {
+ // node.x = 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;
+ }
+ });
+ }
- var kx = (x1 - x0 - dx) / (x - 1);
- graph.nodes.forEach(function(node) {
- node.x1 = (node.x0 = x0 + Math.max(0, Math.min(x - 1, Math.floor(align.call(null, node, x)))) * kx) + dx;
+ function scaleNodeBreadths(kx) {
+ nodes.forEach(function(node) {
+ node.x *= kx;
});
}
- function computeNodeBreadths(graph) {
- var columns = nest()
- .key(function(d) { return d.x0; })
+ function computeNodeDepths(iterations) {
+ var nodesByBreadth = nest()
+ .key(function(d) { return d.x; })
.sortKeys(ascending)
- .entries(graph.nodes)
+ .entries(nodes)
.map(function(d) { return d.values; });
//
- initializeNodeBreadth();
+ initializeNodeDepth();
resolveCollisions();
- for (var alpha = 1, n = iterations; n > 0; --n) {
- relaxRightToLeft(alpha *= 0.99);
+ for (var alpha = 1; iterations > 0; --iterations) {
+ relaxRightToLeft(alpha *= .99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
- function initializeNodeBreadth() {
- var L = max(columns, function(nodes) {
+ function initializeNodeDepth() {
+ var L = max(nodesByBreadth, function(nodes) {
return nodes.length;
});
- var maxNodePadding = maxPaddedSpace * (y1 - y0) / (L - 1);
- if(py > maxNodePadding) py = maxNodePadding;
- var ky = min(columns, function(nodes) {
- return (y1 - y0 - (nodes.length - 1) * py) / sum(nodes, value);
+ var maxNodePadding = maxPaddedSpace * size[1] / (L - 1);
+ if(nodePadding > maxNodePadding) nodePadding = maxNodePadding;
+ var ky = min(nodesByBreadth, function(nodes) {
+ return (size[1] - (nodes.length - 1) * nodePadding) / sum(nodes, value);
});
- columns.forEach(function(nodes) {
+ nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node, i) {
- node.y1 = (node.y0 = i) + node.value * ky;
+ node.y = i;
+ node.dy = node.value * ky;
});
});
- graph.links.forEach(function(link) {
- link.width = link.value * ky;
+ links.forEach(function(link) {
+ link.dy = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
- columns.forEach(function(nodes) {
+ nodesByBreadth.forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
- var dy = (sum(node.targetLinks, weightedSource) / sum(node.targetLinks, value) - nodeCenter(node)) * alpha;
- node.y0 += dy, node.y1 += dy;
+ var y = sum(node.targetLinks, weightedSource) / sum(node.targetLinks, value);
+ node.y += (y - center(node)) * alpha;
}
});
});
+
+ function weightedSource(link) {
+ return center(link.source) * link.value;
+ }
}
function relaxRightToLeft(alpha) {
- columns.slice().reverse().forEach(function(nodes) {
+ nodesByBreadth.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
- var dy = (sum(node.sourceLinks, weightedTarget) / sum(node.sourceLinks, value) - nodeCenter(node)) * alpha;
- node.y0 += dy, node.y1 += dy;
+ var y = sum(node.sourceLinks, weightedTarget) / sum(node.sourceLinks, value);
+ node.y += (y - center(node)) * alpha;
}
});
});
+
+ function weightedTarget(link) {
+ return center(link.target) * link.value;
+ }
}
function resolveCollisions() {
- columns.forEach(function(nodes) {
+ nodesByBreadth.forEach(function(nodes) {
var node,
dy,
- y = y0,
+ y0 = 0,
n = nodes.length,
i;
// Push any overlapping nodes down.
- nodes.sort(ascendingBreadth);
+ nodes.sort(ascendingDepth);
for (i = 0; i < n; ++i) {
node = nodes[i];
- dy = y - node.y0;
- if (dy > 0) node.y0 += dy, node.y1 += dy;
- y = node.y1 + py;
+ 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 = y - py - y1;
+ dy = y0 - nodePadding - size[1];
if (dy > 0) {
- y = (node.y0 -= dy), node.y1 -= dy;
+ y0 = node.y -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
- dy = node.y1 + py - y;
- if (dy > 0) node.y0 -= dy, node.y1 -= dy;
- y = node.y0;
+ 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 computeLinkBreadths(graph) {
- graph.nodes.forEach(function(node) {
- node.sourceLinks.sort(ascendingTargetBreadth);
- node.targetLinks.sort(ascendingSourceBreadth);
+ function computeLinkDepths() {
+ nodes.forEach(function(node) {
+ node.sourceLinks.sort(ascendingTargetDepth);
+ node.targetLinks.sort(ascendingSourceDepth);
});
- graph.nodes.forEach(function(node) {
- var y0 = node.y0, y1 = y0;
+ nodes.forEach(function(node) {
+ var sy = 0, ty = 0;
node.sourceLinks.forEach(function(link) {
- link.y0 = y0 + link.width / 2, y0 += link.width;
+ link.sy = sy;
+ sy += link.dy;
});
node.targetLinks.forEach(function(link) {
- link.y1 = y1 + link.width / 2, y1 += link.width;
+ link.ty = ty;
+ ty += link.dy;
});
});
+
+ function ascendingSourceDepth(a, b) {
+ return (a.source.y - b.source.y) || (a.originalIndex - b.originalIndex);
+ }
+
+ function ascendingTargetDepth(a, b) {
+ return (a.target.y - b.target.y) || (a.originalIndex - b.originalIndex);
+ }
+ }
+
+ function center(node) {
+ return node.y + node.dy / 2;
+ }
+
+ function value(link) {
+ return link.value;
}
return sankey;
diff --git a/src/sankeyLinkHorizontal.js b/src/sankeyLinkHorizontal.js
deleted file mode 100644
index 785bb7b..0000000
--- a/src/sankeyLinkHorizontal.js
+++ /dev/null
@@ -1,15 +0,0 @@
-import {linkHorizontal} from "d3-shape";
-
-function horizontalSource(d) {
- return [d.source.x1, d.y0];
-}
-
-function horizontalTarget(d) {
- return [d.target.x0, d.y1];
-}
-
-export default function() {
- return linkHorizontal()
- .source(horizontalSource)
- .target(horizontalTarget);
-}
diff --git a/test/energy-links.json b/test/energy-links.json
deleted file mode 100644
index 52c5cd8..0000000
--- a/test/energy-links.json
+++ /dev/null
@@ -1,1158 +0,0 @@
-[
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 130,
- "dy": 13.3
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 115.8,
- "dy": 41.5
- },
- "dy": 13.3,
- "sy": 0,
- "ty": 19.4
- },
- {
- "source": {
- "x": 135.7,
- "dx": 15,
- "y": 115.8,
- "dy": 41.5
- },
- "target": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "dy": 0.1,
- "sy": 41.4,
- "ty": 0
- },
- {
- "source": {
- "x": 135.7,
- "dx": 15,
- "y": 115.8,
- "dy": 41.5
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 60.9,
- "dy": 93.7
- },
- "dy": 2.9,
- "sy": 0,
- "ty": 84
- },
- {
- "source": {
- "x": 135.7,
- "dx": 15,
- "y": 115.8,
- "dy": 41.5
- },
- "target": {
- "x": 270.4,
- "dx": 15,
- "y": 132.6,
- "dy": 47.7
- },
- "dy": 29.9,
- "sy": 2.9,
- "ty": 0
- },
- {
- "source": {
- "x": 135.7,
- "dx": 15,
- "y": 115.8,
- "dy": 41.5
- },
- "target": {
- "x": 270.4,
- "dx": 15,
- "y": 190.3,
- "dy": 21.8
- },
- "dy": 8.7,
- "sy": 32.8,
- "ty": 0
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 490.3,
- "dy": 3.7
- },
- "target": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "dy": 3.7,
- "sy": 0,
- "ty": 65.3
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 188.1,
- "dy": 3.7
- },
- "target": {
- "x": 270.4,
- "dx": 15,
- "y": 132.6,
- "dy": 47.7
- },
- "dy": 3.7,
- "sy": 0,
- "ty": 35.9
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 218.7,
- "dy": 1.2
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 198.6,
- "dy": 8.1
- },
- "dy": 1.2,
- "sy": 0,
- "ty": 6.8
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 201.8,
- "dy": 6.8
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 198.6,
- "dy": 8.1
- },
- "dy": 6.8,
- "sy": 0,
- "ty": 0
- },
- {
- "source": {
- "x": 135.7,
- "dx": 15,
- "y": 198.6,
- "dy": 8.1
- },
- "target": {
- "x": 270.4,
- "dx": 15,
- "y": 132.6,
- "dy": 47.7
- },
- "dy": 8.1,
- "sy": 0,
- "ty": 39.7
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 258.6,
- "dy": 8.5
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 225.3,
- "dy": 60.7
- },
- "dy": 1.1,
- "sy": 0,
- "ty": 46.6
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 258.6,
- "dy": 8.5
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 356.8,
- "dy": 14.3
- },
- "dy": 2.4,
- "sy": 6.1,
- "ty": 4.4
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 258.6,
- "dy": 8.5
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 295.9,
- "dy": 39.7
- },
- "dy": 4.9,
- "sy": 1.1,
- "ty": 12.1
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 164.5,
- "dy": 11.1
- },
- "dy": 11.1,
- "sy": 6,
- "ty": 0
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 295.9,
- "dy": 39.7
- },
- "dy": 12.1,
- "sy": 76.2,
- "ty": 0
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 674.6,
- "dx": 15,
- "y": 220.7,
- "dy": 2.9
- },
- "dy": 2.9,
- "sy": 36.8,
- "ty": 0
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 225.3,
- "dy": 60.7
- },
- "dy": 36.5,
- "sy": 39.7,
- "ty": 5
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 392.4,
- "dy": 20.7
- },
- "dy": 4,
- "sy": 94,
- "ty": 0
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 345.6,
- "dy": 1.2
- },
- "dy": 0.5,
- "sy": 88.3,
- "ty": 0.1
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 356.8,
- "dy": 14.3
- },
- "dy": 4.4,
- "sy": 88.8,
- "ty": 0
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 60.9,
- "dy": 93.7
- },
- "dy": 6,
- "sy": 0,
- "ty": 86.8
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 381.1,
- "dy": 1.3
- },
- "dy": 0.8,
- "sy": 93.1,
- "ty": 0
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 185.7,
- "dy": 9.6
- },
- "dy": 9.6,
- "sy": 17.2,
- "ty": 0
- },
- {
- "source": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 205.3,
- "dy": 10
- },
- "dy": 10,
- "sy": 26.8,
- "ty": 0
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 248.7,
- "dy": 4.3
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 225.2,
- "dy": 13.1
- },
- "dy": 4.3,
- "sy": 0,
- "ty": 8.8
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 229.9,
- "dy": 8.8
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 225.2,
- "dy": 13.1
- },
- "dy": 8.8,
- "sy": 0,
- "ty": 0
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 190.3,
- "dy": 21.8
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 356.8,
- "dy": 14.3
- },
- "dy": 0,
- "sy": 21.8,
- "ty": 4.4
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 190.3,
- "dy": 21.8
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 60.9,
- "dy": 93.7
- },
- "dy": 0.1,
- "sy": 16.2,
- "ty": 92.9
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 190.3,
- "dy": 21.8
- },
- "target": {
- "x": 405.1,
- "dx": 15,
- "y": 35.4,
- "dy": 148.5
- },
- "dy": 16.2,
- "sy": 0,
- "ty": 132.3
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 190.3,
- "dy": 21.8
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 345.6,
- "dy": 1.2
- },
- "dy": 0.2,
- "sy": 21.5,
- "ty": 0.6
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 190.3,
- "dy": 21.8
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 225.3,
- "dy": 60.7
- },
- "dy": 5.2,
- "sy": 16.4,
- "ty": 41.5
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 315.9,
- "dy": 0.7
- },
- "target": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "dy": 0.7,
- "sy": 0,
- "ty": 88.9
- },
- {
- "source": {
- "x": 674.6,
- "dx": 15,
- "y": 220.7,
- "dy": 2.9
- },
- "target": {
- "x": 809.3,
- "dx": 15,
- "y": 259.9,
- "dy": 2.2
- },
- "dy": 2.2,
- "sy": 0.7,
- "ty": 0
- },
- {
- "source": {
- "x": 674.6,
- "dx": 15,
- "y": 220.7,
- "dy": 2.9
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 60.9,
- "dy": 93.7
- },
- "dy": 0.7,
- "sy": 0,
- "ty": 93
- },
- {
- "source": {
- "x": 809.3,
- "dx": 15,
- "y": 259.9,
- "dy": 2.2
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 392.4,
- "dy": 20.7
- },
- "dy": 2.2,
- "sy": 0,
- "ty": 4
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 326.7,
- "dy": 0.7
- },
- "target": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "dy": 0.7,
- "sy": 0,
- "ty": 96.1
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 225.3,
- "dy": 60.7
- },
- "dy": 12.9,
- "sy": 0,
- "ty": 47.8
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 455.2,
- "dy": 13.7
- },
- "dy": 13.7,
- "sy": 50.3,
- "ty": 0
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 392.4,
- "dy": 20.7
- },
- "dy": 14.5,
- "sy": 13.8,
- "ty": 6.3
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 492.5,
- "dy": 1.5
- },
- "dy": 1.5,
- "sy": 67.5,
- "ty": 0
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 423.2,
- "dy": 22
- },
- "dy": 22,
- "sy": 28.3,
- "ty": 0
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 345.6,
- "dy": 1.2
- },
- "dy": 0.4,
- "sy": 12.9,
- "ty": 0.8
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 478.9,
- "dy": 3.5
- },
- "dy": 3.5,
- "sy": 64,
- "ty": 0
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 381.1,
- "dy": 1.3
- },
- "dy": 0.5,
- "sy": 13.3,
- "ty": 0.8
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 177.6,
- "dy": 0.5
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 115.8,
- "dy": 41.5
- },
- "dy": 0.5,
- "sy": 0,
- "ty": 41
- },
- {
- "source": {
- "x": 135.7,
- "dx": 15,
- "y": 225.2,
- "dy": 13.1
- },
- "target": {
- "x": 270.4,
- "dx": 15,
- "y": 190.3,
- "dy": 21.8
- },
- "dy": 13.1,
- "sy": 0,
- "ty": 8.7
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 1,
- "dy": 89.6
- },
- "target": {
- "x": 405.1,
- "dx": 15,
- "y": 35.4,
- "dy": 148.5
- },
- "dy": 89.6,
- "sy": 0,
- "ty": 0
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 405,
- "dy": 53.8
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 400.4,
- "dy": 65.3
- },
- "dy": 53.8,
- "sy": 0,
- "ty": 0
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 468.8,
- "dy": 11.5
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 400.4,
- "dy": 65.3
- },
- "dy": 11.5,
- "sy": 0,
- "ty": 53.8
- },
- {
- "source": {
- "x": 135.7,
- "dx": 15,
- "y": 400.4,
- "dy": 65.3
- },
- "target": {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- "dy": 65.3,
- "sy": 0,
- "ty": 0.1
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 153.3,
- "dy": 14.3
- },
- "target": {
- "x": 270.4,
- "dx": 15,
- "y": 132.6,
- "dy": 47.7
- },
- "dy": 6,
- "sy": 8.3,
- "ty": 29.9
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 153.3,
- "dy": 14.3
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 115.8,
- "dy": 41.5
- },
- "dy": 8.3,
- "sy": 0,
- "ty": 32.7
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 366.9,
- "dy": 28.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 295.9,
- "dy": 39.7
- },
- "dy": 20.6,
- "sy": 0,
- "ty": 19.1
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 366.9,
- "dy": 28.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 356.8,
- "dy": 14.3
- },
- "dy": 7.5,
- "sy": 20.6,
- "ty": 6.8
- },
- {
- "source": {
- "x": 135.7,
- "dx": 15,
- "y": 323.8,
- "dy": 6.4
- },
- "target": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "dy": 6.4,
- "sy": 0,
- "ty": 89.7
- },
- {
- "source": {
- "x": 135.7,
- "dx": 15,
- "y": 343.7,
- "dy": 2.1
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 295.9,
- "dy": 39.7
- },
- "dy": 2.1,
- "sy": 0,
- "ty": 17.1
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 348.4,
- "dy": 8.4
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 343.7,
- "dy": 2.1
- },
- "dy": 2.1,
- "sy": 6.4,
- "ty": 0
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 348.4,
- "dy": 8.4
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 323.8,
- "dy": 6.4
- },
- "dy": 6.4,
- "sy": 0,
- "ty": 0
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 132.6,
- "dy": 47.7
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 345.6,
- "dy": 1.2
- },
- "dy": 0.1,
- "sy": 47.6,
- "ty": 0
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 132.6,
- "dy": 47.7
- },
- "target": {
- "x": 405.1,
- "dx": 15,
- "y": 35.4,
- "dy": 148.5
- },
- "dy": 42.7,
- "sy": 0,
- "ty": 89.6
- },
- {
- "source": {
- "x": 270.4,
- "dx": 15,
- "y": 132.6,
- "dy": 47.7
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 225.3,
- "dy": 60.7
- },
- "dy": 5,
- "sy": 42.7,
- "ty": 0
- },
- {
- "source": {
- "x": 405.1,
- "dx": 15,
- "y": 35.4,
- "dy": 148.5
- },
- "target": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "dy": 56.1,
- "sy": 84,
- "ty": 0
- },
- {
- "source": {
- "x": 405.1,
- "dx": 15,
- "y": 35.4,
- "dy": 148.5
- },
- "target": {
- "x": 944,
- "dx": 15,
- "y": 60.9,
- "dy": 93.7
- },
- "dy": 84,
- "sy": 0,
- "ty": 0
- },
- {
- "source": {
- "x": 405.1,
- "dx": 15,
- "y": 35.4,
- "dy": 148.5
- },
- "target": {
- "x": 539.9,
- "dx": 15,
- "y": 258.6,
- "dy": 8.5
- },
- "dy": 8.5,
- "sy": 140,
- "ty": 0
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 337.4,
- "dy": 1
- },
- "target": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "dy": 1,
- "sy": 0,
- "ty": 96.8
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 100.6,
- "dy": 19.4
- },
- "target": {
- "x": 135.7,
- "dx": 15,
- "y": 115.8,
- "dy": 41.5
- },
- "dy": 19.4,
- "sy": 0,
- "ty": 0
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 303.9,
- "dy": 2
- },
- "target": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "dy": 2,
- "sy": 0,
- "ty": 86.9
- },
- {
- "source": {
- "x": 1,
- "dx": 15,
- "y": 263,
- "dy": 30.9
- },
- "target": {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- "dy": 30.9,
- "sy": 0,
- "ty": 56.1
- }
-]
diff --git a/test/energy-nodes.json b/test/energy-nodes.json
deleted file mode 100644
index e6ff57a..0000000
--- a/test/energy-nodes.json
+++ /dev/null
@@ -1,290 +0,0 @@
-[
- {
- "x": 1,
- "dx": 15,
- "y": 130,
- "dy": 13.3
- },
- {
- "x": 135.7,
- "dx": 15,
- "y": 115.8,
- "dy": 41.5
- },
- {
- "x": 270.4,
- "dx": 15,
- "y": 394.2,
- "dy": 69.1
- },
- {
- "x": 944,
- "dx": 15,
- "y": 60.9,
- "dy": 93.7
- },
- {
- "x": 270.4,
- "dx": 15,
- "y": 132.6,
- "dy": 47.7
- },
- {
- "x": 270.4,
- "dx": 15,
- "y": 190.3,
- "dy": 21.8
- },
- {
- "x": 1,
- "dx": 15,
- "y": 490.3,
- "dy": 3.7
- },
- {
- "x": 1,
- "dx": 15,
- "y": 188.1,
- "dy": 3.7
- },
- {
- "x": 1,
- "dx": 15,
- "y": 218.7,
- "dy": 1.2
- },
- {
- "x": 135.7,
- "dx": 15,
- "y": 198.6,
- "dy": 8.1
- },
- {
- "x": 1,
- "dx": 15,
- "y": 201.8,
- "dy": 6.8
- },
- {
- "x": 539.9,
- "dx": 15,
- "y": 258.6,
- "dy": 8.5
- },
- {
- "x": 944,
- "dx": 15,
- "y": 225.3,
- "dy": 60.7
- },
- {
- "x": 944,
- "dx": 15,
- "y": 356.8,
- "dy": 14.3
- },
- {
- "x": 944,
- "dx": 15,
- "y": 295.9,
- "dy": 39.7
- },
- {
- "x": 539.9,
- "dx": 15,
- "y": 150.6,
- "dy": 98
- },
- {
- "x": 944,
- "dx": 15,
- "y": 164.5,
- "dy": 11.1
- },
- {
- "x": 674.6,
- "dx": 15,
- "y": 220.7,
- "dy": 2.9
- },
- {
- "x": 944,
- "dx": 15,
- "y": 392.4,
- "dy": 20.7
- },
- {
- "x": 944,
- "dx": 15,
- "y": 345.6,
- "dy": 1.2
- },
- {
- "x": 944,
- "dx": 15,
- "y": 381.1,
- "dy": 1.3
- },
- {
- "x": 944,
- "dx": 15,
- "y": 185.7,
- "dy": 9.6
- },
- {
- "x": 944,
- "dx": 15,
- "y": 205.3,
- "dy": 10
- },
- {
- "x": 1,
- "dx": 15,
- "y": 248.7,
- "dy": 4.3
- },
- {
- "x": 135.7,
- "dx": 15,
- "y": 225.2,
- "dy": 13.1
- },
- {
- "x": 1,
- "dx": 15,
- "y": 229.9,
- "dy": 8.8
- },
- {
- "x": 405.1,
- "dx": 15,
- "y": 35.4,
- "dy": 148.5
- },
- {
- "x": 1,
- "dx": 15,
- "y": 315.9,
- "dy": 0.7
- },
- {
- "x": 809.3,
- "dx": 15,
- "y": 259.9,
- "dy": 2.2
- },
- {
- "x": 1,
- "dx": 15,
- "y": 326.7,
- "dy": 0.7
- },
- {
- "x": 944,
- "dx": 15,
- "y": 455.2,
- "dy": 13.7
- },
- {
- "x": 944,
- "dx": 15,
- "y": 492.5,
- "dy": 1.5
- },
- {
- "x": 944,
- "dx": 15,
- "y": 423.2,
- "dy": 22
- },
- {
- "x": 944,
- "dx": 15,
- "y": 478.9,
- "dy": 3.5
- },
- {
- "x": 1,
- "dx": 15,
- "y": 177.6,
- "dy": 0.5
- },
- {
- "x": 1,
- "dx": 15,
- "y": 1,
- "dy": 89.6
- },
- {
- "x": 1,
- "dx": 15,
- "y": 405,
- "dy": 53.8
- },
- {
- "x": 135.7,
- "dx": 15,
- "y": 400.4,
- "dy": 65.3
- },
- {
- "x": 1,
- "dx": 15,
- "y": 468.8,
- "dy": 11.5
- },
- {
- "x": 1,
- "dx": 15,
- "y": 153.3,
- "dy": 14.3
- },
- {
- "x": 1,
- "dx": 15,
- "y": 366.9,
- "dy": 28.1
- },
- {
- "x": 135.7,
- "dx": 15,
- "y": 323.8,
- "dy": 6.4
- },
- {
- "x": 135.7,
- "dx": 15,
- "y": 343.7,
- "dy": 2.1
- },
- {
- "x": 1,
- "dx": 15,
- "y": 348.4,
- "dy": 8.4
- },
- {
- "x": 1,
- "dx": 15,
- "y": 337.4,
- "dy": 1
- },
- {
- "x": 1,
- "dx": 15,
- "y": 100.6,
- "dy": 19.4
- },
- {
- "x": 1,
- "dx": 15,
- "y": 303.9,
- "dy": 2
- },
- {
- "x": 1,
- "dx": 15,
- "y": 263,
- "dy": 30.9
- }
-]
diff --git a/test/energy.json b/test/energy.json
deleted file mode 100644
index d1819bf..0000000
--- a/test/energy.json
+++ /dev/null
@@ -1 +0,0 @@
-{"nodes":[{"name":"Agricultural 'waste'"},{"name":"Bio-conversion"},{"name":"Liquid"},{"name":"Losses"},{"name":"Solid"},{"name":"Gas"},{"name":"Biofuel imports"},{"name":"Biomass imports"},{"name":"Coal imports"},{"name":"Coal"},{"name":"Coal reserves"},{"name":"District heating"},{"name":"Industry"},{"name":"Heating and cooling - commercial"},{"name":"Heating and cooling - homes"},{"name":"Electricity grid"},{"name":"Over generation / exports"},{"name":"H2 conversion"},{"name":"Road transport"},{"name":"Agriculture"},{"name":"Rail transport"},{"name":"Lighting & appliances - commercial"},{"name":"Lighting & appliances - homes"},{"name":"Gas imports"},{"name":"Ngas"},{"name":"Gas reserves"},{"name":"Thermal generation"},{"name":"Geothermal"},{"name":"H2"},{"name":"Hydro"},{"name":"International shipping"},{"name":"Domestic aviation"},{"name":"International aviation"},{"name":"National navigation"},{"name":"Marine algae"},{"name":"Nuclear"},{"name":"Oil imports"},{"name":"Oil"},{"name":"Oil reserves"},{"name":"Other waste"},{"name":"Pumped heat"},{"name":"Solar PV"},{"name":"Solar Thermal"},{"name":"Solar"},{"name":"Tidal"},{"name":"UK land based bioenergy"},{"name":"Wave"},{"name":"Wind"}],"links":[{"source":0,"target":1,"value":124.729},{"source":1,"target":2,"value":0.597},{"source":1,"target":3,"value":26.862},{"source":1,"target":4,"value":280.322},{"source":1,"target":5,"value":81.144},{"source":6,"target":2,"value":35},{"source":7,"target":4,"value":35},{"source":8,"target":9,"value":11.606},{"source":10,"target":9,"value":63.965},{"source":9,"target":4,"value":75.571},{"source":11,"target":12,"value":10.639},{"source":11,"target":13,"value":22.505},{"source":11,"target":14,"value":46.184},{"source":15,"target":16,"value":104.453},{"source":15,"target":14,"value":113.726},{"source":15,"target":17,"value":27.14},{"source":15,"target":12,"value":342.165},{"source":15,"target":18,"value":37.797},{"source":15,"target":19,"value":4.412},{"source":15,"target":13,"value":40.858},{"source":15,"target":3,"value":56.691},{"source":15,"target":20,"value":7.863},{"source":15,"target":21,"value":90.008},{"source":15,"target":22,"value":93.494},{"source":23,"target":24,"value":40.719},{"source":25,"target":24,"value":82.233},{"source":5,"target":13,"value":0.129},{"source":5,"target":3,"value":1.401},{"source":5,"target":26,"value":151.891},{"source":5,"target":19,"value":2.096},{"source":5,"target":12,"value":48.58},{"source":27,"target":15,"value":7.013},{"source":17,"target":28,"value":20.897},{"source":17,"target":3,"value":6.242},{"source":28,"target":18,"value":20.897},{"source":29,"target":15,"value":6.995},{"source":2,"target":12,"value":121.066},{"source":2,"target":30,"value":128.69},{"source":2,"target":18,"value":135.835},{"source":2,"target":31,"value":14.458},{"source":2,"target":32,"value":206.267},{"source":2,"target":19,"value":3.64},{"source":2,"target":33,"value":33.218},{"source":2,"target":20,"value":4.413},{"source":34,"target":1,"value":4.375},{"source":24,"target":5,"value":122.952},{"source":35,"target":26,"value":839.978},{"source":36,"target":37,"value":504.287},{"source":38,"target":37,"value":107.703},{"source":37,"target":2,"value":611.99},{"source":39,"target":4,"value":56.587},{"source":39,"target":1,"value":77.81},{"source":40,"target":14,"value":193.026},{"source":40,"target":13,"value":70.672},{"source":41,"target":15,"value":59.901},{"source":42,"target":14,"value":19.263},{"source":43,"target":42,"value":19.263},{"source":43,"target":41,"value":59.901},{"source":4,"target":19,"value":0.882},{"source":4,"target":26,"value":400.12},{"source":4,"target":12,"value":46.477},{"source":26,"target":15,"value":525.531},{"source":26,"target":3,"value":787.129},{"source":26,"target":11,"value":79.329},{"source":44,"target":15,"value":9.452},{"source":45,"target":1,"value":182.01},{"source":46,"target":15,"value":19.013},{"source":47,"target":15,"value":289.366}]}
diff --git a/test/graph.json b/test/graph.json
new file mode 100644
index 0000000..eed22e5
--- /dev/null
+++ b/test/graph.json
@@ -0,0 +1,47 @@
+{
+ "nodes": [{
+ "node": 0,
+ "name": "node0"
+ }, {
+ "node": 1,
+ "name": "node1"
+ }, {
+ "node": 2,
+ "name": "node2"
+ }, {
+ "node": 3,
+ "name": "node3"
+ }, {
+ "node": 4,
+ "name": "node4"
+ }],
+ "links": [{
+ "source": 0,
+ "target": 2,
+ "value": 2
+ }, {
+ "source": 1,
+ "target": 2,
+ "value": 2
+ }, {
+ "source": 1,
+ "target": 3,
+ "value": 2
+ }, {
+ "source": 0,
+ "target": 4,
+ "value": 2
+ }, {
+ "source": 2,
+ "target": 3,
+ "value": 2
+ }, {
+ "source": 2,
+ "target": 4,
+ "value": 2
+ }, {
+ "source": 3,
+ "target": 4,
+ "value": 4
+ }]
+}
diff --git a/test/sankey-test.js b/test/sankey-test.js
index ac02f08..2092051 100644
--- a/test/sankey-test.js
+++ b/test/sankey-test.js
@@ -1,33 +1,109 @@
var tape = require("tape"),
- d3 = require("../");
+ sankey = require("../");
-tape("sankey(energy) returns the expected results", function(test) {
- var sankey = d3.sankey().nodeWidth(15).nodePadding(10).extent([[1, 1], [959, 494]]),
- energy = sankey(require("./energy"));
- test.deepEqual(energy.nodes.map(nodePosition), require("./energy-nodes"));
- test.deepEqual(energy.links.map(linkPosition), require("./energy-links"));
+// Hack to import values from graph.json without doing all sorts of crazy
+// script-fu
+var graph = {
+ "nodes": [{
+ "node": 0,
+ "name": "node0"
+ }, {
+ "node": 1,
+ "name": "node1"
+ }, {
+ "node": 2,
+ "name": "node2"
+ }, {
+ "node": 3,
+ "name": "node3"
+ }, {
+ "node": 4,
+ "name": "node4"
+ }],
+ "links": [{
+ "source": 0,
+ "target": 2,
+ "value": 2
+ }, {
+ "source": 1,
+ "target": 2,
+ "value": 2
+ }, {
+ "source": 1,
+ "target": 3,
+ "value": 2
+ }, {
+ "source": 0,
+ "target": 4,
+ "value": 2
+ }, {
+ "source": 2,
+ "target": 3,
+ "value": 2
+ }, {
+ "source": 2,
+ "target": 4,
+ "value": 2
+ }, {
+ "source": 3,
+ "target": 4,
+ "value": 4
+ }]
+}
+
+var margin = {
+ top: 10,
+ right: 10,
+ bottom: 10,
+ left: 10
+ },
+ width = 1200 - margin.left - margin.right,
+ height = 740 - margin.top - margin.bottom;
+
+var s = sankey.sankey()
+ .nodeWidth(36)
+ .nodePadding(10)
+ .nodes(graph.nodes)
+ .links(graph.links)
+ .size([width, height])
+ .layout(32);
+
+tape("Testing nodeWidth", function(test) {
+ test.plan(1);
+ test.equal(s.nodeWidth(), 36);
test.end();
});
-function nodePosition(node) {
- return {
- x: round(node.x0),
- dx: round(node.x1 - node.x0),
- y: round(node.y0),
- dy: round(node.y1 - node.y0)
- };
-}
+tape("Testing nodePadding", function(test) {
+ test.plan(1);
+ test.equal(s.nodePadding(), 10);
+ test.end();
+});
-function linkPosition(link) {
- return {
- source: nodePosition(link.source),
- target: nodePosition(link.target),
- dy: round(link.width),
- sy: round(link.y0 - link.source.y0 - link.width / 2),
- ty: round(link.y1 - link.target.y0 - link.width / 2)
- };
-}
+tape("Testing nodes", function(test) {
+ test.plan(1);
-function round(x) {
- return Math.round(x * 10) / 10;
-}
+ var node_names = s.nodes().map(function(obj) {
+ return obj.name
+ });
+ test.deepEqual(node_names, ['node0', 'node1', 'node2', 'node3', 'node4'])
+
+ test.end();
+});
+
+tape("Testing links", function(test) {
+ test.plan(1);
+
+ var link_widths = s.links().map(function(obj) {
+ return obj.dy
+ });
+ test.deepEqual(link_widths, [177.5, 177.5, 177.5, 177.5, 177.5, 177.5, 355])
+
+ test.end();
+});
+
+tape("Testing size", function(test) {
+ test.plan(1);
+ test.deepEqual(s.size(), [1180, 720]);
+ test.end();
+});