前言
可视化数据对于有效理解和传达信息至关重要。一种常见的可视化数据的方法是使用Sparkline图表 - 这是一种小型、简单、紧凑的图表,可以快速概览数据集中的趋势和模式。
经常逛github的同学会很经常看到每个代码仓库会有个动态sparkline(迷你图),很直观的展示了这个仓库作者在该仓库的活跃度数据走势。
介绍SVG和Sparkline图表
SVG,可缩放矢量图形(Scalable Vector Graphics),是一种广泛使用的基于XML的格式,用于在Web上创建矢量图形。SVG图形不受分辨率影响,并且可以通过CSS轻松自定义,或者通过JavaScript进行操作。而Sparkline图表则是一种迷你图表,通常出现在文本或小空间内,以提供数据趋势的视觉表示,例如股票价格、温度波动或网站流量。
github迷你图表原理及实现方式
原理:将原始数据转换为可在SVG图表中绘制的坐标点集合,坐标点将确定线条的形状和位置。
可以选择不同的实现方式:
原生SVG实现github迷你图表
SVG(Scalable Vector Graphics)的<path>
元素是用于绘制各种形状和路径的重要工具。元素的数据(通常称为"路径数据"或"path data")使用一种称为路径命令的语言来描述。路径命令由一系列字母和数字组成,用于指示绘制路径的起点、线段、曲线、弧线等。
语法格式:
js
<path d="[路径命令]" />
// 其中,d 属性包含了路径命令,用于定义要绘制的路径的形状。
路径命令由以下一些基本命令组成:
M/m (Move To)
:将绘制位置移动到指定坐标。大写M表示绝对坐标,小写m表示相对坐标。
M x y
:绝对坐标。m dx dy
:相对坐标。
例如:M 10 10
表示将绘制位置移动到坐标 (10, 10)。
L/l (Line To)
:从当前位置绘制一条直线到指定坐标。大写L表示绝对坐标,小写l表示相对坐标。
L x y
:绝对坐标。l dx dy
:相对坐标。
例如:L 20 20
表示从当前位置绘制一条直线到坐标 (20, 20)。
数据准备
假如我们有这么一组数据,将它用迷你图体现出波动趋势
js
const data = [0,15,0,0,2,0,0,8,0,0,9,0]
计算坐标(核心)
首先我们想到的是我们所给的数据实际就是在坐标系中的Y轴的值,X轴就是对应的下标(也就是点的位置)
js
// 计算点的坐标
// const data = [0,15,0,0,2,0,0,8,0,0,9,0]
const points = data.map((value, index) => {
const x = (index / (data.length - 1)) * (width - 1);
const y = value;
return `${x.toFixed(2)},${y.toFixed(2)}`;
});
// 生成的坐标数据
/*
points = [
'0.00,0.00', '13.55,15.00', '27.09,0.00', '40.64,0.00',
'54.18,2.00', '67.73,0.00', '81.27,0.00', '94.82,8.00',
'108.36,0.00', '121.91,0.00', '135.45,9.00', '149.00,0.00'
]
*/
// 新建SVG的初始坐标格式
const pathData = `M ${points[0]} L ${points.join(' ')}`;
const points = data.map((value, index) => { ... });
:
- 这一行首先创建一个名为points的数组,它将存储转换后的坐标点。
- data是传递给函数的数据数组,它包含了要绘制的数据点。
- map函数用于遍历data数组中的每个数据点,并对每个数据点执行指定的操作。
const x = (index / (data.length - 1)) * (width - 1);
:
- 这一行计算x坐标值,它表示数据点在横轴上的位置。
- index 表示当前数据点在data数组中的索引,从0开始。
- (data.length - 1) 表示数据数组中数据点的总数减1,这是为了将数据点等间距分布在x轴上。
- (index / (data.length - 1)) 将索引值归一化到[0, 1]的范围,表示数据点在整个x轴上的相对位置。
- (width - 1) 表示SVG图表的宽度减去1,以确保数据点不会超出图表的范围。
- 最终计算结果x将是当前数据点在SVG图表中的x坐标。
const y = value;
:
- 这一行计算y坐标值,它表示数据点在纵轴上的位置。
- value表示当前数据点的值,它直接作为y坐标值使用。
- 这里并没有进行反向计算或归一化,因为value已经是适合y轴的实际数据值。
return ${x.toFixed(2)},${y.toFixed(2)};
:
- 这一行将计算得到的x和y坐标值格式化为字符串。
- toFixed(2)用于将浮点数保留两位小数,以确保坐标值精确到小数点后两位。
- 最终,坐标点的字符串形式将会被添加到points数组中。
渐变和遮罩
接下来,用到SVG的属性渐变( linearGradient )和遮罩( mask ),以便美化Sparkline图表。
- 渐变是用来填充图表的背景色
- 而遮罩则用来创建动画效果
js
const [color1, color2, color3, color4] = colors;
<linearGradient id="${gradientId}" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="${color1}"></stop>
<stop offset="10%" stop-color="${color2}"></stop>
<stop offset="25%" stop-color="${color3}"></stop>
<stop offset="50%" stop-color="${color4}"></stop>
</linearGradient>
<mask id="${maskId}" x="0" y="0" width="${width}" height="${height - 2}">
...
</mask>
绘制Sparkline图表的线条路径
html
<path d="${pathData}" fill="transparent" stroke="#8cc665" stroke-width="2"></path>
// d 就是坐标集合,绘制成线条路径
创建SVG图表
将上面所有这些元素组合在一起,生成一个完整的SVG图表
html
<svg width="160" height="100" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="gradient-1694522441435" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="#9be9a8"></stop>
<stop offset="10%" stop-color="#40c463"></stop>
<stop offset="25%" stop-color="#30a14e"></stop>
<stop offset="50%" stop-color="#216e39"></stop>
</linearGradient>
<mask id="sparkline-1694522441435" x="0" y="0" width="160" height="98">
<path d="${pathData}" stroke-width="2"></path>
</mask>
</defs>
<g>
<rect x="0" y="0" width="160" height="100" style="stroke: none; fill: url(#gradient-1694522441435); mask: url(#sparkline-1694522441435)"></rect>
</g>
</svg>
封装成一个完整方法并执行
html
<div id="svg-container"> </div>
js
function generateSVG(data, width, height, colors) {
const gradientId = `gradient-${Date.now()}`;
const maskId = `sparkline-${Date.now()}`;
const points = data.map((value, index) => {
const x = (index / (data.length - 1)) * (width - 1);
const y = value.toFixed(2); // 保留两位小数
return `${x},${y}`;
});
const [color1, color2, color3, color4] = colors;
const pathData = `M ${points[0]} L ${points.join(' ')}`;
return `
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="${gradientId}" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="${color1}"></stop>
<stop offset="10%" stop-color="${color2}"></stop>
<stop offset="25%" stop-color="${color3}"></stop>
<stop offset="50%" stop-color="${color4}"></stop>
</linearGradient>
<mask id="${maskId}" x="0" y="0" width="${width}" height="${height - 2}">
<path d="${pathData}" transform="translate(0, ${height - 2}) scale(1,-1)" fill="transparent" stroke="#8cc665" stroke-width="2"></path>
</mask>
</defs>
<g transform="translate(0, 0)">
<rect x="0" y="0" width="${width}" height="${height}" style="stroke: none; fill: url(#${gradientId}); mask: url(#${maskId})"></rect>
</g>
</svg>
`;
}
// 使用示例
const data = [0,0,0,0,2,0,0,8,0,0,9,0,0,0,18,0,2,0,0,8,0,30,9,0,0,0,8,0,2,0,0,8,0,29,9,0,0,55,0,0,2,0,0,8,0,0,9,0,0,80,0,0,2,0,0,8,0,0,9,0];
const width = data.length +100;
const height = Math.max(...data) + 20;
const colors = ["#9be9a8", "#40c463", "#30a14e", "#216e39"];
const svgV = generateSVG(data, width, height, colors);
// 将生成的SVG代码插入到HTML中显示
const svgContainer = document.getElementById('svg-container');
svgContainer.innerHTML = svgV;
目前差不多可以出来效果了:
添加动画
添加动画需要用到SVG的animate
元素
js
<path d="${pathData}" transform="translate(0, ${height - 2}) scale(1,-1)" fill="transparent" stroke="#8cc665" stroke-width="2">
<animate attributeName="stroke-dasharray" from="0 ${width}" to="${width} 0" dur="2s" begin="0s" fill="freeze" />
</path>
效果如下(没放动图):
完整代码
js
<script>
function generateSVG(data, width, height, colors) {
const gradientId = `gradient-${Date.now()}`;
const maskId = `sparkline-${Date.now()}`;
const points = data.map((value, index) => {
const x = (index / (data.length - 1)) * (width - 1);
const y = value.toFixed(2); // 保留两位小数
return `${x},${y}`;
});
const [color1, color2, color3, color4] = colors;
const pathData = `M ${points[0]} L ${points.join(' ')}`;
return `
<svg width="${width}" height="${height}" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="${gradientId}" x1="0" x2="0" y1="1" y2="0">
<stop offset="0%" stop-color="${color1}"></stop>
<stop offset="10%" stop-color="${color2}"></stop>
<stop offset="25%" stop-color="${color3}"></stop>
<stop offset="50%" stop-color="${color4}"></stop>
</linearGradient>
<mask id="${maskId}" x="0" y="0" width="${width}" height="${height - 2}">
<path d="${pathData}" transform="translate(0, ${height - 2}) scale(1,-1)" fill="transparent" stroke="#8cc665" stroke-width="2">
<animate attributeName="stroke-dasharray" from="0 ${width}" to="${width} 0" dur="2s" begin="0s" fill="freeze" />
</path>
</mask>
</defs>
<g transform="translate(0, 0)">
<rect x="0" y="0" width="${width}" height="${height}" style="stroke: none; fill: url(#${gradientId}); mask: url(#${maskId})"></rect>
</g>
</svg>
`;
}
// 使用示例
const data = [0,0,0,0,2,0,0,8,0,0,9,0];
const width = data.length + 800;
const height = Math.max(...data) + 20;
const colors = ["#9be9a8", "#40c463", "#30a14e", "#216e39"];
const svgV = generateSVG(data, width, height, colors);
// 将生成的SVG代码插入到HTML中显示
const svgContainer = document.getElementById('svg-container');
svgContainer.innerHTML = svgV;
</script>
拓展
-
对文字描边动画效果
-
icon图形描边效果