1

Related to this previous post about changing the color of the figure legend text to match graphing colors in ggplot, I would like to extend this to ggsurvplot objects.

fit <- survfit(Surv(time, status) ~ sex, data = lung)
fitgraph <- ggsurvplot(fit, 
                       risk.table = TRUE, risk.table.y.text.col = TRUE)
fitgraph

enter image description here

What I would really like to do is change the color of the text on the legend to match the color of the lines on the graph.

Using this previous solution from Z Cao I can convert the ggsurvplot$plot to a grob, change the legend text color, and then convert back into a ggplot object, which works fine...

g1 <- fitgraph

pGrob <- ggplotGrob(g1$plot)
g.b   <- pGrob[["grobs"]][[which(pGrob$layout$name=="guide-box")]]
l     <- g.b[[1]][[1]][["grobs"]]
# get grobs for legend symbols (extract color)
lg    <- l[sapply(l, function(i) grepl("GRID.segments", i))] 
clr   <- mapply(FUN=function(x){x$gp$col},x=lg)

gb  <- which(grepl("guide-box", pGrob$layout$name))
gb2 <- which(grepl("guides", pGrob$grobs[[gb]]$layout$name))
label_text <- which(grepl("label",pGrob$grobs[[gb]]$grobs[[gb2]]$layout$name))

pGrob$grobs[[gb]]$grobs[[gb2]]$grobs[label_text] <- 
  mapply(FUN = function(x, y) {x[["children"]][[1]][["children"]][[1]]$gp <- gpar(col =y); return(x)},
         x =   pGrob$grobs[[gb]]$grobs[[gb2]]$grobs[label_text],
         y =  clr, SIMPLIFY = FALSE)
grid.draw(pGrob)

Then convert this back into a ggplot object, with color of figure legend text changed to match the color of lines as desired...

plot1 <- as.ggplot(pGrob)
plot1

enter image description here

Where I am stuck is now integrating this with the original ggsurvplot risk table underneath, as in the first graph.

My pretty basic reaction was to simply replace the ggsurvplot$plot with the new ggplot object created after extracting the original plot to a grob and then back to a ggplot...

g1$plot <- plot1

However this does not work...

Error in [.data.frame(g$data[1], "colour") : undefined columns selected

Most likely due to a loss of the underlying data in the process above, storing only a 2x2 table...

plot1$data
  x y
1 0 0
2 1 1

Compared to the original fitgraph$plot$data which yields the entire data set (hundreds of rows, ongoing survival proportions) which presumably feeds the risk table. The other dumb strategy of plot1 + fitgraph$table doesn't work either.

There must be a better strategy - any ideas? Thanks in advance!

EDIT Thanks to Stefan for the ggtext solution below, however each of my ggsurvplots has a different color scheme, and applying this method seems to over-ride these, e.g.

fit <- survfit(Surv(time, status) ~ sex, data = lung)
fitgraph <- ggsurvplot(fit, 
                       risk.table = TRUE, 
                       palette=c("#B79F00", "#619CFF"),
                       risk.table.col = "strata")

For a graph that looks like...

enter image description here

That looks good but when I then apply the method to change the legend color...

cols <- c("#B79F00", "#619CFF")

labels <- function(x, cols) {
  glue::glue("<span style = 'color: {cols}'>{x}</span>")
}

fitgraph$plot <- fitgraph$plot +
  scale_color_discrete(labels = ~labels(.x, cols)) +
  theme(legend.text = element_markdown())

fitgraph

This results in a loss of the original graph colors...

Scale for 'colour' is already present. Adding another scale for 'colour', which will replace the existing scale.

enter image description here

Any ideas? Thanks again...

1 Answer 1

2

While I appreciate your effort the ggtext package offers an easy option to achieve your desired result. Besides making it easier to set the legend text colors the final result could simply assigned back to the plot element of the ggurvplot object:

library(survival)
library(survminer)
library(ggtext)

fit <- survfit(Surv(time, status) ~ sex, data = lung)
fitgraph <- ggsurvplot(fit, risk.table = TRUE, risk.table.y.text.col = TRUE)

cols <- scales::hue_pal()(2)

labels <- function(x, cols) {
  glue::glue("<span style = 'color: {cols}'>{x}</span>")
}

fitgraph$plot <- fitgraph$plot +
  scale_color_discrete(labels = ~labels(.x, cols)) +
  theme(legend.text = element_markdown())

fitgraph

UPDATE In case you pass a custom color palette we have to switch to scale_color_manual and pass the colors to the values argument. One drawback is that in that case we get a warning as we replace the already existing scale_color_manual:

library(survival)
library(survminer)
library(ggtext)

cols <- c("#B79F00", "#619CFF")

fit <- survfit(Surv(time, status) ~ sex, data = lung)
fitgraph <- ggsurvplot(fit, risk.table = TRUE, risk.table.y.text.col = TRUE, palette=cols)

labels <- function(x, cols) {
  glue::glue("<span style = 'color: {cols}'>{x}</span>")
}

fitgraph$plot <- fitgraph$plot +
  scale_color_manual(values = cols, labels = ~labels(.x, cols)) +
  theme(legend.text = element_markdown())
#> Scale for 'colour' is already present. Adding another scale for 'colour',
#> which will replace the existing scale.

fitgraph

2
  • Thanks for that! One issue I run into with that solution is that it over-rides the color palette I had previously chosen for the graph - have edited my question above to show what I mean. Any help would be appreciated!
    – Timmo83
    Commented Aug 17, 2022 at 4:18
  • Hej @Timmo83. I just added an update for the case of a custom color palette.
    – stefan
    Commented Aug 17, 2022 at 18:27

Not the answer you're looking for? Browse other questions tagged or ask your own question.