Horizontal Dumbbell Plot
A dumbbell plot puts two values on one row, a dot for each joined by a bar, so the gap between them is what you see first. Here, a made-up start and end value for each of 18 people.
About this chart
When a dumbbell plot works
A dumbbell plot compares two values per row, like before and after or this year and last. The bar between the dots shows how big each change is and which way it goes, without the clutter of two bars per row. Laid on its side, it leaves room for long names and long lists.
Good for
- Before and after, this year and last
- Gaps between two groups, like men and women
- Long lists of names down the side
Think twice when
- More than two values per row: use clustered bars
- Totals that matter: bars from zero show them
- Changes that go both ways: color the rises and falls apart
For developers
Get the code.
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.
For projects that use npm:
How it reads:
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.How it reads:
"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://docs.amcharts.com/v5/getting-started/#root-element
var root = am5.Root.new("chartdiv");
// Set themes
// https://docs.amcharts.com/v5/concepts/themes/
root.setThemes([
am5themes_Animated.new(root),
am5themes_Responsive.new(root)
]);
// Create chart
// https://docs.amcharts.com/v5/charts/xy-chart/
var chart = root.container.children.push(am5xy.XYChart.new(root, {
// a drag zooms (the cursor's behavior), so the chart itself doesn't pan
panX: false,
panY: false,
wheelX: "panY", // a horizontal wheel or trackpad swipe pans the rows...
wheelY: "zoomY", // ...and the vertical wheel zooms in on them
pinchZoomY: true, // pinch with two fingers to zoom the rows on touch screens
paddingLeft: 0 // the names sit at the chart's left edge
}));
// Add cursor
// https://docs.amcharts.com/v5/charts/xy-chart/cursor/
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
// drag up or down across the plot to zoom in on those rows
behavior: "zoomY"
}));
// only the horizontal line, which follows the rows
cursor.lineX.set("visible", false);
// Add scrollbar along the rows, hidden at first
// https://docs.amcharts.com/v5/charts/xy-chart/scrollbars/
chart.set("scrollbarY", am5.Scrollbar.new(root, { orientation: "vertical", forceHidden: true }));
// Create axes
// https://docs.amcharts.com/v5/charts/xy-chart/axes/
var yRenderer = am5xy.AxisRendererY.new(root, {
minGridDistance: 20, // at least 20px between names; on short charts some are skipped
minorGridEnabled: true // a skipped name still gets a faint grid line
});
var yAxis = chart.yAxes.push(am5xy.CategoryAxis.new(root, {
maxDeviation: 0, // no panning past the first or last name
categoryField: "category",
renderer: yRenderer,
tooltip: am5.Tooltip.new(root, {}) // shows the hovered name on the axis
}));
yRenderer.labels.template.setAll({
multiLocation: 0.5 // a label covering several names sits in the middle of them
})
yRenderer.grid.template.setAll({
location: 1 // grid lines at the end of each row, between the dumbbells
})
var xAxis = chart.xAxes.push(am5xy.ValueAxis.new(root, {
maxDeviation: 0,
renderer: am5xy.AxisRendererX.new(root, {
strokeOpacity: 0.1, // a faint axis line
minGridDistance: 60 // at least 60px between value labels
})
}));
// Create series
// https://docs.amcharts.com/v5/charts/xy-chart/series/
var series = chart.series.push(am5xy.ColumnSeries.new(root, {
xAxis: xAxis,
yAxis: yAxis,
// the category axis is the base, so the columns run sideways along the value axis
baseAxis: yAxis,
valueXField: "close",
openValueXField: "open", // each column starts at its open value instead of at zero
categoryYField: "category",
tooltip: am5.Tooltip.new(root, {
labelText: "{openValueX} to {valueX}" // both ends, as "93 to 99"
})
}));
// a column half a pixel tall: the thin bar between the two dots
series.columns.template.setAll({
height: 0.5
});
// The dots at both ends share one template, so a change to it resizes them all
var dotTemplate = am5.Template.new({
scale: 1
});
var nextColor = chart.get("colors").getIndex(3); // a palette color three steps on, so the end dots stand apart from the start dots
// Round bullets, styled by the template
series.bullets.push(function () {
return am5.Bullet.new(root, {
// 0 puts this dot at the column's open value; the second bullet's 1, at its close value
locationX: 0,
sprite: am5.Circle.new(root, { radius: 5, fill: series.get("fill") }, dotTemplate)
});
});
series.bullets.push(function () {
return am5.Bullet.new(root, {
locationX: 1,
sprite: am5.Circle.new(root, { radius: 5, fill: nextColor }, dotTemplate)
});
});
// Set data
var data = [];
var open = 100;
var close = 120;
var names = ["Raina",
"Demarcus",
"Carlo",
"Jacinda",
"Richie",
"Antony",
"Amada",
"Idalia",
"Janella",
"Marla",
"Curtis",
"Shellie",
"Meggan",
"Nathanael",
"Jannette",
"Tyrell",
"Sheena",
"Maranda"
];
// random ranges: each start drifts a little from the last, each range spans 3 to 13
for (var i = 0; i < names.length; i++) {
open += Math.round((Math.random() < 0.5 ? 1 : -1) * Math.random() * 5);
close = open + Math.round(Math.random() * 10) + 3;
data.push({
category: names[i],
open: open,
close: close
});
}
yAxis.data.setAll(data);
series.data.setAll(data);
// Make stuff animate on load
// https://docs.amcharts.com/v5/concepts/animations/
series.appear(1000);
chart.appear(1000, 100);
{
"type": "XYChart",
"settings": {
"panX": false,
"panY": false,
"wheelX": "panY",
"wheelY": "zoomY",
"pinchZoomY": true,
"paddingLeft": 0,
"cursor": {
"type": "XYCursor",
"settings": {
"behavior": "zoomY"
},
"properties": {
"lineX": {
"settings": {
"visible": false
}
}
}
},
"scrollbarY": {
"type": "Scrollbar",
"settings": {
"orientation": "vertical",
"forceHidden": true
}
}
},
"properties": {
"series": [
"#series-0"
],
"xAxes": [
"#axis-0"
],
"yAxes": [
"#axis-1"
]
},
"refs": [
{
"data-0": [
{
"category": "Raina",
"open": 103,
"close": 113
},
{
"category": "Demarcus",
"open": 102,
"close": 107
},
{
"category": "Carlo",
"open": 98,
"close": 108
},
{
"category": "Jacinda",
"open": 101,
"close": 112
},
{
"category": "Richie",
"open": 97,
"close": 109
},
{
"category": "Antony",
"open": 98,
"close": 105
},
{
"category": "Amada",
"open": 99,
"close": 109
},
{
"category": "Idalia",
"open": 97,
"close": 107
},
{
"category": "Janella",
"open": 99,
"close": 112
},
{
"category": "Marla",
"open": 98,
"close": 101
},
{
"category": "Curtis",
"open": 98,
"close": 104
},
{
"category": "Shellie",
"open": 97,
"close": 108
},
{
"category": "Meggan",
"open": 96,
"close": 104
},
{
"category": "Nathanael",
"open": 99,
"close": 109
},
{
"category": "Jannette",
"open": 97,
"close": 107
},
{
"category": "Tyrell",
"open": 96,
"close": 101
},
{
"category": "Sheena",
"open": 99,
"close": 112
},
{
"category": "Maranda",
"open": 98,
"close": 103
}
]
},
{
"template-0": {
"type": "Template",
"settings": {
"scale": 1
}
}
},
{
"template-1": {
"type": "Template",
"settings": {
"scale": 1
}
}
},
{
"axis-0": {
"type": "ValueAxis",
"settings": {
"maxDeviation": 0,
"renderer": {
"type": "AxisRendererX",
"settings": {
"strokeOpacity": 0.1,
"minGridDistance": 60
}
}
}
}
},
{
"axis-1": {
"type": "CategoryAxis",
"settings": {
"maxDeviation": 0,
"categoryField": "category",
"renderer": {
"type": "AxisRendererY",
"settings": {
"minGridDistance": 20,
"minorGridEnabled": true
},
"properties": {
"labels": {
"properties": {
"template": {
"settings": {
"multiLocation": 0.5
}
}
}
},
"grid": {
"properties": {
"template": {
"settings": {
"location": 1
}
}
}
}
}
},
"tooltip": {
"type": "Tooltip"
}
},
"properties": {
"data": "#data-0"
}
}
},
{
"series-0": {
"type": "ColumnSeries",
"settings": {
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"baseAxis": "#axis-1",
"valueXField": "close",
"openValueXField": "open",
"categoryYField": "category",
"tooltip": {
"type": "Tooltip",
"settings": {
"labelText": "{openValueX} to {valueX}"
}
}
},
"properties": {
"data": "#data-0",
"columns": {
"properties": {
"template": {
"settings": {
"height": 0.5
}
}
}
},
"bullets": [
{
"type": "Bullet",
"settings": {
"locationX": 0,
"sprite": {
"type": "Circle",
"settings": {
"radius": 5,
"fill": "@series.get('fill')"
},
"properties": {
"template": "#template-0"
}
}
}
},
{
"type": "Bullet",
"settings": {
"locationX": 1,
"sprite": {
"type": "Circle",
"settings": {
"radius": 5,
"fill": "@colors.getIndex(3)"
},
"properties": {
"template": "#template-1"
}
}
}
}
]
}
}
}
]
}



