Nivel
Nivel

Reputation: 679

D3.js repeat transition on each element with different durations

I am trying to transform dots along a circular path. Each transform has a different duration. On end the transition start again for all dots once the transform with the shortest speed duration ends. How do I re-start the transition for each dot independently?

const graphWidth = 500;
const graphHeight = 500;
const svg = d3.select("svg")
  .attr("width", graphWidth)
  .attr("height", graphHeight);
const testData = [{
    "country": "Netherlands",
    "speed": 4000
  },
  {
    "country": "Belgium",
    "speed": 2000
  },
  {
    "country": "Austria",
    "speed": 1000
  },
  {
    "country": "Germany",
    "speed": 5000
  },
  {
    "country": "USA",
    "speed": 4000
  }
];

const dots = svg.selectAll('circle')
  .data(testData)
  .enter()
  .append('circle')
  .attr('transform', `translate(${graphWidth / 2}, ${graphHeight / 2})`)
  .attr('cx', 223)
  .attr('r', 5)
  .attr('fill', 'yellow')
  .attr('stroke', 'black');


function transition() {
  svg.selectAll('circle').each(function(d) {
    d3.select(this)
      .transition()
      .ease(d3.easeLinear)
      .duration(d.speed)
      .attrTween('transform', rotTween)
      .on('end', transition);
  })
}
transition();

function rotTween() {
  var i = d3.interpolate(0, 360);
  return function(t) {
    return `translate(${graphWidth / 2}, ${graphHeight / 2}) rotate(${i(t)}, 0, 0)`;
  };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

Upvotes: 1

Views: 154

Answers (1)

Ruben Helsloot
Ruben Helsloot

Reputation: 13129

The easiest way is to change your transition function to handle just one transition of one circle, by taking the .each() out of the function:

const graphWidth = 500;
const graphHeight = 500;
const svg = d3.select("svg")
  .attr("width", graphWidth)
  .attr("height", graphHeight);
const testData = [{
    "country": "Netherlands",
    "speed": 4000
  },
  {
    "country": "Belgium",
    "speed": 2000
  },
  {
    "country": "Austria",
    "speed": 1000
  },
  {
    "country": "Germany",
    "speed": 5000
  },
  {
    "country": "USA",
    "speed": 4000
  }
];

const dots = svg.selectAll('circle')
  .data(testData)
  .enter()
  .append('circle')
  .attr('transform', `translate(${graphWidth / 2}, ${graphHeight / 2})`)
  .attr('cx', 223)
  .attr('r', 5)
  .attr('fill', 'yellow')
  .attr('stroke', 'black');


function transition(circle, d) {
  d3.select(circle)
    .transition()
    .ease(d3.easeLinear)
    .duration(d.speed)
    .attrTween('transform', rotTween)
    .on('end', function() {
      transition(circle, d);
    });
}

svg.selectAll('circle').each(function(d) {
  transition(this, d);
});

function rotTween() {
  var i = d3.interpolate(0, 360);
  return function(t) {
    return `translate(${graphWidth / 2}, ${graphHeight / 2}) rotate(${i(t)}, 0, 0)`;
  };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

Alternatively, you can name the otherwise anonymous function you pass to .each() (in this case I named it repeat), and then call that function again in the .on("end" clause

const graphWidth = 500;
const graphHeight = 500;
const svg = d3.select("svg")
  .attr("width", graphWidth)
  .attr("height", graphHeight);
const testData = [{
    "country": "Netherlands",
    "speed": 4000
  },
  {
    "country": "Belgium",
    "speed": 2000
  },
  {
    "country": "Austria",
    "speed": 1000
  },
  {
    "country": "Germany",
    "speed": 5000
  },
  {
    "country": "USA",
    "speed": 4000
  }
];

const dots = svg.selectAll('circle')
  .data(testData)
  .enter()
  .append('circle')
  .attr('transform', `translate(${graphWidth / 2}, ${graphHeight / 2})`)
  .attr('cx', 223)
  .attr('r', 5)
  .attr('fill', 'yellow')
  .attr('stroke', 'black');

svg.selectAll('circle')
  .each(function repeat(d) {
    d3.select(this)
      .transition()
      .ease(d3.easeLinear)
      .duration(function(d) { return d.speed; })
      .attrTween('transform', rotTween)
      .on("end", repeat);
  });

function rotTween() {
  var i = d3.interpolate(0, 360);
  return function(t) {
    return `translate(${graphWidth / 2}, ${graphHeight / 2}) rotate(${i(t)}, 0, 0)`;
  };
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/d3/5.7.0/d3.min.js"></script>
<svg></svg>

Upvotes: 1

Related Questions