学完这一课,卡片底部会出现一张按性别统计客户数的横向条形图,每添加一位客户,对应的条形就会变长。用 Python 处理数据再展示到界面上,这正是数据看板的基本模式。
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 是 Python 标准库中的工具: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)保持不变。讲师笔记 — “把值存起来、每次手动更新”与“声明计算方法”的区别,可以类比成 Excel 中直接输入数值和写公式(=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_loadgraph() 中的 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 方式的区别。订阅新课时和新课程通知
本课程新增课时或推出后续课程时,我们会发邮件通知你。
问答
关于本课有任何疑问都可以在这里留言。所有语言版本页面的提问会汇集在一起。