A strip plot puts every value of a group on one line, so you see how they spread instead of a single average. Here, made-up posts on twelve social networks: when each went out and, by its size, how many likes it got.
A strip plot shows every single value, so clusters, gaps and outliers stay visible where an average or a box plot would hide them. It suits a few dozen points per group; with hundreds, the dots pile up and a violin or box plot reads better.
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.
/**
* ---------------------------------------
* This demo was created using amCharts 5.
*
* For more information visit:
* https://www.amcharts.com/
*
* Documentation is available at:
* https://www.amcharts.com/docs/v5/
* ---------------------------------------
*/
// 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/
var chart = root.container.children.push(am5xy.XYChart.new(root, {
panX: false, // dragging the plot doesn't pan it
panY: false,
wheelX: "panX", // a horizontal wheel or trackpad swipe moves along the day
wheelY: "zoomX", // the vertical wheel zooms in on the hours
layout: root.verticalLayout
}));
// Social networks with their brand colors; the black ones take the theme's text color, so they show on a dark
// background too
var categories = [
{ category: "Twitter", color: am5.color(0x00aade) },
{ category: "Facebook", color: am5.color(0x385997) },
{ category: "Instagram", color: am5.color(0xf04571) },
{ category: "WhatsApp", color: am5.color(0x03c03e) },
{ category: "YouTube", color: am5.color(0xff0100) },
{ category: "Pinterest", color: am5.color(0xc31e31) },
{ category: "Snapchat", color: am5.color(0xfffc40) },
{ category: "LinkedIn", color: am5.color(0x007fb5) },
{ category: "Reddit", color: am5.color(0xff4500) },
{ category: "TikTok", color: root.interfaceColors.get("text") },
{ category: "Telegram", color: am5.color(0x04acde) },
{ category: "Discord", color: am5.color(0x5865f2) }
];
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var yAxis = chart.yAxes.push(am5xy.CategoryAxis.new(root, {
categoryField: "category",
renderer: am5xy.AxisRendererY.new(root, {
minGridDistance: 10, // rows can get as close as 10px before names are skipped
cellStartLocation: 0.1,
cellEndLocation: 0.9,
inversed: true // the first network at the top
}),
// a dot in the network's color next to its name
bullet: function (root, axis, dataItem) {
return am5xy.AxisBullet.new(root, {
locationY: 0.5, // the dot sits in the middle of the row
sprite: am5.Circle.new(root, {
dx: -14, // moved left, into the space the label padding leaves
radius: 6,
fill: dataItem.dataContext.color // the network's own color
})
});
}
}));
var yRenderer = yAxis.get("renderer");
// room for the colored dot between each name and the plot
yRenderer.labels.template.setAll({
paddingRight: 28
});
// grid lines run through the middle of each row, under the dots
yRenderer.grid.template.setAll({
location: 0.5
});
yAxis.data.setAll(categories);
// The time of day a post went out, from midnight to midnight
var xAxis = chart.xAxes.push(am5xy.DateAxis.new(root, {
baseInterval: { timeUnit: "minute", count: 1 }, // times are to the minute
min: new Date(2026, 0, 1).getTime(), // the axis spans exactly one day, midnight to midnight
max: new Date(2026, 0, 2).getTime(),
strictMinMax: true, // no extra room past midnight at either end
// times only, also at midnight, where the axis would show the date
periodChangeDateFormats: { hour: "HH:mm" },
renderer: am5xy.AxisRendererX.new(root, {
minGridDistance: 60 // at least 60px between time labels
})
}));
var xRenderer = xAxis.get("renderer");
// no label at the right end (midnight again), where it would be cut in half
xRenderer.labels.template.set("maxPosition", 0.98);
xRenderer.grid.template.setAll({
forceHidden: true // no vertical grid lines
});
// Made-up data: up to 20 posts per network, each with the time it went out and the likes it got
var data = [];
for(var i = 0; i < categories.length; i++) {
var category = categories[i];
var count = Math.ceil(Math.random() * 20);
for(var x = 0; x < count; x++) {
data.push({
category: category.category,
time: new Date(2026, 0, 1, 0, 30 + Math.round(Math.random() * 1380)).getTime(),
likes: Math.round(Math.random() * 1000),
bulletSettings: {
fill: category.color
}
})
}
}
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
var series = chart.series.push(am5xy.LineSeries.new(root, {
xAxis: xAxis,
yAxis: yAxis,
baseAxis: yAxis, // the rows are the base axis; the time runs along x
valueXField: "time",
valueField: "likes", // the value the heat rule reads
categoryYField: "category",
// works out the lowest and highest likes, which the heat rule below sizes the dots by
calculateAggregates: true
}));
// no line between the posts: only the dots show
series.strokes.template.setAll({
strokeOpacity: 0
});
series.data.setAll(data);
// Add bullets
var circleTemplate = am5.Template.new({}); // shared by all the dots, so the heat rule can size each one
var bullet = series.bullets.push(function () {
return am5.Bullet.new(root, {
locationY: 0.5, // each dot in the middle of its network's row
sprite: am5.Circle.new(root, {
tooltipText: "{categoryY} at {valueX.formatDate('HH:mm')}: [bold]{value} likes[/]",
radius: 5, // the starting size; the heat rule sets the real one
stroke: root.interfaceColors.get("background"), // a background-colored ring keeps overlapping dots apart
strokeWidth: 1,
fillOpacity: 0.8, // slightly see-through, so dots behind still show
templateField: "bulletSettings" // each dot takes its fill from its post's bulletSettings
}, circleTemplate)
});
});
// Add heat rules to bullets: the more likes, the bigger the dot
series.set("heatRules", [{
target: circleTemplate,
min: 4, // the post with the fewest likes gets a 4px radius...
max: 14, // ...and the one with the most 14px
dataField: "value", // sized by the series' valueField, the likes
key: "radius" // the setting the rule changes
}]);
// Add scrollbar
chart.set("scrollbarX", am5.Scrollbar.new(root, { orientation: "horizontal" }));
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
series.appear();
chart.appear(1000, 100);