Ecosyste.ms: Awesome

An open API service indexing awesome lists of open source software.

Awesome Lists | Featured Topics | Projects

https://github.com/ricklupton/d3-sankey-diagram

Sankey diagram for d3
https://github.com/ricklupton/d3-sankey-diagram

Last synced: 8 days ago
JSON representation

Sankey diagram for d3

Awesome Lists containing this project

README

        

# 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

```

## 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

# sankey()

Creates a new Sankey layout component.

# layout(arguments...)

Apply the layout to the given arguments. The arguments 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

# layout.nodes([nodes])

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;
}
```

# layout.links([links])

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;
}
```

# layout.nodeId([nodeId])

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;
}
```

# layout.sourceId([sourceId])

# layout.targetId([targetId])

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`.

# layout.nodeBackwards([nodeBackwards])

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';
}
```

# layout.linkValue([linkValue])

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;
}
```

#
layout.linkType([linkType])

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

# layout.ordering([ordering])

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"] ],
...
]
```

# layout.rankSets([rankSets])

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
}
```

# layout.sortPorts([sortPorts])

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

# layout.extent([extent])

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.

# layout.size([size])

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*]].

# diagram.scale([scale])

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.

# diagram.whitespace([whitespace])

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

# diagram.nodePosition([nodePosition])

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

# sankeyDiagram()

Creates a new Sankey diagram component.

# diagram(selection)

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

# diagram.margin({ [top], [right], [bottom], [left] })

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

#### Titles

# diagram.nodeTitle([nodeTitle])

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;
}
```

# diagram.nodeValue([nodeValue])

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.

#
diagram.linkTitle([linkTitle])

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:

# sankeyLinkTitle(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`.

# diagram.linkLabel([linkLabel])

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

# diagram.linkColor([linkColor])

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;
}
```

# diagram.linkMinWidth([linkMinWidth])

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

#### Node groups

# diagram.groups([groups])

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

# diagram.on(type[, listener])

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.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 `` 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).