Published unlisted
Edited
Jul 19, 2022
Insert cell
# Custom Easing in d3
Insert cell
Insert cell
{
const width = 1536;
const height = 720;
const svgns = "http://www.w3.org/2000/svg";
const svg = d3.select("svg");

/*create base svg*/
svg.attr("xmlns", svgns).attr("viewBox", `0 0 ${width} ${height}`);

/*create style*/
const style = d3.select("body").append("style").attr("type", "text/css");

//cubic-bezier(x1, y1, x2, y2) ->x1 and x2 must be between 0 and 1; with negative x1 and x2 provided css reverts to default->0.25, 0.1, 0.25, 1.0
//cubic-bezier --please change the value here to move red and green rect with the exact same velocity
//default-0.42, 0, 1, 1
//easeInSine - 0.12, 0, 0.39, 0; easeOutSine-0.61, 1, 0.88, 1; easeInOutSine-0.37, 0, 0.63, 1 etc.
//0.61, -0.25, 0.88, 1; 0.61, -0.15, 0.88, -1.25 etc.
const cubicBezCurvVal = "0.42, 0, 1, 1";

/*background rect*/
svg
.append("rect")
.attr("class", "vBoxRect")
.attr("width", `${width}`)
.attr("height", `${height}`)
.attr("fill", "#EFEFEF")
.attr("stroke", "black");

/*red rect - css animation+animation timing=> explicit cubic-bezier value*/
svg
.append("rect")
.attr("class", "red")
.attr("x", "0")
.attr("y", `${height / 2 - 40}`)
.attr("height", "50")
.attr("width", "50")
.attr("fill", "red")
.style("--dist", `${width - 50 + "px"}`);

svg
.append("line")
.attr("class", "path1")
.attr("x1", "0")
.attr("y1", () => {
return document.querySelector(".red").y.baseVal.value;
})
.attr("x2", `${width}`)
.attr("y2", () => {
return document.querySelector(".red").y.baseVal.value;
})
.attr("stroke", "red")
.attr("stroke-dasharray", "10");

const keyFrame1 = `
.red {
transform-box: fill-box;
animation-name: move1;
animation-duration: 5s;
animation-iteration-count: 1;
animation-timing-function: cubic-bezier(${cubicBezCurvVal});
animation-direction: normal;
animation-fill-mode: both;
}
@keyframes move1 {
0% {
transform: translateX(0px);
}
100% {
transform: translateX(var(--dist));
}
}
`;
style["_groups"][0][0].innerHTML = keyFrame1;

/*green rect - css animation+animation timing=> calculated progress of cubic-bezier value with javascript*/

svg
.append("rect")
.attr("class", "green")
.attr("x", "0")
.attr("y", `${height / 2 + 40}`)
.attr("height", "50")
.attr("width", "50")
.attr("fill", "green")
.style("--dist", `${width - 50 + "px"}`);

svg
.append("line")
.attr("class", "path2")
.attr("x1", "0")
.attr("y1", () => {
return document.querySelector(".green").y.baseVal.value;
})
.attr("x2", `${width}`)
.attr("y2", () => {
return document.querySelector(".green").y.baseVal.value;
})
.attr("stroke", "green")
.attr("stroke-dasharray", "10");

/*credit - https://blog.maximeheckel.com/posts/cubic-bezier-from-math-to-motion/*/

// 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[0],
y: cleanVal[1]
};
//p2
const p2 = {
x: cleanVal[2],
y: cleanVal[3]
};

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);
});
//generate keyFrame string
var str = "@keyframes move{";

for (let i = 0; i < time.length; i++) {
var styleStr = `${time[i] * 100}%{ transform:translateX(${
progress[i]
}px);}`;
str += styleStr;
}

const keyFrame2 = `.green {
transform-box: fill-box;
animation-name: move;
animation-duration: 5s;
animation-iteration-count: 1;
animation-timing-function: linear;
animation-direction: normal;
animation-fill-mode: both;
}
${str}}
`;

style["_groups"][0][0].innerHTML += keyFrame2;

/*blue rect for d3*/

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

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;
}

svg
.append("rect")
.attr("class", "blue")
.attr("x", "0")
.attr("y", `${height / 2 + 120}`)
.attr("height", "50")
.attr("width", "50")
.attr("fill", "blue")
.each(function (d, i) {
d3.select(this)
.transition()
.duration(5000)
// .ease(progress1(d))
.attr("x", `${width - 50}`);
});

svg
.append("line")
.attr("class", "path3")
.attr("x1", "0")
.attr("y1", () => {
return document.querySelector(".blue").y.baseVal.value;
})
.attr("x2", `${width}`)
.attr("y2", () => {
return document.querySelector(".blue").y.baseVal.value;
})
.attr("stroke", "blue")
.attr("stroke-dasharray", "10");
}
Insert cell

Purpose-built for displays of data

Observable is your go-to platform for exploring data and creating expressive data visualizations. Use reactive JavaScript notebooks for prototyping and a collaborative canvas for visual data exploration and dashboard creation.
Learn more