Anatomy of a Map Chart

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

Creating a Map

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.

Importing modules/scripts

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.

Map data

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?

TypeScript/ES6

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

JavaScript

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.

Creating map instance

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);

Map definition

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.

About GeoJSON

"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.

MORE INFO Check official GeoJSON website and their RFC specs.

Using map definition

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.

Regional and county maps

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.

Loading external maps

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.

Setting projection

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);

NOTE Not finding projection you need? You can also use any of the D3's projections in amCharts 4.

Bundled maps

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.

NOTE We're constantly adding new maps. Check currently available maps here.

Low vs high-detail

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:

See the Pen amCharts V4: Map (Low vs High detail) by amCharts (@amcharts) on CodePen.24419

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

Detail differences between low and high detail versions

Ultra-high detail

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.

Finding more maps

@todo Information being prepared.

Creating custom maps

Please refer to this article on creating custom maps.

Map series

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.

NOTE We are going to use polygon series (MapPolygonSeries) in this section to explain generic concepts of the map series. Then, later on we will examine each different type of map series in detail.

Polygon (area) series

Creating polygon series

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);

Loading series data

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:

See the Pen amCharts V4: Map (simple, step 1) by amCharts (@amcharts) on CodePen.24419

Configuring series

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:

See the Pen amCharts V4: Map (simple, step 2) by amCharts (@amcharts) on CodePen.24419

MORE INFO For more about binding data in tooltip visit "Formatters" article.

MORE INFO For more about visual object states visit "States" article.

Object identifiers

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.

Including and excluding objects

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"]
  }]
}

See the Pen amCharts V4: Map (simple, step 3) by amCharts (@amcharts) on CodePen.24419

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:

See the Pen amCharts V4: Map (simple, 4) by amCharts (@amcharts) on CodePen.24419

In-line data

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}",
      // ...
    }
  }]
}
Updated tooltip

NOTE Other type of series might act a bit differently, for example line series (MapLineSeries) will use the data objects to draw lines on the map.

Binding properties to data

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"
      }
    }
  }]
}

See the Pen amCharts V4: Map (simple, step 5) by amCharts (@amcharts) on CodePen.24419

NOTE Using data is not the only way. You can create series objects one by one. Please refer to chapter "Using objects" at the end of this article for further info.

Custom polygon series

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.

Note about data mutation

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))
}

Multiple polygon series

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:

See the Pen amCharts V4: Using multiple map series by amCharts (@amcharts) on CodePen.24419

NOTE As a nice added bonus side-effect of using multiple series, we can now use a regular Legend, complete with automatic rendering, and even toggling.

Overlaying multiple maps

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:

See the Pen amCharts 4: Overlaying multiple maps and series by amCharts (@amcharts) on CodePen.24419

Sorting polygons

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

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.

Image object

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.

Creating image series

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",
    // ...
  }]
}

Configuring image series

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.

Binding marker properties to data

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"
      }
    }
  }]
}

Image data

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:

See the Pen amCharts V4: Map (simple, step 5) by amCharts (@amcharts) on CodePen.24419

NOTE Using data is not the only way. You can create series objects one by one. Please refer to chapter "Using objects" at the end of this article for further info.

Using actual images

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

Line series

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.

Line object

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
    ]
  ]
}]

Creating line series

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:

See the Pen amCharts V4: Map (simple, step 6) by amCharts (@amcharts) on CodePen.24419

NOTE Notice, in the code of the example, we have images section after lines. The map adds elements i the order they are set to the map, which affects the order of display. We want lines to be behind markers, hence lines code must come first.

NOTE Using data is not the only way. You can create series objects one by one. Please refer to chapter "Using objects" at the end of this article for further info.

Configuring line appearance

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"
      }
    }
  }]
}

See the Pen amCharts V4: Map (simple, step 7) by amCharts (@amcharts) on CodePen.24419

Curved vs. straight lines

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.

Using images as line points

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];

Line series types

MORE INFO Please refer to the separate "Map Lines" article for detailed description, demos and code, explaining different types of Line series used on Map chart.

Built-in separator lines

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

Additional controls

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

Zoom control

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
    }
  }
}

Small map

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.

MORE INFO For more options about how to configure Small map check out "Configuring Small Map".

Full map demo

See the Pen amCharts V4: Map (simple, step 7) by amCharts (@amcharts) on CodePen.24419

