Skip to content

Anatomy of a Map Chart

This article will explain how to implement feature-rich maps with amCharts 4.

As with any other chart type, Map Chart requires an instance object to be able to do anything. To create a chart, we’ll use MapChart class.

A Map to be useful will at least need to have these elements:

  1. Map chart instance
  2. Map definition (in GeoJSON format);
  3. At least one map series.

We’ll walk you through these basics before jumping on to more advanced topics.

Needless to say, before you can use amcharts4 module/object, you need to make sure all required modules (in TypeScript or ES6), or files (in JavaScript) are imported. Please refer to our Getting started articles for more details:

For a Map chart, we’ll need to import core (main module), maps module, and a map file in GeoJSON format.

import * as am4core from "@amcharts/amcharts4/core";
import * as am4maps from "@amcharts/amcharts4/maps";
import am4geodata_worldLow from "@amcharts/amcharts4-geodata/worldLow";
<script src="//cdn.amcharts.com/lib/4/core.js"></script>
<script src="//cdn.amcharts.com/lib/4/maps.js"></script>
<script src="//cdn.amcharts.com/lib/4/geodata/worldLow.js"></script>

We’ll be importing and referring these as am4core and am4maps in the course of this article, and overally in our demos and tutorials.

As for map geodata, we’ll be using am4geodata_[map name] syntax.

In amCharts 4, actual map data (map files) are located separately from the actual libraries.

The reason for this is that map files tend to be big, and there are a lot of them. We don’t want you to have to download tens of megabytes of map data, if all you want to do is to draw a Pie chart, do we?

If you are using TypeScript/ES, import files from a separate package: @amcharts/amcharts4-geodata.

If you are using amCharts CDN to load the files, you’re all set.

If you are installing all files on your own server, please make sure to download a separate ZIP file containing all the map data files.

To create an instance we’ll use am4core.create() function. (or am4core.createFromConfig() if you are using JSON-based config approach)

let map = am4core.create("chartdiv", am4maps.MapChart);
var map = am4core.create("chartdiv", am4maps.MapChart);
var map = am4core.createFromConfig({
// ... map config
}, "chartdiv", am4maps.MapChart);

There’s an important distinction between “Map chart” and “Map definition”.

The first is an instance, an object, like any other chart type, which we use to configure our map control.

The latter is a data file, which contains actual geographical data, used to draw map, like contours of countries, etc. amCharts 4 accepts map definitions in GeoJSON format.

“GeoJSON is a format for encoding a variety of geographic data structures.” - that’s how its creators describe it.

We chose it to be used in our library because

  1. It’s open (no corporation behind it);
  2. Has a lot of sources for free, ready-made maps;
  3. Is widely adopted in the industry;
  4. There is a lot of geo software tools that can be used to create and edit maps.

To make the Map chart use specific map definition, simply set map instance’s geodata property.

If you are using TypeScript or ES6 modules, the loaded definition will be in whatever name you specified during import. For plain JavaScript all bundled map definitions end up in am4geodata_[map name] object, and are accessible via map name as key.

map.geodata = am4geodata_worldLow;
map.geodata = am4geodata_worldLow;
var map = am4core.createFromConfig({
"geodata": am4geodata_worldLow,
// Alternatively we could do this:
// "geodata": "worldLow",
// ... map config
}, "chartdiv", am4maps.MapChart);

NOTE ABOUT JSON In JSON-based setup, it’s possible to specify the map by its name: geodata: "worldLow". If your map is loaded via <script> tag, it will automatically replace the contents of geodata with proper JSON.

Since version 4.0.12 geodata package contains county maps for the U.S.

They are located in region/usa subdirectory, and are accessed somewhat differently than regular maps.

Please read this tutorial for further information.

You’re not limited to just using bundled maps. You can use any map definition in GeoJSON format you can lay your hands on.

To make your Map chart automatically load such external file, use geodataSource.url setting instead of geodata.

Here’s how it would look:

map.geodataSource.url = "/path/to/myCustomMap.json";
map.geodataSource.url = "/path/to/myCustomMap.json";
{
// ...
"geodataSource": {
"url": "/path/to/myCustomMap.json"
}
}

For the purpose of this article we are going to be using bundled maps and geodata. But if you’d rather go with geodataSource approach, everything in this article will still apply and work for you.

There’s no proper way to “flatten out” a globe into a 2-dimensional sheet without severely distorting it.

Mapping community has been dealing with this issue since the dawn of maps. There are numerous ways and techniques to do that, and they are called “projections”.

In amCharts 4, by default all maps are displayed in Equirectangular projection.

