Anatomy of a Sankey Diagram

Sankey diagram (wiki) is a special kind of chart type, used to depict branched, multi-level flows of values.

This article will walk you through creating these beautiful and informative charts.

Elements of the diagram

Before we begin, let's establish some terminology.

A diagram consists of two types of elements:

  1. Nodes - static blocks with a name, represented by objects of type SankeyNode;
  2. Links - a connection that carries certain value from one Node to another, represented by objects of type SankeyLink.

A value of a Node is a sum of incoming or outgoing Link values, connected to it.

Creating a Sankey diagram

Sankey diagrams is probably the easiest chart type to create. To create one you will only need two things:

  1. A chart instance;
  2. Data for the chart.

Importing modules/scripts

Needless to say, before you can use modules/objects, you need to make sure all required modules (in TypeScript), or files (in JavaScript) are imported. Please refer to our Getting started articles for more details:

For a Sankey diagram, we'll need to import core (main module) and charts modules.

import * as am4core from "@amcharts/amcharts4/core";
import * as am4charts from "@amcharts/amcharts4/charts";
<script src="//cdn.amcharts.com/lib/4/core.js"></script>
<script src="//cdn.amcharts.com/lib/4/charts.js"></script>

We'll be importing and referring these as am4core and am4charts in the course of this article, and overally in our demos and tutorials.

Creating chart instance

For that we're going to be using am4core.create() function. (or am4core.createFromConfig() if you are using JSON-based config approach)

let chart = am4core.create("chartdiv", am4charts.SankeyDiagram);
var chart = am4core.create("chartdiv", am4charts.SankeyDiagram);
var chart = am4core.createFromConfig({
  // ... chart config
}, "chartdiv", am4charts.SankeyDiagram);

Data

Sankey diagram, just like most charts, use an array of objects as the data.

In-line data

We assign the data to chart's data property:

chart.data = [
  { "from": "A", "to": "D", "value": 10 },
  { "from": "B", "to": "D", "value": 8 },
  { "from": "B", "to": "E", "value": 4 },
  { "from": "C", "to": "E", "value": 3 },
  { "from": "D", "to": "G", "value": 5 },
  { "from": "D", "to": "I", "value": 2 },
  { "from": "D", "to": "H", "value": 3 },
  { "from": "E", "to": "H", "value": 6 },
  { "from": "G", "to": "J", "value": 5 },
  { "from": "I", "to": "J", "value": 1 },
  { "from": "H", "to": "J", "value": 9 }
];
chart.data = [
  { "from": "A", "to": "D", "value": 10 },
  { "from": "B", "to": "D", "value": 8 },
  { "from": "B", "to": "E", "value": 4 },
  { "from": "C", "to": "E", "value": 3 },
  { "from": "D", "to": "G", "value": 5 },
  { "from": "D", "to": "I", "value": 2 },
  { "from": "D", "to": "H", "value": 3 },
  { "from": "E", "to": "H", "value": 6 },
  { "from": "G", "to": "J", "value": 5 },
  { "from": "I", "to": "J", "value": 1 },
  { "from": "H", "to": "J", "value": 9 }
];
{
  // ...
  "data": [
    { "from": "A", "to": "D", "value": 10 },
    { "from": "B", "to": "D", "value": 8 },
    { "from": "B", "to": "E", "value": 4 },
    { "from": "C", "to": "E", "value": 3 },
    { "from": "D", "to": "G", "value": 5 },
    { "from": "D", "to": "I", "value": 2 },
    { "from": "D", "to": "H", "value": 3 },
    { "from": "E", "to": "H", "value": 6 },
    { "from": "G", "to": "J", "value": 5 },
    { "from": "I", "to": "J", "value": 1 },
    { "from": "H", "to": "J", "value": 9 }
  ]
}

External data

We can also make the chart load external data using dataSource.

chart.dataSource.url = "chart_data.json";
chart.dataSource.url = "chart_data.json";
{
  // ...
  "dataSource": {
    "url": "chart_data.json"
  }
}

