Skip to content

Thematic maps

Three thematic maps, each written as one classify: block on an ordinary layer. Every one compiles to a plain MapLibre expression and generates its legend. How it works, and every option: Thematic maps.

Choropleth — quantile classes from the data

Section titled “Choropleth — quantile classes from the data”

Median household income by tract, in five quantile classes (the same number of tracts in each). The breaks are computed when the GeoJSON loads; the legend — automatic, since the document declares none — shows their real edges, formatted as currency. Tracts with no value — null, missing, "", "n/a" in this sample — are light grey, and the numeric strings a CSV export leaves behind ("48200") count as numbers.

Synthetic sample tracts. Click a tract for its value
choropleth.yaml
# yaml-language-server: $schema=https://docs.maplibre-yaml.org/schema/latest/map.schema.json
# A choropleth: tracts coloured by median income in five quantile classes.
# The breaks are computed from the data when it loads (GeoJSON source), and
# the legend shows their real edges. Tracts with no value draw light grey.
# No legend: block — a map with a classify: layer gets its legend
# automatically (legend: false turns it off).
# Data: synthetic tracts made for this example.
type: map
id: classify-choropleth
config:
center: [-75.19, 39.936]
zoom: 12
mapStyle: "https://tiles.openfreemap.org/styles/positron"
sources:
tracts:
type: geojson
url: /data/classify/sample-tracts.geojson
layers:
- id: income
type: fill
source: tracts
label: Median household income
classify:
field: median_income
method: quantile
classes: 5
palette: Blues
format: { currency: USD, max-fraction-digits: 0 }
paint:
fill-opacity: 0.85
fill-outline-color: "#ffffff"
interactive:
click:
popup:
- h4:
- property: tract
- p:
- str: "Median income: "
- property: median_income

normalize: population and multiply: 100000 turn a count into a rate, and the natural breaks are computed on the rate. The tract with no residents is no data (grey), not an Infinity class.

Synthetic sample tracts
rate.yaml
# yaml-language-server: $schema=https://docs.maplibre-yaml.org/schema/latest/map.schema.json
# A rate, not a count: cases per 100,000 residents in each tract. normalize:
# divides by population and multiply: scales it; breaks (here jenks) are
# computed on the rate. A tract with no residents (population 0 or missing)
# is no data — never an Infinity class. Data: synthetic tracts.
type: map
id: classify-rate
config:
center: [-75.19, 39.936]
zoom: 12
mapStyle: "https://tiles.openfreemap.org/styles/positron"
sources:
tracts:
type: geojson
url: /data/classify/sample-tracts.geojson
layers:
- id: rate
type: fill
source: tracts
label: Cases per 100,000 residents
classify:
field: cases
normalize: population
multiply: 100000
method: jenks
classes: 5
palette: OrRd
format: { max-fraction-digits: 0 }
paint:
fill-opacity: 0.85
fill-outline-color: "#ffffff"

Graduated symbols — natural breaks, colour and size

Section titled “Graduated symbols — natural breaks, colour and size”

World cities by population in five natural-breaks (jenks) classes. One block drives both circle-color and circle-radius from the same breaks.

Natural Earth populated places (public domain)
graduated.yaml
# yaml-language-server: $schema=https://docs.maplibre-yaml.org/schema/latest/map.schema.json
# Graduated symbols: world cities sized and coloured by population in five
# natural-breaks (jenks) classes. One classify block drives circle-color and
# circle-radius from the same breaks; the legend draws each class's circle.
# Data: Natural Earth populated places (public domain).
type: map
id: classify-graduated
config:
center: [10, 25]
zoom: 1.3
mapStyle: "https://tiles.openfreemap.org/styles/positron"
legend:
position: bottom-left
sources:
cities:
type: geojson
url: /data/classify/world-cities.geojson
layers:
- id: cities
type: circle
source: cities
label: City population
classify:
field: pop
method: jenks
classes: 5
palette: YlOrRd
radius: [2, 4, 7, 10, 14]
paint:
circle-opacity: 0.85
circle-stroke-color: "#ffffff"
circle-stroke-width: 1
interactive:
click:
popup:
- h4:
- property: name
- p:
- str: "Population: "
- property: pop
format: ",.0f"

The demo tiles’ countries coloured by continent with okabe-ito, a colour-blind-safe palette. Categories compile to a match and need no data scan, so they work on a basemap’s vector tiles; values not listed (Antarctica) take the other: colour.

categories.yaml
# yaml-language-server: $schema=https://docs.maplibre-yaml.org/schema/latest/map.schema.json
# Categories over vector tiles: the demo tiles' countries coloured by
# continent with a CVD-safe palette. Categories need no data scan, so they
# work on tiled sources exactly as on GeoJSON (computed breaks do not).
type: map
id: classify-categories
config:
center: [15, 20]
zoom: 1.2
mapStyle: "https://demotiles.maplibre.org/style.json"
legend:
position: bottom-left
layers:
- id: continents
type: fill
source: maplibre
source-layer: countries
before: countries-boundary
label: Continent
classify:
field: CONTINENT
categories: [Africa, Asia, Europe, North America, South America, Oceania]
palette: okabe-ito
other: "#d9d9d9"
paint:
fill-opacity: 1

mlym emit choropleth.yaml writes a style.json whose fill-color is the compiled step (with the breaks computed from the data at export) and whose layer metadata["maplibre-yaml:classify"] records the classes. It opens in Maputnik and renders in any MapLibre map — no maplibre-yaml required.