MapSnippets logoMapSnippets logo

Choropleth Map with Leaflet

Leaflet

Create a data-driven choropleth map that colors regions by value using inline GeoJSON and Leaflet.

progress_activityLoading map...
const map = L.map('map').setView([50, 10], 4);
L.tileLayer('https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=YOUR_MAPTILER_KEY', {
  tileSize: 256,
  attribution: '&copy; <a href="https://www.maptiler.com/">MapTiler</a> &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap contributors</a>'
}).addTo(map);
const getColor = d => d > 200 ? '#800026' : d > 150 ? '#BD0026' : d > 100 ? '#FC4E2A' : d > 50 ? '#FED976' : '#FFEDA0';
const countries = { type: 'FeatureCollection', features: [
  { type: 'Feature', properties: { name: 'France', density: 119 }, geometry: { type: 'Polygon', coordinates: [[[2.5,51.1],[6.2,49],[8.2,48.9],[7.5,47.5],[6.8,46.4],[3.6,43.7],[-1.8,43.4],[-1.2,46],[1.4,48.6],[2.5,51.1]]] }},
  { type: 'Feature', properties: { name: 'Germany', density: 232 }, geometry: { type: 'Polygon', coordinates: [[[6,51.8],[7,53.5],[10.4,54.8],[14.1,53.9],[14.7,51],[13.6,48.9],[12.5,47.6],[7.5,47.5],[6.2,49],[6,51.8]]] }},
  { type: 'Feature', properties: { name: 'Spain', density: 93 }, geometry: { type: 'Polygon', coordinates: [[[-9.3,43.1],[-1.8,43.4],[3.2,42.3],[3.3,39.5],[0.1,38.7],[-5.6,36.5],[-7.4,37],[-8.9,41.8],[-9.3,43.1]]] }},
  { type: 'Feature', properties: { name: 'Italy', density: 201 }, geometry: { type: 'Polygon', coordinates: [[[6.8,46.4],[12.4,46.8],[13.8,46.5],[12.3,41.8],[15.5,40],[16.5,38.1],[12.5,37.5],[9,39.2],[8.1,43.9],[6.8,46.4]]] }},
  { type: 'Feature', properties: { name: 'Poland', density: 124 }, geometry: { type: 'Polygon', coordinates: [[[14.1,53.9],[18.8,54.7],[23.5,54.2],[24,51.6],[23.5,50],[18.8,49.5],[14.7,51],[14.1,53.9]]] }},
  { type: 'Feature', properties: { name: 'Sweden', density: 25 }, geometry: { type: 'Polygon', coordinates: [[[11.1,58.8],[12.3,56],[16.3,56.5],[19.1,59.4],[20.5,63.5],[24.2,65.8],[20,69],[14,68.4],[11.9,63.1],[11.1,58.8]]] }}
]};
L.geoJSON(countries, {
  style: f => ({ fillColor: getColor(f.properties.density), weight: 2, color: '#fff', fillOpacity: 0.7 }),
  onEachFeature: (f, layer) => layer.bindPopup(`<strong>${f.properties.name}</strong><br>Density: ${f.properties.density}/km²`)
}).addTo(map);

How it works

A choropleth map colors geographic regions based on data values. The getColor() function maps density values to a color scale. L.geoJSON() renders each feature with a style function that reads the feature’s density property and returns the appropriate fill color.

The onEachFeature callback attaches interactive popups to each region. Note that GeoJSON uses [lng, lat] coordinate order: Leaflet’s L.geoJSON() handles the conversion automatically. The inline GeoJSON uses simplified country boundaries for demonstration purposes.

Key APIs

API Description Docs
L.geoJSON() Render GeoJSON data leafletjs.com/reference.html#geojson
style function Data-driven styling leafletjs.com/reference.html#geojson-style
onEachFeature Attach interactions per feature leafletjs.com/reference.html#geojson-oneachfeature

FAQ

How do I add a legend?

Create a custom L.control with onAdd returning a div with colored squares and labels. Position it with { position: 'bottomright' }.

Where can I get real country boundary GeoJSON?

Use Natural Earth data or the countries.geojson from the Leaflet choropleth tutorial. For production, host the file and load it with fetch().

Common mistakes

  • Forgetting that GeoJSON coordinates are [lng, lat] while Leaflet markers use [lat, lng]: L.geoJSON() handles the conversion
  • Using the same color for all regions because the style option is an object instead of a function
  • Not setting fillOpacity: the default is 0.2, which may look too faint for a choropleth
auto_awesome

Build maps using Leaflet with AI

Generate code like this choropleth map with leaflet example using the Leaflet skill in your IDE.