A beeswarm chart shows every value as a dot along one axis, nudging dots up and down so none overlap. The shape of the swarm shows where values bunch up: here, 500 random values, most of them near zero.
A beeswarm keeps every single value on show while still giving the overall shape of the data, like a histogram built from dots. The dots only move up and down, so each one can still be read off the axis.
Built with amCharts 5, a JavaScript library. The code includes your theme and renderer (canvas or SVG), but not 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 D3 simulation and collision force
// The chart puts every dot on one line, at its X value; the simulation then moves dots
// up or down from that line until none of them overlap.
var simulation = d3.forceSimulation();
var collisionForce = d3.forceCollide();
// Update bullet positions on tick
simulation.on("tick", function() {
updatePositions();
});
// Updated bullet positions
function updatePositions() {
am5.array.each(nodes, function(node) {
var circle = node.circle;
// `node.y` is how far the dot sits above or below the line. We set it as `dy` and leave
// `y` to the chart, so the swarm stays on the line when the chart is resized or zoomed
circle.set("dy", node.y);
node.fx = circle.x(); // `x` changes when the chart is resized or zoomed
});
}
// Nodes array which will be used by simulation
var nodes = [];
// Adds nodes to the nodes array
function addNode(dataItem) {
var bullets = dataItem.bullets;
if (bullets) {
var bullet = bullets[0];
if (bullet) {
var circle = bullet.get("sprite");
if (circle) {
// We use `fx` for horizontal position as we don't want `x` to change.
// Every dot starts on the line (`y: 0`); for a vertical chart, swap x and y
var node = {
fx: circle.x(),
y: 0,
circle: circle
};
nodes.push(node);
}
}
}
}
// Updates collision forces
function updateForces() {
simulation.force("collision", collisionForce);
collisionForce.radius(function(node) {
var circle = node.circle;
return circle.get("radius", 1) + 1; // add 1 for padding
});
// A gentle pull back toward the line keeps the swarm compact, also after zooming in
simulation.force("y", d3.forceY(0).strength(0.05));
}
// 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 chart
// https://www.amcharts.com/docs/v5/charts/xy-chart/
// The dots only have a position along X, so zoom and pan work on the X axis only
var chart = root.container.children.push(am5xy.XYChart.new(root, {
panX: false,
panY: false,
wheelY: "zoomX", // the mouse wheel zooms in on the values
pinchZoomX: true // pinch to zoom on touch screens
}));
// Add cursor: drag across the plot to zoom in on a range of values
// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
behavior: "zoomX"
}));
cursor.lineY.set("visible", false); // no horizontal cursor line, only the vertical one
// Add scrollbar, hidden at first
// https://www.amcharts.com/docs/v5/charts/xy-chart/scrollbars/
chart.set("scrollbarX", am5.Scrollbar.new(root, { orientation: "horizontal", forceHidden: true }));
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var xAxis = chart.xAxes.push(am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererX.new(root, {
minGridDistance:30 // at least 30px between labels
}),
// the random values below all fall between -50 and 50
min: -50,
max: 50,
strictMinMax: true // exactly -50 to 50, not rounded out
}));
// every dot has y: 0, so this hidden axis only gives them a line to sit on
var yAxis = chart.yAxes.push(am5xy.ValueAxis.new(root, {
renderer: am5xy.AxisRendererY.new(root, {}),
visible: false
}));
yAxis.get("renderer").grid.template.set("forceHidden", true); // no horizontal grid lines
// Create series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
var series = chart.series.push(am5xy.LineSeries.new(root, {
// finds the lowest and highest value, which the heat rule below sizes the dots by
calculateAggregates: true,
xAxis: xAxis,
yAxis: yAxis,
valueYField: "y",
valueXField: "x",
valueField: "value"
}));
series.strokes.template.set("visible", false); // no line between the dots
// Add bullet
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/#Bullets
// Settings shared by all dots go in a template
var circleTemplate = am5.Template.new({
fillOpacity: 0.8 // a little see-through, so overlaps show
});
series.bullets.push(function() {
var bulletCircle = am5.Circle.new(root, {
radius: 5, // the heat rule replaces this
fill: series.get("fill"), // the series color
tooltipText: "Value: {x}", // the dot's value
tooltipY: 0 // the tooltip points at the dot's center
}, circleTemplate);
bulletCircle.states.create("hover", { // a hovered dot takes another theme color
fill: chart.get("colors").getIndex(4)
})
return am5.Bullet.new(root, {
sprite: bulletCircle
});
});
// Add heat rule
// https://www.amcharts.com/docs/v5/concepts/settings/heat-rules/
// this makes radius different, depending on the value.
// remove if you want all circles to be of the same size
series.set("heatRules", [{
target: circleTemplate,
min: 2, // the smallest dots have a 2px radius
max: 7, // small enough for 500 dots to fit a 380px-high chart
dataField: "value",
key: "radius"
}]);
// Set data
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/#Setting_data
// Generate random data
var data = [];
for (var i = 0; i < 500; i++) {
data.push({
x: am5.math.round(Math.random() * 50 - Math.random() * 50, 2), // a random value between -50 and 50
y: 0,
value: Math.round(Math.random() * 10) // a random size, 0 to 10
});
}
series.data.setAll(data);
// Update forces whenever data is parsed
series.events.on("datavalidated", function() {
// Needs a timeout as bullets are created a bit later
setTimeout(function() {
am5.array.each(series.dataItems, function(dataItem) {
addNode(dataItem);
})
simulation.nodes(nodes);
updateForces();
}, 500)
});
// Update bullet positions when chart bounds change
chart.plotContainer.events.on("boundschanged", function() {
updateForces();
simulation.restart();
});
// Pack the dots again when the X axis is zoomed or panned, as they move closer or further apart
function repack() {
simulation.alpha(0.3).restart(); // warm the simulation up again, gently
}
xAxis.on("start", repack);
xAxis.on("end", repack);
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
chart.appear(1000, 100);