# Convert CSS cubic-bezier timing value to d3 custom ease

I am trying to figure out how can I create custom ease for d3 animation.

To elaborate, in CSS, `animation-timing` function is controlled by `cubic-bezier` value.
In my element, I have three rect- red, green, and blue.

`red` rect is moved with an explicit cubic-bezier value `animation-timing-function: cubic-bezier(0.42, 0, 1, 1);`.

For `green` rect I have explicitly calculated the `keyFrame` `time%` and `progress` using the following, given a cubic-bezier value. By doing this, I am explicitly telling the `green` rect to move as per the calculation. Also, by doing this, I can convert a CSS cubic-bezier value to the penner’s equation `(t,b,c,d) form.

`````` // create percentage container
const pct = [];

for (let i = 0; i <= 100; i++) {
pct.push(i / 100);
}

//cubic-bezier
//const cubicBezCurvVal = "0.42, 0, 1, 1"

//split bezier curve value
var cleanVal = cubicBezCurvVal.split(",");
//clean space with map -retunrns new array with the function, original array unchnaged
var cleanVal = cleanVal.map((x) => parseFloat(x.replace(/ /g, "")));

//p0
const p0 = {
x: 0,
y: 0
};
//p3
const p3 = {
x: 1,
y: 1
};
//p1
const p1 = {
x: cleanVal,
y: cleanVal
};
//p2
const p2 = {
x: cleanVal,
y: cleanVal
};

const x0 = p0.x; //=0
const y0 = p0.y; //=0

const x1 = p1.x;
const y1 = p1.y;

const x2 = p2.x;
const y2 = p2.y;

const x3 = p3.x; //=1
const y3 = p3.y; //=1

/*given a time percentage, calculates the x-axis of the cubic bezier graph, i.e. time elpased% */
const x = (t) =>
Math.pow(1 - t, 3) * x0 +
3 * Math.pow(1 - t, 2) * t * x1 +
3 * (1 - t) * Math.pow(t, 2) * x2 +
Math.pow(t, 3) * x3;

/*given a time percentage, calculates the y-axis of the cubic bezier graph, i.e. progres% */
const y = (t) =>
Math.pow(1 - t, 3) * y0 +
3 * Math.pow(1 - t, 2) * t * y1 +
3 * (1 - t) * Math.pow(t, 2) * y2 +
Math.pow(t, 3) * y3;

//penner's easing equation p=f(t)
const c = width - 50; // c of t,b,c,d of penner's equation
const b = 0; // b of t,b,c,d of penner's equation

//create container
const time = []; //to collect values of x(t), i.e. time elapsed %
const progress = []; //to collect values of y(t), i.e. progress %

//get the time first --- goes into keyframe---not dependent on progress,i.e. y(t)
pct.forEach((a) => {
time.push(x(a));
});

//get the progress for each time --- goes into progress --- not dependent on time x(t)
pct.forEach((a) => {
progress.push(y(a) * c + b);
});
``````

I also have `blue` rect and I was wondering how can translate the same cubic-bezier value into a custom -ease function for d3?

I referred to custom bounce and tried this which did not work unfortunately.

``````function progress1(t) {
//p0
const p0 = {
x: 0,
y: 0
};
//p3
const p3 = {
x: 1,
y: 1
};
//p1
const p1 = {
x: cleanVal,
y: cleanVal
};
//p2
const p2 = {
x: cleanVal,
y: cleanVal
};

const x0 = p0.x; ///0
const y0 = p0.y; ///0

const x1 = p1.x;
const y1 = p1.y;

const x2 = p2.x;
const y2 = p2.y;

const x3 = p3.x; ////1
const y3 = p3.y; ////1

const progress =
Math.pow(1 - t, 3) * y0 +
3 * Math.pow(1 - t, 2) * t * y1 +
3 * (1 - t) * Math.pow(t, 2) * y2 +
Math.pow(t, 3) * y3;

return t >= 1 ? 1 : progress;
}
``````

If you take a closer look at the end of your `progress1()` function you’ll notice that `const progress` does not reference any of the `x` values.

There is a rather popular JS library, bezier-easing, which provides precisely what you are looking for. We can import it like this:

``````bezierEasing = require('bezier-easing@2.1.0/dist/bezier-easing.min.js')
``````

and create a custom easing function:

``````easeBezier = bezierEasing(0.42, 0, 1, 1)
``````

To verify that this works as intended I suggest that you plot the values, for example with:

``````Plot.line(
d3.range(0, 1, .01).map(t => ({x: t, y: easeBezier(t)})),
{x: 'x', y: 'y'}
).plot()
``````

which produces

If you need more complex easings without wanting to use multistep transitions, you might also be interested in

It provides a helper to rescale time offsets. I realize the documentation is sparse, so if you have any questions don’t hesitate to ask!

@mootari thanks for this, how can I use this library in d3.

Yes, I understood that and I thought I was passing at `y=f(x)` but that is not the case. The way I am passing it on CSS, is a `parametric` form `where both x and y are functions of t (i.e. x=f(t) and y=f(t))` of cubic-bezier curve whereas I probably need an equation of cubic-bezier in `explicit ` form `where y=f(x)`

Also, I don’t think (I could be wrong) if it gets resolved, it can’t be done using `d3.transition`, rather it needs to use `d3.attrTween`. I came across this.

So my best guess is, as long as an explicit form of cubic-bezier can be utilized, it might be worth taking a shot by using `attrTween` with a custom interpolator fed with explicit equation.

You import the offset helper:

``````import {weightedOffset} from 'https://observablehq.com/@mootari/stitched-easings'
``````

then create your own easing function. Here is an (unoptimized) example that you can experiment with:

``````function myEase(t) {
const mix = (a, b, t) => a * (1 - t) + b * t;
const steps = [
// We use mix() here to scale and offset each ease vertically.
[.3, t => mix(0, .5, d3.easeCubicIn(t))],
[.7, t => mix(.5, 1, d3.easeBounceInOut(t))],
];
const weights = steps.map(s => s);
const eases = steps.map(s => s);

const [i, tLocal] = weightedOffset(t, weights);
return eases[i](tLocal);
}
``````

If you plot it:

``````Plot.line(
d3.range(0, 1, .01).map(t => ({t, v: myEase(t)})),
{x: 't', y: 'v'}
).plot({y: {domain: [0, 1]}})
``````

it produces the following output:

That sounds reasonable to me, although I don’t have much experience with either the evaluation of bezier curves or d3-transition.

Note that you can find the documentation on GitHub and Observable.

An alternative implementation of this easing is available at Cubic Bezier Easing (#3) / D3 / Observable — it could be merged into d3-ease (with a bit of clean-up work).

2 Likes

That would be amazing