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.
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.
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 chart
// https://www.amcharts.com/docs/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://www.amcharts.com/docs/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://www.amcharts.com/docs/v5/charts/xy-chart/scrollbars/
chart.set("scrollbarY", am5.Scrollbar.new(root, { orientation: "vertical", forceHidden: true }));
// Create axes
// https://www.amcharts.com/docs/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://www.amcharts.com/docs/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://www.amcharts.com/docs/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": 101,
"close": 109
},
{
"category": "Demarcus",
"open": 101,
"close": 112
},
{
"category": "Carlo",
"open": 99,
"close": 108
},
{
"category": "Jacinda",
"open": 95,
"close": 106
},
{
"category": "Richie",
"open": 95,
"close": 104
},
{
"category": "Antony",
"open": 92,
"close": 97
},
{
"category": "Amada",
"open": 89,
"close": 92
},
{
"category": "Idalia",
"open": 92,
"close": 99
},
{
"category": "Janella",
"open": 94,
"close": 107
},
{
"category": "Marla",
"open": 95,
"close": 100
},
{
"category": "Curtis",
"open": 92,
"close": 101
},
{
"category": "Shellie",
"open": 88,
"close": 93
},
{
"category": "Meggan",
"open": 84,
"close": 87
},
{
"category": "Nathanael",
"open": 89,
"close": 99
},
{
"category": "Jannette",
"open": 90,
"close": 94
},
{
"category": "Tyrell",
"open": 93,
"close": 103
},
{
"category": "Sheena",
"open": 90,
"close": 100
},
{
"category": "Maranda",
"open": 87,
"close": 97
}
]
},
{
"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": {
"type": "Color",
"value": "#8067dc"
}
},
"properties": {
"template": "#template-1"
}
}
}
}
]
}
}
}
]
}