Home > OS >  ggplot with different types in one legend by using "colour" and "fill" as asteti
ggplot with different types in one legend by using "colour" and "fill" as asteti

Time:02-26

I have a problem with drawing the legends in a ggplot. I have already searched the net for hours for a solution but have not found anything yet.

I am trying to create a ggplot with different line and polygon shapefiles. I draw the shapefiles with the function geom_sf(). The drawing is not a problem. My problem is related to the legend creation.

The goal is to create a plot that has a single legend and has all the different shapefile types including their colors and or fill astetic. That means, a line-shape should be represented in the legend as a simple line and a polygon-shape as a simple polygon. I have managed to do all that.

The main difficulty is that one of the polygonshape (for better readability) not only uses the "fill" argument as astetic but also the "color" argument.

If I create a plot where all polygonshapes always use the fill argument as astetic and all lineshapes always use the color argument as astetic everything works fine. But as soon as I include a polygonshape in the plot which uses both arguments at the same time, it destroys the whole legend structure. In this case, for example, the symbols for the line shapefiles suddenly have a frame and/or background although they should not have one.

Is it not possible to build something like this with ggplot or am I just being clumsy?

Summary:

Analogous to the minimal example created below, I would like to create a single legend in this plot where:

  • A = a polygon symbol is without border (as it can be seen in the
    plot)
  • B = a polygon symbol is with border (as seen in the plot) and
  • C = a simple line without border or background (as it can be seen in the plot).

How do I get this to work?

library(ggplot2)
library(sf)

poly1 <- cbind(lon = c(5, 6, 7, 5), lat = c(52, 53, 51, 52))
poly2 <- cbind(lon = c(3, 5, 7, 3), lat = c(50, 52, 50, 50))

poly1 <- st_sf(st_sfc(st_polygon(list(poly1))))
poly2 <- st_sf(st_sfc(st_polygon(list(poly2))))
line <- st_sf(st_sfc(list(st_linestring(cbind(lon = c(5.5, 4.5), lat = c(53.5, 54.5))))))

ggplot()  
  geom_sf(data = poly1, 
          aes(fill = "A"),
          colour = NA,
          show.legend = "polygon")  
  geom_sf(data = poly2, 
          aes(fill = "B", 
              colour = "B"),
          show.legend = "polygon")  
  geom_sf(data = line, 
          aes(colour = "C"), 
          show.legend = "line")  
  scale_fill_manual(name="Legend", 
                    values = c("A" = "yellow", "B" = "green"), 
                    guide = guide_legend(override.aes = list(linetype = c("blank","blank"), shape = NA))
                    )  
  scale_colour_manual(name="Legend",values = c("B" = "blue", "C" = "purple"),
                     guide = guide_legend(override.aes = list(linetype = c(0, 1))))  
  theme_bw()

CodePudding user response:

I like @AllanCameron's answer too, but here's a way to do the same thing without ggnewscale.

You have three keys in total. For A and B, these should both be represented as polygons, but B should have a border and A should not. C is the odd one out, since it should be represented by a separate symbol in the legend.

Consequently, the approach here is to let A and B belong to the same legend (one combined from color= and fill=), and keep C into a separate legend.

For A and B, I specify fill= and color= in aes() on both, then workout colors for both with the scale_*_manual() functions.

If you assign color= to C within aes(), you'll force ggplot2 to push it into the legend with A and B. That means we need to specify the legend for C under a different aesthetic! In this case, I use alpha=, but the same would work with linetype, size, etc.

p <-
ggplot()  
  geom_sf(
    data = poly1, aes(fill = "A", colour = "A"))  
  geom_sf(
    data = poly2, aes(fill = "B", colour = "B"))  
  geom_sf(
    data = line, aes(alpha = "C"), colour = "purple")  
  
  scale_fill_manual(
    name="Legend", values = c("A" = "yellow", "B" = "green"))  
  scale_colour_manual(
    name="Legend", values = c("A" = "transparent", "B" = "blue", "C" = "purple"))  
  scale_alpha_manual(name=NULL, values=1)  

  guides(
    fill=guide_legend(order=1, override.aes = list(linetype = c(0,1))),
    colour= "none"
  )  
  
  theme_bw()
p

enter image description here

To get the legends closer to one another, you can specify the legend.spacing via theme(). You have to play around with the number, but it works. In this case, it's important to make the legend.background transparent too, because the legends clip into one another.

p   theme(
  legend.background = element_rect(fill=NA),
  legend.spacing = unit(-15, "pt")
  )

enter image description here

CodePudding user response:

You can do this with ggnewscale and setting the key glyph for each geom layer:

ggplot()  
  geom_sf(data = poly1, 
          aes(fill = "A"),
          colour = NA,
          key_glyph = "rect")  
  scale_fill_manual(values = "yellow", name = NULL, 
                      guide = guide_legend(order = 1))  
  ggnewscale::new_scale_fill()  
  ggnewscale::new_scale_color()  
  geom_sf(data = poly2, 
          aes(fill = "B", 
              colour = "B"),
          key_glyph = "polygon")  
  scale_color_manual(values = "blue", name = NULL, 
                      guide = guide_legend(order = 2))  
  scale_fill_manual(values = "green", name = NULL, 
                      guide = guide_legend(order = 2))  
  ggnewscale::new_scale_color()  
  geom_sf(data = line, 
          aes(colour = "C"), 
          key_glyph = "path")  
  scale_colour_manual(values = "purple", name = NULL, 
                      guide = guide_legend(order = 3))  
  theme_bw()

enter image description here

  • Related