Dropdown menu in R plotly to plot subsample - r

I am trying to use a dropdown menu to plot a subsample of a dataset using the dropdown menu from plotly in R.
This is what I have so far (based on this answer) without sucess:
library(data.table)
library(ggplot2)
library(plotly)
X <- data.table(xcoord = 1:10, ycoord = 1:10)
Z <- X[xcoord < 5]
gg <- ggplot(X, aes(x = xcoord, y = ycoord)) + geom_point()
ggplotly(gg) %>%
layout(updatemenus = list(
list(buttons = list(
list(method = "restyle",
args = list(list("x", list(X$xcoord)),
list("y", list(X$xcoord))),
label = "X"),
list(method = "restyle",
args = list(list("x", list(Z$xcoord)),
list("y", list(Z$ycoord))),
label = "Z")
))
))

Found the solution: had to use named lists instead.
ggplotly(gg) %>%
layout(updatemenus = list(
list(buttons = list(
list(method = "restyle",
args = list(list(x = list(X$xcoord)),
list(y = list(X$xcoord))),
label = "X"),
list(method = "restyle",
args = list(list(x = list(Z$xcoord)),
list(y = list(Z$ycoord))),
label = "Z")
))
))

Related

Plotly grouped barchart : how to create buttons to display different x values R

this is my dataframe :
artists <- c("Black Waitress", "Black Pumas")
tee_x<- c(20, 0)
tee_y <- c(3, 18)
tee_z <- c (30,0)
tee_t <- c(0,35)
data2 <- data.frame(artists, tee_x, tee_y,tee_t)
And this is what I am trying to create :
fig <- plot_ly(data=data2, x = ~artists, y = ~tee_x, type = 'bar', name = 'tee_x')
fig <- fig %>% add_trace(y = ~tee_y, name = 'tee_y')
fig <- fig %>% add_trace(y = ~tee_t, name = 'tee_t')
fig <- fig %>% layout(yaxis = list(title = 'Count'), barmode = 'group',
updatemenus = list(
list(
y = 0.8,
buttons = list(
list(method = "restyle",
args = list("x", list(data2[c(1),(2:4)])),
label = "Black Waitress"),
list(method = "restyle",
args = list("x", list(data2[c(2),(2:4)])),
label = "Black Pumas")))
))
fig
I am trying to create a grouper barplot in plotly which shows, for each artist the number of tees they sold and their type. I am also trying to create buttons so that you can look at individual artists instead of both of them. However it is not working and I have no clue how to solve the problem.
Thank you
EDIT :
I have been also trying this way
product <- c("tee_X","tee_y","tee_t")
artists <- c("Black Waitress", "Black Pumas")
Black_Waitress<- c(20, 0, 0)
Black_Pumas <- c(3, 18, 0)
tee_z <- c (30,0)
tee_t <- c(0,35)
data2 <- data.frame(product, Black_Waitress, Black_Pumas)
show_vec = c()
for (i in 1:length(artists)){
show_vec = c(show_vec,FALSE)
}
get_menu_list <- function(artists){
n_names = length(artists)
buttons = vector("list",n_names)
for(i in seq_along(buttons)){
show_vec[i] = TRUE
buttons[i] = list(list(method = "restyle",
args = list("visible", show_vec),
label = artists[i]))
print(list(show_vec))
show_vec[i] = FALSE
}
return_list = list(
list(
type = 'dropdown',
active = 0,
buttons = buttons
)
)
return(return_list)
}
print(get_menu_list(artists))
fig <- plot_ly(data=data2, x = ~product, y = ~Black_Waitress, type = 'bar')
fig <- fig %>% add_trace(y = ~Black_Pumas)
fig <- fig %>% layout(showlegend = F,yaxis = list(title = 'Count'), barmode = 'group',
updatemenus = get_menu_list(artists))
fig
However the problem is that when I choose an artist in the dropdown menu I want to be shown ONLY his/her products (in other words I would like to get rid of the 0 values dynamically) Is this possible?
Without the 0 Values and Initially Blank Plot
Essentially, you need to add a trace with no data. Additionally, since visibility settings were defined, all of that requires updating (because there are more traces now).
This can be further customized, of course. Here's a basic version of what I think you're looking for.
plot_ly(data = data3, x = ~artists, y = 0, type = "bar", color = ~tees,
visible = c(T, T, T)) %>%
add_bars(y = ~values, split = ~artists, # visibility F for all here
legendgroup = ~tees, name = ~tees, visible = rep(F, times = 4),
color = ~tees) %>%
layout(
yaxis = list(title = "Count"), barmode = "group",
updatemenus = list(
list(y = .8,
buttons = list(
list(method = "restyle", # there are 7 traces now; 3 blank
args = list(list(visible = c(F, F, F, F, T, F, T))),
label = "Black Waitress"),
list(method = "restyle",
args = list(list(visible = c(F, F, F, T, F, T, F))),
label = "Black Pumas")))))
Without the 0 Values
By your request, here is a version where the zero values are removed. First, I filtered the data for the non-zero values. This changed the number of traces from 6 to 4, so that needed to be accounted for in the areas where visibility is declared.
In this version, I only commented where there was something that changed from my original answer.
library(plotly)
library(tidyverse)
artists <- c("Black Waitress", "Black Pumas")
tee_x <- c(20, 0)
tee_y <- c(3, 18)
# tee_z <- c(30,0)
tee_t <- c(0,35)
data2 <- data.frame(artists, tee_x, tee_y, tee_t)
data3 <- pivot_longer(data2, col = starts_with("tee"),
names_to = "tees", values_to = "values") %>%
filter(values != 0) # <----- filter for non-zeros
plot_ly(data = data3, x = ~artists, y = ~values, split = ~artists,
legendgroup = ~tees, name = ~tees,
visible = rep(c(F, T), times = 2), # <---- 4 traces
color = ~tees, type = "bar") %>%
layout(
yaxis = list(title = "Count"), barmode = "group",
updatemenus = list(
list(y = .8,
buttons = list(
list(method = "restyle", # 4 traces
args = list(list(visible = c(F, T, F, T))),
label = "Black Waitress"),
list(method = "restyle", # 4 traces
args = list(list(visible = c(T, F, T, F))),
label = "Black Pumas")))))
With the 0 Values
I think it will be a lot easier to use visibility than trying to change out the data. If you wanted to see one artist at a time and use the dropdown to switch between the groups, this works.
First, I rearranged the data to make this easier. When I plotted it, I used split, so that the traces were split by the values on the x-axis, along with the colors. The traces are ordered artist 1, tee_t, artist 2, tee_t... and so on. When using visibility, you need the method restyle (because it's a trace attribute) and a declaration of true or false for each trace.
library(tidyverse)
library(plotly)
data3 <- pivot_longer(data2, col = starts_with("tee"),
names_to = "tees", values_to = "values")
plot_ly(data = data3, x = ~artists, y = ~values, split = ~artists,
legendgroup = ~tees, name = ~tees, visible = rep(c(F, T), times = 3),
color = ~tees, type = "bar") %>%
layout(
yaxis = list(title = "Count"), barmode = "group",
updatemenus = list(
list(y = .8,
buttons = list(
list(method = "restyle",
args = list(list(visible = c(F, T, F, T, F, T))),
label = "Black Waitress"),
list(method = "restyle",
args = list(list(visible = c(T, F, T, F, T, F))),
label = "Black Pumas")))))

Plotly multiple updatemenus

Building on this post, I am trying to create two updatemenus in R plotly that would allow to select every possible combination of two factors.
This is what I have done so far:
library(plotly)
X <- data.frame(x = 1:6,
y = 1:6,
z = 1:6,
gender = rep(c("M", "F"), each = 3),
eyes = rep(c("B", "G"), 3))
gg <- ggplot(data = X, aes(x = x, y = y)) +
geom_point(aes(color = z, alpha = interaction(gender, eyes))) +
scale_alpha_manual(values = rep(1, 4))
ggplotly(gg) %>%
layout(
updatemenus = list(
list(y = 1,
buttons = list(
list(method = "restyle",
args = list(list(visible = c(TRUE, TRUE, TRUE, TRUE)), 0:3),
label = "F&M"),
list(method = "restyle",
args = list(list(visible = c(TRUE, TRUE)), c(0, 2)),
label = "F"),
list(method = "restyle",
args = list(list(visible = c(TRUE, TRUE)), c(1, 3)),
label = "M"))),
list(y = .8,
buttons = list(
list(method = "restyle",
args = list(list(visible = c(TRUE, TRUE, TRUE, TRUE)), 0:3),
label = "B&G"),
list(method = "restyle",
args = list(list(visible = c(TRUE, TRUE)), 0:1),
label = "B"),
list(method = "restyle",
args = list(list(visible = c(TRUE, TRUE)), 2:3),
label = "G")))
)
)
)
The data:
# x y z gender eyes
# 1 1 1 M B
# 2 2 2 M G
# 3 3 3 M B
# 4 4 4 F G
# 5 5 5 F B
# 6 6 6 F G
This is the output, which does not work at all:
Edit
For instance, if F and B are selected, I would expect only a single data point to be displayed, namely (5, 5).
When you created this plotly object, plotly divided it into five traces. So when you wanted to use visibility, you needed to address each of these traces. If you have any questions, let me know!
I used gg1 to find five traces and what was in each of these traces. You could look at gg1 in the source pane or plotly_json().
Typically (not always!), you'll find the traces at gg1$x$data where gg1 is the plotly object.
There are five traces. For each args, each trace needs a T or F for visibility.
The first trace only contains (5, 5), that's F B
The second trace contains (1, 1) and (3, 3); those are both M B
The third trace contains (4, 4) and (6, 6); those are both F G
The fourth trace contains (2, 2); that's M G
The fifth trace contains the color gradient that all traces use
Here's the code:
gg1 = plotly_build(gg)
ggplotly(gg) %>%
layout(
updatemenus = list(
list(y = 1,
buttons = list(
list(method = "restyle",
args = list("visible", as.list(unlist(rep(T, 5)))),
label = "F&M"),
list(method = "restyle",
args = list("visible", list(T, F, T, F, T)),
label = "F"),
list(method = "restyle",
args = list("visible", list(F, T, F, T, T)),
label = "M")
) # end button
), # end first button list
list(y = .8,
buttons = list(
list(method = "restyle",
args = list("visible", as.list(unlist(rep(T, 5)))),
label = "B&G"),
list(method = "restyle",
args = list("visible", list(T, T, F, F, T)),
label = "B"),
list(method = "restyle",
args = list("visible", list(F, F, T, T, T)),
label = "G")
) # end button
) # end second menu list
) # end updatemenus
) # end layout
Update
From our comments, I do not believe that it's possible at this time to use updatemenus style buttons that interact with each other.

