1

I'm having trouble getting a transition to repeat, for a series of elements, in this case a set of three lines. The animation runs just fine once, but when it is repeated (with the same data), all three lines merge into a single line (the last array in data). What am I doing wrong?

(function() {
    var w = 100, h = 100
    var div = d3.select('#sketches').append('div')
    var svg = div.append("svg")
        .attr("width", w)
        .attr("height", h)

    var x = 0, y = 55

    var data = [
        [x, y, x+20, y-40],
        [x+10, y, x+30, y-40],
        [x+20, y, x+40, y-40]
    ];

    (function lines() {
            svg.selectAll('line')
                .data(data).enter().append('line')
                .attr("stroke", "black")
                .attr('x1', function(d) {return d[0]})
                .attr('y1', function(d) {return d[1]})
                .attr('x2', function(d) {return d[2]})
                .attr('y2', function(d) {return d[3]})
                .transition()
                .duration(3000)
                .ease('linear')
                .attr('x1', function(d) {return d[0] + 60})
                .attr('y1', function(d) {return d[1]})
                .attr('x2', function(d) {return d[2] + 60})
                .attr('y2', function(d) {return d[3]})
                .each('end', function(d) {
                    d3.select(this).remove()
                    lines()
                })
    })()
})()
body {
  padding: 1rem;
}
svg {
  background-color: silver;
  stroke: black;
  stroke-width: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

<div id="sketches"></div>
allanberry
  • 7,325
  • 6
  • 42
  • 71

1 Answers1

1

The issue is the each function will initiate for each line you have. So actually what you are doing is calling lines() three times every time. Why it's yieling the output of one line I'm not entirely sure (still looking into it) but for some reason, it seems like data defaults to the last array so its only setting the drawing to be based on data[3].

To fix it, you want to make sure lines() only gets called after it has finished going through removing all the lines so it only runs once. I'm pretty sure there is better way (i.e. a promise of some kind so after all of each has ran, it'll run a function, but what you can do is set a count and then just run lines() every N times where N is the number of lines you want removed. Because you go through array data and append a line for each index, N is data.length.

(I'm gonna see if there's a cleaner way to do this and I'll edit my answer if I find a way but hopefully this helps you understand the issue at the very least)

(function() {
    var w = 100, h = 100
    var div = d3.select('#sketches').append('div')
    var svg = div.append("svg")
        .attr("width", w)
        .attr("height", h)

    var x = 0, y = 55

    var data = [
        [x, y, x+20, y-40],
        [x+10, y, x+30, y-40],
        [x+20, y, x+40, y-40]
    ];
    
    var count = 0;
    (function lines() {
            svg.selectAll('line')
                .data(data).enter().append('line')
                .attr("stroke", "black")
                .attr('x1', function(d) {return d[0]})
                .attr('y1', function(d) {return d[1]})
                .attr('x2', function(d) {return d[2]})
                .attr('y2', function(d) {return d[3]})
                .transition()
                .duration(3000)
                .ease('linear')
                .attr('x1', function(d) {return d[0] + 60})
                .attr('y1', function(d) {return d[1]})
                .attr('x2', function(d) {return d[2] + 60})
                .attr('y2', function(d) {return d[3]})
                .each('end', function(d) {
                    d3.select(this).remove()
                    count++;
                    if (count == data.length) {
                      count = 0;
                      lines();
                    }
                })
    })()
})()
body {
  padding: 1rem;
}
svg {
  background-color: silver;
  stroke: black;
  stroke-width: 1;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/3.4.11/d3.min.js"></script>

<div id="sketches"></div>
aug
  • 11,138
  • 9
  • 72
  • 93
  • Thanks, this solves it, and now the `each()` call problem seems obvious. Also, since you've given me the right terms to Google, I see at least one other user corroborates your method: http://stackoverflow.com/a/23119420/652626 – allanberry Feb 23 '16 at 16:12
  • 1
    @niteshade yeah after looking into it more, I found this is the practice that Mike Bostock recommends. See [this answer](http://stackoverflow.com/a/20773846/1168661) – aug Feb 23 '16 at 17:36