我希望转换不同的数据集,并认为以下方法可行:
function next(keyIndex)
{
if(keyIndex < 3)
{
d3.selectAll(".dot")
.data(dataForKey(keyIndex))
.transition().duration(3000)
.call(position)
.on("end", next(keyIndex+1));
}
}
但.on("end")
调用在duration(3000)
完成之前被调用,因此数据基本上从第一个数据集转换到最后一个数据集。
有没有办法从dataset[0] -> dataset[1] -> dataset[2]
正确过渡?
答案 0 :(得分:3)
您的主要问题是,您没有向.on("end",...)
提供正确的回调功能。当它像.on("end", next(keyIndex+1))
一样调用时,它将无法正常工作,因为语句next(keyIndex+1)
将立即执行,从而在上一步结束之前开始下一个迭代步骤。在下一步中,刚刚开始的过渡将被取消并由新过渡取代。这样你就不会有一个具有指定持续时间的转换链,但是一些中断的转换后面紧跟一个最后的,不间断的转换,它将一直转换到最终值。
解决方案是将您对next()
的调用包装在一个函数表达式中,以提供一个真正的回调函数,该函数将在 end 事件触发时调用。
function next(keyIndex) {
if(keyIndex < 3) {
d3.selectAll(".dot")
.data(dataForKey(keyIndex))
.transition().duration(3000)
.call(position)
.on("end", function() { next(keyIndex+1) }); // use a function wrapper
}
}
对于工作演示,请查看以下代码段:
var svg = d3.select("svg"),
margin = {top: 40, right: 40, bottom: 40, left: 40},
width = svg.attr("width") - margin.left - margin.right,
height = svg.attr("height") - margin.top - margin.bottom,
g = svg.append("g").attr("transform", "translate(" + margin.left + "," + margin.top + ")");
var xScale = d3.scaleLinear().domain([0, 10]).range([0, width]),
yScale = d3.scaleLinear().domain([0, 10]).range([height, 0]);
var xAxis = d3.axisBottom().scale(xScale).ticks(12, d3.format(",d")),
yAxis = d3.axisLeft().scale(yScale);
svg.append("g")
.attr("class", "x axis")
.attr("transform", "translate(0," + height + ")")
.call(xAxis);
svg.append("g")
.attr("class", "y axis")
.call(yAxis);
// inital draw
var dot = svg.append("g")
.attr("class", "dots")
.selectAll(".dot")
.data(dataForKey(1))
.enter().append("circle")
.attr("class", "dot")
.style("fill", "#000000")
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.attr("r", function(d) { return 5; });
// updated Data
next(1);
function dataForKey(key)
{
return [{x:key, y:key}];
}
function next(keyIndex) {
if (keyIndex < 10) {
d3.selectAll(".dot")
.data(dataForKey(keyIndex))
.transition().duration(3000)
.attr("cx", function(d) { return xScale(d.x); })
.attr("cy", function(d) { return yScale(d.y); })
.on("end", function() { next(keyIndex + 1) });
}
}
<script src="https://d3js.org/d3.v4.js"></script>
<svg width="300" height="300"></svg>
答案 1 :(得分:2)
注意,我打算将其作为副本关闭,因为我认为我理解OP的错误。相反,我创建了一个答案,因为我只是在猜测这个问题并希望提供一些解释。
您认为.on("end") call gets invoked before the duration(3000)
不正确的论点。事实上,最终回调确实会在3秒后触发。我的猜测是你很想理解documentation中的这句话:
在选定节点上调度指定的转换事件时, 将为转换元素调用指定的侦听器, 传递当前数据d和索引i,使用此上下文作为 当前的DOM元素。听众总能看到最新的数据 他们的元素,但索引是选择的属性,是 在分配监听器时修复;更新索引,重新分配 听众。
这是说该事件将在转换中为每个元素触发,因此10个元素表示10个事件。第一个将在3秒后发生,其余时间为0到1毫秒。
请参阅以下代码段:
<!DOCTYPE html>
<html>
<head>
<script data-require="d3@4.0.0" data-semver="4.0.0" src="https://d3js.org/d3.v4.min.js"></script>
</head>
<body>
<svg>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
<circle class="dot"></circle>
</svg>
<script>
dataForKey = {
0: d3.range(10),
1: d3.range(10),
2: d3.range(10),
3: d3.range(10)
};
var date = new Date();
next(1);
function next(keyIndex) {
if (keyIndex < 3) {
d3.selectAll(".dot")
.data(dataForKey[keyIndex])
.transition().duration(3000)
//.call(position)
.on("end", function() {
console.log('end=', new Date().getTime() - date.getTime());
date = new Date();
next(keyIndex + 1)
});
}
}
</script>
</body>
</html>
您将看到的控制台输出类似于:
end= 3008 //<-- first element after 3 seconds
end= 0 //<-- other 9 events after 0 to 1 milliseconds
end= 0
end= 0
end= 0
end= 0
end= 0
end= 0
end= 0
end= 0
现在我认为你的问题变成了,如何在所有转换结束后触发回调 。许多times here已涵盖这一点。
<强> EDITS 强>
感谢小提琴,总是有助于重现这个问题。 然后代码在小提琴中与你在问题中发布的内容不同。
你的问题实际上要简单得多; .on('end', func)
期望一个函数作为第二个参数而你没有给它一个函数,你是调用一个函数并给它函数的返回值(无)。只需将其包装在一个匿名函数中:
.on("end", function(d){
next(keyIndex + 1)
});
更新了fiddle。