跳到正文
misoto22 design

图表

Facet

同一张图按分组各画一遍,共用同一把尺。

什么时候用它

八条序列挤在一个画框里会变成一团乱麻。真的需要逐点比较的两三条序列,还是该待在同一张图里。

示例

default

Visitors by channel, 2026
Visitors
  • Organic search

    Organic search visitors
  • Direct

    Direct visitors
  • Paid search

    Paid search visitors
  • Referral

    Referral visitors
  • Email

    Email visitors
  • Social

    Social visitors
Jan – Aug 2026
Visitors by channel, 2026
monthchannelvisitors
JanOrganic search4,200
JanPaid search2,100
JanDirect1,800
JanReferral620
JanEmail430
JanSocial210
FebOrganic search4,400
FebPaid search2,000
FebDirect1,850
FebReferral700
FebEmail460
FebSocial260
MarOrganic search4,100
MarPaid search1,900
MarDirect1,900
MarReferral880
MarEmail450
MarSocial240
AprOrganic search4,800
AprPaid search1,750
AprDirect1,950
AprReferral810
AprEmail470
AprSocial320
MayOrganic search5,200
MayPaid search1,600
MayDirect2,000
MayReferral1,020
MayEmail520
MaySocial300
JunOrganic search5,600
JunPaid search1,400
JunDirect2,050
JunReferral1,180
JunEmail610
JunSocial280
JulOrganic search6,100
JulPaid search1,250
JulDirect2,100
JulReferral1,260
JulEmail590
JulSocial350
AugOrganic search6,400
AugPaid search1,100
AugDirect2,150
AugReferral1,400
AugEmail640
AugSocial410

overplotted vs faceted

Overplotted — six series, one plot

Visitors by channel, overplotted
Visitors by channel, overplotted
monthOrganic searchPaid searchDirectReferralEmailSocial
Jan4,2002,1001,800620430210
Feb4,4002,0001,850700460260
Mar4,1001,9001,900880450240
Apr4,8001,7501,950810470320
May5,2001,6002,0001,020520300
Jun5,6001,4002,0501,180610280
Jul6,1001,2502,1001,260590350
Aug6,4001,1002,1501,400640410

Faceted — six plots, one domain

Visitors by channel, faceted
Visitors
  • Organic search

    Organic search visitors
  • Direct

    Direct visitors
  • Paid search

    Paid search visitors
  • Referral

    Referral visitors
  • Email

    Email visitors
  • Social

    Social visitors
Visitors by channel, faceted
monthchannelvisitors
JanOrganic search4,200
JanPaid search2,100
JanDirect1,800
JanReferral620
JanEmail430
JanSocial210
FebOrganic search4,400
FebPaid search2,000
FebDirect1,850
FebReferral700
FebEmail460
FebSocial260
MarOrganic search4,100
MarPaid search1,900
MarDirect1,900
MarReferral880
MarEmail450
MarSocial240
AprOrganic search4,800
AprPaid search1,750
AprDirect1,950
AprReferral810
AprEmail470
AprSocial320
MayOrganic search5,200
MayPaid search1,600
MayDirect2,000
MayReferral1,020
MayEmail520
MaySocial300
JunOrganic search5,600
JunPaid search1,400
JunDirect2,050
JunReferral1,180
JunEmail610
JunSocial280
JulOrganic search6,100
JulPaid search1,250
JulDirect2,100
JulReferral1,260
JulEmail590
JulSocial350
AugOrganic search6,400
AugPaid search1,100
AugDirect2,150
AugReferral1,400
AugEmail640
AugSocial410

scale and overflow

Visitors by channel, shared scalescales="shared" — the default
  • Organic search

    6,400
  • Direct

    2,150
  • Referral

    1,400
  • Social

    410
Visitors by channel, independent scalesscales="independent" — every panel lies about its size
  • Organic search

    6,400
  • Direct

    2,150
  • Referral

    1,400
  • Social

    410
Visitors by channel, top sixFourteen channels, sorted by where each ended up
  • Organic search

    6,400
  • Direct

    2,150
  • Paid search

    2,100
  • Referral

    1,400
  • Email

    640
  • Social

    410
  • Other

    1,165
