A dot density map shows how much of something there is and where inside a region it sits. Scatter one dot for every hundred people across a country and the cities appear on their own, without a single boundary being drawn.
This guide covers what a dot density map is, how to choose the dot value, the placement problem that decides whether the map is honest, and how to build one on a web map.
What a dot density map is
A dot density map, also called a dot distribution map, shows quantity by placing a dot for every fixed number of units inside the region the units belong to.
Three things define one:
- The dot value. How many units one dot stands for. One dot equals 100 residents, 50 hectares of vineyard, 10 delivery drops.
- The dot size. How large each dot is drawn, in pixels or millimetres.
- The placement rule. Where inside the region the dots go.
The reader does not count individual dots. They read texture: solid ink where the quantity is concentrated, scattered specks where it is thin. That is the strength of the format, and the reason it survives a century after paper cartographers first hand-stippled them.
One-to-one dot maps are the special case where each dot is a real observation at real coordinates: every wind turbine, every pharmacy, every recorded outbreak. Those are true point maps, and unlike the aggregated kind, their positions can be trusted.
Dot density vs choropleth vs heat map
Three formats, three jobs.
A choropleth map fills each region with a single colour. It is the fastest to read and the easiest to build, and it makes every region look internally uniform. A choropleth of a rural county says the population is spread evenly across it, when in practice most of it lives in one town.
A dot density map keeps the count and shows the internal spread. Large empty regions stay visually empty, which fixes the area bias that plagues choropleth maps. The trade is precision: you cannot recover an exact regional value by eye.
A heat map smooths points into a continuous surface. It finds clusters well and hides quantities, because nothing on it declares what a given colour is worth.
The practical rule: rates go on a choropleth, counts go on dots, and cluster-hunting goes on heat. When area bias is the main problem and internal detail does not matter, a cartogram is the other fix.
Dot density map examples
Where the format earns its place:
- Population by census tract, the best-known use, where dot colour splits the population into groups and segregation patterns appear without any boundary being visible.
- Agricultural output. One dot per thousand head of cattle or per hundred hectares of a crop, the classic use in national atlases.
- Customers or orders by postal code, aggregated for privacy, so no dot maps to a household.
- Housing stock by type, with one colour for apartments and another for detached houses.
- Retail or competitor footprints, where the clustering is the insight.
- Disease case counts, when the aim is showing concentration rather than per-capita risk.
The multi-category version is what a choropleth genuinely cannot do. Two shades cannot occupy one polygon, but two dot colours can share a region and show their mix.
The placement problem
Here is where most dot density maps go wrong.
Dots are usually scattered at random inside the region polygon. That is fine as a statement about the region, and misleading as a statement about any point in it, because the random scatter puts dots in lakes, in industrial estates, on airfields, and on mountainsides where nobody lives.
Dasymetric placement is the fix. Instead of the whole polygon, restrict dots to the parts where the thing being mapped can actually be, using a mask: residential land use, building footprints, or a land cover layer. The regional totals stay the same and the dots land where they are plausible. Building footprints from open map data are the usual mask for population work, and for a country-scale map a coarse land cover mask is enough.
A second rule keeps the map honest across sessions: seed the randomness. An unseeded map redraws differently on every load, and a reader who notices that stops trusting it. Generate the dots once, store them, and serve the same set every time.
Choosing the dot value
The dot value decides whether the map reads at all. Two failure modes:
Too small a dot value produces so many dots that dense areas saturate into a solid blob. Once the ink is solid, more data changes nothing, and the map understates the peaks.
Too large a dot value leaves sparse regions with zero or one dot, so real differences between thin regions disappear.
Start with a working figure of roughly two thousand dots across the whole map at its default zoom, then render and adjust. The target is that the densest cluster still shows texture rather than solid fill, and that the thinnest region with meaningful data still gets several dots.
The dot value depends on the zoom level the map is read at, which is the awkward part of putting one on the web. A dot value tuned for a national view saturates at city zoom. Two common answers: lock the map to one zoom and tune for it, or generate separate dot sets per zoom range and swap them, keeping the declared dot value visible in the legend at all times.
How to build a dot density map
The pipeline is four steps:
- Get region polygons with stable identifiers.
- Attach one count per region on those identifiers.
- Generate points:
count / dotValuedots per region, placed inside the polygon and ideally inside the mask. - Render the points as a circle layer.
Turf handles the point generation:
import randomPoint from '@turf/random';
import booleanPointInPolygon from '@turf/boolean-point-in-polygon';
import bbox from '@turf/bbox';
const DOT_VALUE = 100;
function dotsForRegion(feature, count) {
const target = Math.round(count / DOT_VALUE);
const box = bbox(feature);
const dots = [];
// Rejection sampling: draw inside the bounding box, keep what lands in the polygon
while (dots.length < target) {
const batch = randomPoint(target, { bbox: box }).features;
for (const point of batch) {
if (dots.length >= target) break;
if (booleanPointInPolygon(point, feature)) {
point.properties = { id: feature.properties.id, category: feature.properties.category };
dots.push(point);
}
}
}
return dots;
}
map.addLayer({
id: 'dots',
type: 'circle',
source: 'dot-density',
paint: {
'circle-radius': ['interpolate', ['linear'], ['zoom'], 4, 1.2, 10, 2.6],
'circle-color': ['match', ['get', 'category'], 'residential', '#12813D', 'commercial', '#B7A21F', '#406FBD'],
'circle-opacity': 0.75,
},
});
Two notes on that snippet. Rejection sampling gets slow for long thin polygons, where most of the bounding box misses; triangulating the polygon and sampling by triangle area is the faster route once you pass a few hundred thousand dots. And the radius grows slightly with zoom, which keeps the texture stable as the map scales.
Performance is the other constraint. A MapLibre circle layer handles tens of thousands of dots comfortably. Past that, generate the dots server-side, bake them into vector tiles, and let the tile pipeline handle culling. Deck.gl's ScatterplotLayer is the other route, and it will take millions of points on the GPU.
Pitfalls to watch
Implying precision that is not there. If dots are randomly placed, say so in the caption. Readers will otherwise try to look up an individual dot.
A missing dot value. The legend has to declare what one dot means, with the same prominence a scale bar gets. Without it the map shows pattern and no quantity. The map legend guide covers how to lay that out.
Overplotting at high zoom. Dots that overlap at national zoom separate at street zoom and reveal that they were never real addresses. Cap the maximum zoom, or switch layers.
Too many categories. Three or four dot colours is the limit before the mix becomes mud. Split into small multiples beyond that.
Mapping a rate. Dots count things. A dot density map of a percentage has no honest reading.
Where MapAtlas fits
A dot density map is only as good as the geometry underneath it. The Dynamic Maps API serves country, region, and postal code boundaries as vector tiles on stable identifiers, so generating dots per region is a join rather than a cleaning exercise, and the EU-hosted, version-stamped boundaries mean a map regenerated next quarter still matches the one you published.
For dasymetric placement, the POI and places data behind GeoEnrich gives you the building and land use context to keep dots off the water and out of the airfields, and per-region aggregates keyed to the same IDs to drive the counts. For rate data, use the choropleth map guide; for the wider family of thematic maps, start with the types of maps guide.
Frequently Asked Questions
What is a dot density map?
A dot density map is a thematic map that shows quantity by placing dots inside regions, where each dot stands for a fixed number of units. One dot might mean 100 people, 10 farms, or a single customer. The reader judges the value from how tightly the dots cluster, so the map shows both how much there is and where inside the region it sits.
What is the difference between a dot density map and a choropleth map?
A choropleth map fills each region with one colour for one value, so the whole region reads as uniform. A dot density map scatters dots inside the region, which shows variation within it and avoids making large empty regions look important. Choropleth maps are better for rates and for quick reading. Dot density maps are better for counts, for showing clustering, and for comparing two or more categories on the same map.
What is the difference between a dot density map and a heat map?
A dot density map draws discrete dots at a declared dot value, so the reader can count and the underlying number is recoverable. A heat map smooths point data into a continuous colour surface with no fixed unit, which is easier to read at a glance but hides the count. Use dots when the quantity matters and heat when you only need to find clusters.
How do you choose the dot value on a dot density map?
Pick a dot value that makes the densest area dense but still countable and keeps the sparsest area visible. The usual method is trial and error at the zoom level the map will actually be read at: start with the total divided by roughly two thousand dots, render it, then adjust. A dot value that is too small turns cities into solid ink, and one that is too large makes rural regions look empty.
Are the dots on a dot density map in the right place?
Only in one-to-one dot maps, where each dot is a real observation with real coordinates. In the usual case, dots are scattered at random inside the region, so an individual dot marks no specific address. Placement can be made more honest with dasymetric refinement, which restricts dots to the parts of the region where the thing can actually be, such as residential land rather than lakes and airports.
When should I use a dot density map?
Use one when your data is a count rather than a rate, when regions vary a lot in size, when clustering inside a region is part of the story, or when you want to show several categories at once with different dot colours. Avoid one when readers need an exact value per region, when the map will be glanced at for a second on a dashboard, or when your regions are so small that a few dots each carry no pattern.

