This tutorial will look at the ways drill-down/up navigation can be implemented in a map chart.
Workflow
Typically, a drill-down in a map chart is set up using these steps:
- Catch click/tap event on a country/area.
- Zoom in to the target area.
- Hide or fade out source map polygon series.
- Show or fade in target polygon series. If needed also change geodata for the target polygon series.
The following sections will examine those steps one-by-one.
Static geodata
For the purpose of this tutorial let's examine a scenario where Continent map drills-down into World map.
Creating series
This scenario will use two levels of drill down: Continents and World (countries).
We will pre-create a map polygon series for each of those:
let continentSeries = chart.series.push(am5map.MapPolygonSeries.new(root, { geoJSON: am5geodata_continentsLow, exclude: ["antarctica"] })); let countrySeries = chart.series.push(am5map.MapPolygonSeries.new(root, { geoJSON: am5geodata_worldLow, exclude: ["AQ"], visible: false }));
var continentSeries = chart.series.push(am5map.MapPolygonSeries.new(root, { geoJSON: am5geodata_continentsLow, exclude: ["antarctica"] })); var countrySeries = chart.series.push(am5map.MapPolygonSeries.new(root, { geoJSON: am5geodata_worldLow, exclude: ["AQ"], visible: false }));
When drilling down we will hide continentSeries
and reveal countrySeries
, and vice versa when drilling up.
Capturing clicks
To capture a click (or tap) on a map polygon, we can use click
event, set on polygon template:
continentSeries.mapPolygons.template.events.on("click", function (ev) { // Clicked on a continent // ev.dataItem will container clicked polygon's data item });
continentSeries.mapPolygons.template.events.on("click", function (ev) { // Clicked on a continent // ev.dataItem will container clicked polygon's data item });
Now, our custom function will kick in every time we click on any continent.
We just need to fill in the code to:
- Zoom in on a clicked continent.
- Fade out continent series.
- Fade in country series.
continentSeries.mapPolygons.template.events.on("click", function (ev) { continentSeries.zoomToDataItem(ev.target.dataItem); continentSeries.hide(); countrySeries.show(); });
continentSeries.mapPolygons.template.events.on("click", function (ev) { continentSeries.zoomToDataItem(ev.target.dataItem); continentSeries.hide(); countrySeries.show(); });
MORE INFOFor more information about zooming map via API, refer to "Panning and zooming the map: Zooming to clicked object". For an in-depth info about events, check out "Events" tutorial.
Drill-up
For going back to continent view, let's create a simple button which would be hidden by default, but would be revealed when drill-down happens:
let homeButton = chart.children.push(am5.Button.new(root, { paddingTop: 10, paddingBottom: 10, x: am5.percent(100), centerX: am5.percent(100), opacity: 0, interactiveChildren: false, icon: am5.Graphics.new(root, { svgPath: "M16,8 L14,8 L14,16 L10,16 L10,10 L6,10 L6,16 L2,16 L2,8 L0,8 L8,0 L16,8 Z M16,8", fill: am5.color(0xffffff) }) })); homeButton.events.on("click", function() { chart.goHome(); continentSeries.show(); countrySeries.hide(); homeButton.hide(); });
var homeButton = chart.children.push(am5.Button.new(root, { paddingTop: 10, paddingBottom: 10, x: am5.percent(100), centerX: am5.percent(100), opacity: 0, interactiveChildren: false, icon: am5.Graphics.new(root, { svgPath: "M16,8 L14,8 L14,16 L10,16 L10,10 L6,10 L6,16 L2,16 L2,8 L0,8 L8,0 L16,8 Z M16,8", fill: am5.color(0xffffff) }) })); homeButton.events.on("click", function() { chart.goHome(); continentSeries.show(); countrySeries.hide(); homeButton.hide(); });
In the above code, we add click
event to the button which:
- Uses map chart's
goHome()
method to zoom the map fully back out. - Hides country series and reveals continent series.
- Hides itself because we don't need it on top level of the drill-down map.
Full example
See the Pen amCharts 5: Drill-down map by amCharts team (@amcharts) on CodePen.
Dynamic geodata
In previous section we were just switching back and forth between two fully set up polygon series.
In the following chapters we'll take a look how we can reuse polygon series for displaying dynamically loaded geodata, by building a World map that drill-down into individual country maps.
Here instead of pre-filling target series' geoJSON
setting with a pre-loaded map, we will determine clicked country and load geodata dynamically.
Furthermore, we will use Promise
object to sync zoom-in animations and loading of external geodata.
worldSeries.mapPolygons.template.events.on("click", function (ev) { let country = ev.target.dataItem.get("id"); let map; switch(country) { case "US": map = "usaLow.json"; break; case "CA": map = "canadaLow.json"; break; case "MX": map = "mexicoLow.json"; break; } if (map) { Promise.all([ // Initiate zoom to country animation worldSeries.zoomToDataItem(ev.target.dataItem).waitForStop(), // Start loading target geodata am5.net.load("https://cdn.amcharts.com/lib/5/geodata/json/" + map, chart) ]).then(function(result) { // Both zoom and geodata loading is complete let geodata = am5.JSONParser.parse(result[1].response); countrySeries.setAll({ geoJSON: geodata }); countrySeries.show(); worldSeries.hide(); homeButton.show(); }); } });
worldSeries.mapPolygons.template.events.on("click", function (ev) { var country = ev.target.dataItem.get("id"); var map; switch(country) { case "US": map = "usaLow.json"; break; case "CA": map = "canadaLow.json"; break; case "MX": map = "mexicoLow.json"; break; } if (map) { Promise.all([ // Initiate zoom to country animation worldSeries.zoomToDataItem(ev.target.dataItem).waitForStop(), // Start loading target geodata am5.net.load("https://cdn.amcharts.com/lib/5/geodata/json/" + map, chart) ]).then(function(result) { // Both zoom and geodata loading is complete var geodata = am5.JSONParser.parse(result[1].response); countrySeries.setAll({ geoJSON: geodata }); countrySeries.show(); worldSeries.hide(); homeButton.show(); }); } });
Let's examine the code above.
First we determine ID of a clicked country, by accessing id
setting of its data item.
Then we determine which map is appropriate for the clicked country.
We also use built-in data loader and JSON parser to load GeoJSON data as well as set it on the country series.
Finally, to avoid visual glitches, we don't want country series geodata to be set before it finishes loading, or before zoom animation is played out, so we employ Promise
mechanism to sync up zoom and loading.
Series' zoomToDataItem()
method will return Animation
object, which in turn has a method waitForStop()
that returns a promise that kicks in when animation finishes playing.
Similarly, net.load()
utility also returns a promise.
We sync up both using Promise.all()
method.
See the Pen amCharts 5: Drill-down map by amCharts team (@amcharts) on CodePen.
Examples
Here's another example which uses bundled country data to bind each country with appropriate map.
See the Pen amCharts 5: Drill-down to countries by amCharts team (@amcharts) on CodePen.
And another version of the same map with a zoom control.
See the Pen Drill-Down to Countries with Zoom Control by amCharts team (@amcharts) on CodePen.