Plotly filter not working with transforms

I've made a filtered plotly stacked bar chart however it is showing empty spaces for bars that don't exist under the specified filter.
Here is my code:
event <- c("eve1","eve2","eve3","eve1","eve3","eve4","eve3","eve1","eve1","eve2","eve3","eve4","eve2","eve1")
year <-c("2017","2017","2017","2018","2018","2019","2019","2019","2020","2020","Total","Total","Total","Total")
cat1 <- c(20,14,25,64,0,5,34,2,14,6,78,0,0,23)
cat2 <- c(0,4,0,4,3,9,0,2,4,12,8,42,31,2)
eve <- data.frame(event,year,cat1,cat2)
fig <- plot_ly(eve, x = ~event, y = ~cat1, type = 'bar', name = 'cat1',
transforms = list(list(type = 'filter',target = ~year, operation = '=',value = eve$year))) %>%
add_trace(y = ~cat2, name = 'cat2')%>%
layout(yaxis = list(title = 'count'), barmode = 'stack',
updatemenus = list(list(type = 'dropdown',active = 0,buttons =
list(list(method = "restyle",
args = list("transforms[0].value", unique(eve$year)[1]),
label = unique(eve$year)[1]),
list(method = "restyle",
args = list("transforms[0].value", unique(eve$year)[2]),
label = unique(eve$year)[2]),
list(method = "restyle",
args = list("transforms[0].value", unique(eve$year)[3]),
label = unique(eve$year)[3]),
list(method = "restyle",
args = list("transforms[0].value", unique(eve$year)[4]),
label = unique(eve$year)[4]),
list(method = "restyle",
args = list("transforms[0].value", unique(eve$year)[5]),
label = unique(eve$year)[5])
))))
fig
Why is "eve2" appearing for 2019 when it doesn't appear in the data?
Found the solution a couple of months later, posting it in case anyone else comes across a similar issue.
Under layout you need to specify categoryorder and categoryarray. This stops Plotly from auto-sorting the dataframe which can interfere with the transforms element of the filter.
Here is the corrected code:
event <- c("eve1","eve2","eve3","eve1","eve3","eve4","eve3","eve1","eve1","eve2","eve3","eve4","eve2","eve1")
year <-c("2017","2017","2017","2018","2018","2019","2019","2019","2020","2020","Total","Total","Total","Total")
cat1 <- c(20,14,25,64,0,5,34,2,14,6,78,0,0,23)
cat2 <- c(0,4,0,4,3,9,0,2,4,12,8,42,31,2)
eve <- data.frame(event,year,cat1,cat2)
fig <- plot_ly(eve, x = ~event, y = ~cat1, type = 'bar', name = 'cat1',
transforms = list(list(type = 'filter',target = ~year, operation = '=',value = eve$year))) %>%
add_trace(y = ~cat2, name = 'cat2')%>%
layout(yaxis = list(title = 'count'),xaxis=list(categoryorder = "array",
categoryarray = eve$year), barmode = 'stack',
updatemenus = list(list(type = 'dropdown',active = 0,buttons =
list(list(method = "restyle",
args = list("transforms[0].value", unique(eve$year)[1]),
label = unique(eve$year)[1]),
list(method = "restyle",
args = list("transforms[0].value", unique(eve$year)[2]),
label = unique(eve$year)[2]),
list(method = "restyle",
args = list("transforms[0].value", unique(eve$year)[3]),
label = unique(eve$year)[3]),
list(method = "restyle",
args = list("transforms[0].value", unique(eve$year)[4]),
label = unique(eve$year)[4]),
list(method = "restyle",
args = list("transforms[0].value", unique(eve$year)[5]),
label = unique(eve$year)[5])
))))
fig

