score:4

Accepted answer

In Mike Bostock's post Working with Transitions, he writes that:

For a given element, transitions are exclusive: only one transition can be running on the element at the same time. Starting a new transition on the element stops any transition that is already running.

Now, I detect two problems in the presented code:

  1. The path transform reset is being animated (even with 0 duration). This new transition is cancelling the previous transition. This can be fixed by changing:

    path
      .attr("d", line)
      .transition()
        .attr("transform", null)
        .duration(0)
    

    to:

    path
      .attr("d", line)
      .attr("transform", null)
    
  2. The animateOnce function is being called with the same period as D3's transition and a transition tick lasts for ~17 ms. This new transition is cancelling the previous transition as well. This can be fixed by changing:

    function animate() {
      setTimeout(_=> {
        animateOnce(PERIOD);
        animate();
      },
      PERIOD);
    }
    

    to:

    function animate() {
      setTimeout(_=> {
        animateOnce(PERIOD);
        animate();
      },
      PERIOD + 20);
    }
    

    which can be further refactored with setInterval to:

    function animate() {
      setInterval(animateOnce, PERIOD + 20, PERIOD);
    }
    

These 2 changes should solve the jank issues. Still, updating that line chart every 80 ms will always be taxing on someone's computer or smartphone. I'd advise you to only update it every 200 ms or so.

EDIT: I did some experimenting and noticed that there was still some jank on Firefox. So, a few more points to take into account:

  1. Setting the transform property to null actually creates a new layer. This can be fixed by changing:

    .attr("transform", null)
    

    to:

    .attr("transform", "translate(0)")
    
  2. The transform strings are being recreated every time the animateOnce function is called. We can precompute them outside of animateOnce and then reuse them. This can be fixed by changing:

    .attr("transform", "translate(0)")
    .attr("transform", `translate(${x(1)})`)
    

    to:

    // outside the animateOnce function:
    let startStepAttrs = { transform: "translate(0)" },
        endStepAttrs   = { transform: `translate(${x(1)})` };
    
    // inside the animateOnce function:
    .attr(startStepAttrs)
    .attr(endStepAttrs)
    

Related Query