Example Case: I have a function in my global.R called get_data which returns a list of many items. The reason I don't just put the data in global is so the data can automatically refresh after a certain amount of time
ui.R
my_data <- uiOutput("data") # Doesn't work
### Some more generic manipulation before final use
# The output of my_data will look like the following below.
my_data <- list()
my_data$first_entry <- c("a", "b", "d")
my_data$second_entry <- c("x", "y", "z") # and so on
shinyUI(navbarPage(theme=shinytheme("flatly"),
'App Name',
tabPanel('Title',
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
width=3,
# new box
checkboxGroupButtons(
'name',
'label:',
choices = sort(my_data$first_value),
status = 'primary',
selected = sort(my_data$first_value)[1],
size = 'xs'
# inline = TRUE
))
server.R
shinyServer(function(input, output, session) {
data <- reactive({
invalidateLater(100000,session)
get_data()
})
output$data <- renderUI({
data()
})
})
Two questions:
Is there any way of referencing my_data correctly?
If my function get_data is simply reading a (large) csv which is updated systematically. Is there a better way of doing it than I am currently doing it?
I think you're wondering how to define possible choices= for something within the UI element, when the data is both (1) undefined at the start, and (2) changing periodically. The answer to that is to define it "empty" and update it as the new data is found.
library(shiny)
library(shinyWidgets)
get_data <- function() as.list(mtcars[sample(nrow(mtcars), size=3), sample(ncol(mtcars), size=3)])
logg <- function(...) message(paste0("[", format(Sys.time()), "] ", ...))
shinyApp(
ui = fluidPage(
title = "Hello",
checkboxGroupButtons(inputId = "cb", label = "label:", choices = c("unk"), selected = NULL,
status = "primary", size = "xs"),
br(),
textOutput("txt"),
br(),
textAreaInput("txtarea", NULL, rows = 4)
),
server = function(input, output, session) {
data <- reactive({
logg("in 'data'")
invalidateLater(3000, session)
get_data()
})
observe({
logg("in 'observe'")
req(length(data()) > 0)
updateCheckboxGroupButtons(session = session, inputId = "cb", choices = names(data()))
updateTextAreaInput(session, "txtarea", value = paste(capture.output(str(data())), collapse = "\n"))
})
output$txt <- renderPrint({
logg("in 'txt'")
req(length(data()) > 0)
str(data())
})
}
)
Notice that the definition of checkboxGroupButtons starts with no real choices. I'd prefer to start it empty, but unlike selectInput and similar functions, it does not like starting with an empty vector. It is quickly (nearly-immediately) changed, so I do not see "unk" in the interface.
I demoed two options for "displaying" the data in its raw form: as an output "txt", and as an updatable input "txtarea". I like the latter because it deals well with fixed-width, but it requires an update* function (which is really not a big deal).
Related
I'm struggeling with this one for hours:
In my app a simple test dataset df gets loaded upon starting the app. The user then may add further datasets through a file upload before selecting from a dropdown menu (here selectInput) the dataset he likes to continue working with.
What I'm failing to do:
After starting the app, the reactive df_list should only contain the initial dataset df and the dropdown menu should only hold the values c("", "df"). After adding a dataset through an upload (or else) df_list should be expanded (and the dropdown accordingly). So that I have a list containing all available datasets the user can select from.
But I only manage to create two scenarios: the dropdown menu contains df but I fail to expand the df_list after adding a dataset. Or the dropdown menu stays empty until I add a dataset, so the user has first to add a dataset before he can work with the test dataset.
My code example: I 'simulate' a file upload via an actionButton that creates the data.frame df_upload. Here follows the example without trying to expand df_list with the additional dataset df_upload.
library(shiny)
# df available from start
df <- data.frame(Var = 1:10)
ui <- fluidPage(
selectInput("select", label = "Select data", choices = c("")),
actionButton("upload", "Simulate Upload"),
tableOutput("tabdata")
)
server <- function(input, output, session) {
# reactive that lists all datasets
df_list <- reactive({list(df = df)})
# 'upload' of second df
df_upload <- eventReactive(input$upload, {
data.frame(Var = 11:20)
})
# observes if df_list() gets expanded to update choices
observeEvent(df_list(), {
updateSelectInput(session = session,
inputId = "select",
choices = c("", names(df_list())))
})
# output of selected dataset
output$tabdata <- renderTable({
req(df_list())
df_list()[[input$select]]
})
}
shinyApp(ui, server)
Here one of many things I tried (this adds df_upload succesfully, but fails to show df initially in the dropdown menu after starting the app):
library(shiny)
# df available from start
df <- data.frame(Var = 1:10)
ui <- fluidPage(
selectInput("select", label = "Select data", choices = c("")),
actionButton("upload", "Simulate Upload"),
tableOutput("tabdata")
)
server <- function(input, output, session) {
# reactive that lists all datasets
df_list <- reactive({
df_list <- list(df = df)
# check if there is an uploaded df, and if yes add it to df_list
# does not work, because it does not give me df_list only containing df
# in case no dataset was added yet.
# is.null is not the proper way, because if df_upload does not exist yet,
# it does not yield NULL. I also tried it unsuccessfully
# with exists("df_upload()")
if (!is.null(df_upload())) {
df_list[[2]] <- df_upload()
names(df_list)[2] <- "df_upload"
}
return(df_list)
})
# 'upload' of second df
df_upload <- eventReactive(input$upload, {
data.frame(Var = 11:20)
})
# observes if df_list() gets expanded to update choices
observeEvent(df_list(), {
updateSelectInput(session = session,
inputId = "select",
choices = c("", names(df_list())))
})
# output of selected dataset
output$tabdata <- renderTable({
req(df_list())
df_list()[[input$select]]
})
}
shinyApp(ui, server)
A simple solution using reactiveValues based on #Limey's comment:
library(shiny)
# df available from start
df <- data.frame(Var = 1:10)
reactlog::reactlog_enable()
ui <- fluidPage(
selectInput("select", label = "Select data", choices = c("df")),
actionButton("upload", "Simulate Upload"),
tableOutput("tabdata")
)
server <- function(input, output, session) {
# empty reactiveValues rv to store all datasets in
rv <- reactiveValues()
# store the test df in rv
rv$df <- df
# 'upload' of second df and storing it in rv
observeEvent(input$upload, {
rv$df_upload <- data.frame(Var = 11:20)
})
# update selectInput choices
observe({
updateSelectInput(session = session,
inputId = "select",
choices = names(rv),
selected = "df")
})
# output of selected dataset
output$tabdata <- renderTable({
rv[[input$select]]
})
}
shinyApp(ui, server)
I am trying to create my first shiny app but I am facing a difficulty: in the reproducible example below I am creating a reactive pickerInput (i.e. only show brands proposing a cylindre equal to the input visitors select).
I then want that based on the combination input_cyl and picker_cny (remember that picker_cny depends on input_cyl) to display a table which shows the relevant data for the observation matching the combination input_cyl and picker_cny.
Thank you for your help!
df <- mtcars
df$brand <- rownames(mtcars)
df$brand <- gsub("([A-Za-z]+).*", "\\1", df$brand)
if (interactive()) {
library(shiny)
library(shinyWidgets)
library(shinythemes)
library(shinycssloaders)
# Define UI -----------------------------------------------
ui <- fluidPage(
# Application title
titlePanel("Reproducible Example"),
# Parameters
sidebarLayout(
sidebarPanel(
selectInput(inputId = "input_cyl", label = "Cyl",
choices = c("6", "4", "8")),
pickerInput(
inputId = "picker_cny",
label = "Select Company",
choices = paste0(unique(df$brand)),
options = list(`actions-box` = TRUE),
multiple = TRUE),
width = 2),
# Show Text
mainPanel(
tableOutput("table"),
width = 10)
))
# Define Server ------------------------------------------
server <- function(input, output, session) {
# Reactive pickerInput ---------------------------------
observeEvent(input$input_cyl, {
df_mod <- df[df$cyl == paste0(input$input_cyl), ]
# Method 1
disabled_choices <- !df$cyl %in% df_mod$cyl
updatePickerInput(session = session,
inputId = "picker_cny",
choices = paste0(unique(df$brand)),
choicesOpt = list(
disabled = disabled_choices,
style = ifelse(disabled_choices,
yes = "color: rgba(119, 119, 119, 0.5);",
no = "")
))
}, ignoreInit = TRUE)
output$table <- renderTable(df)
}
}
# Run the application
shinyApp(ui = ui, server = server)
You need a reactive that will handle the change in the input and subset the dataframe before giving it to the output table. For that, you just need to add this block to your server:
data <- reactive({
if (length(input$picker_cny) > 0)
df[df$brand %in% input$picker_cny,]
else
df
})
and update the output$table like this:
output$table <- renderTable(data())
Note: feel free to remove the if else in the reactive to get that:
data <- reactive({
df[df$brand %in% input$picker_cny,]
})
The only difference in that case is: would you show all or nothing when no input has been entered yet. That's a matter of taste.
I have a huge shiny app and met with the below issue. I tried to provide pseudo code for the problem since it is nearly impossible for my expertize to creating working app to demonstrate the problem. I hope i have conveyed with the pseudo code. Kindly help me.
Here is the pseudo code in ui.R file which has an actionButton and a radioButton with underlying selectizeInput and checkboxGroupInput input options and plotOutput to render a plot.
###ui.R#####
tabPanel("Plots",
fluidRow(column(4,wellPanel(
actionButton("action_plot","Generate Plots"),
h6(textOutput("numheat")),
radioButtons("plot_subset",label="Chose by sample or group?",
choices=c("Sample","Group"),selected="Sample"),
conditionalPanel("input.plot_subset=='Sample'",
selectizeInput("view_sample_plot",
label = h5("Select Samples"),
choices = NULL,
multiple = TRUE,
options = list(placeholder = 'select samples to plot')
)
),
conditionalPanel("input.plot_subset=='Group'",
checkboxGroupInput("view_group_plot",
label=h5("Select Groups to View"),
choices="",
selected="")
)
)
),
column(8,
tabsetPanel(
tabPanel(title="Plot",
#textOutput("which_genes"),
h4(textOutput("plot_title")),
plotOutput("plot_rna",height="800px")
)
)
)
)
)
Below is the pseudo server.R code that observes the user input values and updates updateSelectizeInput and updateCheckboxGroupInput with choice from the default loaded R dataset. The user selected choices are used in the subsequent function to generate plot.
###server.R#####
## observed the user input and updated the selectize input and checkBoxGroup input values#####
observe({
print("server-plot-update")
# browser()
data_analyzed = inputData()
tmpgroups = data_analyzed$group_names
tmpdatlong = data_analyzed$data_long
tmpsamples = unique(tmpdatlong$sampleid)
tmpynames = tmpdatlong$
updateSelectizeInput(session,'view_sample_plot',
choices=tmpsamples, selected=NULL)
updateCheckboxGroupInput(session,'view_group_plot',
choices=tmpgroups, selected=NULL)
})
#####code to render plot based on user selection value i.e. by group or samples######
##plot_render utilizes the R functions in Plot.R file to subset the data by user input and generate plot###
plotdatReactive <- reactive({
data_analyzed = inputData
tmp <- plot_data(data_analyzed = data_analyzed,
yname="log2",
orderby="significance",
view_group=input$view_group_plot,
view_sample=input$view_sample_plot)
tmp
})
output$plot_rna <- renderPlot({
if(input$action_plot==0) return()
isolate({
tmp = plotdatReactive()
plot_render( data_analyzed=tmp,
yname = input$heatmapvaluename,
view_group=input$view_group_plot,
view_sample=input$view_sample_plot
)
})
})
Pseudo Code for R functions in plot.R file
####plot.R#####
###function to subset data based on user input samples or groups###
plot_subdat <- function(data_analyzed,
yname="log2",
orderby="significance",
view_sample=NULL,
view_group=NULL) {
if(is.null(view_sample)) view_sample=unique(data_analyzed$sampleid) ## sample names in the dataset
if(is.null(view_group)) view_group=data_analyzed$group_names ## group names in the dataset
tmpdat = data_analyzed$data_long
##subset dataset by **sampleid** if the user selected **samples** in SelectizeInput
tmpdat = tmpdat%>%filter(sampleid%in%view_sample)
subdat = filter(data_analyzed$data_long,unique_id%in%thesegenes,sampleid%in%view_sample)
#subset dataset by **group** if the user selected **group** in checkBoxGroup input
tmpdat = tmpdat%>%filter(group%in%view_group)
subdat = filter(data_analyzed$data_long,unique_id%in%thesegenes,group%in%view_group)
}
###this function generates the plot on the subset of data from the above function#####
plot_data <- function(...) {
tmpdat = plot_subdat(...)
plotdat = tmpdat$data
plotdat
}
The tmpdat and subdat are the inputs to generate the plot in plot_render function. If the user selects and inputs values through selectizeInput then the subsetting of data should be done by samples. If the user selects and input through checkBoxGroupInput then the subsetting should be done by group as commented in the code. I am not unable to subset the data based on user selection i.e. sample/group reactively in plot_subdat function. How can i do this reactively so that the output plot is generated as per the user selection.
I think you might want a reactive expression to subset your data.
Here is a basic working example that includes your inputs, and will plot subsetted data based on input selections reactively.
Edit:
The filtering of data is now in an external .R file, with input variables to filter on passed through.
library(shiny)
source("plot.R", local = TRUE)
ui <- fluidPage(
mainPanel(
tabsetPanel(
tabPanel("Plots",
fluidRow(column(4,wellPanel(
#actionButton("action_plot","Generate Plots"),
h6(textOutput("numheat")),
radioButtons("plot_subset",label="Chose by sample or group?",
choices=c("Sample","Group"),selected="Sample"),
conditionalPanel("input.plot_subset=='Sample'",
selectizeInput("view_sample_plot",
label = h5("Select Samples"),
choices = NULL,
multiple = TRUE,
options = list(placeholder = 'select samples to plot')
)
),
conditionalPanel("input.plot_subset=='Group'",
checkboxGroupInput("view_group_plot",
label=h5("Select Groups to View"),
choices="",
selected="")
)
)),
column(8,
tabsetPanel(
tabPanel(title="Plot",
#textOutput("which_genes"),
h4(textOutput("plot_title")),
plotOutput("plot_rna",height="800px")
)
)
)
)
)
)
)
)
server <- function(input, output, session) {
observe({
updateSelectizeInput(session,'view_sample_plot',
choices=unique(mtcars$gear), selected=NULL)
updateCheckboxGroupInput(session,'view_group_plot',
choices=unique(mtcars$cyl), selected=NULL)
})
plot_prepare <- reactive({
if (input$plot_subset == "Sample") {
plot_subdat(mtcars, "gear", input$view_sample_plot)
} else {
plot_subdat(mtcars, "cyl", input$view_group_plot)
}
})
output$plot_rna <- renderPlot({
plot(plot_prepare())
})
}
shinyApp(ui, server)
plot.R
# plot.R file
library(tidyverse)
plot_subdat <- function(data, variable, choices) {
data %>%
filter((!!sym(variable)) %in% choices) %>%
select(c(!!sym(variable), mpg))
}
I use reactiveValues in Shiny a lot as they are more flexible than just the input and output objects. Nested reactiveValues are tricky since any changes in any of the children also triggers the reactivity linked to the parents. To get around this, I tried to make two different reactiveValues objects ( not two objects in the same list, but two different lists altogether ) and it seems to be working. I'm not able to find any example of this and want to find out if it's suppose to work this way. Are there any issues that might arise because of this?
In this app, there are two reactive values objects - reac1 and reac2. Each of them are linked to a drop down, column1 and column2 respectively. Changing column1 or column2 updates the reactive values with the latest time, updates the plot, and prints the latest values in reac1 and reac2.
ui = fluidPage(
titlePanel("Multiple reactive values"),
sidebarLayout(
sidebarPanel(
selectInput(inputId = "column1", "Reac1", letters, selected = "a"),
selectInput(inputId = "column2", "Reac2", letters, selected = "a")
),
mainPanel(
plotOutput("plot1")
)
)
)
server = function(input, output, session) {
reac1 <- reactiveValues(asdasd = 0)
reac2 <- reactiveValues(qweqwe = 0)
# If any inputs are changed, set the redraw parameter to FALSE
observe({
input$column2
reac2$qweqwe = Sys.time()
})
observe({
input$column1
reac1$asdasd = Sys.time()
})
# Only triggered when the copies of the inputs in reac are updated
# by the code above
output$plot1 <- renderPlot({
print(paste(reac1$asdasd, 'reac1'))
print(paste(reac2$qweqwe, 'reac2'))
hist(runif(1000))
})
}
shinyApp(ui, server)
ReactiveValues are like a read/write version of input$, and you can have several 'independent' variables inside one reactiveValue list. So, you do not need two reactive values in your example. See code below.
ui = fluidPage(
titlePanel("Multiple reactive values"),
sidebarLayout(
sidebarPanel(
selectInput(inputId = "column1", "Reac1", letters, selected = "a"),
selectInput(inputId = "column2", "Reac2", letters, selected = "a")
),
mainPanel(
verbatimTextOutput("txt1"),
verbatimTextOutput("txt2")
)
)
)
server = function(input, output, session) {
reac <- reactiveValues()
#reac2 <- reactiveValues(qweqwe = 0)
# If any inputs are changed, set the redraw parameter to FALSE
observe({
reac$asdasd = input$column1
})
observe({
reac$qweqwe = input$column2
})
# Only triggered when the copies of the inputs in reac are updated
# by the code above
output$txt1 <- renderPrint({
print('output 1')
print(paste(reac$asdasd, 'reac1'))
})
output$txt2 <- renderPrint({
print('output2')
print(paste(reac$qweqwe, 'reac2'))
})
}
shinyApp(ui, server)
I'm trying to add a dynamic ggvis plot to a Shiny app. First, user picks a dimension, and then adds items from that dimension.
For global.R and sample data, see https://gist.github.com/tts/a41c8581b9d77f131b31
server.R:
shinyServer(function(input, output, session) {
# Render a selectize drop-down selection box
output$items <- renderUI({
selectizeInput(
inputId = 'items',
label = 'Select max 4. Click to delete',
multiple = TRUE,
choices = aalto_all[ ,names(aalto_all) %in% input$dim],
options = list(maxItems = 4, placeholder = 'Start typing')
)
})
selected <- reactive({
if (is.null(input$items)) {
return(aalto_all)
}
df <- aalto_all[aalto_all[[input$dim]] %in% input$items, ]
df$keys <-seq(1, nrow(df))
df
})
selected %>%
ggvis(~WoS, ~NrOfAuthors, fill = ~School, key := ~keys) %>%
layer_points() %>%
add_tooltip(show_title) %>%
bind_shiny("gv")
show_title <- function(x=NULL) {
if(is.null(x)) return(NULL)
key <- x["keys"][[1]]
selected()$Title20[key]
}
})
ui.R:
shinyUI(fluidPage(
titlePanel('Some (alt)metric data for articles published since 2010'),
sidebarLayout(
sidebarPanel(
selectInput(
inputId = "dim",
label = "Dimension",
choices = dimensions,
selected = c("Title")),
uiOutput("items")
),
mainPanel(
tabsetPanel(
# I'll add more tabs
tabPanel("Plot with ggvis", ggvisOutput("gv"))
)
)
)
))
This is OK
in the beginning, when there are no items selected, and all data is plotted. This is a hack because the ggvis object throws an error if there is no data served.
when all selected items are deleted (which is the same as 1.) and another dimension is chosen
But when I try to switch to another dimension without deleting the items first, I get this:
Error in `$<-.data.frame`(`*tmp*`, "keys", value = c(1L, 0L)) :
replacement has 2 rows, data has 0
I understand that ggvis is very new and constantly developing, but I suspect that there is merely something in Shiny reactive values that is out of sync. If anyone could point out what I'm doing wrong, thanks a lot!
The error is caused because you have a data.frame with zero rows and have a resulting 1:0.
You can change your selected function to:
selected <- reactive({
if (is.null(input$items)) {
return(aalto_all)
}
df <- aalto_all[aalto_all[[input$dim]] %in% input$items, ]
df$keys <-seq_along(df[,1])
if(nrow(df) == 0){
return(aalto_all)
}
df
})