如何绘制好看的动态排序图,教你用 js包 anichart 来实现!

1. anichart 介绍

啰嗦了这么多,正式开始本期技术案例分享,关于动态排序图制作之前分享过一篇文章,用的是 matplolibanimation 函数,感兴趣的可以看下

基本功能是实现了,但最终效果并不那么友好,这种方法制图的基本原理就是先把数据借助 Matplotlib 可视化为每一帧图片,再将每一帧拼接为视频,缺点很明显:步骤繁琐、代码量多,可视化效果差

image-20210604131428463

今天分享另外一种方法,制作此类状态图,用到的是一个 Github 项目 anichart,众所周知目前如果想做一些丝滑流畅的可视化交互效果,javascripts 是必不可缺少的,而这个项目主要是用 typescript 实现,项目是由一个 B 站 Up主【Jannchie见齐】维护

typescript 语言是基于 js 开发的一门编程语言,为了弥补后者可维护性差、类型混乱等缺点;anichart 的主要功能是来制作动态排序图,可视化效果要好得多,下面是我根据针对项目中提供的测试数据最终绘制的排序图,

img-ZXk1tRET

2. 环境介绍

先交代下本次用到的项目环境,anichart 整体算是一个前端项目,因此本次用到的工具都是一些与前端相关的,本期教程只是介绍给大家这个项目怎么用,关于具体细节不需要去考虑,所以无需担心自己是否具备前端知识;

需要用到的软件如下:

  • node.js;
  • ffmpeg;

测试系统为 Win10;

Node.js 在这里主要有两个作用:1,管理下载项目所需要的一些依赖项,2,启动 js 脚本;

ffmpeg 主要是将序列图片转化为视频

关于 node.js 与 ffmpeg 在 windows下载安装方式比较简单,这里不多做介绍啦,这里给大家推荐两个链接可以参考下:

Node.js 安装:https://www.jianshu.com/p/2958fc051bfb

ffmpeg 安装:https://zhuanlan.zhihu.com/p/118362010

两个软件安装好之后,不要忘记配置环境变量~

3. anichart 库使用

3.1 anichart安装

anichart 作者已经把上传至 npm 官网中,打开命令行,借助 npm i anichart 命令即可安装,不需要我们再从 Github 上克隆,

image-20210604171440229

npm是什么东东?npm其实是Node.js的包管理工具(package manager),也就是说你配置好 Node.js 命令之后,npm 就可以正常使用了

3.2 新建一个 js文件

安装完之后,接下来就能使用了,在anichart 安装路径下 新建一个 js 文件,文件名可任意命名,然后把下面代码添加到刚刚建立的 js 文件

const ani = require('anichart')
const stage = new ani.Stage()
stage.options.fps = 24
stage.options.sec = 30
stage.output = false

const bgAni = new ani.RectAni()
bgAni.component.shape = {
  width: stage.canvas.width,
  height: stage.canvas.height,
}
bgAni.component.fillStyle = '#1e1e1e'

const textLinesAni = new ani.TextLinesAni()

textLinesAni.component.fillStyle = '#eee'
textLinesAni.component.textAlign = 'center'
textLinesAni.component.textBaseline = 'middle'
textLinesAni.component.position = {
  x: stage.canvas.width / 2,
  y: stage.canvas.height / 2,
}

const textAnichart = new ani.TextAni()
textAnichart.component.fontSize = 48
textAnichart.component.font = 'Sarasa Mono Slab SC'
textAnichart.component.text = 'Anichart'
textAnichart.component.fontWeight = 'bolder'
textAnichart.type = 'blur'


textLinesAni.children.push(textAnichart)


ani.recourse.loadImage('H:/Data/data/ANI.png', 'logo')
ani.recourse.loadImage(
  'https://avatars3.githubusercontent.com/u/29743310?s=460&u=8e0d49b98c35738afadc04e70c7f3918d6ad8cdb&v=4',
  'jannchie'
)

ani.recourse.loadCSV('H:/Data/data/test.csv', 'data')


const rectAni = ani.createAni(
  [
    new ani.Rect({
      position: { x: 100, y: 0 },
      shape: { width: 100, height: 0 },
      fillStyle: '#d23',
    }),
    new ani.Rect({
      shape: { width: 100, height: 200 },
      fillStyle: '#2a3',
      alpha: 1,
    }),
    new ani.Rect({
      shape: { width: 100, height: 0 },
      fillStyle: '#569',
      alpha: 0,
    }),
  ],
  [0, 1, 2],
  ani.ease.easeElastic
)

const logoCenter = new ani.Image({
  path: 'H:/Data/data/ANI.png',
  position: {
    x: stage.canvas.width / 2,
    y: stage.canvas.height / 2,
  },
  alpha: 0.25,
  center: { x: 128, y: 128 },
  shape: { width: 256, height: 256 },
})
const logoAni = ani.createAni(
  [
    new ani.Image({
      path: 'H:/Data/data/ANI.png',
      position: {
        x: 0,
        y: stage.canvas.height - 108,
      },
      shape: { width: 128, height: 128 },
    }),
    new ani.Image({
      path: 'H:/Data/data/ANI.png',
      position: {
        x: stage.canvas.width - 128,
        y: stage.canvas.height - 108,
      },
      shape: { width: 128, height: 128 },
      alpha: 1.0,
    }),
    new ani.Image({
      path: 'H:/Data/data/ANI.png',
      position: {
        x: stage.canvas.width - 128,
        y: stage.canvas.height - 108,
      },
      shape: { width: 128, height: 128 },
      alpha: 0,
    }),
  ],
  [0, 1, 2],
  ani.ease.easeBounce
)

