Every Shiny app has a UI (User Interface) portion and a server portion. The UI is where the visual elements are placed—it controls the layout and appearance of your app. The server is where the logic of the app is implemented—for example, where calculations are performed and plots are generated.
An empty UI is created using the fluidPage() function. Adding text to a Shiny app is done by adding text inside fluidPage() as an argument. In fact, the entire UI is built by supplying the fluidPage() function with as many arguments as you want.
# Load the shiny package
library(shiny)
# Define UI for the application
ui <- fluidPage(
# Add the text "Shiny is fun"
"Shiny is fun"
)
# Define the server logic
server <- function(input, output) {}
# Run the application
shinyApp(ui = ui, server = server)
Shiny has many functions that can transform plain text into formatted text. Simply place text inside the h1() function to create a primary header (e.g. a title), h2() for a secondary header, strong() to make text bold, em() to make text italicized, or any of the other formatting functions.
You can also intermingle plain text and formatted text as much as you’d like—just remember to separate all the elements with commas!
# Load the shiny package
library(shiny)
# Define UI for the application
ui <- fluidPage(
# "DataCamp" as a primary header
h1("DataCamp"),
# "Shiny use cases course" as a secondary header
h2("Shiny use cases course"),
# "Shiny" in italics
em("Shiny"),
# "is fun" as bold text
strong("is fun")
)
# Define the server logic
server <- function(input, output) {}
# Run the application
shinyApp(ui = ui, server = server)
Layouts in Shiny are used to give your app some structure by placing elements in certain desired positions.
A sidebar layout, created with the sidebarLayout() function, provides a basic two-column structure with a smaller sidebar on the left and a larger main panel on the right.
The sidebar layout function takes two arguments: sidebarPanel() and mainPanel(). Each of these panels can contain any arbitrary mix of text/HTML elements, in a similar fashion to how you can mix these elements inside a fluidPage().
# Load the shiny package
library(shiny)
# Define UI for the application
ui <- fluidPage(
# Add a sidebar layout to the application
sidebarLayout(
# Add a sidebar panel around the text and inputs
sidebarPanel(
h4("Plot parameters"),
textInput("title", "Plot title", "Car speed vs distance to stop"),
numericInput("num", "Number of cars to show", 30, 1, nrow(cars)),
sliderInput("size", "Point size", 1, 5, 2, 0.5)
),
# Add a main panel around the plot and table
mainPanel(
plotOutput("plot"),
tableOutput("table")
)
)
)
# Define the server logic
server <- function(input, output) {
output$plot <- renderPlot({
plot(cars[1:input$num, ], main = input$title, cex = input$size)
})
output$table <- renderTable({
cars[1:input$num, ]
})
}
# Run the application
shinyApp(ui = ui, server = server)
Inputs are Shiny’s way of allowing users to interact with an app. For example, textInput() is used to let the user enter text and numericInput() lets the user select a number. In the next chapter we will see many other types of inputs.
To add an input to your app, simply add the input function inside fluidPage(). Recall from the video that all input functions have the same first two arguments: inputId and label.
library(shiny)
# Define UI for the application
ui <- fluidPage(
# Create a numeric input with ID "age" and label of
# "How old are you?"
numericInput("age", "How old are you?", value = 20),
# Create a text input with ID "name" and label of
# "What is your name?"
textInput("name", "What is your name?")
)
# Define the server logic
server <- function(input, output) {}
# Run the application
shinyApp(ui = ui, server = server)
Outputs are any object that should be displayed to the user and is generated in R, such as a plot or a table.
To add an output to a Shiny app, the first thing you need to do is add a placeholder for the output that tells Shiny where to place the output.
There are several output placeholder functions provided by Shiny, one for each type of output. For example, plotOutput() is for displaying plots, tableOutput() is for outputting tables, and textOutput() is for dynamic text.
# Run the application
shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:4803
There are three rules to build an output in Shiny:
Build the object with the appropriate render*() function.
Save the result of the render function into the output list, which is a parameter of the server function. Specifically, save it into output$
If the output relies on any user-modified input values, you can access any of the inputs using the input parameter of the server function. Specifically, input$
# Load the shiny package
library(shiny)
# Define UI for the application
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("name", "What is your name?", "Dean"),
numericInput("num", "Number of flowers to show data for",
10, 1, nrow(iris))
),
mainPanel(
textOutput("greeting"),
plotOutput("cars_plot"),
tableOutput("iris_table")
)
)
)
# Define the server logic
server <- function(input, output) {
# Create a plot of the "cars" dataset
output$cars_plot <- renderPlot({
plot(cars)
})
# Render a text greeting as "Hello <name>"
output$greeting <- renderText({
paste("Hello", input$name)
})
# Show a table of the first n rows of the "iris" data
output$iris_table <- renderTable({
data <- iris[1:input$num, ]
data
})
}
# Run the application
shinyApp(ui = ui, server = server)
In reactive programming, an expression gets re-evaluated whenever any of its 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 that variable (such as a render function) reacts to the input variable’s new value by re-evaluating.
You can also create new reactive variables with the reactive() function.
The following code defines a reactive variable called my_sum that calculates the sum of two numeric inputs named num1 and num2.
my_sum <- reactive({
input$num1 + input$num2
})
When the user changes the value of either num1 or num2 input, the reactive variable will be re-evaluated.
Whenever the value of a reactive variable (or an input) changes, the code that uses this reactive variable is re-executed. To determine what code runs when a reactive variable changes its value, Shiny creates a dependency graph from the code. In general, if x depends on y and y depends on z, then modifying z causes y to update, which in turn triggers x to update.
Here is an example of a reactive variable that depends on another reactive variable:
x <- reactive({
input$num1 + 5
})
y <- reactive({
x() + input$num2
})
y gets updated when the user changes the value of either num1 or num2 input.
Reactive values are special constructs in Shiny; they are not seen anywhere else in R programming. As such, they cannot be used in just any R code, 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.
ui <- fluidPage(
numericInput("num1", "Number 1", 5),
numericInput("num2", "Number 2", 10),
textOutput("result")
)
server <- function(input, output) {
# Calculate the sum of the inputs
my_sum <- reactive({
input$num1 + input$num2
})
# Calculate the average of the inputs
my_average <- reactive({
my_sum() / 2
})
output$result <- renderText({
paste(
# Print the calculated sum
"The sum is", my_sum(),
# Print the calculated average
"and the average is", my_average()
)
})
}
shinyApp(ui, server)
Reactive variables are a key part of developing Shiny applications efficiently, and it’s important to understand how to define and use them
The gapminder dataset, which offers a few basic demographic stats for countries over time, is provided by the gapminder package.
# Load the gapminder package
library(gapminder)
library(shiny)
In Shiny, as soon as the user changes the value of any input, Shiny makes the current value of that input immediately available to you in the server through the input argument of the server function. You can retrieve the value of any input using input$
In order to assign a default initial value to a text input, the value argument is used.
# Load the ggplot2 package for plotting
library(ggplot2)
# Define UI for the application
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
# Add a title text input
textInput("title", "Title", "GDP vs life exp")
),
mainPanel(
plotOutput("plot")
)
)
)
# Define the server logic
server <- function(input, output) {
output$plot <- renderPlot({
ggplot(gapminder, aes(gdpPercap, lifeExp)) +
geom_point() +
scale_x_log10() +
# Use the input value as the plot's title
ggtitle(input$title)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Numeric inputs have a few more arguments that text inputs do not have, such as min and max, which define the minimum and maximum numbers that can be chosen.
Note that when the value of an input is accessed in the server code, Shiny is smart enough to know what type of input was used, and therefore what type of object it should return. This means that if you have a numeric input with ID “foo”, then input$foo will return a numeric value.
# Define UI for the application
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("title", "Title", "GDP vs life exp"),
# Add a size numeric input
numericInput("size", "Point size", value = 1, min = 1)
),
mainPanel(
plotOutput("plot")
)
)
)
# Define the server logic
server <- function(input, output) {
output$plot <- renderPlot({
ggplot(gapminder, aes(gdpPercap, lifeExp)) +
# Use the size input as the plot point size
geom_point(size = input$size) +
scale_x_log10() +
ggtitle(input$title)
})
}
# Run the application
shinyApp(ui = ui, server = server)
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.
Note that the value parameter of the checkboxInput() function, which defines the initial value, can only be set to either TRUE or FALSE.
library(ggplot2)
# Define UI for the application
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("title", "Title", "GDP vs life exp"),
numericInput("size", "Point size", 1, 1),
# Add a checkbox for line of best fit
checkboxInput("fit", "Add line of best fit", value = FALSE)
),
mainPanel(
plotOutput("plot")
)
)
)
# Define the server logic
server <- function(input, output) {
output$plot <- renderPlot({
p <- ggplot(gapminder, aes(gdpPercap, lifeExp)) +
geom_point(size = input$size) +
scale_x_log10() +
ggtitle(input$title)
# When the "fit" checkbox is checked, add a line
# of best fit
if (input$fit == TRUE) {
p <- p + geom_smooth(method = "lm")
}
p
})
}
# Run the application
shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:7283
NA
When there are many options to let the user choose from, radio buttons can take up a lot of space and may not be ideal. Select inputs—also called ‘dropdown lists’—can also be used to ask the user to choose an option from a list of choices, but in a more compact way. With a select input, all the options appear in a scrollable list, so it can be used even if you have many choices.
Similar to radio buttons, select inputs also have choices and selected parameters. Additionally, select inputs have a multiple argument, which, when set to TRUE, allows the user to select more than one value.
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("title", "Title", "GDP vs life exp"),
numericInput("size", "Point size", 1, 1),
checkboxInput("fit", "Add line of best fit", FALSE),
radioButtons("color", "Point color",
choices = c("blue", "red", "green", "black")),
# Add a continent dropdown selector
selectInput("continents", "Continents",
choices = levels(unique(gapminder$continent)),
multiple = TRUE,
selected = "Europe")
),
mainPanel(
plotOutput("plot")
)
)
)
# Define the server logic
server <- function(input, output) {
output$plot <- renderPlot({
# Subset the gapminder dataset by the chosen continents
data <- subset(gapminder,
continent %in% input$continents)
p <- ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point(size = input$size, col = input$color) +
scale_x_log10() +
ggtitle(input$title)
if (input$fit) {
p <- p + geom_smooth(method = "lm")
}
p
})
}
shinyApp(ui = ui, server = server)
Slider inputs can be used for similar purposes to numeric inputs, as they both provide the user with a way to select a number.
If the initial provided value (the value argument) of the slider is a single number, then the slider will be used to select single numbers. However, if the initial value is a vector of two numbers, then the slider will be used to select two numbers instead of just a single value.
We have already seen that different inputs may have different arguments. It can be difficult to remember the exact arguments each input uses. The only way to find out what arguments you can use with a specific input function is by looking at its documentation or help file.
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("title", "Title", "GDP vs life exp"),
numericInput("size", "Point size", 1, 1),
checkboxInput("fit", "Add line of best fit", FALSE),
radioButtons("color", "Point color",
choices = c("blue", "red", "green", "black")),
selectInput("continents", "Continents",
choices = levels(gapminder$continent),
multiple = TRUE,
selected = "Europe"),
# Add a slider selector for years to filter
sliderInput("years", "Years", min = min(gapminder$year), max = max(gapminder$year), select = c(1997, 2002))
),
mainPanel(
plotOutput("plot")
)
)
)
Error in sliderInput("years", "Years", min = min(gapminder$year), max = max(gapminder$year), :
unused argument (select = c(1997, 2002))
The colourpicker package provides a color input, available through the colourInput() function. Even though color inputs are not part of the shiny package, they behave in the same way as any other input.
A color input can have many different arguments you can explore, but we will only use the basic arguments: inputId, label, and value. The value argument accepts a color to use as the initial value. Colours can be specified in several different formats, but the easiest one is to simply use English color names such as “red” or “yellow”.
# Load the colourpicker package
library(colourpicker)
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("title", "Title", "GDP vs life exp"),
numericInput("size", "Point size", 1, 1),
checkboxInput("fit", "Add line of best fit", FALSE),
# Replace the radio buttons with a color input
colourInput("color", "Point color",
value = "red"),
selectInput("continents", "Continents",
choices = levels(gapminder$continent),
multiple = TRUE,
selected = "Europe"),
sliderInput("years", "Years",
min(gapminder$year), max(gapminder$year),
value = c(1977, 2002))
),
mainPanel(
plotOutput("plot")
)
)
)
# Define the server logic
server <- function(input, output) {
output$plot <- renderPlot({
data <- subset(gapminder,
continent %in% input$continents &
year >= input$years[1] & year <= input$years[2])
p <- ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point(size = input$size, col = input$color) +
scale_x_log10() +
ggtitle(input$title)
if (input$fit) {
p <- p + geom_smooth(method = "lm")
}
p
})
}
shinyApp(ui = ui, server = server)
Just as input functions can have different arguments depending on the type of input, so can output placeholder functions have different arguments to modify their appearance or behaviour.
For example, when displaying a plot in a Shiny app using plotOutput(), the height of the plot by default will be 400 pixels. The plotOutput() function has some parameters that can be used to modify the height or width of a plot.
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("title", "Title", "GDP vs life exp"),
numericInput("size", "Point size", 1, 1),
checkboxInput("fit", "Add line of best fit", FALSE),
colourInput("color", "Point color", value = "blue"),
selectInput("continents", "Continents",
choices = levels(gapminder$continent),
multiple = TRUE,
selected = "Europe"),
sliderInput("years", "Years",
min(gapminder$year), max(gapminder$year),
value = c(1977, 2002))
),
mainPanel(
# Make the plot 600 pixels wide and 600 pixels tall
plotOutput("plot", width = 600 , height = 600)
)
)
)
# Define the server logic
server <- function(input, output) {
output$plot <- renderPlot({
data <- subset(gapminder,
continent %in% input$continents &
year >= input$years[1] & year <= input$years[2])
p <- ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point(size = input$size, col = input$color) +
scale_x_log10() +
ggtitle(input$title)
if (input$fit) {
p <- p + geom_smooth(method = "lm")
}
p
})
}
shinyApp(ui = ui, server = server)
plotly is a popular package for creating interactive plots in Shiny. There are several other packages for interactive visualizations, but we will use plotly largely because of its function ggplotly(), which converts a ggplot2 plot into an interactive one.
# Load the plotly package
library(plotly)
library(colourpicker)
Attaching package: ‘colourpicker’
The following object is masked from ‘package:shiny’:
runExample
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
textInput("title", "Title", "GDP vs life exp"),
numericInput("size", "Point size", 1, 1),
checkboxInput("fit", "Add line of best fit", FALSE),
colourInput("color", "Point color", value = "blue"),
selectInput("continents", "Continents",
choices = levels(gapminder$continent),
multiple = TRUE,
selected = "Europe"),
sliderInput("years", "Years",
min(gapminder$year), max(gapminder$year),
value = c(1977, 2002))
),
mainPanel(
# Replace the `plotOutput()` with the plotly version
plotlyOutput("plot")
)
)
)
# Define the server logic
server <- function(input, output) {
# Replace the `renderPlot()` with the plotly version
output$plot <- renderPlotly({
# Convert the existing ggplot2 to a plotly plot
ggplotly({
data <- subset(gapminder,
continent %in% input$continents &
year >= input$years[1] & year <= input$years[2])
p <- ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point(size = input$size, col = input$color) +
scale_x_log10() +
ggtitle(input$title)
if (input$fit) {
p <- p + geom_smooth(method = "lm")
}
p
})
})
}
shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:4131
NA
An easy first step in exploring a dataset is to simply view it as a table.
So far we have focused mostly on inputs—interactive widgets that allow the user to select values. Now we want to have a table in our app, and send data to display in the table. To display objects in Shiny, we need to use output and render functions.
ui <- fluidPage(
h1("Gapminder"),
# Add a placeholder for a table output
tableOutput("table")
)
server <- function(input, output) {
# Call the appropriate render function
output$table <- renderTable({
# Show the gapminder object in the table
gapminder
})
}
shinyApp(ui, server)
The real benefit of using Shiny comes when inputs are combined with outputs. The table created in the last exercise is static—it cannot be changed—but for exploration, it would be better if the user could decide what subset of the data to see.
This can be achieved by adding an input that lets the user select a value to filter the data. This way, the table we created in the previous exercise can be made dynamic.
One of the variables in the gapminder dataset is lifeExp (life expectancy). Your task is to add a slider input to the Shiny app that lets the user choose a minimum and maximum life expectancy, and the table will only show data that matches these values.
ui <- fluidPage(
h1("Gapminder"),
# Add a slider for life expectancy filter
sliderInput(inputId = "life", label = "Life expectancy",
min = 0, max = 120,
value = c(30, 50)),
tableOutput("table")
)
server <- function(input, output) {
output$table <- renderTable({
data <- gapminder
data <- subset(
data,
# Use the life expectancy input to filter the data
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
data
})
}
shinyApp(ui, server)
When exploring a dataset, it is often useful to experiment with filtering more than one variable. For example, you might be interested in only seeing data for African countries that had a specific life expectancy.
ui <- fluidPage(
h1("Gapminder"),
sliderInput(inputId = "life", label = "Life expectancy",
min = 0, max = 120,
value = c(30, 50)),
# Add a continent selector dropdown
selectInput("continent", "Continent", choices = levels(gapminder$continent)),
tableOutput("table")
)
server <- function(input, output) {
output$table <- renderTable({
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
data <- subset(
data,
# Filter the data according to the continent input value
continent == input$continent
)
data
})
}
shinyApp(ui, server)
Before adding the continent selector, the Shiny app showed data for all continents. Now that the continent selector was added, the data can be viewed per continent. But what if the user decides they actually don’t want to filter for a specific continent, and they prefer to see all of them? Unfortunately, adding the continent selector removed that ability.
The choices argument of the selectInput() function can be modified to add another value to the continent list, and when this value is chosen, continent filtering can be turned off.
ui <- fluidPage(
h1("Gapminder"),
sliderInput(inputId = "life", label = "Life expectancy",
min = 0, max = 120,
value = c(30, 50)),
# Add an "All" value to the continent list
selectInput("continent", "Continent",
choices = c("All", levels(gapminder$continent))),
tableOutput("table")
)
server <- function(input, output) {
output$table <- renderTable({
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
# Don't subset the data if "All" continent are chosen
if (input$continent != "All") {
data <- subset(
data,
continent == input$continent
)
}
data
})
}
shinyApp(ui, server)
Recall that plots are output objects, and as such they are added to a Shiny app using the plotOutput() + renderPlot() functions. The output function is added to the UI to determine where to place the plot, and the render function in the server code is responsible for generating the plot.
Your task is to add a plot of GDP per capita vs life expectancy to the app. The data used in the plot should be the same data that is shown in the table; that is, the data in the plot should only show records that match the input filters. The code inside renderPlot() does not have access to any variables defined inside renderTable(), so you will have to literally copy and re-use the same code. Later on we’ll learn how to avoid this duplication.
ui <- fluidPage(
h1("Gapminder"),
sliderInput(inputId = "life", label = "Life expectancy",
min = 0, max = 120,
value = c(30, 50)),
selectInput("continent", "Continent",
choices = c("All", levels(gapminder$continent))),
# Add a plot output
plotOutput("plot"),
tableOutput("table")
)
server <- function(input, output) {
output$table <- renderTable({
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
if (input$continent != "All") {
data <- subset(
data,
continent == input$continent
)
}
data
})
# Create the plot render function
output$plot <- renderPlot({
# Use the same filtered data that the table uses
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
if (input$continent != "All") {
data <- subset(
data,
continent == input$continent
)
}
ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point() +
scale_x_log10()
})
}
shinyApp(ui, server)
Downloading files is achieved using the pair of functions downloadButton() and downloadHandler(). These two functions pair together similarly to how output and render functions are paired: downloadButton() determines where in the UI it will show up, while downloadHandler() needs to be saved into the output list and has the actual R code to create the downloaded file.
ui <- fluidPage(
h1("Gapminder"),
sliderInput(inputId = "life", label = "Life expectancy",
min = 0, max = 120,
value = c(30, 50)),
selectInput("continent", "Continent",
choices = c("All", levels(gapminder$continent))),
# Add a download button
downloadButton(outputId = "download_data", label = "Download"),
plotOutput("plot"),
tableOutput("table")
)
server <- function(input, output) {
output$table <- renderTable({
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
if (input$continent != "All") {
data <- subset(
data,
continent == input$continent
)
}
data
})
# Create a download handler
output$download_data <- downloadHandler(
# The downloaded file is named "gapminder_data.csv"
filename = "gapminder_data.csv",
content = function(file) {
# The code for filtering the data is copied from the
# renderTable() function
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
if (input$continent != "All") {
data <- subset(
data,
continent == input$continent
)
}
# Write the filtered data into a CSV file
write.csv(data, file, row.names = FALSE)
}
)
output$plot <- renderPlot({
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
if (input$continent != "All") {
data <- subset(
data,
continent == input$continent
)
}
ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point() +
scale_x_log10()
})
}
shinyApp(ui, server)
Listening on http://127.0.0.1:5473
NA
In the previous exercises, the code to filter gapminder according to the input values is duplicated three times: once in the table, once in the plot, and once in the download handler.
Reactive variables can be used to reduce code duplication, which is generally a good idea because it makes maintenance easier.
ui <- fluidPage(
h1("Gapminder"),
sliderInput(inputId = "life", label = "Life expectancy",
min = 0, max = 120,
value = c(30, 50)),
selectInput("continent", "Continent",
choices = c("All", levels(gapminder$continent))),
downloadButton(outputId = "download_data", label = "Download"),
plotOutput("plot"),
tableOutput("table")
)
server <- function(input, output) {
# Create a reactive variable named "filtered_data"
filtered_data <- reactive({
# Filter the data (copied from previous exercise)
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
if (input$continent != "All") {
data <- subset(
data,
continent == input$continent
)
}
data
})
output$table <- renderTable({
# Use the filtered_data variable to render the table output
data <- filtered_data()
data
})
output$download_data <- downloadHandler(
filename = "gapminder_data.csv",
content = function(file) {
# Use the filtered_data variable to create the data for
# the downloaded file
data <- filtered_data()
write.csv(data, file, row.names = FALSE)
}
)
output$plot <- renderPlot({
# Use the filtered_data variable to create the data for
# the plot
data <- filtered_data()
ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point() +
scale_x_log10()
})
}
shinyApp(ui, server)
Datatables from the DT package are often a better way to display data in a Shiny app when compared to the built-in tables. Shiny tables can be converted to datatables with two simple code modifications: instead of using tableOutput() and renderTable(), you use DT::dataTableOutput() and DT::renderDataTable(). Datatables have a wide variety of customization options, but we will not be using any special options.
Note that with the DT package, the convention is to not load the DT package, and instead use the DT:: prefix when calling the datatable functions.
ui <- fluidPage(
h1("Gapminder"),
sliderInput(inputId = "life", label = "Life expectancy",
min = 0, max = 120,
value = c(30, 50)),
selectInput("continent", "Continent",
choices = c("All", levels(gapminder$continent))),
downloadButton("download_data"),
plotOutput("plot"),
# Replace the tableOutput() with DT's version
DT::dataTableOutput("table")
)
server <- function(input, output) {
filtered_data <- reactive({
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
if (input$continent != "All") {
data <- subset(
data,
continent == input$continent
)
}
data
})
# Replace the renderTable() with DT's version
output$table <- DT::renderDataTable({
data <- filtered_data()
data
})
output$download_data <- downloadHandler(
filename = "gapminder_data.csv",
content = function(file) {
data <- filtered_data()
write.csv(data, file, row.names = FALSE)
}
)
output$plot <- renderPlot({
data <- filtered_data()
ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point() +
scale_x_log10()
})
}
shinyApp(ui, server)
Tabs are useful when you have too much content and want to split it up. To create a tab, you simply wrap UI elements in the tabPanel() function, and you need to supply a title for the tab using the title argument.
In order for tabs to appear in the UI, the tab panels need to be grouped into a tabset “container”, by wrapping all the tab panels inside tabsetPanel().
Your task is to add tabs to the Shiny app, such that the inputs and download button are in one tab, the plot is in another tab, and the table is in a third tab. Since this is purely a visual change, all the code changes are to be done in the UI portion only.
ui <- fluidPage(
h1("Gapminder"),
# Create a container for tab panels
tabsetPanel(
# Create an "Inputs" tab
tabPanel(
title = "Inputs",
sliderInput(inputId = "life", label = "Life expectancy",
min = 0, max = 120,
value = c(30, 50)),
selectInput("continent", "Continent",
choices = c("All", levels(gapminder$continent))),
downloadButton("download_data")
),
# Create a "Plot" tab
tabPanel(
title = "Plot",
plotOutput("plot")
),
# Create "Table" tab
tabPanel(
title = "Table",
DT::dataTableOutput("table")
)
)
)
server <- function(input, output) {
filtered_data <- reactive({
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
if (input$continent != "All") {
data <- subset(
data,
continent == input$continent
)
}
data
})
output$table <- DT::renderDataTable({
data <- filtered_data()
data
})
output$download_data <- downloadHandler(
filename = "gapminder_data.csv",
content = function(file) {
data <- filtered_data()
write.csv(data, file, row.names = FALSE)
}
)
output$plot <- renderPlot({
data <- filtered_data()
ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point() +
scale_x_log10()
})
}
shinyApp(ui, server)
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.
my_css <- "
#download_data {
/* Change the background color of the download button
to orange. */
background: orange;
/* Change the text size to 20 pixels. */
font-size: 20px;
}
#table {
/* Change the text color of the table to red. */
color: red;
}
"
ui <- fluidPage(
h1("Gapminder"),
# Add the CSS that we wrote to the Shiny app
tags$style(my_css),
tabsetPanel(
tabPanel(
title = "Inputs",
sliderInput(inputId = "life", label = "Life expectancy",
min = 0, max = 120,
value = c(30, 50)),
selectInput("continent", "Continent",
choices = c("All", levels(gapminder$continent))),
downloadButton("download_data")
),
tabPanel(
title = "Plot",
plotOutput("plot")
),
tabPanel(
title = "Table",
DT::dataTableOutput("table")
)
)
)
server <- function(input, output) {
filtered_data <- reactive({
data <- gapminder
data <- subset(
data,
lifeExp >= input$life[1] & lifeExp <= input$life[2]
)
if (input$continent != "All") {
data <- subset(
data,
continent == input$continent
)
}
data
})
output$table <- DT::renderDataTable({
data <- filtered_data()
data
})
output$download_data <- downloadHandler(
filename = "gapminder_data.csv",
content = function(file) {
data <- filtered_data()
write.csv(data, file, row.names = FALSE)
}
)
output$plot <- renderPlot({
data <- filtered_data()
ggplot(data, aes(gdpPercap, lifeExp)) +
geom_point() +
scale_x_log10()
})
}
shinyApp(ui, server)
Listening on http://127.0.0.1:5473
NA
You are provided with a sample dataset named artofwar, which contains the entire text of the Art of War book. You can inspect the given Art of War text by running head(artofwar) or tail(artofwar) to see the first and last few verses of the book.
As mentioned in the video, since word clouds are not an output you saw before, they require a new pair of output and render functions: wordcloud2Output() and renderWordcloud2(). These output functions are available from the wordcloud2 package.
library(readr)
artofwar <- read_csv("artofwar.csv")
Missing column names filled in: 'X1' [1]Parsed with column specification:
cols(
X1 = col_double(),
x = col_character()
)
artofwar <- artofwar$x
library(wordcloud2)
library(tm)
Loading required package: NLP
Attaching package: ‘NLP’
The following object is masked from ‘package:ggplot2’:
annotate
create_wordcloud <- function(data, num_words = 100, background = "white") {
# If text is provided, convert it to a dataframe of word frequencies
if (is.character(data)) {
corpus <- Corpus(VectorSource(data))
corpus <- tm_map(corpus, tolower)
corpus <- tm_map(corpus, removePunctuation)
corpus <- tm_map(corpus, removeNumbers)
corpus <- tm_map(corpus, removeWords, stopwords("english"))
tdm <- as.matrix(TermDocumentMatrix(corpus))
data <- sort(rowSums(tdm), decreasing = TRUE)
data <- data.frame(word = names(data), freq = as.numeric(data))
}
# Make sure a proper num_words is provided
if (!is.numeric(num_words) || num_words < 3) {
num_words <- 3
}
# Grab the top n most common words
data <- head(data, n = num_words)
if (nrow(data) == 0) {
return(NULL)
}
wordcloud2(data, backgroundColor = background)
}
# Define UI for the application
ui <- fluidPage(
h1("Word Cloud"),
# Add the word cloud output placeholder to the UI
wordcloud2Output(outputId = "cloud")
)
# Define the server logic
server <- function(input, output) {
# Render the word cloud and assign it to the output list
output$cloud <- renderWordcloud2({
# Create a word cloud object
create_wordcloud(artofwar)
})
}
# Run the application
shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:3868
Error in /: non-numeric argument to binary operator [No stack trace available]
NA
Recall that create_wordcloud() has two optional arguments: num_words, which is an integer specifying the maximum number of words to draw, and background, which specifies the background color of the image.
Right now, the Shiny app simply outputs a word cloud with the exact same parameters all the time. Since the word cloud generating function accepts these two parameters, it would be wasteful not to use them. The parameters should be adjustable by the user using Shiny inputs.
Your task is to add two inputs to the Shiny app, and use the values from these inputs as the num_words and background parameters of the word cloud.
shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:3868
As you have seen in previous chapters, using a layout in a Shiny app is important in order to organize the interface and make it easier to use.
The app currently has very few objects (one title, two inputs, one word cloud output) so it is still manageable without a layout. However, the app is going to grow in the next exercises and having a sidebar layout will be beneficial. It’s a good idea to add a layout to your app earlier rather than later, because placing new Shiny UI elements into an existing layout is easier than rearranging a larger non-structured app later.
As is commonly done with Shiny apps and other interactive applications, the inputs will be kept in the smaller sidebar, while the main output (the word cloud) will be in the larger main panel.
ui <- fluidPage(
h1("Word Cloud"),
# Add a sidebar layout to the UI
sidebarLayout(
# Define a sidebar panel around the inputs
sidebarPanel(
numericInput("num", "Maximum number of words",
value = 100, min = 5),
colourInput("col", "Background color", value = "white")
),
# Define a main panel around the output
mainPanel(
wordcloud2Output("cloud")
)
)
)
server <- function(input, output) {
output$cloud <- renderWordcloud2({
create_wordcloud(artofwar,
num_words = input$num, background = input$col)
})
}
shinyApp(ui = ui, server = server)
he 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, textarea inputs behave very similar to text inputs in every other way.
ui <- fluidPage(
h1("Word Cloud"),
sidebarLayout(
sidebarPanel(
# Add a textarea input
textAreaInput("text", "Enter text", rows = 7),
numericInput("num", "Maximum number of words",
value = 100, min = 5),
colourInput("col", "Background color", value = "white")
),
mainPanel(
wordcloud2Output("cloud")
)
)
)
server <- function(input, output) {
output$cloud <- renderWordcloud2({
# Use the textarea's value as the word cloud data source
create_wordcloud(data = input$text, num_words = input$num,
background = input$col)
})
}
shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:3868
transformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documents
NA
### Upload a text file (ui)
Rather than typing a long piece of text into a box, it can be more convenient to upload a text file if the text is extremely long.
Uploading files to a Shiny app is done using fileInput().
ui <- fluidPage(
h1("Word Cloud"),
sidebarLayout(
sidebarPanel(
textAreaInput("text", "Enter text", rows = 7),
# Add a file input
fileInput("file", "Select a file"),
numericInput("num", "Maximum number of words",
value = 100, min = 5),
colourInput("col", "Background color", value = "white")
),
mainPanel(
wordcloud2Output("cloud")
)
)
)
server <- function(input, output) {
output$cloud <- renderWordcloud2({
create_wordcloud(input$text, num_words = input$num,
background = input$col)
})
}
shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:3868
transformation drops documentstransformation drops documentstransformation drops documentstransformation drops documents
NA
After the user selects a file, that file gets uploaded to the computer that runs the Shiny app, and it becomes available in the server.
If the input ID of a file input is “myfile”, then you might expect input$myfile to give you access to the file that was uploaded, but that is not how file inputs actually work. input$myfile will return a data.frame that contains a few pieces of metadata about the selected file, with the main one to care about being datapath. Assuming the file input’s ID is “myfile”, input$myfile$datapath will be the path where the file is located.
After getting the uploaded file’s path (for example C:\Users\Dean\AppData\Local\Temp\path\to\file.txt), this path can be used to read the file in whatever way you need. You may use read.csv() if the uploaded file is a CSV file, or readLines() if you simply want to read all the lines in the file, or any other function that accepts a file path.
ui <- fluidPage(
h1("Word Cloud"),
sidebarLayout(
sidebarPanel(
textAreaInput("text", "Enter text", rows = 7),
fileInput("file", "Select a file"),
numericInput("num", "Maximum number of words",
value = 100, min = 5),
colourInput("col", "Background color", value = "white")
),
mainPanel(
wordcloud2Output("cloud")
)
)
)
server <- function(input, output) {
# Define a reactive variable named `input_file`
input_file <- reactive({
if (is.null(input$file)) {
return("")
}
# Read the text in the uploaded file
readLines(input$file$datapath)
})
output$cloud <- renderWordcloud2({
# Use the reactive variable as the word cloud data source
create_wordcloud(data = input_file(), num_words = input$num,
background = input$col)
})
}
shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:3868
transformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documents
NA
Using file selectors in Shiny really opens up the door to many possibilities, because now users can upload any data to your app!
Over the last few exercises, you’ve used 3 different sources for the word cloud: the Art of War book, a text field, and a text file. However, only one source was working at any given time. In this exercise, you will provide the user with a way to select which data source to use for the word cloud.
ui <- fluidPage(
h1("Word Cloud"),
sidebarLayout(
sidebarPanel(
# Add radio buttons input
radioButtons(
inputId = "source",
label = "Word source",
choices = c(
# First choice is "book", with "Art of War" displaying
"Art of War" = "book",
# Second choice is "own", with "Use your own words" displaying
"Use your own words" = "own",
# Third choice is "file", with "Upload a file" displaying
"Upload a file" = "file"
)
),
textAreaInput("text", "Enter text", rows = 7),
fileInput("file", "Select a file"),
numericInput("num", "Maximum number of words",
value = 100, min = 5),
colourInput("col", "Background color", value = "white")
),
mainPanel(
wordcloud2Output("cloud")
)
)
)
server <- function(input, output) {
input_file <- reactive({
if (is.null(input$file)) {
return("")
}
readLines(input$file$datapath)
})
output$cloud <- renderWordcloud2({
create_wordcloud(input_file(), num_words = input$num,
background = input$col)
})
}
shinyApp(ui = ui, server = server)
When working with radio buttons, sometimes you need to use conditional logic (if-else statements) when accessing the radio button’s value in the server. This is necessary when different actions are performed depending on the exact choice, and the chosen value needs to be inspected before deciding how to proceed.
For example, with the radio buttons that select a data source, different code will need to run depending on which choice is selected.
Your next task is to use the appropriate data source in the word cloud function, according to what radio button the user chooses.
ui <- fluidPage(
h1("Word Cloud"),
sidebarLayout(
sidebarPanel(
radioButtons(
inputId = "source",
label = "Word source",
choices = c(
"Art of War" = "book",
"Use your own words" = "own",
"Upload a file" = "file"
)
),
textAreaInput("text", "Enter text", rows = 7),
fileInput("file", "Select a file"),
numericInput("num", "Maximum number of words",
value = 100, min = 5),
colourInput("col", "Background color", value = "white")
),
mainPanel(
wordcloud2Output("cloud")
)
)
)
server <- function(input, output) {
# Create a "data_source" reactive variable
data_source <- reactive({
# Return the appropriate data source depending on
# the chosen radio button
if (input$source == "book") {
data <- artofwar
} else if (input$source == "own") {
data <- input$text
} else if (input$source == "file") {
data <- input_file()
}
return(data)
})
input_file <- reactive({
if (is.null(input$file)) {
return("")
}
readLines(input$file$datapath)
})
output$cloud <- renderWordcloud2({
# Use the data_source reactive variable as the data
# in the word cloud function
create_wordcloud(data = data_source(), num_words = input$num,
background = input$col)
})
}
shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:3868
transformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documents
NA
The word cloud app now has three different ways to supply words to the word cloud. Two of these methods involve a specific UI element that is only useful for them: there is a textarea that is only used when the user selects the “own” word source, and there is a file input that is only relevant when the user chooses the “file” source. Ideally, only inputs that are needed would appear at any given moment.
ui <- fluidPage(
h1("Word Cloud"),
sidebarLayout(
sidebarPanel(
radioButtons(
inputId = "source",
label = "Word source",
choices = c(
"Art of War" = "book",
"Use your own words" = "own",
"Upload a file" = "file"
)
),
conditionalPanel(
condition = "input.source == 'own'",
textAreaInput("text", "Enter text", rows = 7)
),
# Wrap the file input in a conditional panel
conditionalPanel(
# The condition should be that the user selects
# "file" from the radio buttons
condition = "input.source == 'file'",
fileInput("file", "Select a file")
),
numericInput("num", "Maximum number of words",
value = 100, min = 5),
colourInput("col", "Background color", value = "white")
),
mainPanel(
wordcloud2Output("cloud")
)
)
)
server <- function(input, output) {
data_source <- reactive({
if (input$source == "book") {
data <- artofwar
} else if (input$source == "own") {
data <- input$text
} else if (input$source == "file") {
data <- input_file()
}
return(data)
})
input_file <- reactive({
if (is.null(input$file)) {
return("")
}
readLines(input$file$datapath)
})
output$cloud <- renderWordcloud2({
create_wordcloud(data_source(), num_words = input$num,
background = input$col)
})
}
shinyApp(ui = ui, server = server)
Conditional panels are a great way to reduce unnecessary clutter on the page. Just remember replacing input$ with input. in the condition!
The word cloud app now has several different inputs, and modifying each one of them causes the word cloud to redraw with the new set of parameters, just as expected.
But this behaviour can also be annoying sometimes. For example, when typing text in the textarea, the word cloud keeps regenerating without waiting for you to finish typing. This can be controlled with isolate().
All the code inside renderWordcloud2() that renders the word cloud has been removed. Your task is to re-create the word cloud and isolate it so that changing the parameters will not automatically trigger a new word cloud.
ui <- fluidPage(
h1("Word Cloud"),
sidebarLayout(
sidebarPanel(
radioButtons(
inputId = "source",
label = "Word source",
choices = c(
"Art of War" = "book",
"Use your own words" = "own",
"Upload a file" = "file"
)
),
conditionalPanel(
condition = "input.source == 'own'",
textAreaInput("text", "Enter text", rows = 7)
),
conditionalPanel(
condition = "input.source == 'file'",
fileInput("file", "Select a file")
),
numericInput("num", "Maximum number of words",
value = 100, min = 5),
colourInput("col", "Background color", value = "white")
),
mainPanel(
wordcloud2Output("cloud")
)
)
)
server <- function(input, output) {
data_source <- reactive({
if (input$source == "book") {
data <- artofwar
} else if (input$source == "own") {
data <- input$text
} else if (input$source == "file") {
data <- input_file()
}
return(data)
})
input_file <- reactive({
if (is.null(input$file)) {
return("")
}
readLines(input$file$datapath)
})
output$cloud <- renderWordcloud2({
# Isolate the code to render the word cloud so that it will
# not automatically re-render on every parameter change
isolate({
# Render the word cloud using inputs and reactives
create_wordcloud(data = data_source(), num_words = input$num,
background = input$col)
})
})
}
shinyApp(ui = ui, server = server)
If there is a reactive variable that appears multiple times in your 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.
The following code defines a reactive variable result and calculates it using three input values:
result <- reactive({
temp <- input$X + input$Y
isolate({
temp <- temp * input$Y * input$Z
})
temp
})
When the user changes the value of either input$X or input$Y, the result reactive variable gets updated.
After isolating the word cloud render code so that it wouldn’t update too often, the last step is to provide a way to render the word cloud only when the user chooses to. This can be achieved with the help of an actionButton().
ui <- fluidPage(
h1("Word Cloud"),
sidebarLayout(
sidebarPanel(
radioButtons(
inputId = "source",
label = "Word source",
choices = c(
"Art of War" = "book",
"Use your own words" = "own",
"Upload a file" = "file"
)
),
conditionalPanel(
condition = "input.source == 'own'",
textAreaInput("text", "Enter text", rows = 7)
),
conditionalPanel(
condition = "input.source == 'file'",
fileInput("file", "Select a file")
),
numericInput("num", "Maximum number of words",
value = 100, min = 5),
colourInput("col", "Background color", value = "white"),
# Add a "draw" button to the app
actionButton(inputId = "draw", label = "Draw!")
),
mainPanel(
wordcloud2Output("cloud")
)
)
)
server <- function(input, output) {
data_source <- reactive({
if (input$source == "book") {
data <- artofwar
} else if (input$source == "own") {
data <- input$text
} else if (input$source == "file") {
data <- input_file()
}
return(data)
})
input_file <- reactive({
if (is.null(input$file)) {
return("")
}
readLines(input$file$datapath)
})
output$cloud <- renderWordcloud2({
# Add the draw button as a dependency to
# cause the word cloud to re-render on click
input$draw
isolate({
create_wordcloud(data_source(), num_words = input$num,
background = input$col)
})
})
}
shinyApp(ui = ui, server = server)
Listening on http://127.0.0.1:3868
transformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documentstransformation drops documents
NA