6 of 14 shown; the other 8 are summed into Other.
Visitors by channel, top six
monthchannelvisitors
M1Organic search3,968
M2Organic search3,712
M3Organic search4,544
M4Organic search4,224
M5Organic search5,120
M6Organic search5,632
M7Organic search6,080
M8Organic search6,400
M1Direct1,333
M2Direct1,247
M3Direct1,527
M4Direct1,419
M5Direct1,720
M6Direct1,892
M7Direct2,043
M8Direct2,150
M1Paid search1,302
M2Paid search1,218
M3Paid search1,491
M4Paid search1,386
M5Paid search1,680
M6Paid search1,848
M7Paid search1,995
M8Paid search2,100
M1Referral868
M2Referral812
M3Referral994
M4Referral924
M5Referral1,120
M6Referral1,232
M7Referral1,330
M8Referral1,400
M1Email397
M2Email371
M3Email454
M4Email422
M5Email512
M6Email563
M7Email608
M8Email640
M1Social254
M2Social238
M3Social291
M4Social271
M5Social328
M6Social361
M7Social390
M8Social410
M1Affiliate198
M2Affiliate186
M3Affiliate227
M4Affiliate211
M5Affiliate256
M6Affiliate282
M7Affiliate304
M8Affiliate320
M1Display161
M2Display151
M3Display185
M4Display172
M5Display208
M6Display229
M7Display247
M8Display260
M1Podcast112
M2Podcast104
M3Podcast128
M4Podcast119
M5Podcast144
M6Podcast158
M7Podcast171
M8Podcast180
M1Newsletter87
M2Newsletter81
M3Newsletter99
M4Newsletter92
M5Newsletter112
M6Newsletter123
M7Newsletter133
M8Newsletter140
M1Partner68
M2Partner64
M3Partner78
M4Partner73
M5Partner88
M6Partner97
M7Partner105
M8Partner110
M1Print QR43
M2Print QR41
M3Print QR50
M4Print QR46
M5Print QR56
M6Print QR62
M7Print QR67
M8Print QR70
M1Events34
M2Events32
M3Events39
M4Events36
M5Events44
M6Events48
M7Events52
M8Events55
M1SMS19
M2SMS17
M3SMS21
M4SMS20
M5SMS24
M6SMS26
M7SMS29
M8SMS30

说明

同一张图,每组重复一次,共用同一把刻度——小型多图(small multiples)。

一张多序列图到五条序列左右就答不上来的那个问题,答案在这里:八条线挤在一张图里是一团乱麻,读者的注意力全花在拆线上,而不是读线。把同样这八条线拆到八张小图里,代价是没法直接叠着比,换回来的是每一条自己的形状——这笔买卖几乎总是划算的。这就是 Tufte 说的小型多图,Observable Plot 和 Vega-Lite 把它当作一等操作,也是同一个理由。

当这些序列确实必须互相对照着读时——交叉、间距、在同一个总量里各占多少——而且它们的数量还少到分得清,那就改用多序列的 <LineChart><AreaChart>。当每一组自己的形状都值得被看见、当组数很多,或者读者的问题是「这里面哪一个不一样」时,用这个。

它比手写一个 data.map() 对在哪:默认情况下,各个面板共用同一个 domain。刻度各自独立时,每个面板都会把自己那张图填满,于是一组峰值 40 和一组峰值 4,000 画出来是同一个形状——比较不只是丢了,而是反过来了,而且屏幕上没有任何东西说出这件事。panel.domain 就是那把共享刻度;渲染函数必须把它交给面板的数值轴。它还会给网格封顶、把长尾折起来或者数出来,并且只印一个图例、一个轴标签,而不是十二个。

属性