To display a map in some other projection, use maps’ projection property. In amCharts 4 all projection class definitions are accessible via am4maps.projections variable. We just need to instantiate a proper one as object, and assign to projection property.

At this moment we support following projections:

am4maps.projections.Albers (since 4.3.0)

am4maps.projections.AlbersUsa (since 4.3.0; more info)

am4maps.projections.AzimuthalEqualArea (since 4.3.0)

am4maps.projections.Eckert6

am4maps.projections.EqualEarth (since 4.3.0)

am4maps.projections.Projection (default “Equirectangular” projection)

am4maps.projections.Mercator

am4maps.projections.Miller

am4maps.projections.NaturalEarth1 (since 4.3.0)

am4maps.projections.Orthographic

am4maps.projections.Stereographic (since 4.3.0)

Let’s set our map to use “Miller” projection:

map.projection = new am4maps.projections.Miller();
map.projection = new am4maps.projections.Miller();
var map = am4core.createFromConfig({
"geodata": am4geodata_worldLow,
"projection": "Miller",
// ... map config
}, "chartdiv", am4maps.MapChart);

Most of maps, bundled with amCharts 4 come in two flavors: high detail and low detail. The modules/files are indicated with “High” and “Low” suffixes respectively.

High-detail versions provides finer border and coastal lines, but they are significantly larger.

Low-detail ones are much smaller in size, but at the expense on details. Since map lines are generalized, using low-detail maps may plot some coastal markers off-shore.

It’s up to you to decide which one of the trade-offs suit your requirements best.

The choice between low or high detail map version comes down to your personal preferences and requirements.

Do you need it to load super fast and don’t need a lot of detail? Low-detail version might seem a better choice.

That said, low-detail version has lines simplified to the point where small hooks, creeks, and even smaller islands might become unrecognizable or even vanish altogether.

If that seems to be a problem, opt for the high-detail version of the map.

Below is a side-by-side comparison of two versions of the map of France:

Open in CodePen

Please note, that while both maps might seem relatively fine when fully zoomed out, the difference become more and more prominent when zooming in:

Since version 4.0.28 of geodata package, most world and region maps come also in “ultra high” detail. File names for those have *Ultra suffix, e.g. worldUltra.

Please note that higher-detail comes with extra file sizes, so make sure you really need all that detail before jumping in.

One final note: Ultra-detail on world/region maps are generated using same detail level as High-detail versions of country maps, so they are a good match for pairing, as it is described in “Overlaying multiple maps” section of this article.

@todo Information being prepared.

Please refer to this article on creating custom maps.

The map differs from other chart types in such regard that it does not use standalone data. All of its data is passed in using various types of MapSeries, like MapPolygonSeries for configuring map geographical areas, such as countries.

We add series to map by instantiating an object out of the proper series class, then pushing them into map’s series list.

Map areas (countries, states, etc.) are represented by objects MapPolygonSeries.

Unless you like empty maps, a map has to have one or more polygon series, so it knows what to draw. Let’s create one:

let polygonSeries = new am4maps.MapPolygonSeries();
map.series.push(polygonSeries);
var polygonSeries = new am4maps.MapPolygonSeries();
map.series.push(polygonSeries);
var map = am4core.createFromConfig({
// ... map config
"series": [{
"type": "MapPolygonSeries"
}]
}, "chartdiv", am4maps.MapChart);

Now, for the series to be really useful, we need data for it. The data can either come from the map definition (GeoJSON), be set directly to series via its data property, or, like you will see in a moment, combined from the two sources.

Obviously, for polygon series we need the geographical data defining the curvature of their outlines. While you can add that directly to data it would be super inconvenient.

For this job we have a handy series’ setting: useGeodata. Set it to true and it will load data for all polygons available in map definition we have already loaded earlier in this article.

let polygonSeries = new am4maps.MapPolygonSeries();
polygonSeries.useGeodata = true;
map.series.push(polygonSeries);
var polygonSeries = new am4maps.MapPolygonSeries();
polygonSeries.useGeodata = true;
map.series.push(polygonSeries);
var map = am4core.createFromConfig({
// ... map config
"series": [{
"type": "MapPolygonSeries",
"useGeodata": true
}]
}, "chartdiv", am4maps.MapChart);

Let’s see how this looks like:

Open in CodePen

Like with most series in amCharts 4, we configure appearance and behavior of its items by accessing templates.

Template for polygon services resides in its mapPolygons property. The series’ mapPolygons.template is a “template” object of type MapPolygon. You can set any property there and it will be automatically applied to all map polygons in the series.

To make the map more interactive let’s set tooltipText so that it would display a tooltip, as well as fill to exchange bland gray into something more lively, and finally, add a slightly darker shade for hovered polygons by adding “hover” state:

