diff --git a/.eslintrc b/.eslintrc
index 551cc59..affd3f6 100644
--- a/.eslintrc
+++ b/.eslintrc
@@ -1,12 +1,5 @@
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 b6c0be9..3ea3f26 100644
--- a/README.md
+++ b/README.md
@@ -1,15 +1,20 @@
# d3-sankey
-D3 4.0 implementation of the Sankey plugin to visualize the flow between nodes in a directed acyclic network.
+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:
-## Installing
+[
](https://bl.ocks.org/mbostock/ca9a0bb7ba204d12974bca90acc507c0)
+
+Source: Department of Energy & Climate Change, Tom Counsell.
-If you use NPM, `npm install d3-sankey`. Otherwise, download the [latest release](https://github.com/d3/d3-sankey/releases/latest).
+## Installing
-You can also load directly from unpkg.
+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:
-```javascript
-
+```html
+
+
+
+
```
-## Demo
-Here is Mike Bostock's famous example [recreated with d3-sankey](http://bl.ocks.org/xaranke/9ada4c74a87b57ae7308).
+## API Reference
-Clone or download the block, then run `npm install` and `npm run build` to create `d3.min.js`.
+# d3.sankey() [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L52 "Source")
-## API Reference
+Constructs a new Sankey generator with the default settings.
+
+# sankey(arguments…) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L62 "Source")
+
+Computes the node and link positions for the given *arguments*, returning a *graph* representing the Sankey layout. The returned *graph* has the following properties:
+
+* *graph*.nodes - the array of [nodes](#sankey_nodes)
+* *graph*.links - the array of [links](#sankey_links)
+
+# sankey.update(graph) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L72 "Source")
+
+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.
+
+# 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:
+
+```js
+function nodes(graph) {
+ return graph.nodes;
+}
+```
+
+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):
-# 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)
-Constructs a new sankey generator with the default settings.
+See also [*sankey*.links](#sankey_links).
-# sankey.nodeWidth([width])
+# sankey.links([links]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L97 "Source")
-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:
+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:
```js
-function nodeWidth() {
- return 24;
+function links(graph) {
+ return graph.links;
}
```
-# sankey.nodePadding([padding])
+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
-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:
+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:
```js
-function nodePadding() {
- return 8;
+function id(d) {
+ return d.index;
}
```
-Here padding refers to the vertical space between nodes that occupy the same horizontal space.
-# sankey.nodes([nodes])
+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:
-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
+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:
```js
-function nodes() {
- return [];
+function id(d) {
+ return d.id;
}
```
-# sankey.links([links])
+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.nodeWidth([width]) [<>](https://github.com/d3/d3-sankey/blob/master/src/sankey.js#L85 "Source")
+
+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.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")
+
+
-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:
+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 links() {
- return [];
+function source(d) {
+ return [d.source.x1, d.y0];
}
```
-# sankey.layout([iterations])
+The [target accessor](https://github.com/d3/d3-shape/blob/master/README.md#link_target) is defined as:
-Returns the current accessor to the SVG layout object. Here iterations is the number of times the converging function computeNodeDepths is run.
+```js
+function target(d) {
+ return [d.target.x0, d.y1];
+}
+```
-# sankey.relayout()
+For example, to render the links of a Sankey diagram in SVG, you might say:
-Similar to layout but only recalculates the depth of links. Primarily used when a node is moved vertically.
+```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; });
+```
diff --git a/build/d3-sankey.js b/build/d3-sankey.js
index 2155cfa..8e9684d 100644
--- a/build/d3-sankey.js
+++ b/build/d3-sankey.js
@@ -1,117 +1,169 @@
-// https://github.com/d3/d3-sankey Version 0.5.0. Copyright 2018 Mike Bostock.
+// https://github.com/d3/d3-sankey Version 0.7.1. Copyright 2019 Mike Bostock.
(function (global, 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) :
+ 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) :
(factory((global.d3 = global.d3 || {}),global.d3,global.d3,global.d3));
-}(this, (function (exports,d3Array,d3Collection,d3Interpolate) { 'use strict';
+}(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;
+}
var sankey = function() {
- var sankey = {},
- nodeWidth = 24,
- nodePadding = 8,
- size = [1, 1],
- nodes = [],
- links = [],
+ 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,
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(_) {
- if (!arguments.length) return nodeWidth;
- nodeWidth = +_;
- return sankey;
+ return arguments.length ? (dx = +_, sankey) : dx;
};
sankey.nodePadding = function(_) {
- if (!arguments.length) return nodePadding;
- nodePadding = +_;
- return sankey;
+ return arguments.length ? (py = +_, sankey) : py;
};
sankey.nodes = function(_) {
- if (!arguments.length) return nodes;
- nodes = _;
- return sankey;
+ return arguments.length ? (nodes = typeof _ === "function" ? _ : constant(_), sankey) : nodes;
};
sankey.links = function(_) {
- if (!arguments.length) return links;
- links = _;
- return sankey;
+ return arguments.length ? (links = typeof _ === "function" ? _ : constant(_), sankey) : links;
};
sankey.size = function(_) {
- if (!arguments.length) return size;
- size = _;
- return sankey;
- };
-
- sankey.layout = function(iterations) {
- computeNodeLinks();
- computeNodeValues();
- computeNodeBreadths();
- computeNodeDepths(iterations);
- computeLinkDepths();
- return sankey;
+ return arguments.length ? (x0 = y0 = 0, x1 = +_[0], y1 = +_[1], sankey) : [x1 - x0, y1 - y0];
};
- sankey.relayout = function() {
- computeLinkDepths();
- 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.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;
+ sankey.iterations = function(_) {
+ return arguments.length ? (iterations = +_, sankey) : iterations;
};
// Populate the sourceLinks and targetLinks for each node.
// Also, if the source and target are not objects, assume they are indices.
- function computeNodeLinks() {
- nodes.forEach(function(node) {
+ function computeNodeLinks(graph) {
+ graph.nodes.forEach(function(node, i) {
+ node.index = i;
node.sourceLinks = [];
node.targetLinks = [];
});
- 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;
+
+ 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);
source.sourceLinks.push(link);
target.targetLinks.push(link);
});
}
// Compute the value (size) of each node by summing the associated links.
- function computeNodeValues() {
- nodes.forEach(function(node) {
+ function computeNodeValues(graph) {
+ graph.nodes.forEach(function(node) {
node.value = Math.max(
d3Array.sum(node.sourceLinks, value),
d3Array.sum(node.targetLinks, value)
@@ -119,202 +171,174 @@ var sankey = function() {
});
}
- // 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;
+ // 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;
node.sourceLinks.forEach(function(link) {
- if (nextNodes.indexOf(link.target) < 0) {
- nextNodes.push(link.target);
+ if (next.indexOf(link.target) < 0) {
+ next.push(link.target);
}
});
});
- remainingNodes = nextNodes;
- ++x;
}
- //
- 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;
- }
- });
- }
+ 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);
+ }
+ });
+ });
+ }
- function scaleNodeBreadths(kx) {
- nodes.forEach(function(node) {
- node.x *= kx;
+ 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 computeNodeDepths(iterations) {
- var nodesByBreadth = d3Collection.nest()
- .key(function(d) { return d.x; })
+ function computeNodeBreadths(graph) {
+ var columns = d3Collection.nest()
+ .key(function(d) { return d.x0; })
.sortKeys(d3Array.ascending)
- .entries(nodes)
+ .entries(graph.nodes)
.map(function(d) { return d.values; });
//
- initializeNodeDepth();
+ initializeNodeBreadth();
resolveCollisions();
- for (var alpha = 1; iterations > 0; --iterations) {
- relaxRightToLeft(alpha *= .99);
+ for (var alpha = 1, n = iterations; n > 0; --n) {
+ relaxRightToLeft(alpha *= 0.99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
- function initializeNodeDepth() {
- var L = d3Array.max(nodesByBreadth, function(nodes) {
+ function initializeNodeBreadth() {
+ var L = d3Array.max(columns, function(nodes) {
return nodes.length;
});
- 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);
+ 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);
});
- nodesByBreadth.forEach(function(nodes) {
+ columns.forEach(function(nodes) {
nodes.forEach(function(node, i) {
- node.y = i;
- node.dy = node.value * ky;
+ node.y1 = (node.y0 = i) + node.value * ky;
});
});
- links.forEach(function(link) {
- link.dy = link.value * ky;
+ graph.links.forEach(function(link) {
+ link.width = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
- nodesByBreadth.forEach(function(nodes) {
+ columns.forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
- var y = d3Array.sum(node.targetLinks, weightedSource) / d3Array.sum(node.targetLinks, value);
- node.y += (y - center(node)) * alpha;
+ var dy = (d3Array.sum(node.targetLinks, weightedSource) / d3Array.sum(node.targetLinks, value) - nodeCenter(node)) * alpha;
+ node.y0 += dy, node.y1 += dy;
}
});
});
-
- function weightedSource(link) {
- return center(link.source) * link.value;
- }
}
function relaxRightToLeft(alpha) {
- nodesByBreadth.slice().reverse().forEach(function(nodes) {
+ columns.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
- var y = d3Array.sum(node.sourceLinks, weightedTarget) / d3Array.sum(node.sourceLinks, value);
- node.y += (y - center(node)) * alpha;
+ var dy = (d3Array.sum(node.sourceLinks, weightedTarget) / d3Array.sum(node.sourceLinks, value) - nodeCenter(node)) * alpha;
+ node.y0 += dy, node.y1 += dy;
}
});
});
-
- function weightedTarget(link) {
- return center(link.target) * link.value;
- }
}
function resolveCollisions() {
- nodesByBreadth.forEach(function(nodes) {
+ columns.forEach(function(nodes) {
var node,
dy,
- y0 = 0,
+ y = y0,
n = nodes.length,
i;
// Push any overlapping nodes down.
- nodes.sort(ascendingDepth);
+ nodes.sort(ascendingBreadth);
for (i = 0; i < n; ++i) {
node = nodes[i];
- dy = y0 - node.y;
- if (dy > 0) node.y += dy;
- y0 = node.y + node.dy + nodePadding;
+ dy = y - node.y0;
+ if (dy > 0) node.y0 += dy, node.y1 += dy;
+ y = node.y1 + py;
}
// If the bottommost node goes outside the bounds, push it back up.
- dy = y0 - nodePadding - size[1];
+ dy = y - py - y1;
if (dy > 0) {
- y0 = node.y -= dy;
+ y = (node.y0 -= dy), node.y1 -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
- dy = node.y + node.dy + nodePadding - y0;
- if (dy > 0) node.y -= dy;
- y0 = node.y;
+ dy = node.y1 + py - y;
+ if (dy > 0) node.y0 -= dy, node.y1 -= dy;
+ y = node.y0;
}
}
});
}
-
- function ascendingDepth(a, b) {
- return a.y - b.y;
- }
}
- function computeLinkDepths() {
- nodes.forEach(function(node) {
- node.sourceLinks.sort(ascendingTargetDepth);
- node.targetLinks.sort(ascendingSourceDepth);
+ function computeLinkBreadths(graph) {
+ graph.nodes.forEach(function(node) {
+ node.sourceLinks.sort(ascendingTargetBreadth);
+ node.targetLinks.sort(ascendingSourceBreadth);
});
- nodes.forEach(function(node) {
- var sy = 0, ty = 0;
+ graph.nodes.forEach(function(node) {
+ var y0 = node.y0, y1 = y0;
node.sourceLinks.forEach(function(link) {
- link.sy = sy;
- sy += link.dy;
+ link.y0 = y0 + link.width / 2, y0 += link.width;
});
node.targetLinks.forEach(function(link) {
- link.ty = ty;
- ty += link.dy;
+ link.y1 = y1 + link.width / 2, y1 += link.width;
});
});
-
- 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;
- }
+ return sankey;
+};
- function value(link) {
- return link.value;
- }
+function horizontalSource(d) {
+ return [d.source.x1, d.y0];
+}
- return sankey;
+function horizontalTarget(d) {
+ return [d.target.x0, d.y1];
+}
+
+var sankeyLinkHorizontal = function() {
+ return d3Shape.linkHorizontal()
+ .source(horizontalSource)
+ .target(horizontalTarget);
};
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 9f8aa0f..5d87e18 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.5.0. Copyright 2018 Mike Bostock.
-!function(n,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports,require("d3-array"),require("d3-collection"),require("d3-interpolate")):"function"==typeof define&&define.amd?define(["exports","d3-array","d3-collection","d3-interpolate"],t):t(n.d3=n.d3||{},n.d3,n.d3,n.d3)}(this,function(n,t,r,e){"use strict";var o=function(){function n(){v.forEach(function(n){n.sourceLinks=[],n.targetLinks=[]}),k.forEach(function(n,t){var r=n.source,e=n.target;"number"==typeof r&&(r=n.source=v[n.source]),"number"==typeof e&&(e=n.target=v[n.target]),n.originalIndex=t,r.sourceLinks.push(n),e.targetLinks.push(n)})}function o(){v.forEach(function(n){n.value=Math.max(t.sum(n.sourceLinks,y),t.sum(n.targetLinks,y))})}function u(){for(var n,t=v,r=0;t.length;)n=[],t.forEach(function(t){t.x=r,t.dx=l,t.sourceLinks.forEach(function(t){n.indexOf(t.target)<0&&n.push(t.target)})}),t=n,++r;i(r),c((h[0]-l)/(r-1))}function i(n){v.forEach(function(t){t.sourceLinks.length||(t.x=n-1)})}function c(n){v.forEach(function(t){t.x*=n})}function f(n){function e(){u.forEach(function(n){var t,r,e,u=0,i=n.length;for(n.sort(o),e=0;e0&&(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
+// 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);t 0; --iterations) {
- relaxRightToLeft(alpha *= .99);
+ for (var alpha = 1, n = iterations; n > 0; --n) {
+ relaxRightToLeft(alpha *= 0.99);
resolveCollisions();
relaxLeftToRight(alpha);
resolveCollisions();
}
- function initializeNodeDepth() {
- var L = max(nodesByBreadth, function(nodes) {
+ function initializeNodeBreadth() {
+ var L = max(columns, function(nodes) {
return nodes.length;
});
- 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);
+ 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);
});
- nodesByBreadth.forEach(function(nodes) {
+ columns.forEach(function(nodes) {
nodes.forEach(function(node, i) {
- node.y = i;
- node.dy = node.value * ky;
+ node.y1 = (node.y0 = i) + node.value * ky;
});
});
- links.forEach(function(link) {
- link.dy = link.value * ky;
+ graph.links.forEach(function(link) {
+ link.width = link.value * ky;
});
}
function relaxLeftToRight(alpha) {
- nodesByBreadth.forEach(function(nodes) {
+ columns.forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.targetLinks.length) {
- var y = sum(node.targetLinks, weightedSource) / sum(node.targetLinks, value);
- node.y += (y - center(node)) * alpha;
+ var dy = (sum(node.targetLinks, weightedSource) / sum(node.targetLinks, value) - nodeCenter(node)) * alpha;
+ node.y0 += dy, node.y1 += dy;
}
});
});
-
- function weightedSource(link) {
- return center(link.source) * link.value;
- }
}
function relaxRightToLeft(alpha) {
- nodesByBreadth.slice().reverse().forEach(function(nodes) {
+ columns.slice().reverse().forEach(function(nodes) {
nodes.forEach(function(node) {
if (node.sourceLinks.length) {
- var y = sum(node.sourceLinks, weightedTarget) / sum(node.sourceLinks, value);
- node.y += (y - center(node)) * alpha;
+ var dy = (sum(node.sourceLinks, weightedTarget) / sum(node.sourceLinks, value) - nodeCenter(node)) * alpha;
+ node.y0 += dy, node.y1 += dy;
}
});
});
-
- function weightedTarget(link) {
- return center(link.target) * link.value;
- }
}
function resolveCollisions() {
- nodesByBreadth.forEach(function(nodes) {
+ columns.forEach(function(nodes) {
var node,
dy,
- y0 = 0,
+ y = y0,
n = nodes.length,
i;
// Push any overlapping nodes down.
- nodes.sort(ascendingDepth);
+ nodes.sort(ascendingBreadth);
for (i = 0; i < n; ++i) {
node = nodes[i];
- dy = y0 - node.y;
- if (dy > 0) node.y += dy;
- y0 = node.y + node.dy + nodePadding;
+ dy = y - node.y0;
+ if (dy > 0) node.y0 += dy, node.y1 += dy;
+ y = node.y1 + py;
}
// If the bottommost node goes outside the bounds, push it back up.
- dy = y0 - nodePadding - size[1];
+ dy = y - py - y1;
if (dy > 0) {
- y0 = node.y -= dy;
+ y = (node.y0 -= dy), node.y1 -= dy;
// Push any overlapping nodes back up.
for (i = n - 2; i >= 0; --i) {
node = nodes[i];
- dy = node.y + node.dy + nodePadding - y0;
- if (dy > 0) node.y -= dy;
- y0 = node.y;
+ dy = node.y1 + py - y;
+ if (dy > 0) node.y0 -= dy, node.y1 -= dy;
+ y = node.y0;
}
}
});
}
-
- function ascendingDepth(a, b) {
- return a.y - b.y;
- }
}
- function computeLinkDepths() {
- nodes.forEach(function(node) {
- node.sourceLinks.sort(ascendingTargetDepth);
- node.targetLinks.sort(ascendingSourceDepth);
+ function computeLinkBreadths(graph) {
+ graph.nodes.forEach(function(node) {
+ node.sourceLinks.sort(ascendingTargetBreadth);
+ node.targetLinks.sort(ascendingSourceBreadth);
});
- nodes.forEach(function(node) {
- var sy = 0, ty = 0;
+ graph.nodes.forEach(function(node) {
+ var y0 = node.y0, y1 = y0;
node.sourceLinks.forEach(function(link) {
- link.sy = sy;
- sy += link.dy;
+ link.y0 = y0 + link.width / 2, y0 += link.width;
});
node.targetLinks.forEach(function(link) {
- link.ty = ty;
- ty += link.dy;
+ link.y1 = y1 + link.width / 2, y1 += link.width;
});
});
-
- 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
new file mode 100644
index 0000000..785bb7b
--- /dev/null
+++ b/src/sankeyLinkHorizontal.js
@@ -0,0 +1,15 @@
+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
new file mode 100644
index 0000000..52c5cd8
--- /dev/null
+++ b/test/energy-links.json
@@ -0,0 +1,1158 @@
+[
+ {
+ "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
new file mode 100644
index 0000000..e6ff57a
--- /dev/null
+++ b/test/energy-nodes.json
@@ -0,0 +1,290 @@
+[
+ {
+ "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
new file mode 100644
index 0000000..d1819bf
--- /dev/null
+++ b/test/energy.json
@@ -0,0 +1 @@
+{"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
deleted file mode 100644
index eed22e5..0000000
--- a/test/graph.json
+++ /dev/null
@@ -1,47 +0,0 @@
-{
- "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 2092051..ac02f08 100644
--- a/test/sankey-test.js
+++ b/test/sankey-test.js
@@ -1,109 +1,33 @@
var tape = require("tape"),
- sankey = require("../");
-
-// 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();
-});
-
-tape("Testing nodePadding", function(test) {
- test.plan(1);
- test.equal(s.nodePadding(), 10);
- test.end();
-});
-
-tape("Testing nodes", function(test) {
- test.plan(1);
-
- var node_names = s.nodes().map(function(obj) {
- return obj.name
- });
- test.deepEqual(node_names, ['node0', 'node1', 'node2', 'node3', 'node4'])
+ d3 = 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"));
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])
+function nodePosition(node) {
+ return {
+ x: round(node.x0),
+ dx: round(node.x1 - node.x0),
+ y: round(node.y0),
+ dy: round(node.y1 - node.y0)
+ };
+}
- 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 size", function(test) {
- test.plan(1);
- test.deepEqual(s.size(), [1180, 720]);
- test.end();
-});
+function round(x) {
+ return Math.round(x * 10) / 10;
+}