Facet props
属性类型默认值说明
by必填keyof TRow & string按哪个数据字段拆分。每一个不同的取值对应一个面板。
children必填(panel: FacetPanel<TRow>) => ReactNode一个面板,每组调用一次。 把 `panel.domain` 交给面板里那张图的数值轴。这个组件存在的意义就在这一行;少了它,各个面板就是各自独立的刻度,这张网格也就只剩装饰。
data必填TRow[]所有的行,跨所有的组。拆分发生在这里,而不是在调用处。
title必填string整张网格在讲什么,用一句读者能据此行动的话。必填,而且即使不显示出来也会播报给读屏软件。
value必填(keyof TRow & string) | (keyof TRow & string)[]存放数值的那个(或那些)数据字段。 共享 domain 是按它量出来的,按统计量排序读的也是它,所以它必须把面板会画的每一条序列都列全——一个画了这里没提到的字段的面板,照样会画出自己那根坐标轴之外。
classNamestring最后合并到 figure 上,所以调用处可以决定它的尺寸和间距。
columns'auto' | number'auto'网格最宽的时候放几列。 `"auto"`——默认——在 `minPanelWidth` 允许的范围内尽量多塞,并且自己重排,而一张卡片、一条侧栏和一个整页宽的页面,从同一个调用处要的正是这个行为。给一个数字只是给列数封顶,而不是把它钉死:容器一窄,网格照样会减少列数,而不是把十二张图压缩到什么都看不见。
descriptionReactNode标题下面的一行——单位、时间窗口、需要提醒的地方。
domain[number, number]直接把 domain 钉死,数据和 `scales` 一并忽略。
emptyChartEmptyProps数据一个组都分不出来时,网格显示什么。 和单张图那个不同,这里没有 `false` 这个逃生口:空白的一对坐标轴好歹还是一张图,而一张空网格什么都不是——这份「空」当不成任何一种读法。
hideDataTablebooleanfalse去掉那份包含所有行的隐藏表格视图。 当面板自己的图表已经带了表格时,值得设上——这个包里每张图都会把自己的数据行也渲染成一张表,否则一张十二面板的网格会往无障碍树里塞进十三张表。
includeZerobooleantrue把共享 domain 往外拉到包含 0。 默认开启,也正是 Recharts 自己会用的那个默认:柱和面积一旦读在一条被截断的基线上,就会把网格里的每一处差异都夸大,而网格存在的意义就是拿来比较的。当取值离 0 很远、而它们之间的差异**就是**要读的东西时,把它关掉——温度、延迟、五分制的评分。
legendReactNode整张网格共用一份图例,印在它上方。 每个面板里各放一份图例,等于把同样三个色块重复十二遍,那是把墨水花在重述读者从第一个面板就已经学会的东西上。把 `<LineChart.Legend>` 放进某一个面板,这个就不需要了;放在这里,就把面板里的那些拿掉。
limitnumber | false12在剩下那些被另行处理之前,先画多少个面板。 默认十二个而不是不限,因为它挡住的那种失败是无声的:一个有四十种取值的 `by` 字段会渲染出四十张图,每张大约邮票那么大,而页面上没有任何东西说这张网格已经读不动了。当调用处确实想清楚了要把它们全画出来时,传 `false` 解除上限。
minPanelWidthnumberMIN_PANEL_PX一个面板最窄能到多少,再窄网格就减掉一列,单位像素。
nicebooleantrue把 domain 向外取整,好让各面板的刻度落在读者认得出的数字上。关掉的话,`[0, 314]` 这样一个共享 domain 会给出 78.5 这种刻度。
order'asc' | 'desc'排序方向。不传就跟着排序方式走:按名字或显式列表时是 A→Z,按统计量时是从大到小。
otherLabelstring'Other'折起来的那个面板叫什么名字。
overflowFacetOverflow'note'超出上限的那些组会怎么样。 `"note"`——默认——画出前 N 个,并在网格下面印一行,说明少了多少个。`"fold"` 再加一个面板,装着长尾在每个分类上加总后的结果,和 `BarList` 那一行「Other」做的是同一笔交易:总量仍然是真的,而长尾由一个面板吸收掉。两种做法都会让一张封了顶的网格自己说出来;没有任何东西是被悄悄丢掉的。
overflowNote(info: FacetOverflowInfo) => ReactNodedefaultOverflowNote写出封顶网格下面的那一行。默认那句是英文的;需要本地化的调用处就在这里把它换掉。
scalesFacetScales'shared'每个面板是画在同一个 domain 上,还是各画各的。 `"shared"`——默认值,也是这个组件存在的全部理由。刻度各自独立时,每个面板都会把自己那张图填满,于是一组峰值 40 和一组峰值 4,000 画出来是同一个形状,而读者打开这张网格本来要做的那个比较,就不只是变难,而是干脆错了。共享的 domain 是在封顶之后活下来的所有面板上算出来的,再作为 `panel.domain` 交给每一个面板;一个不把它传给自己数值轴的面板,等于又选回了坏掉的那个版本。 `"independent"` 是逃生口,留给面板之间根本不可比的情况——单位不同,数量级之间也不存在任何关系——以及每个面板自己的形状才是唯一读法的情况。它从来不是安全的那个答案,这也正是它不是默认值的原因。
showPanelNamesbooleantrue把每个面板的组名印在它那张图的上方。 默认开启。关掉只是把名字藏起来——它仍然留在无障碍树里,因为一张读屏软件分不出面板的网格,就是一张只有一个图形、里面装着十二张无名小图的网格。
showTitleboolean把标题印在网格上方,而不是藏起来。
sortFacetSort<TRow>'max'面板的顺序。 默认峰值最大的排在最前,因为这才是让一张网格读得动的东西:视线顺序就成了名次顺序,「这里面哪一个是问题」在读到任何一根坐标轴之前,就已经由位置回答了。要让读者拿它来查东西,用 `"name"`;数据本身不带的那种顺序——阶段、星期、严重级别——传一个显式的名字数组;其余情况,传一个作用在 `{ name, rows, stats }` 上的比较函数。
xDataKeykeyof TRow & string共享的分类字段——每个面板都共有的那根 x 轴。 它喂给隐藏的表格视图,也正是它让 `overflow="fold"` 能在每个分类上把长尾加总起来,而不是把它们拼接在一起。
xLabelReactNode分类轴的名字,在网格下方印一次。
yLabelReactNode数值轴的名字,在网格上方印一次,而不是每个面板各印一次。

无障碍

  • 共享取值域是默认,也是这件事的全部意义:各自独立取刻度的话,峰值 40 的一组和峰值 4,000 的一组会画出同样的形状,读者要来做的那个比较不只是丢了,是被**反转**了。
  • 每一格都是一个有自己可访问名称的 figure,所以读屏软件走过的是八张各有其名的图,而不是一整块没有名字的网格。
  • 超过 max 的格子会折进一条明说的 overflow 里,而不是被丢掉,而且数量会印出来——一个悄悄少了四组的网格,读者是察觉不到的。
  • 格子的顺序由调用处通过 sort 明确选定,因为读者会把阅读顺序当成排名。