我正在将https://ui.netbeans.org/docs/ui_apis/dide/index.html修改为多行,并将其更新为使用v4。 this example可以使用,但是我的更新功能的递归方式特别不完善:
function repeat() {
paths.each(d => d.push(random()));
paths.attr("d", line)
.attr("transform", null)
.transition()
.attr("transform", "translate(" + x(0) + ")")
.duration(750)
.ease(d3.easeLinear);
paths.each(d => d.shift());
d3.select({}).transition().duration(750).on('end', repeat);
}
请注意在空选择上的过渡只是为了在两次调用之间创建750毫秒的超时。我想改成这样:
function repeat() {
paths.each(d => d.push(random()));
paths.attr("d", line)
.attr("transform", null)
.transition()
.attr("transform", "translate(" + x(0) + ")")
.duration(750)
.ease(d3.easeLinear)
.on('end', repeat);
paths.each(d => d.shift());
}
但是,这导致对repeat
的多个并发调用,选择中的每个元素一个。
有没有一种方法可以简化代码以摆脱多余的过渡?似乎必须有一种更清洁的方法。
答案 0 :(得分:4)
D3转换可能会有些棘手,因为结束事件在每个元素的转换结束时触发。没有transition.on("endAll", ...
,因此,由于您不想在转换结束时触发两次重复功能,因此您将使用空转换来延迟调用重复。
您可以使用计数器来查看上一次转换的完成时间,但这也不太干净。最终,当管理具有分别作用于元素的过渡的多个事件时,您将得到不太优雅的代码。
相反,由于d3转换的每个元素的计时器和过渡事件都是特定于该元素的(即使将它们设置为一个组,每个元素也具有自己的计时器和事件),因此请创建一个特定于每个元素:
function scroll(d) {
d.push(Math.random());
// Transition
d3.select(this).attr("transform",null)
.attr("d",line)
.transition()
.attr("transform", "translate(" + x(0) + ")")
.duration(750)
.ease(d3.easeLinear)
.on("end",scroll); // repeat
d.shift();
}
并使用selection.each(scroll);
进行调用现在,我们通过一次处理选择项中一项的功能来管理每个过渡(分别处理选择项中的每个项)。
这使您可以:
d
调用此函数(而不是在函数中两次使用.each()
),则直接访问paths.each(...
其他注意事项:
您可以将此功能应用于您选择的任何一组路径,而无需修改功能本身。
要转换的元素为this
在这里工作:
let n = 40;
let random = d3.randomUniform(-1, 1);
let data = [d3.range(n).map(random), d3.range(n).map(random)];
let margin = {top: 6, right: 0, bottom: 6, left: 40};
let width = 960 - margin.right;
let height = 120 - margin.top - margin.bottom;
let x = d3.scaleLinear()
.domain([1, n - 2])
.range([0, width]);
let y = d3.scaleLinear()
.domain([-1, 1])
.range([height, 0]);
let line = d3.line()
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); })
.curve(d3.curveBasis);
let svg = d3.select("body").append("p").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("margin-left", -margin.left + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y).ticks(5));
let paths = svg.append('g')
.attr('id', 'lines')
.attr('clip-path', 'url(#clip)')
.selectAll('path').data(data).enter()
.append('path')
.attr('class', 'line')
.attr('stroke', (d, i) => d3.schemeCategory10[i])
.attr('d', line)
.each(scroll);
function scroll(d) {
d.push(random());
d3.select(this).attr("transform",null)
.attr("d",line)
.transition()
.attr("transform", "translate(" + x(0) + ")")
.duration(750)
.ease(d3.easeLinear)
.on("end",scroll);
d.shift();
}
#lines {
fill: none;
stroke: black;
stroke-width: 1.5px;
}
.y.axis path {
stroke: black;
}
p {
padding: 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/4.10.0/d3.min.js"></script>
答案 1 :(得分:1)
注意:这是D3 v5 答案!尽管该问题使用的是v4,但可能会为将来的参考提供帮助。
从v1.2.0模块的d3-transition开始,有transition.end()
返回一个承诺,该承诺在每个选定元素完成过渡时都会解决。
从D3 v5.8.0开始,它被引入了主捆绑包。这是安德鲁·里德(Andrew Reid)在他的answer中描述为endAll
事件的现代实现,即,一旦选择的所有转换都结束后便触发的事件。
您的repeat()
函数可以轻松地重写为:
function repeat() {
paths.each(d => d.push(random()));
paths.attr("d", line)
.attr("transform", null)
.transition()
.attr("transform", "translate(" + x(0) + ")")
.duration(750)
.ease(d3.easeLinear)
.end() // Get the promise.
.then(() => { // Once resolved when all transitions have ended...
paths.each(d => d.shift()); // ...shift the data array...
repeat(); // ...and start a new iteration.
});
}
看看下面的演示,看看它的作用:
let n = 40;
let random = d3.randomUniform(-1, 1);
let data = [d3.range(n).map(random), d3.range(n).map(random)];
let margin = {top: 6, right: 0, bottom: 6, left: 40};
let width = 960 - margin.right;
let height = 120 - margin.top - margin.bottom;
let x = d3.scaleLinear()
.domain([1, n - 2])
.range([0, width]);
let y = d3.scaleLinear()
.domain([-1, 1])
.range([height, 0]);
let line = d3.line()
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); })
.curve(d3.curveBasis);
let svg = d3.select("body").append("p").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("margin-left", -margin.left + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y).ticks(5));
let paths = svg.append('g')
.attr('id', 'lines')
.attr('clip-path', 'url(#clip)')
.selectAll('path').data(data).enter()
.append('path')
.attr('class', 'line')
.attr('stroke', (d, i) => d3.schemeCategory10[i]);
repeat();
function repeat() {
paths.each(d => d.push(random()));
paths.attr("d", line)
.attr("transform", null)
.transition()
.attr("transform", "translate(" + x(0) + ")")
.duration(750)
.ease(d3.easeLinear)
.end()
.then(() => {
paths.each(d => d.shift());
repeat();
})
}
#lines {
fill: none;
stroke: black;
stroke-width: 1.5px;
}
.y.axis path {
stroke: black;
}
p {
padding: 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.15.0/d3.js"></script>
或者,使用ES8 async/await语法重写:
let n = 40;
let random = d3.randomUniform(-1, 1);
let data = [d3.range(n).map(random), d3.range(n).map(random)];
let margin = {top: 6, right: 0, bottom: 6, left: 40};
let width = 960 - margin.right;
let height = 120 - margin.top - margin.bottom;
let x = d3.scaleLinear()
.domain([1, n - 2])
.range([0, width]);
let y = d3.scaleLinear()
.domain([-1, 1])
.range([height, 0]);
let line = d3.line()
.x(function(d, i) { return x(i); })
.y(function(d, i) { return y(d); })
.curve(d3.curveBasis);
let svg = d3.select("body").append("p").append("svg")
.attr("width", width + margin.left + margin.right)
.attr("height", height + margin.top + margin.bottom)
.style("margin-left", -margin.left + "px")
.append("g")
.attr("transform", "translate(" + margin.left + "," + margin.top + ")");
svg.append("defs").append("clipPath")
.attr("id", "clip")
.append("rect")
.attr("width", width)
.attr("height", height);
svg.append("g")
.attr("class", "y axis")
.call(d3.axisLeft(y).ticks(5));
let paths = svg.append('g')
.attr('id', 'lines')
.attr('clip-path', 'url(#clip)')
.selectAll('path').data(data).enter()
.append('path')
.attr('class', 'line')
.attr('stroke', (d, i) => d3.schemeCategory10[i]);
(async function repeat() {
paths.each(d => d.push(random()));
await paths.attr("d", line)
.attr("transform", null)
.transition()
.attr("transform", "translate(" + x(0) + ")")
.duration(750)
.ease(d3.easeLinear)
.end();
paths.each(d => d.shift());
repeat();
})()
#lines {
fill: none;
stroke: black;
stroke-width: 1.5px;
}
.y.axis path {
stroke: black;
}
p {
padding: 40px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.15.0/d3.js"></script>