// Configure series
let polygonTemplate = polygonSeries.mapPolygons.template;
polygonTemplate.tooltipText = "{name}";
polygonTemplate.fill = am4core.color("#74B266");
// Create hover state and set alternative fill color
let hs = polygonTemplate.states.create("hover");
hs.properties.fill = am4core.color("#367B25");
// Configure series
var polygonTemplate = polygonSeries.mapPolygons.template;
polygonTemplate.tooltipText = "{name}";
polygonTemplate.fill = am4core.color("#74B266");
// Create hover state and set alternative fill color
var hs = polygonTemplate.states.create("hover");
hs.properties.fill = am4core.color("#367B25");
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
"useGeodata": true,
"mapPolygons": {
"tooltipText": "{name}",
"fill": "#74B266",
"states": {
"hover": {
"properties": {
"fill": "#367B25"
}
}
}
}
}]
}

Let’s see how it turned out:

Open in CodePen

Before we go any further, let’s stop for a moment to talk about object identifiers.

Most of amCharts maps use ISO2 standard for ID’ing countries and their states.

For multi-country maps, this means all polygons will have a two letter code, e.g. “US” for United States, “FR” for France, and so on.

Country subdivisions use the following identification principle: [ISO2 country code]-[ISO state code]. E.g. “US-NY” for New York state.

OK, but what if I need only few objects out of the whole map? Or I want to get rid of a few items?

For that series have include and exclude arrays. If you add any object ids to include the series will only load those objects and ignore anything else.

If you add some ids to exclude, the series will not load data for those specific objects from map definition.

To illustrate the point, let’s get rid of Antarctica from our map. (Antarctica’s ISO2 code is “AQ” by the way)

polygonSeries.exclude = ["AQ"];
polygonSeries.exclude = ["AQ"];
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
// ...
"exclude": ["AQ"]
}]
}
Open in CodePen

Similarly, if I wanted to include only certain countries, I could use include instead:

polygonSeries.include = ["PT", "ES", "FR", "DE", "BE", "NL", "IT", "AT", "GB", "IE", "CH", "LU"];
polygonSeries.include = ["PT", "ES", "FR", "DE", "BE", "NL", "IT", "AT", "GB", "IE", "CH", "LU"];
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
// ...
"include": ["PT", "ES", "FR", "DE", "BE", "NL", "IT", "AT", "GB", "IE", "CH", "LU"]
}]
}

The above will automatically include countries from the Western Europe, and will position/zoom map to accommodate that:

Open in CodePen

Nice, so why do we need data at all?

If all we require is to display a map like the one above, we don’t. However, most often we’ll need to attach some additional data to polygons that is not available in the GeoJSON.

For example, we can add values to countries for a heat map. Or, override a country name. Or, set a different rollover information, color, you name it.

That’s where data comes in.

Just like with regular charts, map series’ data is an array of objects. Each object represents a map object of some type - as dictated by series type.

[{
"id": "US",
"name": "United States",
"value": 100
}, {
"id": "FR",
"name": "France",
"value": 50
}]

With a data like above the series will now associate value 100 with the U.S. and 50 with France.

Now, since I have additional data attached to those countries, I can use it in a number of ways, like display it in tooltip:

polygonTemplate.tooltipText = "{name}: {value}";
polygonTemplate.tooltipText = "{name}: {value}";
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
// ...
"mapPolygons": {
"tooltipText": "{name}: {value}",
// ...
}
}]
}

Series’ data can be used in a number of ways, not just displaying additional data in a tooltip.

Among such advanced usage is the capability to bind object properties to fields in data.

For that we can use template’s propertyFields object. Basically, every entry in that object will tell: look for value in related data item using this key and apply to this property.

Say we want to enhance our previous demo to apply different fill colors to the U.S. and France.

polygonSeries.data = [{
"id": "US",
"name": "United States",
"value": 100,
"fill": am4core.color("#F05C5C")
}, {
"id": "FR",
"name": "France",
"value": 50,
"fill": am4core.color("#5C5CFF")
}];
polygonTemplate.propertyFields.fill = "fill";
polygonSeries.data = [{
"id": "US",
"name": "United States",
"value": 100,
"fill": am4core.color("#F05C5C")
}, {
"id": "FR",
"name": "France",
"value": 50,
"fill": am4core.color("#5C5CFF")
}];
polygonTemplate.propertyFields.fill = "fill";
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
"data": [{
"id": "US",
"name": "United States",
"value": 100,
"fill": am4core.color("#F05C5C")
}, {
"id": "FR",
"name": "France",
"value": 50,
"fill": am4core.color("#5C5CFF")
}],
// ...
"mapPolygons": {
"tooltipText": "{name}: {value}",
// ...
"propertyFields": {
"fill": "fill"
}
}
}]
}
Open in CodePen

