Create dynamic tabs with their own content - r

I am trying to create an application which dynamically creates different tabs in which there is a version of my initial table filtered according to a variable (among all those selected by the CheckboxGroupInput).
For example if I try with the table iris in which there is a variable Species taking the 3 modalities virginita, setosa and versicolor, then I would like to obtain a first tab with the observations where Species = virginita, a second where Species = setosa etc ...
I found a solution on this forum for dynamically create the tabs but in all of them, the dataset obtained is the one filtered by the last input selected (here versicolor).
I suspect a problem with lapply but I'm new on R and shiny and I can't seem to find a solution.
A little help would be appreciated !
Thanks everyone!
library(shiny)
ui <- pageWithSidebar(
headerPanel = headerPanel('iris'),
sidebarPanel = sidebarPanel(checkboxGroupInput("filter","Choices",c("virginita","setosa","versicolor"), selected=c("virginita","setosa","versicolor"))
),
mainPanel(uiOutput("my_tabs"))
)
server <- function(input, output, session) {
df = iris
output$my_tabs = renderUI({
dt <- list()
for ( i in 1:3) {
output[[paste0("tab",as.character(i))]] <- DT::renderDataTable ({
dt2 <- subset(df, Species==input$filter[i])
return(dt2)
})
dt[[i]] <- DT::DTOutput(paste0("tab",as.character(i)))
}
criteria <- input$filter
n=length(criteria)
myTabs = lapply(1:n, function(j){
tabPanel(criteria[j],
renderUI(dt[[j]])
)
})
do.call(tabsetPanel, myTabs)
})
}
runApp(list(ui = ui, server = server))

There can be problems using for loops in shiny apps:
https://chasemc.github.io/post/the-subtleties-of-shiny-reactive-programming-lapply-and-for-loops/
Instead would use lapply.
Also, I would separate your dynamic creation of output for different tabs to an observe expression (although you could put it at the top of output$my_tabs).
In addition, I noticed that virginica was misspelled in the ui. Otherwise, this includes most of your same code and seems to work.
library(shiny)
library(DT)
ui <- pageWithSidebar(
headerPanel = headerPanel('iris'),
sidebarPanel = sidebarPanel(checkboxGroupInput("filter","Choices",c("virginica","setosa","versicolor"),
selected=c("virginica","setosa","versicolor"))
),
mainPanel(uiOutput("my_tabs"))
)
server <- function(input, output, session) {
df = iris
output$my_tabs = renderUI({
myTabs = lapply(1:length(input$filter), function(i) {
tabPanel(input$filter[i],
DT::DTOutput(paste0("tab",i))
)
})
do.call(tabsetPanel, myTabs)
})
observe(
lapply(1:length(input$filter), function(i) {
output[[paste0("tab",i)]] <- DT::renderDataTable({
subset(df, Species == input$filter[i])
})
})
)
}
runApp(list(ui = ui, server = server))

Related

R Shiny - Construct Two Variables with One Reactive

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!!!

How to use original and updated version of reactive data.table in Shiny?

I'm trying to include a dataset in a Shiny app that first gets read in and used as is, e.g. by displaying it as a table. I would like to allow the user to then be able to manipulate this dataset and update the same table output with the updated dataset.
I can get both parts to work separately - I can display the original data, and I can display reactive updated data. But I can't figure out how to get both to work using the same dataset? The below code is a simple example using iris, with an attempt to display the original dataset and then rbinding it so there are twice as many rows to display in the updated dataset when you hit 'Run'. Note that I've converted the data to data.table because my actual code will be using data.table a lot.
library(shiny)
library(data.table)
iris <- as.data.table(iris)
ui <- fluidPage(
fluidRow(column(4, actionButton("run", "Run"))),
fluidRow(column(12, tabPanel(title = "tab1",
DT::dataTableOutput("table1"))))
)
server <- function(input, output, session) {
irisdata <- reactive({
irisdata <- iris
})
irisdata <- eventReactive(input$run, {
rbind(irisdata(), iris, fill = TRUE)
})
output$table1 <- DT::renderDataTable({
irisdata()
})
}
shinyApp(ui, server)
The rbind results in: Error in : evaluation nested too deeply: infinite recursion / options(expressions=)?
Which is to be expected I suppose as it's self-referencing, but I can't figure out how to write the code otherwise?
Working code of the above example, based on the linked threads in the comments:
library(shiny)
library(data.table)
iris <- as.data.table(iris)
ui <- fluidPage(
fluidRow(column(4, actionButton("run", "Run"))),
fluidRow(column(12, tabPanel(title = "tab1",
DT::dataTableOutput("table1"))))
)
server <- function(input, output, session) {
irisdata <- reactiveValues(data = iris)
observeEvent(input$run, {
irisdata$data <- rbind(irisdata$data, iris, fill = TRUE)
})
output$table1 <- DT::renderDataTable({
irisdata$data
})
}
shinyApp(ui, server)