MORE INFO Please refer to our "External data" article, for more details.

Setting data fields

IMPORTANT Data in Sankey diagram does not carry any information about Nodes. It only contains data about Links.

Each Link has to have at least two fields and at most three fields:

  • Value; (mandatory field)
  • One or two of the following:
    • Source Node name;
    • Destination Node name.

Which field in data is carrying which value is defined by chart's dataFields property, which is a simple object. The key of the object represents named field (like Link value), and the value is a string pointing to a key in data point to look for the value in.

Sankey diagrams support four types of data fields:

  • "value" - numeric value of the Link;
  • "fromName" - name of the source Node (the starting point of the Link);
  • "toName" - name of the destination Node (the end point of the Link);
  • "color" - a color for the source Node (optional, we'll explore this a bit later into the article).

Here's how dataFields would look like for the sample data we had above:

chart.dataFields.fromName = "from";
chart.dataFields.toName = "to";
chart.dataFields.value = "value";
chart.dataFields.fromName = "from";
chart.dataFields.toName = "to";
chart.dataFields.value = "value";
{
  // ...
  "dataFields": {
    "fromName": "from",
    "toName": "to",
    "value": "value"
  }
}

And, we're done. Our chart is ready.

Before we proceed, once again, there is no data for Nodes. Both their names and their values are derived from the information contained in the data for Links.

Data order

The order in which Link data items are defined in your data is important!

The actual Links will be drawn in exactly the same order as they are defined in data.

When preparing your data, make sure items are arranged in such a way, so there are as little crisscrossing as possible.

That said, you may instruct the chart to re-order the Nodes based on their name or sum value. For that there's a chart's sortBy property with available values of "none" (default), "name", and "value".

Complete example

let chart = am4core.create("chartdiv", am4charts.SankeyDiagram);

chart.data = [
  { "from": "A", "to": "D", "value": 10 },
  { "from": "B", "to": "D", "value": 8 },
  { "from": "B", "to": "E", "value": 4 },
  { "from": "C", "to": "E", "value": 3 },
  { "from": "D", "to": "G", "value": 5 },
  { "from": "D", "to": "I", "value": 2 },
  { "from": "D", "to": "H", "value": 3 },
  { "from": "E", "to": "H", "value": 6 },
  { "from": "G", "to": "J", "value": 5 },
  { "from": "I", "to": "J", "value": 1 },
  { "from": "H", "to": "J", "value": 9 }
];

chart.dataFields.fromName = "from";
chart.dataFields.toName = "to";
chart.dataFields.value = "value";
var chart = am4core.create("chartdiv", am4charts.SankeyDiagram);

chart.data = [
  { "from": "A", "to": "D", "value": 10 },
  { "from": "B", "to": "D", "value": 8 },
  { "from": "B", "to": "E", "value": 4 },
  { "from": "C", "to": "E", "value": 3 },
  { "from": "D", "to": "G", "value": 5 },
  { "from": "D", "to": "I", "value": 2 },
  { "from": "D", "to": "H", "value": 3 },
  { "from": "E", "to": "H", "value": 6 },
  { "from": "G", "to": "J", "value": 5 },
  { "from": "I", "to": "J", "value": 1 },
  { "from": "H", "to": "J", "value": 9 }
];

chart.dataFields.fromName = "from";
chart.dataFields.toName = "to";
chart.dataFields.value = "value";
var chart am4core.createFromConfig({
  "type": "SankeyDiagram",
  "data": [
    { "from": "A", "to": "D", "value": 10 },
    { "from": "B", "to": "D", "value": 8 },
    { "from": "B", "to": "E", "value": 4 },
    { "from": "C", "to": "E", "value": 3 },
    { "from": "D", "to": "G", "value": 5 },
    { "from": "D", "to": "I", "value": 2 },
    { "from": "D", "to": "H", "value": 3 },
    { "from": "E", "to": "H", "value": 6 },
    { "from": "G", "to": "J", "value": 5 },
    { "from": "I", "to": "J", "value": 1 },
    { "from": "H", "to": "J", "value": 9 }
  ],
  "dataFields": {
    "fromName": "from",
    "toName": "to",
    "value": "value"
  }
}, "chartdiv");

See the Pen amCharts V4: Sankey diagram (1) by amCharts (@amcharts) on CodePen.

Configuring

Nodes

Configuring Nodes

By default nodes will be displayed in a classical way: a narrow colored ribbon with its name next to it.

The colors will be chosen automatically, out of the currently active theme color list. A new color for each subsequent node.

It's up to you to change the appearance of the Nodes as you see fit.

All Nodes in the diagram are contained in the property nodes, which is a collection with templates. (read here about list templates)

The collection contains property template which contains a sample instance of SankeyNode. Whatever properties you set on that template object, will end up being transferred on each actual node created by the chart.

Consider this code:

let nodeTemplate = chart.nodes.template;
nodeTemplate.width = 30;
nodeTemplate.stroke = am4core.color("#fff");
nodeTemplate.strokeWidth = 2;
nodeTemplate.nameLabel.locationX = 0.2;
nodeTemplate.nameLabel.label.fill = am4core.color("#fff");
nodeTemplate.nameLabel.label.fontWeight = "bold";
var nodeTemplate = chart.nodes.template;
nodeTemplate.width = 30;
nodeTemplate.stroke = am4core.color("#fff");
nodeTemplate.strokeWidth = 2;
nodeTemplate.nameLabel.locationX = 0.2;
nodeTemplate.nameLabel.label.fill = am4core.color("#fff");
nodeTemplate.nameLabel.label.fontWeight = "bold";
{
  // ...
  "nodes": {
    "width": 30,
    "stoke": "#fff",
    "strokeWidth": 2,
    "nameLabel": {
      "locationX": 0.2,
      "label": {
        "fill": "#fff",
        "fontWeight": "bold"
      }
    }
  }
}

The above code uses nodes.template to set width and outline for the Node's strip, then accesses its nameLabel property, which holds the instance of LabelBullet to modify postion and appearance of the Node's name label.

See the Pen amCharts V4: Sankey diagram (2) by amCharts (@amcharts) on CodePen.

Disabling toggling or dragging of Nodes

By default Nodes in Sankey diagram can be toggled on and off by clicking/tapping them. They can also be dragged vertically.

To disable toggling simple set clickable = false on Node template. To disable the latter, set draggable = false.

nodeTemplate.clickable = false;
nodeTemplate.draggable = false;
nodeTemplate.clickable = false;
nodeTemplate.draggable = false;
{
  // ...
  "nodes": {
    // ...
    "clickable": false,
    "draggable": false
  }
}

Or, if you'd rather leave the nodes clickable (e.g. you want to add your own "hit" handlers), you can just remove default event handlers:

nodeTemplate.events.off("hit");
nodeTemplate.events.off("hit");
{
  // ...
  "nodes": {
    // ...
    "callback": function(target) {
      this.events.off("hit");<br />    }
  }
}

Hiding labels for small nodes

If your chart has a lot of nodes, they might get small. In turn their labels might start overlapping.

The solution is to make labels for small nodes automatically be hidden:

nodeTemplate.nameLabel.height = undefined;
nodeTemplate.nameLabel.label.hideOversized = true;
nodeTemplate.nameLabel.height = undefined;
nodeTemplate.nameLabel.label.hideOversized = true;
{
  // ...
  "nodes": {
    // ...
    "nameLabel": {
      "height": undefined,
      "hideOversized": true
    }
  }
}

Links

The Links can configured in much the same way.

All of the links are contained in links property. And their default properties set on links.template object which is an instance of SankeyLink.

Basic configuration

There are quite a few settings you can set on a Link. For example the tension property controls "curviness" of the link. The higher the number (up to 1) the straighter the link line.

controlPointDistance controls the relative length of the straight portion of the link, before the link starts curving towards its destination.

And, just for sports, let's try to change the color of the link itself, using fill property:

let linkTemplate = chart.links.template;
linkTemplate.tension = 1;
linkTemplate.controlPointDistance = 0.1;
linkTemplate.fill = am4core.color("#A8C686");
var linkTemplate = chart.links.template;
linkTemplate.tension = 1;
linkTemplate.controlPointDistance = 0.1;
linkTemplate.fill = am4core.color("#A8C686");
{
  // ...
  "links": {
    "tension": 1,
    "controlPointDistance": 0.1,
    "fill": "#A8C686"
  }
}

Let's see how it turned out:

See the Pen amCharts V4: Sankey diagram (3) by amCharts (@amcharts) on CodePen.

Link color mode

Before we advance, let us point you to another supercool setting of the Links: colorMode.

It supports four options: "fromNode" (default), "toNode", "solid" and "gradient".

"fromNode" means the Link will be colored the same color as Node it originates in.

Similarly, "toNode" will force a color of the destination Node.

Gradient links

If colorMode is set to "gradient", a Link will not be colored in one consistent color, but rather progress smoothly from the source Node's color to the on of the destination Node's.

let linkTemplate = chart.links.template;
linkTemplate.colorMode = "gradient";
linkTemplate.fillOpacity = 1;
var linkTemplate = chart.links.template;
linkTemplate.colorMode = "gradient";
linkTemplate.fillOpacity = 1;
{
  // ...
  "links": {
    "colorMode": "gradient",
    "fillOpacity": 1
  }
}

Notice, we also set fillOpacity to 1 to make Links fully opaque.

See the Pen amCharts V4: Sankey diagram (4) by amCharts (@amcharts) on CodePen.

Fixed-width links

Links in Sankey Diagram are sized to reflect their value.

It's possible to replace them with a fixed-width lines using link's middleLine property:

// Get link template
let linkTemplate = chart.links.template;

// Disable regular full-width link fill
linkTemplate.fillOpacity = 0;

// Enable fixed-width links
linkTemplate.middleLine.strokeOpacity = 0.3;
linkTemplate.middleLine.stroke = am4core.color("#555");
linkTemplate.middleLine.strokeWidth = 5;
// Get link template
var linkTemplate = chart.links.template;

// Disable regular full-width link fill
linkTemplate.fillOpacity = 0;

// Enable fixed-width links
linkTemplate.middleLine.strokeOpacity = 0.3;
linkTemplate.middleLine.stroke = am4core.color("#555");
linkTemplate.middleLine.strokeWidth = 5;
{
  // ...
  "links": {
    "fillOpacity": 0,
    "middleLine": {
      "strokeOpacity": 0.3,
      "stroke": "#555",
      "strokeWidth": 5
    }
  }
}

Here's an example:

See the Pen Fixed-width Sankey Links by amCharts team (@amcharts) on CodePen.

Advanced link config

And, the best thing about Link is that it is a Container. That means you can add anything else to it: label, bullet, image, shapes, buttons, even another chart - anything goes.

Take a look for yourself: example #1 and example #2.

Binding appearance to data

Like with any other chart, every property of every item in Sankey diagram - Nodes or Links - can be configured individually via data.

Node color via "color" data field

In first section, we've shown that Sankey diagram's dataFields can contain fourth data field: "color". However, we didn't get around to demonstrate or explain it. Let's fix that.

A "color" data field means: "Apply this color to the originating (source) Node".

IMPORTANT Since Node can have many Links going out of it, only the first defined color will be used. The rest will be ignored. So, for example, if node "A" has two Links going out of it, and each Link definition have different "color" value, the value from the first defined Link in data will be used.

OK, let's see how this might look code-wise:

/* Set data */
chart.data = [
  { "from": "A", "to": "D", "value": 10, "nodeColor": "#06D6A0" },
  { "from": "B", "to": "D", "value": 8, "nodeColor": "#CDCDCD" },
  { "from": "B", "to": "E", "value": 4, "nodeColor": "#CDCDCD" },
  { "from": "C", "to": "E", "value": 3, "nodeColor": "#CDCDCD" },
  { "from": "D", "to": "G", "value": 5, "nodeColor": "#06D6A0" },
  { "from": "D", "to": "I", "value": 2, "nodeColor": "#06D6A0" },
  { "from": "D", "to": "H", "value": 3, "nodeColor": "#06D6A0" },
  { "from": "E", "to": "H", "value": 6, "nodeColor": "#CDCDCD" },
  { "from": "G", "to": "J", "value": 5, "nodeColor": "#CDCDCD" },
  { "from": "I", "to": "J", "value": 1, "nodeColor": "#CDCDCD" },
  { "from": "H", "to": "J", "value": 9, "nodeColor": "#06D6A0" },
  { "from": "J", "nodeColor": "#06D6A0" }
];

/* Configure data fields */
chart.dataFields.fromName = "from";
chart.dataFields.toName = "to";
chart.dataFields.value = "value";
chart.dataFields.color = "nodeColor";
/* Set data */
chart.data = [
  { "from": "A", "to": "D", "value": 10, "nodeColor": "#06D6A0" },
  { "from": "B", "to": "D", "value": 8, "nodeColor": "#CDCDCD" },
  { "from": "B", "to": "E", "value": 4, "nodeColor": "#CDCDCD" },
  { "from": "C", "to": "E", "value": 3, "nodeColor": "#CDCDCD" },
  { "from": "D", "to": "G", "value": 5, "nodeColor": "#06D6A0" },
  { "from": "D", "to": "I", "value": 2, "nodeColor": "#06D6A0" },
  { "from": "D", "to": "H", "value": 3, "nodeColor": "#06D6A0" },
  { "from": "E", "to": "H", "value": 6, "nodeColor": "#CDCDCD" },
  { "from": "G", "to": "J", "value": 5, "nodeColor": "#CDCDCD" },
  { "from": "I", "to": "J", "value": 1, "nodeColor": "#CDCDCD" },
  { "from": "H", "to": "J", "value": 9, "nodeColor": "#06D6A0" },
  { "from": "J", "nodeColor": "#06D6A0" }
];

/* Configure data fields */
chart.dataFields.fromName = "from";
chart.dataFields.toName = "to";
chart.dataFields.value = "value";
chart.dataFields.color = "nodeColor";
{
  // ...
  "data": [
    { "from": "A", "to": "D", "value": 10, "nodeColor": "#06D6A0" },
    { "from": "B", "to": "D", "value": 8, "nodeColor": "#CDCDCD" },
    { "from": "B", "to": "E", "value": 4, "nodeColor": "#CDCDCD" },
    { "from": "C", "to": "E", "value": 3, "nodeColor": "#CDCDCD" },
    { "from": "D", "to": "G", "value": 5, "nodeColor": "#06D6A0" },
    { "from": "D", "to": "I", "value": 2, "nodeColor": "#06D6A0" },
    { "from": "D", "to": "H", "value": 3, "nodeColor": "#06D6A0" },
    { "from": "E", "to": "H", "value": 6, "nodeColor": "#CDCDCD" },
    { "from": "G", "to": "J", "value": 5, "nodeColor": "#CDCDCD" },
    { "from": "I", "to": "J", "value": 1, "nodeColor": "#CDCDCD" },
    { "from": "H", "to": "J", "value": 9, "nodeColor": "#06D6A0" },
    { "from": "J", "nodeColor": "#06D6A0" }
  ],
  "dataFields": {
    "fromName": "from",
    "toName": "to",
    "value": "value",
    "color": "nodeColor"
  }
}

And, as a live example:

See the Pen amCharts V4: Sankey diagram (5) by amCharts (@amcharts) on CodePen.

Forcing colors to any node, even if it isn't an originating node

"color" of the first appearance of the "from" node in data defines color for the originating (source) Node. Therefore, to apply custom color to the last node, you can have an "empty" data point to the beginning of data. This will force the color to be used whenever the node is drawn in the chart itself.

[
  { "from": "D", "nodeColor": "#CDCDCD" },
  { "from": "J", "nodeColor": "#06D6A0" },
  { "from": "A", "to": "D", "value": 10, "nodeColor": "#06D6A0" },
  { "from": "B", "to": "D", "value": 8, "nodeColor": "#CDCDCD" },
  { "from": "B", "to": "E", "value": 4, "nodeColor": "#CDCDCD" },
  { "from": "C", "to": "E", "value": 3, "nodeColor": "#CDCDCD" },
  { "from": "D", "to": "G", "value": 5, "nodeColor": "#06D6A0" },
  { "from": "D", "to": "I", "value": 2, "nodeColor": "#06D6A0" },
  { "from": "D", "to": "H", "value": 3, "nodeColor": "#06D6A0" },
  { "from": "E", "to": "H", "value": 6, "nodeColor": "#CDCDCD" },
  { "from": "G", "to": "J", "value": 5, "nodeColor": "#CDCDCD" },
  { "from": "I", "to": "J", "value": 1, "nodeColor": "#CDCDCD" },
  { "from": "H", "to": "J", "value": 9, "nodeColor": "#06D6A0" }
]

The above will force D node to be of color "#CDCDCD", and J node as "#06D6A0".

Other properties via property fields

Other properties do not have a dedicated data field, such as "color" does. For the rest of the property folks there's a good old propertyFields in Node or Link to set values to any of their properties with individual values from the data.

It allows binding any property of any object to any property in data.

So, for example, if we want to tie Link's fill color, or fillOpacity with the appropriate properties in data, we'd add that into Link's propertyFields:

let linkTemplate = chart.links.template;
linkTemplate.propertyFields.fill = "linkColor";
linkTemplate.propertyFields.fillOpacity = "linkOpacity";
linkTemplate.colorMode = "solid";
var linkTemplate = chart.links.template;
linkTemplate.propertyFields.fill = "linkColor";
linkTemplate.propertyFields.fillOpacity = "linkOpacity";
linkTemplate.colorMode = "solid";
{
  // ...
  "links": {
    "propertyFields": {
      "fill": "linkColor",
      "fillOpacity": "linkOpacity",
      "colorMode": "solid"
    }
  }
}

Let's make sure our data is complemented with appropriate fields like "linkColor", and "linkOpacity", then run this example:

See the Pen amCharts V4: Sankey diagram (6) by amCharts (@amcharts) on CodePen.

NOTE For the above to work properly we need to also set colorMode. More about it here.

Other topics

Vertical diagrams

Fancy your diagram to go not from left to right, but rather top to bottom? Just set chart's orientation property to "vertical":

See the Pen amCharts V4: Sankey diagram (6 - vertical) by amCharts (@amcharts) on CodePen.

One-ended links

Not all Links need to start and end at a Node.

Certain flow branches are not important, so we do not necessarily need to show where they came from or where they went off.

To make such one-ended links, we just omit either "fromName" (for anonymous incoming Links) or "toName" (for outgoing "off-screen" Links) values in data:

See the Pen amCharts V4: Sankey diagram (7) by amCharts (@amcharts) on CodePen.

NOTE One-ended links will be shown as fading out straight links. You can set their maxWidth or maxHeight properties to set up how long such links are.

Using themes

Like with any chart type, Sankey diagrams might be influenced greatly with the use of themes.

If you haven't done so already, make sure you read our article about "Themes". It has a lot of background information on how to use and apply themes.

Let's just explore two main aspects of themes that influence Sankey diagram.

Each theme brings in it's own list of matching colors. Since each Node is being assigned a new color from that list, switching themes will make your diagram look differently:

Another aspect is make use of animations.

The quickest way to enable animations in amCharts 4 is to use "animated" theme:

am4core.useTheme(am4themes_animated);
am4core.useTheme(am4themes_animated);

This will make Sankey diagram's Nodes and Links "unfold" into view:

See the Pen amCharts V4: Sankey diagram (8 / themes) by amCharts (@amcharts) on CodePen.

Related content

Related demos