动画
与在纸上绘制的图形不同,计算机图形不必是静态的。就像科学怪人的怪物一样,它们可以通过动画来栩栩如生! ✨
生成一个控制按钮
1
| viewof replay = html`<button>Replay`
|

逐步画完的拆线图
1
2
3
4
5
| replay, html`<svg viewBox="0 0 ${width} ${height}">
${d3.select(svg`<path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1" stroke-dasharray="0,1"></path>`).call(reveal).node()} // reveal函数控制动画过程
${d3.select(svg`<g>`).call(xAxis).node()}
${d3.select(svg`<g>`).call(yAxis).node()}
</svg>`
|
1
2
3
4
5
6
7
| reveal = path => path.transition()
.duration(5000)
.ease(d3.easeLinear)
.attrTween("stroke-dasharray", function() {
const length = this.getTotalLength();
return d3.interpolate(`0,${length}`, `${length},${length}`); // 代表隐藏部分的起止
})
|
手动控制逐步画完的拆线图
1
2
3
4
5
6
| viewof t = Scrubber(d3.ticks(0, 1, 100), { // 0->1,分成100份
autoplay: false,
loop: false,
initial: 50, // 初始值为50
format: x => `t = ${x.toFixed(3)}` // 标签格式化为3位小数
})
|
1
2
3
4
5
| html`<svg viewBox="0 0 ${width} ${height}">
<path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1" stroke-dasharray="${lineLength * t},${lineLength}"></path>
${d3.select(svg`<g>`).call(xAxis).node()}
${d3.select(svg`<g>`).call(yAxis).node()}
</svg>`
|
* 估计stroke-dasharray代表为曲线忽略的部分
三种制作动画的方法
- 使用D3’s transitions
- 使用时间t,当t改变时整个图重绘,这种方法效率低,但更易于缩写
- 使用循环yield,每秒生成60次
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| {
replay2;
const path = svg`<path d="${line(data)}" fill="none" stroke="steelblue" stroke-width="1.5" stroke-miterlimit="1">`;
const chart = html`<svg viewBox="0 0 ${width} ${height}">
${path}
${d3.select(svg`<g>`).call(xAxis).node()}
${d3.select(svg`<g>`).call(yAxis).node()}
</svg>`;
for (let i = 0, n = 300; i < n; ++i) {
const t = (i + 1) / n; // 0到1的t值,动画持续时间=300/60,即每秒60帧
path.setAttribute("stroke-dasharray", `${t * lineLength},${lineLength}`);
yield chart;
}
}
|
综合示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
| chart = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, height]);
const zx = x.copy(); // x, but with a new domain.
const line = d3.line()
.x(d => zx(d.date))
.y(d => y(d.close));
const path = svg.append("path")
.attr("fill", "none")
.attr("stroke", "steelblue")
.attr("stroke-width", 1.5)
.attr("stroke-miterlimit", 1)
.attr("d", line(data));
const gx = svg.append("g")
.call(xAxis, zx);
const gy = svg.append("g")
.call(yAxis, y);
return Object.assign(svg.node(), { // 将update置入到svg.node()中,以便可以用chart.update来调用
update(domain) {
const t = svg.transition().duration(750);
zx.domain(domain);
gx.transition(t).call(xAxis, zx);
path.transition(t).attr("d", line(data));
}
});
}
|
Joins
用svg展示一组字符
- 使用代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| chart1 = {
const svg = d3.create("svg")
.attr("viewBox", [0, 0, width, 33])
.attr("font-family", "sans-serif")
.attr("font-size", 10)
.style("display", "block");
svg.selectAll("text")
.data(alphabet)
.join("text")
.attr("x", (d, i) => i * 17)
.attr("y", 17)
.attr("dy", "0.35em")
.text(d => d);
return svg.node();
}
|
- 使用html标签
1
2
3
| html`<svg viewBox="0 0 ${width} 33" font-family="sans-serif" font-size="10" style="display: block;">
${alphabet.map((d, i) => svg`<text x="${i * 17}" y="17" dy="0.35em">${d}</text>`)}
</svg>`
|
以上两种方法结果相同