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 ()
This already looks quite cluttered, and with 49 categories the plot with the original data looks like this.
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))))
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)
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.
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
)
)
)