Toggle multiple annotations on and off with buttons using plotly

I used plotly to create a plot with multiple annotations. This is my code and it works as intended:
library(plotly)
a <- 1:10
b <- 1:10
data <- data.frame(a,b)
annotations <- list(x = a[3:4],
y = b[3:4],
text = c("p1","p2"),
showarrow = TRUE)
p <- plot_ly(data = data, x = ~a, y = ~b, type = "scatter", mode = "lines")
p <- layout(p, annotations = annotations)
p
But I can't figure out how to properly implement buttons to turn the annotations on and off. My code below implements buttons, but when I use them, only one arrow is shown, that is out of place and has no text.
library(plotly)
a <- 1:10
b <- 1:10
data <- data.frame(a,b)
annotations <- list(x = a[3:4],
y = b[3:4],
text = c("p1","p2"),
showarrow = TRUE)
updatemenus <- list(
list(
type= 'buttons',
buttons = list(
list(
label = "ON",
method = "update",
args = list(list(),
list(annotations = list(annotations)))),
list(
label = "OFF",
method = "update",
args = list(list(),
list(annotations = list(c()))))
)
)
)
p <- plot_ly(data = data, x = ~a, y = ~b, type = "scatter", mode = "lines")
p <- layout(p, updatemenus = updatemenus)
p
It would be nice if anyone could help me out here, or show me some alternative way to implement annotations that can be toggled.
You could use a for loop or lapply. For example:
library(plotly)
a <- 1:10
b <- 1:10
text <- LETTERS[seq(1,10)]
data <- data.frame(a,b,text)
annotations = list()
for (i in 1:length(data[,1])) {
annotation <- list(x = data$a[i],
y = data$b[i],
text = data$text[i],
showarrow = TRUE)
annotations[[i]] <- annotation
}
updatemenus <- list(
list(
type= 'buttons',
buttons = list(
list(
label = "ON",
method = "update",
args = list(list(),
list(annotations = annotations))),
list(
label = "OFF",
method = "update",
args = list(list(),
list(annotations = list(c()))))
)
)
)
p <- plot_ly(data = data, x = ~a, y = ~b, type = "scatter", mode = "lines")
p <- layout(p, annotations = annotations, updatemenus = updatemenus)
p

