1

I have a dataframe listing the number of individual engagements made with 49 different committees over 5 years. An example with a reduced number of categories is provided below:

library(dplyr)
df <- data.frame(Year = c(2019, 2020, 2021, 2022, 2023),
                 Committees = c("House Foreign Affairs",
                                "House Foreign Affairs",
                                "House Foreign Affairs",
                                "House Foreign Affairs",
                                "House Foreign Affairs",
                                "House Judiciary",
                                "House Judiciary",
                                "House Judiciary",
                                "House Judiciary",
                                "House Judiciary",
                                "Senate Appropriations",
                                "Senate Appropriations",
                                "Senate Appropriations",
                                "Senate Appropriations",
                                "Senate Appropriations",
                                "Senate Oversight",
                                "Senate Oversight",
                                "Senate Oversight",
                                "Senate Oversight",
                                "Senate Oversight"), 
                 n = c(4,8,4,6,2,7,4,8,2,8,1,4,3,6,4,8,4,3,8,7))

df <- df %>% 
  mutate(across(c(1:2), factor)) %>%
  arrange(Year)

I made a plot to visualize the change in total engagements over these five years with the code below:

library(ggplot2)
plot <- df %>%
ggplot(aes(Year, n, group = Committees, color = Committees)) +
  geom_line() +
  geom_point() +
  theme_bw ()

ggplot line chart

This already looks quite cluttered, and with 49 categories the plot with the original data looks like this.

pain

So, I am trying to turn the plot into a plotly to make the lines more visible, to compress the legend into a dropdown, and to allow for some level of interactivity for my users.

library(plotly)
plot %>%
  ggplotly(x = ~date, y = ~median,
           split = ~city,
           frame = ~frame,  
           type = 'scatter',
           mode = 'lines')

Now, the problem is that making the buttons for 4 categories is simple enough with the code provided here. Also, this answer tackled the problem of having to create 49 different buttons per each category, and this answer further expanded it. However, I am not very well versed with plotly, and the code I built based on these answers just outputs: Error: object 'y_axis_var_names' not found

This is what I ended up with. If you have better methods to make the graph interactive and allow the selection of individual lines, please feel free to suggest it.

create_buttons <- function(df, y_axis_var_names) {
  lapply(
    y_axis_var_names,
    FUN = function(var_name, df) {
      button <- list(
        method = 'restyle',
        args = list(list(y = list(df[, var_name]),x = list(df[, var_name]))),
        label = sprintf('Show %s', var_name)
      )
    },
    df
  )
  
}

y_axis_var_names <- c("House Foreign Affairs",
                        "House Judiciary",
                        "Senate Appropriations",
                        "Senate Oversight")

plot %>%
  ggplotly(type = 'scatter',
           mode = 'lines') %>%
  layout(xaxis = list(domain = c(0.1, 1)),
         yaxis = list(title = "y"),
         updatemenus = list(
      list(
        y = 0.7,
        buttons = create_buttons(plot, y_axis_var_names))))
2
  • 1
    I'm totally lost... What are date, median, city, frame? And plot is not a dataframe, and you pass it to your function create_buttons. Commented Feb 28, 2024 at 23:56
  • Absolutely my mistake. I added the reference code, instead of my actual code. I edited the question to reflect this. Commented Feb 29, 2024 at 21:01

1 Answer 1

1

General Issue

There is a major issue with your code and particular your usage of ggplotly:

The idea is that you pass a ggplot object to it and it transforms it into a plotly object.

The function plot_ly on the other hand creates a plotly object from the data and you have to specify what maps to what.

Your usage looks like the latter (i.e. mapping by yourself), but using ggplotly is wrong here. This is, by the way, the reason why this example can be reproduced despite lacking some needed columns, because ggplotly has all the information it needs from the ggplot object and chooses to ignore additional parameters which could not be resolved anyways (city, frame, median are nowhere defined in your example)

Potential Solution

From what I understand: you want a dropdown with which you can select which lines to plot? If this is the case you can indeed use some custom UI elements to do so and here's an example of how to do that:

library(plotly)

set.seed(29022024)
ex <- expand.grid(year = 2019:2023, cat = LETTERS)
ex$n <- rpois(nrow(ex), 10)


## All categories look cramped

plot_ly(ex) %>%
  add_trace(x = ~ year, y = ~ n, color = ~ cat, type = "scatter", mode = "line",
            colors = "viridis")

## Add Buttons to Toggle Traces
ply <- plot_ly(ex) %>%
  add_trace(x = ~ year, y = ~ n, color = ~ cat, type = "scatter", mode = "line",
            colors = "viridis", visible = ~ cat %in% LETTERS[1:5])

btns <- lapply(levels(ex$cat), \(.x) {
  list(method = "restyle",
       label = paste("Toggle", .x),
       args = list(list(visible = !.x %in% LETTERS[1:5]), 
                   list(which(levels(ex$cat) == .x) - 1L)),
       args2 = list(list(visible = .x %in% LETTERS[1:5]), 
                    list(which(levels(ex$cat) == .x) - 1L)))
})

ply %>%
  layout(
    updatemenus = list(
      list(
        y = 0.8,
        yanchor = "top",
        buttons = btns
      )
    )
  )

A click on the buttons hides / shows the trace, respectively. In the beginning I just show the first 5 traces to de-clutter from the beginning. Then, with the buttons I can decide to add remove traces.

Update

As per the comments if you want to show only one trace at a time you adapt your code as follows:

ply <- plot_ly(ex) %>%
  add_trace(x = ~ year, y = ~ n, color = ~ cat, type = "scatter", mode = "line",
            colors = "viridis", visible = ~ cat == "A")

btns <- lapply(levels(ex$cat), \(.x) {
  list(method = "restyle",
       label = paste("Toggle", .x),
       args = list(list(visible = LETTERS == .x))
  )
})

ply %>%
  layout(
    updatemenus = list(
      list(
        y = 0.8,
        yanchor = "top",
        buttons = btns
      )
    )
  )

Screenshots

Add Traces

Plotly Line Chart with Buttons in a GIF which adds/removes traces upon click

One Trace a Time

Plotly Line Chart with Buttons in a GIF which hides/shows traces upon click

Sign up to request clarification or add additional context in comments.

2 Comments

This solution works well and I made it functional in my code. However, I was looking for something more in the likes of the original example listed on the Plotly website. Basically, I would like my dropdown to only show one line each time you select an option of the dropdown, like it was done in this example (stackoverflow.com/questions/61270022/…). The issue is that I have 49 categories, so setting the arg becomes difficult. Can your solutions be adapted to that?
Ah this is even easier, will post an update.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.