I have the following app which I created with some help from stack overflow
I want to be able to pick the practice for each doctor, then create a table based on the user input, then be able to export that table.
The app needs to adjust for a variable amount of doctors each time (the real app pulls from a dynamic database with new docs being added daily), hence the renderUI with conditional panels
I am having trouble passing selections from the practice into a table which I can render and export.
Any help much appreciated.
Here is my reprex
library(tidyverse)
library(shiny)
find_docs <- dplyr::tibble(record = c("joe", "mary", "dan", "suzie"))
locs_locs <- dplyr::tibble(record = c("practice1", "practice2", "practice3"))
mytable <- dplyr::tibble(
doc = find_docs$record,
location = rep("", length(find_docs$record))
)
ui <- fluidPage(
#numericInput("num_selected", label = "Fields to Display", value = 0, min = 0, max = 10, step = 1),
uiOutput("condPanels"),
tableOutput(outputId = "mydt")
)
server<-function(input,output,session){
output$condPanels <- renderUI({
# if selected value = 0 dont create a condPanel,...
# if(!input$num_selected) return(NULL)
tagList(
lapply(head(find_docs$record), function(nr){
conditionalPanel(
condition = paste0("Find DOC", nr),
fluidRow(
column(3,
tags$br(),
nr
),
column(3, selectInput(paste0("DOC", nr), "pick loc",
choices = locs_locs))
)
)
})
)
})
output$mydt <- renderTable({
#somehow i need to use mytable here
z <- data.frame( g = rep(input$find_docs$record[1], length(find_docs$record)))
z
# i want to render a table of find_docs in one column, and the selections in a second column)
# then i want to be able to export the table as csv
})
}
shinyApp(ui=ui, server=server)
I think I was able to capture your two needs. First, I took the inputs from each of the select inputs to create the table. I used lapply to pass each of the doctors to the input name. Then I combined this with the doctor list to create a data frame and a table.
I used the package DT for the second part of your request, to be able to download. DT has an extension which has a really easy way to download files in different ways. Hopefully this helps, good luck!
library(tidyverse)
library(shiny)
library(DT) #Added DT to download the table easily
find_docs <- dplyr::tibble(record = c("joe", "mary", "dan", "suzie"))
locs_locs <- dplyr::tibble(record = c("practice1", "practice2", "practice3"))
mytable <- dplyr::tibble(
doc = find_docs$record,
location = rep("", length(find_docs$record))
)
ui <- fluidPage(
uiOutput("condPanels"),
DTOutput(outputId = "mydt")
)
server<-function(input,output,session){
output$condPanels <- renderUI({
tagList(
lapply(head(find_docs$record), function(nr){
conditionalPanel(
condition = paste0("Find DOC", nr),
fluidRow(
column(3,
tags$br(),
nr
),
column(3, selectInput(paste0("DOC", nr), "pick loc",
choices = locs_locs))
)
)
})
)
})
output$mydt <- renderDT({
#An error will occur without this as it's trying to pull before these inputs are rendered
req(input[[paste0("DOC",find_docs$record[1])]])
z<-lapply(find_docs$record, function(x){
input[[paste0("DOC",x)]]
}) #Grab each of the inputs
z2 <- data.frame("DOC" = find_docs$record, "LOC" = unlist(z)) #Combine into a data frame
z2
}, extensions = "Buttons", #Using the extension addon of DT to have options to download the table
options = list(dom = 'Bfrtip',
buttons = c('csv')) #Download types
)
}
shinyApp(ui=ui, server=server)
If you didn't want to use DT, you could also put the table into a reactiveValue, and then download it using the download button. Both of these download options are visible on this other page I just realized: Shiny R - download the result of a table
Related
I have a data coming from a server. Now I want to add a free text column ( editable) to add comments to my R shiny application. Once that is done , I want to save it in SQLLite and bring it back once it is refreshed. Please help me with the pointers.
library(shiny)
library(ggplot2) # for the diamonds dataset
ui <- fluidPage(
title = "Examples of DataTables",
sidebarLayout(
sidebarPanel(
conditionalPanel(
'input.dataset === "diamonds"'
)
),
mainPanel(
tabsetPanel(
id = 'dataset',
tabPanel("diamonds", DT::dataTableOutput("mytable1"))
)
)
)
)
library(DT)
server <- function(input, output) {
# choose columns to display
diamonds2 = diamonds[sample(nrow(diamonds), 1000), ]
diamonds2$test <- ifelse(diamonds2$x > diamonds2$y,TRUE,FALSE)
output$mytable1 <- DT::renderDataTable({
DT::datatable(diamonds2[, drop = FALSE],extensions = 'FixedColumns',options = list(
dom = 't',
scrollX = TRUE,
fixedColumns = list(leftColumns =10)
)) %>%
formatStyle(
'x', 'test',
backgroundColor = styleEqual(c(TRUE, FALSE), c('gray', 'yellow'))
)
})
}
Please guide how can I add free text in the end of the table and save it.
Thanks in advance.
Regards,
R
Here is a solution based on DTs editable option. (See this for more information)
Each time the user edits a cell in the "comment" column it is saved to a sqlite database and loaded again after restarting the app:
library(shiny)
library(DT)
library(ggplot2) # diamonds dataset
library(RSQLite)
library(DBI)
# choose columns to display
diamonds2 = diamonds[sample(nrow(diamonds), 1000),]
diamonds2$test <- ifelse(diamonds2$x > diamonds2$y, TRUE, FALSE)
diamonds2$id <- seq_len(nrow(diamonds2))
diamonds2$comment <- NA_character_
con <- dbConnect(RSQLite::SQLite(), "diamonds.db")
if(!"diamonds" %in% dbListTables(con)){
dbWriteTable(con, "diamonds", diamonds2)
}
ui <- fluidPage(title = "Examples of DataTables",
sidebarLayout(sidebarPanel(
conditionalPanel('input.dataset === "diamonds"')
),
mainPanel(tabsetPanel(
id = 'dataset',
tabPanel("diamonds", DT::dataTableOutput("mytable1"))
))))
server <- function(input, output, session) {
# use sqlInterpolate() for production app
# https://shiny.rstudio.com/articles/sql-injections.html
dbDiamonds <- dbGetQuery(con, "SELECT * FROM diamonds;")
output$mytable1 <- DT::renderDataTable({
DT::datatable(
dbDiamonds,
# extensions = 'FixedColumns',
options = list(
dom = 't',
scrollX = TRUE
# , fixedColumns = list(leftColumns = 10)
),
editable = TRUE,
# editable = list(target = "column", disable = list(columns = which(names(diamonds2) %in% setdiff(names(diamonds2), "comment"))))
) %>% formatStyle('x', 'test', backgroundColor = styleEqual(c(TRUE, FALSE), c('gray', 'yellow')))
})
observeEvent(input$mytable1_cell_edit, {
if(input$mytable1_cell_edit$col == which(names(dbDiamonds) == "comment")){
dbExecute(con, sprintf("UPDATE diamonds SET comment = '%s' WHERE id = %s", input$mytable1_cell_edit$value, input$mytable1_cell_edit$row))
}
})
}
shinyApp(ui, server, onStart = function() {
onStop(function() {
dbDisconnect(con) # close connection on app stop
})
})
Initially I wanted to disable editing for all columns except "comment", however, it seems I've found a bug.
The following example adds a <input type="text"> element to each row of the table, where you can add your free text. A simple JavaScript event listener reacts on changes to the text boxes and stores them in the Shiny variable free_text which you can then process on the shiny side according to your needs (in this toy example it is simply output to a verbatimTextOutput).
As for the storing: I would add a save button, which reads input$free_text and saves it back to the data base. To display the text then again in the text boxes is as easy as adding the value in the mutate statement like this mutate(free_text = sprintf("<input type=\"text\" class = \"free-text\" value = \"%s\" />", free_text_field_name))
library(shiny)
library(DT)
library(dplyr)
ui <- fluidPage(
tags$head(
tags$script(
HTML(
"$(function() {
// input event fires for every change, consider maybe a debounce
// or the 'change' event (then it is only triggered if the text box
// loses focus)
$('#tab').on('input', function() {
const inputs = $(this).find('.free-text').map(function() {
return this.value;
})
Shiny.setInputValue('free_text', inputs.get());
})
})
"
)
)
),
fluidRow(
verbatimTextOutput("out")
),
fluidRow(
dataTableOutput("tab")
)
)
server <- function(input, output, session) {
output$tab <- renderDataTable({
my_dat <- mtcars %>%
mutate(free_text =
sprintf("<input type=\"text\" class = \"free-text\" value = \"\" />"))
datatable(my_dat, escape = FALSE,
options = list(dom = "t", pageLength = nrow(mtcars)))
})
output$out <- renderPrint(input$free_text)
}
shinyApp(ui, server)
You may want to have a look at the handsontable package, which allows editing of (columns of) datatable outputs. In your case, you can create a character column and allow editing through the handsontable.
On the topic of persisting data: you table would need either a separate column with comments, or a separate table that maps observations to comment, which is joined. The best solution depends on the volume of comments you expect: if you expect comment to appears sporadically, a separate table may be the best solution. If you expect comments for nearly every row, direct integration into the table may be more favourable. It then becomes a matter of writing to and loading from an SQL database based on user events.
I have two datasets, one with a list of two hundred cities and their corresponding state and another much larger dataset that I'd like to make an app to sort through. I need help making two drop down boxes in my shiny app where the first is the state variable and the second is the list of cities within that chosen state. I then want those selections to filter the much larger, second dataset in the output. I've tried solutions from several similar but slightly different examples online, but I'm having trouble translating it to what I'm doing.
So far I have this:
ui <- fluidPage(
headerPanel(''),
sidebarPanel(
#add selectinput boxs
htmlOutput("state_selector"),
htmlOutput("city_selector"),
),
mainPanel(
fluidRow(
# Create a new row for the table.
DT::dataTableOutput("table")
)
server <- function(session, input, output) {
output$state_selector = renderUI({
selectInput("state", label = h4("State"),
choices = as.character(unique(citystatedata$state)), selected = NULL)
})
output$city_selector = renderUI({
data_available = citystatedata[citystatedata$State == input$state, "state"]
selectInput(inputId = "city", #name of input
label = "City", #label displayed in ui
choices = unique(data_available), #calls list of available cities
selected = unique(data_available)[1])
})
shinyApp(ui = ui, server = server)
I tried to take out the portions of the code that weren't specifically related to the drop down boxes, since that's what I was more specifically asking about. So I'm sorry if I've left anything out! Let me know if I need to include anything else
Using available gapminder data, you can try this.
df <- gapminder
df$state <- gapminder$continent
df$city <- gapminder$country
citystatedata <- df
ui <- fluidPage(
headerPanel('Test'),
sidebarPanel(
#add selectinput boxs
uiOutput("state_selector"),
uiOutput("city_selector"),
),
mainPanel(
fluidRow(
# Create a new row for the table.
DTOutput("table")
)
)
)
server <- function(session, input, output) {
output$state_selector = renderUI({
selectInput("state", label = h4("State"),
choices = as.character(unique(citystatedata$state)), selected = NULL)
})
output$city_selector = renderUI({
data_available = citystatedata[citystatedata$state == req(input$state),]
selectInput(inputId = "city", #name of input
label = "City", #label displayed in ui
choices = unique(data_available$city), #calls list of available cities
selected = 1)
})
mydt <- reactive({
citystatedata %>% filter(citystatedata$state == req(input$state) & citystatedata$city %in% req(input$city))
})
output$table <- renderDT(mydt())
}
shinyApp(ui = ui, server = server)
This one has me really going around in circles.
I am working on an R script that loads a dataframe and uses fields from the dataframe to populate a hierarchical set of selectizeInput. E.g. each of the inputs represent a subset of what is in the previous. Each SubRegion contains multiple LCC’s, Each LCC contains multiple ENB’s, and so on.
When the user select a value in any of the inputs, that value will used to filter the dataframe and all of the other selectizeInputs need to be updated from the filtered data.
It seems to work fine for the first input (SubRegionInput) but every time I try to get it to respond to and/or filter by any of the others (e.g. add input$LCCInput to the observe block) they get populated for a few seconds and then go blank.
I suspect the answer is quite simple and/or I am doing something really dumb, but I am a total hack with no formal R training so am probably missing something quite basic (if so sorry).
Below is a partial chunk of code (sorry I can’t include it all but this is for work and I can’t share the details of what I am doing).
NOTES
The current outputs are just so I can see what is going on while I develop this portion of the code.
I know right now it is only set up to filter on the one value…everything I have tried to do it on more has failed so I included the most functional code I have so far.
ui <- fluidPage(
# Application title
titlePanel("KPI DrillDown"),
# Sidebar with a slider input for number of bins
fluidRow(
selectizeInput("SubRegionInput", "SubRegion", SubRegionList ,selected = NULL, multiple = TRUE),
selectizeInput("LCCInput", "LCC", LCCList,selected = NULL, multiple = TRUE),
selectizeInput("ENBIDInput", "ENBID", ENBIDList,selected = NULL, multiple = TRUE),
selectizeInput("SiteNumInput", "SiteNumber", SiteNumberList,selected = NULL, multiple = TRUE),
selectizeInput("SiteNameInput", "SiteName", SiteNameList,selected = NULL, multiple = TRUE),
selectizeInput("LNCELInput", "LNCell", LNCellList,selected = NULL, multiple = TRUE),
selectizeInput("SectorInput", "Sector", SectorList,selected = NULL, multiple = TRUE),
mainPanel(
#plotOutput("distPlot")
verbatimTextOutput("SubRegionText"),
verbatimTextOutput("LCCText"),
verbatimTextOutput("view")
)
)
)
server <- function(input, output) {
observe({
input$SubRegionInput
temp <- SiteInfo[SiteInfo$SITE_SUB_REGION %in% input$SubRegionInput, ]
thisLCCList = sort(temp$BACKHAUL_LCC[!is.na(temp$BACKHAUL_LCC)])
updateSelectizeInput(session = getDefaultReactiveDomain()
, inputId = "LCCInput"
, choices = thisLCCList
, selected= NULL)
thisENBIDList = sort(temp$ENODEB_ID[!is.na(temp$ENODEB_ID)])
updateSelectizeInput(session = getDefaultReactiveDomain()
, inputId = "ENBIDInput"
, choices = thisENBIDList
, selected= NULL)
thisSiteNumberList = sort(temp$SITE_NUMBER[!is.na(temp$SITE_NUMBER)])
updateSelectizeInput(session = getDefaultReactiveDomain()
, inputId = "SiteNumInput"
, choices = thisSiteNumberList
, selected= NULL)
thisSiteNameList = sort(temp$SITE_NAME[!is.na(temp$SITE_NAME)])
updateSelectizeInput(session = getDefaultReactiveDomain()
, inputId = "SiteNameInput"
, choices = thisSiteNameList
, selected= NULL)
thisLNCellList = sort(temp$SECTOR_NUMBER[!is.na(temp$SECTOR_NUMBER)])
updateSelectizeInput(session = getDefaultReactiveDomain()
, inputId = "LNCELInput"
, choices = thisLNCellList
, selected= NULL)
thisSectorList = sort(temp$Sector[!is.na(temp$Sector)])
updateSelectizeInput(session = getDefaultReactiveDomain()
, inputId = "SectorInput"
, choices = thisSectorList
, selected= NULL)
output$view<- renderPrint(temp)
})
Since I do not have access to your data, I used mtcars as an example.
To begin with, since you have so many filtering, I would suggest creating a search or update button, which is what I did in my codes. I only did one filtering using dplyr after extracting all the selectizeInputs. I have to manually change all the empty searching parameter to select all in order to avoid filtering to NA.
Overall, I think the problem with your code was you are observing too many updateSelectizeInputs at once. I did try to recreate using your way, and what I ended with was that I could only update single selectizeInput, and the other selectizeInputs were not selectable.
Hopefully, this method fits your data.
Codes:
library(shiny)
library(dplyr)
library(DT)
data <- mtcars
SubRegionList <- unique(data$cyl)
LCCList <- unique(data$gear)
ENBIDList <- unique(data$am)
SiteNumberList <- unique(data$vs)
# Define UI
ui <- fluidPage(
# Application title
titlePanel("KPI DrillDown"),
# Sidebar with a slider input for number of bins
fluidRow(
selectizeInput("SubRegionInput", "SubRegion/cyl", SubRegionList ,selected = NULL, multiple = TRUE),
uiOutput("LCCInput"),
uiOutput("ENBIDInput"),
uiOutput("SiteNumInput"),
uiOutput("Search"),
mainPanel(
verbatimTextOutput("view")
)
)
)
# Define server logic required
server <- function(input, output, session) {
SiteInfo <- data
# temp <- ""
observe({
if (!is.null(input$SubRegionInput)){
subRegionSelected <- input$SubRegionInput
## Create a temp dataset with the selected sub regions.
temp <- SiteInfo[SiteInfo$cyl %in% subRegionSelected, ]
## Push the newly created selectizeInput to UI
output$LCCInput <- renderUI({
selectizeInput("LCCInput", "LCC/gear", unique(temp$gear), selected = NULL, multiple = TRUE)
})
output$ENBIDInput <- renderUI({
selectizeInput("ENBIDInput", "ENBID/am", unique(temp$am),selected = NULL, multiple = TRUE)
})
output$SiteNumInput <- renderUI({
selectizeInput("SiteNumInput", "SiteNumber/vs", unique(temp$vs), selected = NULL, multiple = TRUE)
})
output$Search <- renderUI({
actionButton("Search", "Search")
})
## Function that linked to the actionButton
display <- eventReactive(input$Search,{
temp <- SiteInfo[SiteInfo$cyl %in% input$SubRegionInput, ]
# ## manually change all the empty searching parameter to select all in order to avoid filtering to NA
LCC <- input$LCCInput
if (is.null(input$LCCInput)){LCC <- unique(temp$gear)}
ENBID <- input$ENBIDInput
if (is.null(input$ENBIDInput)){EBVID <- unique(temp$am)}
SiteNum <- input$SiteNumInput
if (is.null(input$SiteNumInput)){LCC <- unique(temp$vs)}
## Dplyr::filter data
temp <- temp %>%
filter(gear %in% LCC & am %in% ENBID & vs %in% SiteNum)
temp
})
## Run the actionButton
output$view <- renderPrint({
display()
})
} else {
## Display waht the data looks like when no Sub Region is selected
output$view<- renderPrint(data)
}
})
}
# Run the application
shinyApp(ui = ui, server = server)
I'm trying to create a shiny app where user is able to add text comment to a table.
I created a dataframe with 3 columns: num, id and val. I want my shiny app to do the following:
select an value from id column (selectInput).
add text comment in a text box (textInput)
click on an action button
A new column called comment is created in the data table, text comments are added to the comment column in the row where id equals the value selected.
My shiny app code is below. When I select an value from selectinput, add some comment in the text box and click on `add comment' button, my shiny app window shut down by itself.
Does anyone know why that happens?
Thanks a lot in advance!
library(shiny)
library(DT)
df = data.frame(num=1:10, id=LETTERS[1:10], val=rnorm(10))
ui = fluidPage(
fluidRow(
column(2, selectInput(inputId = 'selectID',
label = 'Select ID2',
choices = LETTERS[1:10],
selected='',
multiple=TRUE)),
column(6, textInput(inputId = 'comment',
label ='Please add comment in the text box:',
value = "", width = NULL,
placeholder = NULL)),
column(2, actionButton(inputId = "button",
label = "Add Comment"))
),
fluidRow (
column(12, DT::dataTableOutput('data') )
)
)
server <- function(input, output, session) {
observeEvent(input$button, {
df[id==input$selectID, 'Comment']=input$comment
})
output$data <- DT::renderDataTable({
DT::datatable(df,
options = list(orderClasses = TRUE,
lengthMenu = c(5, 10, 20), pageLength = 5))
})
}
shinyApp(ui=ui, server=server)
The column id is not recognized as a column of the data.frame df in df[id == input$selectId, "Comment], replacing id by df$id fixes the error.
In order to rerender the datatable after updating df, df should be a reactive object.
To handle multiple selected id's in the selectInput selectId, you might want to replace df$id == input$selectId by df$id %in% input$selectId
This updated server function should help you with these issues:
server <- function(input, output, session) {
## make df reactive
df_current <- reactiveVal(df)
observeEvent(input$button, {
req(df_current())
## update df by adding comments
df_new <- df_current()
df_new[df_current()$id %in% input$selectID, "Comment"] <- input$comment
df_current(df_new)
})
output$data <- DT::renderDataTable({
req(df_current())
DT::datatable(df_current(),
options = list(orderClasses = TRUE,
lengthMenu = c(5, 10, 20), pageLength = 5))
})
}
I would like to extend this application when data frame exists at the beginning. To be honest, my question is bigger than this where you can find the problem in following link: How to add a new row to uploaded datatable in shiny
Via this question, I am gonna chase the big picture with minors.
I have a currently data frame, 2 columns and 3 rows. First column indicates the current date, other one is to be calculated. new row should be appeared like (Current Date - Like in Excel eg. 11.02.2015-, [Input$1 + "perivious value of column2's row"])
However, I have problem about showing the system date. Additionaly, I cannot produce a new line which gives a warning in newLine!
second version: data can be uploaded. with error: Error in read.table(file = file, header = header, sep = sep, quote = quote, :
'file' must be a character string or connection
Warning: Unhandled error in observer: object of type 'closure' is not subsettable
observeEvent(input$update)
library(shiny)
library(gtools)
runApp(
list(
ui = fluidPage(
pageWithSidebar(
headerPanel("Adding entries to table"),
sidebarPanel(
wellPanel(fileInput('file1', 'Choose Planning File:', accept=c('text/csv', 'text/comma-separated-values,text/plain', '.csv'), multiple = FALSE),
selectInput(inputId = "location",label = "Choose Location",
choices = c('All','Lobau'='LOB', 'Graz'='GRA', 'St. Valentin'='VAL'), selected = "GRA"),
selectInput(inputId = "product",label = "Choose Product",
choices = c('All','Gasoline'='OK', 'Diesel'='DK'), selected = "DK")),
numericInput("spotQuantity", "Enter the Spot Quantity",value=0),
actionButton("action","Confirm Spot Sales"),
numericInput("num2", "Column 2", value = 0),
actionButton("update", "Update Table")),
mainPanel(tableOutput("table1")))
),
server = function(input, output, session) {
values <- reactive({ #
#file.choose()
dm <- as.data.frame(read.csv(input$file1$datapath, sep=";",check.names = FALSE))
})
addData <- observeEvent(input$update, {
values$dm <- isolate({
newLine <- data.frame('Month'=1,'Day ID'=2,'Day'="28-11-2012",'starting inventory'=2,'planned (in kTO)'=2,'lifted (in kTO)'="2",'replenishment (in kTO)'="2", 'Product'="OK",'Location'="GRA", check.names=F)
rbind.data.frame(values$dm,newLine)
})
})
output$table1 <- renderTable({
values()
})
}
)
)
There are multiple issues with your code. You start reactiveValues but you never assign anything to it so no data could hope to be reactive. Also, you likely want to use observeEvent so that each time you hit the Update button you get a response. You can also isolate blocks of code. Furthermore, you should use a data.frame for your new data as the 'type' of the data matters (i.e. numeric, character, etc.). The following works well for me.
library(shiny)
runApp(
list(
ui = fluidPage(
pageWithSidebar(
headerPanel("Adding entries to table"),
sidebarPanel(
numericInput("num2", "Column 2", value = 0),
actionButton("update", "Update Table")),
mainPanel(tableOutput("table1")))
),
server = function(input, output, session) {
values <- reactiveValues(
dm = data.frame(Date = as.Date(c("2015-05-10", "2015-10-07", "2015-03-26","2015-07-18")),
Col2 = c(160, 150, 121, 93))
)
addData <- observeEvent(input$update, {
values$dm <- isolate({
newLine <- data.frame(Date = format(Sys.time(), "%Y-%m-%d"),
Col2 = tail(values$dm$Col2, n=1) - 4)
rbind(values$dm,newLine)
})
})
output$table1 <- renderTable({
values$dm
})
}
)
)