Ch 01. 기본 레이아웃 (sidebarLayout, fluidRow)
화면을 어떻게 나누느냐에 따라 앱의 첫인상이 결정됩니다. Shiny는 Bootstrap 그리드를 기반으로 하는 레이아웃 시스템을 제공합니다. 가장 자주 쓰이는 세 가지 레이아웃 패턴을 익혀두면 대부분의 앱 화면을 자연스럽게 구성할 수 있습니다.
sidebarLayout: 가장 고전적인 구성
sidebarLayout()은 왼쪽에 사이드바, 오른쪽에 메인 영역을 배치하는 가장 전형적인 레이아웃입니다. 입력 컨트롤은 사이드바에, 결과 출력은 메인 영역에 두는 것이 관례입니다.
library(shiny)
ui <- fluidPage(
titlePanel("사이드바 레이아웃 예제"),
sidebarLayout(
position = "left", # "right"로 변경하면 사이드바가 오른쪽
sidebarPanel(
width = 3, # 전체 12 열 중 3 열 (25%)
h4("설정"),
sliderInput("n", "데이터 수", 10, 500, 100),
selectInput("color", "색상",
choices = c("파랑" = "steelblue",
"빨강" = "tomato",
"초록" = "seagreen"))
),
mainPanel(
width = 9, # 나머지 9 열 (75%)
plotOutput("scatter")
)
)
)
server <- function(input, output, session) {
output$scatter <- renderPlot({
x <- rnorm(input$n)
y <- rnorm(input$n)
plot(x, y, col = input$color, pch = 19, cex = 1.2,
main = paste("랜덤 산점도 (n =", input$n, ")"))
})
}
shinyApp(ui, server)
sidebarPanel()과 mainPanel()의 width 합은 12가 되어야 합니다. 기본값은 각각 4와 8입니다.
fluidRow와 column: 자유로운 그리드 배치
fluidRow()와 column()은 Bootstrap 12열 그리드를 직접 제어합니다. 사이드바가 없는 대시보드 스타일이나, 여러 위젯을 가로로 나란히 배치할 때 씁니다.
library(shiny)
ui <- fluidPage(
titlePanel("그리드 레이아웃 예제"),
# 첫 번째 행: 요약 카드 3개
fluidRow(
column(4,
wellPanel(
h4("총 관측치"),
textOutput("n_obs")
)
),
column(4,
wellPanel(
h4("평균 연비(mpg)"),
textOutput("mean_mpg")
)
),
column(4,
wellPanel(
h4("최고 마력(hp)"),
textOutput("max_hp")
)
)
),
# 두 번째 행: 필터와 그래프
fluidRow(
column(3,
selectInput("cyl", "실린더",
choices = c("전체" = 0, "4" = 4, "6" = 6, "8" = 8),
selected = 0)
),
column(9,
plotOutput("mpg_plot")
)
)
)
server <- function(input, output, session) {
filtered <- reactive({
if (input$cyl == 0) mtcars
else mtcars[mtcars$cyl == as.integer(input$cyl), ]
})
output$n_obs <- renderText(nrow(filtered()))
output$mean_mpg <- renderText(round(mean(filtered()$mpg), 1))
output$max_hp <- renderText(max(filtered()$hp))
output$mpg_plot <- renderPlot({
boxplot(mpg ~ cyl, data = filtered(),
main = "실린더별 연비", col = "#3498db",
xlab = "실린더 수", ylab = "연비(mpg)")
})
}
shinyApp(ui, server)
column(width, ...)에서 width는 1에서 12 사이 정수입니다. 한 fluidRow() 안 column()의 width 합이 12를 넘으면 자동으로 줄 바꿈됩니다.
wellPanel: 시각적 구분 상자
wellPanel()은 회색 배경의 둥근 테두리 박스를 만들어 내용을 시각적으로 묶습니다. 관련 있는 컨트롤을 그룹화할 때 유용합니다.
sidebarPanel(
wellPanel(
h5("데이터 필터"),
sliderInput("year", "연도", 2010, 2024, c(2015, 2024)),
selectInput("region", "지역", choices = c("전체", "서울", "부산"))
),
wellPanel(
h5("차트 설정"),
radioButtons("chart_type", "종류",
choices = c("막대" = "bar", "선" = "line"),
inline = TRUE),
checkboxInput("grid", "격자 표시", TRUE)
)
)
레이아웃 선택 가이드
어떤 레이아웃을 쓸지 고민될 때 아래 기준을 참고하면 됩니다.
| 상황 | 추천 레이아웃 |
|---|---|
| 입력 컨트롤 + 단일 출력 | sidebarLayout() |
| 여러 차트를 격자로 배치 | fluidRow() + column() |
| 관리자 대시보드 스타일 | fluidRow() + column() + navbarPage() |
| 입력 컨트롤 그룹화 | wellPanel() 중첩 |
fluidPage()는 화면 너비에 따라 유연하게 늘어납니다. 반면 fixedPage()는 최대 너비가 고정된 컨테이너를 씁니다. 대부분의 경우 fluidPage()가 더 자연스럽습니다.
중첩 레이아웃
sidebarLayout() 안에 fluidRow()를 넣는 식으로 레이아웃을 중첩할 수 있습니다.
ui <- fluidPage(
sidebarLayout(
sidebarPanel(
sliderInput("n", "수", 10, 100, 50)
),
mainPanel(
# 메인 영역을 다시 두 열로 분리
fluidRow(
column(6, plotOutput("plot1")),
column(6, plotOutput("plot2"))
),
fluidRow(
column(12, tableOutput("tbl"))
)
)
)
)
레이아웃은 HTML 테이블이 아닌 Bootstrap 그리드이므로, 복잡하게 중첩해도 반응형 동작이 유지됩니다.