Geographical heat-maps

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

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")
});
polygonSeries.heatRules.push({
  "property": "fill",
  "target": polygonSeries.mapPolygons.template,
  "min": am4core.color("#ffffff"),
  "max": am4core.color("#AAAA00")
});
{
  // ...
  "series": [{
    "type": "MapPolygonSeries",
    // ...
    "heatRules": [{
      "target": "mapPolygons.template",
      "property": "fill",
      "min": "#ffffff",
      "max": "#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:

See the Pen amCharts V4: Map (heat-map) by amCharts (@amcharts) on CodePen.24419

Logarithmic rules

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
});
polygonSeries.heatRules.push({
  "property": "fill",
  "target": polygonSeries.mapPolygons.template,
  "min": am4core.color("#ffffff"),
  "max": am4core.color("#AAAA00"),
  "logarithmic": true
});
{
  // ...
  "series": [{
    "type": "MapPolygonSeries",
    // ...
    "heatRules": [{
      "target": "mapPolygons.template",
      "property": "fill",
      "min": "#ffffff",
      "max": "#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:

See the Pen US heat (choropleth) map by amCharts team (@amcharts) on CodePen.24419

IMPORTANT If you are using a heat legend (next chapter) you will need to enable logarithmic scale for it individually. See "Heat Legend" article for more details.

Adding a heat legend

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);
var heatLegend = chart.createChild(am4maps.HeatLegend);
heatLegend.series = polygonSeries;
heatLegend.width = am4core.percent(100);
{
  // ...
  "series": [{
    "id": "s1",
    // ...
  }],
  // ...
  "children": [{
    "type": "HeatLegend",
    "series": "s1",
    "width": "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();
});
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:

See the Pen amCharts V4: Map (heat-map, 2) by amCharts (@amcharts) on CodePen.24419

IMPORTANT If you are using a heat legend (next chapter) you will need to enable logarithmic scale for it individually. See "Heat Legend" article for more details.

Adding a custom legend

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.

Using objects

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.

Creating series object

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];
var france = polygonSeries.mapPolygons.create();
france.id = "FR";

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;

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.

Specifying map line points directly

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 }
]];
var line = lineSeries.mapLines.create();

line.multiGeoLine = [[
  { "latitude": 48.856614, "longitude": 2.352222 },
  { "latitude": 40.712775, "longitude": -74.005973 },
  { "latitude": 49.282729, "longitude": -123.120738 }
]];
{
  // ...
  "series": [{
    "type": "MapLineSeries",
    "mapLines": [{
      "multiGeoLine": [[
        { "latitude": 48.856614, "longitude": 2.352222 },
        { "latitude": 40.712775, "longitude": -74.005973 },
        { "latitude": 49.282729, "longitude": -123.120738 }
      ]]
    }]
  }]
}

Controlling scaling of map objects

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;
imageSeries.mapLines.template.nonScaling = true;
// ...
lineSeries.mapLines.template.nonScalingStroke = true;
{
  // ...
  "series": [{
    "type": "MapImageSeries",
    // ...
    "mapImages": {
      // ...
      "nonScaling": true
    }
  }, {
    "type": "MapLineSeries",
    // ...
    "mapLines": {
      // ...
      "nonScalingStroke": true
    }
  }]
}

Map background

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

Static fill

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;
chart.background.fill = am4core.color("#aadaff");
chart.background.fillOpacity = 1;
{
// ...
"background": {
"fill": "#aadaff",
"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

Background series

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;
chart.backgroundSeries.mapPolygons.template.polygon.fill = am4core.color("#aadaff");
chart.backgroundSeries.mapPolygons.template.polygon.fillOpacity = 1;
{
// ...
"backgroundSeries": {
"mapPolygons": {
"polygon": {
"fill": "#aadaff",
"fillOpacity": 1
}
}
}
}
Using backgroundSeries to apply fill

Better, isn't it?

See the Pen amCharts 4: backgroundSeries by amCharts team (@amcharts) on CodePen.0

Map rotation

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.

Setting map center

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;
chart.deltaLongitude = -160;
{  
// ... map config
"deltaLongitude": -160
}

See the Pen amCharts 4: Pacific-centered map by amCharts (@amcharts) on CodePen.24419

Rotating latitude

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

chart.deltaLatitude = 90; 
chart.deltaLatitude = 90; 
{  
// ... map config
"deltaLatitude": 90
}

See the Pen amCharts 4: Map of Antarctica by amCharts team (@amcharts) on CodePen.0

Map grid

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".

Enabling grid

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();
var grid = chart.series.push(new am4maps.GraticuleSeries());
grid.toBack();
{
// ...
"series": [{
// ...
}, {
"type": "GraticuleSeries",
"zIndex": -1
}]
}

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

See the Pen amCharts 4: backgroundSeries by amCharts team (@amcharts) on CodePen.0

Configuring grid

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;
grid.mapLines.template.line.stroke = am4core.color("#e33");
grid.mapLines.template.line.strokeOpacity = 0.2;
{
// ...
"series": [{
// ...
}, {
"type": "GraticuleSeries",
"zIndex": -1,
"mapLines": {
"line": {
"stroke": "#e33",
"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;
grid.longitudeStep = 20;
grid.latitudeStep = 20;
{
// ...
"series": [{
// ...
}, {
"type": "GraticuleSeries",
// ...
"longitudeStep": 20,
"latitudeStep": 20
}]
}

See the Pen amCharts 4: Map grid by amCharts team (@amcharts) on CodePen.0

Extent of the grid

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;
grid.fitExtent = false;
{
// ...
"series": [{
// ...
}, {
"type": "GraticuleSeries",
// ...
"fotExtent": false
}]
}

Interacting with map

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";
chart.panBehavior = "rotateLongLat";
{
"panBehavior": "rotateLongLat"
}

See the Pen amCharts 4: Pan behavior by amCharts team (@amcharts) on CodePen.0

Related content

Zooming

Interacting

Projection-related

Other topics

Related demos