shiny conditionalPanel update problem with output.table-condition - r

I am trying to build an app that displays tables for different years. In some years some tables do not exist. I solved this with conditionalPanel and the condition that the tables exist. This works at first. The problem is that it doesn't work once a table is retrieved that doesn't exist. You can try this on this test page (http://46.231.205.192/Tests/). After opening the app, one table per year is visible. If you go to the next table with >, the error message for 2021 is correctly displayed at the bottom. If you then go back <, the error continues to be displayed, although the table for 2021 exists in that case.
I think the problem arises from conditionalPanel with output.Table_2021 but I can't fix it.
Can you see a solution?
library(shiny)
library(DT)
Table_1 <- data.frame(Antworten = "mean", Total = 3, US = 3.5, FR = 4, IT = 2, male = 0, female = 1)
Table_2 <- data.frame(Antworten = "mean", Total = 2, US = 2.5, FR = 3, IT = 1, male = 1, female = 2)
Table_1_2021 <- data.frame(Antworten = "mean", Total = 4, US = 4.5, FR = 5, IT = 3, male = 3, female = 10)
# in 2021 the Table_2 is missing
tabnames <- c("Table_1", "Table_2")
# Columns
kopfvariablen <- c("region", "sex")
default_vars <- c("region")
# Shiny ----
ui <- fluidPage(
titlePanel(title=div("Tables")),
sidebarLayout(
sidebarPanel(width = 2, tags$style(".well {background-color: #ffffff; border-color: #ffffff}"),
a(br(), br(), br(), br()),
checkboxInput(
inputId = "year_2022",
label = "Tabs: 2022",
value = TRUE),
checkboxInput(
inputId = "year_2021",
label = "Tabs: 2021",
value = TRUE)
),
mainPanel(
align = "center",
actionButton("prevBin", "<", class="btn btn-success"),
actionButton("nextBin", ">", class="btn btn-success"),
selectInput(
inputId = "dataset",
label = "",
choices = tabnames,
width = "60%"),
conditionalPanel(
condition = "input.year_2022 == 1 ",
DT::dataTableOutput("Table_2022")),
conditionalPanel(inline = T,
condition = "input.year_2021 == 1 && output.Table_2021", # I think the problem comes with this line.
DT::dataTableOutput('Table_2021')),
conditionalPanel(inline = T,
condition = "input.year_2021 == 1 && output.Table_2021 == null",
h4("[This Question was not asked in 2021.]", align = "left", style = "color:grey"))
)
))
server = function(input, output, session) {
# "next" and "previous" buttons
output$prevBin <- renderUI({
actionButton("prevBin",
label = "Previous")
})
output$nextBin <- renderUI({
actionButton("nextBin",
label = "Next")
})
observeEvent(input$prevBin, {
current <- which(tabnames == input$dataset)
if(current > 1){
updateSelectInput(session, "dataset",
choices = as.list(tabnames),
selected = tabnames[current - 1])
}
})
observeEvent(input$nextBin, {
current <- which(tabnames == input$dataset)
if(current < length(tabnames)){
updateSelectInput(session, "dataset",
choices = as.list(tabnames),
selected = tabnames[current + 1])
}
})
# Tables 2022 -----
output$Table_2022 <- DT::renderDataTable({
# Data with names from input
data <- get(input$dataset)
data_fin <- data[,1:7]
#subheader as list
subheader <- list()
subheader <- c(subheader, "US", "FR", "IT")
subheader <- c(subheader, "male", "female")
# The header
topheader_txt <- "tr(th(rowspan = 2, 'Values'), th(rowspan = 2, 'Total') , th(class = 'dt-center', colspan = 3, 'region'), th(class = 'dt-center', colspan = 2, 'sex')"
topheader <- parse(text=paste0(topheader_txt, ")"))
#Container for topheader and subheader
sketch = htmltools::withTags(table(
class = 'display',
thead(
eval(topheader),
tr(lapply(rep(subheader, 1), th))
)
)
)
# Table with container
DT::datatable(data_fin,
container = sketch,
rownames = F)
})
# Tables 2021 -----
output$Table_2021 <- DT::renderDataTable({
#Daten aus Auswahl
data <- get(paste0(input$dataset, "_2021"))
data_fin <- data[,1:7]
#subheader list
subheader <- list()
subheader <- c(subheader, "US", "FR", "IT")
subheader <- c(subheader, "male", "female")
topheader_txt <- "tr(th(rowspan = 2, 'Values'), th(rowspan = 2, 'Total') , th(class = 'dt-center', colspan = 3, 'region'), th(class = 'dt-center', colspan = 2, 'sex')"
topheader <- parse(text=paste0(topheader_txt, ")"))
#Container for topheader and subheader
sketch = htmltools::withTags(table(
class = 'display',
thead(
eval(topheader),
tr(lapply(rep(subheader, 1), th))
)
)
)
js <- c(
"function(settings){",
" var datatable = settings.oInstance.api();",
" var table = datatable.table().node();",
" var caption = '2021'",
" $(table).append('<caption style=\"caption-side: top-right; text-align: center; margin: 8px 0; font-size: 2em\">' + caption + '</caption>');",
"}"
)
# Table with container
DT::datatable(data_fin,
container = sketch,
rownames = F,
caption = tags$caption(
style="caption-side: bottom; text-align: left; margin: 8px 0;"
),
extensions = c('Buttons'),
options = list(initComplete = JS(js))
)
})
}
shinyApp(ui, server)

The solution comes with: outputOptions(output, "Table_2021", suspendWhenHidden = FALSE) in the server function.
library(shiny)
library(DT)
Table_1 <- data.frame(Antworten = "mean", Total = 3, US = 3.5, FR = 4, IT = 2, male = 0, female = 1)
Table_2 <- data.frame(Antworten = "mean", Total = 2, US = 2.5, FR = 3, IT = 1, male = 1, female = 2)
Table_1_2021 <- data.frame(Antworten = "mean", Total = 4, US = 4.5, FR = 5, IT = 3, male = 3, female = 10)
# in 2021 the Table_2 is missing
tabnames <- c("Table_1", "Table_2")
# Columns
kopfvariablen <- c("region", "sex")
default_vars <- c("region")
# Shiny ----
ui <- fluidPage(
titlePanel(title=div("Tables")),
sidebarLayout(
sidebarPanel(width = 2, tags$style(".well {background-color: #ffffff; border-color: #ffffff}"),
a(br(), br(), br(), br()),
checkboxInput(
inputId = "year_2022",
label = "Tabs: 2022",
value = TRUE),
checkboxInput(
inputId = "year_2021",
label = "Tabs: 2021",
value = TRUE)
),
mainPanel(
align = "center",
actionButton("prevBin", "<", class="btn btn-success"),
actionButton("nextBin", ">", class="btn btn-success"),
selectInput(
inputId = "dataset",
label = "",
choices = tabnames,
width = "60%"),
conditionalPanel(
condition = "input.year_2022 == 1 ",
DT::dataTableOutput("Table_2022")),
conditionalPanel(inline = T,
condition = "input.year_2021 == 1 && output.Table_2021", # I think the problem comes with this line.
DT::dataTableOutput('Table_2021')),
conditionalPanel(inline = T,
condition = "input.year_2021 == 1 && output.Table_2021 == null",
h4("[This Question was not asked in 2021.]", align = "left", style = "color:grey"))
)
))
server = function(input, output, session) {
outputOptions(output, "Table_2021", suspendWhenHidden = FALSE) # Solution
# "next" and "previous" buttons
output$prevBin <- renderUI({
actionButton("prevBin",
label = "Previous")
})
output$nextBin <- renderUI({
actionButton("nextBin",
label = "Next")
})
observeEvent(input$prevBin, {
current <- which(tabnames == input$dataset)
if(current > 1){
updateSelectInput(session, "dataset",
choices = as.list(tabnames),
selected = tabnames[current - 1])
}
})
observeEvent(input$nextBin, {
current <- which(tabnames == input$dataset)
if(current < length(tabnames)){
updateSelectInput(session, "dataset",
choices = as.list(tabnames),
selected = tabnames[current + 1])
}
})
# Tables 2022 -----
output$Table_2022 <- DT::renderDataTable({
# Data with names from input
data <- get(input$dataset)
data_fin <- data[,1:7]
#subheader as list
subheader <- list()
subheader <- c(subheader, "US", "FR", "IT")
subheader <- c(subheader, "male", "female")
# The header
topheader_txt <- "tr(th(rowspan = 2, 'Values'), th(rowspan = 2, 'Total') , th(class = 'dt-center', colspan = 3, 'region'), th(class = 'dt-center', colspan = 2, 'sex')"
topheader <- parse(text=paste0(topheader_txt, ")"))
#Container for topheader and subheader
sketch = htmltools::withTags(table(
class = 'display',
thead(
eval(topheader),
tr(lapply(rep(subheader, 1), th))
)
)
)
# Table with container
DT::datatable(data_fin,
container = sketch,
rownames = F)
})
# Tables 2021 -----
output$Table_2021 <- DT::renderDataTable({
#Daten aus Auswahl
data <- get(paste0(input$dataset, "_2021"))
data_fin <- data[,1:7]
#subheader list
subheader <- list()
subheader <- c(subheader, "US", "FR", "IT")
subheader <- c(subheader, "male", "female")
topheader_txt <- "tr(th(rowspan = 2, 'Values'), th(rowspan = 2, 'Total') , th(class = 'dt-center', colspan = 3, 'region'), th(class = 'dt-center', colspan = 2, 'sex')"
topheader <- parse(text=paste0(topheader_txt, ")"))
#Container for topheader and subheader
sketch = htmltools::withTags(table(
class = 'display',
thead(
eval(topheader),
tr(lapply(rep(subheader, 1), th))
)
)
)
js <- c(
"function(settings){",
" var datatable = settings.oInstance.api();",
" var table = datatable.table().node();",
" var caption = '2021'",
" $(table).append('<caption style=\"caption-side: top-right; text-align: center; margin: 8px 0; font-size: 2em\">' + caption + '</caption>');",
"}"
)
# Table with container
DT::datatable(data_fin,
container = sketch,
rownames = F,
caption = tags$caption(
style="caption-side: bottom; text-align: left; margin: 8px 0;"
),
extensions = c('Buttons'),
options = list(initComplete = JS(js))
)
})
}
shinyApp(ui, server)

Related

How to copy multiple row and column headers in a rendered table when using DT table copy function?

A similar question was posted but never answered: r shiny problem with datatable to copy a table with table head (colspan)
When running the below reproducible code, I'd like the DT "copy" button to include ALL table column and row headers, when there are multiple headers. So far DT copy only copies one header.
I have the code to do this using an action button/observeEvent() outside of DT (not shown in below code), but if possible I'd instead like to use DT's native copy clipboard function (like in the code below) because of other benefits it offers including but not limited to simplicity.
The images at the bottom better explain.
Maybe it's not possible. But maybe it is!
Reproducible code:
library(DT)
library(shiny)
library(dplyr)
library(htmltools)
library(data.table)
data <-
data.frame(
ID = c(1,1,1,2,2,2,3,3,3),
Period = c(1, 2, 3, 1, 2, 3, 1, 2, 3),
Values = c(5, 10, 15, 0, 2, 4, 3, 6, 9),
State = c("X0","X1","X2","X0","X2","X0", "X2","X1","X9")
)
numTransit <- function(x, from=1, to=3){
setDT(x)
unique_state <- unique(x$State)
all_states <- setDT(expand.grid(list(from_state = unique_state, to_state = unique_state)))
dcast(x[, .(from_state = State[from],
to_state = State[to]),
by = ID]
[,.N, c("from_state", "to_state")]
[all_states,on = c("from_state", "to_state")],
to_state ~ from_state, value.var = "N"
)
}
ui <- fluidPage(
tags$head(tags$style(".datatables .display {margin-left: 0;}")),
h4(strong("Transition table inputs:")),
numericInput("transFrom", "From period:", 1, min = 1, max = 3),
numericInput("transTo", "To period:", 2, min = 1, max = 3),
h4(strong("Output transition table:")),
DTOutput("resultsDT"),
)
server <- function(input, output, session) {
results <-
reactive({
results <- numTransit(data, input$transFrom, input$transTo) %>%
replace(is.na(.), 0) %>%
bind_rows(summarise_all(., ~(if(is.numeric(.)) sum(.) else "Sum")))
results <- cbind(results, Sum = rowSums(results[,-1]))
})
output$data <- renderTable(data)
output$resultsDT <- renderDT(server=FALSE, {
datatable(
data = results(),
rownames = FALSE,
extensions = c("Buttons", "Select"), # for Copy button
selection = 'none', # for Copy button
filter = 'none',
container = tags$table(
class = 'display',
tags$thead(
tags$tr(
tags$th(rowspan = 2,sprintf('To state where end period = %s',input$transTo),style="border-right: solid 1px;"),
tags$th(colspan = 10,sprintf('From state where initial period = %s', input$transFrom))),
tags$tr(mapply(tags$th, colnames(results())[-1],
style = sprintf("border-right: solid %spx;", rep(0, ncol(results()) - 1L)),
SIMPLIFY = FALSE))
)
),
options = list(scrollX = F,
buttons = list(list(extend = "copy",text = 'Copy',exportOptions = list(modifier = list(selected = TRUE)))), # for Copy button
dom = 'Bft', # added 'B' for Copy button
lengthChange = T,
pagingType = "numbers",
autoWidth = T,
info = FALSE,
searching = FALSE)
) %>%formatStyle(c(1), `border-right` = "solid 1px")
})
}
shinyApp(ui, server)
Additional example:
Below is another, simpler example of trying to copy/paste all headers using DT, starting with the example used in post How to copy tableOutput to clipboard? (however adding the "sketch" container to datatable for a second column header to illustrate the copy/paste issue I'm trying to address):
library(shiny)
library(dplyr)
library(DT)
library(htmltools)
df <- mtcars
one <- function(.data, var, na = TRUE) {
return({
.data %>%
group_by(.data[[var]]) %>%
filter(!is.na(.data[[var]])) %>%
tally() %>%
mutate(`%` = 100*n/sum(n))
})
}
# ADDED SKETCH TO ORIGINAL EXAMPLE:
sketch = htmltools::withTags(table(
class = 'display',
thead(tr(th(colspan = 3, 'Table')),
tr(lapply(c('Variable','n','%'),th))
)
))
ui <- fluidPage(
selectInput("var", label = "Select Variable", choices = c("", names(df))),
DTOutput("valu", width = "15%")
)
server <- function(input, output) {
output$valu <- renderDT({
if(input$var != '') {
data <- df %>% one(input$var, na = input$check)
DT::datatable(data,
class = 'cell-border stripe',
rownames = FALSE,
extensions = c("Buttons", "Select"),
selection = 'none',
container = sketch, # ADDED SKETCH CONTAINER TO ORIGINAL EXAMPLE
options =
list(
select = TRUE,
dom = "Bt",
buttons = list(
list(
extend = "copy",
text = 'Copy'))
)) %>% formatStyle(
0,
target = "row",
fontWeight = styleEqual(1, "bold")
)
}
}, server = FALSE)
output$value <- renderTable({
if(input$var != '') {
data <- df %>% one(input$var, na = input$check)
return(data)
}
}, spacing = "xs", bordered = TRUE)
}
shinyApp(ui, server)
Hmm... for Copy I don't know yet. But you can export such a table to Excel and then copy from Excel. I agree this is not highly convenient, but I don't know another way. This requires some JS libraries:
tags$script(src = "xlsx.core.min.js"), # https://github.com/SheetJS/sheetjs/blob/master/dist/xlsx.core.min.js
tags$script(src = "FileSaver.min.js"), # https://raw.githubusercontent.com/eligrey/FileSaver.js/master/dist/FileSaver.min.js
tags$script(src = "tableexport.min.js"), # https://github.com/clarketm/TableExport/tree/master/dist
tags$link(rel = "stylesheet", href = "tableexport.min.css")
library(shiny)
library(DT)
library(shinyjs)
js_export <-
"
var $table = $('#DTtable').find('table');
var instance = $table.tableExport({
formats: ['xlsx'],
exportButtons: false,
filename: 'myTable',
sheetname: 'Sheet1'
});
var exportData0 = instance.getExportData();
var exportData = exportData0[Object.keys(exportData0)[0]]['xlsx'];
instance.export2file(exportData.data, exportData.mimeType, exportData.filename,
exportData.fileExtension, exportData.merges,
exportData.RTL, exportData.sheetname);
"
ui <- fluidPage(
useShinyjs(),
tags$head(
# put these files in the www subfolder
tags$script(src = "xlsx.core.min.js"),
tags$script(src = "FileSaver.min.js"),
tags$script(src = "tableexport.min.js")
),
DTOutput("DTtable"),
actionButton("export", "Export table", class = "btn-primary")
)
sketch <- htmltools::withTags(table(
class = "display",
thead(
tr(
th(rowspan = 2, "Species"),
th(colspan = 2, "Sepal"),
th(colspan = 2, "Petal")
),
tr(
lapply(rep(c("Length", "Width"), 2), th)
)
)
))
server <- function(input, output, session){
output[["DTtable"]] <- renderDT({
datatable(
head(iris, 6),
container = sketch, rownames = FALSE
) %>%
formatPercentage("Sepal.Length") %>%
formatCurrency("Sepal.Width")
})
observeEvent(input[["export"]], {
runjs(js_export)
})
}
shinyApp(ui, server)
Note that it also takes the formatting into account, but I'm wondering why there are some dates :-/

Some errors happened when I tried to change a value's type in reactive() function

I use ggsurvplot to draw a survival curve, and I want to input text to the parameter P value. When the input content is character, it can be displayed correctly, however, when the input content is numeric, an error will occur.
The input data is as follows:
enter image description here
The full code is as follows:
rm(list = ls())
options(scipen = 200)
options(encoding = "UTF-8")
options(stringsAsFactors = TRUE)
library(survival)
library(survminer)
library(shiny)
library(bslib)
library(shinythemes)
mIHC <<- read.csv("0 expr.csv",header=TRUE,row.names=1,check.names = FALSE)
gene_list <<- colnames(mIHC)[3: dim(mIHC)[2]]
gene_list_order = gene_list[order(gene_list)]
ui <- fixedPage(
tags$style(HTML("
.navbar .navbar-header {float: left}
.navbar .navbar-nav {float: right}
")
),
navbarPage(
windowTitle = "GMAP",
fluid = TRUE,
# theme = bs_theme(bootswatch = "flatly",),
title = span("GMAP"),
tabPanel(
"Introduction",
),
tabPanel(
"Survival analysis",
sidebarLayout(
sidebarPanel(width = 5,
selectInput("gene_name", "Gene symbol", choices = gene_list_order),
sliderInput("cutoff_per", "Cutoff percent",
value = 0.5, min = 0, max = 0.99, step = 0.01,
ticks = TRUE)
),
mainPanel(width = 7,
tabsetPanel(
tabPanel("Plot",
plotOutput("surv", width = "420px", height = "400px"),
downloadButton('downloadPlot','Download Plot')),
tabPanel("Summary"),
tabPanel("Table")
)
)
)
),
tabPanel(
"Statistics analysis",
),
tabPanel(
"Heatmap"
),
tabPanel(
"About"
)
)
)
server <- function(input, output, session) {
env <- parent.frame()
plot2 <- reactive({
gene_name = input$gene_name
cutoff_per = input$cutoff_per
surv_gene = mIHC[ , c("OS", "event", gene_name)]
plot(surv_gene$OS, surv_gene$event)
})
surv_plot <- reactive({
gene_name = input$gene_name
cutoff_per = input$cutoff_per
surv_gene = mIHC[ , c("OS", "event", gene_name)]
surv_temp = surv_gene
surv_temp = cbind(surv_temp,surv_temp[,1])
colnames(surv_temp) = c("OS", "event", gene_name, "group")
for (row_place in 1: dim(surv_temp)[1]) {
if(surv_temp[row_place, 3] > quantile(surv_temp[,3], cutoff_per)) {
surv_temp[row_place, "group"] = "high"
} else {
surv_temp[row_place, "group"] = "low"
}
}
surv_gene <- surv_temp
fit <- eval(parse(text = paste0("survfit(Surv(OS, event) ~ group, data = surv_gene)")))
p_val = surv_pvalue(fit, data = surv_gene, method = "1")
p_val = round(as.numeric(p_val),2)
# p_val = as.character(p_val)
# p_val = "abc"
ggsurv_doc <- eval(parse(text = paste0("survfit(Surv(OS, event) ~ group, data = surv_gene)")))
ggpar(
ggsurvplot(ggsurv_doc,
data = surv_gene,
# ggtheme = theme_bw(),
conf.int = F,
censor = T,
palette = c("#DC143C", "#4071B3"),
legend.title = colnames(surv_gene)[3],
pval = paste("P =", p_val),
# pval = T,
legend.labs=c("High", "Low"),
# legend.labs=unique(surv_gene$group),
surv.median.line = "hv",
break.time.by = 12,
xlab = "Time (months)",
),
font.main = 13,
font.submain = 13,
font.x = 13,
font.y = 13,
font.caption = 13,
font.title = 13,
font.subtitle = 13,
font.legend = 13,
font.tickslab = 13,
)
})
output$surv <- renderPlot({
surv_plot()
}, res = 96)
output$downloadPlot <- downloadHandler(
filename = function() {
paste("plot.pdf")
},
content = function(file) {
pdf(file, width = 4.5,height = 4.5)
print(surv_plot(), newpage = FALSE)
dev.off()
}
)
}
shinyApp(ui, server)
enter image description here
It works correctly when p_val is a character, as follow:
p_val = "abc"
enter image description here
This looks wrong:
p_val = surv_pvalue(fit, data = surv_gene, method = "1")
p_val = round(as.numeric(p_val),2)
surv_pvalue returns a data.frame but you seem to be treating it like a numeric. Perhaps try:
p_val = surv_pvalue(fit, data = surv_gene, method = "1")$pval[[1]]
p_val = round(as.numeric(p_val),2)

PickerInput label issue when inline is TRUE

I have a r shiny app in which users have numerous choices they need to pick prior to plotting. In the pickerInput, the label text goes behind the choices. The code below
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(shinyjs)
library(magrittr)
library(dplyr)
ui <- dashboardPage(
dashboardHeader(title = "PickerInput Query", titleWidth=450),
dashboardSidebar( width = 300,
useShinyjs(),
sidebarMenu(id = "tabs")
),
dashboardBody(
uiOutput('groupvar'),
uiOutput('shapetype')
))
server <- function(input, output, session) {
sx <- c("M","F")
arm <- c(rep("DrugA",2),rep("DrugB",2),rep("Placebo_NotDrug",1)) ## content issue if longer than 6 characters
#arm <- c(rep("Drug A",2),rep("Drug B",2),rep("Placebo NotDrug",1)) ## space issue in pickerintput label
d <- data.frame(
subjectID = c(1:100),
sex = c(rep("F",9),rep(sx,43),rep("M",5)),
treatment = c(rep(arm,20)),
race = c(rep("W",76),rep("B",15),rep("O",5),rep("H",1),rep("A",3)),
baseline_result = c(rnorm(50, 4, 3), rnorm(50, 3, 3)),
postbase_result = c(rnorm(50, 5, 3), rnorm(50, 4, 2))
)
dat <- reactive(d)
myfun <- function(df, var1) {
# Rename column of interest
df <- df %>% rename(tempname := !!var1)
df <- df %>% mutate(newvar = tempname) # create newvar
df <- df %>% rename(UQ(var1) := tempname)
}
output$groupvar<-renderUI({
bc<-colnames(dat()[sapply(dat(),class)=="character"])
tagList(
pickerInput(inputId = 'group.var',
label = 'Select group by variable. Then select order, color and shape',
choices = c("NONE",bc[1:length(bc)]), selected="NONE",
width = "350px",
options = list(`style` = "btn-warning"))
)
})
### pick order, color and shape
observeEvent(input$group.var, {
output$shapetype<-renderUI({
req(input$group.var,dat())
if(is.null(input$group.var)){
return(NULL)
}else if(sum(input$group.var=="NONE")==1){
return(NULL)
}else{
mydf <- subset(dat(), dat()[input$group.var] != "")
mydf2 <- myfun(mydf,input$group.var) ## create a new variable named newvar
mygrp <- as.character(unique(mydf2$newvar))
ngrp <- length(mygrp)
myorder <- (1:ngrp)
mycolor <- c("red", "blue", "green", "brown", "orange", "maroon")
myshape <- c("circle", "triangle", "plus", "cross", "diamond", "downtriangle")
lapply(1:ngrp, function(i){
pickerInput(paste0("line.vars.",i),
label = paste(mygrp[i], ":" ),
choices = list(DisplayOrder = myorder,
ShapeColor = mycolor,
ShapeType = myshape,
Group = mygrp), ## how do we hide or disable this 4th item
selected = list( i, mycolor[i], myshape[i], mygrp[i] ),
multiple = T,
inline = TRUE,
width = "275px" , #mywidth,
options = list('max-options-group' = 1, `style` = "btn-primary"))
})
}
})
}, ignoreInit = TRUE)
}
shinyApp(ui, server)
gives the following output:
How can I expand it so that the label Plac
ebo_NotDrug is fully visible to the left of the last dropdown in the image above? Secondly, if the labels happen to have space, then the display gets messy with labels placed in random places as shown in the output below:
update
I found an easy way to rewrite pickerInput in such a way that it takes a new ratio argument, where you can specify the ration of the label and the actual dropdown menu for the case that inline = TRUE. I think this is the most convenient approach. The downside is that you can only choose numbers adding up to 12, where in your case a split up 55% / 45% would suffice.
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(shinyjs)
library(magrittr)
library(dplyr)
pickerInput2 <- function (inputId, label = NULL, choices, selected = NULL, multiple = FALSE,
options = list(), choicesOpt = NULL, width = NULL, inline = FALSE, ratio = c(2,10))
{
if (ratio[1] + ratio[2] != 12) stop("`ratio` has to add up 12.")
choices <- shinyWidgets:::choicesWithNames(choices)
selected <- restoreInput(id = inputId, default = selected)
if (!is.null(options) && length(options) > 0)
names(options) <- paste("data", names(options), sep = "-")
if (!is.null(width))
options <- c(options, list(`data-width` = width))
if (!is.null(width) && width %in% c("fit"))
width <- NULL
options <- lapply(options, function(x) {
if (identical(x, TRUE))
"true"
else if (identical(x, FALSE))
"false"
else x
})
maxOptGroup <- options[["data-max-options-group"]]
selectTag <- tag("select", shinyWidgets:::dropNulls(options))
selectTag <- tagAppendAttributes(tag = selectTag, id = inputId,
class = "selectpicker form-control")
selectTag <- tagAppendChildren(tag = selectTag, shinyWidgets:::pickerSelectOptions(choices,
selected, choicesOpt, maxOptGroup))
if (multiple)
selectTag$attribs$multiple <- "multiple"
divClass <- "form-group shiny-input-container"
labelClass <- "control-label"
if (inline) {
divClass <- paste(divClass, "form-horizontal")
selectTag <- tags$div(class = paste0("col-sm-", ratio[2]), selectTag)
labelClass <- paste(labelClass, paste0("col-sm-", ratio[1]))
}
pickerTag <- tags$div(class = divClass, style = if (!is.null(width))
paste0("width: ", validateCssUnit(width), ";"), if (!is.null(label))
tags$label(class = labelClass, `for` = inputId, label),
selectTag)
shinyWidgets:::attachShinyWidgetsDep(pickerTag, "picker")
}
ui <- dashboardPage(
dashboardHeader(title = "PickerInput Query",
titleWidth=450
),
dashboardSidebar( width = 300,
useShinyjs(),
sidebarMenu(id = "tabs")
),
dashboardBody(
uiOutput('groupvar'),
uiOutput('shapetype')
))
server <- function(input, output, session) {
sx <- c("M","F")
arm <- c(rep("DrugA",2),rep("DrugB",2),rep("Placebo_NotDrug",1)) ## content issue if longer than 6 characters
# arm <- c(rep("Drug A",2),rep("Drug B",2),rep("Placebo NotDrug",1)) ## space issue in pickerintput label
d <- data.frame(
subjectID = c(1:100),
sex = c(rep("F",9),rep(sx,43),rep("M",5)),
treatment = c(rep(arm,20)),
race = c(rep("W",76),rep("B",15),rep("O",5),rep("H",1),rep("A",3)),
baseline_result = c(rnorm(50, 4, 3), rnorm(50, 3, 3)),
postbase_result = c(rnorm(50, 5, 3), rnorm(50, 4, 2)),
stringsAsFactors = FALSE) # people with R < 4.0 need this line to execute your code correctly
dat <- reactive(d)
myfun <- function(df, var1) { # I have simplified your function
df %>% mutate(newvar = !!sym(var1)) # create newvar
}
output$groupvar<-renderUI({
bc<-colnames(dat()[sapply(dat(),class)=="character"])
tagList(
pickerInput2(inputId = 'group.var',
label = 'Select group by variable. Then select order, color and shape',
choices = c("NONE",bc[1:length(bc)]), selected="NONE",
width = "350px",
options = list(`style` = "btn-warning"))
)
})
### pick order, color and shape
observeEvent(input$group.var, {
output$shapetype<-renderUI({
req(input$group.var,dat())
if(is.null(input$group.var)){
return(NULL)
}else if(sum(input$group.var=="NONE")==1){
return(NULL)
}else{
mydf <- subset(dat(), dat()[input$group.var] != "")
mydf2 <- myfun(mydf,input$group.var) ## create a new variable named newvar
mygrp <- as.character(unique(mydf2$newvar))
ngrp <- length(mygrp)
myorder <- (1:ngrp)
mycolor <- c("red", "blue", "green", "brown", "orange", "maroon")
myshape <- c("circle", "triangle", "plus", "cross", "diamond", "downtriangle")
tagList(lapply(1:ngrp, function(i){
pickerInput2(paste0("line.vars.",i),
label = paste(mygrp[i], ":" ),
choices = list(DisplayOrder = myorder,
ShapeColor = mycolor,
ShapeType = myshape,
Group = mygrp), ## how do we hide or disable this 4th item
selected = list( i, mycolor[i], myshape[i], mygrp[i] ),
multiple = T,
inline = TRUE,
width = "275px" , #mywidth,
ratio = c(7,5),
options = list('max-options-group' = 1, `style` = "btn-primary"))
}))
}
})
}, ignoreInit = TRUE)
}
shinyApp(ui, server)
old answer
I figured out, why your code wasn't working for some of us. You are using R >= 4.0 and therefor do not need to set stringsAsFactors = FALSE when defining your data d. Adding this attribute will help run your code on systems with R <= 4.0.
I guess I figured out whats going on. Your pickerInputs have a very narrow width 275px and you have long label names. You can either (i) set the width higher, or (ii) you need to change how pickerInput is splitting up the width between label and dropdown menue. Under the hood it relies on grid.less css classes .col-sm-10 for the dropdown menue and .col-sm-2 for its label. Here it attributes about 17% width to the label (in your case this is too small) and 83% to the dropdown menue (in your case this is too much). You could (A) rewrite the pickerInput function and define your own css classes and then add a custom css where those classes are defined with enough width to display properly (this is what I recommend). Or you can (B) overwrite the default values of gird.less.css with inline CSS adding !important. This is my approach below, just because it is the quickest way to fix this issue. However, it is not a good way, since other elements in your dashboard may rely on those css classes.
Note that I also streamlined myfun. It should still work as expected.
library(shiny)
library(shinydashboard)
library(shinyWidgets)
library(shinyjs)
library(magrittr)
library(dplyr)
ui <- dashboardPage(
dashboardHeader(title = "PickerInput Query",
titleWidth=450
),
dashboardSidebar( width = 300,
useShinyjs(),
sidebarMenu(id = "tabs")
),
dashboardBody(
# custom CSS to overwrite grid.less defaults
tags$head(
tags$style(HTML("
.col-sm-10 {
width: 45% !important;
}
.col-sm-2 {
width: 55% !important;
}
"))),
uiOutput('groupvar'),
uiOutput('shapetype')
))
server <- function(input, output, session) {
sx <- c("M","F")
arm <- c(rep("DrugA",2),rep("DrugB",2),rep("Placebo_NotDrug",1)) ## content issue if longer than 6 characters
# arm <- c(rep("Drug A",2),rep("Drug B",2),rep("Placebo NotDrug",1)) ## space issue in pickerintput label
d <- data.frame(
subjectID = c(1:100),
sex = c(rep("F",9),rep(sx,43),rep("M",5)),
treatment = c(rep(arm,20)),
race = c(rep("W",76),rep("B",15),rep("O",5),rep("H",1),rep("A",3)),
baseline_result = c(rnorm(50, 4, 3), rnorm(50, 3, 3)),
postbase_result = c(rnorm(50, 5, 3), rnorm(50, 4, 2)),
stringsAsFactors = FALSE) # people with R < 4.0 need this line to execute your code correctly
dat <- reactive(d)
myfun <- function(df, var1) { # I have simplified your function
df %>% mutate(newvar = !!sym(var1)) # create newvar
}
output$groupvar<-renderUI({
bc<-colnames(dat()[sapply(dat(),class)=="character"])
tagList(
pickerInput(inputId = 'group.var',
label = 'Select group by variable. Then select order, color and shape',
choices = c("NONE",bc[1:length(bc)]), selected="NONE",
width = "350px",
options = list(`style` = "btn-warning"))
)
})
### pick order, color and shape
observeEvent(input$group.var, {
output$shapetype<-renderUI({
req(input$group.var,dat())
if(is.null(input$group.var)){
return(NULL)
}else if(sum(input$group.var=="NONE")==1){
return(NULL)
}else{
mydf <- subset(dat(), dat()[input$group.var] != "")
mydf2 <- myfun(mydf,input$group.var) ## create a new variable named newvar
mygrp <- as.character(unique(mydf2$newvar))
ngrp <- length(mygrp)
myorder <- (1:ngrp)
mycolor <- c("red", "blue", "green", "brown", "orange", "maroon")
myshape <- c("circle", "triangle", "plus", "cross", "diamond", "downtriangle")
tagList(lapply(1:ngrp, function(i){
pickerInput(paste0("line.vars.",i),
label = paste(mygrp[i], ":" ),
choices = list(DisplayOrder = myorder,
ShapeColor = mycolor,
ShapeType = myshape,
Group = mygrp), ## how do we hide or disable this 4th item
selected = list( i, mycolor[i], myshape[i], mygrp[i] ),
multiple = T,
inline = TRUE,
width = "275px" , #mywidth,
options = list('max-options-group' = 1, `style` = "btn-primary"))
}))
}
})
}, ignoreInit = TRUE)
}
shinyApp(ui, server)

R Shiny - data table with user filled inputs returning null

I used this post to create a table with numeric inputs. The user should fill/change the default values of this table to suit him:
How to have table in Shiny filled by user?
It is working fine however when I am trying to use these inputs afterwards in the server function these inputs are NULL. I don't understand why this is happening.
functions.R
profile_df <- function(from_date, to_date){
m <- get_months(from_date = from_date, to_date = to_date)
Injection <- c()
Withdrawal <- c()
for(i in 1:length(m)){
Injection <- c(Injection, as.character(numericInput(paste0("x", i, "1"), "", value = 100, step = 10, width = '100%')))
Withdrawal <- c(Withdrawal, as.character(numericInput(paste0("x", i, "2"), "", value = 100, step = 10,width = '100%')))
}
dt <- data.frame(Injection, Withdrawal)
dt <- t(dt)
colnames(dt) <- m
row.names(dt) <- c("Injection [MWh/h]", "Withdrawal [MWh/h]")
return(dt)
}
ui.R:
library(shiny)
library(shinydashboard)
library(DT)
dashboardPage(
dashboardHeader(title = "Valuation Tool",
tags$li(class = "dropdown", actionButton("pdf", "Manual", icon = icon("book"),
onclick = "window.open('valuation-tool-manual.pdf')"))),
dashboardSidebar(
sidebarMenu(
menuItem("Storage Valuation", tabName = "storage_valuation", icon = icon("dashboard"))
)
),
dashboardBody(
tags$head(tags$style(HTML('
.main-header .logo {
font-family: "Georgia", Times, "Times New Roman", serif;
font-weight: bold;
font-size: 24px;
}
'))),
tabItems(
#===============
# Tab Item Data
#===============
tabItem(tabName = "storage_valuation",
fluidRow(box(title = "Inputs", solidHeader = TRUE, status = "warning", width = 12,
column(4, dateRangeInput("storage_rng", "Storage Period From/To", startview = 'year', weekstart = 1,
start = paste(as.character(year(Sys.Date())+1), "04", "01", sep = "-"),
end = paste(as.character(year(Sys.Date())+2), "03", "31", sep = "-")
)),
column(2, selectInput("hub", "Gas Hub",choices = c("TTF" = "ICE/TFM", "NCG" = "ICE/GNM", "GSPL" = "ICE/GER"))),
column(2, numericInput("working_gas", "Working Gas [MWh]", min = 0, value = 1000)),
column(2, numericInput("inj_fees", "Injection Fees [/MWh]", min = 0, value = 0.1)),
column(2, numericInput("wit_fees", "Withdrawal Fees [/MWh]", min = 0, value = 0)),br(),
strong("Storage Injection/Withdrawal Profile:"), br(),
fluidRow(column(12,DT::dataTableOutput('profile_table'))),
fluidRow(column(12,DT::dataTableOutput('prof_table')))
)
)
)))
)
server.R:
Sys.setlocale("LC_TIME", "C")
library(shiny)
library(shinydashboard)
library(DT)
source("functions.R", local = T)
function(input, output, session) {
pr_dt <- reactive({profile_df(input$storage_rng[1], input$storage_rng[2])})
output$profile_table <- renderDT({
DT::datatable(pr_dt(), extensions=list("FixedColumns" = list(leftColumns = 1)),
options = list(dom = 't', ordering= FALSE,
fixedColumns = list(leftColumns = 1), scrollX = T), rownames = T,
escape = F,
callback = JS("table.rows().every(function(i, tab, row) {
var $this = $(this.node());
$this.attr('id', this.data()[0]);
$this.addClass('shiny-input-container');
});
Shiny.unbindAll(table.table().node());
Shiny.bindAll(table.table().node());"))
})
m_pr_dt <- reactive({
inj_m <- c()
wit_m <- c()
for(i in 1:length(colnames(pr_dt()))){
print(is.null(input$x11))
}
})
The results are:
TRUE, TRUE, TRUE,... 12 times since I am looping 12 times over the print

Arrange 3 dynamic number of inputs in a row and action on two columns of inputs based on third column

I am building a shiny app to map two different text inputs. I do the matching using string distances but they might be erroneous. So, I am planning to develop a shiny app where the subject matter experts can use the click and dropdown to select match unique data.
If I have fixed number of rows, I can achieve something like below:: However, when I don't know the number of rows in data, how can I dynamically design user-interface to get the required output?
After the user have performed the required mapping. I want to perform some action after the button click. Additionally, if the user has clicked mapped (the check box). I want to leave that row out of the final action.
library(shiny)
set.seed(42)
n_samp = 5 # this comes from the input
indx <- sample(1:20, n_samp)
let_small <- letters[indx]
let_caps <- sample(LETTERS[indx])
# user input
ui <- fluidPage(
selectInput(inputId = "n_samp_choice", label = NULL,
choices = 1:20, width = 500), # number of samples
fluidRow( # first row checkbox
column(width = 2, offset = 0,
checkboxInput("correct1", label = NULL, FALSE)
),
column(width = 2, offset = 0, # text input originial
textInput(inputId = "original1", value = let_small[1], label = NULL )
),
column(width = 5, # options for match
selectInput(inputId = "options1", label = NULL,
choices = let_caps, width = 500)
)
),
fluidRow(
column(width = 2, offset = 0,
checkboxInput("correct1", label = NULL, FALSE)
),
column(width = 2, offset = 0,
textInput(inputId = "original2", value = let_small[2], label = NULL )
),
column(width = 5,
selectInput(inputId = "options2", label = NULL,
choices = let_caps, width = 500)
)
),
fluidRow(
column(width = 2, offset = 0,
checkboxInput("correct1", label = NULL, FALSE)
),
column(width = 2, offset = 0,
textInput(inputId = "original3", value = let_small[3], label = NULL )
),
column(width = 5,
selectInput(inputId = "options3", label = NULL,
choices = let_caps, width = 500)
)
),
fluidRow(
column(width = 2, offset = 0,
checkboxInput("correct1", label = NULL, FALSE)
),
column(width = 2, offset = 0,
textInput(inputId = "original4", value = let_small[4], label = NULL )
),
column(width = 5,
selectInput(inputId = "options4", label = NULL,
choices = let_caps, width = 500)
)
),
fluidRow(
column(width = 2, offset = 0,
checkboxInput("correct1", label = NULL, FALSE)
),
column(width = 2, offset = 0,
textInput(inputId = "original5", value = let_small[5], label = NULL )
),
column(width = 5,
selectInput(inputId = "options5", label = NULL,
choices = let_caps, width = 500)
),
column(width = 2, offset = 0,
uiOutput("actionBut.out")
)
)
)
server <- function(input, output, session) {
output$actionBut.out <- renderUI({
print(input$original1)
session$sendCustomMessage(type="jsCode",
list(code= "$('#text').prop('disabled',true)"))
actionButton("copyButton1","Copy Code")
})
observeEvent(input$copyButton1, {
if(tolower(input$options1) == tolower(input$options1) &
tolower(input$options2) == tolower(input$options2) &
tolower(input$options3) == tolower(input$options3) &
tolower(input$options4) == tolower(input$options4) &
tolower(input$options5) == tolower(input$options5))
{
print("great job")
}else{
unmapp <- which(c(input$correct1, input$correct2,
input$correct3, input$correct4,
input$correct5))
print("The following are unmatched")
print(let_caps[unmapp])
}
})
}
shinyApp(ui = ui, server = server)
You can create a dynamic design using Shiny Modules and UIOutput.
Step1: Create a module to be called by a loop:
moduleUI <- function(id) {
ns <- NS(id)
tagList(
fluidRow( # first row checkbox
column(width = 2, offset = 0,
checkboxInput(ns("correct"), label = NULL, FALSE)
),
column(width = 2, offset = 0, # text input originial
textInput(inputId = ns("original"), value = let_small[id], label = NULL )
),
column(width = 5, # options for match
selectInput(inputId = ns("options"), label = NULL,
choices = let_caps, width = 500)
)
)
)
}
Step2: Create a UIOutput, that will serve as a placeholder for the module.
uiOutput("module_placeholder")
Step3: Add server logic:
I added a numericInput that allows you to simulate different number of rows. E.g.: If you set it to 5, the module will be generated 5 times.
This observer allows you to generate any number of instances of the module.
observe( {
output$module_placeholder <- renderUI( {
lapply(1:input$num, moduleUI)
})
})
The ids of the objects will be 1-correct, 1-original, 1-options for the first module, 2-correct, 2-original, etc. for the second module, ...
It is important because you can access input elements using input[[NAME_OF_THE_ELEMENT]].
So for example I use lapply to check if input$original == input$options for every module. (Similar to your code, but it's general, so it works for any number of modules)
cond <- unlist(lapply(to_check, function(x) {
tolower(input[[paste(x, "original", sep="-")]]) == tolower(input[[paste(x, "options", sep="-")]])
}))
See full code:
library(shiny)
set.seed(42)
n_samp = 10 # this comes from the input
indx <- sample(1:20, n_samp)
let_small <- letters[indx]
let_caps <- sample(LETTERS[indx])
moduleUI <- function(id) {
ns <- NS(id)
tagList(
fluidRow( # first row checkbox
column(width = 2, offset = 0,
checkboxInput(ns("correct"), label = NULL, FALSE)
),
column(width = 2, offset = 0, # text input originial
textInput(inputId = ns("original"), value = let_small[id], label = NULL )
),
column(width = 5, # options for match
selectInput(inputId = ns("options"), label = NULL,
choices = let_caps, width = 500)
)
)
)
}
ui <- fluidPage(
numericInput(inputId = "num", label = "Select number of modules", value = 1, min = 1),
selectInput(inputId = "n_samp_choice", label = NULL,
choices = 1:20, width = 500), # number of samples
uiOutput("module_placeholder"),
uiOutput("actionBut.out")
)
server <- function(input, output, session) {
observe( {
output$module_placeholder <- renderUI( {
lapply(1:input$num, moduleUI)
})
})
output$actionBut.out <- renderUI({
print(input$original1)
session$sendCustomMessage(type="jsCode",
list(code= "$('#text').prop('disabled',true)"))
actionButton("copyButton","Copy Code")
})
observeEvent(input$copyButton, {
checked <- unlist(lapply(1:input$num, function(x) {
if(input[[paste(x, "correct", sep="-")]]) x
}))
if(length(checked) == 0) {
to_check <- 1:input$num
} else {
to_check <- (1:input$num)[-checked]
}
cond <- unlist(lapply(to_check, function(x) {
tolower(input[[paste(x, "original", sep="-")]]) == tolower(input[[paste(x, "options", sep="-")]])
}))
if(all(cond)) {
print("great job")
} else {
unmapp <- which(!cond)
optns <- unlist(lapply(1:input$num, function(x) {
input[[paste(x, "options", sep="-")]]
}))
print("The following are unmatched")
print(optns[to_check][unmapp])
}
})
}
shinyApp(ui = ui, server = server)
uiOutput("mappings")
where you have the inputs now and in the server you place something like this
output$mappings <- renderUI({
tagList(
lapply(
1:length(someList),
function(idx){
fluidRow( # first row checkbox
column(width = 2, offset = 0,
checkboxInput(paste0("correct",idx), label = NULL, FALSE)
),
column(width = 2, offset = 0, # text input originial
textInput(inputId = paste0("original",idx), value = let_small[1], label = NULL )
),
column(width = 5, # options for match
selectInput(inputId = paste0("options",idx), label = NULL,
choices = let_caps, width = 500)
)
)
}
)
)
})
to then get the values you can do something like this
observe({
lapply(
1:length(someList),
function(idx){input[[paste0("correct",idx)]]}
)
})
taking your example it could look something like this
library(shiny)
set.seed(42)
n_samp = 5 # this comes from the input
indx <- sample(1:20, n_samp)
let_small <- letters[indx]
let_caps <- sample(LETTERS[indx])
# user input
ui <- fluidPage(
selectInput(inputId = "n_samp_choice", label = NULL,
choices = 1:20, width = 500), # number of samples
uiOutput("mappings"),
)
server <- function(input, output, session) {
output$actionBut.out <- renderUI({
print(input$original1)
session$sendCustomMessage(type="jsCode",
list(code= "$('#text').prop('disabled',true)"))
actionButton("copyButton1","Copy Code")
})
output$mappings <- renderUI({
tagList(
lapply(
1:5,
function(idx){
fluidRow( # first row checkbox
column(width = 2, offset = 0,
checkboxInput(paste0("correct",idx), label = NULL, FALSE)
),
column(width = 2, offset = 0, # text input originial
textInput(inputId = paste0("original",idx), value = let_small[idx], label = NULL )
),
column(width = 5, # options for match
selectInput(inputId = paste0("options",idx), label = NULL,
choices = let_caps, width = 500)
)
)
}
)
)
})
lapply(
1:5,
function(idx){
observeEvent(input[[paste0("options",idx)]],
{
print(input[[paste0("options",idx)]])
},
ignoreInit = TRUE)
}
)
observeEvent(input$copyButton1, {
if(tolower(input$options1) == tolower(input$options1) &
tolower(input$options2) == tolower(input$options2) &
tolower(input$options3) == tolower(input$options3) &
tolower(input$options4) == tolower(input$options4) &
tolower(input$options5) == tolower(input$options5))
{
print("great job")
}else{
unmapp <- which(c(input$correct1, input$correct2,
input$correct3, input$correct4,
input$correct5))
print("The following are unmatched")
print(let_caps[unmapp])
}
})
}
shinyApp(ui = ui, server = server)

Resources