图表生成器
粘一份「标签, 数值」的数据,立刻得到柱状图、折线图、饼图或环形图,刻度自动取成好读的整数,可以导出 SVG 矢量图或者指定倍数的高清 PNG。
逗号和制表符都认,可以直接粘表格软件里复制出来的内容。首行除第一列外读不出数字时会被当成系列名的表头。
图表样式
预览与导出
2 倍导出得到 1520 × 920 像素的 PNG,贴进文档放大也清楚;SVG 不用选倍数,任何尺寸都清晰。
实际的刻度尺
量程的上下限是向上取整到 1、2、5 乘 10 的整数次幂再加步长凑出来的,所以刻度才会是整数。柱状图的量程里强制包含 0,柱子的长度才对得上数值。
使用说明
数据一行一个点,格式是「标签, 数值」,逗号和制表符都当分隔符,从表格软件里直接复制的制表符内容粘进来就能用。一行给两列以上数值就是多组数据,比如「季度, 线上, 线下」,这时会按分组柱状图或折线图画出来;柱状图勾上「多组数据堆叠」就把同一类目的柱子摞起来。首行除第一列外一个数字都读不出来时会被当成系列名的表头,「季度, 线上, 线下」正好命中;而「一月, 10」这种只有一列数字的行不会被误判,仍然当数据读。单元格空着按 0 处理,写不出数字的内容也按 0 处理并给出提示,并附上所在行号,方便回去改。
图片里的坐标全靠刻度决定,所以刻度算得好不好直接决定图能不能读。这里用的是 nice numbers 算法:先把数据范围按 1、2、5 乘 10 的整数次幂向上取整,再把区间切成大约五段、把步长也归到 1、2、5 的整数上,最后按步长逐个生成刻度。好处是刻度天然落在 0、40、80 这种数字上,而不是 0、33.6、67.2。柱状图必须把 0 包进量程——柱子的高度就是数值,从 0 起算才对得上;折线图画的是趋势,用数据的最小值到最大值当量程,否则一条在 99 到 103 之间波动的曲线会被压成一条直线。多组数据堆叠时量程要另算:一根柱子的高度是同一类目下各段之和,所以要按「每类目累计值」的最大值取刻度,拿单段的最大值当上限的话,最高的那根柱子会直接顶出绘图区。页面上会把算出来的量程、步长和刻度全部列出来,可以直接对着核对。
饼图和环形图按各段占总和的角度来画,所以只取第一组数据:两组数据各有各的 100%,混在一个圆里就变成两份占比叠在一起,读数必错,页面上会写明其余几组没有画进来。总和为 0 时没有可分配的角,会直接提示检查数据;出现负数时取绝对值再画,并给出提示,因为负值在「占比」这个语义下没有意义,真要比较正负应该改用柱状图。图例关掉时会把标签直接写在扇形上,太小的扇区不写,免得字压在一起;只剩一段有值时圆弧首尾重合画不出路径,这里退化成画一个整圆,颜色和图例仍然对得上。
这一页没有引入任何图表库,图形是自己用 SVG 元素拼出来的。原因是这里只要四种基础图形,不需要拖拽缩放、选区联动这类交互,而图表库体积动辄几百 KB;更要紧的是自己拼出来的是纯字符串,同一条数据源可以同时喂给三个出口——直接插进页面预览、包成 Blob 存成 .svg 文件、交给浏览器的渲染器光栅化成 PNG,三者永远一致,不会出现「预览好看、导出走样」。导出 PNG 时是把 SVG 串包成 Blob 交给 Image 元素,再画到 canvas 上编码,光栅化尺寸靠改写 SVG 根元素的 width 和 height 指定,viewBox 不动,所以浏览器是按目标像素重新渲染,2 倍、3 倍导出都不会糊。这里刻意不按设备的 devicePixelRatio 导出:导出文件是给文档和聊天窗口用的,跟着导出机器的屏幕像素比走,同一份图表在不同机器上导出的像素尺寸会不一样。
两个出口各有各的适用场景。SVG 保存的是坐标和文字,任何分辨率下都清晰,文件小,可以用文本编辑器改颜色和文案,适合还要继续改、要放进网页或打印的场合;代价是文字用的是你本机的字体,换一台机器打开可能字形和宽度略有差别。PNG 是固定像素的位图,任何软件都能显示,适合发到聊天软件、插进 PPT 和文档、当作头像或封面;代价是尺寸在导出时就定死了,后面要放大只能重新导。想要矢量图但需要别的格式,可以拿导出的 SVG 到SVG 转 PNG里换尺寸再转;数据来源是 CSV 文件的话,用CSV 列处理先把列整理好更省事;配色想自己定可以去颜色转换调好再填进来。
常见问题
为什么不直接用 ECharts 或 Chart.js 这类图表库?
这一页只要柱状、折线、饼、环形这四种基础图形,不需要缩放、刷选、动画和几十种扩展图,而图表库的体积动辄几百 KB,引入之后还得跟着它的配置约定走。真正要写的东西只是「把数值映射成坐标、再拼成一段 SVG 字符串」,这点计算量用现成的库反而更绕,所以这里自己画。
Y 轴刻度为什么是 0、50、100 这种整数,而不是数据里的最大值?
这是 nice numbers 算法的结果。它先把数据范围按 1、2、5 乘 10 的整数次幂向上取整,再用同样的方式把步长归到整数上,于是刻度天然落在好读的数字上。直接拿最大值当上限的后果是出现 137、274、411 这种刻度,读图的人得自己在心里换算一遍,小数点还会被截断。
折线图和柱状图的量程为什么不一样?
柱状图必须从 0 起算,因为柱子的高度就是数值本身,从 0 起算才对得上;折线图画的是趋势和相对变化,如果一条在 99 到 103 之间波动的曲线也从 0 起算,起伏会被压成一条直线。所以折线图用数据的最小值到最大值当量程,柱状图则强制把 0 包进量程里。
数据怎么填?可以直接粘 CSV 吗?
每行一个数据点,写「标签, 数值」,逗号和制表符都认;一行给两列以上的数值就是多组数据,比如「季度, 线上, 线下」。直接从表格软件里复制过来的内容会带制表符,粘贴进来也能用。首行如果除第一列外一个数字都读不出来,就被当成系列名的表头,例如「季度, 线上, 线下」;而「一月, 10」这种只有一列数字的行不会被误判成表头。
相关:CSV 列处理
饼图里数值相加是 0,或者出现负数会怎样?
相加为 0 时画不出任何扇形,页面上会直接报错提示检查数据,因为每一段在饼图里表示的是占总体的比例,全 0 时这个比例没有定义。出现负数时会取绝对值再画,同时在图上给出提示:饼图表达的是占比,负值在角度上没有意义,真要比较正负应该改用柱状图。
为什么饼图和环形图只画了第一组数据?
一组数据的各段加起来才是那个「整体」,两组数据各有各的 100%,画进同一个圆里就变成两份占比混在一起,读数会错。所以这里只取第一组,并在图上写明其余几组没有画进来;想比较多组数据的构成,用堆叠柱状图更合适。
导出 SVG 还是 PNG?
还要再改尺寸、用在网页或打印、需要无限放大不糊,就导 SVG,它是矢量坐标,任何分辨率下都清晰,文件也小。要发到聊天软件、插进 PPT 或文档,就导 PNG。PNG 是按当前图表尺寸和倍数光栅化的,1 倍等于图表尺寸那么多像素,倍数调到 2 或 3 再导出,贴到文档里放大也清楚。
相关:SVG 转 PNG
导出的 PNG 为什么比预览看起来糊?
预览是浏览器渲染 SVG,屏幕上永远按实际像素重画,所以怎么放大都清晰;PNG 一旦光栅化,像素就固定了。图表宽 760 像素时 1 倍导出就是 760 像素宽,放大会看到锯齿,换成 2 倍或 3 倍导出即可。另外这里刻意不按设备的 devicePixelRatio 导出——导出文件是给文档和聊天窗口用的,用导出机器的屏幕像素比会导致同一份图表在不同机器上导出的尺寸不一样。