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 };
});
return Object.assign(svg.node(), {
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()
)
.each(function (d) {
const localTime = t - d.createdAt;
d.pos = localTime * d.speed;
const index = Math.floor(d.pos);
const coo = cache[d.route].points[index];
const nextCoo = cache[d.route].points[index + 1];
if (coo && nextCoo) {
const delta = d.pos - index;
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);
}
});
}
});
}