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