变换
单元素动画
<script type="text/javascript">
var body = d3.select("body"),
duration = 5000;
body.append("div")
.classed("box", true)
.style("background-color", "#e9967a")
.transition()
.duration(duration)
.style("background-color", "#add8e6")
.style("margin-left", "600px")
.style("width", "100px")
.style("height", "100px");
</script>
如果没有起始值或者结束值,D3 会自动计算一个值作为过渡的有起始值或者结束值。
多元素动画
<script type="text/javascript">
var id= 0,
data = [],
duration = 500,
chartHeight = 100,
chartWidth = 680;
for(var i = 0; i < 20; i++) push(data);
function render(data) {
var selection = d3.select("body")
.selectAll("div.v-bar")
.data(data, function(d){return d.id;});
// enter
selection.enter()
.append("div")
.attr("class", "v-bar")
.style("z-index", "0")
.style("position", "fixed")
.style("top", chartHeight + "px")
.style("left", function(d, i){
return barLeft(i+1) + "px";
})
.style("height", "0px")
.append("span");
// update
selection
.transition().duration(duration)
.style("top", function (d) {
return chartHeight - 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;});
// exit
selection.exit()
.transition().duration(duration)
.style("left", function(d, i){
return barLeft(-1) + "px";
})
.remove();
}
function push(data) {
data.push({
id: ++id,
value: Math.round(Math.random() * chartHeight)
});
}
function barLeft(i) {
return i * (30 + 2);
}
function barHeight(d) {
return d.value;
}
setInterval(function () {
data.shift();
push(data);
render(data);
}, 2000);
render(data);
d3.select("body")
.append("div")
.attr("class", "baseline")
.style("position", "fixed")
.style("z-index", "1")
.style("top", chartHeight + "px")
.style("left", "0px")
.style("width", chartWidth + "px");
</script>
缓动动画
<script type="text/javascript">
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; }}
],
colors = d3.scaleOrdinal(d3.schemeCategory20);
d3.select("body").selectAll("div")
.data(data)
.enter()
.append("div")
.attr("class", "fixed-cell")
.style("top", function (d, i) {
return i * 40 + "px";
})
.style("background-color", function (d, i) {
return colors(i);
})
.style("color", "white")
.style("left", "500px")
.text(function (d) {
return d.name;
});
d3.selectAll("div").each(function(d){
d3.select(this)
.transition().ease(d.fn) // <-D
.duration(1500)
.style("left", "10px");
});
</script>
使用中间帧计算
<script type="text/javascript">
var body = d3.select("body"), duration = 5000;
body.append("div").append("input")
.attr("type", "button")
.attr("class", "countdown")
.attr("value", "0")
.style("width", "150px")
.transition().duration(duration).ease(d3.easeLinear)
.style("width", "400px")
.attr("value", "9");
body.append("div").append("input")
.attr("type", "button")
.attr("class", "countdown")
.attr("value", "0")
.transition().duration(duration).ease(d3.easeLinear)
.styleTween("width", widthTween)
.attrTween("value", valueTween);
function widthTween(a){
var interpolate = d3.scaleQuantize()
.domain([0, 1])
.range([150, 200, 250, 350, 400]);
return function(t){
return interpolate(t) + "px";
};
}
function valueTween(){
var interpolate = d3.scaleQuantize()
.domain([0, 1])
.range([1, 2, 3, 4, 5, 6, 7, 8, 9]);
return function(t){
return interpolate(t);
};
}
</script>
在 D3 中,tween 函数是一个工厂函数,用来构造执行中间帧的最终函数。
使用级联过渡
<script type="text/javascript">
var body = d3.select("body");
function teleport(s){
s.transition().duration(1000)
.style("width", "200px")
.style("height", "1px")
.transition().duration(500)
.style("left", "600px")
.transition().duration(1000)
.style("left", "800px")
.style("height", "80px")
.style("width", "80px");
}
body.append("div")
.style("position", "fixed")
.style("background-color", "steelblue")
.style("left", "10px")
.style("width", "80px")
.style("height", "80px")
.call(teleport);
</script>
使用选择性过渡
<script type="text/javascript">
var data = ["Cat", "Dog", "Cat", "Dog", "Cat", "Dog", "Cat", "Dog"],
duration = 1500;
d3.select("body").selectAll("div")
.data(data)
.enter()
.append("div")
.attr("class", "fixed-cell")
.style("top", function (d, i) {
return i * 40 + "px";
})
.style("background-color", "steelblue")
.style("color", "white")
.style("left", "500px")
.text(function (d) {
return d;
})
.transition()
.duration(duration)
.style("left", "10px")
.filter(function(d){return d == "Cat";})
.transition()
.duration(duration)
.style("left", "500px");
</script>
监听过渡事件
<script type="text/javascript">
var body = d3.select("body"), duration = 3000;
var div = body.append("div")
.classed("box", true)
.style("background-color", "steelblue")
.style("color", "white")
.text("waiting")
.transition().duration(duration)
.delay(1000)
.on("start", function(){
d3.select(this).text(function (d, i) {
return "transitioning";
});
})
.on("end", function(){
d3.select(this).text(function (d, i) {
return "done";
});
})
.style("margin-left", "600px");
</script>
使用定时器
<script type="text/javascript">
var body = d3.select("body");
var countdown = body.append("div").append("input");
countdown.attr("type", "button")
.attr("class", "countdown")
.attr("value", "0");
function countUp(target){
var t = d3.timer(function(){
var value = countdown.attr("value");
if( value == target ) {
t.stop();
return true;
}
countdown.attr("value", ++value);
});
}
function reset(){
countdown.attr("value", 0);
}
</script>
<div class="control-group">
<button onclick="countUp(100)">
Start
</button>
<button onclick="reset()">
Clear
</button>
</div>
形状
什么是 SVG
- 矢量:矢量的图形更适合可视化
- 可伸缩性:任意尺寸和放大程度下都不会失真
- 可读性
- 开放标准
- 应用广泛
- 互用性
- 轻量
创建简单图形
<style>
svg line{
stroke: grey;
stroke-width: 2;
}
svg circle{
stroke: red;
fill: none;
stroke-width: 2;
}
svg rect{
stroke: steelblue;
fill: none;
stroke-width: 2;
}
svg polygon{
stroke: green;
fill: none;
stroke-width: 2;
}
</style>
<script type="text/javascript">
var width = 600,
height = 500;
var svg = d3.select("body").append("svg");
svg.attr("height", height)
.attr("width", width);
svg.append("line")
.attr("x1", 0)
.attr("y1", 200)
.attr("x2", 100)
.attr("y2", 100);
svg.append("circle")
.attr("cx", 200)
.attr("cy", 150)
.attr("r", 50);
svg.append("rect")
.attr("x", 300)
.attr("y", 100)
.attr("width", 100)
.attr("height", 100)
.attr("rx", 5);
svg.append("polygon")
.attr("points", "450,200 500,100 550,200");
</script>
D3 还支持椭圆(ellipse)和折线(ployline)。
使用线条生成器
<script type="text/javascript">
var width = 500,
height = 500,
margin = 50,
x = d3.scaleLinear()
.domain([0, 10])
.range([margin, width - margin]),
y = d3.scaleLinear()
.domain([0, 10])
.range([height - margin, margin]);
var data = [
[
{x: 0, y: 5},{x: 1, y: 9},{x: 2, y: 7},
{x: 3, y: 5},{x: 4, y: 3},{x: 6, y: 4},
{x: 7, y: 2},{x: 8, y: 3},{x: 9, y: 2}
],
d3.range(10).map(function(i){
return {x: i, y: Math.sin(i) + 5};
})
];
var line = d3.line()
.x(function(d){return x(d.x);})
.y(function(d){return y(d.y);});
var svg = d3.select("body").append("svg");
svg.attr("height", height)
.attr("width", width);
svg.selectAll("path")
.data(data)
.enter()
.append("path")
.attr("class", "line")
.attr("d", function(d){return line(d);});
renderAxes(svg);
function renderAxes(svg){
var xAxis = d3.axisBottom()
.scale(x.range([0, quadrantWidth()]));
var yAxis = d3.axisLeft()
.scale(y.range([quadrantHeight(), 0]));
svg.append("g")
.attr("class", "axis")
.attr("transform", function(){
return "translate(" + xStart()
+ "," + yStart() + ")";
})
.call(xAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", function(){
return "translate(" + xStart()
+ "," + yEnd() + ")";
})
.call(yAxis);
}
function xStart(){
return margin;
}
function yStart(){
return height - margin;
}
function xEnd(){
return width - margin;
}
function yEnd(){
return margin;
}
function quadrantWidth(){
return width - 2 * margin;
}
function quadrantHeight(){
return height - 2 * margin;
}
</script>
使用曲线
<script type="text/javascript">
var width = 500,
height = 500,
margin = 30,
x = d3.scaleLinear()
.domain([0, 10])
.range([margin, width - margin]),
y = d3.scaleLinear()
.domain([0, 10])
.range([height - margin, margin]);
var data = [
[
{x: 0, y: 5},{x: 1, y: 9},{x: 2, y: 7},
{x: 3, y: 5},{x: 4, y: 3},{x: 6, y: 4},
{x: 7, y: 2},{x: 8, y: 3},{x: 9, y: 2}
],
d3.range(10).map(function(i){
return {x: i, y: Math.sin(i) + 5};
})
];
var svg = d3.select("body").append("svg");
svg.attr("height", height)
.attr("width", width);
renderAxes(svg);
render(d3.curveLinear);
renderDots(svg);
function render(mode){
var line = d3.line()
.x(function(d){return x(d.x);})
.y(function(d){return y(d.y);})
.curve(mode);
svg.selectAll("path.line")
.data(data)
.enter()
.append("path")
.attr("class", "line");
svg.selectAll("path.line")
.data(data)
.attr("d", function(d){return line(d);});
}
function renderDots(svg){
data.forEach(function(list){
svg.append("g").selectAll("circle")
.data(list)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.attr("r", 4.5);
});
}
function renderAxes(svg){
var xAxis = d3.axisBottom()
.scale(d3.scaleLinear().range([0, quadrantWidth()]));
var yAxis = d3.axisLeft()
.scale(d3.scaleLinear().range([quadrantHeight(), 0]));
svg.append("g")
.attr("class", "axis")
.attr("transform", function(){
return "translate(" + xStart() + "," + yStart() + ")";
})
.call(xAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", function(){
return "translate(" + xStart() + "," + yEnd() + ")";
})
.call(yAxis);
}
function xStart(){
return margin;
}
function yStart(){
return height - margin;
}
function xEnd(){
return width - margin;
}
function yEnd(){
return margin;
}
function quadrantWidth(){
return width - 2 * margin;
}
function quadrantHeight(){
return height - 2 * margin;
}
</script>
<h4>Curve Mode:</h4>
<div class="control-group">
<button onclick="render(d3.curveLinear)">linear</button>
<button onclick="render(d3.curveLinearClosed)">linear closed</button>
<button onclick="render(d3.curveStepBefore)">step before</button>
<button onclick="render(d3.curveStepAfter)">step after</button>
<button onclick="render(d3.curveBasis)">basis</button>
<button onclick="render(d3.curveBasisOpen)">basis open</button>
<button onclick="render(d3.curveBasisClosed)">basis closed</button>
</div>
<div class="control-group">
<button onclick="render(d3.curveBundle)">bundle</button>
<button onclick="render(d3.curveCardinal)">cardinal</button>
<button onclick="render(d3.curveCardinalOpen)">cardinal open</button>
<button onclick="render(d3.curveCardinalClosed)">cardinal closed</button>
<button onclick="render(d3.curveMonotoneY)">monotone</button>
<button onclick="render(d3.curveCatmullRom)">CatmullRom</button>
</div>
更改线条的张力
<script type="text/javascript">
var width = 500,
height = 500,
margin = 30,
duration = 500,
x = d3.scaleLinear()
.domain([0, 10])
.range([margin, width - margin]),
y = d3.scaleLinear()
.domain([0, 1])
.range([height - margin, margin]);
var data = d3.range(10).map(function(i){
return {x: i, y: (Math.sin(i * 3) + 1) / 2};
});
var svg = d3.select("body").append("svg");
svg.attr("height", height)
.attr("width", width);
renderAxes(svg);
render(1);
function render(tension){
var line = d3.line()
.curve(d3.curveCardinal.tension(tension))
.x(function(d){return x(d.x);})
.y(function(d){return y(d.y);});
svg.selectAll("path.line")
.data([tension])
.enter()
.append("path")
.attr("class", "line");
svg.selectAll("path.line")
.data([tension])
.transition().duration(duration)
.ease(d3.easeLinear)
.attr("d", function(d){
return line(data);
});
svg.selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.attr("r", 4.5);
}
function renderAxes(svg){
var xAxis = d3.axisBottom()
.scale(d3.scaleLinear().range([0, quadrantWidth()]));
var yAxis = d3.axisLeft()
.scale(d3.scaleLinear().range([quadrantHeight(), 0]));
svg.append("g")
.attr("class", "axis")
.attr("transform", function(){
return "translate(" + xStart() + "," + yStart() + ")";
})
.call(xAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", function(){
return "translate(" + xStart() + "," + yEnd() + ")";
})
.call(yAxis);
}
function xStart(){
return margin;
}
function yStart(){
return height - margin;
}
function xEnd(){
return width - margin;
}
function yEnd(){
return margin;
}
function quadrantWidth(){
return width - 2 * margin;
}
function quadrantHeight(){
return height - 2 * margin;
}
</script>
<h4>Line Tension:</h4>
<div class="control-group">
<button onclick="render(0)">0</button>
<button onclick="render(0.2)">0.2</button>
<button onclick="render(0.4)">0.4</button>
<button onclick="render(0.6)">0.6</button>
<button onclick="render(0.8)">0.8</button>
<button onclick="render(1)">1</button>
</div>
使用区域生成器
<script type="text/javascript">
var width = 500,
height = 500,
margin = 30,
duration = 500,
x = d3.scaleLinear()
.domain([0, 10])
.range([margin, width - margin]),
y = d3.scaleLinear()
.domain([0, 10])
.range([height - margin, margin]);
var data = d3.range(11).map(function(i){
return {x: i, y: Math.sin(i)*3 + 5};
});
var svg = d3.select("body").append("svg");
svg.attr("height", height)
.attr("width", width);
renderAxes(svg);
render();
renderDots(svg);
function render(){
var line = d3.line()
.x(function(d){return x(d.x);})
.y(function(d){return y(d.y);});
svg.selectAll("path.line")
.data([data])
.enter()
.append("path")
.attr("class", "line");
svg.selectAll("path.line")
.data([data])
.attr("d", function(d){return line(d);});
var area = d3.area()
.x(function(d) { return x(d.x); })
.y0(y(0))
.y1(function(d) { return y(d.y); });
svg.selectAll("path.area")
.data([data])
.enter()
.append("path")
.attr("class", "area")
.attr("d", function(d){return area(d);});
}
function renderDots(svg){
svg.append("g").selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.attr("r", 4.5);
}
function renderAxes(svg){
var xAxis = d3.axisBottom()
.scale(d3.scaleLinear().range([0, quadrantWidth()]));
var yAxis = d3.axisLeft()
.scale(d3.scaleLinear().range([quadrantHeight(), 0]));
svg.append("g")
.attr("class", "axis")
.attr("transform", function(){
return "translate(" + xStart() + "," + yStart() + ")";
})
.call(xAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", function(){
return "translate(" + xStart() + "," + yEnd() + ")";
})
.call(yAxis);
}
function xStart(){
return margin;
}
function yStart(){
return height - margin;
}
function xEnd(){
return width - margin;
}
function yEnd(){
return margin;
}
function quadrantWidth(){
return width - 2 * margin;
}
function quadrantHeight(){
return height - 2 * margin;
}
</script>
使用断面曲线
<script type="text/javascript">
var width = 500,
height = 500,
margin = 30,
x = d3.scaleLinear()
.domain([0, 10])
.range([margin, width - margin]),
y = d3.scaleLinear()
.domain([0, 10])
.range([height - margin, margin]);
var data = d3.range(11).map(function(i){
return {x: i, y: Math.sin(i)*3 + 5};
});
var svg = d3.select("body").append("svg");
svg.attr("height", height)
.attr("width", width);
renderAxes(svg);
render(d3.curveLinear);
renderDots(svg);
function render(mode){
var line = d3.line()
.x(function(d){return x(d.x);})
.y(function(d){return y(d.y);})
.curve(mode);
svg.selectAll("path.line")
.data([data])
.enter()
.append("path")
.attr("class", "line");
svg.selectAll("path.line")
.data([data])
.attr("d", function(d){return line(d);});
var area = d3.area()
.x(function(d) { return x(d.x); })
.y0(y(0))
.y1(function(d) { return y(d.y); })
.curve(mode);
svg.selectAll("path.area")
.data([data])
.enter()
.append("path")
.attr("class", "area")
svg.selectAll("path.area")
.data([data])
.attr("d", function(d){return area(d);});
}
function renderDots(svg){
svg.append("g").selectAll("circle")
.data(data)
.enter().append("circle")
.attr("class", "dot")
.attr("cx", function(d) { return x(d.x); })
.attr("cy", function(d) { return y(d.y); })
.attr("r", 4.5);
}
function renderAxes(svg){
var xAxis = d3.axisBottom()
.scale(d3.scaleLinear().range([0, quadrantWidth()]));
var yAxis = d3.axisLeft()
.scale(d3.scaleLinear().range([quadrantHeight(), 0]));
svg.append("g")
.attr("class", "axis")
.attr("transform", function(){
return "translate(" + xStart() + "," + yStart() + ")";
})
.call(xAxis);
svg.append("g")
.attr("class", "axis")
.attr("transform", function(){
return "translate(" + xStart() + "," + yEnd() + ")";
})
.call(yAxis);
}
function xStart(){
return margin;
}
function yStart(){
return height - margin;
}
function xEnd(){
return width - margin;
}
function yEnd(){
return margin;
}
function quadrantWidth(){
return width - 2 * margin;
}
function quadrantHeight(){
return height - 2 * margin;
}
</script>
<h4>Curve Mode:</h4>
<div class="control-group">
<button onclick="render(d3.curveLinear)">linear</button>
<button onclick="render(d3.curveLinearClosed)">linear closed</button>
<button onclick="render(d3.curveStepBefore)">step before</button>
<button onclick="render(d3.curveStepAfter)">step after</button>
<button onclick="render(d3.curveBasis)">basis</button>
<button onclick="render(d3.curveBasisOpen)">basis open</button>
<button onclick="render(d3.curveBasisClosed)">basis closed</button>
</div>
<div class="control-group">
<button onclick="render(d3.curveBundle)">bundle</button>
<button onclick="render(d3.curveCardinal)">cardinal</button>
<button onclick="render(d3.curveCardinalOpen)">cardinal open</button>
<button onclick="render(d3.curveCardinalClosed)">cardinal closed</button>
<button onclick="render(d3.curveMonotoneY)">monotone</button>
<button onclick="render(d3.curveCatmullRom)">CatmullRom</button>
</div>
使用圆弧生成器
<script type="text/javascript">
var width = 400,
height = 400,
fullAngle = 2 * Math.PI,
colors = d3.scaleOrdinal(d3.schemeCategory20c);
var svg = d3.select("body").append("svg")
.attr("class", "pie")
.attr("height", height)
.attr("width", width);
function render(innerRadius, endAngle){
if(!endAngle) endAngle = fullAngle;
var data = [
{startAngle: 0, endAngle: 0.1 * endAngle},
{startAngle: 0.1 * endAngle, endAngle: 0.2 * endAngle},
{startAngle: 0.2 * endAngle, endAngle: 0.4 * endAngle},
{startAngle: 0.4 * endAngle, endAngle: 0.6 * endAngle},
{startAngle: 0.6 * endAngle, endAngle: 0.7 * endAngle},
{startAngle: 0.7 * endAngle, endAngle: 0.9 * endAngle},
{startAngle: 0.9 * endAngle, endAngle: endAngle}
];
var arc = d3.arc().outerRadius(200)
.innerRadius(innerRadius);
svg.select("g").remove();
svg.append("g")
.attr("transform", "translate(200,200)")
.selectAll("path.arc")
.data(data)
.enter()
.append("path")
.attr("class", "arc")
.attr("fill", function(d, i){return colors(i);})
.attr("d", function(d, i){
return arc(d, i);
});
}
render(0);
</script>
<div class="control-group">
<button onclick="render(0)">Circle</button>
<button onclick="render(100)">Annulus(Donut)</button>
<button onclick="render(0, Math.PI)">Circular Sector</button>
<button onclick="render(100, Math.PI)">Annulus Sector</button>
</div>
实现圆弧过渡
<script type="text/javascript">
var width = 400,
height = 400,
endAngle = 2 * Math.PI,
colors = d3.scaleOrdinal(d3.schemeCategory20c);
var svg = d3.select("body").append("svg")
.attr("class", "pie")
.attr("height", height)
.attr("width", width);
function render(innerRadius) {
var data = [
{startAngle: 0, endAngle: 0.1 * endAngle},
{startAngle: 0.1 * endAngle, endAngle: 0.2 * endAngle},
{startAngle: 0.2 * endAngle, endAngle: 0.4 * endAngle},
{startAngle: 0.4 * endAngle, endAngle: 0.6 * endAngle},
{startAngle: 0.6 * endAngle, endAngle: 0.7 * endAngle},
{startAngle: 0.7 * endAngle, endAngle: 0.9 * endAngle},
{startAngle: 0.9 * endAngle, endAngle: endAngle}
];
var arc = d3.arc()
.outerRadius(200).innerRadius(innerRadius);
svg.select("g").remove();
svg.append("g")
.attr("transform", "translate(200,200)")
.selectAll("path.arc")
.data(data)
.enter()
.append("path")
.attr("class", "arc")
.attr("fill", function (d, i) {
return colors(i);
})
.transition().duration(1000)
.attrTween("d", function (d) {
var start = {startAngle: 0, endAngle: 0}; // <-A
var interpolate = d3.interpolate(start, d); // <-B
return function (t) {
return arc(interpolate(t)); // <-C
};
});
}
render(100);
</script>
参考
【1】Data Visualization and D3.js(视频)
【2】D3 4.x 数据可视化实战手册@[加]朱启
【3】https://github.com/NickQiZhu/d3-cookbook-v2(D3 4.x 数据可视化实战手册 代码)
【4】D3.js 中文文档