chart = {
const svg = d3.select(DOM.svg(width, height))
const link = svg.append("g").attr('class', 'links')
.attr("fill", "none")
.attr("stroke-opacity", 0.04)
.attr("stroke", "#aaa")
.selectAll("path").data(links)
.join("path")
.attr('d', sankeyLinkCustom)
.attr("stroke-width", yScale.bandwidth())
link.each(function(d) {
const path = this
const length = path.getTotalLength()
const points = d3.range(length).map(l => {
const point = path.getPointAtLength(l)
return { x: point.x, y: point.y }
})
const key = `${d.source}_${d.target}`
cache[key] = { points }
})
// It's Observable-specific, you can see more animations technics here:
// https://observablehq.com/@d3/learn-d3-animation?collection=@d3/learn-d3
//
return Object.assign(svg.node(), {
// update will be called on each tick, so here we'll perform our animation step
update(t) {
if (particles.length < totalParticles) {
addParticlesMaybe(t)
}
svg.selectAll('.particle').data(particles.filter(p => p.pos < p.length), d => d.id)
.join(
enter => enter.append('rect')
.attr('class', 'particle')
.attr('fill', d => d.color)
.attr('width', psize)
.attr('height', psize),
update => update,
exit => exit.remove()
)
// At this point we have `cache` with all possible coordinates.
// We just need to figure out which exactly coordinates to use at time `t`
//
.each(function(d) {
// every particle appears at its own time, so adjust the global time `t` to local time
const localTime = t - d.createdAt
d.pos = localTime * d.speed
// extract current and next coordinates of the point from precomputed cache
const index = Math.floor(d.pos)
const coo = cache[d.route].points[index]
const nextCoo = cache[d.route].points[index + 1]
if (coo && nextCoo) {
// `index` is integer, but `pos` is not, so there are ticks when the particle is
// between the two precomputed points. We use `delta` to compute position between the current
// and the next coordinates to make the animation smoother
const delta = d.pos - index // try to set it to 0 to see how jerky the animation is
const x = coo.x + (nextCoo.x - coo.x) * delta
const y = coo.y + (nextCoo.y - coo.y) * delta
d3.select(this)
.attr('x', x)
.attr('y', y + d.offset)
}
})
}
})
}