Lecturer: Michael Lydeamore
Department of Econometrics and Business Statistics
Important
You will be able to:
Tip
Interactivity is useful when it helps someone answer a question—not merely because it is possible.
Data: Cricsheet, accessed through the cricketdata package.
Someone viewing the chart might want to:
Note
Which of these questions should the display support?
What does the person want to understand or decide?
What can they select, reveal, filter or compare?
How does the display show what changed and what it means?
Tip
The user can influence the path, but the designer still defines the useful paths.
A widget is only useful when its action serves the user’s goal.
Images adapted from Spencer (2022), Data in Wonderland.
Can the user perform the actions needed to obtain the information?
Can the user communicate their intention accurately and understand the response?
Is the insight gained worth the effort required from the user and developer?
Adapted from Tominski and Schumann (2020), Interactive Visual Data Analysis.
Global temperature: NASA Earth Indicator
Note
What question becomes easier to answer through interaction?
How You Will Die — FlowingData
Note
What does personalisation add—and what responsibilities come with it?
Use interaction when it supports:
Important
If every useful path leads to the same conclusion, a well-designed static display may be better.
Shiny is an R framework for building web applications without having to manage the browser–server communication yourself.
Note
As you explore it, identify the user goal, available actions and feedback.
Let a cricket follower compare batting patterns within a chosen WPL season.
It needs only:
app.R file has four partslibrary(shiny) # 1. Load packages and data
library(bslib)
ui <- page_fluid( # 2. Define what the user sees
...
)
server <- function(input, output, session) { # 3. Define responses
...
}
shinyApp(ui, server) # 4. Assemble and run the appSave these four parts together in a file called app.R.
| Function | The user can |
|---|---|
selectInput() |
choose one item from a list |
sliderInput() |
choose a number or range |
checkboxInput() |
turn an option on or off |
dateRangeInput() |
choose a period |
actionButton() |
trigger an explicit action |
fileInput() |
upload a file |
Tip
Choose the control that matches the decision the user is making.
Important
The string "top_n" in the UI becomes input$top_n in the server.
"season" is the input ID."batters_plot" is the output ID. The UI does not draw the plot; it creates a place for it.
| Server | UI |
|---|---|
renderPlot() |
plotOutput() |
renderText() |
textOutput() |
renderTable() |
tableOutput() |
renderPrint() |
verbatimTextOutput() |
renderUI() |
uiOutput() |
DT::renderDT() |
DT::DTOutput() |
Choose the pair that matches the kind of result you are producing.
Important
The string "batters_plot" in the UI becomes output$batters_plot in the server.
output$batters_plot <- renderPlot({
batters |>
filter(season == input$season) |>
ggplot(aes(dot_percent, boundary_percent)) +
geom_point()
})Shiny records that batters_plot depends on input$season.
ui <- page_sidebar(
title = "How do WPL batters score their runs?",
sidebar = sidebar(
selectInput("season", "Choose a season",
choices = sort(unique(batters$season)))
),
plotOutput("batters_plot")
)The UI contains the visible controls and output locations.
server <- function(input, output, session) {
output$batters_plot <- renderPlot({
batters |>
filter(season == input$season) |>
ggplot(aes(dot_percent, boundary_percent)) +
geom_point()
})
}
shinyApp(ui, server)The server reads input$season, creates a plot and assigns it to output$batters_plot.
When the user chooses a different season:
Important
input$season changes → renderPlot() reruns → batters_plot is redrawn.
Shiny invalidates and recomputes only the downstream work that depends on a changed value.
selected_batters <- reactive({
batters |>
filter(season == input$season)
})
output$batters_plot <- renderPlot({
ggplot(selected_batters(),
aes(dot_percent, boundary_percent)) +
geom_point()
})Tip
A reactive expression is called like a function: selected_batters().
req() waits until an input is availableoutput$batters_plot <- renderPlot({
req(input$season)
ggplot(selected_batters(),
aes(dot_percent, boundary_percent)) +
geom_point()
})Use req() when an output cannot be calculated until the user has supplied a value.
The code does not tell the user:
Tip
Write labels in the user’s language, not the implementation’s language.
An effective output includes:
Note
“Upper-left” is meaningful guidance. “Interactive scatterplot” is merely a format label.
For the cricket app:
Defaults influence what users see first; they should be deliberate and defensible.
Check that the app:
card(
card_header(textOutput("plot_title")),
p("Batters towards the upper-left combine more boundaries",
"with fewer dot balls."),
plotOutput("batters_plot"),
card_footer(
"Top 20 run-scorers with at least 50 balls faced.",
"Source: Cricsheet via cricketdata."
)
)Run the complete version with shiny::runApp("week9/cricket_app.R") from the project root.
The user wants to understand why two batters occupy similar positions. Which addition best supports that goal?
Important
Choose features by the question they help answer—not by how impressive they look.
The UI sends input values to the server; the server sends updated output back. Shiny manages this exchange and tracks the reactive dependencies.
Complete these sentences:
Important
bslibpage_sidebar(
title = "App title",
sidebar = sidebar(
selectInput("season", "Choose a season", seasons)
),
card(
card_header("What the output shows"),
plotOutput("plot")
)
)Use layout_columns() for responsive columns and page_navbar() when the content genuinely requires multiple views.
browser() or an IDE breakpoint inside a reactive expression.reactlog when dependencies are unclear.rsconnect::deployApp() or publish from the IDE.inst/ and locate them with system.file().Note
These are follow-on techniques. First make the user goal and the reactive path clear.

ETC5523 Week 9