Introduction
Rstudio’s Shiny is a really convenient way to build sophisticated web applications to not only deploy interactive dashboards and complex reporting tools, but also to aid in models building and monitoring . Moreover, Shiny could also be used alongside RMarkdown to produce interactive documents and presentations.
- ui is the ... that implements the user interface: prebuilt controls, and objects placemarks that will be filled with results built from corresponding commands in the server side (actionoutput vs renderaction)
- server is the ... that with the R server and provides the outputs through render functions that can access the current values of ui objects
App file structure
There are a few different ways to physically structure the code:
- simple single file
- classic double file
- multiple files
- multiple directories
Layouts
Sidebar
Provides a basic two-column structure:
- a smaller sidebar on the left, usually filled with the objects that control the underlying data manipulation
- a larger main panel on the right, that
On top of the panel there can be the application Title
Each of the above panels can contain any arbitrary mix of text/HTML elements, in a similar fashion to how you can mix these elements inside a fluidPage().
To create a sidebar layout, we use the sidebar layout function takes at least two arguments: sidebarPanel() and mainPanel(). We can add titlePanel() for a title, ...
Panels
Panels are visual containers used for grouping multiple elements into a single object that has its own properties.
-
titlePanel -
sidebarPanel -
mainPanel -
conditionalPanel(condition, ...)The first argument in this function is the condition, which is a JavaScript expression, as opposed to an R expression, that will be evaluated repeatedly to determine whether the panel should be displayed.
-
wellPanelis used to group together multiple related interface controls into one single -
tabPanel
Tabs are useful when the app grows and display too much content for a single page, so that it's a good idea to split it up. To create a tab, it's sufficient to wrap all the corresponding UI elements in thetabPanel()function, including a title for the tab using thetitleargument.
In order for the tabs to appear in the UI, all the corresponding tab panels need to be grouped into a tabset "container", by wrapping all the tab panels inside atabsetPanel(), that list all tabs as a header, ornavlistPanel()that bind all tabs along the side, ornavbarPage, that list all tabs as a top header of the entire page. -
tabsetPanel
Its arguments are:type: one among the two possible optionstabsorpillsidselected- some
tabPanel
-
navlistPanel -
navbarPage -
navbarMenuIt's possible to show or hide dinamically any existing
tabPanelornavbarMenufrom an existing tabsetPanel, navlistPanel or navbarPage, using respectively:showTab(inputId, target, select)hideTab(inputId, target)
whereinputIdis the id of the tabsetPanel / navlistPanel in which to find thetargettabPanel to be hidden/shown, whileselect... if the target should be selected upon being shown.
In a similar way it's also possible to hide / show an entire
navbarMenu.It's possible to dinamically insert a new or remove any existing
tabPanelornavbarMenufrom an existing tabsetPanel, navlistPanel or navbarPage, using respectively:insertTab(inputId, tab, target, position, select)removeTab(inputId, target)
where
In the insertion case, it's possible to simplify things if the new tab is going to be the first or the last one:prependTab(inputId, tab, target, select)appendTab(inputId, tab, target)
Tabs and tabsets
Navbar
Inputs
In fact, if you try to run any input control directly in the command line as stand-alone commands, the output you get is the exact HTML code needed to replicate the desired control in a web page.
In Shiny, as soon as the user changes the value of any input control, Shiny makes the current value of that input immediately available to any reactive function in the server side through the input list argument of the server function. It's possible to retrieve the value of any input at any time, but only in reactive functions, using input$<inputId>.
All input objects have their very first two arguments as:
-
inputIdhas to be unique throughout the application code. -
label
Besides the above common arguments, most of the time there will be need to add one or more additional optional arguments, depending on the control itself. -
textInput(inputId = '', label = '', value = 'string')to retrieve some text from the user, wherevalueis used to assign an optional default initial value. -
textAreaInput(inputId = '', label = '', value = 'string', rows = n)to retrieve some text from the user. The textAreaInput() is useful when you want to allow the user to enter much longer text than what a typical textInput() allows. Textareas span multiple rows and have a vertical scrollbar, as well as a rows parameter that can determine how many rows are visible. Except for being larger and the additional parameter, textarea inputs behave very similar to text inputs in every other way. -
numericInput(inputId = '', label = '', value = num, min = num, max = num)minandmaxdefine the minimum and maximum numbers that can be chosen. -
sliderInput(inputId = '', label = '', value = num | c(n1, n2), min = num, max = num)It's an alternative way to the previousnumericInput -
selectInput(inputId = '', label = '', choices = list(), selected = list(), multiple = FALSE)Also called dropdown lists, are used to ask the user to choose an option from a list of choices, in a compact way. With a select input, all the options appear in a scrollable list. -
radioButtons(inputId = '', label = '', choices = list, selected = 'string' )It's a good alternative for the previousselectInputwhen there are few options, or we want all the options to be visible. -
checkboxInput(inputId = '', label = '', = '', value = TRUE)Unlike text and numeric inputs, checkbox inputs are limited to only two possible values: TRUE or FALSE. When the user checks a checkbox input, the input has a value of TRUE, and if the box is unchecked then it returns FALSE. -
radioGroupButtons(inputId = '', label = '', choices = list, selected = list ) -
checkboxGroupInput(inputId = '', label = '', choices = list, selected = list ) -
uiInput(inputId = '', label = '', = '') -
fileInput(inputId = '', label = '', = '', = '')
The variable that this control stores back in the input list is not the file itself, or the files themselves if multiple files have been allowed, but information about the file(s). After the user selects a file, or multiple files if (s)he has been allowed to do it, that file gets uploaded to the computer that runs the Shiny app, and it becomes available in the server. Theinput$<inputId>variable is then populated with a dataframe, with as many rows as the number of uploaded files, and every row stores the same info about each file: name, size, type and datapath. The is the actual directory where the file gets stored on the server that houses Shiny, so that it can be accessed directly from inside the app. For example, if the file is a plain text file, it could be retrieved usingdts <- readlines(file.path(input$<inputId>$datapath, input$<inputId>$name))while if the file is a csv file it's easy to read it with the following usual command:
dts <- read.csv(file.path(input$<inputId>$datapath, input$<inputId>$name))
Other Packages
-
colourpicker::colourInput(inputId = '', label = '', value = "white", showColour = c("both", "text", "background"), palette = c("square", "limited"))
Displaying outputs: the ui *Output functions and server render* functions
While building an input object is most of the time writing a single functions, adding outputs is a multi steps affair:
-
in the UI part of the app, add a placeholder for the output object, that tells Shiny where to actually place the output. All these functions have their name that starts with a reference to the type of object itself, and end with the exact word
Output. Moreover, their first argument is always the uniqueoutputIDthe object itself has been defined in the server part of the app (see below)\ \
There are quite a few predefined output type in the shiny package, that are listed below. On top of those, there are tens, if not hundreds, of other objects that comes with , most notably the objects created through thehtmlwidgetspackage, a wrapper for many JavaScript libraries.-
textOutput(outputId = 'unique_id)is for displaying normal text. -
verbatimTextOutput(outputId = 'unique_id)is for displaying code and quotes. -
tableOutput(outputId = 'unique_id)is for outputting tables -
plotOutput(outputId = 'unique_id)is for drawing plots -
printOutput(outputId = 'unique_id)is for -
imageOutput(outputId = 'unique_id)is for -
DataTableOutput(outputId = 'unique_id)is for -
uiOutput(outputId = 'unique_id)is for -
htmlOutput(outputId = 'unique_id)is for (this is an exception to the rule, as it asks for arenderUIelement in the ui part of the app)
-
-
in the server part of the app, build the object to be displayed using
-
save the result of the previous render* function in the list of outputs, naming it with the chosen unique id
-
If the output relies on any user-modified input values, it is possible to access any of the inputs using the input parameter of the server function. Specifically, input$<inputId> will always return the current value of the input field that has ID inputId.
Other Packages
DT::dataTableOutput(inputId = '', label = '', width = '', height = '')
Perform actions
When the immediate reaction to changes in inputs is too obstrusive, it could be useful to wait for the user to actually trigger it. The typical use case is to add a button, or a link, somewhere in the ui:
actionButton(inputId = 'btn_id', label = '', = '')actionLink(inputId = 'btn_id', label = '', = '')
and then code an observer in the server part that wait for that button to be clicked before running the desired code:my_var <- eventReactive({input$, ...code to be executed... })
Another way to trigger reactivity only after the user action a button, is to first create a dependency on that button, and then isolate the desired code:
my_var <- reactive({
input$,
isolate( ...code to be executed... )
})
Download data
For downloading from a Shiny app we need to use:
- in the ui: a
downloadButton(inputId = 'id', label = '')or adownloadLink(inputId = 'id', label = '') -
in the server: a
downloadHandlerfunction which must have the following generic format:output$id <- downloadHandler( filename = function() { }, content = function(file) { } )
Reactive programming
The value of a normal R object will not change once set, unless it's reset or there exists some arrangement for a program to reset or change it.
In reactive programming, an expression gets re-evaluated whenever any of its reactive dependencies are modified. In Shiny, all inputs are reactive variables. This means that any time the user manipulates an input control to change its value, any code block that depends on the corresponding input variable (such as a render function) reacts to the input variable's new value by re-evaluating. Reactive values can only be accessed within a reactive context. This is the reason why any variable that depends on a reactive value must be created using the reactive() function, otherwise you will get an error. The shiny server itself is not a reactive context, but the reactive function, the observe function, and all render* functions are.
It's important to understand a few
- if an expression contains multiple reactive values, then modifying any of them will cause a re-evaluation.
- if an expression contains a reactive value which in turn contain another reactive value, the last in the chain gets updated every time a reactive value before it get modified. In general, if x depends on y and y depends on z, then modifying z causes both y and x to update.
In general, there are three types of reactivity objects in Shiny:
- a reactive source .
A typical reactive source is any input object in the ui part of an app.
Note that a reactive source is always a parent, and can't never be a child. - a reactive conductor .
A typical reactive conductor is any object in the ui
Note that a reactive conductor is both a parent and a child. - a reactive endpoint .
A typical reactive endpoint is any*Outputin the ui part of an app.
Note that a reactive endpoint is always a child, and can't never be a parent.
In general, reactive conductors let you not repeat yourself, and let you decompose large, complex calculations into smaller pieces to make them more understandable. A typical divide and conquer programming scenario, similar to decomposing a large complex R script into a series of small functions that build on each other.
But there's a fundamental difference vs normal functions. Each time a function is called, R will evaluate it. Reactive expressions instead are lazy, as they only get executed when their input changes.
Using many reactive expressions, an app can create a complicated dependency structure that can become difficult to follow. For this purpose, there exists a graphical tool called reactlog that tries to provide a visual representation of this dependency structure, and it also gives you very detailed information about what’s happening under the hood as Shiny evaluates the app. To view run options(shiny.reactlog = TRUE), and press Ctrl+F3 once the app is up and running.
-
reactive values: values connected to input objects, or created from input objects using reactive*()
-
reactive functions: functions that react to and can access to reactive values, like render*()
-
reactive objects: objects created from reactive functions, that usually end up filling the placeholders created by *Output() functions in ui
The value of a reactive value will change when a user manipulates the app using .
A reactive function can call reactive values and reactive objects. A normal function cannot call reactive values or reactive objects (unless the normal function is called during the execution of a reactive function).
The value of a reactive object will change when the reactive values that it depends on change.
Also, reactive "objects" have a more active role in the context of a Shiny app than normal objects have in R. Objects can trigger actions long after they are made.
Every reactive function/object take a dependency on every reactive value found in its code, and gets "invalidated" (out of date) every time any one of them changes.
In that case, the function function
You can NOT use reactive values inside function that are not reactive, unless they are called inside reactive functions.
render() <=> Output() functions
There exists an entire family of reactive functions render*(). Any one of them creates a reactive object that's usually saved in the output object in the server function, and then used by the ui function to fill the placemarks created with the *Output() function to display some result from the R server.
Every render* function it's saved in the output list object in the server side with a name that in turn is the ID only argument that connects the placemark created by the *output function in the ui side with the object built by the render*() function in the server side and saved in the output object
DT::renderDataTable(expr, options, callback, escape, env, quoted)
needsdataTableOutput(outputId, icon, …)renderImage(expr, env, quoted, deleteFile)
needsimageOutput(outputId, width, height, click, dblclick, hover, hoverDelay, hoverDelayType, brush, clickId, hoverId, inline)renderPlot(expr, width, height, res, …, env, quoted, func)
needsplotOutput(outputId, width, height, click, dblclick, hover, hoverDelay, hoverDelayType, brush, clickId, hoverId, inline)renderPrint(expr, env, quoted, func, width)
needsverbatimTextOutput(outputId)renderTable(expr,…, env, quoted, func)
needstableOutput(outputId)renderText(expr, env, quoted, func)
needstextOutput(outputId, container, inline)renderUI(expr, env, quoted, func)
needs eitheruiOutput(outputId, inline, container, …)orhtmlOutput(outputId, inline, container, …)
reactive()
Beside the above functions, the main reactive function is reactive() used to create reactive objects or expressions to be. A reactive expression is primarily a function, so it has to be called using parenthesis, even if there is no arguments to be passed. Moreover, reactive expressions once validated cache their value, so they don't run their code again and again every time they are called, but only when they are invalidated by changes in the reactive values/expressions they depend upon.
eventReactive()
is used to create a calculated value that only updates in response to a specified event
observe()
observeEvent()
is used to perform an action in response to a specified event
The main difference between reactive and observe functions is that:
- reactive function always return a value or an object, and the required calculation shouldn't have any side effects
- observe functions are not required to return any value, but perform an action in response to an event, with some side effect expected.
The main difference between plain and Event functions is that:
observeandreactiveget triggered once any of its reactive elements changesobserveEventandeventReactive
reactiveVal()
Create a (single) reactive value
reactiveValues()
isolate
If there is a reactive variable that appears multiple times in the code, and you want to ensure that its modification does not trigger a re-evaluation of the code, you need to isolate all the instances of that variable. That means that if a variable x is inside an isolate() but also appears outside of it, then it will trigger reactivity.
invalidateLater()
reactiveFileReader()
reactivePoll()
shiny input objects
Every input object has a name whose, and it should have at least two arguments: ID used in the input server function to store object values, and the label, which is a description to show the meaning of the control. Most if not all objects have also additional optional arguments to configure its behaviour, dependent on the control itself
Plot Interaction
Besides the reactivity between the above functions, it's also possible to induce a reaction from events made by the user on a plot with the mouse: click, dblclick, hover, and brush. In the plotOutput function we have to include one of the above events, with an ; the renderPlot will then contain one of two function, nearPoints for click, dblclick and hover, or brushedPoints for brush, and both will contain the dataset that formed the original plot, together with the previous named selection.
- example with
click:(ui) plotOutput("plot_id", click = "plot_click") (server) ... nearPoints(dts, input$plot_click, xvar = "x", yvar = "y")By default, nearPoints will return all the rows of data that are within 5 pixels of the mouse event, and they will be sorted by distance, with the nearest first. The radius can be customized with threshold, and the number of rows returned can be customized with maxpoints.
- example with
brush:(ui) plotOutput("plot_id", click = "plot_click") (server) ... nearPoints(dts, input$plot_click, xvar = "x", yvar = "y")
Selection with nearPoints
Themes
There is a package from RStudio called shinythemes that wraps up most bootstrap themes, and allows to apply any of them using the theme argument of the fluidPage generic layout:
library(shiny)
library(shinytheme)
fluidPage(theme = shinytheme('themename'), ...
If undecided on which theme to apply, it's possible to include the function shinytheme::themeSelector() anywhere in the ui function, and this will include in the top right of the app a selection object that changes the theme on the fly. This trick should be obviously used only while developing the app, not in the final deployment.
Besides the themes included in the shinythemes, it's possible to include any other themes copying ...
HTML, CSS and JS
HTML tags
The most common tags can be also written down using their own function, meaning that tags$tagname() can convert simply to tagname(). Anyone confortable with the HTML language, could be better off writing it directly and wrapping it up with the HTML() functions. Doing so, it's possible to write more complicate expressions than using tags commmads alone.
The following list is a recap of the main HTML tags rewritten using the Shiny flavour:
h1()create a primary header,h2()for a secondary header, and so on tillh6().strong()to make text boldem()to make text italicized- ``
- ``
- ``
- ``
Both plain and formatted text can be stacked as much as you'd like, just remember to separate all the elements with commas!
Visual Enhancements using CSS
CSS is an extremely popular markup language that is used to tell the browser how to display elements on a page. You need to use CSS if you want to deviate from the default look-and-feel of Shiny and want to customize the appearance of different items in your app.
Recall that CSS is comprised of a set of rules, where each rule is a property: value pair associated with an element on the page. It's possible to include CSS in your app by writing it in a separate file and importing it with includeCSS(), but in this course we will use the simpler approach of placing the CSS code inside tags$style() in the UI.
Improve interactivity using JavaScript
Templates
Single File
# load packages
pkgs <- c('shiny', 'ggplot2')
lapply(pkgs, require, char = TRUE)
# load common dataset(s)
dts <- ...
# Define the UI part of the app
ui <- fluidPage(
# Application title
titlePanel('My Title'),
# the "sidebarLayout" offers a predefined layout with a left grayed side for the "inputs" and a right white container for the "outputs"
sidebarLayout(
# Define the inputs(s) for the left side of the browser window
sidebarPanel(
# Select variable for y-axis
selectInput(inputId = "y",
label = "Y-axis:",
choices = c("IMDB rating" = "imdb_rating",
"IMDB number of votes" = "imdb_num_votes",
"Critics Score" = "critics_score",
"Audience Score" = "audience_score",
"Runtime" = "runtime"),
selected = "audience_score"),
# Select variable for x-axis
selectInput(inputId = "x",
label = "X-axis:",
choices = c("IMDB rating" = "imdb_rating",
"IMDB number of votes" = "imdb_num_votes",
"Critics Score" = "critics_score",
"Audience Score" = "audience_score",
"Runtime" = "runtime"),
selected = "critics_score"),
# Select variable for color
selectInput(inputId = "z",
label = "Color by:",
choices = c("Title Type" = "title_type",
"Genre" = "genre",
"MPAA Rating" = "mpaa_rating",
"Critics Rating" = "critics_rating",
"Audience Rating" = "audience_rating"),
selected = "mpaa_rating"),
# Enter text for plot title
textInput(inputId = "plot_title",
label = "Plot title",
placeholder = "Enter text for plot title"),
# Select which types of movies to plot
checkboxGroupInput(inputId = "selected_type",
label = "Select movie type(s):",
choices = c("Documentary", "Feature Film", "TV Movie"),
selected = "Feature Film")
),
# Define the Output for the right side of the browser window
mainPanel(
textOutput(outputId = "my_text")
DataTableOutput(outputId = "my_table")
plotOutput(outputId = "my_plot"),
)
)
)
# Define the server part of the app
server <- function(input, output) {
# Create a reactive subset of data filtering for selected object types
dts_sub <- reactive({
req(input$selected_type)
filter(dts, title_type %in% input$selected_type)
})
# Create some descriptive text
output$my_text <- renderText({
paste0("Some text to explain the relationship between ", input$x, " and ", input$y, ", conditional on ", input$z, ".")
})
# Create a table
output$my_table <- renderDataTable({
DT::datatable( dts_sub() )
})
# Create a visualization
output$my_plot <- renderPlot({
ggplot(data = dts_sub(), aes_string(x = input$x, y = input$y, color = input$z)) + geom_point()
})
}
# Create the Shiny app object
shinyApp(ui = ui, server = server)
Tips and Tricks
Add an icon to a button
actionButton("goButton", "", icon = icon("play-circle")),
tags$button(id = "...", class="btn action-button", icon("close"))
In the above examples, the lists of available icons are https://fontawesome.com/icons?d=gallery&m=free and http://getbootstrap.com/components/#glyphicons (see ?icon in R)
tags$button(
id = "...",
class = "btn action-button",
tags$img(src = "http://.jpg", height = "50px")
)
Resources
The first stop is obviously the RStudio Website, where you can find:
- the documentation
- basic and advanced tutorials
- Rstudio dedicated webinars
- the RStudio community
- the videos from the 2016 Shiny conference
For more information about mouse events in plots (click, hover and brush), see the following resources:
- Selecting rows of data blog post
- Interactive plots
- Interactive plots - advanced
- Interactive Graphics with Shiny webinar
- plot-interaction-basic demo app
- image-interaction-basic demo app
- plot-interaction-advanced demo app
- plot-interaction-zoom demo app
- plot-interaction-exclude demo app