How to Organize Large Shiny Apps

How to organize large Shiny apps?

After addition of modules to R shiny. Managing of complex structures in shiny applications has become a lot easier.

Detailed description of shiny modules:Here

Advantages of using modules:

  • Once created, they are easily reused
  • ID collisions is easier to avoid
  • Code organization based on inputs and output of modules

In tab based shiny app, one tab can be considered as one module which has inputs and outputs. Outputs of tabs can be then passed to other tabs as inputs.

Single-file app for tab-based structure which exploits modular thinking. App can be tested by using cars dataset. Parts of the code where copied from the Joe Cheng(first link). All comments are welcome.

# Tab module
# This module creates new tab which renders dataTable

dataTabUI <- function(id, input, output) {
# Create a namespace function using the provided id
ns <- NS(id)

tagList(sidebarLayout(sidebarPanel(input),

mainPanel(dataTableOutput(output))))

}

# Tab module
# This module creates new tab which renders plot
plotTabUI <- function(id, input, output) {
# Create a namespace function using the provided id
ns <- NS(id)

tagList(sidebarLayout(sidebarPanel(input),

mainPanel(plotOutput(output))))

}

dataTab <- function(input, output, session) {
# do nothing...
# Should there be some logic?

}

# File input module
# This module takes as input csv file and outputs dataframe
# Module UI function
csvFileInput <- function(id, label = "CSV file") {
# Create a namespace function using the provided id
ns <- NS(id)

tagList(
fileInput(ns("file"), label),
checkboxInput(ns("heading"), "Has heading"),
selectInput(
ns("quote"),
"Quote",
c(
"None" = "",
"Double quote" = "\"",
"Single quote" = "'"
)
)
)
}

# Module server function
csvFile <- function(input, output, session, stringsAsFactors) {
# The selected file, if any
userFile <- reactive({
# If no file is selected, don't do anything
validate(need(input$file, message = FALSE))
input$file
})

# The user's data, parsed into a data frame
dataframe <- reactive({
read.csv(
userFile()$datapath,
header = input$heading,
quote = input$quote,
stringsAsFactors = stringsAsFactors
)
})

# We can run observers in here if we want to
observe({
msg <- sprintf("File %s was uploaded", userFile()$name)
cat(msg, "\n")
})

# Return the reactive that yields the data frame
return(dataframe)
}
basicPlotUI <- function(id) {
ns <- NS(id)
uiOutput(ns("controls"))

}
# Functionality for dataselection for plot
# SelectInput is rendered dynamically based on data

basicPlot <- function(input, output, session, data) {
output$controls <- renderUI({
ns <- session$ns
selectInput(ns("col"), "Columns", names(data), multiple = TRUE)
})
return(reactive({
validate(need(input$col, FALSE))
data[, input$col]
}))
}

##################################################################################
# Here starts main program. Lines above can be sourced: source("path-to-module.R")
##################################################################################

library(shiny)

ui <- shinyUI(navbarPage(
"My Application",
tabPanel("File upload", dataTabUI(
"tab1",
csvFileInput("datafile", "User data (.csv format)"),
"table"
)),
tabPanel("Plot", plotTabUI(
"tab2", basicPlotUI("plot1"), "plotOutput"
))

))

server <- function(input, output, session) {
datafile <- callModule(csvFile, "datafile",
stringsAsFactors = FALSE)

output$table <- renderDataTable({
datafile()
})

plotData <- callModule(basicPlot, "plot1", datafile())

output$plotOutput <- renderPlot({
plot(plotData())
})
}

shinyApp(ui, server)

R [Shiny]: How to make reactive shiny apps which display dynamic systems models?

Needed some minor adjustments. Try this

library(shiny)
library(deSolve)
library(ggplot2)
library(gridExtra)