It is possible to create completely custom shapes on the map by providing inline data for their corners. For more info and examples, please refer to “Custom polygons on Map Chart” tutorial.

MapPolygonSeries is unique in that it will modify the array assigned to its data. So if you’re using that array elsewhere in your app, you’ll want to assign a copy of the array. Here’s a simple way of doing this if there are no custom objects (like am4core.color) in your array:

polygonSeries.data = JSON.parse(JSON.stringify(dataArray));
polygonSeries.data = JSON.parse(JSON.stringify(dataArray));
{
// ...
"data": JSON.parse(JSON.stringify(dataArray))
}

When creating a map, you are not limited to using just one polygon series. Depending on your data structure and target functionality, you may add as many series as you need.

Each series, can reuse the same geodata, but use different include.

The following example shows how we can build a functional map of regions of Europe:

Open in CodePen

It is possible to have several actual maps (geodata) to be shown on a single map instance.

Up until now, we have been using single geodata (or geodataSource) on the chart object.

It is also possible to set geodata (or geodataSource) directly on the Map series. Since we can have multiple Polygon series, we can have multiple maps nicely overlaid over each other, allowing the creation of nested setups.

As an example, let’s create a World map with a US state map overlaid over it, creating a World map with US states.

/**
* Series for the World map
* It will use `chart.geodata`
*/
let worldSeries = chart.series.push(new am4maps.MapPolygonSeries());
worldSeries.exclude = ["AQ"];
worldSeries.useGeodata = true;
/**
* Series for the U.S. map
* It will use its own `geodata`
*/
let usaSeries = chart.series.push(new am4maps.MapPolygonSeries());
usaSeries.geodata = am4geodata_usaLow
/**
* Series for the World map
* It will use `chart.geodata`
*/
var worldSeries = chart.series.push(new am4maps.MapPolygonSeries());
worldSeries.exclude = ["AQ"];
worldSeries.useGeodata = true;
/**
* Series for the U.S. map
* It will use its own `geodata`
*/
var usaSeries = chart.series.push(new am4maps.MapPolygonSeries());
usaSeries.geodata = am4geodata_usaLow
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
"useGeodata": true,
// ...
}, {
"type": "MapPolygonSeries",
"geodata": "usaLow"
// ...
}]
}

Here’s how it turned out:

Open in CodePen

By default, polygons in the series are automatically sorted by size. This is done to ensure that smaller polygons are not obstructed by larger ones.

This also affects their selection when focusing polygons using TAB key (accessibility feature). In this case ordering by size might seem pretty random to user.

Please refer to the tutorial “Ordering map polygons” for further information.

Image series is used to add any visual object (let’s call them markers) anywhere on the map. And by “any object” we literally mean anything.

It could be something as simple as a circle, a label, or image. Or it could be a full-fledged chart. As long as the object is a descendant of Sprite class (which describes almost any visual element in amCharts 4), it can be used as a marker in maps.

Technically, GeoJSON format allows incorporating images and can be loaded using useGeodata. However, it’s not very common, so, for the purpose of this article, we’ll be using just data.

As we briefly mentioned in the intro to this section, a map marker can be anything. It’s represented by a MapImage class (which essentially is an empty container you can add anything to). Map images are collected into MapImageSeries.

Image series is created like any other series, by instantiating of a proper class, in this case MapImageSeries.

let imageSeries = chart.series.push(new am4maps.MapImageSeries());
var imageSeries = chart.series.push(new am4maps.MapImageSeries());
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
// ...
}, {
"type": "MapImageSeries",
// ...
}]
}

Once the series is created, we can configure it and assign data.

There are two important configuration aspects in images series: data fields and appearance.

Just like with other map series, we configure using “template” object. As we mentioned earlier, it holds a MapImage object, which, essentially, is an empty container, you can add anything in it. To start off simple, let’s add a basic Circle to it.

let imageSeriesTemplate = imageSeries.mapImages.template;
let circle = imageSeriesTemplate.createChild(am4core.Circle);
circle.radius = 4;
circle.fill = am4core.color("#B27799");
circle.stroke = am4core.color("#FFFFFF");
circle.strokeWidth = 2;
circle.nonScaling = true;
circle.tooltipText = "{title}";
var imageSeriesTemplate = imageSeries.mapImages.template;
var circle = imageSeriesTemplate.createChild(am4core.Circle);
circle.radius = 4;
circle.fill = am4core.color("#B27799");
circle.stroke = am4core.color("#FFFFFF");
circle.strokeWidth = 2;
circle.nonScaling = true;
circle.tooltipText = "{title}";
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
// ...
}, {
"type": "MapImageSeries",
"mapImages": {
"children": [{
"type": "Circle",
"radius": 4,
"stroke" : "#B27799",
"strokeWidth": 2,
"nonScaling": true,
"tooltipText": "{title}"
}]
}
}]
}

