I am using the rhandsontable package in a Shiny app which should have the following functionality:
the data used in the calculation can be randomly generated, invoked by an actionButton (and when the app starts)
the data can be manually edited by the user via the handsontable object
after manual editing it should be possible to re-generate random data, invoking a new calculation
The following app does exactly that what I want, but I could not figure it out how to get rid of the global variable did_recalc. It is a minimal example, where the data consists of two numeric values which are summed up.
library(shiny)
library(rhandsontable)
did_recalc <- FALSE
ui <- fluidPage(
rHandsontableOutput('table'),
textOutput('result'),
actionButton("recalc", "generate new random vals and calculate")
)
server <- function(input,output,session)({
dataset_generator <- eventReactive(input$recalc, {
df <- as.data.frame(runif(2))
output$table <- renderRHandsontable({rhandsontable(df)})
did_recalc <<- TRUE
df
}, ignoreNULL = FALSE)
output$result <- renderText({
df <- dataset_generator()
if (!is.null(input$table) && !did_recalc)
df <- hot_to_r(input$table)
did_recalc <<- FALSE
sum(df)
})
})
shinyApp(ui = ui, server = server)
If I remove the !did_recalc condition within output$result <- ... then editing the table still invokes a (correct) calculation. But if "recalc" is pressed (after some manual editing was done), then the "recalc" button just generates new random values, but without recalculating the sum.
It seems to me, that input$table can just be changed by manual edits of the table object and does not care about new values given via renderRHandsontable. Hence I need this hack with the global variable, which allows me to track if the user just re-generated the data (causing that input$table is "outdated")
Has anybody an idea how to get the functionality of this example without the global variable?
You could store the data in a reactiveValues and have two observers updating it; one if the button is clicked, one if the table is edited by hand.
In your output$table and output$result, you then just need to use the data that is in the reactiveValues. Here's an example (same ui.R as you posted):
server <- function(input,output,session)({
values <- reactiveValues(data=as.data.frame(runif(2)))
observe({
input$recalc
values$data <- as.data.frame(runif(2))
})
observe({
if(!is.null(input$table))
values$data <- hot_to_r(input$table)
})
output$table <- renderRHandsontable({
rhandsontable(values$data)
})
output$result <- renderText({
sum(values$data)
})
})
Related
I have been going through most of the Q&As related to dataframe manipulation within Shiny and I still don't understand how to do something which, in my mind, should be very simple. I don't have experience writing Shiny apps and I'm still struggling with concepts like reactive events.
I have a dataframe A, loaded into R. I want to be able to see a specific value in a specific column in the dataframe in the UI and then edit it. After I edit the dataframe, I want to close the Shiny app and then see the edited dataframe in the Environment tab of RStudio. How do I go about doing this?
I think this might be a workable example.
Assume df is your data frame (I used iris to test, commented out below). Create a reactiveVal to hold your data, and use for editing with datatable. After editing, you can store the data back into your global environment dataframe df with <<-. An alternative is to do this when exiting the shiny app (such as through the onStop or session$onSessionEnded method).
library(shiny)
library(DT)
#df <- iris
ui <- fluidPage(
DT::dataTableOutput('data'),
)
server <- function(input, output) {
rv <- reactiveVal(df)
output$data <- DT::renderDataTable ({
DT::datatable(rv(), editable = TRUE)
})
observeEvent(input$data_cell_edit, {
info <- input$data_cell_edit
newdf <- rv()
newdf[info$row, info$col] <- info$value
rv(newdf)
df <<- rv()
})
}
shinyApp(ui = ui, server = server)
Alternative with replacing global df on exiting (requires session):
server <- function(input, output, session) {
rv <- reactiveVal(df)
output$data <- DT::renderDataTable ({
DT::datatable(rv(), editable = TRUE)
})
observeEvent(input$data_cell_edit, {
info <- input$data_cell_edit
newdf <- rv()
newdf[info$row, info$col] <- info$value
rv(newdf)
})
session$onSessionEnded(function() {
df <<- isolate(rv())
})
}
If you don't want to use reactive values, I suppose you could try the following. This can update your data.frame in the global environment as edits are made. Note that server = FALSE is added to handle changes in pages:
server <- function(input, output) {
output$data <- DT::renderDT (df, editable = TRUE, server = FALSE)
observeEvent(input$data_cell_edit, {
info <- input$data_cell_edit
df[info$row, info$col] <<- info$value
})
}
I am trying to get familiar with the rhandsontable package. So I tried something I thought should be pretty easy but I can't find a solution. Here is the idea:
I am creating a dataframe with random numbers and in a text box. The mean of column 1 of the dataframe should be displayed. Furthermore, that number should be updated as soon as I change the value of a cell in the dataframe.
My code:
ui <- fluidPage(
textOutput("num"),
rHandsontableOutput(outputId="frame")
)
server <- function(input, output, session) {
datavalue <- reactiveValues(data=df)
observeEvent(input$frame$changes$changes,{
mean_col1 <- mean(datavalue$data[[1]][1:10])
})
output$num <- renderText({
mean(datavalue$data[[1]][1:10])
})
output$frame <- renderRHandsontable({
rhandsontable(datavalue$data)
})
}
shinyApp(ui = ui, server = server)
I think you want to use hot_to_r to convert the handsontable to an R object when there is a change. You can update your reactiveValue datavalue$data when that happens, and your output$num will account for this change as well with the new mean.
Try using this in your observeEvent:
datavalue$data <- hot_to_r(input$frame)
As an alternative, you can do a general observe as follows:
observe({
req(input$frame)
datavalue$data <- hot_to_r(input$frame)
})
I have a problem with my code. I have 2 input files which I want to read with click of button and a numeric input which contains a filter value for the output of the table being created from the 2 files (after manipulating the data). The whole process (read files + create table + filter) right now is executed every time the user click the button. I want to do only the filter action if the input files doesn't change, because the process takes long time.
After the first click I want to do only the filtering command when the numeric input changes, unless the input files is also changed by the user.
The following code reproduces my problem:
library(shiny)
library(data.table)
server <- function(input, output, session) {
output$table1 <- renderDataTable({
input$gobtn
isolate({
infile1 <<- input$f1
infile2 <<- input$f2
if (is.null(infile1) || is.null(infile1)) {
return (NULL)
}
else {
calc()
}
})
})
calc <- function() {
inf1 <<- fread(infile1$datapath)
inf2 <<- fread(infile2$datapath)
# do some process with files data.....
my_table <- as.data.table(rbind(inf1, inf2))
setnames(my_table, c('name', 'rank'))
result <- my_table[rank > input$rank]
return(result)
}
}
ui <- basicPage(
fileInput("f1", "f1"),
fileInput("f2", "f2"),
numericInput("rank", "show rank only above :", value = 6),
actionButton("gobtn", "show"),
dataTableOutput('table1')
)
shinyApp(ui = ui, server = server)
The way to use reactivity is to break things into parts, so that you only need to update what is necessary. The first step in your pipeline is reading and processing the files. This seems like a good reactive: if they don't change, nothing happens, but when they change, everything that needs to be recalculated is recalculated. The next step is filtering, when the filter variable changes we want to refilter the data. Then we can just put that in the output.
server <- function(input, output, session) {
processedData <- reactive({
req(input$f1,input$f2)
inf1 <- fread(input$f1$datapath)
inf2 <- fread(input$f2$datapath)
# do some process with files data.....
my_table <- as.data.table(rbind(inf1, inf2))
setnames(my_table, c('name', 'rank'))
my_table
}
filteredData <- reactive({
req(input$rank)
processedData()[processedData()$rank > input$rank]
})
output$table1 <- renderDataTable({
input$gobtn
isolate({
filteredData()
})
})
}
In R Shiny, Is there a way of capturing a particular instance of reactive value so then that instance is totally unreactive?
So I'd have a table made up of reactive values and when the user hits the submit button those values are copied over to an un reactive table which I can then go on to manipulate etc.
So in the following code, the user enters their values into a table from rhandsontable package (which is awesome btw), and all I am trying to do is convert it to a basic data frame namely tabplot which should be unreactive so I can go ahead and do any type of operations on it.
library(shiny)
library(rhandsontable)
seq1 <- seq(1:6)
mat1 <- matrix(seq1, 2)
tabplot<-data.frame(car=numeric(2),num=numeric(2),truck=numeric(2))
did_recalc <- FALSE
ui <- fluidPage(
rHandsontableOutput('table'),
tableOutput('result'),
tableOutput('kl'),
textOutput('ca'),
actionButton("goButton","Confirm"),
actionButton("checkButton","Apply"),
br(),
actionButton("recalc", "Return to original values")
)
server <- function(input,output,session)({
tabplot<-data.frame(car=numeric(2),num=numeric(2),truck=numeric(2))
seq1 <- seq(1:6)
mat1 <- matrix(seq1, 2)
mat1<-data.frame(mat1)
#creates reactive values for the data frame
#obviously they have to be reactive values to function with the rhandsontable which is being continuously updated
#as the documentation says "values taken from the reactiveValues object are reactive but the object itself is not
values <- reactiveValues(data=mat1)
#if recalc --- which connects to an action button in the ui is hit, values goes back to original data frame
observe({
input$recalc
values$data<-mat1
})
#Where the magic happens
output$table <- renderRHandsontable({
rhandsontable(values$data,selectCallback = TRUE)
})
#this changes the handsontable format to an r object
observe({
if(!is.null(input$table))
values$data <-hot_to_r(input$table)
})
#Here we create a reactive function that creates a data frame of the rhandsontable output but it is a reactive function
fn<-reactive({
co<-data.frame((values$data))
return(co)
})
#Bit of testing, this demonstrates that the fn() is only updated after the button is pressed
output$result<-renderTable({
input$goButton
isolate({
fn()
})
})
isolate({
# tabplot<-reactive({ #Format co[desired row:length(colums)][desired column]
tabplot[1,1:3][1]<-fn()[1,1:3][1]
tabplot[1,1:3][2]<-fn()[1,1:3][2]
tabplot[1,1:3][3]<-fn()[1,1:3][3]
tabplot[2,1:3][1]<-fn()[2,1:3][1]
tabplot[2,1:3][2]<-fn()[2,1:3][2]
tabplot[2,1:3][3]<-fn()[2,1:3][3]
})
output$kl<-renderTable({
tabplot
})
observe({
input$goButton
output$ca<-renderText({
tabplot$car
cat('\nAccessing Subset with $:', tabplot$car)
cat('\nAccessing specific cell:',tabplot[1,3])
cat('\noperations on specific cell:',tabplot[1,3]*2)
})
})
})
shinyApp(ui = ui, server = server)
This might be what you want. It leverages the much scorned <<- operator, but it is what I do when I need to subvert the "lazy reactive" architecture of shiny.
Note I set a parallel dataframe tabplot1 and display it beneath where you display tabplot.
library(shiny)
library(rhandsontable)
seq1 <- seq(1:6)
mat1 <- matrix(seq1, 2)
tabplot<-data.frame(car=numeric(2),num=numeric(2),truck=numeric(2))
did_recalc <- FALSE
ui <- fluidPage(
rHandsontableOutput('table'),
tableOutput('result'),
tableOutput('kl'),
tableOutput('kl1'),
textOutput('ca'),
actionButton("goButton","Confirm"),
actionButton("checkButton","Apply"),
br(),
actionButton("recalc", "Return to original values")
)
server <- function(input,output,session)({
tabplot<-data.frame(car=numeric(2),num=numeric(2),truck=numeric(2))
tabplot1 <- tabplot
seq1 <- seq(1:6)
mat1 <- matrix(seq1, 2)
mat1<-data.frame(mat1)
#creates reactive values for the data frame
#obviously they have to be reactive values to function with the rhandsontable which is being continuously updated
#as the documentation says "values taken from the reactiveValues object are reactive but the object itself is not
values <- reactiveValues(data=mat1)
#if recalc --- which connects to an action button in the ui is hit, values goes back to original data frame
observe({
input$recalc
values$data<-mat1
})
#Where the magic happens
output$table <- renderRHandsontable({
rhandsontable(values$data,selectCallback = TRUE)
})
#this changes the handsontable format to an r object
observe({
if(!is.null(input$table))
values$data <-hot_to_r(input$table)
})
#Here we create a reactive function that creates a data frame of the rhandsontable output but it is a reactive function
fn<-reactive({
co<-data.frame((values$data))
return(co)
})
#Bit of testing, this demonstrates that the fn() is only updated after the button is pressed
output$result<-renderTable({
input$goButton
tabplot1 <<- data.frame(values$data)
colnames(tabplot1) <<- colnames(tabplot)
isolate({
fn()
})
})
isolate({
# tabplot<-reactive({ #Format co[desired row:length(colums)][desired column]
tabplot[1,1:3][1]<-fn()[1,1:3][1]
tabplot[1,1:3][2]<-fn()[1,1:3][2]
tabplot[1,1:3][3]<-fn()[1,1:3][3]
tabplot[2,1:3][1]<-fn()[2,1:3][1]
tabplot[2,1:3][2]<-fn()[2,1:3][2]
tabplot[2,1:3][3]<-fn()[2,1:3][3]
})
output$kl<-renderTable({
tabplot
})
output$kl1<-renderTable({
input$goButton
tabplot1
})
observe({
input$goButton
output$ca<-renderText({
tabplot$car
cat('\nAccessing Subset with $:', tabplot$car)
cat('\nAccessing specific cell:',tabplot[1,3])
cat('\noperations on specific cell:',tabplot[1,3]*2)
})
})
})
shinyApp(ui = ui, server = server)
Yielding:
I have a problem accessing individual values of a data frame that has been created from rhandsontable package. In the following code co[1] retrieves column 1 which is sensible, but then I try to access col[1,1] being row 1 column 1 produces missing value where TRUE/FALSE needed which is a bizarre error in this circumstance. I have tried accessing it with a list method namely co[[[1]][1] with no luck.
library(shiny)
library(rhandsontable)
did_recalc <- FALSE
ui <- fluidPage(
rHandsontableOutput('table'),
dataTableOutput('result'),
br(),
# tableOutput(),
actionButton("recalc", "Return to original values")
)
server <- function(input,output,session)({
a<-c(1,2,4,5,6,7)
b<-c(1,2,4,5,6,7)
c<-rbind(a,b)
df1<-data.frame(c)
#creates reactive values for the data frame
values <- reactiveValues(data=df1)
#if recalc --- which connects to an action button in the ui is hit, values goes back to original data frame
observe({
input$recalc
values$data <- df1
})
#this changes the handsontable format to an r object
observe({
if(!is.null(input$table))
values$data <- hot_to_r(input$table)
})
#important that you are using the values$data that have come from a reactive function
output$table <- renderRHandsontable({
rhandsontable(values$data)
})
fn<-reactive({
co<-data.frame((values$data))
#output$tst<-renderTable(co[3,2]*2) #So if I convert to a new data frame I can do operations on the columns
#Still need to be able to access individual cells
return(co)
})
output$result<-renderDataTable({
fn()
})
})
shinyApp(ui = ui, server = server)
I get the feeling that the object I am trying to access is not clean or structured in the way I am assuming it is.