Skip to main content
R Programming
CHAPTER 26 Beginner

R Shiny Basics

Updated: May 18, 2026
5 min read

# CHAPTER 26

R Shiny Basics

1. Chapter Introduction

Shiny turns R analyses into interactive web applications — no HTML, CSS, or JavaScript required. Executives can filter their own data; analysts can share insights without emailing static reports. This chapter builds a complete interactive analytics dashboard.

2. Shiny Architecture

text
12345678910111213
SHINY APP STRUCTURE:

app.R (or separate ui.R + server.R)
  ├── ui      — What users see (HTML layout)
  │     ├── Input widgets (sliders, dropdowns, date pickers)
  │     └── Output placeholders (plots, tables, text)
  └── server  — What R computes (reactive logic)
        ├── render*() — Creates outputs
        └── reactive() — Intermediate computations

REACTIVITY:
  Input changes → reactive() recomputes → render*() updates output
  All connected automatically — no manual event handling!

3. Basic Shiny App

r
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
library(shiny)
library(ggplot2)
library(dplyr)

# Generate data
set.seed(42)
n <- 500
sales_data <- data.frame(
  date     = seq(as.Date("2024-01-01"), by="day", length.out=n),
  region   = sample(c("North","South","East","West"), n, TRUE),
  product  = sample(c("Laptop","Phone","Tablet","Monitor"), n, TRUE),
  revenue  = round(runif(n, 500, 5000), -2),
  profit   = round(runif(n, 100, 2000), -2)
)

