How do I reset an animation using the d3.js and waypoints.js libraries?

166 Views Asked by At

I am building a data visualization using both d3.js (v3) & waypoints.js (scrollytelling library). While the animation works fine when the user scrolls down, I would like my animation to reset once/if the user decides to scroll back up. Ideally if the user scrolls back up before/after the animation has completed, the data visualization resets entirely and restarts again.

Does anyone have any experience using both of these libraries together?

Here is my code:

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<script src="https://d3js.org/d3.v3.min.js" type="text/javascript"></script>
<script 
src="https://cdnjs.cloudflare.com/ajax/libs/waypoints/4.0.1/noframework.waypoints.js"> 
</script>
<script 
src="https://cdnjs.cloudflare.com/ajax/libs/waypoints/4.0.1/jquery.waypoints.min.js"> 
</script>
<script 
src="https://cdnjs.cloudflare.com/ajax/libs/waypoints/4.0.1/shortcuts/sticky.min.js"> 
</script>

<div id="arcContainer">
 <div id="arcOne"></div>
</div>



function renderOne(innerRadius) {
 var dbl = innerRadius * 2;
 var width = 1000, //1250,
 viewbox = `0 0 ${dbl} ${dbl}`,
 height = 1000, //1250,
 colors = d3.scale.category20();

 var svg = d3.select("#arcOne").append("svg")
  .attr("width", width)
  .attr('viewBox', viewbox);

 var svgContainer = d3.select("#arcContainer").append("svg")
  .attr("width", 2000)
  .attr('viewBox', viewbox);

 var dataArc = [
  {startAngle: -0.5 * Math.PI, endAngle: 0.5 * Math.PI},
 ];

 var arc = d3.svg.arc().outerRadius(995).innerRadius(innerRadius);

 svg.select("g").remove();

 var path = svg.append("g")      
  .selectAll("path.arc")
    .data(dataArc);
    
  path.enter()
    .append("path")
    .attr("transform", `translate(${innerRadius},${innerRadius})`) //625,625
      .attr("id", "arcFirst")
      // .style("stroke", "rgb(53,154,204))")
      .style("stroke", "rgb(0,255,255)")
      .style("stroke-width", 5)
      .style("fill", "black") //"none"
      .style("opacity", 1)
      .attr('d', arc)
      .transition().duration(2000).ease("linear")
      .attrTween("d", function (d) {
          var start = {startAngle: -0.5 * Math.PI, endAngle: -0.5 * Math.PI} // <-A
          var end = d // {startAngle: -0.5 * Math.PI, endAngle: 0.5 * Math.PI}
          var interpolate = d3.interpolate(start, end); // <-B
          return function (t) {
              return arc(interpolate(t)); // <-C
          };
      })
      
  path.enter()
   .append('circle')
    .attr("transform", `translate(${innerRadius},${innerRadius})`) //625,625
    //.attr("cx", d => arc.centroid(d)[0]) // Set the cx
    //.attr("cy", d => arc.centroid(d)[1])
    .attr("id", "ballFirst")
    .transition()
    .delay(2000)
    .duration(2000)
    .attr('r', 20)
    .style("fill", "rgb(0,255,255)")
    .ease("linear")
    .attrTween("pathTween", function (d) {
        const startAngle = d.startAngle;
        const endAngle = d.endAngle;
        const start = {startAngle, endAngle: startAngle} // <-A
        const end = {startAngle: endAngle, endAngle}
        //console.log(start,end)
        const interpolate = d3.interpolate(start, end); // <-B
        const circ = d3.select(this) // Select the circle
        return function (t) {
            const cent = arc.centroid(interpolate(t)); // <-C         
            //return cent[0]
            circ
              .attr("cx", cent[0]) // Set the cx
              .attr("cy", cent[1]) // Set the cy                
        };
    })        
    .transition()
    .delay(4000)
    .duration(2000)
    .attr('r', 20)
    .style("fill", "rgb(0,255,255)")
    .ease("linear")
    .attrTween("pathTween", function (d) {
        const startAngle = d.startAngle;
        const endAngle = d.endAngle;
        const start = {startAngle, endAngle: startAngle} // <-A
        const end = {startAngle: endAngle, endAngle}
        //console.log(start,end)
        const interpolate = d3.interpolate(start, end); // <-B
        const circ = d3.select(this) // Select the circle
        return function (t) {
            const cent = arc.centroid(interpolate(t)); // <-C         
            //return cent[0]
            circ
              .attr("cx", cent[0]) // Set the cx
              .attr("cy", cent[1]) // Set the cy                
        };
    })        
 }


var waypointFour = new Waypoint({
  element: document.getElementById('arcContainer'),
  handler: function(direction) {
  renderOne(1000);
  },
  offset: '50%'
})

Here is my fiddle:

https://jsfiddle.net/bullybear/xLcvqjao/13/

1

There are 1 best solutions below

0
gherka On

I would suggest creating separate functions for resetting and starting your animation, rather than having one function for the entire viz. This way, you can trigger them as and when conditions are right, using waypoints and the direction parameter you get for "free". You can play with offset values to find the balance for when it's feels "natural" to reset the visualisation.

Here's a fiddle with a more basic example showing the two function approach with multiple waypoints.