1
votes

I've recently gotten frustrated by the same issue as reported here regarding the hacks required to make up for ggplot not adding NA fills to the legend (please if anyone knows an update to this, do tell).

I therefore created a similar hack, but in a fairly complex plot where I was trying to demonstrate several things at once. The plot required setting the size of points to a small value so therefore I needed override.aes to have points large enough in the legend. Since I was using alpha as well as colour, I introduced both into the same legend key.

However, bizarrely (at least to me), the aes override creates ugly whitespace between the legend key box margin and the fill, as shown in the map with and without the size aes override:

map with and without the size aes override

How does this happen, and how can I make it go away? Any help would be much appreciated.

Here's the code to reproduce this. As said, my true plot is more complex than this but this shows the general idea.

## create an example using the world data
require(rworldmap) 
require(rgeos)
require(ggplot2)
require(viridis)

## Load world map and subset
world <- getMap()
world <- world[!world@data$ADMIN %in% c("French Southern and Antarctic Lands", 
                                        "Heard Island and McDonald Islands",
                                        "French Guiana"),] #NA entries for these in REGION col
world <- world[world@data$REGION == "South America",]

world@data$id = rownames(world@data)

## create example where we have NA for the fill variable
world@data$fillvar <- world@data$LAT
world@data$fillvar[round(world@data$LAT) == -9] <- NA #Peru

## create spatial data frame for plotting
polydf = ggplot2::fortify(world, region="id")
polydf <- merge(polydf, world@data, by="id")

## create arbitrary grouping variable
polydf$groupvar <- ifelse(polydf$NAME == "Bolivia", "Bolivia", "NotBolivia") # the two main classes


# simulate hack where we name the NA fill case as a new grouping level (which we will use alpha for)
polydf$groupvar[is.na(polydf$fillvar)] <- 'Removed' 
polydf$groupvar <- factor(polydf$groupvar)

## create centroid points to show the grouping with points, again manual hack for NA
pointsdf <- gCentroid(world[!is.na(world@data$fillvar),],byid=TRUE)
pointsdf <- as.data.frame(pointsdf@coords)
pointsdf$groupvar <- ifelse(rownames(pointsdf)=="Bolivia","Bolivia","NotBolivia")

pointsNA <- gCentroid(world[is.na(world@data$fillvar),], byid=TRUE)
pointsNA <- as.data.frame(pointsNA@coords)
pointsNA$groupvar <- "Removed"

pointsdf <- rbind(pointsdf, pointsNA)
pointsdf$groupvar <- factor(pointsdf$groupvar)

## plot with override.aes for size
sizeover <- ggplot(polydf) + theme_bw() +
  aes(long,lat,group=group) + 
  geom_polygon(aes(fill=fillvar, alpha=groupvar)) +  # shading also for groupvar
  geom_path(color="black") +
  coord_equal() +
  scale_fill_viridis("Fill", na.value="black") + 
  geom_point(inherit.aes=FALSE, data=pointsdf, aes(x=x, y=y, group=groupvar, col=groupvar), size=1) +
  scale_color_manual('Alpha + colour', values=c("black","transparent","white")) +
  scale_alpha_manual('Alpha + colour', values=c(0.8,0.8, 1)) +
  theme(legend.key = element_rect(colour = "black"), legend.box = "vertical", legend.position = "top") +
  guides(color=guide_legend(override.aes = list(size=2)), 
         alpha=guide_legend(override.aes = list(alpha=c(1,1,1), fill=c('white','white','black'))))

## plot without override.aes for size
nosizeover <- ggplot(polydf) + theme_bw() +
  aes(long,lat,group=group) + 
  geom_polygon(aes(fill=fillvar, alpha=groupvar)) +  # shading also for groupvar
  geom_path(color="black") +
  coord_equal() +
  scale_fill_viridis("Fill", na.value="black") + 
  geom_point(inherit.aes=FALSE, data=pointsdf, aes(x=x, y=y, group=groupvar, col=groupvar), size=1) +
  scale_color_manual('Alpha + colour', values=c("black","transparent","white")) +
  scale_alpha_manual('Alpha + colour', values=c(0.8,0.8, 1)) +
  theme(legend.key = element_rect(colour = "black"), legend.box = "vertical", legend.position = "top") +
  guides(alpha=guide_legend(override.aes = list(alpha=c(1,1,1), fill=c('white','white','black'))))

grid.arrange(sizeover, nosizeover, ncol=2)
1
I don't understand what exactly the problem is. Can you edit the figure with photoshop to mock up an example of what you'd want to get?Claus Wilke
@ClausWilke The difference was already there in the figure provided but I've just uploaded a version that makes the difference clearer. Had forgotten to label the plots earlier =/Emma Wiik
I saw the difference, I just wasn't sure what issue specifically you were concerned about.Claus Wilke

1 Answers

1
votes

The problem exists in both figures (sizeover and nosizeover), it's just more pronounced in the former. The guilty party is the function draw_key_polygon() in ggplot2. It creates some space around the rectangle it draws, in proportion to the size setting (see here). I'm not quite sure why it's written like that, but presumably there's a reason.

The simplest solution is similar to this answer to a different question. Replace the legend-drawing function for geom_polygon(). In the case here, we don't even have to write a new one ourselves, we can just use an existing one:

GeomPolygon$draw_key <- ggplot2::draw_key_rect
sizeover

enter image description here

The only problem that this has created is that now the legend outline appears a bit thin, because the filled rectangle is drawn on top of the outline. We can fix this by making the line a bit thicker, by adding this to the plot:

+ theme(legend.key = element_rect(colour = "black", size = 1)) 

enter image description here

This seems like a reasonable solution to me.

Note that geom_polygon() will continue using the modified legend drawing function for the remainder of your session. To undo, run:

GeomPolygon$draw_key <- ggplot2::draw_key_polygon