Published unlisted
Edited
Jul 19, 2022
Insert cell
# d3 to calculate exact animation delay
Insert cell
Insert cell
{
const isXGridLine = "y";

////////////////////////////////////////////////////////////
//////////////////////// 1 DATA ///////////////////////////
////////////////////////////////////////////////////////////

const data = [
{
x: 50,
y: 0.1
},
{
x: 100,
y: 0.15
},
{
x: 150,
y: 0.25
},
{
x: 200,
y: 0.35
},
{
x: 250,
y: 0.45
},
{
x: 300,
y: 0.55
},
{
x: 350,
y: 0.65
},
{
x: 400,
y: 0.75
},
{
x: 450,
y: 0.85
},
{
x: 500,
y: 0.87
},
{
x: 550,
y: 0.92
},
{
x: 600,
y: 0.98
}
];
const height = 400;
const width = 720;

const padding = {
top: 70,
bottom: 50,
left: 70,
right: 70
};

const boundHeight = height - padding.top - padding.bottom;
const boundWidth = width - padding.right - padding.left;

////////////////////////////////////////////////////////////
//////////////////////// 2 CREATE SCALE ////////////////////
////////////////////////////////////////////////////////////

const scaleY = d3
.scaleLinear()
.range([boundHeight, 0])
.domain(d3.extent(data, (d) => d.y));

const scaleX = d3
.scaleLinear()
.range([0, boundWidth])
.domain(d3.extent(data, (d) => d.x));

////////////////////////////////////////////////////////////
//////////////////////// 3 SVG// ///////////////////////////
////////////////////////////////////////////////////////////

const svgns = "http://www.w3.org/2000/svg";
const svg = d3.select("svg");

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

svg
.append("rect")
.attr("class", "vBoxRect")
.style("overflow", "visible")
.attr("width", `${width}`)
.attr("height", `${height}`)
.attr("stroke", "black")
.attr("fill", "transparent");

svg
.append("rect")
.attr("class", "boundRect")
.attr("x", `${padding.left}`)
.attr("y", `${padding.top}`)
.attr("width", `${boundWidth}`)
.attr("height", `${boundHeight}`)
.attr("fill", "none")
.attr("stroke", "none");

//create bound element
const bound = svg
.append("g")
.attr("class", "bound")
.style("transform", `translate(${padding.left}px,${padding.top}px)`);

////////////////////////////////////////////////////////////
//////////////////////// 4 AXIS// ///////////////////////////
////////////////////////////////////////////////////////////
bound
.append("g")
.attr("class", "xAxis")
.append("g")
.attr("class", "xAxisBottom")
.call(
isXGridLine == "y"
? d3
.axisBottom(scaleX)
.tickSizeInner([-boundHeight])
.tickSizeOuter(0)
.tickFormat(d3.format("d"))
: d3
.axisBottom(scaleX)
.ticks(data.map((d) => d.x).length - 1)
.tickFormat(d3.format("d"))
)
.style("transform", `translateY(${boundHeight}px)`);

////////////////////////////////////////////////////////////
//////////////////////// 4 AXIS ANIMATION// /////////////////
////////////////////////////////////////////////////////////

/*animation global*/
const xPath = document.querySelector(".xAxis>.xAxisBottom>path.domain");
const xTotalDist = xPath.getTotalLength();
const xPct = 0;
const xPathPoint = xPath.getPointAtLength(xTotalDist * xPct);

const transitionObj = {
duration: 5000
};

////////////////////////////////////////////////////////////
//////////////////////// 5 AXIS PATH ANIMATION// ////////////
////////////////////////////////////////////////////////////

d3.select(".xAxis>.xAxisBottom>path.domain")
.attr("stroke-dasharray", function () {
return `0 ${this.getTotalLength()}`;
})
.transition()
.duration(`${transitionObj.duration}`)
.ease(d3.easeQuadIn)
.attr("stroke-dasharray", function () {
return `${this.getTotalLength()} ${this.getTotalLength()}`;
});

////////////////////////////////////////////////////////////
//////////////////////// 6 AXIS GRIDLINE ANIMATION// ////////
////////////////////////////////////////////////////////////
const finalVal = document.querySelector(".xAxis>.xAxisBottom>.tick>line").y2
.baseVal.value;

d3.selectAll(".xAxis>.xAxisBottom>.tick>line")
.attr("y2", "0")
.transition()
.duration(1000)
.delay(function (d1, i) {
//this equation must be base on the d3 ease
//for different ease the equation will change
//d3 ease is based on penner's equation
//position as a function of time => p=f(t)
//p= c*(t/d)+b; where c = total change, t = elapsed time %, d= total duration % =1, b = beginning position
//total change * (elapsed time/total time)+beginning position
//solve for t
//t = ((p-b)/c)*d -- based on linear easing
const p = parseFloat(
d3
.selectAll(".xAxis>.xAxisBottom>.tick")
.nodes()
[i].getAttribute("transform")
.match(/(\d+\.\d+)(?=\,)|(\d+)(?=\,)/gm)
);
const b = xPathPoint.x;
const c = xTotalDist;
const d = 1;
const elapsedTimePct = Math.sqrt((p - b) / c) * d;
return `${transitionObj.duration}` * elapsedTimePct;
})
.attr("y2", `${finalVal}`);

////////////////////////////////////////////////////////////
//////////////////////// 7 AXIS VALUE ANIMATION// ////////
////////////////////////////////////////////////////////////

d3.selectAll(".xAxis>.xAxisBottom>.tick>text")
.attr("opacity", "0")
.transition()
.duration(1500)
.delay(function (d1, i) {
//this equation must be base on the d3 ease
//for different ease the equation will change
//d3 ease is based on penner's equation
//position as a function of time => p=f(t)
//p= c*(t/d)+b; where c = total change, t = elapsed time %, d= total duration % =1, b = beginning position
//total change * (elapsed time/total time)+beginning position
//solve for t
//t = ((p-b)/c)*d
const p = parseFloat(
d3
.selectAll(".xAxis>.xAxisBottom>.tick")
.nodes()
[i].getAttribute("transform")
.match(/(\d+\.\d+)(?=\,)|(\d+)(?=\,)/gm)
);
const b = xPathPoint.x;
const c = xTotalDist;
const d = 1;
const elapsedTimePct = Math.sqrt((p - b) / c) * d;
return `${transitionObj.duration}` * elapsedTimePct;
})
//with tween
/* .tween('text', function(d) {
const selection = d3.select(this);
const start = 0;
const end = d3.select(this).text();
const interpolator = d3.interpolateNumber(start, end);
return function(t) {
selection.text(Math.round(interpolator(t)));
}; // return value
})*/
//with textTween
.textTween(function () {
const start = 0;
const end = this.textContent;
return d3.interpolateRound(start, end);
})
.attr("opacity", "5");
}
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