图表
Facet
同一张图按分组各画一遍,共用同一把尺。
什么时候用它
示例
default
Organic search
Organic search visitors Direct
Direct visitors Paid search
Paid search visitors Referral
Referral visitors Email
Email visitors Social
Social visitors
| month | channel | visitors |
|---|---|---|
| Jan | Organic search | 4,200 |
| Jan | Paid search | 2,100 |
| Jan | Direct | 1,800 |
| Jan | Referral | 620 |
| Jan | 430 | |
| Jan | Social | 210 |
| Feb | Organic search | 4,400 |
| Feb | Paid search | 2,000 |
| Feb | Direct | 1,850 |
| Feb | Referral | 700 |
| Feb | 460 | |
| Feb | Social | 260 |
| Mar | Organic search | 4,100 |
| Mar | Paid search | 1,900 |
| Mar | Direct | 1,900 |
| Mar | Referral | 880 |
| Mar | 450 | |
| Mar | Social | 240 |
| Apr | Organic search | 4,800 |
| Apr | Paid search | 1,750 |
| Apr | Direct | 1,950 |
| Apr | Referral | 810 |
| Apr | 470 | |
| Apr | Social | 320 |
| May | Organic search | 5,200 |
| May | Paid search | 1,600 |
| May | Direct | 2,000 |
| May | Referral | 1,020 |
| May | 520 | |
| May | Social | 300 |
| Jun | Organic search | 5,600 |
| Jun | Paid search | 1,400 |
| Jun | Direct | 2,050 |
| Jun | Referral | 1,180 |
| Jun | 610 | |
| Jun | Social | 280 |
| Jul | Organic search | 6,100 |
| Jul | Paid search | 1,250 |
| Jul | Direct | 2,100 |
| Jul | Referral | 1,260 |
| Jul | 590 | |
| Jul | Social | 350 |
| Aug | Organic search | 6,400 |
| Aug | Paid search | 1,100 |
| Aug | Direct | 2,150 |
| Aug | Referral | 1,400 |
| Aug | 640 | |
| Aug | Social | 410 |
overplotted vs faceted
Overplotted — six series, one plot
| month | Organic search | Paid search | Direct | Referral | Social | |
|---|---|---|---|---|---|---|
| Jan | 4,200 | 2,100 | 1,800 | 620 | 430 | 210 |
| Feb | 4,400 | 2,000 | 1,850 | 700 | 460 | 260 |
| Mar | 4,100 | 1,900 | 1,900 | 880 | 450 | 240 |
| Apr | 4,800 | 1,750 | 1,950 | 810 | 470 | 320 |
| May | 5,200 | 1,600 | 2,000 | 1,020 | 520 | 300 |
| Jun | 5,600 | 1,400 | 2,050 | 1,180 | 610 | 280 |
| Jul | 6,100 | 1,250 | 2,100 | 1,260 | 590 | 350 |
| Aug | 6,400 | 1,100 | 2,150 | 1,400 | 640 | 410 |
Faceted — six plots, one domain
Organic search
Organic search visitors Direct
Direct visitors Paid search
Paid search visitors Referral
Referral visitors Email
Email visitors Social
Social visitors
| month | channel | visitors |
|---|---|---|
| Jan | Organic search | 4,200 |
| Jan | Paid search | 2,100 |
| Jan | Direct | 1,800 |
| Jan | Referral | 620 |
| Jan | 430 | |
| Jan | Social | 210 |
| Feb | Organic search | 4,400 |
| Feb | Paid search | 2,000 |
| Feb | Direct | 1,850 |
| Feb | Referral | 700 |
| Feb | 460 | |
| Feb | Social | 260 |
| Mar | Organic search | 4,100 |
| Mar | Paid search | 1,900 |
| Mar | Direct | 1,900 |
| Mar | Referral | 880 |
| Mar | 450 | |
| Mar | Social | 240 |
| Apr | Organic search | 4,800 |
| Apr | Paid search | 1,750 |
| Apr | Direct | 1,950 |
| Apr | Referral | 810 |
| Apr | 470 | |
| Apr | Social | 320 |
| May | Organic search | 5,200 |
| May | Paid search | 1,600 |
| May | Direct | 2,000 |
| May | Referral | 1,020 |
| May | 520 | |
| May | Social | 300 |
| Jun | Organic search | 5,600 |
| Jun | Paid search | 1,400 |
| Jun | Direct | 2,050 |
| Jun | Referral | 1,180 |
| Jun | 610 | |
| Jun | Social | 280 |
| Jul | Organic search | 6,100 |
| Jul | Paid search | 1,250 |
| Jul | Direct | 2,100 |
| Jul | Referral | 1,260 |
| Jul | 590 | |
| Jul | Social | 350 |
| Aug | Organic search | 6,400 |
| Aug | Paid search | 1,100 |
| Aug | Direct | 2,150 |
| Aug | Referral | 1,400 |
| Aug | 640 | |
| Aug | Social | 410 |
scale and overflow
Organic search
6,400Direct
2,150Referral
1,400Social
410
Organic search
6,400Direct
2,150Referral
1,400Social
410
Organic search
6,400Direct
2,150Paid search
2,100Referral
1,400Email
640Social
410Other
1,165
| month | channel | visitors |
|---|---|---|
| M1 | Organic search | 3,968 |
| M2 | Organic search | 3,712 |
| M3 | Organic search | 4,544 |
| M4 | Organic search | 4,224 |
| M5 | Organic search | 5,120 |
| M6 | Organic search | 5,632 |
| M7 | Organic search | 6,080 |
| M8 | Organic search | 6,400 |
| M1 | Direct | 1,333 |
| M2 | Direct | 1,247 |
| M3 | Direct | 1,527 |
| M4 | Direct | 1,419 |
| M5 | Direct | 1,720 |
| M6 | Direct | 1,892 |
| M7 | Direct | 2,043 |
| M8 | Direct | 2,150 |
| M1 | Paid search | 1,302 |
| M2 | Paid search | 1,218 |
| M3 | Paid search | 1,491 |
| M4 | Paid search | 1,386 |
| M5 | Paid search | 1,680 |
| M6 | Paid search | 1,848 |
| M7 | Paid search | 1,995 |
| M8 | Paid search | 2,100 |
| M1 | Referral | 868 |
| M2 | Referral | 812 |
| M3 | Referral | 994 |
| M4 | Referral | 924 |
| M5 | Referral | 1,120 |
| M6 | Referral | 1,232 |
| M7 | Referral | 1,330 |
| M8 | Referral | 1,400 |
| M1 | 397 | |
| M2 | 371 | |
| M3 | 454 | |
| M4 | 422 | |
| M5 | 512 | |
| M6 | 563 | |
| M7 | 608 | |
| M8 | 640 | |
| M1 | Social | 254 |
| M2 | Social | 238 |
| M3 | Social | 291 |
| M4 | Social | 271 |
| M5 | Social | 328 |
| M6 | Social | 361 |
| M7 | Social | 390 |
| M8 | Social | 410 |
| M1 | Affiliate | 198 |
| M2 | Affiliate | 186 |
| M3 | Affiliate | 227 |
| M4 | Affiliate | 211 |
| M5 | Affiliate | 256 |
| M6 | Affiliate | 282 |
| M7 | Affiliate | 304 |
| M8 | Affiliate | 320 |
| M1 | Display | 161 |
| M2 | Display | 151 |
| M3 | Display | 185 |
| M4 | Display | 172 |
| M5 | Display | 208 |
| M6 | Display | 229 |
| M7 | Display | 247 |
| M8 | Display | 260 |
| M1 | Podcast | 112 |
| M2 | Podcast | 104 |
| M3 | Podcast | 128 |
| M4 | Podcast | 119 |
| M5 | Podcast | 144 |
| M6 | Podcast | 158 |
| M7 | Podcast | 171 |
| M8 | Podcast | 180 |
| M1 | Newsletter | 87 |
| M2 | Newsletter | 81 |
| M3 | Newsletter | 99 |
| M4 | Newsletter | 92 |
| M5 | Newsletter | 112 |
| M6 | Newsletter | 123 |
| M7 | Newsletter | 133 |
| M8 | Newsletter | 140 |
| M1 | Partner | 68 |
| M2 | Partner | 64 |
| M3 | Partner | 78 |
| M4 | Partner | 73 |
| M5 | Partner | 88 |
| M6 | Partner | 97 |
| M7 | Partner | 105 |
| M8 | Partner | 110 |
| M1 | Print QR | 43 |
| M2 | Print QR | 41 |
| M3 | Print QR | 50 |
| M4 | Print QR | 46 |
| M5 | Print QR | 56 |
| M6 | Print QR | 62 |
| M7 | Print QR | 67 |
| M8 | Print QR | 70 |
| M1 | Events | 34 |
| M2 | Events | 32 |
| M3 | Events | 39 |
| M4 | Events | 36 |
| M5 | Events | 44 |
| M6 | Events | 48 |
| M7 | Events | 52 |
| M8 | Events | 55 |
| M1 | SMS | 19 |
| M2 | SMS | 17 |
| M3 | SMS | 21 |
| M4 | SMS | 20 |
| M5 | SMS | 24 |
| M6 | SMS | 26 |
| M7 | SMS | 29 |
| M8 | SMS | 30 |
说明
同一张图,每组重复一次,共用同一把刻度——小型多图(small multiples)。
一张多序列图到五条序列左右就答不上来的那个问题,答案在这里:八条线挤在一张图里是一团乱麻,读者的注意力全花在拆线上,而不是读线。把同样这八条线拆到八张小图里,代价是没法直接叠着比,换回来的是每一条自己的形状——这笔买卖几乎总是划算的。这就是 Tufte 说的小型多图,Observable Plot 和 Vega-Lite 把它当作一等操作,也是同一个理由。
当这些序列确实必须互相对照着读时——交叉、间距、在同一个总量里各占多少——而且它们的数量还少到分得清,那就改用多序列的 <LineChart> 或 <AreaChart>。当每一组自己的形状都值得被看见、当组数很多,或者读者的问题是「这里面哪一个不一样」时,用这个。
它比手写一个 data.map() 对在哪:默认情况下,各个面板共用同一个 domain。刻度各自独立时,每个面板都会把自己那张图填满,于是一组峰值 40 和一组峰值 4,000 画出来是同一个形状——比较不只是丢了,而是反过来了,而且屏幕上没有任何东西说出这件事。panel.domain 就是那把共享刻度;渲染函数必须把它交给面板的数值轴。它还会给网格封顶、把长尾折起来或者数出来,并且只印一个图例、一个轴标签,而不是十二个。
属性
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
| by必填 | keyof TRow & string | 按哪个数据字段拆分。每一个不同的取值对应一个面板。 | |
| children必填 | (panel: FacetPanel<TRow>) => ReactNode | 一个面板,每组调用一次。 把 `panel.domain` 交给面板里那张图的数值轴。这个组件存在的意义就在这一行;少了它,各个面板就是各自独立的刻度,这张网格也就只剩装饰。 | |
| data必填 | TRow[] | 所有的行,跨所有的组。拆分发生在这里,而不是在调用处。 | |
| title必填 | string | 整张网格在讲什么,用一句读者能据此行动的话。必填,而且即使不显示出来也会播报给读屏软件。 | |
| value必填 | (keyof TRow & string) | (keyof TRow & string)[] | 存放数值的那个(或那些)数据字段。 共享 domain 是按它量出来的,按统计量排序读的也是它,所以它必须把面板会画的每一条序列都列全——一个画了这里没提到的字段的面板,照样会画出自己那根坐标轴之外。 | |
| className | string | 最后合并到 figure 上,所以调用处可以决定它的尺寸和间距。 | |
| columns | 'auto' | number | 'auto' | 网格最宽的时候放几列。 `"auto"`——默认——在 `minPanelWidth` 允许的范围内尽量多塞,并且自己重排,而一张卡片、一条侧栏和一个整页宽的页面,从同一个调用处要的正是这个行为。给一个数字只是给列数封顶,而不是把它钉死:容器一窄,网格照样会减少列数,而不是把十二张图压缩到什么都看不见。 |
| description | ReactNode | 标题下面的一行——单位、时间窗口、需要提醒的地方。 | |
| domain | [number, number] | 直接把 domain 钉死,数据和 `scales` 一并忽略。 | |
| empty | ChartEmptyProps | 数据一个组都分不出来时,网格显示什么。 和单张图那个不同,这里没有 `false` 这个逃生口:空白的一对坐标轴好歹还是一张图,而一张空网格什么都不是——这份「空」当不成任何一种读法。 | |
| hideDataTable | boolean | false | 去掉那份包含所有行的隐藏表格视图。 当面板自己的图表已经带了表格时,值得设上——这个包里每张图都会把自己的数据行也渲染成一张表,否则一张十二面板的网格会往无障碍树里塞进十三张表。 |
| includeZero | boolean | true | 把共享 domain 往外拉到包含 0。 默认开启,也正是 Recharts 自己会用的那个默认:柱和面积一旦读在一条被截断的基线上,就会把网格里的每一处差异都夸大,而网格存在的意义就是拿来比较的。当取值离 0 很远、而它们之间的差异**就是**要读的东西时,把它关掉——温度、延迟、五分制的评分。 |
| legend | ReactNode | 整张网格共用一份图例,印在它上方。 每个面板里各放一份图例,等于把同样三个色块重复十二遍,那是把墨水花在重述读者从第一个面板就已经学会的东西上。把 `<LineChart.Legend>` 放进某一个面板,这个就不需要了;放在这里,就把面板里的那些拿掉。 | |
| limit | number | false | 12 | 在剩下那些被另行处理之前,先画多少个面板。 默认十二个而不是不限,因为它挡住的那种失败是无声的:一个有四十种取值的 `by` 字段会渲染出四十张图,每张大约邮票那么大,而页面上没有任何东西说这张网格已经读不动了。当调用处确实想清楚了要把它们全画出来时,传 `false` 解除上限。 |
| minPanelWidth | number | MIN_PANEL_PX | 一个面板最窄能到多少,再窄网格就减掉一列,单位像素。 |
| nice | boolean | true | 把 domain 向外取整,好让各面板的刻度落在读者认得出的数字上。关掉的话,`[0, 314]` 这样一个共享 domain 会给出 78.5 这种刻度。 |
| order | 'asc' | 'desc' | 排序方向。不传就跟着排序方式走:按名字或显式列表时是 A→Z,按统计量时是从大到小。 | |
| otherLabel | string | 'Other' | 折起来的那个面板叫什么名字。 |
| overflow | FacetOverflow | 'note' | 超出上限的那些组会怎么样。 `"note"`——默认——画出前 N 个,并在网格下面印一行,说明少了多少个。`"fold"` 再加一个面板,装着长尾在每个分类上加总后的结果,和 `BarList` 那一行「Other」做的是同一笔交易:总量仍然是真的,而长尾由一个面板吸收掉。两种做法都会让一张封了顶的网格自己说出来;没有任何东西是被悄悄丢掉的。 |
| overflowNote | (info: FacetOverflowInfo) => ReactNode | defaultOverflowNote | 写出封顶网格下面的那一行。默认那句是英文的;需要本地化的调用处就在这里把它换掉。 |
| scales | FacetScales | 'shared' | 每个面板是画在同一个 domain 上,还是各画各的。 `"shared"`——默认值,也是这个组件存在的全部理由。刻度各自独立时,每个面板都会把自己那张图填满,于是一组峰值 40 和一组峰值 4,000 画出来是同一个形状,而读者打开这张网格本来要做的那个比较,就不只是变难,而是干脆错了。共享的 domain 是在封顶之后活下来的所有面板上算出来的,再作为 `panel.domain` 交给每一个面板;一个不把它传给自己数值轴的面板,等于又选回了坏掉的那个版本。 `"independent"` 是逃生口,留给面板之间根本不可比的情况——单位不同,数量级之间也不存在任何关系——以及每个面板自己的形状才是唯一读法的情况。它从来不是安全的那个答案,这也正是它不是默认值的原因。 |
| showPanelNames | boolean | true | 把每个面板的组名印在它那张图的上方。 默认开启。关掉只是把名字藏起来——它仍然留在无障碍树里,因为一张读屏软件分不出面板的网格,就是一张只有一个图形、里面装着十二张无名小图的网格。 |
| showTitle | boolean | 把标题印在网格上方,而不是藏起来。 | |
| sort | FacetSort<TRow> | 'max' | 面板的顺序。 默认峰值最大的排在最前,因为这才是让一张网格读得动的东西:视线顺序就成了名次顺序,「这里面哪一个是问题」在读到任何一根坐标轴之前,就已经由位置回答了。要让读者拿它来查东西,用 `"name"`;数据本身不带的那种顺序——阶段、星期、严重级别——传一个显式的名字数组;其余情况,传一个作用在 `{ name, rows, stats }` 上的比较函数。 |
| xDataKey | keyof TRow & string | 共享的分类字段——每个面板都共有的那根 x 轴。 它喂给隐藏的表格视图,也正是它让 `overflow="fold"` 能在每个分类上把长尾加总起来,而不是把它们拼接在一起。 | |
| xLabel | ReactNode | 分类轴的名字,在网格下方印一次。 | |
| yLabel | ReactNode | 数值轴的名字,在网格上方印一次,而不是每个面板各印一次。 |
无障碍
- 共享取值域是默认,也是这件事的全部意义:各自独立取刻度的话,峰值 40 的一组和峰值 4,000 的一组会画出同样的形状,读者要来做的那个比较不只是丢了,是被**反转**了。
- 每一格都是一个有自己可访问名称的 figure,所以读屏软件走过的是八张各有其名的图,而不是一整块没有名字的网格。
- 超过 max 的格子会折进一条明说的 overflow 里,而不是被丢掉,而且数量会印出来——一个悄悄少了四组的网格,读者是察觉不到的。
- 格子的顺序由调用处通过 sort 明确选定,因为读者会把阅读顺序当成排名。