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: 전체 화면 네비게이션
앱이 완전히 독립적인 여러 섹션으로 구성될 때는 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: 드롭다운 메뉴
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: 세로 목록 탐색
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() |
왼쪽 세로 목록 | 탭이 많거나 이름이 긴 경우 |