How to grab selected event data from a ggplotly chart? - r
I'm aware of https://plot.ly/r/shinyapp-plotly-events/ and have been using it as a guide. But the ggplot element I'm converting to plotly is the output from the fviz_dend function of the factoextra package. Here's a minimum shiny app example I'm working with:
library(factoextra)
library(plotly)
library(shiny)
library(DT)
ui <- fluidPage(
plotlyOutput("ggp"),
verbatimTextOutput("selected_points"),
DT::dataTableOutput("filtered_table")
)
server <- function(input, output, session) {
## ggplot output
fviz <- fviz_dend(
x = hclust(dist(mtcars)),
k = 5,
show_labels = TRUE,
type = "phylogenic",
phylo_layout = "layout_as_tree",
color_labels_by_k = TRUE,
palette = "igv"
)
## convert to ggplotly
ggfviz <- ggplotly(fviz)
## add keys
for (i in seq(7, 11)) {
ggfviz[["x"]][["data"]][[i-5]][["key"]] <-
as.character(ggfviz[["x"]][["data"]][[i]][["text"]])
}
output$ggp <- renderPlotly({
ggfviz
})
output$selected_points <- renderPrint({
event_data("plotly_selected")[5]
})
output$filtered_table <- DT::renderDataTable(
mtcars[which(rownames(mtcars) == event_data("plotly_selected")[5]), ],
)
}
shinyApp(ui, server)
So I'm trying to use the key accessed with event_data("plotly_selected")[5] in order to filter the data table, and while event_data("plotly_selected")[5] does show the key per output$selected_points, it is somehow not passed to the datatable filter.
It looks like event_data will return a data frame with multiple rows. Instead of filtering with == you will need %in% instead to see which multiple cars are contained within the multiple possible selections from plotly_selected. In addition, even though you subset by column 5, you still have a data frame, and need to include the column key only for filtering (containing a vector of cars). This should work:
mtcars[which(rownames(mtcars) %in% event_data("plotly_selected")$key), ]
Or
mtcars[which(rownames(mtcars) %in% event_data("plotly_selected")[["key"]]), ]
Related
Update variable created by eventReactive in another observeEvent
I'm struggling to update a reactive variable, that is created with eventReactive(), in an observeEvent() with new data. The background is following: I have a data.frame df with some variables (x and y) and number of observations depending on the selected city (created randomly for this example). x and y are initialized with zeros. Because I need to further process df, I pass df to city_df in an eventReactive(). So far, so good. Next, I want to add new data to city_df. The computation of this new data is dependent on the "compute" actionButton (input$compute), wherefore I update city_df in an observeEvent(). I manage to read the data stored in city_df, but I am struggling to overwrite its content. Actually, I am a bit unsure if this is possible at all, but I hope that some of you could give me a hint on how to update the reactive variable city_df with the new data in this observeEvent() and have its output evaluated in the app(?). library(shiny) # global variables cities <- c("Nairobi", "Kansas", "Uppsala", "Sangon", "Auckland", "Temuco") # ui ui <- fluidPage( fluidPage( fluidRow( column(2, selectInput("city", "Select city", choices = cities, selected = sample(cities, size = 1) ), actionButton("compute", "Compute")), column(8, verbatimTextOutput("the_city")) )) ) # server server <- function(input, output, session) { # create variable city_df <- eventReactive(input$city, { len <- round(runif(1, 20, 50), 0) df <- data.frame(city = rep(input$city, len)) # initialize x and y with zeros df <- cbind(df, data.frame(x = rep.int(0, len), y = rep.int(0, len))) }) output$the_city <- renderText({ paste(city_df()) }) observeEvent(input$compute, { # grab data test <- city_df() # compute new data test$x <- runif(dim(test)[1], 11, 12) test$y <- runif(dim(test)[1], 100, 1000) # and how to send this values back to city_df? }) } # run app shinyApp(ui, server) The actual app is far more complex--so forgive me if this MWE app seems a bit overly complicated to achieve this usually simple task (I hope I managed to represent the more complex case in the MWE). Instead of a data.frame, I am parsing layers of a GeoPackage and append some variables initialized with zeros. The selected layer is displayed in a Leaflet map. On pressing the "compute" button, a function computes new data that I wish to add to the layer to then have it displayed on the map. The alternative solution I have on mind is to write the new data to the GeoPackage and then, reread the layer. However, I would appreciate if I could avoid this detour as loading the layer takes some time... Many thanks :)
Rather than using an eventReactive, if you use a proper reactiveVal, then you can change the value whenever you like. Here's what that would look like server <- function(input, output, session) { # create variable city_df <- reactiveVal(NULL) observeEvent(input$city, { len <- round(runif(1, 20, 50), 0) df <- data.frame(city = rep(input$city, len)) # initialize x and y with zeros df <- cbind(df, data.frame(x = rep.int(0, len), y = rep.int(0, len))) city_df(df) }) output$the_city <- renderText({ paste(city_df()) }) observeEvent(input$compute, { # grab data test <- city_df() test$x <- runif(dim(test)[1], 11, 12) test$y <- runif(dim(test)[1], 100, 1000) city_df(test) }) } So calling city_df() get the current value and calling city_df(newval) updates the variable with a new value. We just swap out the eventReactive with observeEvent and do the updating ourselves.
data tibble empties on shiny
I am having trouble sending a tibble loaded from a user file to a plotting function. It seems that the table is indeed read and modified properly, but when I ask another function to use it for plotting the entries are missing. on the Shiny server I have the following: myData <- reactive({ if (is.null(inFile())) { return(NULL) } else { tmp_table = read_csv(inFile()$datapath[1]) # tmp_table = read_csv('Fazael_grain_size.csv') big_table=tmp_table # modify the factor columns into factors big_table$sample_name <- factor(big_table$sample_name) big_table$site <- factor(big_table$site) big_table$fraction <- factor(big_table$fraction) # remove NA rows big_table=big_table[!is.na(big_table$sample_name),] # make sure the table was loaded correctly output$table <- renderTable((big_table)) return(big_table) } }) a screenshot showing the proper loading of the table then I want to use this table for plotting, so I have the following chunk: myPlot <- function(){ # make sure the data exists req(myData()) big_table=myData() # checking the tibble was properly sent to the plotting function output$table <- renderTable(big_table) # filter the data based on user selection big_table = filter(big_table,sample_name %in% input$selected_sample_name) big_table = filter(big_table,site %in% input$selected_site) big_table = filter(big_table,fraction %in% input$fraction) p = big_table %>% ggplot(aes_string(x=colnames(big_table)[4],y=colnames(big_table)[4])) + geom_point() if (!(input$color_variable %in% c("none"))) { p = p + geom_point(aes_string(color=input$color_variable)) } p } for some reason the plot remains empty, and after some debugging I found out that the tibble is forwarded empty to it. any answers? a screenshot demonstrating that the entries in the table are not forwarded into the plotting function and indeed, the plot doesn't show up: output$plot1 <- renderPlot({ myPlot() }) no plot appears on the plotting area
solved. there was a small bug in the filter function: should have been: big_table = filter(big_table,fraction %in% input$selected_fraction)
Updating a data frame in real time in RShiny
I am trying to get my head around RShiny by building what I thought would be a pretty simple but useful app. What I would like the app to do is allow the user to input some data made up of dates, numeric, and characters. Then when the user presses a save/submit button this data is appended onto a pre-existing data frame made up of previous recordings and over write the .csv of these recordings. I would also like this data to be presented to the users in the form of a table in the UI which is updated as soon as the user presses the save/submit button. I have managed to get the majority of the UI features working, however, I am having real difficulty 1) saving the data in the correct format and 2) updating the table displayed on the UI. My current method of saving the data involves creating an isolated list of the input values and rbinding this to the original data frame. However, the formats of the input values appear to all revert to factors which is especially problematic with the date as the output is meaningless as far as I am aware. In terms of updating the UI I have attempted to create a reactive object out of the data frame and use this object as the data displayed in renderDataTable but this approach seems to have no affect. I have created a dummy minimal example below. Thank you for all your help in advance. require(shiny) require(tidyverse) require(lubridate) require(plotly) #Would import the data in reality using read.csv() but to allow for an easily #recreated example I made a dummy data frame DateRecorded <- dmy(c("10/07/2018", "11/07/2018", "13/07/2018")) Value <- c(1, 2, 3) Person <- c("Bob", "Amy", "Charlotte") df <- data.frame(DateRecorded, Value, Person) ui <- fluidPage( #UI Inputs dateInput(inputId = "SessionDate", label = "Date Recorded", format = "dd-mm-yyyy"), numericInput(inputId = "SessionValue", label = "Value Recorded", value = 0), textInput(inputId = "SessionPerson", label = "Person Recording"), actionButton(inputId = "Save", label = "Save"), #UI Outputs dataTableOutput("TheData"), textOutput("TotRecorded") ) # Define server logic required to draw a histogram server <- function(input, output) { #When "Save" is pressed should append data to df and export observeEvent(input$Save, { newLine <- isolate(c(input$SessionDate, input$SessionValue, input$SessionPerson)) isolate(df <- rbind(as.matrix(df), unlist(newLine))) write.csv(df, "ExampleDataFrame.csv") #This export works but the date is saved incorrectly as "17729" not sure why }) #Create a reactive dataset to allow for easy updating ReactiveDf <- reactive({ df }) #Create the table of all the data output$TheData <- renderDataTable({ ReactiveDf() }) #Create the totals print outs output$TotRecorded <- renderPrint({ data <- ReactiveDf() cat(nrow(data)) }) } # Run the application shinyApp(ui = ui, server = server)
I made some small tweaks. You do not need isolate in the body of the observeEvent; it does not take a reactive dependency to values in its body. I made ReactiveDf a reactiveVal instead of a reactive. This allows you to write its value from inside an observeEvent. Instead of rowbinding a matrix and unlisting a list - the issue is that all the new values are parsed to the same class, while they are obviously not - it might be easier to rbind two data.frames, so create the newLine with newLine <- data.frame(DateRecorded = input$SessionDate, Value = input$SessionValue, Person = input$SessionPerson) So a working example would be as shown below. Hope this helps! require(shiny) require(tidyverse) require(lubridate) require(plotly) #Would import the data in reality using read.csv() but to allow for an easily #recreated example I made a dummy data frame DateRecorded <- dmy(c("10/07/2018", "11/07/2018", "13/07/2018")) Value <- c(1, 2, 3) Person <- c("Bob", "Amy", "Charlotte") df <- data.frame(DateRecorded, Value, Person) ui <- fluidPage( #UI Inputs dateInput(inputId = "SessionDate", label = "Date Recorded", format = "dd-mm-yyyy"), numericInput(inputId = "SessionValue", label = "Value Recorded", value = 0), textInput(inputId = "SessionPerson", label = "Person Recording"), actionButton(inputId = "Save", label = "Save"), #UI Outputs dataTableOutput("TheData"), textOutput("TotRecorded") ) # Define server logic required to draw a histogram server <- function(input, output) { #When "Save" is pressed should append data to df and export observeEvent(input$Save, { newLine <- data.frame(DateRecorded = input$SessionDate, Value = input$SessionValue, Person = input$SessionPerson) df <- rbind(df, newLine) ReactiveDf(df) # set reactiveVal's value. write.csv(df, "ExampleDataFrame.csv") #This export works but the date is saved incorrectly as "17729" not sure why }) #Create a reactive dataset to allow for easy updating ReactiveDf <- reactiveVal(df) #Create the table of all the data output$TheData <- renderDataTable({ ReactiveDf() }) #Create the totals print outs output$TotRecorded <- renderPrint({ data <- ReactiveDf() cat(nrow(data)) }) } # Run the application shinyApp(ui = ui, server = server)
Filtering reactive data in an R Shiny App
I have a dataframe that has these columns: document, user, month, views I am using a selectInput to filter the data by document. I want to plot a (Plotly) line chart of views per month, for each user, for the selected document. E.g. If one filters to a document for which ten users exist, I want to display ten plots, each showing the relevant user's views per month. At current: - I filter the data to the selected document (dplyr). - I pass the filtered data to a function. - In the function, I loop through the current document's users. - In each loop, I filter the data to the current user (dplyr), and append a Plotly output to a output list. - At the end of the function, I return the output list. - The result of the function is assigne to a UI output. The app successfully runs, but where the plots should display, I get a Result must have length x, not y error. How would you go about this? I appreciate any advice you can give me. For security reasons I cannot share my existing code, sorry - I understand that it's not very useful. Edit: I've created a minimal reproducible example, based on this. The process has changed slightly from my original question, mainly that I'm not using a separate function. library(plotly) library(tidyverse) # DATA data <- data.frame( document= c("doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc1","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2","doc2"), user= c("user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user3","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user1","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user2","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4","user4"), month= as.Date(c("2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01","2017-01-01","2017-02-01","2017-03-01","2017-04-01","2017-05-01","2017-06-01","2017-07-01","2017-08-01","2017-09-01","2017-10-01","2017-11-01","2017-12-01","2018-01-01","2018-02-01","2018-03-01","2018-04-01","2018-05-01","2018-06-01","2018-07-01")), views= c(19,39,34,3,25,5,1,16,37,21,46,34,23,0,8,10,46,3,47,16,32,4,44,42,12,8,27,39,28,30,26,45,49,38,32,32,1,16,23,34,41,46,37,0,23,44,10,3,43,43,22,38,1,33,11,15,8,21,37,17,7,29,1,33,47,45,37,20,9,41,37,18,30,46,24,45,48,42,49,3,10,17,46,6,12,29,13,6,4,44,37,26,43,5,19,28,44,20,35,40,32,20,41,46,25,47,35,3,25,25,41,5,26,32) ) # SERVER server <- shinyServer(function(input, output) { output$plots <- renderUI({ doc_data <- filter(data, document == input$select_doc) # This is the breaking line plot_output_list <- lapply(1:length(unique(doc_data$user)), function(i) { plotname <- paste("plot", i, sep="") plotlyOutput(plotname) }) do.call(tagList, plot_output_list) }) for (i in 1:length(unique(doc_data$user))) { local({ local_i <- i doc_users <- unique(doc_data$user) plotname <- paste("plot", local_i, sep="") plot_data <- filter(doc_data, user == doc_users[local_i]) %>% arrange(month) output[[plotname]] <- renderPlotly({ p <- plot_ly(x= plot_data$month, y= plot_data$views, type = 'scatter', mode = 'lines') p$elementId <- NULL p }) }) } }) # UI ui <- shinyUI(pageWithSidebar( headerPanel("Minimum reproducible example"), sidebarPanel( selectInput("select_doc", choices= unique(data$document), label="", selected= 'doc1')#, ), mainPanel( uiOutput("plots") ) )) # RUN shinyApp(ui, server)
Creating baseline comparison data in shiny R - Duplicating inputted dataframe
The aim of this exercise is to allow users to compare two different models based on their inputs. To do this, I have created an action button that asks users to specify their base model, and a reset button that takes the dataset back to before the baseline was added. The "base" logical determines whether the user wishes to include the base or not. Once the add baseline actionbutton is clicked, the current state of the data.frame is saved and grouping variable is renamed with "baseline" added before it (using paste). Users can select a different model which renders in comparison to this static base. For some reason, I cannot get the observe event to change the dataset. The observe event creates the baseline dataset fine (tested with print() ), however, the if() function does not alter "data" and therefore stops the base added to the ggplot. The code is written like this for two reasons. 1) by including the if() function after the observe event, any further changes to data only changes "data", it then gets added to the unchanged baseline data. 2) Also allows for the creation of the reset button which simply resets the data.frame to before the rbinding took place. This small issue has infuriated me and I cannot see where I am going wrong. Cheers in advance for any help people can provide. There are simplier ways to do this (open to suggestions), however, the iris data is only an example of the function, and the actual version is more complex. library("ggplot2") if (interactive()) { ui <- fluidPage( selectInput("rows", label = h3("Choose your species"), choices = list("setosa", "versicolor", "virginica") ), actionButton("base", "Create baseline"), actionButton("reset", "Reset baseline"), plotOutput(outputId = "plot") ) # close fluid page server <- function(input, output) { output$plot <- renderPlot({ # create plot base <- "no" # create baseline indicator which we can change once the observeevent below is changed data <- iris data <- iris[which(data$Species == input$rows),] # Get datasubset based on user input observeEvent(input$base, { # If base is Pressed, run code below: baseline <- data # Make Baseline Data by duplicating the users' specification baseline$Species <- paste("Baseline", data$Species, sep = "_") # Rename the grouping variable to add Baseline B4 it base <- "yes" # Change our indicator of whether a baseline had been made to yes }) # Close observe Event observeEvent(input$reset, { base <- "no" # This is placed before the rbind so that if we want to reset it will stop the merging of the two dataframes before it happens. }) if (base == "yes") { data <- rbind(data, baseline) # Run once the observe event has changed baseline to yes.This is kept seperatel that way any subsequent changes to data will not effect # the final data. This command will simple add the base onto the changed "data" before plotting } observeEvent(input$reset, { base <- "no" }) ggplot(data, aes(x=Petal.Width, y = as.numeric(Sepal.Width), colour = Species)) + # variable = each dataset selected, value = respective values for that model labs(x="Hypothetical X", y="Hypothetical X") + geom_line() }) # Close Render Plot } # Close Serve Function shinyApp(ui, server) } EXAMPLE TWO WITH REACTIVE OBJECT library(shiny) library(ggplot2) library("tidyr") library("dplyr") library("data.table") # Lets make a fake dataset called "Data". Has 4 variable options and the Ages each data point relates to. Ages <- 1:750 Variable1 <- rnorm(n=750, sd = 2, mean = 0) Variable2 <- rnorm(n=750, sd = 1, mean = 2) Variable3 <- rnorm(n=750, sd = 8, mean = 6) Variable4 <- rnorm(n=750, sd = 3, mean = 3) Data <- as.data.frame(cbind(Ages, Variable1, Variable2, Variable3, Variable4) ) ### UI ui <- fluidPage( checkboxGroupInput(inputId = "columns", label = h4("Which Variables would you like in your model?"), # Input Checkbox choices = c("Variable1", "Variable2", "Variable3", "Variable4")), plotOutput(outputId = "plot"), # Lets have our plot actionButton("base", "Create baseline"), # Baseline action actionButton("reset", "Reset baseline") # Reset Action ) # Close UI server <- function(input, output) { output$plot <- renderPlot({ validate(need(!is.null(input$columns), 'Please tick a box to show a plot.')) # Place a please choose columns for null input data <- gather(select(Data, "Ages", input$columns), variable, value, - Ages) ## Just doing a little data manipulation to change from wide to long form. This allows for calculations down the track and easier plotting # Now we can modify the data in some way, for example adding 1. Will eventually add lots of model modifications here. data$value <- data$value + 1 rVals <- reactiveValues() # Now we create the reactive values object rVals[['data']] <- data # Making a reactive values function. Place Data as "data". observeEvent(input$base,{ baseline <- data baseline$variable <- paste("Baseline", baseline$variable, sep = "_") # Rename Variables to Baseline preamble rVals[['baseline']] <- baseline # Put the new data into the reactive object under "baseline" }) observeEvent(input$reset,{ # Reset button will wipe the data rVals[['baseline']] <- NULL }) if(!is.null(rVals[['baseline']])) # if a baseline has been . created, then {rVals[['final']] <- bind_rows(rVals[['data']], rVals[['baseline']]) # Here we can simply bind the two datasets together if Baseline exists } else {rVals[['final']] <- rVals[['data']]} # Otherwise we can use keep it as it is ## Make our Plot ! ggplot(rVals[['final']], aes(x=Ages, y = as.numeric(value), colour = variable)) + # variable = each dataset selected, value = respective values for that model labs(x="Age", y="value") + geom_line() }) ## Close the render plot } ## Close the server shinyApp(ui, server)
You have observer inside reactive expression, i have seen this causing problems on number of occasions when i was correcting shiny code. Create reactive expression (your plot function) and observers only to specify which is the baseline value of species (character string) then feed this to filtering data inside the plot function: library(shiny) library(ggplot2) ui <- fluidPage( selectInput("rows", label = h3("Choose your species"), choices = list("setosa", "versicolor", "virginica") ), actionButton("base", "Create baseline"), actionButton("reset", "Reset baseline"), plotOutput(outputId = "plot") ) # close fluid page server <- function(input, output) { rVals = reactiveValues() rVals[['data']] = iris rVals[['baseline']] = NULL output$plot <- renderPlot({ # here we duplicate table to manipulate it before rendering # the reason for duplicate is that you dont want to affect your # base data as it may be used elsewhere # note that due to R's copy-on-write this may be expensive operation and # have impact on app performance # in all cases using data.table package is recommended to mitigate # some of the CoW implications render.data = rVals[['data']][rVals[['data']][['Species']] %in% c(rVals[['baseline']],input$rows),] # here manipulate render.data # and then continue with plot ggplot(data=render.data, aes(x=Petal.Width, y = as.numeric(Sepal.Width), colour = Species,group=Species) ) + labs(x="Hypothetical X", y="Hypothetical X") + geom_line() }) observeEvent(input$base,{ rVals[['baseline']]=input$rows }) observeEvent(input$reset,{ rVals[['baseline']]=NULL }) } shinyApp(ui, server)