I'm trying to make an app where users can edit some tables and run a calculation, and using DT. Is there a way to just read in what's currently in a DT table? This would simplify things a lot for me. All the solutions I've been able to find involve detecting when the table is edited, and then updating the data accordingly. This seems clunky and also might cause problems for my use case later.
Here's an example: after editing the data zTable, I'd like something that just returns what is now in zTable after clicking the calculate button aside from just watching every edit and updating z$data.
library(shiny)
library(DT)
ui <- fluidPage(
DT::dataTableOutput("zTable"),
actionButton("calcButton","Calculate!")
)
server <- function(input, output) {
z<-reactiveValues(data={data.frame(x=c(0,1),
y=c(0,1))
})
output$zTable <- DT::renderDT(z$data,editable=T)
observeEvent(input$calcButton,{
print(z$data)
})
observeEvent(input$zTable_cell_edit, {
info = input$zTable_cell_edit
z$data[as.numeric(info$row),as.numeric(info$col)] <- as.numeric(info$value)
})
}
shinyApp(ui = ui, server = server)
You can do as follows. There's a problem with the current version of DT: when you edit a numeric cell, the new value is stored as a string instead of a number. I've just done a pull request which fixes this issue. With the next version of DT the .map(Number) in the JavaScript callback will not be needed anymore. If you are ok to adopt my solution, tell me if you want to use it with non-numeric cells, and I'll have to improve the code in order to handle this situation. Or you can install my fork of DT in which I fixed the issue: remotes::install_github("stla/DT#numericvalue").
library(shiny)
library(DT)
callback <- c(
'$("#show").on("click", function(){',
' var headers = Array.from(table.columns().header()).map(x => x.innerText);',
' var arrayOfColumns = Array.from(table.columns().data());',
' var rownames = arrayOfColumns[0]',
' headers.shift(); arrayOfColumns.shift();',
' var entries = headers.map((h, i) => [h, arrayOfColumns[i].map(Number)]);',
' var columns = Object.fromEntries(entries);',
' Shiny.setInputValue(',
' "tabledata", {rownames: rownames, columns: columns}, {priority: "event"}',
' );',
'});'
)
ui <- fluidPage(
br(),
DTOutput("dtable"),
br(),
tags$h3("Edit a cell and click"),
actionButton("show", "Print data")
)
server <- function(input, output) {
dat <- data.frame(x=c(0,1),
y=c(0,1))
output[["dtable"]] <- renderDT({
datatable(
dat,
editable = TRUE,
callback = JS(callback)
)
}, server = FALSE)
observeEvent(input[["tabledata"]], {
columns <- lapply(input[["tabledata"]][["columns"]], unlist)
df <- as.data.frame(columns)
rownames(df) <- input[["tabledata"]][["rownames"]]
print(df)
})
}
shinyApp(ui = ui, server = server)
You can rely on JavaScript to get the data via the DataTable api:
library(shiny)
library(DT)
library(shinyjs)
ui <- fluidPage(
useShinyjs(),
DT::dataTableOutput("zTable"),
actionButton("calcButton","Calculate!")
)
server <- function(input, output) {
z <- reactiveValues(data = data.frame(x = 0:1, y = 0:1))
output$zTable <- renderDT(z$data, editable = TRUE)
observeEvent(input$calcButton, {
runjs('Shiny.setInputValue("mydata", $("#zTable table").DataTable().rows().data())')
})
observeEvent(input$mydata, {
dat <- req(input$mydata)
## remove chunk from .data()
dat[c("length", "selector", "ajax", "context")] <- NULL
print(do.call(rbind, dat))
})
}
shinyApp(ui = ui, server = server)
However, as you need to some data wrangling to back-transfrom the data, I am not sure whether this is eventually such a good idea.
What is your general issue with the _cell_edit approach? (which I would prefer because no need to additional data wrangling other than storing it in the right spot?
Related
I would like to run a function that has a shiny app inside, but I can't.
Running this example separately, I first remove column one from my input data frame; then I run shiny to change whatever is necessary in the data frame and, when I close the window, a new object is saved with the changes; and finally I create a new column in the data frame.
This is an example script, but I would like that, when executing the function, the shiny window opens and some things are changed in the data frame for the user interactively. Could someone help?
library(shiny)
library(rhandsontable)
my_function <- function(x){
select <- x[,-1]
ui <- fluidPage(
fluidRow(
column(
width = 12,
rHandsontableOutput("myTable")
)))
server <- function(input, output, session) {
# dummy dataframe
df = select
# convert it to a "rhansontable" object
output$myTable <- renderRHandsontable({rhandsontable(df)
})
observeEvent(input$myTable, {
test_df = hot_to_r(input$myTable)
assign('my_data_frame',test_df,envir=.GlobalEnv)
# browser() # uncomment for debugging
})
}
shinyApp(ui, server)
my_data_frame2 <- my_data_frame %>%
mutate(new_column_test = "hello")
return(my_data_frame2)
}
my_function(mtcars)
Hi you almost made it you don't want to return anything but add the data simply using assign
library(shiny)
library(rhandsontable)
myapp_function <- function(data) {
ui <- basicPage(
actionButton("quit", label = "Close"),
actionButton("create", label = "Create copy"),
textInput("name","Set dataframe name", value = "my_data_frame"),
rHandsontableOutput("myTable")
)
server <- function(input, output, session) {
output$myTable <- renderRHandsontable({
rhandsontable(data)
})
observeEvent(input$create, {
assign( input$name, hot_to_r(input$myTable), envir=.GlobalEnv)
})
observeEvent(input$quit,{
stopApp()
})
}
## launch app
shinyApp(ui, server,options=c(shiny.launch.browser = .rs.invokeShinyPaneViewer))
}
## test
myapp_function(iris)
myapp_function(mtcars)
myapp_function(PlantGrowth)
I would suggest to create the ui and server outside of the myapp_function - otherwise it will become a very large function...also creating a function inside another function is not the best practise.
i have a question regarding Shiny and the usage of Data frames.
I think i understood that i need to create isolated or reactive environmentes to interact with, but if i try to work with the Dataframe i get an error message:
Error in pfData: konnte Funktion "pfData" nicht finden
i tried to manipulate the dataframe by this code:
server <- function(input, output) {
observeEvent(input$go,
{
pf_name <- reactive({input$pfID})
pf_date <- reactive({input$pfDate})
if (pf_name()!="please select a PF") {
pfData <- reactive(read.csv(file =paste(pf_name(),".csv",sep=""),sep=";",dec=","))
MDur <- pfData()[1,15]
pfData <- pfData()[3:nrow(pfData()),]
Total = sum(pfData()$Eco.Exp...Value.long)
}
})
}
If i manipulate my Dataframe in the console it works just fine:
pfData <- pfData[3:nrow(pfData),]
Total = sum(pfData$Eco.Exp...Value.long)
Assets = sum(as.numeric(gsub(",",".",gsub(".","",pfData$Value,fixed=TRUE),fixed=TRUE)))
pfData$Exposure <- with(pfData, Eco.Exp...Value.long /Total)
can you help me?
Edit:
library(shiny)
ui <- fluidPage(
fluidRow(
column(6, offset =3,
wellPanel(
"Choose Mandate and Date",
fluidRow(
column(4,selectInput("pfID",label = "",
choices = list("please select a PF","GF25",
"FPM"),
selected = "please select a PF") ),
column(4, dateInput("pfDate",label="",value = Sys.Date()) ),
column(2, actionButton("go","Submit")),column(2,textOutput("selected_var"))
)
)
)
)
)
# Define server logic ----
server <- function(input, output) {
pfDataReactive <- reactive({
input$go
if (pf_name()!="please select a PF") {
pfData <- read.csv(file =paste(pf_name(),".csv",sep=""),sep=";",dec=",")
MDur <- pfData[1,15]
pfData <- pfData[3:nrow(pfData),]
Total = sum(pfData$Eco.Exp...Value.long)
Assets = sum(as.numeric(gsub(",",".",gsub(".","",pfData$Value,fixed=TRUE),fixed=TRUE)))
pfData$Exposure <- with(pfData, Eco.Exp...Value.long /Total)
pfData
output$selected_var <- renderText({paste(MDur)})
}
})
}
# Run the app ----
shinyApp(ui = ui, server = server)
Thank you
Stefan
Without a working example, it's imposible to be sure what you're trying to do, but it sounds like you need a reactive rather than using observeEvent.
Try something like
pfDataReactive <- reactive({
input$go
pfData <- read.csv(file =paste(pf_name(),".csv",sep=""),sep=";",dec=",")
Total = sum(pfData$Eco.Exp...Value.long)
Assets = sum(as.numeric(gsub(",",".",gsub(".","",pfData$Value,fixed=TRUE),fixed=TRUE)))
pfData$Exposure <- with(pfData, Eco.Exp...Value.long /Total)
pfData
})
And then use pfDataReactive() in your Shiny app's server function wherever you would refer to pfData in your console code.
The standalone reference to input$go ensures the reactive will update whenever input$go changes/is clicked/etc.
Update
There are still significant issues with your code. You've added an assignment to an output object as the last line of the reactive I gave you, so the reactive always returns NULL. That's not helpful and is one of the reasons why it "doesn't active at all"...
Second, you test for the existence of an reactive/function called pf_name when the relevant input object appears to be input$pfID. That's another reason why the reactive is never updated.
Note the change to the definition of input$pfID that I've made to improve the readability of the pfDataReactive object. (This change also probably means that you can do away with input$go entirely.)
As you say, I don't have access to your csv file, so I can't test your code completely. I've modified the body of the pfDataReactive to simply return the mtcars dataset as a string. I've also edited the code I've commented out to hopefully run correctly when you use it with the real csv file.
This code appears to give the behaviour you want,. Though, if I may make a subjective comment, I think the layout of your GUI is appaling. ;=)
library(shiny)
ui <- fluidPage(
fluidRow(
column(6, offset =3,
wellPanel(
"Choose Mandate and Date",
fluidRow(
column(4,selectInput("pfID",label = "",
# Modified to that "Pleaseselect a PF" returns NULL
choices = list("please select a PF"="","GF25", "FPM"),
selected = "please select a PF") ),
column(4, dateInput("pfDate",label="",value = Sys.Date()) ),
column(2, actionButton("go","Submit")),column(2,textOutput("selected_var"))
)
)
)
)
)
# Define server logic ----
server <- function(input, output) {
pfDataReactive <- reactive({
# Don't do anything until we have a PF csv file
req(input$pfID)
input$go
# Note the change to the creation of the file name
# pfData <- read.csv(file =paste(input$pfID,".csv",sep=""),sep=";",dec=",")
# pfData <- pfData[3:nrow(pfData),]
# Total = sum(pfData$Eco.Exp...Value.long)
# Assets = sum(as.numeric(gsub(",",".",gsub(".","",pfData$Value,fixed=TRUE),fixed=TRUE)))
# pfData$Exposure <- with(pfData, Eco.Exp...Value.long /Total)
# MDur <- pfData[1,15]
# If you want to print MDur in the selected_var output, MDur should be the retrun value from this reactive
# MDur
mtcars
})
output$selected_var <- renderText({
print("Yep!")
as.character(pfDataReactive())
})
}
# Run the app ----
shinyApp(ui = ui, server = server)
Next time, please, please, make more effort to provide a MWE. This post may help.
This is a good introduction to Shiny.
I have this question: In a Shiny App, I construct a varible with a reactive(). The thing is that, in the midle of this process (that is a long one) I construct other varibles that I need too.
For example:
#---------------UI------------------
ui <- navbarPage(
title = "example",
tabPanel('panel',
tableOutput("my_table"),
tableOutput("colum_names"))
)
#---------------SERVER------------------
server <- function(input, output) {
a <- reactive({
df_1 <- data.frame("fc"=c(1,2,3), "sc"=c(1,2,3), "tc"=c(1,2,3) )
df_2 <- subset(df_1,select=-c(fc))
column_names <- colnames(df_2)
df_3 <- df_2*2
df_3
})
output$my_table = renderTable({
a()
})
output$colum_names = renderTable({
df_column_names = data.frame(column_names())
df_column_names
})
}
#---------------APP------------------
shinyApp(ui = ui, server = server)
In this (very short) example, I would need the variable "a" (of course) and the variable "column_names". I can do something like create a new reactive that reproduce all the process until the line that contain "column_names" and finish it there. But the process is too long and I prefer to do it more "eficiently".
Any idea??
Thank you so much!
The process you're describing is correct : instead of assigning variables, just assign reactives and Shiny will handle the depedencies between them.
Note that in the example you provided, reactives aren't needed because the content is up to now static.
library(shiny)
#---------------UI------------------
ui <- navbarPage(
title = "example",
tabPanel('panel',
tableOutput("my_table"),
tableOutput("column_names"))
)
#---------------SERVER------------------
server <- function(input, output) {
df_1 <- data.frame("fc"=c(1,2,3), "sc"=c(1,2,3), "tc"=c(1,2,3) )
a <- reactive({subset(df_1,select=-c(fc))})
column_names <- reactive({colnames(a())})
output$my_table = renderTable({a()})
output$column_names = renderTable({column_names()})
}
#---------------APP------------------
shinyApp(ui = ui, server = server)
I found a interesting answer to my own question: if you want to do something like that, you can use "<<-" instead of "<-" and it save the variable when you are working insede a function (like reactive()). Let´s see:
#---------------UI------------------
ui <- navbarPage(
title = "example",
tabPanel('panel',
tableOutput("my_table"),
tableOutput("colum_names"))
)
#---------------SERVER------------------
server <- function(input, output) {
a <- reactive({
df_1 <- data.frame("fc"=c(1,2,3), "sc"=c(1,2,3), "tc"=c(1,2,3) )
df_2 <- subset(df_1,select=-c(fc))
column_names <- colnames(df_2)
# HERE THE SOLUTION!!
column_names_saved <<- column_names
df_3 <- df_2*2
df_3
})
output$my_table = renderTable({
a()
})
output$colum_names = renderTable({
df_column_names = data.frame(column_names_saved)
df_column_names
})
}
#---------------APP------------------
shinyApp(ui = ui, server = server)
Then, into the funtion you must continues with the variable "column_names", but when you need to use it later, you can use "column_name_saved". (just be carefull with one thing: onece you save the variable into the funtion, you canot change it)
Thanks!!!
I'm creating a Shiny app which displays images and text in a data table. This table will need to update depending on the user's input. When I run the app in a window the table updates as expected. However, when I run it in a browser the text updates but the image does not. How do I make it work in a browser?
EDIT: For clarity, the below example is just to reproduce the issue. The real app could display any number of different pictures, which aren't saved locally until the user makes a selection (they're pulled from a database). I was hoping to avoid having different filenames because I could potentially end up with hundreds of thousands of pictures saved locally, but if that's the only solution then I will have to cleanup the folder periodically
Reproducible example (requires 2 local images)
library(shiny)
library(imager)
library(DT)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("Tables to export"),
sidebarLayout(
sidebarPanel(
actionButton("pic1","Pic1"),
actionButton("pic2","Pic2")
),
# Show tables
mainPanel(
fluidRow(
dataTableOutput('tab1')
)
)
)
)
# Define server logic
server <- function(input, output) {
observeEvent(input$pic1, {
pic <- load.image("www/pic1.png")
save.image(pic,"www/picToShow.png")
tab1 <- datatable(t(data.frame("Pic"='<img src="picToShow.png" width=150 height=100>',x1=1,x2=2,x3=3,row.names="p1")),
escape = F, options = list(dom = 't',pageLength = 20))
output$tab1 <- renderDataTable(tab1)
})
observeEvent(input$pic2, {
pic <- load.image("www/pic2.png")
save.image(pic,"www/picToShow.png")
tab1 <- datatable(t(data.frame("Pic"='<img src="picToShow.png" width=150 height=100>',x1=4,x2=5,x3=6,row.names="p1")),
escape = F, options = list(dom = 't',pageLength = 20))
output$tab1 <- renderDataTable(tab1)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Expected behaviour (behaviour in window)
Behaviour in the browser
I agree with #MrFlick's comment. Why do you load both images and resave them with the same name? The browser will think that it knows the image already and will re-use the already loaded image.
Why not just include pic1.png and pic2.png directly?
server <- function(input, output) {
observeEvent(input$pic1, {
tab1 <- datatable(t(data.frame("Pic"='<img src="pic1.png" width=150 height=100>',x1=1,x2=2,x3=3,row.names="p1")),
escape = F, options = list(dom = 't',pageLength = 20))
output$tab1 <- renderDataTable(tab1)
})
observeEvent(input$pic2, {
tab1 <- datatable(t(data.frame("Pic"='<img src="pic2.png" width=150 height=100>',x1=4,x2=5,x3=6,row.names="p1")),
escape = F, options = list(dom = 't',pageLength = 20))
output$tab1 <- renderDataTable(tab1)
})
}
library(shiny)
library(imager)
library(DT)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("Tables to export"),
sidebarLayout(
sidebarPanel(
actionButton("pic1","Pic1"),
actionButton("pic2","Pic2")
),
# Show tables
mainPanel(
fluidRow(
DT::dataTableOutput('tab1')
)
)
)
)
# Define server logic
server <- function(input, output) {
vals = reactiveValues(pic1 = 0, pic2 = 0)
observeEvent(input$pic1, {
vals$pic1 <- 1
vals$pic2 <- 0
})
observeEvent(input$pic2, {
print(vals$pic1)
print(vals$pic2)
vals$pic1 <- 0
vals$pic2 <- 1
})
dynamicdf <- reactive({
if(vals$pic1 == 1) {
df <- data.frame(
pic = c('<img src="http://flaglane.com/download/american-flag/american-flag-large.png" height="52"></img>'),
x1 = c(1),
x2 = c(2),
x3 = c(3)
)
} else {
df <- data.frame(
pic = c('<img src="img2.jpg" width=150 height=100></img>'),
x1 = c(4),
x2 = c(5),
x3 = c(6)
)
}
print(df)
return(df)
})
output$tab1 <- DT::renderDataTable({
DT::datatable(dynamicdf(), escape = FALSE)
})
}
# Run the application
shinyApp(ui = ui, server = server)
In Shiny Apps you do not load or saves images. You display them by using a path to the folder, where your pictures are stored. This can be a Link on the internet or a path on your machine.
Do it like this. I use a reactiveValue to track the last click of your button. This is a good solution if you have a large number of pictures you may want to render. (I adopted that style from the modern JS Library ReactJS) Based on the state you display your pictures. Do NOT use the www path, this is already expected by shiny. Leave it as in the example2 in the App.
For me it also only worked with the escape = FALSE parameter in the App. Try that if it does not work without it.
I am trying to create a table using Shiny, where the user can click on a row in order to see further information about that row. I thought I understood how to do this (see code attached).
However, right now as soon as the user clicks the "getQueue" action button, the observeEvent(input$fileList_cell_clicked, {}) seems to get called. Why would this be called before the user even has the chance to click on a row? Is it also called when the table is generated? Is there any way around this?
I need to replace "output$devel <- renderText("cell_clicked_called")" with code that will have all sorts of errors if there isn't an actual cell to refer to.
Thank you for any advice!
ui <- fluidPage(
actionButton("getQueue", "Get list of queued files"),
verbatimTextOutput("devel"),
DT::dataTableOutput("fileList")
)
shinyServer <- function(input, output) {
observeEvent(input$getQueue, {
#get list of excel files
toTable <<- data.frame("queueFiles" = list.files("queue/", pattern = "*.xlsx")) #need to catch if there are no files in queue
output$fileList <- DT::renderDataTable({
toTable
}, selection = 'single') #, selection = list(mode = 'single', selected = as.character(1))
})
observeEvent(input$fileList_cell_clicked, {
output$devel <- renderText("cell_clicked_called")
})}
shinyApp(ui = ui, server = shinyServer)
minimal error code
DT initializes input$tableId_cell_clicked as an empty list, which causes observeEvent to trigger since observeEvent only ignores NULL values by default. You can stop the reactive expression when this list is empty by inserting something like req(length(input$tableId_cell_clicked) > 0).
Here's a slightly modified version of your example that demonstrates this.
library(shiny)
ui <- fluidPage(
actionButton("getQueue", "Get list of queued files"),
verbatimTextOutput("devel"),
DT::dataTableOutput("fileList")
)
shinyServer <- function(input, output) {
tbl <- eventReactive(input$getQueue, {
mtcars
})
output$fileList <- DT::renderDataTable({
tbl()
}, selection = 'single')
output$devel <- renderPrint({
req(length(input$fileList_cell_clicked) > 0)
input$fileList_cell_clicked
})
}
shinyApp(ui = ui, server = shinyServer)