Search code examples
rplotlybar-chartlegend

How to make the legend appear in a stacked bar chart with `plotly` in R


I want to make a stacked bar chart with a slider in plotly.

I took inspiration from the plotly website.

I can't manage to insert the legend, maybe because I didn't use the legendgroup argument correctly.

summary <- structure(list(Language = c("English", "English", "English", "English", "English", "English", "English", "English", "English", "English", "English", "English", "English", "German", "German", "German", "German", "German", "German", "German", "German", "German", "German", "German", "German", "German", "German"), category = structure(c(1L, 1L, 1L, 1L, 2L, 3L, 3L, 3L, 3L, 4L, 4L, 4L, 4L, 1L, 1L, 1L, 1L, 2L, 2L, 2L, 3L, 3L, 3L, 3L, 4L, 4L, 4L), levels = c("A", "B", "C", "D"), class = "factor"), Year = c(2000, 2001, 2002, 2003, 2002, 2000, 2001, 2002, 2003, 2000, 2001, 2002, 2003, 2000, 2001, 2002, 2003, 2000, 2001, 2003, 2000, 2001, 2002, 2003, 2001, 2002, 2003), n = c(20L, 39L, 41L, 53L, 1L, 3L, 12L, 8L, 22L, 2L, 11L, 17L, 18L, 44L, 29L, 38L, 46L, 1L, 3L, 3L, 2L, 1L, 2L, 4L, 1L, 3L, 3L)), row.names = c(NA, -27L), class = "data.frame")

library(plotly)

# Create data for different traces
traces <- list()
unique_years <- unique(summary$Year) %>% sort
unique_disciplines <- unique(summary$category)
n_colors <- length(unique_disciplines)
colors <- rainbow(n_colors)

for (i in 1:length(unique_years)) {
  year <- unique_years[i]
  trace <- list(
    visible = FALSE,
    name = paste0('Year = ', year),
    x = summary$Language[summary$Year == year],
    y = summary$n[summary$Year == year],
    color = summary$category[summary$Year == year]
  )
  traces[[i]] <- trace
}
traces[[1]]$visible <- TRUE  # Make the first trace visible by default

# Create steps for the slider
steps <- list()
for (i in 1:length(unique_years)) {
  step <- list(
    method = "restyle",
    args = list("visible", rep(FALSE, length(traces))),
    label = paste0('Year = ', unique_years[i])
  )
  step$args[[2]][i] <- TRUE
  steps[[i]] <- step
}
# Create the graph
fig <- plot_ly()
for (i in 1:length(traces)) {
  fig <- fig %>% add_bars(
    type = 'bar', 
    x = traces[[i]]$x,
    y = traces[[i]]$y,
    visible = traces[[i]]$visible,
    marker = list(color = colors[traces[[i]]$color]),
    # The problem comes from the following line probably:
    legendgroup = traces[[i]]$color 
  )
}

# Add the slider to the graph
fig <- fig %>% layout(
  sliders = list(list(active = 1, currentvalue = list(prefix = "Year: "), steps = steps)),
  yaxis = list(title = "Count"),
  barmode = "stack"
)

# Display the graph
fig

enter image description here


Solution

  • I started going down the path of breaking down your code. I can still do that if this answer is not satisfying.

    This does not use your code (just your data); however, it does accomplish your objective. You'll notice that there isn't much to it. With the code you're written and the knowledge needed to put that code together, I think you'll understand what I've done without any explanation. I've added comments in the code and you an always comment on my answer if you have any questions.

    At the end of the code I used plt$x$layout$updatemenus = list(). I did this because using frame adds and populates the slider, but it also adds animation (which you were not asking for). This line of code removes the animation while keeping the slider.

    library(plotly)
    
    plt <- plot_ly(summary, type = "bar", color = ~category, 
            colors = ~setNames(rainbow(4), unique(category)), # colors by category
            frame = ~Year, x = ~Language, y = ~n) %>% 
      layout(barmode = "stack")                               # stack bars
    
    plt <- plotly_build(plt)          # build plot to remove animation
    plt$x$layout$updatemenus = list() # remove animation (keep slider)
    plt
    

    enter image description here

    enter image description here