跳到正文
册页

图表

LineChart - 折线图

连续轴上互相比较的多条序列。

用法

什么时候用它

读者在把序列互相比较。当某条线下方的面积才是重点时,就把它填起来——那是 AreaChart。

来自@misoto22/folio/charts

TSX
import { LineChart } from '@misoto22/folio/charts'

说明

连续轴上互相比较的多条序列——用来回答“这几条各自在往哪走”。

和 <AreaChart> 的区别在于它要求读者做什么:区域图说“读这下面的量”,折线图说“把这几条互相比一比”。把四条互相重叠的序列都填上,第二个问题就没法回答了——这也是折线图不提供填充样式的原因。

结构

LineChart anatomy
部件说明
图框必填ChartFigure 的那个 <figure>,以及它那句 sr-only 的 caption。description 会和 title 一起被播报,只有在 showTitle 下才印出来——而关于坐标轴被裁剪的说明,正该写在这里。
线必填<LineChart.Line>,一条序列一根,描边 1.6px。isClickable 会在可见的那根下面再加一根完全透明的 15px 线,因为一根发丝般的细线不构成指针目标。
数据点标记<LineChart.Dot> 和 <LineChart.ActiveDot>,都是插槽,也都默认关闭。静止的点共用入场擦除的蒙版,所以它和自己那根线一起到达;激活的点从不被蒙版遮住,因为它只在悬停时出现,那时擦除早就结束了。
预测段buffer 用 getPointAtLength 量出真实路径,把最后一段画成虚线,所以在任何曲线类型下,一个预测读起来都是另一种性质的事实。可绘制的点少于两个时,它退回成一条普通曲线。
声化控件<LineChart.Sonify>,绘图区上方一个真正的 <button>,把可见的数据行播成音高。声音从不由副作用启动,只由这次点击启动,而且它读的是刷选出来的窗口、不是整条序列。
隐藏数据表完整数据的那张 sr-only 表格。Recharts 的 accessibilityLayer 给的是一个每次播报一个点的键盘光标,那是导航;这里才是数字。

实践建议

推荐

  • 这是这一族里唯一扛得住值轴被截断的图。折线编码的是斜率,所以把取值域裁到数据自己的范围内,往往正是让百分之二的变动能被看见的原因——这么做的时候,在 description 里把范围说出来。
  • 一个还没结束的周期,用 buffer,别把它丢掉。半个月的数据画成实线读起来像是崩了;画成虚线的最后一段,读起来才是它本来的样子——不完整。
  • 序列稀疏时,组合 <LineChart.Dot>。点默认是关的,那五个点就是四条线段,读者分不出哪个是测出来的值、哪个只是插值拐了个弯。

避免

  • 只有一行数据什么都画不出来:一个点没有线段,dot 在没有组合时是 false,而空状态也不会触发,因为数据行是存在的。一行的情况要在调用处挡掉。
  • 一个画框里八条线是一团乱麻,而 1.6px 的描边配上八级灰阶梯度,比一张彩色图还要更乱。超过大约五条序列,答案是 Facet,不是第九个梯度槽位——SERIES_SLOTS 是 8,没有第九个。
  • connectNulls 会把缺口变成一条看上去像测出来的直线段。它在这里的代价比在面积图上更大,因为读者会把这段被造出来的线的斜率读成一个变化率。

示例

默认

好几条序列相互比较,而这正是折线图存在所针对的那一个问题。这里刻意没有提供填充变体:把四条互相重叠的序列填上,那个比较就没法回答了。差别改由描边承载——第二条线是虚线,而不是把它丢给一档灰去分——而图例给两条都命了名,因为灰阶和虚线都不会自报家门。

Visitors per month
Visitors per month
monthDesktopMobile
Jan18680
Feb305200
Mar237120
Apr273190
May209130
Jun314240

描边、标记点与曲线

