1. anichart 介紹
啰嗦了這么多,正式開(kāi)始本期技術(shù)案例分享,關(guān)于動(dòng)態(tài)排序圖制作之前分享過(guò)一篇文章,用的是 matplolib 的 animation 函數(shù),感興趣的可以看下
基本功能是實(shí)現(xiàn)了,但最終效果并不那么友好,這種方法制圖的基本原理就是先把數(shù)據(jù)借助 Matplotlib 可視化為每一幀圖片,再將每一幀拼接為視頻,缺點(diǎn)很明顯:步驟繁瑣、代碼量多,可視化效果差

今天分享另外一種方法,制作此類(lèi)狀態(tài)圖,用到的是一個(gè) Github 項(xiàng)目 anichart,眾所周知目前如果想做一些絲滑流暢的可視化交互效果,javascripts 是必不可缺少的,而這個(gè)項(xiàng)目主要是用 typescript 實(shí)現(xiàn),項(xiàng)目是由一個(gè) B 站 Up主【Jannchie見(jiàn)齊】維護(hù)
typescript 語(yǔ)言是基于 js 開(kāi)發(fā)的一門(mén)編程語(yǔ)言,為了彌補(bǔ)后者可維護(hù)性差、類(lèi)型混亂等缺點(diǎn);anichart 的主要功能是來(lái)制作動(dòng)態(tài)排序圖,可視化效果要好得多,下面是我根據(jù)針對(duì)項(xiàng)目中提供的測(cè)試數(shù)據(jù)最終繪制的排序圖,

2. 環(huán)境介紹
先交代下本次用到的項(xiàng)目環(huán)境,anichart 整體算是一個(gè)前端項(xiàng)目,因此本次用到的工具都是一些與前端相關(guān)的,本期教程只是介紹給大家這個(gè)項(xiàng)目怎么用,關(guān)于具體細(xì)節(jié)不需要去考慮,所以無(wú)需擔(dān)心自己是否具備前端知識(shí);
需要用到的軟件如下:
- node.js;
- ffmpeg;
測(cè)試系統(tǒng)為 Win10;
Node.js 在這里主要有兩個(gè)作用:1,管理下載項(xiàng)目所需要的一些依賴(lài)項(xiàng),2,啟動(dòng) js 腳本;
ffmpeg 主要是將序列圖片轉(zhuǎn)化為視頻
關(guān)于 node.js 與 ffmpeg 在 windows下載安裝方式比較簡(jiǎn)單,這里不多做介紹啦,這里給大家推薦兩個(gè)鏈接可以參考下:
Node.js 安裝:http://www.itdecent.cn/p/2958fc051bfb
ffmpeg 安裝:https://zhuanlan.zhihu.com/p/118362010
兩個(gè)軟件安裝好之后,不要忘記配置環(huán)境變量~
3. anichart 庫(kù)使用
3.1 anichart安裝
anichart 作者已經(jīng)把上傳至 npm 官網(wǎng)中,打開(kāi)命令行,借助 npm i anichart 命令即可安裝,不需要我們?cè)購(gòu)?Github 上克隆,

npm是什么東東?npm其實(shí)是Node.js的包管理工具(package manager),也就是說(shuō)你配置好 Node.js 命令之后,npm 就可以正常使用了
3.2 新建一個(gè) js文件
安裝完之后,接下來(lái)就能使用了,在anichart 安裝路徑下 新建一個(gè) 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()
運(yùn)行之前,需要改幾個(gè)參數(shù),第一個(gè)更改數(shù)據(jù)路徑
ani.recourse.loadCSV('H:/Data/data/test.csv', 'data')
數(shù)據(jù)形式需以下面形式存放,后面 data 代表數(shù)據(jù)源中更改的列名,比如這里是以日期作為變量進(jìn)行序列化
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
第二個(gè)需要更改一下所有 png 路徑,改成你自己的,可以隨意替換為你的 圖片路徑,影響不大
ani.recourse.loadImage('H:/Data/data/ANI.png', 'logo')
3.2 運(yùn)行 js 腳本
以上修改完之后,接下來(lái)就是啟動(dòng)腳本,在 js 同文件目錄下打開(kāi)一個(gè)命令行,輸入 node XXX.js ,回車(chē)即可,XXX.js 代表你的 js 文件,效果如下

