引言
一幅活泼的可视化作品往往少不了动画的参加。无论是各色各样的图表还是叙事作品,组织周密、成果杰出的动画都能更好的帮忙用户了解潜藏在可视化背地的数据观点。与动态的图像相比,动画以活泼的模式将简单的数据与概念转化为更易了解的视觉模式,在展现数据的变动、关系和趋势的同时也能无效的形容随工夫变动的信息。
那么 VisActor 在动画方面做了哪些事件呢?咱们将通过两篇文章为您揭秘 VisActor 中动画实现原理及利用:
- 《魔力之帧(上):VisActor 动画揭秘》
- 《魔力之帧(下):VisActor 动画实战》
本篇文章重点解说动画实现原理。
简单的动画
目前学界以及业界针对于动画曾经做了许多的钻研,发明了诸多不同的产品,那么为什么目前为止在图表库这样的状态的产品中依然没有一个对立的动画实现呢?
(demo:https://visactor.io/vchart/demo/storytelling/ranking-bar)
上图形容了一个常见的竞速条形图动画,从这个动画展现的内容中,咱们能够分辨出形成可视化作品中动画的两局部因素:
- 动画成果:动画成果形容了在某一特定的动画阶段中图元以怎么的形式执行渲染的变动。动画成果包含一般的视觉通道插值,例如竞速条形图中柱子色彩、宽度、地位的变动;同时动画成果也蕴含一些非凡的变动,例如下图中的图元形变。
(demo:https://visactor.io/vrender/demo/examples/graphic-rect/morphi…)
- 动画编排:在确定每一个阶段的动画成果之后,须要思考的便是如何通过动画编排将这些原子的动画成果进行组合以失去残缺、晦涩的动画内容。例如下图中从入场、更新到出场的一连串动画成果。
(demo:https://visactor.io/vgrammar/demo/animate/basic-animate)
在理论的业务场景中,无论是动画成果还是动画编排都呈现出多种不同的模式。从一般的图表出入场动画到叙事作品中不同叙事元素之间的任意动画成果,动画成果与动画编排的相互交织发明出有数的简单动画需要。
为此,咱们须要为动画提供残缺的解释以及足够弱小的配置形式以反对自在的可视化创作。
动画设计
动画定义
在讲述 VisActor 的动画设计之前,让咱们首先来简略谈谈图形语法。为了系统性的形容各种不同的图表以及可视化作品中所共通的数据逻辑,利兰·威尔金森提出了图形语法的概念。从数据到映射再到具体的图元视觉通道,图形语法的设计将状态差别微小的柱状图与桑基图等纳入到对立的框架中。VisActor 中的图表库 VChart 以及语法引擎 VGrammar 同样基于图形语法的概念为可视化的创作提供反对。
对于图形语法的更多形容能够参见利兰·威尔金森影响深远的著述《The Grammar of Graphics》。
(图形语法的流程)
尽管图形语法残缺的论述了从数据变量到美学解决的整个流程,然而这一流程中并没有为图元的动画作出解释。图形语法的外围在于构建数据与图形渲染之间的分割,而动画与数据之间的关联并不那么亲密:
- 有些动画设计只出于美学考量,并不具备理论的数据含意:
(demo:https://visactor.io/vchart/demo/gauge-chart/clock)
- 有些动画设计则反映了数据变更的过程:
(demo:https://visactor.io/vchart/demo/storytelling/ranking-bar)
为了更好的解释动画的含意,在 VisActor 中动画被视作为渲染阶段的润饰:动画配置与图形语法流程执行失去的图元视觉通道一起决定了渲染阶段的后果。动画的体现是具体图形元素在某一时间段内视觉通道属性的插值计算或者非凡计算逻辑,而动画配置形容了这一计算的触发机会以及执行时长。
(VGrammar 动画流程)
动画触发机会
动画的申明形式能够划分为两种:
- 被动模式的申明:配置申明图元在接下来的一段时间里该当执行何种动画成果(例如 Canis);
- 被动模式的申明:配置申明图元在某种状态下该当触发何种动画成果。
在图表库的场景中,动画出现往往是随同着交互动作或者特定机会的,例如出场 / 入场 / hover / select 等均会触发相应的动画成果,因而被动模式的申明更便于动画的配置。
VGrammar 所提供的动画配置实际上形容了渲染阶段的执行逻辑,开发者并不间接触发动画的执行,而是通过申明动画的执行逻辑,在特定的动画机会触发时由 VGrammar 逻辑触发相应的动画计算。
从图元状态的角度来看,动画的触发机会能够分为:
- enter:新增图形元素时的动画触发;
- exit:移除图形元素的动画触发;
- update:图形元素视觉通道更新的动画触发;
- state:图形元素交互状态变更的动画触发,在最为常见的交互与动画配合的场景中,动画体现为随同着交互状态变更的插值,例如 hover 动画。针对这一动画状态,咱们独自在底层渲染库做额定解决,防止低廉的数据流计算以晋升性能;
- 任意机会触发的动画:动画配置将会立刻利用于图元,在可视化叙事的场景中,这一模式的动画往往更为常见;
临时无奈在飞书文档外展现此内容
(动画触发机会)
基于动画与数据流的拆散,咱们可能自在划定动画的触发机会,并且配置相应的动画成果。例如在 VGrammar 的图元上能够如此配置矩形图元上各个动画阶段的成果:(参见 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}
}
}
同时,为了便于叙事可视化场景下自在的触发动画成果,VGrammar 在图元以及顶层对象 View 上同样提供了 animate 对象以反对主动的动画接口调用:
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;
}
动画根本单元
VGrammar 通过动画配置形容了对应的图元动画执行逻辑,其中蕴含具体的动画成果以及不同动画内容之间的编排。动画配置形成的示意图为:
(VGrammar 动画单元)
其中整体的动画配置由互相独立的根本的动画单元 Aunit 形成,一个 Aunit 将被利用到某一个具体的动画触发机会上,其形容了该机会达到时如何执行动画插值计算。动画单元的定义为:
其中的形成元素为:
- mark:动画单元关联的具体图元,动画申明将绑定到该图元的各个动画触发机会上;
- timeline:动画的工夫线,timeline 形容了一段时间内图元的动画体现。timeline 上蕴含了一组串行执行的动画分片,不同 timeline 之间动画能够并行。一个 timeline 能够被设置 loop 以执行循环的动画插值计算;
- partitioner:动画分区器,对相应图元中的内容进行筛选,并将动画配置利用到这些筛选的图形元素上;
- sort:图元排序,相应图元中的图形元素执行程序依赖于 sort 所指定的排序。
动画工夫线 Timeline 的定义为:
Timline 中的形成元素蕴含:
- timeslice:动画的分片,形容了具体的某一段插值动画配置,蕴含相干的动画成果、动画执行工夫等具体动画配置。在一个 timeline 上所有的 timeslice 头衔尾的串联在一起;
- startTime:动画的开始执行工夫,形容了以后 timeline 触发执行之后开始执行动画的工夫;
- duration:动画工夫线的执行时长,形容了以后 timeline 的动画时长;
- loop:一个 timeline 能够被设置为循环,其中蕴含的所有 timeslice 所形容的动画过程将会被反复的执行。
动画分片 Timeslice 的定义为:
Timeslice 中的形成元素蕴含:
- effect:动画的具体执行成果,形容了具体的图元视觉通道属性插值计算逻辑。effect 能够是封装好的特定动画成果,或者由开发者配置起始状态以及结尾状态的动画配置,形容了动画的属性插值的计算逻辑;
- duration:动画分片的执行时长;
- delay:动画分片的执行前的等待时间;
- OneByOne:形容了相应图元内具体图形元素顺次执行的逻辑;
动画成果 Effect 的定义为:
Effect 中的形成元素蕴含:
- channel:变更的视觉通道属性,形容了插值计算开始以及结尾状态时的视觉通道属性;
- easing:差值计算的缓动策略;
一个形容柱子先变亮后变暗的残缺动画配置如下所示:
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
}
]
}
(具体动画实例参见:https://visactor.io/vgrammar/demo/animate/loop-animate)
同时,VGrammar 也提供了动画配置以及一系列的内置动画成果,例如渐入渐出动画、宽高成长动画等,例如:
animation: {
enter: {type: 'fadeIn'},
exit: {type: 'fadeOut'}
}
具体的内置动画以及动画配置参见 VGrammar 教程:https://visactor.io/vgrammar/guide/guides/animation
全局形变动画
在常见的插值动画之外,咱们还提供了图元与图元之间的全局形变动画以反对不同图形元素、不同图表之间的平滑过渡成果。
例如图表从柱状图切换到饼图的时候,咱们通常心愿可能将柱状图中的柱子平滑的过渡到饼图中的饼环:
(demo:https://visactor.io/vchart/demo/example/morphing/bar-to-pie)
VGrammar 中默认开启了全局形变配置。当开发者调用 updateSpec 更新场景的 spec 申明或者通过 api 模式卸载 / 挂载语法元素后,VGrammar 会对更新前后的图元进行 diff 并根据相应的配置决定执行图元复用或者执行全局形变。
开发者能够为须要执行全局形变的图元配置相应的成果:
- mark.morph: 图元是否利用全局形变
- mark.morphKey: 图元在进行前后的匹配时所依赖的 key 值
- mark.morphElementKey: 图元中具体的图形元素进行前后的匹配时所依赖的 key 值
一个简略的全局形变示例为:
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
贝塞尔曲线
实现全局形变动画的外围就在于将所有的图元的边转化为贝塞尔曲线,通过对贝塞尔曲线的控制点进行插值,能够实现不同图形之间的形变动画。
贝塞尔曲线能够用于绘制直线、圆弧、椭圆等根底图形,咱们平时在计算机上看到的大量曲线图形简直都是通过贝塞尔曲线进行形容的。一个三阶贝塞尔曲线的的根本表达式如下:
其中 P0,P1,P2,P3 代表了贝塞尔曲线的四个控制点,B(t) 通过 t 在 [0, 1] 区间内的变动形容曲线上所有的点。如下所示:
(三阶贝塞尔曲线)
贝塞尔曲线具备凸包性,曲线内的所有点都被蕴含在控制点所形成的凸包中。同时贝塞尔曲线也能够被细化,在贝塞尔曲线的两头任意点,能够将贝塞尔曲线划分成两条新的曲线。例如划分如下曲线:
拆分之后两条贝塞尔曲线的的 T 参数被从新映射到 [0, 1] 区间,同时控制点也进行相应的拆分:
划分原始贝塞尔曲线之后,P1、P’1、P”1、P”’ 与 P”’、P”2、P’3、P4 散布形成了两条新的贝塞尔曲线的控制点。
一对一的形变动画
动画流程
上面以圆形图元变成方形图元来举例:
其根本的动画流程为:
- 对齐控制点个数(在以上视频中保障圆形和方形的控制点都是 4 个);
- 通过指标函数将动画前后的控制点两两配对(在以上视频中,将圆形和方形的控制点两两配对,保障配对的控制点之间的间隔和最小);
- 在配对点之间进行插值实现动画。
圆形 -> 矩形
多边形 -> 扇形
圆形 -> 面积
面积 -> 折线
图形拆分
如果要进行一对多动画和多对一动画,图形拆分是必须的步骤。其次要目标是保障动画前后的图元数量统一。
上面别离是矩形、圆形、拱形、多边形的可能拆分成果:
多对一的形变动画
动画流程
上面以 3 个圆形图元变动为 1 个矩形图元举例,其根本的动画流程为:
- 将指标图元拆分成 3 个子图元并排序,而后通过 VRender 影子节点的机制进行渲染
- 将动画开始的图元排序,和指标图元的 3 个子图元进行匹配
- 将匹配后的图元,进行一对一的形变动画
- 动画完结后,回收指标图元的子图元,展现指标图元
流程图如下:
对于最初一步的合并办法,则有以下两种策略。一个是拆分策略,将多个形态作为拼图合并为一个形态:
一个是复制策略,将多个形态变动为和指标统一的形态再进行叠加:
一对多的形变动画
一对多的动画流程是多对一动画逆运算,步骤齐全相同就能够。以 1 个矩形图元变动为 3 个圆形图元举例,其根本的动画流程为:
示例如下,这个例子应用了拆分策略实现一对多的转换过程。
Demo 演示
VGrammar 动画编排示例:对题目、坐标轴以及柱子图元进行简略的动画编排以及自定义动画申明
(示例地址:https://visactor.io/vgrammar/demo/animate/arrange-animate)
VGrammar 叙事示例:通过 Glyph 图元与动画的配合实现滚动时间轴的叙事成果
(示例地址:https://visactor.io/vgrammar/demo/animate/timeline)
VChart 形变动画示例:柱状图与饼环图之间切换,通过形变动画达到平滑过渡成果
(示例地址:https://visactor.io/vchart/demo/example/morphing/bar-to-pie?k…)
VChart 叙事示例:在双向条形图中通过入场与更新动画的组织展现出随着工夫变更的数据动态变化过程
(示例地址:https://visactor.io/vchart/demo/storytelling/dynamic-comparat…)
总结
本文介绍了 VisActor 可视化解决方案中的动画实现原理,并为您展现了局部 Demo。下一篇文章咱们将具体解说 VChart 和 VGrammar 中的动画语法及动画配置,并为您展现如何通过 VChart 和 VGrammar 定制炫酷的动画成果及叙事作品。
欢送大家与咱们进行交换:
1)VisActor 微信订阅号留言(能够通过订阅号菜单退出微信群):
2)VisActor Discord 社群:https://discord.gg/3wPyxVyH6m
3)VisActor 官网及 github:
https://www.visactor.io/
https://github.com/visactor