Note, that radius, stroke, and other properties are Circle properties. Other objects will have their own properties.

Now, whenever a series is creating a new marker, it will create purple circle with a white outline.

What we also need to do, is to bind specific images properties to data.

As we saw with polygon series, we bind series’ items’ properties to data using propertyFields.

So, for example, if we want to bind latitude and longitude properties to “latitude” and “longitude” keys in data, we’ll do something like this:

imageSeriesTemplate.propertyFields.latitude = "latitude";
imageSeriesTemplate.propertyFields.longitude = "longitude";
imageSeriesTemplate.propertyFields.latitude = "latitude";
imageSeriesTemplate.propertyFields.longitude = "longitude";
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
// ...
}, {
"type": "MapImageSeries",
// ...
"mapImages": {
"propertyFields": {
"latitude": "latitude",
"longitude": "longitude"
}
}
}]
}

Now, all we need to do is to top it off with actual data.

Let’s add a marker for Paris (48.856614, 2.352222) to New York City (40.712775, -74.005973) to Vancouver (49.282729, -123.120738).

imageSeries.data = [{
"latitude": 48.856614,
"longitude": 2.352222,
"title": "Paris"
}, {
"latitude": 40.712775,
"longitude": -74.005973,
"title": "New York"
}, {
"latitude": 49.282729,
"longitude": -123.120738,
"title": "Vancouver"
}];
imageSeries.data = [{
"latitude": 48.856614,
"longitude": 2.352222,
"title": "Paris"
}, {
"latitude": 40.712775,
"longitude": -74.005973,
"title": "New York"
}, {
"latitude": 49.282729,
"longitude": -123.120738,
"title": "Vancouver"
}];
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
// ...
}, {
"type": "MapImageSeries",
// ...
"data": [{
"latitude": 48.856614,
"longitude": 2.352222,
"title": "Paris"
}, {
"latitude": 40.712775,
"longitude": -74.005973,
"title": "New York"
}, {
"latitude": 49.282729,
"longitude": -123.120738,
"title": "Vancouver"
}]
}]
}

And, let’s see how it went:

Open in CodePen

Please refer to “Using actual images in MapImageSeries” tutorial for info and examples on how you can use various images for map markers.

The map can draw a number of lines connecting two or more points. They are assembled into line series.

Lines can be straight (MapLineSeries), arched (MapArcSeries), or smoothely curved (MapSplineSeries; please note for lines to be curved they must pass through at least 3 points).

As with polygons and images, lines can also be loaded from map definition (GeoJSON) using useGeodata, although is much less common. In the course of this article we’ll be adding lines using just in-line data.

Probably the most common way to draw a line is to fill its multiGeoLine array with “geo point” coordinates. A geo point is a simple object with two properties: latitude and longitude.

Each geo point in array represents a line point. There should be at least two. If you have more than two, the series will draw multi-segment line.

Let’s draw a line connecting our three cities from the images - Paris, New York City, and Vancouver.

[{
"multiGeoLine": [
[
{ "latitude": 48.856614, "longitude": 2.352222 },
{ "latitude": 40.712775, "longitude": -74.005973 },
{ "latitude": 49.282729, "longitude": -123.120738 }
]
]
}]

The above defines data for map line series. It contains one data for one line.

Notice, how the multiGeoLine is an array of array. This is not a mistake but rather clever way (inherited from GeoJSON standard) to define multiple clusters of multi-segmented lines.

This can be used if you want to introduce a break in the multi-segmented line, i.e. some points to not be connected by an actual line:

[{
"multiGeoLine": [
[
// points for the first cluster of segments
],
[
// points for the second cluster of segments
],
[
// points for the third cluster of segments
]
]
}]

Line series is created just like polygon series:

let lineSeries = chart.series.push(new am4maps.MapLineSeries());
lineSeries.data = [{
"multiGeoLine": [
[
{ "latitude": 48.856614, "longitude": 2.352222 },
{ "latitude": 40.712775, "longitude": -74.005973 },
{ "latitude": 49.282729, "longitude": -123.120738 }
]
]
}];
var lineSeries = chart.series.push(new am4maps.MapLineSeries());
lineSeries.data = [{
"multiGeoLine": [
[
{ "latitude": 48.856614, "longitude": 2.352222 },
{ "latitude": 40.712775, "longitude": -74.005973 },
{ "latitude": 49.282729, "longitude": -123.120738 }
]
]
}];
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
// ...
}, {
"type": "MapLineSeries",
"data": [{
"multiGeoLine": [
[
{ "latitude": 48.856614, "longitude": 2.352222 },
{ "latitude": 40.712775, "longitude": -74.005973 },
{ "latitude": 49.282729, "longitude": -123.120738 }
]
]
}]
}]
}

