引言
一幅生動(dòng)的可視化作品往往少不了動(dòng)畫的參與。無論是各色各樣的圖表還是敘事作品,組織周密菱鸥、效果出色的動(dòng)畫都能更好的幫助用戶理解潛藏在可視化背后的數(shù)據(jù)觀點(diǎn)澎蛛。與靜態(tài)的圖像相比抚垄,動(dòng)畫以生動(dòng)的形式將復(fù)雜的數(shù)據(jù)與概念轉(zhuǎn)化為更易理解的視覺形式,在展示數(shù)據(jù)的變化谋逻、關(guān)系和趨勢(shì)的同時(shí)也能有效的描述隨時(shí)間變化的信息呆馁。
那么 VisActor 在動(dòng)畫方面做了哪些事情呢?我們將通過兩篇文章為您揭秘VisActor中動(dòng)畫實(shí)現(xiàn)原理及應(yīng)用:
- 《魔力之幀(上):VisActor動(dòng)畫揭秘》
- 《魔力之幀(下):VisActor動(dòng)畫實(shí)戰(zhàn)》
本篇文章重點(diǎn)講解動(dòng)畫實(shí)現(xiàn)原理毁兆。
復(fù)雜的動(dòng)畫
目前學(xué)界以及業(yè)界針對(duì)于動(dòng)畫已經(jīng)做了許多的研究浙滤,創(chuàng)造了諸多不同的產(chǎn)品,那么為什么目前為止在圖表庫(kù)這樣的形態(tài)的產(chǎn)品中仍然沒有一個(gè)統(tǒng)一的動(dòng)畫實(shí)現(xiàn)呢气堕?
(demo:https://visactor.io/vchart/demo/storytelling/ranking-bar)
上圖描述了一個(gè)常見的競(jìng)速條形圖動(dòng)畫纺腊,從這個(gè)動(dòng)畫展示的內(nèi)容中,我們可以分辨出構(gòu)成可視化作品中動(dòng)畫的兩部分要素:
- 動(dòng)畫效果:動(dòng)畫效果描述了在某一特定的動(dòng)畫階段中圖元以怎樣的方式執(zhí)行渲染的變化茎芭。動(dòng)畫效果包括普通的視覺通道插值揖膜,例如競(jìng)速條形圖中柱子顏色、寬度梅桩、位置的變化壹粟;同時(shí)動(dòng)畫效果也包含一些特殊的變化,例如下圖中的圖元形變宿百。
(demo:https://visactor.io/vrender/demo/examples/graphic-rect/morphing-animate)
- 動(dòng)畫編排:在確定每一個(gè)階段的動(dòng)畫效果之后趁仙,需要考慮的便是如何通過動(dòng)畫編排將這些原子的動(dòng)畫效果進(jìn)行組合以得到完整洪添、流暢的動(dòng)畫內(nèi)容。例如下圖中從入場(chǎng)幸撕、更新到出場(chǎng)的一連串動(dòng)畫效果薇组。
(demo:https://visactor.io/vgrammar/demo/animate/basic-animate)
在實(shí)際的業(yè)務(wù)場(chǎng)景中,無論是動(dòng)畫效果還是動(dòng)畫編排都呈現(xiàn)出多種不同的形式坐儿。從普通的圖表出入場(chǎng)動(dòng)畫到敘事作品中不同敘事元素之間的任意動(dòng)畫效果律胀,動(dòng)畫效果與動(dòng)畫編排的相互交織創(chuàng)造出無數(shù)的復(fù)雜動(dòng)畫需求。
為此貌矿,我們需要為動(dòng)畫提供完整的解釋以及足夠強(qiáng)大的配置方式以支持自由的可視化創(chuàng)作炭菌。
動(dòng)畫設(shè)計(jì)
動(dòng)畫定義
在講述 VisActor 的動(dòng)畫設(shè)計(jì)之前,讓我們首先來簡(jiǎn)單談?wù)剤D形語法逛漫。為了系統(tǒng)性的描述各種不同的圖表以及可視化作品中所共通的數(shù)據(jù)邏輯黑低,利蘭·威爾金森提出了圖形語法的概念。從數(shù)據(jù)到映射再到具體的圖元視覺通道酌毡,圖形語法的設(shè)計(jì)將形態(tài)差異巨大的柱狀圖與煽宋眨基圖等納入到統(tǒng)一的框架中。VisActor 中的圖表庫(kù) VChart 以及語法引擎 VGrammar 同樣基于圖形語法的概念為可視化的創(chuàng)作提供支持枷踏。
關(guān)于圖形語法的更多描述可以參見利蘭·威爾金森影響深遠(yuǎn)的著作《The Grammar of Graphics》菩暗。
(圖形語法的流程)
雖然圖形語法完整的闡述了從數(shù)據(jù)變量到美學(xué)處理的整個(gè)流程,但是這一流程中并沒有為圖元的動(dòng)畫作出解釋旭蠕。圖形語法的核心在于構(gòu)建數(shù)據(jù)與圖形渲染之間的聯(lián)系停团,而動(dòng)畫與數(shù)據(jù)之間的關(guān)聯(lián)并不那么密切:
- 有些動(dòng)畫設(shè)計(jì)只出于美學(xué)考量,并不具有實(shí)際的數(shù)據(jù)含義:
(demo:https://visactor.io/vchart/demo/gauge-chart/clock)
- 有些動(dòng)畫設(shè)計(jì)則反映了數(shù)據(jù)變更的過程:
(demo:https://visactor.io/vchart/demo/storytelling/ranking-bar)
為了更好的解釋動(dòng)畫的含義掏熬,在 VisActor 中動(dòng)畫被視作為渲染階段的修飾:動(dòng)畫配置與圖形語法流程執(zhí)行得到的圖元視覺通道一起決定了渲染階段的結(jié)果佑稠。動(dòng)畫的表現(xiàn)是具體圖形元素在某一時(shí)間段內(nèi)視覺通道屬性的插值計(jì)算或者特殊計(jì)算邏輯,而動(dòng)畫配置描述了這一計(jì)算的觸發(fā)時(shí)機(jī)以及執(zhí)行時(shí)長(zhǎng)旗芬。
(VGrammar 動(dòng)畫流程)
動(dòng)畫觸發(fā)時(shí)機(jī)
動(dòng)畫的聲明方式可以劃分為兩種:
- 主動(dòng)形式的聲明:配置聲明圖元在接下來的一段時(shí)間里應(yīng)當(dāng)執(zhí)行何種動(dòng)畫效果(例如 Canis)舌胶;
- 被動(dòng)形式的聲明:配置聲明圖元在某種狀態(tài)下應(yīng)當(dāng)觸發(fā)何種動(dòng)畫效果。
在圖表庫(kù)的場(chǎng)景中疮丛,動(dòng)畫呈現(xiàn)往往是伴隨著交互動(dòng)作或者特定時(shí)機(jī)的辆琅,例如出場(chǎng) / 入場(chǎng) / hover / select 等均會(huì)觸發(fā)相應(yīng)的動(dòng)畫效果,因此被動(dòng)形式的聲明更便于動(dòng)畫的配置这刷。
VGrammar 所提供的動(dòng)畫配置實(shí)際上描述了渲染階段的執(zhí)行邏輯,開發(fā)者并不直接觸發(fā)動(dòng)畫的執(zhí)行娩井,而是通過聲明動(dòng)畫的執(zhí)行邏輯暇屋,在特定的動(dòng)畫時(shí)機(jī)觸發(fā)時(shí)由 VGrammar 邏輯觸發(fā)相應(yīng)的動(dòng)畫計(jì)算。
從圖元狀態(tài)的角度來看洞辣,動(dòng)畫的觸發(fā)時(shí)機(jī)可以分為:
- enter:新增圖形元素時(shí)的動(dòng)畫觸發(fā)咐刨;
- exit:移除圖形元素的動(dòng)畫觸發(fā)昙衅;
- update:圖形元素視覺通道更新的動(dòng)畫觸發(fā);
- state:圖形元素交互狀態(tài)變更的動(dòng)畫觸發(fā)定鸟,在最為常見的交互與動(dòng)畫配合的場(chǎng)景中而涉,動(dòng)畫表現(xiàn)為伴隨著交互狀態(tài)變更的插值,例如 hover 動(dòng)畫联予。針對(duì)這一動(dòng)畫狀態(tài)啼县,我們單獨(dú)在底層渲染庫(kù)做額外處理,避免昂貴的數(shù)據(jù)流計(jì)算以提升性能沸久;
- 任意時(shí)機(jī)觸發(fā)的動(dòng)畫:動(dòng)畫配置將會(huì)立即應(yīng)用于圖元季眷,在可視化敘事的場(chǎng)景中,這一形式的動(dòng)畫往往更為常見卷胯;
(動(dòng)畫觸發(fā)時(shí)機(jī))
基于動(dòng)畫與數(shù)據(jù)流的分離子刮,我們能夠自由劃定動(dòng)畫的觸發(fā)時(shí)機(jī),并且配置相應(yīng)的動(dòng)畫效果窑睁。例如在 VGrammar 的圖元上可以如此配置矩形圖元上各個(gè)動(dòng)畫階段的效果:(參見 VGrammar 教程 https://visactor.io/vgrammar/guide/guides/animation)
{
type: 'rect',
// other mark specs
animation: {
enter: {
type: 'growHeightIn',
duration: 2000,
options: (datum, element, params) => {
return { orient: 'negative' };
}
},
update: {
type: 'update',
duration: 2000
},
exit: {
type: 'fadeOut',
duration: 2000
},
state: {
duration: 500
}
}
}
同時(shí)挺峡,為了便于敘事可視化場(chǎng)景下自由的觸發(fā)動(dòng)畫效果,VGrammar 在圖元以及頂層對(duì)象 View 上同樣提供了 animate 對(duì)象以支持自動(dòng)的動(dòng)畫接口調(diào)用:
interface IAnimate {
stop: () => this;
pause: () => this;
resume: () => this;
run: (config: IAnimationConfig | IAnimationConfig[]) => IAnimateArranger;
runAnimationByState: (animationState: string) => IAnimateArranger;
stopAnimationByState: (animationState: string) => this;
pauseAnimationByState: (animationState: string) => this;
resumeAnimationByState: (animationState: string) => this;
}
動(dòng)畫基本單元
VGrammar 通過動(dòng)畫配置描述了對(duì)應(yīng)的圖元?jiǎng)赢媹?zhí)行邏輯担钮,其中包含具體的動(dòng)畫效果以及不同動(dòng)畫內(nèi)容之間的編排橱赠。動(dòng)畫配置構(gòu)成的示意圖為:
(VGrammar 動(dòng)畫單元)
其中整體的動(dòng)畫配置由相互獨(dú)立的基本的動(dòng)畫單元 Aunit 構(gòu)成,一個(gè) Aunit 將被應(yīng)用到某一個(gè)具體的動(dòng)畫觸發(fā)時(shí)機(jī)上裳朋,其描述了該時(shí)機(jī)到達(dá)時(shí)如何執(zhí)行動(dòng)畫插值計(jì)算病线。動(dòng)畫單元的定義為:
其中的構(gòu)成元素為:
- mark:動(dòng)畫單元關(guān)聯(lián)的具體圖元,動(dòng)畫聲明將綁定到該圖元的各個(gè)動(dòng)畫觸發(fā)時(shí)機(jī)上鲤嫡;
- timeline:動(dòng)畫的時(shí)間線送挑,timeline 描述了一段時(shí)間內(nèi)圖元的動(dòng)畫表現(xiàn)。timeline 上包含了一組串行執(zhí)行的動(dòng)畫分片暖眼,不同 timeline 之間動(dòng)畫可以并行惕耕。一個(gè) timeline 可以被設(shè)置 loop 以執(zhí)行循環(huán)的動(dòng)畫插值計(jì)算;
- partitioner:動(dòng)畫分區(qū)器诫肠,對(duì)相應(yīng)圖元中的內(nèi)容進(jìn)行篩選司澎,并將動(dòng)畫配置應(yīng)用到這些篩選的圖形元素上;
- sort:圖元排序栋豫,相應(yīng)圖元中的圖形元素執(zhí)行順序依賴于 sort 所指定的排序挤安。
動(dòng)畫時(shí)間線 Timeline 的定義為:
Timline 中的構(gòu)成元素包含:
- timeslice:動(dòng)畫的分片,描述了具體的某一段插值動(dòng)畫配置丧鸯,包含相關(guān)的動(dòng)畫效果蛤铜、動(dòng)畫執(zhí)行時(shí)間等具體動(dòng)畫配置。在一個(gè) timeline 上所有的 timeslice 頭銜尾的串聯(lián)在一起;
- startTime:動(dòng)畫的開始執(zhí)行時(shí)間围肥,描述了當(dāng)前 timeline 觸發(fā)執(zhí)行之后開始執(zhí)行動(dòng)畫的時(shí)間剿干;
- duration:動(dòng)畫時(shí)間線的執(zhí)行時(shí)長(zhǎng),描述了當(dāng)前 timeline 的動(dòng)畫時(shí)長(zhǎng)穆刻;
- loop:一個(gè) timeline 可以被設(shè)置為循環(huán)置尔,其中包含的所有 timeslice 所描述的動(dòng)畫過程將會(huì)被重復(fù)的執(zhí)行。
動(dòng)畫分片 Timeslice 的定義為:
Timeslice 中的構(gòu)成元素包含:
- effect:動(dòng)畫的具體執(zhí)行效果氢伟,描述了具體的圖元視覺通道屬性插值計(jì)算邏輯榜轿。effect 可以是封裝好的特定動(dòng)畫效果,或者由開發(fā)者配置起始狀態(tài)以及結(jié)尾狀態(tài)的動(dòng)畫配置腐芍,描述了動(dòng)畫的屬性插值的計(jì)算邏輯差导;
- duration:動(dòng)畫分片的執(zhí)行時(shí)長(zhǎng);
- delay:動(dòng)畫分片的執(zhí)行前的等待時(shí)間猪勇;
- OneByOne:描述了相應(yīng)圖元內(nèi)具體圖形元素依次執(zhí)行的邏輯设褐;
動(dòng)畫效果 Effect 的定義為:
Effect 中的構(gòu)成元素包含:
- channel:變更的視覺通道屬性,描述了插值計(jì)算開始以及結(jié)尾狀態(tài)時(shí)的視覺通道屬性泣刹;
- easing:差值計(jì)算的緩動(dòng)策略助析;
一個(gè)描述柱子先變亮后變暗的完整動(dòng)畫配置如下所示:
loop: {
loop: true,
oneByOne: 300,
timeSlices: [
{
effects: {
channel: {
fillOpacity: { to: 0.3 }
},
easing: 'linear'
},
duration: 500
},
{
effects: {
channel: {
fillOpacity: { to: 0.3 }
},
easing: 'linear'
},
duration: 1000
},
{
effects: {
channel: {
fillOpacity: { to: 1 }
},
easing: 'linear'
},
duration: 500
}
]
}
(具體動(dòng)畫實(shí)例參見:https://visactor.io/vgrammar/demo/animate/loop-animate)
同時(shí),VGrammar 也提供了動(dòng)畫配置以及一系列的內(nèi)置動(dòng)畫效果椅您,例如漸入漸出動(dòng)畫外冀、寬高生長(zhǎng)動(dòng)畫等,例如:
animation: {
enter: {
type: 'fadeIn'
},
exit: {
type: 'fadeOut'
}
}
具體的內(nèi)置動(dòng)畫以及動(dòng)畫配置參見 VGrammar 教程:https://visactor.io/vgrammar/guide/guides/animation
全局形變動(dòng)畫
在常見的插值動(dòng)畫之外掀泳,我們還提供了圖元與圖元之間的全局形變動(dòng)畫以支持不同圖形元素雪隧、不同圖表之間的平滑過渡效果。
例如圖表從柱狀圖切換到餅圖的時(shí)候员舵,我們通常希望能夠?qū)⒅鶢顖D中的柱子平滑的過渡到餅圖中的餅環(huán):
(demo:https://visactor.io/vchart/demo/example/morphing/bar-to-pie)
VGrammar 中默認(rèn)開啟了全局形變配置脑沿。當(dāng)開發(fā)者調(diào)用 updateSpec 更新場(chǎng)景的 spec 聲明或者通過 api 形式卸載/掛載語法元素后,VGrammar 會(huì)對(duì)更新前后的圖元進(jìn)行 diff 并依據(jù)相應(yīng)的配置決定執(zhí)行圖元復(fù)用或者執(zhí)行全局形變马僻。
開發(fā)者可以為需要執(zhí)行全局形變的圖元配置相應(yīng)的效果:
- mark.morph: 圖元是否應(yīng)用全局形變
- mark.morphKey: 圖元在進(jìn)行前后的匹配時(shí)所依賴的 key 值
- mark.morphElementKey: 圖元中具體的圖形元素進(jìn)行前后的匹配時(shí)所依賴的 key 值
一個(gè)簡(jiǎn)單的全局形變示例為:
const vGrammarView = new VGrammarView({
width: roseSpec.width,
height: roseSpec.height,
container: CHART_CONTAINER_DOM_ID,
hover: true
});
vGrammarView.parseSpec(roseSpec);
vGrammarView.runAsync();
setTimeout(() => {
vGrammarView.updateSpec(radarSpec);
vGrammarView.runAsync({ morph: true });
}, 500);
setTimeout(() => {
vGrammarView.updateSpec(funnelSpec);
vGrammarView.runAsync({ morph: true });
}, 2000);
具體示例參見:https://visactor.io/vgrammar/demo/animate/morph-animate
貝塞爾曲線
實(shí)現(xiàn)全局形變動(dòng)畫的核心就在于將所有的圖元的邊轉(zhuǎn)化為貝塞爾曲線庄拇,通過對(duì)貝塞爾曲線的控制點(diǎn)進(jìn)行插值,可以實(shí)現(xiàn)不同圖形之間的形變動(dòng)畫韭邓。
貝塞爾曲線可以用于繪制直線措近、圓弧、橢圓等基礎(chǔ)圖形女淑,我們平時(shí)在計(jì)算機(jī)上看到的大量曲線圖形幾乎都是通過貝塞爾曲線進(jìn)行描述的瞭郑。一個(gè)三階貝塞爾曲線的的基本表達(dá)式如下:
其中 P0,P1鸭你,P2凰浮,P3 代表了貝塞爾曲線的四個(gè)控制點(diǎn)我抠,B(t) 通過 t 在 [0, 1] 區(qū)間內(nèi)的變化描述曲線上所有的點(diǎn)。如下所示:
(三階貝塞爾曲線)
貝塞爾曲線具有凸包性袜茧,曲線內(nèi)的所有點(diǎn)都被包含在控制點(diǎn)所構(gòu)成的凸包中。同時(shí)貝塞爾曲線也可以被細(xì)化瓣窄,在貝塞爾曲線的中間任意點(diǎn)笛厦,可以將貝塞爾曲線劃分成兩條新的曲線。例如劃分如下曲線:
拆分之后兩條貝塞爾曲線的的 T 參數(shù)被重新映射到 [0, 1] 區(qū)間俺夕,同時(shí)控制點(diǎn)也進(jìn)行相應(yīng)的拆分:
劃分原始貝塞爾曲線之后裳凸,P1、P'1劝贸、P''1姨谷、P''' 與 P'''、P''2映九、P'3梦湘、P4 分布構(gòu)成了兩條新的貝塞爾曲線的控制點(diǎn)。
一對(duì)一的形變動(dòng)畫
動(dòng)畫流程
下面以圓形圖元變成方形圖元來舉例:
其基本的動(dòng)畫流程為:
- 對(duì)齊控制點(diǎn)個(gè)數(shù)(在以上視頻中保證圓形和方形的控制點(diǎn)都是4個(gè))件甥;
- 通過目標(biāo)函數(shù)將動(dòng)畫前后的控制點(diǎn)兩兩配對(duì)(在以上視頻中捌议,將圓形和方形的控制點(diǎn)兩兩配對(duì),保證配對(duì)的控制點(diǎn)之間的距離和最幸小)瓣颅;
-
在配對(duì)點(diǎn)之間進(jìn)行插值完成動(dòng)畫。
圓形 -> 矩形
多邊形 -> 扇形
圓形 -> 面積
面積 -> 折線
圖形拆分
如果要進(jìn)行一對(duì)多動(dòng)畫和多對(duì)一動(dòng)畫譬正,圖形拆分是必需的步驟宫补。其主要目的是保證動(dòng)畫前后的圖元數(shù)量一致。
下面分別是矩形曾我、圓形粉怕、拱形、多邊形的可能拆分效果:
多對(duì)一的形變動(dòng)畫
動(dòng)畫流程
下面以 3 個(gè)圓形圖元變化為 1 個(gè)矩形圖元舉例您单,其基本的動(dòng)畫流程為:
- 將目標(biāo)圖元拆分成3個(gè)子圖元并排序斋荞,然后通過VRender影子節(jié)點(diǎn)的機(jī)制進(jìn)行渲染
- 將動(dòng)畫開始的圖元排序,和目標(biāo)圖元的3個(gè)子圖元進(jìn)行匹配
- 將匹配后的圖元虐秦,進(jìn)行一對(duì)一的形變動(dòng)畫
- 動(dòng)畫結(jié)束后平酿,回收目標(biāo)圖元的子圖元,展示目標(biāo)圖元
流程圖如下:
對(duì)于最后一步的合并方法悦陋,則有以下兩種策略蜈彼。一個(gè)是拆分策略,將多個(gè)形狀作為拼圖合并為一個(gè)形狀:
一個(gè)是復(fù)制策略俺驶,將多個(gè)形狀變化為和目標(biāo)一致的形狀再進(jìn)行疊加:
一對(duì)多的形變動(dòng)畫
一對(duì)多的動(dòng)畫流程是多對(duì)一動(dòng)畫逆運(yùn)算幸逆,步驟完全相反就可以棍辕。以 1 個(gè)矩形圖元變化為 3 個(gè)圓形圖元舉例,其基本的動(dòng)畫流程為:
示例如下还绘,這個(gè)例子使用了拆分策略完成一對(duì)多的轉(zhuǎn)換過程楚昭。
Demo 演示
VGrammar 動(dòng)畫編排示例:對(duì)標(biāo)題、坐標(biāo)軸以及柱子圖元進(jìn)行簡(jiǎn)單的動(dòng)畫編排以及自定義動(dòng)畫聲明
(示例地址:https://visactor.io/vgrammar/demo/animate/arrange-animate)
VGrammar 敘事示例:通過 Glyph 圖元與動(dòng)畫的配合實(shí)現(xiàn)滾動(dòng)時(shí)間軸的敘事效果
(示例地址:https://visactor.io/vgrammar/demo/animate/timeline)
VChart 形變動(dòng)畫示例:柱狀圖與餅環(huán)圖之間切換拍顷,通過形變動(dòng)畫達(dá)到平滑過渡效果
(示例地址:https://visactor.io/vchart/demo/example/morphing/bar-to-pie?keyword=animation)
VChart 敘事示例:在雙向條形圖中通過入場(chǎng)與更新動(dòng)畫的組織展示出隨著時(shí)間變更的數(shù)據(jù)動(dòng)態(tài)變化過程
(示例地址:https://visactor.io/vchart/demo/storytelling/dynamic-comparative-bar-chart?keyword=animation)
總結(jié)
本文介紹了 VisActor 可視化解決方案中的動(dòng)畫實(shí)現(xiàn)原理抚太,并為您展示了部分Demo。 下一篇文章我們將詳細(xì)講解 VChart 和 VGrammar中的動(dòng)畫語法及動(dòng)畫配置昔案,并為您展示如何通過 VChart 和 VGrammar 定制炫酷的動(dòng)畫效果及敘事作品尿贫。
歡迎大家與我們進(jìn)行交流:
1)VisActor 微信訂閱號(hào)留言(可以通過訂閱號(hào)菜單加入微信群):
2)VisActor Discord 社群:https://discord.gg/3wPyxVyH6m
3)VisActor 官網(wǎng)及github: