A population pyramid: men to the left, women to the right, one pair of bars for each age group, from 0–4 at the bottom to 85+ at the top. The men’s values are negative in the data, and the labels show them without the minus sign.
Putting two groups back to back on one axis compares them row by row and shows the shape of the whole at a glance: a wide base for a young population, a column for an aging one. It needs two groups that split the same categories, like men and women.
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, {
panX: false, // a drag doesn't pan: the cursor zooms with it
panY: false,
wheelX: "panX", // a horizontal wheel or trackpad swipe pans...
wheelY: "zoomX", // ...and the vertical wheel zooms the value axis
layout: root.verticalLayout,
arrangeTooltips: false, // overlapping tooltips aren't moved apart
paddingLeft: 0, // no gap at the left edge...
paddingRight: 10 // ...and 10px at the right
})
);
// Use only absolute numbers
chart.getNumberFormatter().set("numberFormat", "#.#s");
// Data
var data = [
{
age: "85+",
male: -0.1,
female: 0.3
},
{
age: "80-84",
male: -0.2,
female: 0.3
},
{
age: "75-79",
male: -0.3,
female: 0.6
},
{
age: "70-74",
male: -0.5,
female: 0.8
},
{
age: "65-69",
male: -0.8,
female: 1.0
},
{
age: "60-64",
male: -1.1,
female: 1.3
},
{
age: "55-59",
male: -1.7,
female: 1.9
},
{
age: "50-54",
male: -2.2,
female: 2.5
},
{
age: "45-49",
male: -2.8,
female: 3.0
},
{
age: "40-44",
male: -3.4,
female: 3.6
},
{
age: "35-39",
male: -4.2,
female: 4.1
},
{
age: "30-34",
male: -5.2,
female: 4.8
},
{
age: "25-29",
male: -5.6,
female: 5.1
},
{
age: "20-24",
male: -5.1,
female: 5.1
},
{
age: "15-19",
male: -3.8,
female: 3.8
},
{
age: "10-14",
male: -3.2,
female: 3.4
},
{
age: "5-9",
male: -4.4,
female: 4.1
},
{
age: "0-4",
male: -5.0,
female: 4.8
}
];
// Create axes
// https://www.amcharts.com/docs/v5/charts/xy-chart/axes/
var yAxis = chart.yAxes.push(
am5xy.CategoryAxis.new(root, {
categoryField: "age",
renderer: am5xy.AxisRendererY.new(root, {
// the first age group in the data, 85+, goes at the top
inversed: true,
// each row's bars take the middle 80% of it, leaving a gap between rows
cellStartLocation: 0.1,
cellEndLocation: 0.9,
minorGridEnabled: true, // fainter grid lines between the labeled rows
minGridDistance: 20 // at least 20px between the age labels
})
})
);
yAxis.data.setAll(data);
var xAxis = chart.xAxes.push(
am5xy.ValueAxis.new(root, {
// some room past the longest bars for their value labels
extraMin: 0.1,
extraMax: 0.1,
renderer: am5xy.AxisRendererX.new(root, {
minGridDistance: 60, // at least 60px between the labels
strokeOpacity: 0.1 // a faint axis line
})
})
);
// Add series
// https://www.amcharts.com/docs/v5/charts/xy-chart/series/
// adds one side of the pyramid: its bars, their value labels and a line with the side's name
function createSeries(field, labelCenterX, pointerOrientation, rangeValue) {
var series = chart.series.push(
am5xy.ColumnSeries.new(root, {
xAxis: xAxis,
yAxis: yAxis,
valueXField: field,
categoryYField: "age",
sequencedInterpolation: true, // the bars grow in one after another on load
// both series share each row instead of sitting side by side
clustered: false,
tooltip: am5.Tooltip.new(root, {
pointerOrientation: pointerOrientation, // the tooltip sits on the outer side of the bar's end
labelText: "{categoryY}: {valueX}" // the age group and its value
})
})
);
series.columns.template.setAll({
height: am5.p100, // each bar fills its row's band
strokeOpacity: 0, // no outline
fillOpacity: 0.8 // a little see-through
});
series.bullets.push(function() {
return am5.Bullet.new(root, {
locationX: 1, // at the bar's end...
locationY: 0.5, // ...in the middle of its height
sprite: am5.Label.new(root, {
centerY: am5.p50, // centered on the bar's middle
text: "{valueX}",
populateText: true, // fill in {valueX} from the data
centerX: labelCenterX // outside the bar: right-aligned on the left side, left-aligned on the right
})
});
});
series.data.setAll(data);
series.appear();
var rangeDataItem = xAxis.makeDataItem({
value: rangeValue // -4 on the left side, 4 on the right
});
xAxis.createAxisRange(rangeDataItem);
rangeDataItem.get("grid").setAll({
strokeOpacity: 1, // a solid line...
stroke: series.get("stroke") // ...in the series' color
});
var label = rangeDataItem.get("label");
label.setAll({
text: field.toUpperCase(), // the side's name in capitals
fontSize: "1.1em", // a little bigger than the other text
fill: series.get("stroke"),
paddingTop: 10, // a little below the plot's top
isMeasured: false, // doesn't make the axis taller
centerX: labelCenterX // on the outer side of the line
});
// lift the label from the axis to the top of the plot, whatever the plot's height
label.adapters.add("dy", function() {
return -chart.plotContainer.height();
});
return series;
}
createSeries("male", am5.p100, "right", -4); // left side: labels end at the bars, line at -4
createSeries("female", 0, "left", 4); // right side: labels start at the bars, line at 4
// Add cursor
// https://www.amcharts.com/docs/v5/charts/xy-chart/cursor/
var cursor = chart.set("cursor", am5xy.XYCursor.new(root, {
behavior: "zoomY" // a drag up or down zooms in on those age groups
}));
cursor.lineY.set("forceHidden", true); // no cursor lines...
cursor.lineX.set("forceHidden", true); // ...only the tooltips
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
chart.appear(1000, 100);
{
"type": "XYChart",
"settings": {
"panX": false,
"panY": false,
"wheelX": "panX",
"wheelY": "zoomX",
"layout": {
"type": "VerticalLayout"
},
"arrangeTooltips": false,
"paddingLeft": 0,
"paddingRight": 10,
"cursor": {
"type": "XYCursor",
"settings": {
"behavior": "zoomY"
},
"properties": {
"lineX": {
"settings": {
"forceHidden": true
}
},
"lineY": {
"settings": {
"forceHidden": true
}
}
}
}
},
"properties": {
"series": [
"#series-0",
"#series-1"
],
"xAxes": [
"#axis-0"
],
"yAxes": [
"#axis-1"
]
},
"refs": [
{
"data-0": [
{
"age": "85+",
"male": -0.1,
"female": 0.3
},
{
"age": "80-84",
"male": -0.2,
"female": 0.3
},
{
"age": "75-79",
"male": -0.3,
"female": 0.6
},
{
"age": "70-74",
"male": -0.5,
"female": 0.8
},
{
"age": "65-69",
"male": -0.8,
"female": 1
},
{
"age": "60-64",
"male": -1.1,
"female": 1.3
},
{
"age": "55-59",
"male": -1.7,
"female": 1.9
},
{
"age": "50-54",
"male": -2.2,
"female": 2.5
},
{
"age": "45-49",
"male": -2.8,
"female": 3
},
{
"age": "40-44",
"male": -3.4,
"female": 3.6
},
{
"age": "35-39",
"male": -4.2,
"female": 4.1
},
{
"age": "30-34",
"male": -5.2,
"female": 4.8
},
{
"age": "25-29",
"male": -5.6,
"female": 5.1
},
{
"age": "20-24",
"male": -5.1,
"female": 5.1
},
{
"age": "15-19",
"male": -3.8,
"female": 3.8
},
{
"age": "10-14",
"male": -3.2,
"female": 3.4
},
{
"age": "5-9",
"male": -4.4,
"female": 4.1
},
{
"age": "0-4",
"male": -5,
"female": 4.8
}
]
},
{
"axis-0": {
"type": "ValueAxis",
"settings": {
"extraMin": 0.1,
"extraMax": 0.1,
"renderer": {
"type": "AxisRendererX",
"settings": {
"minGridDistance": 60,
"strokeOpacity": 0.1
}
}
}
}
},
{
"axis-1": {
"type": "CategoryAxis",
"settings": {
"categoryField": "age",
"renderer": {
"type": "AxisRendererY",
"settings": {
"inversed": true,
"cellStartLocation": 0.1,
"cellEndLocation": 0.9,
"minorGridEnabled": true,
"minGridDistance": 20
}
}
},
"properties": {
"data": "#data-0"
}
}
},
{
"series-0": {
"type": "ColumnSeries",
"settings": {
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueXField": "male",
"categoryYField": "age",
"sequencedInterpolation": true,
"clustered": false,
"tooltip": {
"type": "Tooltip",
"settings": {
"pointerOrientation": "right",
"labelText": "{categoryY}: {valueX}"
}
}
},
"properties": {
"data": "#data-0",
"columns": {
"properties": {
"template": {
"settings": {
"height": {
"type": "Percent",
"value": 100
},
"strokeOpacity": 0,
"fillOpacity": 0.8
}
}
}
},
"bullets": [
{
"type": "Bullet",
"settings": {
"locationX": 1,
"locationY": 0.5,
"sprite": {
"type": "Label",
"settings": {
"centerY": {
"type": "Percent",
"value": 50
},
"text": "{valueX}",
"populateText": true,
"centerX": {
"type": "Percent",
"value": 100
}
}
}
}
}
]
}
}
},
{
"series-1": {
"type": "ColumnSeries",
"settings": {
"xAxis": "#axis-0",
"yAxis": "#axis-1",
"valueXField": "female",
"categoryYField": "age",
"sequencedInterpolation": true,
"clustered": false,
"tooltip": {
"type": "Tooltip",
"settings": {
"pointerOrientation": "left",
"labelText": "{categoryY}: {valueX}"
}
}
},
"properties": {
"data": "#data-0",
"columns": {
"properties": {
"template": {
"settings": {
"height": {
"type": "Percent",
"value": 100
},
"strokeOpacity": 0,
"fillOpacity": 0.8
}
}
}
},
"bullets": [
{
"type": "Bullet",
"settings": {
"locationX": 1,
"locationY": 0.5,
"sprite": {
"type": "Label",
"settings": {
"centerY": {
"type": "Percent",
"value": 50
},
"text": "{valueX}",
"populateText": true,
"centerX": 0
}
}
}
}
]
}
}
},
{
"axisRange-0": {
"axis": "#axis-0",
"settings": {
"value": -4,
"label": {
"type": "AxisLabel",
"settings": {
"centerX": {
"type": "Percent",
"value": 100
},
"paddingTop": 10,
"fontSize": "1.1em",
"fill": {
"type": "Color",
"value": "#67b7dc"
},
"isMeasured": false,
"text": "MALE"
}
},
"grid": {
"type": "Grid",
"settings": {
"strokeOpacity": 1,
"stroke": {
"type": "Color",
"value": "#67b7dc"
}
}
}
},
"__parseLogic": "axisRange"
}
},
{
"axisRange-1": {
"axis": "#axis-0",
"settings": {
"value": 4,
"label": {
"type": "AxisLabel",
"settings": {
"centerX": 0,
"paddingTop": 10,
"fontSize": "1.1em",
"fill": {
"type": "Color",
"value": "#6794dc"
},
"isMeasured": false,
"text": "FEMALE"
}
},
"grid": {
"type": "Grid",
"settings": {
"strokeOpacity": 1,
"stroke": {
"type": "Color",
"value": "#6794dc"
}
}
}
},
"__parseLogic": "axisRange"
}
}
],
"root": {
"properties": {
"numberFormatter": {
"settings": {
"numberFormat": "##.#s"
}
}
}
}
}