# d3-sankey-diagram

[![Build Status](https://travis-ci.org/ricklupton/d3-sankey-diagram.svg?branch=master)](https://travis-ci.org/ricklupton/d3-sankey-diagram)

[Sankey diagrams](https://en.wikipedia.org/wiki/Sankey_diagram) show flows between processes or relationships between sets. This library, is a [reusable d3 diagram](https://bost.ocks.org/mike/chart/) featuring:
- automatic layout
- multiple types of flow
- loops / reversed flows
- flow routing across layers

See the **[demo](https://ricklupton.github.io/d3-sankey-diagram)** for examples of these.

d3-sankey-diagram versions v0.6 and up are based on d3 v4.

## Installation

Install using npm if you are using browserify or the like:
```js
npm install d3-sankey-diagram
```

Or download the [standalone bundle](https://github.com/ricklupton/d3-sankey-diagram/releases/latest) and include in your page as
```html
<script src="d3-sankey-diagram.js" charset="utf-8"></script>
```

## Usage

```js
var layout = d3.sankey()
               .extent([[100, 10], [840, 580]]);

var diagram = d3.sankeyDiagram()
                .linkColor(function(d) { return d.color; });

d3.json('uk_energy.json', function(energy) {
  layout.ordering(energy.order);
  d3.select('#sankey')
      .datum(layout(energy))
      .call(diagram);
});
```

Try more [live examples](https://ricklupton.github.io/d3-sankey-diagram).

If you use the Jupyter notebook, try
[ipysankeywidget](https://github.com/ricklupton/ipysankeywidget).

`d3-sankey-diagram` works both in node (using jsdom) and in the browser.

## Contributing 🎁

Thanks for your interest in contributing! To get started see [CONTRIBUTING.md](CONTRIBUTING.md) and our [code of
conduct](CODE_OF_CONDUCT.md). We have a [Roadmap](https://github.com/ricklupton/d3-sankey-diagram/projects/1) showing what we
are working on, and you can browse the list of [good first issues](https://github.com/ricklupton/d3-sankey-diagram/labels/good%20first%20bug) for ideas.

## Documentation
d3-sankey-diagram is a JavaScript library for creating [Sankey diagrams](https://en.wikipedia.org/wiki/Sankey_diagram) using [d3](https://d3js.org/). See the [live examples](https://ricklupton.github.io/d3-sankey-diagram) to get an idea of what it does.

The main high-level components:
- [d3.sankey](#sankey): Sankey layout
- [d3.sankeyDiagram](#sankeyDiagram): SVG Sankey diagram component

Lower-level components:
- [d3.sankeyNode](#sankeyNode): SVG Sankey node component
- [d3.sankeyLink](#sankeyLink): SVG path generator for Sankey diagram links
- [d3.sankeyLinkTitle](#sankeyLinkTitle): Helper function to generate link titles

### Sankey layout

<a name="sankey" href="#sankey">#</a> <b>sankey</b>()

Creates a new Sankey layout component.

<a name="layout" href="#layout">#</a> <b>layout</b>(<i>arguments...</i>)

Apply the layout to the given <i>arguments</i>. The <i>arguments</i> are
arbitrary; they are simply propagated to the accessor functions
[nodes](#layout-nodes) and [links](#layout-links).

```js
var layout = d3.sankey();

var graph = layout({
  nodes: [
    {"id": "a", "title": "Source"},
    {"id": "b", "title": "Stage 1"}
  ],
  links: [
    {"source": "a", "target": "b", "type": "x", "value": 2}
  ]
});
// Resulting graph object can be used as datum for d3.sankeyDiagram() below
```

#### Data accessors

<a name="layout-nodes" href="#layout-nodes">#</a> layout.<b>nodes</b>([<i>nodes</i>])

If *nodes* is specified, sets the nodes accessor to the specified function and
returns this layout. If *nodes* is not specified, return the current accessor, which defaults to:
```js
function nodes(graph) {
  return graph.nodes;
}
```

<a name="layout-links" href="#layout-links">#</a> layout.<b>links</b>([<i>links</i>])

If *links* is specified, sets the links accessor to the specified function and
returns this layout. If *links* is not specified, return the current accessor, which defaults to:
```js
function links(graph) {
  return graph.links;
}
```

<a name="layout-nodeId" href="#layout-nodeId">#</a> layout.<b>nodeId</b>([<i>nodeId</i>])

If *nodeId* is specified, sets the node id accessor to the specified function and
returns this layout. If *nodeId* is not specified, return the current accessor, which defaults to:
```js
function nodeId(d) {
  return d.id;
}
```

<a name="layout-sourceId" href="#layout-sourceId">#</a> layout.<b>sourceId</b>([<i>sourceId</i>])<br/>
<a name="layout-targetId" href="#layout-targetId">#</a> layout.<b>targetId</b>([<i>targetId</i>])

If *sourceId*/*targetId* is specified, sets the link source/target id accessor to the specified function and
returns this layout. If *sourceId*/*targetId* is not specified, return the current accessor, which defaults to:
```js
function sourceId (d) {
  return {
    id: typeof d.source === 'object' ? d.source.id : d.source,
    port: typeof d.sourcePort === 'object' ? d.sourcePort.id : d.sourcePort
  }
}
// similarly for targetId
```

See below for more discussion of ports. If this accessor returns a string, it is
interpreted as the node id and the port is set to `undefined`.

<a name="layout-nodeBackwards" href="#layout-nodeBackwards">#</a> layout.<b>nodeBackwards</b>([<i>nodeBackwards</i>])

If *nodeBackwards* is specified, sets the node direction accessor to the specified function and
returns this layout. If *nodeBackwards* is not specified, return the current accessor, which defaults to:
```js
function nodeBackwards(d) {
  return d.direction && d.direction.toLowerCase() === 'l';
}
```

<a name="layout-linkValue" href="#layout-linkValue">#</a> layout.<b>linkValue</b>([<i>linkValue</i>])

If *linkValue* is specified, sets the link value accessor to the specified function and
returns this layout. If *linkValue* is not specified, return the current accessor, which defaults to:
```js
function linkValue(d) {
  return d.value;
}
```

<a name="layout-linkType" href="#layout-linkType">#</a>
layout.<b>linkType</b>([<i>linkType</i>])

If *linkType* is specified, sets the link type accessor to the specified function and
returns this layout. If *linkType* is not specified, return the current accessor, which defaults to:
```js
function linkType(d) {
  return d.type;
}
```

#### Adjusting layout

<a name="layout-ordering" href="#layout-ordering">#</a> layout.<b>ordering</b>([<i>ordering</i>])

If *ordering* is specified, sets the node ordering to the specified value and
returns this layout. If *ordering* is not specified, return the current value, which defaults to `null`. 

When *ordering* is null, the node ordering will be calculated automatically.

When *ordering* is specified, it is used directly and no rank assignment or
ordering algorithm takes place. The *ordering* structure has three nested lists:
*ordering* is a list of layers, each of which is a list of bands, each of which is
a list of node ids. For example,
```js
[
  [ ["layer 1 band 1"], ["layer 1 band 2"] ],
  [ ["layer 2 band 1"], ["layer 2 band 2"] ],
  ...
]
```

<a name="layout-rankSets" href="#layout-rankSets">#</a> layout.<b>rankSets</b>([<i>rankSets</i>])

If *rankSets* is specified, sets the rank sets to the specified value and
returns this layout. If *rankSets* is not specified, return the current value,
which defaults to `[]`.

Rank sets are optional constraints to keep nodes in the same layer. Each entry
has the form
```js
{
    type: 'same|min',   // optional, default 'min'
    nodes: [node ids],  // required
}
```

<a name="layout-sortPorts" href="#layout-sortPorts">#</a> layout.<b>sortPorts</b>([<i>sortPorts</i>])

If *sortPorts* is specified, sets the port sorting function to the specified
function and returns this layout. If *sortPorts* is not specified, return the
current value, which defaults to:
```js
function sortPorts(a, b) {
  return a.id.localeCompare(b.id)
}
```

**Note**: in a future version this may be changed to sort ports to avoid
crossings by default.

#### Dimensions

<a name="layout-extent" href="#layout-extent">#</a> layout.<b>extent</b>([<i>extent</i>])

If *extent* is specified, sets this layout’s extent to the specified array of
points [[*x0*, *y0*], [*x1*, *y1*]], where [*x0*, *y0*] is the top-left corner
and [*x1*, *y1*] is the bottom-right corner, and returns this tile layout. If
*extent* is not specified, returns the current layout extent.

<a name="layout-size" href="#layout-size">#</a> layout.<b>size</b>([<i>size</i>])

If *size* is specified, sets this layout’s size to the specified two-element
array of numbers [*width*, *height*] and returns this layout. If *size* is not
specified, returns the current layout size. This is a convenience method
equivalent to setting the [extent](#layout-extent) to [[0, 0], [*width*,
*height*]].

<a name="layout-scale" href="#layout-scale">#</a> diagram.<b>scale</b>([<i>scale</i>])

If *scale* is specified as a number, sets the layout's scale (from data units to
pixels). If *scale* is `null`, the scale will be reset and automatically
calculated the next time the diagram is called, to achieve the desired
whitespace fraction (below). If *scale* is not specified, return the current
scale.

<a name="layout-whitespace" href="#layout-whitespace">#</a> diagram.<b>whitespace</b>([<i>whitespace</i>])

If *whitespace* is specified as a number, sets the layout's whitespace fraction,
used when automatically calculating a scale (above). If *whitespace* is not
specified, return the current whitespace.

#### Manual positioning

<a name="layout-nodePosition" href="#layout-nodePosition">#</a> diagram.<b>nodePosition</b>([<i>nodePosition</i>])

If *nodePosition* is specified, use the specified function to directly set node
positions, bypassing the layout algorithm (link positions and shapes are still
calculated). If *nodePosition* is not specified, return the current
function, which defaults to `null`.

### SVG Sankey diagram component

<a name="sankeyDiagram" href="#sankeyDiagram">#</a> <b>sankeyDiagram</b>()

Creates a new Sankey diagram component.

<a name="diagram" href="#diagram">#</a> <b>diagram</b>(<i>selection</i>)

Apply the diagram to a selection, which should be an `svg` element.

```js
var diagram = d3.sankeyDiagram();
d3.select('#sankey')
    .datum(sankey)
    .call(diagram);
```

The Sankey data is taken from the selection's bound data, which should be a
graph object, as generated by [d3.sankey](#sankey).

#### Dimensions

<a name="margin" href="#margin">#</a> diagram.<b>margin</b>({ [<i>top</i>], [<i>right</i>], [<i>bottom</i>], [<i>left</i>] })

If called with an argument, set the margins of the diagram, otherwise return the
current value.

#### Titles

<a name="nodeTitle" href="#nodeTitle">#</a> diagram.<b>nodeTitle</b>([<i>nodeTitle</i>])

If called with an argument, set the node title to the specified function,
otherwise return the current function, which defaults to:
```js
function nodeTitle(d) {
  return d.title !== undefined ? d.title : d.id;
}
```

<a name="nodeValue" href="#nodeValue">#</a> diagram.<b>nodeValue</b>([<i>nodeValue</i>])

If called with an argument, set the node value getter to the specified function,
otherwise return the current function, which defaults to:
```js
function nodeValue(d) {
  return null;
}
```

The node value is shown with an SVG text element within the node body, only when
the `nodeWidth` is greater than zero.

<a name="linkTitle" href="#linkTitle">#</a>
diagram.<b>linkTitle</b>([<i>linkTitle</i>])

If called with an argument, set the link title to the specified function,
otherwise return the current function, which defaults to:
```js
const fmt = d3.format('.3s')
function linkTitle(d) {
  const parts = []
  const sourceTitle = nodeTitle(d.source)
  const targetTitle = nodeTitle(d.target)
  const matTitle = d.type

  parts.push(`${sourceTitle} → ${targetTitle}`)
  if (matTitle) parts.push(matTitle)
  parts.push(fmt(d.value))
  return parts.join('\n')
}
```

The link title is displayed in an SVG `title` element, visible on hover.

To make it easier to customise this function to your data, you can use
`d3.sankeyLinkTitle` to generate new functions:

<a name="sankeyLinkTitle" href="#sankeyLinkTitle">#</a> <b>sankeyLinkTitle</b>(nodeTitle, typeTitle, fmt)

Generates a function similar to the one above, with custom accessors for the
node title `nodeTitle`, link-type title `typeTitle` and number format `fmt`.

<a name="linkLabel" href="#linkLabel">#</a> diagram.<b>linkLabel</b>([<i>linkLabel</i>])

If called with an argument, set the link label to the specified function,
otherwise return the current function, which defaults to:
```js
function linkLabel(d) {
  return null
}
```

The link label is displayed in an SVG `text` element, so unlike the /title/, it
is visible all the time.

#### Link appearance

<a name="linkColor" href="#linkColor">#</a> diagram.<b>linkColor</b>([<i>linkColor</i>])

If *linkColor* is specified, sets the link color accessor to the specified
function, otherwise return the current accessor, which defaults to:
```js
function linkColor(d) {
  return null;
}
```

<a name="linkMinWidth" href="#linkMinWidth">#</a> diagram.<b>linkMinWidth</b>([<i>linkMinWidth</i>])

If *linkMinWidth* is specified, sets the minimum link width accessor to the
specified function, otherwise return the current accessor, which by default
returns `1`.

<a name="linkImportance" href="#linkImportance">#</a> diagram.<b>linkImportance</b>([<i>linkImportance</i>])

If *linkImportance* is specified, sets the link importance accessor to the
specified function, otherwise return the current accessor, which defaults to:
```js
function linkImportance(d) {
  // Return negative values for special cases to put them at the bottom
  if (!d.source || (d.target && d.target.direction === 'd')) return -2
  if (!d.target || (d.source && d.source.direction === 'd')) return -1
  // Return dy (visual width) for normal links
  return d.dy
}
```

The link importance determines the z-order (stacking order) of links in the
diagram. Links are grouped by source-target pair, and groups are sorted by their
aggregated importance (see [linkImportanceAgg](#linkImportanceAgg) below). This
ensures that multiple links between the same nodes are always rendered together,
avoiding a "woven" pattern where some links from one group go over and others go
under links from another group.

<a name="linkImportanceAgg" href="#linkImportanceAgg">#</a> diagram.<b>linkImportanceAgg</b>([<i>linkImportanceAgg</i>])

If *linkImportanceAgg* is specified, sets the link importance aggregation
function to the specified function, otherwise return the current function, which
defaults to:
```js
function linkImportanceAgg(values) {
  // Sum of importance values for all links in the group
  return values.reduce((a, b) => a + b, 0)
}
```

This function aggregates the individual link importance values (from
[linkImportance](#linkImportance)) for all links between the same source and
target nodes. The aggregated value is used to determine the z-order of link
groups.

#### Node groups

<a name="groups" href="#groups">#</a> diagram.<b>groups</b>([<i>groups</i>])

If *groups* is specified, sets the list of node groups to the specified value.
If *groups* is not specified, return the current list. Node groups display a box
around the specified nodes, and should be given in the following format:
```js
[
    { title: "Group title to be displayed above nodes",
      nodes: ["nodeid1", "nodeid", ...]
    }
]
```

#### Events

<a name="on" href="#on">#</a> diagram.<b>on</b>(<i>type</i>[, <i>listener</i>])

Adds or removes an event *listener* for the specified *type*. The *type* string
is one of `selectNode`, `selectLink` or `selectGroup`. The *listener* is invoked
with the context as the element and one argument, the corresponding data.

If *listener* is not specified, returns the currently-assigned listener for the
specified *type*, if any. 

## Tests

Run the tests:
```js
npm test
```

## Licence

MIT licence.

## Contributors

- Rick Lupton
- @harisbal
- @svwielga4

## Changelog

### Unreleased

### v0.9.2

- Add `linkImportance` and `linkImportanceAgg`: links between the same source
  and target node are now kept grouped together in the SVG so they have similar
  Z-orders, avoiding the effect of links "weaving" through each other.

### v0.9.1

- Fix package.json metadata from ESM module import.

### v0.9.0

- Update many dependencies.

### v0.8.0

- Modified code to assign `type` and `value` attributes to `link` objects
  (thanks @harisbal)
- EXPERIMENTAL: allow short "stub" flows in or out of nodes using
  `fromElsewhere` and `toElsewhere` attributes. These can be useful for
  simplifying diagrams by avoiding uninteresting flows while still showing how
  nodes balance.

### v0.7.3

- Fix packaging to avoid overwriting the d3 global object in UMD module!
- Add a basic link label feature to show the values of links with SVG text
  elements. See [`diagram.linkLabel`](#linkLabel). (#2)

### v0.7.2

- Update packaging to produce different module builds:
    - `d3-sankey-diagram.esm.js`: An ES module
    - `d3-sankey-diagram.cjs.js`: A CommonJS module for use with NodeJS
    - `d3-sankey-diagram.umd.js`: A UMD module for use in `<script>` tags
    - `d3-sankey-diagram.min.js`: A minified version of the UMD module

### v0.7.1

- Unused code tidied up (thanks svwielga4)
- Improved node value labels: with wide nodes, the labels are shown within the
  nodes. Otherwise they are now shown after the node titles in parentheses.
  **[See
  example](https://bl.ocks.org/ricklupton/8a9a9501883a5645202cb439def65d31)**.
- By default, node values are not shown by default (the default in v0.7.0 was to
  show them). Use [`diagram.nodeValue`](#nodeValue) to set the format to show
  them.

### v0.7.0

- Add option to show node values, customizable with
  [`diagram.nodeValue`](#nodeValue).
