The world's time zones in two layers: colored areas show the zone each piece of land keeps, and faint bands run from pole to pole, with each whole-hour offset from UTC labeled along the bottom.
Time zones follow borders on land but run straight across the sea, so this map uses two geodata files: the zone areas on land, colored apart, and the full bands. The whole-hour offsets are labeled in a row along the bottom; the zones that are off by half or three quarters of an hour show theirs in the tooltip. The map shows standard time, without daylight saving.
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 and @amcharts/amcharts5-geodata. 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/
var responsive = am5themes_Responsive.new(root); // kept in a variable: a rule of its own is added below
root.setThemes([
am5themes_Animated.new(root),
responsive
]);
// 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, // the map can zoom out to half its fitted size
// go to the home view once the map is fitted
autoHome: true,
panX: "translateX", // drag the map to move it sideways...
panY: "translateY", // ...and up and down
// hold Shift and drag to zoom into the area you draw
boxZoom: "shift",
projection: am5map.geoEqualEarth() // equal-area: countries keep their true relative size
})
);
var colorSet = am5.ColorSet.new(root, {}); // the theme's colors, for the time zone areas
// 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...
fillOpacity: 0.05, // ...at 5%, 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 graticule series: grid lines every 10 degrees
// https://www.amcharts.com/docs/v5/charts/map-chart/graticule-series/
var graticuleSeries = chart.series.push(am5map.GraticuleSeries.new(root, {
step: 10
}));
graticuleSeries.mapLines.template.setAll({
stroke: root.interfaceColors.get("alternativeBackground"), // the theme's contrast color...
strokeOpacity: 0.08 // ...very faint
});
// Create main polygon series for time zone areas: the land of each time zone,
// in a color of its own
// https://www.amcharts.com/docs/v5/charts/map-chart/map-polygon-series/
var areaSeries = chart.series.push(
am5map.MapPolygonSeries.new(root, {
geoJSON: am5geodata_worldTimeZoneAreasLow // the land, cut up by time zone
})
);
var areaPolygonTemplate = areaSeries.mapPolygons.template;
areaPolygonTemplate.setAll({ fillOpacity: 0.6 }); // slightly see-through
// each area takes the next color of the set, at half saturation for softer tones
areaPolygonTemplate.adapters.add("fill", function (fill, target) {
return am5.Color.saturate(
colorSet.getIndex(areaSeries.mapPolygons.indexOf(target)),
0.5
);
});
areaPolygonTemplate.states.create("hover", { fillOpacity: 0.8 }); // stronger under the mouse
// Create main polygon series for time zones: bands from pole to pole, shaded
// lightly, that run over the sea too
// https://www.amcharts.com/docs/v5/charts/map-chart/map-polygon-series/
var zoneSeries = chart.series.push(
am5map.MapPolygonSeries.new(root, {
geoJSON: am5geodata_worldTimeZonesLow, // the time zone bands
// the map fits the land, not the bands, which run from pole to pole
affectsBounds: false
})
);
var zonePolygonTemplate = zoneSeries.mapPolygons.template;
zonePolygonTemplate.setAll({
fill: root.interfaceColors.get("alternativeBackground"), // the theme's contrast color...
fillOpacity: 0.08, // ...very faint
interactive: true, // the bands react to the mouse
tooltipText: "{id}" // the zone's id from the map data
});
zonePolygonTemplate.states.create("hover", { fillOpacity: 0.3 }); // a band under the mouse shows stronger
// Create point series for the labels: a row of whole-hour offsets along the
// bottom of the map, one under each band
// https://www.amcharts.com/docs/v5/charts/map-chart/map-point-series/
var labelSeries = chart.series.push(am5map.MapPointSeries.new(root, {}));
labelSeries.bullets.push(function () {
return am5.Bullet.new(root, {
sprite: am5.Label.new(root, {
text: "{offset}",
populateText: true, // fills in {offset} from the data
centerX: am5.p50, // centered on its point
centerY: am5.p50,
fontSize: "0.7em" // 70% of the chart's text size
})
});
});
// The offsets, every step hours from -12 to +12
function offsetData(step) {
var data = [];
for (var hours = -12; hours <= 12; hours += step) {
data.push({
offset: hours > 0 ? "+" + hours : String(hours), // a plus sign before positive hours
// the bands of UTC-12 and UTC+12 are half as wide, at the date line
longitude: Math.max(-176, Math.min(176, hours * 15)),
latitude: -50 // 50 degrees south, below most of the land
});
}
return data;
}
labelSeries.data.setAll(offsetData(1)); // every whole hour
// Add 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, {}));
zoomControl.homeButton.set("visible", true); // a button that goes back to the home view
// On a narrow chart (a phone) whole hours run together: the row shows every third offset, and the
// zoom buttons line up across the bottom corner instead of standing over the end of the row.
// The rule is undone on a wider chart
responsive.addRule({
relevant: function (width) { return width < 600; }, // the chart is under 600px wide
applying: function () {
labelSeries.data.setAll(offsetData(3)); // -12, -9, -6 and so on
zoomControl.set("layout", root.horizontalLayout); // the buttons side by side
},
removing: function () {
labelSeries.data.setAll(offsetData(1));
zoomControl.set("layout", root.verticalLayout);
}
});
// Make stuff animate on load
chart.appear(1000, 100);