一条线上的三个旋钮:描边怎么画、每个点用什么标记,以及曲线怎么从一行走到下一行。每个圆点都是拿它所在的那个 x 处采样出的序列渐变上的色来涂的——一个填了那道渐变的满宽矩形,再裁成一个圆——因为直接去填那个圆,会让渐变在每个标记里重新开始,于是线上每个点都会涂成同一种颜色。curve 是那个会改变这张图所宣称的内容、而不只是它长相的旋钮,而那些行本身没法告诉你哪一档是诚实的。

Visitors per month
Visitors per month
Desktop
186
305
237
273
209
314

图例样式

七种色块形状,而这个标记在这里干的活,比它在一套有彩色的系统里要多:两条线的差别是一档灰加一种虚线样式,而两者都不会自报家门,这正是为什么一条序列以上时图例是必需的、而不是装饰性的。让色块和标记对上——柱状图旁边配一根条、散点图旁边配一个点——常常是读者把图例和图连起来的最快方式。

Visitors per month
Visitors per month
DesktopMobile
18680
305200
237120
273190

背景底板

十一块装饰性的底板,而这个数量正是重点:在没有色相可花的地方,一张图表的个性总得从某处来,而这块底板是唯一一个纹理可以放开手脚、又不跟标记打架的地方。它是画在绘图矩形上的、不是画在整张 SVG 上,所以数据到哪儿它就停在哪儿,绝不会跑到刻度标签底下去。不过它不是网格——这个例子是把它组合进来顶替网格的,而一块底板帮不了任何人从坐标轴上读出一个值。

Visitors per month — dots plate
Visitors per month — dots plate
Desktop
186
305
237
273
209
314

强调

把一条线从另一条里标出来的两种方式。一张单色图表没有更亮的颜色可以去拿,所以 glowing 在那条作为这张图重点的序列背后放一圈光晕,而它是这个包里唯一一处模糊。buffer 把最后那一段画成虚线,那是「这是一个预测、而不是一次测量」的惯用画法。showTitle 会把每个图形的名字印出来,默认藏起来,因为大多数调用处在图表上方本来就已经有一个标题了。

Glowing — one series is the point
Glowing — one series is the point
DesktopMobile
18680
305200
237120
273190
209130
314240
Buffer — the last leg is a projection
Buffer — the last leg is a projection
DesktopMobile
18680
305200
237120
273190
209130
314240

区间选择条

四十行、两条序列,而这正是折线图开始自己跟自己交叉的地方。下面那条带子是整条序列的缩微版,上面罩着一扇被点亮的窗,对应上面那张图正在显示的那一段,于是读者在挑的过程中就能看见自己是从什么里面挑。它的手柄是用内联样式定位的,而不是靠 start 和 end 这两个类,这是刻意的:一张笛卡尔图在任何书写方向下都是第一行画在最前面,所以在 dir="rtl" 下把它们镜像过去,会把靠前的那个手柄放到一张仍然朝另一个方向走的图表的末端。

Visitors per day
Visitors per day
dayDesktopMobile
D1160140
D2177139
D3194136
D4208131
D5219125
D6226117
D7230108
D822998
D922489
D1021479
D1120269
D1218761
D1317053
D1415247
D1513543
D1612041
D1710740
D189742
D199245
D209050
D219357
D2210065
D2311175
D2412484
D2514094
D26158104
D27175113
D28192122
D29206129
D30218134
D31226138
D32230140
D33229140
D34225138
D35216133
D36204128
D37189120
D38172112
D39155103
D4013893

加载中

一页上每张图表在首次绘制时所处的状态。isLoading 把标记换成一份骨架,并保住图表量出来的高度,所以数据行落下来时页面上没有任何东西动。骨架的各行是在微光离开绘图区时重新掷出来的,而不是按定时器,因为一个 interval 会和动画错开相位,最终会在众目睽睽之下重掷,那读起来像图表在抽风,而不像它在加载。

Visitors per month
Loading

轴标签与格式

