Interactive Maps Made Easy with amCharts: From Setup to Production Dashboard
Interactive maps in a dashboard used to mean half a sprint of D3 plumbing. amCharts 5 turns it into about thirty lines of code โ without giving up the polish a real product needs. Here is the practical setup, the data-binding patterns, and the gotchas teams hit on the way to production.
Full-stack web developer with hands-on production experience in React, Next.js, Node.js, PostgreSQL, and Prisma. Founder of ToolsWaves โ a privacy-first toolkit of 35+ free developer and design utilities. I write every tutorial from real shipping experience, focusing on performance, scalable architecture, and clean, type-safe code.

Inspect GeoJSON cleanly โ try our free JSON Formatter
JSON Formatter
Why Interactive Maps Are Worth the Effort
A static image of a map answers one question. An interactive map answers every question a user thinks to ask: hover for the underlying number, click to drill into a region, zoom into a country to see provinces, filter by date range, switch from absolute counts to per-capita rates. The same chunk of dashboard real estate works ten times harder once it becomes interactive โ and modern users have stopped accepting static maps in any product that calls itself analytics.
The catch has historically been the implementation cost. D3 gives you everything but assumes you want to assemble the wheel yourself; Leaflet is excellent for true geographical tile maps but verbose for choropleths and heatmaps; Google Maps charges you for what should be a static data overlay. amCharts 5 sits in the gap: a high-level API tuned specifically for data visualization rather than navigation, with built-in support for the chart types that fill actual dashboards โ choropleths, bubble maps, heatmaps, route maps โ and good defaults for the polish details (animations, tooltips, responsive behavior) that take the longest to write by hand.
What amCharts Actually Gives You
amCharts 5 is a TypeScript-first charting library with a dedicated maps module. The map module bundles three things you would otherwise stitch together: a renderer that draws SVG or Canvas paths from GeoJSON, a data-binding layer that ties your business data to geographic features, and a polish layer for tooltips, animations, zoom, pan, and responsive scaling. The headline benefit is that the same library handles everything from a simple country-shaded chart to a real-time bubble map updating from a websocket.
- Country and region-level choropleths โ color each shape by your data value
- Bubble and pie-bubble maps โ proportional circles at city or region centroids
- Heatmaps โ density-based overlays for high-cardinality location data
- Route maps โ animated lines between origin and destination pairs
- Drill-down navigation โ click a country, zoom into its provinces or states
- Real-time updates โ push new values without re-rendering the whole map
The free tier ships with the full library plus a small attribution link in the corner; the commercial license removes it. For internal dashboards the attribution is rarely a blocker; for public-facing product it usually is. Budget for the license before you ship to customers.
Installation and Basic Setup
Two packages cover almost every map use case โ the core library and the maps module. The geodata package ships the pre-built GeoJSON files for every country and continent, so you do not have to find and host them yourself.
npm install @amcharts/amcharts5 @amcharts/amcharts5-geodataamcharts5-geodata is the unsung hero of this stack. It bundles cleaned, optimized GeoJSON for the world, every continent, every country, and most subdivisions (US states, EU regions, Indian states, etc.). Without it you would spend a day hunting down the right shapefile, converting it, and stripping the polygon detail that bloats your bundle. With it, you import a constant and the geometry is ready.
Your First Interactive Map โ Thirty Lines
A world map with country-level data shading takes less code than people expect. The pattern below is the minimum viable map: load the world, color it by an arbitrary value, show a tooltip on hover.
import * as am5 from '@amcharts/amcharts5';
import * as am5map from '@amcharts/amcharts5/map';
import am5geodata_worldLow from '@amcharts/amcharts5-geodata/worldLow';
// Mount on a div with id="chartdiv" in your component.
const root = am5.Root.new('chartdiv');
const chart = root.container.children.push(
am5map.MapChart.new(root, {
projection: am5map.geoMercator(),
})
);
const series = chart.series.push(
am5map.MapPolygonSeries.new(root, {
geoJSON: am5geodata_worldLow,
valueField: 'value',
calculateAggregates: true,
})
);
series.mapPolygons.template.setAll({
tooltipText: '{name}: {value}',
interactive: true,
fill: am5.color(0x2a2a35),
strokeWidth: 0.5,
});
series.set('heatRules', [{
target: series.mapPolygons.template,
dataField: 'value',
min: am5.color(0x3b82f6),
max: am5.color(0xa78bfa),
key: 'fill',
}]);
series.data.setAll([
{ id: 'US', value: 320 },
{ id: 'IN', value: 1380 },
{ id: 'BR', value: 215 },
{ id: 'DE', value: 84 },
]);Read that and you have the full mental model. A Root mounts to your DOM node. A MapChart sits inside it with a projection. A MapPolygonSeries renders shapes from GeoJSON. heatRules tie data values to colors automatically. Data binds by matching the id field in your data array against the id property in the GeoJSON features (ISO 3166-1 alpha-2 country codes here). Everything else โ hover behavior, tooltips, responsive scaling โ is on by default.
Framework Integration: React, Vue, Angular
amCharts is framework-agnostic but every frontend framework has its own lifecycle concerns. The pattern below is the React idiom โ create the chart in useEffect, dispose it in the cleanup. Vue and Angular follow the same pattern with their respective mount/unmount hooks.
// React 18+ with TypeScript.
'use client';
import { useEffect, useRef } from 'react';
import * as am5 from '@amcharts/amcharts5';
import * as am5map from '@amcharts/amcharts5/map';
import am5geodata_worldLow from '@amcharts/amcharts5-geodata/worldLow';
export function WorldMap({ data }: { data: { id: string; value: number }[] }) {
const rootRef = useRef<am5.Root | null>(null);
const seriesRef = useRef<am5map.MapPolygonSeries | null>(null);
useEffect(() => {
const root = am5.Root.new('worldmap-container');
const chart = root.container.children.push(
am5map.MapChart.new(root, { projection: am5map.geoMercator() })
);
const series = chart.series.push(
am5map.MapPolygonSeries.new(root, {
geoJSON: am5geodata_worldLow,
valueField: 'value',
calculateAggregates: true,
})
);
rootRef.current = root;
seriesRef.current = series;
return () => root.dispose();
}, []);
// Re-bind data without recreating the chart.
useEffect(() => {
seriesRef.current?.data.setAll(data);
}, [data]);
return <div id="worldmap-container" style={{ width: '100%', height: 500 }} />;
}Two patterns to internalize. First, root.dispose() in the cleanup function is non-negotiable โ without it, React's hot-reload during development leaks chart instances and your dev tab becomes sluggish within minutes. Second, separate the chart creation effect from the data-update effect โ recreating the chart on every data change is wasteful and breaks animations.
Production Patterns That Matter
Five patterns that distinguish a working map from a production map:
1. Use the low-detail GeoJSON variants
amcharts5-geodata ships both /worldLow and /worldHigh variants of every map. The High versions are ~10x larger but only visibly higher resolution when zoomed in past country level. For most dashboards, /worldLow is the right default โ saves hundreds of kilobytes per chart with no visible quality loss at typical zoom.
2. Lazy-load the geodata bundle
GeoJSON for a single country can be 100-500KB minified. Static-import it eagerly and you slow down your initial page load on every dashboard that includes the map. Dynamic-import inside the useEffect so the bytes only arrive when the user actually opens the page with the chart.
3. Bind data by stable IDs, not country names
Country names are political and inconsistent ('United States' vs 'United States of America' vs 'USA'). ISO 3166-1 alpha-2 codes are stable. Always store and bind by ISO code; render the country name in the tooltip via {name} from the GeoJSON.
4. Pre-compute aggregates server-side
calculateAggregates: true makes the chart re-compute min/max on every data update. For dashboards with many countries or live-streamed updates, do the aggregation on the server (the same query that produces the data) and disable calculateAggregates. Saves visible CPU on every update.
5. Set explicit heat scale endpoints
Auto-scaled heat rules look broken when one country has a 10x outlier. Set the heatRules min/max values to your data's expected range (or to percentile-clipped values) so the color gradient stays meaningful even with outliers.
Drill-Down: Click a Country, Zoom Into Its Regions
One of the features that genuinely separates amCharts from simpler libraries is built-in drill-down navigation. You start with a world map; the user clicks France; the world fades out and France's regions fade in with their own data layer.
import am5geodata_franceLow from '@amcharts/amcharts5-geodata/franceLow';
series.mapPolygons.template.events.on('click', (ev) => {
const id = ev.target.dataItem?.dataContext?.id;
if (id === 'FR') {
// Swap the GeoJSON to France's regions.
series.set('geoJSON', am5geodata_franceLow);
series.data.setAll(franceRegionData);
chart.appear(1000, 100); // re-animate the new map in
}
});For full drill-down with a back button, ship a small breadcrumb component that tracks which level the user is on and reverts to the parent GeoJSON on click. amCharts provides hooks for this but does not ship a default breadcrumb โ most teams build a simple one in their framework of choice that lives alongside the chart.
Common Pitfalls
Mistakes that show up over and over when teams adopt amCharts maps for the first time:
- Forgetting root.dispose() in cleanup โ fastest path to memory leaks during development hot reloads and SPA navigation
- Importing high-detail GeoJSON for charts that only show country-level data โ wastes hundreds of KB of bundle size with no visible benefit
- Mixing country names from different sources in the data binding โ switch to ISO 3166 codes and the inconsistency disappears
- Server-side rendering the chart โ amCharts requires a DOM and window object. Always wrap with dynamic import (Next.js: { ssr: false }) or run in a useEffect
- Setting fixed pixel widths on the container โ maps should be fluid; let CSS control the parent size and amCharts will reflow into it on resize
- Forgetting the commercial license for public-facing products โ the attribution mark is unobtrusive but it is a license requirement, not a stylistic choice
When to Pick Something Else
amCharts is the right call for data-visualization maps โ dashboards, analytics, reports, any view where the geography is a canvas for showing numbers. It is the wrong call for navigation maps (driving directions, store finders, tile-based satellite imagery, search-by-address) โ for those, Mapbox GL JS or Leaflet with OpenStreetMap tiles are the standard. The mental test: if you would never zoom past 'see entire region' level, amCharts wins; if your users will pan and zoom to street level, switch to a tile-based map library.
Final Thoughts
Interactive maps used to be the most time-consuming chart type in any dashboard โ half a sprint of D3 wrangling for something most users glance at once and move on. amCharts 5 collapses that to about thirty lines of code without giving up the polish that makes maps feel like part of a serious product. Two things matter most in practice: bind data by stable IDs (ISO codes, not names) and dispose the root in your framework's cleanup hook. Get those right and the rest of the API is pleasant to work in. The choropleth is your default; bubble and heatmap series cover most of the cases choropleth does not; drill-down opens a second dimension when a single view is not enough. If you are building a dashboard or analytics product, amCharts maps are the lowest-effort way to add a map view that does not look like a 2015 prototype.
Open JSON Formatter โFrequently Asked Questions
Is amCharts free to use?
Free for personal projects, open-source projects, and non-profits โ with a small attribution mark in the corner. Commercial use requires a paid license that removes the attribution and adds business-grade support. Pricing is per-developer; check the amCharts site for current numbers. The technical features are identical between the free and paid versions.
How does amCharts compare to Leaflet or Mapbox?
Different use cases. Leaflet and Mapbox are tile-based map libraries optimized for navigation โ driving directions, search by address, street-level zoom. amCharts is a data-visualization library optimized for dashboards โ choropleths, bubble maps, route maps, heatmaps. Pick by user intent: 'show me a location' โ Leaflet/Mapbox; 'show me data overlaid on geography' โ amCharts.
Can I use amCharts with server-side rendering?
Not directly โ amCharts requires window and DOM access. In Next.js, dynamic-import the chart component with { ssr: false } so it only renders client-side. In Nuxt, wrap with <client-only>. In Angular, use @ViewChild in afterViewInit. The chart itself works perfectly; you just have to skip SSR for the component that mounts it.
How do I bind real-time data to an amCharts map?
Create the chart once, keep a ref to the series, and call series.data.setAll(newData) every time fresh data arrives. amCharts will animate the color transitions automatically. For high-frequency updates (multiple per second), throttle the updates to maybe 4-5 per second โ beyond that the animation budget cannot keep up.
What is the bundle-size impact of adding amCharts to a Next.js app?
Core + maps + worldLow geodata is roughly 200-300KB gzipped. Dynamic-import keeps it off the initial page bundle, so only users who actually open the page with the chart pay the cost. For dashboards where every user sees the chart, it is worth the bytes; for marketing pages with a single hero map, consider a server-side static SVG instead.
Can amCharts maps display Indian states, US states, or other country subdivisions?
Yes โ amcharts5-geodata ships pre-built GeoJSON for most country subdivisions. Import @amcharts/amcharts5-geodata/usaLow for US states, /indiaLow for Indian states, /ukLow for UK counties, and so on. Bind data by the subdivision's ISO 3166-2 code (US-CA for California, IN-MH for Maharashtra) and the rendering is identical to the world-map pattern.
Related Articles

Mastering Caching in Next.js: Request Memoization, Data Cache, Route Cache & Router Cache
Next.js ships with four distinct caching layers โ and most developers never touch three of them. Get them right and pages load instantly; get them wrong and you serve stale data or hammer your APIs. Here is the practical playbook.

Prisma + Next.js + PostgreSQL: The Modern Backend Stack Explained
Prisma turns your PostgreSQL schema into a type-safe API your Next.js code can use without ever writing raw SQL. Here is the practical setup, migration strategy, and the one command that quietly causes production incidents.

JSON Formatter Online: Format, Validate & Beautify JSON (Free Tool)
Working with messy JSON? Learn what a JSON formatter does, why it matters for developers, and how to format any JSON in seconds with our free online tool.