I've observed some strange behaviour of rhandsontable in a shiny app. In this simple example I am assigning a data.frame to a reactiveValues element if some event happens. The data is then shown in a rhandsontable. But when I change some entry of the table the function hot_to_r fails with: Error in seq.default: argument 'length.out' must be of length 1
Strangely the error only happens if I use iris, but not when I use iris[1:50, ], which should be identical. Does somebody have an idea, how to fix this?
(There is another error when values$data is still NULL before the actionButton is clicked. I'm aware of this, but this is not relevant for the question.)
library(shiny)
ui <- fluidPage(
actionButton("click", "click"),
rHandsontableOutput("table")
)
server <- function(input, output, session) {
values <- reactiveValues(data = NULL)
observeEvent(input$click, {
values$data <- iris # with iris[1:50, ] no error appears
})
output$table <- renderRHandsontable({
rhandsontable(t(values$data))
})
observe({
if (!is.null(input$table$changes$changes)) {
table_data <- hot_to_r(input$table)
print(table_data)
}
})
}
shinyApp(ui, server)
#BigDataScientist is on to something, colnames(t(iris)) is NULL, whereas colnames(t(iris[1:50,])) is not. That is a mystery to me, but preventing that nullness should resolve your problem. Using something in the call to rhandsontable should do the trick. Using
rhandsontable(data.frame(t(values$data)))
worked for me.
Related
Problem: I have the following sample app in which the user can make changes to an rhandsontable object. I want to check if the modifications that the user is doing is valid. Already implemented: If not valid, the cell color changes to dark red.
Question: Is there a possibility to check in R (not only visually) the whole rhandsontable if it contains of only valid inputs, i.e. some TRUE/FALSE flag that can be returned and is an attribute of rhandsontable object or some hidden option or so?
library(shiny)
library(rhandsontable)
ui <- fluidPage(
rHandsontableOutput("table")
)
server <- function(input, output, session) {
output$table <- renderRHandsontable(
rhandsontable(mtcars)
)
observe({
str(input$table)
})
}
shinyApp(ui, server)
You can read in the table with hot_to_r and then do the checks. In the example, if you change one cell to a character, the flag is set to FALSE. This is because a character is returned as NA in the input (I'm not sure why not the character is returned):
library(shiny)
library(rhandsontable)
ui <- fluidPage(
rHandsontableOutput("table"),
verbatimTextOutput("flag")
)
server <- function(input, output, session) {
# flag for numeric values
is_table_ok <- reactiveVal(FALSE)
output$table <- renderRHandsontable(
rhandsontable(mtcars)
)
observeEvent(input$table, {
table_object <- hot_to_r(input$table)
flag <- !is.na(table_object)
flag <- purrr::reduce(flag, `&&`)
is_table_ok(flag)
})
output$flag <- renderPrint({
is_table_ok()
})
}
shinyApp(ui, server)
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'm trying to get a reactiveValue that is depending on a reactive. In the real code (this is a very simplified version), I load a dataset interactively. It changes when pushing the buttons (prevBtn/nextBtn). I need to know the number of rows in the dataset, using this to plot the datapoints with different colors.
The question: Why can't I use the reactive ro() in the reactiveValues function?
For understanding: Why is the error saying "You tried to do something that can only be done from inside a reactive expression or observer.", although ro() is used inside a reactive context.
The error is definitely due to vals(), I already checked the rest.
The code :
library(shiny)
datasets <- list(mtcars, iris, PlantGrowth)
ui <- fluidPage(
mainPanel(
titlePanel("Simplified example"),
tableOutput("cars"),
actionButton("prevBtn", icon = icon("arrow-left"), ""),
actionButton("nextBtn", icon = icon("arrow-right"), ""),
verbatimTextOutput("rows")
)
)
server <- function(input, output) {
output$cars <- renderTable({
head(dat())
})
dat <- reactive({
if (is.null(rv$nr)) {
d <- mtcars
}
else{
d <- datasets[[rv$nr]]
}
})
rv <- reactiveValues(nr = 1)
set_nr <- function(direction) {
rv$nr <- rv$nr + direction
}
observeEvent(input$nextBtn, {
set_nr(1)
})
observeEvent(input$prevBtn, {
set_nr(-1)
})
ro <- reactive({
nrow(dat())
})
output$rows <- renderPrint({
print(paste(as.character(ro()), "rows"))
})
vals <- reactiveValues(needThisForLater = 30 * ro())
}
shinyApp(ui = ui, server = server)
Error in .getReactiveEnvironment()$currentContext() :
Operation not allowed without an active reactive context. (You tried to do something that can only be done from inside a reactive expression or observer.)
I think you want
vals <- reactiveValues(needThisForLater = reactive(30 * ro()))
Not everything in a reactiveValues list is assumed to be reactive. It's also a good place to store constant values. So since it's trying to evaluate the parameter you are passing at run time and you are not calling that line in a reactive environment, you get that error. So by just wrapping it in a call to reactive(), you provide a reactive environment for ro() to be called in.
For part of a Shiny application I am building, I need to have the user select a directory. The directory path is stored in a reactive variable. The directory can either be selected by the user from a file window or the path can be manually entered by textInput. I have figured out how to do this, but I don't understand why the solution I have works! A minimal example of the working app:
library(shiny)
ui <- fluidPage(
actionButton("button1", "First Button"),
textInput("inText", "Input Text"),
actionButton("button2", "Second Button"),
textOutput("outText"),
textOutput("outFiles")
)
server <- function(input, output) {
values <- reactiveValues(inDir = NULL)
observeEvent(input$button1, {values$inDir <- tcltk::tk_choose.dir()})
observeEvent(input$button2, {values$inDir <- input$inText})
inPath <- eventReactive(values$inDir, {values$inDir})
output$outText <- renderText(inPath())
fileList <- reactive(list.files(path=inPath()))
output$outFiles <- renderPrint(fileList())
}
shinyApp(ui, server)
The first thing I tried was to just use eventReactive and assign the two sources of input to the reactive variable:
server <- function(input, output) {
inPath <- eventReactive(input$button1, {tcltk::tk_choose.dir()})
inPath <- eventReactive(input$button2, {input$inText})
output$outText <- renderText(inPath())
fileList <- reactive(list.files(path=inPath()))
output$outFiles <- renderPrint(fileList())
}
The effect of this as far as I can tell is that only one of the buttons does anything. What I don't really understand is why this doesn't work. What I thought would happen is that the first button pushed would create inPath and then subsequent pushes would update the value and trigger updates to dependent values (here output$outText). What exactly is happening here then?
The second thing I tried, which was almost there, was based off of this answer:
server <- function(input, output) {
values <- reactiveValues(inDir = NULL)
observeEvent(input$button1, {values$inDir <- tcltk::tk_choose.dir()})
observeEvent(input$button2, {values$inDir <- input$inText})
inPath <- reactive({if(is.null(values$inDir)) return()
values$inDir})
output$outText <- renderText(inPath())
fileList <- reactive(list.files(path=inPath()))
output$outFiles <- renderPrint(fileList())
}
This works correctly except that it shows an "Error: invalid 'path' argument" message for list.files. I think this may mean that fileList is being evaluated with inPath = NULL. Why does this happen when I use reactive instead of eventReactive?
Thanks!
You could get rid of the inPath reactive and just use values$inDir instead.
With req() you'll wait until values are available. Otherwise you'll get the same error (invalid 'path' argument).
The reactive triggers right away, while the eventReactive will wait until the given event occurs and the eventReactive is called.
And if(is.null(values$inDir)) return() won't work correctly, as it will return NULL if values$inDir is NULL, which is then passed to list.files. And list.files(NULL) gives the error: invalid 'path' argument.
Replace it with req(values$inDir) and you won't get that error.
And your example with 2 inPath - eventReactive's won't work, as the first one will be overwritten by the second one, so input$button1 won't trigger anything.
library(shiny)
ui <- fluidPage(
actionButton("button1", "First Button"),
textInput("inText", "Input Text"),
actionButton("button2", "Second Button"),
textOutput("outText"),
textOutput("outFiles")
)
server <- function(input, output) {
values <- reactiveValues(inDir = NULL)
observeEvent(input$button1, {values$inDir <- tcltk::tk_choose.dir()})
observeEvent(input$button2, {values$inDir <- input$inText})
output$outText <- renderText(values$inDir)
fileList <- reactive({
req(values$inDir);
list.files(path=values$inDir)
})
output$outFiles <- renderPrint(fileList())
}
shinyApp(ui, server)
You could also use an eventReactive for button1 and an observeEvent for button2, but note that you need an extra observe({ inPath() }) to make it work. I prefer the above solution, as it is more clear what's happening and also less code.
server <- function(input, output) {
values <- reactiveValues(inDir = NULL)
inPath = eventReactive(input$button1, {values$inDir <- tcltk::tk_choose.dir()})
observe({
inPath()
})
observeEvent(input$button2, {values$inDir <- input$inText})
output$outText <- renderText(values$inDir)
fileList <- reactive({
req(values$inDir);
list.files(path=values$inDir)
})
output$outFiles <- renderPrint(fileList())
}
And to illustrate why if(is.null(values$inDir)) return() won't work, consider the following function:
test <- function() { if (TRUE) return() }
test()
Although the if-condition evaluates to TRUE, there is still gonna be a return value (in this case NULL), which will be passed on to the following functions and in your case list.files, which will cause the error.
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)
})
})