给一根轴所度量的东西命名,并且按读者心里的方式写它的数字。一根读作 0、100、200 的轴,完全没说这些是人数、毫秒还是美元,所以把单位命名一次——在轴上,或者在序列标签里,但只一次,因为第二份副本是噪音。formatNumber 返回的是一个函数而不是一个字符串,好让它能直接交给 tickFormatter,而 Recharts 每个刻度会调用它一次,它绝不能每次都重建一个 Intl.NumberFormat;格式化过的刻度也更宽,那正是那个显式 width 存在的原因。

p95 latency
p95 latency
p95 latency
1.42
1.61
2.04
1.88
1.34
Ad spend
Ad spend
Ad spend
4,210
5,180
4,890
6,320
7,010

声音化

两条序列一前一后播放,各自先报出名字,而不是同时播放、一条向左耳一条向右耳。声像是更好的演示、更糟的选择:它假定有立体声输出和两只能用的耳朵,所以一个单声道扬声器或者一只助听器,会把两次播放塌成一段听者拆不开的旋律,而且它把这个特性的上限锁死在两条序列。两条都是对着同一个范围定音高的,正如它们在屏幕上共用一根数值轴,所以 p50 在范围低处的平坦是作为平坦被听见的,而不是被重新归一化成第二条跌宕起伏的线。

Latency per week, milliseconds
Latency per week, milliseconds
weekp95 latencyp50 latency
W114261
W216164
W320466
W418863
W513458
W612957

工具栏

工具栏和 brush 驱动的是同一扇窗,所以它们不可能各说各话:用按钮走一步缩放,下面的手柄会动;拖一下手柄,重置缩放会亮起来。九十天正是那种两个都想要的场景——带子显示你在从什么里面挑,按钮则精确地一步步走进去。组合这个工具栏,同时也会打开绘图区自己的手势:它变得可聚焦,方向键平移,加号和减号缩放,零重置,而横着拖过绘图区会缩放到那一段。滚轮被挡在 Ctrl 或 Cmd 后面,因为一张吞掉普通滚轮事件的图表,会把页面的滚动困在指针底下。

Visitors per day

Arrow keys pan by one point, Page Up and Page Down by a screenful, Home and End jump to the ends. Plus and minus zoom, zero resets. Ctrl and the wheel zoom around the pointer, and dragging across the plot zooms to that span.

Showing D1 to D90, 90 of 90 points

Visitors per day
dayDesktopMobile
D1160140
D2172140
D3183139
D4194137
D5203135
D6212132
D7219129
D8224126
D9228122
D10230117
D11230112
D12228107
D13224102
D1421896
D1521191
D1620285
D1719280
D1818174
D1917069
D2015864
D2114760
D2213555
D2312552
D2411548
D2510746
D2610043
D279542
D289241
D299040
D309140
D319341
D329742
D3310344
D3411147
D3512050
D3613053
D3714057
D3815262
D3916466
D4017571
D4118677
D4219782
D4320688
D4421493
D4522199
D46226104
D47229109
D48230114
D49229119
D50227123
D51222127
D52216131
D53208134
D54199136
D55189138
D56178139
D57166140
D58155140
D59143139
D60132138
D61122136
D62113134
D63105131
D6498128
D6594124
D6691120
D6790115
D6891110
D6994105
D709999
D7110594
D7211388
D7312283
D7413377
D7514472
D7615567
D7716762
D7817858
D7918954
D8020050
D8120947
D8221644
D8322242
D8422741
D8522940
D8623040
D8722940
D8822541
D8922043
D9021445

类型

TSX
export type LineStrokeVariant = AreaStrokeVariant

无障碍

  • title 必填;数据行同样会渲染成一份视觉隐藏的表格。
  • 可点击的线下面会垫一条 15px 的透明线,因为 1.6px 的描边不构成指针目标。
  • buffer 通过测量真实路径长度把最后一段画成虚线,所以在任何曲线类型下,“预测”都明确是另一种性质的事实。