HAHS.
Back to Catalog

Bubble Map

chart

Also known as: proportional symbol map, graduated symbol map, circle map

Show geographyCompareShow distribution GeographicNumericalCategorical Map

Description

A bubble map places circles (or other symbols) at geographic locations, with the area of each circle proportional to a quantitative value. This combines the positional information of a map with the magnitude encoding of a bubble chart, allowing readers to see both where something is and how much of it there is. Common applications include showing city populations, earthquake magnitudes, sales by location, and disease case counts.

Bubble maps address a key weakness of choropleths: the visual dominance of large regions. In a choropleth, geographically large but sparsely populated regions (like Siberia or Montana) draw disproportionate attention. Bubble maps decouple the data encoding from the geographic area, placing the emphasis squarely on the data values. A small city with a large value gets a large bubble regardless of its geographic footprint.

The primary perceptual challenge is that humans underestimate area differences: a circle with twice the area does not look twice as big. Scaling by the square root of the value (so that area, not radius, is proportional) helps but does not fully eliminate the bias. For precise comparisons, tooltips or data labels are essential supplements. Overlapping bubbles in dense regions are another concern, mitigated by transparency, collision detection, or jittering.

Bubble Map — interactive example

When to Use

  • Showing quantitative values at geographic locations where both position and magnitude matter
  • Comparing values across cities, facilities, or regions without the area distortion of choropleths
  • Displaying event magnitudes (earthquakes, outbreaks, incidents) on a map
  • When the data represents point locations rather than area aggregates
  • Overlaying a quantitative dimension on a reference map for spatial analysis

When NOT to Use

  • When the data is a rate or density across regions — use a choropleth (rates should not be shown as bubbles since they misrepresent area-based measures)
  • When bubbles overlap so heavily that individual values are unreadable — aggregate or filter first
  • When precise comparison of values is critical — area perception is imprecise; use a bar chart alongside the map
  • When geographic context is not important — use a standalone bubble chart or bar chart

Anatomy

  • Base map: Geographic reference layer showing boundaries, coastlines, or terrain for spatial context.
  • Bubbles (circles): Circles centered at geographic coordinates; area is proportional to the quantitative value.
  • Color encoding: Bubble fill colored by category (type, region) or by a second quantitative variable using a sequential scale.
  • Opacity: Semi-transparent fills help with overlapping bubbles and reveal density.
  • Size legend: A reference showing the mapping from circle area to value (e.g., three graduated circles labeled with their values).
  • Tooltips/labels: On-hover detail showing the exact value and location name.

Variations

  • Graduated symbol map: Uses symbols other than circles (squares, triangles) proportional to values; circles are the most common.
  • Dorling cartogram: Bubbles replace geographic regions entirely; position is approximate but area encoding is preserved without overlap.
  • Multi-variable bubble map: Bubble size encodes one variable and color encodes a second, enabling bivariate analysis.
  • Animated bubble map: Bubbles grow or shrink over time to show temporal change in a geographic context.
  • Clustered bubble map: At low zoom levels, nearby bubbles are aggregated; zooming reveals individual points.

Code Reference

// Observable Plot - bubble map
Plot.plot({
  projection: "albers-usa",
  marks: [
    Plot.geo(stateBoundaries, { stroke: "#ccc", strokeWidth: 0.5 }),
    Plot.dot(cities, {
      x: "longitude",
      y: "latitude",
      r: d => Math.sqrt(d.population) / 100,
      fill: "region",
      fillOpacity: 0.6,
      stroke: "white",
      strokeWidth: 0.5,
      tip: true
    })
  ],
  color: { legend: true, label: "Region" },
  r: { range: [3, 40] }
})