I maintain an R package that uses ggplot2 to display brain imaging data. The package fundamentally depends on ggnewscale
and the ability to re-use the fill mapping for multiple images that are overlaid. This approach has worked well until relatively recently, with the release of ggplot2 3.5. Some use cases involve overlaying multiple categorical images where I want to preserve the unused factor levels in the legend. Previously, this could be achieved using drop=FALSE
in the scale_fill_*
specification. It also depended on only setting show.legend
to FALSE
for layers whose legend I wanted to hide and not setting show.legend=TRUE
. (see ).
With the release of ggplot2 3.5, I'm now expected to set show.legend=TRUE
to maintain the unused factor levels. As a result, I can no longer achieve the intended display because the later layers in the build undue the settings of the lower layers, resulting in all-black legends that don't make sense.
I've poked at the objects (e.g., using gginnards
) and I see a number of differences, but I can't figure out what difference causes the error. Does anyone have advice on how to identify and patch the ggplot object to yield the desired result? (also see: ) I've attached a simple reprex below. Thanks so much for any advice!!
data1 <- expand.grid(dim1=1:10, dim2=1:10)
data1$value1 <- rnorm(100)
data2 <- expand.grid(dim1=1:10, dim2=1:10)
data2$value2 <- factor(sample(c(NA, "f1", "f2", "f3", "f4"), size = 100, replace=TRUE), levels = paste0("f", 1:5))
data3 <- expand.grid(dim1=1:10, dim2=1:10)
data3$value3 <- factor(sample(c(NA, "i1", "i2", "i3", "i4"), size = 100, replace=TRUE), levels = paste0("i", 1:5))
# leaving as NA shows the right layers in legend, but the missing level is not plotted
#
ggplot(mapping=aes(x=dim1, y=dim2)) +
geom_raster(data=data1, mapping=aes(fill=value1), show.legend = FALSE) +
scale_fill_gradient(low = "grey8", high = "grey92") +
ggnewscale::new_scale_fill() +
geom_raster(data=data2, mapping=aes(fill=value2), show.legend=NA) +
scale_fill_brewer("layer2", palette = "Set3", drop=FALSE, na.value = "transparent", na.translate=FALSE) +
ggnewscale::new_scale_fill() +
geom_raster(data=data3, mapping=aes(fill=value3), show.legend = NA) +
scale_fill_brewer("layer3", palette = "Dark2", drop=FALSE, na.value = "transparent", na.translate=FALSE)
# if I provide show.legend=TRUE as now recommended in ggplot2 3.5.0+, it undermines the lower layers
ggplot(mapping=aes(x=dim1, y=dim2)) +
geom_raster(data=data1, mapping=aes(fill=value1), show.legend = FALSE) + # will become fill_ggnewscale_2
scale_fill_gradient(low = "grey8", high = "grey92") +
ggnewscale::new_scale_fill() +
geom_raster(data=data2, mapping=aes(fill=value2), show.legend=TRUE) +
scale_fill_brewer("layer2", palette = "Set3", drop=FALSE, na.value = "transparent", na.translate=FALSE) + # will become fill_ggnewscale_1
ggnewscale::new_scale_fill() +
geom_raster(data=data3, mapping=aes(fill=value3), show.legend = TRUE) +
scale_fill_brewer("layer3", palette = "Dark2", drop=FALSE, na.value = "transparent", na.translate=FALSE)
# I tried to be more specific about the show.legend argument, per
#
# but this has no effect
ggplot(mapping=aes(x=dim1, y=dim2)) +
geom_raster(data=data1, mapping=aes(fill=value1), show.legend = c(fill_ggnewscale_2=FALSE)) + # will become fill_ggnewscale_2
scale_fill_gradient(low = "grey8", high = "grey92") +
ggnewscale::new_scale_fill() +
geom_raster(data=data2, mapping=aes(fill=value2), show.legend=c(fill_ggnewscale_1=TRUE)) +
scale_fill_brewer("layer2", palette = "Set3", drop=FALSE, na.value = "transparent", na.translate=FALSE) + # will become fill_ggnewscale_1
ggnewscale::new_scale_fill() +
geom_raster(data=data3, mapping=aes(fill=value3), show.legend = c(fill=TRUE)) +
scale_fill_brewer("layer3", palette = "Dark2", drop=FALSE, na.value = "transparent", na.translate=FALSE)
I maintain an R package that uses ggplot2 to display brain imaging data. The package fundamentally depends on ggnewscale
and the ability to re-use the fill mapping for multiple images that are overlaid. This approach has worked well until relatively recently, with the release of ggplot2 3.5. Some use cases involve overlaying multiple categorical images where I want to preserve the unused factor levels in the legend. Previously, this could be achieved using drop=FALSE
in the scale_fill_*
specification. It also depended on only setting show.legend
to FALSE
for layers whose legend I wanted to hide and not setting show.legend=TRUE
. (see https://github.com/eliocamp/ggnewscale/issues/32).
With the release of ggplot2 3.5, I'm now expected to set show.legend=TRUE
to maintain the unused factor levels. As a result, I can no longer achieve the intended display because the later layers in the build undue the settings of the lower layers, resulting in all-black legends that don't make sense.
I've poked at the objects (e.g., using gginnards
) and I see a number of differences, but I can't figure out what difference causes the error. Does anyone have advice on how to identify and patch the ggplot object to yield the desired result? (also see: https://github.com/eliocamp/ggnewscale/issues/59) I've attached a simple reprex below. Thanks so much for any advice!!
data1 <- expand.grid(dim1=1:10, dim2=1:10)
data1$value1 <- rnorm(100)
data2 <- expand.grid(dim1=1:10, dim2=1:10)
data2$value2 <- factor(sample(c(NA, "f1", "f2", "f3", "f4"), size = 100, replace=TRUE), levels = paste0("f", 1:5))
data3 <- expand.grid(dim1=1:10, dim2=1:10)
data3$value3 <- factor(sample(c(NA, "i1", "i2", "i3", "i4"), size = 100, replace=TRUE), levels = paste0("i", 1:5))
# leaving as NA shows the right layers in legend, but the missing level is not plotted
# https://github.com/tidyverse/ggplot2/issues/5728
ggplot(mapping=aes(x=dim1, y=dim2)) +
geom_raster(data=data1, mapping=aes(fill=value1), show.legend = FALSE) +
scale_fill_gradient(low = "grey8", high = "grey92") +
ggnewscale::new_scale_fill() +
geom_raster(data=data2, mapping=aes(fill=value2), show.legend=NA) +
scale_fill_brewer("layer2", palette = "Set3", drop=FALSE, na.value = "transparent", na.translate=FALSE) +
ggnewscale::new_scale_fill() +
geom_raster(data=data3, mapping=aes(fill=value3), show.legend = NA) +
scale_fill_brewer("layer3", palette = "Dark2", drop=FALSE, na.value = "transparent", na.translate=FALSE)
# if I provide show.legend=TRUE as now recommended in ggplot2 3.5.0+, it undermines the lower layers
ggplot(mapping=aes(x=dim1, y=dim2)) +
geom_raster(data=data1, mapping=aes(fill=value1), show.legend = FALSE) + # will become fill_ggnewscale_2
scale_fill_gradient(low = "grey8", high = "grey92") +
ggnewscale::new_scale_fill() +
geom_raster(data=data2, mapping=aes(fill=value2), show.legend=TRUE) +
scale_fill_brewer("layer2", palette = "Set3", drop=FALSE, na.value = "transparent", na.translate=FALSE) + # will become fill_ggnewscale_1
ggnewscale::new_scale_fill() +
geom_raster(data=data3, mapping=aes(fill=value3), show.legend = TRUE) +
scale_fill_brewer("layer3", palette = "Dark2", drop=FALSE, na.value = "transparent", na.translate=FALSE)
# I tried to be more specific about the show.legend argument, per
# https://github.com/tidyverse/ggplot2/issues/5728
# but this has no effect
ggplot(mapping=aes(x=dim1, y=dim2)) +
geom_raster(data=data1, mapping=aes(fill=value1), show.legend = c(fill_ggnewscale_2=FALSE)) + # will become fill_ggnewscale_2
scale_fill_gradient(low = "grey8", high = "grey92") +
ggnewscale::new_scale_fill() +
geom_raster(data=data2, mapping=aes(fill=value2), show.legend=c(fill_ggnewscale_1=TRUE)) +
scale_fill_brewer("layer2", palette = "Set3", drop=FALSE, na.value = "transparent", na.translate=FALSE) + # will become fill_ggnewscale_1
ggnewscale::new_scale_fill() +
geom_raster(data=data3, mapping=aes(fill=value3), show.legend = c(fill=TRUE)) +
scale_fill_brewer("layer3", palette = "Dark2", drop=FALSE, na.value = "transparent", na.translate=FALSE)
If I understand you correctly, you can achieve your desired result by being more specific about which aesthetics should be displayed in the legend for each geom, i.e. in contrast what you have tried set
fill=FALSE
for the first two geom_raster
for which will the fill
be renamed to fill_ggnewscale_xxx
and to TRUE
for the last.fill_ggnewscale_xxx=FALSE
for the last layer. I did some experimenting and at least for your example it does not seem to matter which one is set to FALSE
.library(ggplot2)
library(ggnewscale)
set.seed(123)
ggplot(mapping = aes(x = dim1, y = dim2)) +
geom_raster(
data = data1, mapping = aes(fill = value1),
show.legend = c(fill = FALSE)
) +
scale_fill_gradient(low = "grey8", high = "grey92") +
ggnewscale::new_scale_fill() +
geom_raster(
data = data2, mapping = aes(fill = value2),
show.legend = c(fill = FALSE)
) +
scale_fill_brewer("layer2",
palette = "Set3", drop = FALSE,
na.value = "transparent", na.translate = FALSE
) +
ggnewscale::new_scale_fill() +
geom_raster(
data = data3, mapping = aes(fill = value3),
show.legend = c(fill = TRUE, fill_ggnewscale_2 = FALSE)
) +
scale_fill_brewer("layer3",
palette = "Dark2", drop = FALSE,
na.value = "transparent", na.translate = FALSE
)