d3
是一个Js库,它是基于Svg
来绘图,提供丰富的Api接口,国产的Echarts
和HeightChart
能提供的图,它都能实现,非常强大。d3也是可视化
和可视分析
领域一个重要的工具。今天本文要讲的是D3中的一个功能:Transition
官方关于Transition的定义
官方是英文的,就一段话:
------D3’s focus on transformation extends naturally to animated transitions. Transitions gradually interpolate styles and attributes over time. Tweening can be controlled via easing functions such as “elastic”, “cubic-in-out” and “linear”. D3’s interpolators support both primitives, such as numbers and numbers embedded within strings (font sizes, path data, etc.), and compound values. You can even extend D3’s interpolator registry to support complex properties and data structures.
Google翻译过来是:
------D3对转换的关注自然会扩展到动画转换。 过渡会随着时间逐渐插入样式和属性。 可以通过诸如“弹性”,“三次进出”和“线性”之类的缓动功能来控制补间。 D3的插值器同时支持数字和字串等原语,例如数字和嵌入字符串中的数字(字体大小,路径数据等)。 您甚至可以扩展D3的插值器注册表以支持复杂的属性和数据结构。
简单来说,就是很强大。基于原生的Html的Animation,不会有什么兼容性的问题。
Nick Zhu 的渐变柱状图
加拿大的朱哥(Nick Zhu)写过一本关于D3的书(D3 4.X 数据可视化实战手册
),强烈推荐想学的同学去看看。有一章专门讲了渐变(transition
),下面我借花献佛,学习一下其中的一个例子。首先看一下最终效果:
可以看到右侧的柱子从0到1 逐渐变大,并且移动。这个效果就是运用了Transition,那到底是怎么实现的呢?
大概分3步:
- 数据输入(Enter)
- 数据更新,执行Transition(Update)
- 退出(Exit)
1. 数据输入(Enter)
根据数据Append
元素,指定元素的位置和大小。与其他例子不同的是这里没有用到SVG
元素而是单纯地对普通Dom
节点进行Transition
:
var selection = d3.select("body")
.selectAll("div.v-bar")
.data(data, function(d, i) { return d.id;});
//Enter
selection.enter()
.append("div")
.attr("class", "v-bar")
.style("z-index", "0")
.style("position", "fixed")
.style("top", 200 + "px")
.style("left", function(d, i) {
return barLeft(i + 1) + "px";
})
.style("height", "0px")
.append("span");
伪代码假定已经定义了Barleft
方法 和数据 Data
。这个时候不会有任何效果,只是添加了一排柱状图的位置。
2. 数据更新,执行Transition(Update)
这一步,就是指定新的位置,然后利用Transition
特性,让元素从原来的位置缓动到新的位置。代码很简单:
selection
.transition().duration(duration)
.style("top", function(d) {
return 200 - barHeight(d) + "px";
})
.style("left", function(d, i) {
return barLeft(i) + "px";
})
.style("height", function(d) {
return barHeight(d) + "px";
})
.select("span")
.text(function(d) { return d.value; });
伪代码假定已经定义了Barleft
方法、barHeight
方法和duration
。到这一步,图中的效果就有了,你可以看到柱子会向左移,但是,不是很完美,最左侧的柱子会叠在一起:
那么怎样解决左边的堆叠呢,其实我们需要的就是让他“消失”。
3. 退出(Exit)
让它消失其实就是remove
,但是,为了优雅地”消失“,我们还需要用到Transition
:
selection.exit()
.transition().duration(duration)
.style("left", function(d, i) {
return barLeft(-1) + "px";
})
.remove();
原理就是让Dom
元素先优雅地移动到左边隐藏,然后Remove
掉。怎么样,是不是很简单,柯柯~
另一种形态
很多时候,并不会用到上面的第3步,也就是移除。仅仅是让有限的元素变换位置,这样的例子有很多,比较典型的就是散点图,或者聚类图,下面我们看一个单个节点的例子:
图中点做来回运动,并没有Remove
操作,只是让它缓动,重复两次即可实现这种效果:
function repeat() {
dom.attr('cx', 40)
.attr('cy', 250)
.transition()
.duration(2000)
.attr('cx', 920)
.transition()
.duration(2000)
.attr('cx', 40)
.on("end", repeat);
};
假定伪代码中Dom
已定义,几行代码搞定的效果,是不是比Echarts
强大多了呢,柯柯~
缓动函数
还有另一个跟Transition相关的概念,介绍给大家,它就是缓动函数。 用大白话说就是,元素A从位置B移动到位置C的形式。看下面这个图:
每个小方块代表着一种形式,从右移动到左。默认的形式就是Linear,也就是线性移动。它其实是利用了D3中的函数实现的,代码如下:
var data = [
{ name: 'Linear', fn: d3.easeLinear },
{ name: 'Cubic', fn: d3.easeCubic },
{ name: 'CubicIn', fn: d3.easeCubicIn },
{ name: 'Sin', fn: d3.easeSin },
{ name: 'SinIn', fn: d3.easeSinIn },
{ name: 'Exp', fn: d3.easeExp },
{ name: 'Circle', fn: d3.easeCircle },
{ name: 'Back', fn: d3.easeBack },
{ name: 'Bounce', fn: d3.easeBounce },
{ name: 'Elastic', fn: d3.easeElastic },
{ name: 'Custom', fn: function (t) { return t * t; } }
],
.......
d3.selectAll("div").each(function (d) {
d3.select(this)
.transition().ease(d.fn)
.duration(1500)
.style("left", "10px");
});
所以,上面的例子都可以用这些缓动函数。除了这些之外,还有个概念是 中间帧(tween) 和级联过渡,感兴趣的同学可以自行搜索一下,这里就不再细说了。
Transition 源码浅析
所以,这个东西是怎么实现的?让我们打开Github来看看。
对于Transition
,它定义了一个Function
:
selection.prototype.transition = selection_transition;
function selection_transition(name) {
var id,timing;
if (name instanceof Transition) {
id = name._id, name = name._name;
} else {
id = newId(), (timing = defaultTiming).time = now(), name = name == null ? null : name + "";
}
for (var groups = this._groups, m = groups.length, j = 0; j < m; ++j) {
for (var group = groups[j], n = group.length, node, i = 0; i < n; ++i) {
if (node = group[i]) {
schedule(node, name, id, i, group, timing || inherit(node, id));
}
}
}
return new Transition(groups, this._parents, name, id);
}
Transition.prototype = transition.prototype = {
constructor: Transition,
select: transition_select,
selectAll: transition_selectAll,
......
style: transition_style,
styleTween: transition_styleTween,
tween: transition_tween,
delay: transition_delay,
duration: transition_duration,
ease: transition_ease
};
挂载在原型上方法有很多属性,在Selection
集合上为每个节点挂载Transition
。通过schedule
为它们分配Duration
,控制调用频率,通过tween
方法去设置每个Style
属性,实现视觉上的缓动。
整个Transition
的流程大概如下(这里只讲了它的一个场景,他还有attrTween
的场景):
所以, 它根
Css3
的transition
还是有很大区别的,实质上是去多次设置元素的Style
,让它达到变化的效果。这几个步骤中,其他几步都很好理解的,这里就把
Tween
这里贴出来看一下:
function styleTween(name, value, priority) {
function tween() {
var node = this, i = value.apply(node, arguments);
return i && function(t) {
node.style.setProperty(name, i(t), priority);
};
}
tween._value = value;
return tween;
}
每个Tween
实质上都是去做一件事,设置属性(setProperty
). 通过 Schedule
和Timer
方法去控制它的调用频率,达到缓动效果。
最后
看完此文,是不是对这个属性不那么陌生了呢,相对Css3
对Transition
的封装,我觉得这个理解起来更直接,至少让我们看到了它的实现过程。 喜欢我的文章就点个关注,一起进步,柯柯~
注:本文所有关于D3的实例均基于V4版本。
本文转自我的CSDN博客:(https://blog.csdn.net/kingbox000/article/details/107472851),转载请注明出处。
参考:
- Looping a transition in v4.
- d3 transition.
- Nick Zhu的例子.