Quiero hacer la transición a través de diferentes conjuntos de datos y pensé que el siguiente enfoque funcionaría:

function next(keyIndex)
{
    if(keyIndex < 3)
    {
        d3.selectAll(".dot")
           .data(dataForKey(keyIndex))
           .transition().duration(3000)
           .call(position)
           .on("end", next(keyIndex+1));
    }
}

Pero la llamada .on("end") se invoca antes de que duration(3000) finalice, por lo que los datos básicamente pasan del primer al último conjunto de datos.
¿Hay alguna forma de realizar una transición adecuada desde dataset[0] -> dataset[1] -> dataset[2]?

4
TommyF 15 dic. 2016 a las 12:42

2 respuestas

La mejor respuesta

Su principal problema es que no proporciona una función de devolución de llamada adecuada a .on("end",...). Al llamarlo como .on("end", next(keyIndex+1)), no funcionará como se esperaba, porque la instrucción next(keyIndex+1) se ejecutará inmediatamente, por lo que comenzará el siguiente paso de iteración antes de que finalice el paso anterior. En el siguiente paso, la transición, que acaba de comenzar, será cancelada y reemplazada por la nueva transición. De esa manera, no tendrá una cadena de transiciones que tengan las duraciones especificadas, sino algunas interrumpidas inmediatamente seguidas de una última, no interrumpida, que hará la transición hasta los valores finales.

La solución es envolver su llamada a next() en una expresión de función para proporcionar una devolución de llamada real que se llamará cuando se active el evento 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
  }
}

Para una demostración funcional, eche un vistazo al siguiente fragmento:

   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>
4
altocumulus 15 dic. 2016 a las 13:55

Nota , iba a cerrar esto como un duplicado porque creo que entiendo el error del OP. Creé una respuesta, en cambio, porque solo estoy adivinando el problema y quería dar una pequeña explicación.


Su afirmación de que .on("end") call gets invoked before the duration(3000) es incorrecta. De hecho, la devolución de llamada final se activa después de 3 segundos. Supongo que no comprende esta declaración en la documentación:

Cuando se distribuye un evento de transición especificado en un nodo seleccionado, el oyente especificado se invocará para el elemento de transición, y se le pasará el dato actual d y el índice i, con el contexto this como el elemento DOM actual. Los oyentes siempre ven el último dato de su elemento, pero el índice es una propiedad de la selección y se fija cuando se asigna el oyente; para actualizar el índice, reasigne el oyente.

Lo que esto significa es que el evento se activará para cada elemento en la transición, por lo que 10 elementos significan 10 eventos. El primero ocurrirá después de 3 segundos, el resto de 0 a 1 milisegundo después.

Ver este fragmento de código:

<!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>

La salida de la consola que verá será algo como:

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

Ahora creo que su pregunta es, ¿cómo puedo activar una devolución de llamada después de que hayan finalizado todas las transiciones . Esto se ha cubierto muchas veces aquí.

EDICIONES

Gracias por el violín, siempre ayuda a reproducir el problema. Y el código es diferente en el violín que lo que publicaste en la pregunta.

Su problema es mucho más simple; .on('end', func) espera una función como segundo argumento y no le está dando una función, está llamando a una función y le está dando el valor de retorno de la función (nada). Simplemente envuélvalo en una función anónima:

.on("end", function(d){
  next(keyIndex + 1)
});

fiddle actualizado.

3
Community 23 may. 2017 a las 12:30