Let’s see if we have a line connecting our three awesome cities:

Open in CodePen

As with other map object series, Line series elements are configured via series’ templates.

In this case it’s the mapLines.template.line object.

Notice we are using line property of the template. Regardless of what type of line series we have, the template will contain a lineproperty, which will have an actual line element of the relative type, we can use to configure appearance of.

For MapLineSeries it’ll be an object of type Polyline. For MapArcSeries it’ll be a Polyarc, etc.

You can set properties like stroke (line color), strokeWidth (line thickness in pixels), strokeOpacity (line opacity), strokeDasharray (to make line dashed), and some other settings. E.g.:

lineSeries.mapLines.template.line.stroke = am4core.color("#5C5CFF");
lineSeries.mapLines.template.line.strokeOpacity = 0.5;
lineSeries.mapLines.template.line.strokeWidth = 4;
lineSeries.mapLines.template.line.strokeDasharray = "3,3";
lineSeries.mapLines.template.line.stroke = am4core.color("#5C5CFF");
lineSeries.mapLines.template.line.strokeOpacity = 0.5;
lineSeries.mapLines.template.line.strokeWidth = 4;
lineSeries.mapLines.template.line.strokeDasharray = "3,3";
{
// ... map config
"series": [{
"type": "MapPolygonSeries",
// ...
}, {
"type": "MapLineSeries",
// ...
"mapLines": {
"line": {
"stroke": "#5C5CFF",
"strokeOpacity": 0.5,
"strokeWidth": 4,
"strokeDasharray": "3,3"
}
}
}]
}
Open in CodePen

By now, you might be wondering why our “lines” are not straight, but rather slightly curved.

The answer is simple - they follow the curvature of the Earth. While they might look like they are taking longer route between point A and point B on screen, in reality they plot the shortest course between the two points.

The distortion comes in because the actual flattened map introduces certain modifications.

If you’d like your lines look straight on screen, simply set line’s (or MapLineSeries template’s) shortestDistance = false.

If your lines are connecting actual markers (map images), you can skip the multiGeoLine property, and use imagesToConnect instead.

This property is an array of references to MapImage objects. E.g.:

let paris = imageSeries.mapImages.create();
paris.latitude = 48.856614;
paris.longitude = 2.352222;
let nyc = imageSeries.mapImages.create();
nyc.latitude = 40.712776;
nyc.longitude = -74.005973;
let paris_nyc = lineSeries.mapLines.create();
paris_nyc.imagesToConnect = [paris, nyc];
var paris = imageSeries.mapImages.create();
paris.latitude = 48.856614;
paris.longitude = 2.352222;
var nyc = imageSeries.mapImages.create();
nyc.latitude = 40.712776;
nyc.longitude = -74.005973;
var paris_nyc = lineSeries.mapLines.create();
paris_nyc.imagesToConnect = [paris, nyc];

Some maps have separator line information built-in. Please refer to “Maps with separator lines” tutorial for more info.

As with any chart type, Map chart allows adding some specific controls.

Adding zoom control, is as simple as instantiating an object of ZoomControl class and assigning it to map’s zoomControl property:

chart.zoomControl = new am4maps.ZoomControl();
chart.zoomControl = new am4maps.ZoomControl();
{
// ... map config
"zoomControl": {
// No need to set `type` here.
// There's only one type of zoom control, so the map
// will be able to figure it out by itself.
// ...
}
}

Please refer to the reference of ZoomControl for available configuration options.

Normally zoom control would show only zoom in and out buttons.

It can also show a neat slider in-between. To enable it simply set its height:

chart.zoomControl.slider.height = 100;
chart.zoomControl.slider.height = 100;
{
// ... map config
"zoomControl": {
"slider": {
"height": 100
}
}
}

Same is with a small map - a tiny control that will display a mini version of the full map as well as highlight your current view port.

However, before it can be useful, we need to configure it, by providing what to show in it. Since it is essentially a small version of the map, we need to assign map series to small map to show.

If we wanted, we could create and show any new series in the small map. For simplicity, let’s just replicate our polygon series we already have on the map.