const barChart = new ani.BarChart({
  shape: { width: stage.canvas.width, height: stage.canvas.height },
  labelFormat(id) {
    return id
    // return meta.get(id).name;
  },
  aniTime: [0, 30],
})

const lineChart = new ani.LineChart({
  aniTime: [0, 30],
  shape: { width: stage.canvas.width, height: stage.canvas.height / 2 },
  position: { x: 0, y: stage.canvas.height / 2 },
})


stage.addChild(bgAni)
// stage.addChild(a)
stage.addChild(logoCenter)
stage.addChild(textLinesAni)
stage.addChild(rectAni)
stage.addChild(logoAni)
stage.addChild(barChart)
stage.addChild(lineChart)


const pie = new ani.PieChart({
  aniTime: [0, 30],
  radius: [80, 120],
  position: { x: stage.canvas.width / 2, y: stage.canvas.height / 2 },
})

stage.addChild(pie)
stage.play()

运行之前,需要改几个参数,第一个更改数据路径

ani.recourse.loadCSV('H:/Data/data/test.csv', 'data')

数据形式需以下面形式存放,后面 data 代表数据源中更改的列名,比如这里是以日期作为变量进行序列化

name,date,value,channel,other
Jannchie,2020-01-01,1,科技,other
Jannchie,2020-01-03,6,科技,other
Jannchie,2020-01-05,3,科技,other
Jannchie,2020-01-07,-,科技,other
Jannchie,2020-01-09,7,科技,other
Jannchie,2020-01-12,12,科技,other
Cake47,2020-01-03,10,生活,other
Cake47,2020-01-02,5,生活,other
Cake47,2020-01-06,2,生活,other
Cake47,2020-01-09,3,生活,other
Cake47,2020-01-11,4,生活,other

第二个需要更改一下所有 png 路径,改成你自己的,可以随意替换为你的 图片路径,影响不大

ani.recourse.loadImage('H:/Data/data/ANI.png', 'logo')

3.2 运行 js 脚本

以上修改完之后,接下来就是启动脚本,在 js 同文件目录下打开一个命令行,输入 node XXX.js ,回车即可,XXX.js 代表你的 js 文件,效果如下

image-20210604173143277

之后会有一个out 文件夹生成,里面存放的就是 anichart 绘制好的图片

Snipaste_2021-06-04_15-19-26

3.3 借助ffmpeg图片生成视频

最后,进入out 文件夹,借助ffmpeg 命令将图片合成视频,

ffmpeg -f image2 -framerate 12 -i output-%d.png foo.avi

-framerate 后面参数 12 代表生成视频的fps,可 根据自己情况设定,这里我设置的是 24;

Snipaste_2021-06-04_15-22-51

最终一个动态排序视频就生成了,随后自己也可以加一些 bgm 给视频加一些 feel

image-20210604173944116

4. js中一些参数介绍

关于 anichart 的使用,原作者并没有介绍太多,Github 主页上只介绍了最简单的使用方法

image-20210604182018595

因此为了大家生成一些比较不错的可视化图,对上面 js 代码中的部分参数做一些简单介绍,增加一些对这个 anichart 库的理解

4.1 fps、sec

stage.options.fps = 24
stage.options.sec = 30

fps 顾名思义就是一秒多少帧,sec 代表生成切片持续多长时间(单位:秒);用上面参数来设定的话,会生成 24x30 = 720 张图片;这两个参数决定最终视频的流畅度,后面用 ffmpeg 生成视频时,建议 framerate 参数 fps保持 一致

4.2 ani.recourse.loadImage

ani.recourse.loadImage(
  'https://avatars3.githubusercontent.com/u/29743310?s=460&u=8e0d49b98c35738afadc04e70c7f3918d6ad8cdb&v=4',
  'jannchie'
)

loadImage用来给柱状图中每个数据条上加一个 图片 logo,方法需要加入两个参数,前者表示图片路径或网页链接,后者表示需要加logo 的数据条名字,例如这里选择的数据条名字为 jannchie,可视化效果如下

image-20210604184247469

4.3 BarChart、LineChart、PieChart

BarChart、LineChart、PieChart 分别表示柱状图、曲线图、饼图;anichart 除了这三类图形外还有 ItemChart、BaseChart、MapChart 等,

image-20210604185524192

anichart 在所有图形对象中,都需要加入两个参数,shape 和 aniTime,前者代表形状大小、后者表示该图形持续时间,单位 s

4.4 stage.addChild(xx)

anichart 通过 stage.addChild() 函数使得创建的对象生效 ,stage 表示全局 画布,通过以下命令生成

const ani = require('anichart')
const stage = new ani.Stage()

5. 源码数据获取

为了方便,本文中涉及到的源码、测试数据已经被我打包在一起了,获取方式:在公众号 小张Python 后台,回复关键字:210604 即可获取

6. 小结

关于动态排序图制作除了这两种方法之外,再向大家推荐一个网站名叫 flourish,网址:https://flourish.studio/examples/

flourish 最终生成效果也非常不错,但缺点是需要微调大量参数

image-20210604190833926

好了,关于排序图的制作就介绍到这里了,如果内容对你有帮助的话不妨点个赞来鼓励一下我~

最后感谢大家的阅读,我们下期见

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,230评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,261评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,089评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,542评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,542评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,544评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,922评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,578评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,816评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,576评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,658评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,359评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,937评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,920评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,859评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,381评论 2 342

推荐阅读更多精彩内容