图表
LineChart - 折线图
连续轴上互相比较的多条序列。
用法
什么时候用它
来自@misoto22/folio/charts
import { LineChart } from '@misoto22/folio/charts'说明
连续轴上互相比较的多条序列——用来回答“这几条各自在往哪走”。
和 <AreaChart> 的区别在于它要求读者做什么:区域图说“读这下面的量”,折线图说“把这几条互相比一比”。把四条互相重叠的序列都填上,第二个问题就没法回答了——这也是折线图不提供填充样式的原因。
结构
| 部件 | 说明 |
|---|---|
| 图框必填 | 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 会把缺口变成一条看上去像测出来的直线段。它在这里的代价比在面积图上更大,因为读者会把这段被造出来的线的斜率读成一个变化率。
示例
默认
好几条序列相互比较,而这正是折线图存在所针对的那一个问题。这里刻意没有提供填充变体:把四条互相重叠的序列填上,那个比较就没法回答了。差别改由描边承载——第二条线是虚线,而不是把它丢给一档灰去分——而图例给两条都命了名,因为灰阶和虚线都不会自报家门。
| month | Desktop | Mobile |
|---|---|---|
| Jan | 186 | 80 |
| Feb | 305 | 200 |
| Mar | 237 | 120 |
| Apr | 273 | 190 |
| May | 209 | 130 |
| Jun | 314 | 240 |
描边、标记点与曲线
一条线上的三个旋钮:描边怎么画、每个点用什么标记,以及曲线怎么从一行走到下一行。每个圆点都是拿它所在的那个 x 处采样出的序列渐变上的色来涂的——一个填了那道渐变的满宽矩形,再裁成一个圆——因为直接去填那个圆,会让渐变在每个标记里重新开始,于是线上每个点都会涂成同一种颜色。curve 是那个会改变这张图所宣称的内容、而不只是它长相的旋钮,而那些行本身没法告诉你哪一档是诚实的。
| Desktop |
|---|
| 186 |
| 305 |
| 237 |
| 273 |
| 209 |
| 314 |
图例样式
七种色块形状,而这个标记在这里干的活,比它在一套有彩色的系统里要多:两条线的差别是一档灰加一种虚线样式,而两者都不会自报家门,这正是为什么一条序列以上时图例是必需的、而不是装饰性的。让色块和标记对上——柱状图旁边配一根条、散点图旁边配一个点——常常是读者把图例和图连起来的最快方式。
| Desktop | Mobile |
|---|---|
| 186 | 80 |
| 305 | 200 |
| 237 | 120 |
| 273 | 190 |
背景底板
十一块装饰性的底板,而这个数量正是重点:在没有色相可花的地方,一张图表的个性总得从某处来,而这块底板是唯一一个纹理可以放开手脚、又不跟标记打架的地方。它是画在绘图矩形上的、不是画在整张 SVG 上,所以数据到哪儿它就停在哪儿,绝不会跑到刻度标签底下去。不过它不是网格——这个例子是把它组合进来顶替网格的,而一块底板帮不了任何人从坐标轴上读出一个值。
| Desktop |
|---|
| 186 |
| 305 |
| 237 |
| 273 |
| 209 |
| 314 |
强调
把一条线从另一条里标出来的两种方式。一张单色图表没有更亮的颜色可以去拿,所以 glowing 在那条作为这张图重点的序列背后放一圈光晕,而它是这个包里唯一一处模糊。buffer 把最后那一段画成虚线,那是「这是一个预测、而不是一次测量」的惯用画法。showTitle 会把每个图形的名字印出来,默认藏起来,因为大多数调用处在图表上方本来就已经有一个标题了。
| Desktop | Mobile |
|---|---|
| 186 | 80 |
| 305 | 200 |
| 237 | 120 |
| 273 | 190 |
| 209 | 130 |
| 314 | 240 |
| Desktop | Mobile |
|---|---|
| 186 | 80 |
| 305 | 200 |
| 237 | 120 |
| 273 | 190 |
| 209 | 130 |
| 314 | 240 |
区间选择条
四十行、两条序列,而这正是折线图开始自己跟自己交叉的地方。下面那条带子是整条序列的缩微版,上面罩着一扇被点亮的窗,对应上面那张图正在显示的那一段,于是读者在挑的过程中就能看见自己是从什么里面挑。它的手柄是用内联样式定位的,而不是靠 start 和 end 这两个类,这是刻意的:一张笛卡尔图在任何书写方向下都是第一行画在最前面,所以在 dir="rtl" 下把它们镜像过去,会把靠前的那个手柄放到一张仍然朝另一个方向走的图表的末端。
| day | Desktop | Mobile |
|---|---|---|
| D1 | 160 | 140 |
| D2 | 177 | 139 |
| D3 | 194 | 136 |
| D4 | 208 | 131 |
| D5 | 219 | 125 |
| D6 | 226 | 117 |
| D7 | 230 | 108 |
| D8 | 229 | 98 |
| D9 | 224 | 89 |
| D10 | 214 | 79 |
| D11 | 202 | 69 |
| D12 | 187 | 61 |
| D13 | 170 | 53 |
| D14 | 152 | 47 |
| D15 | 135 | 43 |
| D16 | 120 | 41 |
| D17 | 107 | 40 |
| D18 | 97 | 42 |
| D19 | 92 | 45 |
| D20 | 90 | 50 |
| D21 | 93 | 57 |
| D22 | 100 | 65 |
| D23 | 111 | 75 |
| D24 | 124 | 84 |
| D25 | 140 | 94 |
| D26 | 158 | 104 |
| D27 | 175 | 113 |
| D28 | 192 | 122 |
| D29 | 206 | 129 |
| D30 | 218 | 134 |
| D31 | 226 | 138 |
| D32 | 230 | 140 |
| D33 | 229 | 140 |
| D34 | 225 | 138 |
| D35 | 216 | 133 |
| D36 | 204 | 128 |
| D37 | 189 | 120 |
| D38 | 172 | 112 |
| D39 | 155 | 103 |
| D40 | 138 | 93 |
加载中
一页上每张图表在首次绘制时所处的状态。isLoading 把标记换成一份骨架,并保住图表量出来的高度,所以数据行落下来时页面上没有任何东西动。骨架的各行是在微光离开绘图区时重新掷出来的,而不是按定时器,因为一个 interval 会和动画错开相位,最终会在众目睽睽之下重掷,那读起来像图表在抽风,而不像它在加载。
轴标签与格式
给一根轴所度量的东西命名,并且按读者心里的方式写它的数字。一根读作 0、100、200 的轴,完全没说这些是人数、毫秒还是美元,所以把单位命名一次——在轴上,或者在序列标签里,但只一次,因为第二份副本是噪音。formatNumber 返回的是一个函数而不是一个字符串,好让它能直接交给 tickFormatter,而 Recharts 每个刻度会调用它一次,它绝不能每次都重建一个 Intl.NumberFormat;格式化过的刻度也更宽,那正是那个显式 width 存在的原因。
| p95 latency |
|---|
| 1.42 |
| 1.61 |
| 2.04 |
| 1.88 |
| 1.34 |
| Ad spend |
|---|
| 4,210 |
| 5,180 |
| 4,890 |
| 6,320 |
| 7,010 |
声音化
两条序列一前一后播放,各自先报出名字,而不是同时播放、一条向左耳一条向右耳。声像是更好的演示、更糟的选择:它假定有立体声输出和两只能用的耳朵,所以一个单声道扬声器或者一只助听器,会把两次播放塌成一段听者拆不开的旋律,而且它把这个特性的上限锁死在两条序列。两条都是对着同一个范围定音高的,正如它们在屏幕上共用一根数值轴,所以 p50 在范围低处的平坦是作为平坦被听见的,而不是被重新归一化成第二条跌宕起伏的线。
| week | p95 latency | p50 latency |
|---|---|---|
| W1 | 142 | 61 |
| W2 | 161 | 64 |
| W3 | 204 | 66 |
| W4 | 188 | 63 |
| W5 | 134 | 58 |
| W6 | 129 | 57 |
工具栏
工具栏和 brush 驱动的是同一扇窗,所以它们不可能各说各话:用按钮走一步缩放,下面的手柄会动;拖一下手柄,重置缩放会亮起来。九十天正是那种两个都想要的场景——带子显示你在从什么里面挑,按钮则精确地一步步走进去。组合这个工具栏,同时也会打开绘图区自己的手势:它变得可聚焦,方向键平移,加号和减号缩放,零重置,而横着拖过绘图区会缩放到那一段。滚轮被挡在 Ctrl 或 Cmd 后面,因为一张吞掉普通滚轮事件的图表,会把页面的滚动困在指针底下。
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
| day | Desktop | Mobile |
|---|---|---|
| D1 | 160 | 140 |
| D2 | 172 | 140 |
| D3 | 183 | 139 |
| D4 | 194 | 137 |
| D5 | 203 | 135 |
| D6 | 212 | 132 |
| D7 | 219 | 129 |
| D8 | 224 | 126 |
| D9 | 228 | 122 |
| D10 | 230 | 117 |
| D11 | 230 | 112 |
| D12 | 228 | 107 |
| D13 | 224 | 102 |
| D14 | 218 | 96 |
| D15 | 211 | 91 |
| D16 | 202 | 85 |
| D17 | 192 | 80 |
| D18 | 181 | 74 |
| D19 | 170 | 69 |
| D20 | 158 | 64 |
| D21 | 147 | 60 |
| D22 | 135 | 55 |
| D23 | 125 | 52 |
| D24 | 115 | 48 |
| D25 | 107 | 46 |
| D26 | 100 | 43 |
| D27 | 95 | 42 |
| D28 | 92 | 41 |
| D29 | 90 | 40 |
| D30 | 91 | 40 |
| D31 | 93 | 41 |
| D32 | 97 | 42 |
| D33 | 103 | 44 |
| D34 | 111 | 47 |
| D35 | 120 | 50 |
| D36 | 130 | 53 |
| D37 | 140 | 57 |
| D38 | 152 | 62 |
| D39 | 164 | 66 |
| D40 | 175 | 71 |
| D41 | 186 | 77 |
| D42 | 197 | 82 |
| D43 | 206 | 88 |
| D44 | 214 | 93 |
| D45 | 221 | 99 |
| D46 | 226 | 104 |
| D47 | 229 | 109 |
| D48 | 230 | 114 |
| D49 | 229 | 119 |
| D50 | 227 | 123 |
| D51 | 222 | 127 |
| D52 | 216 | 131 |
| D53 | 208 | 134 |
| D54 | 199 | 136 |
| D55 | 189 | 138 |
| D56 | 178 | 139 |
| D57 | 166 | 140 |
| D58 | 155 | 140 |
| D59 | 143 | 139 |
| D60 | 132 | 138 |
| D61 | 122 | 136 |
| D62 | 113 | 134 |
| D63 | 105 | 131 |
| D64 | 98 | 128 |
| D65 | 94 | 124 |
| D66 | 91 | 120 |
| D67 | 90 | 115 |
| D68 | 91 | 110 |
| D69 | 94 | 105 |
| D70 | 99 | 99 |
| D71 | 105 | 94 |
| D72 | 113 | 88 |
| D73 | 122 | 83 |
| D74 | 133 | 77 |
| D75 | 144 | 72 |
| D76 | 155 | 67 |
| D77 | 167 | 62 |
| D78 | 178 | 58 |
| D79 | 189 | 54 |
| D80 | 200 | 50 |
| D81 | 209 | 47 |
| D82 | 216 | 44 |
| D83 | 222 | 42 |
| D84 | 227 | 41 |
| D85 | 229 | 40 |
| D86 | 230 | 40 |
| D87 | 229 | 40 |
| D88 | 225 | 41 |
| D89 | 220 | 43 |
| D90 | 214 | 45 |
类型
export type LineStrokeVariant = AreaStrokeVariant无障碍
- title 必填;数据行同样会渲染成一份视觉隐藏的表格。
- 可点击的线下面会垫一条 15px 的透明线,因为 1.6px 的描边不构成指针目标。
- buffer 通过测量真实路径长度把最后一段画成虚线,所以在任何曲线类型下,“预测”都明确是另一种性质的事实。