A network you draw yourself: click one person, then another, and a line joins them; click a linked pair again to cut it. The forces pull linked people together as you go.
Most network charts show links that already exist; this one lets people make them. That suits planning and teaching, like who should work with whom or which ideas connect, and each new link pulls its two circles together, so groups form on screen as they are built.
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)
]);
// Fifteen people; the value sets the size of each circle
var data = {
value: 0,
children: [
{ name: "Ada", value: 18 },
{ name: "Ben", value: 7 },
{ name: "Cleo", value: 12 },
{ name: "Dan", value: 4 },
{ name: "Eva", value: 15 },
{ name: "Finn", value: 9 },
{ name: "Gia", value: 21 },
{ name: "Hugo", value: 6 },
{ name: "Iris", value: 11 },
{ name: "Jack", value: 3 },
{ name: "Kai", value: 14 },
{ name: "Lena", value: 8 },
{ name: "Max", value: 17 },
{ name: "Nora", value: 5 },
{ name: "Omar", value: 10 }
]
}
// Create wrapper container
var chart = root.container.children.push(
am5.SerialChartContainer.new(root, {
width: am5.percent(100), // fills the whole chart area
height: am5.percent(100),
layout: root.verticalLayout
})
);
// Create series
// https://www.amcharts.com/docs/v5/charts/hierarchy/#Adding
var series = chart.series.push(
am5hierarchy.ForceDirected.new(root, {
singleBranchOnly: false,
downDepth: 2,
// the root node isn't drawn, so the people float free of each other
topDepth: 1,
initialDepth: 1, // show one level on load: the people
maxRadius: 60, // the biggest value gets a 60px circle...
minRadius: 20, // ...the smallest 20px
valueField: "value",
categoryField: "name",
childDataField: "children",
// the circles push each other away a little and are pulled firmly to the middle
manyBodyStrength: -13,
centerStrength: 0.8
})
);
series.get("colors").setAll({
step: 1 // each person takes the next palette color, none skipped
});
series.links.template.setAll({
strokeWidth: 2 // 2px links
});
// A soft shadow under each circle
series.circles.template.setAll({
shadowColor: am5.color(0x000000),
shadowOpacity: 0.3,
shadowBlur: 6,
shadowOffsetX: 2,
shadowOffsetY: 2
});
series.nodes.template.setAll({
tooltipText: null, // no tooltips
cursorOverStyle: "pointer" // a hand pointer: the circles can be clicked
});
var selectedDataItem; // the first circle clicked, waiting for a second one
// handle clicking on nodes and link/unlink them
series.nodes.template.events.on("click", function(e) {
// check if we have a selected data item
if (selectedDataItem) {
var targetDataItem = e.target.dataItem;
// if yes, and it's the same, unselect
if (e.target.dataItem == selectedDataItem) {
selectedDataItem.get("outerCircle").setPrivate("visible", false); // hide the ring around it
selectedDataItem = undefined;
}
// otherwise connect selected with a clicked point
else {
if (series.areLinked(selectedDataItem, targetDataItem)) {
series.unlinkDataItems(selectedDataItem, targetDataItem);
}
else {
// 0.2 is how strongly the new link pulls the two circles together
series.linkDataItems(selectedDataItem, targetDataItem, 0.2);
}
}
}
// if no selected data item, select
else {
selectedDataItem = e.target.dataItem;
selectedDataItem.get("outerCircle").setPrivate("visible", true) // a ring marks the selected circle
}
})
series.data.setAll([data]);
series.set("selectedDataItem", series.dataItems[0]); // start with the root selected, so the people show
// Make stuff animate on load
series.appear(1000, 100);
{
"type": "SerialChartContainer",
"settings": {
"width": {
"type": "Percent",
"value": 100
},
"height": {
"type": "Percent",
"value": 100
},
"layout": {
"type": "VerticalLayout"
}
},
"properties": {
"series": [
"#series-0"
]
},
"refs": [
{
"data-0": [
{
"value": 0,
"children": [
{
"name": "Ada",
"value": 18
},
{
"name": "Ben",
"value": 7
},
{
"name": "Cleo",
"value": 12
},
{
"name": "Dan",
"value": 4
},
{
"name": "Eva",
"value": 15
},
{
"name": "Finn",
"value": 9
},
{
"name": "Gia",
"value": 21
},
{
"name": "Hugo",
"value": 6
},
{
"name": "Iris",
"value": 11
},
{
"name": "Jack",
"value": 3
},
{
"name": "Kai",
"value": 14
},
{
"name": "Lena",
"value": 8
},
{
"name": "Max",
"value": 17
},
{
"name": "Nora",
"value": 5
},
{
"name": "Omar",
"value": 10
}
],
"__parse": true
}
]
},
{
"series-0": {
"type": "ForceDirected",
"settings": {
"singleBranchOnly": false,
"downDepth": 2,
"topDepth": 1,
"initialDepth": 1,
"maxRadius": 60,
"minRadius": 20,
"valueField": "value",
"categoryField": "name",
"childDataField": "children",
"manyBodyStrength": -13,
"centerStrength": 0.8,
"selectedDataItem": "@self.dataItems.0",
"colors": {
"type": "ColorSet",
"settings": {
"step": 1,
"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"
},
{
"type": "Color",
"value": "#dcaf67"
},
{
"type": "Color",
"value": "#dcd267"
},
{
"type": "Color",
"value": "#c3dc67"
},
{
"type": "Color",
"value": "#a0dc67"
}
],
"reuse": false,
"startIndex": 0,
"stateAnimationDuration": 600
}
}
},
"properties": {
"data": "#data-0",
"links": {
"properties": {
"template": {
"settings": {
"strokeWidth": 2
}
}
}
},
"nodes": {
"properties": {
"template": {
"settings": {
"tooltipText": null,
"cursorOverStyle": "pointer"
}
}
}
},
"circles": {
"properties": {
"template": {
"settings": {
"shadowColor": {
"type": "Color",
"value": "#000000"
},
"shadowOpacity": 0.3,
"shadowBlur": 6,
"shadowOffsetX": 2,
"shadowOffsetY": 2
}
}
}
}
}
}
}
]
}