Cartogram
chartAlso known as: area cartogram, distorted map, value-by-area map, Dorling cartogram
Description
A cartogram distorts the geometry of geographic regions so that the area of each region is proportional to a data variable (such as population, GDP, or number of votes) rather than physical land area. The result is a map where visually large regions represent large values, directly counteracting the most common problem with choropleth maps: that the visual prominence of a region reflects its physical size, not its data importance.
The distortion can range from subtle (contiguous cartograms that warp boundaries while maintaining adjacency) to radical (Dorling cartograms that replace regions with non-overlapping circles). Between these extremes lie several algorithmic approaches: diffusion-based cartograms smoothly inflate or deflate regions, rubber-sheet methods stretch a continuous surface, and grid/tile cartograms snap regions to a regular grid of uniform squares or hexagons.
Cartograms are most powerful when the mismatch between physical area and data magnitude creates misleading impressions in standard maps. Election maps are the classic example: in a choropleth, sparsely populated rural areas dominate visually, while densely populated cities are tiny. A population-weighted cartogram corrects this by making each voter equally visible, producing a map that more honestly represents the data.
When to Use
- Correcting the visual distortion of choropleths where large but sparsely populated regions dominate
- Showing population-weighted data (election results, disease prevalence per capita)
- Making every unit of measurement (person, dollar, vote) equally visible on a map
- Creating memorable, striking visualizations for editorial or journalistic storytelling
When NOT to Use
- When geographic shape and location must be preserved accurately — use a choropleth or bubble map
- When the audience is unfamiliar with the geography — distortion makes unfamiliar regions unrecognizable
- When precise spatial patterns (clustering, proximity) are the focus — distortion destroys spatial relationships
- When the data variable correlates strongly with physical area — the cartogram will look nearly identical to a regular map
- When there are very many small regions — they may become too tiny to see or interact with
Anatomy
- Distorted regions: Geographic areas resized so that their visual area is proportional to the data variable
- Adjacency preservation: Contiguous cartograms attempt to maintain which regions border each other
- Shape preservation: The degree to which original geographic shapes are recognizable varies by algorithm
- Color encoding: Fill color typically encodes a second variable (e.g., party affiliation, growth rate)
- Labels: Region names or abbreviations placed inside or near each shape
- Legend: A size legend showing the mapping from area to data value, plus a color legend if applicable
Variations
- Contiguous cartogram (Gastner-Newman): Regions are smoothly warped while maintaining shared borders; maximizes geographic recognizability
- Dorling cartogram: Each region is replaced by a non-overlapping circle sized by the data variable; sacrifices geography for clarity
- Demers cartogram: Like Dorling but uses squares instead of circles, with better label placement
- Tile grid map (hex cartogram): Each region is a uniform hexagon or square on a grid; sacrifices proportionality for consistent sizing and easy labeling
- Non-contiguous cartogram: Regions maintain their shape but are scaled and repositioned, creating gaps between them
Code Reference
// D3 Dorling cartogram using force simulation
import * as d3 from "d3";
const r = d3.scaleSqrt()
.domain([0, d3.max(data, d => d.population)])
.range([5, 50]);
const simulation = d3.forceSimulation(data)
.force("x", d3.forceX(d => projection([d.lon, d.lat])[0]))
.force("y", d3.forceY(d => projection([d.lon, d.lat])[1]))
.force("collide", d3.forceCollide(d => r(d.population) + 1));
svg.selectAll("circle")
.data(data).join("circle")
.attr("r", d => r(d.population))
.attr("fill", d => color(d.party));