ui <- fluidPage(
sliderInput("iDesired.Growth", "Desired.Growth", min = 0, max = 0.15, step = 0.01, value = 0.07),
sliderInput("iDepreciation", "Depreciation", min = 0, max = 0.15, step = 0.01, value = 0.07),

plotOutput(outputId = "arrange")
)

server <- function(input, output, session) {

growth <- reactiveVal(1)
dep <- reactiveVal(1)

START <-0; FINISH<-200; STEP<-0.25
simtime <- seq(START, FINISH, by = STEP)
stocks <- c(sCapital=5, sResource=1000)

x.Resource <- seq(0,1000, by=100)
y.Efficiency<- c(0,0.25,0.45,0.63,0.75,0.86,0.92, 0.96,0.98, 0.99,1.0)
func.Efficiency <- approxfun(x=x.Resource,
y=y.Efficiency,
method = "linear",
yleft = 0, yright = 1.0)

observe({

model <- function(time,stocks,auxs){
with(as.list(c(stocks,auxs)),{
aExtr.Efficiency <- func.Efficiency(sResource)

fExtraction <- aExtr.Efficiency*sCapital

aTotal.Revenue <- aRevenue.Per.Unit * fExtraction
aCapital.Costs <- sCapital *0.1
aProfit <- aTotal.Revenue - aCapital.Costs
aCapital.Funds <- aFraction.Reinvested * aProfit
aMaximum.Investment <- aCapital.Funds/aCost.Per.Investment

aDesired.Investment <- sCapital * aDesired.Growth

fInvestment <- min(aMaximum.Investment,
aDesired.Investment)
fDepreciation <- sCapital * aDepreciation

dS_dt <- fInvestment -fDepreciation
dR_dt <- -fExtraction

return(list(c(dS_dt, dR_dt),
DesiredInvestment=aDesired.Investment,
MaximumInvestment=aMaximum.Investment,
Investment=fInvestment,
Depreciation=fDepreciation,
Extraction=fExtraction))
})
}

growth(input$iDesired.Growth)
dep(input$iDepreciation)

auxs <- list(aDesired.Growth = growth(),
aDepreciation = dep(),
aCost.Per.Investment = 2,
aFraction.Reinvested =0.12,
aRevenue.Per.Unit =3.00)

o <- data.frame(ode(y=stocks, times=simtime, func = model,
parms = auxs, method = "euler"))


flow_plot <- ggplot(data = o, mapping = aes(time, Investment)) + theme_classic() +
geom_line(data = o, mapping = aes(time, Investment), size = 1, color = "blue", linetype =2)+
geom_line(data = o, mapping = aes(time, Depreciation), size = 1, color = "red",linetype =2)+
geom_line(data = o, mapping = aes(time, Investment-Depreciation), size = 1, color = "black")

f <- renderPlot({
flow_plot <- ggplot(data = o, mapping = aes(time, Investment)) + theme_classic() +
geom_line(data = o, mapping = aes(time, Investment), size = 1, color = "blue", linetype =2)+
geom_line(data = o, mapping = aes(time, Depreciation), size = 1, color = "red",linetype =2)+
geom_line(data = o, mapping = aes(time, Investment-Depreciation), size = 1, color = "black")
})

capital_plot <- ggplot(data = o, mapping = aes(time, sCapital)) + theme_classic() +
geom_line(data = o, mapping = aes(time, sCapital), size = 1, color = "blue", linetype =2)+
geom_line(data = o, mapping = aes(time, Extraction), size = 1, color = "black")

ressource_plot <- ggplot(data = o, mapping = aes(time, sCapital)) + theme_classic() +
geom_line(data = o, mapping = aes(time, sResource), size = 1, color = "black", linetype =1)

output$arrange <- renderPlot({
grid.arrange(flow_plot,capital_plot,ressource_plot, nrow = 3)
})

})
}

shinyApp(ui, server)

How are you handling theme updates across multiple Shiny apps?