chart.smallMap = new am4maps.SmallMap();
chart.smallMap.series.push(polygonSeries);
chart.smallMap = new am4maps.SmallMap();
chart.smallMap.series.push(polygonSeries);
{
// ... map config
"series": [{
"type": "MapPolygonSeries"
"id": "s1", // setting id so we can reference it in `smallMap`
// ...
}],
"smallMap": {
"series": ["s1"]
}
}

Please refer to the reference of SmallMap for available configuration options.

Open in CodePen

The map can automatically set filll color (and any other quantitative value for that matter) based on some arbitrary value, which allows us creating geographical heat-maps.

The functionality, which is not limited to just maps, is called “heat rules”, or (heatRules) and can be applied to any series, including series for map objects.

REQUIRED READING There’s a special section “Heat-maps” in our Series tutorial. Make sure you read it, because we’ll spare you the details here.

We’re going to assume you did read the Series article section linked above. Now, let’s put this into the context of maps.

Creating heat rules for map series is identical to creating heat rules for any series: you just specify start and end values, as well as the property, and what object, you need to be controlled by the rule:

polygonSeries.heatRules.push({
"property": "fill",
"target": polygonSeries.mapPolygons.template,
"min": am4core.color("#ffffff"),
"max": am4core.color("#AAAA00")
});

This basically instructs the series to “check "value" field in data for each polygon, and color it between white and greenish based on where that value stands in the spectrum of the whole range of values in the series”.

Here’s how it turned out:

Open in CodePen

Regular heat rules might work when you have values of comparable scale. However, should there be even one value that is significantly larger than other values, it would skew the whole heat map, since all other values would look very small in comparison, and thus would use basically the same color.

That’s where logarithmic rules come in.

If logarithmic: true is set for the rule, the value’s “mass” in the whole spectrum would be calculated not linear but logarithmic scale.

polygonSeries.heatRules.push({
"property": "fill",
"target": polygonSeries.mapPolygons.template,
"min": am4core.color("#ffffff"),
"max": am4core.color("#AAAA00"),
"logarithmic": true
});

As you can see in the first example above, just a few large-value polygons, makes difference between the rest of them nearly indistinguishable.

Enabling logarithmic scale fixes that.

Live example:

Open in CodePen

You know what goes well with a heat-map? Yes, a Heat Legend.

It’s a special kind of Legend, that instead of showing individual items, shows a spectrum of colors, available in some selected series.

REQUIRED READING Again, we’ll have to drop some required reading on you, as we have a dedicated “Heat Legend”.

There’s absolutely nothing special with adding Head Legend to a map: you create an instance of HeatLegend, you add it to one of the chart containers as a child, you hook the legend up with your map series, so that it knows where to get its data from:

let heatLegend = chart.createChild(am4maps.HeatLegend);
heatLegend.series = polygonSeries;
heatLegend.width = am4core.percent(100);

And, if we’re especially generous with our users, we might also add some interactivity, so that when user rolls over some polygon, we show where in the overall spectrum of values it stands:

polygonSeries.mapPolygons.template.events.on("over", function(ev) {
if (!isNaN(ev.target.dataItem.value)) {
heatLegend.valueAxis.showTooltipAt(ev.target.dataItem.value)
}
else {
heatLegend.valueAxis.hideTooltip();
}
});
polygonSeries.mapPolygons.template.events.on("out", function(ev) {
heatLegend.valueAxis.hideTooltip();
});

Let’s see how that went:

Open in CodePen

You can also add a Legend with custom items in it. For more details and a working demo, check out “Adding custom Items” section in our “Legend” article.

So far we have shown you how to use map series’ data property to add map objects like polygons, lines and markers.

It’s a convenient way to add a lot of data to map. However, there’s another way - creating objects of appropriate type, then setting their properties directly.

Since you’re setting properties directly on the object, there’s no need to map fields like “latitude” and “longitude” to data. That’s the most valuable advantage of using objects over data.

The most common way to create an object in the series is to use its create() method:

let france = polygonSeries.mapPolygons.create();
france.id = "FR";
let paris = imageSeries.mapImages.create();
paris.latitude = 48.856614;
paris.longitude = 2.352222;
let nyc = imageSeries.mapImages.create();
nyc.latitude = 40.712776;
nyc.longitude = -74.005973;
let paris_nyc = lineSeries.mapLines.create();
paris_nyc.imagesToConnect = [paris, nyc];

There is no need to push() newly created objects into series - create() will take care of that.

Calling create() will create a new object out of the series template, like we’ve shown you earlier. So you might want to configure templates before starting creating objects.

If you didn’t specify line series points via series data, or do not have images which you can use in imagesToConnect, you can set line’s multiGeoLine property directly:

