Custom polygons on Map Chart

If you are working with Map Chart, you're probably already familiar with MapPolygonSeries thatr get their geographical data from loaded map file, or "geodata". However, Map polygons are not limited to this source of data. You can draw any shape and polygon on the map using in-line lat/long coordinates. This tutorial will show how.

The task

In the course of this tutorial we will build a custom polygon, depicting a so called Bermuda triangle.

We've already created a map, pre-zoomed on the Caribbean, which we will use as a base.

See the Pen amCharts 4: Custom map shapes by amCharts (@amcharts) on CodePen.24419

Preparing the map

While not exactly necessary, let's add three map images - one for each corner of our triangle, so that we have some reference points.

let placeSeries = chart.series.push(new am4maps.MapImageSeries());

let place = placeSeries.mapImages.template;
place.nonScaling = true;
place.propertyFields.latitude = "latitude";
place.propertyFields.longitude = "longitude";
placeSeries.data = [{
latitude: 25.774,
longitude: -80.190,
name: "Miami",
hcenter: "right",
vcenter: "middle"
}, {
latitude: 18.466,
longitude: -66.118,
name: "San Juan",
hcenter: "left",
vcenter: "middle"
}, {
latitude: 32.321,
longitude: -64.757,
name: "Bermuda",
hcenter: "middle",
vcenter: "bottom"
}];

let circle = place.createChild(am4core.Circle);
circle.radius = 5;
circle.fill = am4core.color("#e33");
circle.stroke = am4core.color("#fff");
circle.strokeWidth = 2;

let label = place.createChild(am4core.Label);
label.padding(15, 15, 15, 15);
label.propertyFields.text = "name";
label.propertyFields.horizontalCenter = "hcenter";
label.propertyFields.verticalCenter = "vcenter";
var placeSeries = chart.series.push(new am4maps.MapImageSeries());

var place = placeSeries.mapImages.template;
place.nonScaling = true;
place.propertyFields.latitude = "latitude";
place.propertyFields.longitude = "longitude";
placeSeries.data = [{
latitude: 25.774,
longitude: -80.190,
name: "Miami",
hcenter: "right",
vcenter: "middle"
}, {
latitude: 18.466,
longitude: -66.118,
name: "San Juan",
hcenter: "left",
vcenter: "middle"
}, {
latitude: 32.321,
longitude: -64.757,
name: "Bermuda",
hcenter: "middle",
vcenter: "bottom"
}];

var circle = place.createChild(am4core.Circle);
circle.radius = 5;
circle.fill = am4core.color("#e33");
circle.stroke = am4core.color("#fff");
circle.strokeWidth = 2;

var label = place.createChild(am4core.Label);
label.padding(15, 15, 15, 15);
label.propertyFields.text = "name";
label.propertyFields.horizontalCenter = "hcenter";
label.propertyFields.verticalCenter = "vcenter";
{
// ..
"series": [{
// ...
}, {
"type": "MapImageSeries",
"data": [{
latitude: 25.774,
longitude: -80.190,
name: "Miami",
hcenter: "right",
vcenter: "middle"
}, {
latitude: 18.466,
longitude: -66.118,
name: "San Juan",
hcenter: "left",
vcenter: "middle"
}, {
latitude: 32.321,
longitude: -64.757,
name: "Bermuda",
hcenter: "middle",
vcenter: "bottom"
}],
"mapImages": {
"nonScaling": true,
"propertyFields": {
"latitude": "latitude",
"longitude": "longitude"
},
"children": [{
"type": "Circle",
"radius": 5,
"fill": "#e33",
"stroke": "#fff",
"strokeWidth": 2
}, {
"type": "Label",
"paddingTop": 15,
"paddingRight": 15,
"paddingBottom": 15,
"paddingLeft": 15,
"propertyFields": {
"text": "name",
"horizontalCenter": "hcenter",
"verticalCenter": "vcenter"
}
}]
}
}]
}

See the Pen amCharts 4: Custom map shapes (2) by amCharts (@amcharts) on CodePen.24419

Creating a polygon

Now that we have our reference points, we need to create our Map polygon.

We're going to create a separate MapImageSeries to hold our custom map polygons.

Differently from the main Map image series for countries, we are not going to set useGeodata because we don't want it to get polygon data from loaded map file - we will be defining polygon coordinates ourselves.

"Custom" polygon series works identically as regular polygon series, except we also need to provide GeoJSON data for each polygon.

The data property, responsible for holding that data is  geoPolygon. Just like in GeoJSON format for multi-polygon objects. In a nutshell it is an array of polygon objects. Each item in the array represents a single polygon, or a chunk of land. If there's one chunk, there will be one item. If there are more chunks, there will be several items in this array.

Each polygon is another array, that holds objects for each joint in the polygons contours, represented by latitude/longitude coordinates.

let shapeSeries = chart.series.push(new am4maps.MapPolygonSeries());

shapeSeries.data = [{
"title": "Bermuda triangle",
"geoPolygon": [
[
{latitude: 25.774, longitude: -80.190},
{latitude: 32.321, longitude: -64.757},
{latitude: 18.466, longitude: -66.118},
{latitude: 25.774, longitude: -80.190}
]
]
}];

let shapeTemplate = shapeSeries.mapPolygons.template;
shapeTemplate.stroke = am4core.color("#e33");
shapeTemplate.strokeWidth = 2;
shapeTemplate.fill = shapeTemplate.stroke;
shapeTemplate.fillOpacity = 0.2;
shapeTemplate.nonScalingStroke = true;
var shapeSeries = chart.series.push(new am4maps.MapPolygonSeries());

shapeSeries.data = [{
"title": "Bermuda triangle",
"geoPolygon": [
[
{latitude: 25.774, longitude: -80.190},
{latitude: 32.321, longitude: -64.757},
{latitude: 18.466, longitude: -66.118},
{latitude: 25.774, longitude: -80.190}
]
]
}];

var shapeTemplate = shapeSeries.mapPolygons.template;
shapeTemplate.stroke = am4core.color("#e33");
shapeTemplate.strokeWidth = 2;
shapeTemplate.fill = shapeTemplate.stroke;
shapeTemplate.fillOpacity = 0.2;
shapeTemplate.nonScalingStroke = true;
{
// ..
"series": [{
// ...
}, {
// ...
}, {
"type": "MapPolygonSeries",
"data": [{
"title": "Bermuda triangle",
"geoPolygon": [
[
{latitude: 25.774, longitude: -80.190},
{latitude: 32.321, longitude: -64.757},
{latitude: 18.466, longitude: -66.118},
{latitude: 25.774, longitude: -80.190}
]
]
}],
"mapPolygons": {
"stroke": "#e33",
"strokeWidth": 2,
"fill": "#e33",
"fillOpacity": 0.2,
"nonScalingStroke": true
}
}]
}

See the Pen amCharts 4: Custom map shapes (3) by amCharts (@amcharts) on CodePen.24419

IMPORTANT The order of points is important. According to GeoJSON specification, they must come in clockwise order. The first and last point should be present.

Final word

A triangle polygon we just created is not a mere few lines connected and filled.

It's a full-fledged MapPolygon object, which can be configured just like any other polygon, including setting appearance, property fields, states, events, etc.