For those who might be interested, I built the theme into a package I was developing. Within my package's root dir, I added an inst folder and, inside that, a www dir. I added the organisation logo and some extra CSS that couldn't be easily configured with {fresh}.

To set the theme, I created a function like this:

set_theme<-function(){

theme<-fresh::create_theme(

fresh::adminlte_color(
light_blue = "#383F48",
aqua = "#094357",
green = "#094357",
blue = "#383F48"
),

fresh::adminlte_global(
box_bg = "#FFFFFF",
info_box_bg = "#D1E0E5"
),

fresh::adminlte_vars(
"sidebar-width" = "275px",
"sidebar-dark-bg" = "#3A3F46",
"sidebar-dark-hover-color" = "#FFB151",
"btn-border-radius" = "1px"
)
)

shiny::addResourcePath('www', system.file("www", package = "myPackage"))

return(theme)

}

I used shiny::addResourcePath to create a resource path to link the package's inst/www folder to the project within which myPackage::set_theme() is called. Then, within my Shiny dashboards:

library(myPackage)

theme <- myPackage::set_theme()

ui<-(
fresh::use_theme(theme)
...
tags$head(tags$link(rel = "stylesheet", type = "text/css", href =
"www/style.css"))
...
)

Make note of the www/ in the href argument. If you were to keep these files 'locally' within your Shiny app, you'd create a www dir in root and forego the www/ prefix. When calling files from your package, simply include the www/

How to convert a piece of R code into Shiny apps?

My first suggestion is just to look at tutorials on shiny, they give a great overview on how to start a project: https://shiny.rstudio.com/tutorial/

I didn't know a thing about programming a few years back, so I understand it can be hard figuring out where to start, so I wanted to give you an idea of how to implement a function, and use shiny inputs to make the resulting table/plot be dynamic.

I switched up your code to be easier to reproduce for myself. I hope this gives you the starting point you need:

library(tidyverse)
library(ggplot2)
library(shiny)

WR_sim_OC <- function(MPG, CYL, DISP){

results <- mtcars%>% #Function to make a table
filter(cyl > CYL,
mpg > MPG,
disp > DISP)

out1 <- ggplot(data = results, aes(x = mpg, y = disp, group = cyl)) +
geom_line(aes(color = hp), size = 1) #Function to make a plot

list(results, out1) #List to create table and function
}

ui <- fluidPage(
numericInput("MilesPerGallon", "mpg", value = 15),
numericInput("Cylinders", "cyl", value = 4),
numericInput("Displacement", "disp", value = 200),
tableOutput("TABLE"),
plotOutput("PLOT")
)

server <- function(input, output, session) {
output$TABLE<-renderTable({
req(input$MilesPerGallon, input$Cylinders, input$Displacement) #Requires all three inputs before it makes the table

WR_sim_OC(input$MilesPerGallon, input$Cylinders, input$Displacement)[1] #Only pulling the table from the function

})

output$PLOT<-renderPlot({
req(input$MilesPerGallon, input$Cylinders, input$Displacement) #Requires all three inputs before it makes the plot

WR_sim_OC(input$MilesPerGallon, input$Cylinders, input$Displacement)[2] #Only pulling the plot from the function
})
}

shinyApp(ui, server)

Essentially on the server side where you render the plot or table, you use those inputs from the ui as the dynamic points in your function. I used req() for both of the renderTable and renderPlot to make sure the inputs are filled out before it makes the table plot. Best of luck!

R Shiny: Using a Large SpatialPolygonsDataFrame in my Shiny App

When the Shiny app is launched, it looks only for server.R and ui.R (see ?shiny::runApp). load_data.R is thus not sourced.

Try adding source("load_data.R") to server.R.

Music files do not play in shiny

In order to use music from the new directory, you will also need to change addResourcePath to the relevant path. In this case, if you use addResourcePath("Music", "Music") this should be enough for it to work.



Related Topics



Leave a reply



Submit