How to update DT datatable in Shiny when within a module and the selection criteria are changed

I try to make a shiny module to present data from dataframes using the DT package. I would like to use a module to have a standard set up of DT-table options like language and others.
I want the user to be able to select different subsets of the data interactively and thereafter be able to see the data as a DT-table. The selection of the subset will be generated outside the module because I would like the subset to be available for other uses, for example to be exported to a csv-file.
This works as intended when I don't use a module for making the DT table. When I put the code inside a module, a table is produced when the app starts. But when the selection criteria are changed, the table don't update.
I have included an app illustrating the problem. Table 1 is generated without using shiny module and updates as expected when the selection changes. Table 2 is output using the module and don't update when the selection is changed.
I'm running R-studio 1.1.463, R version 3.5.2 and DT version 0.5.
require("DT")
require("shiny")
# module for presenting data using DT
showDTdataUI <- function(id) {
ns <- NS(id)
tagList(
DT::dataTableOutput(ns("table"))
)
}
showDTdata <- function(input, output, session, DTdata) {
output$table <- renderDataTable({
DT::datatable(DTdata)
})
}
# User interface
ui <-
fluidPage(
sidebarLayout(
sidebarPanel(id="DT",
width = 4,
helpText(h4("Select")),
selectInput("selectedSpecies", label = "Species",
choices = c("setosa","versicolor","virginica"),
selected = "versicolor")
),
mainPanel(
h3("Table 1. Presenting selected data from Iris" ),
DT::dataTableOutput("table"),
h5(br("")),
h3("Table 2. Presenting selected data from Iris using shiny module"),
showDTdataUI(id="testDTModule")
)
)
)
# Define server logic ----
server <- function(session, input, output) {
selectedIris <- reactive ( {
selected <- iris[which(iris$Species==input$selectedSpecies),]
selected
})
output$table <- renderDataTable({
DT::datatable(selectedIris())
})
callModule(showDTdata, id="testDTModule", DTdata=selectedIris())
}
# Run the app ----
shinyApp(ui = ui, server = server)
You have to pass the reactive conductor in showDTdata:
showDTdata <- function(input, output, session, DTdata) {
output$table <- renderDataTable({
DT::datatable(DTdata()) # not datatable(DTdata)
})
}
callModule(showDTdata, id="testDTModule", DTdata=selectedIris) # not DTdata=selectedIris()
Does this do what you want? I removed your functions and added the selection ='multiple' to table 1 (tableX) so that we can then listen to tableX_rows_selected
P.S.: I have noticed that if you first load DT and then shiny, that the whole app won't work anymore. This is a bit weird since we call all datatable functions with DT::... but, you do get a warning message that some parts of DT are masked by shiny or viceversa.
require("shiny")
require('DT')
# User interface
ui <-
fluidPage(
sidebarLayout(
sidebarPanel(id="DT",
width = 4,
helpText(h4("Select")),
selectInput("selectedSpecies", label = "Species",
choices = c("setosa","versicolor","virginica"),
selected = "versicolor")
),
mainPanel(
h3("Table 1. Presenting selected data from Iris" ),
DT::dataTableOutput("tablex"),
br(),
h3("Table 2. Presenting selected data from Iris using shiny module"),
DT::dataTableOutput("table2")
)
)
)
# Define server logic ----
server <- function(session, input, output) {
values <- reactiveValues(rowselect = numeric())
selectedIris <- reactive ( {
selected <- iris[which(iris$Species==input$selectedSpecies),]
selected
})
output$tablex <- renderDataTable({
DT::datatable(selectedIris(), selection = 'multiple')
})
IrisSelected <- reactive({
df <- iris[c(input$tablex_rows_selected), ]
df
})
output$table2 <- renderDataTable({
req(nrow(IrisSelected()) > 0)
DT::datatable( IrisSelected())
})
}
# Run the app ----
shinyApp(ui = ui, server = server)
Without knowing of the shiny module approach, I would have probably written it like a normal function. The app below works but I am curious now after seeing the answer by #Stephane what the advantages are of using callModule approach over regular function approach
require("DT")
require("shiny")
makeTable <- function(dataframe) { DT::datatable(dataframe) %>%
formatStyle(names(dataframe), background = '#fff')
}
# User interface
ui <-
fluidPage(
sidebarLayout(
sidebarPanel(id="DT",
width = 4,
helpText(h4("Select")),
selectInput("selectedSpecies", label = "Species",
choices = c("setosa","versicolor","virginica"),
selected = "versicolor")
),
mainPanel(
dataTableOutput('Table1')
)
)
)
# Define server logic ----
server <- function(session, input, output) {
selectedIris <- reactive ( {
selected <- iris[which(iris$Species==input$selectedSpecies),]
selected
})
output$Table1 <- renderDataTable(makeTable(selectedIris()))
}
# Run the app ----
shinyApp(ui = ui, server = server)

