A world map drawn as a grid of dots, each colored by its country's approximate median age: the older the population, the darker.
A pixel series draws each country as a grid of dots, squares, hexagons or diamonds in the country's color, so heat rules, hover and tooltips work as on a plain map. It trades exact borders for a clean, poster-like look. Set columnHeight and the pixels rise into 3D columns; a second heat rule sets each country's height.
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 and @amcharts/amcharts5-geodata. See Getting started.
The same chart as a JSON config. Map 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)
]);
// Colors from the theme, so the map follows the theme
var colors = am5.ColorSet.new(root, {});
// Create the map chart
// https://www.amcharts.com/docs/v5/charts/map-chart/
var chart = root.container.children.push(am5map.MapChart.new(root, {
minZoomLevel: 0.5, // can zoom out to half the size of the fitted map
// go to the home view once the map is fitted
autoHome: true,
projection: am5map.geoEqualEarth(), // Equal Earth keeps the countries' true sizes against each other
panX: "rotateX", // a sideways drag turns the world around, so the map wraps
panY: "translateY" // an up-down drag moves the map
}));
// Zoom control
// https://www.amcharts.com/docs/v5/charts/map-chart/map-pan-zoom/#Zoom_control
var zoomControl = chart.set("zoomControl", am5map.ZoomControl.new(root, {}));
// the home button is hidden by default
zoomControl.homeButton.set("visible", true);
// Create series for the water: a faint fill behind the countries
// https://www.amcharts.com/docs/v5/charts/map-chart/map-polygon-series/#Background_polygon
var waterSeries = chart.series.push(am5map.MapPolygonSeries.new(root, {
// the map fits the countries, not this rectangle around the whole world
affectsBounds: false
}));
waterSeries.mapPolygons.template.setAll({
fill: root.interfaceColors.get("alternativeBackground"), // the theme's contrast color, dark on light
fillOpacity: 0.05, // at 5%, only a faint tint
strokeOpacity: 0 // no outline
});
waterSeries.data.push({
geometry: am5map.getGeoRectangle(90, 180, -90, -180) // north, east, south and west edges: the whole world
});
// Create the pixel series: each country drawn as a grid of dots, 2 degrees
// apart. columnHeight raises the dots into 3D columns; 0 keeps them flat
var pixelSeries = chart.series.push(am5map.PixelMapSeries.new(root, {
geoJSON: am5geodata_worldLow, // the world's countries, low detail
pixelType: "circle", // round dots (or "square", "diamond", "hexagon")
step: 2,
pixelSize: 0.8, // each dot 80% of the step, so there's a gap between them
// every other row shifted by half a pixel, like bricks
stagger: true,
// the same number of pixels in every row, so they line up across the map
equalArea: false,
columnHeight: 0,
valueField: "value",
// works out the lowest and highest values, which the heat rules need
calculateAggregates: true
}));
pixelSeries.mapPolygons.template.setAll({
fill: root.interfaceColors.get("disabled"), // countries with no data stay gray
tooltipText: "{name}: {value} years" // {name} and {value} come from the map and the data
});
// Countries that are not in the data say so
pixelSeries.mapPolygons.template.adapters.add("tooltipText", function(text, target) {
return target.dataItem.get("value") == null ? "{name}: no data" : text;
});
// Color the countries by value, and set the height of their columns
// https://www.amcharts.com/docs/v5/concepts/settings/heat-rules/
pixelSeries.set("heatRules", [{
target: pixelSeries.mapPolygons.template,
dataField: "value",
min: am5.Color.lighten(colors.getIndex(0), 0.6), // the youngest: a light shade of the first theme color
max: am5.Color.brighten(colors.getIndex(0), -0.5), // the oldest: a dark shade of it
key: "fill"
}, {
target: pixelSeries.mapPolygons.template,
dataField: "value",
min: 0.1, // the youngest country's columns are 10% of columnHeight...
max: 1, // ...and the oldest ones full height
key: "pixelHeight"
}]);
// Set data: approximate median age, in years (rounded estimates for this example)
pixelSeries.data.setAll([
{ id: "US", value: 38 },
{ id: "CA", value: 41 },
{ id: "MX", value: 29 },
{ id: "BR", value: 33 },
{ id: "AR", value: 32 },
{ id: "CO", value: 31 },
{ id: "PE", value: 31 },
{ id: "VE", value: 30 },
{ id: "CL", value: 35 },
{ id: "BO", value: 26 },
{ id: "GB", value: 40 },
{ id: "FR", value: 42 },
{ id: "DE", value: 46 },
{ id: "IT", value: 47 },
{ id: "ES", value: 45 },
{ id: "PT", value: 46 },
{ id: "PL", value: 41 },
{ id: "RO", value: 43 },
{ id: "GR", value: 46 },
{ id: "SE", value: 41 },
{ id: "NO", value: 40 },
{ id: "FI", value: 43 },
{ id: "UA", value: 41 },
{ id: "RU", value: 39 },
{ id: "TR", value: 32 },
{ id: "KZ", value: 31 },
{ id: "IR", value: 32 },
{ id: "IQ", value: 21 },
{ id: "SA", value: 32 },
{ id: "AF", value: 18 },
{ id: "PK", value: 23 },
{ id: "IN", value: 28 },
{ id: "BD", value: 28 },
{ id: "CN", value: 38 },
{ id: "MN", value: 28 },
{ id: "JP", value: 48 },
{ id: "KR", value: 44 },
{ id: "MM", value: 29 },
{ id: "TH", value: 40 },
{ id: "VN", value: 32 },
{ id: "PH", value: 26 },
{ id: "ID", value: 30 },
{ id: "AU", value: 38 },
{ id: "EG", value: 24 },
{ id: "LY", value: 29 },
{ id: "DZ", value: 29 },
{ id: "MA", value: 30 },
{ id: "SD", value: 20 },
{ id: "ML", value: 16 },
{ id: "NE", value: 15 },
{ id: "TD", value: 17 },
{ id: "NG", value: 18 },
{ id: "ET", value: 19 },
{ id: "KE", value: 20 },
{ id: "TZ", value: 18 },
{ id: "CD", value: 17 },
{ id: "AO", value: 17 },
{ id: "MZ", value: 18 },
{ id: "MG", value: 20 },
{ id: "ZA", value: 28 }
]);
// Make stuff animate on load
// https://www.amcharts.com/docs/v5/concepts/animations/
chart.appear(1000, 100);
{
"type": "MapChart",
"settings": {
"minZoomLevel": 0.5,
"autoHome": true,
"panX": "rotateX",
"panY": "translateY",
"zoomControl": {
"type": "ZoomControl",
"properties": {
"homeButton": {
"settings": {
"visible": true
}
}
}
},
"projectionName": "geoEqualEarth"
},
"properties": {
"series": [
"#series-0",
"#series-1"
]
},
"refs": [
{
"data-0": [
{
"geometry": {
"type": "GeoRectangle",
"north": 90,
"east": 180,
"south": -90,
"west": -180
},
"__parse": true
}
]
},
{
"data-1": [
{
"id": "US",
"value": 38,
"__parse": true
},
{
"id": "CA",
"value": 41,
"__parse": true
},
{
"id": "MX",
"value": 29,
"__parse": true
},
{
"id": "BR",
"value": 33,
"__parse": true
},
{
"id": "AR",
"value": 32,
"__parse": true
},
{
"id": "CO",
"value": 31,
"__parse": true
},
{
"id": "PE",
"value": 31,
"__parse": true
},
{
"id": "VE",
"value": 30,
"__parse": true
},
{
"id": "CL",
"value": 35,
"__parse": true
},
{
"id": "BO",
"value": 26,
"__parse": true
},
{
"id": "GB",
"value": 40,
"__parse": true
},
{
"id": "FR",
"value": 42,
"__parse": true
},
{
"id": "DE",
"value": 46,
"__parse": true
},
{
"id": "IT",
"value": 47,
"__parse": true
},
{
"id": "ES",
"value": 45,
"__parse": true
},
{
"id": "PT",
"value": 46,
"__parse": true
},
{
"id": "PL",
"value": 41,
"__parse": true
},
{
"id": "RO",
"value": 43,
"__parse": true
},
{
"id": "GR",
"value": 46,
"__parse": true
},
{
"id": "SE",
"value": 41,
"__parse": true
},
{
"id": "NO",
"value": 40,
"__parse": true
},
{
"id": "FI",
"value": 43,
"__parse": true
},
{
"id": "UA",
"value": 41,
"__parse": true
},
{
"id": "RU",
"value": 39,
"__parse": true
},
{
"id": "TR",
"value": 32,
"__parse": true
},
{
"id": "KZ",
"value": 31,
"__parse": true
},
{
"id": "IR",
"value": 32,
"__parse": true
},
{
"id": "IQ",
"value": 21,
"__parse": true
},
{
"id": "SA",
"value": 32,
"__parse": true
},
{
"id": "AF",
"value": 18,
"__parse": true
},
{
"id": "PK",
"value": 23,
"__parse": true
},
{
"id": "IN",
"value": 28,
"__parse": true
},
{
"id": "BD",
"value": 28,
"__parse": true
},
{
"id": "CN",
"value": 38,
"__parse": true
},
{
"id": "MN",
"value": 28,
"__parse": true
},
{
"id": "JP",
"value": 48,
"__parse": true
},
{
"id": "KR",
"value": 44,
"__parse": true
},
{
"id": "MM",
"value": 29,
"__parse": true
},
{
"id": "TH",
"value": 40,
"__parse": true
},
{
"id": "VN",
"value": 32,
"__parse": true
},
{
"id": "PH",
"value": 26,
"__parse": true
},
{
"id": "ID",
"value": 30,
"__parse": true
},
{
"id": "AU",
"value": 38,
"__parse": true
},
{
"id": "EG",
"value": 24,
"__parse": true
},
{
"id": "LY",
"value": 29,
"__parse": true
},
{
"id": "DZ",
"value": 29,
"__parse": true
},
{
"id": "MA",
"value": 30,
"__parse": true
},
{
"id": "SD",
"value": 20,
"__parse": true
},
{
"id": "ML",
"value": 16,
"__parse": true
},
{
"id": "NE",
"value": 15,
"__parse": true
},
{
"id": "TD",
"value": 17,
"__parse": true
},
{
"id": "NG",
"value": 18,
"__parse": true
},
{
"id": "ET",
"value": 19,
"__parse": true
},
{
"id": "KE",
"value": 20,
"__parse": true
},
{
"id": "TZ",
"value": 18,
"__parse": true
},
{
"id": "CD",
"value": 17,
"__parse": true
},
{
"id": "AO",
"value": 17,
"__parse": true
},
{
"id": "MZ",
"value": 18,
"__parse": true
},
{
"id": "MG",
"value": 20,
"__parse": true
},
{
"id": "ZA",
"value": 28,
"__parse": true
}
]
},
{
"template-0": {
"type": "Template",
"settings": {
"fill": "@root.interfaceColors.get('disabled')",
"tooltipText": "{name}: {value} years"
}
}
},
{
"series-0": {
"type": "MapPolygonSeries",
"settings": {
"affectsBounds": false
},
"properties": {
"data": "#data-0",
"mapPolygons": {
"properties": {
"template": {
"settings": {
"fill": "@root.interfaceColors.get('alternativeBackground')",
"fillOpacity": 0.05,
"strokeOpacity": 0
}
}
}
}
}
}
},
{
"series-1": {
"type": "PixelMapSeries",
"settings": {
"geoJSON": "am5geodata_worldLow",
"pixelType": "circle",
"step": 2,
"pixelSize": 0.8,
"stagger": true,
"equalArea": false,
"columnHeight": 0,
"valueField": "value",
"calculateAggregates": true,
"heatRules": [
{
"target": "#template-0",
"dataField": "value",
"min": {
"type": "Color",
"value": "#c2e2f1"
},
"max": {
"type": "Color",
"value": "#00496e"
},
"key": "fill",
"__parse": true
},
{
"target": "#template-0",
"dataField": "value",
"min": 0.1,
"max": 1,
"key": "pixelHeight",
"__parse": true
}
]
},
"properties": {
"data": "#data-1",
"mapPolygons": {
"properties": {
"template": "#template-0"
}
}
}
}
}
]
}
