15 Graphical interfaces with Shiny
You have had a preview of a shiny
interface in the previous section with the interactive parameter input in a Rmarkdown file.
Using the shiny
package, you can actually easily build an interactive graphical user interface (GUI) in which you will be able to set parameters (values, files…), visualize the outputs (plots, images, tables…), and write files as output. This is very useful when you have to always repeat the same task with a varying input parameter, for example.
15.1 Stand-alone shiny application
A shiny application is an app.R
file (it must be named like that) containing 3 elements:
ui
: definition of the interface layout (where are the buttons, text input, plot output, etc.) and the input parametersserver
: definition of the various actions to perform with the input parametersshinyApp(ui, server)
: launches the shiny app with the above defined parameters
In Rstudio, create a new “Shiny web app”. It will create an app.R
file containing this:
library(shiny)
# Define UI for application that draws a histogram
<- fluidPage(
ui # Application title
titlePanel("Old Faithful Geyser Data"),
# Sidebar with a slider input for number of bins
sidebarLayout(
sidebarPanel(
sliderInput("bins",
"Number of bins:",
min = 1,
max = 50,
value = 30)
),# Show a plot of the generated distribution
mainPanel(
plotOutput("distPlot")
)
)
)# Define server logic required to draw a histogram
<- function(input, output, session) {
server $distPlot <- renderPlot({
output# generate bins based on input$bins from ui.R
<- faithful[, 2]
x <- seq(min(x), max(x), length.out = input$bins + 1)
bins
# draw the histogram with the specified number of bins
hist(x, breaks = bins, col = 'darkgray', border = 'white')
})
}# Run the application
shinyApp(ui = ui, server = server)
Run it by clicking “Run App”: a window opens and you can pan the slider and see the resulting output.
In case you want to clean up the code, you can separate app.R
into ui.R
and server.R
. No need to add the shinyApp(ui = ui, server = server)
line in that case.
All user-defined functions and variable definitions can be defined in a global.R
file that will be sourced by default when launching the app.
15.1.1 The layout
In the ui <- fluidPage(...)
item, you define the layout of your application. In the above example:
titlePanel("Title")
creates a titlesidebarLayout()
separates the layout in a short one on the left (sidebarPanel()
) and a main one on the right (mainPanel()
)sliderInput("name_of_slider", "text to display", min=min_value, max=max_value,
value=current_value, step=step_value)
creates a slider to input a value. This value will be retrieved byinput$name_of_slider
in theserver()
function.plotOutput("name_of_plot")
plots the result ofoutput$name_of_plot
defined in theserver()
function.
See the guide to application layout for more layout options. I also recommend taking a look at the packages shinydashboard
and shinymaterial
.
15.1.2 The server
In the server <- function(input, output){...}
function, you define the various actions and outputs in reaction to an input change.
In the above example, we define output$distPlot
as a renderPlot()
function whose results depends on input$bins
. The plot is rendered in the ui
by plotOutput("distPlot")
.
15.1.3 Various useful functions
15.1.3.1 Input
15.1.3.1.2 Checkbox
# # # # # # # # #
# In ui:
checkboxInput("checkbox_name", "Text to display", value=FALSE)
# # # # # # # # #
# In server:
$checkbox_name #TRUE or FALSE input
15.1.3.1.3 Text/numeric
# # # # # # # # #
# In ui:
textInput("text_name",
label = "Text to display",
value = "initial value",
width = '100%')
textAreaInput("text_name",
label="Text to display",
value = "initial_value",
rows = 5) %>%
::tagAppendAttributes(style = 'width: 100%;')
shiny
numericInput("value_name", 'Text to display', value=0)
# # # # # # # # #
# In server, retrieve it as:
$text_name
input$value_name input
15.1.3.1.4 Slider
# # # # # # # # #
# In ui:
sliderInput("slider_name", "Text to display",
min = 1,
max = 50,
step= 1,
value = 30)
# # # # # # # # #
# In server, retrieve it as:
$slider_name input
15.1.3.1.5 File
# # # # # # # # #
# In ui:
fileInput("file_in",
"Choose input file:", accept = c(".txt")
)# # # # # # # # #
# In server, retrieve it as:
$file_in$datapath
input# For example, read it as a data.frame with myData():
<- reactive({
myData <- input$file_in
inFile if (is.null(inFile)) {
return(NULL)
else {
} return(read.table(inFile$datapath, header=TRUE))
} })
15.1.3.2 Output
15.1.3.2.1 Display a plot
# # # # # # # # #
# In ui:
plotOutput("plot_name", height = 600,
click = "plot_click", # to retrieve the click position
dblclick = "plot_dblclick", # to retrieve the double click position
hover = "plot_hover", # to retrieve the mouse position
brush = "plot_brush" # to retrieve the rectangle coordinates
)# # # # # # # # #
# In server:
$plot_name <- renderPlot({
output# do plot:
plot(...)
# or
ggplot(...)
})
If you want an interactive plot, use plotlyOutput()
and renderPlotly()
instead.
15.1.3.2.2 Display text
# # # # # # # # #
# In ui:
textOutput("text_to_display")
# Verbatim text (fixed width characters):
verbatimTextOutput("text_to_display")
# # # # # # # # #
# In server:
$text_to_display <- renderText({ "some text" })
output$text_to_display <- renderPrint({ "some text" }) output
15.1.3.2.3 Display a table
# # # # # # # # #
# In ui:
tableOutput("table_to_display")
# # # # # # # # #
# In server:
$table_to_display <- renderTable({ df }) output
Or in case you want interactive tables, use the package datatable:
library(DT)
# # # # # # # # #
# In ui:
dataTableOutput("table_to_display")
# # # # # # # # #
# In server:
$table_to_display <- renderDataTable({ df }) output
15.1.3.2.4 Reactive events
In case you want the plots or text display to react to a change in input value, you can wrap the corresponding code in the reactive()
function on the server side:
# # # # # # # # #
# In ui:
fileInput("file_in",
"Choose input file:", accept = c(".txt")
),checkboxInput("header", "Header?", value=TRUE),
selectInput("menu", "Columns to display",
choices=1, selected = 1, multiple = TRUE),
tableOutput("table")
# # # # # # # # #
# In server:
<- reactive({
myData <- input$file_in
inFile if (is.null(inFile)) {
return(NULL)
else {
} <- read.table(inFile$datapath, header=input$header)
df updateSelectInput(session, "menu", choices=1:ncol(df), selected=input$menu)
return(df)
}
})$table <- renderTable( myData()[,sort(as.numeric(input$menu))] ) output
The various input default values can be updated using the following functions on the server side:
# Dropdown menu
updateSelectInput(session, "menu_name", choices=new_choices)
# Text
updateTextInput(session, "text_name", value = new_value)
# Numeric
updateNumericInput(session, "value_name", value = new_value)
15.1.3.2.5 Writing a file
This is not a function of shiny, but you may want to write a text file. If this comes from a data.frame
, you can use the function write.table()
:
<- data.frame(x=1:10,y=sin(1:10))
df write.table(df, "test.dat", quote=FALSE, row.names=FALSE)
For other forms of printing, look into the write()
function:
<- paste("hello", "world")
toprint <- file("file_name.txt", encoding="UTF-8")
outfile write(toprint, file=outfile)
close(outfile)
You can for example write a Rmd file that you will render (as pdf, etc…) using render()
:
::render("file_name.Rmd") rmarkdown
15.1.4 Example
Create a new shiny app with the following code, and play around with it. The input file should be the tidy population.txt.
library(shiny)
library(tidyverse)
library(plotly)
library(DT)
<- fluidPage(
ui titlePanel("City population in France"),
sidebarLayout(
sidebarPanel(
fileInput("file_in", "Choose input file:",
accept = c(".txt") ),
selectInput("sel_city", "City:", choices = "", multiple = TRUE)
),mainPanel(
tabsetPanel(
tabPanel("Plot", plotlyOutput("cityplot", height = "400px")),
tabPanel("Table", dataTableOutput("table"))
)
)
)
)
<- function(input, output, session) {
server
# myData() returns the data if a file is provided
<- reactive({
myData <- input$file_in
inFile if (is.null(inFile)) {
return(NULL)
else {
} <- read.table(inFile$datapath, header=TRUE)
df # in case something changes,
# update the city input selection list
updateSelectInput(session, "sel_city",
choices = unique(df$city),
selected = unique(df$city)[1])
return(df)
}
})
# plot the pop vs year for the selected cities
$cityplot <- renderPlotly({
output<- myData()
df if(is.null(df)) return(NULL)
<- df %>%
p filter(city %in% input$sel_city) %>%
ggplot(aes(x=year, y=pop, size=pop, color=city)) +
geom_point() +
geom_smooth(method="lm", alpha=0.1,
show.legend = FALSE,
aes(fill=city)) +
ggtitle(paste0("Population in ",
paste(input$sel_city, collapse = ", ")
+
))labs(x="Year", y="Population")+
theme_light()
ggplotly(p, dynamicTicks = TRUE)
})
# show data as a table
$table <- renderDataTable({
output<- myData() %>% filter(city %in% input$sel_city)
df if(is.null(df)) return(NULL)
<- pivot_wider(df, names_from=year, values_from=pop)
df datatable(df, rownames = FALSE)
})
}
shinyApp(ui = ui, server = server)
This will render like this.
15.2 Rmarkdown-embedded shiny application
A shiny application can even be embedded inside a Rmarkdown document by providing runtime: shiny
in the YAML header. A short example here, try to compile it:
---
title: "Test"
output: html_document
runtime: shiny
---
This is a test Rmarkdown document.
`r ''````{r, echo=FALSE, message=FALSE}
library(ggplot2)
library(plotly)
df <- read.table("Data/population.txt", header=TRUE)
shinyApp(
ui = fluidPage(
selectInput("city", "City:", choices = unique(df$city)),
plotlyOutput("cityplot", height = 600)
),
server = function(input, output) {
output$cityplot = renderPlotly({
p <- ggplot(data=subset(df,city==input$city),
aes(x=year, y=pop, size=pop)) +
geom_point() +
geom_smooth(method="lm", alpha=0.1, show.legend = FALSE) +
ggtitle(paste("Population in ",input$city,sep=""))+
labs(x="Year", y="Population")+
theme_light()
ggplotly(p)
})
}
)```
The only “problem” with this solution is that the html file that is produced will not run the shiny app by itself, you have to open the Rmd file in Rstudio and hit “Run Document”.
Another solution consists in deploying your app on shinyapps.io and embedding the page in your document with:
`r ''````{r, echo=FALSE}
knitr::include_app("https://cbousige.shinyapps.io/shiny_example/",
height = "800px")
```
15.3 Deploying your shiny app
There are 4 ways to deploy your app: passing the app.R
file to your users, deploying to shinyapps.io, deploying on your own server, or building an executable with Electron.
15.3.1 Passing the app.R file to your users
This option is certainly easy: just send your app.R
file (or Rmd file with shiny embedded app) as well as any other files needed (e.g. global.R
) to your users, explain to them how to run it, and voilà.
However, this needs a little bit of know-how from the users: they need to install R and Rstudio, install the needed packages, and run the app.
A good option to remove the “package-installing” step is to define a function check.package()
that will check if the package is installed, install it if needed, and load it:
<- function(pkg){
check.packages <- pkg[!(pkg %in% installed.packages()[, "Package"])]
new.pkg if (length(new.pkg))
install.packages(new.pkg, dependencies = TRUE)
sapply(pkg, require, character.only = TRUE)
}# Usage:
check.packages("ggplot2")
15.3.2 Deploying to shinyapps.io
Applications deployed on shinyapps.io will be accessible from anywhere through a weblink. See for example my application to determine the pressure from a ruby Raman spectrum or the expected Raman shift for a given pressure and laser wavelength. Your application will however be public and you will have some limitations in the number of online applications and time of use (if you don’t pay a fee, see here for the various plans).
- First, create an account on shinyapps.io
- Follow the steps described here to:
- Configure RSconnect: in your shinyapps.io dashboard, click your name, then Tokens, and create a token for a new app. Copy the text in the popup window.
- Deploy the app from the Rstudio window by clicking on the “Publish” button in the top right corner of the interface. Follow the steps along the shinyapps.io way.
Note that in that case, you should not have any install.package()
command in your code. Most packages are supported by shinyapps.io.
15.3.3 Deploying on your own Linux server
This option is more advanced and I’m not going into details for that, but you have a number of tutorials online. See e.g. here, here or here.
You might consider this option if you work in a company that want to handle privately its data (which sounds plausible) and not pay the shinyapps.io fee to password protect the app. In that case, just work with the IT department to get it running.
15.3.4 Building an executable
On Windows, there is this possibility that looks nice but that I never tried because I don’t have Windows: RInno.
On any platform: there is the possibility described here with the corresponding github page. This option is actually awesome and a quite recent possibility. However, since the produced application will contain R and the needed packages, the executable file is quite heavy.
15.4 Further reading
- The Shiny cheatsheet
- Help on deploying your shiny app
- Guide to application layout
- The Shiny Gallery: find what you want to do and adapt it to your needs
- The official Shiny video tutorial
15.5 Exercises
Exercise 1
- Create a new empty app with a blank user-interface and run it.
- Add a title, a left panel and a main panel
- Add an input numerical value defaulting to 1 and with a step of 0.05, name it “bw”
- Add a slider input from 0 to 1e3 by steps of 1e2 defaulting to 5e2, name it “N_val”
- Add a plot of the
density
ofrnorm(N_val)
with bandwidthbw
- Make sure
bw>0
, otherwise don’t produce the plot
Solution
library(shiny)
<- fluidPage(
ui titlePanel("Some title"),
sidebarLayout(
sidebarPanel(
numericInput("bw", "Enter bandwidth:", 1, step=0.05),
sliderInput("N_val", "Number of points:",
min = 0, max = 1e4, step= 1e2, value = 5e2)
),mainPanel(
plotOutput("plot", height = 600)
)
)
)
<- function(input, output, session) {
server $plot <- renderPlot({
outputif(input$bw==0) return(NULL)
plot(density(rnorm(input$N_val), bw=abs(input$bw)))
})
}
shinyApp(ui = ui, server = server)
Exercise 2
Create a shiny application that will:
- read an input (through a file dialog) Raman spectrum from a ruby (XPdata.zip)
- fit the data by two Lorentzians
- plot the data interactively
- ask for the laser wavelength as an input and give 568.189 nm as default
- write the corresponding pressure on the page using the
Pruby()
function defined inmyfunc.R
found in XPdata.zip. - insert a button that will, when pressed, render a pdf report displaying the laser wavelength, the plot, the fit and the pressure found:
- write a separate Rmd file with the proper parameters
- render the Rmd file as a pdf (see the
render()
function and this help)