图表
WaterfallChart
一个总数是怎么从一个数走到另一个数的。
什么时候用它
「为什么变了」,而且贡献项可以是负的。饼图放不下负的扇形;各部分不必刚好补齐两个总数之间的差额时,用 BarChart。
示例
default
| name | Change | Running total |
|---|---|---|
| FY24 ARR | 4,200 | 4,200 |
| New business | 1,180 | 5,380 |
| Expansion | 640 | 6,020 |
| Downgrades | -310 | 5,710 |
| Churn | -820 | 4,890 |
| FY25 ARR | 0 | 4,890 |
subtotals
| name | Change | Running total |
|---|---|---|
| Revenue | 8,400 | 8,400 |
| COGS | -3,100 | 5,300 |
| Gross profit | 0 | 5,300 |
| Sales | -1,450 | 3,850 |
| R&D | -1,900 | 1,950 |
| G&A | -720 | 1,230 |
| Operating profit | 0 | 1,230 |
说明
一个总量是怎么从一个数走到另一个数的——期初的余额、把它推动过去的那些带正负号的变动,以及最后收在哪里。
这是回答「为什么会变」的那种形式:两根柱子答不了,饼图答得是错的——因为变动可以是负的,而扇区不能。当各部分并不需要正好凑成两个总量之间的差额时,用 <BarChart>;当这个量只会越来越小时,用 <FunnelChart>。
真正要当心的断言,是那些连接线。它们把各步画成了一个序列——先发生这个,再发生那个——而大多数拆解压根没有先后:同一个月里的流失和扩张是同时发生的,它们被列出来的顺序不过是一次编辑取舍。算术怎么排都成立,故事不成立,而读者一定会把最左边那根柱子当成第一个原因。还有两个连带的坑:中间那些柱子是悬空的长度,没有共同的基线可对,所以瀑布高处的一小步很难和接近零处的一大步比大小;而任何一步如果本身是两笔更大的反向变动净出来的,从图上根本看不出它是个净额。顺序是随手排的,就在 description 里说明白。
Recharts 在这里挣得的位置是坐标轴、网格和 tooltip。每根柱子都是一段悬空的区间——从当前累计值到新的累计值——上面再盖一个自定义形状,因为 Recharts 没有瀑布图这种标记,而连接线必须和它所连接的柱子出自同一套几何。
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| children必填 | ReactNode | 组合进来的各个部分——坐标轴、网格、tooltip,以及 `<WaterfallChart.Bars>`。 | |
| data必填 | WaterfallStep[] | 各步变动,按它们生效的顺序。顺序就是算术本身:这里不会替你排序,因为换一个顺序,每一根中间柱子上的累计值都变了。 | |
| title必填 | string | 这张图在讲什么,用一句读者能据此行动的话。必填,而且即使不显示出来也会播报给读屏软件。 | |
| chartProps | ComponentProps<typeof RechartsBarChart> | 通往原始 Recharts 图表元素的逃生口。 | |
| className | string | 最后合并到 figure 上,所以调用处可以决定它的尺寸和间距。 | |
| config | ChartConfig | DEFAULT_CONFIG | 唯一的那条序列——它的标签和上色。只会读**第一条**;瀑布图只有一个量,有多少步就有多少根柱子。 |
| description | ReactNode | 标题下面的一行——单位、时间窗口、需要提醒的地方。 | |
| empty | ChartEmptyProps | false | 图表没东西可画时显示什么。传 `false` 保留坐标轴,用于那种「空本身就是结论」的图。 | |
| formatValue | (value: number) => string | defaultTick | 格式化图上印出来的每一个数字——刻度、标签、tooltip、表格。 |
| hideDataTable | boolean | false | 去掉隐藏的表格视图。只有当页面自己已经把数据印出来时,这才是对的。 |
| showTitle | boolean | 把标题印在图上方,而不是藏起来。 |
类型
export type WaterfallStepType = 'delta' | 'total'
export type WaterfallDirection = 'increase' | 'decrease' | 'total'无障碍
- 连接线是要小心对待的那个断言:它把各步画成一个**序列**,可绝大多数拆解根本不是序列——同一个月里的流失和扩张是同时发生的,而读者会把最左边那根柱子当成第一个原因。顺序是任意的时候,在 description 里说出来。
- 中间那些柱子是悬空的长度,没有基线可读,所以级联上方一个小的变动很难和接近零处一个大的变动作比较。只有 total 柱坐在坐标轴上,也只有它们能被绝对地读出来。
- 方向由标签的正负号和柱子的纹理承担,不只靠位置——所以在灰度和强制颜色下这个读法仍然成立。
- 不给 value 的收尾柱由各个增减量算出来,把这道算术留在数据里,而不是留在调用者脑子里。