折线图的 'enter' 选择可以动画化吗?
Can the 'enter' selection for a line graph be animated?
我有一个更新得很好的折线图。直到新数据进入问题。那时 'update' 选择会按应有的方式向下动画,但新数据不会。
很难解释发生了什么,但是一张 gif 值一千字(看它何时过渡向下):
我已使用更新和渲染方法将图形包装在 class 中。这是我写的渲染方法。
render(){
// Update both Axes
this.xAxis.transition()
.call( d3.axisBottom( this.xScale ) );
this.yAxis.transition()
.call( this.createYAxis );
// Exit
this.line.exit().remove();
// Enter
this.line
.enter().append( 'path' )
.attr( 'class', 'valueLine' )
.transition()
.attr( 'd', this.valueLine );
// Update
this.line.transition().attr( 'd', this.valueLine );
// Allows method chaining
return this;
}
为什么 'new' 数据没有动画?
简答
是的,但是您必须定义 d
属性的起始值。
长答案
从起始值到目标值的转换。现在,在您的 "enter" 选择中,d
属性没有起始值(即 transition()
之前):
// Enter
this.line
.enter().append( 'path' )
.attr( 'class', 'valueLine' )
.transition()//no 'd' attribute before this line
.attr( 'd', this.valueLine );
因此,该路径的 d
属性是 null
(阅读更多关于此 )的信息。
让我们看看:
var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
.y(function(d) {
return d
})
.x(function(d, i) {
return i * 60
});
var path = svg.selectAll(null)
.data([data])
.enter()
.append("path");
console.log(path.attr("d"))
path {
fill: none;
stroke-width: 2px;
stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>
此处相同的代码,设置 d
属性:
var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
.y(function(d) {
return d
})
.x(function(d, i) {
return i * 60
});
var path = svg.selectAll(null)
.data([data])
.enter()
.append("path")
.attr("d", lineGenerator);
path {
fill: none;
stroke-width: 2px;
stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>
回到你的问题:
您无法将 d
属性从 null
转换为您的字符串。不可能。不过,有一些可能的解决方法。
其中之一是在转换前创建默认 d
属性。例如:
var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
.y(function(d) {
return d
})
.x(function(d, i) {
return i * 60
});
var path = svg.selectAll(null)
.data([data])
.enter()
.append("path")
.attr("d", "M0,100 L600,100")
.transition()
.duration(2000)
.attr("d", lineGenerator);
path {
fill: none;
stroke-width: 2px;
stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>
但这是一个可怕的转变!问题是初始的d
属性点数太少。因此,字符串插值器无法创建我们希望的插值。
因此,我们可以使用自定义函数,例如来自 Mike Bostock 的 this one。这是演示:
var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
.y(function(d) {
return d
})
.x(function(d, i) {
return i * 60
});
var path = svg.selectAll(null)
.data([data])
.enter()
.append("path")
.attr("d", "M0,100 L600,100")
.transition()
.duration(2000)
.attrTween("d", function(d) {
return pathTween(lineGenerator(d), 4, this)()
});
function pathTween(d1, precision, self) {
return function() {
var path0 = self,
path1 = path0.cloneNode(),
n0 = path0.getTotalLength(),
n1 = (path1.setAttribute("d", d1), path1).getTotalLength();
// Uniform sampling of distance based on specified precision.
var distances = [0],
i = 0,
dt = precision / Math.max(n0, n1);
while ((i += dt) < 1) distances.push(i);
distances.push(1);
// Compute point-interpolators at each distance.
var points = distances.map(function(t) {
var p0 = path0.getPointAtLength(t * n0),
p1 = path1.getPointAtLength(t * n1);
return d3.interpolate([p0.x, p0.y], [p1.x, p1.y]);
});
return function(t) {
return t < 1 ? "M" + points.map(function(p) {
return p(t);
}).join("L") : d1;
};
};
}
path {
fill: none;
stroke-width: 2px;
stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>
我有一个更新得很好的折线图。直到新数据进入问题。那时 'update' 选择会按应有的方式向下动画,但新数据不会。
很难解释发生了什么,但是一张 gif 值一千字(看它何时过渡向下):
我已使用更新和渲染方法将图形包装在 class 中。这是我写的渲染方法。
render(){
// Update both Axes
this.xAxis.transition()
.call( d3.axisBottom( this.xScale ) );
this.yAxis.transition()
.call( this.createYAxis );
// Exit
this.line.exit().remove();
// Enter
this.line
.enter().append( 'path' )
.attr( 'class', 'valueLine' )
.transition()
.attr( 'd', this.valueLine );
// Update
this.line.transition().attr( 'd', this.valueLine );
// Allows method chaining
return this;
}
为什么 'new' 数据没有动画?
简答
是的,但是您必须定义 d
属性的起始值。
长答案
从起始值到目标值的转换。现在,在您的 "enter" 选择中,d
属性没有起始值(即 transition()
之前):
// Enter
this.line
.enter().append( 'path' )
.attr( 'class', 'valueLine' )
.transition()//no 'd' attribute before this line
.attr( 'd', this.valueLine );
因此,该路径的 d
属性是 null
(阅读更多关于此
让我们看看:
var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
.y(function(d) {
return d
})
.x(function(d, i) {
return i * 60
});
var path = svg.selectAll(null)
.data([data])
.enter()
.append("path");
console.log(path.attr("d"))
path {
fill: none;
stroke-width: 2px;
stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>
此处相同的代码,设置 d
属性:
var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
.y(function(d) {
return d
})
.x(function(d, i) {
return i * 60
});
var path = svg.selectAll(null)
.data([data])
.enter()
.append("path")
.attr("d", lineGenerator);
path {
fill: none;
stroke-width: 2px;
stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>
回到你的问题:
您无法将 d
属性从 null
转换为您的字符串。不可能。不过,有一些可能的解决方法。
其中之一是在转换前创建默认 d
属性。例如:
var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
.y(function(d) {
return d
})
.x(function(d, i) {
return i * 60
});
var path = svg.selectAll(null)
.data([data])
.enter()
.append("path")
.attr("d", "M0,100 L600,100")
.transition()
.duration(2000)
.attr("d", lineGenerator);
path {
fill: none;
stroke-width: 2px;
stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>
但这是一个可怕的转变!问题是初始的d
属性点数太少。因此,字符串插值器无法创建我们希望的插值。
因此,我们可以使用自定义函数,例如来自 Mike Bostock 的 this one。这是演示:
var data = [10, 180, 30, 60, 190, 180, 50, 110, 40, 90, 90];
var svg = d3.select("svg");
var lineGenerator = d3.line()
.y(function(d) {
return d
})
.x(function(d, i) {
return i * 60
});
var path = svg.selectAll(null)
.data([data])
.enter()
.append("path")
.attr("d", "M0,100 L600,100")
.transition()
.duration(2000)
.attrTween("d", function(d) {
return pathTween(lineGenerator(d), 4, this)()
});
function pathTween(d1, precision, self) {
return function() {
var path0 = self,
path1 = path0.cloneNode(),
n0 = path0.getTotalLength(),
n1 = (path1.setAttribute("d", d1), path1).getTotalLength();
// Uniform sampling of distance based on specified precision.
var distances = [0],
i = 0,
dt = precision / Math.max(n0, n1);
while ((i += dt) < 1) distances.push(i);
distances.push(1);
// Compute point-interpolators at each distance.
var points = distances.map(function(t) {
var p0 = path0.getPointAtLength(t * n0),
p1 = path1.getPointAtLength(t * n1);
return d3.interpolate([p0.x, p0.y], [p1.x, p1.y]);
});
return function(t) {
return t < 1 ? "M" + points.map(function(p) {
return p(t);
}).join("L") : d1;
};
};
}
path {
fill: none;
stroke-width: 2px;
stroke: teal;
}
<script src="https://d3js.org/d3.v4.min.js"></script>
<svg width="600" height="200"></svg>