图表
WaterfallChart - 瀑布图
一个总数是怎么从一个数走到另一个数的。
用法
什么时候用它
来自@misoto22/folio/charts
import { WaterfallChart } from '@misoto22/folio/charts'说明
一个总量是怎么从一个数走到另一个数的——期初的余额、把它推动过去的那些带正负号的变动,以及最后收在哪里。
这是回答「为什么会变」的那种形式:两根柱子答不了,饼图答得是错的——因为变动可以是负的,而扇区不能。当各部分并不需要正好凑成两个总量之间的差额时,用 <BarChart>;当这个量只会越来越小时,用 <FunnelChart>。
真正要当心的断言,是那些连接线。它们把各步画成了一个序列——先发生这个,再发生那个——而大多数拆解压根没有先后:同一个月里的流失和扩张是同时发生的,它们被列出来的顺序不过是一次编辑取舍。算术怎么排都成立,故事不成立,而读者一定会把最左边那根柱子当成第一个原因。还有两个连带的坑:中间那些柱子是悬空的长度,没有共同的基线可对,所以瀑布高处的一小步很难和接近零处的一大步比大小;而任何一步如果本身是两笔更大的反向变动净出来的,从图上根本看不出它是个净额。顺序是随手排的,就在 description 里说明白。
Recharts 在这里挣得的位置是坐标轴、网格和 tooltip。每根柱子都是一段悬空的区间——从当前累计值到新的累计值——上面再盖一个自定义形状,因为 Recharts 没有瀑布图这种标记,而连接线必须和它所连接的柱子出自同一套几何。
结构
| 部件 | 说明 |
|---|---|
| 图框必填 | ChartFigure 的那个 <figure>,零个步骤时给出空状态。关于顺序的那句告诫该写进 description,而它在设置 showTitle 之前是 sr-only 的。 |
| 步骤必填 | <WaterfallChart.Bars>:一个步骤一根悬空的区间条,从上一个合计走到新的合计,上面盖着一个自定义 shape。增量和合计取实心的序列填充,减量取 45 度的斜纹,所以方向在灰度和强制颜色下仍然成立。 |
| 连接线 | connectors,默认打开,把每根柱子的收尾边和下一根的起点连起来。没有它们,瀑布图就是一排悬在互不相干的高度上的柱子,读者得自己把这道级联重建出来。 |
| 零基线必填 | 一条零处的参考线,由 <WaterfallChart.Bars> 自己画。合计柱就站在它上面,而一张看不见零的瀑布图,是在要求读者对每一根悬空的柱子照单全信。 |
| 步骤标签 | showValues,而且它默认是关的。它把每一步带正负号的变化量印在柱子旁边——这在别处都不如在这里值钱,因为中间的柱子底下没有基线,而它的长度恰恰是坐标轴还不回来的那一样东西。 |
| 隐藏数据表 | 那张 sr-only 表格带着每一步的变化量和累计值,正好就是图里编码成一个长度和一个位置的那一对。 |
实践建议
推荐
- 把 showValues 打开。它默认是关的,而它正是这种图形核心弱点的解药:中间的柱子是悬空的,所以读者看得出某一步很小,却看不出小到什么程度。
- 收尾的合计那一步不要写 value。省略掉,它就由上面各个增减量算出来;手写上去,它就可能和那些量对不上,而图会一声不吭地把这个矛盾画出来。
- 步骤顺序是人为编排的时候,在 description 里说出来,并且传 showTitle,让这句话真的被印出来。连接线把各步画成一个序列,可绝大多数拆解并不是序列——同一个月里的流失和扩张是同时发生的——而读者会把最左边那根柱子当成第一个原因。
避免
- 不要把两个方向相反的变动轧成一个步骤。一根读作负二十、实际上是正一百八十对负两百的柱子,画出来和一个风平浪静的月份一模一样,而把“动了什么”显示出来正是这种图形存在的全部目的。
- 不要用眼睛去比中间的柱子和合计柱。只有合计坐在零线上;它们之间的一切都是悬在任意高度上的一段长度,所以级联上方一个小的步骤和接近零处一个大的步骤,根本不站在可比的基准上。
- 贡献项带正负号时,不要去用饼图。这种图形之所以存在,就是因为饼图放不下一块负的扇形——而当各部分不必刚好补齐两个合计之间的差额时,对的那个是 BarChart。
示例
默认
一个期初合计、四笔带正负号的贡献,以及一根本身不带值的收尾柱:它就是那些变动加起来的结果,所以它永远不可能和它上面那些步骤对不上。减少取 45 度斜纹,增加取实心填充——用纹理而不是第二种颜色,于是这个读法在灰度和强制色彩下都还活着——而一个合计是靠几何和一次增加区分开的,因为它是唯一一种站在基线上的柱子。这里的各步是同时发生的,它们的顺序是一次编辑取舍,而描述里把这件事说了出来:算术怎么排都成立,但那些连接线画的是一个序列,而读者一定会把最左边那根柱子当成第一个原因。
| 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 |
小计
type 用 total 会在基线上立一根柱子并重设累计值,而这里在中间用了两次——毛利,然后营业利润——于是这条瀑布在读者本来要在脑子里加五段悬空长度的地方,把自己重述了一遍。两个小计都不带值,所以两个都不可能和它们上面那些步骤对不上。把连接线关掉,同样这些柱子就成了一排高度毫不相干的长度,而这既是把它们留着的理由,也是把它们刻意去掉的理由:当各步的顺序是随手排的,那些连接线宣称的是一个数据本身并不具有的序列。
| 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 |
对照计划
那根收尾柱说出总量落在了哪里;它没说那是不是计划所说的地方。一条参考线把那一个数字横着画过整条瀑布,画在柱子之上而不是背后——一个被柱子盖住的目标是一个没人核查得了的目标——而且它会把坐标轴撑开去容纳自己,而不是被丢掉,这正是它在这里看得见的原因,因为这个计划值高于图中每一根柱子。weight 用 firm 是实心墨色,留给那一条图表真正在讲的阈值;安静的默认值是一条虚线细线,留给那些必须在场、又必须明显不是数据的背景。
| name | Change | Running total |
|---|---|---|
| H1 actual | 5,200 | 5,200 |
| New business | 1,420 | 6,620 |
| Expansion | 780 | 7,400 |
| Discounts | -260 | 7,140 |
| Churn | -940 | 6,200 |
| H2 actual | 0 | 6,200 |
类型
export type WaterfallStepType = 'delta' | 'total'
export type WaterfallDirection = 'increase' | 'decrease' | 'total'无障碍
- 连接线是要小心对待的那个断言:它把各步画成一个序列,可绝大多数拆解根本不是序列——同一个月里的流失和扩张是同时发生的,而读者会把最左边那根柱子当成第一个原因。顺序是任意的时候,在 description 里说出来。
- 中间那些柱子是悬空的长度,没有基线可读,所以级联上方一个小的变动很难和接近零处一个大的变动作比较。只有 total 柱坐在坐标轴上,也只有它们能被绝对地读出来。
- 方向由标签的正负号和柱子的纹理承担,不只靠位置——所以在灰度和强制颜色下这个读法仍然成立。
- 不给 value 的收尾柱由各个增减量算出来,把这道算术留在数据里,而不是留在调用者脑子里。