이번 교시가 끝나면 카드 아래쪽에 성별별 고객 수 가로 막대 차트가 생기고, 고객을 추가할 때마다 막대가 늘어납니다. 파이썬으로 데이터를 가공해 화면에 보여주는, 데이터 대시보드의 기본 패턴입니다.
Recharts(Reflex가 감싸서 제공하는 React 차트 라이브러리)는 딕셔너리의 리스트를 받습니다. 딕셔너리 하나가 막대 하나입니다.
# 우리가 가진 데이터 # 차트가 원하는 데이터
[User(..., gender="Male"), [{"name": "Male", "value": 1},
User(..., gender="Female")] → {"name": "Female", "value": 1}]그래서 차트를 그리기 전에 가공(집계) 단계가 필요합니다.
from collections import Counter
class State(rx.State):
users: list[User] = [...]
users_for_graph: list[dict] = [] # 차트용 데이터
add_user_dialog_open: bool = False
@rx.event
def transform_data(self):
gender_counts = Counter(user.gender for user in self.users)
self.users_for_graph = [
{"name": gender_group, "value": count}
for gender_group, count in gender_counts.items()
]Counter는 파이썬 표준 라이브러리입니다. Counter(["Male", "Female", "Male"]) → {"Male": 2, "Female": 1}.users_for_graph의 초깃값은 빈 리스트입니다. 두 시점에 채워야 합니다.
페이지가 열릴 때: add_page의 on_load에 핸들러를 등록합니다.
app.add_page(
index,
title="Customer Data App",
description="A simple app to manage customer data.",
on_load=State.transform_data, # 페이지 진입·새로고침 때마다 실행
)고객을 추가할 때: add_user 안에서 직접 호출합니다.
@rx.event
def add_user(self, form_data: dict):
self.users.append(User(**form_data))
self.transform_data() # 차트 데이터 다시 계산
self.add_user_dialog_open = Falsedescription은 검색엔진·링크 미리보기에 나오는 페이지 설명(meta 태그)입니다.users를 바꾸는 곳마다 transform_data()를 잊지 말고 호출해야 한다는 점입니다. 삭제 기능을 추가하면서 호출을 빠뜨리면 차트와 테이블이 어긋납니다.@rx.var를 붙이면 "다른 상태로부터 계산되는 값"을 선언할 수 있습니다. users가 바뀔 때마다 Reflex가 알아서 다시 계산합니다.
class State(rx.State):
users: list[User] = [...]
@rx.var
def users_for_graph(self) -> list[dict]: # 반환 타입 어노테이션 필수
counts = Counter(user.gender for user in self.users)
return [{"name": g, "value": c} for g, c in counts.items()]transform_data, on_load, add_user 안의 호출을 모두 지울 수 있습니다. 화면 코드(data=State.users_for_graph)는 그대로입니다.강사 노트 — "값을 저장해 두고 매번 갱신" vs "계산 방법을 선언". 엑셀에서 값을 직접 입력하는 것과 수식(=COUNTIF(...))을 거는 것의 차이로 비유하면 잘 전달됩니다.
def graph():
return rx.recharts.bar_chart(
rx.recharts.cartesian_grid(
horizontal=False, stroke_dasharray="3 3", stroke=rx.color("slate", 4)
),
rx.recharts.x_axis(
type_="number",
allow_decimals=False,
axis_line=False,
tick_line=False,
domain=[0, "dataMax + 1"],
),
rx.recharts.y_axis(
type_="category",
data_key="name",
axis_line=False,
tick_line=False,
width=64,
),
rx.recharts.bar(
data_key="value",
fill=rx.color("accent", 9),
radius=[0, 6, 6, 0],
bar_size=28,
),
data=State.users_for_graph,
layout="vertical",
width="100%",
height=160,
margin={"top": 0, "right": 16, "bottom": 0, "left": 0},
)차트도 컴포넌트입니다. 부품을 자식으로 넣고, 데이터와 크기는 키워드 인자로 줍니다.
| 부품 | 역할 | 핵심 인자 |
|---|---|---|
bar_chart | 차트 전체 틀 | data(딕셔너리 리스트), layout="vertical"(가로 막대) |
cartesian_grid | 배경 격자선 | horizontal=False(세로선만), stroke_dasharray="3 3"(점선) |
x_axis | 가로축 = 고객 수 | type_="number", allow_decimals=False(1.5명 같은 눈금 방지), domain=[0, "dataMax + 1"](최댓값보다 1 더 여유) |
y_axis | 세로축 = 성별 | type_="category", data_key="name"(딕셔너리의 어느 키를 이름으로 쓸지) |
bar | 막대 | data_key="value"(길이로 쓸 키), radius=[0, 6, 6, 0](왼위·오른위·오른아래·왼아래 모서리, 끝만 둥글게) |
layout="vertical"일 때는 x축이 숫자, y축이 범주입니다. 세로 막대(기본값)와 반대라 가장 자주 헷갈리는 부분입니다.rx.color("accent", 9)는 테마의 강조색입니다. rxconfig.py의 RadixThemesPlugin(theme=rx.theme(accent_color="indigo"))처럼 바꾸면 버튼과 막대 색이 함께 바뀝니다.graph()는 카드의 세 번째 자식으로, 제목 문구와 함께 넣습니다.rx.box(
rx.text("Audience breakdown", size="3", weight="bold"),
rx.text(
"Customers by gender",
size="2",
color=rx.color("slate", 11),
margin_top="4px",
margin_bottom="20px",
),
graph(),
padding="24px",
),브라우저 확인 — 테이블 아래에 "Audience breakdown" 영역이 생기고, Male과 Female 막대가 각각 1까지 그려집니다. x축이 2까지 있는 것은 domain=[0, "dataMax + 1"] 때문입니다. 막대는 왼쪽에서 차오르는 애니메이션으로 나타나므로 1~2초 기다려 봅니다.
localhost:3000
직접 눌러 보세요Your customer directory and audience breakdown.
Audience breakdown
Customers by gender
위 미리보기에서 Female 고객을 한 명 추가하면 Female 막대가 2로 늘어나고 x축이 3까지 넓어집니다. 차트가 그대로라면 add_user 안의 self.transform_data() 호출을 확인합니다.
5-5까지 동작을 확인했으면, 화면은 그대로 둔 채 이벤트 방식을 computed var로 바꿉니다. 세 군데를 고칩니다.
State에서 users_for_graph: list[dict] = [] 필드와 transform_data 메서드를 지우고, 5-4의 @rx.var 메서드를 넣습니다. 같은 이름이 겹치므로 필드는 반드시 지웁니다.add_user 안의 self.transform_data() 줄을 지웁니다.app.add_page에서 on_load=State.transform_data를 지웁니다.class State(rx.State):
users: list[User] = [...]
add_user_dialog_open: bool = False
@rx.event
def set_add_user_dialog_open(self, open_: bool):
self.add_user_dialog_open = open_
@rx.event
def add_user(self, form_data: dict):
self.users.append(User(**form_data))
self.add_user_dialog_open = False # transform_data() 호출 없음
@rx.var
def users_for_graph(self) -> list[dict]:
counts = Counter(user.gender for user in self.users)
return [{"name": g, "value": c} for g, c in counts.items()]
app = rx.App()
app.add_page(
index,
title="Customer Data App",
description="A simple app to manage customer data.",
) # on_load 없음graph()의 data=State.users_for_graph는 한 글자도 바꾸지 않습니다. 화면 쪽에서는 일반 상태 변수와 computed var가 똑같이 보입니다.from collections import Counter는 그대로 둡니다.브라우저 확인 — 저장하면 핫 리로드 후 차트가 5-5와 똑같이 보입니다. Female 고객을 추가해 막대가 2로 늘어나면 리팩터링 성공입니다. 호출을 하나도 안 했는데 갱신되는 것이 computed var의 핵심입니다.
rx.recharts.graphing_tooltip()을 차트 자식으로 추가해 마우스를 올리면 값이 보이게 해 보세요.@rx.var def female_count(self) -> int를 하나 더 만들고, 차트 제목 옆에 "Female N명"으로 보여주세요.@ 뒤)별 고객 수 차트를 하나 더 만들어 보세요.on_load 방식과 computed var 방식의 차이를 설명할 수 있다.새 교시·새 강의 소식 받기
이 강의에 새 교시가 더해지거나 이어지는 강의가 열리면 이메일로 알려 드려요.
R
ReflexKR
눌러 보며 배우는 파이썬과 Reflex. 모든 예제는 Reflex 0.10.0에서 직접 실행해 확인했습니다.
ReflexKR은 배너 광고 없이 무료로 운영돼요. 도움이 되셨다면 커피 한 잔으로 응원해 주세요.
© 2026 ReflexKR · Since 2026 · CC BY-NC 4.0
질문과 답변
이 교시에서 궁금한 점을 남겨 주세요. 모든 언어 페이지의 질문이 함께 모입니다.