iBetter Books
수정

Ch 02. 탭과 네비게이션 (tabsetPanel, navbarPage)

앱의 기능이 늘어날수록 한 화면에 모든 것을 담기가 어렵습니다. 탭과 네비게이션 바는 기능을 논리적 섹션으로 나누어 사용자가 원하는 부분으로 쉽게 이동할 수 있게 해줍니다. 규모에 따라 알맞은 네비게이션 방식을 선택하면 앱이 한결 정돈되어 보입니다.

tabsetPanel과 tabPanel

tabsetPanel()은 메인 영역 안에 탭을 만듭니다. 같은 입력 컨트롤을 공유하면서 결과만 다른 탭으로 분리할 때 좋습니다.

library(shiny)
library(ggplot2)

ui <- fluidPage(
  titlePanel("mtcars 분석"),
  sidebarLayout(
    sidebarPanel(
      selectInput("cyl", "실린더",
                  choices = c("전체" = "all", "4", "6", "8"),
                  selected = "all")
    ),
    mainPanel(
      tabsetPanel(
        id = "main_tabs",  # 서버에서 현재 탭 확인 시 사용
        selected = "차트",  # 초기 활성 탭

        tabPanel("차트",
          plotOutput("bar_plot")
        ),
        tabPanel("테이블",
          tableOutput("data_table")
        ),
        tabPanel("요약",
          verbatimTextOutput("summary_text")
        )
      )
    )
  )
)

server <- function(input, output, session) {
  filtered <- reactive({
    if (input$cyl == "all") mtcars
    else mtcars[mtcars$cyl == as.integer(input$cyl), ]
  })

  output$bar_plot <- renderPlot({
    ggplot(filtered(), aes(x = rownames(filtered()), y = mpg)) +
      geom_col(fill = "#2980b9") +
      coord_flip() +
      labs(x = NULL, y = "연비(mpg)") +
      theme_minimal()
  })

  output$data_table <- renderTable(filtered(), rownames = TRUE)

  output$summary_text <- renderPrint(summary(filtered()))

  # 현재 선택된 탭 확인
  observe({
    cat("현재 탭:", input$main_tabs, "\n")
  })
}

shinyApp(ui, server)

tabsetPanel(id = "main_tabs")처럼 id를 지정하면, 서버에서 input$main_tabs로 현재 선택된 탭 이름을 알 수 있습니다. 이를 이용해 특정 탭에서만 무거운 계산을 실행하도록 최적화할 수 있습니다.

tabsetPanel 스타일 변경

type 인수로 탭 스타일을 바꿀 수 있습니다.

tabsetPanel(
  type = "tabs",   # 기본 탭 스타일
  # type = "pills", # 알약 모양 버튼 스타일
  tabPanel("탭1", ...),
  tabPanel("탭2", ...)
)

앱이 완전히 독립적인 여러 섹션으로 구성될 때는 navbarPage()를 씁니다. fluidPage() 대신 최상위 UI 함수로 사용하며, 상단에 네비게이션 바를 만듭니다.

library(shiny)

ui <- navbarPage(
  title    = "실전 R 대시보드",
  id       = "nav",
  selected = "데이터 탐색",

  tabPanel("데이터 탐색",
    sidebarLayout(
      sidebarPanel(
        selectInput("dataset", "데이터셋",
                    choices = c("mtcars", "iris"))
      ),
      mainPanel(
        tableOutput("explore_table")
      )
    )
  ),

  tabPanel("시각화",
    fluidRow(
      column(6, plotOutput("plot_left")),
      column(6, plotOutput("plot_right"))
    )
  ),

  tabPanel("보고서",
    fluidRow(
      column(12,
        downloadButton("dl_report", "보고서 다운로드"),
        hr(),
        verbatimTextOutput("report_text")
      )
    )
  )
)

server <- function(input, output, session) {
  output$explore_table <- renderTable({
    head(get(input$dataset))
  })

  output$plot_left <- renderPlot({
    plot(mtcars$hp, mtcars$mpg, pch = 19, col = "steelblue",
         xlab = "마력", ylab = "연비")
  })

  output$plot_right <- renderPlot({
    boxplot(mpg ~ cyl, data = mtcars, col = "tomato",
            xlab = "실린더", ylab = "연비")
  })

  output$report_text <- renderPrint({
    cat("분석 일시:", format(Sys.time(), "%Y-%m-%d %H:%M:%S"), "\n")
    cat("데이터셋:", input$dataset, "\n")
  })

  output$dl_report <- downloadHandler(
    filename = function() paste0("report_", Sys.Date(), ".txt"),
    content  = function(file) {
      writeLines(c("실전 R 보고서", format(Sys.Date())), file)
    }
  )
}

shinyApp(ui, server)

navbarMenu()를 쓰면 네비게이션 바에 드롭다운 메뉴를 만들 수 있습니다.

ui <- navbarPage(
  title = "분석 앱",

  tabPanel("홈", p("메인 페이지입니다.")),

  navbarMenu("분석",
    tabPanel("기초 통계", verbatimTextOutput("basic_stat")),
    "----",              # 구분선
    tabPanel("회귀 분석", plotOutput("regression_plot")),
    tabPanel("군집 분석", plotOutput("cluster_plot"))
  ),

  navbarMenu("보고서",
    tabPanel("일별 보고서", tableOutput("daily_tbl")),
    tabPanel("월별 보고서", tableOutput("monthly_tbl"))
  ),

  tabPanel("설정", p("설정 페이지"))
)

"----" 문자열을 tabPanel() 대신 넣으면 메뉴 내 구분선이 됩니다.

navlistPanel()은 세로 목록으로 탭을 나열합니다. 탭이 많거나 제목이 길 때 가로 탭보다 보기 좋습니다.

ui <- fluidPage(
  navlistPanel(
    id      = "nav_list",
    widths  = c(2, 10),  # 목록:내용 너비 비율

    tabPanel("소개",     p("이 앱은...")),
    tabPanel("데이터",   tableOutput("data_view")),
    "분석 섹션",          # 헤더 그룹
    tabPanel("기초 통계", verbatimTextOutput("stats")),
    tabPanel("시각화",   plotOutput("viz"))
  )
)

탭 간 전환 (서버에서 제어)

서버 코드에서 탭을 프로그래밍으로 전환하려면 updateTabsetPanel() 또는 updateNavbarPage()를 씁니다.

server <- function(input, output, session) {
  observeEvent(input$go_to_report, {
    # 버튼 클릭 시 "보고서" 탭으로 이동
    updateNavbarPage(session, "nav", selected = "보고서")
  })
}

이 패턴은 "분석 완료" 버튼을 누르면 자동으로 결과 탭으로 넘어가는 UX 흐름을 만들 때 유용합니다.

네비게이션 방식 비교

함수 위치 사용 시기
tabsetPanel() 메인 영역 내부 같은 입력을 공유하는 관련 탭
navbarPage() 최상위, 상단 바 독립적인 섹션이 여럿인 앱
navlistPanel() 왼쪽 세로 목록 탭이 많거나 이름이 긴 경우