How to render a varying numbers of tables based on user input in R Shiny?

In an R shiny app I'm attempting to render a varying number of tables based on user input. As an example, I've created the following app:
# ui.R
fluidPage(
numericInput("numeric.input", "Select Number of Tables:", 0, min = 0),
tableOutput("table")
)
# server.R
data(iris)
function(input, output) {
output$table <- renderTable({
head(iris)
})
}
What I'd like for this app to do is to generate a number of tables dependent on the value selected for numeric.input. Currently numeric.input doesn't influence the app and is only shown for example. If numeric.input is set to zero, I'd like for the app to display no copies of the table, if numeric.input is set to one, I'd like for the app to display one copy of the table, etc.
Is something like this possible in R Shiny?
I've solved the issue by using the R Shiny Gallery app on creating UI in a loop, but rendering the UI loop within the R Shiny server. The following code works correctly:
# ui.R
fluidPage(
numericInput("numeric.input", "Select Number of Tables:", 1, min = 1),
uiOutput("tables")
)
# server.R
data(iris)
function(input, output) {
# Rendering tables dependent on user input.
observeEvent(input$numeric.input, {
lapply(1:input$numeric.input, function(i) {
output[[paste0('table', i)]] <- renderTable({
head(iris)
})
})
})
# Rendering UI and outputtign tables dependent on user input.
output$tables <- renderUI({
lapply(1:input$numeric.input, function(i) {
uiOutput(paste0('table', i))
})
})
}
Your approach is simple and straightforward. Just putting out the usage of the insertUI and removeUI for this purpose based on the link provided in comments by #r2evans.
ui <- fluidPage(
numericInput("numericinput", "Select Number of Tables:", 0, min = 0),
tags$div(id = 'tabledisplay')
)
server <- function(input, output) {
inserted <- c()
observeEvent(input$numericinput, {
tablenum <- input$numericinput
id <- paste0('table', tablenum)
if (input$numericinput > length(inserted)) {
insertUI(selector = '#tabledisplay',
ui = tags$div(h4(
paste0("Table number ", input$numericinput)
), tags$p(renderTable({
head(iris)
})),
id = id))
inserted <<- c(id, inserted)
}
else {
inserted <- sort(inserted)
removeUI(selector = paste0('#', inserted[length(inserted)]))
inserted <<- inserted[-length(inserted)]
}
})
}
shinyApp(ui, server)

using a function to renderUI(selectInput()) in Shiny app

Here's server.r
server <- function(input, output) {
output$species <- renderUI({
selectInput("species",
label = "blah",
choices = as.list(unique(iris$Species)))
})
}
Then over in ui.r
ui <- fluidPage(
fluidRow(
uiOutput("species")
)
This works as expected, a drop down select input appears like this:
Since I have multiple features I need to create a similar filter for in my actual data frame, I tried to do the same with a function:
In server.r
outputFilters <- function(id, df) {
output$id <- renderUI({
selectInput(id,
label = "blah",
choices = as.list(unique(df$id)))
})
}
outputFilters("species", iris)
Then in ui.r same as before uiOutput("species")
However, now no drop down appears. Presumably my function is flawed. How can I use a function to generate the drop downs?
Note that you could also do without a separate function in this case, by wrapping the desired ui component in lapply, or putting the lapply within the uiOutput to create all inputs at once, below is an example for the both two cases. Hope this helps!
ibrary(shiny)
ui <- fluidPage(
uiOutput('Species'),
uiOutput('Sepal.Length'),
h2('All inputs: '),
uiOutput('my_inputs')
)
server <- function(input, output) {
# Use lapply to create multiple uiOutputs.
lapply(colnames(iris), function(x){
output[[x]] <- renderUI({
selectInput(paste0('input_',x),
label = x,
choices = as.list(unique(iris[['x']])))
})
})
# Create all dropdown's at once.
output$my_inputs <- renderUI({
lapply(colnames(iris), function(x){
selectInput(paste0('input_',x),
label = x,
choices = as.list(unique(iris)))
})
})
}
shinyApp(ui, server)
Your problem is that each UI element needs its own id in the output
outputFilters <- function(id, df) {
output[[id]] <- renderUI({
selectInput(id,
label = "blah",
choices = as.list(unique(df[[id]])))
})
}
now as long as id is a string in the function input it should generate the output element and you can refer with said id
You could then even use lapply to iterate over numerous, kind of how florian suggests.

Resources