nluigi
nluigi

Reputation: 1345

remove legend duplicates in R plotly subplots

I have an R Plotly subplot graph with the issue that the trace legends are duplicates:

enter image description here

This is a common problem:

Some of these have (ir)relevant answers, some don't.

The solution in essence is to force the first subplot to show the legend and disable it for all other subplots. However, the way to do this depends on the implementation, i.e. whether the subplots are generated using a list or a function. In my case I am using a function which makes it tricky to force one subplot to show the legends but not the others.

A minimum working example:

# clear env
rm(list = ls())

# libraries
library(plotly)
library(reshape2)

# prepare dataset
attach(mtcars)
mtcars$car <- rownames(mtcars)
mtcars <- melt(mtcars)

# generate plot of variable
pl <- . %>% 
  plot_ly(
    x = ~car, 
    y = ~value,
    type = 'scatter', 
    mode = 'markers',
    color = I('blue'),
    name = 'value',
    legendgroup = 'batches',
    showlegend = TRUE
  ) %>%
  layout(
    xaxis = list(
      type = 'category'
    ),
    yaxis = list(
      title = ~unique(variable)
    )
  )

# generate subplot view
mtcars %>%
  group_by(variable) %>%
  do(
    p = pl(.)
  ) %>%
  subplot(
    nrows = NROW(.),
    shareX = TRUE,
    titleY = TRUE
  )

This generates the image above.

In my attempt I have been trying to use a closure with a global variable which is set to FALSE after the first subplot but it is not working as expected. All legend entries disappear despite seeing in the console it is setting the global variable correctly.

# set global showlegend variable
# we will set to FALSE after first plot
showlegend <- TRUE

# generate plot of variable
pl <- . %>% 
  plot_ly(
    x = ~car, 
    y = ~value,
    type = 'scatter', 
    mode = 'markers',
    color = I('blue'),
    name = 'value',
    legendgroup = 'batches',
    # use a closure to set showlegend
    showlegend = (function(){
      # echo to console
      print(showlegend)
      if(showlegend){
        # at first plot this will run
        # set to FALSE for subsequent plots
        showlegend <<- FALSE
        return(TRUE)
      } else {
        # runs for all except first
        return(FALSE)
      }
    })()
  ) %>%
  layout(
    xaxis = list(
      type = 'category'
    ),
    yaxis = list(
      title = ~unique(variable)
    )
  )

This generates: enter image description here

while the console outputs: TRUE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE, FALSE

based on this I expect there to be one legend entry, but the graph shows no legend entries. Why is this not working as expected?

Is there perhaps a more efficient way to toggle the showlegend variable without a closure and global variable?

Upvotes: 2

Views: 2616

Answers (1)

nluigi
nluigi

Reputation: 1345

After thinking about the problem some more I realized that perhaps Plotly removes single legend entries, i.e. only legends are shown when more than one traces with a legend are plotted.

And indeed when adding an additional trace the graph shows the legends as expected.

enter image description here

Solution:

# set global showlegend variable
# we will set to FALSE after first plot
showlegend <- TRUE

# generate plot of variable
pl <- . %>% 
  plot_ly(
    x = ~car, 
    y = ~value,
    type = 'scatter', 
    mode = 'markers',
    color = I('blue'),
    name = 'value',
    legendgroup = 'batches',
    showlegend = showlegend
  ) %>%
  add_trace(
    y = ~value,
    name = 'target',
    type='scatter', 
    mode = 'lines',
    line=list(
      color='black', 
      dash='dash'
    ),
    showlegend = (function(){
      if(showlegend){
        showlegend <<- FALSE
        return(TRUE)
      } else {
        return(FALSE)
      }
    })()
  ) %>%
  layout(
    xaxis = list(
      type = 'category'
    ),
    yaxis = list(
      title = ~unique(variable)
    )
  )

Note that the closure must be applied to the last trace plotted otherwise the global variable is set to FALSE too early.

Update: During testing i found that the following also works:

showlegend = (~unique(variable) == "mpg")

or if you do not want to hardcode the variable comparison:

showlegend = (~unique(variable) == unique(mtcars$variable)[1])

This is now my preferred method over the global variable closure

Upvotes: 2

Related Questions