SVG实现github代码仓库的活跃动态sparkline(迷你图)

前言

可视化数据对于有效理解和传达信息至关重要。一种常见的可视化数据的方法是使用Sparkline图表 - 这是一种小型、简单、紧凑的图表,可以快速概览数据集中的趋势和模式。

经常逛github的同学会很经常看到每个代码仓库会有个动态sparkline(迷你图),很直观的展示了这个仓库作者在该仓库的活跃度数据走势。

介绍SVG和Sparkline图表

SVG,可缩放矢量图形(Scalable Vector Graphics),是一种广泛使用的基于XML的格式,用于在Web上创建矢量图形。SVG图形不受分辨率影响,并且可以通过CSS轻松自定义,或者通过JavaScript进行操作。而Sparkline图表则是一种迷你图表,通常出现在文本或小空间内,以提供数据趋势的视觉表示,例如股票价格、温度波动或网站流量。

github迷你图表原理及实现方式

原理:将原始数据转换为可在SVG图表中绘制的坐标点集合,坐标点将确定线条的形状和位置。

可以选择不同的实现方式:

  1. 第三方库:echartsd3.js
  2. 原生SVG

原生SVG实现github迷你图表

SVG(Scalable Vector Graphics)的<path>元素是用于绘制各种形状和路径的重要工具。元素的数据(通常称为"路径数据"或"path data")使用一种称为路径命令的语言来描述。路径命令由一系列字母和数字组成,用于指示绘制路径的起点、线段、曲线、弧线等。

语法格式:

js 复制代码
<path d="[路径命令]" />
// 其中,d 属性包含了路径命令,用于定义要绘制的路径的形状。

路径命令由以下一些基本命令组成:

  1. M/m (Move To):将绘制位置移动到指定坐标。大写M表示绝对坐标,小写m表示相对坐标。
  • M x y:绝对坐标。
  • m dx dy:相对坐标。

例如:M 10 10 表示将绘制位置移动到坐标 (10, 10)。

  1. 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(' ')}`;
  1. const points = data.map((value, index) => { ... });
  • 这一行首先创建一个名为points的数组,它将存储转换后的坐标点。
  • data是传递给函数的数据数组,它包含了要绘制的数据点。
  • map函数用于遍历data数组中的每个数据点,并对每个数据点执行指定的操作。
  1. 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坐标。
  1. const y = value;
  • 这一行计算y坐标值,它表示数据点在纵轴上的位置。
  • value表示当前数据点的值,它直接作为y坐标值使用。
  • 这里并没有进行反向计算或归一化,因为value已经是适合y轴的实际数据值。
  1. 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图形描边效果

附件:🚦github迷你图表示例html在线下载

相关推荐
秦jh_7 分钟前
【Linux】多线程(概念,控制)
linux·运维·前端
蜗牛快跑21319 分钟前
面向对象编程 vs 函数式编程
前端·函数式编程·面向对象编程
Dread_lxy20 分钟前
vue 依赖注入(Provide、Inject )和混入(mixins)
前端·javascript·vue.js
涔溪1 小时前
Ecmascript(ES)标准
前端·elasticsearch·ecmascript
榴莲千丞1 小时前
第8章利用CSS制作导航菜单
前端·css
奔跑草-1 小时前
【前端】深入浅出 - TypeScript 的详细讲解
前端·javascript·react.js·typescript
羡与1 小时前
echarts-gl 3D柱状图配置
前端·javascript·echarts
guokanglun2 小时前
CSS样式实现3D效果
前端·css·3d
咔咔库奇2 小时前
ES6进阶知识一
前端·ecmascript·es6
渗透测试老鸟-九青2 小时前
通过投毒Bingbot索引挖掘必应中的存储型XSS
服务器·前端·javascript·安全·web安全·缓存·xss