Related
I am new to the world of RShiny and i think reactivity is bit complex to understand. I am trying to make a datatable output based on row and column condition given as per user selectinput dropdown buttons. My DataTable is editable and i want to store the updated table after user edited the cells of the datatable in a new variable but i am stuck.
I tried couple of chunks suggested on stackoverflow but none of them worked for me mostly using proxytable or reactivity. I want to store the datatable after i hit proceed button. Any help would be much appreciated.
Here is my code:
library(shiny)
library(shinyWidgets)
library(shinythemes)
library(DT)
library(data.table)
#ui
shinyApp(
ui = fluidPage(
theme = shinythemes::shinytheme("flatly"),
titlePanel("Trial"),
sidebarLayout(
shiny::sidebarPanel(
#to take multiple user input
shiny::textAreaInput(
"text_input",
label = "Write input"
),
#to slect the columns to be added
shinyWidgets::pickerInput(
inputId = "somevalue",
label = "Columns to add",
choices = colnames(df),
options = list(`actions-box` = TRUE),
multiple = TRUE
),
#action button tot show the table
shinyWidgets::actionBttn(
"show_table",
label = "Show",
size = "sm",
color = "default",
block = TRUE
), width = 2
),
mainPanel(
shiny::tabsetPanel(type = "tabs",
shiny::tabPanel("Table", DT::dataTableOutput("table")),
actionBttn("proceed","proceed")
),width = 10
)
)
),
server = function(input, output,session) {
#to add reactivity to the show button
df_filter <- reactive({
text_input <- trimws(strsplit(input$text_input, ",")[[1]])
df_filter <- df[df$make %chin% text_input, input$somevalue]
})%>% shiny::bindEvent(input$show_table)
#to output hte dt table with the filters
output$table <- DT::renderDT({
DT::datatable(df_filter(),
editable = 'cell',
options = list(scrollX = TRUE , lengthChange = FALSE, autoWidth = TRUE)
# editable = list(target = "row", disable = list(columns = c(2, 4, 5))))
)
})%>% shiny::bindEvent(df_filter())
}
)
I am creating an R Shiny app, where a user can upload their own csv, and the app generates a synthetic dataset. I am trying to add an additional function where the user can select a column they wish to anonymise to make it a shareable file. The rest of the app is currently working, however when I select the column which I want to anonomise, when I press update, the datatable isn't refreshing.
Any help or insight here would be greatly appreciated! I've tried and tried to solve it, but am stuck.
A shortened/reproducible version of the code app is below
`
library(shiny)
library(synthpop)
library(DT)
library(tidyverse)
library(data.table)
library(rsconnect)
library(fontawesome)
library(DT)
library(htmltools)
library(shinythemes)
library(RcppRoll)
library(grid)
library(reactable)
library(shinydashboard)
library(shinydashboardPlus)
library(formattable)
library(dashboardthemes)
library(deidentifyr)
library(anonymizer)
library(digest)
# User interface
ui <- fluidPage(theme = shinytheme("cosmo"),
navbarPage("Synthetic data",
# Upload data tab
tabPanel("Upload data",
sidebarLayout(
sidebarPanel(width = 3,
h4(strong("Upload original data")),
br(),
fileInput(inputId = "datafile", label = "1. Upload a csv file then press 'Update' below.
Note, the larger your dataset, the longer it will take to load", multiple = FALSE, placeholder = "No file selected",
accept = c("text/csv", "text/comma-separated-values,text/plain", ".csv")),
actionButton(inputId = "update", label = "Update", icon = icon("fas fa-sync")),
br(),
br(),
h5(strong("2. To view and download the synthesised dataset, click on the 'Synthetic data' tab at the top"))),
mainPanel(dataTableOutput("table"), style = "font-size:80%"))),
# Synthetic data download
tabPanel("Synthetic data",
sidebarLayout(
sidebarPanel(width = 3,
h4(strong("Anonomise data?")),
br(),
uiOutput(outputId = "anon"),
br(),
actionButton(inputId = "update2", label = "Update", icon = icon("fas fa-sync"))),
mainPanel(dataTableOutput("synth"), style = "font-size:75%"))),
))
# Server function
server <- function(input, output, session) {
options(shiny.maxRequestSize=20*1024^2)
contentsrea <- reactive({
inFile <- input$datafile
if(is.null(inFile))
return(NULL)
dataset <- read_csv(inFile$datapath)
})
observeEvent(input$update, {
if(!is.null(input$datafile)){
original <- read_csv(input$datafile$datapath)
my.seed <- 17914709
synResult <- syn(original, seed = my.seed, maxfaclevels = 150)
# Synthetic data
df <- synResult$syn
# Add 'SYNTH' to column headings
colnames(df) <- paste("SYNTH", colnames(df), sep="_")
# Variable dropdown to anonomise data
output$anon <- renderUI({
selectInput(inputId = "anon",
label="1. Select the variable you'd like to anonomise (i.e., athlete name). If not necessary, leave as blank",
choices = c(" ", colnames(df)),
selected = NULL)
})
## Original
output$table <- DT::renderDataTable(original,
caption = htmltools::tags$caption(
style = 'caption-side: top; text-align: left; color: black; font-size:140%',
h3(strong("Original data"))), server = FALSE, rownames=FALSE,
options = list(bFilter=0, iDisplayLength=18,
columnDefs = list(list(className = 'dt-center', targets = '_all')),
dom = 'frtip'))
# Synthetic dataset
output$synth <- DT::renderDataTable(df,
caption = htmltools::tags$caption(
style = 'caption-side: top; text-align: left; color: black; font-size:140%',
h3(strong("Simulated synthetic data"))),
server = FALSE, rownames=FALSE, extensions = c("Buttons"),
options = list(iDisplayLength=18, bFilter=0,
columnDefs = list(list(className = 'dt-center', targets = '_all')),
dom = 'Bfrtip'))
}})
}
# Synthetic dataset with update for anon
observeEvent(input$update2, {
if(!is.null(input$datafile)){
output$synth <- DT::renderDataTable({
# Anonomise
df$ID <- sapply(input$anon, digest, algo = "crc32")
datatable(df,
caption = htmltools::tags$caption(
style = 'caption-side: top; text-align: left; color: black; font-size:140%',
h3(strong("Simulated synthetic data"))),
server = FALSE, rownames=FALSE, extensions = c("Buttons"),
options = list(iDisplayLength=18, bFilter=0,
columnDefs = list(list(className = 'dt-center', targets = '_all')),
dom = 'Bfrtip'))
})
}})
# Run the app ----
shinyApp(ui, server)
`
I have tried removing the update button ans using a reactive table, as well as other anonomise functions. I am completely stuck.
I have a datatable where the footer has "Previous/Next" text. Currently it is overlapping the "Displaying 1 of 15" text. Is there a way to move this so I dont get an over lap?
Here is a picture as well as the code. I am not very familiar with html/java script so if you could provide an answer explaining why you are using the specific code that would be greatly appreciated
library(shiny)
library(bs4Dash)
library(DT)
x = data.frame(one = rep("Hey how is everyones day? I need some help on this shiny application and learn how to use some of the features on datatable.", 10),
two = rep("this is the second column of text. ", 10),
three = rep("this is the third column of text", 10))
ui = bs4DashPage(
old_school = FALSE,
sidebar_min = TRUE,
sidebar_collapsed = FALSE,
controlbar_collapsed = FALSE,
controlbar_overlay = TRUE,
title = "Basic Dashboard",
navbar = bs4DashNavbar(),
sidebar = bs4DashSidebar(),
controlbar = bs4DashControlbar(),
footer = bs4DashFooter(),
body = bs4DashBody(
DTOutput("table")
)
)
server = function(input, output) {
output$table = renderDataTable({
datatable(x, rownames = F, style = "bootstrap", extensions = 'Responsive', options = list(
#dom = 't'
))
})
}
shinyApp(ui, server)
When i run the same code on shiny dashboard, it comes out the way I would want it to look like. So i believe it is something to do with Bs4Dash styling sheet. Below is how it looks with shinydashboard
You need to redraw your table after initialization.
$('#tableIdHere').DataTable().draw();
You can try this out , Hope it will sove your problem.
library(shiny)
library(bs4Dash)
library(DT)
x = data.frame(one = rep("Hey how is everyones day? I need some help on this shiny application and learn how to use some of the features on datatable.", 10),
two = rep("this is the second column of text. ", 10),
three = rep("this is the third column of text", 10))
ui = bs4DashPage(
old_school = FALSE,
sidebar_min = TRUE,
sidebar_collapsed = FALSE,
controlbar_collapsed = FALSE,
controlbar_overlay = TRUE,
title = "Basic Dashboard",
navbar = bs4DashNavbar(),
#sidebar = bs4DashSidebar(),
controlbar = bs4DashControlbar(),
footer = bs4DashFooter(),
body = bs4DashBody(
DTOutput("table")
)
)
server = function(input, output) {
output$table = renderDataTable({
datatable(x, rownames = F, extensions = 'Responsive', options = list(
#dom = 't'
))
})
}
shinyApp(ui, server)
I have only removed style = "bootstrap" from the renderDataTable and that serve the perpose
Your css is overwritten with some other default css. Please check if your classes are similarly named or clashing with each other.
Below is a sample code which takes two inputs: 1) input file and 2) input number of rows. Upon clicking the "Analyze" button the output from the server command return to the "Table" in "Results" tabset. This is a simple example where the command will be executed quickly and switches to the "Results" tabsetpanel.
The below withProgress code only shows the progress bar for the set time and disappears and then the actual code is executed. I would like to show a "Status Message" or "Progress Bar" when the "Analyze" is hit and show as long as the command is run. As long as the progress bar is running the current user (other users can use the app) cannot perform any action from the side bar. Because in the real app, sidebar has more menuItems which does similar tasks like this and each task has a Analyze button. If the user is allowed to browse to sidebar pages and hit Analyze then the app will have overload of performing multiple tasks. Ideally the progress bar functionality should we used with multiple actionButtons.
I read the blogs about async but unable to put right code in the right place. any help is appreciated with a bounty!!
library(shiny)
library(shinydashboard)
sidebar <- dashboardSidebar(width = 200,
sidebarMenu(id = "tabs",
menuItem(
"File", tabName = "tab1", icon = icon("fas fa-file")
)))
body <- tabItem(tabName = "tab1",
h2("Input File"),
fluidRow(
tabPanel(
"Upload file",
value = "upload_file",
fileInput(
inputId = "uploadFile",
label = "Upload Input file",
multiple = FALSE,
accept = c(".txt")
),
checkboxInput('header', label = 'Header', TRUE)
),
box(
title = "Filter X rows",
width = 7,
status = "info",
tabsetPanel(
id = "input_tab",
tabPanel(
"Parameters",
numericInput(
"nrows",
label = "Entire number of rows",
value = 5,
max = 10
),
actionButton("run", "Analyze")
),
tabPanel(
"Results",
value = "results",
navbarPage(NULL,
tabPanel(
"Table", DT::dataTableOutput("res_table"),
icon = icon("table")
)),
downloadButton("downList", "Download")
)
)
)
))
ui <-
shinyUI(dashboardPage(
dashboardHeader(title = "TestApp", titleWidth = 150),
sidebar,dashboardBody(tabItems(body))
))
server <- function(input, output, session) {
file_rows <- reactiveVal()
observeEvent(input$run, {
withProgress(session, min = 1, max = 15, {
setProgress(message = 'Analysis in progress',
detail = 'This may take a while...')
for (i in 1:15) {
setProgress(value = i)
Sys.sleep(0.5)
}
})
system(paste(
"cat",
input$uploadFile$datapath,
"|",
paste0("head -", input$nrows) ,
">",
"out.txt"
),
intern = TRUE)
head_rows <- read.delim("out.txt")
file_rows(head_rows)
})
observeEvent(file_rows(), {
updateTabsetPanel(session, "input_tab", "results")
output$res_table <-
DT::renderDataTable(DT::datatable(
file_rows(),
options = list(
searching = TRUE,
pageLength = 10,
rownames(NULL),
scrollX = T
)
))
})
output$downList <- downloadHandler(
filename = function() {
paste0("output", ".txt")
}, content = function(file) {
write.table(file_rows(), file, row.names = FALSE)
}
)
}
shinyApp(ui = ui, server = server)
Here is a solution based on the (absolutely under-star-ed) library(ipc).
I came across this library due to a question of #Dean Attali, where Joe Cheng mentioned it.
The quick-start guide of the ipc-package gives an example of what you are asking for: AsyncProgress.
Furthermore it provides an example on how to kill a future using AsyncInterruptor.
However, I haven't been able to test it yet.
I worked around the cancel-problem by using #Dean Attali's great package shinyjs to simply start a new session and ignore the old Future (You might be able to improve this, by using AsyncInterruptor).
But nevertheless, I gave your code a Future, dropped your system() cmd because I'm currently running R on Windows and found a way to disable (tribute to #Dean Attali) the analyze button session-wise by giving it session-dependant names:
library(shiny)
library(shinydashboard)
library(ipc)
library(promises)
library(future)
library(shinyjs)
library(datasets)
library(V8)
plan(multiprocess)
header <- dashboardHeader(title = "TestApp", titleWidth = 150)
sidebar <- dashboardSidebar(width = 200,
sidebarMenu(id = "tabs",
menuItem(
"File", tabName = "tab1", icon = icon("fas fa-file")
)))
body <- dashboardBody(useShinyjs(),
fluidRow(column(
12, tabItem(
tabName = "tab1",
h2("Input File"),
textOutput("shiny_session"),
tabPanel(
"Upload file",
value = "upload_file",
fileInput(
inputId = "uploadFile",
label = "Upload Input file",
multiple = FALSE,
accept = c(".txt")
),
checkboxInput('header', label = 'Header', TRUE)
),
box(
title = "Filter X rows",
width = 7,
status = "info",
tabsetPanel(
id = "input_tab",
tabPanel(
"Parameters",
numericInput(
"nrows",
label = "Entire number of rows",
value = 5,
max = 10
),
column(1, uiOutput("sessionRun")),
column(1, uiOutput("sessionCancel"))
),
tabPanel(
"Results",
value = "results",
navbarPage(NULL,
tabPanel(
"Table", DT::dataTableOutput("res_table"),
icon = icon("table")
)),
downloadButton("downList", "Download")
)
)
)
)
)))
ui <- shinyUI(dashboardPage(
header = header,
sidebar = sidebar,
body = body,
title = "TestApp"
))
server <- function(input, output, session) {
output$shiny_session <-
renderText(paste("Shiny session:", session$token))
file_rows <- reactiveVal()
run_btn_id <- paste0("run_", session$token)
cancel_btn_id <- paste0("cancel_", session$token)
output$sessionRun <- renderUI({
actionButton(run_btn_id, "Analyze")
})
output$sessionCancel <- renderUI({
actionButton(cancel_btn_id, "Cancel")
})
paste("Shiny session:", session$token)
observeEvent(input[[run_btn_id]], {
file_rows(NULL)
shinyjs::disable(id = run_btn_id)
progress <- AsyncProgress$new(message = 'Analysis in progress',
detail = 'This may take a while...')
row_cnt <- isolate(input$nrows)
get_header <- isolate(input$header)
future({
fileCon <- file("out.txt", "w+", blocking = TRUE)
linesCnt <- nrow(iris)
for (i in seq(linesCnt)) {
Sys.sleep(0.1)
progress$inc(1 / linesCnt)
writeLines(as.character(iris$Species)[i],
con = fileCon,
sep = "\n")
}
close(fileCon)
head_rows <- read.delim("out.txt", nrows = row_cnt, header=get_header)
progress$close() # Close the progress bar
return(head_rows)
}) %...>% file_rows
return(NULL) # Return something other than the future so we don't block the UI
})
observeEvent(input[[cancel_btn_id]],{
session$reload()
})
observeEvent(file_rows(), {
shinyjs::enable(id = run_btn_id)
updateTabsetPanel(session, "input_tab", "results")
output$res_table <-
DT::renderDataTable(DT::datatable(
req(file_rows()),
options = list(
searching = TRUE,
pageLength = 10,
rownames(NULL),
scrollX = T
)
))
})
output$downList <- downloadHandler(
filename = function() {
paste0("output", ".txt")
},
content = function(file) {
write.table(file_rows(), file, row.names = FALSE)
}
)
}
shinyApp(ui = ui, server = server)
App running:
This question has been answered on a different forum
For future reference, if anyone comes across this question, here's the full answer (I did not come up with this answer, it's by Joe Cheng)
This seems to be the main piece of code you're asking about:
observeEvent(input$run, {
withProgress(session, min = 1, max = 15, {
setProgress(message = 'Analysis in progress',
detail = 'This may take a while...')
for (i in 1:15) {
setProgress(value = i)
Sys.sleep(0.5)
}
})
system(paste(
"cat",
input$uploadFile$datapath,
"|",
paste0("head -", input$nrows) ,
">",
"out.txt"
),
intern = TRUE)
head_rows <- read.delim("out.txt")
file_rows(head_rows)
})
With futures/promises, you need to clearly decide what operations happen inside of the Shiny process, and what operations happen in the future process. In this case, here are the steps that we want to happen, in order:
Show progress message (Shiny process)
Read reactives: input$uploadFile$datapath, input$nrows (Shiny)
Write all but the last nrows to out.txt (future process)
Read out.txt (Could be either, let's say future)
Dismiss progress (Shiny)
Assign result to file_rows (Shiny)
Here's what that looks like:
observeEvent(input$run, {
prog <- Progress$new(session)
prog$set(message = "Analysis in progress",
detail = "This may take a while...",
value = NULL)
path <- input$uploadFile$datapath
nrows <- input$nrows
future({
readLines(path) %>% head(-nrows) %>% writeLines("out.txt")
read.delim("out.txt")
}) %...>%
file_rows() %>%
finally(~prog$close())
})
As long as the future/promise pipeline is the last expression in the observeEvent (which it is in this case, as file_rows() and finally(...) are part of the pipeline) then Shiny will hold off on processing any messages on behalf of the user.
There are two things this solution doesn't address.
Progress messages take a step back; not only are we forced to use the Progress$new() syntax instead of the cleaner withProgress(), but we lost the ability to report on the progress percentage. You can try the new ipc package for a solution to that problem.
This doesn't stop the user from clicking around in the UI; it won't do anything while the async operation is executing, but when the operation is done those interactions will have accumulated in a queue and will be handled in the order that they arrived. If you'd like to actually disable the UI entirely so that they're not able to do anything at all, there's not currently a built-in way to do that in Shiny. Although come to think of it, you might try replacing the use of Progress with showModal(modalDialog(title = "Analysis in progress", "This may take a while...", footer=NULL)); I think that will at least stop mouse clicks.
I'm trying to figure out how to use downloadButton to download a plot with shiny. The example in the package demonstrates downloadButton/downloadHandler to save a .pdf. I'm going to make a reproducible example based on that.
output$downloadPlot <- downloadHandler(
filename = function() {
"plotname .pdf"
},
content = function(file) {
pdf(file = file,
width = 12,
height = 12)
print(buildPlot())
dev.off()
}
)
I would suggest you to use Highcharter package. This way you don't need to create a download button, because the chart has options to download in several extensions. Here I give you an example of an histogram, choosing to export in PNG, SVG, JPEG or PDF.
## Export charts with Highcharter in Shiny
# Load package
library('highcharter')
# UI side
highchartOutput('plot')
# Server side
output$plot <- renderHighchart({
# Define your data, here I am using Iris dataset as example
DT <- iris$Sepal.Length
# Define export options
export <- list(
list(
text = "PNG",
onclick = JS("function () {
this.exportChart({ type: 'image/png' }); }")
),
list(
text = "JPEG",
onclick = JS("function () {
this.exportChart({ type: 'image/jpeg' }); }")
),
list(
text = "SVG",
onclick = JS("function () {
this.exportChart({ type: 'image/svg+xml' }); }")
),
list(
text = "PDF",
onclick = JS("function () {
this.exportChart({ type: 'application/pdf' }); }")
)
)
# Plot histogram
hchart(DT,
type = "area",
name = colnames(iris[1])
) %>%
hc_exporting(
enabled = TRUE,
formAttributes = list(target = "_blank"),
buttons = list(contextButton = list(
text = "Export",
theme = list(fill = "transparent"),
menuItems = export
))
)
})
The output should be something like this:
Hope this helps.
Wlademir.