레이아웃과 탭 구성 (mo.vstack·hstack)
Ch 02에서 데이터 파이프라인을 완성했습니다. filtered_df가 위젯에 반응하고, summary_md가 그 결과를 요약합니다. 이제 이 요소들을 화면에 배치합니다.
marimo의 레이아웃 도구는 두 가지입니다. mo.vstack은 요소를 위에서 아래로 쌓습니다. mo.hstack은 요소를 나란히 배치하고, widths 파라미터로 각 칸의 너비 비율을 지정합니다. 이 둘을 중첩하면 어떤 레이아웃도 만들 수 있습니다.
mo.hstack의 widths 파라미터
mo.hstack의 widths 파라미터는 두 가지 값을 받습니다.
# "equal": 모든 칸을 동일한 너비로mo.hstack([col1, col2, col3], widths="equal")# 숫자 리스트: 상대 비율로 너비 지정mo.hstack([sidebar, main], widths=[1, 3])
widths=[1, 3]은 왼쪽이 1, 오른쪽이 3입니다. 전체를 4로 나눌 때 왼쪽이 25%, 오른쪽이 75%를 차지합니다. 사이드바와 메인 영역을 나누기에 적합한 비율입니다.
셀 7: 카테고리별 차트
filtered_df를 카테고리별로 집계해 막대 차트를 만듭니다.
cat_agg = mo.sql(f""" SELECT category, COUNT(*) AS 주문수, ROUND(SUM(amount), 0) AS 총_금액 FROM filtered_df GROUP BY category ORDER BY 총_금액 DESC""")fig_category = px.bar( cat_agg, x="category", y="총_금액", color="category", title="카테고리별 총 주문 금액", labels={"category": "카테고리", "총_금액": "총 금액 (원)"},)
cat_agg는 filtered_df를 다시 SQL로 집계합니다. filtered_df가 바뀌면 이 셀도 재실행됩니다. px는 셀 1에서 정의된 변수입니다. 같은 변수를 여기서 다시 정의하면 MB002 오류가 발생합니다.
셀 8: 지역별 차트
지역별 평균 주문 금액을 가로 막대 차트로 보여줍니다.
region_agg = mo.sql(f""" SELECT region, COUNT(*) AS 주문수, ROUND(AVG(amount), 0) AS 평균_금액 FROM filtered_df GROUP BY region ORDER BY 평균_금액 DESC""")fig_region = px.bar( region_agg, x="평균_금액", y="region", orientation="h", title="지역별 평균 주문 금액", labels={"region": "지역", "평균_금액": "평균 금액 (원)"}, color="평균_금액", color_continuous_scale="Blues",)
셀 9: 요일별 차트
요일 순서를 월요일부터 일요일 순으로 정렬해 꺾은선 차트로 보여줍니다.
weekday_order = ["월", "화", "수", "목", "금", "토", "일"]weekday_agg = mo.sql(f""" SELECT weekday, COUNT(*) AS 주문수 FROM filtered_df GROUP BY weekday""")weekday_pd = weekday_agg.copy()weekday_pd["weekday"] = pd.Categorical( weekday_pd["weekday"], categories=weekday_order, ordered=True,)weekday_pd = weekday_pd.sort_values("weekday")fig_weekday = px.line( weekday_pd, x="weekday", y="주문수", markers=True, title="요일별 주문 건수", labels={"weekday": "요일", "주문수": "주문 건수"},)
셀 10: 화면 선택에 따른 차트 분기
view_selector.value에 따라 세 차트 중 하나를 선택합니다. 이 분기를 하나의 셀 안에 작성합니다.
if view_selector.value == "카테고리별 현황": active_chart = fig_categoryelif view_selector.value == "지역별 현황": active_chart = fig_regionelse: active_chart = fig_weekday
active_chart를 하나의 셀 안에서 조건문으로 할당하는 것이 중요합니다. 조건 분기를 여러 셀로 나누면 active_chart를 두 셀 이상에서 정의하게 되어 MB002 오류가 발생합니다. 하나의 셀 안에서 if/elif/else로 분기하고, 마지막 표현식은 두지 않습니다. 이 셀은 변수를 정의할 뿐이며, 셀 12의 main_panel이 active_chart를 소비해 화면에 렌더링합니다.
view_selector.value가 바뀌면 이 셀이 재실행됩니다. 갱신된 active_chart를 참조하는 셀 12와 셀 13이 연쇄적으로 재실행됩니다.
라디오 버튼으로 화면을 전환하는 이유
다중 화면 전환이 필요할 때 라디오 버튼이나 드롭다운 위젯으로 active_chart를 분기하는 방식은 marimo에서 안정적으로 검증된 패턴입니다. 위젯 .value는 문자열이고, if/elif 분기는 표준 Python이며, active_chart를 하나의 셀에서 정의하므로 MB002 오류도 없습니다. 구현이 단순하고 동작이 예측 가능합니다.
셀 11: 사이드바 패널 구성
왼쪽 필터 영역을 mo.vstack으로 조립합니다.
sidebar = mo.vstack([ mo.md("### 필터"), category_filter, amount_min_filter, region_filter,])
mo.vstack은 리스트의 요소를 위에서 아래로 쌓습니다. 마크다운 제목, 위젯 세 개가 순서대로 배치됩니다. mo.md("### 필터")는 섹션 제목 역할을 합니다.
셀 12: 메인 패널 구성
오른쪽 결과 영역을 mo.vstack으로 조립합니다.
main_panel = mo.vstack([ mo.md("### 현황 지표"), summary_md, mo.md("---"), view_selector, active_chart,])
요약 지표 테이블, 구분선, 화면 선택 위젯, 선택된 차트 순서로 배치됩니다. summary_md와 active_chart는 앞 셀에서 정의된 변수입니다. mo.vstack은 이들을 순서대로 세로로 쌓습니다.
셀 13: 전체 레이아웃
사이드바와 메인 패널을 mo.hstack으로 나란히 배치합니다.
app_layout = mo.hstack([sidebar, main_panel], widths=[1, 3])app_layout
widths=[1, 3]이 화면을 1:3 비율로 나눕니다. 왼쪽 사이드바가 전체의 25%, 오른쪽 메인 영역이 75%를 차지합니다. app_layout을 셀 마지막 표현식으로 두면 이 레이아웃이 셀 출력이 됩니다.
앱 모드에서는 셀 코드가 숨겨지고 이 출력만 화면에 표시됩니다.
레이아웃 중첩 패턴
mo.vstack과 mo.hstack은 서로 중첩할 수 있습니다. 이 챕터에서 만든 레이아웃 구조를 정리합니다.
레이아웃 코드가 단순합니다. mo.hstack과 mo.vstack 각각에 파이썬 리스트를 넘기는 것이 전부입니다. CSS나 그리드 설정 없이 대시보드 레이아웃을 만들 수 있습니다.
여기까지 노트북의 상태
셀 1: import (mo, np, pd, px)셀 2: df 생성 (고정 시드, 500행)셀 3: category_filter, amount_min_filter, region_filter 위젯셀 4: filtered_df (SQL)셀 5: summary_md셀 6: view_selector 위젯셀 7: cat_agg SQL + fig_category셀 8: region_agg SQL + fig_region셀 9: weekday_agg SQL + fig_weekday셀 10: active_chart (view_selector.value로 분기)셀 11: sidebar셀 12: main_panel셀 13: app_layout
셀 3의 위젯을 바꾸면 셀 4(filtered_df), 셀 5(summary_md), 셀 7, 8, 9(차트 집계), 셀 10(active_chart), 셀 12(main_panel), 셀 13(app_layout)이 순서대로 재실행됩니다. 셀 1, 셀 2, 셀 6, 셀 11은 영향을 받지 않습니다.
view_selector를 바꾸면 셀 10, 12, 13만 재실행됩니다. 데이터 집계 셀은 다시 실행되지 않습니다. DAG가 최소 범위만 재실행합니다.
정리
mo.vstack은 요소를 세로로 쌓고,mo.hstack(widths=[1, 3])은 요소를 가로로 나눠 1:3 비율의 사이드바-메인 레이아웃을 만듭니다.- 다중 화면 전환은
mo.ui.radio위젯과 if/elif 분기로 구현합니다. 분기 결과를 하나의 변수에 담아 하나의 셀에서 정의하되, 마지막 표현식으로 두지 않습니다. 하위 레이아웃 셀(main_panel)이 이 변수를 소비합니다. MB002 오류를 피하고 앱 화면에 중복 출력이 생기지 않습니다. - 위젯,
mo.md(), plotly Figure,mo.sql()결과는 모두mo.vstack과mo.hstack의 아이템으로 사용할 수 있습니다. - 앱 모드 화면에 표시되는 셀은 마지막 표현식이 있는 셀뿐입니다. 이 파트에서는 셀 13(
app_layout)만 출력을 가집니다. 나머지 셀은 변수를 정의할 뿐이며 앱 화면에 나타나지 않습니다.