r - Getting multiple fill with categorical data to display using ggplot2 3.5.0+ - Stack Overflow

admin2025-04-16  5

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)

Share Improve this question edited Feb 4 at 10:00 stefan 127k6 gold badges38 silver badges76 bronze badges Recognized by R Language Collective asked Feb 4 at 3:07 Michael HallquistMichael Hallquist 4925 silver badges7 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 0

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.
  • Set the 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
  )

转载请注明原文地址:http://www.anycun.com/QandA/1744741517a86961.html