A Sankey diagram with amounts that keep floating along the bands, bigger numbers on bigger flows. Here, an example of $1.77 trillion in company cash, split by industry and by where it is held.
Floating numbers make a still Sankey diagram move the way the money does. That catches attention on a landing page or in a talk; for careful reading, the labels on the bands carry the same numbers and stay put.
Built with amCharts 5, a JavaScript library. The JavaScript includes your theme and renderer (canvas or SVG); the JSON also follows the changes you make with the controls.
npm install @amcharts/amcharts5. See Getting started.
The same chart as a JSON config. Chart Maker reads and writes this format, the library's JSON parser turns it into a chart, and the <am5-chart> element puts it on a page with no code."type" is the class to create and "settings" what you would pass to its new() in code; "properties" fill in its parts, lists such as series and axes or templates such as columns.template; "refs" holds objects used in more than one place ("#series-0" stands for the one named series-0); a string starting with "@" takes a value from another object, such as "@series.get('fill')".
A complete page. Save it as an .html file and open it in a browser.
Paste this into an AI coding assistant to build the chart from scratch. More in amCharts & AI.
// Create root element
// https://www.amcharts.com/docs/v5/getting-started/#Root_element
var root = am5.Root.new("chartdiv");
// Set themes
// https://www.amcharts.com/docs/v5/concepts/themes/
root.setThemes([
am5themes_Animated.new(root),
am5themes_Responsive.new(root)
]);
var serialChartContainer = root.container.children.push(
am5.SerialChartContainer.new(root, {
width: am5.percent(100), // the container fills the whole chart area
height: am5.percent(100)
})
);
// Create series
// https://www.amcharts.com/docs/v5/charts/flow-charts/
var series = serialChartContainer.series.push(
am5flow.Sankey.new(root, {
orientation: "horizontal", // the flows run left to right
sourceIdField: "from",
targetIdField: "to",
valueField: "value",
nodePadding: 5, // 5px between the nodes stacked in one column
// room for the labels left of the first node and right of the last ones
paddingLeft: 45,
paddingRight: 130
})
);
// Nodes are hidden: the bands themselves make the picture
series.nodes.nodes.template.setAll({
toggleKey: "none", // clicking a node doesn't hide its bands
cursorOverStyle: "default" // a plain arrow pointer over the nodes
});
series.nodes.rectangles.template.setAll({
fillOpacity: 0,
strokeOpacity: 0
});
series.links.template.setAll({
tooltipText: "{sourceId} > {targetId}: [bold]${value}B", // source > target: the amount in billions
fillOpacity: 1, // solid bands
strokeOpacity: 1, // with an opaque outline
interactive: true // the bands react to hover: tooltip and hover state
});
series.links.template.states.create("hover", {
fillOpacity: 0.8 // a hovered band turns a little see-through
});
// Colors from the theme: one for the companies, two more for where the cash is held
var colors = am5.ColorSet.new(root, {});
var companyColor = colors.getIndex(0);
// of the theme's text color and its alternative, the one that stands out on the companies' color
var textColor = am5.Color.alternative(companyColor, root.interfaceColors.get("text"), root.interfaceColors.get("alternativeText"));
// Only the first and the last nodes get a label: their own text from the node data
series.nodes.labels.template.setAll({
forceHidden: true, // hidden unless a node's labelSettings says otherwise
templateField: "labelSettings" // settings from each node's labelSettings
});
series.nodes.data.setAll([
{
id: "Non-financial companies",
fill: companyColor,
labelSettings: {
text: "NON-FINANCIAL COMPANIES\n[bold]$1.77 TRILLION[/] IN CASH",
textAlign: "center",
forceHidden: false, // this node's label shows
rotation: -90, // reading upward along the first node
centerX: am5.p50,
centerY: am5.p100,
paddingLeft: 0,
paddingBottom: 6
}
},
{ id: "Non-tech companies", fill: companyColor },
{ id: "Tech companies", fill: companyColor },
{
id: "Cash in the U.S.",
fill: colors.getIndex(1), // the bands into it fade to the next theme color
labelSettings: {
text: "CASH IN THE U.S.\n[bold]$459 BILLION",
forceHidden: false,
paddingLeft: 8
}
},
{
id: "Cash overseas",
fill: colors.getIndex(2), // and these to the one after
labelSettings: {
text: "CASH OVERSEAS\n[bold]$1.31 TRILLION",
forceHidden: false,
paddingLeft: 8
}
},
{ id: "Rest of tech", fill: companyColor },
{ id: "Top 5 tech companies", fill: companyColor },
{ id: "Joytechs", fill: companyColor },
{ id: "Fireex", fill: companyColor },
{ id: "Globalworld", fill: companyColor },
{ id: "Betagate", fill: companyColor },
{ id: "Apexi", fill: companyColor }
]);
// Amounts that float along every band, again and again, fading in and out on the way. They come first, so the
// band labels below are drawn on top of them.
series.bullets.push(function (root, series, dataItem) {
var label = am5.Label.new(root, {
text: "${value}B", // the band's amount in billions
populateText: true, // fill in {value} from the band's data
centerX: am5.p50, // centered on its spot on the band
centerY: am5.p50,
fill: textColor, // the text color that stands out on the bands
opacity: 0, // invisible at first; the adapter below sets it
// bigger flows carry bigger numbers, from 9px to 24px
fontSize: Math.max(9, Math.min(24, dataItem.get("value") / 25))
});
var bullet = am5.Bullet.new(root, {
locationX: 0, // starts at the beginning of the band
sprite: label,
autoRotate: true // turns with the band's curve
});
// most visible halfway along the band
label.adapters.add("opacity", function (opacity) {
return 0.5 - Math.abs(0.5 - bullet.get("locationX"));
});
bullet.animate({
key: "locationX", // move along the band...
from: 0, // ...from its start...
to: 1, // ...to its end
// each amount travels at its own speed, 2 to 12 seconds per trip
duration: Math.random() * 10000 + 2000,
loops: Infinity // again and again
});
// set the opacity again on every move, so the adapter above works it out for the new spot
bullet.on("locationX", function () {
label.set("opacity", label.get("opacity"));
});
return bullet;
});
// Labels on the bands, from each link's labelSettings. The labeled bands are all in the companies' color, so the
// text takes the color that stands out on it
series.bullets.push(function () {
return am5.Bullet.new(root, {
locationX: 0.5, // halfway along the band
sprite: am5.Label.new(root, {
templateField: "labelSettings", // text from each link's labelSettings; links without one get none
fill: textColor,
fontSize: "0.85em", // a little smaller than the chart's text
textAlign: "center", // both lines centered
centerX: am5.p50,
centerY: am5.p50,
paddingTop: 0, // no padding above and below the text,
paddingBottom: 0 // so two lines fit the band
})
});
});
// The five companies' bands are narrow: their labels end at each company's node, where the band is straight
series.bullets.push(function () {
return am5.Bullet.new(root, {
locationX: 1, // at the end of the band, by the company's node
sprite: am5.Label.new(root, {
templateField: "companyLabel", // text from each link's companyLabel
fill: textColor,
fontSize: "0.75em", // smaller still, for the narrow bands
centerX: am5.p100, // the label ends at that point...
centerY: am5.p50,
paddingRight: 5, // ...5px before the node
paddingTop: 0,
paddingBottom: 0
})
});
});
// Set data
// https://www.amcharts.com/docs/v5/charts/flow-charts/#Setting_data
series.data.setAll([
{
from: "Non-financial companies",
to: "Non-tech companies",
value: 907,
labelSettings: { text: "NON-TECH COMPANIES\n[bold]$907 BILLION" }
},
{
from: "Non-financial companies",
to: "Tech companies",
value: 861,
labelSettings: { text: "TECH COMPANIES\n[bold]$861 BILLION" }
},
{ from: "Non-tech companies", to: "Cash in the U.S.", value: 324 },
{ from: "Non-tech companies", to: "Cash overseas", value: 583 },
{
from: "Tech companies",
to: "Rest of tech",
value: 274,
labelSettings: { text: "REST OF TECH\n[bold]$274 BILLION" }
},
{
from: "Tech companies",
to: "Top 5 tech companies",
value: 587,
labelSettings: { text: "TOP 5 TECH COMPANIES\n[bold]$587 BILLION" }
},
{ from: "Rest of tech", to: "Cash in the U.S.", value: 74 },
{ from: "Rest of tech", to: "Cash overseas", value: 200 },
{ from: "Top 5 tech companies", to: "Joytechs", value: 67, companyLabel: { text: "JOYTECHS [bold]$67B" } },
{ from: "Joytechs", to: "Cash in the U.S.", value: 10 },
{ from: "Joytechs", to: "Cash overseas", value: 57 },
{ from: "Top 5 tech companies", to: "Fireex", value: 68, companyLabel: { text: "FIREEX [bold]$68B" } },
{ from: "Fireex", to: "Cash in the U.S.", value: 8 },
{ from: "Fireex", to: "Cash overseas", value: 60 },
{ from: "Top 5 tech companies", to: "Globalworld", value: 85, companyLabel: { text: "GLOBALWORLD [bold]$85B" } },
{ from: "Globalworld", to: "Cash in the U.S.", value: 10 },
{ from: "Globalworld", to: "Cash overseas", value: 75 },
{ from: "Top 5 tech companies", to: "Betagate", value: 115, companyLabel: { text: "BETAGATE [bold]$115B" } },
{ from: "Betagate", to: "Cash in the U.S.", value: 10 },
{ from: "Betagate", to: "Cash overseas", value: 105 },
{ from: "Top 5 tech companies", to: "Apexi", value: 252, companyLabel: { text: "APEXI [bold]$252B" } },
{ from: "Apexi", to: "Cash in the U.S.", value: 23 },
{ from: "Apexi", to: "Cash overseas", value: 229 }
]);
// Make stuff animate on load
series.appear(1000, 100);
{
"type": "SerialChartContainer",
"settings": {
"width": {
"type": "Percent",
"value": 100
},
"height": {
"type": "Percent",
"value": 100
}
},
"properties": {
"series": [
"#series-0"
]
},
"refs": [
{
"data-0": [
{
"from": "Non-financial companies",
"to": "Non-tech companies",
"value": 907,
"labelSettings": {
"text": "NON-TECH COMPANIES\n[bold]$907 BILLION"
},
"__parse": true
},
{
"from": "Non-financial companies",
"to": "Tech companies",
"value": 861,
"labelSettings": {
"text": "TECH COMPANIES\n[bold]$861 BILLION"
},
"__parse": true
},
{
"from": "Non-tech companies",
"to": "Cash in the U.S.",
"value": 324
},
{
"from": "Non-tech companies",
"to": "Cash overseas",
"value": 583
},
{
"from": "Tech companies",
"to": "Rest of tech",
"value": 274,
"labelSettings": {
"text": "REST OF TECH\n[bold]$274 BILLION"
},
"__parse": true
},
{
"from": "Tech companies",
"to": "Top 5 tech companies",
"value": 587,
"labelSettings": {
"text": "TOP 5 TECH COMPANIES\n[bold]$587 BILLION"
},
"__parse": true
},
{
"from": "Rest of tech",
"to": "Cash in the U.S.",
"value": 74
},
{
"from": "Rest of tech",
"to": "Cash overseas",
"value": 200
},
{
"from": "Top 5 tech companies",
"to": "Joytechs",
"value": 67,
"companyLabel": {
"text": "JOYTECHS [bold]$67B"
},
"__parse": true
},
{
"from": "Joytechs",
"to": "Cash in the U.S.",
"value": 10
},
{
"from": "Joytechs",
"to": "Cash overseas",
"value": 57
},
{
"from": "Top 5 tech companies",
"to": "Fireex",
"value": 68,
"companyLabel": {
"text": "FIREEX [bold]$68B"
},
"__parse": true
},
{
"from": "Fireex",
"to": "Cash in the U.S.",
"value": 8
},
{
"from": "Fireex",
"to": "Cash overseas",
"value": 60
},
{
"from": "Top 5 tech companies",
"to": "Globalworld",
"value": 85,
"companyLabel": {
"text": "GLOBALWORLD [bold]$85B"
},
"__parse": true
},
{
"from": "Globalworld",
"to": "Cash in the U.S.",
"value": 10
},
{
"from": "Globalworld",
"to": "Cash overseas",
"value": 75
},
{
"from": "Top 5 tech companies",
"to": "Betagate",
"value": 115,
"companyLabel": {
"text": "BETAGATE [bold]$115B"
},
"__parse": true
},
{
"from": "Betagate",
"to": "Cash in the U.S.",
"value": 10
},
{
"from": "Betagate",
"to": "Cash overseas",
"value": 105
},
{
"from": "Top 5 tech companies",
"to": "Apexi",
"value": 252,
"companyLabel": {
"text": "APEXI [bold]$252B"
},
"__parse": true
},
{
"from": "Apexi",
"to": "Cash in the U.S.",
"value": 23
},
{
"from": "Apexi",
"to": "Cash overseas",
"value": 229
}
]
},
{
"series-0": {
"type": "Sankey",
"settings": {
"orientation": "horizontal",
"sourceIdField": "from",
"targetIdField": "to",
"valueField": "value",
"nodePadding": 5,
"paddingLeft": 45,
"paddingRight": 130
},
"properties": {
"data": "#data-0",
"links": {
"properties": {
"template": {
"settings": {
"tooltipText": "{sourceId} > {targetId}: [bold]${value}B",
"fillOpacity": 1,
"strokeOpacity": 1,
"interactive": true
},
"states": [
{
"key": "hover",
"settings": {
"fillOpacity": 0.8
}
}
]
}
}
},
"nodes": {
"properties": {
"data": [
{
"id": "Non-financial companies",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"labelSettings": {
"text": "NON-FINANCIAL COMPANIES\n[bold]$1.77 TRILLION[/] IN CASH",
"textAlign": "center",
"forceHidden": false,
"rotation": -90,
"centerX": {
"type": "Percent",
"value": 50
},
"centerY": {
"type": "Percent",
"value": 100
},
"paddingLeft": 0,
"paddingBottom": 6,
"__parse": true
},
"__parse": true
},
{
"id": "Non-tech companies",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"__parse": true
},
{
"id": "Tech companies",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"__parse": true
},
{
"id": "Cash in the U.S.",
"fill": {
"type": "Color",
"value": "#6794dc"
},
"labelSettings": {
"text": "CASH IN THE U.S.\n[bold]$459 BILLION",
"forceHidden": false,
"paddingLeft": 8
},
"__parse": true
},
{
"id": "Cash overseas",
"fill": {
"type": "Color",
"value": "#6771dc"
},
"labelSettings": {
"text": "CASH OVERSEAS\n[bold]$1.31 TRILLION",
"forceHidden": false,
"paddingLeft": 8
},
"__parse": true
},
{
"id": "Rest of tech",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"__parse": true
},
{
"id": "Top 5 tech companies",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"__parse": true
},
{
"id": "Joytechs",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"__parse": true
},
{
"id": "Fireex",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"__parse": true
},
{
"id": "Globalworld",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"__parse": true
},
{
"id": "Betagate",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"__parse": true
},
{
"id": "Apexi",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"__parse": true
}
],
"rectangles": {
"properties": {
"template": {
"settings": {
"fillOpacity": 0,
"strokeOpacity": 0
}
}
}
},
"labels": {
"properties": {
"template": {
"settings": {
"forceHidden": true,
"templateField": "labelSettings"
}
}
}
}
}
},
"bullets": [
{
"type": "Bullet",
"settings": {
"sprite": {
"type": "Label",
"settings": {
"text": "${value}B",
"populateText": true,
"centerX": {
"type": "Percent",
"value": 50
},
"centerY": {
"type": "Percent",
"value": 50
},
"fill": {
"type": "Color",
"value": "#000000"
},
"opacity": 0,
"fontSize": 24
}
},
"autoRotate": true,
"animations": [
{
"key": "locationX",
"from": 0,
"to": 1,
"duration": 11222.054808263583,
"loops": 0
}
]
}
},
{
"type": "Bullet",
"settings": {
"locationX": 0.5,
"sprite": {
"type": "Label",
"settings": {
"templateField": "labelSettings",
"fill": {
"type": "Color",
"value": "#000000"
},
"fontSize": "0.85em",
"textAlign": "center",
"centerX": {
"type": "Percent",
"value": 50
},
"centerY": {
"type": "Percent",
"value": 50
},
"paddingTop": 0,
"paddingBottom": 0
}
}
}
},
{
"type": "Bullet",
"settings": {
"locationX": 1,
"sprite": {
"type": "Label",
"settings": {
"templateField": "companyLabel",
"fill": {
"type": "Color",
"value": "#000000"
},
"fontSize": "0.75em",
"centerX": {
"type": "Percent",
"value": 100
},
"centerY": {
"type": "Percent",
"value": 50
},
"paddingRight": 5,
"paddingTop": 0,
"paddingBottom": 0
}
}
}
}
]
}
}
}
]
}