单次事件可进行多个转换

时间:2018-06-28 16:20:25

标签: d3.js

我正在将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的多个并发调用,选择中的每个元素一个。

有没有一种方法可以简化代码以摆脱多余的过渡?似乎必须有一种更清洁的方法。

2 个答案:

答案 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>