let line = lineSeries.mapLines.create();
line.multiGeoLine = [[
{ "latitude": 48.856614, "longitude": 2.352222 },
{ "latitude": 40.712775, "longitude": -74.005973 },
{ "latitude": 49.282729, "longitude": -123.120738 }
]];

By default all objects on the map - polygons, images, and lines - will scale in size when you zoom in the map.

To disable such scaling we do have to configure relative templates.

To prevent map images from scaling we have to set nonScaling = true on the template of the map images.

Similarly, to prevent map lines to getting thicker with zoom, we can set nonScalingStroke = true on their respective templates.

imageSeries.mapImages.template.nonScaling = true;
// ...
lineSeries.mapImages.template.nonScalingStroke = true;

In some cases, you might want to to give your map a background.

Sure, you can give your whole Map Chart a fill background using its background property:

chart.background.fill = am4core.color("#aadaff");chart.background.fillOpacity = 1;

However, that would fill the whole area of the map, which in some projections could look a bit off:

Map with full background fill

The solution is to use “background series”, that are readily accessible via map’s backgroundSeries property.

It holds a Series of MapPolygonSeries so you can configure its appearance just like a regular Polygon series.

The difference is that it will automatically add a polygon, shaped exactly as the map, so you don’t have to add any data into it - just configure its polygon template:

chart.backgroundSeries.mapPolygons.template.polygon.fill = am4core.color("#aadaff");chart.backgroundSeries.mapPolygons.template.polygon.fillOpacity = 1;
Using backgroundSeries to apply fill

Better, isn’t it?

Open in CodePen

Since version 4.3.0 Map Chart supports rotation around any of its axes: vertical (Y), horizontal (X), and perpendicular (Z).

While the map starts off centered perfectly - like you are used to seeing the maps - it can be rotated in any of the directions around any of its axes using deltaLongitude, deltaLatitude, and deltaGamma properties respectively.

Want to create a Pacific-centered map? Or just about any-longitude-centered?

You can use MapChart’s deltaLongitude property to set longitude offset when centering the map.

Basically, in layman’s terms, it means “move the map center by X degrees”.

If longitude zero is center by default, and we set deltaLongitude = -160 the longitude 20 will be our new center, creating a Pacific-centered map:

chart.deltaLongitude = -160;
Open in CodePen

Want to show a map of Antarctica? Rotate it along it’s horizontal axis using deltaLatitude:

chart.deltaLatitude = 90;
Open in CodePen

Since version 4.3.0 Map Chart supports automatic grid.

The grid consists of a vertical and horizontal grid lines - let’s call it “graticules” - drawn every X latitude and longitude.

They also follow map projection, so they give the whole map “shape”.

To enable grid, simply add an additional series of type GraticuleSeries, just like you would add any other series.

let grid = chart.series.push(new am4maps.GraticuleSeries());grid.toBack();

This will cause a simple gray lines drawn every 10 longitude and latitude.

Open in CodePen

If you’re happy with how default grid looks, you can leave it at that. But if you’d like to give it some personal touch, there’s plenty of options.

GraticuleSeries is in a nutshell a version of MapLineSeries.

This means that we can configure the appearance of the lines just like we would on a regular Map line series: via its mapLines.template property.

GraticuleSeries automatically creates the lines for us, so we don’t need to add any data to it.

grid.mapLines.template.line.stroke = am4core.color("#e33");grid.mapLines.template.line.strokeOpacity = 0.2;

Besides regular Map line series settings, you also have specific settings that are relevant only to the grid. Such as longitudeStep and latitudeStep.

Those, as the name suggests, define density of the grid, e.g. draw a graticule every X longitude or latitude.

grid.longitudeStep = 20;grid.latitudeStep = 20;
Open in CodePen

By default, map grid (or GraticuleSeries) will automatically cover the area occupied by other Polygon series.

If we’d rather span it whole area of the world, we can disable this “cropping” using its fitExtent property:

grid.fitExtent = false;

By default, when you drag the map by mouse or touch, it will just move the whole of it as it is.

Using Map Chart’s panBehavior setting we can modify this functionality according to our needs.

Available settings are as follows:

  • "move" (default) - moves the whole of the map without modifying it.
  • "rotateLong" - rotates the map using its vertical axis, same as changing deltaLongitude.
  • "rotateLat" - rotates the map using its horizontal axis, same as changing deltaLatitude.
  • "rotateLongLat" - rotates the map freely around both of its axes, same as changing deltaLongitude and deltaLatitude.

So, if we’d like to allow our users to rotate globe in any direction, we’d just do this:

chart.panBehavior = "rotateLongLat";
Open in CodePen