geom_tile border missing at corners

As others have noted, this is due to the lineend specification, which can be found in environment(GeomTile$draw_panel)$f:

function (self, data, panel_params, coord) 
{
    if (!coord$is_linear()) {
        ... #omitted for space
    }
    else {
        coords <- coord$transform(data, panel_params)
        ggname("geom_rect", 
               rectGrob(coords$xmin, coords$ymax, 
                        width = coords$xmax - coords$xmin, height = coords$ymax - 
                        coords$ymin, default.units = "native", just = c("left", "top"), 
                        gp = gpar(col = coords$colour, 
                                  fill = alpha(coords$fill, coords$alpha), 
                                  lwd = coords$size * .pt, 
                                  lty = coords$linetype, 
                                  lineend = "butt"))) # look here
    }
}

The creation of a geom_tile layer is powered by rectGrob, with a hard-coded lineend parameter value of "butt". The graphic below (found here) illustrates the difference between the 3 lineend values nicely:

lineend values

If you feel like digging into the underlying GeomTile's functions and changing the graphics parameters for all geom_tile layers in your code, you can do that. (I answered a similar question recently with that solution.) For a single plot, though, I'd just convert the ggplot to a grob object, & mess with the gp parameters there instead:

library(grid)
gp <- ggplotGrob(p)
grid.draw(gp) 

# this "sharpens" the top left corner
gp$grobs[[which(grepl("panel", gp$layout$name))]]$children[[3]]$gp$lineend <- "square"
grid.draw(gp)

plot after changing line end

# this further "sharpens" the other three corners
gp$grobs[[which(grepl("panel", gp$layout$name))]]$children[[3]]$gp$linejoin <- "mitre"
grid.draw(gp)

plot after changing line join

Note: the actual location of the correct grob corresponding to geom_tile is not necessarily going to be gp$grobs[[which(grepl("panel", gp$layout$name))]]$children[[3]]$gp$linejoin. It's children[[3]] here, but having other geom layers in the ggplot object, either under or above the geom_tile layer, can shift its relative position. In that case, you may want to check the output from gp$grobs[[which(grepl("panel", gp$layout$name))]]$children in the console to identify the correct position number.


I think this happens because the starting point of each line is just that: a point. And because the size of the line makes it thicker, this starting point makes this blank spaces. This plot uses four geom_segment to make one square and the result shows the same problem you encountered:

ggplot(df) + 
   geom_segment(x = 1, y = 1, xend = 2, yend = 1, size = 3) +
   geom_segment(x = 1, y = 1, xend = 1, yend = 2, size = 3) +
   geom_segment(x = 1, y = 2, xend = 2, yend = 2, size = 3) +
   geom_segment(x = 2, y = 2, xend = 2, yend = 1, size = 3) +
   scale_x_continuous(limits = c(0, 3)) + 
   scale_y_continuous(limits = c(0, 3))

square with blank spaces

The only solution I can think of is making the starting and end points of one of the x or y axis a little behind (for starting) and ahead (for finishing). This solution is far from ideal, but is the only one I can think of. For a line of size = 3, I found that substracting and adding 0.01 to the starting and finishing points fills the blank space:

ggplot(df) + 
 geom_segment(x = 1-0.01, y = 1, xend = 2+0.01, yend = 1, size = 3) +
 geom_segment(x = 1, y = 1, xend = 1, yend = 2, size = 3) +
 geom_segment(x = 1-0.01, y = 2, xend = 2+0.01, yend = 2, size = 3) +
 geom_segment(x = 2, y = 2, xend = 2, yend = 1, size = 3) +
 scale_x_continuous(limits = c(0, 3)) + 
 scale_y_continuous(limits = c(0, 3))

square without blank spaces

But again, this solution is not ideal because this value should change according to the size of the line and the scale of the figure you are showing.


EDIT: geom_path() connects the corners of the square without leaving blank spaces, but the problem persist in the point where the line meets it's origin:

 df <- data.frame(
       x = c(1, 1, 2, 2, 1),
       y = c(1, 2, 2, 1, 1)
       )

 ggplot(df, aes(x, y)) + 
       geom_path(size = 3, linejoin = "mitre") +
       scale_x_continuous(limits = c(0, 3)) +
       scale_y_continuous(limits = c(0, 3))

enter image description here

Tags:

R

Ggplot2