1

I need to loop through i iteration of factors, and each factor needs to be plotted as one plot in a subplot. What I would like to do is hiding the legend for every iteration bar the first one, and use legendgroup to tie all the legends together. This is what I have done so far:

library(plotly)
library(dplyr)

mtcars %>%
  mutate(vs = as.factor(vs)) %>%
  group_split(cyl) %>%
  lapply(function(i) {

    #show.legend <- ifelse(i == 1, TRUE, FALSE)

    show.legend <- if(i == 1) {TRUE} else {FALSE}

    plot_ly(
      data = i
      ,x = ~gear
      ,y = ~mpg
      ,color = ~vs
      ,type = "bar"
      ,legendgroup = ~vs
    ) %>%
      layout(
        barmode = "stack"
        ,showlegend = show.legend
      )
  }) %>%
  subplot(
    nrows = NROW(.)
    ,shareX = TRUE
    ,shareY = TRUE
    ,titleX = TRUE
    ,titleY = TRUE
    ,margin = 0.05
  )

However this produces an error and no legend:

Warning messages:
1: In if (i == 1) { :
  the condition has length > 1 and only the first element will be used

If I use show.legend <- ifelse(i == 1, TRUE, FALSE) (commented out above), I get multiple legends instead of just once.

I am aware I could do the below, but I need to this in a loop.

p1 <- plot_ly(blah, showlegend = TRUE)
p2 <- plot_ly(blah, showlegend = FALSE)
P3 <- plot_ly(blah, showlegend = FALSE)

subplot(p1,p2,p3)

I believe I am not calling the i iteration properly. As another option I tried case_when:

show.legend <- case_when(
      i == 1 ~ TRUE
      ,i != 1 ~ FALSE
    )

However this produces the same result as ifelse.

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
sactyr
  • 172
  • 1
  • 1
  • 14

2 Answers2

1

There are two issues in your code:

  1. i is not 1:3 but your current tibble you are iterating through via lapply (see seq_along below). That is why you get the warning:

In if (i == 1) { : the condition has length > 1 and only the first element will be used

  1. showlegend needs to be an argument to plot_ly not to layout because subplot always adopts the layout from one of its plots. see ?subplot and its argument which_layout.

layout options found later in the sequence of plots will override options found earlier in the sequence


Here is what I think you are after:

library(plotly)
library(dplyr)

tibble_list <- mtcars %>%
  mutate(vs = as.factor(vs)) %>%
  group_split(cyl)

lapply(seq_along(tibble_list), function(i) {
  show_legend <- if (i == 1) {TRUE} else {FALSE}
  plot_ly(
    data = tibble_list[[i]],
    x = ~ gear,
    y = ~ mpg,
    color = ~ vs,
    type = "bar",
    legendgroup = ~ vs,
    showlegend = show_legend
  ) %>% layout(barmode = "stack")
}) %>% subplot(
  nrows = NROW(.),
  shareX = TRUE,
  shareY = TRUE,
  titleX = TRUE,
  titleY = TRUE,
  margin = 0.05,
  which_layout = 1
)

Result

Please find an offical example here.

ismirsehregal
  • 30,045
  • 5
  • 31
  • 78
  • 1
    This works like a charm, thank you. I wasn't aware of the which_layout argument at all. And didn't think to use use seq_along. I thought because I was piping group_cycle into lapply the pipe will automatically feed the iteration. Cheers – sactyr Dec 02 '19 at 02:26
0
library(plotly)
library(dplyr)

    ## store plot as variable p
        p <- mtcars %>%
            mutate(vs = as.factor(vs)) %>%
            group_split(cyl) %>%
            lapply(function(i) {
                plot_ly(
                    data = i
                    ,x = ~gear
                    ,y = ~mpg
                    ,color = ~vs
                    ,type = "bar"
                    ,showlegend = TRUE ## include all legends in stored variable
                ) %>%
                    layout(
                        barmode = "stack"
                    )
            }) %>%
            subplot(
                nrows = NROW(.)
                ,shareX = TRUE
                ,shareY = TRUE
                ,titleX = TRUE
                ,titleY = TRUE
                ,margin = 0.05
            )
    ## remove unwanted legends from plot
        for (i in seq(3, length(p[["x"]][["data"]]))) {
            p[["x"]][["data"]][[i]][["showlegend"]] <- FALSE
        }
    ## show plot
        p

enter image description here

saheed
  • 340
  • 2
  • 12
  • Hi @saheed thanks for this - this is in the right direction but I just have one follow up question before I accept the answer. This part of the code ```seq(3, length(p[["x"]][["data"]]))``` refers to the legend entries 3,4,5 and I understand we removing this. Is there a way to do this programatically? But actual data could have dynamic number of legends, and so I am looking for a way to remove them like how you have done – sactyr Nov 28 '19 at 02:57
  • I have also added ```legendgroup = ~vs``` so that we can control variables on all subplots. – sactyr Nov 28 '19 at 03:04
  • Per the question asked, this solution will remove the legend for every iteration except the first one (even if there are more.) I'm not sure if there's a way to do this when calling the plotly function. – saheed Nov 28 '19 at 16:48
  • Looking at this line ```for (i in seq(3, length(p[["x"]][["data"]])))``` the code removes legend entries 3,4,5 and leaves 1 and 2. It is not however removing "legend for every iteration except the first one". Thank you for your input though - I do think it's in the right direction – sactyr Nov 28 '19 at 23:12