plotly: Updating data with dropdown selection

I am not sure if this is possible, but here is what I would like to do. I would like to update the data in a plotly plot by selecting from a dropdown menu.
As a simple example, let's assume I have a data frame
df <- data.frame(x = runif(200), y = runif(200), z = runif(200))
from which I use df$x and df$y in a scatter plot. Two scenarios of data manipulation I would like to achieve using a dropdown:
Replace df$y with df$z
Plot only the first n values of df$x and df$y
I looked at the following two examples, which I can easily reproduce:
https://plot.ly/r/dropdowns/
However, I have no idea how to pass the information regarding the data to be plotted based on the dropdown selection. For scenario 2 e.g. I have tried it with args = list("data", df[1:n,]) which did not work.
For scenario 1 the (only?) way to go (according to the examples) seems to be hiding/showing the traces respectively. Is that the only way for scenario 2 as well?
Any alternative ideas?
Update 1: Add reproducible example
So here is an example which achieve what I would like in scenario 1.
require(plotly)
df <- data.frame(x = runif(200), y = runif(200), z = runif(200))
Sys.setenv("plotly_username"="xxx") #actual credentials replaced
Sys.setenv("plotly_api_key"="xxx") #actual credentials replaced
p <- plot_ly(df, x = df$x, y = df$y, mode = "markers", name = "A", visible = T) %>%
add_trace(mode = "markers", y = df$z, name = "B", visible = T) %>%
layout(
title = "Drop down menus - Styling",
xaxis = list(domain = c(0.1, 1)),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
buttons = list(
list(method = "restyle",
args = list("visible", list(TRUE, TRUE)),
label = "Show All"),
list(method = "restyle",
args = list("visible", list(TRUE, FALSE)),
label = "Show A"),
list(method = "restyle",
args = list("visible", list(FALSE, TRUE)),
label = "Show B")))
))
plotly_POST(p)
Result here: https://plot.ly/~spietrzyk/96/drop-down-menus-styling/
This is based on the example from https://plot.ly/r/dropdowns/
However, I am wondering if one could pass the data to be plotted instead of triggering changes to the visible property of individual traces.
The one thing I tried was the following:
p <- plot_ly(df, x = df$x, y = df$y, mode = "markers", name = "A", visible = T) %>%
layout(
title = "Drop down menus - Styling",
xaxis = list(domain = c(0.1, 1)),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
buttons = list(
list(method = "restyle",
args = list("y", df$y),
label = "Show A"),
list(method = "restyle",
args = list("y", df$z),
label = "Show B")))
))
Result here: https://plot.ly/~spietrzyk/98/drop-down-menus-styling/
This approach cannot work, as the data from df$z is not posted to the grid (https://plot.ly/~spietrzyk/99/).
So I was wondering is there anyway to manipulate the data to be plotted based on dropdown selection, beyond plotting all traces and than switching the visible property by dropdown selections.
Is this what you were after?
require(plotly)
df <- data.frame(x = runif(200), y = runif(200), z = runif(200))
p <- plot_ly(df, x = ~x, y = ~y, mode = "markers", name = "A", visible = T) %>%
layout(
title = "Drop down menus - Styling",
xaxis = list(domain = c(0.1, 1)),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
buttons = list(
list(method = "restyle",
args = list("y", list(df$y)), # put it in a list
label = "Show A"),
list(method = "restyle",
args = list("y", list(df$z)), # put it in a list
label = "Show B")))
))
p
On the top of #jimmy G's answer.
You can automatically create the buttons so that you don't have to manually specify every single variable you want in the plot.
library(plotly)
df <- data.frame(x = runif(200), y = runif(200), z = runif(200), j = runif(200), k = rep(0.7, 200), i = rnorm(200,0.6,0.05))
create_buttons <- function(df, y_axis_var_names) {
lapply(
y_axis_var_names,
FUN = function(var_name, df) {
button <- list(
method = 'restyle',
args = list('y', list(df[, var_name])),
label = sprintf('Show %s', var_name)
)
},
df
)
}
y_axis_var_names <- c('y', 'z', 'j', 'k', 'i')
p <- plot_ly(df, x = ~x, y = ~y, mode = "markers", name = "A", visible = T) %>%
layout(
title = "Drop down menus - Styling",
xaxis = list(domain = c(0.1, 1)),
yaxis = list(title = "y"),
updatemenus = list(
list(
y = 0.7,
buttons = create_buttons(df, y_axis_var_names)
)
))
p
Hope you find it useful.

Resources