之后會(huì)有一個(gè)out 文件夾生成,里面存放的就是 anichart 繪制好的圖片

3.3 借助ffmpeg圖片生成視頻
最后,進(jìn)入out 文件夾,借助ffmpeg 命令將圖片合成視頻,
ffmpeg -f image2 -framerate 12 -i output-%d.png foo.avi
-framerate 后面參數(shù) 12 代表生成視頻的fps,可 根據(jù)自己情況設(shè)定,這里我設(shè)置的是 24;

最終一個(gè)動(dòng)態(tài)排序視頻就生成了,隨后自己也可以加一些 bgm 給視頻加一些 feel

4. js中一些參數(shù)介紹
關(guān)于 anichart 的使用,原作者并沒(méi)有介紹太多,Github 主頁(yè)上只介紹了最簡(jiǎn)單的使用方法

因此為了大家生成一些比較不錯(cuò)的可視化圖,對(duì)上面 js 代碼中的部分參數(shù)做一些簡(jiǎn)單介紹,增加一些對(duì)這個(gè) anichart 庫(kù)的理解
4.1 fps、sec
stage.options.fps = 24
stage.options.sec = 30
fps 顧名思義就是一秒多少幀,sec 代表生成切片持續(xù)多長(zhǎng)時(shí)間(單位:秒);用上面參數(shù)來(lái)設(shè)定的話,會(huì)生成 24x30 = 720 張圖片;這兩個(gè)參數(shù)決定最終視頻的流暢度,后面用 ffmpeg 生成視頻時(shí),建議 framerate 參數(shù) fps保持 一致
4.2 ani.recourse.loadImage
ani.recourse.loadImage(
'https://avatars3.githubusercontent.com/u/29743310?s=460&u=8e0d49b98c35738afadc04e70c7f3918d6ad8cdb&v=4',
'jannchie'
)
loadImage用來(lái)給柱狀圖中每個(gè)數(shù)據(jù)條上加一個(gè) 圖片 logo,方法需要加入兩個(gè)參數(shù),前者表示圖片路徑或網(wǎng)頁(yè)鏈接,后者表示需要加logo 的數(shù)據(jù)條名字,例如這里選擇的數(shù)據(jù)條名字為 jannchie,可視化效果如下

4.3 BarChart、LineChart、PieChart
BarChart、LineChart、PieChart 分別表示柱狀圖、曲線圖、餅圖;anichart 除了這三類(lèi)圖形外還有 ItemChart、BaseChart、MapChart 等,

anichart 在所有圖形對(duì)象中,都需要加入兩個(gè)參數(shù),shape 和 aniTime,前者代表形狀大小、后者表示該圖形持續(xù)時(shí)間,單位 s
4.4 stage.addChild(xx)
anichart 通過(guò) stage.addChild() 函數(shù)使得創(chuàng)建的對(duì)象生效 ,stage 表示全局 畫(huà)布,通過(guò)以下命令生成
const ani = require('anichart')
const stage = new ani.Stage()
5. 源碼數(shù)據(jù)獲取
為了方便,本文中涉及到的源碼、測(cè)試數(shù)據(jù)已經(jīng)被我打包在一起了,獲取方式:在公眾號(hào) 小張Python 后臺(tái),回復(fù)關(guān)鍵字:210604 即可獲取
6. 小結(jié)
關(guān)于動(dòng)態(tài)排序圖制作除了這兩種方法之外,再向大家推薦一個(gè)網(wǎng)站名叫 flourish,網(wǎng)址:https://flourish.studio/examples/
flourish 最終生成效果也非常不錯(cuò),但缺點(diǎn)是需要微調(diào)大量參數(shù)

好了,關(guān)于排序圖的制作就介紹到這里了,如果內(nèi)容對(duì)你有幫助的話不妨點(diǎn)個(gè)贊來(lái)鼓勵(lì)一下我~
最后感謝大家的閱讀,我們下期見(jiàn)