-
Notifications
You must be signed in to change notification settings - Fork 2.1k
Description
I noticed this while answering a question on SO recently.
geom_rect() uses the locations of the four corners (xmin, xmax, ymin and ymax), while geom_tile() uses the center of the tile and its size (x, y, width, height). With appropriate calculations in aesthetic mappings, the two geoms can be used to produce identical results when x-axis & y-axis are both linear.
When non-linear transformations are applied to x or y scales, on the other hand, the two behave differently. Based on what I see from debugging in ggplot_build, scale transformations are performed on the inputted dataset much earlier than the geom's setup_data, so GeomTile$setup_data, which aims to convert (x, y, width, height) to (xmin, xmax, ymin and ymax), performs its transformation on a dataset that has already gone through scale transformation, with results differing from those of GeomRect$setup_data.
Here is the code to reproduce the bug for geom_tile:
library(ggplot2)
df <- data.frame(
x = rep(c(3, 6, 8, 10, 13), 2),
y = rep(c(1, 2), each = 5),
z = factor(rep(1:5, each = 2)),
w = rep(diff(c(0, 4, 6, 8, 10, 14)), 2)
)
p1 <- ggplot(df, aes(fill = z)) +
geom_tile(aes(x = x, y = y, width = w, height = 1),
colour = "grey50", alpha = 0.5, linewidth = 1) +
geom_rect(aes(xmin = x - w/2, xmax = x + w/2,
ymin = y - 0.5, ymax = y + 0.5),
colour = "white", alpha = 0.5, linewidth = 1) + # white lines overlap grey when scales are linear
ggtitle("linear scales") +
theme_void() +
theme(legend.position = "none")
p2 <- p1 + scale_x_log10() + ggtitle("transformed x scale")
p3 <- p1 + scale_y_log10() + ggtitle("transformed y scale")
p4 <- p1 + scale_x_log10() + scale_y_log10() + ggtitle("transformed both scales")
library(patchwork)
(p1 | p2) / (p3 | p4)The same behaviour can be observed with geom_raster(), though in that case we'd at least get a warning that raster pixels are placed at uneven intervals and will be shifted.
set.seed(1)
df1 <- expand.grid(x = 1:5, y = 1:5)
df1$z <- runif(nrow(df1))
ggplot(df1, aes(x, y, fill = z)) +
geom_raster(alpha = 0.5) +
geom_rect(aes(xmin = x - 0.5, xmax = x + 0.5,
ymin = y - 0.5, ymax = y + 0.5),
colour = "white", alpha = 0.5, linewidth = 1) +
theme_void() +
scale_x_log10() +
scale_y_log10()
It may not be worth changing the underlying code (I for one can't imagine many scenarios where one would want to perform scale transformations while plotting tiles), but having a note in the help page cautioning people about possibly unintended consequences from scale transformations could be helpful.

