ggplot2 - how to fill nested polygons with colour?

3.9k Views Asked by At

I have > 100 nested polygons in a unique SpatialPolygonsDataFrame. I desire to plot them with ggplot2 and all of them need to be visible in the map, i.e. overlaying larger polygons have to be in the background.

I found that I can achieve this by using alpha = 0 within the geom_polygon function but how can I assign a fill colour to each polygon?

Here an example of my code with just 2 polygons:

library(ggplot2)

Read csv file with two shapefiles merged and then converted to data.frame with fortify from maptools.

#read csv file shape_1_2.csv
shape_1_2 = read.csv('shape_1_2.csv', stringsAsFactors = FALSE)

#plot
ggplot() +
geom_polygon(data = shape_1_2, aes(x = long, y = lat, group = group), 
             colour = 'black', size = 1, linetype = 'solid', alpha = 0)

And relative map:

enter image description here

How can I fill with colour these two polygons?

I tried to add fill='black' in both aes and geom_polygon but it doesn't work.

Thanks


Update

I am sorry but I realised that my example data contains NO nested polygons.

So starting from the following data.frame as per https://gis.stackexchange.com/questions/280671/r-create-multipolygon-from-overlapping-polygons-using-sf-package :

shape_df = data.frame(
    lon = c(0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 2, 2, 0.8, 1, 1, 2, 2, 1, 1),
    lat = c(0, 0, 1, 1.5, 0, 1, 1, 2, 2, 1, 1, 1, 2, 2, 1, 0, 0, 1, 1, 0),
    var = c(1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3 ,3 ,3 ,3 ,3, 4 ,4 ,4, 4, 4)
)

And my plotting code (with alpha=0):

ggplot() +
    geom_polygon(data = shape_df, aes(x = lon, y = lat, group = var), 
                 colour = 'black', size = 1, linetype = 'solid', alpha = 0)

With relative map:

enter image description here

How can I fill the different areas present in the map with one OR up to 4 colours so that the larger polygons remains in the background of the smaller?

3

There are 3 best solutions below

3
camille On BEST ANSWER

If you do this with sf, you can use st_area to get the area of each polygon (area doesn't make a ton of sense with unprojected toy data, but will make sense with the actual shapes), then order polygons based on area. That way, ggplot will create polygons in order by ID. To use geom_sf, you need the github dev version of ggplot2, though it's being added to the next CRAN release, slated for next month (July 2018).

First create a simple features collection from the data. In this case, I had to use summarise(do_union = F) to make each series of points into a polygon in the proper order (per this recent question), then calculate the area of each.

library(tidyverse)
library(sf)
#> Linking to GEOS 3.6.1, GDAL 2.1.3, proj.4 4.9.3

shape_df <- data.frame(
  lon = c(0, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 2, 2, 0.8, 1, 1, 2, 2, 1, 1),
  lat = c(0, 0, 1, 1.5, 0, 1, 1, 2, 2, 1, 1, 1, 2, 2, 1, 0, 0, 1, 1, 0),
  var = c(1, 1, 1, 1, 1, 2, 2, 2, 2, 2, 3 ,3 ,3 ,3 ,3, 4 ,4 ,4, 4, 4)
)

shape_areas <- shape_df %>%
  st_as_sf(coords = c("lon", "lat")) %>%
  group_by(var) %>%
  summarise(do_union = F) %>%
  st_cast("POLYGON") %>%
  st_cast("MULTIPOLYGON") %>%
  mutate(area = st_area(geometry)) %>% 
  mutate(var = as.factor(var)) 

shape_areas
#> Simple feature collection with 4 features and 3 fields
#> geometry type:  MULTIPOLYGON
#> dimension:      XY
#> bbox:           xmin: 0 ymin: 0 xmax: 2 ymax: 2
#> epsg (SRID):    NA
#> proj4string:    NA
#>   var do_union area                       geometry
#> 1   1    FALSE 1.25 MULTIPOLYGON (((0 0, 1 0, 1...
#> 2   2    FALSE 1.00 MULTIPOLYGON (((0 1, 1 1, 1...
#> 3   3    FALSE 1.10 MULTIPOLYGON (((1 1, 2 1, 2...
#> 4   4    FALSE 1.00 MULTIPOLYGON (((1 0, 2 0, 2...

If I plot at this point, the area has no bearing on the order of plotting; it just orders by var, numerically:

shape_areas %>%
  ggplot() +
    geom_sf(aes(fill = var), alpha = 0.9)

But if I use forcats::fct_reorder to reorder var as a factor by decreasing area, polygons will be plotting in order with the largest polygons at the bottom, and smaller polygons layering on top. Edit: as @SeGa pointed out below, this was originally putting larger shapes on top. Use -area or desc(area) to order descending.

shape_areas %>%
  mutate(var = var %>% fct_reorder(-area)) %>%
  ggplot() +
  geom_sf(aes(fill = var), alpha = 0.9)

Created on 2018-06-30 by the reprex package (v0.2.0).

3
Tarssio Barreto On

Adding a variable named "aux" to each of the shapes you can use "fill = aux" in ggplot2

You can try to do:

library(maptools)
library(ggplot2)

shape_1 <- readShapePoly('poly_1.shp') %>%
fortify()  %>%
mutate(aux = c("1"))

shape_2 <- readShapePoly('poly_2.shp') %>%
fortify()  %>%
mutate(aux = c("2"))

shape_1_2 = rbind(shape_1, shape_2)

ggplot() +
geom_polygon(data = shape_1_2, aes(x = long, y = lat, group = group), 
             colour = 'black', size = 0.1, linetype = 'solid', alpha = 0, fill = aux)
3
SeGa On

If you add a fill but have alpha=0, you wont see any colors, as they will be 100% transparent.

I'm not sure what exactly you want to achieve, as I can see 3 polygons, that you can color differently, not just 2.

What if you try this ggplot-call, which takes the id as fill variable (as factor):

ggplot() +
  geom_polygon(data = shape_1_2, aes(x = long, y = lat, group = group, fill=factor(id)), 
               colour = 'black', size = 1, linetype = 'solid', alpha = 1)

Or this one, which takes group as fill variable (as factor) and allows you to define your own colors.

ggplot(shape_1_2) +
  aes(x = long, y = lat, group = group, fill=factor(group)) + 
  geom_polygon() +
  scale_fill_manual(values = c("green", "red", "blue")) +
  geom_path(color="black")

enter image description here

You could also use the scale_fill_brewer function, which lets you pick a predefined color palette (show possible palettes with: display.brewer.all())

ggplot(shape_1_2) +
  aes(x = long, y = lat, group = group, fill=factor(group)) + 
  geom_polygon() +
  scale_fill_brewer(palette = "RdYlGn") +
  geom_path(color="black") +
  ggtitle("fill=factor(group)")