R Shiny Handsontable automatically updating function after changing a single cell - r

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)
})

Related

Updating non-reactive values in R Shiny

I have the following example:
library(shiny)
ui <- fluidPage(
textOutput("out"),
actionButton("plusX", "Increase X"),
actionButton("redraw", "redraw")
)
server <- function(input, output, session) {
x <- 0
observeEvent(input$plusX, {x <<- x+1})
output$out <- renderText({
input$redraw
x
})
}
shinyApp(ui, server)
Is this considered an anti-pattern in Shiny to modify a non-reactive variable in this way? Obviating the super assignment which can be problematic by itself.
I know this could be done, for example with a reactiveVal to store X, and isolate to obtain a similar result. This second way seems clearer and that would be my usual choice, but I was wondering if there any caveats in the first one, or it is possible way of doing that.
library(shiny)
ui <- fluidPage(
textOutput("out"),
actionButton("plusX", "Increase X"),
actionButton("redraw", "redraw")
)
server <- function(input, output, session) {
x <- reactiveVal(0)
observeEvent(input$plusX, {x(x()+1)})
output$out <- renderText({
input$redraw
isolate(x())
})
}
shinyApp(ui, server)
In this example there is no important difference between both codes as you are not using the benefit of ReactiveVal.
The benefit of ReactiveVal is that it has a reactive nature and thus can interact with other reactive elements.
Try for example to add a table to your code that depends on x:
output$tab <- renderTable({data.frame(y = x)})
(x() in the case of ReactiveVal)
The difference you will see that in the case of ReactiveVal the table automatically updates with plusX whereas in the case of the regular variable it does not update.

Using Shiny to update dataframe values and access it in the local environment after Shiny session ends

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
})
}

Object not found R Shiny

