MapSnippets logoMapSnippets logo

Choropleth Map

Color regions by data values for thematic visualization Each library has its own approach: here's how they compare.

Quick Recommendations

Leaflet — Best for simple choropleths with GeoJSON data where you style each feature with a JavaScript function and need interactive hover/click.

MapLibre GL JS — Makes the most sense when choropleth colors should update dynamically using match or interpolate expressions without re-rendering features.

OpenLayers — A strong fit when choropleth data comes from WFS or other OGC sources and needs integration with OpenLayers' style function pipeline.

MapTiler SDK — The MapTiler SDK take: MapLibre's expression-based choropleth, with simplified style configuration.

At a Glance

LeafletMapLibreOpenLayersSDK
Coordinates[lat, lng][lng, lat]fromLonLat([lng, lat])[lng, lat]
RenderingDOM + Canvas (2D)WebGL (GPU, 60fps)Canvas + WebGLWebGL (GPU, 60fps)
Bundle size~42 KB gzipped~250 KB gzipped~290 KB gzipped (tree-shakeable)~280 KB gzipped (includes MapLibre)
Learning curveEasiest (simple API, great tutorials)Moderate (source/layer model, expressions)Steepest (projections, OGC standards, verbose API)Easy (MapLibre API with convenience helpers)
3D supportNo native 3D (2D only)Full: terrain, fill-extrusion, globe, skyLimited: WebGL layers, no native globeFull (same as MapLibre)
Code lines18243025

Code Snippets

Click a library to expand its code. Each snippet shows only the core logic: the scaffold handles HTML boilerplate.

Leaflet18 linesShow code
arrow_forwardFull example
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);
MapLibre GL JS24 linesShow code
arrow_forwardFull example
const map = new maplibregl.Map({
  container: 'map',
  style: 'https://api.maptiler.com/maps/streets-v4/style.json?key=YOUR_MAPTILER_KEY',
  center: [10, 50],
  zoom: 4
});
map.on('load', () => {
  const data = { type: 'FeatureCollection', features: [
    { type: 'Feature', properties: { name: 'France', value: 67 }, geometry: { type: 'Polygon', coordinates: [[[-5,42],[8,42],[8,51],[-5,51],[-5,42]]] } },
    { type: 'Feature', properties: { name: 'Germany', value: 83 }, geometry: { type: 'Polygon', coordinates: [[[6,47],[15,47],[15,55],[6,55],[6,47]]] } },
    { type: 'Feature', properties: { name: 'Spain', value: 47 }, geometry: { type: 'Polygon', coordinates: [[[-10,36],[4,36],[4,44],[-10,44],[-10,36]]] } },
    { type: 'Feature', properties: { name: 'Italy', value: 60 }, geometry: { type: 'Polygon', coordinates: [[[7,37],[19,37],[19,47],[7,47],[7,37]]] } }
  ] };
  map.addSource('countries', { type: 'geojson', data });
  const labels = map.getStyle().layers.find(l => l.type === 'symbol' && /label/.test(l.id));
  map.addLayer({ id: 'choropleth', type: 'fill', source: 'countries',
    paint: { 'fill-color': ['interpolate', ['linear'], ['get', 'value'], 40, '#fee5d9', 60, '#fcae91', 80, '#fb6a4a', 100, '#cb181d'], 'fill-opacity': 0.6 }
  }, labels ? labels.id : undefined);
  map.addLayer({ id: 'choropleth-border', type: 'line', source: 'countries', paint: { 'line-color': '#333', 'line-width': 1 } });
  map.on('click', 'choropleth', (e) => {
    const f = e.features[0];
    new maplibregl.Popup().setLngLat(e.lngLat).setHTML(`<b>${f.properties.name}</b><br>Pop: ${f.properties.value}M`).addTo(map);
  });
});
OpenLayers30 linesShow code
arrow_forwardFull example
function getColor(value) {
  return value > 80 ? '#b5179e' : value > 60 ? '#e63946' : value > 40 ? '#f4a261' : value > 20 ? '#e9c46a' : '#2a9d8f';
}
var geojson = {
  type: 'FeatureCollection',
  features: [
    { type: 'Feature', properties: { name: 'Zone A', value: 85 }, geometry: { type: 'Polygon', coordinates: [[[2.30, 48.87], [2.34, 48.87], [2.34, 48.85], [2.30, 48.85], [2.30, 48.87]]] } },
    { type: 'Feature', properties: { name: 'Zone B', value: 45 }, geometry: { type: 'Polygon', coordinates: [[[2.34, 48.87], [2.38, 48.87], [2.38, 48.85], [2.34, 48.85], [2.34, 48.87]]] } },
    { type: 'Feature', properties: { name: 'Zone C', value: 15 }, geometry: { type: 'Polygon', coordinates: [[[2.30, 48.85], [2.34, 48.85], [2.34, 48.83], [2.30, 48.83], [2.30, 48.85]]] } }
  ]
};
var vectorLayer = new ol.layer.Vector({
  source: new ol.source.Vector({
    features: new ol.format.GeoJSON().readFeatures(geojson, { featureProjection: 'EPSG:3857' })
  }),
  style: function(feature) {
    return new ol.style.Style({
      fill: new ol.style.Fill({ color: getColor(feature.get('value')) + '99' }),
      stroke: new ol.style.Stroke({ color: '#333', width: 1 })
    });
  }
});
var map = new ol.Map({
  target: 'map',
  layers: [
    new ol.layer.Tile({ source: new ol.source.XYZ({ url: 'https://api.maptiler.com/maps/streets-v4/256/{z}/{x}/{y}.png?key=YOUR_MAPTILER_KEY', attributions: '&copy; <a href="https://www.maptiler.com/copyright/">MapTiler</a>' }) }),
    vectorLayer
  ],
  view: new ol.View({ center: ol.proj.fromLonLat([2.34, 48.855]), zoom: 14 })
});
MapTiler SDK25 linesShow code
arrow_forwardFull example
maptilersdk.config.apiKey = 'YOUR_MAPTILER_KEY';
const map = new maptilersdk.Map({
  container: 'map',
  style: maptilersdk.MapStyle.STREETS,
  center: [15.0, 50.0],
  zoom: 4
});
map.on('load', () => {
  map.addSource('countries', {
    type: 'geojson',
    data: 'https://d2ad6b4ur7yvpq.cloudfront.net/naturalearth-3.3.0/ne_50m_admin_0_countries.geojson'
  });
  map.addLayer({
    id: 'countries-fill', type: 'fill', source: 'countries',
    paint: {
      'fill-color': ['interpolate', ['linear'], ['get', 'pop_est'],
        0, '#e8d5f5', 10000000, '#ce93d8', 100000000, '#9C27B0', 1000000000, '#4a148c'],
      'fill-opacity': 0.7
    }
  });
  map.addLayer({
    id: 'countries-border', type: 'line', source: 'countries',
    paint: { 'line-color': '#2a2a2a', 'line-width': 1 }
  });
});

Things to Know

Implementation-specific details that matter for choropleth map.

Leaflet

Style function runs per-feature and returns { fillColor, fillOpacity, ... }; updating colors requires re-setting style on each feature.

MapLibre GL JS

Uses match or step expressions in the fill-color paint property to map data values to colors declaratively.

OpenLayers

Style function receives each feature and returns an ol.style.Style; supports complex multi-pass rendering.

MapTiler SDK

Same expression-based approach as MapLibre for fill-color; identical GeoJSON source and fill layer configuration.

Which Library to Use?

auto_awesome

Build Maps with AI

Generate code like these examples using mapping library skills in your IDE.