# ─── UI ───────────────────────────────────────────────
ui <- fluidPage(
  titlePanel("📊 Sales Analytics Dashboard"),
  tags$head(tags$style(HTML("
    body { font-family: &#039;Inter', sans-serif; background: #F8F9FA; }
    .well { background: white; border-radius: 8px; }
    h2 { color: #1565C0; }
  "))),

  sidebarLayout(
    sidebarPanel(
      h4("Filters"),
      dateRangeInput("date_range", "Date Range:",
                     start=min(sales_data$date),
                     end=max(sales_data$date)),
      selectInput("region", "Region:",
                  choices=c("All", unique(sales_data$region)),
                  selected="All"),
      checkboxGroupInput("products", "Products:",
                          choices=unique(sales_data$product),
                          selected=unique(sales_data$product)),
      hr(),
      actionButton("reset", "Reset Filters", class="btn-primary btn-sm"),
      br(), br(),
      downloadButton("download_data", "Download CSV")
    ),

    mainPanel(
      # KPI cards
      fluidRow(
        valueBoxOutput("total_revenue", width=4),
        valueBoxOutput("total_profit",  width=4),
        valueBoxOutput("total_orders",  width=4)
      ),
      # Charts
      fluidRow(
        column(8, plotOutput("revenue_trend", height="300px")),
        column(4, plotOutput("region_pie",    height="300px"))
      ),
      fluidRow(
        column(6, plotOutput("product_bar", height="250px")),
        column(6, plotOutput("profit_scatter", height="250px"))
      ),
      # Data table
      h4("Transaction Details"),
      DT::dataTableOutput("data_table")
    )
  )
)

# ─── SERVER ───────────────────────────────────────────
server <- function(input, output, session) {

  # Reactive: filtered data (recomputes whenever input changes)
  filtered <- reactive({
    df <- sales_data %>%
      filter(date >= input$date_range[1],
             date <= input$date_range[2],
             product %in% input$products)
    if (input$region != "All") df <- df %>% filter(region == input$region)
    df
  })

  # Reset filters
  observeEvent(input$reset, {
    updateDateRangeInput(session, "date_range",
                          start=min(sales_data$date), end=max(sales_data$date))
    updateSelectInput(session, "region", selected="All")
    updateCheckboxGroupInput(session, "products",
                              selected=unique(sales_data$product))
  })

  # KPI Outputs
  output$total_revenue <- renderValueBox({
    valueBox(paste0("$", format(round(sum(filtered()$revenue)/1000,1), nsmall=1), "K"),
              "Total Revenue", icon=icon("dollar-sign"), color="blue")
  })
  output$total_profit <- renderValueBox({
    valueBox(paste0("$", format(round(sum(filtered()$profit)/1000,1), nsmall=1), "K"),
              "Total Profit", icon=icon("trending-up"), color="green")
  })
  output$total_orders <- renderValueBox({
    valueBox(nrow(filtered()), "Total Orders", icon=icon("shopping-cart"), color="purple")
  })

  # Revenue trend chart
  output$revenue_trend <- renderPlot({
    filtered() %>%
      group_by(date) %>%
      summarise(revenue=sum(revenue), .groups="drop") %>%
      ggplot(aes(date, revenue)) +
      geom_line(color="#1565C0", size=1) +
      geom_smooth(method="loess", color="red", alpha=0.2) +
      scale_y_continuous(labels=function(x) paste0("$",x/1000,"K")) +
      labs(title="Daily Revenue Trend", x=NULL, y="Revenue") +
      theme_minimal()
  })

  # Region pie
  output$region_pie <- renderPlot({
    filtered() %>%
      group_by(region) %>%
      summarise(revenue=sum(revenue), .groups="drop") %>%
      ggplot(aes(x="", y=revenue, fill=region)) +
      geom_col(width=1) + coord_polar("y") +
      scale_fill_manual(values=c("#1565C0","#2E7D32","#E65100","#6A1B9A")) +
      labs(title="Revenue by Region") +
      theme_void() + theme(legend.position="bottom")
  })

  # Product bar
  output$product_bar <- renderPlot({
    filtered() %>%
      group_by(product) %>%
      summarise(revenue=sum(revenue), .groups="drop") %>%
      ggplot(aes(reorder(product,revenue), revenue, fill=product)) +
      geom_col(show.legend=FALSE) + coord_flip() +
      scale_y_continuous(labels=function(x) paste0("$",x/1000,"K")) +
      labs(title="Revenue by Product", x=NULL) + theme_minimal()
  })

  # Scatter
  output$profit_scatter <- renderPlot({
    ggplot(filtered(), aes(revenue, profit, color=region)) +
      geom_point(alpha=0.4, size=2) +
      scale_color_manual(values=c("#1565C0","#2E7D32","#E65100","#6A1B9A")) +
      labs(title="Revenue vs Profit", x="Revenue", y="Profit") +
      theme_minimal() + theme(legend.position="bottom")
  })

  # Data table
  output$data_table <- DT::renderDataTable(
    filtered() %>% arrange(desc(date)) %>%
      mutate(revenue=paste0("$", format(revenue, big.mark=",")),
             profit=paste0("$", format(profit, big.mark=","))),
    options=list(pageLength=10, scrollX=TRUE)
  )

  # Download
  output$download_data <- downloadHandler(
    filename = function() paste0("sales_", Sys.Date(), ".csv"),
    content  = function(file) write.csv(filtered(), file, row.names=FALSE)
  )
}

shinyApp(ui, server)
# Run: app.R → Click "Run App" in RStudio

4. Common Mistakes

  • Calling filtered() multiple times in server: Each call re-executes the reactive. Store result: df <- filtered() then use df multiple times — or the reactive handles caching automatically.
  • Heavy computation inside render*(): Move expensive operations into reactive() so they run once and are shared. Never put database queries directly inside render functions.

5. MCQs

Question 1

Shiny ui defines?

Question 2

reactive() in server creates?

Question 3

renderPlot() produces?

Question 4

observeEvent(input$btn, {...}) fires?

Question 5

downloadHandler() creates?

Question 6

Shiny requires?

Question 7

filtered()$revenue (with parentheses) is because?

Question 8

DT::renderDataTable() creates?

Question 9

updateSelectInput() changes?

Question 10

Shiny app is deployed to production via?

6. Interview Questions

  • Q: How does Shiny's reactive system work?
  • Q: Where would you host a Shiny app for production use?

7. Summary

Shiny: ui (HTML layout) + server (reactive logic). Input widgets: selectInput, sliderInput, dateRangeInput. Outputs: renderPlot() + plotOutput(), renderTable() + tableOutput(). Reactivity: reactive() caches computations, auto-updates when inputs change. observeEvent() for button clicks. Deploy: shinyapps.io (free), Posit Connect (enterprise). Add DT::dataTableOutput() for searchable tables.

8. Next Chapter Recommendation

In Chapter 27: Advanced R Programming Concepts, we master functional programming, apply family, environments, and performance optimization.

Finish this Chapter

Save your progress on your learning path and prepare for coding interview challenges.

Discussion

Join the discussion

Log in or create a free account to participate.

Sort: ·