I am trying to access the data frame created in one render function into another render function.
There are two server outputs, lvi and Category, in lvi I have created Data1 data frame and Category I have created Data2 dataframe. I want to select Data2 where Data1 ID is matching.
I am following the below steps to achieve my objective but I get error "Object Data1 not found".
My UI is
ui <- fluidPage(
# App title ----
titlePanel("Phase1"),
fluidPage(
column(4,
# Input: Select a file ----
fileInput("file1", "Import file1")
)
),
fluidPage(
column(4,
# Input: Select a file ----
fileInput("file2", "Import File2")
)
),
# Main panel for displaying outputs ----
mainPanel(
# Output: Data file ----
dataTableOutput("lvi"),
dataTableOutput("category")
)
)
My server code is
server <- function(input, output) {
output$lvi <- renderDataTable({
req(input$file1)
Data1 <- as.data.frame(read_excel(input$file1$datapath, sheet = "Sheet1"))
})
output$category <- renderDataTable({
req(input$file2)
Data2 <- as.data.frame(read_excel(input$file2$datapath, sheet = "Sheet1"))
Data2 <- Data2[,c(2,8)]
Data2 <- Data2[Data1$ID == "ID001",]
})
}
shinyApp(ui, server)
Once a reactive block is done executing, all elements within it go away, like a function. The only thing that survives is what is "returned" from that block, which is typically either the last expression in the block (or, when in a real function, something in return(...)). If you think of reactive (and observe) blocks as "functions", you may realize that the only thing that something outside of the function knows of what goes on inside the function is if the function explicitly returns it somehow.
With that in mind, the way you get to a frame inside one render/reactive block is to not calculate it inside that reactive block: instead, create that frame in its own data-reactive block and use it in both the render and the other render.
Try this (untested):
server <- function(input, output) {
Data1_rx <- eventReactive(input$file1, {
req(input$file1, file.exists(input$file1$datapath))
as.dataframe(read_excel(input$file1$datapath, sheet = "Sheet1"))
})
output$lvi <- renderDataTable({ req(Data1_rx()) })
output$category <- renderDataTable({
req(input$file2, file.exists(input$file2$datapath),
Data1_rx(), "ID" %in% names(Data1_rx()))
Data2 <- as.data.frame(read_excel(input$file2$datapath, sheet = "Sheet1"))
Data2 <- Data2[,c(2,8)]
Data2 <- Data2[Data1_rx()$ID == "ID001",]
})
}
shinyApp(ui, server)
But since we're already going down the road of "better design" and "best practices", let's break data2 out and the data2-filtered frame as well ... you may not be using it separately now, but it's often better to separate "loading/generate frames" from "rendering into something beautiful". That way, if you need to know something about the data you loaded, you don't have to (a) reload it elsewhere, inefficient; or (b) try to rip into the internals of the shiny DataTable object and get it manually. (Both are really bad ideas.)
So a slightly better solution might start with:
server <- function(input, output) {
Data1_rx <- eventReactive(input$file1, {
req(input$file1, file.exists(input$file1$datapath))
as.dataframe(read_excel(input$file1$datapath, sheet = "Sheet1"))
})
Data2_rx <- eventReactive(input$file2, {
req(input$file2, file.exists(input$file2$datapath))
dat <- as.dataframe(read_excel(input$file2$datapath, sheet = "Sheet1"))
dat[,c(2,8)]
})
Data12_rx <- reactive({
req(Data1_rx(), Data2_rx())
Data2_rx()[ Data1_rx()$ID == "ID001", ]
})
output$lvi <- renderDataTable({ req(Data1_rx()); })
output$category <- renderDataTable({ req(Data12_rx()); })
}
shinyApp(ui, server)
While this code is a little longer, it also groups "data loading/munging" together, and "render data into something beautiful" together. And if you need to look at early data or filtered data, it's all right there.
(Side note: one performance hit you might see from this is that you now have more copies of data floating around. As long you are not dealing with "large" data, this isn't a huge deal.)

Shiny rhandsontable that is reactive to itself

I am trying to create a shiny app that has a rhandsontable in it. I want rhandsontable to be able to update its values in one of its columns if the corresponding values in another column is selected/ checked. So far, I have been able to use reactive / observe events to change the output values between two objects but i am unable to wrap my head around it , i.e, how do i make once column of rhandsontable reactive to another column in the same table ?
Here is a simple example of what i am trying to build:
library(shiny)
library(rhandsontable)
ui <- fluidPage(
rHandsontableOutput('table')
)
server <- function(input,output,session)({
data <- data.frame(c1=c(5,10,15), c2=c(3,6,9) , diff=c(0,0,0), select= as.logical( c(FALSE,FALSE,FALSE)))
output$table <- renderRHandsontable({
rhandsontable(data)
})
})
shinyApp(ui = ui, server = server)
So if i check the column 'Select', column 'diff' should produce the difference between column c1 & c2
From what I understand, your goal is to do some calculation depending on the values of some other column. So if for example a box of the third column is checked, you might want to compute the difference between elements of column 1 and 2.
If you had just a data frame, that would be easy, wouldn't it? Well, this is possible using reactive values. The main idea is that you can store the rhandsontable in a data frame in the backend, modify the data frame and then render the modified data frame once again back in the handsontable.
I hope this helps:
For a more detailed example on reactive values you can see
this: http://stla.github.io/stlapblog/posts/shiny_editTable.html
and this : https://www.youtube.com/watch?v=BzE1JmC0F6Q&list=PL6wLL_RojB5wXR3NR3K38sIvexZ_45alY&index=3
library(rhandsontable)
library(shiny)
ui <- fluidPage(
mainPanel(
rHandsontableOutput("hot")
)
)
server = function(input, output, session){
df<- data.frame(c1=c(5,10,15), c2=c(3,6,9) , diff=c(0,0,0), select= as.logical( c(FALSE,FALSE,FALSE)))
values <- reactiveValues(data = df)
observe({
if(!is.null(input$hot)){
values$data <- as.data.frame(hot_to_r(input$hot))
isolate(values$data[,'diff'] <- ifelse(values$data[,'select'], values$data[,'c1']-values$data[,'c2'] ,0))
print(values$data)
output$hot <- renderRHandsontable({
rhandsontable(values$data)
})
}
})
output$hot <- renderRHandsontable({
rhandsontable(values$data)
})
}
shinyApp(ui, server)

Update handsontable by editing table and/or eventReactive

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)
})
})

Resources