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:
- Nodes - static blocks with a name, represented by objects of type
SankeyNode
; - 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:
- A chart instance;
- 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.