ggplot2 geom_polygon() in R: Filled Closed Shapes

The geom_polygon() function in ggplot2 draws filled CLOSED shapes from a sequence of x and y coordinates. It is essential for map plots, custom shapes, and area fills.

⚡ Quick Answer
ggplot(df, aes(x, y)) + geom_polygon()
ggplot(df, aes(x, y, group = id, fill = id)) + geom_polygon()
ggplot(map_data("world"), aes(long, lat, group = group)) + geom_polygon()
geom_path(...)                          # different: open path

Need explanation? Read on for examples and pitfalls.

📊 Is geom_polygon() the right tool?
STARTclosed filled shapegeom_polygon()map regionsgeom_polygon (or geom_sf for sf objects)open pathgeom_path()rectanglegeom_rect()multiple polygonsaes(group = id)

What geom_polygon() does in one sentence

geom_polygon() connects points in row order, closes the loop (last to first), and fills the interior. Used for maps, custom shapes, and area fills.

Syntax

geom_polygon(mapping = NULL, data = NULL, fill = "grey50", ...). Always uses row order to determine vertices.

Run live
Run live, no install needed. Every R block on this page runs in your browser. Click Run, edit the code, re-run instantly. No setup.
RWorld map
library(ggplot2) library(maps) ggplot(map_data("world"), aes(long, lat, group = group)) + geom_polygon(fill = "lightgrey", color = "black")

  
Tip
For maps, ALWAYS pass group = group in aes. Otherwise polygons connect across regions, producing visual artifacts.

Five common patterns

1. Simple polygon

RTriangle
df <- tibble(x = c(0, 1, 0.5), y = c(0, 0, 1)) ggplot(df, aes(x, y)) + geom_polygon()

  

2. Multiple polygons via group

RTwo triangles
df <- tibble( x = c(0,1,0.5, 2,3,2.5), y = c(0,0,1, 0,0,1), id = c("A","A","A","B","B","B") ) ggplot(df, aes(x, y, group = id, fill = id)) + geom_polygon()

  

3. World map

RCountry boundaries
ggplot(map_data("world"), aes(long, lat, group = group)) + geom_polygon(fill = "white", color = "grey50")

  

4. Choropleth via fill

RColor regions by metric
us_states <- map_data("state") metrics <- tibble(region = unique(us_states$region), value = runif(49)) us_states |> left_join(metrics, by = "region") |> ggplot(aes(long, lat, group = group, fill = value)) + geom_polygon(color = "white") + coord_map()

  

5. Custom shape with alpha

RTranslucent polygon over points
ggplot(mtcars, aes(wt, mpg)) + geom_point() + geom_polygon(data = tibble(wt = c(2,4,4,2), mpg = c(20,20,30,30)), fill = "yellow", alpha = 0.3)

  
Key Insight
Polygons need group to keep boundaries separate. Without group, ggplot connects all points into one giant polygon. Always provide group when plotting multiple regions.

geom_polygon() vs geom_rect() vs geom_sf()

Function Inputs Best for
geom_polygon() x, y per vertex Custom polygons / maps
geom_rect() xmin, xmax, ymin, ymax Rectangles
geom_sf() sf objects Modern map data

For modern map work, geom_sf with the sf package is often easier than geom_polygon.

A practical workflow

Map plotting is geom_polygon's signature use case.

RInteractive R
us_data <- map_data("state") |> left_join(state_metrics, by = "region") ggplot(us_data, aes(long, lat, group = group, fill = value)) + geom_polygon(color = "white", linewidth = 0.2) + coord_map() + scale_fill_viridis_c() + theme_void()

  

Standard choropleth recipe.

Common pitfalls

Pitfall 1: forgetting group. Without it, polygons join into one tangled mess. Always set group for maps.

Pitfall 2: confusing with geom_path. geom_path is OPEN; geom_polygon is CLOSED and FILLED.

Warning
geom_polygon() requires vertices in ORDER (clockwise or counterclockwise). Random row order produces self-intersecting polygons.

Try it yourself

Try it: Plot a simple square polygon. Save to ex_plot.

RYour turn: a square
df <- tibble(x = c(0, 2, 2, 0), y = c(0, 0, 2, 2)) ex_plot <- df |> # your code here

  
Click to reveal solution
RSolution
ex_plot <- ggplot(df, aes(x, y)) + geom_polygon(fill = "steelblue") + coord_fixed()

  

Explanation: Four vertices in order; geom_polygon closes the loop and fills.

After mastering geom_polygon, look at:

  • geom_path(): open path
  • geom_rect(): rectangles
  • geom_sf(): sf objects (maps)
  • coord_map() / coord_sf(): map projections
  • geom_ribbon(): between-curves area

FAQ

What does geom_polygon do in ggplot2?

geom_polygon() draws filled closed shapes from x and y coordinates connected in row order.

Why do I need group in geom_polygon?

To distinguish multiple polygons. Without it, all points connect into one polygon. Group separates them.

What is the difference between geom_polygon and geom_path?

geom_polygon CLOSES the loop and FILLS. geom_path is open (no fill, no auto-close).

Should I use geom_polygon or geom_sf for maps?

For modern sf-based map data, geom_sf is easier. For traditional data frames from maps package, geom_polygon is the standard.

Can I have holes in a polygon?

geom_polygon does not natively support holes. Use geom_sf with sf objects for proper polygon-with-holes support.