🏠 back to Observable

Gradient transition based on data rank values

Hello!

I am trying to transition the gradient stop attribute based on the rank values in the data. Right now, the transition is delayed by index values. I would like each curve with the highest rank to move faster than others each month and the subsequent curves to move slower based on their ranking. That way each curve should speed up or slow down based on their ranks each month.

Is there a way to achieve this?

Here’s a link to my notebook.

That looks difficult to me, but I’m not an expert.

This segment controls the rendering of the animation, the easing is currently linear so you might what to try changing that. GitHub - d3/d3-ease: Easing functions for smooth animation.

  gradient.select("stop")
    .transition()
    .duration(10000)
    .delay((d,i)=>i*800)
    .ease(d3.easeLinear)
    .attr("stop-color",d=>color(d[0]))
    .attr("offset","100%")

The .duration(10000) could be changed for each line by making it a function
like .duration((d,i)=>i*2000)

1 Like

Could this notebook example be applied to my notebook? Would implementing the keyframes with svg transition help in achieving the path transition race? Any pointers would be helpful, thank you! Bar Chart Race, Explained / D3 / Observable

I’m not sure the datasets are alike. It might take a bit of data wrangling to get it converted but then it should work.

How did adjusting the duration go?

  gradient.select("stop")
    .transition()
     .duration((d,i)=>time[i])
    .delay(200)
    .ease(d3.easeLinear)
    .attr("stop-color",d=>color(d[0]))
    .attr("offset","100%")

time = [8000,9000,8000,5000,3000] // these times could be computed or precalculated to suit your needs

Hello Brett!

Yes, I have wrangled my dataset to match the keyframes data structure. But somehow, the paths do not appear, I’m making an error in the data join part. Anyway, I’m still trying to figure it out.

Yes I did try the duration change but it’s not giving me the desired effect I’m looking for. Thank you for your response, I really appreciate you trying to help me with this. :smiley:

Feel free to share a link on where you are upto and someone make figure it out

1 Like

I think you’ll need a custom ease function:

  1. For simplicity, let’s assume your values are a time series with constant time steps.
  2. For each distance between two data points n and n+1 you determine the (relative) duration it should take for the animation to pass along that distance.
  3. Since you want the speed to increase when y increases, you can simply take the absolute value as duration.
  4. We can get the time steps via d3.cumsum(), but before doing so they need to be rescaled so that there are no negative values. While we’re at it, we rescale to [1, .1], where .1 is the minimum duration a step can have. In summary: domain = d3.extent(values), range = [1, .1].
  5. Now we create a running total from the individual durations.
  6. We also need to ensure that our running total stays in the [0…1] range, so we rescale it again to [0…1].
  7. Lastly we need an interpolator that maps from [0…1] to our actual duration at that point in time. We can make our life easy by using d3.interpolateBasis(runningTotal).
2 Likes

Here’s an example of what that might look like (blue = linear easing, red = custom easing):

Kapture 2022-01-07 at 20.27.03

Hm, interpolateBasis is actually not the right choice. Instead you’d need something like

return t => {
    let i = d3.bisectRight(offsets, t);
    const a = runningTotal[i-1];
    const b = runningTotal[i];
    if(b !== a) i += (t - a) / (b - a);
    return (i-1) / maxIndex;
  };

which will look like this:

Kapture 2022-01-07 at 21.05.33

1 Like

Thank you! I’ll try this out! :smiley: