组件工坊:Reflex 组件库 100 例
·
课程概览
布局
·
Box
·
Flex
·
Stack
·
Grid
·
Container
·
Section
·
Center
·
Spacer
·
Separator
·
Card
·
Inset
·
Aspect Ratio
·
Fragment
排版
·
Text
·
Heading
·
Link
·
Code
·
Markdown
·
Blockquote
·
Quote
·
Strong
·
Em
·
Kbd
表单
·
Button
·
Input
·
Text Area
·
Checkbox
·
Switch
·
Radio Group
·
Select
·
Slider
·
Form
·
Upload
数据展示
·
Badge
·
Avatar
·
Icon
·
Callout
·
List
·
Data List
·
Code Block
·
Progress
·
Spinner
·
Scroll Area
·
Moment
展开与切换
·
Tabs
·
Accordion
·
Segmented Control
浮层
·
Tooltip
·
Popover
·
Hover Card
·
Dialog
·
Alert Dialog
·
Drawer
·
Dropdown Menu
·
Context Menu
·
Toast
动态渲染
·
Cond
·
Match
·
Foreach
·
Auto Scroll
媒体
·
Image
·
Audio
·
Video
表格与数据网格
·
Table
·
Data Table
·
Data Editor
图表
·
Area Chart
·
Bar Chart
·
Line Chart
·
Composed Chart
·
Pie Chart
·
Radar Chart
·
Radial Bar Chart
·
Scatter Chart
·
Funnel Chart
·
Treemap
·
Sankey Chart
·
Error Bar
图表部件
·
Axis
·
Cartesian Grid
·
Chart Tooltip
·
Legend
·
Label
·
Reference
·
Brush
其他图表库
·
Plotly
·
Pyplot
HTML 元素
·
HTML
·
HTML Text
·
HTML Layout
·
HTML Forms
·
HTML Media
·
HTML Tables
·
SVG
其他工具
·
Theme
·
Skeleton
·
Clipboard
·
Html Embed
·
Script
·
Memo
工坊订单
请用流动带的粗细,展示一个月的收入从哪里来,又分别流向房租、伙食和储蓄多少。
rx.recharts.sankey_chart 是桑基图,用带子的粗细展示节点以及节点之间流动的量。查看访客经过哪些页面最终下单、钱从哪里来又到哪里去这类"流向"时就用它。
data 不是列表,而是形如 {"nodes": [...], "links": [...]} 的 dict。links 中的 source 和 target 是 nodes 列表里的序号(从 0 开始),value 是这股流的量。提示框作为子组件放进去。
sankeychart_data = {
"nodes": [
{"name": "Website"},
{"name": "Landing"},
{"name": "Product"},
{"name": "Checkout"},
{"name": "Purchase"},
],
"links": [
{"source": 0, "target": 1, "value": 1200},
{"source": 1, "target": 2, "value": 900},
{"source": 2, "target": 3, "value": 420},
{"source": 3, "target": 4, "value": 260},
],
}
rx.recharts.sankey_chart(
rx.recharts.graphing_tooltip(),
data=sankeychart_data,
node_padding=24,
node_width=12,
link_curvature=0.55,
width="100%",
height=300,
)给 node 和 link 传一个 dict,所有节点和带子都会套用同样的 SVG 样式。dict 的键直接用 SVG 属性名(像 strokeOpacity 这样的驼峰写法)。link_curvature 是带子的弯曲程度(0 为直线)。
试着改属性
node_color
link_opacity
0.35
link_curvature
0.5
sort
rx.recharts.sankey_chart(
rx.recharts.graphing_tooltip(),
data={
"nodes": [{"name": "Ads"}, {"name": "Search"}, {"name": "Signup"}, {"name": "Leave"}, {"name": "Paid"}],
"links": [
{"source": 0, "target": 2, "value": 300},
{"source": 0, "target": 3, "value": 500},
{"source": 1, "target": 2, "value": 600},
{"source": 1, "target": 3, "value": 200},
{"source": 2, "target": 4, "value": 350},
],
},
node={"fill": rx.color("indigo", 9), "stroke": "none"},
link={"stroke": rx.color("gray", 9), "strokeOpacity": 0.35},
link_curvature=0.5,
sort=True,
node_padding=20,
node_width=12,
width="100%",
height=300,
)| 属性 | 取值 | 默认值 | 用途 |
|---|---|---|---|
data | {"nodes": [...], "links": [...]} | 无 | 节点和流向。链接用节点序号相连 |
name_key | 字符串 | "name" | 节点名称的键 |
data_key | 字符串 | "value" | 流量的键 |
node | dict 或 @sankey_chart.node 函数 | 节点样式或自定义绘制函数 | |
link | dict 或 @sankey_chart.link 函数 | 带子样式或自定义绘制函数 | |
node_width | 数字 | 10 | 节点条的粗细 |
node_padding | 数字 | 10 | 同一列节点之间的间距 |
link_curvature | 0~1 | 0.5 | 带子的弯曲程度 |
sort | True / False | True | 是否上下排序节点 |
iterations | 数字 | 32 | 布局计算的迭代次数 |
margin, width, height | dict / 数字或百分比 | 各 5 / "100%" | 留白和尺寸。右侧名称被截断时加大 margin |
| 事件 | on_click on_mouse_enter on_mouse_leave on_mouse_move | 图表区域的鼠标事件 |
data 接上了 State 里的 dict。点按钮,链接的 value 会重新生成,带子的粗细和节点高度也会重新计算。这里用 dict 给 node 和 link 设置颜色、边框和透明度。
import random
from typing import Any
class SankeyChartLiveState(rx.State):
data: dict[str, Any] = {
"nodes": [
{"name": "Marketing"},
{"name": "Trial"},
{"name": "Sales"},
{"name": "Support"},
{"name": "Retained"},
],
"links": [
{"source": 0, "target": 1, "value": 600},
{"source": 1, "target": 2, "value": 320},
{"source": 2, "target": 4, "value": 210},
{"source": 1, "target": 3, "value": 180},
{"source": 3, "target": 4, "value": 130},
],
}
@rx.event
def shuffle(self):
self.data = {
"nodes": self.data["nodes"],
"links": [{**link, "value": random.randint(80, 700)} for link in self.data["links"]],
}
rx.vstack(
rx.recharts.sankey_chart(
rx.recharts.graphing_tooltip(),
data=SankeyChartLiveState.data,
node={
"fill": rx.color("accent", 7),
"stroke": rx.color("accent", 10),
"strokeWidth": 2,
},
link={
"stroke": rx.color("gray", 7),
"strokeOpacity": 0.35,
},
node_padding=18,
node_width=14,
width="100%",
height=300,
),
rx.button("Randomize flows", on_click=SankeyChartLiveState.shuffle),
width="100%",
)dict 不够用时,就自己写绘制节点和带子的函数。加了 @rx.recharts.sankey_chart.node 的函数以 Var 形式接收单个节点的信息(SankeyNodeProps:位置 x、y,尺寸 width、height,原始数据 payload),并返回 SVG 组件。@rx.recharts.sankey_chart.link 接收单条带子的信息(SankeyLinkProps:起点和终点坐标、粗细 linkWidth、payload)。
rx.recharts.use_chart_width() 是图表的实际宽度,用来把最右侧节点的名称放到左边。"fill")要像 payload.to(dict)["fill"] 这样转成 dict 再读取。rx.vars.use_id() 为每条带子生成不重复的渐变 id。带子从起点节点的颜色渐变到终点节点的颜色。这两个函数要在界面中使用之前,先在模块级别定义一次。
sankeychart_styled = {
"nodes": [
{"name": "Sources", "fill": rx.color("blue", 8)},
{"name": "Direct", "fill": rx.color("green", 8)},
{"name": "Search", "fill": rx.color("grass", 8)},
{"name": "Paid", "fill": rx.color("amber", 8)},
{"name": "Revenue", "fill": rx.color("purple", 8)},
],
"links": [
{"source": 0, "target": 1, "value": 350},
{"source": 0, "target": 2, "value": 500},
{"source": 0, "target": 3, "value": 220},
{"source": 1, "target": 4, "value": 190},
{"source": 2, "target": 4, "value": 260},
{"source": 3, "target": 4, "value": 150},
],
}
@rx.recharts.sankey_chart.node
def sankeychart_node(node: rx.Var[rx.recharts.SankeyNodeProps]) -> rx.Component:
# put the name on the left for the rightmost nodes
is_out = node.x + node.width + 6 > rx.recharts.use_chart_width()
return rx.fragment(
rx.el.svg.text(
node.payload.name,
x=rx.cond(is_out, node.x - 6, node.x + node.width + 6).to(int),
y=(node.y + node.height / 2).to(int),
text_anchor=rx.cond(is_out, "end", "start"),
fill=rx.color("gray", 12),
font_size=10,
),
rx.el.svg.rect(
x=node.x.to(int),
y=node.y.to(int),
width=node.width.to(int),
height=node.height.to(int),
# read the payload's custom key (fill) after converting it to a dict
fill=node.payload.to(dict)["fill"],
stroke=rx.color("gray", 12),
stroke_width=1,
),
)
@rx.recharts.sankey_chart.link
def sankeychart_link(link: rx.Var[rx.recharts.SankeyLinkProps]) -> rx.Component:
link_id = rx.vars.use_id()
source = link.payload.source.to(dict)
target = link.payload.target.to(dict)
return rx.fragment(
rx.el.svg.linear_gradient(
rx.el.svg.stop(offset="0%", stop_color=source["fill"]),
rx.el.svg.stop(offset="100%", stop_color=target["fill"]),
id=link_id,
),
rx.el.svg.path(
d=(
f"M{link.sourceX},{link.sourceY} "
f"C{link.sourceControlX},{link.sourceY} "
f"{link.targetControlX},{link.targetY} "
f"{link.targetX},{link.targetY}"
),
fill="none",
stroke=f"url(#{link_id})",
stroke_opacity=0.35,
stroke_width=link.linkWidth,
),
rx.el.svg.text(
link.payload.value,
x=((link.sourceX + link.targetX) / 2).to(int),
y=((link.sourceY + link.targetY) / 2).to(int),
text_anchor="middle",
fill=rx.color("gray", 12),
font_size=10,
),
)
rx.recharts.sankey_chart(
data=sankeychart_styled,
node=sankeychart_node,
link=sankeychart_link,
width="100%",
height=320,
)两处收入汇入 "Budget" 节点,再分成三路。节点用深色,带子用同色系的浅色,看起来就像流从节点里延伸出来;再加宽右侧 margin,让最后一列的名称不被截断。
rx.recharts.sankey_chart(
rx.recharts.graphing_tooltip(),
data={
"nodes": [
{"name": "Salary"},
{"name": "Side job"},
{"name": "Budget"},
{"name": "Rent"},
{"name": "Food"},
{"name": "Savings"},
],
"links": [
{"source": 0, "target": 2, "value": 3000},
{"source": 1, "target": 2, "value": 600},
{"source": 2, "target": 3, "value": 1400},
{"source": 2, "target": 4, "value": 900},
{"source": 2, "target": 5, "value": 1300},
],
},
node={"fill": rx.color("accent", 9), "stroke": "none"},
link={"stroke": rx.color("accent", 6), "strokeOpacity": 0.5},
node_padding=28,
node_width=10,
margin={"top": 10, "right": 80, "bottom": 10, "left": 10},
width="100%",
height=300,
)工坊笔记
data 是 {"nodes": [...], "links": [...]}。链接的 source、target 是节点序号。
给 node、link 传 dict 就是整体样式(SVG 属性名用驼峰写法)。
用 node_width、node_padding、link_curvature、sort 调整布局。
想画得更自由,就用 @rx.recharts.sankey_chart.node / .link 函数返回 SVG。
把 State 里的 dict 接到 data 上,流向变化时会重新计算。
订阅新课时和新课程通知
本课程新增课时或推出后续课程时,我们会发邮件通知你。
问答
关于本课有任何疑问都可以在这里留言。所有语言版本页面的提问会汇集在一起。