A directed chord diagram with dots that keep running along the ribbons, so the direction of every flow is plain to see. Here, six nodes and nine flows.
In a chord diagram, direction is easy to miss: a ribbon looks the same from both ends. Moving dots fix that without arrows or a legend, and they draw the eye to a chart on a landing page or a big screen.
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)]);
// Create series
// https://www.amcharts.com/docs/v5/charts/flow-charts/
var chart = root.container.children.push(am5.SerialChartContainer.new(root, {})); // holds the chord diagram
var series = chart.series.push(
am5flow.ChordDirected.new(root, {
sourceIdField: "from",
targetIdField: "to",
valueField: "value",
sort: "ascending" // the nodes go round from the smallest total to the largest
})
);
// each link takes the color of the node it comes from
series.links.template.set("fillStyle", "source");
series.nodes.get("colors").set("step", 2); // skip every other color, so neighboring nodes differ more
series.nodes.data.setAll([
{ id: "A" },
{ id: "B" },
{ id: "C" },
{ id: "D" },
{ id: "E" },
{ id: "F" }
]);
// a dot on each link travels from source to target, over and over, at its own speed
series.bullets.push(function (_root, _series, dataItem) {
var bullet = am5.Bullet.new(root, {
sprite: am5.Circle.new(root, {
radius: 5, // a 5px dot...
fill: dataItem.get("source").get("fill") // ...in the source node's color
})
});
bullet.animate({
key: "locationY",
to: 1, // from the source end...
from: 0, // ...to the target end
duration: Math.random() * 1000 + 2000, // 2 to 3 seconds per trip
loops: Infinity // over and over
});
return bullet;
});
series.nodes.labels.template.setAll({
textType: "regular", // plain horizontal labels
fill: root.interfaceColors.get("background"), // in the background color, over the node's circle
fontSize: "1.1em", // a little bigger than the chart's text
radius: -5 // 5px in from the ring
});
// a big circle on each node, behind its label
series.nodes.bullets.push(function (_root, _series, dataItem) {
return am5.Bullet.new(root, {
sprite: am5.Circle.new(root, {
radius: 20, // a 20px radius
fill: dataItem.get("fill") // in the node's color
})
});
});
// move the bullets to the bottom layer, under the links and the node labels
series.children.moveValue(series.bulletsContainer, 0);
// Set data
// https://www.amcharts.com/docs/v5/charts/flow-charts/#Setting_data
series.data.setAll([
{ from: "A", to: "D", value: 10 },
{ from: "B", to: "C", value: 8 },
{ from: "B", to: "D", value: 4 },
{ from: "B", to: "E", value: 2 },
{ from: "C", to: "A", value: 14 },
{ from: "C", to: "E", value: 4 },
{ from: "E", to: "D", value: 8 },
{ from: "F", to: "A", value: 7 },
{ from: "D", to: "B", value: 2 }
]);
// Make stuff animate on load
series.appear(1000, 100);
{
"type": "SerialChartContainer",
"properties": {
"series": [
"#series-0"
]
},
"refs": [
{
"data-0": [
{
"from": "A",
"to": "D",
"value": 10
},
{
"from": "B",
"to": "C",
"value": 8
},
{
"from": "B",
"to": "D",
"value": 4
},
{
"from": "B",
"to": "E",
"value": 2
},
{
"from": "C",
"to": "A",
"value": 14
},
{
"from": "C",
"to": "E",
"value": 4
},
{
"from": "E",
"to": "D",
"value": 8
},
{
"from": "F",
"to": "A",
"value": 7
},
{
"from": "D",
"to": "B",
"value": 2
}
]
},
{
"series-0": {
"type": "ChordDirected",
"settings": {
"sourceIdField": "from",
"targetIdField": "to",
"valueField": "value",
"sort": "ascending"
},
"properties": {
"data": "#data-0",
"links": {
"properties": {
"template": {
"settings": {
"fillStyle": "source"
}
}
}
},
"nodes": {
"properties": {
"data": [
{
"id": "A"
},
{
"id": "B"
},
{
"id": "C"
},
{
"id": "D"
},
{
"id": "E"
},
{
"id": "F"
}
],
"labels": {
"properties": {
"template": {
"settings": {
"textType": "regular",
"fill": "@root.interfaceColors.get('background')",
"fontSize": "1.1em",
"radius": -5
}
}
}
},
"bullets": [
{
"type": "Bullet",
"settings": {
"sprite": {
"type": "Circle",
"settings": {
"radius": 20,
"fill": {
"type": "Color",
"value": "#67b7dc"
}
}
}
}
}
]
},
"settings": {
"colors": {
"type": "ColorSet",
"settings": {
"step": 2,
"passOptions": {
"hue": 0.05,
"saturation": 0,
"lightness": 0
},
"colors": [
{
"type": "Color",
"value": "#67b7dc"
},
{
"type": "Color",
"value": "#6794dc"
},
{
"type": "Color",
"value": "#6771dc"
},
{
"type": "Color",
"value": "#8067dc"
},
{
"type": "Color",
"value": "#a367dc"
},
{
"type": "Color",
"value": "#c767dc"
},
{
"type": "Color",
"value": "#dc67ce"
},
{
"type": "Color",
"value": "#dc67ab"
},
{
"type": "Color",
"value": "#dc6788"
},
{
"type": "Color",
"value": "#dc6967"
},
{
"type": "Color",
"value": "#dc8c67"
}
],
"reuse": false,
"startIndex": 0,
"stateAnimationDuration": 600
}
}
}
},
"bullets": [
{
"type": "Bullet",
"settings": {
"sprite": {
"type": "Circle",
"settings": {
"radius": 5,
"fill": {
"type": "Color",
"value": "#67b7dc"
}
}
},
"animations": [
{
"key": "locationY",
"from": 0,
"to": 1,
"duration": 2558.4191277122604,
"loops": 0
}
]
}
}
]
}
}
}
]
}